[
  {
    "path": ".editorconfig",
    "content": "# http://EditorConfig.org\n\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n"
  },
  {
    "path": ".gitattributes",
    "content": "* text=auto eol=lf\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: Bug Report\ndescription: Submit a bug report.\nlabels: ['bug', 'Triage']\n\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thanks for taking the time to fill out this bug report! **Please write in English**.\n        Before creating an issue please make sure you are using the latest version of Docsify.\n\n  - type: textarea\n    attributes:\n      label: Description\n      description: A clear and concise description of what the bug is, and why you consider it to be a bug.\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: Expected behavior\n      description: A description of what you expected to happen.\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Actual behavior\n      description: A description of what is actually happening.\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Steps to reproduce\n      description: |\n        A description with steps to reproduce the issue.\n        Provide a link to a public repository or create a reproducible [sandbox](https://codesandbox.io/s/307qqv236):\n      placeholder: |\n        1. Step 1\n        2. Step 2\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: Environment\n      description: |\n        Please provide the following information if relevant to the issue:\n        ```markdown\n        - Your OS:\n        - Node.js version:\n        - npm/yarn version:\n        - Browser version:\n        - Docsify version:\n        - Docsify plugins (if the bug happens when plugins enabled, please try to isolate the issue):\n        ```\n\n  - type: checkboxes\n    attributes:\n      label: Additional Information\n      options:\n        - label: Bug still occurs when all/other plugins are disabled?\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Discord - the community chat\n    url: https://discord.gg/docsify\n    about: Join the Discord community and chat about Docsify\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "name: Feature Request\ndescription: Propose a new feature or improvement for this project.\nlabels: ['feature request', 'Triage']\n\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thank you for suggesting a feature! Please provide as much detail as possible to help us understand your idea. **Write in English.**\n\n  - type: textarea\n    attributes:\n      label: Problem or Desire\n      description: What problem or need will this feature address? Why is it important?\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: Proposal\n      description: What is your proposed solution? How should this feature work?\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: Implementation Details\n      description: If you have any ideas about how this feature could be implemented, please share them here.\n\n  - type: textarea\n    attributes:\n      label: Additional Context\n      description: Add any other context, screenshots, or references that might help us understand your request.\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "## Summary\n\n<!-- Describe what the change does and why it should be merged. Provide **before/after** screenshots for any UI changes. -->\n\n## Related issue, if any:\n\n<!-- Paste issue's link or number hashtag here. -->\n\n## What kind of change does this PR introduce?\n\n<!-- (Change \"[ ]\" to \"[x]\" to check a box.) -->\n\n- [ ] Bugfix\n- [ ] Feature\n- [ ] Code style update (formatting, renaming)\n- [ ] Refactoring (no functional changes, no api changes)\n- [ ] Build related changes\n- [ ] Documentation content changes\n- [ ] Other (please describe):\n\n## For any code change,\n\n- [ ] Related documentation has been updated, if needed\n- [ ] Related tests have been added or updated, if needed\n\n## Does this PR introduce a breaking change?\n\n<!-- If yes, describe the impact and migration path for existing applications. -->\n\n- [ ] Yes\n- [ ] No\n\n## Tested in the following browsers:\n\n- [ ] Chrome\n- [ ] Firefox\n- [ ] Safari\n- [ ] Edge\n"
  },
  {
    "path": ".github/crowdin/crowdin-push.yml",
    "content": "files:\n  - source: /*.md\n    translation: /%two_letters_code%/%original_file_name%\n    ignore:\n      - '/_coverpage.md'\n      - '/_navbar.md'\n\nproject_id_env: CROWDIN_PROJECT_ID\napi_token_env: CROWDIN_PERSONAL_TOKEN\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for all configuration options:\n# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates\n\nversion: 2\nupdates:\n  - package-ecosystem: npm # See documentation for possible values\n    directory: '/' # Location of package manifests\n    open-pull-requests-limit: 10\n    schedule:\n      interval: monthly\n    ignore:\n      # For all dependencies, trigger pull requests only for semver-major updates (ignore minor and patch).\n      - dependency-name: '*'\n        update-types:\n          ['version-update:semver-patch', 'version-update:semver-minor']\n  - package-ecosystem: 'github-actions'\n    directory: '/'\n    open-pull-requests-limit: 10\n    schedule:\n      interval: monthly\n    ignore:\n      # For all dependencies, trigger pull requests only for semver-major and semver-minor updates (ignore patch).\n      - dependency-name: '*'\n        update-types:\n          ['version-update:semver-patch', 'version-update:semver-minor']\n"
  },
  {
    "path": ".github/semantic.yml",
    "content": "titleAndCommits: true\nallowMergeCommits: true\nallowRevertCommits: true\nanyCommit: true\n"
  },
  {
    "path": ".github/workflows/crowdin.yml",
    "content": "name: Crowdin Action\n\non:\n  workflow_dispatch:\n  push:\n    paths:\n      - 'docs/**.md'\n    branches: [develop, main]\n\njobs:\n  crowdin:\n    if: github.repository == 'docsifyjs/docsify'\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Crowdin push\n        uses: crowdin/github-action@v2\n        with:\n          upload_sources: true\n          upload_translations: false\n          download_translations: false\n          push_translations: false\n          config: '.github/crowdin/crowdin-push.yml'\n          crowdin_branch_name: ${{ github.ref_name }}\n          base_path: ${{ github.workspace }}/docs\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}\n          CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/emoji.yml",
    "content": "name: Sync Emoji\n\non:\n  schedule:\n    - cron: '0 2 * * *'\n  workflow_dispatch:\n\njobs:\n  sync-emoji:\n    if: github.repository == 'docsifyjs/docsify'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n\n      - uses: actions/setup-node@v6\n        with:\n          node-version: latest\n          cache: 'npm'\n\n      - name: Install dependencies\n        run: npm ci --ignore-scripts\n\n      - name: Run script to sync emoji data\n        run: npm run build:emoji\n\n      - name: Commit\n        id: auto-commit-action\n        uses: stefanzweifel/git-auto-commit-action@v7\n        with:\n          commit_message: 'chore: Sync emoji data with GitHub emoji API'\n          branch: sync-emoji\n          create_branch: true\n          file_pattern: 'src/core/render/emoji-data.js docs/emoji.md'\n          push_options: '--force'\n\n      - name: Create Pull Request\n        if: ${{ steps.auto-commit-action.outputs.changes_detected == 'true' }}\n        run: |\n          gh pr create --title 'chore: Sync emoji data with GitHub emoji API' --body 'Found updated github emojis need to sync.' --base develop --reviewer docsifyjs/reviewers\n        continue-on-error: true\n        env:\n          GH_TOKEN: ${{ secrets.READ_TEAM_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Build & Test\n\non:\n  push:\n    branches: [main, develop]\n  pull_request:\n    branches: [main, develop]\n  workflow_dispatch:\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        node-version: ['lts/*']\n    steps:\n      - uses: actions/checkout@v6\n      - name: Setup Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v6\n        with:\n          node-version: ${{ matrix.node-version }}\n          cache: 'npm'\n      - name: Install dependencies\n        run: npm ci --ignore-scripts\n      - name: Build\n        run: npm run build\n      - name: Lint\n        run: npm run lint\n\n  test-jest:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        node-version: ['lts/*']\n        os: ['macos-latest', 'ubuntu-latest', 'windows-latest']\n    steps:\n      - uses: actions/checkout@v6\n      - name: Setup Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v6\n        with:\n          node-version: ${{ matrix.node-version }}\n          cache: 'npm'\n      - name: Install dependencies\n        run: npm ci --ignore-scripts\n      - name: Build\n        run: npm run build\n      - name: Unit Tests\n        run: npm run test:unit -- --ci --runInBand\n      - name: Integration Tests\n        run: npm run test:integration -- --ci --runInBand\n      - name: Consumption Tests\n        run: npm run test:consume-types\n\n  test-playwright:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        node-version: ['lts/*']\n    steps:\n      - uses: actions/checkout@v6\n      - name: Setup Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v6\n        with:\n          node-version: ${{ matrix.node-version }}\n          cache: 'npm'\n      - name: Install dependencies\n        run: npm ci --ignore-scripts\n      - name: Build\n        run: npm run build\n      - name: Install Playwright\n        run: npx playwright install --with-deps\n      - name: E2E Tests (Playwright)\n        run: npm run test:e2e\n\n      # In the interest of not having to modify GitHub settings to update required\n      # builds for passing, I stuck this test here.\n      - name: Test v4 build\n        run: |\n          git fetch --tags\n          npm run build:v4\n          # ensure v4 build files exist:\n          test -f lib/docsify.js\n          test -f themes/pure.css\n          # ensure no git changes after building v4:\n          git diff --exit-code\n          npm run clean:v4\n          # ensure v4 build files are removed:\n          test ! -f lib/docsify.js\n          test ! -f themes/pure.css\n          # ensure no git changes after cleaning v4:\n          git diff --exit-code\n\n      - name: Store artifacts\n        uses: actions/upload-artifact@v7\n        if: failure()\n        with:\n          name: ${{ matrix.os }}-${{ matrix.node-version }}-artifacts\n          path: |\n            _playwright-results/\n            _playwright-report/\n"
  },
  {
    "path": ".gitignore",
    "content": "# Directories\n.husky/_\n.idea\n.vercel\n_playwright-report\n_playwright-results\nnode_modules\n\n# Files\n.DS_Store\n*.log\n\n# Exceptions\n!.gitkeep\n\n# Output folder for the global build only\ndist/\n\n# Output folders for the legacy v4 build\nlib/\nthemes/\n\n# TypeScript declaration files for standard ESM consumption\nsrc/**/*.d.ts\nsrc/**/*.d.ts.map\n"
  },
  {
    "path": ".gitpod.yml",
    "content": "tasks:\n  - command: gp await-port 3000 && sleep 3 && gp preview $(gp url 3000)\n  - init: npm install\n    command: npm run dev\nports:\n  - port: 3000\n    onOpen: ignore\nvscode:\n  extensions:\n    - sysoev.language-stylus@1.11.0:xX39oruAJ5UQzTNVRdbBaQ==\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "#!/usr/bin/env sh\n. \"$(dirname -- \"$0\")/_/husky.sh\"\n\nnpm run prettier\nnpx lint-staged\n"
  },
  {
    "path": ".npmignore",
    "content": ".eslintignore\n.github\n.gitignore\n"
  },
  {
    "path": ".prettierignore",
    "content": "# Directories\n_playwright-*\ndist\nlib\n\n# Files\n_vars.css\n_vars-advanced.css\nCHANGELOG.md\nemoji-data.*\nHISTORY.md\n"
  },
  {
    "path": ".prettierrc.json",
    "content": "{\n  \"arrowParens\": \"avoid\",\n  \"singleQuote\": true\n}\n"
  },
  {
    "path": ".vscode/launch.json",
    "content": "{\n  // Use IntelliSense to learn about possible attributes.\n  // Hover to view descriptions of existing attributes.\n  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"type\": \"node\",\n      \"request\": \"launch\",\n      \"name\": \"Run unit tests\",\n      \"runtimeExecutable\": \"npm\",\n      \"runtimeArgs\": [\"run\", \"test:jest\"],\n      \"console\": \"integratedTerminal\"\n    },\n    {\n      \"type\": \"node\",\n      \"request\": \"launch\",\n      \"name\": \"Run current test file\",\n      \"runtimeExecutable\": \"npm\",\n      \"runtimeArgs\": [\"run\", \"test:jest\", \"--\"],\n      \"args\": [\"-i\", \"${relativeFile}\", \"--testPathIgnorePatterns\"],\n      \"console\": \"integratedTerminal\"\n    },\n    {\n      \"type\": \"node\",\n      \"request\": \"launch\",\n      \"name\": \"Run selected test name\",\n      \"runtimeExecutable\": \"npm\",\n      \"runtimeArgs\": [\"run\", \"test:jest\", \"--\"],\n      \"args\": [\n        \"-i\",\n        \"${relativeFile}\",\n        \"-t\",\n        \"${selectedText}\",\n        \"--testPathIgnorePatterns\"\n      ],\n      \"console\": \"integratedTerminal\"\n    },\n    {\n      \"type\": \"node\",\n      \"request\": \"launch\",\n      \"name\": \"Update current test file snapshot(s)\",\n      \"runtimeExecutable\": \"npm\",\n      \"runtimeArgs\": [\"run\", \"test:jest\", \"--\"],\n      \"args\": [\n        \"-i\",\n        \"${relativeFile}\",\n        \"--updateSnapshot\",\n        \"--testPathIgnorePatterns\"\n      ],\n      \"console\": \"integratedTerminal\"\n    },\n    {\n      \"type\": \"node\",\n      \"request\": \"launch\",\n      \"name\": \"Update selected test name snapshot(s)\",\n      \"runtimeExecutable\": \"npm\",\n      \"runtimeArgs\": [\"run\", \"test:jest\", \"--\"],\n      \"args\": [\n        \"-i\",\n        \"${relativeFile}\",\n        \"-t\",\n        \"${selectedText}\",\n        \"--updateSnapshot\",\n        \"--testPathIgnorePatterns\"\n      ],\n      \"console\": \"integratedTerminal\"\n    }\n  ]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"editor.defaultFormatter\": \"esbenp.prettier-vscode\",\n  \"cSpell.words\": [\"coverpage\"],\n  \"typescript.tsdk\": \"node_modules/typescript/lib\"\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# [5.0.0-rc.4](https://github.com/docsifyjs/docsify/compare/v5.0.0-rc.3...v5.0.0-rc.4) (2026-03-11)\n\n\n### Bug Fixes\n\n* adjust intersection observer threshold for sticky class toggle ([#2637](https://github.com/docsifyjs/docsify/issues/2637)) ([4e8be38](https://github.com/docsifyjs/docsify/commit/4e8be38304ad5fcf64c585f42afef46c72adabb7))\n* enhance accessibility for sidebar toggle button ([#2604](https://github.com/docsifyjs/docsify/issues/2604)) ([3014945](https://github.com/docsifyjs/docsify/commit/3014945e4d677d24124a3a01fe88ec82951ebfa7))\n* improve word breaking for code blocks in markdown ([#2636](https://github.com/docsifyjs/docsify/issues/2636)) ([73d41e9](https://github.com/docsifyjs/docsify/commit/73d41e9e48ab7effae4c5dffd4aec4bd4e1b9168))\n* include h6 headings in heading element selection ([#2649](https://github.com/docsifyjs/docsify/issues/2649)) ([44326ca](https://github.com/docsifyjs/docsify/commit/44326ca423b25ddbb12b172ab7180edd553c61f4))\n* strip HTML tags from config.name when setting page title ([#2690](https://github.com/docsifyjs/docsify/issues/2690)) ([0cfee34](https://github.com/docsifyjs/docsify/commit/0cfee34dbc0d511a272070e4124eb2f3102d988e)), closes [#2610](https://github.com/docsifyjs/docsify/issues/2610)\n* typo ([#2632](https://github.com/docsifyjs/docsify/issues/2632)) ([2d45376](https://github.com/docsifyjs/docsify/commit/2d45376bb8dc3f7a07c633a7be4d38af3a56501b))\n\n\n### Features\n\n* add fallback default language support ([#2607](https://github.com/docsifyjs/docsify/issues/2607)) ([1abe3a9](https://github.com/docsifyjs/docsify/commit/1abe3a9ea48cd3a96ee65cd958090bf2936de3c9))\n* enhance embed handling for table cells ([#2606](https://github.com/docsifyjs/docsify/issues/2606)) ([422a745](https://github.com/docsifyjs/docsify/commit/422a745acc4783c325d55413c5349ca70c42c67c))\n* Fragment identifier full line ignore ([#2626](https://github.com/docsifyjs/docsify/issues/2626)) ([e811756](https://github.com/docsifyjs/docsify/commit/e8117563eae4aa9468c36b76fe83c5ce3f7423ea))\n* output type definitions, and allow `new Docsify(opts)` to accept options for ESM usage ([#2392](https://github.com/docsifyjs/docsify/issues/2392)) ([b960519](https://github.com/docsifyjs/docsify/commit/b960519608747015d1ab974c9ed809c21f3b1bda))\n\n\n\n# [5.0.0-rc.3](https://github.com/docsifyjs/docsify/compare/v5.0.0-rc.1...v5.0.0-rc.3) (2025-09-05)\n\n\n### Bug Fixes\n\n* enhance focus handling ([#2595](https://github.com/docsifyjs/docsify/issues/2595)) ([22ac7e8](https://github.com/docsifyjs/docsify/commit/22ac7e855de24b13280e9904930e58af58dbd6a7))\n* enhance focus handling by adding smooth scroll to content area ([#2569](https://github.com/docsifyjs/docsify/issues/2569)) ([20d095b](https://github.com/docsifyjs/docsify/commit/20d095b53c827c4d0590dee5fd377ba21560d7fb))\n* escape HTML in search keywords ([#2586](https://github.com/docsifyjs/docsify/issues/2586)) ([743e9cb](https://github.com/docsifyjs/docsify/commit/743e9cb484cc70859a582fb74d78ea1f52ef2c2a))\n* exclude app-name-link from sidebar text overflow styling ([#2564](https://github.com/docsifyjs/docsify/issues/2564)) ([375c058](https://github.com/docsifyjs/docsify/commit/375c058e3e1b12f3507d97a41b3fd8d6c4e904b0))\n* handle hash navigation to prevent duplicate callbacks ([#2575](https://github.com/docsifyjs/docsify/issues/2575)) ([72569de](https://github.com/docsifyjs/docsify/commit/72569dee8db92a01e8de5448e05c32afbd090fb2))\n* heading link overflow style ([#2568](https://github.com/docsifyjs/docsify/issues/2568)) ([be21637](https://github.com/docsifyjs/docsify/commit/be2163725ae314de3aa9a56c1216ce8c774cfcc6))\n* improve slug generation ([#2581](https://github.com/docsifyjs/docsify/issues/2581)) ([9bc58c9](https://github.com/docsifyjs/docsify/commit/9bc58c9ca24db513a90a06752181954aab0628ca))\n* normalize slugs to NFC and remove emoji variation selector ([#2597](https://github.com/docsifyjs/docsify/issues/2597)) ([5999f09](https://github.com/docsifyjs/docsify/commit/5999f09a4bac23169766af68fbdc71215161c1c6))\n* remove block display from anchor links to prevent layout issues ([#2576](https://github.com/docsifyjs/docsify/issues/2576)) ([6e45024](https://github.com/docsifyjs/docsify/commit/6e4502416886b9494cd7b510968c351c7e6fc3cd))\n* **sidebar:** remove ignored headings and children ([#2580](https://github.com/docsifyjs/docsify/issues/2580)) ([2a49bd0](https://github.com/docsifyjs/docsify/commit/2a49bd0aa4bb17eeb3d9f363c6f2b01e61e778ac))\n* update relative link handling ([#2579](https://github.com/docsifyjs/docsify/issues/2579)) ([eeacfcc](https://github.com/docsifyjs/docsify/commit/eeacfcce2a3df4e4146b5b54275553452b317352))\n\n\n### Features\n\n* GitHub style callouts ([#2487](https://github.com/docsifyjs/docsify/issues/2487)) ([2e59b0f](https://github.com/docsifyjs/docsify/commit/2e59b0f50cf8123b98e4c97c3053aa6985ae13a2))\n* support config helper multi keys if supported ([#2571](https://github.com/docsifyjs/docsify/issues/2571)) ([a2f734f](https://github.com/docsifyjs/docsify/commit/a2f734f223cae475aefe7d41995a2030a1f9c0a2))\n* support extract content between fragment markers from Markdown ([#2582](https://github.com/docsifyjs/docsify/issues/2582)) ([32aa74e](https://github.com/docsifyjs/docsify/commit/32aa74e0d2fcd89705e5c30fc8b8e2562e7b774e))\n\n\n\n# [5.0.0-rc.2](https://github.com/docsifyjs/docsify/compare/v5.0.0-rc.1...v5.0.0-rc.2) (2025-09-05)\n\n\n### Bug Fixes\n\n* enhance focus handling ([#2595](https://github.com/docsifyjs/docsify/issues/2595)) ([22ac7e8](https://github.com/docsifyjs/docsify/commit/22ac7e855de24b13280e9904930e58af58dbd6a7))\n* enhance focus handling by adding smooth scroll to content area ([#2569](https://github.com/docsifyjs/docsify/issues/2569)) ([20d095b](https://github.com/docsifyjs/docsify/commit/20d095b53c827c4d0590dee5fd377ba21560d7fb))\n* escape HTML in search keywords ([#2586](https://github.com/docsifyjs/docsify/issues/2586)) ([743e9cb](https://github.com/docsifyjs/docsify/commit/743e9cb484cc70859a582fb74d78ea1f52ef2c2a))\n* exclude app-name-link from sidebar text overflow styling ([#2564](https://github.com/docsifyjs/docsify/issues/2564)) ([375c058](https://github.com/docsifyjs/docsify/commit/375c058e3e1b12f3507d97a41b3fd8d6c4e904b0))\n* handle hash navigation to prevent duplicate callbacks ([#2575](https://github.com/docsifyjs/docsify/issues/2575)) ([72569de](https://github.com/docsifyjs/docsify/commit/72569dee8db92a01e8de5448e05c32afbd090fb2))\n* heading link overflow style ([#2568](https://github.com/docsifyjs/docsify/issues/2568)) ([be21637](https://github.com/docsifyjs/docsify/commit/be2163725ae314de3aa9a56c1216ce8c774cfcc6))\n* improve slug generation ([#2581](https://github.com/docsifyjs/docsify/issues/2581)) ([9bc58c9](https://github.com/docsifyjs/docsify/commit/9bc58c9ca24db513a90a06752181954aab0628ca))\n* remove block display from anchor links to prevent layout issues ([#2576](https://github.com/docsifyjs/docsify/issues/2576)) ([6e45024](https://github.com/docsifyjs/docsify/commit/6e4502416886b9494cd7b510968c351c7e6fc3cd))\n* **sidebar:** remove ignored headings and children ([#2580](https://github.com/docsifyjs/docsify/issues/2580)) ([2a49bd0](https://github.com/docsifyjs/docsify/commit/2a49bd0aa4bb17eeb3d9f363c6f2b01e61e778ac))\n* update relative link handling ([#2579](https://github.com/docsifyjs/docsify/issues/2579)) ([eeacfcc](https://github.com/docsifyjs/docsify/commit/eeacfcce2a3df4e4146b5b54275553452b317352))\n\n\n### Features\n\n* GitHub style callouts ([#2487](https://github.com/docsifyjs/docsify/issues/2487)) ([2e59b0f](https://github.com/docsifyjs/docsify/commit/2e59b0f50cf8123b98e4c97c3053aa6985ae13a2))\n* support config helper multi keys if supported ([#2571](https://github.com/docsifyjs/docsify/issues/2571)) ([a2f734f](https://github.com/docsifyjs/docsify/commit/a2f734f223cae475aefe7d41995a2030a1f9c0a2))\n* support extract content between fragment markers from Markdown ([#2582](https://github.com/docsifyjs/docsify/issues/2582)) ([32aa74e](https://github.com/docsifyjs/docsify/commit/32aa74e0d2fcd89705e5c30fc8b8e2562e7b774e))\n\n\n\n# Changelog\n\n## [5.0.0-rc.1](https://github.com/docsifyjs/docsify/compare/v4.13.1...v5.0.0-rc.1) (2025-05-27)\n\n\n### Bug Fixes\n\n* auto header config heading generate func ([#2474](https://github.com/docsifyjs/docsify/issues/2474)) ([4bc5062](https://github.com/docsifyjs/docsify/commit/4bc5062fc13a3a43c7ed432f1b585fdab41f1447))\n* carbon broken ([#2387](https://github.com/docsifyjs/docsify/issues/2387)) ([87fd55d](https://github.com/docsifyjs/docsify/commit/87fd55d7125539b929f3260fca92e666e988b6da))\n* **ci:** run test action for pull requests ([#2445](https://github.com/docsifyjs/docsify/issues/2445)) ([15ed3b7](https://github.com/docsifyjs/docsify/commit/15ed3b76b00eac06cc4230b1f592993adf2a893b))\n* correct loadSidebar=false render structure issue ([#2470](https://github.com/docsifyjs/docsify/issues/2470)) ([7cbd532](https://github.com/docsifyjs/docsify/commit/7cbd5322d056bb87b4340bdb19909a9c32d19abb))\n* dev mode hot reload and add sourcemaps ([#2402](https://github.com/docsifyjs/docsify/issues/2402)) ([947d8de](https://github.com/docsifyjs/docsify/commit/947d8decb8c5c62f3ce50d0c6ac0e27bb6c7a6b5))\n* enhancement of isExternal ([#2093](https://github.com/docsifyjs/docsify/issues/2093)) ([7f13ba0](https://github.com/docsifyjs/docsify/commit/7f13ba0f9841776008d0707f027bd80c4e3cbf0c))\n* fix cross-origin links in history router mode ([#1967](https://github.com/docsifyjs/docsify/issues/1967)) ([ef6905b](https://github.com/docsifyjs/docsify/commit/ef6905b53a5c1587c3ebf870f0d11ff111a2350d))\n* fix dependabot.yml ([a321e83](https://github.com/docsifyjs/docsify/commit/a321e8373b3c6afc9d0b08714e34bd8bd68716d9))\n* fix docsify-server-renderer dependency. ([#1915](https://github.com/docsifyjs/docsify/issues/1915)) ([c73f858](https://github.com/docsifyjs/docsify/commit/c73f8587b2f67e28c228e521518110ff504cefeb))\n* fix id with pure number. ([#2021](https://github.com/docsifyjs/docsify/issues/2021)) ([f4f21a3](https://github.com/docsifyjs/docsify/commit/f4f21a3f74d265f16b1e658feda417bf851458da))\n* genIndex error for search ([#1933](https://github.com/docsifyjs/docsify/issues/1933)) ([a8f9fc1](https://github.com/docsifyjs/docsify/commit/a8f9fc1d5f5c7808efe65e5ca9359f38014b1bcd))\n* husky can not auto install issue after upgrade. ([#2325](https://github.com/docsifyjs/docsify/issues/2325)) ([cec43d7](https://github.com/docsifyjs/docsify/commit/cec43d71774556d38fa9746f4df4319a6ad768c2))\n* parse heading error ([#2526](https://github.com/docsifyjs/docsify/issues/2526)) ([561c777](https://github.com/docsifyjs/docsify/commit/561c777df898a71f8b44b25e5946b56eb1d2c106))\n* Prevent initial unnecessary IntersectionObserver callback execution ([#2523](https://github.com/docsifyjs/docsify/issues/2523)) ([a73e07e](https://github.com/docsifyjs/docsify/commit/a73e07eac06a70f63896e2bb7f0c205534dc21c8))\n* prevent unnecessary themeColor deprecation notice ([#2403](https://github.com/docsifyjs/docsify/issues/2403)) ([a3ab2be](https://github.com/docsifyjs/docsify/commit/a3ab2be0b070a1bacc4fec3c2d2f0ffe279bada0))\n* **search:** clean markdown elements in search contents ([#2457](https://github.com/docsifyjs/docsify/issues/2457)) ([95901eb](https://github.com/docsifyjs/docsify/commit/95901eb8a829865c883a4374c53a0b8909b53c41))\n* skip-to-content scroll behavior ([#2401](https://github.com/docsifyjs/docsify/issues/2401)) ([2d986fe](https://github.com/docsifyjs/docsify/commit/2d986feb34fcdb2a2731cff7b29b0a4d0563787e))\n* sync the page title regarding the title config ([#2478](https://github.com/docsifyjs/docsify/issues/2478)) ([2eec7c4](https://github.com/docsifyjs/docsify/commit/2eec7c4884146add6e0c8142e463cfad34fb7637))\n* upgrade debug from 4.3.3 to 4.3.4 ([#1919](https://github.com/docsifyjs/docsify/issues/1919)) ([0c9221e](https://github.com/docsifyjs/docsify/commit/0c9221e5a425984470ebc89ffb682e988b08b06e))\n* upgrade medium-zoom from 1.0.6 to 1.0.7 ([#1934](https://github.com/docsifyjs/docsify/issues/1934)) ([2601392](https://github.com/docsifyjs/docsify/commit/26013929ccf1927cc6a46c5455fb70f71d90492f))\n* upgrade medium-zoom from 1.0.7 to 1.0.8 ([#1939](https://github.com/docsifyjs/docsify/issues/1939)) ([81fc1b7](https://github.com/docsifyjs/docsify/commit/81fc1b7dce15d74bb025d3f95bfa5dae55540a10))\n* upgrade node-fetch from 2.6.8 to 2.6.9 ([#1996](https://github.com/docsifyjs/docsify/issues/1996)) ([bb88d63](https://github.com/docsifyjs/docsify/commit/bb88d6302b22b2484cca6436c7b4279d5e7fc5ea))\n* When alias contains parameters, append ext error ([#1855](https://github.com/docsifyjs/docsify/issues/1855)) ([1a32fb7](https://github.com/docsifyjs/docsify/commit/1a32fb73347308696f6e82c0757ba081a4e7b8de))\n\n\n### Features\n\n* Add \"Skip to main content\" link and update nav behavior ([#2253](https://github.com/docsifyjs/docsify/issues/2253)) ([50b84f7](https://github.com/docsifyjs/docsify/commit/50b84f74b25c27f1a3a102055e7f099db31155f8))\n* add google analytics gtag.js plugin ([#1702](https://github.com/docsifyjs/docsify/issues/1702)) ([29f3c82](https://github.com/docsifyjs/docsify/commit/29f3c82faaffb817d2149d23a0e3a884cd1cc843))\n* Allow top nav to receive keyboard focus ([#2269](https://github.com/docsifyjs/docsify/issues/2269)) ([4d5bf5a](https://github.com/docsifyjs/docsify/commit/4d5bf5ac48499c57cfdffd98bfaa29bf20d85260))\n* Keyboard bindings ([#2279](https://github.com/docsifyjs/docsify/issues/2279)) ([cf61192](https://github.com/docsifyjs/docsify/commit/cf61192f9a467c96372bce9e4e371a3f0c6a1780))\n* **search:** use dexie.js instead of localStorage ([#2464](https://github.com/docsifyjs/docsify/issues/2464)) ([42f2548](https://github.com/docsifyjs/docsify/commit/42f25482fa1ea6ec666800145f6da33e9119aa8c))\n* Support dark mode for zoom-image plugin ([#2524](https://github.com/docsifyjs/docsify/issues/2524)) ([5826863](https://github.com/docsifyjs/docsify/commit/58268632c8ebc2855c38305a05b8326727e388c1))\n* support prism langs dependencies import validation ([#2489](https://github.com/docsifyjs/docsify/issues/2489)) ([87e43f1](https://github.com/docsifyjs/docsify/commit/87e43f157f749dda01b54e33117ef851b12bda7c))\n* support relative path with target config. ([#1751](https://github.com/docsifyjs/docsify/issues/1751)) ([e15ad0c](https://github.com/docsifyjs/docsify/commit/e15ad0c7d530287692e98ca587f8f07d90624919))\n* v5 style overhaul ([#2469](https://github.com/docsifyjs/docsify/issues/2469)) ([77d93fa](https://github.com/docsifyjs/docsify/commit/77d93fae7886346e739285630ea865ac8197149e))\n\n\n\n## [4.13.1](https://github.com/docsifyjs/docsify/compare/v4.13.0...v4.13.1) (2023-06-24)\n\n\n### Bug Fixes\n\n* enhancement of isExternal ([#2093](https://github.com/docsifyjs/docsify/issues/2093)) ([6a7d15b](https://github.com/docsifyjs/docsify/commit/6a7d15b1d5b93e19d3cf9a328cdbf5f1a166b5bd))\n* fix cross-origin links in history router mode ([#1967](https://github.com/docsifyjs/docsify/issues/1967)) ([2312fee](https://github.com/docsifyjs/docsify/commit/2312feef459211a8bcdcbf9164a9ffe051609b70))\n* genIndex error for search ([#1933](https://github.com/docsifyjs/docsify/issues/1933)) ([68d8735](https://github.com/docsifyjs/docsify/commit/68d873587c29d694ece466177984aa5fd739dd4b))\n\n\n\n# [4.13.0](https://github.com/docsifyjs/docsify/compare/v4.12.4...v4.13.0) (2022-10-26)\n\n\n### Bug Fixes\n\n* cornerExternalLinkTarget config. ([#1814](https://github.com/docsifyjs/docsify/issues/1814)) ([54cc5f9](https://github.com/docsifyjs/docsify/commit/54cc5f939b9499ae56604f589eef4e3f1c13cdc5))\n* correctly fix missing +1, -1 ([#1722](https://github.com/docsifyjs/docsify/issues/1722)) ([719dcbe](https://github.com/docsifyjs/docsify/commit/719dcbea5cb0c8b0835f8e9fc473b094feecb7ec))\n* Coverpage when content > viewport height ([#1744](https://github.com/docsifyjs/docsify/issues/1744)) ([301b516](https://github.com/docsifyjs/docsify/commit/301b5169613e95765eda335a4b21d0f9f9cbbbfd)), closes [#381](https://github.com/docsifyjs/docsify/issues/381)\n* filter null node first. ([#1909](https://github.com/docsifyjs/docsify/issues/1909)) ([d27af3d](https://github.com/docsifyjs/docsify/commit/d27af3dd09a882fce4f1e2774065de57a3501858))\n* fix docsify-ignore in search title. ([#1872](https://github.com/docsifyjs/docsify/issues/1872)) ([9832805](https://github.com/docsifyjs/docsify/commit/9832805681cc6099e9c78deecf6dc0c6fb61fd9b))\n* fix search with no content. ([#1878](https://github.com/docsifyjs/docsify/issues/1878)) ([9b74744](https://github.com/docsifyjs/docsify/commit/9b74744299ece0108573a142e5a2e949dc569254))\n* Ignore emoji shorthand codes in URIs ([#1847](https://github.com/docsifyjs/docsify/issues/1847)) ([3c9b3d9](https://github.com/docsifyjs/docsify/commit/3c9b3d9702bb05a5ff45a4ce4233e144cf1ebecb)), closes [#1823](https://github.com/docsifyjs/docsify/issues/1823)\n* Legacy bugs (styles, site plugin error, and dev server error) ([#1743](https://github.com/docsifyjs/docsify/issues/1743)) ([fa6df6d](https://github.com/docsifyjs/docsify/commit/fa6df6d58487ec294e22be04ac159dfb5745bd66))\n* package.json & package-lock.json to reduce vulnerabilities ([#1756](https://github.com/docsifyjs/docsify/issues/1756)) ([2dc5b12](https://github.com/docsifyjs/docsify/commit/2dc5b12b715e3ad1922a6401e3fd9837a99ef9c0))\n* packages/docsify-server-renderer/package.json & packages/docsify-server-renderer/package-lock.json to reduce vulnerabilities ([#1715](https://github.com/docsifyjs/docsify/issues/1715)) ([c1227b2](https://github.com/docsifyjs/docsify/commit/c1227b22cb8a3fb6c362ca8ac109082ab2251cc3))\n\n\n### Features\n\n* Emoji build ([#1766](https://github.com/docsifyjs/docsify/issues/1766)) ([ba5ee26](https://github.com/docsifyjs/docsify/commit/ba5ee26f00e957b58173f96b1901f6ffd3d8e5f5))\n* Native emoji w/ image-based fallbacks and improved parsing ([#1746](https://github.com/docsifyjs/docsify/issues/1746)) ([35002c9](https://github.com/docsifyjs/docsify/commit/35002c92b762f0d12e51582d7de7914fa380596a)), closes [#779](https://github.com/docsifyjs/docsify/issues/779)\n* Plugin error handling ([#1742](https://github.com/docsifyjs/docsify/issues/1742)) ([63b2535](https://github.com/docsifyjs/docsify/commit/63b2535a45a98945ec897277f4fbddec2ddba887))\n\n\n\n## [4.12.2](https://github.com/docsifyjs/docsify/compare/v4.12.1...v4.12.2) (2022-01-06)\n\n\n### Bug Fixes\n\n* Add escapeHtml for search ([#1551](https://github.com/docsifyjs/docsify/issues/1551)) ([c24f7f6](https://github.com/docsifyjs/docsify/commit/c24f7f6f0b87a87f6dd3755f69eb0969ebb029c9))\n* allow also \" inside of an embed ([ec16e4a](https://github.com/docsifyjs/docsify/commit/ec16e4a9d5718ac4f4c25bb3dcaea3b7551372e0))\n* buble theme missing generic fallback font ([#1568](https://github.com/docsifyjs/docsify/issues/1568)) ([37d9f0e](https://github.com/docsifyjs/docsify/commit/37d9f0e1214276e93b2a11ed87390aafa1bdbcec))\n* Cannot read property 'classList' of null ([#1527](https://github.com/docsifyjs/docsify/issues/1527)) ([d6df2b8](https://github.com/docsifyjs/docsify/commit/d6df2b85a99371bb9a87402a10dd515bb734182e))\n* Cannot read property 'tagName' of null ([#1655](https://github.com/docsifyjs/docsify/issues/1655)) ([c3cdadc](https://github.com/docsifyjs/docsify/commit/c3cdadc37137edcd9e219359973902d2fc8b66ff))\n* upgrade debug from 4.3.2 to 4.3.3 ([#1692](https://github.com/docsifyjs/docsify/issues/1692)) ([40e7749](https://github.com/docsifyjs/docsify/commit/40e77490c68b4143c75dfaebcd0b7f640581306b))\n* Upgrade docsify from 4.12.0 to 4.12.1 ([#1544](https://github.com/docsifyjs/docsify/issues/1544)) ([d607f6d](https://github.com/docsifyjs/docsify/commit/d607f6d71c35b50f586806a832f65061f5e3427e))\n* upgrade dompurify from 2.2.6 to 2.2.7 ([#1552](https://github.com/docsifyjs/docsify/issues/1552)) ([407e4d4](https://github.com/docsifyjs/docsify/commit/407e4d4f3de78bebd639a3fdae751f8045728e57))\n* Upgrade dompurify from 2.2.6 to 2.2.7 ([#1553](https://github.com/docsifyjs/docsify/issues/1553)) ([93c48f3](https://github.com/docsifyjs/docsify/commit/93c48f3d615d95dba550a0e95df6b545d68c3593))\n* upgrade dompurify from 2.2.7 to 2.2.8 ([#1577](https://github.com/docsifyjs/docsify/issues/1577)) ([0dd44cc](https://github.com/docsifyjs/docsify/commit/0dd44cc828cc54f7c3b776d45b32925b66cae499))\n* upgrade dompurify from 2.2.7 to 2.3.0 ([#1619](https://github.com/docsifyjs/docsify/issues/1619)) ([66303fe](https://github.com/docsifyjs/docsify/commit/66303fec4c7115621e556ad742cfac9d19f26bd9))\n* upgrade dompurify from 2.2.8 to 2.2.9 ([#1600](https://github.com/docsifyjs/docsify/issues/1600)) ([baf5a8a](https://github.com/docsifyjs/docsify/commit/baf5a8a4962656d8be8f714283064d2ea10c7e14))\n* upgrade dompurify from 2.2.9 to 2.3.0 ([#1616](https://github.com/docsifyjs/docsify/issues/1616)) ([b07fa3c](https://github.com/docsifyjs/docsify/commit/b07fa3cc8323e63dd7b105c7e29b2e1914f5c117))\n* upgrade dompurify from 2.3.0 to 2.3.1 ([#1635](https://github.com/docsifyjs/docsify/issues/1635)) ([5ac8237](https://github.com/docsifyjs/docsify/commit/5ac8237cc76e19ca2b373a1a1da6eb4a4da6d8b2))\n* upgrade dompurify from 2.3.1 to 2.3.2 ([#1647](https://github.com/docsifyjs/docsify/issues/1647)) ([ff6acfa](https://github.com/docsifyjs/docsify/commit/ff6acfa7623a7db8b00d62c51a9c3037215c4888))\n* upgrade node-fetch from 2.6.1 to 2.6.2 ([#1641](https://github.com/docsifyjs/docsify/issues/1641)) ([6ee1c14](https://github.com/docsifyjs/docsify/commit/6ee1c142769a6442aa8c1523ab215106707fa7fc))\n* upgrade node-fetch from 2.6.2 to 2.6.4 ([#1649](https://github.com/docsifyjs/docsify/issues/1649)) ([6f81034](https://github.com/docsifyjs/docsify/commit/6f81034ba6a7a6b64ccf1acd2d1fc73761f70a63))\n* upgrade node-fetch from 2.6.4 to 2.6.5 ([#1654](https://github.com/docsifyjs/docsify/issues/1654)) ([d16e657](https://github.com/docsifyjs/docsify/commit/d16e657f708777e8377d8e158b50b4010623282d))\n* upgrade node-fetch from 2.6.5 to 2.6.6 ([#1668](https://github.com/docsifyjs/docsify/issues/1668)) ([cefe3f8](https://github.com/docsifyjs/docsify/commit/cefe3f87e697a6c54a74d601df2eeb331fcd8933))\n\n\n\n## [4.12.1](https://github.com/docsifyjs/docsify/compare/v4.12.0...v4.12.1) (2021-03-07)\n\n\n### Bug Fixes\n\n* isExternal check with malformed URL + tests ([#1510](https://github.com/docsifyjs/docsify/issues/1510)) ([ff2a66f](https://github.com/docsifyjs/docsify/commit/ff2a66f12752471277fe81a64ad6c4b2c08111fe)), closes [#1477](https://github.com/docsifyjs/docsify/issues/1477) [#1126](https://github.com/docsifyjs/docsify/issues/1126) [#1489](https://github.com/docsifyjs/docsify/issues/1489)\n* Replace ES6 usage for IE11 compatibility ([#1500](https://github.com/docsifyjs/docsify/issues/1500)) ([a0f61b2](https://github.com/docsifyjs/docsify/commit/a0f61b2af72cb888ea5b635021a5c9da6beb7ac5))\n* theme switcher in IE11 ([#1502](https://github.com/docsifyjs/docsify/issues/1502)) ([8cda078](https://github.com/docsifyjs/docsify/commit/8cda07891afeb1ea6e198d2a600f205357ab4b89))\n* Upgrade docsify from 4.11.6 to 4.12.0 ([#1518](https://github.com/docsifyjs/docsify/issues/1518)) ([47cd86c](https://github.com/docsifyjs/docsify/commit/47cd86c8f196a241fc23720e3addfe95d4c973cd))\n\n\n### Features\n\n* Support search when there is no title ([#1519](https://github.com/docsifyjs/docsify/issues/1519)) ([bc37268](https://github.com/docsifyjs/docsify/commit/bc3726853fb2d1f9241927ea0317970ab0c8a2f2))\n\n\n### Chore\n\n- Fix missing carbon ([#1501](https://github.com/docsifyjs/docsify/issues/1501))\n- Change Gitter to Discord throughout project ([#1507](https://github.com/docsifyjs/docsify/issues/1507))\n- Add test cases on isExternal ([#1515](https://github.com/docsifyjs/docsify/issues/1515))\n\n\n# [4.12.0](https://github.com/docsifyjs/docsify/compare/v4.11.6...v4.12.0) (2021-02-08)\n\n\n### Bug Fixes\n\n* add missing argument for highlighting code ([#1365](https://github.com/docsifyjs/docsify/issues/1365)) ([f35bf99](https://github.com/docsifyjs/docsify/commit/f35bf99d9c762774e328b30347707e62eb8e6f63))\n* Can't search homepage content ([#1391](https://github.com/docsifyjs/docsify/issues/1391)) ([25bc9b7](https://github.com/docsifyjs/docsify/commit/25bc9b7eb7e878a6a50ed5f91d33d6a75f9811b0))\n* Cannot read property 'startsWith' of undefined ([#1358](https://github.com/docsifyjs/docsify/issues/1358)) ([9351729](https://github.com/docsifyjs/docsify/commit/9351729634b52db0e7e241bed7784fbcd5c39fe0))\n* Cannot read property level of undefined ([#1357](https://github.com/docsifyjs/docsify/issues/1357)) ([4807e58](https://github.com/docsifyjs/docsify/commit/4807e58cb994de83f063cb82d2b4a695f29378c8))\n* cannot search list content ([#1361](https://github.com/docsifyjs/docsify/issues/1361)) ([8d17dcb](https://github.com/docsifyjs/docsify/commit/8d17dcbe68048d654e62adb01a3925e39a8e0c44))\n* duplicate search content when `/README` or `/` exists in the sidebar ([#1403](https://github.com/docsifyjs/docsify/issues/1403)) ([7c3bf98](https://github.com/docsifyjs/docsify/commit/7c3bf98df869188d5956ed1a331f7048b6eba441))\n* package.json & package-lock.json to reduce vulnerabilities ([#1419](https://github.com/docsifyjs/docsify/issues/1419)) ([69b6907](https://github.com/docsifyjs/docsify/commit/69b6907c864dbcdf1fe5c75f51a80e6ae6ec279d))\n* packages/docsify-server-renderer/package.json & packages/docsify-server-renderer/package-lock.json to reduce vulnerabilities ([#1389](https://github.com/docsifyjs/docsify/issues/1389)) ([62cd35e](https://github.com/docsifyjs/docsify/commit/62cd35ee8345270aab7a48bc761db007d54a0f48))\n* packages/docsify-server-renderer/package.json & packages/docsify-server-renderer/package-lock.json to reduce vulnerabilities ([#1418](https://github.com/docsifyjs/docsify/issues/1418)) ([58fbca0](https://github.com/docsifyjs/docsify/commit/58fbca00ebd12f636c213d386761df9ebfb2bd4c))\n* Prevent loading remote content via URL hash ([#1489](https://github.com/docsifyjs/docsify/issues/1489)) ([14ce7f3](https://github.com/docsifyjs/docsify/commit/14ce7f3d862ac79fc7d9d316cb2e057d50e1b506)), closes [#1477](https://github.com/docsifyjs/docsify/issues/1477) [#1126](https://github.com/docsifyjs/docsify/issues/1126)\n* search on homepage test ([#1398](https://github.com/docsifyjs/docsify/issues/1398)) ([ee550d0](https://github.com/docsifyjs/docsify/commit/ee550d0c51f222851854c7cd7e9e6f76e26eb6f4))\n* search titles containing ignored characters ([#1395](https://github.com/docsifyjs/docsify/issues/1395)) ([a2ebb21](https://github.com/docsifyjs/docsify/commit/a2ebb2192ac73211a8924111d736df9574abf61b))\n* sidebar active class and expand don't work as expect when use \"space\" in markdown filename ([#1454](https://github.com/docsifyjs/docsify/issues/1454)) ([dcf5a64](https://github.com/docsifyjs/docsify/commit/dcf5a644eb6a2eef65fb940f3407c12828a679bc)), closes [#1032](https://github.com/docsifyjs/docsify/issues/1032)\n* sidebar horizontal scroll bar ([#1362](https://github.com/docsifyjs/docsify/issues/1362)) ([b480822](https://github.com/docsifyjs/docsify/commit/b480822286c66b478e5a7a9b2c82a10b99c69121))\n* sidebar title error ([#1360](https://github.com/docsifyjs/docsify/issues/1360)) ([2100fc3](https://github.com/docsifyjs/docsify/commit/2100fc318b009c6e448e48ffff6a693f1988916c))\n* slugs are still broken when headings contain html ([#1443](https://github.com/docsifyjs/docsify/issues/1443)) ([76c5e68](https://github.com/docsifyjs/docsify/commit/76c5e685d75ee6df9acc0a7cf92d5daa138c3240))\n* the sidebar links to another site. ([#1336](https://github.com/docsifyjs/docsify/issues/1336)) ([c9d4f7a](https://github.com/docsifyjs/docsify/commit/c9d4f7abc94a2cbc4bb558013775df380c1c8376))\n* title error when sidebar link exists with html tag ([#1404](https://github.com/docsifyjs/docsify/issues/1404)) ([8ccc202](https://github.com/docsifyjs/docsify/commit/8ccc20225104376af2e5df6757c4dbd58c0e758e)), closes [#1408](https://github.com/docsifyjs/docsify/issues/1408)\n* Unable to navigate on server without default index support ([#1372](https://github.com/docsifyjs/docsify/issues/1372)) ([759ffac](https://github.com/docsifyjs/docsify/commit/759ffac992b19dbb05b92114ec5620d3bb180d0d))\n* upgrade debug from 4.1.1 to 4.3.0 ([#1390](https://github.com/docsifyjs/docsify/issues/1390)) ([ae45b32](https://github.com/docsifyjs/docsify/commit/ae45b3201ba03303a2feb5a347b18fda818a569a))\n* upgrade debug from 4.3.0 to 4.3.1 ([#1446](https://github.com/docsifyjs/docsify/issues/1446)) ([bc3350f](https://github.com/docsifyjs/docsify/commit/bc3350f6e6bfe670c95569b4e9c51321f5c7dbb9))\n* upgrade debug from 4.3.1 to 4.3.2 ([#1463](https://github.com/docsifyjs/docsify/issues/1463)) ([df21153](https://github.com/docsifyjs/docsify/commit/df21153ab5e841ad89707e07c68a04873a2f3fe8))\n* upgrade docsify from 4.11.4 to 4.11.6 ([#1373](https://github.com/docsifyjs/docsify/issues/1373)) ([c2d12ed](https://github.com/docsifyjs/docsify/commit/c2d12ed27fe86b0c5cd690b4d8a22bf06d2a90b9))\n* upgrade dompurify from 2.0.17 to 2.1.0 ([#1397](https://github.com/docsifyjs/docsify/issues/1397)) ([1863d8e](https://github.com/docsifyjs/docsify/commit/1863d8edb70da234bf7f211986ba71706351682f))\n* upgrade dompurify from 2.1.0 to 2.1.1 ([#1402](https://github.com/docsifyjs/docsify/issues/1402)) ([8cf9fd8](https://github.com/docsifyjs/docsify/commit/8cf9fd8150bd67709c68d8dfe4dc881624583ac8))\n* upgrade dompurify from 2.2.2 to 2.2.3 ([#1457](https://github.com/docsifyjs/docsify/issues/1457)) ([720d909](https://github.com/docsifyjs/docsify/commit/720d9091c8e571d6c891426983f54d9c94739182))\n* upgrade dompurify from 2.2.2 to 2.2.6 ([#1483](https://github.com/docsifyjs/docsify/issues/1483)) ([eee9507](https://github.com/docsifyjs/docsify/commit/eee9507d435459ae8e68e1112bb58a63b2f58530))\n* upgrade dompurify from 2.2.3 to 2.2.6 ([#1482](https://github.com/docsifyjs/docsify/issues/1482)) ([7adad57](https://github.com/docsifyjs/docsify/commit/7adad57df1b7efa469b0cde37f788c36dc27cf8b))\n* upgrade marked from 1.2.4 to 1.2.9 ([#1486](https://github.com/docsifyjs/docsify/issues/1486)) ([716a7fa](https://github.com/docsifyjs/docsify/commit/716a7fa777a1eee66964977e1d4405cff3275c53))\n* upgrade prismjs from 1.21.0 to 1.22.0 ([#1415](https://github.com/docsifyjs/docsify/issues/1415)) ([0806f48](https://github.com/docsifyjs/docsify/commit/0806f48531b6cb5e6a395c12ab88f0b59281bbf8))\n* upgrade prismjs from 1.22.0 to 1.23.0 ([#1481](https://github.com/docsifyjs/docsify/issues/1481)) ([5f29cde](https://github.com/docsifyjs/docsify/commit/5f29cde84c7b74d70c34e3f1f43c479f1bba670d))\n* Use legacy-compatible methods for IE11 ([#1495](https://github.com/docsifyjs/docsify/issues/1495)) ([06cbebf](https://github.com/docsifyjs/docsify/commit/06cbebfc0d34726f4a7102a7dc9020520f3a7f86))\n\n\n### Features\n\n* search ignore diacritical marks ([#1434](https://github.com/docsifyjs/docsify/issues/1434)) ([8968a74](https://github.com/docsifyjs/docsify/commit/8968a744cea5910057ba59ef690316722a35b341))\n* Add Jest + Playwright Testing ([#1276](https://github.com/docsifyjs/docsify/issues/1276))\n* Add Vue components, mount options, global options, and v3 support ([#1409](https://github.com/docsifyjs/docsify/issues/1409))\n\n\n\n## [4.11.6](https://github.com/docsifyjs/docsify/compare/v4.11.5...v4.11.6) (2020-08-22)\n\n\n### Bug Fixes\n\n* Add patch for {docsify-ignore} and {docsify-ignore-all} ([ce31607](https://github.com/docsifyjs/docsify/commit/ce316075e033afdbeb43ce01e284a29fe1870e38))\n\n\n\n## [4.11.5](https://github.com/docsifyjs/docsify/compare/v4.11.4...v4.11.5) (2020-08-21)\n\n\n### Bug Fixes\n\n*  Russian language link error ([#1270](https://github.com/docsifyjs/docsify/issues/1270)) ([2a52460](https://github.com/docsifyjs/docsify/commit/2a52460a59448abaf681046fbc5dca642285ae1f))\n* {docsify-updated} in the sample code is parsed into time ([#1321](https://github.com/docsifyjs/docsify/issues/1321)) ([2048610](https://github.com/docsifyjs/docsify/commit/2048610aacd4e3c6a592f4247834a726c7ca33fb))\n* Add error handling for missing dependencies (fixes [#1210](https://github.com/docsifyjs/docsify/issues/1210)) ([#1232](https://github.com/docsifyjs/docsify/issues/1232)) ([3673001](https://github.com/docsifyjs/docsify/commit/3673001a24cb24c57454f9bc7619de49d2c3a044))\n* after setting the background image, the button is obscured ([#1234](https://github.com/docsifyjs/docsify/issues/1234)) ([34d918f](https://github.com/docsifyjs/docsify/commit/34d918f9973bdb8e893248853e3ef7e803d4c253))\n* convert {docsify-ignore} and {docsify-ignore-all} to HTML comments ([#1318](https://github.com/docsifyjs/docsify/issues/1318)) ([90d283d](https://github.com/docsifyjs/docsify/commit/90d283d340502456a5d8495df596bb4a02ceb39b))\n* fallback page should use path not file location ([#1301](https://github.com/docsifyjs/docsify/issues/1301)) ([2bceabc](https://github.com/docsifyjs/docsify/commit/2bceabcb8e623570540493e2f1d956adf45c99e7))\n* Fix search error when exist translations documents ([#1300](https://github.com/docsifyjs/docsify/issues/1300)) ([b869019](https://github.com/docsifyjs/docsify/commit/b8690199006366e86084e9e018def7b9b8f46512))\n* gitignore was ignoring folders in src, so VS Code search results or file fuzzy finder were not working, etc ([d4c9247](https://github.com/docsifyjs/docsify/commit/d4c9247b87c0a2701683ed1a17383cfb451cf609))\n* packages/docsify-server-renderer/package.json & packages/docsify-server-renderer/package-lock.json to reduce vulnerabilities ([#1250](https://github.com/docsifyjs/docsify/issues/1250)) ([d439bac](https://github.com/docsifyjs/docsify/commit/d439bac93f479d0480799880538fc3104e54c907))\n* search can not search the table header ([#1256](https://github.com/docsifyjs/docsify/issues/1256)) ([3f03e78](https://github.com/docsifyjs/docsify/commit/3f03e78418993d8e9a4f5062e10dc79c3753389e))\n* Search plugin: matched text is replaced with search text ([#1298](https://github.com/docsifyjs/docsify/issues/1298)) ([78775b6](https://github.com/docsifyjs/docsify/commit/78775b6ee73102cc5ac71c0ee2b392c5f4f6f4f8))\n* the uncaught typeerror when el is null ([#1308](https://github.com/docsifyjs/docsify/issues/1308)) ([952f4c9](https://github.com/docsifyjs/docsify/commit/952f4c921b7a6a558c500ca6b105582d39ad36a2))\n* Updated docs with instructions for installing specific version (fixes [#780](https://github.com/docsifyjs/docsify/issues/780)) ([#1225](https://github.com/docsifyjs/docsify/issues/1225)) ([b90c948](https://github.com/docsifyjs/docsify/commit/b90c948090e89fa778279c95060dbd7668285658))\n* upgrade medium-zoom from 1.0.5 to 1.0.6 ([3beaa66](https://github.com/docsifyjs/docsify/commit/3beaa6666b78518f1ffaa37f6942f3cb08fef896))\n* upgrade tinydate from 1.2.0 to 1.3.0 ([#1341](https://github.com/docsifyjs/docsify/issues/1341)) ([59d090f](https://github.com/docsifyjs/docsify/commit/59d090fe9096bc03e259c166634bb75bb2623f85))\n\n\n### Features\n\n* **search:** add pathNamespaces option ([d179dde](https://github.com/docsifyjs/docsify/commit/d179dde1c71acdcbe66cb762377b123926c55bf2))\n* Add title to sidebar links ([#1286](https://github.com/docsifyjs/docsify/issues/1286)) ([667496b](https://github.com/docsifyjs/docsify/commit/667496b85d99b168255f58e60a6bfe902cc6ee03))\n\n\n\n## [4.11.4](https://github.com/docsifyjs/docsify/compare/v4.11.3...v4.11.4) (2020-06-18)\n\n\n### Bug Fixes\n\n* consistent location of search result ([e9dd2de](https://github.com/docsifyjs/docsify/commit/e9dd2de384b81619aae2bcbf92f52721cb76a177))\n* cover overlapping sidebar by removing z-index ([0bf03f5](https://github.com/docsifyjs/docsify/commit/0bf03f58103037d100b1635cf3989c8d3672b4ba))\n* cross-origin url cannot be redirected when  \"externalLinkTarget\" is set to \"_self\" and \"routerMode\" is set to \"history\". ([#1062](https://github.com/docsifyjs/docsify/issues/1062)) ([fd2cec6](https://github.com/docsifyjs/docsify/commit/fd2cec6bd66c46d6957811fefae4c615c3052a4f)), closes [#1046](https://github.com/docsifyjs/docsify/issues/1046) [#1046](https://github.com/docsifyjs/docsify/issues/1046) [#1046](https://github.com/docsifyjs/docsify/issues/1046)\n* default html img resize if no height included ([#1065](https://github.com/docsifyjs/docsify/issues/1065)) ([9ff4d06](https://github.com/docsifyjs/docsify/commit/9ff4d0677304bc190e7bd9e89bbbdc64895197fa))\n* fixed target and rel issue (fixes [#1183](https://github.com/docsifyjs/docsify/issues/1183)) ([3d662a5](https://github.com/docsifyjs/docsify/commit/3d662a5bf71bbfef077cfbc478df241d794f55a0))\n* Inconsistent search and body rendering ([dcb0aae](https://github.com/docsifyjs/docsify/commit/dcb0aaea99efbd68175f1d1aeb5076b6dde9801e))\n* rendering cover width bug ([717991c](https://github.com/docsifyjs/docsify/commit/717991c90cf709f4da91fe32610129de6529266b))\n* search does not find the contents of the table ([#1198](https://github.com/docsifyjs/docsify/issues/1198)) ([31010e4](https://github.com/docsifyjs/docsify/commit/31010e4979b3d3ab4bd247a09c4ac5fd1405eaa8))\n* The search error after setting the ID in the title ([#1159](https://github.com/docsifyjs/docsify/issues/1159)) ([6e554f8](https://github.com/docsifyjs/docsify/commit/6e554f8ebd3d4a2c5c7e4f66cff3dfe2b6aa1e31))\n* upgrade docsify from 4.10.2 to 4.11.2 ([60b7f89](https://github.com/docsifyjs/docsify/commit/60b7f89b373b0d48ec8406a51eddeaed8126696d))\n\n\n### Features\n\n* added html sanitizer for remote rendering ([#1128](https://github.com/docsifyjs/docsify/issues/1128)) ([714ef29](https://github.com/docsifyjs/docsify/commit/714ef29afe779a6db5c4761ebaacdfc70ee2d8dd))\n* update src/core/index.js to export all global APIs, deprecate old globals in favor of a single global DOCSIFY, and add tests for this ([7e002bf](https://github.com/docsifyjs/docsify/commit/7e002bf939d7837843908417b5445b4f8d36c1cd))\n\n\n### Reverts\n\n* Revert \"Updated docs site dark and light mode with switch and redesigned search bar using docsify-darklight-theme\" (#1207) ([26cb940](https://github.com/docsifyjs/docsify/commit/26cb940b51d34ee584b8425012a336f38a4abd76)), closes [#1207](https://github.com/docsifyjs/docsify/issues/1207) [#1182](https://github.com/docsifyjs/docsify/issues/1182)\n\n\n\n## [4.11.3](https://github.com/docsifyjs/docsify/compare/v4.11.2...v4.11.3) (2020-03-24)\n\n\n### Bug Fixes\n\n* fix: digit issue with sidebar (complete REVERT to old method) ([154abf5](https://github.com/docsifyjs/docsify/commit/154abf59a6153e84b018fcdffa86892776d6da7d))\n\n\n\n## [4.11.2](https://github.com/docsifyjs/docsify/compare/v4.11.1...v4.11.2) (2020-03-09)\n\n\n### Bug Fixes\n\n* fixed rendering of color in coverpage issue ([406670c](https://github.com/docsifyjs/docsify/commit/406670c3d619a627142900fd45019fb8ce00f60a))\n\n\n\n## [4.11.1](https://github.com/docsifyjs/docsify/compare/v4.11.0...v4.11.1) (2020-03-09)\n\n\n\n# [4.11.0](https://github.com/docsifyjs/docsify/compare/v4.10.2...v4.11.0) (2020-03-09)\n\n\n### Bug Fixes\n\n* emojis in titles not working correctly and update ([#1016](https://github.com/docsifyjs/docsify/issues/1016)) ([b3d9b96](https://github.com/docsifyjs/docsify/commit/b3d9b966dfbb6f456c2c457da1d2a366e85d9190))\n* searching table content ([6184e50](https://github.com/docsifyjs/docsify/commit/6184e502629932ca71fdd0a1b10150d118f5a7c8))\n* stage modified files as part of pre-commit hook ([#985](https://github.com/docsifyjs/docsify/issues/985)) ([5b77b0f](https://github.com/docsifyjs/docsify/commit/5b77b0f628f056b7ebb6d0b617561d19964516a2))\n* config initialization and coercion ([#861](https://github.com/docsifyjs/docsify/pull/861))\n* strip indent when embedding code fragment ([#996](https://github.com/docsifyjs/docsify/pull/996))\n* Ensure autoHeader dom result is similar to parsed H1 ([#811](https://github.com/docsifyjs/docsify/pull/811))\n* upgrade docsify from 4.9.4 to 4.10.2 ([#1054](https://github.com/docsifyjs/docsify/issues/1054)) ([78290b2](https://github.com/docsifyjs/docsify/commit/78290b21038a3ae09c4c7438bd89b14ca4c02805))\n* upgrade medium-zoom from 1.0.4 to 1.0.5 ([39ebd73](https://github.com/docsifyjs/docsify/commit/39ebd73021290439180878cae32e663b9e60e214))\n* upgrade prismjs from 1.17.1 to 1.19.0 ([9981c43](https://github.com/docsifyjs/docsify/commit/9981c4361ad690d0ed32cf1fb5b48cc5b9f770bb))\n\n\n### Features\n\n* configure pre-commit hook ([#983](https://github.com/docsifyjs/docsify/issues/983)) ([eea41a1](https://github.com/docsifyjs/docsify/commit/eea41a1207c46533ea9c6c59d82e2c94aa4dd70e))\n* Add a prepare script. ([efbea24](https://github.com/docsifyjs/docsify/commit/efbea24de71f2287993b52ed1cef1a2dd6a53f81))\n* added capability to add css class and id to images + links + refactoring ([#820](https://github.com/docsifyjs/docsify/issues/820)) ([724ac02](https://github.com/docsifyjs/docsify/commit/724ac024ddfc28e93d8b5dd909e722747286fa00))\n* added dark mode to docs closes [#1031](https://github.com/docsifyjs/docsify/issues/1031) ([dc43d3c](https://github.com/docsifyjs/docsify/commit/dc43d3c512c2f04750e76176c25ece626ae7fe2a))\n* new option `hideSidebar` ([#1026](https://github.com/docsifyjs/docsify/issues/1026)) ([b7547f1](https://github.com/docsifyjs/docsify/commit/b7547f151e928b3a0eb6a94b2af36023da4fa877))\n* new option `topMargin` ([#1045](https://github.com/docsifyjs/docsify/pull/1045)) ([8faee03](https://github.com/docsifyjs/docsify/pull/1024/commits/b53ea1e304d3a2782b125c1d8711295d88faee03))\n\n\n### Docs\n\n* update docs for the `name` config option ([#992](https://github.com/docsifyjs/docsify/pull/992))\n* about cache ([#854](https://github.com/docsifyjs/docsify/pull/854))\n* removed FOSSA badge\n* documented `__colon__` tip ([#1025](https://github.com/docsifyjs/docsify/pull/1025))\n\n### Chore\n\n* Migrate relative links to absolute in embedded markdown ([#867](https://github.com/docsifyjs/docsify/pull/867))\n* smarter scroll behavior ([#744](https://github.com/docsifyjs/docsify/pull/744))\n* improve basic layout style ([#884](https://github.com/docsifyjs/docsify/pull/884))\n* There are currently {three=>four} themes available. ([#892](https://github.com/docsifyjs/docsify/pull/892))\n* Added a redirect for images to show up in Amplify ([#918](https://github.com/docsifyjs/docsify/pull/918))\n* removed the escaping of the name of sidebar ([#991](https://github.com/docsifyjs/docsify/pull/991))\n* Eslint fixes for v4x ([#989](https://github.com/docsifyjs/docsify/pull/989))\n* added github Actions for CI ([#1000](https://github.com/docsifyjs/docsify/pull/1000))\n* Add a prepare script. ([#1010](https://github.com/docsifyjs/docsify/pull/1010))\n* chore(GH-action): using ubuntu 16 and removed node 8 from CI\n* chore: add config ([#1014](https://github.com/docsifyjs/docsify/pull/1014))\n* chore(stale): added enhancement label to exemptlabels\n* chore(stale): added bug label to exemptlabels\n* .markdown-section max-width 800px to 80% ([#1017](https://github.com/docsifyjs/docsify/pull/1017))\n* changed the CDN from unpkg to jsDelivr #1020 ([#1022](https://github.com/docsifyjs/docsify/pull/1022))\n* migrate CI to github, refactore CI and npm scripts, linting fixes ([#1023](https://github.com/docsifyjs/docsify/pull/1023))\n* chore(readme): added CI badges and fixed the logo issue\n* added new linter config ([#1028](https://github.com/docsifyjs/docsify/pull/1028))\n\n\n## [4.10.2](https://github.com/docsifyjs/docsify/compare/v4.10.0...v4.10.2) (2019-12-16)\n\n\n\n# [4.10.0](https://github.com/docsifyjs/docsify/compare/v4.9.4...v4.10.0) (2019-12-16)\n\n\n### Bug Fixes\n\n* fixed security alert for chokidar(update dep) ([a62b037](https://github.com/docsifyjs/docsify/commit/a62b037becb36941c11c8eab6e4d83df8db85af3))\n* npm audit issues ([#934](https://github.com/docsifyjs/docsify/issues/934)) ([615205c](https://github.com/docsifyjs/docsify/commit/615205cfdb7aea8f37a1ec5dd928105eeef56357))\n* package security alerts ([f5f1561](https://github.com/docsifyjs/docsify/commit/f5f15619f1a239d6ce12a2f83ad8817352a3352b))\n* security alerts of cssnano ([d7d5c8f](https://github.com/docsifyjs/docsify/commit/d7d5c8f302d7c18dbb32e982202a07b73badf6f6))\n\n\n\n<a name=\"4.9.4\"></a>\n## [4.9.4](https://github.com/docsifyjs/docsify/compare/v4.9.2...v4.9.4) (2019-05-05)\n\n\n\n<a name=\"4.9.2\"></a>\n## [4.9.2](https://github.com/docsifyjs/docsify/compare/v4.9.1...v4.9.2) (2019-04-21)\n\n\n### Bug Fixes\n\n* re-render gitalk when router changed ([11ea1f8](https://github.com/docsifyjs/docsify/commit/11ea1f8))\n\n\n### Features\n\n* allows relative path, fixed [#590](https://github.com/docsifyjs/docsify/issues/590) ([31654f1](https://github.com/docsifyjs/docsify/commit/31654f1))\n\n\n\n<a name=\"4.9.1\"></a>\n## [4.9.1](https://github.com/docsifyjs/docsify/compare/v4.9.0...v4.9.1) (2019-02-21)\n\n\n### Bug Fixes\n\n* github assets url ([#774](https://github.com/docsifyjs/docsify/issues/774)) ([140bf10](https://github.com/docsifyjs/docsify/commit/140bf10))\n\n\n\n<a name=\"4.9.0\"></a>\n# [4.9.0](https://github.com/docsifyjs/docsify/compare/v4.8.6...v4.9.0) (2019-02-19)\n\n\n### Bug Fixes\n\n* task list rendering (Fix [#749](https://github.com/docsifyjs/docsify/issues/749)) ([#757](https://github.com/docsifyjs/docsify/issues/757)) ([69ef489](https://github.com/docsifyjs/docsify/commit/69ef489))\n* upgrade npm-run-all ([049726e](https://github.com/docsifyjs/docsify/commit/049726e))\n\n\n### Features\n\n* **search-plugin:** add namespace option ([#706](https://github.com/docsifyjs/docsify/issues/706)) ([28beff8](https://github.com/docsifyjs/docsify/commit/28beff8))\n* Add new theme \"dolphin\" ([#735](https://github.com/docsifyjs/docsify/issues/735)) ([c3345ba](https://github.com/docsifyjs/docsify/commit/c3345ba))\n* Provide code fragments feature ([#748](https://github.com/docsifyjs/docsify/issues/748)) ([1447c8a](https://github.com/docsifyjs/docsify/commit/1447c8a))\n\n\n\n<a name=\"4.8.6\"></a>\n## [4.8.6](https://github.com/docsifyjs/docsify/compare/v4.8.5...v4.8.6) (2018-11-12)\n\n\n### Bug Fixes\n\n* IE10 compatibility ([#691](https://github.com/docsifyjs/docsify/issues/691)) ([4db8cd6](https://github.com/docsifyjs/docsify/commit/4db8cd6))\n\n\n\n<a name=\"4.8.5\"></a>\n## [4.8.5](https://github.com/docsifyjs/docsify/compare/v4.8.4...v4.8.5) (2018-11-02)\n\n\n### Bug Fixes\n\n* expose version info for Docsify, fixed [#641](https://github.com/docsifyjs/docsify/issues/641) ([aa719e3](https://github.com/docsifyjs/docsify/commit/aa719e3))\n\n\n\n<a name=\"4.8.4\"></a>\n## [4.8.4](https://github.com/docsifyjs/docsify/compare/v4.8.3...v4.8.4) (2018-11-01)\n\n\n### Bug Fixes\n\n* **cover:** Compatible with legacy styles, fixed [#677](https://github.com/docsifyjs/docsify/issues/677) ([#678](https://github.com/docsifyjs/docsify/issues/678)) ([1a945d4](https://github.com/docsifyjs/docsify/commit/1a945d4))\n\n\n\n<a name=\"4.8.3\"></a>\n\n## [4.8.3](https://github.com/docsifyjs/docsify/compare/v4.8.2...v4.8.3) (2018-11-01)\n\nFix the last release files has the old version marked...\n\n<a name=\"4.8.2\"></a>\n\n## [4.8.2](https://github.com/docsifyjs/docsify/compare/v4.8.1...v4.8.2) (2018-11-01)\n\n### Bug Fixes\n\n- cover button style, fixed [#670](https://github.com/docsifyjs/docsify/issues/670), fixed [#665](https://github.com/docsifyjs/docsify/issues/665) ([#675](https://github.com/docsifyjs/docsify/issues/675)) ([fcd1087](https://github.com/docsifyjs/docsify/commit/fcd1087))\n- update match regex ([#669](https://github.com/docsifyjs/docsify/issues/669)) ([2edf47e](https://github.com/docsifyjs/docsify/commit/2edf47e))\n- use copy of cached value ([#668](https://github.com/docsifyjs/docsify/issues/668)) ([5fcf210](https://github.com/docsifyjs/docsify/commit/5fcf210))\n- **compiler:** import prism-markup-templating, fixed [#672](https://github.com/docsifyjs/docsify/issues/672) ([#676](https://github.com/docsifyjs/docsify/issues/676)) ([fdd8826](https://github.com/docsifyjs/docsify/commit/fdd8826))\n\n### Features\n\n- add heading config id ([#671](https://github.com/docsifyjs/docsify/issues/671)) ([ab19b13](https://github.com/docsifyjs/docsify/commit/ab19b13))\n\n<a name=\"4.8.1\"></a>\n\n## [4.8.1](https://github.com/docsifyjs/docsify/compare/v4.8.0...v4.8.1) (2018-10-31)\n\n### Bug Fixes\n\n- ssr package dep, fixed [#605](https://github.com/docsifyjs/docsify/issues/605) ([2bc880d](https://github.com/docsifyjs/docsify/commit/2bc880d))\n- **compiler:** extra quotes for codeblock ([4f588e0](https://github.com/docsifyjs/docsify/commit/4f588e0))\n- **compiler:** prevent render of html code in paragraph, fixed [#663](https://github.com/docsifyjs/docsify/issues/663) ([d35059d](https://github.com/docsifyjs/docsify/commit/d35059d))\n\n### Features\n\n- upgrade PrismJS, fixed [#534](https://github.com/docsifyjs/docsify/issues/534) ([4805cb5](https://github.com/docsifyjs/docsify/commit/4805cb5))\n\n<a name=\"4.8.0\"></a>\n\n# [4.8.0](https://github.com/docsifyjs/docsify/compare/v4.7.1...v4.8.0) (2018-10-31)\n\n### Bug Fixes\n\n- Cache TOC for later usage in the case of cached file html ([#649](https://github.com/docsifyjs/docsify/issues/649)) ([9e86017](https://github.com/docsifyjs/docsify/commit/9e86017))\n- improve external script plugin ([#632](https://github.com/docsifyjs/docsify/issues/632)) ([50c2434](https://github.com/docsifyjs/docsify/commit/50c2434))\n- missing variable declaration ([#660](https://github.com/docsifyjs/docsify/issues/660)) ([1ce37bd](https://github.com/docsifyjs/docsify/commit/1ce37bd))\n- Remove target for mailto links ([#652](https://github.com/docsifyjs/docsify/issues/652)) ([18f0f03](https://github.com/docsifyjs/docsify/commit/18f0f03))\n- Update getAllPath query selector ([#653](https://github.com/docsifyjs/docsify/issues/653)) ([f6f4e32](https://github.com/docsifyjs/docsify/commit/f6f4e32))\n- Update vue.styl ([#634](https://github.com/docsifyjs/docsify/issues/634)) ([bf060be](https://github.com/docsifyjs/docsify/commit/bf060be))\n\n### Features\n\n- Add docsify version to $window.docsify object ([#641](https://github.com/docsifyjs/docsify/issues/641)) ([94bc415](https://github.com/docsifyjs/docsify/commit/94bc415)), closes [#521](https://github.com/docsifyjs/docsify/issues/521)\n- **compiler:** support embedded mermaid ([#629](https://github.com/docsifyjs/docsify/issues/629)) ([42ea8af](https://github.com/docsifyjs/docsify/commit/42ea8af))\n- Add hideOtherSidebarContent option ([#661](https://github.com/docsifyjs/docsify/issues/661)) ([4a23c4a](https://github.com/docsifyjs/docsify/commit/4a23c4a))\n- Allow base64, external, and relative logo values ([#642](https://github.com/docsifyjs/docsify/issues/642)) ([0a0802a](https://github.com/docsifyjs/docsify/commit/0a0802a)), closes [#577](https://github.com/docsifyjs/docsify/issues/577)\n- upgrade marked to 0.5.x, fixed [#645](https://github.com/docsifyjs/docsify/issues/645), close [#644](https://github.com/docsifyjs/docsify/issues/644) ([#662](https://github.com/docsifyjs/docsify/issues/662)) ([a39b214](https://github.com/docsifyjs/docsify/commit/a39b214))\n\n<a name=\"4.7.1\"></a>\n\n## [4.7.1](https://github.com/docsifyjs/docsify/compare/v4.7.0...v4.7.1) (2018-08-30)\n\n<a name=\"4.7.0\"></a>\n\n# [4.7.0](https://github.com/QingWei-Li/docsify/compare/v4.6.9...v4.7.0) (2018-06-29)\n\n### Bug Fixes\n\n- alldow addition content in sidebar, fix [#518](https://github.com/QingWei-Li/docsify/issues/518), fix 539 ([#543](https://github.com/QingWei-Li/docsify/issues/543)) ([04b36b0](https://github.com/QingWei-Li/docsify/commit/04b36b0))\n- async install config, fixed [#425](https://github.com/QingWei-Li/docsify/issues/425) ([e4e011c](https://github.com/QingWei-Li/docsify/commit/e4e011c))\n- loading embed files synchronously, fixed [#525](https://github.com/QingWei-Li/docsify/issues/525), fixed [#527](https://github.com/QingWei-Li/docsify/issues/527) ([#544](https://github.com/QingWei-Li/docsify/issues/544)) ([feea7f9](https://github.com/QingWei-Li/docsify/commit/feea7f9))\n- path include chinese character cause hilight bug ([#556](https://github.com/QingWei-Li/docsify/issues/556)) ([a5f333a](https://github.com/QingWei-Li/docsify/commit/a5f333a))\n\n### Features\n\n- add logo option, [#264](https://github.com/QingWei-Li/docsify/issues/264) ([#541](https://github.com/QingWei-Li/docsify/issues/541)) ([ee72dd0](https://github.com/QingWei-Li/docsify/commit/ee72dd0))\n- add unpkg field, close [#531](https://github.com/QingWei-Li/docsify/issues/531) ([#558](https://github.com/QingWei-Li/docsify/issues/558)) ([5c0de0a](https://github.com/QingWei-Li/docsify/commit/5c0de0a))\n- support image resizing, resolve [#508](https://github.com/QingWei-Li/docsify/issues/508) ([#545](https://github.com/QingWei-Li/docsify/issues/545)) ([3a7ad62](https://github.com/QingWei-Li/docsify/commit/3a7ad62))\n\n<a name=\"4.6.10\"></a>\n\n## [4.6.10](https://github.com/QingWei-Li/docsify/compare/v4.6.9...v4.6.10) (2018-03-25)\n\n### Bug Fixes\n\n- async install config, fixed [#425](https://github.com/QingWei-Li/docsify/issues/425) ([e4e011c](https://github.com/QingWei-Li/docsify/commit/e4e011c))\n\n<a name=\"4.6.9\"></a>\n\n## [4.6.9](https://github.com/QingWei-Li/docsify/compare/v4.6.8...v4.6.9) (2018-03-10)\n\n### Bug Fixes\n\n- upgrade medium-zoom, fixed [#417](https://github.com/QingWei-Li/docsify/issues/417) ([6a3d69a](https://github.com/QingWei-Li/docsify/commit/6a3d69a))\n\n<a name=\"4.6.8\"></a>\n\n## [4.6.8](https://github.com/QingWei-Li/docsify/compare/v4.6.7...v4.6.8) (2018-03-06)\n\n### Bug Fixes\n\n- resolve path of image and embed files, fixed [#412](https://github.com/QingWei-Li/docsify/issues/412) ([bfd0d18](https://github.com/QingWei-Li/docsify/commit/bfd0d18))\n\n<a name=\"4.6.7\"></a>\n\n## [4.6.7](https://github.com/QingWei-Li/docsify/compare/v4.6.6...v4.6.7) (2018-03-03)\n\n### Bug Fixes\n\n- layout css, fixed [#409](https://github.com/QingWei-Li/docsify/issues/409) ([aeb692e](https://github.com/QingWei-Li/docsify/commit/aeb692e))\n\n<a name=\"4.6.6\"></a>\n\n## [4.6.6](https://github.com/QingWei-Li/docsify/compare/v4.6.5...v4.6.6) (2018-03-03)\n\n<a name=\"4.6.5\"></a>\n\n## [4.6.5](https://github.com/QingWei-Li/docsify/compare/v4.6.4...v4.6.5) (2018-03-03)\n\n### Bug Fixes\n\n- **navbar:** Now Navbar isn't append to DOM when loadNavbar is falsy ([#407](https://github.com/QingWei-Li/docsify/issues/407)) ([0933445](https://github.com/QingWei-Li/docsify/commit/0933445))\n\n### Features\n\n- **config:** Add 404 page options. ([#406](https://github.com/QingWei-Li/docsify/issues/406)) ([9b3b445](https://github.com/QingWei-Li/docsify/commit/9b3b445))\n\n<a name=\"4.6.4\"></a>\n\n## [4.6.4](https://github.com/QingWei-Li/docsify/compare/v4.6.3...v4.6.4) (2018-03-01)\n\n### Bug Fixes\n\n- **render:** Disable markdown parsing when the file is an HTML ([#403](https://github.com/QingWei-Li/docsify/issues/403)) ([278a75e](https://github.com/QingWei-Li/docsify/commit/278a75e))\n\n### Features\n\n- **fetch:** Add fallback languages configuration. ([#402](https://github.com/QingWei-Li/docsify/issues/402)) ([ecc0e04](https://github.com/QingWei-Li/docsify/commit/ecc0e04))\n\n<a name=\"4.6.3\"></a>\n\n## [4.6.3](https://github.com/QingWei-Li/docsify/compare/v4.6.2...v4.6.3) (2018-02-15)\n\n### Bug Fixes\n\n- **hook:** beforeEach don\\'t work, fixed [#393](https://github.com/QingWei-Li/docsify/issues/393) ([6a09059](https://github.com/QingWei-Li/docsify/commit/6a09059))\n\n<a name=\"4.6.2\"></a>\n\n## [4.6.2](https://github.com/QingWei-Li/docsify/compare/v4.6.1...v4.6.2) (2018-02-14)\n\n### Bug Fixes\n\n- **embed:** broken in IE, fixed [#389](https://github.com/QingWei-Li/docsify/issues/389), fixed [#391](https://github.com/QingWei-Li/docsify/issues/391) ([45a7464](https://github.com/QingWei-Li/docsify/commit/45a7464))\n- **embed:** init value ([890a7bf](https://github.com/QingWei-Li/docsify/commit/890a7bf))\n\n<a name=\"4.6.1\"></a>\n\n## [4.6.1](https://github.com/QingWei-Li/docsify/compare/v4.6.0...v4.6.1) (2018-02-12)\n\n### Bug Fixes\n\n- **embed** compatible ssr ([dc0c3ce](https://github.com/QingWei-Li/docsify/commit/dc0c3ce))\n- **embed** async fetch embed files, fixed [#387](https://github.com/QingWei-Li/docsify/issues/387)\n\n<a name=\"4.6.0\"></a>\n\n# [4.6.0](https://github.com/QingWei-Li/docsify/compare/v4.5.9...v4.6.0) (2018-02-11)\n\n### Bug Fixes\n\n- **search:** custom clear button, fixed [#271](https://github.com/QingWei-Li/docsify/issues/271) ([864aa18](https://github.com/QingWei-Li/docsify/commit/864aa18))\n- **search:** escape special characters for search, fixed [#369](https://github.com/QingWei-Li/docsify/issues/369) ([9755439](https://github.com/QingWei-Li/docsify/commit/9755439))\n- build config ([342438f](https://github.com/QingWei-Li/docsify/commit/342438f))\n- button style for coverpage, fixed [#362](https://github.com/QingWei-Li/docsify/issues/362) ([85428ef](https://github.com/QingWei-Li/docsify/commit/85428ef))\n- dropdown scroll style, fixed [#346](https://github.com/QingWei-Li/docsify/issues/346) ([c4d83f2](https://github.com/QingWei-Li/docsify/commit/c4d83f2))\n- highlight homepage link, fixed [#304](https://github.com/QingWei-Li/docsify/issues/304) ([f960c19](https://github.com/QingWei-Li/docsify/commit/f960c19))\n- homepage link ([e097f88](https://github.com/QingWei-Li/docsify/commit/e097f88))\n- onlyCover ([033be4f](https://github.com/QingWei-Li/docsify/commit/033be4f))\n- ssr compatible embedd ([ebc10c4](https://github.com/QingWei-Li/docsify/commit/ebc10c4))\n- ssr coverpage, fixed [#273](https://github.com/QingWei-Li/docsify/issues/273) ([9e824a4](https://github.com/QingWei-Li/docsify/commit/9e824a4))\n\n### Features\n\n- click sidebar menu add collapse and expand, close [#294](https://github.com/QingWei-Li/docsify/issues/294) ([5e161a1](https://github.com/QingWei-Li/docsify/commit/5e161a1))\n- **compiler:** support embedded file as code block, close [#134](https://github.com/QingWei-Li/docsify/issues/134) ([761ccc2](https://github.com/QingWei-Li/docsify/commit/761ccc2))\n- **compiler:** support embedded markdown, html, video, etc files, close [#383](https://github.com/QingWei-Li/docsify/issues/383), close [#333](https://github.com/QingWei-Li/docsify/issues/333) ([524f52f](https://github.com/QingWei-Li/docsify/commit/524f52f))\n- **cover:** add onlyCover option, close [#382](https://github.com/QingWei-Li/docsify/issues/382) ([b265fdd](https://github.com/QingWei-Li/docsify/commit/b265fdd))\n- **fetch:** add requestHeaders option, fixed [#336](https://github.com/QingWei-Li/docsify/issues/336) ([54ab4c9](https://github.com/QingWei-Li/docsify/commit/54ab4c9))\n- **render:** add ext option for custom file extension, close [#340](https://github.com/QingWei-Li/docsify/issues/340) ([248aa72](https://github.com/QingWei-Li/docsify/commit/248aa72))\n- **render:** mutilple coverpage, close [#315](https://github.com/QingWei-Li/docsify/issues/315) ([f68ddf5](https://github.com/QingWei-Li/docsify/commit/f68ddf5))\n\n<a name=\"4.5.9\"></a>\n\n## [4.5.9](https://github.com/QingWei-Li/docsify/compare/v4.5.8...v4.5.9) (2018-02-07)\n\n### Bug Fixes\n\n- upgrade marked ([4157173](https://github.com/QingWei-Li/docsify/commit/4157173))\n\n<a name=\"4.5.8\"></a>\n\n## [4.5.8](https://github.com/QingWei-Li/docsify/compare/v4.5.6...v4.5.8) (2018-02-07)\n\n### Bug Fixes\n\n- cover style, fixed [#381](https://github.com/QingWei-Li/docsify/issues/381) ([368754e](https://github.com/QingWei-Li/docsify/commit/368754e))\n- updated deps ([#337](https://github.com/QingWei-Li/docsify/issues/337)) ([a12d393](https://github.com/QingWei-Li/docsify/commit/a12d393))\n\n<a name=\"4.5.7\"></a>\n\n## [4.5.7](https://github.com/QingWei-Li/docsify/compare/v4.5.6...v4.5.7) (2017-12-29)\n\n### Features\n\n- add navigation plugin, closed [#180](https://github.com/QingWei-Li/docsify/issues/180) ([f78be4c](https://github.com/QingWei-Li/docsify/commit/f78be4c))\n\n<a name=\"4.5.6\"></a>\n\n## [4.5.6](https://github.com/QingWei-Li/docsify/compare/v4.5.3...v4.5.6) (2017-12-14)\n\n### Bug Fixes\n\n- **style:** increase the tap targets of menu button, fixed [#325](https://github.com/QingWei-Li/docsify/issues/325) ([888f217](https://github.com/QingWei-Li/docsify/commit/888f217))\n\n<a name=\"4.5.5\"></a>\n\n## [4.5.5](https://github.com/QingWei-Li/docsify/compare/v4.5.4...v4.5.5) (2017-11-30)\n\n### Bug Fixes\n\n- disqus plugin issue ([#318](https://github.com/QingWei-Li/docsify/issues/318)) ([041b33e](https://github.com/QingWei-Li/docsify/commit/041b33e)), closes [#317](https://github.com/QingWei-Li/docsify/issues/317)\n\n<a name=\"4.5.4\"></a>\n\n## [4.5.4](https://github.com/QingWei-Li/docsify/compare/v4.5.2...v4.5.4) (2017-11-29)\n\n### Bug Fixes\n\n- **compiler:** task lists style, fixed [#215](https://github.com/QingWei-Li/docsify/issues/215) ([e43ded4](https://github.com/QingWei-Li/docsify/commit/e43ded4))\n\n### Features\n\n- add gitalk plugin ([#306](https://github.com/QingWei-Li/docsify/issues/306)) ([9208e64](https://github.com/QingWei-Li/docsify/commit/9208e64))\n\n<a name=\"4.5.3\"></a>\n\n## [4.5.3](https://github.com/QingWei-Li/docsify/compare/v4.5.2...v4.5.3) (2017-11-11)\n\n### Features\n\n- add gitalk plugin ([#306](https://github.com/QingWei-Li/docsify/issues/306)) ([9208e64](https://github.com/QingWei-Li/docsify/commit/9208e64))\n\n<a name=\"4.5.2\"></a>\n\n## [4.5.2](https://github.com/QingWei-Li/docsify/compare/v4.5.1...v4.5.2) (2017-11-09)\n\n### Features\n\n- github task lists, close [#215](https://github.com/QingWei-Li/docsify/issues/215) ([#305](https://github.com/QingWei-Li/docsify/issues/305)) ([d486eef](https://github.com/QingWei-Li/docsify/commit/d486eef))\n\n<a name=\"4.5.1\"></a>\n\n## [4.5.1](https://github.com/QingWei-Li/docsify/compare/v4.5.0...v4.5.1) (2017-11-07)\n\n### Features\n\n- fetch files with the query params, fixed [#303](https://github.com/QingWei-Li/docsify/issues/303) ([2a2ed96](https://github.com/QingWei-Li/docsify/commit/2a2ed96))\n\n<a name=\"4.5.0\"></a>\n\n# [4.5.0](https://github.com/QingWei-Li/docsify/compare/v4.4.1...v4.5.0) (2017-11-04)\n\n### Features\n\n- add disqus plugin, closed [#123](https://github.com/QingWei-Li/docsify/issues/123) ([fd7d4e0](https://github.com/QingWei-Li/docsify/commit/fd7d4e0))\n\n<a name=\"4.4.1\"></a>\n\n## [4.4.1](https://github.com/QingWei-Li/docsify/compare/v4.4.0...v4.4.1) (2017-10-31)\n\n### Bug Fixes\n\n- {docsify-ignore-all} and {docsify-ignore} bug ([#299](https://github.com/QingWei-Li/docsify/issues/299)) ([cc98f56](https://github.com/QingWei-Li/docsify/commit/cc98f56))\n- zoom image plugin issue, fixed [#187](https://github.com/QingWei-Li/docsify/issues/187) ([#300](https://github.com/QingWei-Li/docsify/issues/300)) ([fa772cf](https://github.com/QingWei-Li/docsify/commit/fa772cf))\n\n<a name=\"4.4.0\"></a>\n\n# [4.4.0](https://github.com/QingWei-Li/docsify/compare/v4.3.15...v4.4.0) (2017-10-30)\n\n### Bug Fixes\n\n- sidebar style issue on firefox, fixed [#184](https://github.com/QingWei-Li/docsify/issues/184) ([#297](https://github.com/QingWei-Li/docsify/issues/297)) ([36bfc9d](https://github.com/QingWei-Li/docsify/commit/36bfc9d))\n\n### Features\n\n- add helper for disabled link, fixed [#295](https://github.com/QingWei-Li/docsify/issues/295) ([#296](https://github.com/QingWei-Li/docsify/issues/296)) ([4ad96f3](https://github.com/QingWei-Li/docsify/commit/4ad96f3))\n\n<a name=\"4.3.15\"></a>\n\n## [4.3.15](https://github.com/QingWei-Li/docsify/compare/v4.3.14...v4.3.15) (2017-10-20)\n\n### Bug Fixes\n\n- scroll active sidebar ([a2b8eae](https://github.com/QingWei-Li/docsify/commit/a2b8eae))\n\n<a name=\"4.3.14\"></a>\n\n## [4.3.14](https://github.com/QingWei-Li/docsify/compare/v4.3.13...v4.3.14) (2017-10-20)\n\n### Bug Fixes\n\n- codesponsor style ([ab68268](https://github.com/QingWei-Li/docsify/commit/ab68268))\n\n<a name=\"4.3.13\"></a>\n\n## [4.3.13](https://github.com/QingWei-Li/docsify/compare/v4.3.12...v4.3.13) (2017-10-17)\n\n### Bug Fixes\n\n- duplicate results in search fixed [#257](https://github.com/QingWei-Li/docsify/issues/257) ([#284](https://github.com/QingWei-Li/docsify/issues/284)) ([3476f6f](https://github.com/QingWei-Li/docsify/commit/3476f6f))\n\n### Features\n\n- make whole search result clickable ([#285](https://github.com/QingWei-Li/docsify/issues/285)) ([1b91227](https://github.com/QingWei-Li/docsify/commit/1b91227))\n\n<a name=\"4.3.12\"></a>\n\n## [4.3.12](https://github.com/QingWei-Li/docsify/compare/v4.3.11...v4.3.12) (2017-10-15)\n\n### Bug Fixes\n\n- incorrect active link ([#281](https://github.com/QingWei-Li/docsify/issues/281)) ([a3ab379](https://github.com/QingWei-Li/docsify/commit/a3ab379))\n\n<a name=\"4.3.11\"></a>\n\n## [4.3.11](https://github.com/QingWei-Li/docsify/compare/v4.3.10...v4.3.11) (2017-10-15)\n\n### Bug Fixes\n\n- broken links to same page heading, fix [#278](https://github.com/QingWei-Li/docsify/issues/278), fix [#279](https://github.com/QingWei-Li/docsify/issues/279) ([91d6337](https://github.com/QingWei-Li/docsify/commit/91d6337))\n\n<a name=\"4.3.10\"></a>\n\n## [4.3.10](https://github.com/QingWei-Li/docsify/compare/v4.3.9...v4.3.10) (2017-10-12)\n\n### Bug Fixes\n\n- link render issue after page refreshing ([#276](https://github.com/QingWei-Li/docsify/issues/276)) ([abd885e](https://github.com/QingWei-Li/docsify/commit/abd885e))\n\n<a name=\"4.3.9\"></a>\n\n## [4.3.9](https://github.com/QingWei-Li/docsify/compare/v4.3.8...v4.3.9) (2017-10-11)\n\n### Bug Fixes\n\n- scroll issue in IE ([#275](https://github.com/QingWei-Li/docsify/issues/275)) ([3e94cb6](https://github.com/QingWei-Li/docsify/commit/3e94cb6))\n\n<a name=\"4.3.8\"></a>\n\n## [4.3.8](https://github.com/QingWei-Li/docsify/compare/v4.3.7...v4.3.8) (2017-10-07)\n\n### Bug Fixes\n\n- **slugify:** GitHub compatible heading links, fixed [#272](https://github.com/QingWei-Li/docsify/issues/272) ([9b4e666](https://github.com/QingWei-Li/docsify/commit/9b4e666))\n\n<a name=\"4.3.7\"></a>\n\n## [4.3.7](https://github.com/QingWei-Li/docsify/compare/v4.3.6...v4.3.7) (2017-10-02)\n\n### Bug Fixes\n\n- **slugify:** GitHub compatible heading links, fixed [#267](https://github.com/QingWei-Li/docsify/issues/267) ([c195d2d](https://github.com/QingWei-Li/docsify/commit/c195d2d))\n\n<a name=\"4.3.6\"></a>\n\n## [4.3.6](https://github.com/QingWei-Li/docsify/compare/v4.3.5...v4.3.6) (2017-09-21)\n\n### Bug Fixes\n\n- style for codesponsor plugin ([08afec7](https://github.com/QingWei-Li/docsify/commit/08afec7))\n\n<a name=\"4.3.5\"></a>\n\n## [4.3.5](https://github.com/QingWei-Li/docsify/compare/v4.3.4...v4.3.5) (2017-09-20)\n\n### Bug Fixes\n\n- missed symbol ([#254](https://github.com/QingWei-Li/docsify/issues/254)) ([6c702d3](https://github.com/QingWei-Li/docsify/commit/6c702d3))\n\n### Features\n\n- **plugin:** add codesponsor plugin ([46ac4c3](https://github.com/QingWei-Li/docsify/commit/46ac4c3))\n\n<a name=\"4.3.4\"></a>\n\n## [4.3.4](https://github.com/QingWei-Li/docsify/compare/v4.3.3...v4.3.4) (2017-09-07)\n\n### Bug Fixes\n\n- scroll position issue, fixed [#234](https://github.com/QingWei-Li/docsify/issues/234) ([388ed3d](https://github.com/QingWei-Li/docsify/commit/388ed3d))\n\n<a name=\"4.3.3\"></a>\n\n## [4.3.3](https://github.com/QingWei-Li/docsify/compare/v4.3.2...v4.3.3) (2017-09-06)\n\n### Bug Fixes\n\n- **buble.css:** tweaks code block style, fixed [#249](https://github.com/QingWei-Li/docsify/issues/249) ([9d43051](https://github.com/QingWei-Li/docsify/commit/9d43051))\n\n### Features\n\n- add doc for react and vue demo box plugin ([#247](https://github.com/QingWei-Li/docsify/issues/247)) ([f0aca19](https://github.com/QingWei-Li/docsify/commit/f0aca19))\n\n<a name=\"4.3.2\"></a>\n\n## [4.3.2](https://github.com/QingWei-Li/docsify/compare/v4.3.1...v4.3.2) (2017-09-01)\n\n### Bug Fixes\n\n- sidebar highlight ([f82f419](https://github.com/QingWei-Li/docsify/commit/f82f419))\n\n### Features\n\n- add Edit on github plugin (thanks [@njleonzhang](https://github.com/njleonzhang)) ([a0e1ea8](https://github.com/QingWei-Li/docsify/commit/a0e1ea8))\n\n<a name=\"4.3.1\"></a>\n\n## [4.3.1](https://github.com/QingWei-Li/docsify/compare/v4.2.9...v4.3.1) (2017-08-30)\n\n### Features\n\n- **markdown:** supports mermaid [#137](https://github.com/QingWei-Li/docsify/issues/137) ([f4800e0](https://github.com/QingWei-Li/docsify/commit/f4800e0))\n\n<a name=\"4.3.0\"></a>\n\n# [4.3.0](https://github.com/QingWei-Li/docsify/compare/v4.2.9...v4.3.0) (2017-08-17)\n\n### Features\n\n- **markdown:** supports mermaid [#137](https://github.com/QingWei-Li/docsify/issues/137) ([f4800e0](https://github.com/QingWei-Li/docsify/commit/f4800e0))\n\n<a name=\"4.2.9\"></a>\n\n## [4.2.9](https://github.com/QingWei-Li/docsify/compare/v4.2.8...v4.2.9) (2017-08-15)\n\n### Bug Fixes\n\n- ensure document ready before init Docsify [#233](https://github.com/QingWei-Li/docsify/issues/233)\n\n<a name=\"4.2.8\"></a>\n\n## [4.2.8](https://github.com/QingWei-Li/docsify/compare/v4.2.7...v4.2.8) (2017-08-10)\n\n### Features\n\n- **compiler:** support for setting target attribute for link, fixed [#230](https://github.com/QingWei-Li/docsify/issues/230) ([7f270f9](https://github.com/QingWei-Li/docsify/commit/7f270f9))\n\n<a name=\"4.2.7\"></a>\n\n## [4.2.7](https://github.com/QingWei-Li/docsify/compare/v4.2.4...v4.2.7) (2017-08-05)\n\n### Bug Fixes\n\n- **release:** release shell ([628e211](https://github.com/QingWei-Li/docsify/commit/628e211))\n- **style:** nowrap => pre-wrap, fixed [#228](https://github.com/QingWei-Li/docsify/issues/228) ([a88252c](https://github.com/QingWei-Li/docsify/commit/a88252c))\n\n<a name=\"4.2.6\"></a>\n\n## [4.2.6](https://github.com/QingWei-Li/docsify/compare/v4.2.4...v4.2.6) (2017-07-27)\n\n### Bug Fixes\n\n- **css:** hide the nav when the content has not yet been loaded ([1fa1619](https://github.com/QingWei-Li/docsify/commit/1fa1619))\n- **release:** release shell ([628e211](https://github.com/QingWei-Li/docsify/commit/628e211))\n\n<a name=\"4.2.4\"></a>\n\n## [4.2.4](https://github.com/QingWei-Li/docsify/compare/v4.2.2...v4.2.4) (2017-07-26)\n\n### Bug Fixes\n\n- **render:** Remove getRootNode to be compatible with the lower version of Chrome, fixed [#225](https://github.com/QingWei-Li/docsify/issues/225) ([b8dd346](https://github.com/QingWei-Li/docsify/commit/b8dd346))\n\n<a name=\"4.2.3\"></a>\n\n## [4.2.3](https://github.com/QingWei-Li/docsify/compare/v4.2.2...v4.2.3) (2017-07-26)\n\n### Features\n\n- **search:** Supports the max depth of the search headline, fixed [#223](https://github.com/QingWei-Li/docsify/issues/223), resolve [#129](https://github.com/QingWei-Li/docsify/issues/129) ([b7b589b](https://github.com/QingWei-Li/docsify/commit/b7b589b))\n\n<a name=\"4.2.2\"></a>\n\n## [4.2.2](https://github.com/QingWei-Li/docsify/compare/v4.2.1...v4.2.2) (2017-07-24)\n\n### Bug Fixes\n\n- style rerender due to setting themeColor ([17ff3d1](https://github.com/QingWei-Li/docsify/commit/17ff3d1))\n\n<a name=\"4.2.1\"></a>\n\n## [4.2.1](https://github.com/QingWei-Li/docsify/compare/v4.2.0...v4.2.1) (2017-07-19)\n\n- give the navbar some line-height (#216)\n- Remove unnecessary moduleName option from rollup config for plugins (#209)\n\n<a name=\"4.2.0\"></a>\n\n# [4.2.0](https://github.com/QingWei-Li/docsify/compare/v4.1.14...v4.2.0) (2017-07-10)\n\n### Bug Fixes\n\n- not found page ([9af8559](https://github.com/QingWei-Li/docsify/commit/9af8559))\n\n### Features\n\n- alias option supports regexp, resolve [#183](https://github.com/QingWei-Li/docsify/issues/183) ([c4aa22c](https://github.com/QingWei-Li/docsify/commit/c4aa22c))\n- ignore to compiled link, fixed [#203](https://github.com/QingWei-Li/docsify/issues/203) ([#204](https://github.com/QingWei-Li/docsify/issues/204)) ([2e00f4c](https://github.com/QingWei-Li/docsify/commit/2e00f4c))\n\n<a name=\"4.1.14\"></a>\n\n## [4.1.14](https://github.com/QingWei-Li/docsify/compare/v4.1.13...v4.1.14) (2017-06-24)\n\n### Bug Fixes\n\n- get file path ([e8117e5](https://github.com/QingWei-Li/docsify/commit/e8117e5))\n\n### Features\n\n- add context attribute, fixed [#191](https://github.com/QingWei-Li/docsify/issues/191) ([ce0e9ac](https://github.com/QingWei-Li/docsify/commit/ce0e9ac))\n\n<a name=\"4.1.13\"></a>\n\n## [4.1.13](https://github.com/QingWei-Li/docsify/compare/v4.1.12...v4.1.13) (2017-06-11)\n\n<a name=\"4.1.12\"></a>\n\n## [4.1.12](https://github.com/QingWei-Li/docsify/compare/v4.1.11...v4.1.12) (2017-06-03)\n\n### Bug Fixes\n\n- **render:** subtitle in side bar shows undefined, fixed [#182](https://github.com/QingWei-Li/docsify/issues/182) ([d087d57](https://github.com/QingWei-Li/docsify/commit/d087d57))\n\n<a name=\"4.1.11\"></a>\n\n## [4.1.11](https://github.com/QingWei-Li/docsify/compare/v4.1.10...v4.1.11) (2017-06-02)\n\n### Bug Fixes\n\n- **compiler:** force reset toc when rendering sidebar fixed [#181](https://github.com/QingWei-Li/docsify/issues/181) ([ccf4c7c](https://github.com/QingWei-Li/docsify/commit/ccf4c7c))\n- **render:** autoHeader does not work ([1304d2e](https://github.com/QingWei-Li/docsify/commit/1304d2e))\n\n<a name=\"4.1.10\"></a>\n\n## [4.1.10](https://github.com/QingWei-Li/docsify/compare/v4.1.9...v4.1.10) (2017-06-02)\n\n### Bug Fixes\n\n- **hash:** hash routing crashes when url has querystring ([6d48ce1](https://github.com/QingWei-Li/docsify/commit/6d48ce1))\n\n<a name=\"4.1.9\"></a>\n\n## [4.1.9](https://github.com/QingWei-Li/docsify/compare/v4.1.8...v4.1.9) (2017-05-31)\n\n### Bug Fixes\n\n- can't render toc on first load ([d9b487e](https://github.com/QingWei-Li/docsify/commit/d9b487e))\n- **lifecycle:** continue to handle data ([955d3d5](https://github.com/QingWei-Li/docsify/commit/955d3d5))\n- **render:** broken name link, fixed [#167](https://github.com/QingWei-Li/docsify/issues/167) ([91b66a5](https://github.com/QingWei-Li/docsify/commit/91b66a5))\n\n<a name=\"4.1.8\"></a>\n\n## [4.1.8](https://github.com/QingWei-Li/docsify/compare/v4.1.7...v4.1.8) (2017-05-31)\n\n### Bug Fixes\n\n- auto replace version ([22b50f0](https://github.com/QingWei-Li/docsify/commit/22b50f0))\n- update edit button demo ([ec887c1](https://github.com/QingWei-Li/docsify/commit/ec887c1))\n\n### Features\n\n- add edit button demo ([a64cee1](https://github.com/QingWei-Li/docsify/commit/a64cee1))\n- add edit button demo, close [#162](https://github.com/QingWei-Li/docsify/issues/162) ([036fdac](https://github.com/QingWei-Li/docsify/commit/036fdac))\n\n<a name=\"4.1.7\"></a>\n\n## [4.1.7](https://github.com/QingWei-Li/docsify/compare/v4.1.6...v4.1.7) (2017-05-30)\n\n### Bug Fixes\n\n- **ssr:** clean files ([0014895](https://github.com/QingWei-Li/docsify/commit/0014895))\n\n<a name=\"4.1.6\"></a>\n\n## [4.1.6](https://github.com/QingWei-Li/docsify/compare/v4.1.5...v4.1.6) (2017-05-30)\n\n### Bug Fixes\n\n- **ssr:** add debug ([6b9e092](https://github.com/QingWei-Li/docsify/commit/6b9e092))\n\n<a name=\"4.1.5\"></a>\n\n## [4.1.5](https://github.com/QingWei-Li/docsify/compare/v4.1.4...v4.1.5) (2017-05-30)\n\n### Bug Fixes\n\n- **ssr:** missing package ([6db8c9e](https://github.com/QingWei-Li/docsify/commit/6db8c9e))\n\n<a name=\"4.1.4\"></a>\n\n## [4.1.4](https://github.com/QingWei-Li/docsify/compare/v4.1.3...v4.1.4) (2017-05-30)\n\n### Bug Fixes\n\n- **ssr:** file path ([79a83bc](https://github.com/QingWei-Li/docsify/commit/79a83bc))\n\n<a name=\"4.1.3\"></a>\n\n## [4.1.3](https://github.com/QingWei-Li/docsify/compare/v4.1.2...v4.1.3) (2017-05-30)\n\n### Bug Fixes\n\n- update babel config ([9825db4](https://github.com/QingWei-Li/docsify/commit/9825db4))\n\n<a name=\"4.1.2\"></a>\n\n## [4.1.2](https://github.com/QingWei-Li/docsify/compare/v4.1.1...v4.1.2) (2017-05-30)\n\n### Bug Fixes\n\n- update babel config ([80dba19](https://github.com/QingWei-Li/docsify/commit/80dba19))\n\n<a name=\"4.1.1\"></a>\n\n## [4.1.1](https://github.com/QingWei-Li/docsify/compare/v4.1.0...v4.1.1) (2017-05-30)\n\n### Bug Fixes\n\n- build for ssr package ([4cb20a5](https://github.com/QingWei-Li/docsify/commit/4cb20a5))\n- remove history mode ([0e74e6c](https://github.com/QingWei-Li/docsify/commit/0e74e6c))\n\n<a name=\"4.1.0\"></a>\n\n# [4.1.0](https://github.com/QingWei-Li/docsify/compare/v4.0.2...v4.1.0) (2017-05-30)\n\n<a name=\"4.0.2\"></a>\n\n## [4.0.2](https://github.com/QingWei-Li/docsify/compare/v4.0.1...v4.0.2) (2017-05-30)\n\n### Bug Fixes\n\n- basePath for history mode ([fc1cd3f](https://github.com/QingWei-Li/docsify/commit/fc1cd3f))\n\n<a name=\"4.0.1\"></a>\n\n## [4.0.1](https://github.com/QingWei-Li/docsify/compare/v4.0.0...v4.0.1) (2017-05-29)\n\n### Bug Fixes\n\n- **ssr:** remove context ([4626157](https://github.com/QingWei-Li/docsify/commit/4626157))\n- lint ([b764b6e](https://github.com/QingWei-Li/docsify/commit/b764b6e))\n\n<a name=\"4.0.0\"></a>\n\n# [4.0.0](https://github.com/QingWei-Li/docsify/compare/v3.7.3...v4.0.0) (2017-05-29)\n\n### Bug Fixes\n\n- **render:** init event in ssr ([eba1c98](https://github.com/QingWei-Li/docsify/commit/eba1c98))\n- lint ([1f4514d](https://github.com/QingWei-Li/docsify/commit/1f4514d))\n\n### Features\n\n- finish ssr ([3444884](https://github.com/QingWei-Li/docsify/commit/3444884))\n- init docsify-server-renderer ([6dea685](https://github.com/QingWei-Li/docsify/commit/6dea685))\n- support history mode ([f095eb8](https://github.com/QingWei-Li/docsify/commit/f095eb8))\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contribute\n\n## Introduction\n\nFirst, thank you for considering contributing to docsify! It's people like you that make the open source community such a great community! 😊\n\nWe welcome any type of contribution, not only code. You can help with\n\n- **QA**: file bug reports, the more details you can give the better (e.g. screenshots with the console open)\n- **Marketing**: writing blog posts, howto's, printing stickers, ...\n- **Community**: presenting the project at meetups, organizing a dedicated meetup for the local community, ...\n- **Code**: take a look at the [open issues](https://github.com/docsifyjs/docsify/issues). Even if you can't write code, commenting on them, showing that you care about a given issue matters. It helps us triage them.\n- **Money**: we welcome financial contributions in full transparency on our [open collective](https://opencollective.com/docsify).\n\n## Your First Contribution\n\nWorking on your first Pull Request ever? You can learn how from this _free_ series, [How to Contribute to an Open Source Project on GitHub](https://app.egghead.io/playlists/how-to-contribute-to-an-open-source-project-on-github).\n\n## Online one-click setup for Contributing\n\nYou can use Gitpod (a free online VS Code-like IDE) for contributing. With a single click it'll launch a workspace and automatically:\n\n- clone the docsify repo.\n- install the dependencies.\n- start `npm run dev`.\n\n```bash\nnpm install && npm run dev\n```\n\nSo that you can start straight away.\n\n[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/docsifyjs/docsify)\n\n- Fork it!\n- Create your feature branch: `git checkout -b my-new-feature`\n- Commit your changes: `git add . && git commit -m 'Add some feature'`\n- Push to the branch: `git push origin my-new-feature`\n- Submit a pull request\n\n## Submitting code\n\nAny code change should be submitted as a pull request. The description should explain what the code does and give steps to execute it. The pull request should also contain tests.\n\n## Testing\n\nEnsure that things work by running:\n\n```sh\nnpm test\n```\n\n## Test Snapshots\n\nIf a snapshot fails, or to add new snapshots, run:\n\n```sh\nnpx jest --updateSnapshot\n```\n\n## Code review process\n\nThe bigger the pull request, the longer it will take to review and merge. Try to break down large pull requests in smaller chunks that are easier to review and merge.\nIt is also always helpful to have some context for your pull request. What was the purpose? Why does it matter to you?\n\n## Financial contributions\n\nWe also welcome financial contributions in full transparency on our [open collective](https://opencollective.com/docsify).\nAnyone can file an expense. If the expense makes sense for the development of the community, it will be \"merged\" in the ledger of our open collective by the core contributors and the person who filed the expense will be reimbursed.\n\n## Questions\n\nIf you have any questions, create an [issue](https://github.com/docsifyjs/docsify/issues) (protip: do a quick search first to see if someone else didn't ask the same question before!).\nYou can also reach us at hello@docsify.opencollective.com.\n\n## Credits\n\n### Contributors\n\nThank you to all the people who have already contributed to docsify!\n<a href=\"graphs/contributors\"><img src=\"https://opencollective.com/docsify/contributors.svg?width=890\" /></a>\n\n### Backers\n\nThank you to all our backers! [[Become a backer](https://opencollective.com/docsify#backer)]\n\n<a href=\"https://opencollective.com/docsify#backers\" target=\"_blank\"><img src=\"https://opencollective.com/docsify/backers.svg?width=890\"></a>\n\n### Sponsors\n\nThank you to all our sponsors! (please ask your company to also support this open source project by [becoming a sponsor](https://opencollective.com/docsify#sponsor))\n\n<a href=\"https://opencollective.com/docsify/sponsor/0/website\" target=\"_blank\"><img src=\"https://opencollective.com/docsify/sponsor/0/avatar.svg\"></a>\n<a href=\"https://opencollective.com/docsify/sponsor/1/website\" target=\"_blank\"><img src=\"https://opencollective.com/docsify/sponsor/1/avatar.svg\"></a>\n<a href=\"https://opencollective.com/docsify/sponsor/2/website\" target=\"_blank\"><img src=\"https://opencollective.com/docsify/sponsor/2/avatar.svg\"></a>\n<a href=\"https://opencollective.com/docsify/sponsor/3/website\" target=\"_blank\"><img src=\"https://opencollective.com/docsify/sponsor/3/avatar.svg\"></a>\n<a href=\"https://opencollective.com/docsify/sponsor/4/website\" target=\"_blank\"><img src=\"https://opencollective.com/docsify/sponsor/4/avatar.svg\"></a>\n<a href=\"https://opencollective.com/docsify/sponsor/5/website\" target=\"_blank\"><img src=\"https://opencollective.com/docsify/sponsor/5/avatar.svg\"></a>\n<a href=\"https://opencollective.com/docsify/sponsor/6/website\" target=\"_blank\"><img src=\"https://opencollective.com/docsify/sponsor/6/avatar.svg\"></a>\n<a href=\"https://opencollective.com/docsify/sponsor/7/website\" target=\"_blank\"><img src=\"https://opencollective.com/docsify/sponsor/7/avatar.svg\"></a>\n<a href=\"https://opencollective.com/docsify/sponsor/8/website\" target=\"_blank\"><img src=\"https://opencollective.com/docsify/sponsor/8/avatar.svg\"></a>\n<a href=\"https://opencollective.com/docsify/sponsor/9/website\" target=\"_blank\"><img src=\"https://opencollective.com/docsify/sponsor/9/avatar.svg\"></a>\n\n<!-- This `CONTRIBUTING.md` is based on @nayafia's template https://github.com/nayafia/contributing-template -->\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM mcr.microsoft.com/playwright:focal\nWORKDIR /app\nCOPY . .\nRUN rm package-lock.json\nRUN npm install\nRUN npx playwright install  \nRUN npm run build\nENTRYPOINT [\"npm\", \"run\"]\nCMD [\"test\"]"
  },
  {
    "path": "HISTORY.md",
    "content": "<!-- Historical history file (do not edit). -->\n\n<a name=\"3.7.3\"></a>\n## [3.7.3](https://github.com/QingWei-Li/docsify/compare/v3.7.2...v3.7.3) (2017-05-22)\n\n\n### Bug Fixes\n\n* **render:** find => filter ([eca3368](https://github.com/QingWei-Li/docsify/commit/eca3368))\n\n\n\n<a name=\"3.7.2\"></a>\n## [3.7.2](https://github.com/QingWei-Li/docsify/compare/v3.7.1...v3.7.2) (2017-05-19)\n\n\n\n<a name=\"3.7.1\"></a>\n## [3.7.1](https://github.com/QingWei-Li/docsify/compare/v3.7.0...v3.7.1) (2017-05-19)\n\n\n### Bug Fixes\n\n* docsify-updated is undefined ([b2b4742](https://github.com/QingWei-Li/docsify/commit/b2b4742))\n\n\n\n<a name=\"3.7.0\"></a>\n# [3.7.0](https://github.com/QingWei-Li/docsify/compare/v3.6.6...v3.7.0) (2017-05-16)\n\n\n### Features\n\n* add docsify-updated, close [#158](https://github.com/QingWei-Li/docsify/issues/158) ([d2be5ae](https://github.com/QingWei-Li/docsify/commit/d2be5ae))\n* add externalLinkTarget, close [#149](https://github.com/QingWei-Li/docsify/issues/149) ([2d73285](https://github.com/QingWei-Li/docsify/commit/2d73285))\n\n\n\n<a name=\"3.6.6\"></a>\n## [3.6.6](https://github.com/QingWei-Li/docsify/compare/v3.6.5...v3.6.6) (2017-05-06)\n\n\n### Features\n\n* support query string for the search, fixed [#156](https://github.com/QingWei-Li/docsify/issues/156) ([da75d70](https://github.com/QingWei-Li/docsify/commit/da75d70))\n\n\n\n<a name=\"3.6.5\"></a>\n## [3.6.5](https://github.com/QingWei-Li/docsify/compare/v3.6.4...v3.6.5) (2017-04-28)\n\n\n### Bug Fixes\n\n* **util:** fix crash, fixed [#154](https://github.com/QingWei-Li/docsify/issues/154) ([51832d3](https://github.com/QingWei-Li/docsify/commit/51832d3))\n\n\n\n<a name=\"3.6.4\"></a>\n## [3.6.4](https://github.com/QingWei-Li/docsify/compare/v3.6.3...v3.6.4) (2017-04-28)\n\n\n### Bug Fixes\n\n* **util:** correctly clean up duplicate slashes, fixed [#153](https://github.com/QingWei-Li/docsify/issues/153) ([76c041a](https://github.com/QingWei-Li/docsify/commit/76c041a))\n\n\n\n<a name=\"3.6.3\"></a>\n## [3.6.3](https://github.com/QingWei-Li/docsify/compare/v3.6.2...v3.6.3) (2017-04-25)\n\n\n### Bug Fixes\n\n* **external-script:** script attrs ([2653849](https://github.com/QingWei-Li/docsify/commit/2653849))\n\n\n\n<a name=\"3.6.2\"></a>\n## [3.6.2](https://github.com/QingWei-Li/docsify/compare/v3.6.0...v3.6.2) (2017-04-12)\n\n\n### Features\n\n* **event:** Collapse the sidebar when click outside element in the small screen ([9b7e5f5](https://github.com/QingWei-Li/docsify/commit/9b7e5f5))\n* **external-script:** detect more than one script dom, fixed [#146](https://github.com/QingWei-Li/docsify/issues/146) ([94d6603](https://github.com/QingWei-Li/docsify/commit/94d6603))\n\n\n\n<a name=\"3.6.0\"></a>\n# [3.6.0](https://github.com/QingWei-Li/docsify/compare/v3.5.2...v3.6.0) (2017-04-09)\n\n\n### Features\n\n* **render:** add mergeNavbar option, close [#125](https://github.com/QingWei-Li/docsify/issues/125), [#124](https://github.com/QingWei-Li/docsify/issues/124) ([#145](https://github.com/QingWei-Li/docsify/issues/145)) ([9220523](https://github.com/QingWei-Li/docsify/commit/9220523))\n\n\n\n<a name=\"3.5.2\"></a>\n## [3.5.2](https://github.com/QingWei-Li/docsify/compare/v3.5.1...v3.5.2) (2017-04-05)\n\n\n\n<a name=\"3.5.1\"></a>\n## [3.5.1](https://github.com/QingWei-Li/docsify/compare/v3.5.0...v3.5.1) (2017-03-25)\n\n\n### Bug Fixes\n\n* .md file extension regex ([594299f](https://github.com/QingWei-Li/docsify/commit/594299f))\n\n\n\n<a name=\"3.5.0\"></a>\n# [3.5.0](https://github.com/QingWei-Li/docsify/compare/v3.4.4...v3.5.0) (2017-03-25)\n\n\n### Bug Fixes\n\n* adjust display on small screens ([bf35471](https://github.com/QingWei-Li/docsify/commit/bf35471))\n* navbar labels for German ([b022aaf](https://github.com/QingWei-Li/docsify/commit/b022aaf))\n\n\n### Features\n\n* **route:** auto remove .md extension ([8f11653](https://github.com/QingWei-Li/docsify/commit/8f11653))\n\n\n\n<a name=\"3.4.4\"></a>\n## [3.4.4](https://github.com/QingWei-Li/docsify/compare/v3.4.3...v3.4.4) (2017-03-17)\n\n\n### Bug Fixes\n\n* **search:** fix input style ([2d6a51b](https://github.com/QingWei-Li/docsify/commit/2d6a51b))\n\n\n\n<a name=\"3.4.3\"></a>\n## [3.4.3](https://github.com/QingWei-Li/docsify/compare/v3.4.2...v3.4.3) (2017-03-16)\n\n\n\n<a name=\"3.4.2\"></a>\n## [3.4.2](https://github.com/QingWei-Li/docsify/compare/v3.4.1...v3.4.2) (2017-03-11)\n\n\n### Features\n\n* **emojify:** add no-emoji option ([3aef37a](https://github.com/QingWei-Li/docsify/commit/3aef37a))\n\n\n\n<a name=\"3.4.1\"></a>\n## [3.4.1](https://github.com/QingWei-Li/docsify/compare/v3.4.0...v3.4.1) (2017-03-10)\n\n\n### Bug Fixes\n\n* **dom:** Disable the dom cache when vue is present, fixed [#119](https://github.com/QingWei-Li/docsify/issues/119) ([b9a7275](https://github.com/QingWei-Li/docsify/commit/b9a7275))\n\n\n\n<a name=\"3.4.0\"></a>\n# [3.4.0](https://github.com/QingWei-Li/docsify/compare/v3.3.0...v3.4.0) (2017-03-09)\n\n\n### Features\n\n* **zoom-image:** add plugin ([50fa6fc](https://github.com/QingWei-Li/docsify/commit/50fa6fc))\n\n\n\n<a name=\"3.3.0\"></a>\n# [3.3.0](https://github.com/QingWei-Li/docsify/compare/v3.2.0...v3.3.0) (2017-03-07)\n\n\n\n<a name=\"3.2.0\"></a>\n# [3.2.0](https://github.com/QingWei-Li/docsify/compare/v3.1.2...v3.2.0) (2017-02-28)\n\n\n### Bug Fixes\n\n* **fetch:** load sidebar and navbar for parent path, fixed [#100](https://github.com/QingWei-Li/docsify/issues/100) ([f3fc596](https://github.com/QingWei-Li/docsify/commit/f3fc596))\n* **render:** Toc rendering error, fixed [#106](https://github.com/QingWei-Li/docsify/issues/106) ([0d59ee9](https://github.com/QingWei-Li/docsify/commit/0d59ee9))\n\n\n### Features\n\n* **search:** Localization for no data tip, close [#103](https://github.com/QingWei-Li/docsify/issues/103) ([d3c9fbd](https://github.com/QingWei-Li/docsify/commit/d3c9fbd))\n\n\n\n<a name=\"3.1.2\"></a>\n## [3.1.2](https://github.com/QingWei-Li/docsify/compare/v3.1.1...v3.1.2) (2017-02-27)\n\n\n\n<a name=\"3.1.1\"></a>\n## [3.1.1](https://github.com/QingWei-Li/docsify/compare/v3.1.0...v3.1.1) (2017-02-24)\n\n\n### Bug Fixes\n\n* **render:** custom cover background image ([8f9bf29](https://github.com/QingWei-Li/docsify/commit/8f9bf29))\n* **search:** don't search nameLink, fixed [#102](https://github.com/QingWei-Li/docsify/issues/102) ([507d9e8](https://github.com/QingWei-Li/docsify/commit/507d9e8))\n* **tpl:** extra character, fixed [#101](https://github.com/QingWei-Li/docsify/issues/101) ([d67d25f](https://github.com/QingWei-Li/docsify/commit/d67d25f))\n\n\n\n<a name=\"3.1.0\"></a>\n# [3.1.0](https://github.com/QingWei-Li/docsify/compare/v3.0.5...v3.1.0) (2017-02-22)\n\n\n### Bug Fixes\n\n* **search:** incorrect anchor link, fixed [#90](https://github.com/QingWei-Li/docsify/issues/90) ([b8a3d8f](https://github.com/QingWei-Li/docsify/commit/b8a3d8f))\n* **sw:** update white list ([f2975a5](https://github.com/QingWei-Li/docsify/commit/f2975a5))\n\n\n### Features\n\n* **emoji:** add emoji plugin ([855c450](https://github.com/QingWei-Li/docsify/commit/855c450))\n\n\n\n<a name=\"3.0.5\"></a>\n## [3.0.5](https://github.com/QingWei-Li/docsify/compare/v3.0.4...v3.0.5) (2017-02-21)\n\n\n### Bug Fixes\n\n* **event:** highlight sidebar when clicked, fixed [#86](https://github.com/QingWei-Li/docsify/issues/86) ([2a1157a](https://github.com/QingWei-Li/docsify/commit/2a1157a))\n* **gen-tree:** cache toc list, fixed [#88](https://github.com/QingWei-Li/docsify/issues/88) ([3394ebb](https://github.com/QingWei-Li/docsify/commit/3394ebb))\n* **layout.css:** loading style ([42b2dba](https://github.com/QingWei-Li/docsify/commit/42b2dba))\n\n\n### Features\n\n* **pwa:** add sw.js ([f7111b5](https://github.com/QingWei-Li/docsify/commit/f7111b5))\n\n\n\n<a name=\"3.0.4\"></a>\n## [3.0.4](https://github.com/QingWei-Li/docsify/compare/v3.0.3...v3.0.4) (2017-02-20)\n\n\n### Bug Fixes\n\n* **render:** disable rendering sub list when loadSidebar is false ([35dd2e1](https://github.com/QingWei-Li/docsify/commit/35dd2e1))\n* **render:** execute script ([780c1e5](https://github.com/QingWei-Li/docsify/commit/780c1e5))\n\n\n\n<a name=\"3.0.3\"></a>\n## [3.0.3](https://github.com/QingWei-Li/docsify/compare/v3.0.2...v3.0.3) (2017-02-19)\n\n\n\n<a name=\"3.0.2\"></a>\n## [3.0.2](https://github.com/QingWei-Li/docsify/compare/v3.0.1...v3.0.2) (2017-02-19)\n\n\n### Bug Fixes\n\n* **compiler:** link ([3b127a1](https://github.com/QingWei-Li/docsify/commit/3b127a1))\n* **search:** add lazy input ([bf593a7](https://github.com/QingWei-Li/docsify/commit/bf593a7))\n\n\n\n<a name=\"3.0.1\"></a>\n## [3.0.1](https://github.com/QingWei-Li/docsify/compare/v3.0.0...v3.0.1) (2017-02-19)\n\n\n### Bug Fixes\n\n* **route:** empty alias ([cd99b52](https://github.com/QingWei-Li/docsify/commit/cd99b52))\n\n\n\n<a name=\"3.0.0\"></a>\n# [3.0.0](https://github.com/QingWei-Li/docsify/compare/v2.4.3...v3.0.0) (2017-02-19)\n\n\n### Bug Fixes\n\n* **compiler:** link ([c7e09c3](https://github.com/QingWei-Li/docsify/commit/c7e09c3))\n* **render:** support html file ([7b6a2ac](https://github.com/QingWei-Li/docsify/commit/7b6a2ac))\n* **search:** escape html ([fcb66e8](https://github.com/QingWei-Li/docsify/commit/fcb66e8))\n* **search:** fix default config ([2efd859](https://github.com/QingWei-Li/docsify/commit/2efd859))\n\n\n### Features\n\n* **front-matter:** add front matter[WIP] ([dbb9278](https://github.com/QingWei-Li/docsify/commit/dbb9278))\n* **render:** add auto header ([b7768b1](https://github.com/QingWei-Li/docsify/commit/b7768b1))\n* **search:** Localization for search placeholder, close [#80](https://github.com/QingWei-Li/docsify/issues/80) ([2351c3e](https://github.com/QingWei-Li/docsify/commit/2351c3e))\n* **themes:** add loading info ([86594a3](https://github.com/QingWei-Li/docsify/commit/86594a3))\n\n\n\n<a name=\"2.4.3\"></a>\n## [2.4.3](https://github.com/QingWei-Li/docsify/compare/v2.4.2...v2.4.3) (2017-02-15)\n\n\n\n<a name=\"2.4.2\"></a>\n## [2.4.2](https://github.com/QingWei-Li/docsify/compare/v2.4.1...v2.4.2) (2017-02-14)\n\n\n### Bug Fixes\n\n* **index:** load file path error ([dc536a3](https://github.com/QingWei-Li/docsify/commit/dc536a3))\n\n\n\n<a name=\"2.4.1\"></a>\n## [2.4.1](https://github.com/QingWei-Li/docsify/compare/v2.4.0...v2.4.1) (2017-02-13)\n\n\n### Bug Fixes\n\n* **index:** cover page ([dd0c84b](https://github.com/QingWei-Li/docsify/commit/dd0c84b))\n\n\n\n<a name=\"2.4.0\"></a>\n# [2.4.0](https://github.com/QingWei-Li/docsify/compare/v2.3.0...v2.4.0) (2017-02-13)\n\n\n### Features\n\n* **hook:** add doneEach ([c6f7602](https://github.com/QingWei-Li/docsify/commit/c6f7602))\n\n\n\n<a name=\"2.3.0\"></a>\n# [2.3.0](https://github.com/QingWei-Li/docsify/compare/v2.2.1...v2.3.0) (2017-02-13)\n\n\n### Bug Fixes\n\n* **event:**  has no effect on a FF mobile browser, fixed [#67](https://github.com/QingWei-Li/docsify/issues/67) ([0ff36c2](https://github.com/QingWei-Li/docsify/commit/0ff36c2))\n* **render:** custom marked renderer ([bf559b4](https://github.com/QingWei-Li/docsify/commit/bf559b4))\n* **render:** fix render link ([a866744](https://github.com/QingWei-Li/docsify/commit/a866744))\n* **render:** image url ([6f87529](https://github.com/QingWei-Li/docsify/commit/6f87529))\n* **render:** render link ([38ea660](https://github.com/QingWei-Li/docsify/commit/38ea660))\n* **src:** fix route ([324301a](https://github.com/QingWei-Li/docsify/commit/324301a))\n* **src:** get alias ([784173e](https://github.com/QingWei-Li/docsify/commit/784173e))\n* **src:** get alias ([ce99a04](https://github.com/QingWei-Li/docsify/commit/ce99a04))\n* **themes:** fix navbar style ([fa54b52](https://github.com/QingWei-Li/docsify/commit/fa54b52))\n* **themes:** update navbar style ([4864d1b](https://github.com/QingWei-Li/docsify/commit/4864d1b))\n\n\n### Features\n\n* **hook:** support custom plugin ([9e81a59](https://github.com/QingWei-Li/docsify/commit/9e81a59))\n* **src:** add alias feature ([24412cd](https://github.com/QingWei-Li/docsify/commit/24412cd))\n* **src:** dynamic title and fix sidebar style ([6b30eb6](https://github.com/QingWei-Li/docsify/commit/6b30eb6))\n\n\n\n<a name=\"2.2.1\"></a>\n## [2.2.1](https://github.com/QingWei-Li/docsify/compare/v2.2.0...v2.2.1) (2017-02-11)\n\n\n### Bug Fixes\n\n* **event:** scroll active sidebar ([50f5fc2](https://github.com/QingWei-Li/docsify/commit/50f5fc2))\n* **search:** crash when not content, fixed [#68](https://github.com/QingWei-Li/docsify/issues/68) ([9d3cc89](https://github.com/QingWei-Li/docsify/commit/9d3cc89))\n* **search:** not work in mobile ([3941304](https://github.com/QingWei-Li/docsify/commit/3941304))\n\n\n\n<a name=\"2.2.0\"></a>\n# [2.2.0](https://github.com/QingWei-Li/docsify/compare/v2.1.0...v2.2.0) (2017-02-09)\n\n\n### Features\n\n* **plugins:** add Google Analytics plugin ([#66](https://github.com/QingWei-Li/docsify/issues/66)) ([ac61bb0](https://github.com/QingWei-Li/docsify/commit/ac61bb0))\n\n\n\n<a name=\"2.1.0\"></a>\n# [2.1.0](https://github.com/QingWei-Li/docsify/compare/v2.0.3...v2.1.0) (2017-02-09)\n\n\n### Bug Fixes\n\n* render name ([12e2479](https://github.com/QingWei-Li/docsify/commit/12e2479))\n* **vue.css:** update sidebar style ([fc140ef](https://github.com/QingWei-Li/docsify/commit/fc140ef))\n\n\n### Features\n\n* add search, close [#43](https://github.com/QingWei-Li/docsify/issues/43) ([eb5ff3e](https://github.com/QingWei-Li/docsify/commit/eb5ff3e))\n\n\n\n<a name=\"2.0.3\"></a>\n## [2.0.3](https://github.com/QingWei-Li/docsify/compare/v2.0.2...v2.0.3) (2017-02-07)\n\n\n### Bug Fixes\n\n* css var polyfill ([8cd386a](https://github.com/QingWei-Li/docsify/commit/8cd386a))\n* css var polyfill ([cbaee21](https://github.com/QingWei-Li/docsify/commit/cbaee21))\n* rendering emojis ([8c7e4d7](https://github.com/QingWei-Li/docsify/commit/8c7e4d7))\n\n\n\n<a name=\"2.0.2\"></a>\n## [2.0.2](https://github.com/QingWei-Li/docsify/compare/v2.0.1...v2.0.2) (2017-02-05)\n\n\n### Bug Fixes\n\n* button style in cover page ([4470855](https://github.com/QingWei-Li/docsify/commit/4470855))\n\n\n\n<a name=\"2.0.1\"></a>\n## [2.0.1](https://github.com/QingWei-Li/docsify/compare/v2.0.0...v2.0.1) (2017-02-05)\n\n\n\n<a name=\"2.0.0\"></a>\n# [2.0.0](https://github.com/QingWei-Li/docsify/compare/v1.10.5...v2.0.0) (2017-02-05)\n\n\n### Features\n\n* customize the theme color ([5cc9f05](https://github.com/QingWei-Li/docsify/commit/5cc9f05))\n\n\n\n<a name=\"1.10.5\"></a>\n## [1.10.5](https://github.com/QingWei-Li/docsify/compare/v1.10.4...v1.10.5) (2017-01-28)\n\n\n\n<a name=\"1.10.4\"></a>\n## [1.10.4](https://github.com/QingWei-Li/docsify/compare/v1.10.3...v1.10.4) (2017-01-27)\n\n\n\n<a name=\"1.10.3\"></a>\n## [1.10.3](https://github.com/QingWei-Li/docsify/compare/v1.10.2...v1.10.3) (2017-01-27)\n\n\n\n<a name=\"1.10.2\"></a>\n## [1.10.2](https://github.com/QingWei-Li/docsify/compare/v1.10.1...v1.10.2) (2017-01-25)\n\n\n\n<a name=\"1.10.1\"></a>\n## [1.10.1](https://github.com/QingWei-Li/docsify/compare/v1.10.0...v1.10.1) (2017-01-25)\n\n\n\n<a name=\"1.10.0\"></a>\n# [1.10.0](https://github.com/QingWei-Li/docsify/compare/v1.9.0...v1.10.0) (2017-01-25)\n\n\n\n<a name=\"1.9.0\"></a>\n# [1.9.0](https://github.com/QingWei-Li/docsify/compare/v1.8.0...v1.9.0) (2017-01-24)\n\n\n\n<a name=\"1.8.0\"></a>\n# [1.8.0](https://github.com/QingWei-Li/docsify/compare/v1.7.4...v1.8.0) (2017-01-24)\n\n\n\n<a name=\"1.7.4\"></a>\n## [1.7.4](https://github.com/QingWei-Li/docsify/compare/v1.7.3...v1.7.4) (2017-01-13)\n\n\n\n<a name=\"1.7.3\"></a>\n## [1.7.3](https://github.com/QingWei-Li/docsify/compare/v1.7.2...v1.7.3) (2017-01-13)\n\n\n\n<a name=\"1.7.2\"></a>\n## [1.7.2](https://github.com/QingWei-Li/docsify/compare/v1.7.1...v1.7.2) (2017-01-12)\n\n\n\n<a name=\"1.7.1\"></a>\n## [1.7.1](https://github.com/QingWei-Li/docsify/compare/v1.7.0...v1.7.1) (2017-01-12)\n\n\n\n<a name=\"1.7.0\"></a>\n# [1.7.0](https://github.com/QingWei-Li/docsify/compare/v1.6.1...v1.7.0) (2017-01-12)\n\n\n\n<a name=\"1.6.1\"></a>\n## [1.6.1](https://github.com/QingWei-Li/docsify/compare/v1.6.0...v1.6.1) (2017-01-10)\n\n\n\n<a name=\"1.6.0\"></a>\n# [1.6.0](https://github.com/QingWei-Li/docsify/compare/v1.5.2...v1.6.0) (2017-01-10)\n\n\n\n<a name=\"1.5.2\"></a>\n## [1.5.2](https://github.com/QingWei-Li/docsify/compare/v1.5.1...v1.5.2) (2017-01-10)\n\n\n\n<a name=\"1.5.1\"></a>\n## [1.5.1](https://github.com/QingWei-Li/docsify/compare/v1.5.0...v1.5.1) (2017-01-09)\n\n\n\n<a name=\"1.5.0\"></a>\n# [1.5.0](https://github.com/QingWei-Li/docsify/compare/v1.4.3...v1.5.0) (2017-01-04)\n\n\n### Features\n\n* Markdown parser is configurable, [#42](https://github.com/QingWei-Li/docsify/issues/42) ([8b1000a](https://github.com/QingWei-Li/docsify/commit/8b1000a))\n\n\n\n<a name=\"1.4.3\"></a>\n## [1.4.3](https://github.com/QingWei-Li/docsify/compare/v1.4.2...v1.4.3) (2017-01-01)\n\n\n\n<a name=\"1.4.2\"></a>\n## [1.4.2](https://github.com/QingWei-Li/docsify/compare/v1.4.1...v1.4.2) (2016-12-31)\n\n\n\n<a name=\"1.4.1\"></a>\n## [1.4.1](https://github.com/QingWei-Li/docsify/compare/v1.4.0...v1.4.1) (2016-12-31)\n\n\n\n<a name=\"1.4.0\"></a>\n# [1.4.0](https://github.com/QingWei-Li/docsify/compare/v1.3.5...v1.4.0) (2016-12-31)\n\n\n\n<a name=\"1.3.5\"></a>\n## [1.3.5](https://github.com/QingWei-Li/docsify/compare/v1.3.4...v1.3.5) (2016-12-25)\n\n\n\n<a name=\"1.3.4\"></a>\n## [1.3.4](https://github.com/QingWei-Li/docsify/compare/v1.3.3...v1.3.4) (2016-12-25)\n\n\n\n<a name=\"1.3.3\"></a>\n## [1.3.3](https://github.com/QingWei-Li/docsify/compare/v1.3.2...v1.3.3) (2016-12-23)\n\n\n\n<a name=\"1.3.2\"></a>\n## [1.3.2](https://github.com/QingWei-Li/docsify/compare/v1.3.1...v1.3.2) (2016-12-22)\n\n\n\n<a name=\"1.3.1\"></a>\n## [1.3.1](https://github.com/QingWei-Li/docsify/compare/v1.3.0...v1.3.1) (2016-12-22)\n\n\n\n<a name=\"1.3.0\"></a>\n# [1.3.0](https://github.com/QingWei-Li/docsify/compare/v1.2.0...v1.3.0) (2016-12-21)\n\n\n\n<a name=\"1.2.0\"></a>\n# [1.2.0](https://github.com/QingWei-Li/docsify/compare/v1.1.7...v1.2.0) (2016-12-20)\n\n\n\n<a name=\"1.1.7\"></a>\n## [1.1.7](https://github.com/QingWei-Li/docsify/compare/v1.1.6...v1.1.7) (2016-12-19)\n\n\n\n<a name=\"1.1.6\"></a>\n## [1.1.6](https://github.com/QingWei-Li/docsify/compare/v1.1.5...v1.1.6) (2016-12-18)\n\n\n\n<a name=\"1.1.5\"></a>\n## [1.1.5](https://github.com/QingWei-Li/docsify/compare/v1.1.4...v1.1.5) (2016-12-18)\n\n\n\n<a name=\"1.1.4\"></a>\n## [1.1.4](https://github.com/QingWei-Li/docsify/compare/v1.1.3...v1.1.4) (2016-12-17)\n\n\n\n<a name=\"1.1.3\"></a>\n## [1.1.3](https://github.com/QingWei-Li/docsify/compare/v1.1.2...v1.1.3) (2016-12-17)\n\n\n\n<a name=\"1.1.2\"></a>\n## [1.1.2](https://github.com/QingWei-Li/docsify/compare/v1.1.1...v1.1.2) (2016-12-17)\n\n\n\n<a name=\"1.1.1\"></a>\n## [1.1.1](https://github.com/QingWei-Li/docsify/compare/v1.1.0...v1.1.1) (2016-12-17)\n\n\n\n<a name=\"1.1.0\"></a>\n# [1.1.0](https://github.com/QingWei-Li/docsify/compare/v1.0.3...v1.1.0) (2016-12-16)\n\n\n\n<a name=\"1.0.3\"></a>\n## [1.0.3](https://github.com/QingWei-Li/docsify/compare/v1.0.2...v1.0.3) (2016-12-13)\n\n\n\n<a name=\"1.0.2\"></a>\n## [1.0.2](https://github.com/QingWei-Li/docsify/compare/v1.0.1...v1.0.2) (2016-12-13)\n\n\n\n<a name=\"1.0.1\"></a>\n## [1.0.1](https://github.com/QingWei-Li/docsify/compare/v1.0.0...v1.0.1) (2016-12-08)\n\n\n\n<a name=\"1.0.0\"></a>\n# [1.0.0](https://github.com/QingWei-Li/docsify/compare/v0.7.0...v1.0.0) (2016-12-08)\n\n\n\n<a name=\"0.7.0\"></a>\n# [0.7.0](https://github.com/QingWei-Li/docsify/compare/v0.6.1...v0.7.0) (2016-11-30)\n\n\n\n<a name=\"0.6.1\"></a>\n## [0.6.1](https://github.com/QingWei-Li/docsify/compare/v0.6.0...v0.6.1) (2016-11-29)\n\n\n\n<a name=\"0.6.0\"></a>\n# [0.6.0](https://github.com/QingWei-Li/docsify/compare/v0.5.0...v0.6.0) (2016-11-29)\n\n\n\n<a name=\"0.5.0\"></a>\n# [0.5.0](https://github.com/QingWei-Li/docsify/compare/v0.4.2...v0.5.0) (2016-11-28)\n\n\n\n<a name=\"0.4.2\"></a>\n## [0.4.2](https://github.com/QingWei-Li/docsify/compare/v0.4.1...v0.4.2) (2016-11-28)\n\n\n\n<a name=\"0.4.1\"></a>\n## [0.4.1](https://github.com/QingWei-Li/docsify/compare/v0.4.0...v0.4.1) (2016-11-28)\n\n\n\n<a name=\"0.4.0\"></a>\n# [0.4.0](https://github.com/QingWei-Li/docsify/compare/v0.3.1...v0.4.0) (2016-11-27)\n\n\n### Features\n\n* custom sidebar, [#4](https://github.com/QingWei-Li/docsify/issues/4) ([#5](https://github.com/QingWei-Li/docsify/issues/5)) ([37e7984](https://github.com/QingWei-Li/docsify/commit/37e7984))\n\n\n\n<a name=\"0.3.1\"></a>\n## [0.3.1](https://github.com/QingWei-Li/docsify/compare/v0.3.0...v0.3.1) (2016-11-27)\n\n\n\n<a name=\"0.3.0\"></a>\n# [0.3.0](https://github.com/QingWei-Li/docsify/compare/v0.2.1...v0.3.0) (2016-11-27)\n\n\n\n<a name=\"0.2.1\"></a>\n## [0.2.1](https://github.com/QingWei-Li/docsify/compare/v0.2.0...v0.2.1) (2016-11-26)\n\n\n\n<a name=\"0.2.0\"></a>\n# [0.2.0](https://github.com/QingWei-Li/docsify/compare/v0.1.0...v0.2.0) (2016-11-26)\n\n\n\n<a name=\"0.1.0\"></a>\n# [0.1.0](https://github.com/QingWei-Li/docsify/compare/v0.0.5...v0.1.0) (2016-11-26)\n\n\n\n<a name=\"0.0.5\"></a>\n## [0.0.5](https://github.com/QingWei-Li/docsify/compare/v0.0.4...v0.0.5) (2016-11-24)\n\n\n\n<a name=\"0.0.4\"></a>\n## [0.0.4](https://github.com/QingWei-Li/docsify/compare/v0.0.3...v0.0.4) (2016-11-22)\n\n\n\n<a name=\"0.0.3\"></a>\n## [0.0.3](https://github.com/QingWei-Li/docsify/compare/v0.0.2...v0.0.3) (2016-11-20)\n\n\n\n<a name=\"0.0.2\"></a>\n## [0.0.2](https://github.com/QingWei-Li/docsify/compare/v0.0.1...v0.0.2) (2016-11-20)\n\n\n\n<a name=\"0.0.1\"></a>\n## 0.0.1 (2016-11-20)\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2016 - present Docsify Contributors (https://github.com/docsifyjs/docsify/graphs/contributors)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <a href=\"https://docsify.js.org\">\n    <img alt=\"docsify\" src=\"./docs/_media/icon.svg\">\n  </a>\n</p>\n\n<p align=\"center\">\n  A magical documentation site generator.\n</p>\n\n<p align=\"center\">\n  <a href=\"#backers\"><img alt=\"Backers on Open Collective\" src=\"https://opencollective.com/docsify/backers/badge.svg?style=flat-square\"></a>\n  <a href=\"#sponsors\">\n    <img alt=\"Sponsors on Open Collective\" src=\"https://opencollective.com/docsify/sponsors/badge.svg?style=flat-square\"></a>\n  <a href=\"https://github.com/docsifyjs/docsify/actions/workflows/test.yml\"><img src=\"https://github.com/docsifyjs/docsify/actions/workflows/test.yml/badge.svg\" alt=\"Build & Test\"></a>\n  <a href=\"https://www.npmjs.com/package/docsify\"><img alt=\"npm\" src=\"https://img.shields.io/npm/v/docsify.svg?style=flat-square\"></a>\n  <a href=\"https://discord.gg/3NwKFyR\"><img alt=\"Join Discord community and chat about Docsify\" src=\"https://img.shields.io/discord/713647066802421792.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2&cacheSeconds=60\"></a>\n  <a href=\"https://gitpod.io/#https://github.com/docsifyjs/docsify\"><img src=\"https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod\" alt=\"Gitpod Ready-to-Code\"></a>\n</p>\n\n<p align=\"center\">Gold Sponsor via <a href=\"https://opencollective.com/docsify\">Open Collective</a></p>\n\n<p align=\"center\">\n  <a href=\"https://opencollective.com/docsify/order/3254\">\n    <img src=\"https://opencollective.com/docsify/tiers/gold-sponsor.svg?avatarHeight=48\">\n  </a>\n</p>\n\nDocsify turns one or more Markdown files into a Website, with no build process required.\n\n## Features\n\n- No statically built HTML files\n- Simple and lightweight\n- Smart full-text search plugin\n- Multiple themes\n- Useful plugin API\n- Emoji support\n\n## Quick Start\n\nGet going fast by using a static web server or GitHub Pages with this ready-to-use [Docsify Template](https://github.com/docsifyjs/docsify-template), review the [quick start tutorial](https://docsify.js.org/#/quickstart) or jump right into a CodeSandbox example site with the button below.\n\n[![Edit 307qqv236](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/307qqv236)\n\n## Showcase\n\nA large collection of showcase projects are included in [awesome-docsify](https://github.com/docsifyjs/awesome-docsify#showcase).\n\n## Links\n\n- [Documentation](https://docsify.js.org)\n- [Docsify CLI (Command Line Interface)](https://github.com/docsifyjs/docsify-cli)\n- CDN: [UNPKG](https://unpkg.com/docsify/) | [jsDelivr](https://cdn.jsdelivr.net/npm/docsify/) | [cdnjs](https://cdnjs.com/libraries/docsify)\n- [`develop` branch preview](https://docsify-preview.vercel.app/)\n- [Awesome docsify](https://github.com/docsifyjs/awesome-docsify)\n- [Community chat](https://discord.gg/3NwKFyR)\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md).\n\n## Backers\n\nThank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/docsify/contribute)]\n\n<a href=\"https://opencollective.com/docsify#backers\" target=\"_blank\"><img src=\"https://opencollective.com/docsify/backers.svg?width=890\"></a>\n\n## Sponsors\n\nThank you for supporting this project! ❤️ [[Become a sponsor](https://opencollective.com/docsify/contribute)]\n\n<img src=\"https://opencollective.com/docsify/sponsors.svg?width=890\" />\n\n## Contributors\n\nThis project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].\n\n<a href=\"https://github.com/docsifyjs/docsify/graphs/contributors\"><img src=\"https://opencollective.com/docsify/contributors.svg?width=890\" /></a>\n\n## License\n\n[MIT](LICENSE)\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\nIf you believe you have found a security vulnerability in docsify, please report it to us asap.\n\n## Reporting a Vulnerability\n\n**Please do not report security vulnerabilities through our public GitHub issues.**\n\nSend email to us via :email: maintainers@docsifyjs.org.\n\nPlease include as much of the following information as possible to help us better understand the possible issue:\n\n- Type of issue (e.g. cross-site scripting)\n- Full paths of source file(s) related to the manifestation of the issue\n- The location of the affected source code (tag/branch/commit or direct URL)\n- Any special configuration required to reproduce the issue\n- Step-by-step instructions to reproduce the issue\n- Proof-of-concept or exploit code\n- Impact of the issue, including how an attacker might exploit the issue\n\nThis information will help us triage your report more quickly.\n\nThank you in advance.\n"
  },
  {
    "path": "babel.config.json",
    "content": "{\n  \"presets\": [\n    [\n      \"@babel/preset-env\",\n      {\n        \"targets\": \"defaults\"\n      }\n    ]\n  ]\n}\n"
  },
  {
    "path": "build/cover.js",
    "content": "import fs from 'fs';\nimport { relative } from './util.js';\n\nconst read = fs.readFileSync;\nconst write = fs.writeFileSync;\nconst pkgPath = relative(import.meta, '..', 'package.json');\nconst pkg = JSON.parse(read(pkgPath).toString());\nconst version = process.env.VERSION || pkg.version;\n\nconst file = relative(import.meta, '..', 'docs', '_coverpage.md');\nlet cover = read(file, 'utf8').toString();\n\nconsole.log('Replace version number in cover page...');\ncover = cover.replace(\n  /<small>(\\S+)?<\\/small>/g,\n  /* html */ `<small>${version}</small>`,\n);\nwrite(file, cover);\n"
  },
  {
    "path": "build/emoji.js",
    "content": "import fs from 'fs';\nimport path from 'path';\nimport axios from 'axios';\n\nconst filePaths = {\n  emojiMarkdown: path.resolve(process.cwd(), 'docs', 'emoji.md'),\n  emojiJS: path.resolve(\n    process.cwd(),\n    'src',\n    'core',\n    'render',\n    'emoji-data.js',\n  ),\n};\n\nasync function getEmojiData() {\n  const emojiDataURL = 'https://api.github.com/emojis';\n\n  console.info(`- Fetching emoji data from ${emojiDataURL}`);\n\n  const response = await axios.get(emojiDataURL);\n  const baseURL = Object.values(response.data)\n    .find(url => /unicode\\//)\n    .split('unicode/')[0];\n  const data = { ...response.data };\n\n  // Remove base URL from emoji URLs\n  Object.entries(data).forEach(\n    ([key, value]) => (data[key] = value.replace(baseURL, '')),\n  );\n\n  console.info(`- Retrieved ${Object.keys(data).length} emoji entries`);\n\n  return {\n    baseURL,\n    data,\n  };\n}\n\nfunction writeEmojiPage(emojiData) {\n  const isExistingPage = fs.existsSync(filePaths.emojiMarkdown);\n  const emojiPage =\n    (isExistingPage && fs.readFileSync(filePaths.emojiMarkdown, 'utf8')) ||\n    '<!-- START -->\\n\\n<!-- END -->';\n  const emojiRegEx = /(<!--\\s*START.*-->\\n)([\\s\\S]*)(\\n<!--\\s*END.*-->)/;\n  const emojiMatch = emojiPage.match(emojiRegEx);\n  const emojiMarkdownStart = emojiMatch[1].trim();\n  const emojiMarkdown = emojiMatch[2].trim();\n  const emojiMarkdownEnd = emojiMatch[3].trim();\n  const newEmojiMarkdown = Object.keys(emojiData.data)\n    .reduce(\n      (preVal, curVal) =>\n        (preVal += `:${curVal}: ` + '`' + `:${curVal}:` + '`' + '\\n\\n'),\n      '',\n    )\n    .trim();\n\n  if (emojiMarkdown !== newEmojiMarkdown) {\n    const newEmojiPage = emojiPage.replace(\n      emojiMatch[0],\n      `${emojiMarkdownStart}\\n\\n${newEmojiMarkdown}\\n\\n${emojiMarkdownEnd}`,\n    );\n\n    fs.writeFileSync(filePaths.emojiMarkdown, newEmojiPage);\n\n    console.info(\n      `- ${!isExistingPage ? 'Created' : 'Updated'}: ${filePaths.emojiMarkdown}`,\n    );\n  } else {\n    console.info(`- No changes: ${filePaths.emojiMarkdown}`);\n  }\n}\n\nfunction writeEmojiJS(emojiData) {\n  const isExistingPage = fs.existsSync(filePaths.emojiJS);\n  const emojiJS = isExistingPage && fs.readFileSync(filePaths.emojiJS, 'utf8');\n  const newEmojiJS = [\n    '// =============================================================================',\n    '// DO NOT EDIT: This file is auto-generated by an /build/emoji.js',\n    '// =============================================================================\\n',\n    `export default ${JSON.stringify(emojiData, {}, 2)}`,\n  ].join('\\n');\n\n  if (!emojiJS || emojiJS !== newEmojiJS) {\n    fs.writeFileSync(filePaths.emojiJS, newEmojiJS);\n\n    console.info(\n      `- ${!isExistingPage ? 'Created' : 'Updated'}: ${filePaths.emojiJS}`,\n    );\n  } else {\n    console.info(`- No changes: ${filePaths.emojiJS}`);\n  }\n}\n\nconsole.info('Build emoji');\n\nconst emojiData = await getEmojiData();\n\nwriteEmojiPage(emojiData);\nwriteEmojiJS(emojiData);\n\nconsole.info('Finish update');\n"
  },
  {
    "path": "build/release.sh",
    "content": "set -e\n\nif [[ -z $1 ]]; then\n  echo \"Enter new version: \"\n  read VERSION\nelse\n  VERSION=$1\nfi\n\nRELEASE_TAG=${RELEASE_TAG:-\"\"}\n\nif [[ -n \"$RELEASE_TAG\" && \"$VERSION\" != *\"$RELEASE_TAG\"* ]]; then\n  RELEASE_MSG=\"$VERSION ($RELEASE_TAG)\"\nelse\n  RELEASE_MSG=\"$VERSION\"\nfi\n\nread -p \"Releasing $VERSION $RELEASE_TAG - are you sure? (y/n) \" -n 1 -r\necho\nif [[ $REPLY =~ ^[Yy]$ ]]; then\n  echo \"Releasing $VERSION ...\"\n\n  # Update version (don't commit or tag yet)\n  npm --no-git-tag-version version \"$VERSION\"\n\n  # Build and test\n  npm run build\n  npm run test:update:snapshot\n  npm run test\n  git stash\n  npm run build:v4 # builds legacy v4 lib/ and themes/ folders for backwards compat while people transition to v5.\n  git stash pop\n\n  # Changelog\n  npx conventional-changelog -p angular -i CHANGELOG.md -s\n\n  # Commit all changes\n  git add -A\n  git commit -m \"[release] $RELEASE_MSG\"\n\n  # Tag and push\n  git tag \"v$VERSION\"\n  git push origin \"v$VERSION\"\n  git push\n\n  # Publish to npm\n  if [[ -z $RELEASE_TAG ]]; then\n    npm publish\n  else\n    npm publish --tag \"$RELEASE_TAG\"\n  fi\n\n  npm run clean:v4 # clean up legacy v4 build files\nfi\n"
  },
  {
    "path": "build/util.js",
    "content": "import url from 'url';\nimport path from 'path';\n\n/** Get a new path relative to the current module (pass import.meta). */\nexport const relative = (meta, ...to) =>\n  path.resolve(path.dirname(url.fileURLToPath(meta.url)), ...to);\n"
  },
  {
    "path": "docs/.nojekyll",
    "content": ""
  },
  {
    "path": "docs/CNAME",
    "content": "docsify.js.org"
  },
  {
    "path": "docs/README.md",
    "content": "# docsify\n\n> A magical documentation site generator.\n\n## What it is\n\nDocsify turns your Markdown files into a documentation website instantly. Unlike most other documentation site generator tools, it doesn't need to build HTML files. Instead, it dynamically loads and parses your Markdown files and displays them as a website. To get started, create an `index.html` file and [deploy it on GitHub Pages](deploy.md) (for more details see the [Quick start](quickstart.md) guide).\n\n## Features\n\n- No statically built HTML files\n- Simple and lightweight\n- Smart full-text search plugin\n- Multiple themes\n- Useful plugin API\n- Emoji support\n\n## Examples\n\nCheck out the [Showcase](https://github.com/docsifyjs/awesome-docsify#showcase) to see docsify in use.\n\n## Donate\n\nPlease consider donating if you think docsify is helpful to you or that my work is valuable. I am happy if you can help me [buy a cup of coffee](https://github.com/QingWei-Li/donate). :heart:\n\n## Community\n\nUsers and the development team are usually in the [Discord server](https://discord.gg/3NwKFyR).\n"
  },
  {
    "path": "docs/_coverpage.md",
    "content": "<!-- markdownlint-disable first-line-h1 -->\n\n![logo](_media/icon.svg)\n\n# docsify <small>5.0.0-rc.4</small> :id=docsify\n\n> A magical documentation site generator\n\n- Simple and lightweight\n- No statically built HTML files\n- Multiple themes\n\n[Get Started](#docsify)\n[GitHub](https://github.com/docsifyjs/docsify/)\n\n<!-- ![color](#f0f0f0) -->\n<!-- ![](/_media/icon.svg) -->\n"
  },
  {
    "path": "docs/_media/example-with-yaml.md",
    "content": "---\nauthor: John Smith\ndate: 2020-1-1\n---\n\n> This is from the `example-with-yaml.md`\n"
  },
  {
    "path": "docs/_media/example.html",
    "content": "<style>\n  html,\n  body {\n    margin: 0;\n    padding: 0;\n    min-height: 100%;\n  }\n\n  body {\n    background-color: #4158d0;\n    background-image: linear-gradient(\n      43deg,\n      #4158d0 0%,\n      #c850c0 46%,\n      #ffcc70 100%\n    );\n    color: #fff;\n    font-family: sans-serif;\n  }\n\n  main {\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    height: 100vh;\n  }\n</style>\n\n<main>\n  <p>Example HTML Page</p>\n</main>\n"
  },
  {
    "path": "docs/_media/example.js",
    "content": "import fetch from 'fetch';\n\nconst URL = 'https://example.com';\nconst PORT = 8080;\n\n/// [demo]\nconst result = fetch(`${URL}:${PORT}`)\n  .then(response => {\n    return response.json();\n  })\n  .then(myJson => {\n    console.log(JSON.stringify(myJson));\n  });\n/// [demo]\n\nresult.then(console.log).catch(console.error);\n"
  },
  {
    "path": "docs/_media/example.md",
    "content": "> This is from the `example.md`\n"
  },
  {
    "path": "docs/_navbar.md",
    "content": "<!-- markdownlint-disable first-line-h1 -->\n\n- Translations\n\n  - [English](/)\n  - [简体中文](/zh-cn/)\n"
  },
  {
    "path": "docs/_sidebar.md",
    "content": "<!-- markdownlint-disable first-line-h1 -->\n\n- Getting started\n\n  - [Quick start](quickstart.md)\n  - [Adding pages](adding-pages.md)\n  - [Cover page](cover.md)\n  - [Custom navbar](custom-navbar.md)\n\n- Customization\n\n  - [Configuration](configuration.md)\n  - [Themes](themes.md)\n  - [List of Plugins](plugins.md)\n  - [Write a Plugin](write-a-plugin.md)\n  - [Markdown configuration](markdown.md)\n  - [Language highlighting](language-highlight.md)\n  - [Emoji](emoji.md)\n\n- Guide\n\n  - [Deploy](deploy.md)\n  - [Helpers](helpers.md)\n  - [Vue compatibility](vue.md)\n  - [CDN](cdn.md)\n  - [Offline Mode (PWA)](pwa.md)\n  - [Embed Files](embed-files.md)\n  - [UI Kit](ui-kit.md)\n\n- Upgrading\n\n  - [v4 to v5](v5-upgrade.md)\n\n* [Awesome docsify](awesome.md)\n* [Changelog](changelog.md)\n"
  },
  {
    "path": "docs/adding-pages.md",
    "content": "# Adding pages\n\nIf you need more pages, you can simply create more markdown files in your docsify directory. If you create a file named `guide.md`, then it is accessible via `/#/guide`.\n\nFor example, the directory structure is as follows:\n\n```text\n.\n└── docs\n    ├── README.md\n    ├── guide.md\n    └── zh-cn\n        ├── README.md\n        └── guide.md\n```\n\nMatching routes\n\n```text\ndocs/README.md        => http://domain.com\ndocs/guide.md         => http://domain.com/#/guide\ndocs/zh-cn/README.md  => http://domain.com/#/zh-cn/\ndocs/zh-cn/guide.md   => http://domain.com/#/zh-cn/guide\n```\n\n## Sidebar\n\nIn order to have a sidebar, you can create your own `_sidebar.md` (see [this documentation's sidebar](https://github.com/docsifyjs/docsify/blob/main/docs/_sidebar.md) for an example):\n\nFirst, you need to set `loadSidebar` to **true**. Details are available in the [configuration paragraph](configuration#loadsidebar).\n\n```html\n<!-- index.html -->\n\n<script>\n  window.$docsify = {\n    loadSidebar: true,\n  };\n</script>\n<script src=\"//cdn.jsdelivr.net/npm/docsify@5/dist/docsify.min.js\"></script>\n```\n\nCreate the `_sidebar.md`:\n\n```markdown\n<!-- docs/_sidebar.md -->\n\n- [Home](/)\n- [Page 1](page-1.md)\n```\n\nTo create section headers:\n\n```markdown\n<!-- docs/_sidebar.md -->\n\n- Section Header 1\n\n  - [Home](/)\n  - [Page 1](page-1.md)\n\n- Section Header 2\n\n  - [Page 2](page-2.md)\n  - [Page 3](page-3.md)\n```\n\nYou need to create a `.nojekyll` in `./docs` to prevent GitHub Pages from ignoring files that begin with an underscore.\n\n> [!IMPORTANT] Docsify only looks for `_sidebar.md` in the current folder, and uses that, otherwise it falls back to the one configured using `window.$docsify.loadSidebar` config.\n\nExample file structure:\n\n```text\n└── docs/\n    ├── _sidebar.md\n    ├── index.md\n    ├── getting-started.md\n    └── running-services.md\n```\n\n## Nested Sidebars\n\nYou may want the sidebar to update after navigation to reflect the current directory. This can be done by adding a `_sidebar.md` file to each folder.\n\n`_sidebar.md` is loaded from each level directory. If the current directory doesn't have `_sidebar.md`, it will fall back to the parent directory. If, for example, the current path is `/guide/quick-start`, the `_sidebar.md` will be loaded from `/guide/_sidebar.md`.\n\nYou can specify `alias` to avoid unnecessary fallback.\n\n```html\n<script>\n  window.$docsify = {\n    loadSidebar: true,\n    alias: {\n      '/.*/_sidebar.md': '/_sidebar.md',\n    },\n  };\n</script>\n```\n\n> [!IMPORTANT] You can create a `README.md` file in a subdirectory to use it as the landing page for the route.\n\n## Set Page Titles from Sidebar Selection\n\nA page's `title` tag is generated from the _selected_ sidebar item name. For better SEO, you can customize the title by specifying a string after the filename.\n\n```markdown\n<!-- docs/_sidebar.md -->\n\n- [Home](/)\n- [Guide](guide.md 'The greatest guide in the world')\n```\n\n## Table of Contents\n\nOnce you've created `_sidebar.md`, the sidebar content is automatically generated based on the headers in the markdown files.\n\nA custom sidebar can also automatically generate a table of contents by setting a `subMaxLevel`, compare [subMaxLevel configuration](configuration#submaxlevel).\n\n```html\n<!-- index.html -->\n\n<script>\n  window.$docsify = {\n    loadSidebar: true,\n    subMaxLevel: 2,\n  };\n</script>\n<script src=\"//cdn.jsdelivr.net/npm/docsify@5/dist/docsify.min.js\"></script>\n```\n\n## Ignoring Subheaders\n\nWhen `subMaxLevel` is set, each header is automatically added to the table of contents by default. If you want to ignore a specific header, add `<!-- {docsify-ignore} -->` to it.\n\n```markdown\n# Getting Started\n\n## Header <!-- {docsify-ignore} -->\n\nThis header won't appear in the sidebar table of contents.\n```\n\nTo ignore all headers on a specific page, you can use `<!-- {docsify-ignore-all} -->` on the first header of the page.\n\n```markdown\n# Getting Started <!-- {docsify-ignore-all} -->\n\n## Header\n\nThis header won't appear in the sidebar table of contents.\n```\n\nBoth `<!-- {docsify-ignore} -->` and `<!-- {docsify-ignore-all} -->` will not be rendered on the page when used.\n\nAnd the `{docsify-ignore}` and `{docsify-ignore-all}` can do the samething as well.\n"
  },
  {
    "path": "docs/cdn.md",
    "content": "# CDN\n\nThe docsify [npm package](https://www.npmjs.com/package/docsify) is auto-published to CDNs with each release. The contents can be viewed on each CDN.\n\nDocsify recommends [jsDelivr](//cdn.jsdelivr.net) as its preferred CDN:\n\n- https://cdn.jsdelivr.net/npm/docsify/\n\nOther CDNs are available and may be required in locations where jsDelivr is not available:\n\n- https://cdnjs.com/libraries/docsify\n- https://unpkg.com/browse/docsify/\n- https://www.bootcdn.cn/docsify/\n\n## Specifying versions\n\nNote the `@` version lock in the CDN URLs below. This allows specifying the latest major, minor, patch, or specific [semver](https://semver.org) version number.\n\n- MAJOR versions include breaking changes<br>\n  `1.0.0` → `2.0.0`\n- MINOR versions include non-breaking new functionality<br>\n  `1.0.0` → `1.1.0`\n- PATCH versions include non-breaking bug fixes<br>\n  `1.0.0` → `1.0.1`\n\nUncompressed resources are available by omitting the `.min` from the filename.\n\n## Latest \"major\" version\n\nSpecifying the latest major version allows your site to receive all non-breaking enhancements (\"minor\" updates) and bug fixes (\"patch\" updates) as they are released. This is good option for those who prefer a zero-maintenance way of keeping their site up to date with minimal risk as new versions are published.\n\n> [!TIP] When a new major version is released, you will need to manually update the major version number after the `@` symbol in your CDN URLs.\n\n<!-- prettier-ignore -->\n```html\n<!-- Theme -->\n<link rel=\"stylesheet\" href=\"//cdn.jsdelivr.net/npm/docsify@5/dist/themes/addons/vue.min.css\" />\n\n<!-- Docsify -->\n<script src=\"//cdn.jsdelivr.net/npm/docsify@5/dist/docsify.min.js\"></script>\n```\n\n## Specific version\n\nSpecifying an exact version prevents any future updates from affecting your site. This is good option for those who prefer to manually update their resources as new versions are published.\n\n> [!TIP] When a new version is released, you will need to manually update the version number after the `@` symbol in your CDN URLs.\n\n<!-- prettier-ignore -->\n```html\n<!-- Theme -->\n<link rel=\"stylesheet\" href=\"//cdn.jsdelivr.net/npm/docsify@5.0.0/dist/themes/addons/vue.min.css\" />\n\n<!-- Docsify -->\n<script src=\"//cdn.jsdelivr.net/npm/docsify@5.0.0/dist/docsify.min.js\"></script>\n```\n"
  },
  {
    "path": "docs/configuration.md",
    "content": "# Configuration\n\nYou can configure Docsify by defining `window.$docsify` as an object:\n\n```html\n<script>\n  window.$docsify = {\n    repo: 'docsifyjs/docsify',\n    maxLevel: 3,\n    coverpage: true,\n  };\n</script>\n```\n\nThe config can also be defined as a function, in which case the first argument is the Docsify `vm` instance. The function should return a config object. This can be useful for referencing `vm` in places like the markdown configuration:\n\n```html\n<script>\n  window.$docsify = function (vm) {\n    return {\n      markdown: {\n        renderer: {\n          code(code, lang) {\n            // ... use `vm` ...\n          },\n        },\n      },\n    };\n  };\n</script>\n```\n\n## alias\n\n- Type: `Object`\n\nSet the route alias. You can freely manage routing rules. Supports RegExp.\nDo note that order matters! If a route can be matched by multiple aliases, the one you declared first takes precedence.\n\n```js\nwindow.$docsify = {\n  alias: {\n    '/foo/(.*)': '/bar/$1', // supports regexp\n    '/zh-cn/changelog': '/changelog',\n    '/changelog':\n      'https://raw.githubusercontent.com/docsifyjs/docsify/main/CHANGELOG',\n\n    // You may need this if you use routerMode:'history'.\n    '/.*/_sidebar.md': '/_sidebar.md', // See #301\n  },\n};\n```\n\n> **Note** If you change [`routerMode`](#routermode) to `'history'`, you may\n> want to configure an alias for your `_sidebar.md` and `_navbar.md` files.\n\n## auto2top\n\n- Type: `Boolean`\n- Default: `false`\n\nScrolls to the top of the screen when the route is changed.\n\n```js\nwindow.$docsify = {\n  auto2top: true,\n};\n```\n\n## autoHeader\n\n- Type: `Boolean`\n- Default: `false`\n\nIf `loadSidebar` and `autoHeader` are both enabled, for each link in `_sidebar.md`, prepend a header to the page before converting it to HTML — but only if the page does not already contain an H1 heading.\n\nFor more details, see [#78](https://github.com/docsifyjs/docsify/issues/78).\n\n```js\nwindow.$docsify = {\n  loadSidebar: true,\n  autoHeader: true,\n};\n```\n\n## basePath\n\n- Type: `String`\n\nBase path of the website. You can set it to another directory or another domain name.\n\n```js\nwindow.$docsify = {\n  basePath: '/path/',\n\n  // Load the files from another site\n  basePath: 'https://docsify.js.org/',\n\n  // Even can load files from other repo\n  basePath:\n    'https://raw.githubusercontent.com/ryanmcdermott/clean-code-javascript/master/',\n};\n```\n\n## catchPluginErrors\n\n- Type: `Boolean`\n- Default: `true`\n\nDetermines if Docsify should handle uncaught _synchronous_ plugin errors automatically. This can prevent plugin errors from affecting docsify's ability to properly render live site content.\n\n## cornerExternalLinkTarget\n\n- Type: `String`\n- Default: `'_blank'`\n\nTarget to open external link at the top right corner. Default `'_blank'` (new window/tab)\n\n```js\nwindow.$docsify = {\n  cornerExternalLinkTarget: '_self', // default: '_blank'\n};\n```\n\n## coverpage\n\n- Type: `Boolean|String|String[]|Object`\n- Default: `false`\n\nActivate the [cover feature](cover.md). If true, it will load from `_coverpage.md`.\n\n```js\nwindow.$docsify = {\n  coverpage: true,\n\n  // Custom file name\n  coverpage: 'cover.md',\n\n  // multiple covers\n  coverpage: ['/', '/zh-cn/'],\n\n  // multiple covers and custom file name\n  coverpage: {\n    '/': 'cover.md',\n    '/zh-cn/': 'cover.md',\n  },\n};\n```\n\n## el\n\n- Type: `String`\n- Default: `'#app'`\n\nThe DOM element to be mounted on initialization. It can be a CSS selector string or an actual [HTMLElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement).\n\n```js\nwindow.$docsify = {\n  el: '#app',\n};\n```\n\n## executeScript\n\n- Type: `Boolean`\n- Default: `null`\n\nExecute the script on the page. Only parses the first script tag ([demo](themes)). If Vue is detected, this is `true` by default.\n\n```js\nwindow.$docsify = {\n  executeScript: true,\n};\n```\n\n```markdown\n## This is test\n\n<script>\n  console.log(2333)\n</script>\n```\n\nNote that if you are running an external script, e.g. an embedded jsfiddle demo, make sure to include the [external-script](plugins.md?id=external-script) plugin.\n\n## ext\n\n- Type: `String`\n- Default: `'.md'`\n\nRequest file extension.\n\n```js\nwindow.$docsify = {\n  ext: '.md',\n};\n```\n\n## externalLinkRel\n\n- Type: `String`\n- Default: `'noopener'`\n\nDefault `'noopener'` (no opener) prevents the newly opened external page (when [externalLinkTarget](#externallinktarget) is `'_blank'`) from having the ability to control our page. No `rel` is set when it's not `'_blank'`. See [this post](https://mathiasbynens.github.io/rel-noopener/) for more information about why you may want to use this option.\n\n```js\nwindow.$docsify = {\n  externalLinkRel: '', // default: 'noopener'\n};\n```\n\n## externalLinkTarget\n\n- Type: `String`\n- Default: `'_blank'`\n\nTarget to open external links inside the markdown. Default `'_blank'` (new window/tab)\n\n```js\nwindow.$docsify = {\n  externalLinkTarget: '_self', // default: '_blank'\n};\n```\n\n## fallbackLanguages\n\n- Type: `Array<string>`\n\nList of languages that will fallback to the default language when a page is requested and it doesn't exist for the given locale.\n\nExample:\n\n- try to fetch the page of `/de/overview`. If this page exists, it'll be displayed.\n- then try to fetch the default page `/overview` (depending on the default language). If this page exists, it'll be displayed.\n- then display the 404 page.\n\n```js\nwindow.$docsify = {\n  fallbackLanguages: ['fr', 'de'],\n};\n```\n\n## fallbackDefaultLanguage\n\n- Type: `String`\n- Default: `''`\n\nWhen a page is requested and it doesn't exist for the given locale, Docsify will fallback to the language specified by this option.\n\nFor example, in the scenario described above, if `/de/overview` does not exist and `fallbackDefaultLanguage` is configured as `zh-cn`, Docsify will fetch `/zh-cn/overview` instead of `/overview`.\n\n```js\nwindow.$docsify = {\n  fallbackLanguages: ['fr', 'de'],\n  fallbackDefaultLanguage: 'zh-cn', // default: ''\n};\n```\n\n## formatUpdated\n\n- Type: `String|Function`\n\nWe can display the file update date through **{docsify-updated<span>}</span>** variable. And format it by `formatUpdated`.\nSee https://github.com/lukeed/tinydate#patterns\n\n```js\nwindow.$docsify = {\n  formatUpdated: '{MM}/{DD} {HH}:{mm}',\n\n  formatUpdated(time) {\n    // ...\n\n    return time;\n  },\n};\n```\n\n## hideSidebar\n\n- Type : `Boolean`\n- Default: `false`\n\nThis option will completely hide your sidebar and won't render any content on the side.\n\n```js\nwindow.$docsify = {\n  hideSidebar: true,\n};\n```\n\n## homepage\n\n- Type: `String`\n- Default: `'README.md'`\n\n`README.md` in your docs folder will be treated as the homepage for your website, but sometimes you may need to serve another file as your homepage.\n\n```js\nwindow.$docsify = {\n  // Change to /home.md\n  homepage: 'home.md',\n\n  // Or use the readme in your repo\n  homepage:\n    'https://raw.githubusercontent.com/docsifyjs/docsify/main/README.md',\n};\n```\n\n## keyBindings\n\n- Type: `Boolean|Object`\n- Default: `Object`\n  - <kbd>\\\\</kbd> Toggle the sidebar menu\n  - <kbd>/</kbd> Focus on [search](plugins#full-text-search) field. Also supports <kbd>alt</kbd>&nbsp;/&nbsp;<kbd>ctrl</kbd>&nbsp;+&nbsp;<kbd>k</kbd>.\n\nBinds key combination(s) to a custom callback function.\n\nKey `bindings` are defined as case insensitive string values separated by `+`. Modifier key values include `alt`, `ctrl`, `meta`, and `shift`. Non-modifier key values should match the keyboard event's [key](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key) or [code](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code) value.\n\nThe `callback` function receive a [keydown event](https://developer.mozilla.org/en-US/docs/Web/API/Element/keydown_event) as an argument.\n\n> [!IMPORTANT] Let site visitors know your custom key bindings are available! If a binding is associated with a DOM element, consider inserting a `<kbd>` element as a visual cue (e.g., <kbd>alt</kbd> + <kbd>a</kbd>) or adding [title](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/title) and [aria-keyshortcuts](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-keyshortcuts) attributes for hover/focus hints.\n\n```js\nwindow.$docsify = {\n  keyBindings: {\n    // Custom key binding\n    myCustomBinding: {\n      bindings: ['alt+a', 'shift+a'],\n      callback(event) {\n        alert('Hello, World!');\n      },\n    },\n  },\n};\n```\n\nKey bindings can be disabled entirely or individually by setting the binding configuration to `false`.\n\n```js\nwindow.$docsify = {\n  // Disable all key bindings\n  keyBindings: false,\n};\n```\n\n```js\nwindow.$docsify = {\n  keyBindings: {\n    // Disable individual key bindings\n    focusSearch: false,\n    toggleSidebar: false,\n  },\n};\n```\n\n## loadNavbar\n\n- Type: `Boolean|String`\n- Default: `false`\n\nLoads navbar from the Markdown file `_navbar.md` if **true**, else loads it from the path specified.\n\n```js\nwindow.$docsify = {\n  // load from _navbar.md\n  loadNavbar: true,\n\n  // load from nav.md\n  loadNavbar: 'nav.md',\n};\n```\n\n## loadSidebar\n\n- Type: `Boolean|String`\n- Default: `false`\n\nLoads sidebar from the Markdown file `_sidebar.md` if **true**, else loads it from the path specified.\n\n```js\nwindow.$docsify = {\n  // load from _sidebar.md\n  loadSidebar: true,\n\n  // load from summary.md\n  loadSidebar: 'summary.md',\n};\n```\n\n## logo\n\n- Type: `String`\n\nWebsite logo as it appears in the sidebar. You can resize it using CSS.\n\n> [!IMPORTANT] Logo will only be visible if `name` prop is also set. See [name](#name) configuration.\n\n```js\nwindow.$docsify = {\n  logo: '/_media/icon.svg',\n};\n```\n\n## markdown\n\n- Type: `Function`\n\nSee [Markdown configuration](markdown.md).\n\n```js\nwindow.$docsify = {\n  // object\n  markdown: {\n    smartypants: true,\n    renderer: {\n      link() {\n        // ...\n      },\n    },\n  },\n\n  // function\n  markdown(marked, renderer) {\n    // ...\n    return marked;\n  },\n};\n```\n\n## maxLevel\n\n- Type: `Number`\n- Default: `6`\n\nMaximum Table of content level.\n\n```js\nwindow.$docsify = {\n  maxLevel: 4,\n};\n```\n\n## mergeNavbar\n\n- Type: `Boolean`\n- Default: `false`\n\nNavbar will be merged with the sidebar on smaller screens.\n\n```js\nwindow.$docsify = {\n  mergeNavbar: true,\n};\n```\n\n## name\n\n- Type: `Boolean | String`\n\nWebsite name as it appears in the sidebar.\n\n```js\nwindow.$docsify = {\n  name: 'docsify',\n};\n```\n\nThe name field can also contain custom HTML for easier customization:\n\n```js\nwindow.$docsify = {\n  name: '<span>docsify</span>',\n};\n```\n\nIf `true`, the website name will be inferred from the document's `<title>` tag.\n\n```js\nwindow.$docsify = {\n  name: true,\n};\n```\n\nIf `false` or empty, no name will be displayed.\n\n```js\nwindow.$docsify = {\n  name: false,\n};\n```\n\n## nameLink\n\n- Type: `String`\n- Default: `'window.location.pathname'`\n\nThe URL that the website `name` links to.\n\n```js\nwindow.$docsify = {\n  nameLink: '/',\n\n  // For each route\n  nameLink: {\n    '/zh-cn/': '#/zh-cn/',\n    '/': '#/',\n  },\n};\n```\n\n## nativeEmoji\n\n- Type: `Boolean`\n- Default: `false`\n\nRender emoji shorthand codes using GitHub-style emoji images or native emoji characters.\n\n```js\nwindow.$docsify = {\n  nativeEmoji: true,\n};\n```\n\n```markdown\n:smile:\n:partying_face:\n:joy:\n:+1:\n:-1:\n```\n\nGitHub-style images when `false`:\n\n<output data-lang=\"output\">\n  <img class=\"emoji\" src=\"https://github.githubassets.com/images/icons/emoji/unicode/1f604.png\" alt=\"smile\">\n  <img class=\"emoji\" src=\"https://github.githubassets.com/images/icons/emoji/unicode/1f973.png\" alt=\"partying_face\">\n  <img class=\"emoji\" src=\"https://github.githubassets.com/images/icons/emoji/unicode/1f602.png\" alt=\"joy\">\n  <img class=\"emoji\" src=\"https://github.githubassets.com/images/icons/emoji/unicode/1f44d.png\" alt=\"+1\">\n  <img class=\"emoji\" src=\"https://github.githubassets.com/images/icons/emoji/unicode/1f44e.png\" alt=\"-1\">\n</output>\n\nNative characters when `true`:\n\n<output data-lang=\"output\">\n  <span class=\"emoji\">😄︎</span>\n  <span class=\"emoji\">🥳︎</span>\n  <span class=\"emoji\">😂︎</span>\n  <span class=\"emoji\">👍︎</span>\n  <span class=\"emoji\">👎︎</span>\n</output>\n\nTo render shorthand codes as text, replace `:` characters with the `&colon;` HTML entity.\n\n```markdown\n&colon;100&colon;\n```\n\n<output data-lang=\"output\">\n\n&colon;100&colon;\n\n</output>\n\n## noCompileLinks\n\n- Type: `Array<string>`\n\nSometimes we do not want docsify to handle our links. See [#203](https://github.com/docsifyjs/docsify/issues/203). We can skip compiling of certain links by specifying an array of strings. Each string is converted into to a regular expression (`RegExp`) and the _whole_ href of a link is matched against it.\n\n```js\nwindow.$docsify = {\n  noCompileLinks: ['/foo', '/bar/.*'],\n};\n```\n\n## noEmoji\n\n- Type: `Boolean`\n- Default: `false`\n\nDisabled emoji parsing and render all emoji shorthand as text.\n\n```js\nwindow.$docsify = {\n  noEmoji: true,\n};\n```\n\n```markdown\n:100:\n```\n\n<output data-lang=\"output\">\n\n&colon;100&colon;\n\n</output>\n\nTo disable emoji parsing of individual shorthand codes, replace `:` characters with the `&colon;` HTML entity.\n\n```markdown\n:100:\n\n&colon;100&colon;\n```\n\n<output data-lang=\"output\">\n\n:100:\n\n&colon;100&colon;\n\n</output>\n\n## notFoundPage\n\n- Type: `Boolean|String|Object`\n- Default: `false`\n\nDisplay default \"404 - Not Found\" message:\n\n```js\nwindow.$docsify = {\n  notFoundPage: false,\n};\n```\n\nLoad the `_404.md` file:\n\n```js\nwindow.$docsify = {\n  notFoundPage: true,\n};\n```\n\nLoad the customized path of the 404 page:\n\n```js\nwindow.$docsify = {\n  notFoundPage: 'my404.md',\n};\n```\n\nLoad the right 404 page according to the localization:\n\n```js\nwindow.$docsify = {\n  notFoundPage: {\n    '/': '_404.md',\n    '/de': 'de/_404.md',\n  },\n};\n```\n\n> Note: The options for fallbackLanguages don't work with the `notFoundPage` options.\n\n## onlyCover\n\n- Type: `Boolean`\n- Default: `false`\n\nOnly coverpage is loaded when visiting the home page.\n\n```js\nwindow.$docsify = {\n  onlyCover: false,\n};\n```\n\n## plugins\n\nSee [Plugins](./plugins.md).\n\n## relativePath\n\n- Type: `Boolean`\n- Default: `false`\n\nIf **true**, links are relative to the current context.\n\nFor example, the directory structure is as follows:\n\n```text\n.\n└── docs\n    ├── README.md\n    ├── guide.md\n    └── zh-cn\n        ├── README.md\n        ├── guide.md\n        └── config\n            └── example.md\n```\n\nWith relative path **enabled** and current URL `http://domain.com/zh-cn/README`, given links will resolve to:\n\n```text\nguide.md              => http://domain.com/zh-cn/guide\nconfig/example.md     => http://domain.com/zh-cn/config/example\n../README.md          => http://domain.com/README\n/README.md            => http://domain.com/README\n```\n\n```js\nwindow.$docsify = {\n  // Relative path enabled\n  relativePath: true,\n\n  // Relative path disabled (default value)\n  relativePath: false,\n};\n```\n\n## repo\n\n- Type: `String`\n\nConfigure the repository url, or a string of `username/repo`, to add the [GitHub Corner](http://tholman.com/github-corners/) widget in the top right corner of the site.\n\n```js\nwindow.$docsify = {\n  repo: 'docsifyjs/docsify',\n  // or\n  repo: 'https://github.com/docsifyjs/docsify/',\n};\n```\n\nIf undefined or empty, no GitHub corner will be displayed.\n\n## requestHeaders\n\n- Type: `Object`\n\nSet the request resource headers.\n\n```js\nwindow.$docsify = {\n  requestHeaders: {\n    'x-token': 'xxx',\n  },\n};\n```\n\nSuch as setting the cache\n\n```js\nwindow.$docsify = {\n  requestHeaders: {\n    'cache-control': 'max-age=600',\n  },\n};\n```\n\n## routerMode\n\nConfigure the URL format that the paths of your site will use.\n\n- Type: `String`\n- Default: `'hash'`\n\n```js\nwindow.$docsify = {\n  routerMode: 'history', // default: 'hash'\n};\n```\n\nFor statically-deployed sites (f.e. on GitHub Pages) hash-based routing is\nsimpler to set up. For websites that can re-write URLs, the history-based format\nis better (especially for search-engine optimization, hash-based routing is not\nso search-engine friendly)\n\nHash-based routing means all URL paths will be prefixed with `/#/` in the\naddress bar. This is a trick that allows the site to load `/index.html`, then it\nuses the path that follows the `#` to determine what markdown files to load. For\nexample, a complete hash-based URL may look like this:\n`https://example.com/#/path/to/page`. The browser will actually load\n`https://example.com` (assuming your static server serves\n`index.html` by default, as most do), and then the Docsify JavaScript code will\nlook at the `/#/...` and determine the markdown file to load and render.\nAdditionally, when clicking on a link, the Docsify router will change the\ncontent after the hash dynamically. The value of `location.pathname` will still be\n`/` no matter what. The parts of a hash path are _not_ sent to the server when\nvisiting such a URL in a browser.\n\nOn the other hand, history-based routing means the Docsify JavaScript will use\nthe [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API)\nto dynamically change the URL without using a `#`. This means that all URLs will\nbe considered \"real\" by search engines, and the full path will be sent to the\nserver when visiting the URL in your browser. For example, a URL may look like\n`https://example.com/path/to/page`. The browser will try to load that full URL\ndirectly from the server, not just `https://example.com`. The upside of this is\nthat these types of URLs are much more friendly for search engines, and can be\nindexed (yay!). The downside, however, is that your server, or the place where\nyou host your site files, has to be able to handle these URLs. Various static\nwebsite hosting services allow \"rewrite rules\" to be configured, such that a\nserver can be configured to always send back `/index.html` no matter what path\nis visited. The value of `location.pathname` will show `/path/to/page`, because\nit was actually sent to the server.\n\nTLDR: start with `hash` routing (the default). If you feel adventurous, learn\nhow to configure a server, then switch to `history` mode for better experience\nwithout the `#` in the URL and SEO optimization.\n\n> **Note** If you use `routerMode: 'history'`, you may want to add an\n> [`alias`](#alias) to make your `_sidebar.md` and `_navbar.md` files always be\n> loaded no matter which path is being visited.\n>\n> ```js\n> window.$docsify = {\n>   routerMode: 'history',\n>   alias: {\n>     '/.*/_sidebar.md': '/_sidebar.md',\n>     '/.*/_navbar.md': '/_navbar.md',\n>   },\n> };\n> ```\n\n## routes\n\n- Type: `Object`\n\nDefine \"virtual\" routes that can provide content dynamically. A route is a map between the expected path, to either a string or a function. If the mapped value is a string, it is treated as markdown and parsed accordingly. If it is a function, it is expected to return markdown content.\n\nA route function receives up to three parameters:\n\n1. `route` - the path of the route that was requested (e.g. `/bar/baz`)\n2. `matched` - the [`RegExpMatchArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match) that was matched by the route (e.g. for `/bar/(.+)`, you get `['/bar/baz', 'baz']`)\n3. `next` - this is a callback that you may call when your route function is async\n\nDo note that order matters! Routes are matched the same order you declare them in, which means that in cases where you have overlapping routes, you might want to list the more specific ones first.\n\n```js\nwindow.$docsify = {\n  routes: {\n    // Basic match w/ return string\n    '/foo': '# Custom Markdown',\n\n    // RegEx match w/ synchronous function\n    '/bar/(.*)'(route, matched) {\n      return '# Custom Markdown';\n    },\n\n    // RegEx match w/ asynchronous function\n    '/baz/(.*)'(route, matched, next) {\n      fetch('/api/users?id=12345')\n        .then(response => {\n          next('# Custom Markdown');\n        })\n        .catch(err => {\n          // Handle error...\n        });\n    },\n  },\n};\n```\n\nOther than strings, route functions can return a falsy value (`null` \\ `undefined`) to indicate that they ignore the current request:\n\n```js\nwindow.$docsify = {\n  routes: {\n    // accepts everything other than dogs (synchronous)\n    '/pets/(.+)'(route, matched) {\n      if (matched[0] === 'dogs') {\n        return null;\n      } else {\n        return 'I like all pets but dogs';\n      }\n    }\n\n    // accepts everything other than cats (asynchronous)\n    '/pets/(.*)'(route, matched, next) {\n      if (matched[0] === 'cats') {\n        next();\n      } else {\n        // Async task(s)...\n        next('I like all pets but cats');\n      }\n    }\n  }\n}\n```\n\nFinally, if you have a specific path that has a real markdown file (and therefore should not be matched by your route), you can opt it out by returning an explicit `false` value:\n\n```js\nwindow.$docsify = {\n  routes: {\n    // if you look up /pets/cats, docsify will skip all routes and look for \"pets/cats.md\"\n    '/pets/cats'(route, matched) {\n      return false;\n    }\n\n    // but any other pet should generate dynamic content right here\n    '/pets/(.+)'(route, matched) {\n      const pet = matched[0];\n      return `your pet is ${pet} (but not a cat)`;\n    }\n  }\n}\n```\n\n## skipLink\n\n- Type: `Boolean|String|Object`\n- Default: `'Skip to main content'`\n\nDetermines if/how the site's [skip navigation link](https://webaim.org/techniques/skipnav/) will be rendered.\n\n```js\n// Render skip link for all routes\nwindow.$docsify = {\n  skipLink: 'Skip to content',\n};\n```\n\n```js\n// Render localized skip links based on route paths\nwindow.$docsify = {\n  skipLink: {\n    '/es/': 'Saltar al contenido principal',\n    '/de-de/': 'Ga naar de hoofdinhoud',\n    '/ru-ru/': 'Перейти к основному содержанию',\n    '/zh-cn/': '跳到主要内容',\n  },\n};\n```\n\n```js\n// Do not render skip link\nwindow.$docsify = {\n  skipLink: false,\n};\n```\n\n```js\n// Use default\nwindow.$docsify = {\n  skipLink: true, // \"Skip to main content\"\n};\n```\n\n## subMaxLevel\n\n- Type: `Number`\n- Default: `0`\n\nAdd table of contents (TOC) in custom sidebar.\n\n```js\nwindow.$docsify = {\n  subMaxLevel: 2,\n};\n```\n\nIf you have a link to the homepage in the sidebar and want it to be shown as active when accessing the root url, make sure to update your sidebar accordingly:\n\n```markdown\n- Sidebar\n  - [Home](/)\n  - [Another page](another.md)\n```\n\nFor more details, see [#1131](https://github.com/docsifyjs/docsify/issues/1131).\n\n## themeColor ⚠️ :id=themecolor\n\n> [!IMPORTANT] Deprecated as of v5. Use the `--theme-color` [theme property](themes#theme-properties) to [customize](themes#customization) your theme color.\n\n- Type: `String`\n\nCustomize the theme color.\n\n```js\nwindow.$docsify = {\n  themeColor: '#3F51B5',\n};\n```\n\n## topMargin ⚠️ :id=topmargin\n\n> [!IMPORTANT] Deprecated as of v5. Use the `--scroll-padding-top` [theme property](themes#theme-properties) to specify a scroll margin when using a sticky navbar.\n\n- Type: `Number|String`\n- Default: `0`\n\nAdds scroll padding to the top of the viewport. This is useful when you have added a sticky or \"fixed\" element and would like auto scrolling to align with the bottom of your element.\n\n```js\nwindow.$docsify = {\n  topMargin: 90, // 90, '90px', '2rem', etc.\n};\n```\n\n## vueComponents\n\n- Type: `Object`\n\nCreates and registers global [Vue](https://vuejs.org/guide/essentials/component-basics.html). Components are specified using the component name as the key with an object containing Vue options as the value. Component `data` is unique for each instance and will not persist as users navigate the site.\n\n```js\nwindow.$docsify = {\n  vueComponents: {\n    'button-counter': {\n      template: `\n        <button @click=\"count += 1\">\n          You clicked me {{ count }} times\n        </button>\n      `,\n      data() {\n        return {\n          count: 0,\n        };\n      },\n    },\n  },\n};\n```\n\n```markdown\n<button-counter></button-counter>\n```\n\n<output data-lang=\"output\">\n  <button-counter></button-counter>\n</output>\n\n## vueGlobalOptions\n\n- Type: `Object`\n\nSpecifies global Vue options for use with Vue content not explicitly mounted with [vueMounts](#mounting-dom-elements), [vueComponents](#components), or a [markdown script](#markdown-script). Changes to global `data` will persist and be reflected anywhere global references are used.\n\n```js\nwindow.$docsify = {\n  vueGlobalOptions: {\n    data() {\n      return {\n        count: 0,\n      };\n    },\n  },\n};\n```\n\n```markdown\n<p>\n  <button @click=\"count -= 1\">-</button>\n  {{ count }}\n  <button @click=\"count += 1\">+</button>\n</p>\n```\n\n<output data-lang=\"output\">\n  <p>\n    <button @click=\"count -= 1\">-</button>\n    {{ count }}\n    <button @click=\"count += 1\">+</button>\n  </p>\n</output>\n\n## vueMounts\n\n- Type: `Object`\n\nSpecifies DOM elements to mount as Vue instances and their associated options. Mount elements are specified using a [CSS selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) as the key with an object containing Vue options as their value. Docsify will mount the first matching element in the main content area each time a new page is loaded. Mount element `data` is unique for each instance and will not persist as users navigate the site.\n\n```js\nwindow.$docsify = {\n  vueMounts: {\n    '#counter': {\n      data() {\n        return {\n          count: 0,\n        };\n      },\n    },\n  },\n};\n```\n\n```markdown\n<div id=\"counter\">\n  <button @click=\"count -= 1\">-</button>\n  {{ count }}\n  <button @click=\"count += 1\">+</button>\n</div>\n```\n\n<output id=\"counter\">\n  <button @click=\"count -= 1\">-</button>\n  {{ count }}\n  <button @click=\"count += 1\">+</button>\n</output>\n"
  },
  {
    "path": "docs/cover.md",
    "content": "# Cover\n\nActivate the cover feature by setting `coverpage` to **true**. See [coverpage configuration](configuration#coverpage).\n\n## Basic usage\n\nSet `coverpage` to **true**, and create a `_coverpage.md`:\n\n```js\nwindow.$docsify = {\n  coverpage: true,\n};\n```\n\n```markdown\n<!-- _coverpage.md -->\n\n![logo](_media/icon.svg)\n\n# docsify\n\n> A magical documentation site generator\n\n- Simple and lightweight\n- No statically built HTML files\n- Multiple themes\n\n[GitHub](https://github.com/docsifyjs/docsify/)\n[Get Started](#docsify)\n```\n\n## Customization\n\nThe cover page can be customized using [theme properties](themes#theme-properties):\n\n<!-- prettier-ignore -->\n```css\n:root {\n  --cover-bg         : url('path/to/image.png');\n  --cover-bg-overlay : rgba(0, 0, 0, 0.5);\n  --cover-color      : #fff;\n  --cover-title-color: var(--theme-color);\n  --cover-title-font : 600 var(--font-size-xxxl) var(--font-family);\n}\n```\n\nAlternatively, a background color or image can be specified in the cover page markdown.\n\n```markdown\n<!-- background color -->\n\n![color](#f0f0f0)\n```\n\n```markdown\n<!-- background image -->\n\n![](_media/bg.png)\n```\n\n## Coverpage as homepage\n\nNormally, the coverpage and the homepage appear at the same time. Of course, you can also separate the coverpage by [`onlyCover`](configuration#onlycover) option.\n\n## Multiple covers\n\nIf your docs site is in more than one language, it may be useful to set multiple covers.\n\nFor example, your docs structure is like this\n\n```text\n.\n└── docs\n    ├── README.md\n    ├── guide.md\n    ├── _coverpage.md\n    └── zh-cn\n        ├── README.md\n        └── guide.md\n        └── _coverpage.md\n```\n\nNow, you can set\n\n```js\nwindow.$docsify = {\n  coverpage: ['/', '/zh-cn/'],\n};\n```\n\nOr a special file name\n\n```js\nwindow.$docsify = {\n  coverpage: {\n    '/': 'cover.md',\n    '/zh-cn/': 'cover.md',\n  },\n};\n```\n"
  },
  {
    "path": "docs/custom-navbar.md",
    "content": "# Custom navbar\n\n## HTML\n\nIf you need custom navigation, you can create a HTML-based navigation bar.\n\n> [!IMPORTANT] Note that documentation links begin with `#/`.\n\n```html\n<!-- index.html -->\n\n<body>\n  <nav>\n    <a href=\"#/\">EN</a>\n    <a href=\"#/zh-cn/\">简体中文</a>\n  </nav>\n  <div id=\"app\"></div>\n</body>\n```\n\n## Markdown\n\nAlternatively, you can create a custom markdown-based navigation file by setting `loadNavbar` to **true** and creating `_navbar.md`, compare [loadNavbar configuration](configuration#loadnavbar).\n\n```html\n<!-- index.html -->\n\n<script>\n  window.$docsify = {\n    loadNavbar: true,\n  };\n</script>\n<script src=\"//cdn.jsdelivr.net/npm/docsify@5/dist/docsify.min.js\"></script>\n```\n\n```markdown\n<!-- _navbar.md -->\n\n- [En](/)\n- [chinese](/zh-cn/)\n```\n\nTo create drop-down menus:\n\n```markdown\n<!-- _navbar.md -->\n\n- Translations\n\n  - [En](/)\n  - [chinese](/zh-cn/)\n```\n\n> [!IMPORTANT] You need to create a `.nojekyll` in `./docs` to prevent GitHub Pages from ignoring files that begin with an underscore.\n\n`_navbar.md` is loaded from each level directory. If the current directory doesn't have `_navbar.md`, it will fall back to the parent directory. If, for example, the current path is `/guide/quick-start`, the `_navbar.md` will be loaded from `/guide/_navbar.md`.\n\n## Nesting\n\nYou can create sub-lists by indenting items that are under a certain parent.\n\n```markdown\n<!-- _navbar.md -->\n\n- Getting started\n\n  - [Quick start](quickstart.md)\n  - [Writing more pages](more-pages.md)\n  - [Custom navbar](custom-navbar.md)\n  - [Cover page](cover.md)\n\n- Configuration\n\n  - [Configuration](configuration.md)\n  - [Themes](themes.md)\n  - [Using plugins](plugins.md)\n  - [Markdown configuration](markdown.md)\n  - [Language highlight](language-highlight.md)\n```\n\nrenders as\n\n![Nesting navbar](_images/nested-navbar.png 'Nesting navbar')\n\n## Combining custom navbars with the emoji plugin\n\nIf you use the [emoji plugin](plugins#emoji):\n\n```html\n<!-- index.html -->\n\n<script>\n  window.$docsify = {\n    // ...\n  };\n</script>\n<script src=\"//cdn.jsdelivr.net/npm/docsify@5/dist/docsify.min.js\"></script>\n<script src=\"//cdn.jsdelivr.net/npm/docsify@5/dist/plugins/emoji.min.js\"></script>\n```\n\nyou could, for example, use flag emojis in your custom navbar Markdown file:\n\n```markdown\n<!-- _navbar.md -->\n\n- [:us:, :uk:](/)\n- [:cn:](/zh-cn/)\n```\n"
  },
  {
    "path": "docs/deploy.md",
    "content": "# Deploy\n\nSimilar to [GitBook](https://www.gitbook.com), you can deploy files to GitHub Pages, GitLab Pages or VPS.\n\n## GitHub Pages\n\nThere are three places to populate your docs for your GitHub repository:\n\n- `docs/` folder\n- main branch\n- gh-pages branch\n\nIt is recommended that you save your files to the `./docs` subfolder of the `main` branch of your repository. Then select `main branch /docs folder` as your GitHub Pages source in your repository's settings page.\n\n![GitHub Pages](_images/deploy-github-pages.png)\n\n> [!IMPORTANT] You can also save files in the root directory and select `main branch`.\n> You'll need to place a `.nojekyll` file in the deploy location (such as `/docs` or the gh-pages branch)\n\n## GitLab Pages\n\nIf you are deploying your master branch, create a `.gitlab-ci.yml` with the following script:\n\n> [!TIP] The `.public` workaround is so `cp` doesn't also copy `public/` to itself in an infinite loop.\n\n```YAML\npages:\n  stage: deploy\n  script:\n  - mkdir .public\n  - cp -r * .public\n  - mv .public public\n  artifacts:\n    paths:\n    - public\n  only:\n  - master\n```\n\n> [!IMPORTANT] You can replace script with `- cp -r docs/. public`, if `./docs` is your Docsify subfolder.\n\n## Firebase Hosting\n\n> [!IMPORTANT] You'll need to install the Firebase CLI using `npm i -g firebase-tools` after signing into the [Firebase Console](https://console.firebase.google.com) using a Google Account.\n\nUsing a terminal, determine and navigate to the directory for your Firebase Project. This could be `~/Projects/Docs`, etc. From there, run `firebase init` and choose `Hosting` from the menu (use **space** to select, **arrow keys** to change options and **enter** to confirm). Follow the setup instructions.\n\nYour `firebase.json` file should look similar to this (I changed the deployment directory from `public` to `site`):\n\n```json\n{\n  \"hosting\": {\n    \"public\": \"site\",\n    \"ignore\": [\"firebase.json\", \"**/.*\", \"**/node_modules/**\"]\n  }\n}\n```\n\nOnce finished, build the starting template by running `docsify init ./site` (replacing site with the deployment directory you determined when running `firebase init` - public by default). Add/edit the documentation, then run `firebase deploy` from the root project directory.\n\n## VPS\n\nUse the following nginx config.\n\n```nginx\nserver {\n  listen 80;\n  server_name  your.domain.com;\n\n  location / {\n    alias /path/to/dir/of/docs/;\n    index index.html;\n  }\n}\n```\n\n## Netlify\n\n1.  Login to your [Netlify](https://www.netlify.com/) account.\n2.  In the [dashboard](https://app.netlify.com/) page, click **Add New Site**.\n3.  Select GitHub.\n4.  Choose the repository where you store your docs, in the **Base Directory** add the subfolder where the files are stored. For example, it should be `docs`.\n5.  In the **Build Command** area leave it blank.\n6.  In the **Publish directory** area, if you have added the `docs` in the **Base Directory** you will see the publish directory populated with `docs/`\n7.  Netlify is smart enough to look for the the `index.html` file inside the `docs/` folder.\n\n### HTML5 router\n\nWhen using the HTML5 router, you need to set up redirect rules that redirect all requests to your `index.html`. It's pretty simple when you're using Netlify. Just create a file named `_redirects` in the docs directory, add this snippet to the file, and you're all set:\n\n```sh\n/*    /index.html   200\n```\n\n## Vercel\n\n1. Install [Vercel CLI](https://vercel.com/download), `npm i -g vercel`\n2. Change directory to your docsify website, for example `cd docs`\n3. Deploy with a single command, `vercel`\n\n## AWS Amplify\n\n1. Set the routerMode in the Docsify project `index.html` to _history_ mode.\n\n```html\n<script>\n  window.$docsify = {\n    loadSidebar: true,\n    routerMode: 'history',\n  };\n</script>\n```\n\n2. Login to your [AWS Console](https://aws.amazon.com).\n3. Go to the [AWS Amplify Dashboard](https://aws.amazon.com/amplify).\n4. Choose the **Deploy** route to setup your project.\n5. When prompted, keep the build settings empty if you're serving your docs within the root directory. If you're serving your docs from a different directory, customise your amplify.yml\n\n```yml\nversion: 0.1\nfrontend:\n  phases:\n    build:\n      commands:\n        - echo \"Nothing to build\"\n  artifacts:\n    baseDirectory: /docs\n    files:\n      - '**/*'\n  cache:\n    paths: []\n```\n\n6. Add the following Redirect rules in their displayed order. Note that the second record is a PNG image where you can change it with any image format you are using.\n\n| Source address | Target address | Type          |\n| -------------- | -------------- | ------------- |\n| /<\\*>.md       | /<\\*>.md       | 200 (Rewrite) |\n| /<\\*>.png      | /<\\*>.png      | 200 (Rewrite) |\n| /<\\*>          | /index.html    | 200 (Rewrite) |\n\n## Stormkit\n\n1.  Login to your [Stormkit](https://www.stormkit.io) account.\n2.  Using the user interface, import your docsify project from one of the three supported Git providers (GitHub, GitLab, or Bitbucket).\n3.  Navigate to the project’s production environment in Stormkit or create a new environment if needed.\n4.  Verify the build command in your Stormkit configuration. By default, Stormkit CI will run `npm run build` but you can specify a custom build command on this page.\n5.  Set output folder to `docs`\n6.  Click the “Deploy Now” button to deploy your site.\n\nRead more in the [Stormkit Documentation](https://stormkit.io/docs).\n\n## Docker\n\n- Create docsify files\n\n  You need prepare the initial files instead of making them inside the container.\n  See the [Quickstart](https://docsify.js.org/#/quickstart) section for instructions on how to create these files manually or using [docsify-cli](https://github.com/docsifyjs/docsify-cli).\n\n  ```sh\n  index.html\n  README.md\n  ```\n\n- Create Dockerfile\n\n  ```Dockerfile\n    FROM node:latest\n    LABEL description=\"A demo Dockerfile for build Docsify.\"\n    WORKDIR /docs\n    RUN npm install -g docsify-cli@latest\n    EXPOSE 3000/tcp\n    ENTRYPOINT docsify serve .\n\n  ```\n\n  The current directory structure should be this:\n\n  ```sh\n   index.html\n   README.md\n   Dockerfile\n  ```\n\n- Build docker image\n\n  ```sh\n  docker build -f Dockerfile -t docsify/demo .\n  ```\n\n- Run docker image\n\n  ```sh\n  docker run -itp 3000:3000 --name=docsify -v $(pwd):/docs docsify/demo\n  ```\n\n## Kinsta Static Site Hosting\n\nYou can deploy **Docsify** as a Static Site on [Kinsta](https://kinsta.com/static-site-hosting/).\n\n1. Login or create an account to view your [MyKinsta](https://my.kinsta.com/) dashboard.\n\n2. Authorize Kinsta with your Git provider.\n\n3. Select **Static Sites** from the left sidebar and press **Add sites**.\n\n4. Select the repository and branch you want to deploy.\n\n5. During the build settings, Kinsta will automatically try to fill out the **Build command**, **Node version**, and **Publish directory**. If it won't, fill out the following:\n\n   - Build command: leave empty\n   - Node version: leave on default selection or a specific version (e.g. `18.16.0`)\n   - Publish directory: `docs`\n\n6. Click the **Create site**.\n\n## DeployHQ\n\n[DeployHQ](https://www.deployhq.com/) is a deployment automation platform that deploys your code to SSH/SFTP servers, FTP servers, cloud storage (Amazon S3, Cloudflare R2), and modern hosting platforms (Netlify, Heroku).\n\n> [!IMPORTANT] DeployHQ does not host your site. It automates deploying your Docsify files to your chosen hosting provider or server.\n\nTo deploy your Docsify site using DeployHQ:\n\n1. Sign up for a [DeployHQ account](https://www.deployhq.com/) and verify your email.\n\n2. Create your first project by clicking on **Projects** and **New Project**. Connect your Git repository (GitHub, GitLab, Bitbucket, or any private repository). Authorize DeployHQ to access your repository.\n\n3. Add a server and enter your server details:\n\n   - Give your server a name\n   - Select your protocol (SSH/SFTP, FTP, or cloud platform)\n   - Enter your server hostname, username, and password/SSH key\n   - Set **Deployment Path** to your web root (e.g., `public_html/`)\n\n4. Since Docsify doesn't require a build step, you can deploy your files directly. If your Docsify files are in a `docs/` folder, configure the **Source Path** in your server settings to `docs/`.\n\n5. Click **Deploy Project**, then select your server and click **Deploy** to start your first deployment.\n\nYour Docsify site will be deployed to your server. You can enable automatic deployments to deploy on every Git push, or schedule deployments for specific times.\n\nFor more information on advanced deployment features, see [DeployHQ's documentation](https://www.deployhq.com/support).\n"
  },
  {
    "path": "docs/embed-files.md",
    "content": "# Embed files\n\nWith docsify 4.6 it is now possible to embed any type of file.\n\nYou can embed these files as video, audio, iframes, or code blocks, and even Markdown files can even be embedded directly into the document.\n\nFor example, here is an embedded Markdown file. You only need to do this:\n\n```markdown\n[filename](_media/example.md ':include')\n```\n\nThen the content of `example.md` will be displayed directly here:\n\n[filename](_media/example.md ':include')\n\nYou can check the original content for [example.md](_media/example.md ':ignore').\n\nNormally, this will be compiled into a link, but in docsify, if you add `:include` it will be embedded. You can use single or double quotation marks around as you like.\n\nExternal links can be used too - just replace the target. If you want to use a gist URL, see [Embed a gist](#embed-a-gist) section.\n\n## Embedded file type\n\nCurrently, file extensions are automatically recognized and embedded in different ways.\n\nThese types are supported:\n\n- **iframe** `.html`, `.htm`\n- **markdown** `.markdown`, `.md`\n- **audio** `.mp3`\n- **video** `.mp4`, `.ogg`\n- **code** other file extension\n\nOf course, you can force the specified type. For example, a Markdown file can be embedded as a code block by setting `:type=code`.\n\n```markdown\n[filename](_media/example.md ':include :type=code')\n```\n\nYou will get:\n\n[filename](_media/example.md ':include :type=code')\n\n## Markdown with YAML Front Matter\n\nFront Matter, commonly utilized in blogging systems like Jekyll, serves to define metadata for a document. The [front-matter.js](https://www.npmjs.com/package/front-matter) package facilitates the extraction of metadata (front matter) from documents.\n\nWhen using Markdown, YAML front matter will be stripped from the rendered content. The attributes cannot be used in this case.\n\n```markdown\n[filename](_media/example-with-yaml.md ':include')\n```\n\nYou will get just the content\n\n[filename](_media/example-with-yaml.md ':include')\n\n## Embedded code fragments\n\nSometimes you don't want to embed a whole file. Maybe because you need just a few lines but you want to compile and test the file in CI.\n\n```markdown\n[filename](_media/example.js ':include :type=code :fragment=demo')\n```\n\nIn your code file you need to surround the fragment between `/// [demo]` lines (before and after the fragment).\nAlternatively you can use `### [demo]`. By default, only identifiers are omitted. To omit the entire line containing the identifier in the fragment output, add the `:omitFragmentLine` option. This is useful if your code fragment is e.g. HTML and you want to hide the Docsify fragment identifier from showing in your HTML source. `<!-- /// [demo] -->` in your source file and `:omitFragmentLine` will make the `-->` not show up in your Docsify code fragment section.\n\nExample: In the source file \\_media/example.js, `/// [demo]` identifiers have been included:\n\n```markdown\n[filename](_media/example.js ':include :type=code')\n```\n\n[filename](_media/example.js ':include :type=code')\n\nAdding the `:fragment=demo` results in the following:\n\n```markdown\n[filename](_media/example.js ':include :type=code :fragment=demo')\n```\n\n[filename](_media/example.js ':include :type=code :fragment=demo')\n\n## Tag attribute\n\nIf you embed the file as `iframe`, `audio` and `video`, then you may need to set the attributes of these tags.\n\n> [!TIP] Note, for the `audio` and `video` types, docsify adds the `controls` attribute by default. When you want add more attributes, the `controls` attribute need to be added manually if need be.\n\n```md\n[filename](_media/example.mp4 ':include :type=video controls width=100%')\n```\n\n```markdown\n[cinwell website](https://cinwell.com ':include :type=iframe width=100% height=400px')\n```\n\n[cinwell website](https://cinwell.com ':include :type=iframe width=100% height=400px')\n\nDid you see it? You only need to write directly. You can check [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe) for these attributes.\n\n## The code block highlight\n\nEmbedding any type of source code file, you can specify the highlighted language or automatically identify.\n\n```markdown\n[](_media/example.html ':include :type=code text')\n```\n\n⬇️\n\n[](_media/example.html ':include :type=code text')\n\n> [!TIP] How to set highlight? You can see [here](language-highlight.md).\n\n## Embed a gist\n\nYou can embed a gist as markdown content or as a code block - this is based on the approach at the start of [Embed Files](#embed-files) section, but uses a raw gist URL as the target.\n\n> [!TIP] **No** plugin or app config change is needed here to make this work. In fact, the \"Embed\" `script` tag that is copied from a gist will _not_ load even if you make plugin or config changes to allow an external script.\n\n### Identify the gist's metadata\n\nStart by viewing a gist on `gist.github.com`. For the purposes of this guide, we use this gist:\n\n- https://gist.github.com/anikethsaha/f88893bb563bb7229d6e575db53a8c15\n\nIdentify the following items from the gist:\n\n| Field        | Example                            | Description                                                                                        |\n| ------------ | ---------------------------------- | -------------------------------------------------------------------------------------------------- |\n| **Username** | `anikethsaha`                      | The gist's owner.                                                                                  |\n| **Gist ID**  | `c2bece08f27c4277001f123898d16a7c` | Identifier for the gist. This is fixed for the gist's lifetime.                                    |\n| **Filename** | `content.md`                       | Select a name of a file in the gist. This needed even on a single-file gist for embedding to work. |\n\nYou will need those to build the _raw gist URL_ for the target file. This has the following format:\n\n- `https://gist.githubusercontent.com/USERNAME/GIST_ID/raw/FILENAME`\n\nHere are two examples based on the sample gist:\n\n- https://gist.githubusercontent.com/anikethsaha/f88893bb563bb7229d6e575db53a8c15/raw/content.md\n- https://gist.githubusercontent.com/anikethsaha/f88893bb563bb7229d6e575db53a8c15/raw/script.js\n\n> [!TIP] Alternatively, you can get a raw URL directly clicking the _Raw_ button on a gist file. But, if you use that approach, just be sure to **remove** the revision number between `raw/` and the filename so that the URL matches the pattern above instead. Otherwise your embedded gist will **not** show the latest content when the gist is updated.\n\nContinue with one of the sections below to embed the gist on a Docsify page.\n\n### Render markdown content from a gist\n\nThis is a great way to embed content **seamlessly** in your docs, without sending someone to an external link. This approach is well-suited to reusing a gist of say installation instructions across doc sites of multiple repos. This approach works equally well with a gist owned by your account or by another user.\n\nHere is the format:\n\n```markdown\n[LABEL](https://gist.githubusercontent.com/USERNAME/GIST_ID/raw/FILENAME ':include')\n```\n\nFor example:\n\n```markdown\n[gist: content.md](https://gist.githubusercontent.com/anikethsaha/f88893bb563bb7229d6e575db53a8c15/raw/content.md ':include')\n```\n\nWhich renders as:\n\n[gist: content.md](https://gist.githubusercontent.com/anikethsaha/f88893bb563bb7229d6e575db53a8c15/raw/content.md ':include')\n\nThe `LABEL` can be any text you want. It acts as a _fallback_ message if the link is broken - so it is useful to repeat the filename here in case you need to fix a broken link. It also makes an embedded element easy to read at a glance.\n\n### Render a codeblock from a gist\n\nThe format is the same as the previous section, but with `:type=code` added to the alt text. As with the [Embedded file type](#embedded-file-type) section, the syntax highlighting will be **inferred** from the extension (e.g. `.js` or `.py`), so you can leave the `type` set as `code`.\n\nHere is the format:\n\n```markdown\n[LABEL](https://gist.githubusercontent.com/USERNAME/GIST_ID/raw/FILENAME ':include :type=code')\n```\n\nFor example:\n\n```markdown\n[gist: script.js](https://gist.githubusercontent.com/anikethsaha/f88893bb563bb7229d6e575db53a8c15/raw/script.js ':include :type=code')\n```\n\nWhich renders as:\n\n[gist: script.js](https://gist.githubusercontent.com/anikethsaha/f88893bb563bb7229d6e575db53a8c15/raw/script.js ':include :type=code')\n"
  },
  {
    "path": "docs/emoji.md",
    "content": "# Emoji\n\nBelow is a complete list of emoji shorthand codes. Docsify can be configured to render emoji using GitHub-style emoji images or native emoji characters using the [`nativeEmoji`](configuration#nativeemoji) configuration option.\n\n<div style=\"display: grid; grid-template-columns: repeat(auto-fill, minmax(15em, 1fr));\">\n\n<!-- START: Auto-generated content (/build/emoji.js) -->\n\n:100: `:100:`\n\n:1234: `:1234:`\n\n:+1: `:+1:`\n\n:-1: `:-1:`\n\n:1st_place_medal: `:1st_place_medal:`\n\n:2nd_place_medal: `:2nd_place_medal:`\n\n:3rd_place_medal: `:3rd_place_medal:`\n\n:8ball: `:8ball:`\n\n:a: `:a:`\n\n:ab: `:ab:`\n\n:abacus: `:abacus:`\n\n:abc: `:abc:`\n\n:abcd: `:abcd:`\n\n:accept: `:accept:`\n\n:accessibility: `:accessibility:`\n\n:accordion: `:accordion:`\n\n:adhesive_bandage: `:adhesive_bandage:`\n\n:adult: `:adult:`\n\n:aerial_tramway: `:aerial_tramway:`\n\n:afghanistan: `:afghanistan:`\n\n:airplane: `:airplane:`\n\n:aland_islands: `:aland_islands:`\n\n:alarm_clock: `:alarm_clock:`\n\n:albania: `:albania:`\n\n:alembic: `:alembic:`\n\n:algeria: `:algeria:`\n\n:alien: `:alien:`\n\n:ambulance: `:ambulance:`\n\n:american_samoa: `:american_samoa:`\n\n:amphora: `:amphora:`\n\n:anatomical_heart: `:anatomical_heart:`\n\n:anchor: `:anchor:`\n\n:andorra: `:andorra:`\n\n:angel: `:angel:`\n\n:anger: `:anger:`\n\n:angola: `:angola:`\n\n:angry: `:angry:`\n\n:anguilla: `:anguilla:`\n\n:anguished: `:anguished:`\n\n:ant: `:ant:`\n\n:antarctica: `:antarctica:`\n\n:antigua_barbuda: `:antigua_barbuda:`\n\n:apple: `:apple:`\n\n:aquarius: `:aquarius:`\n\n:argentina: `:argentina:`\n\n:aries: `:aries:`\n\n:armenia: `:armenia:`\n\n:arrow_backward: `:arrow_backward:`\n\n:arrow_double_down: `:arrow_double_down:`\n\n:arrow_double_up: `:arrow_double_up:`\n\n:arrow_down: `:arrow_down:`\n\n:arrow_down_small: `:arrow_down_small:`\n\n:arrow_forward: `:arrow_forward:`\n\n:arrow_heading_down: `:arrow_heading_down:`\n\n:arrow_heading_up: `:arrow_heading_up:`\n\n:arrow_left: `:arrow_left:`\n\n:arrow_lower_left: `:arrow_lower_left:`\n\n:arrow_lower_right: `:arrow_lower_right:`\n\n:arrow_right: `:arrow_right:`\n\n:arrow_right_hook: `:arrow_right_hook:`\n\n:arrow_up: `:arrow_up:`\n\n:arrow_up_down: `:arrow_up_down:`\n\n:arrow_up_small: `:arrow_up_small:`\n\n:arrow_upper_left: `:arrow_upper_left:`\n\n:arrow_upper_right: `:arrow_upper_right:`\n\n:arrows_clockwise: `:arrows_clockwise:`\n\n:arrows_counterclockwise: `:arrows_counterclockwise:`\n\n:art: `:art:`\n\n:articulated_lorry: `:articulated_lorry:`\n\n:artificial_satellite: `:artificial_satellite:`\n\n:artist: `:artist:`\n\n:aruba: `:aruba:`\n\n:ascension_island: `:ascension_island:`\n\n:asterisk: `:asterisk:`\n\n:astonished: `:astonished:`\n\n:astronaut: `:astronaut:`\n\n:athletic_shoe: `:athletic_shoe:`\n\n:atm: `:atm:`\n\n:atom: `:atom:`\n\n:atom_symbol: `:atom_symbol:`\n\n:australia: `:australia:`\n\n:austria: `:austria:`\n\n:auto_rickshaw: `:auto_rickshaw:`\n\n:avocado: `:avocado:`\n\n:axe: `:axe:`\n\n:azerbaijan: `:azerbaijan:`\n\n:b: `:b:`\n\n:baby: `:baby:`\n\n:baby_bottle: `:baby_bottle:`\n\n:baby_chick: `:baby_chick:`\n\n:baby_symbol: `:baby_symbol:`\n\n:back: `:back:`\n\n:bacon: `:bacon:`\n\n:badger: `:badger:`\n\n:badminton: `:badminton:`\n\n:bagel: `:bagel:`\n\n:baggage_claim: `:baggage_claim:`\n\n:baguette_bread: `:baguette_bread:`\n\n:bahamas: `:bahamas:`\n\n:bahrain: `:bahrain:`\n\n:balance_scale: `:balance_scale:`\n\n:bald_man: `:bald_man:`\n\n:bald_woman: `:bald_woman:`\n\n:ballet_shoes: `:ballet_shoes:`\n\n:balloon: `:balloon:`\n\n:ballot_box: `:ballot_box:`\n\n:ballot_box_with_check: `:ballot_box_with_check:`\n\n:bamboo: `:bamboo:`\n\n:banana: `:banana:`\n\n:bangbang: `:bangbang:`\n\n:bangladesh: `:bangladesh:`\n\n:banjo: `:banjo:`\n\n:bank: `:bank:`\n\n:bar_chart: `:bar_chart:`\n\n:barbados: `:barbados:`\n\n:barber: `:barber:`\n\n:baseball: `:baseball:`\n\n:basecamp: `:basecamp:`\n\n:basecampy: `:basecampy:`\n\n:basket: `:basket:`\n\n:basketball: `:basketball:`\n\n:basketball_man: `:basketball_man:`\n\n:basketball_woman: `:basketball_woman:`\n\n:bat: `:bat:`\n\n:bath: `:bath:`\n\n:bathtub: `:bathtub:`\n\n:battery: `:battery:`\n\n:beach_umbrella: `:beach_umbrella:`\n\n:beans: `:beans:`\n\n:bear: `:bear:`\n\n:bearded_person: `:bearded_person:`\n\n:beaver: `:beaver:`\n\n:bed: `:bed:`\n\n:bee: `:bee:`\n\n:beer: `:beer:`\n\n:beers: `:beers:`\n\n:beetle: `:beetle:`\n\n:beginner: `:beginner:`\n\n:belarus: `:belarus:`\n\n:belgium: `:belgium:`\n\n:belize: `:belize:`\n\n:bell: `:bell:`\n\n:bell_pepper: `:bell_pepper:`\n\n:bellhop_bell: `:bellhop_bell:`\n\n:benin: `:benin:`\n\n:bento: `:bento:`\n\n:bermuda: `:bermuda:`\n\n:beverage_box: `:beverage_box:`\n\n:bhutan: `:bhutan:`\n\n:bicyclist: `:bicyclist:`\n\n:bike: `:bike:`\n\n:biking_man: `:biking_man:`\n\n:biking_woman: `:biking_woman:`\n\n:bikini: `:bikini:`\n\n:billed_cap: `:billed_cap:`\n\n:biohazard: `:biohazard:`\n\n:bird: `:bird:`\n\n:birthday: `:birthday:`\n\n:bison: `:bison:`\n\n:biting_lip: `:biting_lip:`\n\n:black_bird: `:black_bird:`\n\n:black_cat: `:black_cat:`\n\n:black_circle: `:black_circle:`\n\n:black_flag: `:black_flag:`\n\n:black_heart: `:black_heart:`\n\n:black_joker: `:black_joker:`\n\n:black_large_square: `:black_large_square:`\n\n:black_medium_small_square: `:black_medium_small_square:`\n\n:black_medium_square: `:black_medium_square:`\n\n:black_nib: `:black_nib:`\n\n:black_small_square: `:black_small_square:`\n\n:black_square_button: `:black_square_button:`\n\n:blond_haired_man: `:blond_haired_man:`\n\n:blond_haired_person: `:blond_haired_person:`\n\n:blond_haired_woman: `:blond_haired_woman:`\n\n:blonde_woman: `:blonde_woman:`\n\n:blossom: `:blossom:`\n\n:blowfish: `:blowfish:`\n\n:blue_book: `:blue_book:`\n\n:blue_car: `:blue_car:`\n\n:blue_heart: `:blue_heart:`\n\n:blue_square: `:blue_square:`\n\n:blueberries: `:blueberries:`\n\n:blush: `:blush:`\n\n:boar: `:boar:`\n\n:boat: `:boat:`\n\n:bolivia: `:bolivia:`\n\n:bomb: `:bomb:`\n\n:bone: `:bone:`\n\n:book: `:book:`\n\n:bookmark: `:bookmark:`\n\n:bookmark_tabs: `:bookmark_tabs:`\n\n:books: `:books:`\n\n:boom: `:boom:`\n\n:boomerang: `:boomerang:`\n\n:boot: `:boot:`\n\n:bosnia_herzegovina: `:bosnia_herzegovina:`\n\n:botswana: `:botswana:`\n\n:bouncing_ball_man: `:bouncing_ball_man:`\n\n:bouncing_ball_person: `:bouncing_ball_person:`\n\n:bouncing_ball_woman: `:bouncing_ball_woman:`\n\n:bouquet: `:bouquet:`\n\n:bouvet_island: `:bouvet_island:`\n\n:bow: `:bow:`\n\n:bow_and_arrow: `:bow_and_arrow:`\n\n:bowing_man: `:bowing_man:`\n\n:bowing_woman: `:bowing_woman:`\n\n:bowl_with_spoon: `:bowl_with_spoon:`\n\n:bowling: `:bowling:`\n\n:bowtie: `:bowtie:`\n\n:boxing_glove: `:boxing_glove:`\n\n:boy: `:boy:`\n\n:brain: `:brain:`\n\n:brazil: `:brazil:`\n\n:bread: `:bread:`\n\n:breast_feeding: `:breast_feeding:`\n\n:bricks: `:bricks:`\n\n:bride_with_veil: `:bride_with_veil:`\n\n:bridge_at_night: `:bridge_at_night:`\n\n:briefcase: `:briefcase:`\n\n:british_indian_ocean_territory: `:british_indian_ocean_territory:`\n\n:british_virgin_islands: `:british_virgin_islands:`\n\n:broccoli: `:broccoli:`\n\n:broken_heart: `:broken_heart:`\n\n:broom: `:broom:`\n\n:brown_circle: `:brown_circle:`\n\n:brown_heart: `:brown_heart:`\n\n:brown_square: `:brown_square:`\n\n:brunei: `:brunei:`\n\n:bubble_tea: `:bubble_tea:`\n\n:bubbles: `:bubbles:`\n\n:bucket: `:bucket:`\n\n:bug: `:bug:`\n\n:building_construction: `:building_construction:`\n\n:bulb: `:bulb:`\n\n:bulgaria: `:bulgaria:`\n\n:bullettrain_front: `:bullettrain_front:`\n\n:bullettrain_side: `:bullettrain_side:`\n\n:burkina_faso: `:burkina_faso:`\n\n:burrito: `:burrito:`\n\n:burundi: `:burundi:`\n\n:bus: `:bus:`\n\n:business_suit_levitating: `:business_suit_levitating:`\n\n:busstop: `:busstop:`\n\n:bust_in_silhouette: `:bust_in_silhouette:`\n\n:busts_in_silhouette: `:busts_in_silhouette:`\n\n:butter: `:butter:`\n\n:butterfly: `:butterfly:`\n\n:cactus: `:cactus:`\n\n:cake: `:cake:`\n\n:calendar: `:calendar:`\n\n:call_me_hand: `:call_me_hand:`\n\n:calling: `:calling:`\n\n:cambodia: `:cambodia:`\n\n:camel: `:camel:`\n\n:camera: `:camera:`\n\n:camera_flash: `:camera_flash:`\n\n:cameroon: `:cameroon:`\n\n:camping: `:camping:`\n\n:canada: `:canada:`\n\n:canary_islands: `:canary_islands:`\n\n:cancer: `:cancer:`\n\n:candle: `:candle:`\n\n:candy: `:candy:`\n\n:canned_food: `:canned_food:`\n\n:canoe: `:canoe:`\n\n:cape_verde: `:cape_verde:`\n\n:capital_abcd: `:capital_abcd:`\n\n:capricorn: `:capricorn:`\n\n:car: `:car:`\n\n:card_file_box: `:card_file_box:`\n\n:card_index: `:card_index:`\n\n:card_index_dividers: `:card_index_dividers:`\n\n:caribbean_netherlands: `:caribbean_netherlands:`\n\n:carousel_horse: `:carousel_horse:`\n\n:carpentry_saw: `:carpentry_saw:`\n\n:carrot: `:carrot:`\n\n:cartwheeling: `:cartwheeling:`\n\n:cat: `:cat:`\n\n:cat2: `:cat2:`\n\n:cayman_islands: `:cayman_islands:`\n\n:cd: `:cd:`\n\n:central_african_republic: `:central_african_republic:`\n\n:ceuta_melilla: `:ceuta_melilla:`\n\n:chad: `:chad:`\n\n:chains: `:chains:`\n\n:chair: `:chair:`\n\n:champagne: `:champagne:`\n\n:chart: `:chart:`\n\n:chart_with_downwards_trend: `:chart_with_downwards_trend:`\n\n:chart_with_upwards_trend: `:chart_with_upwards_trend:`\n\n:checkered_flag: `:checkered_flag:`\n\n:cheese: `:cheese:`\n\n:cherries: `:cherries:`\n\n:cherry_blossom: `:cherry_blossom:`\n\n:chess_pawn: `:chess_pawn:`\n\n:chestnut: `:chestnut:`\n\n:chicken: `:chicken:`\n\n:child: `:child:`\n\n:children_crossing: `:children_crossing:`\n\n:chile: `:chile:`\n\n:chipmunk: `:chipmunk:`\n\n:chocolate_bar: `:chocolate_bar:`\n\n:chopsticks: `:chopsticks:`\n\n:christmas_island: `:christmas_island:`\n\n:christmas_tree: `:christmas_tree:`\n\n:church: `:church:`\n\n:cinema: `:cinema:`\n\n:circus_tent: `:circus_tent:`\n\n:city_sunrise: `:city_sunrise:`\n\n:city_sunset: `:city_sunset:`\n\n:cityscape: `:cityscape:`\n\n:cl: `:cl:`\n\n:clamp: `:clamp:`\n\n:clap: `:clap:`\n\n:clapper: `:clapper:`\n\n:classical_building: `:classical_building:`\n\n:climbing: `:climbing:`\n\n:climbing_man: `:climbing_man:`\n\n:climbing_woman: `:climbing_woman:`\n\n:clinking_glasses: `:clinking_glasses:`\n\n:clipboard: `:clipboard:`\n\n:clipperton_island: `:clipperton_island:`\n\n:clock1: `:clock1:`\n\n:clock10: `:clock10:`\n\n:clock1030: `:clock1030:`\n\n:clock11: `:clock11:`\n\n:clock1130: `:clock1130:`\n\n:clock12: `:clock12:`\n\n:clock1230: `:clock1230:`\n\n:clock130: `:clock130:`\n\n:clock2: `:clock2:`\n\n:clock230: `:clock230:`\n\n:clock3: `:clock3:`\n\n:clock330: `:clock330:`\n\n:clock4: `:clock4:`\n\n:clock430: `:clock430:`\n\n:clock5: `:clock5:`\n\n:clock530: `:clock530:`\n\n:clock6: `:clock6:`\n\n:clock630: `:clock630:`\n\n:clock7: `:clock7:`\n\n:clock730: `:clock730:`\n\n:clock8: `:clock8:`\n\n:clock830: `:clock830:`\n\n:clock9: `:clock9:`\n\n:clock930: `:clock930:`\n\n:closed_book: `:closed_book:`\n\n:closed_lock_with_key: `:closed_lock_with_key:`\n\n:closed_umbrella: `:closed_umbrella:`\n\n:cloud: `:cloud:`\n\n:cloud_with_lightning: `:cloud_with_lightning:`\n\n:cloud_with_lightning_and_rain: `:cloud_with_lightning_and_rain:`\n\n:cloud_with_rain: `:cloud_with_rain:`\n\n:cloud_with_snow: `:cloud_with_snow:`\n\n:clown_face: `:clown_face:`\n\n:clubs: `:clubs:`\n\n:cn: `:cn:`\n\n:coat: `:coat:`\n\n:cockroach: `:cockroach:`\n\n:cocktail: `:cocktail:`\n\n:coconut: `:coconut:`\n\n:cocos_islands: `:cocos_islands:`\n\n:coffee: `:coffee:`\n\n:coffin: `:coffin:`\n\n:coin: `:coin:`\n\n:cold_face: `:cold_face:`\n\n:cold_sweat: `:cold_sweat:`\n\n:collision: `:collision:`\n\n:colombia: `:colombia:`\n\n:comet: `:comet:`\n\n:comoros: `:comoros:`\n\n:compass: `:compass:`\n\n:computer: `:computer:`\n\n:computer_mouse: `:computer_mouse:`\n\n:confetti_ball: `:confetti_ball:`\n\n:confounded: `:confounded:`\n\n:confused: `:confused:`\n\n:congo_brazzaville: `:congo_brazzaville:`\n\n:congo_kinshasa: `:congo_kinshasa:`\n\n:congratulations: `:congratulations:`\n\n:construction: `:construction:`\n\n:construction_worker: `:construction_worker:`\n\n:construction_worker_man: `:construction_worker_man:`\n\n:construction_worker_woman: `:construction_worker_woman:`\n\n:control_knobs: `:control_knobs:`\n\n:convenience_store: `:convenience_store:`\n\n:cook: `:cook:`\n\n:cook_islands: `:cook_islands:`\n\n:cookie: `:cookie:`\n\n:cool: `:cool:`\n\n:cop: `:cop:`\n\n:copilot: `:copilot:`\n\n:copyright: `:copyright:`\n\n:coral: `:coral:`\n\n:corn: `:corn:`\n\n:costa_rica: `:costa_rica:`\n\n:cote_divoire: `:cote_divoire:`\n\n:couch_and_lamp: `:couch_and_lamp:`\n\n:couple: `:couple:`\n\n:couple_with_heart: `:couple_with_heart:`\n\n:couple_with_heart_man_man: `:couple_with_heart_man_man:`\n\n:couple_with_heart_woman_man: `:couple_with_heart_woman_man:`\n\n:couple_with_heart_woman_woman: `:couple_with_heart_woman_woman:`\n\n:couplekiss: `:couplekiss:`\n\n:couplekiss_man_man: `:couplekiss_man_man:`\n\n:couplekiss_man_woman: `:couplekiss_man_woman:`\n\n:couplekiss_woman_woman: `:couplekiss_woman_woman:`\n\n:cow: `:cow:`\n\n:cow2: `:cow2:`\n\n:cowboy_hat_face: `:cowboy_hat_face:`\n\n:crab: `:crab:`\n\n:crayon: `:crayon:`\n\n:credit_card: `:credit_card:`\n\n:crescent_moon: `:crescent_moon:`\n\n:cricket: `:cricket:`\n\n:cricket_game: `:cricket_game:`\n\n:croatia: `:croatia:`\n\n:crocodile: `:crocodile:`\n\n:croissant: `:croissant:`\n\n:crossed_fingers: `:crossed_fingers:`\n\n:crossed_flags: `:crossed_flags:`\n\n:crossed_swords: `:crossed_swords:`\n\n:crown: `:crown:`\n\n:crutch: `:crutch:`\n\n:cry: `:cry:`\n\n:crying_cat_face: `:crying_cat_face:`\n\n:crystal_ball: `:crystal_ball:`\n\n:cuba: `:cuba:`\n\n:cucumber: `:cucumber:`\n\n:cup_with_straw: `:cup_with_straw:`\n\n:cupcake: `:cupcake:`\n\n:cupid: `:cupid:`\n\n:curacao: `:curacao:`\n\n:curling_stone: `:curling_stone:`\n\n:curly_haired_man: `:curly_haired_man:`\n\n:curly_haired_woman: `:curly_haired_woman:`\n\n:curly_loop: `:curly_loop:`\n\n:currency_exchange: `:currency_exchange:`\n\n:curry: `:curry:`\n\n:cursing_face: `:cursing_face:`\n\n:custard: `:custard:`\n\n:customs: `:customs:`\n\n:cut_of_meat: `:cut_of_meat:`\n\n:cyclone: `:cyclone:`\n\n:cyprus: `:cyprus:`\n\n:czech_republic: `:czech_republic:`\n\n:dagger: `:dagger:`\n\n:dancer: `:dancer:`\n\n:dancers: `:dancers:`\n\n:dancing_men: `:dancing_men:`\n\n:dancing_women: `:dancing_women:`\n\n:dango: `:dango:`\n\n:dark_sunglasses: `:dark_sunglasses:`\n\n:dart: `:dart:`\n\n:dash: `:dash:`\n\n:date: `:date:`\n\n:de: `:de:`\n\n:deaf_man: `:deaf_man:`\n\n:deaf_person: `:deaf_person:`\n\n:deaf_woman: `:deaf_woman:`\n\n:deciduous_tree: `:deciduous_tree:`\n\n:deer: `:deer:`\n\n:denmark: `:denmark:`\n\n:department_store: `:department_store:`\n\n:dependabot: `:dependabot:`\n\n:derelict_house: `:derelict_house:`\n\n:desert: `:desert:`\n\n:desert_island: `:desert_island:`\n\n:desktop_computer: `:desktop_computer:`\n\n:detective: `:detective:`\n\n:diamond_shape_with_a_dot_inside: `:diamond_shape_with_a_dot_inside:`\n\n:diamonds: `:diamonds:`\n\n:diego_garcia: `:diego_garcia:`\n\n:disappointed: `:disappointed:`\n\n:disappointed_relieved: `:disappointed_relieved:`\n\n:disguised_face: `:disguised_face:`\n\n:diving_mask: `:diving_mask:`\n\n:diya_lamp: `:diya_lamp:`\n\n:dizzy: `:dizzy:`\n\n:dizzy_face: `:dizzy_face:`\n\n:djibouti: `:djibouti:`\n\n:dna: `:dna:`\n\n:do_not_litter: `:do_not_litter:`\n\n:dodo: `:dodo:`\n\n:dog: `:dog:`\n\n:dog2: `:dog2:`\n\n:dollar: `:dollar:`\n\n:dolls: `:dolls:`\n\n:dolphin: `:dolphin:`\n\n:dominica: `:dominica:`\n\n:dominican_republic: `:dominican_republic:`\n\n:donkey: `:donkey:`\n\n:door: `:door:`\n\n:dotted_line_face: `:dotted_line_face:`\n\n:doughnut: `:doughnut:`\n\n:dove: `:dove:`\n\n:dragon: `:dragon:`\n\n:dragon_face: `:dragon_face:`\n\n:dress: `:dress:`\n\n:dromedary_camel: `:dromedary_camel:`\n\n:drooling_face: `:drooling_face:`\n\n:drop_of_blood: `:drop_of_blood:`\n\n:droplet: `:droplet:`\n\n:drum: `:drum:`\n\n:duck: `:duck:`\n\n:dumpling: `:dumpling:`\n\n:dvd: `:dvd:`\n\n:e-mail: `:e-mail:`\n\n:eagle: `:eagle:`\n\n:ear: `:ear:`\n\n:ear_of_rice: `:ear_of_rice:`\n\n:ear_with_hearing_aid: `:ear_with_hearing_aid:`\n\n:earth_africa: `:earth_africa:`\n\n:earth_americas: `:earth_americas:`\n\n:earth_asia: `:earth_asia:`\n\n:ecuador: `:ecuador:`\n\n:egg: `:egg:`\n\n:eggplant: `:eggplant:`\n\n:egypt: `:egypt:`\n\n:eight: `:eight:`\n\n:eight_pointed_black_star: `:eight_pointed_black_star:`\n\n:eight_spoked_asterisk: `:eight_spoked_asterisk:`\n\n:eject_button: `:eject_button:`\n\n:el_salvador: `:el_salvador:`\n\n:electric_plug: `:electric_plug:`\n\n:electron: `:electron:`\n\n:elephant: `:elephant:`\n\n:elevator: `:elevator:`\n\n:elf: `:elf:`\n\n:elf_man: `:elf_man:`\n\n:elf_woman: `:elf_woman:`\n\n:email: `:email:`\n\n:empty_nest: `:empty_nest:`\n\n:end: `:end:`\n\n:england: `:england:`\n\n:envelope: `:envelope:`\n\n:envelope_with_arrow: `:envelope_with_arrow:`\n\n:equatorial_guinea: `:equatorial_guinea:`\n\n:eritrea: `:eritrea:`\n\n:es: `:es:`\n\n:estonia: `:estonia:`\n\n:ethiopia: `:ethiopia:`\n\n:eu: `:eu:`\n\n:euro: `:euro:`\n\n:european_castle: `:european_castle:`\n\n:european_post_office: `:european_post_office:`\n\n:european_union: `:european_union:`\n\n:evergreen_tree: `:evergreen_tree:`\n\n:exclamation: `:exclamation:`\n\n:exploding_head: `:exploding_head:`\n\n:expressionless: `:expressionless:`\n\n:eye: `:eye:`\n\n:eye_speech_bubble: `:eye_speech_bubble:`\n\n:eyeglasses: `:eyeglasses:`\n\n:eyes: `:eyes:`\n\n:face_exhaling: `:face_exhaling:`\n\n:face_holding_back_tears: `:face_holding_back_tears:`\n\n:face_in_clouds: `:face_in_clouds:`\n\n:face_with_diagonal_mouth: `:face_with_diagonal_mouth:`\n\n:face_with_head_bandage: `:face_with_head_bandage:`\n\n:face_with_open_eyes_and_hand_over_mouth: `:face_with_open_eyes_and_hand_over_mouth:`\n\n:face_with_peeking_eye: `:face_with_peeking_eye:`\n\n:face_with_spiral_eyes: `:face_with_spiral_eyes:`\n\n:face_with_thermometer: `:face_with_thermometer:`\n\n:facepalm: `:facepalm:`\n\n:facepunch: `:facepunch:`\n\n:factory: `:factory:`\n\n:factory_worker: `:factory_worker:`\n\n:fairy: `:fairy:`\n\n:fairy_man: `:fairy_man:`\n\n:fairy_woman: `:fairy_woman:`\n\n:falafel: `:falafel:`\n\n:falkland_islands: `:falkland_islands:`\n\n:fallen_leaf: `:fallen_leaf:`\n\n:family: `:family:`\n\n:family_man_boy: `:family_man_boy:`\n\n:family_man_boy_boy: `:family_man_boy_boy:`\n\n:family_man_girl: `:family_man_girl:`\n\n:family_man_girl_boy: `:family_man_girl_boy:`\n\n:family_man_girl_girl: `:family_man_girl_girl:`\n\n:family_man_man_boy: `:family_man_man_boy:`\n\n:family_man_man_boy_boy: `:family_man_man_boy_boy:`\n\n:family_man_man_girl: `:family_man_man_girl:`\n\n:family_man_man_girl_boy: `:family_man_man_girl_boy:`\n\n:family_man_man_girl_girl: `:family_man_man_girl_girl:`\n\n:family_man_woman_boy: `:family_man_woman_boy:`\n\n:family_man_woman_boy_boy: `:family_man_woman_boy_boy:`\n\n:family_man_woman_girl: `:family_man_woman_girl:`\n\n:family_man_woman_girl_boy: `:family_man_woman_girl_boy:`\n\n:family_man_woman_girl_girl: `:family_man_woman_girl_girl:`\n\n:family_woman_boy: `:family_woman_boy:`\n\n:family_woman_boy_boy: `:family_woman_boy_boy:`\n\n:family_woman_girl: `:family_woman_girl:`\n\n:family_woman_girl_boy: `:family_woman_girl_boy:`\n\n:family_woman_girl_girl: `:family_woman_girl_girl:`\n\n:family_woman_woman_boy: `:family_woman_woman_boy:`\n\n:family_woman_woman_boy_boy: `:family_woman_woman_boy_boy:`\n\n:family_woman_woman_girl: `:family_woman_woman_girl:`\n\n:family_woman_woman_girl_boy: `:family_woman_woman_girl_boy:`\n\n:family_woman_woman_girl_girl: `:family_woman_woman_girl_girl:`\n\n:farmer: `:farmer:`\n\n:faroe_islands: `:faroe_islands:`\n\n:fast_forward: `:fast_forward:`\n\n:fax: `:fax:`\n\n:fearful: `:fearful:`\n\n:feather: `:feather:`\n\n:feelsgood: `:feelsgood:`\n\n:feet: `:feet:`\n\n:female_detective: `:female_detective:`\n\n:female_sign: `:female_sign:`\n\n:ferris_wheel: `:ferris_wheel:`\n\n:ferry: `:ferry:`\n\n:field_hockey: `:field_hockey:`\n\n:fiji: `:fiji:`\n\n:file_cabinet: `:file_cabinet:`\n\n:file_folder: `:file_folder:`\n\n:film_projector: `:film_projector:`\n\n:film_strip: `:film_strip:`\n\n:finland: `:finland:`\n\n:finnadie: `:finnadie:`\n\n:fire: `:fire:`\n\n:fire_engine: `:fire_engine:`\n\n:fire_extinguisher: `:fire_extinguisher:`\n\n:firecracker: `:firecracker:`\n\n:firefighter: `:firefighter:`\n\n:fireworks: `:fireworks:`\n\n:first_quarter_moon: `:first_quarter_moon:`\n\n:first_quarter_moon_with_face: `:first_quarter_moon_with_face:`\n\n:fish: `:fish:`\n\n:fish_cake: `:fish_cake:`\n\n:fishing_pole_and_fish: `:fishing_pole_and_fish:`\n\n:fishsticks: `:fishsticks:`\n\n:fist: `:fist:`\n\n:fist_left: `:fist_left:`\n\n:fist_oncoming: `:fist_oncoming:`\n\n:fist_raised: `:fist_raised:`\n\n:fist_right: `:fist_right:`\n\n:five: `:five:`\n\n:flags: `:flags:`\n\n:flamingo: `:flamingo:`\n\n:flashlight: `:flashlight:`\n\n:flat_shoe: `:flat_shoe:`\n\n:flatbread: `:flatbread:`\n\n:fleur_de_lis: `:fleur_de_lis:`\n\n:flight_arrival: `:flight_arrival:`\n\n:flight_departure: `:flight_departure:`\n\n:flipper: `:flipper:`\n\n:floppy_disk: `:floppy_disk:`\n\n:flower_playing_cards: `:flower_playing_cards:`\n\n:flushed: `:flushed:`\n\n:flute: `:flute:`\n\n:fly: `:fly:`\n\n:flying_disc: `:flying_disc:`\n\n:flying_saucer: `:flying_saucer:`\n\n:fog: `:fog:`\n\n:foggy: `:foggy:`\n\n:folding_hand_fan: `:folding_hand_fan:`\n\n:fondue: `:fondue:`\n\n:foot: `:foot:`\n\n:football: `:football:`\n\n:footprints: `:footprints:`\n\n:fork_and_knife: `:fork_and_knife:`\n\n:fortune_cookie: `:fortune_cookie:`\n\n:fountain: `:fountain:`\n\n:fountain_pen: `:fountain_pen:`\n\n:four: `:four:`\n\n:four_leaf_clover: `:four_leaf_clover:`\n\n:fox_face: `:fox_face:`\n\n:fr: `:fr:`\n\n:framed_picture: `:framed_picture:`\n\n:free: `:free:`\n\n:french_guiana: `:french_guiana:`\n\n:french_polynesia: `:french_polynesia:`\n\n:french_southern_territories: `:french_southern_territories:`\n\n:fried_egg: `:fried_egg:`\n\n:fried_shrimp: `:fried_shrimp:`\n\n:fries: `:fries:`\n\n:frog: `:frog:`\n\n:frowning: `:frowning:`\n\n:frowning_face: `:frowning_face:`\n\n:frowning_man: `:frowning_man:`\n\n:frowning_person: `:frowning_person:`\n\n:frowning_woman: `:frowning_woman:`\n\n:fu: `:fu:`\n\n:fuelpump: `:fuelpump:`\n\n:full_moon: `:full_moon:`\n\n:full_moon_with_face: `:full_moon_with_face:`\n\n:funeral_urn: `:funeral_urn:`\n\n:gabon: `:gabon:`\n\n:gambia: `:gambia:`\n\n:game_die: `:game_die:`\n\n:garlic: `:garlic:`\n\n:gb: `:gb:`\n\n:gear: `:gear:`\n\n:gem: `:gem:`\n\n:gemini: `:gemini:`\n\n:genie: `:genie:`\n\n:genie_man: `:genie_man:`\n\n:genie_woman: `:genie_woman:`\n\n:georgia: `:georgia:`\n\n:ghana: `:ghana:`\n\n:ghost: `:ghost:`\n\n:gibraltar: `:gibraltar:`\n\n:gift: `:gift:`\n\n:gift_heart: `:gift_heart:`\n\n:ginger_root: `:ginger_root:`\n\n:giraffe: `:giraffe:`\n\n:girl: `:girl:`\n\n:globe_with_meridians: `:globe_with_meridians:`\n\n:gloves: `:gloves:`\n\n:goal_net: `:goal_net:`\n\n:goat: `:goat:`\n\n:goberserk: `:goberserk:`\n\n:godmode: `:godmode:`\n\n:goggles: `:goggles:`\n\n:golf: `:golf:`\n\n:golfing: `:golfing:`\n\n:golfing_man: `:golfing_man:`\n\n:golfing_woman: `:golfing_woman:`\n\n:goose: `:goose:`\n\n:gorilla: `:gorilla:`\n\n:grapes: `:grapes:`\n\n:greece: `:greece:`\n\n:green_apple: `:green_apple:`\n\n:green_book: `:green_book:`\n\n:green_circle: `:green_circle:`\n\n:green_heart: `:green_heart:`\n\n:green_salad: `:green_salad:`\n\n:green_square: `:green_square:`\n\n:greenland: `:greenland:`\n\n:grenada: `:grenada:`\n\n:grey_exclamation: `:grey_exclamation:`\n\n:grey_heart: `:grey_heart:`\n\n:grey_question: `:grey_question:`\n\n:grimacing: `:grimacing:`\n\n:grin: `:grin:`\n\n:grinning: `:grinning:`\n\n:guadeloupe: `:guadeloupe:`\n\n:guam: `:guam:`\n\n:guard: `:guard:`\n\n:guardsman: `:guardsman:`\n\n:guardswoman: `:guardswoman:`\n\n:guatemala: `:guatemala:`\n\n:guernsey: `:guernsey:`\n\n:guide_dog: `:guide_dog:`\n\n:guinea: `:guinea:`\n\n:guinea_bissau: `:guinea_bissau:`\n\n:guitar: `:guitar:`\n\n:gun: `:gun:`\n\n:guyana: `:guyana:`\n\n:hair_pick: `:hair_pick:`\n\n:haircut: `:haircut:`\n\n:haircut_man: `:haircut_man:`\n\n:haircut_woman: `:haircut_woman:`\n\n:haiti: `:haiti:`\n\n:hamburger: `:hamburger:`\n\n:hammer: `:hammer:`\n\n:hammer_and_pick: `:hammer_and_pick:`\n\n:hammer_and_wrench: `:hammer_and_wrench:`\n\n:hamsa: `:hamsa:`\n\n:hamster: `:hamster:`\n\n:hand: `:hand:`\n\n:hand_over_mouth: `:hand_over_mouth:`\n\n:hand_with_index_finger_and_thumb_crossed: `:hand_with_index_finger_and_thumb_crossed:`\n\n:handbag: `:handbag:`\n\n:handball_person: `:handball_person:`\n\n:handshake: `:handshake:`\n\n:hankey: `:hankey:`\n\n:hash: `:hash:`\n\n:hatched_chick: `:hatched_chick:`\n\n:hatching_chick: `:hatching_chick:`\n\n:headphones: `:headphones:`\n\n:headstone: `:headstone:`\n\n:health_worker: `:health_worker:`\n\n:hear_no_evil: `:hear_no_evil:`\n\n:heard_mcdonald_islands: `:heard_mcdonald_islands:`\n\n:heart: `:heart:`\n\n:heart_decoration: `:heart_decoration:`\n\n:heart_eyes: `:heart_eyes:`\n\n:heart_eyes_cat: `:heart_eyes_cat:`\n\n:heart_hands: `:heart_hands:`\n\n:heart_on_fire: `:heart_on_fire:`\n\n:heartbeat: `:heartbeat:`\n\n:heartpulse: `:heartpulse:`\n\n:hearts: `:hearts:`\n\n:heavy_check_mark: `:heavy_check_mark:`\n\n:heavy_division_sign: `:heavy_division_sign:`\n\n:heavy_dollar_sign: `:heavy_dollar_sign:`\n\n:heavy_equals_sign: `:heavy_equals_sign:`\n\n:heavy_exclamation_mark: `:heavy_exclamation_mark:`\n\n:heavy_heart_exclamation: `:heavy_heart_exclamation:`\n\n:heavy_minus_sign: `:heavy_minus_sign:`\n\n:heavy_multiplication_x: `:heavy_multiplication_x:`\n\n:heavy_plus_sign: `:heavy_plus_sign:`\n\n:hedgehog: `:hedgehog:`\n\n:helicopter: `:helicopter:`\n\n:herb: `:herb:`\n\n:hibiscus: `:hibiscus:`\n\n:high_brightness: `:high_brightness:`\n\n:high_heel: `:high_heel:`\n\n:hiking_boot: `:hiking_boot:`\n\n:hindu_temple: `:hindu_temple:`\n\n:hippopotamus: `:hippopotamus:`\n\n:hocho: `:hocho:`\n\n:hole: `:hole:`\n\n:honduras: `:honduras:`\n\n:honey_pot: `:honey_pot:`\n\n:honeybee: `:honeybee:`\n\n:hong_kong: `:hong_kong:`\n\n:hook: `:hook:`\n\n:horse: `:horse:`\n\n:horse_racing: `:horse_racing:`\n\n:hospital: `:hospital:`\n\n:hot_face: `:hot_face:`\n\n:hot_pepper: `:hot_pepper:`\n\n:hotdog: `:hotdog:`\n\n:hotel: `:hotel:`\n\n:hotsprings: `:hotsprings:`\n\n:hourglass: `:hourglass:`\n\n:hourglass_flowing_sand: `:hourglass_flowing_sand:`\n\n:house: `:house:`\n\n:house_with_garden: `:house_with_garden:`\n\n:houses: `:houses:`\n\n:hugs: `:hugs:`\n\n:hungary: `:hungary:`\n\n:hurtrealbad: `:hurtrealbad:`\n\n:hushed: `:hushed:`\n\n:hut: `:hut:`\n\n:hyacinth: `:hyacinth:`\n\n:ice_cream: `:ice_cream:`\n\n:ice_cube: `:ice_cube:`\n\n:ice_hockey: `:ice_hockey:`\n\n:ice_skate: `:ice_skate:`\n\n:icecream: `:icecream:`\n\n:iceland: `:iceland:`\n\n:id: `:id:`\n\n:identification_card: `:identification_card:`\n\n:ideograph_advantage: `:ideograph_advantage:`\n\n:imp: `:imp:`\n\n:inbox_tray: `:inbox_tray:`\n\n:incoming_envelope: `:incoming_envelope:`\n\n:index_pointing_at_the_viewer: `:index_pointing_at_the_viewer:`\n\n:india: `:india:`\n\n:indonesia: `:indonesia:`\n\n:infinity: `:infinity:`\n\n:information_desk_person: `:information_desk_person:`\n\n:information_source: `:information_source:`\n\n:innocent: `:innocent:`\n\n:interrobang: `:interrobang:`\n\n:iphone: `:iphone:`\n\n:iran: `:iran:`\n\n:iraq: `:iraq:`\n\n:ireland: `:ireland:`\n\n:isle_of_man: `:isle_of_man:`\n\n:israel: `:israel:`\n\n:it: `:it:`\n\n:izakaya_lantern: `:izakaya_lantern:`\n\n:jack_o_lantern: `:jack_o_lantern:`\n\n:jamaica: `:jamaica:`\n\n:japan: `:japan:`\n\n:japanese_castle: `:japanese_castle:`\n\n:japanese_goblin: `:japanese_goblin:`\n\n:japanese_ogre: `:japanese_ogre:`\n\n:jar: `:jar:`\n\n:jeans: `:jeans:`\n\n:jellyfish: `:jellyfish:`\n\n:jersey: `:jersey:`\n\n:jigsaw: `:jigsaw:`\n\n:jordan: `:jordan:`\n\n:joy: `:joy:`\n\n:joy_cat: `:joy_cat:`\n\n:joystick: `:joystick:`\n\n:jp: `:jp:`\n\n:judge: `:judge:`\n\n:juggling_person: `:juggling_person:`\n\n:kaaba: `:kaaba:`\n\n:kangaroo: `:kangaroo:`\n\n:kazakhstan: `:kazakhstan:`\n\n:kenya: `:kenya:`\n\n:key: `:key:`\n\n:keyboard: `:keyboard:`\n\n:keycap_ten: `:keycap_ten:`\n\n:khanda: `:khanda:`\n\n:kick_scooter: `:kick_scooter:`\n\n:kimono: `:kimono:`\n\n:kiribati: `:kiribati:`\n\n:kiss: `:kiss:`\n\n:kissing: `:kissing:`\n\n:kissing_cat: `:kissing_cat:`\n\n:kissing_closed_eyes: `:kissing_closed_eyes:`\n\n:kissing_heart: `:kissing_heart:`\n\n:kissing_smiling_eyes: `:kissing_smiling_eyes:`\n\n:kite: `:kite:`\n\n:kiwi_fruit: `:kiwi_fruit:`\n\n:kneeling_man: `:kneeling_man:`\n\n:kneeling_person: `:kneeling_person:`\n\n:kneeling_woman: `:kneeling_woman:`\n\n:knife: `:knife:`\n\n:knot: `:knot:`\n\n:koala: `:koala:`\n\n:koko: `:koko:`\n\n:kosovo: `:kosovo:`\n\n:kr: `:kr:`\n\n:kuwait: `:kuwait:`\n\n:kyrgyzstan: `:kyrgyzstan:`\n\n:lab_coat: `:lab_coat:`\n\n:label: `:label:`\n\n:lacrosse: `:lacrosse:`\n\n:ladder: `:ladder:`\n\n:lady_beetle: `:lady_beetle:`\n\n:lantern: `:lantern:`\n\n:laos: `:laos:`\n\n:large_blue_circle: `:large_blue_circle:`\n\n:large_blue_diamond: `:large_blue_diamond:`\n\n:large_orange_diamond: `:large_orange_diamond:`\n\n:last_quarter_moon: `:last_quarter_moon:`\n\n:last_quarter_moon_with_face: `:last_quarter_moon_with_face:`\n\n:latin_cross: `:latin_cross:`\n\n:latvia: `:latvia:`\n\n:laughing: `:laughing:`\n\n:leafy_green: `:leafy_green:`\n\n:leaves: `:leaves:`\n\n:lebanon: `:lebanon:`\n\n:ledger: `:ledger:`\n\n:left_luggage: `:left_luggage:`\n\n:left_right_arrow: `:left_right_arrow:`\n\n:left_speech_bubble: `:left_speech_bubble:`\n\n:leftwards_arrow_with_hook: `:leftwards_arrow_with_hook:`\n\n:leftwards_hand: `:leftwards_hand:`\n\n:leftwards_pushing_hand: `:leftwards_pushing_hand:`\n\n:leg: `:leg:`\n\n:lemon: `:lemon:`\n\n:leo: `:leo:`\n\n:leopard: `:leopard:`\n\n:lesotho: `:lesotho:`\n\n:level_slider: `:level_slider:`\n\n:liberia: `:liberia:`\n\n:libra: `:libra:`\n\n:libya: `:libya:`\n\n:liechtenstein: `:liechtenstein:`\n\n:light_blue_heart: `:light_blue_heart:`\n\n:light_rail: `:light_rail:`\n\n:link: `:link:`\n\n:lion: `:lion:`\n\n:lips: `:lips:`\n\n:lipstick: `:lipstick:`\n\n:lithuania: `:lithuania:`\n\n:lizard: `:lizard:`\n\n:llama: `:llama:`\n\n:lobster: `:lobster:`\n\n:lock: `:lock:`\n\n:lock_with_ink_pen: `:lock_with_ink_pen:`\n\n:lollipop: `:lollipop:`\n\n:long_drum: `:long_drum:`\n\n:loop: `:loop:`\n\n:lotion_bottle: `:lotion_bottle:`\n\n:lotus: `:lotus:`\n\n:lotus_position: `:lotus_position:`\n\n:lotus_position_man: `:lotus_position_man:`\n\n:lotus_position_woman: `:lotus_position_woman:`\n\n:loud_sound: `:loud_sound:`\n\n:loudspeaker: `:loudspeaker:`\n\n:love_hotel: `:love_hotel:`\n\n:love_letter: `:love_letter:`\n\n:love_you_gesture: `:love_you_gesture:`\n\n:low_battery: `:low_battery:`\n\n:low_brightness: `:low_brightness:`\n\n:luggage: `:luggage:`\n\n:lungs: `:lungs:`\n\n:luxembourg: `:luxembourg:`\n\n:lying_face: `:lying_face:`\n\n:m: `:m:`\n\n:macau: `:macau:`\n\n:macedonia: `:macedonia:`\n\n:madagascar: `:madagascar:`\n\n:mag: `:mag:`\n\n:mag_right: `:mag_right:`\n\n:mage: `:mage:`\n\n:mage_man: `:mage_man:`\n\n:mage_woman: `:mage_woman:`\n\n:magic_wand: `:magic_wand:`\n\n:magnet: `:magnet:`\n\n:mahjong: `:mahjong:`\n\n:mailbox: `:mailbox:`\n\n:mailbox_closed: `:mailbox_closed:`\n\n:mailbox_with_mail: `:mailbox_with_mail:`\n\n:mailbox_with_no_mail: `:mailbox_with_no_mail:`\n\n:malawi: `:malawi:`\n\n:malaysia: `:malaysia:`\n\n:maldives: `:maldives:`\n\n:male_detective: `:male_detective:`\n\n:male_sign: `:male_sign:`\n\n:mali: `:mali:`\n\n:malta: `:malta:`\n\n:mammoth: `:mammoth:`\n\n:man: `:man:`\n\n:man_artist: `:man_artist:`\n\n:man_astronaut: `:man_astronaut:`\n\n:man_beard: `:man_beard:`\n\n:man_cartwheeling: `:man_cartwheeling:`\n\n:man_cook: `:man_cook:`\n\n:man_dancing: `:man_dancing:`\n\n:man_facepalming: `:man_facepalming:`\n\n:man_factory_worker: `:man_factory_worker:`\n\n:man_farmer: `:man_farmer:`\n\n:man_feeding_baby: `:man_feeding_baby:`\n\n:man_firefighter: `:man_firefighter:`\n\n:man_health_worker: `:man_health_worker:`\n\n:man_in_manual_wheelchair: `:man_in_manual_wheelchair:`\n\n:man_in_motorized_wheelchair: `:man_in_motorized_wheelchair:`\n\n:man_in_tuxedo: `:man_in_tuxedo:`\n\n:man_judge: `:man_judge:`\n\n:man_juggling: `:man_juggling:`\n\n:man_mechanic: `:man_mechanic:`\n\n:man_office_worker: `:man_office_worker:`\n\n:man_pilot: `:man_pilot:`\n\n:man_playing_handball: `:man_playing_handball:`\n\n:man_playing_water_polo: `:man_playing_water_polo:`\n\n:man_scientist: `:man_scientist:`\n\n:man_shrugging: `:man_shrugging:`\n\n:man_singer: `:man_singer:`\n\n:man_student: `:man_student:`\n\n:man_teacher: `:man_teacher:`\n\n:man_technologist: `:man_technologist:`\n\n:man_with_gua_pi_mao: `:man_with_gua_pi_mao:`\n\n:man_with_probing_cane: `:man_with_probing_cane:`\n\n:man_with_turban: `:man_with_turban:`\n\n:man_with_veil: `:man_with_veil:`\n\n:mandarin: `:mandarin:`\n\n:mango: `:mango:`\n\n:mans_shoe: `:mans_shoe:`\n\n:mantelpiece_clock: `:mantelpiece_clock:`\n\n:manual_wheelchair: `:manual_wheelchair:`\n\n:maple_leaf: `:maple_leaf:`\n\n:maracas: `:maracas:`\n\n:marshall_islands: `:marshall_islands:`\n\n:martial_arts_uniform: `:martial_arts_uniform:`\n\n:martinique: `:martinique:`\n\n:mask: `:mask:`\n\n:massage: `:massage:`\n\n:massage_man: `:massage_man:`\n\n:massage_woman: `:massage_woman:`\n\n:mate: `:mate:`\n\n:mauritania: `:mauritania:`\n\n:mauritius: `:mauritius:`\n\n:mayotte: `:mayotte:`\n\n:meat_on_bone: `:meat_on_bone:`\n\n:mechanic: `:mechanic:`\n\n:mechanical_arm: `:mechanical_arm:`\n\n:mechanical_leg: `:mechanical_leg:`\n\n:medal_military: `:medal_military:`\n\n:medal_sports: `:medal_sports:`\n\n:medical_symbol: `:medical_symbol:`\n\n:mega: `:mega:`\n\n:melon: `:melon:`\n\n:melting_face: `:melting_face:`\n\n:memo: `:memo:`\n\n:men_wrestling: `:men_wrestling:`\n\n:mending_heart: `:mending_heart:`\n\n:menorah: `:menorah:`\n\n:mens: `:mens:`\n\n:mermaid: `:mermaid:`\n\n:merman: `:merman:`\n\n:merperson: `:merperson:`\n\n:metal: `:metal:`\n\n:metro: `:metro:`\n\n:mexico: `:mexico:`\n\n:microbe: `:microbe:`\n\n:micronesia: `:micronesia:`\n\n:microphone: `:microphone:`\n\n:microscope: `:microscope:`\n\n:middle_finger: `:middle_finger:`\n\n:military_helmet: `:military_helmet:`\n\n:milk_glass: `:milk_glass:`\n\n:milky_way: `:milky_way:`\n\n:minibus: `:minibus:`\n\n:minidisc: `:minidisc:`\n\n:mirror: `:mirror:`\n\n:mirror_ball: `:mirror_ball:`\n\n:mobile_phone_off: `:mobile_phone_off:`\n\n:moldova: `:moldova:`\n\n:monaco: `:monaco:`\n\n:money_mouth_face: `:money_mouth_face:`\n\n:money_with_wings: `:money_with_wings:`\n\n:moneybag: `:moneybag:`\n\n:mongolia: `:mongolia:`\n\n:monkey: `:monkey:`\n\n:monkey_face: `:monkey_face:`\n\n:monocle_face: `:monocle_face:`\n\n:monorail: `:monorail:`\n\n:montenegro: `:montenegro:`\n\n:montserrat: `:montserrat:`\n\n:moon: `:moon:`\n\n:moon_cake: `:moon_cake:`\n\n:moose: `:moose:`\n\n:morocco: `:morocco:`\n\n:mortar_board: `:mortar_board:`\n\n:mosque: `:mosque:`\n\n:mosquito: `:mosquito:`\n\n:motor_boat: `:motor_boat:`\n\n:motor_scooter: `:motor_scooter:`\n\n:motorcycle: `:motorcycle:`\n\n:motorized_wheelchair: `:motorized_wheelchair:`\n\n:motorway: `:motorway:`\n\n:mount_fuji: `:mount_fuji:`\n\n:mountain: `:mountain:`\n\n:mountain_bicyclist: `:mountain_bicyclist:`\n\n:mountain_biking_man: `:mountain_biking_man:`\n\n:mountain_biking_woman: `:mountain_biking_woman:`\n\n:mountain_cableway: `:mountain_cableway:`\n\n:mountain_railway: `:mountain_railway:`\n\n:mountain_snow: `:mountain_snow:`\n\n:mouse: `:mouse:`\n\n:mouse2: `:mouse2:`\n\n:mouse_trap: `:mouse_trap:`\n\n:movie_camera: `:movie_camera:`\n\n:moyai: `:moyai:`\n\n:mozambique: `:mozambique:`\n\n:mrs_claus: `:mrs_claus:`\n\n:muscle: `:muscle:`\n\n:mushroom: `:mushroom:`\n\n:musical_keyboard: `:musical_keyboard:`\n\n:musical_note: `:musical_note:`\n\n:musical_score: `:musical_score:`\n\n:mute: `:mute:`\n\n:mx_claus: `:mx_claus:`\n\n:myanmar: `:myanmar:`\n\n:nail_care: `:nail_care:`\n\n:name_badge: `:name_badge:`\n\n:namibia: `:namibia:`\n\n:national_park: `:national_park:`\n\n:nauru: `:nauru:`\n\n:nauseated_face: `:nauseated_face:`\n\n:nazar_amulet: `:nazar_amulet:`\n\n:neckbeard: `:neckbeard:`\n\n:necktie: `:necktie:`\n\n:negative_squared_cross_mark: `:negative_squared_cross_mark:`\n\n:nepal: `:nepal:`\n\n:nerd_face: `:nerd_face:`\n\n:nest_with_eggs: `:nest_with_eggs:`\n\n:nesting_dolls: `:nesting_dolls:`\n\n:netherlands: `:netherlands:`\n\n:neutral_face: `:neutral_face:`\n\n:new: `:new:`\n\n:new_caledonia: `:new_caledonia:`\n\n:new_moon: `:new_moon:`\n\n:new_moon_with_face: `:new_moon_with_face:`\n\n:new_zealand: `:new_zealand:`\n\n:newspaper: `:newspaper:`\n\n:newspaper_roll: `:newspaper_roll:`\n\n:next_track_button: `:next_track_button:`\n\n:ng: `:ng:`\n\n:ng_man: `:ng_man:`\n\n:ng_woman: `:ng_woman:`\n\n:nicaragua: `:nicaragua:`\n\n:niger: `:niger:`\n\n:nigeria: `:nigeria:`\n\n:night_with_stars: `:night_with_stars:`\n\n:nine: `:nine:`\n\n:ninja: `:ninja:`\n\n:niue: `:niue:`\n\n:no_bell: `:no_bell:`\n\n:no_bicycles: `:no_bicycles:`\n\n:no_entry: `:no_entry:`\n\n:no_entry_sign: `:no_entry_sign:`\n\n:no_good: `:no_good:`\n\n:no_good_man: `:no_good_man:`\n\n:no_good_woman: `:no_good_woman:`\n\n:no_mobile_phones: `:no_mobile_phones:`\n\n:no_mouth: `:no_mouth:`\n\n:no_pedestrians: `:no_pedestrians:`\n\n:no_smoking: `:no_smoking:`\n\n:non-potable_water: `:non-potable_water:`\n\n:norfolk_island: `:norfolk_island:`\n\n:north_korea: `:north_korea:`\n\n:northern_mariana_islands: `:northern_mariana_islands:`\n\n:norway: `:norway:`\n\n:nose: `:nose:`\n\n:notebook: `:notebook:`\n\n:notebook_with_decorative_cover: `:notebook_with_decorative_cover:`\n\n:notes: `:notes:`\n\n:nut_and_bolt: `:nut_and_bolt:`\n\n:o: `:o:`\n\n:o2: `:o2:`\n\n:ocean: `:ocean:`\n\n:octocat: `:octocat:`\n\n:octopus: `:octopus:`\n\n:oden: `:oden:`\n\n:office: `:office:`\n\n:office_worker: `:office_worker:`\n\n:oil_drum: `:oil_drum:`\n\n:ok: `:ok:`\n\n:ok_hand: `:ok_hand:`\n\n:ok_man: `:ok_man:`\n\n:ok_person: `:ok_person:`\n\n:ok_woman: `:ok_woman:`\n\n:old_key: `:old_key:`\n\n:older_adult: `:older_adult:`\n\n:older_man: `:older_man:`\n\n:older_woman: `:older_woman:`\n\n:olive: `:olive:`\n\n:om: `:om:`\n\n:oman: `:oman:`\n\n:on: `:on:`\n\n:oncoming_automobile: `:oncoming_automobile:`\n\n:oncoming_bus: `:oncoming_bus:`\n\n:oncoming_police_car: `:oncoming_police_car:`\n\n:oncoming_taxi: `:oncoming_taxi:`\n\n:one: `:one:`\n\n:one_piece_swimsuit: `:one_piece_swimsuit:`\n\n:onion: `:onion:`\n\n:open_book: `:open_book:`\n\n:open_file_folder: `:open_file_folder:`\n\n:open_hands: `:open_hands:`\n\n:open_mouth: `:open_mouth:`\n\n:open_umbrella: `:open_umbrella:`\n\n:ophiuchus: `:ophiuchus:`\n\n:orange: `:orange:`\n\n:orange_book: `:orange_book:`\n\n:orange_circle: `:orange_circle:`\n\n:orange_heart: `:orange_heart:`\n\n:orange_square: `:orange_square:`\n\n:orangutan: `:orangutan:`\n\n:orthodox_cross: `:orthodox_cross:`\n\n:otter: `:otter:`\n\n:outbox_tray: `:outbox_tray:`\n\n:owl: `:owl:`\n\n:ox: `:ox:`\n\n:oyster: `:oyster:`\n\n:package: `:package:`\n\n:page_facing_up: `:page_facing_up:`\n\n:page_with_curl: `:page_with_curl:`\n\n:pager: `:pager:`\n\n:paintbrush: `:paintbrush:`\n\n:pakistan: `:pakistan:`\n\n:palau: `:palau:`\n\n:palestinian_territories: `:palestinian_territories:`\n\n:palm_down_hand: `:palm_down_hand:`\n\n:palm_tree: `:palm_tree:`\n\n:palm_up_hand: `:palm_up_hand:`\n\n:palms_up_together: `:palms_up_together:`\n\n:panama: `:panama:`\n\n:pancakes: `:pancakes:`\n\n:panda_face: `:panda_face:`\n\n:paperclip: `:paperclip:`\n\n:paperclips: `:paperclips:`\n\n:papua_new_guinea: `:papua_new_guinea:`\n\n:parachute: `:parachute:`\n\n:paraguay: `:paraguay:`\n\n:parasol_on_ground: `:parasol_on_ground:`\n\n:parking: `:parking:`\n\n:parrot: `:parrot:`\n\n:part_alternation_mark: `:part_alternation_mark:`\n\n:partly_sunny: `:partly_sunny:`\n\n:partying_face: `:partying_face:`\n\n:passenger_ship: `:passenger_ship:`\n\n:passport_control: `:passport_control:`\n\n:pause_button: `:pause_button:`\n\n:paw_prints: `:paw_prints:`\n\n:pea_pod: `:pea_pod:`\n\n:peace_symbol: `:peace_symbol:`\n\n:peach: `:peach:`\n\n:peacock: `:peacock:`\n\n:peanuts: `:peanuts:`\n\n:pear: `:pear:`\n\n:pen: `:pen:`\n\n:pencil: `:pencil:`\n\n:pencil2: `:pencil2:`\n\n:penguin: `:penguin:`\n\n:pensive: `:pensive:`\n\n:people_holding_hands: `:people_holding_hands:`\n\n:people_hugging: `:people_hugging:`\n\n:performing_arts: `:performing_arts:`\n\n:persevere: `:persevere:`\n\n:person_bald: `:person_bald:`\n\n:person_curly_hair: `:person_curly_hair:`\n\n:person_feeding_baby: `:person_feeding_baby:`\n\n:person_fencing: `:person_fencing:`\n\n:person_in_manual_wheelchair: `:person_in_manual_wheelchair:`\n\n:person_in_motorized_wheelchair: `:person_in_motorized_wheelchair:`\n\n:person_in_tuxedo: `:person_in_tuxedo:`\n\n:person_red_hair: `:person_red_hair:`\n\n:person_white_hair: `:person_white_hair:`\n\n:person_with_crown: `:person_with_crown:`\n\n:person_with_probing_cane: `:person_with_probing_cane:`\n\n:person_with_turban: `:person_with_turban:`\n\n:person_with_veil: `:person_with_veil:`\n\n:peru: `:peru:`\n\n:petri_dish: `:petri_dish:`\n\n:philippines: `:philippines:`\n\n:phone: `:phone:`\n\n:pick: `:pick:`\n\n:pickup_truck: `:pickup_truck:`\n\n:pie: `:pie:`\n\n:pig: `:pig:`\n\n:pig2: `:pig2:`\n\n:pig_nose: `:pig_nose:`\n\n:pill: `:pill:`\n\n:pilot: `:pilot:`\n\n:pinata: `:pinata:`\n\n:pinched_fingers: `:pinched_fingers:`\n\n:pinching_hand: `:pinching_hand:`\n\n:pineapple: `:pineapple:`\n\n:ping_pong: `:ping_pong:`\n\n:pink_heart: `:pink_heart:`\n\n:pirate_flag: `:pirate_flag:`\n\n:pisces: `:pisces:`\n\n:pitcairn_islands: `:pitcairn_islands:`\n\n:pizza: `:pizza:`\n\n:placard: `:placard:`\n\n:place_of_worship: `:place_of_worship:`\n\n:plate_with_cutlery: `:plate_with_cutlery:`\n\n:play_or_pause_button: `:play_or_pause_button:`\n\n:playground_slide: `:playground_slide:`\n\n:pleading_face: `:pleading_face:`\n\n:plunger: `:plunger:`\n\n:point_down: `:point_down:`\n\n:point_left: `:point_left:`\n\n:point_right: `:point_right:`\n\n:point_up: `:point_up:`\n\n:point_up_2: `:point_up_2:`\n\n:poland: `:poland:`\n\n:polar_bear: `:polar_bear:`\n\n:police_car: `:police_car:`\n\n:police_officer: `:police_officer:`\n\n:policeman: `:policeman:`\n\n:policewoman: `:policewoman:`\n\n:poodle: `:poodle:`\n\n:poop: `:poop:`\n\n:popcorn: `:popcorn:`\n\n:portugal: `:portugal:`\n\n:post_office: `:post_office:`\n\n:postal_horn: `:postal_horn:`\n\n:postbox: `:postbox:`\n\n:potable_water: `:potable_water:`\n\n:potato: `:potato:`\n\n:potted_plant: `:potted_plant:`\n\n:pouch: `:pouch:`\n\n:poultry_leg: `:poultry_leg:`\n\n:pound: `:pound:`\n\n:pouring_liquid: `:pouring_liquid:`\n\n:pout: `:pout:`\n\n:pouting_cat: `:pouting_cat:`\n\n:pouting_face: `:pouting_face:`\n\n:pouting_man: `:pouting_man:`\n\n:pouting_woman: `:pouting_woman:`\n\n:pray: `:pray:`\n\n:prayer_beads: `:prayer_beads:`\n\n:pregnant_man: `:pregnant_man:`\n\n:pregnant_person: `:pregnant_person:`\n\n:pregnant_woman: `:pregnant_woman:`\n\n:pretzel: `:pretzel:`\n\n:previous_track_button: `:previous_track_button:`\n\n:prince: `:prince:`\n\n:princess: `:princess:`\n\n:printer: `:printer:`\n\n:probing_cane: `:probing_cane:`\n\n:puerto_rico: `:puerto_rico:`\n\n:punch: `:punch:`\n\n:purple_circle: `:purple_circle:`\n\n:purple_heart: `:purple_heart:`\n\n:purple_square: `:purple_square:`\n\n:purse: `:purse:`\n\n:pushpin: `:pushpin:`\n\n:put_litter_in_its_place: `:put_litter_in_its_place:`\n\n:qatar: `:qatar:`\n\n:question: `:question:`\n\n:rabbit: `:rabbit:`\n\n:rabbit2: `:rabbit2:`\n\n:raccoon: `:raccoon:`\n\n:racehorse: `:racehorse:`\n\n:racing_car: `:racing_car:`\n\n:radio: `:radio:`\n\n:radio_button: `:radio_button:`\n\n:radioactive: `:radioactive:`\n\n:rage: `:rage:`\n\n:rage1: `:rage1:`\n\n:rage2: `:rage2:`\n\n:rage3: `:rage3:`\n\n:rage4: `:rage4:`\n\n:railway_car: `:railway_car:`\n\n:railway_track: `:railway_track:`\n\n:rainbow: `:rainbow:`\n\n:rainbow_flag: `:rainbow_flag:`\n\n:raised_back_of_hand: `:raised_back_of_hand:`\n\n:raised_eyebrow: `:raised_eyebrow:`\n\n:raised_hand: `:raised_hand:`\n\n:raised_hand_with_fingers_splayed: `:raised_hand_with_fingers_splayed:`\n\n:raised_hands: `:raised_hands:`\n\n:raising_hand: `:raising_hand:`\n\n:raising_hand_man: `:raising_hand_man:`\n\n:raising_hand_woman: `:raising_hand_woman:`\n\n:ram: `:ram:`\n\n:ramen: `:ramen:`\n\n:rat: `:rat:`\n\n:razor: `:razor:`\n\n:receipt: `:receipt:`\n\n:record_button: `:record_button:`\n\n:recycle: `:recycle:`\n\n:red_car: `:red_car:`\n\n:red_circle: `:red_circle:`\n\n:red_envelope: `:red_envelope:`\n\n:red_haired_man: `:red_haired_man:`\n\n:red_haired_woman: `:red_haired_woman:`\n\n:red_square: `:red_square:`\n\n:registered: `:registered:`\n\n:relaxed: `:relaxed:`\n\n:relieved: `:relieved:`\n\n:reminder_ribbon: `:reminder_ribbon:`\n\n:repeat: `:repeat:`\n\n:repeat_one: `:repeat_one:`\n\n:rescue_worker_helmet: `:rescue_worker_helmet:`\n\n:restroom: `:restroom:`\n\n:reunion: `:reunion:`\n\n:revolving_hearts: `:revolving_hearts:`\n\n:rewind: `:rewind:`\n\n:rhinoceros: `:rhinoceros:`\n\n:ribbon: `:ribbon:`\n\n:rice: `:rice:`\n\n:rice_ball: `:rice_ball:`\n\n:rice_cracker: `:rice_cracker:`\n\n:rice_scene: `:rice_scene:`\n\n:right_anger_bubble: `:right_anger_bubble:`\n\n:rightwards_hand: `:rightwards_hand:`\n\n:rightwards_pushing_hand: `:rightwards_pushing_hand:`\n\n:ring: `:ring:`\n\n:ring_buoy: `:ring_buoy:`\n\n:ringed_planet: `:ringed_planet:`\n\n:robot: `:robot:`\n\n:rock: `:rock:`\n\n:rocket: `:rocket:`\n\n:rofl: `:rofl:`\n\n:roll_eyes: `:roll_eyes:`\n\n:roll_of_paper: `:roll_of_paper:`\n\n:roller_coaster: `:roller_coaster:`\n\n:roller_skate: `:roller_skate:`\n\n:romania: `:romania:`\n\n:rooster: `:rooster:`\n\n:rose: `:rose:`\n\n:rosette: `:rosette:`\n\n:rotating_light: `:rotating_light:`\n\n:round_pushpin: `:round_pushpin:`\n\n:rowboat: `:rowboat:`\n\n:rowing_man: `:rowing_man:`\n\n:rowing_woman: `:rowing_woman:`\n\n:ru: `:ru:`\n\n:rugby_football: `:rugby_football:`\n\n:runner: `:runner:`\n\n:running: `:running:`\n\n:running_man: `:running_man:`\n\n:running_shirt_with_sash: `:running_shirt_with_sash:`\n\n:running_woman: `:running_woman:`\n\n:rwanda: `:rwanda:`\n\n:sa: `:sa:`\n\n:safety_pin: `:safety_pin:`\n\n:safety_vest: `:safety_vest:`\n\n:sagittarius: `:sagittarius:`\n\n:sailboat: `:sailboat:`\n\n:sake: `:sake:`\n\n:salt: `:salt:`\n\n:saluting_face: `:saluting_face:`\n\n:samoa: `:samoa:`\n\n:san_marino: `:san_marino:`\n\n:sandal: `:sandal:`\n\n:sandwich: `:sandwich:`\n\n:santa: `:santa:`\n\n:sao_tome_principe: `:sao_tome_principe:`\n\n:sari: `:sari:`\n\n:sassy_man: `:sassy_man:`\n\n:sassy_woman: `:sassy_woman:`\n\n:satellite: `:satellite:`\n\n:satisfied: `:satisfied:`\n\n:saudi_arabia: `:saudi_arabia:`\n\n:sauna_man: `:sauna_man:`\n\n:sauna_person: `:sauna_person:`\n\n:sauna_woman: `:sauna_woman:`\n\n:sauropod: `:sauropod:`\n\n:saxophone: `:saxophone:`\n\n:scarf: `:scarf:`\n\n:school: `:school:`\n\n:school_satchel: `:school_satchel:`\n\n:scientist: `:scientist:`\n\n:scissors: `:scissors:`\n\n:scorpion: `:scorpion:`\n\n:scorpius: `:scorpius:`\n\n:scotland: `:scotland:`\n\n:scream: `:scream:`\n\n:scream_cat: `:scream_cat:`\n\n:screwdriver: `:screwdriver:`\n\n:scroll: `:scroll:`\n\n:seal: `:seal:`\n\n:seat: `:seat:`\n\n:secret: `:secret:`\n\n:see_no_evil: `:see_no_evil:`\n\n:seedling: `:seedling:`\n\n:selfie: `:selfie:`\n\n:senegal: `:senegal:`\n\n:serbia: `:serbia:`\n\n:service_dog: `:service_dog:`\n\n:seven: `:seven:`\n\n:sewing_needle: `:sewing_needle:`\n\n:seychelles: `:seychelles:`\n\n:shaking_face: `:shaking_face:`\n\n:shallow_pan_of_food: `:shallow_pan_of_food:`\n\n:shamrock: `:shamrock:`\n\n:shark: `:shark:`\n\n:shaved_ice: `:shaved_ice:`\n\n:sheep: `:sheep:`\n\n:shell: `:shell:`\n\n:shield: `:shield:`\n\n:shinto_shrine: `:shinto_shrine:`\n\n:ship: `:ship:`\n\n:shipit: `:shipit:`\n\n:shirt: `:shirt:`\n\n:shit: `:shit:`\n\n:shoe: `:shoe:`\n\n:shopping: `:shopping:`\n\n:shopping_cart: `:shopping_cart:`\n\n:shorts: `:shorts:`\n\n:shower: `:shower:`\n\n:shrimp: `:shrimp:`\n\n:shrug: `:shrug:`\n\n:shushing_face: `:shushing_face:`\n\n:sierra_leone: `:sierra_leone:`\n\n:signal_strength: `:signal_strength:`\n\n:singapore: `:singapore:`\n\n:singer: `:singer:`\n\n:sint_maarten: `:sint_maarten:`\n\n:six: `:six:`\n\n:six_pointed_star: `:six_pointed_star:`\n\n:skateboard: `:skateboard:`\n\n:ski: `:ski:`\n\n:skier: `:skier:`\n\n:skull: `:skull:`\n\n:skull_and_crossbones: `:skull_and_crossbones:`\n\n:skunk: `:skunk:`\n\n:sled: `:sled:`\n\n:sleeping: `:sleeping:`\n\n:sleeping_bed: `:sleeping_bed:`\n\n:sleepy: `:sleepy:`\n\n:slightly_frowning_face: `:slightly_frowning_face:`\n\n:slightly_smiling_face: `:slightly_smiling_face:`\n\n:slot_machine: `:slot_machine:`\n\n:sloth: `:sloth:`\n\n:slovakia: `:slovakia:`\n\n:slovenia: `:slovenia:`\n\n:small_airplane: `:small_airplane:`\n\n:small_blue_diamond: `:small_blue_diamond:`\n\n:small_orange_diamond: `:small_orange_diamond:`\n\n:small_red_triangle: `:small_red_triangle:`\n\n:small_red_triangle_down: `:small_red_triangle_down:`\n\n:smile: `:smile:`\n\n:smile_cat: `:smile_cat:`\n\n:smiley: `:smiley:`\n\n:smiley_cat: `:smiley_cat:`\n\n:smiling_face_with_tear: `:smiling_face_with_tear:`\n\n:smiling_face_with_three_hearts: `:smiling_face_with_three_hearts:`\n\n:smiling_imp: `:smiling_imp:`\n\n:smirk: `:smirk:`\n\n:smirk_cat: `:smirk_cat:`\n\n:smoking: `:smoking:`\n\n:snail: `:snail:`\n\n:snake: `:snake:`\n\n:sneezing_face: `:sneezing_face:`\n\n:snowboarder: `:snowboarder:`\n\n:snowflake: `:snowflake:`\n\n:snowman: `:snowman:`\n\n:snowman_with_snow: `:snowman_with_snow:`\n\n:soap: `:soap:`\n\n:sob: `:sob:`\n\n:soccer: `:soccer:`\n\n:socks: `:socks:`\n\n:softball: `:softball:`\n\n:solomon_islands: `:solomon_islands:`\n\n:somalia: `:somalia:`\n\n:soon: `:soon:`\n\n:sos: `:sos:`\n\n:sound: `:sound:`\n\n:south_africa: `:south_africa:`\n\n:south_georgia_south_sandwich_islands: `:south_georgia_south_sandwich_islands:`\n\n:south_sudan: `:south_sudan:`\n\n:space_invader: `:space_invader:`\n\n:spades: `:spades:`\n\n:spaghetti: `:spaghetti:`\n\n:sparkle: `:sparkle:`\n\n:sparkler: `:sparkler:`\n\n:sparkles: `:sparkles:`\n\n:sparkling_heart: `:sparkling_heart:`\n\n:speak_no_evil: `:speak_no_evil:`\n\n:speaker: `:speaker:`\n\n:speaking_head: `:speaking_head:`\n\n:speech_balloon: `:speech_balloon:`\n\n:speedboat: `:speedboat:`\n\n:spider: `:spider:`\n\n:spider_web: `:spider_web:`\n\n:spiral_calendar: `:spiral_calendar:`\n\n:spiral_notepad: `:spiral_notepad:`\n\n:sponge: `:sponge:`\n\n:spoon: `:spoon:`\n\n:squid: `:squid:`\n\n:sri_lanka: `:sri_lanka:`\n\n:st_barthelemy: `:st_barthelemy:`\n\n:st_helena: `:st_helena:`\n\n:st_kitts_nevis: `:st_kitts_nevis:`\n\n:st_lucia: `:st_lucia:`\n\n:st_martin: `:st_martin:`\n\n:st_pierre_miquelon: `:st_pierre_miquelon:`\n\n:st_vincent_grenadines: `:st_vincent_grenadines:`\n\n:stadium: `:stadium:`\n\n:standing_man: `:standing_man:`\n\n:standing_person: `:standing_person:`\n\n:standing_woman: `:standing_woman:`\n\n:star: `:star:`\n\n:star2: `:star2:`\n\n:star_and_crescent: `:star_and_crescent:`\n\n:star_of_david: `:star_of_david:`\n\n:star_struck: `:star_struck:`\n\n:stars: `:stars:`\n\n:station: `:station:`\n\n:statue_of_liberty: `:statue_of_liberty:`\n\n:steam_locomotive: `:steam_locomotive:`\n\n:stethoscope: `:stethoscope:`\n\n:stew: `:stew:`\n\n:stop_button: `:stop_button:`\n\n:stop_sign: `:stop_sign:`\n\n:stopwatch: `:stopwatch:`\n\n:straight_ruler: `:straight_ruler:`\n\n:strawberry: `:strawberry:`\n\n:stuck_out_tongue: `:stuck_out_tongue:`\n\n:stuck_out_tongue_closed_eyes: `:stuck_out_tongue_closed_eyes:`\n\n:stuck_out_tongue_winking_eye: `:stuck_out_tongue_winking_eye:`\n\n:student: `:student:`\n\n:studio_microphone: `:studio_microphone:`\n\n:stuffed_flatbread: `:stuffed_flatbread:`\n\n:sudan: `:sudan:`\n\n:sun_behind_large_cloud: `:sun_behind_large_cloud:`\n\n:sun_behind_rain_cloud: `:sun_behind_rain_cloud:`\n\n:sun_behind_small_cloud: `:sun_behind_small_cloud:`\n\n:sun_with_face: `:sun_with_face:`\n\n:sunflower: `:sunflower:`\n\n:sunglasses: `:sunglasses:`\n\n:sunny: `:sunny:`\n\n:sunrise: `:sunrise:`\n\n:sunrise_over_mountains: `:sunrise_over_mountains:`\n\n:superhero: `:superhero:`\n\n:superhero_man: `:superhero_man:`\n\n:superhero_woman: `:superhero_woman:`\n\n:supervillain: `:supervillain:`\n\n:supervillain_man: `:supervillain_man:`\n\n:supervillain_woman: `:supervillain_woman:`\n\n:surfer: `:surfer:`\n\n:surfing_man: `:surfing_man:`\n\n:surfing_woman: `:surfing_woman:`\n\n:suriname: `:suriname:`\n\n:sushi: `:sushi:`\n\n:suspect: `:suspect:`\n\n:suspension_railway: `:suspension_railway:`\n\n:svalbard_jan_mayen: `:svalbard_jan_mayen:`\n\n:swan: `:swan:`\n\n:swaziland: `:swaziland:`\n\n:sweat: `:sweat:`\n\n:sweat_drops: `:sweat_drops:`\n\n:sweat_smile: `:sweat_smile:`\n\n:sweden: `:sweden:`\n\n:sweet_potato: `:sweet_potato:`\n\n:swim_brief: `:swim_brief:`\n\n:swimmer: `:swimmer:`\n\n:swimming_man: `:swimming_man:`\n\n:swimming_woman: `:swimming_woman:`\n\n:switzerland: `:switzerland:`\n\n:symbols: `:symbols:`\n\n:synagogue: `:synagogue:`\n\n:syria: `:syria:`\n\n:syringe: `:syringe:`\n\n:t-rex: `:t-rex:`\n\n:taco: `:taco:`\n\n:tada: `:tada:`\n\n:taiwan: `:taiwan:`\n\n:tajikistan: `:tajikistan:`\n\n:takeout_box: `:takeout_box:`\n\n:tamale: `:tamale:`\n\n:tanabata_tree: `:tanabata_tree:`\n\n:tangerine: `:tangerine:`\n\n:tanzania: `:tanzania:`\n\n:taurus: `:taurus:`\n\n:taxi: `:taxi:`\n\n:tea: `:tea:`\n\n:teacher: `:teacher:`\n\n:teapot: `:teapot:`\n\n:technologist: `:technologist:`\n\n:teddy_bear: `:teddy_bear:`\n\n:telephone: `:telephone:`\n\n:telephone_receiver: `:telephone_receiver:`\n\n:telescope: `:telescope:`\n\n:tennis: `:tennis:`\n\n:tent: `:tent:`\n\n:test_tube: `:test_tube:`\n\n:thailand: `:thailand:`\n\n:thermometer: `:thermometer:`\n\n:thinking: `:thinking:`\n\n:thong_sandal: `:thong_sandal:`\n\n:thought_balloon: `:thought_balloon:`\n\n:thread: `:thread:`\n\n:three: `:three:`\n\n:thumbsdown: `:thumbsdown:`\n\n:thumbsup: `:thumbsup:`\n\n:ticket: `:ticket:`\n\n:tickets: `:tickets:`\n\n:tiger: `:tiger:`\n\n:tiger2: `:tiger2:`\n\n:timer_clock: `:timer_clock:`\n\n:timor_leste: `:timor_leste:`\n\n:tipping_hand_man: `:tipping_hand_man:`\n\n:tipping_hand_person: `:tipping_hand_person:`\n\n:tipping_hand_woman: `:tipping_hand_woman:`\n\n:tired_face: `:tired_face:`\n\n:tm: `:tm:`\n\n:togo: `:togo:`\n\n:toilet: `:toilet:`\n\n:tokelau: `:tokelau:`\n\n:tokyo_tower: `:tokyo_tower:`\n\n:tomato: `:tomato:`\n\n:tonga: `:tonga:`\n\n:tongue: `:tongue:`\n\n:toolbox: `:toolbox:`\n\n:tooth: `:tooth:`\n\n:toothbrush: `:toothbrush:`\n\n:top: `:top:`\n\n:tophat: `:tophat:`\n\n:tornado: `:tornado:`\n\n:tr: `:tr:`\n\n:trackball: `:trackball:`\n\n:tractor: `:tractor:`\n\n:traffic_light: `:traffic_light:`\n\n:train: `:train:`\n\n:train2: `:train2:`\n\n:tram: `:tram:`\n\n:transgender_flag: `:transgender_flag:`\n\n:transgender_symbol: `:transgender_symbol:`\n\n:triangular_flag_on_post: `:triangular_flag_on_post:`\n\n:triangular_ruler: `:triangular_ruler:`\n\n:trident: `:trident:`\n\n:trinidad_tobago: `:trinidad_tobago:`\n\n:tristan_da_cunha: `:tristan_da_cunha:`\n\n:triumph: `:triumph:`\n\n:troll: `:troll:`\n\n:trolleybus: `:trolleybus:`\n\n:trollface: `:trollface:`\n\n:trophy: `:trophy:`\n\n:tropical_drink: `:tropical_drink:`\n\n:tropical_fish: `:tropical_fish:`\n\n:truck: `:truck:`\n\n:trumpet: `:trumpet:`\n\n:tshirt: `:tshirt:`\n\n:tulip: `:tulip:`\n\n:tumbler_glass: `:tumbler_glass:`\n\n:tunisia: `:tunisia:`\n\n:turkey: `:turkey:`\n\n:turkmenistan: `:turkmenistan:`\n\n:turks_caicos_islands: `:turks_caicos_islands:`\n\n:turtle: `:turtle:`\n\n:tuvalu: `:tuvalu:`\n\n:tv: `:tv:`\n\n:twisted_rightwards_arrows: `:twisted_rightwards_arrows:`\n\n:two: `:two:`\n\n:two_hearts: `:two_hearts:`\n\n:two_men_holding_hands: `:two_men_holding_hands:`\n\n:two_women_holding_hands: `:two_women_holding_hands:`\n\n:u5272: `:u5272:`\n\n:u5408: `:u5408:`\n\n:u55b6: `:u55b6:`\n\n:u6307: `:u6307:`\n\n:u6708: `:u6708:`\n\n:u6709: `:u6709:`\n\n:u6e80: `:u6e80:`\n\n:u7121: `:u7121:`\n\n:u7533: `:u7533:`\n\n:u7981: `:u7981:`\n\n:u7a7a: `:u7a7a:`\n\n:uganda: `:uganda:`\n\n:uk: `:uk:`\n\n:ukraine: `:ukraine:`\n\n:umbrella: `:umbrella:`\n\n:unamused: `:unamused:`\n\n:underage: `:underage:`\n\n:unicorn: `:unicorn:`\n\n:united_arab_emirates: `:united_arab_emirates:`\n\n:united_nations: `:united_nations:`\n\n:unlock: `:unlock:`\n\n:up: `:up:`\n\n:upside_down_face: `:upside_down_face:`\n\n:uruguay: `:uruguay:`\n\n:us: `:us:`\n\n:us_outlying_islands: `:us_outlying_islands:`\n\n:us_virgin_islands: `:us_virgin_islands:`\n\n:uzbekistan: `:uzbekistan:`\n\n:v: `:v:`\n\n:vampire: `:vampire:`\n\n:vampire_man: `:vampire_man:`\n\n:vampire_woman: `:vampire_woman:`\n\n:vanuatu: `:vanuatu:`\n\n:vatican_city: `:vatican_city:`\n\n:venezuela: `:venezuela:`\n\n:vertical_traffic_light: `:vertical_traffic_light:`\n\n:vhs: `:vhs:`\n\n:vibration_mode: `:vibration_mode:`\n\n:video_camera: `:video_camera:`\n\n:video_game: `:video_game:`\n\n:vietnam: `:vietnam:`\n\n:violin: `:violin:`\n\n:virgo: `:virgo:`\n\n:volcano: `:volcano:`\n\n:volleyball: `:volleyball:`\n\n:vomiting_face: `:vomiting_face:`\n\n:vs: `:vs:`\n\n:vulcan_salute: `:vulcan_salute:`\n\n:waffle: `:waffle:`\n\n:wales: `:wales:`\n\n:walking: `:walking:`\n\n:walking_man: `:walking_man:`\n\n:walking_woman: `:walking_woman:`\n\n:wallis_futuna: `:wallis_futuna:`\n\n:waning_crescent_moon: `:waning_crescent_moon:`\n\n:waning_gibbous_moon: `:waning_gibbous_moon:`\n\n:warning: `:warning:`\n\n:wastebasket: `:wastebasket:`\n\n:watch: `:watch:`\n\n:water_buffalo: `:water_buffalo:`\n\n:water_polo: `:water_polo:`\n\n:watermelon: `:watermelon:`\n\n:wave: `:wave:`\n\n:wavy_dash: `:wavy_dash:`\n\n:waxing_crescent_moon: `:waxing_crescent_moon:`\n\n:waxing_gibbous_moon: `:waxing_gibbous_moon:`\n\n:wc: `:wc:`\n\n:weary: `:weary:`\n\n:wedding: `:wedding:`\n\n:weight_lifting: `:weight_lifting:`\n\n:weight_lifting_man: `:weight_lifting_man:`\n\n:weight_lifting_woman: `:weight_lifting_woman:`\n\n:western_sahara: `:western_sahara:`\n\n:whale: `:whale:`\n\n:whale2: `:whale2:`\n\n:wheel: `:wheel:`\n\n:wheel_of_dharma: `:wheel_of_dharma:`\n\n:wheelchair: `:wheelchair:`\n\n:white_check_mark: `:white_check_mark:`\n\n:white_circle: `:white_circle:`\n\n:white_flag: `:white_flag:`\n\n:white_flower: `:white_flower:`\n\n:white_haired_man: `:white_haired_man:`\n\n:white_haired_woman: `:white_haired_woman:`\n\n:white_heart: `:white_heart:`\n\n:white_large_square: `:white_large_square:`\n\n:white_medium_small_square: `:white_medium_small_square:`\n\n:white_medium_square: `:white_medium_square:`\n\n:white_small_square: `:white_small_square:`\n\n:white_square_button: `:white_square_button:`\n\n:wilted_flower: `:wilted_flower:`\n\n:wind_chime: `:wind_chime:`\n\n:wind_face: `:wind_face:`\n\n:window: `:window:`\n\n:wine_glass: `:wine_glass:`\n\n:wing: `:wing:`\n\n:wink: `:wink:`\n\n:wireless: `:wireless:`\n\n:wolf: `:wolf:`\n\n:woman: `:woman:`\n\n:woman_artist: `:woman_artist:`\n\n:woman_astronaut: `:woman_astronaut:`\n\n:woman_beard: `:woman_beard:`\n\n:woman_cartwheeling: `:woman_cartwheeling:`\n\n:woman_cook: `:woman_cook:`\n\n:woman_dancing: `:woman_dancing:`\n\n:woman_facepalming: `:woman_facepalming:`\n\n:woman_factory_worker: `:woman_factory_worker:`\n\n:woman_farmer: `:woman_farmer:`\n\n:woman_feeding_baby: `:woman_feeding_baby:`\n\n:woman_firefighter: `:woman_firefighter:`\n\n:woman_health_worker: `:woman_health_worker:`\n\n:woman_in_manual_wheelchair: `:woman_in_manual_wheelchair:`\n\n:woman_in_motorized_wheelchair: `:woman_in_motorized_wheelchair:`\n\n:woman_in_tuxedo: `:woman_in_tuxedo:`\n\n:woman_judge: `:woman_judge:`\n\n:woman_juggling: `:woman_juggling:`\n\n:woman_mechanic: `:woman_mechanic:`\n\n:woman_office_worker: `:woman_office_worker:`\n\n:woman_pilot: `:woman_pilot:`\n\n:woman_playing_handball: `:woman_playing_handball:`\n\n:woman_playing_water_polo: `:woman_playing_water_polo:`\n\n:woman_scientist: `:woman_scientist:`\n\n:woman_shrugging: `:woman_shrugging:`\n\n:woman_singer: `:woman_singer:`\n\n:woman_student: `:woman_student:`\n\n:woman_teacher: `:woman_teacher:`\n\n:woman_technologist: `:woman_technologist:`\n\n:woman_with_headscarf: `:woman_with_headscarf:`\n\n:woman_with_probing_cane: `:woman_with_probing_cane:`\n\n:woman_with_turban: `:woman_with_turban:`\n\n:woman_with_veil: `:woman_with_veil:`\n\n:womans_clothes: `:womans_clothes:`\n\n:womans_hat: `:womans_hat:`\n\n:women_wrestling: `:women_wrestling:`\n\n:womens: `:womens:`\n\n:wood: `:wood:`\n\n:woozy_face: `:woozy_face:`\n\n:world_map: `:world_map:`\n\n:worm: `:worm:`\n\n:worried: `:worried:`\n\n:wrench: `:wrench:`\n\n:wrestling: `:wrestling:`\n\n:writing_hand: `:writing_hand:`\n\n:x: `:x:`\n\n:x_ray: `:x_ray:`\n\n:yarn: `:yarn:`\n\n:yawning_face: `:yawning_face:`\n\n:yellow_circle: `:yellow_circle:`\n\n:yellow_heart: `:yellow_heart:`\n\n:yellow_square: `:yellow_square:`\n\n:yemen: `:yemen:`\n\n:yen: `:yen:`\n\n:yin_yang: `:yin_yang:`\n\n:yo_yo: `:yo_yo:`\n\n:yum: `:yum:`\n\n:zambia: `:zambia:`\n\n:zany_face: `:zany_face:`\n\n:zap: `:zap:`\n\n:zebra: `:zebra:`\n\n:zero: `:zero:`\n\n:zimbabwe: `:zimbabwe:`\n\n:zipper_mouth_face: `:zipper_mouth_face:`\n\n:zombie: `:zombie:`\n\n:zombie_man: `:zombie_man:`\n\n:zombie_woman: `:zombie_woman:`\n\n:zzz: `:zzz:`\n\n<!-- END: Auto-generated content (/build/emoji.js) -->\n\n</div>\n"
  },
  {
    "path": "docs/helpers.md",
    "content": "# Doc helper\n\ndocsify extends Markdown syntax to make your documents more readable.\n\n> Note: For the special code syntax cases, it's better to put them within code backticks to avoid any conflict from configurations or emojis.\n\n## Callouts\n\nDocsify supports [GitHub style](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts) callouts (also known as \"admonitions\" or \"alerts\").\n\n<!-- prettier-ignore -->\n> [!CAUTION]\n> **Caution** callouts communicate negative potential consequences of an action.\n\n<!-- prettier-ignore -->\n> [!IMPORTANT]\n> **Important** callouts communicate information necessary for users to succeed.\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> **Note** callouts communicate information that users should take into account.\n\n<!-- prettier-ignore -->\n> [!TIP]\n> **Tip** callouts communicate optional information to help a user be more successful.\n\n<!-- prettier-ignore -->\n> [!WARNING]\n> **Warning** callouts communicate potential risks user should be aware of.\n\n**Markdown**\n\n<!-- prettier-ignore -->\n```markdown\n> [!CAUTION]\n> **Caution** callouts communicate negative potential consequences of an action.\n\n> [!IMPORTANT]\n> **Important** callouts communicate information necessary for users to succeed.\n\n> [!NOTE]\n> **Note** callouts communicate information that users should take into account.\n\n> [!TIP]\n> **Tip** callouts communicate optional information to help a user be more successful.\n\n> [!WARNING]\n> **Warning** callouts communicate potential risks user should be aware of.\n```\n\n### Legacy Style ⚠️\n\nThe following Docsify v4 callout syntax has been deprecated and will be removed in a future version.\n\n!> Legacy **Important** callouts are deprecated.\n\n?> Legacy **Tip** callouts are deprecated.\n\n**Markdown**\n\n```markdown\n!> Legacy **Important** callouts are deprecated.\n\n?> Legacy **Tip** callouts are deprecated.\n```\n\n## Link attributes\n\n### disabled\n\n```markdown\n[link](/demo ':disabled')\n```\n\n### href\n\nSometimes we will use some other relative path for the link, and we have to tell docsify that we don't need to compile this link. For example:\n\n```markdown\n[link](/demo/)\n```\n\nIt will be compiled to `<a href=\"/#/demo/\">link</a>` and will load `/demo/README.md`. Maybe you want to jump to `/demo/index.html`.\n\nNow you can do that\n\n```markdown\n[link](/demo/ ':ignore')\n```\n\nYou will get `<a href=\"/demo/\">link</a>`html. Do not worry, you can still set the title for the link.\n\n```markdown\n[link](/demo/ ':ignore title')\n\n<a href=\"/demo/\" title=\"title\">link</a>\n```\n\n### target\n\n```markdown\n[link](/demo ':target=_blank')\n[link](/demo2 ':target=_self')\n```\n\n## Task lists\n\n```markdown\n- [ ] foo\n- bar\n- [x] baz\n- [] bam <~ not working\n  - [ ] bim\n  - [ ] lim\n```\n\n- [ ] foo\n- bar\n- [x] baz\n- [] bam <~ not working\n  - [ ] bim\n  - [ ] lim\n\n## Images\n\n### Class names\n\n```markdown\n![logo](https://docsify.js.org/_media/icon.svg ':class=someCssClass')\n\n<!-- Multiple class names -->\n\n![logo](https://docsify.js.org/_media/icon.svg ':class=someCssClass :class=anotherCssClass')\n```\n\n### IDs\n\n```markdown\n![logo](https://docsify.js.org/_media/icon.svg ':id=someCssId')\n```\n\n### Sizes\n\n```markdown\n![logo](https://docsify.js.org/_media/icon.svg ':size=WIDTHxHEIGHT')\n![logo](https://docsify.js.org/_media/icon.svg ':size=50x100')\n![logo](https://docsify.js.org/_media/icon.svg ':size=100')\n\n<!-- Support percentage -->\n\n![logo](https://docsify.js.org/_media/icon.svg ':size=10%')\n```\n\n![logo](https://docsify.js.org/_media/icon.svg ':size=50x100')\n![logo](https://docsify.js.org/_media/icon.svg ':size=100')\n![logo](https://docsify.js.org/_media/icon.svg ':size=10%')\n\n## Heading IDs\n\n```markdown\n### Hello, world! :id=hello-world\n```\n\n## Markdown + HTML\n\nYou need to insert a space between the html and markdown content.\nThis is useful for rendering markdown content in the details element.\n\n```markdown\n<details>\n<summary>Self-assessment (Click to expand)</summary>\n\n- Abc\n- Abc\n\n</details>\n```\n\n<details>\n<summary>Self-assessment (Click to expand)</summary>\n\n- Abc\n- Abc\n\n</details>\n\nMarkdown content can also be wrapped in html tags.\n\n```markdown\n<div style='color: red'>\n\n- listitem\n- listitem\n- listitem\n\n</div>\n```\n\n<div style='color: red'>\n\n- listitem\n- listitem\n- listitem\n\n</div>\n"
  },
  {
    "path": "docs/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>docsify</title>\n    <link rel=\"icon\" href=\"_media/favicon.ico\" />\n    <meta\n      name=\"google-site-verification\"\n      content=\"6t0LoIeFksrjF4c9sqUEsVXiQNxLp2hgoqo0KryT-sE\"\n    />\n    <meta\n      name=\"keywords\"\n      content=\"doc,docs,documentation,gitbook,creator,generator,github,jekyll,github-pages\"\n    />\n    <meta name=\"description\" content=\"A magical documentation generator.\" />\n    <meta\n      name=\"viewport\"\n      content=\"width=device-width, initial-scale=1.0, viewport-fit=cover\"\n    />\n\n    <!-- Core Theme -->\n    <link\n      rel=\"stylesheet\"\n      href=\"//cdn.jsdelivr.net/npm/docsify@5/dist/themes/core.min.css\"\n    />\n\n    <!-- Theme Add-on(s) -->\n    <link\n      rel=\"stylesheet\"\n      href=\"//cdn.jsdelivr.net/npm/docsify@5/dist/themes/addons/core-dark.min.css\"\n      media=\"(prefers-color-scheme: dark)\"\n      data-group=\"addon\"\n      data-sheet=\"core-dark-auto\"\n    />\n    <link\n      rel=\"stylesheet\"\n      href=\"//cdn.jsdelivr.net/npm/docsify@5/dist/themes/addons/core-dark.min.css\"\n      data-group=\"addon\"\n      data-sheet=\"core-dark\"\n      disabled\n    />\n    <link\n      rel=\"stylesheet\"\n      href=\"//cdn.jsdelivr.net/npm/docsify@5/dist/themes/addons/vue.min.css\"\n      data-group=\"addon\"\n      data-sheet=\"vue\"\n      disabled\n    />\n\n    <!-- Prism Theme -->\n    <!-- <link\n      rel=\"stylesheet\"\n      href=\"//cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-twilight.min.css\"\n    /> -->\n\n    <!-- Site styles -->\n    <style>\n      /* Plugin: Carbon Ads */\n      #carbonads > :last-child {\n        margin-block: calc((1em * var(--line-height)) / 2);\n      }\n\n      /* UI Kit */\n      .ui-kit-color {\n        display: flex;\n        justify-content: space-between;\n        gap: 10px;\n        font-size: var(--font-size-s);\n      }\n\n      .ui-kit-color figure {\n        flex-grow: 1;\n        margin: 0;\n        padding: 0;\n      }\n\n      .ui-kit-color figure div {\n        height: 4rem;\n        border-radius: var(--border-radius);\n      }\n\n      .ui-kit-color figcaption {\n        text-align: center;\n        padding: 0.5em 0;\n      }\n    </style>\n    <script>\n      // Set html \"lang\" attribute based on URL\n      (function () {\n        const lang = location.hash.match(/#\\/(zh-cn)\\//);\n\n        if (lang) {\n          document.documentElement.setAttribute('lang', lang[1]);\n        }\n      })();\n    </script>\n  </head>\n\n  <body class=\"loading sidebar-chevron-right sidebar-group-box\">\n    <div id=\"app\"></div>\n    <script src=\"//cdn.jsdelivr.net/npm/docsify-plugin-carbon@1\"></script>\n    <script>\n      // Docsify configuration\n      window.$docsify = {\n        alias: {\n          '.*?/awesome':\n            'https://raw.githubusercontent.com/docsifyjs/awesome-docsify/master/README.md',\n          '.*?/changelog':\n            'https://raw.githubusercontent.com/docsifyjs/docsify/main/CHANGELOG.md',\n          '/.*/_navbar.md': '/_navbar.md',\n          '/zh-cn/(.*)':\n            'https://cdn.jsdelivr.net/gh/docsifyjs/docs-zh@main/$1',\n        },\n        auto2top: true,\n        coverpage: true,\n        executeScript: true,\n        // hideSidebar: true,\n        loadSidebar: true,\n        loadNavbar: true,\n        // mergeNavbar: true,\n        maxLevel: 4,\n        // repo: 'docsifyjs/docsify',\n        // routerMode: 'history',\n        subMaxLevel: 2,\n        gtag: 'G-H77Y58DS3L',\n        name: 'docsify',\n        nameLink: {\n          '/zh-cn/': '#/zh-cn/',\n          '/': '#/',\n        },\n        search: {\n          // insertAfter: '.app-name',\n          // insertBefore: '.sidebar-nav',\n          noData: {\n            '/zh-cn/': '没有结果!',\n            '/': 'No results!',\n          },\n          paths: 'auto',\n          placeholder: {\n            '/zh-cn/': '搜索',\n            '/': 'Search',\n          },\n          pathNamespaces: ['/zh-cn'],\n          depth: 6,\n        },\n        skipLink: {\n          '/zh-cn/': '跳到主要内容',\n        },\n        vueComponents: {\n          'button-counter': {\n            template: /* html */ `<button @click=\"count += 1\">You clicked me {{ count }} times</button>`,\n            data() {\n              return {\n                count: 0,\n              };\n            },\n          },\n        },\n        vueGlobalOptions: {\n          data() {\n            return {\n              count: 0,\n              message: 'Hello, World!',\n              // Fake API response\n              images: [\n                { title: 'Image 1', url: 'https://picsum.photos/150?random=1' },\n                { title: 'Image 2', url: 'https://picsum.photos/150?random=2' },\n                { title: 'Image 3', url: 'https://picsum.photos/150?random=3' },\n              ],\n            };\n          },\n          computed: {\n            timeOfDay() {\n              const date = new Date();\n              const hours = date.getHours();\n\n              if (hours < 12) {\n                return 'morning';\n              } else if (hours < 18) {\n                return 'afternoon';\n              } else {\n                return 'evening';\n              }\n            },\n          },\n          methods: {\n            hello() {\n              alert(this.message);\n            },\n          },\n        },\n        vueMounts: {\n          '#counter': {\n            data() {\n              return {\n                count: 0,\n              };\n            },\n          },\n        },\n        plugins: [\n          DocsifyCarbon.create('CEBI6KQE', 'docsifyjsorg'),\n          // Plugin: Footer\n          function (hook, vm) {\n            hook.beforeEach(html => {\n              if (/githubusercontent\\.com/.test(vm.route.file)) {\n                url = vm.route.file\n                  .replace('raw.githubusercontent.com', 'github.com')\n                  .replace(/\\/master/, '/blob/master')\n                  .replace(/\\/main/, '/blob/main');\n              } else if (/jsdelivr\\.net/.test(vm.route.file)) {\n                url = vm.route.file\n                  .replace('cdn.jsdelivr.net/gh', 'github.com')\n                  .replace('@main', '/blob/main');\n              } else {\n                url =\n                  'https://github.com/docsifyjs/docsify/blob/develop/docs/' +\n                  vm.route.file;\n              }\n\n              const footerHTML = [\n                '<hr>',\n                '<div style=\"display: flex; align-items: center; justify-content: space-between;\">',\n                '  <span>Powered by <a href=\"/\">Docsify.js</a></span>',\n                `  <a href=\"${url}\" style=\"display: inline-flex; align-items: center; gap: 0.25em;\">:memo: Edit Page</a>`,\n                '</div>',\n              ].join('\\n');\n\n              return html + footerHTML;\n            });\n          },\n        ],\n      };\n    </script>\n    <script src=\"//cdn.jsdelivr.net/npm/docsify@5/dist/docsify.min.js\"></script>\n    <script src=\"//cdn.jsdelivr.net/npm/docsify@5/dist/plugins/search.min.js\"></script>\n    <script src=\"//cdn.jsdelivr.net/npm/docsify@5/dist/plugins/front-matter.min.js\"></script>\n    <script src=\"//cdn.jsdelivr.net/npm/docsify@5/dist/plugins/gtag.min.js\"></script>\n    <script src=\"//cdn.jsdelivr.net/npm/prismjs@1/components/prism-bash.min.js\"></script>\n    <script src=\"//cdn.jsdelivr.net/npm/prismjs@1/components/prism-markdown.min.js\"></script>\n    <script src=\"//cdn.jsdelivr.net/npm/prismjs@1/components/prism-nginx.min.js\"></script>\n    <script src=\"//cdn.jsdelivr.net/npm/prismjs@1/components/prism-php.min.js\"></script>\n\n    <!-- Vue: Development -->\n    <!-- <script src=\"//cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js\"></script> -->\n    <!-- <script src=\"//cdn.jsdelivr.net/npm/vue@3/dist/vue.runtime.global.js\"></script> -->\n\n    <!-- Vue: Production -->\n    <script src=\"//cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js\"></script>\n    <!-- <script src=\"//cdn.jsdelivr.net/npm/vue@3/dist/vue.runtime.global.prod.js\"></script> -->\n  </body>\n</html>\n"
  },
  {
    "path": "docs/language-highlight.md",
    "content": "# Language highlighting\n\n## Prism\n\nDocsify uses [Prism](https://prismjs.com) for syntax highlighting within code blocks. Prism supports the following languages by default (additional [language support](#language-support) also available):\n\n- Markup: HTML, XML, SVG, MathML, SSML, Atom, RSS\n- CSS\n- C-like\n- JavaScript\n\nTo enable syntax highlighting, create a markdown codeblock using backticks (` ``` `) with a [language](https://prismjs.com/#supported-languages) specified on the first line (e.g., `html`, `css`, `js`):\n\n````text\n```html\n<p>This is a paragraph</p>\n<a href=\"//docsify.js.org/\">Docsify</a>\n```\n````\n\n````text\n```css\np {\n  color: red;\n}\n```\n````\n\n````text\n```js\nfunction add(a, b) {\n  return a + b;\n}\n```\n````\n\nThe above markdown will be rendered as:\n\n```html\n<p>This is a paragraph</p>\n<a href=\"//docsify.js.org/\">Docsify</a>\n```\n\n```css\np {\n  color: red;\n}\n```\n\n```js\nfunction add(a, b) {\n  return a + b;\n}\n```\n\n## Language support\n\nSupport for additional [languages](https://prismjs.com/#supported-languages) is available by loading the Prism [grammar files](https://cdn.jsdelivr.net/npm/prismjs@1/components/):\n\n> [!IMPORTANT] Prism grammar files must be loaded after Docsify.\n\n```html\n<script src=\"//cdn.jsdelivr.net/npm/prismjs@1/components/prism-bash.min.js\"></script>\n<script src=\"//cdn.jsdelivr.net/npm/prismjs@1/components/prism-docker.min.js\"></script>\n<script src=\"//cdn.jsdelivr.net/npm/prismjs@1/components/prism-git.min.js\"></script>\n<script src=\"//cdn.jsdelivr.net/npm/prismjs@1/components/prism-java.min.js\"></script>\n<script src=\"//cdn.jsdelivr.net/npm/prismjs@1/components/prism-jsx.min.js\"></script>\n<script src=\"//cdn.jsdelivr.net/npm/prismjs@1/components/prism-markdown.min.js\"></script>\n<script src=\"//cdn.jsdelivr.net/npm/prismjs@1/components/prism-php.min.js\"></script>\n<script src=\"//cdn.jsdelivr.net/npm/prismjs@1/components/prism-python.min.js\"></script>\n<script src=\"//cdn.jsdelivr.net/npm/prismjs@1/components/prism-rust.min.js\"></script>\n<script src=\"//cdn.jsdelivr.net/npm/prismjs@1/components/prism-sql.min.js\"></script>\n<script src=\"//cdn.jsdelivr.net/npm/prismjs@1/components/prism-swift.min.js\"></script>\n<script src=\"//cdn.jsdelivr.net/npm/prismjs@1/components/prism-typescript.min.js\"></script>\n<script src=\"//cdn.jsdelivr.net/npm/prismjs@1/components/prism-yaml.min.js\"></script>\n```\n\n## Theme support\n\nDocsify's official [themes](themes) are compatible with Prism syntax highlighting themes.\n\n> [!IMPORTANT] Prism themes must be loaded after Docsify themes.\n\n```html\n<!-- Light and dark mode -->\n<link\n  rel=\"stylesheet\"\n  href=\"//cdn.jsdelivr.net/npm/prism-themes@1/themes/prism-one-light.min.css\"\n/>\n```\n\nThemes can be applied in light and/or dark mode\n\n```html\n<!-- Dark mode only -->\n<link\n  rel=\"stylesheet\"\n  media=\"(prefers-color-scheme: dark)\"\n  href=\"//cdn.jsdelivr.net/npm/prism-themes@1/themes/prism-one-dark.min.css\"\n/>\n\n<!-- Light mode only -->\n<link\n  rel=\"stylesheet\"\n  media=\"(prefers-color-scheme: light)\"\n  href=\"//cdn.jsdelivr.net/npm/prism-themes@1/themes/prism-one-light.min.css\"\n/>\n```\n\nThe following Docsify [theme properties](themes#theme-properties) will override Prism theme styles by default:\n\n```text\n--border-radius\n--font-family-mono\n--font-size-mono\n```\n\nTo use the values specified in the Prism theme, set the desired theme property to `unset`:\n\n<!-- prettier-ignore -->\n```html\n<style>\n  :root {\n    --border-radius   : unset;\n    --font-family-mono: unset;\n    --font-size-mono  : unset;\n  }\n</style>\n```\n\n## Dynamic content\n\nDynamically generated Code blocks can be highlighted using Prism's [`highlightElement()`](https://prismjs.com/docs/Prism.html#.highlightElement) method:\n\n```js\nconst code = document.createElement('code');\ncode.innerHTML = \"console.log('Hello World!')\";\ncode.setAttribute('class', 'language-javascript');\nPrism.highlightElement(code);\n```\n"
  },
  {
    "path": "docs/markdown.md",
    "content": "# Markdown configuration\n\n**docsify** uses [marked](https://github.com/markedjs/marked) as its Markdown parser. You can customize how it renders your Markdown content to HTML by customizing `renderer`:\n\n```js\nwindow.$docsify = {\n  markdown: {\n    smartypants: true,\n    renderer: {\n      link() {\n        // ...\n      },\n    },\n  },\n};\n```\n\n> [!TIP] Configuration Options Reference: [marked documentation](https://marked.js.org/#/USING_ADVANCED.md)\n\nYou can completely customize the parsing rules.\n\n```js\nwindow.$docsify = {\n  markdown(marked, renderer) {\n    // ...\n\n    return marked;\n  },\n};\n```\n\n## Supports mermaid\n\n> [!IMPORTANT] Currently, docsify doesn't support the async mermaid render (the latest mermaid version supported is `v9.3.0`).\n\n```js\n//  <link rel=\"stylesheet\" href=\"//cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.css\">\n//  <script src=\"//cdn.jsdelivr.net/npm/mermaid@9.3.0/dist/mermaid.min.js\"></script>\n\nlet num = 0;\nmermaid.initialize({ startOnLoad: false });\n\nwindow.$docsify = {\n  markdown: {\n    renderer: {\n      code({ text, lang }) {\n        if (lang === 'mermaid') {\n          return /* html */ `\n            <div class=\"mermaid\">${mermaid.render(\n              'mermaid-svg-' + num++,\n              text,\n            )}</div>\n          `;\n        }\n        return this.origin.code.apply(this, arguments);\n      },\n    },\n  },\n};\n```\n"
  },
  {
    "path": "docs/plugins.md",
    "content": "# List of Plugins\n\nThese are built-in and external plugins for Docsify.\n\nSee also how to [Write a Plugin](./write-a-plugin.md).\n\n## Full text search\n\nBy default, the hyperlink on the current page is recognized and the content is saved in `IndexedDB`. You can also specify the path to the files.\n\n<!-- prettier-ignore -->\n```html\n<script>\n  window.$docsify = {\n    search: 'auto', // default\n\n    search: [\n      '/',            // => /README.md\n      '/guide',       // => /guide.md\n      '/get-started', // => /get-started.md\n      '/zh-cn/',      // => /zh-cn/README.md\n    ],\n\n    // Complete configuration parameters\n    search: {\n      // Location in sidebar (default: prepended as first child)\n      // Optionally specify insertAfter or insertBefore (not both)\n      insertAfter: '.app-name', // CSS selector in .sidebar scope\n      insertBefore: '.sidebar-nav', // CSS selector in .sidebar scope\n\n      maxAge: 86400000, // Expiration time, the default one day\n      paths: [], // string[] of files to search in, or 'auto' for discovery based on your sidebar\n      placeholder: 'Type to search',\n\n      // Localization\n      placeholder: {\n        '/zh-cn/': '搜索',\n        '/': 'Type to search',\n      },\n\n      noData: 'No Results!',\n\n      // Localization\n      noData: {\n        '/zh-cn/': '找不到结果',\n        '/': 'No Results',\n      },\n\n      // Headline depth, 1 - 6\n      depth: 2,\n\n      hideOtherSidebarContent: true, // Deprecated as of v5\n\n      // To avoid search index collision\n      // between multiple websites under the same domain\n      namespace: 'website-1',\n\n      // Use different indexes for path prefixes (namespaces).\n      // NOTE: Only works in 'auto' mode.\n      //\n      // When initialiazing an index, we look for the first path from the sidebar.\n      // If it matches the prefix from the list, we switch to the corresponding index.\n      pathNamespaces: ['/zh-cn', '/ru-ru', '/ru-ru/v1'],\n\n      // You can provide a regexp to match prefixes. In this case,\n      // the matching substring will be used to identify the index\n      pathNamespaces: /^(\\/(zh-cn|ru-ru))?(\\/(v1|v2))?/,\n    },\n  };\n</script>\n<script src=\"//cdn.jsdelivr.net/npm/docsify@5/dist/docsify.min.js\"></script>\n<script src=\"//cdn.jsdelivr.net/npm/docsify@5/dist/plugins/search.min.js\"></script>\n```\n\nThis plugin ignores diacritical marks when performing a full text search (e.g., \"cafe\" will also match \"café\").\n\n## Google Analytics\n\n> Google's Universal Analytics service will no longer process new data in standard properties beginning July 1, 2023. Prepare now by setting up and switching over to a Google Analytics 4 property and docsify's gtag.js plugin.\n\nInstall the plugin and configure the track id.\n\n```html\n<script>\n  window.$docsify = {\n    ga: 'UA-XXXXX-Y',\n  };\n</script>\n<script src=\"//cdn.jsdelivr.net/npm/docsify@5/dist/docsify.min.js\"></script>\n<script src=\"//cdn.jsdelivr.net/npm/docsify@5/dist/plugins/ga.min.js\"></script>\n```\n\nConfigure by `data-ga`.\n\n<!-- prettier-ignore -->\n```html\n<script src=\"//cdn.jsdelivr.net/npm/docsify@5/dist/docsify.min.js\" data-ga=\"UA-XXXXX-Y\"></script>\n<script src=\"//cdn.jsdelivr.net/npm/docsify@5/dist/plugins/ga.min.js\"></script>\n```\n\n## Google Analytics 4 (GA4)\n\nInstall the plugin and configure the track id.\n\n```html\n<script>\n  // Single ID\n  window.$docsify = {\n    gtag: 'UA-XXXXX-Y',\n  };\n\n  // Multiple IDs\n  window.$docsify = {\n    gtag: [\n      'G-XXXXXXXX', // Google Analytics 4 (GA4)\n      'UA-XXXXXXXX', // Google Universal Analytics (GA3)\n      'AW-XXXXXXXX', // Google Ads\n      'DC-XXXXXXXX', // Floodlight\n    ],\n  };\n</script>\n<script src=\"//cdn.jsdelivr.net/npm/docsify@5/dist/docsify.min.js\"></script>\n<script src=\"//cdn.jsdelivr.net/npm/docsify@5/dist/plugins/gtag.min.js\"></script>\n```\n\n## Emoji\n\nRenders a larger collection of emoji shorthand codes. Without this plugin, Docsify is able to render only a limited number of emoji shorthand codes.\n\n> [!IMPORTANT] Deprecated as of v4.13. Docsify no longer requires this plugin for full emoji support.\n\n```html\n<script src=\"//cdn.jsdelivr.net/npm/docsify@5/dist/plugins/emoji.min.js\"></script>\n```\n\n## External Script\n\nIf the script on the page is an external one (imports a js file via `src` attribute), you'll need this plugin to make it work.\n\n```html\n<script src=\"//cdn.jsdelivr.net/npm/docsify@5/dist/plugins/external-script.min.js\"></script>\n```\n\n## Zoom image\n\nMedium's image zoom. Based on [medium-zoom](https://github.com/francoischalifour/medium-zoom).\n\n```html\n<script src=\"//cdn.jsdelivr.net/npm/docsify@5/dist/plugins/zoom-image.min.js\"></script>\n```\n\nExclude the special image\n\n```markdown\n![](image.png ':no-zoom')\n```\n\n## Edit on github\n\nAdd `Edit on github` button on every pages. Provided by [@njleonzhang](https://github.com/njleonzhang), see this [document](https://github.com/njleonzhang/docsify-edit-on-github)\n\n## Demo code with instant preview and jsfiddle integration\n\nWith this plugin, sample code can be rendered on the page instantly, so that the readers can see the preview immediately.\nWhen readers expand the demo box, the source code and description are shown there. if they click the button `Try in Jsfiddle`,\n`jsfiddle.net` will be open with the code of this sample, which allow readers to revise the code and try on their own.\n\n[Vue](https://njleonzhang.github.io/docsify-demo-box-vue/) and [React](https://njleonzhang.github.io/docsify-demo-box-react/) are both supported.\n\n## Copy to Clipboard\n\nAdd a simple `Click to copy` button to all preformatted code blocks to effortlessly allow users to copy example code from your docs. Provided by [@jperasmus](https://github.com/jperasmus)\n\n```html\n<script src=\"//cdn.jsdelivr.net/npm/docsify-copy-code/dist/docsify-copy-code.min.js\"></script>\n```\n\nSee [here](https://github.com/jperasmus/docsify-copy-code/blob/master/README.md) for more details.\n\n## Disqus\n\nDisqus comments. https://disqus.com/\n\n```html\n<script>\n  window.$docsify = {\n    disqus: 'shortname',\n  };\n</script>\n<script src=\"//cdn.jsdelivr.net/npm/docsify@5/dist/plugins/disqus.min.js\"></script>\n```\n\n## Gitalk\n\n[Gitalk](https://github.com/gitalk/gitalk) is a modern comment component based on GitHub Issue and Preact.\n\n```html\n<link rel=\"stylesheet\" href=\"//cdn.jsdelivr.net/npm/gitalk/dist/gitalk.css\" />\n\n<script src=\"//cdn.jsdelivr.net/npm/docsify@5/dist/plugins/gitalk.min.js\"></script>\n<script src=\"//cdn.jsdelivr.net/npm/gitalk/dist/gitalk.min.js\"></script>\n<script>\n  const gitalk = new Gitalk({\n    clientID: 'GitHub Application Client ID',\n    clientSecret: 'GitHub Application Client Secret',\n    repo: 'GitHub repo',\n    owner: 'GitHub repo owner',\n    admin: [\n      'GitHub repo collaborators, only these guys can initialize github issues',\n    ],\n    // facebook-like distraction free mode\n    distractionFreeMode: false,\n  });\n</script>\n```\n\n## Pagination\n\nPagination for docsify. By [@imyelo](https://github.com/imyelo)\n\n```html\n<script src=\"//cdn.jsdelivr.net/npm/docsify@5/dist/docsify.min.js\"></script>\n<script src=\"//cdn.jsdelivr.net/npm/docsify-pagination/dist/docsify-pagination.min.js\"></script>\n```\n\nClick [here](https://github.com/imyelo/docsify-pagination#readme) to get more information.\n\n## Tabs\n\nA docsify.js plugin for displaying tabbed content from markdown.\n\n- [Documentation & Demos](https://jhildenbiddle.github.io/docsify-tabs)\n\nProvided by [@jhildenbiddle](https://github.com/jhildenbiddle/docsify-tabs).\n\n## More plugins\n\nSee [awesome-docsify](awesome?id=plugins)\n"
  },
  {
    "path": "docs/pwa.md",
    "content": "# Offline Mode\n\n[Progressive Web Apps](https://developers.google.com/web/progressive-web-apps/) (PWA) are experiences that combine the best of the web with the best of apps. We can enhance our website with service workers to work **offline** or on low-quality networks.\n\nIt is also very easy to use.\n\n## Create serviceWorker\n\nCreate a `sw.js` file in your project's root directory and copy the following code:\n\n_sw.js_\n\n```js\n/* ===========================================================\n * docsify sw.js\n * ===========================================================\n * Copyright 2016 @huxpro\n * Licensed under Apache 2.0\n * Register service worker.\n * ========================================================== */\n\nconst RUNTIME = 'docsify';\nconst HOSTNAME_WHITELIST = [\n  self.location.hostname,\n  'fonts.gstatic.com',\n  'fonts.googleapis.com',\n  'cdn.jsdelivr.net',\n];\n\n// The Util Function to hack URLs of intercepted requests\nconst getFixedUrl = req => {\n  const now = Date.now();\n  const url = new URL(req.url);\n\n  // 1. fixed http URL\n  // Just keep syncing with location.protocol\n  // fetch(httpURL) belongs to active mixed content.\n  // And fetch(httpRequest) is not supported yet.\n  url.protocol = self.location.protocol;\n\n  // 2. add query for caching-busting.\n  // GitHub Pages served with Cache-Control: max-age=600\n  // max-age on mutable content is error-prone, with SW life of bugs can even extend.\n  // Until cache mode of Fetch API landed, we have to workaround cache-busting with query string.\n  // Cache-Control-Bug: https://bugs.chromium.org/p/chromium/issues/detail?id=453190\n  if (url.hostname === self.location.hostname) {\n    url.search += (url.search ? '&' : '?') + 'cache-bust=' + now;\n  }\n  return url.href;\n};\n\n/**\n *  @Lifecycle Activate\n *  New one activated when old isnt being used.\n *\n *  waitUntil(): activating ====> activated\n */\nself.addEventListener('activate', event => {\n  event.waitUntil(self.clients.claim());\n});\n\n/**\n *  @Functional Fetch\n *  All network requests are being intercepted here.\n *\n *  void respondWith(Promise<Response> r)\n */\nself.addEventListener('fetch', event => {\n  // Skip some of cross-origin requests, like those for Google Analytics.\n  if (HOSTNAME_WHITELIST.indexOf(new URL(event.request.url).hostname) > -1) {\n    // Stale-while-revalidate\n    // similar to HTTP's stale-while-revalidate: https://www.mnot.net/blog/2007/12/12/stale\n    // Upgrade from Jake's to Surma's: https://gist.github.com/surma/eb441223daaedf880801ad80006389f1\n    const cached = caches.match(event.request);\n    const fixedUrl = getFixedUrl(event.request);\n    const fetched = fetch(fixedUrl, { cache: 'no-store' });\n    const fetchedCopy = fetched.then(resp => resp.clone());\n\n    // Call respondWith() with whatever we get first.\n    // If the fetch fails (e.g disconnected), wait for the cache.\n    // If there’s nothing in cache, wait for the fetch.\n    // If neither yields a response, return offline pages.\n    event.respondWith(\n      Promise.race([fetched.catch(_ => cached), cached])\n        .then(resp => resp || fetched)\n        .catch(_ => {\n          /* eat any errors */\n        }),\n    );\n\n    // Update the cache with the version we fetched (only for ok status)\n    event.waitUntil(\n      Promise.all([fetchedCopy, caches.open(RUNTIME)])\n        .then(\n          ([response, cache]) =>\n            response.ok && cache.put(event.request, response),\n        )\n        .catch(_ => {\n          /* eat any errors */\n        }),\n    );\n  }\n});\n```\n\n## Register\n\nNow, register it in your `index.html`. It only works on some modern browsers, so we need to check:\n\n_index.html_\n\n```html\n<script>\n  if (typeof navigator.serviceWorker !== 'undefined') {\n    navigator.serviceWorker.register('sw.js');\n  }\n</script>\n```\n\n## Enjoy it\n\nRelease your website and start experiencing the magical offline feature.\n"
  },
  {
    "path": "docs/quickstart.md",
    "content": "# Quick start\n\nIt is recommended to install `docsify-cli` globally, which helps initializing and previewing the website locally.\n\n```bash\nnpm i docsify-cli -g\n```\n\n## Initialize\n\nIf you want to write the documentation in the `./docs` subdirectory, you can use the `init` command.\n\n```bash\ndocsify init ./docs\n```\n\n## Writing content\n\nAfter the `init` is complete, you can see the file list in the `./docs` subdirectory.\n\n- `index.html` as the entry file\n- `README.md` as the home page\n- `.nojekyll` prevents GitHub Pages from ignoring files that begin with an underscore\n\nYou can easily update the documentation in `./docs/README.md`, and of course you can add [more pages](adding-pages.md).\n\n## Preview your site\n\nRun the local server with `docsify serve`. You can preview your site in your browser on `http://localhost:3000`.\n\n```bash\ndocsify serve docs\n```\n\n> [!TIP] For more use cases of `docsify-cli`, head over to the [docsify-cli documentation](https://github.com/docsifyjs/docsify-cli).\n\n## Manual initialization\n\nDownload or create an `index.html` template using the following markup:\n\n<div id=\"template\">\n\n<a href=\"#\" class=\"button primary\" download=\"index.html\">Download Template</a>\n\n<!-- prettier-ignore -->\n```html\n<!doctype html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, viewport-fit=cover\">\n\n    <!-- Core Theme -->\n    <link rel=\"stylesheet\" href=\"//cdn.jsdelivr.net/npm/docsify@5/dist/themes/core.min.css\">\n  </head>\n  <body class=\"loading\">\n    <div id=\"app\"></div>\n\n    <!-- Configuration -->\n    <script>\n      window.$docsify = {\n        //...\n      };\n    </script>\n\n    <!-- Docsify.js -->\n    <script src=\"//cdn.jsdelivr.net/npm/docsify@5\"></script>\n\n    <!-- Plugins (optional) -->\n    <!-- <script src=\"//cdn.jsdelivr.net/npm/docsify@5/dist/plugins/search.min.js\"></script> -->\n  </body>\n</html>\n```\n\n</div>\n\n### Specifying docsify versions\n\n> [!TIP] Note that in both of the examples below, docsify URLs will need to be\n> manually updated when a new major version of docsify is released (e.g. `v5.x.x`\n> => `v6.x.x`). Check the docsify website periodically to see if a new major\n> version has been released.\n\nSpecifying a major version in the URL (`@5`) will allow your site to receive\nnon-breaking enhancements (i.e. \"minor\" updates) and bug fixes (i.e. \"patch\"\nupdates) automatically. This is the recommended way to load docsify resources.\n\n<!-- prettier-ignore -->\n```html\n<!-- Core Theme -->\n<link rel=\"stylesheet\" href=\"//cdn.jsdelivr.net/npm/docsify@5/dist/themes/core.min.css\">\n\n<!-- Docsify -->\n<script src=\"//cdn.jsdelivr.net/npm/docsify@5\"></script>\n```\n\nIf you'd like to ensure absolutely no possibility of changes that will break\nyour site (non-major updates can unintentionally introduce breaking changes\ndespite that they aim not to), specify the full version after the `@` symbol in\nthe URL. This is the safest way to ensure your site will look and behave the\nsame way regardless of any changes made to future versions of docsify.\n\n<!-- prettier-ignore -->\n```html\n<!-- Core Theme -->\n<link rel=\"stylesheet\" href=\"//cdn.jsdelivr.net/npm/docsify@5.0.0/themes/core.min.css\">\n\n<!-- Docsify -->\n<script src=\"//cdn.jsdelivr.net/npm/docsify@5.0.0\"></script>\n```\n\nJSDelivr supports [npm-compatible semver ranges](https://docs.npmjs.com/cli/v11/configuring-npm/package-json#dependencies),\nso you can also use version syntax such as `@^5.0.0` for the latest v5 release,\n`@5.0.x` for the latest v5.0 patch release (f.e. you will receive 5.0.4 but\nnot 5.1.0), `@5.x` for the latest v5 minor and patch releases (which is\neffectively the same as `@5` and `@^5.0.0`), etc.\n\n### Manually preview your site\n\nIf you have Python installed on your system, you can easily use it to run a\nstatic server to preview your site instead of using `docsify serve` from\n`docsify-cli`.\n\n```python\n# Python 2\ncd docs && python -m SimpleHTTPServer 3000\n```\n\n```python\n# Python 3\ncd docs && python -m http.server 3000\n```\n\n<script>\n  (function() {\n    const linkElm = document.querySelector('#template a[download=\"index.html\"]');\n    const codeElm = document.querySelector('#template code');\n    const html = codeElm?.textContent;\n\n    linkElm?.setAttribute('href', `data:text/plain,${html}`);\n  })();\n</script>\n"
  },
  {
    "path": "docs/themes.md",
    "content": "# Themes\n\n## Core theme\n\nThe Docsify \"core\" theme contains all of the styles and [theme properties](#theme-properties) needed to render a Docsify site. This theme is designed to serve as a minimalist theme on its own, in combination with [theme add-ons](#theme-add-ons), modified using core [classes](#classes), and as a starting point for [customization](#customization).\n\n<!-- prettier-ignore -->\n```html\n<!-- Core Theme -->\n<link rel=\"stylesheet\" href=\"//cdn.jsdelivr.net/npm/docsify@5/dist/themes/core.min.css\" />\n```\n\n## Theme add-ons\n\nTheme add-ons are used in combination with the [core theme](#core-theme). Add-ons contain CSS rules that modify [theme properties](#theme-properties) values and/or add custom style declarations. They can often (but not always) be used with other add-ons.\n\n> [!IMPORTANT] Theme add-ons must be loaded after the [core theme](#core-theme).\n\n<!-- prettier-ignore -->\n```html\n<!-- Core Theme -->\n<link rel=\"stylesheet\" href=\"...\" />\n\n<!-- Theme (add-on) -->\n<link rel=\"stylesheet\" href=\"...\" />\n```\n\n### Core Dark (Add-on)\n\nDark mode styles for the [core theme](#core-theme). Styles can applied only when an operating system's dark mode is active by specifying a `media` attribute.\n\n<label>\n  <input\n    class=\"toggle\"\n    type=\"checkbox\"\n    value=\"core-dark\"\n    data-group=\"addon\"\n    data-sheet\n  >\n  Preview Core Dark\n</label>\n<br>\n<label>\n  <input\n    class=\"toggle\"\n    type=\"checkbox\"\n    value=\"core-dark-auto\"\n    data-group=\"addon\"\n    data-sheet\n  >\n  Preview Core Dark (Dark Mode Only)\n</label>\n\n<!-- prettier-ignore -->\n```html\n<!-- Core Dark (add-on) -->\n<link rel=\"stylesheet\" href=\"//cdn.jsdelivr.net/npm/docsify@5/dist/themes/addons/core-dark.min.css\" />\n```\n\n<!-- prettier-ignore -->\n```html\n<!-- Core Dark - Dark Mode Only (add-on) -->\n<link rel=\"stylesheet\" href=\"//cdn.jsdelivr.net/npm/docsify@5/dist/themes/addons/core-dark.min.css\" media=\"(prefers-color-scheme: dark)\" />\n```\n\n### Vue theme (Add-on)\n\nThe popular Docsify v4 theme.\n\n<label>\n  <input\n   class=\"toggle\"\n   type=\"checkbox\"\n   value=\"vue\"\n   data-group=\"addon\"\n   data-sheet\n  >\n  Preview Vue\n</label>\n\n<!-- prettier-ignore -->\n```html\n<!-- Vue Theme (add-on) -->\n<link rel=\"stylesheet\" href=\"//cdn.jsdelivr.net/npm/docsify@5/dist/themes/addons/vue.min.css\" />\n```\n\n## Classes\n\nThe [core theme](#core-theme) provides several CSS classes for customizing your Docsify site. These classes should be applied to the `<body>` element within your `index.html` page.\n\n<!-- prettier-ignore -->\n```html\n<body class=\"...\">\n```\n\n### Loading\n\nDisplay a loading animation while waiting for Docsify to initialize.\n\n<!-- prettier-ignore -->\n```html\n<body class=\"loading\">\n```\n\n<output data-lang=\"output\">\n  <div class=\"loading\" style=\"margin: auto;\"></div>\n</output>\n\n### Sidebar chevrons\n\nDisplay expand/collapse icons on page links in the sidebar.\n\n<label>\n  <input class=\"toggle\" type=\"checkbox\" value=\"sidebar-chevron-right\" data-class data-group=\"sidebar-chevron\"> Preview <code>sidebar-chevron-right</code>\n</label>\n<br>\n<label>\n  <input class=\"toggle\" type=\"checkbox\" value=\"sidebar-chevron-left\" data-class data-group=\"sidebar-chevron\"> Preview <code>sidebar-chevron-left</code>\n</label>\n\n<!-- prettier-ignore -->\n```html\n<body class=\"sidebar-chevron-right\">\n```\n\n<!-- prettier-ignore -->\n```html\n<body class=\"sidebar-chevron-left\">\n```\n\nTo prevent chevrons from displaying for specific page links, add a `no-chevron` class as follows:\n\n```md\n[My Page](page.md ':class=no-chevron')\n```\n\n**Theme properties**\n\n<!-- prettier-ignore -->\n```css\n:root {\n  --sidebar-chevron-collapsed-color: var(--color-mono-3);\n  --sidebar-chevron-expanded-color : var(--theme-color);\n}\n```\n\n### Sidebar groups\n\nAdd visual distinction between groups of links in the sidebar.\n\n<label>\n  <input class=\"toggle\" type=\"checkbox\" value=\"sidebar-group-box\" data-class data-group=\"sidebar-group\"> Preview <code>sidebar-group-box</code>\n</label>\n<br>\n<label>\n  <input class=\"toggle\" type=\"checkbox\" value=\"sidebar-group-underline\" data-class data-group=\"sidebar-group\"> Preview <code>sidebar-group-underline</code>\n</label>\n\n<!-- prettier-ignore -->\n```html\n<body class=\"sidebar-group-box\">\n```\n\n<!-- prettier-ignore -->\n```html\n<body class=\"sidebar-group-underline\">\n```\n\n### Sidebar link clamp\n\nLimit multi-line sidebar links to a single line followed by an ellipses.\n\n<label>\n  <input class=\"toggle\" type=\"checkbox\" value=\"sidebar-link-clamp\" data-class>\n  Preview <code>sidebar-link-clamp</code>\n</label>\n\n<!-- prettier-ignore -->\n```html\n<body class=\"sidebar-link-clamp\">\n```\n\n### Sidebar toggle\n\nDisplay a \"hamburger\" icon (three lines) in the sidebar toggle button instead of the default \"kebab\" icon.\n\n<label>\n  <input class=\"toggle\" type=\"checkbox\" value=\"sidebar-toggle-chevron\" data-class data-group=\"sidebar-toggle\">\n  Preview <code>sidebar-toggle-chevron</code>\n</label>\n<br>\n<label>\n  <input class=\"toggle\" type=\"checkbox\" value=\"sidebar-toggle-hamburger\" data-class data-group=\"sidebar-toggle\">\n  Preview <code>sidebar-toggle-hamburger</code>\n</label>\n\n<!-- prettier-ignore -->\n```html\n<body class=\"sidebar-toggle-chevron\">\n```\n\n<!-- prettier-ignore -->\n```html\n<body class=\"sidebar-toggle-hamburger\">\n```\n\n## Customization\n\nDocsify provides [theme properties](#theme-properties) for simplified customization of frequently modified styles.\n\n1. Add a `<style>` tag after the theme stylesheet in your `index.html`.\n\n   <!-- prettier-ignore -->\n   ```html\n   <!-- Theme -->\n   <link rel=\"stylesheet\" href=\"//cdn.jsdelivr.net/npm/docsify@5/dist/themes/core.min.css\" />\n\n   <!-- Custom theme styles -->\n   <style>\n     :root {\n       /* ... */\n     }\n   </style>\n   ```\n\n   Theme properties can also be set on a per-page basis in markdown.\n\n   ```markdown\n   # My Heading\n\n   Hello, World!\n\n   <style>\n     :root {\n       /* ... */\n     }\n   </style>\n   ```\n\n2. Set custom [theme properties](#theme-properties) within a `:root` declaration.\n\n   <!-- prettier-ignore -->\n   ```css\n   :root {\n     --theme-color: red;\n     --font-size  : 15px;\n     --line-height: 1.5;\n   }\n   ```\n\n   Custom [theme properties](#theme-properties) can be conditionally applied in light and/or dark mode.\n\n   <!-- prettier-ignore -->\n   ```css\n   /* Light and dark mode */\n   :root {\n     --theme-color: pink;\n   }\n\n   /* Light mode only */\n   @media (prefers-color-scheme: light) {\n     :root {\n       --color-bg  : #eee;\n       --color-text: #444;\n     }\n   }\n\n   /* Dark mode only */\n   @media screen and (prefers-color-scheme: dark) {\n     :root {\n       --color-bg  : #222;\n       --color-text: #ddd;\n     }\n   }\n   ```\n\n3. Custom fonts can be used by adding web font resources and modifying `--font-family` properties as needed:\n\n   <!-- prettier-ignore -->\n   ```css\n   /* Fonts: Noto Sans, Noto Emoji, Noto Mono */\n   @import url('https://fonts.googleapis.com/css2?family=Noto+Color+Emoji&family=Noto+Sans+Mono:wght@100..900&family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap');\n\n   :root {\n     --font-family      : 'Noto Sans', sans-serif;\n     --font-family-emoji: 'Noto Color Emoji', sans-serif;\n     --font-family-mono : 'Noto Sans Mono', monospace;\n   }\n   ```\n\n   > [!TIP] **Theme authors**: Consider providing instructions for loading your recommended web fonts manually instead of including them in your theme using `@import`. This allows users who prefer a different font to avoid loading your recommended web font(s) unnecessarily.\n\n4. Advanced styling may require custom CSS declarations. This is expected, however custom CSS declarations may break when new versions of Docsify are released. When possible, leverage [theme properties](#theme-properties) instead of custom declarations or lock your [CDN](cdn) URLs to a [specific version](cdn#specific-version) to avoid potential issues when using custom CSS declarations.\n\n   ```css\n   .sidebar li.active > a {\n     border-right: 3px solid var(--theme-color);\n   }\n   ```\n\n## Theme properties\n\nThe following properties are available in all official Docsify themes. Default values for the \"Core\" theme are shown.\n\n> [!TIP] **Theme and plugin authors**: We encourage you to leverage these custom theme properties and to offer similar customization options in your projects.\n\n### Common\n\nBelow are the most commonly modified theme properties. [Advanced](#advanced) theme properties are also available for use but typically do not need to be modified.\n\n[\\_vars.css](https://raw.githubusercontent.com/docsifyjs/docsify/refs/heads/develop/src/themes/shared/_vars.css ':include')\n\n### Advanced\n\nAdvanced theme properties are also available for use but typically do not need to be modified. Values derived from [common](#common) theme properties but can be set explicitly if preferred.\n\n[\\_vars-advanced.css](https://raw.githubusercontent.com/docsifyjs/docsify/refs/heads/develop/src/themes/shared/_vars-advanced.css ':include')\n\n## Community\n\nSee [Awesome Docsify](awesome) for additional community themes.\n\n<script>\n  (function () {\n    const toggleElms = Docsify.dom.findAll(\n      'input:where([data-class], [data-sheet])',\n    );\n    const previewSheets = Docsify.dom.findAll(\n      'link[rel=\"stylesheet\"][data-sheet]',\n    );\n\n    function handleChange(e) {\n      const elm = e.target.closest('[data-class], [data-sheet]');\n      const value = elm.value;\n      const groupVal = elm.getAttribute('data-group');\n      const radioGroupName = elm.matches('[type=\"radio\"]') ? elm.name : undefined;\n\n      // Toggle class\n      if (elm.matches('[data-class]')) {\n        document.body.classList.toggle(value, elm.checked);\n      }\n      // Toggle sheet\n      else {\n        const themeSheet = previewSheets.find(\n          sheet => sheet.getAttribute('data-sheet') === value,\n        );\n\n        themeSheet && (themeSheet.disabled = !elm.checked);\n      }\n\n      if (!elm.checked || (!groupVal && !radioGroupName)) {\n        return;\n      }\n\n      // Group elements & values\n      const groupElms = toggleElms.filter(elm =>\n        groupVal\n          ? groupVal === elm.getAttribute('data-group')\n          : radioGroupName === elm.name,\n      );\n      const groupVals = groupElms.map(elm => elm.value);\n\n      if (groupElms.length <= 1) {\n        return;\n      }\n\n      if (groupVal) {\n        // Uncheck other group elements\n        groupElms.forEach(groupElm => {\n          if (groupElm !== elm) {\n            groupElm.checked = false;\n          }\n        });\n      }\n\n      // Remove group classes\n      if (elm.matches('[data-class]')) {\n        groupVals.forEach(className => {\n          if (className !== value) {\n            document.body.classList.remove(className);\n          }\n        });\n      }\n      // Disable group sheets\n      else {\n        const otherSheets = groupVals\n          .map(val =>\n            previewSheets.find(sheet => sheet.getAttribute('data-sheet') === val),\n          )\n          .filter(sheet => sheet && sheet.getAttribute('data-sheet') !== value);\n        const disableSheets = otherSheets.length ? otherSheets : previewSheets;\n\n        disableSheets.forEach(sheet => sheet.disabled = true);\n      }\n    }\n\n    // Toggle active elms\n    toggleElms.forEach(elm => {\n      const value = elm.value;\n\n      // Class toggle\n      if (elm.matches('[data-class]')) {\n        elm.checked = document.body.classList.contains(value);\n      }\n      // Sheet toggle\n      else {\n        const previewSheet = previewSheets.find(\n          sheet => sheet.getAttribute('data-sheet') === value,\n        );\n\n        elm.checked = previewSheet && !previewSheet.disabled;\n      }\n    });\n\n    toggleElms.forEach(elm => elm.addEventListener('change', handleChange));\n  })();\n</script>\n"
  },
  {
    "path": "docs/ui-kit.md",
    "content": "<!-- markdownlint-disable single-title no-duplicate-heading -->\n\n# UI Kit\n\n<details>\n  <summary>View the markdown source for this page</summary>\n  <div style=\"max-height: 50vh; overflow: auto;\">\n\n[ui-kit.md](ui-kit.md ':include :type=code')\n\n  </div>\n</details>\n\n## Blockquotes\n\n> Cras aliquet nulla quis metus tincidunt, sed placerat enim cursus. Etiam\n> turpis nisl, posuere eu condimentum ut, interdum a risus. Sed non luctus mi.\n> Quisque malesuada risus sit amet tortor aliquet, a posuere ex iaculis. Vivamus\n> ultrices enim dui, eleifend porttitor elit aliquet sed.\n>\n> _- Quote Source_\n\n#### Nested\n\n<!-- prettier-ignore -->\n> Level 1\n> > Level 2\n> > > Level 3\n\n## Buttons\n\n#### Default\n\n<button>Button</button>\n\n#### Basic\n\n<button type=\"button\">Button</button>\n<a href=\"javascript:void(0);\" class=\"button\">Link Button</a>\n<input type=\"button\" value=\"Input Button\" class=\"button\">\n\n#### Primary\n\n<button type=\"button\" class=\"primary\">Button</button>\n<a href=\"javascript:void(0);\" class=\"button primary\">Link Button</a>\n<input type=\"button\" value=\"Input Button\" class=\"primary\">\n\n#### Secondary\n\n<button type=\"button\" class=\"secondary\">Button</button>\n<a href=\"javascript:void(0);\" class=\"button secondary\">Link Button</a>\n<input type=\"button\" value=\"Input Button\" class=\"secondary\">\n\n## Callouts\n\n<!-- prettier-ignore -->\n> [!CAUTION]\n> **Caution** callout with `inline code`.\n\n<!-- prettier-ignore -->\n> [!IMPORTANT]\n> **Important** callout with `inline code`.\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> **Note** callout with `inline code`.\n\n<!-- prettier-ignore -->\n> [!TIP]\n> **Tip** callout with `inline code`.\n\n<!-- prettier-ignore -->\n> [!WARNING]\n> **Warning** callout with `inline code`.\n\n**Multi Line**\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> - List item 1\n> - List item 2\n>\n> Text\n>\n> ```html\n> <p>Hello, World!</p>\n> ```\n\n**Nested**\n\n<!-- prettier-ignore -->\n> [!NOTE]\n> Level 1\n> > [!NOTE]\n> > Level 2\n> > > [!NOTE]\n> > > Level 3\n\n**Legacy Style**\n\n!> Legacy **Important** callout with `inline code`.\n\n?> Legacy **Tip** with `inline code`.\n\n## Code\n\nThis is `inline code`\n\n```javascript\nconst add = (num1, num2) => num1 + num2;\nconst total = add(1, 2);\n\nconsole.log(total); // 3\n```\n\n```html\n<body>\n  <p>Hello</p>\n</body>\n```\n\n## Colors\n\n#### Theme\n\n<div class=\"ui-kit-color\">\n  <figure>\n    <div style=\"background: var(--theme-color-1);\"></div>\n    <figcaption>1<figcaption>\n  </figure>\n  <figure>\n    <div style=\"background: var(--theme-color-2);\"></div>\n    <figcaption>2<figcaption>\n  </figure>\n  <figure>\n    <div style=\"background: var(--theme-color-3);\"></div>\n    <figcaption>3<figcaption>\n  </figure>\n  <figure>\n    <div style=\"background: var(--theme-color-4);\"></div>\n    <figcaption>4<figcaption>\n  </figure>\n  <figure>\n    <div style=\"background: var(--theme-color);\"></div>\n    <figcaption>Theme Color<figcaption>\n  </figure>\n  <figure>\n    <div style=\"background: var(--theme-color-5);\"></div>\n    <figcaption>5<figcaption>\n  </figure>\n  <figure>\n    <div style=\"background: var(--theme-color-6);\"></div>\n    <figcaption>6<figcaption>\n  </figure>\n  <figure>\n    <div style=\"background: var(--theme-color-7);\"></div>\n    <figcaption>7<figcaption>\n  </figure>\n  <figure>\n    <div style=\"background: var(--theme-color-8);\"></div>\n    <figcaption>8<figcaption>\n  </figure>\n</div>\n\n#### Monochromatic\n\n<div class=\"ui-kit-color\">\n  <figure>\n    <div style=\"background: var(--color-mono-min); border: 1px solid var(--color-mono-2);\"></div>\n    <figcaption>Min<figcaption>\n  </figure>\n  <figure>\n    <div style=\"background: var(--color-mono-1);\"></div>\n    <figcaption>1<figcaption>\n  </figure>\n  <figure>\n    <div style=\"background: var(--color-mono-2);\"></div>\n    <figcaption>2<figcaption>\n  </figure>\n  <figure>\n    <div style=\"background: var(--color-mono-3);\"></div>\n    <figcaption>3<figcaption>\n  </figure>\n  <figure>\n    <div style=\"background: var(--color-mono-4);\"></div>\n    <figcaption>4<figcaption>\n  </figure>\n  <figure>\n    <div style=\"background: var(--color-mono-5);\"></div>\n    <figcaption>5<figcaption>\n  </figure>\n  <figure>\n    <div style=\"background: var(--color-mono-6);\"></div>\n    <figcaption>6<figcaption>\n  </figure>\n  <figure>\n    <div style=\"background: var(--color-mono-7);\"></div>\n    <figcaption>7<figcaption>\n  </figure>\n  <figure>\n    <div style=\"background: var(--color-mono-8);\"></div>\n    <figcaption>8<figcaption>\n  </figure>\n  <figure>\n    <div style=\"background: var(--color-mono-9);\"></div>\n    <figcaption>9<figcaption>\n  </figure>\n  <figure>\n    <div style=\"background: var(--color-mono-max);\"></div>\n    <figcaption>Max<figcaption>\n  </figure>\n</div>\n\n## Details\n\n<details>\n  <summary>Details (click to open)</summary>\n\nSuscipit nemo aut ex suscipit voluptatem laboriosam odio velit. Ipsum eveniet labore sequi non optio vel. Ut culpa ad accusantium est aut harum ipsam voluptatum. Velit eum incidunt non sint. Et molestiae veniam natus autem vel assumenda ut numquam esse. Non nisi id qui vero corrupti quos et.\n\n</details>\n\n<details open>\n  <summary>Details (open by default)</summary>\n\nSuscipit nemo aut ex suscipit voluptatem laboriosam odio velit. Ipsum eveniet labore sequi non optio vel. Ut culpa ad accusantium est aut harum ipsam voluptatum. Velit eum incidunt non sint. Et molestiae veniam natus autem vel assumenda ut numquam esse. Non nisi id qui vero corrupti quos et.\n\n</details>\n\n## Form Elements\n\n### Fieldset\n\n<form>\n  <fieldset>\n    <legend>Legend</legend>\n    <p>\n      <label>\n        Label<br>\n        <input type=\"text\" placeholder=\"Placeholder\">\n      </label>\n    </p>\n  </fieldset>\n</form>\n\n### Input\n\n#### Checkbox\n\n<form>\n  <label><input type=\"checkbox\" value=\"HTML\" checked> HTML</label><br>\n  <label><input type=\"checkbox\" value=\"CSS\"> CSS</label><br>\n  <label><input type=\"checkbox\" value=\"JavaScript\"> JavaScript</label>\n</form>\n\n#### Datalist\n\n<form>\n  <label>\n    Label<br>\n    <input list=\"planets\">\n    <datalist id=\"planets\">\n      <option value=\"Earth\">Earth</option>\n      <option value=\"Jupiter\">Jupiter</option>\n      <option value=\"Mars\">Mars</option>\n      <option value=\"Mercury\">Mercury</option>\n      <option value=\"Neptune\">Neptune</option>\n      <option value=\"Saturn\">Saturn</option>\n      <option value=\"Uranus\">Uranus</option>\n      <option value=\"Venus\">Venus</option>\n    </datalist>\n  </label>\n</form>\n\n#### Radio\n\n<form>\n  <label><input type=\"radio\" name=\"language\" value=\"HTML\" checked> HTML</label><br>\n  <label><input type=\"radio\" name=\"language\" value=\"CSS\"> CSS</label><br>\n  <label><input type=\"radio\" name=\"language\" value=\"JavaScript\"> JavaScript</label>\n</form>\n\n#### Text\n\n<form>\n  <label>\n    First name<br>\n    <input type=\"text\" placeholder=\"Placeholder\">\n  </label>\n</form>\n\n#### Toggles\n\n<form>\nCheckbox (multi-select)\n\n<label><input class=\"toggle\" type=\"checkbox\" checked> HTML</label><br>\n<label><input class=\"toggle\" type=\"checkbox\"> CSS</label><br>\n<label><input class=\"toggle\" type=\"checkbox\"> JavaScript</label>\n\nRadio (single-select)\n\n<label><input class=\"toggle\" type=\"radio\" name=\"toggle\" checked> HTML</label><br>\n<label><input class=\"toggle\" type=\"radio\" name=\"toggle\"> CSS</label><br>\n<label><input class=\"toggle\" type=\"radio\" name=\"toggle\"> JavaScript</label>\n\n</form>\n\n### Select\n\n<form>\n  <label>\n    Label<br>\n    <select>\n      <option value=\"Earth\">Select a planet...</option>\n      <option value=\"Earth\">Earth</option>\n      <option value=\"Jupiter\">Jupiter</option>\n      <option value=\"Mars\">Mars</option>\n      <option value=\"Mercury\">Mercury</option>\n      <option value=\"Neptune\">Neptune</option>\n      <option value=\"Saturn\">Saturn</option>\n      <option value=\"Uranus\">Uranus</option>\n      <option value=\"Venus\">Venus</option>\n    </select>\n  </label>\n</form>\n\n### Textarea\n\n<textarea rows=\"5\" cols=\"40\">\nIpsam totam tempora. Dolorum voluptas error tempore asperiores vitae error laboriosam autem possimus.\n</textarea>\n\n## Headings\n\n# Heading 1 {docsify-ignore}\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse luctus nulla eu ex varius, a varius elit tincidunt. Aenean arcu magna, gravida id purus a, interdum convallis turpis. Aenean id ipsum eu tortor sollicitudin scelerisque in quis elit.\n\n## Heading 2 {docsify-ignore}\n\nVestibulum lobortis laoreet nunc vel vulputate. In et augue non lectus pellentesque molestie et ac justo. Sed sed turpis ut diam gravida sagittis nec at neque. Vivamus id tellus est. Nam ac dignissim mi. Vestibulum nec sem convallis, condimentum augue at, commodo diam.\n\n### Heading 3 {docsify-ignore}\n\nSuspendisse sit amet tincidunt nibh, ac interdum velit. Ut orci diam, dignissim at enim sit amet, placerat rutrum magna. Mauris consectetur nibh eget sem feugiat, sit amet congue quam laoreet. Curabitur sed massa metus.\n\n#### Heading 4 {docsify-ignore}\n\nDonec odio orci, facilisis ac vehicula in, vestibulum ut urna. Ut bibendum ullamcorper risus, ac euismod leo maximus sed. In pulvinar sagittis rutrum. Morbi quis cursus diam. Cras ac laoreet nulla, rhoncus sodales dui.\n\n##### Heading 5 {docsify-ignore}\n\nCommodo sit veniam nulla cillum labore ullamco aliquip quis. Consequat nulla fugiat consequat ex duis proident. Adipisicing excepteur tempor exercitation ad. Consectetur voluptate Lorem sint elit exercitation ullamco dolor.\n\n###### Heading 6 {docsify-ignore}\n\nIpsum ea amet dolore mollit incididunt fugiat nulla laboris est sint voluptate. Ex culpa id amet ipsum amet pariatur ipsum officia sit laborum irure ullamco deserunt. Consequat qui tempor occaecat nostrud proident.\n\n## Horizontal Rule\n\nText before rule.\n\n---\n\nText after rule.\n\n## IFrame\n\n[Example](_media/example.html ':include height=200px')\n\n## Images\n\n#### Inline-style\n\n![Docsify Logo](/_media/icon.svg 'This is the Docsify logo!')\n\n#### Reference-style\n\n![Docsify Logo][logo]\n\n[logo]: /_media/icon.svg 'This is the Docsify logo!'\n\n#### Light / Dark Theme\n\n<picture>\n  <source media=\"(prefers-color-scheme: dark)\" srcset=\"_media/moon.svg\">\n  <source media=\"(prefers-color-scheme: light)\" srcset=\"_media/sun.svg\">\n  <img alt=\"BinaryTree\" src=\"_media/sun.svg\" width=\"122\">\n</picture>\n\n## Keyboard\n\n#### Default\n\n<kbd>&#8963;</kbd><kbd>&#8997;</kbd><kbd>&#9003;</kbd>\n\n<kbd>Ctrl</kbd><kbd>Alt</kbd><kbd>Del</kbd>\n\n<kbd>&#8963; Control</kbd><kbd>&#8997; Alt</kbd><kbd>&#9003; Delete</kbd>\n\n#### Alternate\n\n<kbd class=\"alt\">&#8963;</kbd><kbd class=\"alt\">&#8997;</kbd><kbd class=\"alt\">&#9003;</kbd>\n\n<kbd class=\"alt\">Ctrl</kbd><kbd class=\"alt\">Alt</kbd><kbd class=\"alt\">Del</kbd>\n\n<kbd class=\"alt\">&#8963; Control</kbd><kbd class=\"alt\">&#8997; Alt</kbd><kbd class=\"alt\">&#9003; Delete</kbd>\n\n#### Entities\n\n<div style=\"display: grid; grid-template-columns: auto auto 1fr; gap: 1em 0.2em; align-items: end;\">\n  <div><kbd class=\"alt\">&uarr;</kbd></div>  <div><kbd>&uarr;</kbd></div>  <div>Arrow Up</div>\n  <div><kbd class=\"alt\">&darr;</kbd></div>  <div><kbd>&darr;</kbd></div>  <div>Arrow Down</div>\n  <div><kbd class=\"alt\">&larr;</kbd></div>  <div><kbd>&larr;</kbd></div>  <div>Arrow Left</div>\n  <div><kbd class=\"alt\">&rarr;</kbd></div>  <div><kbd>&rarr;</kbd></div>  <div>Arrow Right</div>\n  <div><kbd class=\"alt\">&#8682;</kbd></div> <div><kbd>&#8682;</kbd></div> <div>Caps Lock</div>\n  <div><kbd class=\"alt\">&#8984;</kbd></div> <div><kbd>&#8984;</kbd></div> <div>Command</div>\n  <div><kbd class=\"alt\">&#8963;</kbd></div> <div><kbd>&#8963;</kbd></div> <div>Control</div>\n  <div><kbd class=\"alt\">&#9003;</kbd></div> <div><kbd>&#9003;</kbd></div> <div>Delete</div>\n  <div><kbd class=\"alt\">&#8998;</kbd></div> <div><kbd>&#8998;</kbd></div> <div>Delete (Forward)</div>\n  <div><kbd class=\"alt\">&#8600;</kbd></div> <div><kbd>&#8600;</kbd></div> <div>End</div>\n  <div><kbd class=\"alt\">&#8996;</kbd></div> <div><kbd>&#8996;</kbd></div> <div>Enter</div>\n  <div><kbd class=\"alt\">&#9099;</kbd></div> <div><kbd>&#9099;</kbd></div> <div>Escape</div>\n  <div><kbd class=\"alt\">&#8598;</kbd></div> <div><kbd>&#8598;</kbd></div> <div>Home</div>\n  <div><kbd class=\"alt\">&#8670;</kbd></div> <div><kbd>&#8670;</kbd></div> <div>Page Up</div>\n  <div><kbd class=\"alt\">&#8671;</kbd></div> <div><kbd>&#8671;</kbd></div> <div>Page Down</div>\n  <div><kbd class=\"alt\">&#8997;</kbd></div> <div><kbd>&#8997;</kbd></div> <div>Option, Alt</div>\n  <div><kbd class=\"alt\">&#8629;</kbd></div> <div><kbd>&#8629;</kbd></div> <div>Return</div>\n  <div><kbd class=\"alt\">&#8679;</kbd></div> <div><kbd>&#8679;</kbd></div> <div>Shift</div>\n  <div><kbd class=\"alt\">&#9251;</kbd></div> <div><kbd>&#9251;</kbd></div> <div>Space</div>\n  <div><kbd class=\"alt\">&#8677;</kbd></div> <div><kbd>&#8677;</kbd></div> <div>Tab</div>\n  <div><kbd class=\"alt\">&#8676;</kbd></div> <div><kbd>&#8676;</kbd></div> <div>Tab + Shift</div>\n </div>\n\n## Links\n\n[Inline link](https://google.com)\n\n[Inline link with title](https://google.com 'Google')\n\n[Reference link by name][link1]\n\n[Reference link by number][1]\n\n[Reference link by self]\n\n[link1]: https://google.com\n[1]: https://google.com\n[Reference link by self]: https://google.com\n\n## Lists\n\n### Ordered List\n\n1. Ordered\n1. Ordered\n   1. Nested\n   1. Nested (Wrapping): Similique tempora et. Voluptatem consequuntur ut. Rerum minus et sed beatae. Consequatur ut nemo laboriosam quo architecto quia qui. Corrupti aut omnis velit.\n1. Ordered (Wrapping): Error minima modi rem sequi facere voluptatem. Est nihil veritatis doloribus et corporis ipsam. Pariatur eos ipsam qui odit labore est voluptatem enim. Veritatis est qui ut pariatur inventore.\n\n### Unordered List\n\n- Unordered\n- Unordered\n  - Nested\n  - Nested (Wrapping): Quia consectetur sint vel ut excepturi ipsa voluptatum suscipit hic. Ipsa error qui molestiae harum laboriosam. Rerum non amet illo voluptatem odio pariatur. Ut minus enim.\n- Unordered (Wrapping): Fugiat qui tempore ratione amet repellendus repudiandae non. Rerum nisi officia enim. Itaque est alias voluptatibus id molestiae accusantium. Cupiditate sequi qui omnis sed facere aliquid quia ut.\n\n### Task List\n\n- [x] Task\n- [ ] Task\n  - [ ] Subtask\n  - [ ] Subtask\n  - [x] Subtask\n- [ ] Task (Wrapping): Earum consequuntur itaque numquam sunt error omnis ipsum repudiandae. Est assumenda neque eum quia quisquam laborum beatae autem ad. Fuga fugiat perspiciatis harum quia dignissimos molestiae. Officia quo eveniet tempore modi voluptates consequatur. Eum odio adipisci labore.\n  - [x] Subtask (Wrapping): Vel possimus eaque laborum. Voluptates qui debitis quaerat atque molestiae quia explicabo doloremque. Reprehenderit perspiciatis a aut impedit temporibus aut quasi quia. Incidunt sed recusandae vitae asperiores sit in.\n\n## Output\n\n<output data-lang=\"output\">\n  <p>Et cum fugiat nesciunt voluptates. A atque quos doloribus dolorem quo. Et dignissimos omnis nam. Recusandae voluptatem nam. Tenetur veniam et qui consequatur. Aut sequi atque fuga itaque iusto eum nihil quod iure.</p>\n  <ol>\n    <li>Item</li>\n    <li>Item</li>\n    <li>Item</li>\n  </ol>\n</output>\n\n## Tables\n\n### Alignment\n\n| Left Align | Center Align | Right Align | Non&#8209;Breaking&nbsp;Header |\n| ---------- | :----------: | ----------: | ------------------------------ |\n| A1         |      A2      |          A3 | A4                             |\n| B1         |      B2      |          B3 | B4                             |\n| C1         |      C2      |          C3 | C4                             |\n\n### Headerless\n\n|     |     |     |     |\n| --- | --- | --- | --- |\n| A1  | A2  | A3  | A4  |\n| B1  | B2  | B3  | B4  |\n| C1  | C2  | C3  | C4  |\n\n### Scrolling\n\n| Header                                                                                                                                                                                                                                                                                                                   |\n| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| Dicta&nbsp;in&nbsp;nobis&nbsp;dolor&nbsp;adipisci&nbsp;qui.&nbsp;Accusantium&nbsp;voluptates&nbsp;est&nbsp;dolor&nbsp;laboriosam&nbsp;qui&nbsp;voluptatibus.&nbsp;Veritatis&nbsp;eos&nbsp;aspernatur&nbsp;iusto&nbsp;et&nbsp;dicta&nbsp;quas.&nbsp;Fugit&nbsp;voluptatem&nbsp;dolorum&nbsp;qui&nbsp;quisquam.&nbsp;nihil |\n| Aut&nbsp;praesentium&nbsp;officia&nbsp;aut&nbsp;delectus.&nbsp;Quas&nbsp;atque&nbsp;reprehenderit&nbsp;saepe.&nbsp;Et&nbsp;voluptatibus&nbsp;qui&nbsp;dolores&nbsp;rem&nbsp;facere&nbsp;in&nbsp;dignissimos&nbsp;id&nbsp;aut.&nbsp;Debitis&nbsp;excepturi&nbsp;delectus&nbsp;et&nbsp;quos&nbsp;numquam&nbsp;magnam.      |\n| Sed&nbsp;eum&nbsp;atque&nbsp;at&nbsp;laborum&nbsp;aut&nbsp;et&nbsp;repellendus&nbsp;ullam&nbsp;dolor.&nbsp;Cupiditate&nbsp;saepe&nbsp;voluptatibus&nbsp;odit&nbsp;est&nbsp;pariatur&nbsp;qui.&nbsp;Hic&nbsp;sunt&nbsp;nihil&nbsp;optio&nbsp;enim&nbsp;eum&nbsp;laudantium.&nbsp;Repellendus&nbsp;voluptate.              |\n\n## Text Elements\n\n<mark>Marked text</mark>\n\n<pre>Preformatted text</pre>\n\n<samp>Sample Output</samp>\n\n<small>Small Text</small>\n\nThis is <sub>subscript</sub>\n\nThis is <sup>superscript</sup>\n\n<ins>Underlined Text</ins>\n\n## Text Styles\n\nBody text\n\n**Bold text**\n\n_Italic text_\n\n**_Bold and italic text_**\n\n~~Strikethrough~~\n"
  },
  {
    "path": "docs/v5-upgrade.md",
    "content": "# Upgrading v4 to v5\n\nThe main changes when upgrading a Docsify v4 site to v5 involve updating CDN URLs and theme files. Your configuration settings remain mostly the same, so the upgrade is fairly straightforward.\n\n## Before You Begin\n\nSome older Docsify sites may use non-version-locked URLs like:\n\n```html\n<script src=\"//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js\"></script>\n```\n\nIf your site uses URLs without `@4` or a specific version number, follow the same steps below. You'll need to update both the version specifier and the path structure.\n\n## Step-by-Step Instructions\n\n### 1. Update the Theme CSS\n\n**Replace the theme (v4):**\n\n```html\n<link\n  rel=\"stylesheet\"\n  href=\"//cdn.jsdelivr.net/npm/docsify@4/lib/themes/vue.css\"\n/>\n<!-- OR if you have non-versioned URL: -->\n<link\n  rel=\"stylesheet\"\n  href=\"//cdn.jsdelivr.net/npm/docsify/lib/themes/vue.css\"\n/>\n```\n\n**With this (v5):**\n\n```html\n<!-- Core Theme -->\n<link\n  rel=\"stylesheet\"\n  href=\"//cdn.jsdelivr.net/npm/docsify@5/dist/themes/core.min.css\"\n/>\n<!-- Optional: Dark Mode Support -->\n<link\n  rel=\"stylesheet\"\n  href=\"//cdn.jsdelivr.net/npm/docsify@5/dist/themes/addons/core-dark.min.css\"\n  media=\"(prefers-color-scheme: dark)\"\n/>\n```\n\n**Note:** If you were using a different v4 theme (buble, dark, pure), the v5 core theme replaces these, though Vue and Dark themes are available as add-ons if preferred.\n\nView [Themes](themes.md) for more details.\n\n### 2. Add Optional Body Class (for styling)\n\n**Update your opening body tag:**\n\n```html\n<body class=\"sidebar-chevron-right\"></body>\n```\n\nThis adds a chevron indicator to the sidebar. You can omit this if you prefer.\n\nView [Theme Classes](themes.md?id=classes) for more details.\n\n### 3. Update the Main Docsify Script\n\n**Change:**\n\n```html\n<script src=\"https://cdn.jsdelivr.net/npm/docsify@4/lib/docsify.min.js\"></script>\n<!-- OR if you have non-versioned URL: -->\n<script src=\"//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js\"></script>\n```\n\n**To:**\n\n```html\n<script src=\"//cdn.jsdelivr.net/npm/docsify@5/dist/docsify.min.js\"></script>\n```\n\n### 4. Update Plugin URLs\n\n**Search Plugin:**\n\n```html\n<!-- v4 -->\n<script src=\"https://cdn.jsdelivr.net/npm/docsify@4/lib/plugins/search.js\"></script>\n<!-- OR non-versioned: -->\n<script src=\"//cdn.jsdelivr.net/npm/docsify/lib/plugins/search.js\"></script>\n\n<!-- v5 -->\n<script src=\"//cdn.jsdelivr.net/npm/docsify@5/dist/plugins/search.min.js\"></script>\n```\n\n**Zoom Plugin:**\n\n```html\n<!-- v4 -->\n<script src=\"https://cdn.jsdelivr.net/npm/docsify@4/lib/plugins/zoom-image.min.js\"></script>\n<!-- OR non-versioned: -->\n<script src=\"//cdn.jsdelivr.net/npm/docsify/lib/plugins/zoom-image.min.js\"></script>\n\n<!-- v5 -->\n<script src=\"//cdn.jsdelivr.net/npm/docsify@5/dist/plugins/zoom.min.js\"></script>\n```\n\n**Note:** If you're using additional Docsify plugins (such as emoji, external-script, front-matter, etc.), you'll need to update those URLs as well following the same pattern:\n\n- Change `/lib/plugins/` to `/dist/plugins/`\n- Update version from `@4` (or non-versioned) to `@5`\n- Example: `//cdn.jsdelivr.net/npm/docsify/lib/plugins/emoji.min.js` becomes `//cdn.jsdelivr.net/npm/docsify@5/dist/plugins/emoji.min.js`\n\n## Key Differences Summary\n\n- **CDN Path**: Changed from `/lib/` to `/dist/`\n- **Version**: Updated from `@4` to `@5`\n- **Themes**: v5 uses a core theme (with optional add-ons available)\n- **Plugin Names**: `zoom-image` → `zoom`\n\n## Additional Notes\n\n- Your configuration in `window.$docsify` stays the same\n- All your markdown content remains unchanged\n- The upgrade is non-breaking for most sites (however, legacy browsers like Internet Explorer 11 are no longer supported)\n- To maintain the same visual styling as Docsify v4, the [Vue theme (Add-on)](themes.md?id=vue-theme-add-on) is available\n- Custom CSS targeting v4 theme-specific classes or elements may need to be updated for v5\n- The v5 core theme can be customized using CSS variables - view [Theme Customization](themes.md?id=customization) for more details\n\nThat's it! Your Docsify site should now be running on v5.\n"
  },
  {
    "path": "docs/vue.md",
    "content": "# Vue compatibility\n\nDocsify allows [Vue.js](https://vuejs.org) content to be added directly to your markdown pages. This can greatly simplify working with data and adding reactivity to your site.\n\nVue [template syntax](https://vuejs.org/guide/essentials/template-syntax) can be used to add dynamic content to your pages. Vue content becomes more interesting when [data](#data), [computed properties](#computed-properties), [methods](#methods), and [lifecycle hooks](#lifecycle-hooks) are used. These options can be specified as [global options](#global-options) or within DOM [mounts](#mounts) and [components](#components).\n\n## Setup\n\nTo get started, add Vue.js to your `index.html` file. Choose the production version for your live site or the development version for helpful console warnings and [Vue.js devtools](https://github.com/vuejs/vue-devtools) support.\n\n```html\n<!-- Production -->\n<script src=\"//cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js\"></script>\n\n<!-- Development -->\n<script src=\"//cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js\"></script>\n```\n\n## Template syntax\n\nVue [template syntax](https://vuejs.org/guide/essentials/template-syntax) offers several useful features like support for [JavaScript expressions](https://vuejs.org/guide/essentials/template-syntax.html#using-javascript-expressions) and Vue [directives](https://vuejs.org/guide/essentials/template-syntax.html#directives) for loops and conditional rendering.\n\n```markdown\n<!-- Hide in docsify, show elsewhere (e.g. GitHub) -->\n<p v-if=\"false\">Text for GitHub</p>\n\n<!-- Sequenced content (i.e. loop)-->\n<ul>\n  <li v-for=\"i in 3\">Item {{ i }}</li>\n</ul>\n\n<!-- JavaScript expressions -->\n<p>2 + 2 = {{ 2 + 2 }}</p>\n```\n\n<output data-lang=\"output\">\n  <p v-if=\"false\">Text for GitHub</p>\n\n  <ul>\n    <li v-for=\"i in 3\">Item {{ i }}</li>\n  </ul>\n\n  <p>2 + 2 = {{ 2 + 2 }}</p>\n</output>\n\n[View output on GitHub](https://github.com/docsifyjs/docsify/blob/develop/docs/vue.md#template-syntax)\n\n## Code Blocks\n\nDocsify ignores Vue template syntax within code blocks by default:\n\n````markdown\n```\n{{ message }}\n```\n````\n\nTo process Vue template syntax within a code block, wrap the code block in an element with a `v-template` attribute:\n\n````markdown\n<div v-template>\n\n```\n{{ message }}\n```\n\n</div>\n````\n\n## Data\n\n```js\n{\n  data() {\n    return {\n      message: 'Hello, World!'\n    };\n  }\n}\n```\n\n<!-- prettier-ignore-start -->\n```markdown\n<!-- Show message in docsify, show \"{{ message }}\" elsewhere (e.g. GitHub)  -->\n{{ message }}\n\n<!-- Show message in docsify, hide elsewhere (e.g. GitHub)  -->\n<p v-text=\"message\"></p>\n```\n<!-- prettier-ignore-end -->\n\n<output data-lang=\"output\">\n  <p>{{ message }}</p>\n\n  <p v-text=\"message\"></p>\n</output>\n\n[View output on GitHub](https://github.com/docsifyjs/docsify/blob/develop/docs/vue.md#data)\n\n## Computed properties\n\n```js\n{\n  computed: {\n    timeOfDay() {\n      const date = new Date();\n      const hours = date.getHours();\n\n      if (hours < 12) {\n        return 'morning';\n      }\n      else if (hours < 18) {\n        return 'afternoon';\n      }\n      else {\n        return 'evening'\n      }\n    }\n  },\n}\n```\n\n```markdown\nGood {{ timeOfDay }}!\n```\n\n<output data-lang=\"output\">\n\nGood {{ timeOfDay }}!\n\n</output>\n\n## Methods\n\n```js\n{\n  data() {\n    return {\n      message: 'Hello, World!'\n    };\n  },\n  methods: {\n    hello() {\n      alert(this.message);\n    }\n  },\n}\n```\n\n```markdown\n<button @click=\"hello\">Say Hello</button>\n```\n\n<output data-lang=\"output\">\n  <p><button @click=\"hello\">Say Hello</button></p>\n</output>\n\n## Lifecycle Hooks\n\n```js\n{\n  data() {\n    return {\n      images: null,\n    };\n  },\n  created() {\n    fetch('https://api.domain.com/')\n      .then(response => response.json())\n      .then(data => (this.images = data))\n      .catch(err => console.log(err));\n  }\n}\n\n// API response:\n// [\n//   { title: 'Image 1', url: 'https://domain.com/1.jpg' },\n//   { title: 'Image 2', url: 'https://domain.com/2.jpg' },\n//   { title: 'Image 3', url: 'https://domain.com/3.jpg' },\n// ];\n```\n\n```markdown\n<div style=\"display: flex;\">\n  <figure style=\"flex: 1;\">\n    <img v-for=\"image in images\" :src=\"image.url\" :title=\"image.title\">\n    <figcaption>{{ image.title }}</figcaption>\n  </figure>\n</div>\n```\n\n<output data-lang=\"output\">\n  <div style=\"display: flex;\">\n    <figure v-for=\"image in images\" style=\"flex: 1; text-align: center;\">\n      <img :src=\"image.url\">\n      <figcaption>{{ image.title }}</figcaption>\n    </figure>\n  </div>\n</output>\n\n## Global options\n\nUse `vueGlobalOptions` to specify global Vue options for use with Vue content not explicitly mounted with [vueMounts](#mounts), [vueComponents](#components), or a [markdown script](#markdown-script). Changes to global `data` will persist and be reflected anywhere global references are used.\n\n```js\nwindow.$docsify = {\n  vueGlobalOptions: {\n    data() {\n      return {\n        count: 0,\n      };\n    },\n  },\n};\n```\n\n```markdown\n<p>\n  <button @click=\"count += 1\">+</button>\n  {{ count }}\n  <button @click=\"count -= 1\">-</button>\n</p>\n```\n\n<output data-lang=\"output\">\n  <p>\n    <button @click=\"count += 1\">+</button>\n    {{ count }}\n    <button @click=\"count -= 1\">-</button>\n  </p>\n</output>\n\nNotice the behavior when multiple global counters are rendered:\n\n<output data-lang=\"output\">\n  <p>\n    <button @click=\"count += 1\">+</button>\n    {{ count }}\n    <button @click=\"count -= 1\">-</button>\n  </p>\n</output>\n\nChanges made to one counter affect the both counters. This is because both instances reference the same global `count` value. Now, navigate to a new page and return to this section to see how changes made to global data persist between page loads.\n\n## Mounts\n\nUse `vueMounts` to specify DOM elements to mount as Vue instances and their associated options. Mount elements are specified using a [CSS selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) as the key with an object containing Vue options as their value. Docsify will mount the first matching element in the main content area each time a new page is loaded. Mount element `data` is unique for each instance and will not persist as users navigate the site.\n\n```js\nwindow.$docsify = {\n  vueMounts: {\n    '#counter': {\n      data() {\n        return {\n          count: 0,\n        };\n      },\n    },\n  },\n};\n```\n\n```markdown\n<div id=\"counter\">\n  <button @click=\"count += 1\">+</button>\n  {{ count }}\n  <button @click=\"count -= 1\">-</button>\n</div>\n```\n\n<output id=\"counter\">\n  <button @click=\"count += 1\">+</button>\n  {{ count }}\n  <button @click=\"count -= 1\">-</button>\n</output>\n\n## Components\n\nUse `vueComponents` to create and register global [Vue components](https://vuejs.org/guide/essentials/component-basics.html). Components are specified using the component name as the key with an object containing Vue options as the value. Component `data` is unique for each instance and will not persist as users navigate the site.\n\n```js\nwindow.$docsify = {\n  vueComponents: {\n    'button-counter': {\n      template: `\n        <button @click=\"count += 1\">\n          You clicked me {{ count }} times\n        </button>\n      `,\n      data() {\n        return {\n          count: 0,\n        };\n      },\n    },\n  },\n};\n```\n\n```markdown\n<button-counter></button-counter>\n<button-counter></button-counter>\n```\n\n<output data-lang=\"output\">\n  <button-counter></button-counter>\n  <button-counter></button-counter>\n</output>\n\n## Markdown script\n\nVue content can mounted using a `<script>` tag in your markdown pages.\n\n> [!IMPORTANT] Only the first `<script>` tag in a markdown file is executed. If you wish to mount multiple Vue instances using a script tag, all instances must be mounted within the first script tag in your markdown.\n\n```html\n<script>\n  Vue.createApp({\n    // Options...\n  }).mount('#example');\n</script>\n```\n\n## Technical Notes\n\n- Docsify processes Vue content in the following order on each page load:\n  1. Execute markdown `<script>`\n  1. Register global `vueComponents`\n  1. Mount `vueMounts`\n  1. Auto-mount unmounted `vueComponents`\n  1. Auto-mount unmounted Vue template syntax using `vueGlobalOptions`\n- When auto-mounting Vue content, docsify will mount each top-level element in your markdown that contains template syntax or a component. For example, in the following HTML the top-level `<p>`, `<my-component />`, and `<div>` elements will be mounted.\n  ```html\n  <p>{{ foo }}</p>\n  <my-component />\n  <div>\n    <span>{{ bar }}</span>\n    <some-other-component />\n  </div>\n  ```\n- Docsify will not mount an existing Vue instance or an element that contains an existing Vue instance.\n- Docsify will automatically destroy/unmount all Vue instances it creates before each page load.\n"
  },
  {
    "path": "docs/write-a-plugin.md",
    "content": "# Write a plugin\n\nA docsify plugin is a function with the ability to execute custom JavaScript code at various stages of Docsify's lifecycle.\n\n## Setup\n\nDocsify plugins can be added directly to the `plugins` array:\n\n```js\nwindow.$docsify = {\n  plugins: [\n    function myPlugin1(hook, vm) {\n      // ...\n    },\n    function myPlugin2(hook, vm) {\n      // ...\n    },\n  ],\n};\n```\n\nAlternatively, a plugin can be stored in a separate file and \"installed\" using a standard `<script>` tag:\n\n```js\n// docsify-plugin-myplugin.js\n\n{\n  function myPlugin(hook, vm) {\n    // ...\n  }\n\n  // Add plugin to docsify's plugin array\n  window.$docsify = window.$docsify || {};\n  $docsify.plugins = [...($docsify.plugins || []), myPlugin];\n}\n```\n\n```html\n<script src=\"docsify-plugin-myplugin.js\"></script>\n```\n\n## Template\n\nBelow is a plugin template with placeholders for all available lifecycle hooks.\n\n1. Copy the template\n1. Modify the `myPlugin` name as appropriate\n1. Add your plugin logic\n1. Remove unused lifecycle hooks\n1. Save the file as `docsify-plugin-[name].js`\n1. Load your plugin using a standard `<script>` tag\n\n```js\n{\n  function myPlugin(hook, vm) {\n    // Invoked one time when docsify script is initialized\n    hook.init(() => {\n      // ...\n    });\n\n    // Invoked one time when the docsify instance has mounted on the DOM\n    hook.mounted(() => {\n      // ...\n    });\n\n    // Invoked on each page load before new markdown is transformed to HTML.\n    // Supports asynchronous tasks (see beforeEach documentation for details).\n    hook.beforeEach(markdown => {\n      // ...\n      return markdown;\n    });\n\n    // Invoked on each page load after new markdown has been transformed to HTML.\n    // Supports asynchronous tasks (see afterEach documentation for details).\n    hook.afterEach(html => {\n      // ...\n      return html;\n    });\n\n    // Invoked on each page load after new HTML has been appended to the DOM\n    hook.doneEach(() => {\n      // ...\n    });\n\n    // Invoked one time after rendering the initial page\n    hook.ready(() => {\n      // ...\n    });\n  }\n\n  // Add plugin to docsify's plugin array\n  window.$docsify = window.$docsify || {};\n  $docsify.plugins = [myPlugin, ...($docsify.plugins || [])];\n}\n```\n\n## Lifecycle Hooks\n\nLifecycle hooks are provided via the `hook` argument passed to the plugin function.\n\n### init()\n\nInvoked one time when docsify script is initialized.\n\n```js\nhook.init(() => {\n  // ...\n});\n```\n\n### mounted()\n\nInvoked one time when the docsify instance has mounted on the DOM.\n\n```js\nhook.mounted(() => {\n  // ...\n});\n```\n\n### beforeEach()\n\nInvoked on each page load before new markdown is transformed to HTML.\n\n```js\nhook.beforeEach(markdown => {\n  // ...\n  return markdown;\n});\n```\n\nFor asynchronous tasks, the hook function accepts a `next` callback as a second argument. Call this function with the final `markdown` value when ready. To prevent errors from affecting docsify and other plugins, wrap async code in a `try/catch/finally` block.\n\n```js\nhook.beforeEach((markdown, next) => {\n  try {\n    // Async task(s)...\n  } catch (err) {\n    // ...\n  } finally {\n    next(markdown);\n  }\n});\n```\n\n### afterEach()\n\nInvoked on each page load after new markdown has been transformed to HTML.\n\n```js\nhook.afterEach(html => {\n  // ...\n  return html;\n});\n```\n\nFor asynchronous tasks, the hook function accepts a `next` callback as a second argument. Call this function with the final `html` value when ready. To prevent errors from affecting docsify and other plugins, wrap async code in a `try/catch/finally` block.\n\n```js\nhook.afterEach((html, next) => {\n  try {\n    // Async task(s)...\n  } catch (err) {\n    // ...\n  } finally {\n    next(html);\n  }\n});\n```\n\n### doneEach()\n\nInvoked on each page load after new HTML has been appended to the DOM.\n\n```js\nhook.doneEach(() => {\n  // ...\n});\n```\n\n### ready()\n\nInvoked one time after rendering the initial page.\n\n```js\nhook.ready(() => {\n  // ...\n});\n```\n\n## Tips\n\n- Access Docsify methods and properties using `window.Docsify`\n- Access the current Docsify instance using the `vm` argument\n- Developers who prefer using a debugger can set the [`catchPluginErrors`](configuration#catchpluginerrors) configuration option to `false` to allow their debugger to pause JavaScript execution on error\n- Be sure to test your plugin on all supported platforms and with related configuration options (if applicable) before publishing\n\n## Examples\n\n#### Page Footer\n\n```js\nwindow.$docsify = {\n  plugins: [\n    function pageFooter(hook, vm) {\n      const footer = /* html */ `\n        <hr/>\n        <footer>\n          <span><a href=\"https://github.com/QingWei-Li\">cinwell</a> &copy;2017.</span>\n          <span>Proudly published with <a href=\"https://github.com/docsifyjs/docsify\" target=\"_blank\">docsify</a>.</span>\n        </footer>\n      `;\n\n      hook.afterEach(html => {\n        return html + footer;\n      });\n    },\n  ],\n};\n```\n\n### Edit Button (GitHub)\n\n```js\nwindow.$docsify = {\n  plugins: [\n    function editButton(hook, vm) {\n      // The date template pattern\n      $docsify.formatUpdated = '{YYYY}/{MM}/{DD} {HH}:{mm}';\n\n      hook.beforeEach(html => {\n        const url =\n          'https://github.com/docsifyjs/docsify/blob/main/docs/' +\n          vm.route.file;\n        const editHtml = '[📝 EDIT DOCUMENT](' + url + ')\\n';\n\n        return (\n          editHtml +\n          html +\n          '\\n----\\n' +\n          'Last modified {docsify-updated}' +\n          editHtml\n        );\n      });\n    },\n  ],\n};\n```\n"
  },
  {
    "path": "eslint.config.js",
    "content": "import eslintConfigPrettier from 'eslint-config-prettier';\nimport playwrightPlugin from 'eslint-plugin-playwright';\nimport jestPlugin from 'eslint-plugin-jest';\nimport globals from 'globals';\nimport js from '@eslint/js';\n\nexport default [\n  // Ignore (Must be first item in array)\n  {\n    ignores: [\n      // Directories\n      '_playwright-*',\n      'dist',\n      'docs',\n      'lib',\n      'node_modules',\n      // Files\n      '**/*.md',\n      'CHANGELOG.md',\n      'emoji-data.*',\n      'HISTORY.md',\n    ],\n  },\n  // ESLint Recommended\n  js.configs.recommended,\n  // Configuration: Prettier\n  eslintConfigPrettier,\n  // All Files\n  {\n    languageOptions: {\n      globals: {\n        ...globals.browser,\n        ...globals.node,\n        $docsify: 'readonly',\n        Docsify: 'readonly',\n      },\n    },\n    rules: {\n      'array-callback-return': ['error'],\n      'block-scoped-var': ['error'],\n      curly: ['error'],\n      'dot-notation': ['error'],\n      eqeqeq: ['error'],\n      'no-implicit-coercion': ['error', { boolean: false }],\n      'no-implicit-globals': ['error'],\n      'no-loop-func': ['error'],\n      'no-return-assign': ['error'],\n      'no-template-curly-in-string': ['error'],\n      'no-unneeded-ternary': ['error'],\n      'no-unused-vars': ['error', { args: 'none' }],\n      'no-useless-computed-key': ['error'],\n      'no-useless-return': ['error'],\n      'no-var': ['error'],\n      'prefer-const': [\n        'error',\n        {\n          destructuring: 'all',\n        },\n      ],\n    },\n  },\n  // Source Files\n  {\n    files: ['src/**'],\n    rules: {\n      'no-console': ['warn'],\n    },\n  },\n  // Tests: E2E (Playwright)\n  {\n    files: ['test/e2e/**'],\n    ...playwrightPlugin.configs['flat/recommended'],\n  },\n  // Tests: Integration & Unit (Jest)\n  {\n    files: ['test/{integration,unit}/**'],\n    ...jestPlugin.configs['flat/recommended'],\n  },\n];\n"
  },
  {
    "path": "jest.config.js",
    "content": "import { testConfig } from './server.configs.js';\n\nconst { hostname, port } = testConfig;\nconst TEST_HOST = `http://${hostname}:${port}`;\nconst sharedConfig = {\n  errorOnDeprecated: true,\n  globalSetup: './test/config/jest.setup.js',\n  globalTeardown: './test/config/jest.teardown.js',\n  prettierPath: null, // Fix for Jest v29 and Prettier v3 (https://github.com/jestjs/jest/issues/14305)\n  resetModules: true,\n  restoreMocks: true,\n  setupFilesAfterEnv: ['<rootDir>/test/config/jest.setup-tests.js'],\n  testEnvironment: 'jest-environment-jsdom',\n  testEnvironmentOptions: {\n    url: `${TEST_HOST}/_blank.html`,\n  },\n};\n\nprocess.env.TEST_HOST = TEST_HOST;\n\nexport default {\n  transform: {},\n  projects: [\n    // Unit Tests\n    {\n      displayName: 'unit',\n      ...sharedConfig,\n      testMatch: ['<rootDir>/test/unit/*.test.js'],\n    },\n    // Integration Tests\n    {\n      displayName: 'integration',\n      ...sharedConfig,\n      testMatch: ['<rootDir>/test/integration/*.test.js'],\n    },\n  ],\n};\n"
  },
  {
    "path": "middleware.js",
    "content": "// Exports\n// =============================================================================\nexport const config = {\n  matcher: ['/preview/(index.html)?'],\n};\n\n// Rewrite rules shared with local server configurations\nexport const rewriteRules = [\n  // Replace CDN URLs with local paths\n  {\n    match: /https?.*\\/CHANGELOG.md/g,\n    replace: '/CHANGELOG.md',\n  },\n  {\n    // CDN versioned default\n    // Ex1: //cdn.com/package-name\n    // Ex2: http://cdn.com/package-name@1.0.0\n    // Ex3: https://cdn.com/package-name@latest\n    match: /(?:https?:)*\\/\\/.*cdn.*docsify[@\\d.latest]*(?=[\"'])/g,\n    replace: '/dist/docsify.min.js',\n  },\n  {\n    // CDN paths to local paths\n    // Ex1: //cdn.com/package-name/path/file.js => /path/file.js\n    // Ex2: http://cdn.com/package-name@1.0.0/dist/file.js => /dist/file.js\n    // Ex3: https://cdn.com/package-name@latest/dist/file.js => /dist/file.js\n    match: /(?:https?:)*\\/\\/.*cdn.*docsify[@\\d.latest]*\\/(?:dist\\/)/g,\n    replace: '/dist/',\n  },\n];\n\n// Serve virtual /preview/index.html\n// Note: See vercel.json for preview routing configuration\n// 1. Fetch index.html from /docs/ directory\n// 2. Replace CDN URLs with local paths (see rewriteRules)\n// 3. Return preview HTML\nexport default async function middleware(request) {\n  const { origin } = new URL(request.url);\n  const indexURL = `${origin}/docs/index.html`;\n  const indexHTML = await fetch(indexURL).then(res => res.text());\n  const previewHTML = rewriteRules.reduce(\n    (html, rule) => html.replace(rule.match, rule.replace),\n    indexHTML,\n  );\n\n  return new Response(previewHTML, {\n    status: 200,\n    headers: {\n      'content-type': 'text/html',\n      'x-robots-tag': 'noindex',\n    },\n  });\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"docsify\",\n  \"version\": \"5.0.0-rc.4\",\n  \"description\": \"A magical documentation generator.\",\n  \"homepage\": \"https://docsify.js.org\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/docsifyjs/docsify.git\"\n  },\n  \"authors\": \"https://github.com/docsifyjs/docsify/graphs/contributors\",\n  \"license\": \"MIT\",\n  \"collective\": {\n    \"url\": \"https://opencollective.com/docsify\"\n  },\n  \"keywords\": [\n    \"client\",\n    \"creator\",\n    \"crs\",\n    \"doc\",\n    \"docs\",\n    \"documentation\",\n    \"generator\",\n    \"markdown\"\n  ],\n  \"engines\": {\n    \"node\": \">=20.11.0\"\n  },\n  \"type\": \"module\",\n  \"main\": \"dist/docsify.js\",\n  \"types\": \"src/core/Docsify.d.ts\",\n  \"exports\": {\n    \".\": \"./src/core/Docsify.js\",\n    \"./*\": \"./*\"\n  },\n  \"files\": [\n    \"dist\",\n    \"src\",\n    \"lib\",\n    \"themes\"\n  ],\n  \"lint-staged\": {\n    \"*.js\": \"eslint --fix\"\n  },\n  \"dependencies\": {\n    \"common-tags\": \"^1.8.0\",\n    \"dexie\": \"^4.0.11\",\n    \"marked\": \"^17.0.1\",\n    \"medium-zoom\": \"^1.1.0\",\n    \"opencollective-postinstall\": \"^2.0.2\",\n    \"prismjs\": \"^1.29.0\",\n    \"tinydate\": \"^1.3.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/eslint-parser\": \"^7.24.5\",\n    \"@babel/preset-env\": \"^7.11.5\",\n    \"@eslint/js\": \"^10.0.0\",\n    \"@playwright/test\": \"^1.57.0\",\n    \"@rollup/plugin-babel\": \"^6.0.4\",\n    \"@rollup/plugin-commonjs\": \"^29.0.0\",\n    \"@rollup/plugin-node-resolve\": \"^16.0.0\",\n    \"@rollup/plugin-replace\": \"^6.0.1\",\n    \"@rollup/plugin-terser\": \"^0.4.3\",\n    \"@types/common-tags\": \"^1.8.4\",\n    \"@types/eslint\": \"^8.40.2\",\n    \"@types/prismjs\": \"^1.26.5\",\n    \"axios\": \"^1.5.0\",\n    \"browser-sync\": \"^3.0.2\",\n    \"conventional-changelog-cli\": \"^3.0.0\",\n    \"cross-env\": \"^10.0.0\",\n    \"cssnano\": \"^7.0.1\",\n    \"eslint\": \"^9.3.0\",\n    \"eslint-config-prettier\": \"^10.0.1\",\n    \"eslint-plugin-jest\": \"^29.2.1\",\n    \"eslint-plugin-playwright\": \"^2.1.0\",\n    \"eslint-plugin-prettier\": \"^5.1.3\",\n    \"glob\": \"^13.0.0\",\n    \"globals\": \"^17.1.0\",\n    \"husky\": \"^9.0.11\",\n    \"jest\": \"^30.0.4\",\n    \"jest-environment-jsdom\": \"^30.0.5\",\n    \"lint-staged\": \"^16.1.0\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"postcss-cli\": \"^11.0.0\",\n    \"postcss-import\": \"^16.1.0\",\n    \"postcss-nesting\": \"^13.0.0\",\n    \"prettier\": \"^3.2.5\",\n    \"rimraf\": \"^6.1.0\",\n    \"rollup\": \"^4.17.2\",\n    \"rollup-plugin-import-css\": \"^4.0.1\",\n    \"typescript\": \"^5.9.3\",\n    \"vue\": \"^3.4.27\",\n    \"xhr-mock\": \"^2.5.1\"\n  },\n  \"scripts\": {\n    \"build:cover\": \"node build/cover.js\",\n    \"build:css\": \"postcss \\\"src/themes/*.css\\\" \\\"src/themes/**/[!_]*.css\\\" --base src/themes --dir dist/themes --map\",\n    \"build:css:min\": \"cross-env NODE_ENV='production' npm run build:css -- --ext .min.css\",\n    \"build:emoji\": \"node ./build/emoji.js\",\n    \"build:js\": \"rollup -c\",\n    \"build:types\": \"tsc\",\n    \"build:v4\": \"git checkout release-v4 && npm clean-install && git checkout docs/emoji.md src/core/render/emoji-data.js && rimraf packages/ && git checkout - && npm clean-install && npm run build:v4:deprecate -- lib/docsify.js && npm run build:v4:deprecate -- lib/docsify.min.js\",\n    \"build:v4:deprecate\": \"echo ';console.warn(\\\"Docsify v4 is no longer supported. See https://docsify.js.org for the latest version.\\\")' >> \",\n    \"build\": \"run-s clean build:types build:js build:css build:css:min build:cover\",\n    \"clean\": \"rimraf --glob \\\"dist/**\\\" \\\"_playwright*/**\\\" \\\"src/**/*.d.ts\\\" \\\"src/**/*.d.ts.map\\\"\",\n    \"clean:v4\": \"rimraf lib/ themes/\",\n    \"dev\": \"run-p serve:dev watch:*\",\n    \"docker:build:test\": \"npm run docker:cli -- build:test\",\n    \"docker:build\": \"docker build -f Dockerfile -t docsify-test:local .\",\n    \"docker:clean\": \"docker rmi docsify-test:local\",\n    \"docker:cli\": \"docker run --rm -it --ipc=host --mount type=bind,source=$(pwd)/test,target=/app/test docsify-test:local\",\n    \"docker:rebuild\": \"run-s docker:clean docker:build\",\n    \"docker:test:e2e\": \"npm run docker:cli -- test:e2e\",\n    \"docker:test:integration\": \"npm run docker:cli -- test:integration\",\n    \"docker:test:unit\": \"npm run docker:cli -- test:unit\",\n    \"docker:test\": \"npm run docker:cli -- test\",\n    \"lint:fix\": \"prettier . --write && eslint . --fix\",\n    \"lint\": \"prettier . --check && eslint .\",\n    \"postinstall\": \"opencollective-postinstall && npx husky install\",\n    \"prepare\": \"npm run build\",\n    \"prettier\": \"prettier . --write\",\n    \"pub:next\": \"cross-env RELEASE_TAG=next sh build/release.sh\",\n    \"pub\": \"sh build/release.sh\",\n    \"serve:dev\": \"npm run serve -- --dev\",\n    \"serve\": \"node server\",\n    \"test:e2e\": \"playwright test\",\n    \"test:e2e:chromium\": \"playwright test --project='chromium'\",\n    \"test:e2e:ui\": \"playwright test --ui\",\n    \"test:e2e:consume-types\": \"echo TODO: test the consume-types example with ESM modules\",\n    \"test:integration\": \"npm run test:jest -- --selectProjects integration\",\n    \"test:jest\": \"cross-env NODE_OPTIONS=--experimental-vm-modules jest\",\n    \"test:unit\": \"npm run test:jest -- --selectProjects unit\",\n    \"test:update:snapshot\": \"npm run test:jest -- --updateSnapshot\",\n    \"test:consume-types\": \"cd test/consume-types && npm clean-install --install-links && npm run typecheck\",\n    \"test\": \"run-s test:jest test:e2e test:consume-types\",\n    \"typecheck\": \"tsc --noEmit\",\n    \"typecheck:watch\": \"tsc --noEmit --watch\",\n    \"watch:css\": \"run-p 'build:css -- --watch' 'build:css:min -- --watch'\",\n    \"watch:js\": \"npm run build:js -- --watch\"\n  }\n}\n"
  },
  {
    "path": "playwright.config.js",
    "content": "import { devices } from '@playwright/test';\nimport { testConfig } from './server.configs.js';\n\nconst { hostname, port } = testConfig;\nconst TEST_HOST = `http://${hostname}:${port}`;\n\nprocess.env.TEST_HOST = TEST_HOST;\n\n/**\n * @see https://playwright.dev/docs/test-configuration\n * @type {import('@playwright/test').PlaywrightTestConfig}\n */\nconst config = {\n  // Setup / Teardown\n  globalSetup: './test/config/playwright.setup.js',\n  globalTeardown: './test/config/playwright.teardown.js',\n\n  // Test Execution\n  expect: {\n    timeout: 5000,\n  },\n  retries: process.env.CI ? 2 : 0, // Retry on CI only\n  testDir: './test/e2e',\n  timeout: 30 * 1000,\n  workers: process.env.CI ? 1 : undefined, // No parallel tests on CI\n  forbidOnly: !!process.env.CI, // Fail on CI if test.only in source\n\n  // Output\n  outputDir: './_playwright-results/', // screenshots, videos, traces, etc.\n  reporter: [\n    [\n      'html',\n      {\n        open: 'never',\n        outputFolder: '_playwright-report',\n      },\n    ],\n  ],\n  snapshotDir: './test/e2e/__snapshots__',\n\n  // Config - Shared\n  // See https://playwright.dev/docs/api/class-testoptions\n  use: {\n    actionTimeout: 0,\n    baseURL: TEST_HOST, // Allow relative page.goto() (e.g. `await page.goto('/')`).\n    trace: 'on-first-retry',\n  },\n\n  // Projects\n  projects: [\n    {\n      name: 'chromium',\n      use: { ...devices['Desktop Chrome'] },\n    },\n    {\n      name: 'firefox',\n      use: { ...devices['Desktop Firefox'] },\n    },\n    {\n      name: 'webkit',\n      use: { ...devices['Desktop Safari'] },\n    },\n    // {\n    //   name: 'Mobile Safari',\n    //   use: { ...devices['iPhone 12'] }\n    // },\n  ],\n};\n\nexport default config;\n"
  },
  {
    "path": "postcss.config.cjs",
    "content": "module.exports = ctx => ({\n  map: ctx.options.map,\n  plugins: {\n    'postcss-import': {},\n    'postcss-nesting': {\n      edition: '2024-02',\n    },\n    cssnano: ctx.env === 'production' ? { preset: 'default' } : false,\n  },\n});\n"
  },
  {
    "path": "rollup.config.js",
    "content": "import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { babel } from '@rollup/plugin-babel';\nimport commonjs from '@rollup/plugin-commonjs';\nimport css from 'rollup-plugin-import-css';\nimport replace from '@rollup/plugin-replace';\nimport resolve from '@rollup/plugin-node-resolve';\nimport terser from '@rollup/plugin-terser';\nimport { glob } from 'glob';\nimport { stripIndent } from 'common-tags';\n\n// Setup\n// =============================================================================\n// Docsify\nconst docsifyConfig = {\n  inputPath: 'src/core/index.js',\n  outputDir: 'dist',\n  outputName: 'docsify',\n  title: 'Docsify',\n};\n\n// Plugins\nconst pluginPaths = await glob(['src/plugins/*.js', 'src/plugins/*/index.js']);\nconst pluginConfigs = pluginPaths.map(pluginPath => {\n  const dir = path.basename(path.dirname(pluginPath)); // path/to/dir/file.js => dir\n  const name = path.basename(pluginPath, '.js'); // path/to/dir/file.js => file\n  const outputName = name === 'index' ? dir : name;\n\n  return {\n    inputPath: pluginPath,\n    outputDir: 'dist/plugins',\n    outputName,\n    title: `Docsify Plugin: ${outputName}`,\n  };\n});\n\n// Rollup configurations\n// =============================================================================\nconst currentYear = new Date().getFullYear();\nconst { homepage, license, version } = JSON.parse(\n  await fs.readFile(path.join(import.meta.dirname, 'package.json'), 'utf8'),\n);\nconst baseConfig = {\n  output: {\n    format: 'iife',\n  },\n  plugins: [\n    resolve(),\n    commonjs(),\n    css(),\n    replace({\n      preventAssignment: true,\n      values: {\n        __VERSION__: version,\n      },\n    }),\n    babel({\n      babelHelpers: 'bundled',\n    }),\n  ],\n  watch: {\n    clearScreen: false,\n  },\n};\nconst bundleConfigs = [];\n\n// Generate rollup configurations\n[docsifyConfig, ...pluginConfigs].forEach(bundleConfig => {\n  const { inputPath, outputDir, outputName, title } = bundleConfig;\n  // prettier-ignore\n  const banner = stripIndent`\n    /*!\n     * ${title} v${version}\n     * ${homepage}\n     * (c) 2017-${currentYear}\n     * ${license} license\n     */\n  `;\n  const minifiedConfig = {\n    ...baseConfig,\n    input: inputPath,\n    output: {\n      ...baseConfig.output,\n      banner,\n      file: path.join(outputDir, `${outputName}.min.js`),\n      sourcemap: true,\n    },\n    plugins: [\n      ...baseConfig.plugins,\n      terser({\n        output: {\n          comments: /^!/,\n        },\n      }),\n    ],\n  };\n  const unminifiedConfig = {\n    ...baseConfig,\n    input: inputPath,\n    output: {\n      ...baseConfig.output,\n      banner,\n      file: path.join(outputDir, `${outputName}.js`),\n    },\n    plugins: [\n      ...baseConfig.plugins,\n      terser({\n        compress: false,\n        mangle: false,\n        output: {\n          beautify: true,\n          comments: /^!/,\n        },\n      }),\n    ],\n  };\n\n  bundleConfigs.push(minifiedConfig, unminifiedConfig);\n});\n\n// Exports\n// =============================================================================\nexport default [...bundleConfigs];\n"
  },
  {
    "path": "server.configs.js",
    "content": "import * as path from 'node:path';\nimport * as url from 'node:url';\nimport { rewriteRules } from './middleware.js';\n\nconst __filename = url.fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n// Production (CDN URLs, watch disabled)\nexport const prodConfig = {\n  ghostMode: false,\n  hostname: '127.0.0.1',\n  notify: false,\n  open: false,\n  port: 8080,\n  server: {\n    baseDir: './docs',\n  },\n  snippet: false,\n  ui: false,\n};\n\n// Development (local URLs, watch enabled)\nexport const devConfig = {\n  ...prodConfig,\n  files: ['CHANGELOG.md', 'docs/**/*', 'dist/**/*'],\n  port: 3000,\n  rewriteRules,\n  reloadDebounce: 1000,\n  reloadOnRestart: true,\n  server: {\n    ...prodConfig.server,\n    routes: {\n      '/changelog.md': path.resolve(__dirname, 'CHANGELOG.md'),\n      '/dist': path.resolve(__dirname, 'dist'),\n      '/node_modules': path.resolve(__dirname, 'node_modules'), // Required for automated Vue tests\n    },\n  },\n  snippet: true,\n};\n\n// Test (local URLs, watch disabled)\nexport const testConfig = {\n  ...devConfig,\n  port: 4000,\n  server: {\n    ...devConfig.server,\n    middleware: [\n      // Blank page required for test environment\n      {\n        route: '/_blank.html',\n        handle(req, res, next) {\n          res.setHeader('Content-Type', 'text/html');\n          res.end('<!DOCTYPE html><html><body></body></html>');\n          next();\n        },\n      },\n    ],\n  },\n  snippet: false,\n  watch: false,\n};\n"
  },
  {
    "path": "server.js",
    "content": "import { create } from 'browser-sync';\nimport { devConfig, prodConfig } from './server.configs.js';\n\nconst bsServer = create();\nconst args = process.argv.slice(2);\nconst config = args.includes('--dev') ? devConfig : prodConfig;\nconst configName = config === devConfig ? 'development' : 'production';\nconst isWatch = Boolean(config.files) && config.watch !== false;\nconst urlType = config === devConfig ? 'local' : 'CDN';\n\n// prettier-ignore\nconsole.log(`\\nStarting ${configName} server (${urlType} URLs, watch: ${isWatch})\\n`);\n\nbsServer.init(config);\n"
  },
  {
    "path": "src/core/Docsify.js",
    "content": "import prism from 'prismjs';\nimport { Router } from './router/index.js';\nimport { Render } from './render/index.js';\nimport { Fetch } from './fetch/index.js';\nimport { Events } from './event/index.js';\nimport { VirtualRoutes } from './virtual-routes/index.js';\n\nimport config from './config.js';\nimport { isFn } from './util/core.js';\nimport { Lifecycle } from './init/lifecycle.js';\n\nexport { prism };\nexport { marked } from 'marked';\nexport * as util from './util/index.js';\nexport * as dom from './util/dom.js';\nexport { Compiler } from './render/compiler.js';\nexport { slugify } from './render/slugify.js';\nexport { get } from './util/ajax.js';\n\n/** @typedef {new (...args: any[]) => any} Constructor */\n/** @typedef {import('./config.js').DocsifyConfig} DocsifyConfig */\n\nexport class Docsify extends Fetch(\n  Events(Render(VirtualRoutes(Router(Lifecycle(Object))))),\n) {\n  /** @type {DocsifyConfig} */\n  config;\n\n  /** @param {Partial<DocsifyConfig>} conf */\n  constructor(conf = {}) {\n    super();\n\n    this.config = config(this, conf);\n\n    this.initLifecycle(); // Init hooks\n    this.initPlugin(); // Install plugins\n    this.callHook('init');\n    this.initRouter(); // Add router\n    this.initRender(); // Render base DOM\n    this.initEvent(); // Bind events\n    this.initFetch(); // Fetch data\n    this.callHook('mounted');\n  }\n\n  initPlugin() {\n    this.config.plugins.forEach(fn => {\n      try {\n        isFn(fn) && fn(this._lifecycle, this);\n      } catch (err) {\n        if (this.config.catchPluginErrors) {\n          const errTitle = 'Docsify plugin error';\n\n          // eslint-disable-next-line no-console\n          console.error(errTitle, err);\n        } else {\n          throw err;\n        }\n      }\n    });\n  }\n}\n\nexport const version = '__VERSION__';\n"
  },
  {
    "path": "src/core/config.js",
    "content": "import { stripIndent } from 'common-tags';\nimport { hyphenate, isPrimitive } from './util/core.js';\n/** @import { Docsify } from './Docsify.js' */\n/** @import { Hooks } from './init/lifecycle.js' */\n\nconst currentScript = document.currentScript;\n\nconst defaultDocsifyConfig = () => ({\n  alias: /** @type {Record<string, string>} */ ({}),\n  auto2top: false,\n  autoHeader: false,\n  basePath: '',\n  catchPluginErrors: true,\n  cornerExternalLinkTarget:\n    /** @type {'_blank' | '_self' | '_parent' | '_top'  | '_unfencedTop'} */ (\n      '_blank'\n    ),\n  coverpage: /** @type {boolean | string} */ (''),\n  el: '#app',\n  executeScript: /** @type {null | boolean} */ (null),\n  ext: '.md',\n  externalLinkRel: /** @type {'noopener' | string} */ ('noopener'), // TODO string union type based on spec\n  externalLinkTarget:\n    /** @type {'_blank' | '_self' | '_parent' | '_top'  | '_unfencedTop'} */ (\n      '_blank'\n    ),\n  fallbackLanguages: /** @type {null | string[]} */ (null),\n  fallbackDefaultLanguage: '',\n  formatUpdated: /** @type {string | ((updatedAt: string) => string)} */ (''),\n  /** For the frontmatter plugin. */\n  frontMatter: /** @type {Record<string, TODO> | null} */ (null),\n  hideSidebar: false,\n  homepage: 'README.md',\n  keyBindings:\n    /** @type {false | { [commandName: string]: { bindings: string[]; callback: Function } }} */ ({}),\n  loadNavbar: /** @type {null | boolean | string} */ (null),\n  loadSidebar: /** @type {null | boolean | string} */ (null),\n  logo: false,\n  markdown: null,\n  maxLevel: 6,\n  mergeNavbar: false,\n  name: /** @type {boolean | string} */ (''),\n  nameLink: window.location.pathname,\n  nativeEmoji: false,\n  noCompileLinks: /** @type {string[]} */ ([]),\n  noEmoji: false,\n  notFoundPage: /** @type {boolean | string | Record<string, string>} */ (\n    false\n  ),\n  onlyCover: false,\n  plugins: /** @type {Plugin[]} */ ([]),\n  relativePath: false,\n  repo: /** @type {string} */ (''),\n  requestHeaders: /** @type {Record<string, string>} */ ({}),\n  routerMode: /** @type {'hash' | 'history'} */ 'hash',\n  routes: /** @type {Record<string, string | RouteHandler>} */ ({}),\n  skipLink: /** @type {false | string | Record<string, string>} */ (\n    'Skip to main content'\n  ),\n  subMaxLevel: 0,\n  vueComponents: /** @type {Record<string, TODO>} */ ({}),\n  vueGlobalOptions: /** @type {Record<string, TODO>} */ ({}),\n  vueMounts: /** @type {Record<string, TODO>} */ ({}),\n\n  // Deprecations //////////////////\n\n  /** @deprecated */\n  get themeColor() {\n    return this.__themeColor;\n  },\n  set themeColor(value) {\n    if (value) {\n      this.__themeColor = value;\n\n      // eslint-disable-next-line no-console\n      console.warn(\n        stripIndent(`\n          $docsify.themeColor is deprecated as of v5. Use the \"--theme-color\" CSS property to customize your theme color.\n          <style>\n            :root {\n              --theme-color: deeppink;\n            }\n          </style>\n        `).trim(),\n      );\n    }\n  },\n  __themeColor: '',\n\n  /** @deprecated */\n  get topMargin() {\n    return this.__topMargin;\n  },\n  set topMargin(value) {\n    if (value) {\n      this.__topMargin = value;\n\n      // eslint-disable-next-line no-console\n      console.warn(\n        stripIndent(`\n          $docsify.topMargin is deprecated as of v5. Use the \"--scroll-padding-top\" CSS property to specify a scroll margin when using a sticky navbar.\n          <style>\n            :root {\n              --scroll-padding-top: 10px;\n            }\n          </style>\n        `).trim(),\n      );\n    }\n  },\n  __topMargin: 0,\n});\n\n/** @typedef {ReturnType<typeof defaultDocsifyConfig>} DocsifyConfig */\n\n/**\n * @param {import('./Docsify.js').Docsify} vm\n * @param {Partial<DocsifyConfig>} config\n * @returns {DocsifyConfig}\n */\nexport default function (vm, config = {}) {\n  config = Object.assign(\n    defaultDocsifyConfig(),\n\n    // Handle non-function configs no matter what (f.e. plugins assign options onto it)\n    window.$docsify,\n\n    // Also handle function config (the app can specificy a function, and plugins will assign options onto it)\n    typeof window.$docsify === 'function' ? window.$docsify(vm) : undefined,\n\n    // Finally, user config passed directly to the instance has priority.\n    config,\n  );\n\n  // Merge default and user-specified key bindings\n  if (config.keyBindings !== false) {\n    config.keyBindings = Object.assign(\n      // Default\n      {\n        toggleSidebar: {\n          bindings: ['\\\\'],\n          callback(/** @type {KeyboardEvent} */ e) {\n            const toggleElm = /** @type {HTMLElement} */ (\n              document.querySelector('.sidebar-toggle-button')\n            );\n\n            if (toggleElm) {\n              toggleElm.click();\n            }\n          },\n        },\n      },\n      // User-specified\n      config.keyBindings,\n    );\n  }\n\n  const script =\n    currentScript ||\n    Array.from(document.getElementsByTagName('script')).filter(n =>\n      /docsify\\./.test(n.src),\n    )[0];\n\n  if (script) {\n    for (const prop of /** @type {(keyof DocsifyConfig)[]} */ (\n      Object.keys(config)\n    )) {\n      const val = script.getAttribute('data-' + hyphenate(prop));\n\n      if (isPrimitive(val)) {\n        // eslint-disable-next-line no-console\n        console.warn(\n          `DEPRECATION: data-* attributes on the docsify global script (f.e. ${\n            'data-' + hyphenate(prop)\n          }) are deprecated.`,\n        );\n\n        // @ts-expect-error too dynamic for TS\n        config[prop] = val === '' ? true : val;\n      }\n    }\n  }\n\n  if (config.loadSidebar === true) {\n    config.loadSidebar = '_sidebar' + config.ext;\n  }\n\n  if (config.loadNavbar === true) {\n    config.loadNavbar = '_navbar' + config.ext;\n  }\n\n  if (config.coverpage === true) {\n    config.coverpage = '_coverpage' + config.ext;\n  }\n\n  if (config.name === true) {\n    config.name = '';\n  }\n\n  return /** @type {DocsifyConfig} */ (config);\n}\n\n/** @typedef {any} TODO */\n\n/** @typedef {(hooks: Hooks, vm: Docsify) => void} Plugin */\n\n/**\n @typedef {(\n    ((route: string, matched: RegExpMatchArray) => string) |\n    ((route: string, matched: RegExpMatchArray, next: (markdown?: string) => void) => void)\n )} RouteHandler - Given a route, provides the markdown to render for that route.\n */\n\n/**\n@typedef {\n  {\n    subMaxLevel: number,\n    themeColor: string,\n    topMargin: number,\n  }\n} DocsifyConfigOld\n*/\n"
  },
  {
    "path": "src/core/event/index.js",
    "content": "import { isMobile, mobileBreakpoint } from '../util/env.js';\nimport * as dom from '../util/dom.js';\nimport { stripUrlExceptId } from '../router/util.js';\n\n/** @typedef {import('../Docsify.js').Constructor} Constructor */\n\n/**\n * @template {!Constructor} T\n * @param {T} Base - The class to extend\n */\nexport function Events(Base) {\n  return class Events extends Base {\n    #intersectionObserver = new IntersectionObserver(() => {});\n    #isScrolling = false;\n    #title = dom.$.title;\n\n    // Initialization\n    // =========================================================================\n    /**\n     * Initialize Docsify events\n     * One-time setup of listeners, observers, and tasks.\n     * @void\n     */\n    initEvent() {\n      const { topMargin } = this.config;\n\n      // Apply topMargin to scrolled content\n      if (topMargin) {\n        const value =\n          typeof topMargin === 'number' ? `${topMargin}px` : topMargin;\n\n        document.documentElement.style.setProperty(\n          '--scroll-padding-top',\n          value,\n        );\n      }\n\n      this.#initCover();\n      this.#initSkipToContent();\n      this.#initSidebar();\n      this.#initSidebarToggle();\n      this.#initKeyBindings();\n    }\n\n    // Sub-Initializations\n    // =========================================================================\n    /**\n     * Initialize cover observer\n     * Toggles sticky behavior when cover is not in view\n     * @void\n     */\n    #initCover() {\n      const coverElm = dom.find('section.cover');\n\n      if (!coverElm) {\n        dom.body.classList.add('sticky');\n        return;\n      }\n\n      const observer = new IntersectionObserver(\n        entries => {\n          const isIntersecting = entries[0].isIntersecting;\n          const op = isIntersecting ? 'remove' : 'add';\n\n          dom.body.classList[op]('sticky');\n        },\n        { threshold: 0.01 },\n      );\n\n      observer.observe(coverElm);\n    }\n\n    /**\n     * Initialize heading observer\n     * Toggles sidebar active item based on top viewport edge intersection\n     * @void\n     */\n    #initHeadings() {\n      const headingElms = dom.findAll('#main :where(h1, h2, h3, h4, h5, h6)');\n      const headingsInView = new Set();\n      let isInitialLoad = true;\n\n      // Mark sidebar active item on heading intersection\n      this.#intersectionObserver?.disconnect();\n      this.#intersectionObserver = new IntersectionObserver(\n        entries => {\n          if (isInitialLoad) {\n            isInitialLoad = false;\n            return;\n          }\n\n          if (this.#isScrolling) {\n            return;\n          }\n\n          for (const entry of entries) {\n            const op = entry.isIntersecting ? 'add' : 'delete';\n\n            headingsInView[op](entry.target);\n          }\n\n          let activeHeading;\n          if (headingsInView.size === 1) {\n            // Get first and only item in set.\n            // May be undefined if no headings are in view.\n            activeHeading = headingsInView.values().next().value;\n          } else if (headingsInView.size > 1) {\n            // Find the closest heading to the top of the viewport\n            // Reduce over the Set of headings currently in view (headingsInView) to determine the closest heading.\n            activeHeading = Array.from(headingsInView).reduce(\n              (closest, current) => {\n                return !closest ||\n                  closest.compareDocumentPosition(current) &\n                    Node.DOCUMENT_POSITION_FOLLOWING\n                  ? current\n                  : closest;\n              },\n              null,\n            );\n          }\n\n          if (activeHeading) {\n            const id = activeHeading.getAttribute('id');\n            const href = this.router.toURL(this.router.getCurrentPath(), {\n              id,\n            });\n            const newSidebarActiveElm = this.#markSidebarActiveElm(href);\n\n            newSidebarActiveElm?.scrollIntoView({\n              behavior: 'instant',\n              block: 'nearest',\n              inline: 'nearest',\n            });\n          }\n        },\n        {\n          rootMargin: '0% 0% -50% 0%', // Top half of viewport\n        },\n      );\n\n      headingElms.forEach(elm => {\n        this.#intersectionObserver.observe(elm);\n      });\n    }\n\n    /**\n     * Initialize keyboard bindings\n     * @void\n     */\n    #initKeyBindings() {\n      const { keyBindings } = this.config;\n      const modifierKeys = ['alt', 'ctrl', 'meta', 'shift'];\n\n      if (keyBindings && keyBindings.constructor === Object) {\n        // Prepare key binding configurations\n        Object.values(keyBindings || []).forEach(bindingConfig => {\n          const { bindings } = bindingConfig;\n\n          if (!bindings) {\n            return;\n          }\n\n          // Convert bindings to arrays\n          // Ex: 'alt+t' => ['alt+t']\n          bindingConfig.bindings = Array.isArray(bindings)\n            ? bindings\n            : [bindings];\n\n          // Convert key sequences to sorted arrays (modifiers first)\n          // Ex: ['alt+t', 't+ctrl'] => [['alt', 't'], ['ctrl', 't']]\n          bindingConfig.bindings = bindingConfig.bindings.map(\n            (/** @type {string | string[]} */ keys) => {\n              /** @type {string[][]} */\n              const sortedKeys = [[], []]; // Modifier keys, non-modifier keys\n\n              if (typeof keys === 'string') {\n                keys = keys.split('+');\n              }\n\n              keys.forEach(key => {\n                const isModifierKey = modifierKeys.includes(key);\n                const targetArray = sortedKeys[isModifierKey ? 0 : 1];\n                const newKeyValue = key.trim().toLowerCase();\n\n                targetArray.push(newKeyValue);\n              });\n\n              sortedKeys.forEach(arr => arr.sort());\n\n              return sortedKeys.flat();\n            },\n          );\n        });\n\n        // Handle keyboard events\n        dom.on('keydown', (/** @type {KeyboardEvent} */ e) => {\n          const isTextEntry = /** @type {HTMLElement} */ (\n            document.activeElement\n          ).matches('input, select, textarea');\n\n          if (isTextEntry) {\n            return;\n          }\n\n          const bindingConfigs = Object.values(keyBindings || []);\n          const matchingConfigs = bindingConfigs.filter(\n            (/** @type {{ bindings: string[][] }} */ { bindings }) =>\n              bindings &&\n              // bindings: [['alt', 't'], ['ctrl', 't']]\n              bindings.some((/** @type {string[]} */ keys) =>\n                // keys: ['alt', 't']\n                keys.every(\n                  // k: 'alt'\n                  k =>\n                    (modifierKeys.includes(k) &&\n                      e[/** @type {keyof KeyboardEvent} */ (k + 'Key')]) ||\n                    e.key === k || // Ex: \" \", \"a\"\n                    e.code.toLowerCase() === k || // \"space\"\n                    e.code.toLowerCase() === `key${k}`, // \"keya\"\n                ),\n              ),\n          );\n\n          matchingConfigs.forEach(({ callback }) => {\n            e.preventDefault();\n            callback(e);\n          });\n        });\n      }\n    }\n\n    /**\n     * Initialize sidebar event listeners\n     *\n     * @void\n     */\n    #initSidebar() {\n      const sidebarElm = document.querySelector('.sidebar');\n\n      if (!sidebarElm) {\n        return;\n      }\n\n      // Auto-toggle on resolution change\n      window\n        ?.matchMedia?.(`(max-width: ${mobileBreakpoint})`)\n        .addEventListener('change', evt => {\n          this.#toggleSidebar(!evt.matches);\n        });\n\n      // Collapse toggle\n      dom.on(sidebarElm, 'click', (/** @type {MouseEvent} */ { target }) => {\n        const linkElm = /** @type {HTMLElement} */ (target).closest('a');\n        const linkParent = /** @type {HTMLLIElement} */ (\n          linkElm?.closest('li')\n        );\n        const hasSubSidebar = linkParent?.querySelector('.app-sub-sidebar');\n\n        if (hasSubSidebar) {\n          linkParent.classList.toggle('collapse');\n        }\n      });\n    }\n\n    /**\n     * Initialize sidebar show/hide toggle behavior\n     *\n     * @void\n     */\n    #initSidebarToggle() {\n      const contentElm = dom.find('main > .content');\n      const toggleElm = dom.find('button.sidebar-toggle');\n\n      if (!toggleElm) {\n        return;\n      }\n\n      /** @type {HTMLElement | null} */\n      let lastContentFocusElm;\n\n      // Store last focused content element (restored via #toggleSidebar)\n      dom.on(contentElm, 'focusin', (/** @type {FocusEvent} */ e) => {\n        const focusAttr = 'data-restore-focus';\n\n        lastContentFocusElm?.removeAttribute(focusAttr);\n        lastContentFocusElm = /** @type {HTMLElement} */ (e.target);\n        lastContentFocusElm.setAttribute(focusAttr, '');\n      });\n\n      // Toggle sidebar\n      dom.on(toggleElm, 'click', (/** @type {MouseEvent} */ e) => {\n        e.stopPropagation();\n        this.#toggleSidebar();\n      });\n    }\n\n    /**\n     * Initialize skip to content behavior\n     *\n     * @void\n     */\n    #initSkipToContent() {\n      const skipElm = document.querySelector('#skip-to-content');\n\n      if (!skipElm) {\n        return;\n      }\n\n      skipElm.addEventListener('click', evt => {\n        const focusElm = this.#focusContent();\n\n        evt.preventDefault();\n        focusElm?.scrollIntoView({\n          behavior: 'smooth',\n        });\n      });\n    }\n\n    // Callbacks\n    // =========================================================================\n    /**\n     * Handle rendering UI element updates and new content\n     * @void\n     */\n    onRender() {\n      const { name } = this.config;\n      const currentPath = this.router.toURL(this.router.getCurrentPath());\n      const currentSection = dom\n        .find(`.sidebar a[href='${currentPath}']`)\n        ?.getAttribute('title');\n\n      const plainName = name ? name.replace(/<[^>]+>/g, '').trim() : name;\n      const currentTitle = plainName\n        ? currentSection\n          ? `${currentSection} - ${plainName}`\n          : plainName\n        : currentSection;\n\n      // Update page title\n      dom.$.title = currentTitle || this.#title;\n\n      this.#markAppNavActiveElm();\n      this.#markSidebarCurrentPage();\n      this.#initHeadings();\n    }\n\n    /**\n     * Handle navigation events\n     *\n     * @param {undefined|\"history\"|\"navigate\"} source Type of navigation where\n     * undefined is initial load, \"history\" is forward/back, and \"navigate\" is\n     * user click/tap\n     * @void\n     */\n    onNavigate(source) {\n      const { auto2top, topMargin } = this.config;\n      const { path, query } = this.route;\n      const activeSidebarElm = this.#markSidebarActiveElm();\n\n      // Note: Scroll position set by browser on forward/back (i.e. \"history\")\n      if (source !== 'history') {\n        // Anchor link\n        if (query.id) {\n          const headingElm = dom.find(\n            `.markdown-section :where(h1, h2, h3, h4, h5, h6)[id=\"${query.id}\"]`,\n          );\n\n          if (headingElm) {\n            this.#watchNextScroll();\n            headingElm.scrollIntoView({\n              behavior: 'smooth',\n              block: 'start',\n            });\n          }\n        }\n        // User click/tap\n        else if (source === 'navigate') {\n          // Scroll to top\n          if (auto2top) {\n            /** @type {Element} */ (document.scrollingElement).scrollTop =\n              topMargin ?? 0;\n          }\n        }\n      }\n\n      const isNavigate = source === 'navigate';\n      const hasId = 'id' in query;\n      const noSubSidebar = !activeSidebarElm?.querySelector('.app-sub-sidebar');\n\n      // Clicked anchor link\n      const shouldCloseSidebar =\n        path === '/' || (isNavigate && (hasId || noSubSidebar));\n\n      if (shouldCloseSidebar && isMobile()) {\n        this.#toggleSidebar(false);\n      }\n\n      // Clicked anchor link or page load with anchor ID\n      if (hasId || isNavigate) {\n        this.#focusContent();\n      }\n    }\n\n    // Functions\n    // =========================================================================\n    /**\n     * Set focus on the main content area: current route ID, first heading, or\n     * the main content container\n     *\n     * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus\n     * @param {Object} options HTMLElement focus() method options\n     * @returns HTMLElement|undefined\n     * @void\n     */\n    #focusContent(options = {}) {\n      const settings = {\n        preventScroll: true,\n        ...options,\n      };\n      const { query } = this.route;\n      const focusEl = /** @type {HTMLElement|null} */ (\n        query.id\n          ? // Heading ID\n            dom.find(`#${query.id}`)\n          : // First heading\n            dom.find('#main :where(h1, h2, h3, h4, h5, h6)') ||\n            // Content container\n            dom.find('#main')\n      );\n\n      // Move focus to content area\n      if (focusEl) {\n        if (!focusEl.hasAttribute('tabindex')) {\n          focusEl.setAttribute('tabindex', '-1');\n          focusEl.setAttribute('data-added-tabindex', 'true');\n        }\n\n        if (focusEl.hasAttribute('data-added-tabindex')) {\n          focusEl.scrollIntoView({ behavior: 'smooth' });\n        }\n\n        focusEl.focus(settings);\n      }\n\n      return focusEl;\n    }\n\n    /**\n     * Marks the active app nav item\n     */\n    #markAppNavActiveElm() {\n      const href = decodeURIComponent(this.router.toURL(this.route.path));\n\n      ['.app-nav', '.app-nav-merged'].forEach(selector => {\n        const navElm = dom.find(selector);\n\n        if (!navElm) {\n          return;\n        }\n\n        const newActive = /** @type {HTMLAnchorElement[]} */ (\n          dom.findAll(navElm, 'a')\n        )\n          .sort((a, b) => b.href.length - a.href.length)\n          .find(\n            a =>\n              href.includes(/** @type {string} */ (a.getAttribute('href'))) ||\n              href.includes(\n                decodeURI(/** @type {string} */ (a.getAttribute('href'))),\n              ),\n          )\n          ?.closest('li');\n        const oldActive = dom.find(navElm, 'li.active');\n\n        if (newActive && newActive !== oldActive) {\n          oldActive?.classList.remove('active');\n          newActive.classList.add('active');\n        }\n      });\n    }\n\n    /**\n     * Marks the active sidebar item\n     *\n     * @param {string} [href] Matching element HREF value. If unspecified,\n     * defaults to the current path (with query params)\n     * @returns Element|undefined\n     */\n    #markSidebarActiveElm(href) {\n      href ??= this.router.toURL(this.router.getCurrentPath());\n\n      const sidebar = dom.find('.sidebar');\n\n      if (!sidebar) {\n        return;\n      }\n\n      href = stripUrlExceptId(href);\n\n      const oldActive = dom.find(sidebar, 'li.active');\n      const newActive = dom\n        .find(\n          sidebar,\n          `a[href=\"${href}\"], a[href=\"${decodeURIComponent(/** @type {string} */ (href))}\"]`,\n        )\n        ?.closest('li');\n\n      if (newActive && newActive !== oldActive) {\n        oldActive?.classList.remove('active');\n        newActive.classList.add('active');\n      }\n\n      return newActive;\n    }\n\n    /**\n     * Marks the current page in the sidebar\n     *\n     * @param {string} [href] Matching sidebar element HREF value. If\n     * unspecified, defaults to the current path (without query params)\n     * @returns Element|undefined\n     */\n    #markSidebarCurrentPage(href) {\n      href ??= this.router.toURL(this.route.path);\n\n      const sidebar = dom.find('.sidebar');\n\n      if (!sidebar) {\n        return;\n      }\n\n      const path = href?.split('?')[0];\n      const oldPage = dom.find(sidebar, 'li[aria-current]');\n      const newPage = dom\n        .find(\n          sidebar,\n          `a[href=\"${path}\"], a[href=\"${decodeURIComponent(/** @type {string} */ (path))}\"]`,\n        )\n        ?.closest('li');\n\n      if (newPage && newPage !== oldPage) {\n        oldPage?.removeAttribute('aria-current');\n        newPage.setAttribute('aria-current', 'page');\n      }\n\n      return newPage;\n    }\n\n    /**\n     * @param {boolean} [force]\n     */\n    #toggleSidebar(force) {\n      const sidebarElm = /** @type {HTMLElement|null} */ (dom.find('.sidebar'));\n\n      if (!sidebarElm) {\n        return;\n      }\n\n      const ariaElms = dom.findAll('[aria-controls=\"__sidebar\"]');\n      const inertElms = dom.findAll(\n        'body > *:not(main, script), main > .content',\n      );\n      const isShow = sidebarElm.classList.toggle('show', force);\n\n      // Set aria-expanded attribute\n      ariaElms.forEach(toggleElm => {\n        const expanded = force ?? sidebarElm.classList.contains('show');\n        toggleElm.setAttribute('aria-expanded', String(expanded));\n        toggleElm.setAttribute(\n          'aria-label',\n          expanded ? 'Hide primary navigation' : 'Show primary navigation',\n        );\n      });\n\n      // Add inert attributes (focus trap)\n      if (isShow && isMobile()) {\n        inertElms.forEach(elm => elm.setAttribute('inert', ''));\n      }\n      // Remove inert attributes\n      else {\n        inertElms.forEach(elm => elm.removeAttribute('inert'));\n      }\n\n      if (isShow) {\n        sidebarElm.focus();\n      }\n      // Restore focus\n      else {\n        const restoreElm = /** @type {HTMLElement|null} */ (\n          document.querySelector('main > .content [data-restore-focus]')\n        );\n\n        if (restoreElm) {\n          restoreElm.focus({\n            preventScroll: true,\n          });\n        }\n      }\n    }\n\n    /**\n     * Monitor next scroll start/end and set #isScrolling to true/false\n     * accordingly. Listeners are removed after the start/end events are fired.\n     * @void\n     */\n    #watchNextScroll() {\n      // Scroll start\n      document.addEventListener(\n        'scroll',\n        () => {\n          this.#isScrolling = true;\n\n          // Scroll end\n          if ('onscrollend' in window) {\n            document.addEventListener(\n              'scrollend',\n              () => (this.#isScrolling = false),\n              { once: true },\n            );\n          }\n          // Browsers w/o native scrollend event support (Safari)\n          else {\n            /** @type {any} */\n            let scrollTimer;\n\n            const callback = () => {\n              clearTimeout(scrollTimer);\n\n              scrollTimer = setTimeout(() => {\n                document.removeEventListener('scroll', callback);\n                this.#isScrolling = false;\n              }, 100);\n            };\n\n            document.addEventListener('scroll', callback, false);\n          }\n        },\n        { once: true },\n      );\n    }\n  };\n}\n"
  },
  {
    "path": "src/core/fetch/index.js",
    "content": "import { getParentPath, stringifyQuery } from '../router/util.js';\nimport { noop, isExternal } from '../util/core.js';\nimport { get } from '../util/ajax.js';\n\n/** @typedef {import('../Docsify.js').Constructor} Constructor */\n\n/**\n * @template {!Constructor} T\n * @param {T} Base - The class to extend\n */\nexport function Fetch(Base) {\n  return class Fetch extends Base {\n    /**\n     * @param {any} path\n     * @param {any} qs\n     * @param {any} file\n     * @param {any} next\n     * @param {any} vm\n     * @param {any} [first]\n     */\n    #loadNested(path, qs, file, next, vm, first) {\n      path = first ? path : path.replace(/\\/$/, '');\n      path = getParentPath(path);\n\n      if (!path) {\n        return;\n      }\n\n      get(\n        vm.router.getFile(path + file) + qs,\n        false,\n        vm.config.requestHeaders,\n      ).then(next, _error => this.#loadNested(path, qs, file, next, vm));\n    }\n\n    /** @type {any} */\n    #last;\n\n    #abort = () => this.#last && this.#last.abort && this.#last.abort();\n\n    /**\n     * @param {any} url\n     * @param {any} requestHeaders\n     */\n    #request = (url, requestHeaders) => {\n      this.#abort();\n      this.#last = get(url, true, requestHeaders);\n      return this.#last;\n    };\n\n    /**\n     * @param {any} path\n     * @param {any} config\n     */\n    #get404Path = (path, config) => {\n      const { notFoundPage, ext } = config;\n      const defaultPath = '_404' + (ext || '.md');\n      let key;\n      let path404;\n\n      switch (typeof notFoundPage) {\n        case 'boolean':\n          path404 = defaultPath;\n          break;\n        case 'string':\n          path404 = notFoundPage;\n          break;\n\n        case 'object':\n          key = Object.keys(notFoundPage)\n            .sort((a, b) => b.length - a.length)\n            .filter(k => path.match(new RegExp('^' + k)))[0];\n\n          path404 = (key && notFoundPage[key]) || defaultPath;\n          break;\n\n        default:\n          break;\n      }\n\n      return path404;\n    };\n\n    /**\n     * @param {any} path\n     * @param {any} qs\n     * @param {any} loadSidebar\n     * @param {any} cb\n     */\n    _loadSideAndNav(path, qs, loadSidebar, cb) {\n      return () => {\n        /**\n         * @param {any} result\n         */\n        const renderSidebar = result => {\n          this._renderSidebar(result);\n          cb();\n        };\n\n        if (!loadSidebar) {\n          // Although, we don't load sidebar from sidebar file, we still need call the render to auto generate sidebar from headings toc\n          renderSidebar(null);\n          return;\n        }\n\n        // Load sidebar from the sidebar file\n        this.#loadNested(path, qs, loadSidebar, renderSidebar, this, true);\n      };\n    }\n\n    _fetch(cb = noop) {\n      const { query } = this.route;\n      const { path } = this.route;\n\n      // Prevent loading remote content via URL hash\n      // Ex: https://foo.com/#//bar.com/file.md\n      if (isExternal(path)) {\n        history.replaceState(null, '', '#');\n        this.router.normalize();\n      } else {\n        const qs = stringifyQuery(query, ['id']);\n        const { loadNavbar, requestHeaders, loadSidebar } = this.config;\n        // Abort last request\n\n        const file = this.router.getFile(path);\n\n        this.isRemoteUrl = isExternal(file);\n        // Current page is html\n        this.isHTML = /\\.html$/g.test(file);\n\n        // create a handler that should be called if content was fetched successfully\n        /**\n         * @param {any} text\n         * @param {any} [opt]\n         * @param {any} [response]\n         */\n        const contentFetched = (text, opt, response) => {\n          this.route.response = response;\n          this._renderMain(\n            text,\n            opt,\n            this._loadSideAndNav(path, qs, loadSidebar, cb),\n          );\n        };\n\n        // and a handler that is called if content failed to fetch\n        /**\n         * @param {any} _error\n         * @param {any} [response]\n         */\n        const contentFailedToFetch = (_error, response) => {\n          this.route.response = response;\n          this._fetchFallbackPage(path, qs, cb) || this._fetch404(file, qs, cb);\n        };\n\n        // attempt to fetch content from a virtual route, and fallback to fetching the actual file\n        if (!this.isRemoteUrl) {\n          this.matchVirtualRoute(path).then((/** @type {any} */ contents) => {\n            if (typeof contents === 'string') {\n              contentFetched(contents);\n            } else {\n              this.#request(file + qs, requestHeaders).then(\n                contentFetched,\n                contentFailedToFetch,\n              );\n            }\n          });\n        } else {\n          // if the requested url is not local, just fetch the file\n          this.#request(file + qs, requestHeaders).then(\n            contentFetched,\n            contentFailedToFetch,\n          );\n        }\n\n        // Load nav\n        loadNavbar &&\n          this.#loadNested(\n            path,\n            qs,\n            loadNavbar,\n            (/** @type {string} */ text) => this._renderNav(text),\n            this,\n            true,\n          );\n      }\n    }\n\n    _fetchCover() {\n      const { coverpage, requestHeaders } = this.config;\n      const query = this.route.query;\n      const root = getParentPath(this.route.path);\n\n      if (coverpage) {\n        let path = null;\n        const routePath = this.route.path;\n        if (typeof coverpage === 'string') {\n          if (routePath === '/') {\n            path = coverpage;\n          }\n        } else if (Array.isArray(coverpage)) {\n          path = coverpage.indexOf(routePath) > -1 && '_coverpage';\n        } else {\n          const cover = coverpage[routePath];\n          path = cover === true ? '_coverpage' : cover;\n        }\n\n        const coverOnly = Boolean(path) && this.config.onlyCover;\n        if (path) {\n          path = this.router.getFile(root + path);\n          this.coverIsHTML = /\\.html$/g.test(path);\n          get(path + stringifyQuery(query, ['id']), false, requestHeaders).then(\n            text => this._renderCover(text, coverOnly),\n          );\n        } else {\n          this._renderCover(null, coverOnly);\n        }\n\n        return coverOnly;\n      }\n    }\n\n    $fetch(cb = noop, onNavigate = this.onNavigate.bind(this)) {\n      const done = () => {\n        this.callHook('doneEach');\n        cb();\n      };\n\n      const onlyCover = this._fetchCover();\n\n      if (onlyCover) {\n        done();\n      } else {\n        this._fetch(() => {\n          onNavigate();\n          done();\n        });\n      }\n    }\n\n    /**\n     * @param {any} path\n     * @param {any} qs\n     * @param {any} [cb]\n     */\n    _fetchFallbackPage(path, qs, cb = noop) {\n      const {\n        requestHeaders,\n        fallbackLanguages,\n        fallbackDefaultLanguage,\n        loadSidebar,\n      } = this.config;\n\n      if (!fallbackLanguages) {\n        return false;\n      }\n\n      const local = path.split('/')[1];\n\n      if (fallbackLanguages.indexOf(local) === -1) {\n        return false;\n      }\n\n      const newPath = this.router.getFile(\n        path.replace(new RegExp(`^/${local}`), fallbackDefaultLanguage),\n      );\n      const req = this.#request(newPath + qs, requestHeaders);\n\n      req.then(\n        /**\n         * @param {any} text\n         * @param {any} [opt]\n         */\n        (text, opt) =>\n          this._renderMain(\n            text,\n            opt,\n            this._loadSideAndNav(path, qs, loadSidebar, cb),\n          ),\n        /** @param {any} _error */\n        _error => this._fetch404(path, qs, cb),\n      );\n\n      return true;\n    }\n\n    /**\n     * Load the 404 page\n     * @param {String} path URL to be loaded\n     * @param {*} qs TODO: define\n     * @param {Function} cb Callback\n     * @returns {Boolean} True if the requested page is not found\n     */\n    _fetch404(path, qs, cb = noop) {\n      const { loadSidebar, requestHeaders, notFoundPage } = this.config;\n\n      const fnLoadSideAndNav = this._loadSideAndNav(path, qs, loadSidebar, cb);\n      if (notFoundPage) {\n        const path404 = this.#get404Path(path, this.config);\n\n        this.#request(this.router.getFile(path404), requestHeaders).then(\n          /**\n           * @param {any} text\n           * @param {any} [opt]\n           */\n          (text, opt) => this._renderMain(text, opt, fnLoadSideAndNav),\n          /** @param {any} _error */\n          _error => this._renderMain(null, {}, fnLoadSideAndNav),\n        );\n        return true;\n      }\n\n      this._renderMain(null, {}, fnLoadSideAndNav);\n      return false;\n    }\n\n    initFetch() {\n      this.$fetch(() => this.callHook('ready'));\n    }\n  };\n}\n"
  },
  {
    "path": "src/core/global-api.js",
    "content": "import * as prism from 'prismjs';\nimport { marked } from 'marked';\nimport * as util from './util/index.js';\nimport * as dom from './util/dom.js';\nimport { Compiler } from './render/compiler.js';\nimport { slugify } from './render/slugify.js';\nimport { get } from './util/ajax.js';\n\nexport default function initGlobalAPI() {\n  window.Docsify = {\n    util,\n    dom,\n    get,\n    slugify,\n    version: '__VERSION__',\n  };\n  window.DocsifyCompiler = Compiler;\n  window.marked = marked;\n  window.Prism = prism;\n}\n"
  },
  {
    "path": "src/core/globals.ts",
    "content": "import prism from 'prismjs';\nimport { marked as _marked } from 'marked';\nimport * as util from './util/index.js';\nimport * as dom from './util/dom.js';\nimport { Compiler } from './render/compiler.js';\nimport { slugify } from './render/slugify.js';\nimport { get } from './util/ajax.js';\nimport { DocsifyConfig } from './config.js';\nimport { Docsify } from './Docsify.js';\n\ntype DocsifyConfigOrFn =\n  | Partial<DocsifyConfig>\n  | (Partial<DocsifyConfig> & ((config: Docsify) => Partial<DocsifyConfig>));\n\ndeclare global {\n  interface Window {\n    $docsify?: DocsifyConfigOrFn;\n\n    Docsify: {\n      util: typeof util;\n      dom: typeof dom;\n      get: typeof get;\n      slugify: typeof slugify;\n      version: string;\n    };\n    DocsifyCompiler: typeof Compiler;\n    marked: typeof _marked;\n    Prism: typeof prism;\n    Vue: any; // TODO Get Vue types and apply them here\n\n    __current_docsify_compiler__?: Compiler;\n  }\n\n  const $docsify: Window['$docsify'];\n\n  const Docsify: Window['Docsify'];\n  const DocsifyCompiler: Window['DocsifyCompiler'];\n  const marked: Window['marked'];\n  // @ts-expect-error Prism types are wonky\n  const Prism: Window['Prism'];\n  const Vue: Window['Vue'];\n}\n"
  },
  {
    "path": "src/core/index.js",
    "content": "import { documentReady } from './util/dom.js';\nimport { Docsify } from './Docsify.js';\nimport initGlobalAPI from './global-api.js';\n\n/**\n * Global API\n */\ninitGlobalAPI();\n\n/**\n * Run Docsify\n */\ndocumentReady(() => new Docsify());\n"
  },
  {
    "path": "src/core/init/lifecycle.js",
    "content": "import { noop } from '../util/core.js';\n\n/** @typedef {import('../Docsify.js').Constructor} Constructor */\n\n/**\n * @template {!Constructor} T\n * @param {T} Base - The class to extend\n */\nexport function Lifecycle(Base) {\n  return class Lifecycle extends Base {\n    /** @type {Record<string, Function[]>} */\n    _hooks = {};\n\n    _lifecycle = /** @type {Hooks} */ ({});\n\n    initLifecycle() {\n      const hooks = [\n        'init',\n        'mounted',\n        'beforeEach',\n        'afterEach',\n        'doneEach',\n        'ready',\n      ];\n\n      hooks.forEach((/** @type {string} */ hook) => {\n        /** @type {Function[]} */\n        const arr = (this._hooks[hook] = []);\n        this._lifecycle[hook] = (/** @type {Function} */ fn) => arr.push(fn);\n      });\n    }\n\n    /**\n     * @param {string} hookName\n     * @param {any} [data]\n     * @param {Function} [next]\n     */\n    callHook(hookName, data, next = noop) {\n      const queue = this._hooks[hookName];\n      const catchPluginErrors = this.config.catchPluginErrors;\n\n      /**\n       * @param {number} index\n       */\n      const step = function (index) {\n        const hookFn = queue[index];\n\n        if (index >= queue.length) {\n          next(data);\n        } else if (typeof hookFn === 'function') {\n          const errTitle = 'Docsify plugin error';\n\n          if (hookFn.length === 2) {\n            // FIXME this does not catch async errors. We can support async\n            // functions for this, or add a second arg to next() functions.\n            try {\n              hookFn(data, (/** @type {string} */ result) => {\n                data = result === undefined ? data : result;\n                step(index + 1);\n              });\n            } catch (err) {\n              if (catchPluginErrors) {\n                // eslint-disable-next-line no-console\n                console.error(errTitle, err);\n              } else {\n                throw err;\n              }\n\n              step(index + 1);\n            }\n          } else {\n            try {\n              const result = hookFn(data);\n\n              data = result === undefined ? data : result;\n              step(index + 1);\n            } catch (err) {\n              if (catchPluginErrors) {\n                // eslint-disable-next-line no-console\n                console.error(errTitle, err);\n              } else {\n                throw err;\n              }\n\n              step(index + 1);\n            }\n          }\n        } else {\n          step(index + 1);\n        }\n      };\n\n      step(0);\n    }\n  };\n}\n\n/**\n@typedef {{\n  init(): void\n  mounted(): void\n  beforeEach: (\n    ((markdown: string) => string) |\n    ((markdown: string, next: (markdown?: string) => void) => void)\n  )\n  afterEach: (\n    ((html: string) => string) |\n    ((html: string, next: (html?: string) => void) => void)\n  )\n  doneEach(): void\n  ready(): void\n}} Hooks\n*/\n"
  },
  {
    "path": "src/core/module.js",
    "content": "export * from './Docsify.js';\n"
  },
  {
    "path": "src/core/modules.ts",
    "content": "declare module '*.css' {\n  const cssText: string;\n  export default cssText;\n}\n"
  },
  {
    "path": "src/core/render/compiler/blockquote.js",
    "content": "export const blockquoteCompiler = ({ renderer }) =>\n  (renderer.blockquote = function ({ tokens }) {\n    let openTag = '<blockquote>';\n    let closeTag = '</blockquote>';\n\n    // Find the first paragraph token in the blockquote\n    const firstParagraphIndex = tokens.findIndex(t => t.type === 'paragraph');\n    const firstParagraph = tokens[firstParagraphIndex];\n\n    if (firstParagraph) {\n      // Check if the paragraph starts with a callout like [!TIP] or [!NOTE]\n      const calloutData = firstParagraph.raw.match(/^(\\[!(\\w+)\\])/);\n\n      if (calloutData) {\n        const calloutMark = calloutData[1]; // \"[!TIP]\"\n        const calloutType = calloutData[2].toLowerCase(); // \"tip\"\n\n        // Remove the callout mark from the paragraph raw text\n        firstParagraph.raw = firstParagraph.raw\n          .replace(calloutMark, '')\n          .trimStart();\n        if (firstParagraph.tokens && firstParagraph.tokens.length > 0) {\n          firstParagraph.tokens.forEach(t => {\n            if (t.raw) {\n              t.raw = t.raw.replace(calloutMark, '');\n            }\n            if (t.text) {\n              t.text = t.text.replace(calloutMark, '');\n            }\n          });\n        }\n\n        // If the first paragraph is now empty after removing [!TIP], remove it\n        if (!firstParagraph.raw.trim()) {\n          tokens.splice(firstParagraphIndex, 1);\n        }\n\n        openTag = `<div class=\"callout ${calloutType}\">`;\n        closeTag = `</div>`;\n      }\n    }\n\n    const body = this.parser.parse(tokens);\n    return `${openTag}${body}${closeTag}`;\n  });\n"
  },
  {
    "path": "src/core/render/compiler/code.js",
    "content": "import * as Prism from 'prismjs';\n// See https://github.com/PrismJS/prism/pull/1367\nimport 'prismjs/components/prism-markup-templating.js';\nimport checkLangDependenciesAllLoaded from '../../util/prism.js';\n\nexport const highlightCodeCompiler = ({ renderer }) =>\n  (renderer.code = function ({ text, lang = 'markup' }) {\n    checkLangDependenciesAllLoaded(lang);\n    const langOrMarkup = Prism.languages[lang] || Prism.languages.markup;\n    const code = Prism.highlight(\n      text.replace(/@DOCSIFY_QM@/g, '`'),\n      langOrMarkup,\n      lang,\n    );\n\n    return /* html */ `<pre data-lang=\"${lang}\" class=\"language-${lang}\"><code class=\"lang-${lang} language-${lang}\" tabindex=\"0\">${code}</code></pre>`;\n  });\n"
  },
  {
    "path": "src/core/render/compiler/heading.js",
    "content": "import {\n  getAndRemoveConfig,\n  removeAtag,\n  getAndRemoveDocsifyIgnoreConfig,\n} from '../utils.js';\nimport { slugify } from '../slugify.js';\nimport { stripUrlExceptId } from '../../router/util.js';\n\nexport const headingCompiler = ({ renderer, router, compiler }) =>\n  (renderer.heading = function ({ tokens, depth, text }) {\n    const parsedText = this.parser.parseInline(tokens);\n    let { str, config } = getAndRemoveConfig(parsedText);\n    const nextToc = { depth, title: str };\n\n    const { content, ignoreAllSubs, ignoreSubHeading } =\n      getAndRemoveDocsifyIgnoreConfig(str);\n    str = content.trim();\n\n    nextToc.title = removeAtag(str);\n    nextToc.ignoreAllSubs = ignoreAllSubs;\n    nextToc.ignoreSubHeading = ignoreSubHeading;\n    const slug = slugify(config.id || text);\n    const url = router.toURL(router.getCurrentPath(), { id: slug });\n    nextToc.slug = stripUrlExceptId(url);\n    compiler.toc.push(nextToc);\n\n    // Note: tabindex=\"-1\" allows programmatically focusing on heading\n    // elements after navigation. This is preferred over focusing on the link\n    // within the heading because it matches the focus behavior of screen\n    // readers when navigating page content.\n    return `<h${depth} id=\"${slug}\" tabindex=\"-1\"><a href=\"${url}\" data-id=\"${slug}\" class=\"anchor\"><span>${str}</span></a></h${depth}>`;\n  });\n"
  },
  {
    "path": "src/core/render/compiler/image.js",
    "content": "import { getAndRemoveConfig } from '../utils.js';\nimport { isAbsolutePath, getPath, getParentPath } from '../../router/util.js';\n\nexport const imageCompiler = ({ renderer, contentBase, router }) =>\n  (renderer.image = ({ href, title, text }) => {\n    let url = href;\n    const attrs = [];\n\n    const { str, config } = getAndRemoveConfig(title);\n    title = str;\n\n    if (config['no-zoom']) {\n      attrs.push('data-no-zoom');\n    }\n\n    if (title) {\n      attrs.push(`title=\"${title}\"`);\n    }\n\n    if (config.size) {\n      const [width, height] = /** @type {string} */ (config.size).split('x');\n      if (height) {\n        attrs.push(`width=\"${width}\" height=\"${height}\"`);\n      } else {\n        attrs.push(`width=\"${width}\"`);\n      }\n    }\n\n    if (config.class) {\n      let classes = config.class;\n      if (Array.isArray(config.class)) {\n        classes = config.class.join(' ');\n      }\n      attrs.push(`class=\"${classes}\"`);\n    }\n\n    if (config.id) {\n      attrs.push(`id=\"${config.id}\"`);\n    }\n\n    if (!isAbsolutePath(href)) {\n      url = getPath(contentBase, getParentPath(router.getCurrentPath()), href);\n    }\n\n    return /* html */ `<img src=\"${url}\" data-origin=\"${href}\" alt=\"${text}\" ${attrs.join(\n      ' ',\n    )} />`;\n  });\n"
  },
  {
    "path": "src/core/render/compiler/link.js",
    "content": "import { getAndRemoveConfig } from '../utils.js';\nimport { isAbsolutePath } from '../../router/util.js';\n\nexport const linkCompiler = ({\n  renderer,\n  router,\n  linkTarget,\n  linkRel,\n  compiler,\n}) =>\n  (renderer.link = function ({ href, title = '', tokens }) {\n    const attrs = [];\n    const text = this.parser.parseInline(tokens) || '';\n    const { str, config } = getAndRemoveConfig(title);\n    const isAbsolute = isAbsolutePath(href);\n    const isNotCompilable = compiler._matchNotCompileLink(href);\n    const isMailto = href.startsWith('mailto:');\n\n    linkTarget = config.target || linkTarget;\n    linkRel =\n      linkTarget === '_blank'\n        ? compiler.config.externalLinkRel || 'noopener'\n        : '';\n    title = str;\n\n    if (!isAbsolute && !isNotCompilable && !config.ignore) {\n      if (href === compiler.config.homepage) {\n        href = 'README';\n      }\n      href = router.toURL(href, null, router.getCurrentPath());\n\n      if (config.target && !isMailto) {\n        attrs.push(`target=\"${linkTarget}\"`);\n      }\n    } else {\n      if (!isAbsolute && href.startsWith('./')) {\n        href = router\n          .toURL(href, null, router.getCurrentPath())\n          .replace(/^#\\//, '/');\n      }\n\n      if (!isMailto) {\n        attrs.push(`target=\"${linkTarget}\"`);\n        if (linkRel !== '') {\n          attrs.push(`rel=\"${linkRel}\"`);\n        }\n      }\n    }\n\n    if (config.disabled) {\n      attrs.push('disabled');\n      href = 'javascript:void(0)';\n    }\n\n    if (config.class) {\n      let classes = config.class;\n      if (Array.isArray(config.class)) {\n        classes = config.class.join(' ');\n      }\n      attrs.push(`class=\"${classes}\"`);\n    }\n\n    if (config.id) {\n      attrs.push(`id=\"${config.id}\"`);\n    }\n\n    if (title) {\n      attrs.push(`title=\"${title}\"`);\n    }\n\n    return /* html */ `<a href=\"${href}\" ${attrs.join(' ')}>${text}</a>`;\n  });\n"
  },
  {
    "path": "src/core/render/compiler/media.js",
    "content": "export const compileMedia = {\n  markdown(url) {\n    return {\n      url,\n    };\n  },\n  mermaid(url) {\n    return {\n      url,\n    };\n  },\n  iframe(url, title) {\n    return {\n      html: `<iframe src=\"${url}\" ${\n        title || 'width=100% height=400'\n      }></iframe>`,\n    };\n  },\n  video(url, title) {\n    return {\n      html: `<video src=\"${url}\" ${title || 'controls'}>Not Supported</video>`,\n    };\n  },\n  audio(url, title) {\n    return {\n      html: `<audio src=\"${url}\" ${title || 'controls'}>Not Supported</audio>`,\n    };\n  },\n  code(url, title) {\n    let lang = url.match(/\\.(\\w+)$/);\n\n    lang = title || (lang && lang[1]);\n    if (lang === 'md') {\n      lang = 'markdown';\n    }\n\n    return {\n      url,\n      lang,\n    };\n  },\n};\n"
  },
  {
    "path": "src/core/render/compiler/paragraph.js",
    "content": "import { helper as helperTpl } from '../tpl.js';\n\nexport const paragraphCompiler = ({ renderer }) =>\n  (renderer.paragraph = function ({ tokens }) {\n    const text = this.parser.parseInline(tokens);\n    let result;\n\n    if (text.startsWith('!&gt;')) {\n      result = helperTpl('callout important', text);\n    } else if (text.startsWith('?&gt;')) {\n      result = helperTpl('callout tip', text);\n    } else {\n      result = /* html */ `<p>${text}</p>`;\n    }\n\n    return result;\n  });\n"
  },
  {
    "path": "src/core/render/compiler/tableCell.js",
    "content": "export const tableCellCompiler = ({ renderer }) =>\n  (renderer.tablecell = function (token) {\n    let content;\n\n    if (token.embedTokens && token.embedTokens.length > 0) {\n      content = this.parser.parse(token.embedTokens);\n    } else {\n      content = this.parser.parseInline(token.tokens);\n    }\n\n    const type = token.header ? 'th' : 'td';\n    const tag = token.align ? `<${type} align=\"${token.align}\">` : `<${type}>`;\n\n    return tag + content + `</${type}>\\n`;\n  });\n"
  },
  {
    "path": "src/core/render/compiler/taskList.js",
    "content": "export const taskListCompiler = ({ renderer }) =>\n  (renderer.list = function (token) {\n    const ordered = token.ordered;\n    const start = token.start;\n\n    let body = '';\n    for (let j = 0; j < token.items.length; j++) {\n      const item = token.items[j];\n      body += this.listitem?.(item);\n    }\n\n    const isTaskList = /<li class=\"task-list-item\">/.test(\n      body.split('class=\"task-list\"')[0],\n    );\n    const isStartReq = start && start > 1;\n    const tag = ordered ? 'ol' : 'ul';\n    const tagAttrs = [\n      isTaskList ? 'class=\"task-list\"' : '',\n      isStartReq ? `start=\"${start}\"` : '',\n    ]\n      .join(' ')\n      .trim();\n\n    return `<${tag} ${tagAttrs}>${body}</${tag}>`;\n  });\n"
  },
  {
    "path": "src/core/render/compiler/taskListItem.js",
    "content": "export const taskListItemCompiler = ({ renderer }) =>\n  (renderer.listitem = function (item) {\n    let text = '';\n    if (item.task) {\n      const checkbox = this.checkbox?.({ checked: !!item.checked });\n      if (item.loose) {\n        if (item.tokens.length > 0 && item.tokens[0].type === 'paragraph') {\n          item.tokens[0].text = checkbox + ' ' + item.tokens[0].text;\n          if (\n            item.tokens[0].tokens &&\n            item.tokens[0].tokens.length > 0 &&\n            item.tokens[0].tokens[0].type === 'text'\n          ) {\n            item.tokens[0].tokens[0].text =\n              checkbox + ' ' + item.tokens[0].tokens[0].text;\n          }\n        } else {\n          item.tokens.unshift({\n            type: 'text',\n            raw: checkbox + ' ',\n            text: checkbox + ' ',\n          });\n        }\n      }\n    }\n\n    text += this.parser?.parse(item.tokens, !!item.loose);\n\n    const isTaskItem = /^(<input.*type=\"checkbox\"[^>]*>)/.test(text);\n    const html = isTaskItem\n      ? /* html */ `<li class=\"task-list-item\"><label>${text}</label></li>`\n      : /* html */ `<li>${text}</li>`;\n\n    return html;\n  });\n"
  },
  {
    "path": "src/core/render/compiler.js",
    "content": "import { marked } from 'marked';\nimport { isAbsolutePath, getPath, getParentPath } from '../router/util.js';\nimport { isFn, cached, isPrimitive } from '../util/core.js';\nimport { tree as treeTpl } from './tpl.js';\nimport { genTree } from './gen-tree.js';\nimport { slugify } from './slugify.js';\nimport { emojify } from './emojify.js';\nimport { getAndRemoveConfig } from './utils.js';\nimport { imageCompiler } from './compiler/image.js';\nimport { headingCompiler } from './compiler/heading.js';\nimport { highlightCodeCompiler } from './compiler/code.js';\nimport { paragraphCompiler } from './compiler/paragraph.js';\nimport { blockquoteCompiler } from './compiler/blockquote.js';\nimport { taskListCompiler } from './compiler/taskList.js';\nimport { taskListItemCompiler } from './compiler/taskListItem.js';\nimport { linkCompiler } from './compiler/link.js';\nimport { compileMedia } from './compiler/media.js';\nimport { tableCellCompiler } from './compiler/tableCell.js';\n\nconst cachedLinks = {};\n\nexport class Compiler {\n  constructor(config, router) {\n    this.config = config;\n    this.router = router;\n    this.cacheTree = {};\n    this.toc = [];\n    this.cacheTOC = {};\n    this.linkTarget = config.externalLinkTarget || '_blank';\n    this.linkRel =\n      this.linkTarget === '_blank' ? config.externalLinkRel || 'noopener' : '';\n    this.contentBase = router.getBasePath();\n\n    this.renderer = this._initRenderer();\n    let compile;\n    const mdConf = config.markdown || {};\n\n    if (isFn(mdConf)) {\n      compile = mdConf(marked, this.renderer);\n    } else {\n      marked.setOptions(\n        Object.assign(mdConf, {\n          renderer: Object.assign(this.renderer, mdConf.renderer),\n        }),\n      );\n      compile = marked;\n    }\n\n    this._marked = compile;\n    this.compile = text => {\n      let isCached = true;\n\n      // FIXME: this is not cached.\n      const result = cached(_ => {\n        isCached = false;\n        let html = '';\n\n        if (!text) {\n          return text;\n        }\n\n        if (isPrimitive(text)) {\n          html = compile(text);\n        } else {\n          html = compile.parser(text);\n        }\n\n        html = config.noEmoji ? html : emojify(html, config.nativeEmoji);\n        slugify.clear();\n\n        return html;\n      })(text);\n\n      const curFileName = this.router.parse().file;\n\n      if (isCached) {\n        this.toc = this.cacheTOC[curFileName];\n      } else {\n        this.cacheTOC[curFileName] = [...this.toc];\n      }\n\n      return result;\n    };\n  }\n\n  /**\n   * Pulls content from file and renders inline on the page as a embedded item.\n   *\n   * This allows you to embed different file types on the returned\n   * page.\n   * The basic format is:\n   * ```\n   *   [filename](_media/example.md ':include')\n   * ```\n   *\n   * @param {string}   href   The href to the file to embed in the page.\n   * @param {string}   title  Title of the link used to make the embed.\n   *\n   * @return {any} Return value description.\n   */\n  compileEmbed(href, title) {\n    const { str, config } = getAndRemoveConfig(title);\n    let embed;\n    title = str;\n\n    if (config.include) {\n      if (!isAbsolutePath(href)) {\n        href = getPath(\n          this.contentBase,\n          getParentPath(this.router.getCurrentPath()),\n          href,\n        );\n      }\n\n      let media;\n      if (config.type && (media = compileMedia[config.type])) {\n        embed = media.call(this, href, title);\n        embed.type = config.type;\n      } else {\n        let type = 'code';\n        if (/\\.(md|markdown)/.test(href)) {\n          type = 'markdown';\n        } else if (/\\.mmd/.test(href)) {\n          type = 'mermaid';\n        } else if (/\\.html?/.test(href)) {\n          type = 'iframe';\n        } else if (/\\.(mp4|ogg)/.test(href)) {\n          type = 'video';\n        } else if (/\\.mp3/.test(href)) {\n          type = 'audio';\n        }\n\n        embed = compileMedia[type](href, title);\n        embed.type = type;\n      }\n\n      embed.fragment = config.fragment;\n      embed.omitFragmentLine = config.omitFragmentLine;\n\n      return embed;\n    }\n  }\n\n  _matchNotCompileLink(link) {\n    const links = this.config.noCompileLinks || [];\n\n    for (const n of links) {\n      const re = cachedLinks[n] || (cachedLinks[n] = new RegExp(`^${n}$`));\n\n      if (re.test(link)) {\n        return link;\n      }\n    }\n  }\n\n  _initRenderer() {\n    const renderer = new marked.Renderer();\n    const { linkTarget, linkRel, router, contentBase } = this;\n    // Supports mermaid\n    const origin = {};\n\n    // Renderer customizers\n    origin.heading = headingCompiler({\n      renderer,\n      router,\n      compiler: this,\n    });\n    origin.blockquoteCompiler = blockquoteCompiler({ renderer });\n    origin.code = highlightCodeCompiler({ renderer });\n    origin.link = linkCompiler({\n      renderer,\n      router,\n      linkTarget,\n      linkRel,\n      compiler: this,\n    });\n    origin.paragraph = paragraphCompiler({ renderer });\n    origin.image = imageCompiler({ renderer, contentBase, router });\n    origin.list = taskListCompiler({ renderer });\n    origin.listitem = taskListItemCompiler({ renderer });\n    origin.tablecell = tableCellCompiler({ renderer });\n\n    // @ts-expect-error\n    renderer.origin = origin;\n\n    return renderer;\n  }\n\n  /**\n   * Compile sidebar, it uses _sidebar.md (or specific file) or the content's headings toc to render sidebar.\n   * @param {String} text Text content from the sidebar file, maybe empty\n   * @param {Number} level Type of heading (h<level> tag)\n   * @returns {String} Sidebar element\n   */\n  sidebar(text, level) {\n    const { toc } = this;\n    const currentPath = this.router.getCurrentPath();\n    let html = '';\n\n    // compile sidebar from _sidebar.md\n    if (text) {\n      return this.compile(text);\n    }\n    // compile sidebar from content's headings toc\n    for (let i = 0; i < toc.length; i++) {\n      if (toc[i].ignoreSubHeading) {\n        const deletedHeaderLevel = toc[i].depth;\n        toc.splice(i, 1);\n\n        // Remove all following headings with greater depth\n        while (i < toc.length && toc[i].depth > deletedHeaderLevel) {\n          toc.splice(i, 1);\n        }\n\n        i--;\n      }\n    }\n\n    const tree = this.cacheTree[currentPath] || genTree(toc, level);\n    html = treeTpl(tree);\n    this.cacheTree[currentPath] = tree;\n    return html;\n  }\n\n  /**\n   * When current content redirect to a new path file, clean pre content headings toc\n   */\n  resetToc() {\n    this.toc = [];\n  }\n\n  /**\n   * Compile sub sidebar\n   * @param {Number} level Type of heading (h<level> tag)\n   * @returns {String} Sub-sidebar element\n   */\n  subSidebar(level) {\n    const currentPath = this.router.getCurrentPath();\n    const { cacheTree, toc } = this;\n\n    toc[0] && toc[0].ignoreAllSubs && toc.splice(0);\n    // remove the first heading from the toc if it is a top-level heading\n    toc[0] && toc[0].depth === 1 && toc.shift();\n\n    for (let i = 0; i < toc.length; i++) {\n      toc[i].ignoreSubHeading && toc.splice(i, 1) && i--;\n    }\n\n    const tree = cacheTree[currentPath] || genTree(toc, level);\n\n    cacheTree[currentPath] = tree;\n    this.toc = [];\n    return treeTpl(tree);\n  }\n\n  /**\n   * Compile the text to generate HTML heading element based on the level\n   * @param {*} text Text content, for now it is only from the _sidebar.md file\n   * @param {*} level Type of heading (h<level> tag), for now it is always 1\n   * @returns\n   */\n  header(text, level) {\n    const tokenHeading = {\n      type: 'heading',\n      raw: text,\n      depth: level,\n      text: text,\n      tokens: [{ type: 'text', raw: text, text: text }],\n    };\n    // @ts-expect-error\n    return this.renderer.heading(tokenHeading);\n  }\n\n  /**\n   * Compile cover page\n   * @param {Text} text Text content\n   * @returns {String} Cover page\n   */\n  cover(text) {\n    const cacheToc = this.toc.slice();\n    const html = this.compile(text);\n\n    this.toc = cacheToc.slice();\n\n    return html;\n  }\n}\n"
  },
  {
    "path": "src/core/render/embed.js",
    "content": "import { stripIndent } from 'common-tags';\nimport { get } from '../util/ajax.js';\n\nconst cached = {};\n\n/**\n * Extracts the content between matching fragment markers in the text.\n *\n * Supported markers:\n * - ### [fragment] ... ### [fragment]\n * - /// [fragment] ... /// [fragment]\n *\n * @param {string} text - The input text that may contain embedded fragments.\n * @param {string} fragment - The fragment identifier to search for.\n * @param {boolean} fullLine - Boolean flag to enable full-line matching of fragment identifiers.\n * @returns {string} - The extracted and dedented content, or an empty string if not found.\n */\nfunction extractFragmentContent(text, fragment, fullLine) {\n  if (!fragment) {\n    return text;\n  }\n  let fragmentRegex = `(?:###|\\\\/\\\\/\\\\/)\\\\s*\\\\[${fragment}\\\\]`;\n  const contentRegex = `[\\\\s\\\\S]*?`;\n  if (fullLine) {\n    // Match full line containing fragment identifier (e.g. /// [demo])\n    fragmentRegex = `.*${fragmentRegex}.*\\n`;\n  }\n  const pattern = new RegExp(\n    `(?:${fragmentRegex})(${contentRegex})(?:${fragmentRegex})`,\n  ); // content is the capture group\n  const match = text.match(pattern);\n  return stripIndent((match || [])[1] || '').trim();\n}\n\nfunction walkFetchEmbed({ embedTokens, compile, fetch }, cb) {\n  let token;\n  let step = 0;\n  let count = 0;\n\n  if (!embedTokens.length) {\n    return cb({});\n  }\n\n  while ((token = embedTokens[step++])) {\n    const currentToken = token;\n\n    // eslint-disable-next-line no-loop-func\n    const next = text => {\n      let embedToken;\n      if (text) {\n        if (currentToken.embed.type === 'markdown') {\n          let path = currentToken.embed.url.split('/');\n          path.pop();\n          path = path.join('/');\n          // Resolves relative links to absolute\n          text = text.replace(/\\[([^[\\]]+)\\]\\(([^)]+)\\)/g, x => {\n            const linkBeginIndex = x.indexOf('(');\n            if (x.slice(linkBeginIndex, linkBeginIndex + 2) === '(.') {\n              return (\n                x.substring(0, linkBeginIndex) +\n                `(${window.location.protocol}//${window.location.host}${path}/` +\n                x.substring(linkBeginIndex + 1, x.length - 1) +\n                ')'\n              );\n            }\n            return x;\n          });\n\n          // This may contain YAML front matter and will need to be stripped.\n          const frontMatterInstalled = $docsify?.frontMatter?.installed;\n          if (frontMatterInstalled) {\n            text = $docsify.frontMatter?.parseMarkdown(text);\n          }\n\n          if (currentToken.embed.fragment) {\n            text = extractFragmentContent(\n              text,\n              currentToken.embed.fragment,\n              currentToken.embed.omitFragmentLine,\n            );\n          }\n\n          embedToken = compile.lexer(text);\n        } else if (currentToken.embed.type === 'code') {\n          if (currentToken.embed.fragment) {\n            text = extractFragmentContent(\n              text,\n              currentToken.embed.fragment,\n              currentToken.embed.omitFragmentLine,\n            );\n          }\n\n          embedToken = compile.lexer(\n            '```' +\n              currentToken.embed.lang +\n              '\\n' +\n              text.replace(/`/g, '@DOCSIFY_QM@') +\n              '\\n```\\n',\n          );\n        } else if (currentToken.embed.type === 'mermaid') {\n          embedToken = [\n            {\n              type: 'html',\n              text: /* html */ `<div class=\"mermaid\">\\n${text}\\n</div>`,\n            },\n          ];\n          /** @type {any} */ (embedToken).links = {};\n        } else {\n          embedToken = [{ type: 'html', text }];\n          /** @type {any} */ (embedToken).links = {};\n        }\n      }\n\n      cb({\n        token: currentToken,\n        embedToken,\n        rowIndex: currentToken.rowIndex,\n        cellIndex: currentToken.cellIndex,\n        tokenRef: currentToken.tokenRef,\n      });\n\n      if (++count >= embedTokens.length) {\n        cb({});\n      }\n    };\n\n    if (token.embed.url) {\n      get(token.embed.url).then(next);\n    } else {\n      next(token.embed.html);\n    }\n  }\n}\n\nexport function prerenderEmbed({ compiler, raw = '', fetch }, done) {\n  const hit = cached[raw];\n  if (hit) {\n    const copy = hit.slice();\n    copy.links = hit.links;\n    return done(copy);\n  }\n\n  const compile = compiler._marked;\n  let tokens = compile.lexer(raw);\n  const embedTokens = [];\n  const linkRE = compile.Lexer.rules.inline.normal.link;\n  const links = tokens.links;\n\n  const linkMatcher = new RegExp(linkRE.source, 'g');\n\n  tokens.forEach((token, index) => {\n    if (token.type === 'paragraph') {\n      token.text = token.text.replace(\n        linkMatcher,\n        (src, filename, href, title) => {\n          const embed = compiler.compileEmbed(href, title);\n          if (embed) {\n            embedTokens.push({\n              index,\n              tokenRef: token,\n              embed,\n            });\n          }\n          return src;\n        },\n      );\n    } else if (token.type === 'table') {\n      token.rows.forEach((row, rowIndex) => {\n        row.forEach((cell, cellIndex) => {\n          cell.text = cell.text.replace(\n            linkMatcher,\n            (src, filename, href, title) => {\n              const embed = compiler.compileEmbed(href, title);\n              if (embed) {\n                embedTokens.push({\n                  index,\n                  tokenRef: token,\n                  rowIndex,\n                  cellIndex,\n                  embed,\n                });\n              }\n              return src;\n            },\n          );\n        });\n      });\n    }\n  });\n\n  // keep track of which tokens have been embedded so far\n  // so that we know where to insert the embedded tokens as they\n  // are returned\n  const moves = [];\n  walkFetchEmbed(\n    { compile, embedTokens, fetch },\n    ({ embedToken, token, rowIndex, cellIndex, tokenRef }) => {\n      if (token) {\n        if (typeof rowIndex === 'number' && typeof cellIndex === 'number') {\n          const cell = tokenRef.rows[rowIndex][cellIndex];\n\n          cell.embedTokens = embedToken;\n        } else {\n          // iterate through the array of previously inserted tokens\n          // to determine where the current embedded tokens should be inserted\n          let index = token.index;\n          moves.forEach(pos => {\n            if (index > pos.start) {\n              index += pos.length;\n            }\n          });\n\n          Object.assign(links, embedToken.links);\n\n          tokens = tokens\n            .slice(0, index)\n            .concat(embedToken, tokens.slice(index + 1));\n          moves.push({ start: index, length: embedToken.length - 1 });\n        }\n      } else {\n        cached[raw] = tokens.concat();\n        tokens.links = cached[raw].links = links;\n        done(tokens);\n      }\n    },\n  );\n}\n"
  },
  {
    "path": "src/core/render/emoji-data.js",
    "content": "// =============================================================================\n// DO NOT EDIT: This file is auto-generated by an /build/emoji.js\n// =============================================================================\n\nexport default {\n  \"baseURL\": \"https://github.githubassets.com/images/icons/emoji/\",\n  \"data\": {\n    \"100\": \"unicode/1f4af.png?v8\",\n    \"1234\": \"unicode/1f522.png?v8\",\n    \"+1\": \"unicode/1f44d.png?v8\",\n    \"-1\": \"unicode/1f44e.png?v8\",\n    \"1st_place_medal\": \"unicode/1f947.png?v8\",\n    \"2nd_place_medal\": \"unicode/1f948.png?v8\",\n    \"3rd_place_medal\": \"unicode/1f949.png?v8\",\n    \"8ball\": \"unicode/1f3b1.png?v8\",\n    \"a\": \"unicode/1f170.png?v8\",\n    \"ab\": \"unicode/1f18e.png?v8\",\n    \"abacus\": \"unicode/1f9ee.png?v8\",\n    \"abc\": \"unicode/1f524.png?v8\",\n    \"abcd\": \"unicode/1f521.png?v8\",\n    \"accept\": \"unicode/1f251.png?v8\",\n    \"accessibility\": \"accessibility.png?v8\",\n    \"accordion\": \"unicode/1fa97.png?v8\",\n    \"adhesive_bandage\": \"unicode/1fa79.png?v8\",\n    \"adult\": \"unicode/1f9d1.png?v8\",\n    \"aerial_tramway\": \"unicode/1f6a1.png?v8\",\n    \"afghanistan\": \"unicode/1f1e6-1f1eb.png?v8\",\n    \"airplane\": \"unicode/2708.png?v8\",\n    \"aland_islands\": \"unicode/1f1e6-1f1fd.png?v8\",\n    \"alarm_clock\": \"unicode/23f0.png?v8\",\n    \"albania\": \"unicode/1f1e6-1f1f1.png?v8\",\n    \"alembic\": \"unicode/2697.png?v8\",\n    \"algeria\": \"unicode/1f1e9-1f1ff.png?v8\",\n    \"alien\": \"unicode/1f47d.png?v8\",\n    \"ambulance\": \"unicode/1f691.png?v8\",\n    \"american_samoa\": \"unicode/1f1e6-1f1f8.png?v8\",\n    \"amphora\": \"unicode/1f3fa.png?v8\",\n    \"anatomical_heart\": \"unicode/1fac0.png?v8\",\n    \"anchor\": \"unicode/2693.png?v8\",\n    \"andorra\": \"unicode/1f1e6-1f1e9.png?v8\",\n    \"angel\": \"unicode/1f47c.png?v8\",\n    \"anger\": \"unicode/1f4a2.png?v8\",\n    \"angola\": \"unicode/1f1e6-1f1f4.png?v8\",\n    \"angry\": \"unicode/1f620.png?v8\",\n    \"anguilla\": \"unicode/1f1e6-1f1ee.png?v8\",\n    \"anguished\": \"unicode/1f627.png?v8\",\n    \"ant\": \"unicode/1f41c.png?v8\",\n    \"antarctica\": \"unicode/1f1e6-1f1f6.png?v8\",\n    \"antigua_barbuda\": \"unicode/1f1e6-1f1ec.png?v8\",\n    \"apple\": \"unicode/1f34e.png?v8\",\n    \"aquarius\": \"unicode/2652.png?v8\",\n    \"argentina\": \"unicode/1f1e6-1f1f7.png?v8\",\n    \"aries\": \"unicode/2648.png?v8\",\n    \"armenia\": \"unicode/1f1e6-1f1f2.png?v8\",\n    \"arrow_backward\": \"unicode/25c0.png?v8\",\n    \"arrow_double_down\": \"unicode/23ec.png?v8\",\n    \"arrow_double_up\": \"unicode/23eb.png?v8\",\n    \"arrow_down\": \"unicode/2b07.png?v8\",\n    \"arrow_down_small\": \"unicode/1f53d.png?v8\",\n    \"arrow_forward\": \"unicode/25b6.png?v8\",\n    \"arrow_heading_down\": \"unicode/2935.png?v8\",\n    \"arrow_heading_up\": \"unicode/2934.png?v8\",\n    \"arrow_left\": \"unicode/2b05.png?v8\",\n    \"arrow_lower_left\": \"unicode/2199.png?v8\",\n    \"arrow_lower_right\": \"unicode/2198.png?v8\",\n    \"arrow_right\": \"unicode/27a1.png?v8\",\n    \"arrow_right_hook\": \"unicode/21aa.png?v8\",\n    \"arrow_up\": \"unicode/2b06.png?v8\",\n    \"arrow_up_down\": \"unicode/2195.png?v8\",\n    \"arrow_up_small\": \"unicode/1f53c.png?v8\",\n    \"arrow_upper_left\": \"unicode/2196.png?v8\",\n    \"arrow_upper_right\": \"unicode/2197.png?v8\",\n    \"arrows_clockwise\": \"unicode/1f503.png?v8\",\n    \"arrows_counterclockwise\": \"unicode/1f504.png?v8\",\n    \"art\": \"unicode/1f3a8.png?v8\",\n    \"articulated_lorry\": \"unicode/1f69b.png?v8\",\n    \"artificial_satellite\": \"unicode/1f6f0.png?v8\",\n    \"artist\": \"unicode/1f9d1-1f3a8.png?v8\",\n    \"aruba\": \"unicode/1f1e6-1f1fc.png?v8\",\n    \"ascension_island\": \"unicode/1f1e6-1f1e8.png?v8\",\n    \"asterisk\": \"unicode/002a-20e3.png?v8\",\n    \"astonished\": \"unicode/1f632.png?v8\",\n    \"astronaut\": \"unicode/1f9d1-1f680.png?v8\",\n    \"athletic_shoe\": \"unicode/1f45f.png?v8\",\n    \"atm\": \"unicode/1f3e7.png?v8\",\n    \"atom\": \"atom.png?v8\",\n    \"atom_symbol\": \"unicode/269b.png?v8\",\n    \"australia\": \"unicode/1f1e6-1f1fa.png?v8\",\n    \"austria\": \"unicode/1f1e6-1f1f9.png?v8\",\n    \"auto_rickshaw\": \"unicode/1f6fa.png?v8\",\n    \"avocado\": \"unicode/1f951.png?v8\",\n    \"axe\": \"unicode/1fa93.png?v8\",\n    \"azerbaijan\": \"unicode/1f1e6-1f1ff.png?v8\",\n    \"b\": \"unicode/1f171.png?v8\",\n    \"baby\": \"unicode/1f476.png?v8\",\n    \"baby_bottle\": \"unicode/1f37c.png?v8\",\n    \"baby_chick\": \"unicode/1f424.png?v8\",\n    \"baby_symbol\": \"unicode/1f6bc.png?v8\",\n    \"back\": \"unicode/1f519.png?v8\",\n    \"bacon\": \"unicode/1f953.png?v8\",\n    \"badger\": \"unicode/1f9a1.png?v8\",\n    \"badminton\": \"unicode/1f3f8.png?v8\",\n    \"bagel\": \"unicode/1f96f.png?v8\",\n    \"baggage_claim\": \"unicode/1f6c4.png?v8\",\n    \"baguette_bread\": \"unicode/1f956.png?v8\",\n    \"bahamas\": \"unicode/1f1e7-1f1f8.png?v8\",\n    \"bahrain\": \"unicode/1f1e7-1f1ed.png?v8\",\n    \"balance_scale\": \"unicode/2696.png?v8\",\n    \"bald_man\": \"unicode/1f468-1f9b2.png?v8\",\n    \"bald_woman\": \"unicode/1f469-1f9b2.png?v8\",\n    \"ballet_shoes\": \"unicode/1fa70.png?v8\",\n    \"balloon\": \"unicode/1f388.png?v8\",\n    \"ballot_box\": \"unicode/1f5f3.png?v8\",\n    \"ballot_box_with_check\": \"unicode/2611.png?v8\",\n    \"bamboo\": \"unicode/1f38d.png?v8\",\n    \"banana\": \"unicode/1f34c.png?v8\",\n    \"bangbang\": \"unicode/203c.png?v8\",\n    \"bangladesh\": \"unicode/1f1e7-1f1e9.png?v8\",\n    \"banjo\": \"unicode/1fa95.png?v8\",\n    \"bank\": \"unicode/1f3e6.png?v8\",\n    \"bar_chart\": \"unicode/1f4ca.png?v8\",\n    \"barbados\": \"unicode/1f1e7-1f1e7.png?v8\",\n    \"barber\": \"unicode/1f488.png?v8\",\n    \"baseball\": \"unicode/26be.png?v8\",\n    \"basecamp\": \"basecamp.png?v8\",\n    \"basecampy\": \"basecampy.png?v8\",\n    \"basket\": \"unicode/1f9fa.png?v8\",\n    \"basketball\": \"unicode/1f3c0.png?v8\",\n    \"basketball_man\": \"unicode/26f9-2642.png?v8\",\n    \"basketball_woman\": \"unicode/26f9-2640.png?v8\",\n    \"bat\": \"unicode/1f987.png?v8\",\n    \"bath\": \"unicode/1f6c0.png?v8\",\n    \"bathtub\": \"unicode/1f6c1.png?v8\",\n    \"battery\": \"unicode/1f50b.png?v8\",\n    \"beach_umbrella\": \"unicode/1f3d6.png?v8\",\n    \"beans\": \"unicode/1fad8.png?v8\",\n    \"bear\": \"unicode/1f43b.png?v8\",\n    \"bearded_person\": \"unicode/1f9d4.png?v8\",\n    \"beaver\": \"unicode/1f9ab.png?v8\",\n    \"bed\": \"unicode/1f6cf.png?v8\",\n    \"bee\": \"unicode/1f41d.png?v8\",\n    \"beer\": \"unicode/1f37a.png?v8\",\n    \"beers\": \"unicode/1f37b.png?v8\",\n    \"beetle\": \"unicode/1fab2.png?v8\",\n    \"beginner\": \"unicode/1f530.png?v8\",\n    \"belarus\": \"unicode/1f1e7-1f1fe.png?v8\",\n    \"belgium\": \"unicode/1f1e7-1f1ea.png?v8\",\n    \"belize\": \"unicode/1f1e7-1f1ff.png?v8\",\n    \"bell\": \"unicode/1f514.png?v8\",\n    \"bell_pepper\": \"unicode/1fad1.png?v8\",\n    \"bellhop_bell\": \"unicode/1f6ce.png?v8\",\n    \"benin\": \"unicode/1f1e7-1f1ef.png?v8\",\n    \"bento\": \"unicode/1f371.png?v8\",\n    \"bermuda\": \"unicode/1f1e7-1f1f2.png?v8\",\n    \"beverage_box\": \"unicode/1f9c3.png?v8\",\n    \"bhutan\": \"unicode/1f1e7-1f1f9.png?v8\",\n    \"bicyclist\": \"unicode/1f6b4.png?v8\",\n    \"bike\": \"unicode/1f6b2.png?v8\",\n    \"biking_man\": \"unicode/1f6b4-2642.png?v8\",\n    \"biking_woman\": \"unicode/1f6b4-2640.png?v8\",\n    \"bikini\": \"unicode/1f459.png?v8\",\n    \"billed_cap\": \"unicode/1f9e2.png?v8\",\n    \"biohazard\": \"unicode/2623.png?v8\",\n    \"bird\": \"unicode/1f426.png?v8\",\n    \"birthday\": \"unicode/1f382.png?v8\",\n    \"bison\": \"unicode/1f9ac.png?v8\",\n    \"biting_lip\": \"unicode/1fae6.png?v8\",\n    \"black_bird\": \"unicode/1f426-2b1b.png?v8\",\n    \"black_cat\": \"unicode/1f408-2b1b.png?v8\",\n    \"black_circle\": \"unicode/26ab.png?v8\",\n    \"black_flag\": \"unicode/1f3f4.png?v8\",\n    \"black_heart\": \"unicode/1f5a4.png?v8\",\n    \"black_joker\": \"unicode/1f0cf.png?v8\",\n    \"black_large_square\": \"unicode/2b1b.png?v8\",\n    \"black_medium_small_square\": \"unicode/25fe.png?v8\",\n    \"black_medium_square\": \"unicode/25fc.png?v8\",\n    \"black_nib\": \"unicode/2712.png?v8\",\n    \"black_small_square\": \"unicode/25aa.png?v8\",\n    \"black_square_button\": \"unicode/1f532.png?v8\",\n    \"blond_haired_man\": \"unicode/1f471-2642.png?v8\",\n    \"blond_haired_person\": \"unicode/1f471.png?v8\",\n    \"blond_haired_woman\": \"unicode/1f471-2640.png?v8\",\n    \"blonde_woman\": \"unicode/1f471-2640.png?v8\",\n    \"blossom\": \"unicode/1f33c.png?v8\",\n    \"blowfish\": \"unicode/1f421.png?v8\",\n    \"blue_book\": \"unicode/1f4d8.png?v8\",\n    \"blue_car\": \"unicode/1f699.png?v8\",\n    \"blue_heart\": \"unicode/1f499.png?v8\",\n    \"blue_square\": \"unicode/1f7e6.png?v8\",\n    \"blueberries\": \"unicode/1fad0.png?v8\",\n    \"blush\": \"unicode/1f60a.png?v8\",\n    \"boar\": \"unicode/1f417.png?v8\",\n    \"boat\": \"unicode/26f5.png?v8\",\n    \"bolivia\": \"unicode/1f1e7-1f1f4.png?v8\",\n    \"bomb\": \"unicode/1f4a3.png?v8\",\n    \"bone\": \"unicode/1f9b4.png?v8\",\n    \"book\": \"unicode/1f4d6.png?v8\",\n    \"bookmark\": \"unicode/1f516.png?v8\",\n    \"bookmark_tabs\": \"unicode/1f4d1.png?v8\",\n    \"books\": \"unicode/1f4da.png?v8\",\n    \"boom\": \"unicode/1f4a5.png?v8\",\n    \"boomerang\": \"unicode/1fa83.png?v8\",\n    \"boot\": \"unicode/1f462.png?v8\",\n    \"bosnia_herzegovina\": \"unicode/1f1e7-1f1e6.png?v8\",\n    \"botswana\": \"unicode/1f1e7-1f1fc.png?v8\",\n    \"bouncing_ball_man\": \"unicode/26f9-2642.png?v8\",\n    \"bouncing_ball_person\": \"unicode/26f9.png?v8\",\n    \"bouncing_ball_woman\": \"unicode/26f9-2640.png?v8\",\n    \"bouquet\": \"unicode/1f490.png?v8\",\n    \"bouvet_island\": \"unicode/1f1e7-1f1fb.png?v8\",\n    \"bow\": \"unicode/1f647.png?v8\",\n    \"bow_and_arrow\": \"unicode/1f3f9.png?v8\",\n    \"bowing_man\": \"unicode/1f647-2642.png?v8\",\n    \"bowing_woman\": \"unicode/1f647-2640.png?v8\",\n    \"bowl_with_spoon\": \"unicode/1f963.png?v8\",\n    \"bowling\": \"unicode/1f3b3.png?v8\",\n    \"bowtie\": \"bowtie.png?v8\",\n    \"boxing_glove\": \"unicode/1f94a.png?v8\",\n    \"boy\": \"unicode/1f466.png?v8\",\n    \"brain\": \"unicode/1f9e0.png?v8\",\n    \"brazil\": \"unicode/1f1e7-1f1f7.png?v8\",\n    \"bread\": \"unicode/1f35e.png?v8\",\n    \"breast_feeding\": \"unicode/1f931.png?v8\",\n    \"bricks\": \"unicode/1f9f1.png?v8\",\n    \"bride_with_veil\": \"unicode/1f470-2640.png?v8\",\n    \"bridge_at_night\": \"unicode/1f309.png?v8\",\n    \"briefcase\": \"unicode/1f4bc.png?v8\",\n    \"british_indian_ocean_territory\": \"unicode/1f1ee-1f1f4.png?v8\",\n    \"british_virgin_islands\": \"unicode/1f1fb-1f1ec.png?v8\",\n    \"broccoli\": \"unicode/1f966.png?v8\",\n    \"broken_heart\": \"unicode/1f494.png?v8\",\n    \"broom\": \"unicode/1f9f9.png?v8\",\n    \"brown_circle\": \"unicode/1f7e4.png?v8\",\n    \"brown_heart\": \"unicode/1f90e.png?v8\",\n    \"brown_square\": \"unicode/1f7eb.png?v8\",\n    \"brunei\": \"unicode/1f1e7-1f1f3.png?v8\",\n    \"bubble_tea\": \"unicode/1f9cb.png?v8\",\n    \"bubbles\": \"unicode/1fae7.png?v8\",\n    \"bucket\": \"unicode/1faa3.png?v8\",\n    \"bug\": \"unicode/1f41b.png?v8\",\n    \"building_construction\": \"unicode/1f3d7.png?v8\",\n    \"bulb\": \"unicode/1f4a1.png?v8\",\n    \"bulgaria\": \"unicode/1f1e7-1f1ec.png?v8\",\n    \"bullettrain_front\": \"unicode/1f685.png?v8\",\n    \"bullettrain_side\": \"unicode/1f684.png?v8\",\n    \"burkina_faso\": \"unicode/1f1e7-1f1eb.png?v8\",\n    \"burrito\": \"unicode/1f32f.png?v8\",\n    \"burundi\": \"unicode/1f1e7-1f1ee.png?v8\",\n    \"bus\": \"unicode/1f68c.png?v8\",\n    \"business_suit_levitating\": \"unicode/1f574.png?v8\",\n    \"busstop\": \"unicode/1f68f.png?v8\",\n    \"bust_in_silhouette\": \"unicode/1f464.png?v8\",\n    \"busts_in_silhouette\": \"unicode/1f465.png?v8\",\n    \"butter\": \"unicode/1f9c8.png?v8\",\n    \"butterfly\": \"unicode/1f98b.png?v8\",\n    \"cactus\": \"unicode/1f335.png?v8\",\n    \"cake\": \"unicode/1f370.png?v8\",\n    \"calendar\": \"unicode/1f4c6.png?v8\",\n    \"call_me_hand\": \"unicode/1f919.png?v8\",\n    \"calling\": \"unicode/1f4f2.png?v8\",\n    \"cambodia\": \"unicode/1f1f0-1f1ed.png?v8\",\n    \"camel\": \"unicode/1f42b.png?v8\",\n    \"camera\": \"unicode/1f4f7.png?v8\",\n    \"camera_flash\": \"unicode/1f4f8.png?v8\",\n    \"cameroon\": \"unicode/1f1e8-1f1f2.png?v8\",\n    \"camping\": \"unicode/1f3d5.png?v8\",\n    \"canada\": \"unicode/1f1e8-1f1e6.png?v8\",\n    \"canary_islands\": \"unicode/1f1ee-1f1e8.png?v8\",\n    \"cancer\": \"unicode/264b.png?v8\",\n    \"candle\": \"unicode/1f56f.png?v8\",\n    \"candy\": \"unicode/1f36c.png?v8\",\n    \"canned_food\": \"unicode/1f96b.png?v8\",\n    \"canoe\": \"unicode/1f6f6.png?v8\",\n    \"cape_verde\": \"unicode/1f1e8-1f1fb.png?v8\",\n    \"capital_abcd\": \"unicode/1f520.png?v8\",\n    \"capricorn\": \"unicode/2651.png?v8\",\n    \"car\": \"unicode/1f697.png?v8\",\n    \"card_file_box\": \"unicode/1f5c3.png?v8\",\n    \"card_index\": \"unicode/1f4c7.png?v8\",\n    \"card_index_dividers\": \"unicode/1f5c2.png?v8\",\n    \"caribbean_netherlands\": \"unicode/1f1e7-1f1f6.png?v8\",\n    \"carousel_horse\": \"unicode/1f3a0.png?v8\",\n    \"carpentry_saw\": \"unicode/1fa9a.png?v8\",\n    \"carrot\": \"unicode/1f955.png?v8\",\n    \"cartwheeling\": \"unicode/1f938.png?v8\",\n    \"cat\": \"unicode/1f431.png?v8\",\n    \"cat2\": \"unicode/1f408.png?v8\",\n    \"cayman_islands\": \"unicode/1f1f0-1f1fe.png?v8\",\n    \"cd\": \"unicode/1f4bf.png?v8\",\n    \"central_african_republic\": \"unicode/1f1e8-1f1eb.png?v8\",\n    \"ceuta_melilla\": \"unicode/1f1ea-1f1e6.png?v8\",\n    \"chad\": \"unicode/1f1f9-1f1e9.png?v8\",\n    \"chains\": \"unicode/26d3.png?v8\",\n    \"chair\": \"unicode/1fa91.png?v8\",\n    \"champagne\": \"unicode/1f37e.png?v8\",\n    \"chart\": \"unicode/1f4b9.png?v8\",\n    \"chart_with_downwards_trend\": \"unicode/1f4c9.png?v8\",\n    \"chart_with_upwards_trend\": \"unicode/1f4c8.png?v8\",\n    \"checkered_flag\": \"unicode/1f3c1.png?v8\",\n    \"cheese\": \"unicode/1f9c0.png?v8\",\n    \"cherries\": \"unicode/1f352.png?v8\",\n    \"cherry_blossom\": \"unicode/1f338.png?v8\",\n    \"chess_pawn\": \"unicode/265f.png?v8\",\n    \"chestnut\": \"unicode/1f330.png?v8\",\n    \"chicken\": \"unicode/1f414.png?v8\",\n    \"child\": \"unicode/1f9d2.png?v8\",\n    \"children_crossing\": \"unicode/1f6b8.png?v8\",\n    \"chile\": \"unicode/1f1e8-1f1f1.png?v8\",\n    \"chipmunk\": \"unicode/1f43f.png?v8\",\n    \"chocolate_bar\": \"unicode/1f36b.png?v8\",\n    \"chopsticks\": \"unicode/1f962.png?v8\",\n    \"christmas_island\": \"unicode/1f1e8-1f1fd.png?v8\",\n    \"christmas_tree\": \"unicode/1f384.png?v8\",\n    \"church\": \"unicode/26ea.png?v8\",\n    \"cinema\": \"unicode/1f3a6.png?v8\",\n    \"circus_tent\": \"unicode/1f3aa.png?v8\",\n    \"city_sunrise\": \"unicode/1f307.png?v8\",\n    \"city_sunset\": \"unicode/1f306.png?v8\",\n    \"cityscape\": \"unicode/1f3d9.png?v8\",\n    \"cl\": \"unicode/1f191.png?v8\",\n    \"clamp\": \"unicode/1f5dc.png?v8\",\n    \"clap\": \"unicode/1f44f.png?v8\",\n    \"clapper\": \"unicode/1f3ac.png?v8\",\n    \"classical_building\": \"unicode/1f3db.png?v8\",\n    \"climbing\": \"unicode/1f9d7.png?v8\",\n    \"climbing_man\": \"unicode/1f9d7-2642.png?v8\",\n    \"climbing_woman\": \"unicode/1f9d7-2640.png?v8\",\n    \"clinking_glasses\": \"unicode/1f942.png?v8\",\n    \"clipboard\": \"unicode/1f4cb.png?v8\",\n    \"clipperton_island\": \"unicode/1f1e8-1f1f5.png?v8\",\n    \"clock1\": \"unicode/1f550.png?v8\",\n    \"clock10\": \"unicode/1f559.png?v8\",\n    \"clock1030\": \"unicode/1f565.png?v8\",\n    \"clock11\": \"unicode/1f55a.png?v8\",\n    \"clock1130\": \"unicode/1f566.png?v8\",\n    \"clock12\": \"unicode/1f55b.png?v8\",\n    \"clock1230\": \"unicode/1f567.png?v8\",\n    \"clock130\": \"unicode/1f55c.png?v8\",\n    \"clock2\": \"unicode/1f551.png?v8\",\n    \"clock230\": \"unicode/1f55d.png?v8\",\n    \"clock3\": \"unicode/1f552.png?v8\",\n    \"clock330\": \"unicode/1f55e.png?v8\",\n    \"clock4\": \"unicode/1f553.png?v8\",\n    \"clock430\": \"unicode/1f55f.png?v8\",\n    \"clock5\": \"unicode/1f554.png?v8\",\n    \"clock530\": \"unicode/1f560.png?v8\",\n    \"clock6\": \"unicode/1f555.png?v8\",\n    \"clock630\": \"unicode/1f561.png?v8\",\n    \"clock7\": \"unicode/1f556.png?v8\",\n    \"clock730\": \"unicode/1f562.png?v8\",\n    \"clock8\": \"unicode/1f557.png?v8\",\n    \"clock830\": \"unicode/1f563.png?v8\",\n    \"clock9\": \"unicode/1f558.png?v8\",\n    \"clock930\": \"unicode/1f564.png?v8\",\n    \"closed_book\": \"unicode/1f4d5.png?v8\",\n    \"closed_lock_with_key\": \"unicode/1f510.png?v8\",\n    \"closed_umbrella\": \"unicode/1f302.png?v8\",\n    \"cloud\": \"unicode/2601.png?v8\",\n    \"cloud_with_lightning\": \"unicode/1f329.png?v8\",\n    \"cloud_with_lightning_and_rain\": \"unicode/26c8.png?v8\",\n    \"cloud_with_rain\": \"unicode/1f327.png?v8\",\n    \"cloud_with_snow\": \"unicode/1f328.png?v8\",\n    \"clown_face\": \"unicode/1f921.png?v8\",\n    \"clubs\": \"unicode/2663.png?v8\",\n    \"cn\": \"unicode/1f1e8-1f1f3.png?v8\",\n    \"coat\": \"unicode/1f9e5.png?v8\",\n    \"cockroach\": \"unicode/1fab3.png?v8\",\n    \"cocktail\": \"unicode/1f378.png?v8\",\n    \"coconut\": \"unicode/1f965.png?v8\",\n    \"cocos_islands\": \"unicode/1f1e8-1f1e8.png?v8\",\n    \"coffee\": \"unicode/2615.png?v8\",\n    \"coffin\": \"unicode/26b0.png?v8\",\n    \"coin\": \"unicode/1fa99.png?v8\",\n    \"cold_face\": \"unicode/1f976.png?v8\",\n    \"cold_sweat\": \"unicode/1f630.png?v8\",\n    \"collision\": \"unicode/1f4a5.png?v8\",\n    \"colombia\": \"unicode/1f1e8-1f1f4.png?v8\",\n    \"comet\": \"unicode/2604.png?v8\",\n    \"comoros\": \"unicode/1f1f0-1f1f2.png?v8\",\n    \"compass\": \"unicode/1f9ed.png?v8\",\n    \"computer\": \"unicode/1f4bb.png?v8\",\n    \"computer_mouse\": \"unicode/1f5b1.png?v8\",\n    \"confetti_ball\": \"unicode/1f38a.png?v8\",\n    \"confounded\": \"unicode/1f616.png?v8\",\n    \"confused\": \"unicode/1f615.png?v8\",\n    \"congo_brazzaville\": \"unicode/1f1e8-1f1ec.png?v8\",\n    \"congo_kinshasa\": \"unicode/1f1e8-1f1e9.png?v8\",\n    \"congratulations\": \"unicode/3297.png?v8\",\n    \"construction\": \"unicode/1f6a7.png?v8\",\n    \"construction_worker\": \"unicode/1f477.png?v8\",\n    \"construction_worker_man\": \"unicode/1f477-2642.png?v8\",\n    \"construction_worker_woman\": \"unicode/1f477-2640.png?v8\",\n    \"control_knobs\": \"unicode/1f39b.png?v8\",\n    \"convenience_store\": \"unicode/1f3ea.png?v8\",\n    \"cook\": \"unicode/1f9d1-1f373.png?v8\",\n    \"cook_islands\": \"unicode/1f1e8-1f1f0.png?v8\",\n    \"cookie\": \"unicode/1f36a.png?v8\",\n    \"cool\": \"unicode/1f192.png?v8\",\n    \"cop\": \"unicode/1f46e.png?v8\",\n    \"copilot\": \"copilot.png?v8\",\n    \"copyright\": \"unicode/00a9.png?v8\",\n    \"coral\": \"unicode/1fab8.png?v8\",\n    \"corn\": \"unicode/1f33d.png?v8\",\n    \"costa_rica\": \"unicode/1f1e8-1f1f7.png?v8\",\n    \"cote_divoire\": \"unicode/1f1e8-1f1ee.png?v8\",\n    \"couch_and_lamp\": \"unicode/1f6cb.png?v8\",\n    \"couple\": \"unicode/1f46b.png?v8\",\n    \"couple_with_heart\": \"unicode/1f491.png?v8\",\n    \"couple_with_heart_man_man\": \"unicode/1f468-2764-1f468.png?v8\",\n    \"couple_with_heart_woman_man\": \"unicode/1f469-2764-1f468.png?v8\",\n    \"couple_with_heart_woman_woman\": \"unicode/1f469-2764-1f469.png?v8\",\n    \"couplekiss\": \"unicode/1f48f.png?v8\",\n    \"couplekiss_man_man\": \"unicode/1f468-2764-1f48b-1f468.png?v8\",\n    \"couplekiss_man_woman\": \"unicode/1f469-2764-1f48b-1f468.png?v8\",\n    \"couplekiss_woman_woman\": \"unicode/1f469-2764-1f48b-1f469.png?v8\",\n    \"cow\": \"unicode/1f42e.png?v8\",\n    \"cow2\": \"unicode/1f404.png?v8\",\n    \"cowboy_hat_face\": \"unicode/1f920.png?v8\",\n    \"crab\": \"unicode/1f980.png?v8\",\n    \"crayon\": \"unicode/1f58d.png?v8\",\n    \"credit_card\": \"unicode/1f4b3.png?v8\",\n    \"crescent_moon\": \"unicode/1f319.png?v8\",\n    \"cricket\": \"unicode/1f997.png?v8\",\n    \"cricket_game\": \"unicode/1f3cf.png?v8\",\n    \"croatia\": \"unicode/1f1ed-1f1f7.png?v8\",\n    \"crocodile\": \"unicode/1f40a.png?v8\",\n    \"croissant\": \"unicode/1f950.png?v8\",\n    \"crossed_fingers\": \"unicode/1f91e.png?v8\",\n    \"crossed_flags\": \"unicode/1f38c.png?v8\",\n    \"crossed_swords\": \"unicode/2694.png?v8\",\n    \"crown\": \"unicode/1f451.png?v8\",\n    \"crutch\": \"unicode/1fa7c.png?v8\",\n    \"cry\": \"unicode/1f622.png?v8\",\n    \"crying_cat_face\": \"unicode/1f63f.png?v8\",\n    \"crystal_ball\": \"unicode/1f52e.png?v8\",\n    \"cuba\": \"unicode/1f1e8-1f1fa.png?v8\",\n    \"cucumber\": \"unicode/1f952.png?v8\",\n    \"cup_with_straw\": \"unicode/1f964.png?v8\",\n    \"cupcake\": \"unicode/1f9c1.png?v8\",\n    \"cupid\": \"unicode/1f498.png?v8\",\n    \"curacao\": \"unicode/1f1e8-1f1fc.png?v8\",\n    \"curling_stone\": \"unicode/1f94c.png?v8\",\n    \"curly_haired_man\": \"unicode/1f468-1f9b1.png?v8\",\n    \"curly_haired_woman\": \"unicode/1f469-1f9b1.png?v8\",\n    \"curly_loop\": \"unicode/27b0.png?v8\",\n    \"currency_exchange\": \"unicode/1f4b1.png?v8\",\n    \"curry\": \"unicode/1f35b.png?v8\",\n    \"cursing_face\": \"unicode/1f92c.png?v8\",\n    \"custard\": \"unicode/1f36e.png?v8\",\n    \"customs\": \"unicode/1f6c3.png?v8\",\n    \"cut_of_meat\": \"unicode/1f969.png?v8\",\n    \"cyclone\": \"unicode/1f300.png?v8\",\n    \"cyprus\": \"unicode/1f1e8-1f1fe.png?v8\",\n    \"czech_republic\": \"unicode/1f1e8-1f1ff.png?v8\",\n    \"dagger\": \"unicode/1f5e1.png?v8\",\n    \"dancer\": \"unicode/1f483.png?v8\",\n    \"dancers\": \"unicode/1f46f.png?v8\",\n    \"dancing_men\": \"unicode/1f46f-2642.png?v8\",\n    \"dancing_women\": \"unicode/1f46f-2640.png?v8\",\n    \"dango\": \"unicode/1f361.png?v8\",\n    \"dark_sunglasses\": \"unicode/1f576.png?v8\",\n    \"dart\": \"unicode/1f3af.png?v8\",\n    \"dash\": \"unicode/1f4a8.png?v8\",\n    \"date\": \"unicode/1f4c5.png?v8\",\n    \"de\": \"unicode/1f1e9-1f1ea.png?v8\",\n    \"deaf_man\": \"unicode/1f9cf-2642.png?v8\",\n    \"deaf_person\": \"unicode/1f9cf.png?v8\",\n    \"deaf_woman\": \"unicode/1f9cf-2640.png?v8\",\n    \"deciduous_tree\": \"unicode/1f333.png?v8\",\n    \"deer\": \"unicode/1f98c.png?v8\",\n    \"denmark\": \"unicode/1f1e9-1f1f0.png?v8\",\n    \"department_store\": \"unicode/1f3ec.png?v8\",\n    \"dependabot\": \"dependabot.png?v8\",\n    \"derelict_house\": \"unicode/1f3da.png?v8\",\n    \"desert\": \"unicode/1f3dc.png?v8\",\n    \"desert_island\": \"unicode/1f3dd.png?v8\",\n    \"desktop_computer\": \"unicode/1f5a5.png?v8\",\n    \"detective\": \"unicode/1f575.png?v8\",\n    \"diamond_shape_with_a_dot_inside\": \"unicode/1f4a0.png?v8\",\n    \"diamonds\": \"unicode/2666.png?v8\",\n    \"diego_garcia\": \"unicode/1f1e9-1f1ec.png?v8\",\n    \"disappointed\": \"unicode/1f61e.png?v8\",\n    \"disappointed_relieved\": \"unicode/1f625.png?v8\",\n    \"disguised_face\": \"unicode/1f978.png?v8\",\n    \"diving_mask\": \"unicode/1f93f.png?v8\",\n    \"diya_lamp\": \"unicode/1fa94.png?v8\",\n    \"dizzy\": \"unicode/1f4ab.png?v8\",\n    \"dizzy_face\": \"unicode/1f635.png?v8\",\n    \"djibouti\": \"unicode/1f1e9-1f1ef.png?v8\",\n    \"dna\": \"unicode/1f9ec.png?v8\",\n    \"do_not_litter\": \"unicode/1f6af.png?v8\",\n    \"dodo\": \"unicode/1f9a4.png?v8\",\n    \"dog\": \"unicode/1f436.png?v8\",\n    \"dog2\": \"unicode/1f415.png?v8\",\n    \"dollar\": \"unicode/1f4b5.png?v8\",\n    \"dolls\": \"unicode/1f38e.png?v8\",\n    \"dolphin\": \"unicode/1f42c.png?v8\",\n    \"dominica\": \"unicode/1f1e9-1f1f2.png?v8\",\n    \"dominican_republic\": \"unicode/1f1e9-1f1f4.png?v8\",\n    \"donkey\": \"unicode/1facf.png?v8\",\n    \"door\": \"unicode/1f6aa.png?v8\",\n    \"dotted_line_face\": \"unicode/1fae5.png?v8\",\n    \"doughnut\": \"unicode/1f369.png?v8\",\n    \"dove\": \"unicode/1f54a.png?v8\",\n    \"dragon\": \"unicode/1f409.png?v8\",\n    \"dragon_face\": \"unicode/1f432.png?v8\",\n    \"dress\": \"unicode/1f457.png?v8\",\n    \"dromedary_camel\": \"unicode/1f42a.png?v8\",\n    \"drooling_face\": \"unicode/1f924.png?v8\",\n    \"drop_of_blood\": \"unicode/1fa78.png?v8\",\n    \"droplet\": \"unicode/1f4a7.png?v8\",\n    \"drum\": \"unicode/1f941.png?v8\",\n    \"duck\": \"unicode/1f986.png?v8\",\n    \"dumpling\": \"unicode/1f95f.png?v8\",\n    \"dvd\": \"unicode/1f4c0.png?v8\",\n    \"e-mail\": \"unicode/1f4e7.png?v8\",\n    \"eagle\": \"unicode/1f985.png?v8\",\n    \"ear\": \"unicode/1f442.png?v8\",\n    \"ear_of_rice\": \"unicode/1f33e.png?v8\",\n    \"ear_with_hearing_aid\": \"unicode/1f9bb.png?v8\",\n    \"earth_africa\": \"unicode/1f30d.png?v8\",\n    \"earth_americas\": \"unicode/1f30e.png?v8\",\n    \"earth_asia\": \"unicode/1f30f.png?v8\",\n    \"ecuador\": \"unicode/1f1ea-1f1e8.png?v8\",\n    \"egg\": \"unicode/1f95a.png?v8\",\n    \"eggplant\": \"unicode/1f346.png?v8\",\n    \"egypt\": \"unicode/1f1ea-1f1ec.png?v8\",\n    \"eight\": \"unicode/0038-20e3.png?v8\",\n    \"eight_pointed_black_star\": \"unicode/2734.png?v8\",\n    \"eight_spoked_asterisk\": \"unicode/2733.png?v8\",\n    \"eject_button\": \"unicode/23cf.png?v8\",\n    \"el_salvador\": \"unicode/1f1f8-1f1fb.png?v8\",\n    \"electric_plug\": \"unicode/1f50c.png?v8\",\n    \"electron\": \"electron.png?v8\",\n    \"elephant\": \"unicode/1f418.png?v8\",\n    \"elevator\": \"unicode/1f6d7.png?v8\",\n    \"elf\": \"unicode/1f9dd.png?v8\",\n    \"elf_man\": \"unicode/1f9dd-2642.png?v8\",\n    \"elf_woman\": \"unicode/1f9dd-2640.png?v8\",\n    \"email\": \"unicode/1f4e7.png?v8\",\n    \"empty_nest\": \"unicode/1fab9.png?v8\",\n    \"end\": \"unicode/1f51a.png?v8\",\n    \"england\": \"unicode/1f3f4-e0067-e0062-e0065-e006e-e0067-e007f.png?v8\",\n    \"envelope\": \"unicode/2709.png?v8\",\n    \"envelope_with_arrow\": \"unicode/1f4e9.png?v8\",\n    \"equatorial_guinea\": \"unicode/1f1ec-1f1f6.png?v8\",\n    \"eritrea\": \"unicode/1f1ea-1f1f7.png?v8\",\n    \"es\": \"unicode/1f1ea-1f1f8.png?v8\",\n    \"estonia\": \"unicode/1f1ea-1f1ea.png?v8\",\n    \"ethiopia\": \"unicode/1f1ea-1f1f9.png?v8\",\n    \"eu\": \"unicode/1f1ea-1f1fa.png?v8\",\n    \"euro\": \"unicode/1f4b6.png?v8\",\n    \"european_castle\": \"unicode/1f3f0.png?v8\",\n    \"european_post_office\": \"unicode/1f3e4.png?v8\",\n    \"european_union\": \"unicode/1f1ea-1f1fa.png?v8\",\n    \"evergreen_tree\": \"unicode/1f332.png?v8\",\n    \"exclamation\": \"unicode/2757.png?v8\",\n    \"exploding_head\": \"unicode/1f92f.png?v8\",\n    \"expressionless\": \"unicode/1f611.png?v8\",\n    \"eye\": \"unicode/1f441.png?v8\",\n    \"eye_speech_bubble\": \"unicode/1f441-1f5e8.png?v8\",\n    \"eyeglasses\": \"unicode/1f453.png?v8\",\n    \"eyes\": \"unicode/1f440.png?v8\",\n    \"face_exhaling\": \"unicode/1f62e-1f4a8.png?v8\",\n    \"face_holding_back_tears\": \"unicode/1f979.png?v8\",\n    \"face_in_clouds\": \"unicode/1f636-1f32b.png?v8\",\n    \"face_with_diagonal_mouth\": \"unicode/1fae4.png?v8\",\n    \"face_with_head_bandage\": \"unicode/1f915.png?v8\",\n    \"face_with_open_eyes_and_hand_over_mouth\": \"unicode/1fae2.png?v8\",\n    \"face_with_peeking_eye\": \"unicode/1fae3.png?v8\",\n    \"face_with_spiral_eyes\": \"unicode/1f635-1f4ab.png?v8\",\n    \"face_with_thermometer\": \"unicode/1f912.png?v8\",\n    \"facepalm\": \"unicode/1f926.png?v8\",\n    \"facepunch\": \"unicode/1f44a.png?v8\",\n    \"factory\": \"unicode/1f3ed.png?v8\",\n    \"factory_worker\": \"unicode/1f9d1-1f3ed.png?v8\",\n    \"fairy\": \"unicode/1f9da.png?v8\",\n    \"fairy_man\": \"unicode/1f9da-2642.png?v8\",\n    \"fairy_woman\": \"unicode/1f9da-2640.png?v8\",\n    \"falafel\": \"unicode/1f9c6.png?v8\",\n    \"falkland_islands\": \"unicode/1f1eb-1f1f0.png?v8\",\n    \"fallen_leaf\": \"unicode/1f342.png?v8\",\n    \"family\": \"unicode/1f46a.png?v8\",\n    \"family_man_boy\": \"unicode/1f468-1f466.png?v8\",\n    \"family_man_boy_boy\": \"unicode/1f468-1f466-1f466.png?v8\",\n    \"family_man_girl\": \"unicode/1f468-1f467.png?v8\",\n    \"family_man_girl_boy\": \"unicode/1f468-1f467-1f466.png?v8\",\n    \"family_man_girl_girl\": \"unicode/1f468-1f467-1f467.png?v8\",\n    \"family_man_man_boy\": \"unicode/1f468-1f468-1f466.png?v8\",\n    \"family_man_man_boy_boy\": \"unicode/1f468-1f468-1f466-1f466.png?v8\",\n    \"family_man_man_girl\": \"unicode/1f468-1f468-1f467.png?v8\",\n    \"family_man_man_girl_boy\": \"unicode/1f468-1f468-1f467-1f466.png?v8\",\n    \"family_man_man_girl_girl\": \"unicode/1f468-1f468-1f467-1f467.png?v8\",\n    \"family_man_woman_boy\": \"unicode/1f468-1f469-1f466.png?v8\",\n    \"family_man_woman_boy_boy\": \"unicode/1f468-1f469-1f466-1f466.png?v8\",\n    \"family_man_woman_girl\": \"unicode/1f468-1f469-1f467.png?v8\",\n    \"family_man_woman_girl_boy\": \"unicode/1f468-1f469-1f467-1f466.png?v8\",\n    \"family_man_woman_girl_girl\": \"unicode/1f468-1f469-1f467-1f467.png?v8\",\n    \"family_woman_boy\": \"unicode/1f469-1f466.png?v8\",\n    \"family_woman_boy_boy\": \"unicode/1f469-1f466-1f466.png?v8\",\n    \"family_woman_girl\": \"unicode/1f469-1f467.png?v8\",\n    \"family_woman_girl_boy\": \"unicode/1f469-1f467-1f466.png?v8\",\n    \"family_woman_girl_girl\": \"unicode/1f469-1f467-1f467.png?v8\",\n    \"family_woman_woman_boy\": \"unicode/1f469-1f469-1f466.png?v8\",\n    \"family_woman_woman_boy_boy\": \"unicode/1f469-1f469-1f466-1f466.png?v8\",\n    \"family_woman_woman_girl\": \"unicode/1f469-1f469-1f467.png?v8\",\n    \"family_woman_woman_girl_boy\": \"unicode/1f469-1f469-1f467-1f466.png?v8\",\n    \"family_woman_woman_girl_girl\": \"unicode/1f469-1f469-1f467-1f467.png?v8\",\n    \"farmer\": \"unicode/1f9d1-1f33e.png?v8\",\n    \"faroe_islands\": \"unicode/1f1eb-1f1f4.png?v8\",\n    \"fast_forward\": \"unicode/23e9.png?v8\",\n    \"fax\": \"unicode/1f4e0.png?v8\",\n    \"fearful\": \"unicode/1f628.png?v8\",\n    \"feather\": \"unicode/1fab6.png?v8\",\n    \"feelsgood\": \"feelsgood.png?v8\",\n    \"feet\": \"unicode/1f43e.png?v8\",\n    \"female_detective\": \"unicode/1f575-2640.png?v8\",\n    \"female_sign\": \"unicode/2640.png?v8\",\n    \"ferris_wheel\": \"unicode/1f3a1.png?v8\",\n    \"ferry\": \"unicode/26f4.png?v8\",\n    \"field_hockey\": \"unicode/1f3d1.png?v8\",\n    \"fiji\": \"unicode/1f1eb-1f1ef.png?v8\",\n    \"file_cabinet\": \"unicode/1f5c4.png?v8\",\n    \"file_folder\": \"unicode/1f4c1.png?v8\",\n    \"film_projector\": \"unicode/1f4fd.png?v8\",\n    \"film_strip\": \"unicode/1f39e.png?v8\",\n    \"finland\": \"unicode/1f1eb-1f1ee.png?v8\",\n    \"finnadie\": \"finnadie.png?v8\",\n    \"fire\": \"unicode/1f525.png?v8\",\n    \"fire_engine\": \"unicode/1f692.png?v8\",\n    \"fire_extinguisher\": \"unicode/1f9ef.png?v8\",\n    \"firecracker\": \"unicode/1f9e8.png?v8\",\n    \"firefighter\": \"unicode/1f9d1-1f692.png?v8\",\n    \"fireworks\": \"unicode/1f386.png?v8\",\n    \"first_quarter_moon\": \"unicode/1f313.png?v8\",\n    \"first_quarter_moon_with_face\": \"unicode/1f31b.png?v8\",\n    \"fish\": \"unicode/1f41f.png?v8\",\n    \"fish_cake\": \"unicode/1f365.png?v8\",\n    \"fishing_pole_and_fish\": \"unicode/1f3a3.png?v8\",\n    \"fishsticks\": \"fishsticks.png?v8\",\n    \"fist\": \"unicode/270a.png?v8\",\n    \"fist_left\": \"unicode/1f91b.png?v8\",\n    \"fist_oncoming\": \"unicode/1f44a.png?v8\",\n    \"fist_raised\": \"unicode/270a.png?v8\",\n    \"fist_right\": \"unicode/1f91c.png?v8\",\n    \"five\": \"unicode/0035-20e3.png?v8\",\n    \"flags\": \"unicode/1f38f.png?v8\",\n    \"flamingo\": \"unicode/1f9a9.png?v8\",\n    \"flashlight\": \"unicode/1f526.png?v8\",\n    \"flat_shoe\": \"unicode/1f97f.png?v8\",\n    \"flatbread\": \"unicode/1fad3.png?v8\",\n    \"fleur_de_lis\": \"unicode/269c.png?v8\",\n    \"flight_arrival\": \"unicode/1f6ec.png?v8\",\n    \"flight_departure\": \"unicode/1f6eb.png?v8\",\n    \"flipper\": \"unicode/1f42c.png?v8\",\n    \"floppy_disk\": \"unicode/1f4be.png?v8\",\n    \"flower_playing_cards\": \"unicode/1f3b4.png?v8\",\n    \"flushed\": \"unicode/1f633.png?v8\",\n    \"flute\": \"unicode/1fa88.png?v8\",\n    \"fly\": \"unicode/1fab0.png?v8\",\n    \"flying_disc\": \"unicode/1f94f.png?v8\",\n    \"flying_saucer\": \"unicode/1f6f8.png?v8\",\n    \"fog\": \"unicode/1f32b.png?v8\",\n    \"foggy\": \"unicode/1f301.png?v8\",\n    \"folding_hand_fan\": \"unicode/1faad.png?v8\",\n    \"fondue\": \"unicode/1fad5.png?v8\",\n    \"foot\": \"unicode/1f9b6.png?v8\",\n    \"football\": \"unicode/1f3c8.png?v8\",\n    \"footprints\": \"unicode/1f463.png?v8\",\n    \"fork_and_knife\": \"unicode/1f374.png?v8\",\n    \"fortune_cookie\": \"unicode/1f960.png?v8\",\n    \"fountain\": \"unicode/26f2.png?v8\",\n    \"fountain_pen\": \"unicode/1f58b.png?v8\",\n    \"four\": \"unicode/0034-20e3.png?v8\",\n    \"four_leaf_clover\": \"unicode/1f340.png?v8\",\n    \"fox_face\": \"unicode/1f98a.png?v8\",\n    \"fr\": \"unicode/1f1eb-1f1f7.png?v8\",\n    \"framed_picture\": \"unicode/1f5bc.png?v8\",\n    \"free\": \"unicode/1f193.png?v8\",\n    \"french_guiana\": \"unicode/1f1ec-1f1eb.png?v8\",\n    \"french_polynesia\": \"unicode/1f1f5-1f1eb.png?v8\",\n    \"french_southern_territories\": \"unicode/1f1f9-1f1eb.png?v8\",\n    \"fried_egg\": \"unicode/1f373.png?v8\",\n    \"fried_shrimp\": \"unicode/1f364.png?v8\",\n    \"fries\": \"unicode/1f35f.png?v8\",\n    \"frog\": \"unicode/1f438.png?v8\",\n    \"frowning\": \"unicode/1f626.png?v8\",\n    \"frowning_face\": \"unicode/2639.png?v8\",\n    \"frowning_man\": \"unicode/1f64d-2642.png?v8\",\n    \"frowning_person\": \"unicode/1f64d.png?v8\",\n    \"frowning_woman\": \"unicode/1f64d-2640.png?v8\",\n    \"fu\": \"unicode/1f595.png?v8\",\n    \"fuelpump\": \"unicode/26fd.png?v8\",\n    \"full_moon\": \"unicode/1f315.png?v8\",\n    \"full_moon_with_face\": \"unicode/1f31d.png?v8\",\n    \"funeral_urn\": \"unicode/26b1.png?v8\",\n    \"gabon\": \"unicode/1f1ec-1f1e6.png?v8\",\n    \"gambia\": \"unicode/1f1ec-1f1f2.png?v8\",\n    \"game_die\": \"unicode/1f3b2.png?v8\",\n    \"garlic\": \"unicode/1f9c4.png?v8\",\n    \"gb\": \"unicode/1f1ec-1f1e7.png?v8\",\n    \"gear\": \"unicode/2699.png?v8\",\n    \"gem\": \"unicode/1f48e.png?v8\",\n    \"gemini\": \"unicode/264a.png?v8\",\n    \"genie\": \"unicode/1f9de.png?v8\",\n    \"genie_man\": \"unicode/1f9de-2642.png?v8\",\n    \"genie_woman\": \"unicode/1f9de-2640.png?v8\",\n    \"georgia\": \"unicode/1f1ec-1f1ea.png?v8\",\n    \"ghana\": \"unicode/1f1ec-1f1ed.png?v8\",\n    \"ghost\": \"unicode/1f47b.png?v8\",\n    \"gibraltar\": \"unicode/1f1ec-1f1ee.png?v8\",\n    \"gift\": \"unicode/1f381.png?v8\",\n    \"gift_heart\": \"unicode/1f49d.png?v8\",\n    \"ginger_root\": \"unicode/1fada.png?v8\",\n    \"giraffe\": \"unicode/1f992.png?v8\",\n    \"girl\": \"unicode/1f467.png?v8\",\n    \"globe_with_meridians\": \"unicode/1f310.png?v8\",\n    \"gloves\": \"unicode/1f9e4.png?v8\",\n    \"goal_net\": \"unicode/1f945.png?v8\",\n    \"goat\": \"unicode/1f410.png?v8\",\n    \"goberserk\": \"goberserk.png?v8\",\n    \"godmode\": \"godmode.png?v8\",\n    \"goggles\": \"unicode/1f97d.png?v8\",\n    \"golf\": \"unicode/26f3.png?v8\",\n    \"golfing\": \"unicode/1f3cc.png?v8\",\n    \"golfing_man\": \"unicode/1f3cc-2642.png?v8\",\n    \"golfing_woman\": \"unicode/1f3cc-2640.png?v8\",\n    \"goose\": \"unicode/1fabf.png?v8\",\n    \"gorilla\": \"unicode/1f98d.png?v8\",\n    \"grapes\": \"unicode/1f347.png?v8\",\n    \"greece\": \"unicode/1f1ec-1f1f7.png?v8\",\n    \"green_apple\": \"unicode/1f34f.png?v8\",\n    \"green_book\": \"unicode/1f4d7.png?v8\",\n    \"green_circle\": \"unicode/1f7e2.png?v8\",\n    \"green_heart\": \"unicode/1f49a.png?v8\",\n    \"green_salad\": \"unicode/1f957.png?v8\",\n    \"green_square\": \"unicode/1f7e9.png?v8\",\n    \"greenland\": \"unicode/1f1ec-1f1f1.png?v8\",\n    \"grenada\": \"unicode/1f1ec-1f1e9.png?v8\",\n    \"grey_exclamation\": \"unicode/2755.png?v8\",\n    \"grey_heart\": \"unicode/1fa76.png?v8\",\n    \"grey_question\": \"unicode/2754.png?v8\",\n    \"grimacing\": \"unicode/1f62c.png?v8\",\n    \"grin\": \"unicode/1f601.png?v8\",\n    \"grinning\": \"unicode/1f600.png?v8\",\n    \"guadeloupe\": \"unicode/1f1ec-1f1f5.png?v8\",\n    \"guam\": \"unicode/1f1ec-1f1fa.png?v8\",\n    \"guard\": \"unicode/1f482.png?v8\",\n    \"guardsman\": \"unicode/1f482-2642.png?v8\",\n    \"guardswoman\": \"unicode/1f482-2640.png?v8\",\n    \"guatemala\": \"unicode/1f1ec-1f1f9.png?v8\",\n    \"guernsey\": \"unicode/1f1ec-1f1ec.png?v8\",\n    \"guide_dog\": \"unicode/1f9ae.png?v8\",\n    \"guinea\": \"unicode/1f1ec-1f1f3.png?v8\",\n    \"guinea_bissau\": \"unicode/1f1ec-1f1fc.png?v8\",\n    \"guitar\": \"unicode/1f3b8.png?v8\",\n    \"gun\": \"unicode/1f52b.png?v8\",\n    \"guyana\": \"unicode/1f1ec-1f1fe.png?v8\",\n    \"hair_pick\": \"unicode/1faae.png?v8\",\n    \"haircut\": \"unicode/1f487.png?v8\",\n    \"haircut_man\": \"unicode/1f487-2642.png?v8\",\n    \"haircut_woman\": \"unicode/1f487-2640.png?v8\",\n    \"haiti\": \"unicode/1f1ed-1f1f9.png?v8\",\n    \"hamburger\": \"unicode/1f354.png?v8\",\n    \"hammer\": \"unicode/1f528.png?v8\",\n    \"hammer_and_pick\": \"unicode/2692.png?v8\",\n    \"hammer_and_wrench\": \"unicode/1f6e0.png?v8\",\n    \"hamsa\": \"unicode/1faac.png?v8\",\n    \"hamster\": \"unicode/1f439.png?v8\",\n    \"hand\": \"unicode/270b.png?v8\",\n    \"hand_over_mouth\": \"unicode/1f92d.png?v8\",\n    \"hand_with_index_finger_and_thumb_crossed\": \"unicode/1faf0.png?v8\",\n    \"handbag\": \"unicode/1f45c.png?v8\",\n    \"handball_person\": \"unicode/1f93e.png?v8\",\n    \"handshake\": \"unicode/1f91d.png?v8\",\n    \"hankey\": \"unicode/1f4a9.png?v8\",\n    \"hash\": \"unicode/0023-20e3.png?v8\",\n    \"hatched_chick\": \"unicode/1f425.png?v8\",\n    \"hatching_chick\": \"unicode/1f423.png?v8\",\n    \"headphones\": \"unicode/1f3a7.png?v8\",\n    \"headstone\": \"unicode/1faa6.png?v8\",\n    \"health_worker\": \"unicode/1f9d1-2695.png?v8\",\n    \"hear_no_evil\": \"unicode/1f649.png?v8\",\n    \"heard_mcdonald_islands\": \"unicode/1f1ed-1f1f2.png?v8\",\n    \"heart\": \"unicode/2764.png?v8\",\n    \"heart_decoration\": \"unicode/1f49f.png?v8\",\n    \"heart_eyes\": \"unicode/1f60d.png?v8\",\n    \"heart_eyes_cat\": \"unicode/1f63b.png?v8\",\n    \"heart_hands\": \"unicode/1faf6.png?v8\",\n    \"heart_on_fire\": \"unicode/2764-1f525.png?v8\",\n    \"heartbeat\": \"unicode/1f493.png?v8\",\n    \"heartpulse\": \"unicode/1f497.png?v8\",\n    \"hearts\": \"unicode/2665.png?v8\",\n    \"heavy_check_mark\": \"unicode/2714.png?v8\",\n    \"heavy_division_sign\": \"unicode/2797.png?v8\",\n    \"heavy_dollar_sign\": \"unicode/1f4b2.png?v8\",\n    \"heavy_equals_sign\": \"unicode/1f7f0.png?v8\",\n    \"heavy_exclamation_mark\": \"unicode/2757.png?v8\",\n    \"heavy_heart_exclamation\": \"unicode/2763.png?v8\",\n    \"heavy_minus_sign\": \"unicode/2796.png?v8\",\n    \"heavy_multiplication_x\": \"unicode/2716.png?v8\",\n    \"heavy_plus_sign\": \"unicode/2795.png?v8\",\n    \"hedgehog\": \"unicode/1f994.png?v8\",\n    \"helicopter\": \"unicode/1f681.png?v8\",\n    \"herb\": \"unicode/1f33f.png?v8\",\n    \"hibiscus\": \"unicode/1f33a.png?v8\",\n    \"high_brightness\": \"unicode/1f506.png?v8\",\n    \"high_heel\": \"unicode/1f460.png?v8\",\n    \"hiking_boot\": \"unicode/1f97e.png?v8\",\n    \"hindu_temple\": \"unicode/1f6d5.png?v8\",\n    \"hippopotamus\": \"unicode/1f99b.png?v8\",\n    \"hocho\": \"unicode/1f52a.png?v8\",\n    \"hole\": \"unicode/1f573.png?v8\",\n    \"honduras\": \"unicode/1f1ed-1f1f3.png?v8\",\n    \"honey_pot\": \"unicode/1f36f.png?v8\",\n    \"honeybee\": \"unicode/1f41d.png?v8\",\n    \"hong_kong\": \"unicode/1f1ed-1f1f0.png?v8\",\n    \"hook\": \"unicode/1fa9d.png?v8\",\n    \"horse\": \"unicode/1f434.png?v8\",\n    \"horse_racing\": \"unicode/1f3c7.png?v8\",\n    \"hospital\": \"unicode/1f3e5.png?v8\",\n    \"hot_face\": \"unicode/1f975.png?v8\",\n    \"hot_pepper\": \"unicode/1f336.png?v8\",\n    \"hotdog\": \"unicode/1f32d.png?v8\",\n    \"hotel\": \"unicode/1f3e8.png?v8\",\n    \"hotsprings\": \"unicode/2668.png?v8\",\n    \"hourglass\": \"unicode/231b.png?v8\",\n    \"hourglass_flowing_sand\": \"unicode/23f3.png?v8\",\n    \"house\": \"unicode/1f3e0.png?v8\",\n    \"house_with_garden\": \"unicode/1f3e1.png?v8\",\n    \"houses\": \"unicode/1f3d8.png?v8\",\n    \"hugs\": \"unicode/1f917.png?v8\",\n    \"hungary\": \"unicode/1f1ed-1f1fa.png?v8\",\n    \"hurtrealbad\": \"hurtrealbad.png?v8\",\n    \"hushed\": \"unicode/1f62f.png?v8\",\n    \"hut\": \"unicode/1f6d6.png?v8\",\n    \"hyacinth\": \"unicode/1fabb.png?v8\",\n    \"ice_cream\": \"unicode/1f368.png?v8\",\n    \"ice_cube\": \"unicode/1f9ca.png?v8\",\n    \"ice_hockey\": \"unicode/1f3d2.png?v8\",\n    \"ice_skate\": \"unicode/26f8.png?v8\",\n    \"icecream\": \"unicode/1f366.png?v8\",\n    \"iceland\": \"unicode/1f1ee-1f1f8.png?v8\",\n    \"id\": \"unicode/1f194.png?v8\",\n    \"identification_card\": \"unicode/1faaa.png?v8\",\n    \"ideograph_advantage\": \"unicode/1f250.png?v8\",\n    \"imp\": \"unicode/1f47f.png?v8\",\n    \"inbox_tray\": \"unicode/1f4e5.png?v8\",\n    \"incoming_envelope\": \"unicode/1f4e8.png?v8\",\n    \"index_pointing_at_the_viewer\": \"unicode/1faf5.png?v8\",\n    \"india\": \"unicode/1f1ee-1f1f3.png?v8\",\n    \"indonesia\": \"unicode/1f1ee-1f1e9.png?v8\",\n    \"infinity\": \"unicode/267e.png?v8\",\n    \"information_desk_person\": \"unicode/1f481.png?v8\",\n    \"information_source\": \"unicode/2139.png?v8\",\n    \"innocent\": \"unicode/1f607.png?v8\",\n    \"interrobang\": \"unicode/2049.png?v8\",\n    \"iphone\": \"unicode/1f4f1.png?v8\",\n    \"iran\": \"unicode/1f1ee-1f1f7.png?v8\",\n    \"iraq\": \"unicode/1f1ee-1f1f6.png?v8\",\n    \"ireland\": \"unicode/1f1ee-1f1ea.png?v8\",\n    \"isle_of_man\": \"unicode/1f1ee-1f1f2.png?v8\",\n    \"israel\": \"unicode/1f1ee-1f1f1.png?v8\",\n    \"it\": \"unicode/1f1ee-1f1f9.png?v8\",\n    \"izakaya_lantern\": \"unicode/1f3ee.png?v8\",\n    \"jack_o_lantern\": \"unicode/1f383.png?v8\",\n    \"jamaica\": \"unicode/1f1ef-1f1f2.png?v8\",\n    \"japan\": \"unicode/1f5fe.png?v8\",\n    \"japanese_castle\": \"unicode/1f3ef.png?v8\",\n    \"japanese_goblin\": \"unicode/1f47a.png?v8\",\n    \"japanese_ogre\": \"unicode/1f479.png?v8\",\n    \"jar\": \"unicode/1fad9.png?v8\",\n    \"jeans\": \"unicode/1f456.png?v8\",\n    \"jellyfish\": \"unicode/1fabc.png?v8\",\n    \"jersey\": \"unicode/1f1ef-1f1ea.png?v8\",\n    \"jigsaw\": \"unicode/1f9e9.png?v8\",\n    \"jordan\": \"unicode/1f1ef-1f1f4.png?v8\",\n    \"joy\": \"unicode/1f602.png?v8\",\n    \"joy_cat\": \"unicode/1f639.png?v8\",\n    \"joystick\": \"unicode/1f579.png?v8\",\n    \"jp\": \"unicode/1f1ef-1f1f5.png?v8\",\n    \"judge\": \"unicode/1f9d1-2696.png?v8\",\n    \"juggling_person\": \"unicode/1f939.png?v8\",\n    \"kaaba\": \"unicode/1f54b.png?v8\",\n    \"kangaroo\": \"unicode/1f998.png?v8\",\n    \"kazakhstan\": \"unicode/1f1f0-1f1ff.png?v8\",\n    \"kenya\": \"unicode/1f1f0-1f1ea.png?v8\",\n    \"key\": \"unicode/1f511.png?v8\",\n    \"keyboard\": \"unicode/2328.png?v8\",\n    \"keycap_ten\": \"unicode/1f51f.png?v8\",\n    \"khanda\": \"unicode/1faaf.png?v8\",\n    \"kick_scooter\": \"unicode/1f6f4.png?v8\",\n    \"kimono\": \"unicode/1f458.png?v8\",\n    \"kiribati\": \"unicode/1f1f0-1f1ee.png?v8\",\n    \"kiss\": \"unicode/1f48b.png?v8\",\n    \"kissing\": \"unicode/1f617.png?v8\",\n    \"kissing_cat\": \"unicode/1f63d.png?v8\",\n    \"kissing_closed_eyes\": \"unicode/1f61a.png?v8\",\n    \"kissing_heart\": \"unicode/1f618.png?v8\",\n    \"kissing_smiling_eyes\": \"unicode/1f619.png?v8\",\n    \"kite\": \"unicode/1fa81.png?v8\",\n    \"kiwi_fruit\": \"unicode/1f95d.png?v8\",\n    \"kneeling_man\": \"unicode/1f9ce-2642.png?v8\",\n    \"kneeling_person\": \"unicode/1f9ce.png?v8\",\n    \"kneeling_woman\": \"unicode/1f9ce-2640.png?v8\",\n    \"knife\": \"unicode/1f52a.png?v8\",\n    \"knot\": \"unicode/1faa2.png?v8\",\n    \"koala\": \"unicode/1f428.png?v8\",\n    \"koko\": \"unicode/1f201.png?v8\",\n    \"kosovo\": \"unicode/1f1fd-1f1f0.png?v8\",\n    \"kr\": \"unicode/1f1f0-1f1f7.png?v8\",\n    \"kuwait\": \"unicode/1f1f0-1f1fc.png?v8\",\n    \"kyrgyzstan\": \"unicode/1f1f0-1f1ec.png?v8\",\n    \"lab_coat\": \"unicode/1f97c.png?v8\",\n    \"label\": \"unicode/1f3f7.png?v8\",\n    \"lacrosse\": \"unicode/1f94d.png?v8\",\n    \"ladder\": \"unicode/1fa9c.png?v8\",\n    \"lady_beetle\": \"unicode/1f41e.png?v8\",\n    \"lantern\": \"unicode/1f3ee.png?v8\",\n    \"laos\": \"unicode/1f1f1-1f1e6.png?v8\",\n    \"large_blue_circle\": \"unicode/1f535.png?v8\",\n    \"large_blue_diamond\": \"unicode/1f537.png?v8\",\n    \"large_orange_diamond\": \"unicode/1f536.png?v8\",\n    \"last_quarter_moon\": \"unicode/1f317.png?v8\",\n    \"last_quarter_moon_with_face\": \"unicode/1f31c.png?v8\",\n    \"latin_cross\": \"unicode/271d.png?v8\",\n    \"latvia\": \"unicode/1f1f1-1f1fb.png?v8\",\n    \"laughing\": \"unicode/1f606.png?v8\",\n    \"leafy_green\": \"unicode/1f96c.png?v8\",\n    \"leaves\": \"unicode/1f343.png?v8\",\n    \"lebanon\": \"unicode/1f1f1-1f1e7.png?v8\",\n    \"ledger\": \"unicode/1f4d2.png?v8\",\n    \"left_luggage\": \"unicode/1f6c5.png?v8\",\n    \"left_right_arrow\": \"unicode/2194.png?v8\",\n    \"left_speech_bubble\": \"unicode/1f5e8.png?v8\",\n    \"leftwards_arrow_with_hook\": \"unicode/21a9.png?v8\",\n    \"leftwards_hand\": \"unicode/1faf2.png?v8\",\n    \"leftwards_pushing_hand\": \"unicode/1faf7.png?v8\",\n    \"leg\": \"unicode/1f9b5.png?v8\",\n    \"lemon\": \"unicode/1f34b.png?v8\",\n    \"leo\": \"unicode/264c.png?v8\",\n    \"leopard\": \"unicode/1f406.png?v8\",\n    \"lesotho\": \"unicode/1f1f1-1f1f8.png?v8\",\n    \"level_slider\": \"unicode/1f39a.png?v8\",\n    \"liberia\": \"unicode/1f1f1-1f1f7.png?v8\",\n    \"libra\": \"unicode/264e.png?v8\",\n    \"libya\": \"unicode/1f1f1-1f1fe.png?v8\",\n    \"liechtenstein\": \"unicode/1f1f1-1f1ee.png?v8\",\n    \"light_blue_heart\": \"unicode/1fa75.png?v8\",\n    \"light_rail\": \"unicode/1f688.png?v8\",\n    \"link\": \"unicode/1f517.png?v8\",\n    \"lion\": \"unicode/1f981.png?v8\",\n    \"lips\": \"unicode/1f444.png?v8\",\n    \"lipstick\": \"unicode/1f484.png?v8\",\n    \"lithuania\": \"unicode/1f1f1-1f1f9.png?v8\",\n    \"lizard\": \"unicode/1f98e.png?v8\",\n    \"llama\": \"unicode/1f999.png?v8\",\n    \"lobster\": \"unicode/1f99e.png?v8\",\n    \"lock\": \"unicode/1f512.png?v8\",\n    \"lock_with_ink_pen\": \"unicode/1f50f.png?v8\",\n    \"lollipop\": \"unicode/1f36d.png?v8\",\n    \"long_drum\": \"unicode/1fa98.png?v8\",\n    \"loop\": \"unicode/27bf.png?v8\",\n    \"lotion_bottle\": \"unicode/1f9f4.png?v8\",\n    \"lotus\": \"unicode/1fab7.png?v8\",\n    \"lotus_position\": \"unicode/1f9d8.png?v8\",\n    \"lotus_position_man\": \"unicode/1f9d8-2642.png?v8\",\n    \"lotus_position_woman\": \"unicode/1f9d8-2640.png?v8\",\n    \"loud_sound\": \"unicode/1f50a.png?v8\",\n    \"loudspeaker\": \"unicode/1f4e2.png?v8\",\n    \"love_hotel\": \"unicode/1f3e9.png?v8\",\n    \"love_letter\": \"unicode/1f48c.png?v8\",\n    \"love_you_gesture\": \"unicode/1f91f.png?v8\",\n    \"low_battery\": \"unicode/1faab.png?v8\",\n    \"low_brightness\": \"unicode/1f505.png?v8\",\n    \"luggage\": \"unicode/1f9f3.png?v8\",\n    \"lungs\": \"unicode/1fac1.png?v8\",\n    \"luxembourg\": \"unicode/1f1f1-1f1fa.png?v8\",\n    \"lying_face\": \"unicode/1f925.png?v8\",\n    \"m\": \"unicode/24c2.png?v8\",\n    \"macau\": \"unicode/1f1f2-1f1f4.png?v8\",\n    \"macedonia\": \"unicode/1f1f2-1f1f0.png?v8\",\n    \"madagascar\": \"unicode/1f1f2-1f1ec.png?v8\",\n    \"mag\": \"unicode/1f50d.png?v8\",\n    \"mag_right\": \"unicode/1f50e.png?v8\",\n    \"mage\": \"unicode/1f9d9.png?v8\",\n    \"mage_man\": \"unicode/1f9d9-2642.png?v8\",\n    \"mage_woman\": \"unicode/1f9d9-2640.png?v8\",\n    \"magic_wand\": \"unicode/1fa84.png?v8\",\n    \"magnet\": \"unicode/1f9f2.png?v8\",\n    \"mahjong\": \"unicode/1f004.png?v8\",\n    \"mailbox\": \"unicode/1f4eb.png?v8\",\n    \"mailbox_closed\": \"unicode/1f4ea.png?v8\",\n    \"mailbox_with_mail\": \"unicode/1f4ec.png?v8\",\n    \"mailbox_with_no_mail\": \"unicode/1f4ed.png?v8\",\n    \"malawi\": \"unicode/1f1f2-1f1fc.png?v8\",\n    \"malaysia\": \"unicode/1f1f2-1f1fe.png?v8\",\n    \"maldives\": \"unicode/1f1f2-1f1fb.png?v8\",\n    \"male_detective\": \"unicode/1f575-2642.png?v8\",\n    \"male_sign\": \"unicode/2642.png?v8\",\n    \"mali\": \"unicode/1f1f2-1f1f1.png?v8\",\n    \"malta\": \"unicode/1f1f2-1f1f9.png?v8\",\n    \"mammoth\": \"unicode/1f9a3.png?v8\",\n    \"man\": \"unicode/1f468.png?v8\",\n    \"man_artist\": \"unicode/1f468-1f3a8.png?v8\",\n    \"man_astronaut\": \"unicode/1f468-1f680.png?v8\",\n    \"man_beard\": \"unicode/1f9d4-2642.png?v8\",\n    \"man_cartwheeling\": \"unicode/1f938-2642.png?v8\",\n    \"man_cook\": \"unicode/1f468-1f373.png?v8\",\n    \"man_dancing\": \"unicode/1f57a.png?v8\",\n    \"man_facepalming\": \"unicode/1f926-2642.png?v8\",\n    \"man_factory_worker\": \"unicode/1f468-1f3ed.png?v8\",\n    \"man_farmer\": \"unicode/1f468-1f33e.png?v8\",\n    \"man_feeding_baby\": \"unicode/1f468-1f37c.png?v8\",\n    \"man_firefighter\": \"unicode/1f468-1f692.png?v8\",\n    \"man_health_worker\": \"unicode/1f468-2695.png?v8\",\n    \"man_in_manual_wheelchair\": \"unicode/1f468-1f9bd.png?v8\",\n    \"man_in_motorized_wheelchair\": \"unicode/1f468-1f9bc.png?v8\",\n    \"man_in_tuxedo\": \"unicode/1f935-2642.png?v8\",\n    \"man_judge\": \"unicode/1f468-2696.png?v8\",\n    \"man_juggling\": \"unicode/1f939-2642.png?v8\",\n    \"man_mechanic\": \"unicode/1f468-1f527.png?v8\",\n    \"man_office_worker\": \"unicode/1f468-1f4bc.png?v8\",\n    \"man_pilot\": \"unicode/1f468-2708.png?v8\",\n    \"man_playing_handball\": \"unicode/1f93e-2642.png?v8\",\n    \"man_playing_water_polo\": \"unicode/1f93d-2642.png?v8\",\n    \"man_scientist\": \"unicode/1f468-1f52c.png?v8\",\n    \"man_shrugging\": \"unicode/1f937-2642.png?v8\",\n    \"man_singer\": \"unicode/1f468-1f3a4.png?v8\",\n    \"man_student\": \"unicode/1f468-1f393.png?v8\",\n    \"man_teacher\": \"unicode/1f468-1f3eb.png?v8\",\n    \"man_technologist\": \"unicode/1f468-1f4bb.png?v8\",\n    \"man_with_gua_pi_mao\": \"unicode/1f472.png?v8\",\n    \"man_with_probing_cane\": \"unicode/1f468-1f9af.png?v8\",\n    \"man_with_turban\": \"unicode/1f473-2642.png?v8\",\n    \"man_with_veil\": \"unicode/1f470-2642.png?v8\",\n    \"mandarin\": \"unicode/1f34a.png?v8\",\n    \"mango\": \"unicode/1f96d.png?v8\",\n    \"mans_shoe\": \"unicode/1f45e.png?v8\",\n    \"mantelpiece_clock\": \"unicode/1f570.png?v8\",\n    \"manual_wheelchair\": \"unicode/1f9bd.png?v8\",\n    \"maple_leaf\": \"unicode/1f341.png?v8\",\n    \"maracas\": \"unicode/1fa87.png?v8\",\n    \"marshall_islands\": \"unicode/1f1f2-1f1ed.png?v8\",\n    \"martial_arts_uniform\": \"unicode/1f94b.png?v8\",\n    \"martinique\": \"unicode/1f1f2-1f1f6.png?v8\",\n    \"mask\": \"unicode/1f637.png?v8\",\n    \"massage\": \"unicode/1f486.png?v8\",\n    \"massage_man\": \"unicode/1f486-2642.png?v8\",\n    \"massage_woman\": \"unicode/1f486-2640.png?v8\",\n    \"mate\": \"unicode/1f9c9.png?v8\",\n    \"mauritania\": \"unicode/1f1f2-1f1f7.png?v8\",\n    \"mauritius\": \"unicode/1f1f2-1f1fa.png?v8\",\n    \"mayotte\": \"unicode/1f1fe-1f1f9.png?v8\",\n    \"meat_on_bone\": \"unicode/1f356.png?v8\",\n    \"mechanic\": \"unicode/1f9d1-1f527.png?v8\",\n    \"mechanical_arm\": \"unicode/1f9be.png?v8\",\n    \"mechanical_leg\": \"unicode/1f9bf.png?v8\",\n    \"medal_military\": \"unicode/1f396.png?v8\",\n    \"medal_sports\": \"unicode/1f3c5.png?v8\",\n    \"medical_symbol\": \"unicode/2695.png?v8\",\n    \"mega\": \"unicode/1f4e3.png?v8\",\n    \"melon\": \"unicode/1f348.png?v8\",\n    \"melting_face\": \"unicode/1fae0.png?v8\",\n    \"memo\": \"unicode/1f4dd.png?v8\",\n    \"men_wrestling\": \"unicode/1f93c-2642.png?v8\",\n    \"mending_heart\": \"unicode/2764-1fa79.png?v8\",\n    \"menorah\": \"unicode/1f54e.png?v8\",\n    \"mens\": \"unicode/1f6b9.png?v8\",\n    \"mermaid\": \"unicode/1f9dc-2640.png?v8\",\n    \"merman\": \"unicode/1f9dc-2642.png?v8\",\n    \"merperson\": \"unicode/1f9dc.png?v8\",\n    \"metal\": \"unicode/1f918.png?v8\",\n    \"metro\": \"unicode/1f687.png?v8\",\n    \"mexico\": \"unicode/1f1f2-1f1fd.png?v8\",\n    \"microbe\": \"unicode/1f9a0.png?v8\",\n    \"micronesia\": \"unicode/1f1eb-1f1f2.png?v8\",\n    \"microphone\": \"unicode/1f3a4.png?v8\",\n    \"microscope\": \"unicode/1f52c.png?v8\",\n    \"middle_finger\": \"unicode/1f595.png?v8\",\n    \"military_helmet\": \"unicode/1fa96.png?v8\",\n    \"milk_glass\": \"unicode/1f95b.png?v8\",\n    \"milky_way\": \"unicode/1f30c.png?v8\",\n    \"minibus\": \"unicode/1f690.png?v8\",\n    \"minidisc\": \"unicode/1f4bd.png?v8\",\n    \"mirror\": \"unicode/1fa9e.png?v8\",\n    \"mirror_ball\": \"unicode/1faa9.png?v8\",\n    \"mobile_phone_off\": \"unicode/1f4f4.png?v8\",\n    \"moldova\": \"unicode/1f1f2-1f1e9.png?v8\",\n    \"monaco\": \"unicode/1f1f2-1f1e8.png?v8\",\n    \"money_mouth_face\": \"unicode/1f911.png?v8\",\n    \"money_with_wings\": \"unicode/1f4b8.png?v8\",\n    \"moneybag\": \"unicode/1f4b0.png?v8\",\n    \"mongolia\": \"unicode/1f1f2-1f1f3.png?v8\",\n    \"monkey\": \"unicode/1f412.png?v8\",\n    \"monkey_face\": \"unicode/1f435.png?v8\",\n    \"monocle_face\": \"unicode/1f9d0.png?v8\",\n    \"monorail\": \"unicode/1f69d.png?v8\",\n    \"montenegro\": \"unicode/1f1f2-1f1ea.png?v8\",\n    \"montserrat\": \"unicode/1f1f2-1f1f8.png?v8\",\n    \"moon\": \"unicode/1f314.png?v8\",\n    \"moon_cake\": \"unicode/1f96e.png?v8\",\n    \"moose\": \"unicode/1face.png?v8\",\n    \"morocco\": \"unicode/1f1f2-1f1e6.png?v8\",\n    \"mortar_board\": \"unicode/1f393.png?v8\",\n    \"mosque\": \"unicode/1f54c.png?v8\",\n    \"mosquito\": \"unicode/1f99f.png?v8\",\n    \"motor_boat\": \"unicode/1f6e5.png?v8\",\n    \"motor_scooter\": \"unicode/1f6f5.png?v8\",\n    \"motorcycle\": \"unicode/1f3cd.png?v8\",\n    \"motorized_wheelchair\": \"unicode/1f9bc.png?v8\",\n    \"motorway\": \"unicode/1f6e3.png?v8\",\n    \"mount_fuji\": \"unicode/1f5fb.png?v8\",\n    \"mountain\": \"unicode/26f0.png?v8\",\n    \"mountain_bicyclist\": \"unicode/1f6b5.png?v8\",\n    \"mountain_biking_man\": \"unicode/1f6b5-2642.png?v8\",\n    \"mountain_biking_woman\": \"unicode/1f6b5-2640.png?v8\",\n    \"mountain_cableway\": \"unicode/1f6a0.png?v8\",\n    \"mountain_railway\": \"unicode/1f69e.png?v8\",\n    \"mountain_snow\": \"unicode/1f3d4.png?v8\",\n    \"mouse\": \"unicode/1f42d.png?v8\",\n    \"mouse2\": \"unicode/1f401.png?v8\",\n    \"mouse_trap\": \"unicode/1faa4.png?v8\",\n    \"movie_camera\": \"unicode/1f3a5.png?v8\",\n    \"moyai\": \"unicode/1f5ff.png?v8\",\n    \"mozambique\": \"unicode/1f1f2-1f1ff.png?v8\",\n    \"mrs_claus\": \"unicode/1f936.png?v8\",\n    \"muscle\": \"unicode/1f4aa.png?v8\",\n    \"mushroom\": \"unicode/1f344.png?v8\",\n    \"musical_keyboard\": \"unicode/1f3b9.png?v8\",\n    \"musical_note\": \"unicode/1f3b5.png?v8\",\n    \"musical_score\": \"unicode/1f3bc.png?v8\",\n    \"mute\": \"unicode/1f507.png?v8\",\n    \"mx_claus\": \"unicode/1f9d1-1f384.png?v8\",\n    \"myanmar\": \"unicode/1f1f2-1f1f2.png?v8\",\n    \"nail_care\": \"unicode/1f485.png?v8\",\n    \"name_badge\": \"unicode/1f4db.png?v8\",\n    \"namibia\": \"unicode/1f1f3-1f1e6.png?v8\",\n    \"national_park\": \"unicode/1f3de.png?v8\",\n    \"nauru\": \"unicode/1f1f3-1f1f7.png?v8\",\n    \"nauseated_face\": \"unicode/1f922.png?v8\",\n    \"nazar_amulet\": \"unicode/1f9ff.png?v8\",\n    \"neckbeard\": \"neckbeard.png?v8\",\n    \"necktie\": \"unicode/1f454.png?v8\",\n    \"negative_squared_cross_mark\": \"unicode/274e.png?v8\",\n    \"nepal\": \"unicode/1f1f3-1f1f5.png?v8\",\n    \"nerd_face\": \"unicode/1f913.png?v8\",\n    \"nest_with_eggs\": \"unicode/1faba.png?v8\",\n    \"nesting_dolls\": \"unicode/1fa86.png?v8\",\n    \"netherlands\": \"unicode/1f1f3-1f1f1.png?v8\",\n    \"neutral_face\": \"unicode/1f610.png?v8\",\n    \"new\": \"unicode/1f195.png?v8\",\n    \"new_caledonia\": \"unicode/1f1f3-1f1e8.png?v8\",\n    \"new_moon\": \"unicode/1f311.png?v8\",\n    \"new_moon_with_face\": \"unicode/1f31a.png?v8\",\n    \"new_zealand\": \"unicode/1f1f3-1f1ff.png?v8\",\n    \"newspaper\": \"unicode/1f4f0.png?v8\",\n    \"newspaper_roll\": \"unicode/1f5de.png?v8\",\n    \"next_track_button\": \"unicode/23ed.png?v8\",\n    \"ng\": \"unicode/1f196.png?v8\",\n    \"ng_man\": \"unicode/1f645-2642.png?v8\",\n    \"ng_woman\": \"unicode/1f645-2640.png?v8\",\n    \"nicaragua\": \"unicode/1f1f3-1f1ee.png?v8\",\n    \"niger\": \"unicode/1f1f3-1f1ea.png?v8\",\n    \"nigeria\": \"unicode/1f1f3-1f1ec.png?v8\",\n    \"night_with_stars\": \"unicode/1f303.png?v8\",\n    \"nine\": \"unicode/0039-20e3.png?v8\",\n    \"ninja\": \"unicode/1f977.png?v8\",\n    \"niue\": \"unicode/1f1f3-1f1fa.png?v8\",\n    \"no_bell\": \"unicode/1f515.png?v8\",\n    \"no_bicycles\": \"unicode/1f6b3.png?v8\",\n    \"no_entry\": \"unicode/26d4.png?v8\",\n    \"no_entry_sign\": \"unicode/1f6ab.png?v8\",\n    \"no_good\": \"unicode/1f645.png?v8\",\n    \"no_good_man\": \"unicode/1f645-2642.png?v8\",\n    \"no_good_woman\": \"unicode/1f645-2640.png?v8\",\n    \"no_mobile_phones\": \"unicode/1f4f5.png?v8\",\n    \"no_mouth\": \"unicode/1f636.png?v8\",\n    \"no_pedestrians\": \"unicode/1f6b7.png?v8\",\n    \"no_smoking\": \"unicode/1f6ad.png?v8\",\n    \"non-potable_water\": \"unicode/1f6b1.png?v8\",\n    \"norfolk_island\": \"unicode/1f1f3-1f1eb.png?v8\",\n    \"north_korea\": \"unicode/1f1f0-1f1f5.png?v8\",\n    \"northern_mariana_islands\": \"unicode/1f1f2-1f1f5.png?v8\",\n    \"norway\": \"unicode/1f1f3-1f1f4.png?v8\",\n    \"nose\": \"unicode/1f443.png?v8\",\n    \"notebook\": \"unicode/1f4d3.png?v8\",\n    \"notebook_with_decorative_cover\": \"unicode/1f4d4.png?v8\",\n    \"notes\": \"unicode/1f3b6.png?v8\",\n    \"nut_and_bolt\": \"unicode/1f529.png?v8\",\n    \"o\": \"unicode/2b55.png?v8\",\n    \"o2\": \"unicode/1f17e.png?v8\",\n    \"ocean\": \"unicode/1f30a.png?v8\",\n    \"octocat\": \"octocat.png?v8\",\n    \"octopus\": \"unicode/1f419.png?v8\",\n    \"oden\": \"unicode/1f362.png?v8\",\n    \"office\": \"unicode/1f3e2.png?v8\",\n    \"office_worker\": \"unicode/1f9d1-1f4bc.png?v8\",\n    \"oil_drum\": \"unicode/1f6e2.png?v8\",\n    \"ok\": \"unicode/1f197.png?v8\",\n    \"ok_hand\": \"unicode/1f44c.png?v8\",\n    \"ok_man\": \"unicode/1f646-2642.png?v8\",\n    \"ok_person\": \"unicode/1f646.png?v8\",\n    \"ok_woman\": \"unicode/1f646-2640.png?v8\",\n    \"old_key\": \"unicode/1f5dd.png?v8\",\n    \"older_adult\": \"unicode/1f9d3.png?v8\",\n    \"older_man\": \"unicode/1f474.png?v8\",\n    \"older_woman\": \"unicode/1f475.png?v8\",\n    \"olive\": \"unicode/1fad2.png?v8\",\n    \"om\": \"unicode/1f549.png?v8\",\n    \"oman\": \"unicode/1f1f4-1f1f2.png?v8\",\n    \"on\": \"unicode/1f51b.png?v8\",\n    \"oncoming_automobile\": \"unicode/1f698.png?v8\",\n    \"oncoming_bus\": \"unicode/1f68d.png?v8\",\n    \"oncoming_police_car\": \"unicode/1f694.png?v8\",\n    \"oncoming_taxi\": \"unicode/1f696.png?v8\",\n    \"one\": \"unicode/0031-20e3.png?v8\",\n    \"one_piece_swimsuit\": \"unicode/1fa71.png?v8\",\n    \"onion\": \"unicode/1f9c5.png?v8\",\n    \"open_book\": \"unicode/1f4d6.png?v8\",\n    \"open_file_folder\": \"unicode/1f4c2.png?v8\",\n    \"open_hands\": \"unicode/1f450.png?v8\",\n    \"open_mouth\": \"unicode/1f62e.png?v8\",\n    \"open_umbrella\": \"unicode/2602.png?v8\",\n    \"ophiuchus\": \"unicode/26ce.png?v8\",\n    \"orange\": \"unicode/1f34a.png?v8\",\n    \"orange_book\": \"unicode/1f4d9.png?v8\",\n    \"orange_circle\": \"unicode/1f7e0.png?v8\",\n    \"orange_heart\": \"unicode/1f9e1.png?v8\",\n    \"orange_square\": \"unicode/1f7e7.png?v8\",\n    \"orangutan\": \"unicode/1f9a7.png?v8\",\n    \"orthodox_cross\": \"unicode/2626.png?v8\",\n    \"otter\": \"unicode/1f9a6.png?v8\",\n    \"outbox_tray\": \"unicode/1f4e4.png?v8\",\n    \"owl\": \"unicode/1f989.png?v8\",\n    \"ox\": \"unicode/1f402.png?v8\",\n    \"oyster\": \"unicode/1f9aa.png?v8\",\n    \"package\": \"unicode/1f4e6.png?v8\",\n    \"page_facing_up\": \"unicode/1f4c4.png?v8\",\n    \"page_with_curl\": \"unicode/1f4c3.png?v8\",\n    \"pager\": \"unicode/1f4df.png?v8\",\n    \"paintbrush\": \"unicode/1f58c.png?v8\",\n    \"pakistan\": \"unicode/1f1f5-1f1f0.png?v8\",\n    \"palau\": \"unicode/1f1f5-1f1fc.png?v8\",\n    \"palestinian_territories\": \"unicode/1f1f5-1f1f8.png?v8\",\n    \"palm_down_hand\": \"unicode/1faf3.png?v8\",\n    \"palm_tree\": \"unicode/1f334.png?v8\",\n    \"palm_up_hand\": \"unicode/1faf4.png?v8\",\n    \"palms_up_together\": \"unicode/1f932.png?v8\",\n    \"panama\": \"unicode/1f1f5-1f1e6.png?v8\",\n    \"pancakes\": \"unicode/1f95e.png?v8\",\n    \"panda_face\": \"unicode/1f43c.png?v8\",\n    \"paperclip\": \"unicode/1f4ce.png?v8\",\n    \"paperclips\": \"unicode/1f587.png?v8\",\n    \"papua_new_guinea\": \"unicode/1f1f5-1f1ec.png?v8\",\n    \"parachute\": \"unicode/1fa82.png?v8\",\n    \"paraguay\": \"unicode/1f1f5-1f1fe.png?v8\",\n    \"parasol_on_ground\": \"unicode/26f1.png?v8\",\n    \"parking\": \"unicode/1f17f.png?v8\",\n    \"parrot\": \"unicode/1f99c.png?v8\",\n    \"part_alternation_mark\": \"unicode/303d.png?v8\",\n    \"partly_sunny\": \"unicode/26c5.png?v8\",\n    \"partying_face\": \"unicode/1f973.png?v8\",\n    \"passenger_ship\": \"unicode/1f6f3.png?v8\",\n    \"passport_control\": \"unicode/1f6c2.png?v8\",\n    \"pause_button\": \"unicode/23f8.png?v8\",\n    \"paw_prints\": \"unicode/1f43e.png?v8\",\n    \"pea_pod\": \"unicode/1fadb.png?v8\",\n    \"peace_symbol\": \"unicode/262e.png?v8\",\n    \"peach\": \"unicode/1f351.png?v8\",\n    \"peacock\": \"unicode/1f99a.png?v8\",\n    \"peanuts\": \"unicode/1f95c.png?v8\",\n    \"pear\": \"unicode/1f350.png?v8\",\n    \"pen\": \"unicode/1f58a.png?v8\",\n    \"pencil\": \"unicode/1f4dd.png?v8\",\n    \"pencil2\": \"unicode/270f.png?v8\",\n    \"penguin\": \"unicode/1f427.png?v8\",\n    \"pensive\": \"unicode/1f614.png?v8\",\n    \"people_holding_hands\": \"unicode/1f9d1-1f91d-1f9d1.png?v8\",\n    \"people_hugging\": \"unicode/1fac2.png?v8\",\n    \"performing_arts\": \"unicode/1f3ad.png?v8\",\n    \"persevere\": \"unicode/1f623.png?v8\",\n    \"person_bald\": \"unicode/1f9d1-1f9b2.png?v8\",\n    \"person_curly_hair\": \"unicode/1f9d1-1f9b1.png?v8\",\n    \"person_feeding_baby\": \"unicode/1f9d1-1f37c.png?v8\",\n    \"person_fencing\": \"unicode/1f93a.png?v8\",\n    \"person_in_manual_wheelchair\": \"unicode/1f9d1-1f9bd.png?v8\",\n    \"person_in_motorized_wheelchair\": \"unicode/1f9d1-1f9bc.png?v8\",\n    \"person_in_tuxedo\": \"unicode/1f935.png?v8\",\n    \"person_red_hair\": \"unicode/1f9d1-1f9b0.png?v8\",\n    \"person_white_hair\": \"unicode/1f9d1-1f9b3.png?v8\",\n    \"person_with_crown\": \"unicode/1fac5.png?v8\",\n    \"person_with_probing_cane\": \"unicode/1f9d1-1f9af.png?v8\",\n    \"person_with_turban\": \"unicode/1f473.png?v8\",\n    \"person_with_veil\": \"unicode/1f470.png?v8\",\n    \"peru\": \"unicode/1f1f5-1f1ea.png?v8\",\n    \"petri_dish\": \"unicode/1f9eb.png?v8\",\n    \"philippines\": \"unicode/1f1f5-1f1ed.png?v8\",\n    \"phone\": \"unicode/260e.png?v8\",\n    \"pick\": \"unicode/26cf.png?v8\",\n    \"pickup_truck\": \"unicode/1f6fb.png?v8\",\n    \"pie\": \"unicode/1f967.png?v8\",\n    \"pig\": \"unicode/1f437.png?v8\",\n    \"pig2\": \"unicode/1f416.png?v8\",\n    \"pig_nose\": \"unicode/1f43d.png?v8\",\n    \"pill\": \"unicode/1f48a.png?v8\",\n    \"pilot\": \"unicode/1f9d1-2708.png?v8\",\n    \"pinata\": \"unicode/1fa85.png?v8\",\n    \"pinched_fingers\": \"unicode/1f90c.png?v8\",\n    \"pinching_hand\": \"unicode/1f90f.png?v8\",\n    \"pineapple\": \"unicode/1f34d.png?v8\",\n    \"ping_pong\": \"unicode/1f3d3.png?v8\",\n    \"pink_heart\": \"unicode/1fa77.png?v8\",\n    \"pirate_flag\": \"unicode/1f3f4-2620.png?v8\",\n    \"pisces\": \"unicode/2653.png?v8\",\n    \"pitcairn_islands\": \"unicode/1f1f5-1f1f3.png?v8\",\n    \"pizza\": \"unicode/1f355.png?v8\",\n    \"placard\": \"unicode/1faa7.png?v8\",\n    \"place_of_worship\": \"unicode/1f6d0.png?v8\",\n    \"plate_with_cutlery\": \"unicode/1f37d.png?v8\",\n    \"play_or_pause_button\": \"unicode/23ef.png?v8\",\n    \"playground_slide\": \"unicode/1f6dd.png?v8\",\n    \"pleading_face\": \"unicode/1f97a.png?v8\",\n    \"plunger\": \"unicode/1faa0.png?v8\",\n    \"point_down\": \"unicode/1f447.png?v8\",\n    \"point_left\": \"unicode/1f448.png?v8\",\n    \"point_right\": \"unicode/1f449.png?v8\",\n    \"point_up\": \"unicode/261d.png?v8\",\n    \"point_up_2\": \"unicode/1f446.png?v8\",\n    \"poland\": \"unicode/1f1f5-1f1f1.png?v8\",\n    \"polar_bear\": \"unicode/1f43b-2744.png?v8\",\n    \"police_car\": \"unicode/1f693.png?v8\",\n    \"police_officer\": \"unicode/1f46e.png?v8\",\n    \"policeman\": \"unicode/1f46e-2642.png?v8\",\n    \"policewoman\": \"unicode/1f46e-2640.png?v8\",\n    \"poodle\": \"unicode/1f429.png?v8\",\n    \"poop\": \"unicode/1f4a9.png?v8\",\n    \"popcorn\": \"unicode/1f37f.png?v8\",\n    \"portugal\": \"unicode/1f1f5-1f1f9.png?v8\",\n    \"post_office\": \"unicode/1f3e3.png?v8\",\n    \"postal_horn\": \"unicode/1f4ef.png?v8\",\n    \"postbox\": \"unicode/1f4ee.png?v8\",\n    \"potable_water\": \"unicode/1f6b0.png?v8\",\n    \"potato\": \"unicode/1f954.png?v8\",\n    \"potted_plant\": \"unicode/1fab4.png?v8\",\n    \"pouch\": \"unicode/1f45d.png?v8\",\n    \"poultry_leg\": \"unicode/1f357.png?v8\",\n    \"pound\": \"unicode/1f4b7.png?v8\",\n    \"pouring_liquid\": \"unicode/1fad7.png?v8\",\n    \"pout\": \"unicode/1f621.png?v8\",\n    \"pouting_cat\": \"unicode/1f63e.png?v8\",\n    \"pouting_face\": \"unicode/1f64e.png?v8\",\n    \"pouting_man\": \"unicode/1f64e-2642.png?v8\",\n    \"pouting_woman\": \"unicode/1f64e-2640.png?v8\",\n    \"pray\": \"unicode/1f64f.png?v8\",\n    \"prayer_beads\": \"unicode/1f4ff.png?v8\",\n    \"pregnant_man\": \"unicode/1fac3.png?v8\",\n    \"pregnant_person\": \"unicode/1fac4.png?v8\",\n    \"pregnant_woman\": \"unicode/1f930.png?v8\",\n    \"pretzel\": \"unicode/1f968.png?v8\",\n    \"previous_track_button\": \"unicode/23ee.png?v8\",\n    \"prince\": \"unicode/1f934.png?v8\",\n    \"princess\": \"unicode/1f478.png?v8\",\n    \"printer\": \"unicode/1f5a8.png?v8\",\n    \"probing_cane\": \"unicode/1f9af.png?v8\",\n    \"puerto_rico\": \"unicode/1f1f5-1f1f7.png?v8\",\n    \"punch\": \"unicode/1f44a.png?v8\",\n    \"purple_circle\": \"unicode/1f7e3.png?v8\",\n    \"purple_heart\": \"unicode/1f49c.png?v8\",\n    \"purple_square\": \"unicode/1f7ea.png?v8\",\n    \"purse\": \"unicode/1f45b.png?v8\",\n    \"pushpin\": \"unicode/1f4cc.png?v8\",\n    \"put_litter_in_its_place\": \"unicode/1f6ae.png?v8\",\n    \"qatar\": \"unicode/1f1f6-1f1e6.png?v8\",\n    \"question\": \"unicode/2753.png?v8\",\n    \"rabbit\": \"unicode/1f430.png?v8\",\n    \"rabbit2\": \"unicode/1f407.png?v8\",\n    \"raccoon\": \"unicode/1f99d.png?v8\",\n    \"racehorse\": \"unicode/1f40e.png?v8\",\n    \"racing_car\": \"unicode/1f3ce.png?v8\",\n    \"radio\": \"unicode/1f4fb.png?v8\",\n    \"radio_button\": \"unicode/1f518.png?v8\",\n    \"radioactive\": \"unicode/2622.png?v8\",\n    \"rage\": \"unicode/1f621.png?v8\",\n    \"rage1\": \"rage1.png?v8\",\n    \"rage2\": \"rage2.png?v8\",\n    \"rage3\": \"rage3.png?v8\",\n    \"rage4\": \"rage4.png?v8\",\n    \"railway_car\": \"unicode/1f683.png?v8\",\n    \"railway_track\": \"unicode/1f6e4.png?v8\",\n    \"rainbow\": \"unicode/1f308.png?v8\",\n    \"rainbow_flag\": \"unicode/1f3f3-1f308.png?v8\",\n    \"raised_back_of_hand\": \"unicode/1f91a.png?v8\",\n    \"raised_eyebrow\": \"unicode/1f928.png?v8\",\n    \"raised_hand\": \"unicode/270b.png?v8\",\n    \"raised_hand_with_fingers_splayed\": \"unicode/1f590.png?v8\",\n    \"raised_hands\": \"unicode/1f64c.png?v8\",\n    \"raising_hand\": \"unicode/1f64b.png?v8\",\n    \"raising_hand_man\": \"unicode/1f64b-2642.png?v8\",\n    \"raising_hand_woman\": \"unicode/1f64b-2640.png?v8\",\n    \"ram\": \"unicode/1f40f.png?v8\",\n    \"ramen\": \"unicode/1f35c.png?v8\",\n    \"rat\": \"unicode/1f400.png?v8\",\n    \"razor\": \"unicode/1fa92.png?v8\",\n    \"receipt\": \"unicode/1f9fe.png?v8\",\n    \"record_button\": \"unicode/23fa.png?v8\",\n    \"recycle\": \"unicode/267b.png?v8\",\n    \"red_car\": \"unicode/1f697.png?v8\",\n    \"red_circle\": \"unicode/1f534.png?v8\",\n    \"red_envelope\": \"unicode/1f9e7.png?v8\",\n    \"red_haired_man\": \"unicode/1f468-1f9b0.png?v8\",\n    \"red_haired_woman\": \"unicode/1f469-1f9b0.png?v8\",\n    \"red_square\": \"unicode/1f7e5.png?v8\",\n    \"registered\": \"unicode/00ae.png?v8\",\n    \"relaxed\": \"unicode/263a.png?v8\",\n    \"relieved\": \"unicode/1f60c.png?v8\",\n    \"reminder_ribbon\": \"unicode/1f397.png?v8\",\n    \"repeat\": \"unicode/1f501.png?v8\",\n    \"repeat_one\": \"unicode/1f502.png?v8\",\n    \"rescue_worker_helmet\": \"unicode/26d1.png?v8\",\n    \"restroom\": \"unicode/1f6bb.png?v8\",\n    \"reunion\": \"unicode/1f1f7-1f1ea.png?v8\",\n    \"revolving_hearts\": \"unicode/1f49e.png?v8\",\n    \"rewind\": \"unicode/23ea.png?v8\",\n    \"rhinoceros\": \"unicode/1f98f.png?v8\",\n    \"ribbon\": \"unicode/1f380.png?v8\",\n    \"rice\": \"unicode/1f35a.png?v8\",\n    \"rice_ball\": \"unicode/1f359.png?v8\",\n    \"rice_cracker\": \"unicode/1f358.png?v8\",\n    \"rice_scene\": \"unicode/1f391.png?v8\",\n    \"right_anger_bubble\": \"unicode/1f5ef.png?v8\",\n    \"rightwards_hand\": \"unicode/1faf1.png?v8\",\n    \"rightwards_pushing_hand\": \"unicode/1faf8.png?v8\",\n    \"ring\": \"unicode/1f48d.png?v8\",\n    \"ring_buoy\": \"unicode/1f6df.png?v8\",\n    \"ringed_planet\": \"unicode/1fa90.png?v8\",\n    \"robot\": \"unicode/1f916.png?v8\",\n    \"rock\": \"unicode/1faa8.png?v8\",\n    \"rocket\": \"unicode/1f680.png?v8\",\n    \"rofl\": \"unicode/1f923.png?v8\",\n    \"roll_eyes\": \"unicode/1f644.png?v8\",\n    \"roll_of_paper\": \"unicode/1f9fb.png?v8\",\n    \"roller_coaster\": \"unicode/1f3a2.png?v8\",\n    \"roller_skate\": \"unicode/1f6fc.png?v8\",\n    \"romania\": \"unicode/1f1f7-1f1f4.png?v8\",\n    \"rooster\": \"unicode/1f413.png?v8\",\n    \"rose\": \"unicode/1f339.png?v8\",\n    \"rosette\": \"unicode/1f3f5.png?v8\",\n    \"rotating_light\": \"unicode/1f6a8.png?v8\",\n    \"round_pushpin\": \"unicode/1f4cd.png?v8\",\n    \"rowboat\": \"unicode/1f6a3.png?v8\",\n    \"rowing_man\": \"unicode/1f6a3-2642.png?v8\",\n    \"rowing_woman\": \"unicode/1f6a3-2640.png?v8\",\n    \"ru\": \"unicode/1f1f7-1f1fa.png?v8\",\n    \"rugby_football\": \"unicode/1f3c9.png?v8\",\n    \"runner\": \"unicode/1f3c3.png?v8\",\n    \"running\": \"unicode/1f3c3.png?v8\",\n    \"running_man\": \"unicode/1f3c3-2642.png?v8\",\n    \"running_shirt_with_sash\": \"unicode/1f3bd.png?v8\",\n    \"running_woman\": \"unicode/1f3c3-2640.png?v8\",\n    \"rwanda\": \"unicode/1f1f7-1f1fc.png?v8\",\n    \"sa\": \"unicode/1f202.png?v8\",\n    \"safety_pin\": \"unicode/1f9f7.png?v8\",\n    \"safety_vest\": \"unicode/1f9ba.png?v8\",\n    \"sagittarius\": \"unicode/2650.png?v8\",\n    \"sailboat\": \"unicode/26f5.png?v8\",\n    \"sake\": \"unicode/1f376.png?v8\",\n    \"salt\": \"unicode/1f9c2.png?v8\",\n    \"saluting_face\": \"unicode/1fae1.png?v8\",\n    \"samoa\": \"unicode/1f1fc-1f1f8.png?v8\",\n    \"san_marino\": \"unicode/1f1f8-1f1f2.png?v8\",\n    \"sandal\": \"unicode/1f461.png?v8\",\n    \"sandwich\": \"unicode/1f96a.png?v8\",\n    \"santa\": \"unicode/1f385.png?v8\",\n    \"sao_tome_principe\": \"unicode/1f1f8-1f1f9.png?v8\",\n    \"sari\": \"unicode/1f97b.png?v8\",\n    \"sassy_man\": \"unicode/1f481-2642.png?v8\",\n    \"sassy_woman\": \"unicode/1f481-2640.png?v8\",\n    \"satellite\": \"unicode/1f4e1.png?v8\",\n    \"satisfied\": \"unicode/1f606.png?v8\",\n    \"saudi_arabia\": \"unicode/1f1f8-1f1e6.png?v8\",\n    \"sauna_man\": \"unicode/1f9d6-2642.png?v8\",\n    \"sauna_person\": \"unicode/1f9d6.png?v8\",\n    \"sauna_woman\": \"unicode/1f9d6-2640.png?v8\",\n    \"sauropod\": \"unicode/1f995.png?v8\",\n    \"saxophone\": \"unicode/1f3b7.png?v8\",\n    \"scarf\": \"unicode/1f9e3.png?v8\",\n    \"school\": \"unicode/1f3eb.png?v8\",\n    \"school_satchel\": \"unicode/1f392.png?v8\",\n    \"scientist\": \"unicode/1f9d1-1f52c.png?v8\",\n    \"scissors\": \"unicode/2702.png?v8\",\n    \"scorpion\": \"unicode/1f982.png?v8\",\n    \"scorpius\": \"unicode/264f.png?v8\",\n    \"scotland\": \"unicode/1f3f4-e0067-e0062-e0073-e0063-e0074-e007f.png?v8\",\n    \"scream\": \"unicode/1f631.png?v8\",\n    \"scream_cat\": \"unicode/1f640.png?v8\",\n    \"screwdriver\": \"unicode/1fa9b.png?v8\",\n    \"scroll\": \"unicode/1f4dc.png?v8\",\n    \"seal\": \"unicode/1f9ad.png?v8\",\n    \"seat\": \"unicode/1f4ba.png?v8\",\n    \"secret\": \"unicode/3299.png?v8\",\n    \"see_no_evil\": \"unicode/1f648.png?v8\",\n    \"seedling\": \"unicode/1f331.png?v8\",\n    \"selfie\": \"unicode/1f933.png?v8\",\n    \"senegal\": \"unicode/1f1f8-1f1f3.png?v8\",\n    \"serbia\": \"unicode/1f1f7-1f1f8.png?v8\",\n    \"service_dog\": \"unicode/1f415-1f9ba.png?v8\",\n    \"seven\": \"unicode/0037-20e3.png?v8\",\n    \"sewing_needle\": \"unicode/1faa1.png?v8\",\n    \"seychelles\": \"unicode/1f1f8-1f1e8.png?v8\",\n    \"shaking_face\": \"unicode/1fae8.png?v8\",\n    \"shallow_pan_of_food\": \"unicode/1f958.png?v8\",\n    \"shamrock\": \"unicode/2618.png?v8\",\n    \"shark\": \"unicode/1f988.png?v8\",\n    \"shaved_ice\": \"unicode/1f367.png?v8\",\n    \"sheep\": \"unicode/1f411.png?v8\",\n    \"shell\": \"unicode/1f41a.png?v8\",\n    \"shield\": \"unicode/1f6e1.png?v8\",\n    \"shinto_shrine\": \"unicode/26e9.png?v8\",\n    \"ship\": \"unicode/1f6a2.png?v8\",\n    \"shipit\": \"shipit.png?v8\",\n    \"shirt\": \"unicode/1f455.png?v8\",\n    \"shit\": \"unicode/1f4a9.png?v8\",\n    \"shoe\": \"unicode/1f45e.png?v8\",\n    \"shopping\": \"unicode/1f6cd.png?v8\",\n    \"shopping_cart\": \"unicode/1f6d2.png?v8\",\n    \"shorts\": \"unicode/1fa73.png?v8\",\n    \"shower\": \"unicode/1f6bf.png?v8\",\n    \"shrimp\": \"unicode/1f990.png?v8\",\n    \"shrug\": \"unicode/1f937.png?v8\",\n    \"shushing_face\": \"unicode/1f92b.png?v8\",\n    \"sierra_leone\": \"unicode/1f1f8-1f1f1.png?v8\",\n    \"signal_strength\": \"unicode/1f4f6.png?v8\",\n    \"singapore\": \"unicode/1f1f8-1f1ec.png?v8\",\n    \"singer\": \"unicode/1f9d1-1f3a4.png?v8\",\n    \"sint_maarten\": \"unicode/1f1f8-1f1fd.png?v8\",\n    \"six\": \"unicode/0036-20e3.png?v8\",\n    \"six_pointed_star\": \"unicode/1f52f.png?v8\",\n    \"skateboard\": \"unicode/1f6f9.png?v8\",\n    \"ski\": \"unicode/1f3bf.png?v8\",\n    \"skier\": \"unicode/26f7.png?v8\",\n    \"skull\": \"unicode/1f480.png?v8\",\n    \"skull_and_crossbones\": \"unicode/2620.png?v8\",\n    \"skunk\": \"unicode/1f9a8.png?v8\",\n    \"sled\": \"unicode/1f6f7.png?v8\",\n    \"sleeping\": \"unicode/1f634.png?v8\",\n    \"sleeping_bed\": \"unicode/1f6cc.png?v8\",\n    \"sleepy\": \"unicode/1f62a.png?v8\",\n    \"slightly_frowning_face\": \"unicode/1f641.png?v8\",\n    \"slightly_smiling_face\": \"unicode/1f642.png?v8\",\n    \"slot_machine\": \"unicode/1f3b0.png?v8\",\n    \"sloth\": \"unicode/1f9a5.png?v8\",\n    \"slovakia\": \"unicode/1f1f8-1f1f0.png?v8\",\n    \"slovenia\": \"unicode/1f1f8-1f1ee.png?v8\",\n    \"small_airplane\": \"unicode/1f6e9.png?v8\",\n    \"small_blue_diamond\": \"unicode/1f539.png?v8\",\n    \"small_orange_diamond\": \"unicode/1f538.png?v8\",\n    \"small_red_triangle\": \"unicode/1f53a.png?v8\",\n    \"small_red_triangle_down\": \"unicode/1f53b.png?v8\",\n    \"smile\": \"unicode/1f604.png?v8\",\n    \"smile_cat\": \"unicode/1f638.png?v8\",\n    \"smiley\": \"unicode/1f603.png?v8\",\n    \"smiley_cat\": \"unicode/1f63a.png?v8\",\n    \"smiling_face_with_tear\": \"unicode/1f972.png?v8\",\n    \"smiling_face_with_three_hearts\": \"unicode/1f970.png?v8\",\n    \"smiling_imp\": \"unicode/1f608.png?v8\",\n    \"smirk\": \"unicode/1f60f.png?v8\",\n    \"smirk_cat\": \"unicode/1f63c.png?v8\",\n    \"smoking\": \"unicode/1f6ac.png?v8\",\n    \"snail\": \"unicode/1f40c.png?v8\",\n    \"snake\": \"unicode/1f40d.png?v8\",\n    \"sneezing_face\": \"unicode/1f927.png?v8\",\n    \"snowboarder\": \"unicode/1f3c2.png?v8\",\n    \"snowflake\": \"unicode/2744.png?v8\",\n    \"snowman\": \"unicode/26c4.png?v8\",\n    \"snowman_with_snow\": \"unicode/2603.png?v8\",\n    \"soap\": \"unicode/1f9fc.png?v8\",\n    \"sob\": \"unicode/1f62d.png?v8\",\n    \"soccer\": \"unicode/26bd.png?v8\",\n    \"socks\": \"unicode/1f9e6.png?v8\",\n    \"softball\": \"unicode/1f94e.png?v8\",\n    \"solomon_islands\": \"unicode/1f1f8-1f1e7.png?v8\",\n    \"somalia\": \"unicode/1f1f8-1f1f4.png?v8\",\n    \"soon\": \"unicode/1f51c.png?v8\",\n    \"sos\": \"unicode/1f198.png?v8\",\n    \"sound\": \"unicode/1f509.png?v8\",\n    \"south_africa\": \"unicode/1f1ff-1f1e6.png?v8\",\n    \"south_georgia_south_sandwich_islands\": \"unicode/1f1ec-1f1f8.png?v8\",\n    \"south_sudan\": \"unicode/1f1f8-1f1f8.png?v8\",\n    \"space_invader\": \"unicode/1f47e.png?v8\",\n    \"spades\": \"unicode/2660.png?v8\",\n    \"spaghetti\": \"unicode/1f35d.png?v8\",\n    \"sparkle\": \"unicode/2747.png?v8\",\n    \"sparkler\": \"unicode/1f387.png?v8\",\n    \"sparkles\": \"unicode/2728.png?v8\",\n    \"sparkling_heart\": \"unicode/1f496.png?v8\",\n    \"speak_no_evil\": \"unicode/1f64a.png?v8\",\n    \"speaker\": \"unicode/1f508.png?v8\",\n    \"speaking_head\": \"unicode/1f5e3.png?v8\",\n    \"speech_balloon\": \"unicode/1f4ac.png?v8\",\n    \"speedboat\": \"unicode/1f6a4.png?v8\",\n    \"spider\": \"unicode/1f577.png?v8\",\n    \"spider_web\": \"unicode/1f578.png?v8\",\n    \"spiral_calendar\": \"unicode/1f5d3.png?v8\",\n    \"spiral_notepad\": \"unicode/1f5d2.png?v8\",\n    \"sponge\": \"unicode/1f9fd.png?v8\",\n    \"spoon\": \"unicode/1f944.png?v8\",\n    \"squid\": \"unicode/1f991.png?v8\",\n    \"sri_lanka\": \"unicode/1f1f1-1f1f0.png?v8\",\n    \"st_barthelemy\": \"unicode/1f1e7-1f1f1.png?v8\",\n    \"st_helena\": \"unicode/1f1f8-1f1ed.png?v8\",\n    \"st_kitts_nevis\": \"unicode/1f1f0-1f1f3.png?v8\",\n    \"st_lucia\": \"unicode/1f1f1-1f1e8.png?v8\",\n    \"st_martin\": \"unicode/1f1f2-1f1eb.png?v8\",\n    \"st_pierre_miquelon\": \"unicode/1f1f5-1f1f2.png?v8\",\n    \"st_vincent_grenadines\": \"unicode/1f1fb-1f1e8.png?v8\",\n    \"stadium\": \"unicode/1f3df.png?v8\",\n    \"standing_man\": \"unicode/1f9cd-2642.png?v8\",\n    \"standing_person\": \"unicode/1f9cd.png?v8\",\n    \"standing_woman\": \"unicode/1f9cd-2640.png?v8\",\n    \"star\": \"unicode/2b50.png?v8\",\n    \"star2\": \"unicode/1f31f.png?v8\",\n    \"star_and_crescent\": \"unicode/262a.png?v8\",\n    \"star_of_david\": \"unicode/2721.png?v8\",\n    \"star_struck\": \"unicode/1f929.png?v8\",\n    \"stars\": \"unicode/1f320.png?v8\",\n    \"station\": \"unicode/1f689.png?v8\",\n    \"statue_of_liberty\": \"unicode/1f5fd.png?v8\",\n    \"steam_locomotive\": \"unicode/1f682.png?v8\",\n    \"stethoscope\": \"unicode/1fa7a.png?v8\",\n    \"stew\": \"unicode/1f372.png?v8\",\n    \"stop_button\": \"unicode/23f9.png?v8\",\n    \"stop_sign\": \"unicode/1f6d1.png?v8\",\n    \"stopwatch\": \"unicode/23f1.png?v8\",\n    \"straight_ruler\": \"unicode/1f4cf.png?v8\",\n    \"strawberry\": \"unicode/1f353.png?v8\",\n    \"stuck_out_tongue\": \"unicode/1f61b.png?v8\",\n    \"stuck_out_tongue_closed_eyes\": \"unicode/1f61d.png?v8\",\n    \"stuck_out_tongue_winking_eye\": \"unicode/1f61c.png?v8\",\n    \"student\": \"unicode/1f9d1-1f393.png?v8\",\n    \"studio_microphone\": \"unicode/1f399.png?v8\",\n    \"stuffed_flatbread\": \"unicode/1f959.png?v8\",\n    \"sudan\": \"unicode/1f1f8-1f1e9.png?v8\",\n    \"sun_behind_large_cloud\": \"unicode/1f325.png?v8\",\n    \"sun_behind_rain_cloud\": \"unicode/1f326.png?v8\",\n    \"sun_behind_small_cloud\": \"unicode/1f324.png?v8\",\n    \"sun_with_face\": \"unicode/1f31e.png?v8\",\n    \"sunflower\": \"unicode/1f33b.png?v8\",\n    \"sunglasses\": \"unicode/1f60e.png?v8\",\n    \"sunny\": \"unicode/2600.png?v8\",\n    \"sunrise\": \"unicode/1f305.png?v8\",\n    \"sunrise_over_mountains\": \"unicode/1f304.png?v8\",\n    \"superhero\": \"unicode/1f9b8.png?v8\",\n    \"superhero_man\": \"unicode/1f9b8-2642.png?v8\",\n    \"superhero_woman\": \"unicode/1f9b8-2640.png?v8\",\n    \"supervillain\": \"unicode/1f9b9.png?v8\",\n    \"supervillain_man\": \"unicode/1f9b9-2642.png?v8\",\n    \"supervillain_woman\": \"unicode/1f9b9-2640.png?v8\",\n    \"surfer\": \"unicode/1f3c4.png?v8\",\n    \"surfing_man\": \"unicode/1f3c4-2642.png?v8\",\n    \"surfing_woman\": \"unicode/1f3c4-2640.png?v8\",\n    \"suriname\": \"unicode/1f1f8-1f1f7.png?v8\",\n    \"sushi\": \"unicode/1f363.png?v8\",\n    \"suspect\": \"suspect.png?v8\",\n    \"suspension_railway\": \"unicode/1f69f.png?v8\",\n    \"svalbard_jan_mayen\": \"unicode/1f1f8-1f1ef.png?v8\",\n    \"swan\": \"unicode/1f9a2.png?v8\",\n    \"swaziland\": \"unicode/1f1f8-1f1ff.png?v8\",\n    \"sweat\": \"unicode/1f613.png?v8\",\n    \"sweat_drops\": \"unicode/1f4a6.png?v8\",\n    \"sweat_smile\": \"unicode/1f605.png?v8\",\n    \"sweden\": \"unicode/1f1f8-1f1ea.png?v8\",\n    \"sweet_potato\": \"unicode/1f360.png?v8\",\n    \"swim_brief\": \"unicode/1fa72.png?v8\",\n    \"swimmer\": \"unicode/1f3ca.png?v8\",\n    \"swimming_man\": \"unicode/1f3ca-2642.png?v8\",\n    \"swimming_woman\": \"unicode/1f3ca-2640.png?v8\",\n    \"switzerland\": \"unicode/1f1e8-1f1ed.png?v8\",\n    \"symbols\": \"unicode/1f523.png?v8\",\n    \"synagogue\": \"unicode/1f54d.png?v8\",\n    \"syria\": \"unicode/1f1f8-1f1fe.png?v8\",\n    \"syringe\": \"unicode/1f489.png?v8\",\n    \"t-rex\": \"unicode/1f996.png?v8\",\n    \"taco\": \"unicode/1f32e.png?v8\",\n    \"tada\": \"unicode/1f389.png?v8\",\n    \"taiwan\": \"unicode/1f1f9-1f1fc.png?v8\",\n    \"tajikistan\": \"unicode/1f1f9-1f1ef.png?v8\",\n    \"takeout_box\": \"unicode/1f961.png?v8\",\n    \"tamale\": \"unicode/1fad4.png?v8\",\n    \"tanabata_tree\": \"unicode/1f38b.png?v8\",\n    \"tangerine\": \"unicode/1f34a.png?v8\",\n    \"tanzania\": \"unicode/1f1f9-1f1ff.png?v8\",\n    \"taurus\": \"unicode/2649.png?v8\",\n    \"taxi\": \"unicode/1f695.png?v8\",\n    \"tea\": \"unicode/1f375.png?v8\",\n    \"teacher\": \"unicode/1f9d1-1f3eb.png?v8\",\n    \"teapot\": \"unicode/1fad6.png?v8\",\n    \"technologist\": \"unicode/1f9d1-1f4bb.png?v8\",\n    \"teddy_bear\": \"unicode/1f9f8.png?v8\",\n    \"telephone\": \"unicode/260e.png?v8\",\n    \"telephone_receiver\": \"unicode/1f4de.png?v8\",\n    \"telescope\": \"unicode/1f52d.png?v8\",\n    \"tennis\": \"unicode/1f3be.png?v8\",\n    \"tent\": \"unicode/26fa.png?v8\",\n    \"test_tube\": \"unicode/1f9ea.png?v8\",\n    \"thailand\": \"unicode/1f1f9-1f1ed.png?v8\",\n    \"thermometer\": \"unicode/1f321.png?v8\",\n    \"thinking\": \"unicode/1f914.png?v8\",\n    \"thong_sandal\": \"unicode/1fa74.png?v8\",\n    \"thought_balloon\": \"unicode/1f4ad.png?v8\",\n    \"thread\": \"unicode/1f9f5.png?v8\",\n    \"three\": \"unicode/0033-20e3.png?v8\",\n    \"thumbsdown\": \"unicode/1f44e.png?v8\",\n    \"thumbsup\": \"unicode/1f44d.png?v8\",\n    \"ticket\": \"unicode/1f3ab.png?v8\",\n    \"tickets\": \"unicode/1f39f.png?v8\",\n    \"tiger\": \"unicode/1f42f.png?v8\",\n    \"tiger2\": \"unicode/1f405.png?v8\",\n    \"timer_clock\": \"unicode/23f2.png?v8\",\n    \"timor_leste\": \"unicode/1f1f9-1f1f1.png?v8\",\n    \"tipping_hand_man\": \"unicode/1f481-2642.png?v8\",\n    \"tipping_hand_person\": \"unicode/1f481.png?v8\",\n    \"tipping_hand_woman\": \"unicode/1f481-2640.png?v8\",\n    \"tired_face\": \"unicode/1f62b.png?v8\",\n    \"tm\": \"unicode/2122.png?v8\",\n    \"togo\": \"unicode/1f1f9-1f1ec.png?v8\",\n    \"toilet\": \"unicode/1f6bd.png?v8\",\n    \"tokelau\": \"unicode/1f1f9-1f1f0.png?v8\",\n    \"tokyo_tower\": \"unicode/1f5fc.png?v8\",\n    \"tomato\": \"unicode/1f345.png?v8\",\n    \"tonga\": \"unicode/1f1f9-1f1f4.png?v8\",\n    \"tongue\": \"unicode/1f445.png?v8\",\n    \"toolbox\": \"unicode/1f9f0.png?v8\",\n    \"tooth\": \"unicode/1f9b7.png?v8\",\n    \"toothbrush\": \"unicode/1faa5.png?v8\",\n    \"top\": \"unicode/1f51d.png?v8\",\n    \"tophat\": \"unicode/1f3a9.png?v8\",\n    \"tornado\": \"unicode/1f32a.png?v8\",\n    \"tr\": \"unicode/1f1f9-1f1f7.png?v8\",\n    \"trackball\": \"unicode/1f5b2.png?v8\",\n    \"tractor\": \"unicode/1f69c.png?v8\",\n    \"traffic_light\": \"unicode/1f6a5.png?v8\",\n    \"train\": \"unicode/1f68b.png?v8\",\n    \"train2\": \"unicode/1f686.png?v8\",\n    \"tram\": \"unicode/1f68a.png?v8\",\n    \"transgender_flag\": \"unicode/1f3f3-26a7.png?v8\",\n    \"transgender_symbol\": \"unicode/26a7.png?v8\",\n    \"triangular_flag_on_post\": \"unicode/1f6a9.png?v8\",\n    \"triangular_ruler\": \"unicode/1f4d0.png?v8\",\n    \"trident\": \"unicode/1f531.png?v8\",\n    \"trinidad_tobago\": \"unicode/1f1f9-1f1f9.png?v8\",\n    \"tristan_da_cunha\": \"unicode/1f1f9-1f1e6.png?v8\",\n    \"triumph\": \"unicode/1f624.png?v8\",\n    \"troll\": \"unicode/1f9cc.png?v8\",\n    \"trolleybus\": \"unicode/1f68e.png?v8\",\n    \"trollface\": \"trollface.png?v8\",\n    \"trophy\": \"unicode/1f3c6.png?v8\",\n    \"tropical_drink\": \"unicode/1f379.png?v8\",\n    \"tropical_fish\": \"unicode/1f420.png?v8\",\n    \"truck\": \"unicode/1f69a.png?v8\",\n    \"trumpet\": \"unicode/1f3ba.png?v8\",\n    \"tshirt\": \"unicode/1f455.png?v8\",\n    \"tulip\": \"unicode/1f337.png?v8\",\n    \"tumbler_glass\": \"unicode/1f943.png?v8\",\n    \"tunisia\": \"unicode/1f1f9-1f1f3.png?v8\",\n    \"turkey\": \"unicode/1f983.png?v8\",\n    \"turkmenistan\": \"unicode/1f1f9-1f1f2.png?v8\",\n    \"turks_caicos_islands\": \"unicode/1f1f9-1f1e8.png?v8\",\n    \"turtle\": \"unicode/1f422.png?v8\",\n    \"tuvalu\": \"unicode/1f1f9-1f1fb.png?v8\",\n    \"tv\": \"unicode/1f4fa.png?v8\",\n    \"twisted_rightwards_arrows\": \"unicode/1f500.png?v8\",\n    \"two\": \"unicode/0032-20e3.png?v8\",\n    \"two_hearts\": \"unicode/1f495.png?v8\",\n    \"two_men_holding_hands\": \"unicode/1f46c.png?v8\",\n    \"two_women_holding_hands\": \"unicode/1f46d.png?v8\",\n    \"u5272\": \"unicode/1f239.png?v8\",\n    \"u5408\": \"unicode/1f234.png?v8\",\n    \"u55b6\": \"unicode/1f23a.png?v8\",\n    \"u6307\": \"unicode/1f22f.png?v8\",\n    \"u6708\": \"unicode/1f237.png?v8\",\n    \"u6709\": \"unicode/1f236.png?v8\",\n    \"u6e80\": \"unicode/1f235.png?v8\",\n    \"u7121\": \"unicode/1f21a.png?v8\",\n    \"u7533\": \"unicode/1f238.png?v8\",\n    \"u7981\": \"unicode/1f232.png?v8\",\n    \"u7a7a\": \"unicode/1f233.png?v8\",\n    \"uganda\": \"unicode/1f1fa-1f1ec.png?v8\",\n    \"uk\": \"unicode/1f1ec-1f1e7.png?v8\",\n    \"ukraine\": \"unicode/1f1fa-1f1e6.png?v8\",\n    \"umbrella\": \"unicode/2614.png?v8\",\n    \"unamused\": \"unicode/1f612.png?v8\",\n    \"underage\": \"unicode/1f51e.png?v8\",\n    \"unicorn\": \"unicode/1f984.png?v8\",\n    \"united_arab_emirates\": \"unicode/1f1e6-1f1ea.png?v8\",\n    \"united_nations\": \"unicode/1f1fa-1f1f3.png?v8\",\n    \"unlock\": \"unicode/1f513.png?v8\",\n    \"up\": \"unicode/1f199.png?v8\",\n    \"upside_down_face\": \"unicode/1f643.png?v8\",\n    \"uruguay\": \"unicode/1f1fa-1f1fe.png?v8\",\n    \"us\": \"unicode/1f1fa-1f1f8.png?v8\",\n    \"us_outlying_islands\": \"unicode/1f1fa-1f1f2.png?v8\",\n    \"us_virgin_islands\": \"unicode/1f1fb-1f1ee.png?v8\",\n    \"uzbekistan\": \"unicode/1f1fa-1f1ff.png?v8\",\n    \"v\": \"unicode/270c.png?v8\",\n    \"vampire\": \"unicode/1f9db.png?v8\",\n    \"vampire_man\": \"unicode/1f9db-2642.png?v8\",\n    \"vampire_woman\": \"unicode/1f9db-2640.png?v8\",\n    \"vanuatu\": \"unicode/1f1fb-1f1fa.png?v8\",\n    \"vatican_city\": \"unicode/1f1fb-1f1e6.png?v8\",\n    \"venezuela\": \"unicode/1f1fb-1f1ea.png?v8\",\n    \"vertical_traffic_light\": \"unicode/1f6a6.png?v8\",\n    \"vhs\": \"unicode/1f4fc.png?v8\",\n    \"vibration_mode\": \"unicode/1f4f3.png?v8\",\n    \"video_camera\": \"unicode/1f4f9.png?v8\",\n    \"video_game\": \"unicode/1f3ae.png?v8\",\n    \"vietnam\": \"unicode/1f1fb-1f1f3.png?v8\",\n    \"violin\": \"unicode/1f3bb.png?v8\",\n    \"virgo\": \"unicode/264d.png?v8\",\n    \"volcano\": \"unicode/1f30b.png?v8\",\n    \"volleyball\": \"unicode/1f3d0.png?v8\",\n    \"vomiting_face\": \"unicode/1f92e.png?v8\",\n    \"vs\": \"unicode/1f19a.png?v8\",\n    \"vulcan_salute\": \"unicode/1f596.png?v8\",\n    \"waffle\": \"unicode/1f9c7.png?v8\",\n    \"wales\": \"unicode/1f3f4-e0067-e0062-e0077-e006c-e0073-e007f.png?v8\",\n    \"walking\": \"unicode/1f6b6.png?v8\",\n    \"walking_man\": \"unicode/1f6b6-2642.png?v8\",\n    \"walking_woman\": \"unicode/1f6b6-2640.png?v8\",\n    \"wallis_futuna\": \"unicode/1f1fc-1f1eb.png?v8\",\n    \"waning_crescent_moon\": \"unicode/1f318.png?v8\",\n    \"waning_gibbous_moon\": \"unicode/1f316.png?v8\",\n    \"warning\": \"unicode/26a0.png?v8\",\n    \"wastebasket\": \"unicode/1f5d1.png?v8\",\n    \"watch\": \"unicode/231a.png?v8\",\n    \"water_buffalo\": \"unicode/1f403.png?v8\",\n    \"water_polo\": \"unicode/1f93d.png?v8\",\n    \"watermelon\": \"unicode/1f349.png?v8\",\n    \"wave\": \"unicode/1f44b.png?v8\",\n    \"wavy_dash\": \"unicode/3030.png?v8\",\n    \"waxing_crescent_moon\": \"unicode/1f312.png?v8\",\n    \"waxing_gibbous_moon\": \"unicode/1f314.png?v8\",\n    \"wc\": \"unicode/1f6be.png?v8\",\n    \"weary\": \"unicode/1f629.png?v8\",\n    \"wedding\": \"unicode/1f492.png?v8\",\n    \"weight_lifting\": \"unicode/1f3cb.png?v8\",\n    \"weight_lifting_man\": \"unicode/1f3cb-2642.png?v8\",\n    \"weight_lifting_woman\": \"unicode/1f3cb-2640.png?v8\",\n    \"western_sahara\": \"unicode/1f1ea-1f1ed.png?v8\",\n    \"whale\": \"unicode/1f433.png?v8\",\n    \"whale2\": \"unicode/1f40b.png?v8\",\n    \"wheel\": \"unicode/1f6de.png?v8\",\n    \"wheel_of_dharma\": \"unicode/2638.png?v8\",\n    \"wheelchair\": \"unicode/267f.png?v8\",\n    \"white_check_mark\": \"unicode/2705.png?v8\",\n    \"white_circle\": \"unicode/26aa.png?v8\",\n    \"white_flag\": \"unicode/1f3f3.png?v8\",\n    \"white_flower\": \"unicode/1f4ae.png?v8\",\n    \"white_haired_man\": \"unicode/1f468-1f9b3.png?v8\",\n    \"white_haired_woman\": \"unicode/1f469-1f9b3.png?v8\",\n    \"white_heart\": \"unicode/1f90d.png?v8\",\n    \"white_large_square\": \"unicode/2b1c.png?v8\",\n    \"white_medium_small_square\": \"unicode/25fd.png?v8\",\n    \"white_medium_square\": \"unicode/25fb.png?v8\",\n    \"white_small_square\": \"unicode/25ab.png?v8\",\n    \"white_square_button\": \"unicode/1f533.png?v8\",\n    \"wilted_flower\": \"unicode/1f940.png?v8\",\n    \"wind_chime\": \"unicode/1f390.png?v8\",\n    \"wind_face\": \"unicode/1f32c.png?v8\",\n    \"window\": \"unicode/1fa9f.png?v8\",\n    \"wine_glass\": \"unicode/1f377.png?v8\",\n    \"wing\": \"unicode/1fabd.png?v8\",\n    \"wink\": \"unicode/1f609.png?v8\",\n    \"wireless\": \"unicode/1f6dc.png?v8\",\n    \"wolf\": \"unicode/1f43a.png?v8\",\n    \"woman\": \"unicode/1f469.png?v8\",\n    \"woman_artist\": \"unicode/1f469-1f3a8.png?v8\",\n    \"woman_astronaut\": \"unicode/1f469-1f680.png?v8\",\n    \"woman_beard\": \"unicode/1f9d4-2640.png?v8\",\n    \"woman_cartwheeling\": \"unicode/1f938-2640.png?v8\",\n    \"woman_cook\": \"unicode/1f469-1f373.png?v8\",\n    \"woman_dancing\": \"unicode/1f483.png?v8\",\n    \"woman_facepalming\": \"unicode/1f926-2640.png?v8\",\n    \"woman_factory_worker\": \"unicode/1f469-1f3ed.png?v8\",\n    \"woman_farmer\": \"unicode/1f469-1f33e.png?v8\",\n    \"woman_feeding_baby\": \"unicode/1f469-1f37c.png?v8\",\n    \"woman_firefighter\": \"unicode/1f469-1f692.png?v8\",\n    \"woman_health_worker\": \"unicode/1f469-2695.png?v8\",\n    \"woman_in_manual_wheelchair\": \"unicode/1f469-1f9bd.png?v8\",\n    \"woman_in_motorized_wheelchair\": \"unicode/1f469-1f9bc.png?v8\",\n    \"woman_in_tuxedo\": \"unicode/1f935-2640.png?v8\",\n    \"woman_judge\": \"unicode/1f469-2696.png?v8\",\n    \"woman_juggling\": \"unicode/1f939-2640.png?v8\",\n    \"woman_mechanic\": \"unicode/1f469-1f527.png?v8\",\n    \"woman_office_worker\": \"unicode/1f469-1f4bc.png?v8\",\n    \"woman_pilot\": \"unicode/1f469-2708.png?v8\",\n    \"woman_playing_handball\": \"unicode/1f93e-2640.png?v8\",\n    \"woman_playing_water_polo\": \"unicode/1f93d-2640.png?v8\",\n    \"woman_scientist\": \"unicode/1f469-1f52c.png?v8\",\n    \"woman_shrugging\": \"unicode/1f937-2640.png?v8\",\n    \"woman_singer\": \"unicode/1f469-1f3a4.png?v8\",\n    \"woman_student\": \"unicode/1f469-1f393.png?v8\",\n    \"woman_teacher\": \"unicode/1f469-1f3eb.png?v8\",\n    \"woman_technologist\": \"unicode/1f469-1f4bb.png?v8\",\n    \"woman_with_headscarf\": \"unicode/1f9d5.png?v8\",\n    \"woman_with_probing_cane\": \"unicode/1f469-1f9af.png?v8\",\n    \"woman_with_turban\": \"unicode/1f473-2640.png?v8\",\n    \"woman_with_veil\": \"unicode/1f470-2640.png?v8\",\n    \"womans_clothes\": \"unicode/1f45a.png?v8\",\n    \"womans_hat\": \"unicode/1f452.png?v8\",\n    \"women_wrestling\": \"unicode/1f93c-2640.png?v8\",\n    \"womens\": \"unicode/1f6ba.png?v8\",\n    \"wood\": \"unicode/1fab5.png?v8\",\n    \"woozy_face\": \"unicode/1f974.png?v8\",\n    \"world_map\": \"unicode/1f5fa.png?v8\",\n    \"worm\": \"unicode/1fab1.png?v8\",\n    \"worried\": \"unicode/1f61f.png?v8\",\n    \"wrench\": \"unicode/1f527.png?v8\",\n    \"wrestling\": \"unicode/1f93c.png?v8\",\n    \"writing_hand\": \"unicode/270d.png?v8\",\n    \"x\": \"unicode/274c.png?v8\",\n    \"x_ray\": \"unicode/1fa7b.png?v8\",\n    \"yarn\": \"unicode/1f9f6.png?v8\",\n    \"yawning_face\": \"unicode/1f971.png?v8\",\n    \"yellow_circle\": \"unicode/1f7e1.png?v8\",\n    \"yellow_heart\": \"unicode/1f49b.png?v8\",\n    \"yellow_square\": \"unicode/1f7e8.png?v8\",\n    \"yemen\": \"unicode/1f1fe-1f1ea.png?v8\",\n    \"yen\": \"unicode/1f4b4.png?v8\",\n    \"yin_yang\": \"unicode/262f.png?v8\",\n    \"yo_yo\": \"unicode/1fa80.png?v8\",\n    \"yum\": \"unicode/1f60b.png?v8\",\n    \"zambia\": \"unicode/1f1ff-1f1f2.png?v8\",\n    \"zany_face\": \"unicode/1f92a.png?v8\",\n    \"zap\": \"unicode/26a1.png?v8\",\n    \"zebra\": \"unicode/1f993.png?v8\",\n    \"zero\": \"unicode/0030-20e3.png?v8\",\n    \"zimbabwe\": \"unicode/1f1ff-1f1fc.png?v8\",\n    \"zipper_mouth_face\": \"unicode/1f910.png?v8\",\n    \"zombie\": \"unicode/1f9df.png?v8\",\n    \"zombie_man\": \"unicode/1f9df-2642.png?v8\",\n    \"zombie_woman\": \"unicode/1f9df-2640.png?v8\",\n    \"zzz\": \"unicode/1f4a4.png?v8\"\n  }\n}"
  },
  {
    "path": "src/core/render/emojify.js",
    "content": "import emojiData from './emoji-data.js';\n\nfunction replaceEmojiShorthand(m, $1, useNativeEmoji) {\n  const emojiMatch = emojiData.data[$1];\n\n  let result = m;\n\n  if (emojiMatch) {\n    if (useNativeEmoji && /unicode/.test(emojiMatch)) {\n      const emojiUnicode = emojiMatch\n        .replace('unicode/', '')\n        .replace(/\\.png.*/, '')\n        .split('-')\n        .map(u => `&#x${u};`)\n        // Separate multi-character emoji with zero width joiner sequence (ZWJ)\n        // Hat tip: https://about.gitlab.com/blog/2018/05/30/journey-in-native-unicode-emoji/#emoji-made-up-of-multiple-characters\n        .join('&zwj;')\n        .concat('&#xFE0E;');\n      result = /* html */ `<span class=\"emoji\">${emojiUnicode}</span>`;\n    } else {\n      result = /* html */ `<img src=\"${emojiData.baseURL}${emojiMatch}.png\" alt=\"${$1}\" class=\"emoji\" loading=\"lazy\">`;\n    }\n  }\n\n  return result;\n}\n\nexport function emojify(text, useNativeEmoji) {\n  return (\n    text\n      // Mark colons in tags\n      .replace(\n        /<(code|pre|script|template)[^>]*?>[\\s\\S]+?<\\/(code|pre|script|template)>/g,\n        m => m.replace(/:/g, '__colon__'),\n      )\n      // Mark colons in comments\n      .replace(/<!--[\\s\\S]+?-->/g, m => m.replace(/:/g, '__colon__'))\n      // Mark colons in URIs\n      .replace(/([a-z]{2,}:)?\\/\\/[^\\s'\">)]+/gi, m =>\n        m.replace(/:/g, '__colon__'),\n      )\n      // Replace emoji shorthand codes\n      .replace(/:([a-z0-9_\\-+]+?):/g, (m, $1) =>\n        replaceEmojiShorthand(m, $1, useNativeEmoji),\n      )\n      // Restore colons in tags and comments\n      .replace(/__colon__/g, ':')\n  );\n}\n"
  },
  {
    "path": "src/core/render/gen-tree.js",
    "content": "/**\n * Gen toc tree\n * @link https://github.com/killercup/grock/blob/5280ae63e16c5739e9233d9009bc235ed7d79a50/styles/solarized/assets/js/behavior.coffee#L54-L81\n * @param  {Array} toc List of TOC elements\n * @param  {Number} maxLevel Deep level\n * @return {Array} Headlines\n */\nexport function genTree(toc, maxLevel) {\n  const headlines = [];\n  const last = {};\n\n  toc.forEach(headline => {\n    const level = headline.depth || 1;\n    const len = level - 1;\n\n    if (level > maxLevel) {\n      return;\n    }\n\n    if (last[len]) {\n      last[len].children = [...(last[len].children || []), headline];\n    } else {\n      headlines.push(headline);\n    }\n\n    last[level] = headline;\n  });\n\n  return headlines;\n}\n"
  },
  {
    "path": "src/core/render/index.js",
    "content": "import tinydate from 'tinydate';\nimport * as dom from '../util/dom.js';\nimport { getPath, isAbsolutePath } from '../router/util.js';\nimport { isMobile } from '../util/env.js';\nimport { isPrimitive } from '../util/core.js';\nimport { Compiler } from './compiler.js';\nimport * as tpl from './tpl.js';\nimport { prerenderEmbed } from './embed.js';\n\n/** @typedef {import('../Docsify.js').Constructor} Constructor */\n\n// TODO replace with Vue types if available\n/** @typedef {{ _isVue?: boolean, $destroy?: () => void }} Vue2Instance */\n/** @typedef {{ __vue__?: Vue2Instance }} WithVue2 */\n/** @typedef {{ __v_skip?: boolean }} VNode3 */\n/** @typedef {{ _vnode?: VNode3, __vue_app__?: { unmount: () => void } }} WithVue3 */\n/** @typedef {Element & WithVue2 & WithVue3} VueMountElement */\n\n/**\n * @template {!Constructor} T\n * @param {T} Base - The class to extend\n */\nexport function Render(Base) {\n  return class Render extends Base {\n    /** @type {Compiler | undefined} */\n    compiler;\n    #vueGlobalData;\n\n    #addTextAsTitleAttribute(cssSelector) {\n      dom.findAll(cssSelector).forEach(elm => {\n        const e = /** @type {HTMLElement} */ (elm);\n        if (!e.title && e.innerText) {\n          e.title = e.innerText;\n        }\n      });\n    }\n\n    #executeScript() {\n      const script = dom\n        .findAll('.markdown-section>script')\n        .filter(\n          s => !/template/.test(/** @type {HTMLScriptElement} */ (s).type),\n        )[0];\n      if (!script) {\n        return false;\n      }\n\n      const code = /** @type {HTMLElement} */ (script).innerText.trim();\n      if (!code) {\n        return false;\n      }\n\n      new Function(code)();\n    }\n\n    #formatUpdated(html, updated, fn) {\n      updated =\n        typeof fn === 'function'\n          ? fn(updated)\n          : typeof fn === 'string'\n            ? tinydate(fn)(new Date(updated))\n            : updated;\n\n      return html.replace(/{docsify-updated}/g, updated);\n    }\n\n    #renderMain(html) {\n      const docsifyConfig = this.config;\n      const markdownElm = dom.find('.markdown-section');\n      const vueVersion =\n        'Vue' in window &&\n        window.Vue.version &&\n        Number(window.Vue.version.charAt(0));\n\n      /**\n       * @param {VueMountElement} elm\n       */\n      const isMountedVue = elm => {\n        const isVue2 = Boolean(elm.__vue__ && elm.__vue__._isVue);\n        const isVue3 = Boolean(elm._vnode && elm._vnode.__v_skip);\n\n        return isVue2 || isVue3;\n      };\n\n      if ('Vue' in window) {\n        const mountedElms = dom\n          .findAll('.markdown-section > *')\n          .filter(elm => isMountedVue(elm));\n\n        // Destroy/unmount existing Vue instances\n        for (const mountedElm of mountedElms) {\n          if (vueVersion === 2) {\n            /** @type {VueMountElement} */ (mountedElm).__vue__?.$destroy?.();\n          } else if (vueVersion === 3) {\n            /** @type {VueMountElement} */ (mountedElm).__vue_app__?.unmount();\n          }\n        }\n      }\n\n      dom.setHTML(markdownElm, html);\n\n      // Execute markdown <script>\n      if (\n        docsifyConfig.executeScript ||\n        ('Vue' in window && docsifyConfig.executeScript !== false)\n      ) {\n        this.#executeScript();\n      }\n\n      // Handle Vue content not mounted by markdown <script>\n      if ('Vue' in window) {\n        const vueGlobalOptions = docsifyConfig.vueGlobalOptions || {};\n        const vueMountData = [];\n        const vueComponentNames = Object.keys(\n          docsifyConfig.vueComponents || {},\n        );\n\n        // Register global vueComponents\n        if (vueVersion === 2 && vueComponentNames.length) {\n          vueComponentNames.forEach(name => {\n            const isNotRegistered = !window.Vue.options.components[name];\n\n            if (isNotRegistered) {\n              window.Vue.component(name, docsifyConfig.vueComponents[name]);\n            }\n          });\n        }\n\n        // Store global data() return value as shared data object\n        if (\n          !this.#vueGlobalData &&\n          vueGlobalOptions.data &&\n          typeof vueGlobalOptions.data === 'function'\n        ) {\n          this.#vueGlobalData = vueGlobalOptions.data();\n        }\n\n        // vueMounts\n        vueMountData.push(\n          ...Object.keys(docsifyConfig.vueMounts || {})\n            .map(cssSelector => [\n              dom.find(markdownElm, cssSelector),\n              docsifyConfig.vueMounts[cssSelector],\n            ])\n            .filter(([elm, vueConfig]) => elm),\n        );\n\n        // Template syntax, vueComponents, vueGlobalOptions ...\n        const reHasBraces = /{{2}[^{}]*}{2}/;\n        // Matches Vue full and shorthand syntax as attributes in HTML tags.\n        //\n        // Full syntax examples:\n        // v-foo, v-foo[bar], v-foo-bar, v-foo:bar-baz.prop\n        //\n        // Shorthand syntax examples:\n        // @foo, @foo.bar, @foo.bar.baz, @[foo], :foo, :[foo]\n        //\n        // Markup examples:\n        // <div v-html>{{ html }}</div>\n        // <div v-text=\"msg\"></div>\n        // <div v-bind:text-content.prop=\"text\">\n        // <button v-on:click=\"doThis\"></button>\n        // <button v-on:click.once=\"doThis\"></button>\n        // <button v-on:[event]=\"doThis\"></button>\n        // <button @click.stop.prevent=\"doThis\">\n        // <a :href=\"url\">\n        // <a :[key]=\"url\">\n        const reHasDirective = /<[^>/]+\\s([@:]|v-)[\\w-:.[\\]]+[=>\\s]/;\n\n        vueMountData.push(\n          ...dom\n            .findAll('.markdown-section > *')\n            // Remove duplicates\n            .filter(elm => !vueMountData.some(([e, c]) => e === elm))\n            // Detect Vue content\n            .filter(elm => {\n              const selector = vueComponentNames.join(',');\n              const hasComponents = selector\n                ? Boolean(elm.querySelector(selector))\n                : false;\n              const isVueMount =\n                // is a component\n                elm.tagName.toLowerCase() in\n                  (docsifyConfig.vueComponents || {}) ||\n                // has a component(s)\n                hasComponents ||\n                // has curly braces\n                reHasBraces.test(elm.outerHTML) ||\n                // has content directive\n                reHasDirective.test(elm.outerHTML);\n\n              return isVueMount;\n            })\n            .map(elm => {\n              // Clone global configuration\n              const vueConfig = {\n                ...vueGlobalOptions,\n              };\n              // Replace vueGlobalOptions data() return value with shared data object.\n              // This provides a global store for all Vue instances that receive\n              // vueGlobalOptions as their configuration.\n              if (this.#vueGlobalData) {\n                vueConfig.data = () => this.#vueGlobalData;\n              }\n\n              return [elm, vueConfig];\n            }),\n        );\n\n        // Not found mounts but import Vue resource\n        if (vueMountData.length === 0) {\n          return;\n        }\n\n        // Mount\n        for (const [mountElm, vueConfig] of vueMountData) {\n          const isVueAttr = 'data-isvue';\n          const isSkipElm =\n            // Is an invalid tag\n            mountElm.matches('pre, :not([v-template]):has(pre), script') ||\n            // Is a mounted instance\n            isMountedVue(mountElm) ||\n            // Has mounted instance(s)\n            mountElm.querySelector(`[${isVueAttr}]`);\n\n          if (!isSkipElm) {\n            mountElm.setAttribute(isVueAttr, '');\n\n            if (vueVersion === 2) {\n              vueConfig.el = undefined;\n              new window.Vue(vueConfig).$mount(mountElm);\n            } else if (vueVersion === 3) {\n              const app = window.Vue.createApp(vueConfig);\n\n              // Register global vueComponents\n              vueComponentNames.forEach(name => {\n                const config = docsifyConfig.vueComponents[name];\n\n                app.component(name, config);\n              });\n\n              app.mount(mountElm);\n            }\n          }\n        }\n      }\n    }\n\n    #renderNameLink(vm) {\n      const el = dom.getNode('.app-name-link');\n      const nameLink = vm.config.nameLink;\n      const path = vm.route.path;\n\n      if (!el) {\n        return;\n      }\n\n      if (isPrimitive(vm.config.nameLink)) {\n        el.setAttribute('href', nameLink);\n      } else if (typeof nameLink === 'object') {\n        const match = Object.keys(nameLink).filter(\n          key => path.indexOf(key) > -1,\n        )[0];\n\n        el.setAttribute('href', nameLink[match]);\n      }\n    }\n\n    #renderSkipLink(vm) {\n      const { skipLink } = vm.config;\n\n      if (skipLink !== false) {\n        const el = dom.getNode('#skip-to-content');\n\n        let skipLinkText =\n          typeof skipLink === 'string' ? skipLink : 'Skip to main content';\n\n        if (skipLink?.constructor === Object) {\n          const matchingPath = Object.keys(skipLink).find(path =>\n            vm.route.path.startsWith(path.startsWith('/') ? path : `/${path}`),\n          );\n          const matchingText = matchingPath && skipLink[matchingPath];\n\n          skipLinkText = matchingText || skipLinkText;\n        }\n\n        if (el) {\n          el.innerHTML = skipLinkText;\n        } else {\n          const html = `<button type=\"button\" id=\"skip-to-content\" class=\"primary\">${skipLinkText}</button>`;\n          dom.body.insertAdjacentHTML('afterbegin', html);\n        }\n      }\n    }\n\n    _renderSidebar(text) {\n      const { maxLevel, subMaxLevel, loadSidebar, hideSidebar } = this.config;\n      const sidebarEl = dom.getNode('aside.sidebar');\n      const sidebarNavEl = dom.getNode('.sidebar-nav');\n      const sidebarToggleEl = dom.getNode('button.sidebar-toggle');\n\n      if (hideSidebar) {\n        sidebarEl?.remove();\n        sidebarToggleEl?.remove();\n\n        return null;\n      }\n\n      if (!this.compiler) {\n        throw new Error('Compiler is not initialized');\n      }\n\n      dom.setHTML('.sidebar-nav', this.compiler.sidebar(text, maxLevel));\n\n      sidebarToggleEl.setAttribute('aria-expanded', String(!isMobile()));\n\n      const activeElmHref = this.router.toURL(this.route.path);\n      const activeEl = /** @type {HTMLElement | null} */ (\n        dom.find(`.sidebar-nav a[href=\"${activeElmHref}\"]`)\n      );\n\n      this.#addTextAsTitleAttribute('.sidebar-nav a');\n\n      if (loadSidebar && activeEl) {\n        const parent = /** @type {HTMLElement} */ (activeEl.parentElement);\n        parent.innerHTML += this.compiler.subSidebar(subMaxLevel) || '';\n      } else {\n        this.compiler.resetToc();\n      }\n\n      // Bind event\n      this._bindEventOnRendered(activeEl);\n\n      // Mark page links and groups\n      const pageLinks = dom.findAll(\n        sidebarNavEl,\n        'a:is(li > a, li > p > a):not(.section-link, [target=\"_blank\"])',\n      );\n      const pageLinkGroups = dom\n        // NOTE: Using filter() method as a replacement for :has() selector. It\n        // would be preferable to use only 'li:not(:has(> a, > p > a))' selector\n        // but the :has() selector is not supported by our Jest test environment\n        // See: https://github.com/jsdom/jsdom/issues/3506#issuecomment-1769782333\n        .findAll(sidebarEl, 'li')\n        .filter(\n          elm =>\n            elm.querySelector(':scope > ul') &&\n            !elm.querySelectorAll(':scope > a, :scope > p > a').length,\n        );\n\n      pageLinks.forEach(elm => {\n        elm.classList.add('page-link');\n      });\n\n      pageLinkGroups.forEach(elm => {\n        elm.classList.add('group');\n        elm\n          .querySelector(':scope > p:not(:has(> *))')\n          ?.classList.add('group-title');\n      });\n    }\n\n    /**\n     * @param {HTMLElement | null} activeEl\n     */\n    _bindEventOnRendered(activeEl) {\n      const { autoHeader } = this.config;\n\n      this.onRender();\n\n      if (autoHeader && activeEl) {\n        const main = dom.getNode('#main');\n        const hasH1 = main.querySelector('h1');\n\n        if (!hasH1) {\n          const h1HTML = /** @type {Compiler} */ (this.compiler).header(\n            activeEl.innerText,\n            1,\n          );\n          const h1Node = dom.create('div', h1HTML).firstElementChild;\n\n          if (h1Node) {\n            dom.before(main, h1Node);\n          }\n        }\n      }\n    }\n\n    _renderNav(text) {\n      if (!text) {\n        return;\n      }\n\n      const html = /** @type {Compiler} */ (this.compiler).compile(text);\n\n      ['.app-nav', '.app-nav-merged'].forEach(selector => {\n        dom.setHTML(selector, html);\n        this.#addTextAsTitleAttribute(`${selector} a`);\n      });\n    }\n\n    _renderMain(text, opt = {}, next) {\n      const { response } = this.route;\n\n      // Note: It is possible for the response to be undefined in environments\n      // where XMLHttpRequest has been modified or mocked\n      if (response && !response.ok && (!text || response.status !== 404)) {\n        text = `# ${response.status} - ${response.statusText}`;\n      }\n\n      this.callHook('beforeEach', text, result => {\n        let html;\n        const callback = () => {\n          if (opt.updatedAt) {\n            html = this.#formatUpdated(\n              html,\n              opt.updatedAt,\n              this.config.formatUpdated,\n            );\n          }\n\n          this.callHook('afterEach', html, hookData => {\n            this.#renderMain(hookData);\n            next();\n          });\n        };\n\n        if (this.isHTML) {\n          html = this.result = text;\n          callback();\n        } else {\n          prerenderEmbed(\n            {\n              compiler: /** @type {Compiler} */ (this.compiler),\n              raw: result,\n              fetch: undefined,\n            },\n            tokens => {\n              html = /** @type {Compiler} */ (this.compiler).compile(tokens);\n              callback();\n            },\n          );\n        }\n      });\n    }\n\n    _renderCover(text, coverOnly) {\n      const el = dom.getNode('.cover');\n      const rootElm = document.documentElement;\n      const coverBg = getComputedStyle(rootElm).getPropertyValue('--cover-bg');\n\n      dom.getNode('main').classList[coverOnly ? 'add' : 'remove']('hidden');\n\n      if (!text) {\n        el.classList.remove('show');\n        return;\n      }\n\n      el.classList.add('show');\n\n      let html = this.coverIsHTML\n        ? text\n        : /** @type {Compiler} */ (this.compiler).cover(text);\n\n      if (!coverBg) {\n        const mdBgMatch = html\n          .trim()\n          .match(\n            '<p><img.*?data-origin=\"(.*?)\".*?alt=\"(.*?)\"[^>]*?>([^<]*?)</p>$',\n          );\n\n        let mdCoverBg;\n\n        if (mdBgMatch) {\n          const [bgMatch, bgValue, bgType] = mdBgMatch;\n\n          // Color\n          if (bgType === 'color') {\n            mdCoverBg = bgValue;\n          }\n          // Image\n          else {\n            const path = !isAbsolutePath(bgValue)\n              ? getPath(this.router.getBasePath(), bgValue)\n              : bgValue;\n\n            mdCoverBg = `center center / cover url(${path})`;\n          }\n\n          html = html.replace(bgMatch, '');\n        }\n        // Gradient background\n        else {\n          const degrees = Math.round((Math.random() * 120) / 2);\n\n          let hue1 = Math.round(Math.random() * 360);\n          let hue2 = Math.round(Math.random() * 360);\n\n          // Ensure hue1 and hue2 are at least 50 degrees apart\n          if (Math.abs(hue1 - hue2) < 50) {\n            const hueShift = Math.round(Math.random() * 25) + 25;\n\n            hue1 = Math.max(hue1, hue2) + hueShift;\n            hue2 = Math.min(hue1, hue2) - hueShift;\n          }\n\n          // OKLCH color\n          if (window?.CSS?.supports('color', 'oklch(0 0 0 / 1%)')) {\n            const l = 90; // Lightness\n            const c = 20; // Chroma\n\n            // prettier-ignore\n            mdCoverBg = `linear-gradient(\n              ${degrees}deg,\n              oklch(${l}% ${c}% ${hue1}) 0%,\n              oklch(${l}% ${c}% ${hue2}) 100%\n            )`.replace(/\\s+/g, ' ');\n          }\n          // HSL color (Legacy)\n          else {\n            const s = 100; // Saturation\n            const l = 85; // Lightness\n            const o = 100; // Opacity\n\n            // prettier-ignore\n            mdCoverBg = `linear-gradient(\n              ${degrees}deg,\n              hsl(${hue1} ${s}% ${l}% / ${o}%) 0%,\n              hsl(${hue2} ${s}% ${l}% / ${o}%) 100%\n            )`.replace(/\\s+/g, ' ');\n          }\n        }\n\n        rootElm.style.setProperty('--cover-bg', mdCoverBg);\n      }\n\n      dom.setHTML('.cover-main', html);\n\n      // Button styles\n      dom\n        .findAll('.cover-main > p:last-of-type > a:not([class])')\n        .forEach(elm => {\n          const buttonType = elm.matches(':first-child')\n            ? 'primary'\n            : 'secondary';\n\n          elm.classList.add('button', buttonType);\n        });\n    }\n\n    _updateRender() {\n      // Render name link\n      this.#renderNameLink(this);\n\n      // Render skip link\n      this.#renderSkipLink(this);\n    }\n\n    initRender() {\n      const config = this.config;\n\n      // Init markdown compiler\n      this.compiler = new Compiler(config, this.router);\n      window.__current_docsify_compiler__ = this.compiler;\n\n      const id = config.el || '#app';\n      const el = dom.find(id);\n\n      if (el) {\n        let html = '';\n\n        if (config.repo) {\n          html += tpl.corner(config.repo, config.cornerExternalLinkTarget);\n        }\n\n        if (config.coverpage) {\n          html += tpl.cover();\n        }\n\n        if (config.logo) {\n          const isBase64 = /^data:image/.test(config.logo);\n          const isExternal = /(?:http[s]?:)?\\/\\//.test(config.logo);\n          const isRelative = /^\\./.test(config.logo);\n\n          if (!isBase64 && !isExternal && !isRelative) {\n            config.logo = getPath(this.router.getBasePath(), config.logo);\n          }\n        }\n\n        html += tpl.main(config);\n\n        // Render main app\n        dom.setHTML(el, html, true);\n      } else {\n        this.rendered = true;\n      }\n\n      // Add nav\n      if (config.loadNavbar) {\n        const navEl = dom.find('nav') || dom.create('nav');\n        const isMergedSidebar = config.mergeNavbar;\n\n        navEl.classList.add('app-nav');\n        navEl.setAttribute('aria-label', 'secondary');\n        dom.body.prepend(navEl);\n\n        if (isMergedSidebar) {\n          const mergedNavEl = dom.create('div');\n          const sidebarEl = dom.find('.sidebar');\n          const sidebarNavEl = dom.find('.sidebar-nav');\n\n          mergedNavEl?.classList.add('app-nav-merged');\n          sidebarEl?.insertBefore(mergedNavEl, sidebarNavEl);\n        }\n      }\n\n      if (config.themeColor) {\n        const themeNode = dom.create(\n          'div',\n          tpl.theme(config.themeColor),\n        ).firstElementChild;\n        if (themeNode) {\n          dom.$.head.appendChild(themeNode);\n        }\n      }\n\n      this._updateRender();\n      dom.body.classList.add('ready');\n    }\n  };\n}\n"
  },
  {
    "path": "src/core/render/progressbar.js",
    "content": "import * as dom from '../util/dom.js';\n\nlet barEl;\nlet timeId;\n\n/**\n * Init progress component\n */\nfunction init() {\n  const div = dom.create('div');\n\n  div.classList.add('progress');\n  div.setAttribute('role', 'progressbar');\n  div.setAttribute('aria-valuemin', '0');\n  div.setAttribute('aria-valuemax', '100');\n  div.setAttribute('aria-label', 'Loading...');\n  dom.appendTo(dom.body, div);\n  barEl = div;\n}\n\n/**\n * Render progress bar\n * @param {{step: number, loaded?: undefined, total?: undefined} | {step?: undefined, loaded: number, total: number}} info\n */\nexport default function (info) {\n  const { loaded, total, step } = info;\n  let num;\n\n  !barEl && init();\n\n  if (typeof step !== 'undefined') {\n    num = parseInt(barEl.style.width || 0, 10) + step;\n    num = num > 80 ? 80 : num;\n  } else {\n    num = Math.floor((loaded / total) * 100);\n  }\n\n  barEl.style.opacity = 1;\n  barEl.style.width = num >= 95 ? '100%' : num + '%';\n  barEl.setAttribute('aria-valuenow', num >= 95 ? 100 : num);\n\n  if (num >= 95) {\n    clearTimeout(timeId);\n\n    timeId = setTimeout(_ => {\n      barEl.style.opacity = 0;\n      barEl.style.width = '0%';\n      barEl.removeAttribute('aria-valuenow');\n    }, 200);\n  }\n}\n"
  },
  {
    "path": "src/core/render/slugify.js",
    "content": "let cache = {};\nconst re = /[\\u2000-\\u206F\\u2E00-\\u2E7F\\\\'!\"#$%&()*+,./:;<=>?@[\\]^`{|}~]/g;\n\nfunction lower(string) {\n  return string.toLowerCase();\n}\n\nexport function slugify(str) {\n  if (typeof str !== 'string') {\n    return '';\n  }\n\n  let slug = str\n    .trim()\n    .normalize('NFC')\n    .replace(/\\uFE0F/g, '')\n    .replace(/[\\p{Emoji_Presentation}\\p{Extended_Pictographic}]/gu, '')\n    .replace(/[A-Z]+/g, lower)\n    .replace(/<[^>]+>/g, '')\n    .replace(re, '')\n    .replace(/\\s/g, '-')\n    .replace(/^(\\d)/, '_$1');\n  let count = cache[slug];\n\n  count = Object.keys(cache).includes(slug) ? count + 1 : 0;\n  cache[slug] = count;\n\n  if (count) {\n    slug = slug + '-' + count;\n  }\n\n  return slug;\n}\n\nslugify.clear = function () {\n  cache = {};\n};\n"
  },
  {
    "path": "src/core/render/tpl.js",
    "content": "import { isMobile } from '../util/env.js';\n\n/**\n * Render github corner\n * @param  {Object} data URL for the View Source on GitHub link\n * @param {String} cornerExternalLinkTarget value of the target attribute of the link\n * @return {String} SVG element as string\n */\nexport function corner(data, cornerExternalLinkTarget) {\n  if (!data) {\n    return '';\n  }\n\n  if (!/\\/\\//.test(data)) {\n    data = 'https://github.com/' + data;\n  }\n\n  data = data.replace(/^git\\+/, '');\n  // Double check\n  cornerExternalLinkTarget = cornerExternalLinkTarget || '_blank';\n\n  return /* html */ `\n    <a href=\"${data}\" target=\"${cornerExternalLinkTarget}\" class=\"github-corner\" aria-label=\"View source on GitHub\">\n      <svg viewBox=\"0 0 250 250\" aria-hidden=\"true\">\n        <path d=\"M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z\"></path>\n        <path d=\"M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2\" fill=\"currentColor\" style=\"transform-origin: 130px 106px;\" class=\"octo-arm\"></path>\n        <path d=\"M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z\" fill=\"currentColor\" class=\"octo-body\"></path>\n      </svg>\n    </a>\n  `;\n}\n\n/**\n * Renders main content\n * @param {Object} config Configuration object\n * @returns {String} HTML of the main content\n */\nexport function main(config) {\n  const { hideSidebar, name } = config;\n  // const name = config.name ? config.name : '';\n\n  const aside = /* html */ hideSidebar\n    ? ''\n    : `\n    <button class=\"sidebar-toggle\" tabindex=\"-1\" title=\"Press \\\\ to toggle\">\n      <div class=\"sidebar-toggle-button\" tabindex=\"0\" aria-label=\"Hide primary navigation\" aria-keyshortcuts=\"Use shortcut key \\\\\" aria-controls=\"__sidebar\" role=\"button\">\n        <span></span><span></span><span></span>\n      </div>\n    </button>\n    <aside id=\"__sidebar\" class=\"sidebar${!isMobile() ? ' show' : ''}\" tabindex=\"-1\" role=\"none\">\n      ${\n        config.name\n          ? /* html */ `\n            <h1 class=\"app-name\"><a class=\"app-name-link\" data-nosearch>${\n              config.logo ? `<img alt=\"${name}\" src=${config.logo} />` : name\n            }</a></h1>\n          `\n          : ''\n      }\n      <div class=\"sidebar-nav\" role=\"navigation\" aria-label=\"primary\"><!--sidebar--></div>\n    </aside>\n  `;\n\n  return /* html */ `\n    <main role=\"presentation\">\n      ${aside}\n      <section class=\"content\">\n        <article id=\"main\" class=\"markdown-section\" role=\"main\" tabindex=\"-1\"><!--main--></article>\n      </section>\n    </main>\n  `;\n}\n\n/**\n * Cover Page\n * @returns {String} Cover page\n */\nexport function cover() {\n  return /* html */ `\n    <section class=\"cover show\" role=\"complementary\" aria-label=\"cover\">\n      <div class=\"mask\"></div>\n      <div class=\"cover-main\"><!--cover--></div>\n    </section>\n  `;\n}\n\n/**\n * Render tree\n * @param  {Array} toc Array of TOC section links\n * @param  {String} tpl TPL list\n * @return {String} Rendered tree\n */\nexport function tree(\n  toc,\n  tpl = /* html */ '<ul class=\"app-sub-sidebar\">{inner}</ul>',\n) {\n  if (!toc || !toc.length) {\n    return '';\n  }\n\n  let innerHTML = '';\n  toc.forEach(node => {\n    const title = node.title.replace(/(<([^>]+)>)/g, '');\n    let current = `<li><a class=\"section-link\" href=\"${node.slug}\" title=\"${title}\">${node.title}</a></li>`;\n    if (node.children) {\n      // when current node has children, we need put them all in parent's <li> block without the `class=\"app-sub-sidebar\"` attribute\n      const children = tree(node.children, '<ul>{inner}</ul>');\n      current = `<li><a class=\"section-link\" href=\"${node.slug}\" title=\"${title}\">${node.title}</a>${children}</li>`;\n    }\n    innerHTML += current;\n  });\n  return tpl.replace('{inner}', innerHTML);\n}\n\n/**\n * @deprecated\n */\nexport function helper(className, content) {\n  return /* html */ `<p class=\"${className}\">${content.slice(5).trim()}</p>`;\n}\n\n/**\n * @deprecated\n */\nexport function theme(color) {\n  return /* html */ `<style>:root{--theme-color: ${color};}</style>`;\n}\n"
  },
  {
    "path": "src/core/render/utils.js",
    "content": "/**\n * Converts a colon formatted string to a object with properties.\n *\n * This is process a provided string and look for any tokens in the format\n * of `:name[=value]` and then convert it to a object and return.\n * An example of this is ':include :type=code :fragment=demo' is taken and\n * then converted to:\n *\n * ```\n * {\n *  include: '',\n *  type: 'code',\n *  fragment: 'demo'\n * }\n * ```\n *\n * @param {string}   str   The string to parse.\n *\n * @return {{str: string, config: Record<string, string | string[]>}} The original string formatted, and parsed object, { str, config }.\n */\nexport function getAndRemoveConfig(str = '') {\n  /** @type {Record<string, string | string[]>} */\n  const config = {};\n\n  if (str) {\n    str = str\n      .replace(/^('|\")/, '')\n      .replace(/('|\")$/, '')\n      .replace(/(?:^|\\s):([\\w-]+:?)=?([\\w-%]+)?/g, (m, key, value) => {\n        if (key.indexOf(':') !== -1) {\n          return m;\n        }\n\n        value = (value && value.replace(/&quot;/g, '')) || true;\n\n        if (value !== true && config[key] !== undefined) {\n          if (!Array.isArray(config[key]) && value !== config[key]) {\n            config[key] = [config[key]];\n          }\n          config[key].includes(value) ||\n            /** @type {string[]} */ (config[key]).push(value);\n        } else {\n          config[key] = value;\n        }\n        return '';\n      })\n      .trim();\n  }\n\n  return { str, config };\n}\n\n/**\n * Remove the <a> tag from sidebar when the header with link, details see issue 1069\n * @param {string}   str   The string to deal with.\n *\n * @return {string}   The string after delete the <a> element.\n */\nexport function removeAtag(str = '') {\n  return str.replace(/(<\\/?a.*?>)/gi, '');\n}\n\n/**\n * Remove the docsifyIgnore configs and return the str\n * @param {string}   content   The string to deal with.\n *\n * @return {{content: string, ignoreAllSubs: boolean, ignoreSubHeading: boolean}} The string after delete the docsifyIgnore configs, and whether to ignore some or all.\n */\nexport function getAndRemoveDocsifyIgnoreConfig(content = '') {\n  let ignoreAllSubs, ignoreSubHeading;\n  if (/<!-- {docsify-ignore} -->/g.test(content)) {\n    content = content.replace('<!-- {docsify-ignore} -->', '');\n    ignoreSubHeading = true;\n  }\n\n  if (/{docsify-ignore}/g.test(content)) {\n    content = content.replace('{docsify-ignore}', '');\n    ignoreSubHeading = true;\n  }\n\n  if (/<!-- {docsify-ignore-all} -->/g.test(content)) {\n    content = content.replace('<!-- {docsify-ignore-all} -->', '');\n    ignoreAllSubs = true;\n  }\n\n  if (/{docsify-ignore-all}/g.test(content)) {\n    content = content.replace('{docsify-ignore-all}', '');\n    ignoreAllSubs = true;\n  }\n\n  return /** @type {{content: string, ignoreAllSubs: boolean, ignoreSubHeading: boolean}} */ ({\n    content,\n    ignoreAllSubs,\n    ignoreSubHeading,\n  });\n}\n"
  },
  {
    "path": "src/core/router/history/base.js",
    "content": "import {\n  cleanPath,\n  getPath,\n  isAbsolutePath,\n  replaceSlug,\n  resolvePath,\n  stringifyQuery,\n} from '../util.js';\nimport { noop } from '../../util/core.js';\n\nexport class History {\n  #cached = {};\n\n  constructor(config) {\n    this.config = config;\n  }\n\n  #getAlias(path, alias, last) {\n    const match = Object.keys(alias).filter(key => {\n      const re =\n        this.#cached[key] || (this.#cached[key] = new RegExp(`^${key}$`));\n      return re.test(path) && path !== last;\n    })[0];\n\n    return match\n      ? this.#getAlias(\n          path.replace(this.#cached[match], alias[match]),\n          alias,\n          path,\n        )\n      : path;\n  }\n\n  #getFileName(path, ext) {\n    const [basePath, query] = path.split('?');\n\n    const hasValidExt = new RegExp(`\\\\.(${ext.replace(/^\\./, '')}|html)$`).test(\n      basePath,\n    );\n\n    const updatedPath = hasValidExt\n      ? basePath\n      : /\\/$/g.test(basePath)\n        ? `${basePath}README${ext}`\n        : `${basePath}${ext}`;\n\n    return query ? `${updatedPath}?${query}` : updatedPath;\n  }\n\n  getBasePath() {\n    return this.config.basePath;\n  }\n\n  /**\n   * @param {string} path\n   * @param {boolean} isRelative\n   */\n  getFile(path = this.getCurrentPath(), isRelative = false) {\n    const { config } = this;\n    const base = this.getBasePath();\n    const ext = typeof config.ext === 'string' ? config.ext : '.md';\n\n    path = config.alias ? this.#getAlias(path, config.alias) : path;\n    path = this.#getFileName(path, ext);\n    path = path === `/README${ext}` ? config.homepage || path : path;\n    path = isAbsolutePath(path) ? path : getPath(base, path);\n\n    if (isRelative) {\n      path = path.replace(new RegExp(`^${base}`), '');\n    }\n\n    return path;\n  }\n\n  onchange(cb = noop) {\n    cb();\n  }\n\n  /**\n   * @return {string}\n   */\n  getCurrentPath() {\n    throw new Error('Subclass should implement');\n  }\n\n  normalize() {\n    throw new Error('Subclass should implement');\n  }\n\n  /**\n   * @param {string} path\n   * @return {import('../index.js').Route} { path, query, file, response }\n   */\n  parse(path) {\n    throw new Error('Subclass should implement');\n  }\n\n  toURL(path, params, currentRoute) {\n    const local = currentRoute && path[0] === '#';\n    const route = this.parse(replaceSlug(path));\n\n    route.query = { ...route.query, ...params };\n    path = route.path + stringifyQuery(route.query);\n    path = path.replace(/\\.md(\\?)|\\.md$/, '$1');\n\n    if (local) {\n      const idIndex = currentRoute.indexOf('?');\n      path =\n        (idIndex > 0 ? currentRoute.substring(0, idIndex) : currentRoute) +\n        path;\n    }\n\n    if (this.config.relativePath && path.indexOf('/') !== 0) {\n      const currentDir = currentRoute.substring(\n        0,\n        currentRoute.lastIndexOf('/') + 1,\n      );\n      return cleanPath(resolvePath(currentDir + path));\n    }\n\n    return cleanPath('/' + path);\n  }\n}\n"
  },
  {
    "path": "src/core/router/history/hash.js",
    "content": "import { isExternal, noop } from '../../util/core.js';\nimport { on } from '../../util/dom.js';\nimport { parseQuery, cleanPath, replaceSlug } from '../util.js';\nimport { History } from './base.js';\n\nfunction replaceHash(path) {\n  const i = location.href.indexOf('#');\n  location.replace(location.href.slice(0, i >= 0 ? i : 0) + '#' + path);\n}\n\nexport class HashHistory extends History {\n  mode = 'hash';\n\n  getBasePath() {\n    const path = window.location.pathname || '';\n    const base = this.config.basePath;\n\n    // This handles the case where Docsify is served off an\n    // explicit file path, i.e.`/base/index.html#/blah`. This\n    // prevents the `/index.html` part of the URI from being\n    // remove during routing.\n    // See here: https://github.com/docsifyjs/docsify/pull/1372\n    const basePath = path.endsWith('.html')\n      ? path + '#/' + base\n      : path + '/' + base;\n    return /^(\\/|https?:)/g.test(base) ? base : cleanPath(basePath);\n  }\n\n  getCurrentPath() {\n    // We can't use location.hash here because it's not\n    // consistent across browsers - Firefox will pre-decode it!\n    const href = location.href;\n    const index = href.indexOf('#');\n    return index === -1 ? '' : href.slice(index + 1);\n  }\n\n  /** @param {(params: {source: any, event?: any}) => void} [cb] */\n  onchange(cb = noop) {\n    // The hashchange event does not tell us if it originated from\n    // a clicked link or by moving back/forward in the history;\n    // therefore we set a `navigating` flag when a link is clicked\n    // to be able to tell these two scenarios apart\n    let navigating = false;\n\n    on('click', e => {\n      const el = e.target.tagName === 'A' ? e.target : e.target.parentNode;\n\n      if (el && el.tagName === 'A' && !isExternal(el.href)) {\n        navigating = true;\n\n        // Do not compare hash containing these classes.\n        if (['app-name-link', 'page-link'].includes(el.className)) {\n          return;\n        }\n\n        if (el.hash === location.hash) {\n          cb({ event: e, source: 'navigate' });\n        }\n      }\n    });\n\n    on('hashchange', e => {\n      const source = navigating ? 'navigate' : 'history';\n      navigating = false;\n      cb({ event: e, source });\n    });\n  }\n\n  normalize() {\n    let path = this.getCurrentPath();\n\n    path = replaceSlug(path);\n\n    if (path.charAt(0) === '/') {\n      return replaceHash(path);\n    }\n\n    replaceHash('/' + path);\n  }\n\n  /**\n   * Parse the url\n   * @param {string} path URL to be parsed\n   * @return {import('../index.js').Route} { path, query, file, response }\n   */\n  parse(path = location.href) {\n    let query = '';\n\n    const hashIndex = path.indexOf('#');\n    if (hashIndex >= 0) {\n      path = path.slice(hashIndex + 1);\n    }\n\n    const queryIndex = path.indexOf('?');\n    if (queryIndex >= 0) {\n      query = path.slice(queryIndex + 1);\n      path = path.slice(0, queryIndex);\n    }\n\n    return {\n      path,\n      file: this.getFile(path, true),\n      query: parseQuery(query),\n      response: {},\n    };\n  }\n\n  toURL(path, params, currentRoute) {\n    return '#' + super.toURL(path, params, currentRoute);\n  }\n}\n\n/** @typedef {any} TODO */\n"
  },
  {
    "path": "src/core/router/history/html5.js",
    "content": "import { isExternal, noop } from '../../util/core.js';\nimport { on } from '../../util/dom.js';\nimport { parseQuery, getPath } from '../util.js';\nimport { History } from './base.js';\n\nexport class HTML5History extends History {\n  mode = 'history';\n\n  getCurrentPath() {\n    const base = this.getBasePath();\n    let path = window.location.pathname;\n\n    if (base && path.indexOf(base) === 0) {\n      path = path.slice(base.length);\n    }\n\n    return (path || '/') + window.location.search + window.location.hash;\n  }\n\n  /** @param {(params: any) => void} [cb] */\n  onchange(cb = noop) {\n    on('click', e => {\n      const el = e.target.tagName === 'A' ? e.target : e.target.parentNode;\n\n      if (el && el.tagName === 'A' && !isExternal(el.href)) {\n        e.preventDefault();\n        const url = el.href;\n        window.history.pushState({ key: url }, '', url);\n        cb({ event: e, source: 'navigate' });\n      }\n    });\n\n    on('popstate', e => {\n      cb({ event: e, source: 'history' });\n    });\n  }\n\n  /**\n   * Parse the url\n   * @param {string} [path=location.href] URL to be parsed\n   * @return {import('../index.js').Route} { path, query, file, response }\n   */\n  parse(path = location.href) {\n    let query = '';\n\n    const queryIndex = path.indexOf('?');\n    if (queryIndex >= 0) {\n      query = path.slice(queryIndex + 1);\n      path = path.slice(0, queryIndex);\n    }\n\n    const base = getPath(location.origin);\n    const baseIndex = path.indexOf(base);\n\n    if (baseIndex > -1) {\n      path = path.slice(baseIndex + base.length);\n    }\n\n    return {\n      path,\n      file: this.getFile(path),\n      query: parseQuery(query),\n      response: {},\n    };\n  }\n}\n"
  },
  {
    "path": "src/core/router/index.js",
    "content": "import * as dom from '../util/dom.js';\nimport { noop } from '../util/core.js';\nimport { HashHistory } from './history/hash.js';\nimport { HTML5History } from './history/html5.js';\n\n/**\n * @typedef {{\n *   path: string\n *   query: Record<string, string>;\n *   file: string;\n *   response: {}\n * }} Route\n */\n\n/** @type {Partial<Route>} */\nlet lastRoute = {};\n\n/** @typedef {import('../Docsify.js').Constructor} Constructor */\n\n/**\n * @template {!Constructor} T\n * @param {T} Base - The class to extend\n */\nexport function Router(Base) {\n  return class Router extends Base {\n    /** @type {Partial<Route>} */\n    route = {};\n\n    updateRender() {\n      this.router?.normalize();\n      this.route = this.router?.parse() ?? {};\n      dom.body.setAttribute('data-page', this.route.file ?? '');\n    }\n\n    initRouter() {\n      const config = this.config;\n      const mode = config.routerMode || 'hash';\n      let router;\n\n      if (mode === 'history') {\n        router = new HTML5History(config);\n      } else {\n        router = new HashHistory(config);\n      }\n\n      this.router = router;\n      this.updateRender();\n      lastRoute = this.route;\n\n      router.onchange(params => {\n        this.updateRender();\n        this._updateRender();\n\n        if (lastRoute.path === this.route.path) {\n          this.onNavigate(params.source);\n          return;\n        }\n\n        this.$fetch(noop, this.onNavigate.bind(this, params.source));\n        lastRoute = this.route;\n      });\n    }\n  };\n}\n"
  },
  {
    "path": "src/core/router/util.js",
    "content": "import { cached } from '../util/core.js';\n\nconst decode = decodeURIComponent;\nconst encode = encodeURIComponent;\n\n/**\n * @param {string} query\n * @return {Record<string, string>}\n */\nexport function parseQuery(query) {\n  /** @type {Record<string, string>} */\n  const res = {};\n\n  query = query.trim().replace(/^(\\?|#|&)/, '');\n\n  if (!query) {\n    return res;\n  }\n\n  // Simple parse\n  query.split('&').forEach(param => {\n    const parts = param.replace(/\\+/g, ' ').split('=');\n\n    res[parts[0]] = parts[1] && decode(parts[1]);\n  });\n\n  return res;\n}\n\nexport function stringifyQuery(obj, ignores = []) {\n  const qs = [];\n\n  for (const key in obj) {\n    if (ignores.indexOf(key) > -1) {\n      continue;\n    }\n\n    qs.push(\n      obj[key]\n        ? `${encode(key)}=${encode(obj[key])}`.toLowerCase()\n        : encode(key),\n    );\n  }\n\n  return qs.length ? `?${qs.join('&')}` : '';\n}\n\nexport function stripUrlExceptId(str) {\n  const [path, queryString] = str.split('?');\n  if (!queryString) {\n    return str;\n  }\n\n  const params = new URLSearchParams(queryString);\n  const id = params.get('id');\n\n  if (id !== null) {\n    return `${path}?id=${id}`;\n  }\n\n  return path;\n}\n\nexport const isAbsolutePath = cached(path => {\n  return /(:|(\\/{2}))/g.test(path);\n});\n\nexport const removeParams = cached(path => {\n  return path.split(/[?#]/)[0];\n});\n\nexport const getParentPath = cached(path => {\n  if (/\\/$/g.test(path)) {\n    return path;\n  }\n\n  const matchingParts = path.match(/(\\S*\\/)[^/]+$/);\n  return matchingParts ? matchingParts[1] : '';\n});\n\nexport const cleanPath = cached(path => {\n  return path.replace(/^\\/+/, '/').replace(/([^:])\\/{2,}/g, '$1/');\n});\n\nexport const resolvePath = cached(path => {\n  const segments = path.replace(/^\\//, '').split('/');\n  const resolved = [];\n  for (const segment of segments) {\n    if (segment === '..') {\n      resolved.pop();\n    } else if (segment !== '.') {\n      resolved.push(segment);\n    }\n  }\n\n  return '/' + resolved.join('/');\n});\n\n/**\n * Normalises the URI path to handle the case where Docsify is\n * hosted off explicit files, i.e. /index.html. This function\n * eliminates any path segments that contain `#` fragments.\n *\n * This is used to map browser URIs to markdown file sources.\n *\n * For example:\n *\n * http://example.org/base/index.html#/blah\n *\n * would be mapped to:\n *\n * http://example.org/base/blah.md.\n *\n * See here for more information:\n *\n * https://github.com/docsifyjs/docsify/pull/1372\n *\n * @param {string} path The URI path to normalise\n * @return {string} { path, query }\n */\n\nfunction normaliseFragment(path) {\n  return path\n    .split('/')\n    .filter(p => p.indexOf('#') === -1)\n    .join('/');\n}\n\nexport function getPath(...args) {\n  return cleanPath(args.map(normaliseFragment).join('/'));\n}\n\nexport const replaceSlug = cached(path => {\n  return path.replace('#', '?id=');\n});\n"
  },
  {
    "path": "src/core/util/ajax.js",
    "content": "// @ts-check\n\nimport progressbar from '../render/progressbar.js';\nimport { noop } from './core.js';\n\n/** @typedef {{updatedAt: string}} CacheOpt */\n/** @typedef {{content: string, opt: CacheOpt}} CacheItem */\n/** @typedef {{ok: boolean, status: number, statusText: string}} ResponseStatus */\n/** @type {Record<string, CacheItem>} */\n\nconst cache = {};\n\n/**\n * Ajax GET implementation\n * @param {string} url Resource URL\n * @param {boolean} [hasBar=false] Has progress bar\n * @param {String[]} headers Array of headers\n * @return A Promise-like response with error callback (error callback is not Promise-like)\n */\n// TODO update to using fetch() + Streams API instead of XMLHttpRequest. See an\n// example of download progress calculation using fetch() here:\n// https://streams.spec.whatwg.org/demos/\n/**\n * @param {string} url\n * @param {boolean} [hasBar]\n * @param {Record<string, string>} [headers]\n */\nexport function get(url, hasBar = false, headers = {}) {\n  const xhr = new XMLHttpRequest();\n  const cached = cache[url];\n\n  if (cached) {\n    return { then: cb => cb(cached.content, cached.opt), abort: noop };\n  }\n\n  xhr.open('GET', url);\n  for (const i of Object.keys(headers)) {\n    xhr.setRequestHeader(i, headers[i]);\n  }\n\n  xhr.send();\n\n  return {\n    /**\n     * @param {(text: string, opt: CacheOpt, response: ResponseStatus) => void} success\n     * @param {(event: ProgressEvent<XMLHttpRequestEventTarget>, response: ResponseStatus) => void} error\n     */\n    then(success, error = noop) {\n      const getResponseStatus = event => ({\n        ok: event.target.status >= 200 && event.target.status < 300,\n        status: event.target.status,\n        statusText: event.target.statusText,\n      });\n\n      if (hasBar) {\n        const id = setInterval(\n          _ =>\n            progressbar({\n              step: Math.floor(Math.random() * 5 + 1),\n            }),\n          500,\n        );\n\n        xhr.addEventListener('progress', progressbar);\n        xhr.addEventListener('loadend', evt => {\n          progressbar(evt);\n          clearInterval(id);\n        });\n      }\n\n      xhr.addEventListener('error', event => {\n        error(event, getResponseStatus(event));\n      });\n\n      xhr.addEventListener('load', event => {\n        const target = /** @type {XMLHttpRequest} */ (event.target);\n\n        if (target.status >= 400) {\n          error(event, getResponseStatus(event));\n        } else {\n          if (typeof target.response !== 'string') {\n            throw new TypeError('Unsupported content type.');\n          }\n\n          const result = (cache[url] = {\n            content: target.response,\n            opt: {\n              updatedAt: xhr.getResponseHeader('last-modified') ?? '',\n            },\n          });\n\n          success(result.content, result.opt, getResponseStatus(event));\n        }\n      });\n    },\n    abort: _ => xhr.readyState !== 4 && xhr.abort(),\n  };\n}\n"
  },
  {
    "path": "src/core/util/core.js",
    "content": "/**\n * Create a cached version of fn that given an input string returns a\n * cached return value mapped from the string, regardless if fn is a new function every time.\n * created.\n * @param {*} fn The function call to be cached\n * @void\n */\n// TODO Replace this with a proper memo(fn) based on args per function.\nexport function cached(fn) {\n  const cache = Object.create(null);\n  return function (str) {\n    const key = isPrimitive(str) ? str : JSON.stringify(str);\n    const hit = cache[key];\n    return hit || (cache[key] = fn(str));\n  };\n}\n\n/**\n * Hyphenate a camelCase string.\n */\nexport const hyphenate = cached(str => {\n  return str.replace(/([A-Z])/g, m => '-' + m.toLowerCase());\n});\n\n/**\n * Check if value is primitive\n * @param {*} value Checks if a value is primitive\n * @returns {value is string | number} Result of the check\n */\nexport function isPrimitive(value) {\n  return typeof value === 'string' || typeof value === 'number';\n}\n\n/**\n * Performs no operation.\n * @void\n */\nexport function noop() {}\n\n/**\n * Check if value is function\n * @param {*} obj Any javascript object\n * @returns {obj is Function} True if the passed-in value is a function\n */\nexport function isFn(obj) {\n  return typeof obj === 'function';\n}\n\n/**\n * Check if url is external\n * @param {String} url  url\n * @returns {Boolean} True if the passed-in url is external\n */\nexport function isExternal(url) {\n  /** @type {any} */\n  const match = url.match(\n    /^([^:/?#]+:)?(?:\\/{2,}([^/?#]*))?([^?#]+)?(\\?[^#]*)?(#.*)?/,\n  );\n\n  if (\n    typeof match[1] === 'string' &&\n    match[1].length > 0 &&\n    match[1].toLowerCase() !== location.protocol\n  ) {\n    return true;\n  }\n  if (\n    typeof match[2] === 'string' &&\n    match[2].length > 0 &&\n    match[2].replace(\n      new RegExp(\n        ':(' + { 'http:': 80, 'https:': 443 }[location.protocol] + ')?$',\n      ),\n      '',\n    ) !== location.host\n  ) {\n    return true;\n  }\n  if (/^\\/\\\\/.test(url)) {\n    return true;\n  }\n  return false;\n}\n"
  },
  {
    "path": "src/core/util/dom.js",
    "content": "import { isFn } from '../util/core.js';\n\n/** @type {Record<string, Element>} */\nconst cacheNode = {};\n\n/**\n * Get Node\n * @param  {String|Element} el A DOM element\n * @param  {Boolean} noCache Flag to use or not use the cache\n * @return {Element} The found node element\n */\nexport function getNode(el, noCache = false) {\n  if (typeof el === 'string') {\n    if (typeof window.Vue !== 'undefined') {\n      return find(el);\n    }\n\n    el = noCache ? find(el) : cacheNode[el] || (cacheNode[el] = find(el));\n  }\n\n  return el;\n}\n\n/**\n *\n * @param {*} el the target element or the selector\n * @param {*} content the content to be rendered as HTML\n * @param {*} replace To replace the content (true) or insert instead (false) , default is false\n */\n/**\n * @param {string|Element} el\n * @param {string} content\n * @param {boolean} [replace]\n */\nexport function setHTML(el, content, replace) {\n  const node = getNode(el);\n  if (node) {\n    node[replace ? 'outerHTML' : 'innerHTML'] = content;\n  }\n}\n\nexport const $ = document;\n\nexport const body = $.body;\n\nexport const head = $.head;\n\n/**\n * Find the first matching element\n * @param {string|Element} el The root element on which to perform the query\n * from, or a query string to query from `document`.\n * @param {string} [query] The query string to use on `el` if `el` is an\n * element.\n * @returns {Element} The found DOM element\n * @example\n * find('nav') => document.querySelector('nav')\n * find(nav, 'a') => nav.querySelector('a')\n */\nexport function find(el, query = ':is()') {\n  return /** @type {Element} */ (\n    typeof el !== 'string' ? el.querySelector(query) : $.querySelector(el)\n  );\n}\n\n/**\n * Find all matching elements\n * @param {string|Element} el The root element on which to perform the query\n * from, or a query string to query from `document`.\n * @param {string} [query] The query string to use on `el` if `el` is an\n * element.\n * @returns {Array<Element>} An array of DOM elements\n * @example\n * findAll('a') => Array.from(document.querySelectorAll('a'))\n * findAll(nav, 'a') => Array.from(nav.querySelectorAll('a'))\n */\nexport function findAll(el, query = ':is()') {\n  return Array.from(\n    typeof el !== 'string'\n      ? el.querySelectorAll(query)\n      : $.querySelectorAll(el),\n  );\n}\n\n/**\n * @param {string} node\n * @param {string} [tpl]\n * @returns {HTMLElement}\n */\nexport function create(node, tpl) {\n  const element = $.createElement(node);\n  if (tpl) {\n    element.innerHTML = tpl;\n  }\n\n  return element;\n}\n\n/**\n * @param {Element} target\n * @param {Element} el\n */\nexport function appendTo(target, el) {\n  return target.appendChild(el);\n}\n\n/**\n * @param {Element} target\n * @param {Element} el\n */\nexport function before(target, el) {\n  return target.insertBefore(el, target.children[0]);\n}\n\n/**\n * @param {any} el\n * @param {any} type\n * @param {any} [handler]\n */\nexport function on(el, type, handler) {\n  isFn(type)\n    ? window.addEventListener(el, type)\n    : el.addEventListener(type, handler);\n}\n\n/**\n * @param {any} el\n * @param {any} type\n * @param {any} [handler]\n */\nexport function off(el, type, handler) {\n  isFn(type)\n    ? window.removeEventListener(el, type)\n    : el.removeEventListener(type, handler);\n}\n\n/**\n * @param {string} content\n */\nexport function style(content) {\n  appendTo(head, /** @type {Element} */ (create('style', content)));\n}\n\n/**\n * Fork https://github.com/bendrucker/document-ready/blob/master/index.js\n * @param {(event: Event) => void} callback The callbacack to be called when the page is loaded\n * @returns {number|void} If the page is already loaded returns the result of the setTimeout callback,\n *  otherwise it only attaches the callback to the DOMContentLoaded event\n */\nexport function documentReady(callback, doc = document) {\n  const state = doc.readyState;\n\n  if (state === 'complete' || state === 'interactive') {\n    return setTimeout(callback, 0);\n  }\n\n  doc.addEventListener('DOMContentLoaded', callback);\n}\n"
  },
  {
    "path": "src/core/util/env.js",
    "content": "const computedStyle = getComputedStyle(document.documentElement, null);\n\nexport const mobileBreakpoint = computedStyle.getPropertyValue(\n  '--_mobile-breakpoint',\n);\n\nexport function isMobile() {\n  return window?.matchMedia?.(`(max-width: ${mobileBreakpoint})`)?.matches;\n}\n"
  },
  {
    "path": "src/core/util/index.js",
    "content": "export * from './core.js';\nexport * from './env.js';\nexport * from '../router/util.js';\n"
  },
  {
    "path": "src/core/util/prism.js",
    "content": "import * as Prism from 'prismjs';\n/**\n *\n * The dependencies map which syncs from\n * https://github.com/PrismJS/prism/blob/master/plugins/autoloader/prism-autoloader.js\n *\n */\nconst lang_dependencies = {\n  javascript: 'clike',\n  actionscript: 'javascript',\n  apex: ['clike', 'sql'],\n  arduino: 'cpp',\n  aspnet: ['markup', 'csharp'],\n  birb: 'clike',\n  bison: 'c',\n  c: 'clike',\n  csharp: 'clike',\n  cpp: 'c',\n  cfscript: 'clike',\n  chaiscript: ['clike', 'cpp'],\n  cilkc: 'c',\n  cilkcpp: 'cpp',\n  coffeescript: 'javascript',\n  crystal: 'ruby',\n  'css-extras': 'css',\n  d: 'clike',\n  dart: 'clike',\n  django: 'markup-templating',\n  ejs: ['javascript', 'markup-templating'],\n  etlua: ['lua', 'markup-templating'],\n  erb: ['ruby', 'markup-templating'],\n  fsharp: 'clike',\n  'firestore-security-rules': 'clike',\n  flow: 'javascript',\n  ftl: 'markup-templating',\n  gml: 'clike',\n  glsl: 'c',\n  go: 'clike',\n  gradle: 'clike',\n  groovy: 'clike',\n  haml: 'ruby',\n  handlebars: 'markup-templating',\n  haxe: 'clike',\n  hlsl: 'c',\n  idris: 'haskell',\n  java: 'clike',\n  javadoc: ['markup', 'java', 'javadoclike'],\n  jolie: 'clike',\n  jsdoc: ['javascript', 'javadoclike', 'typescript'],\n  'js-extras': 'javascript',\n  json5: 'json',\n  jsonp: 'json',\n  'js-templates': 'javascript',\n  kotlin: 'clike',\n  latte: ['clike', 'markup-templating', 'php'],\n  less: 'css',\n  lilypond: 'scheme',\n  liquid: 'markup-templating',\n  markdown: 'markup',\n  'markup-templating': 'markup',\n  mongodb: 'javascript',\n  n4js: 'javascript',\n  objectivec: 'c',\n  opencl: 'c',\n  parser: 'markup',\n  php: 'markup-templating',\n  phpdoc: ['php', 'javadoclike'],\n  'php-extras': 'php',\n  plsql: 'sql',\n  processing: 'clike',\n  protobuf: 'clike',\n  pug: ['markup', 'javascript'],\n  purebasic: 'clike',\n  purescript: 'haskell',\n  qsharp: 'clike',\n  qml: 'javascript',\n  qore: 'clike',\n  racket: 'scheme',\n  cshtml: ['markup', 'csharp'],\n  jsx: ['markup', 'javascript'],\n  tsx: ['jsx', 'typescript'],\n  reason: 'clike',\n  ruby: 'clike',\n  sass: 'css',\n  scss: 'css',\n  scala: 'java',\n  'shell-session': 'bash',\n  smarty: 'markup-templating',\n  solidity: 'clike',\n  soy: 'markup-templating',\n  sparql: 'turtle',\n  sqf: 'clike',\n  squirrel: 'clike',\n  stata: ['mata', 'java', 'python'],\n  't4-cs': ['t4-templating', 'csharp'],\n  't4-vb': ['t4-templating', 'vbnet'],\n  tap: 'yaml',\n  tt2: ['clike', 'markup-templating'],\n  textile: 'markup',\n  twig: 'markup-templating',\n  typescript: 'javascript',\n  v: 'clike',\n  vala: 'clike',\n  vbnet: 'basic',\n  velocity: 'markup',\n  wiki: 'markup',\n  xeora: 'markup',\n  'xml-doc': 'markup',\n  xquery: 'markup',\n};\n\nconst lang_aliases = {\n  html: 'markup',\n  xml: 'markup',\n  svg: 'markup',\n  mathml: 'markup',\n  ssml: 'markup',\n  atom: 'markup',\n  rss: 'markup',\n  js: 'javascript',\n  g4: 'antlr4',\n  ino: 'arduino',\n  'arm-asm': 'armasm',\n  art: 'arturo',\n  adoc: 'asciidoc',\n  avs: 'avisynth',\n  avdl: 'avro-idl',\n  gawk: 'awk',\n  sh: 'bash',\n  shell: 'bash',\n  shortcode: 'bbcode',\n  rbnf: 'bnf',\n  oscript: 'bsl',\n  cs: 'csharp',\n  dotnet: 'csharp',\n  cfc: 'cfscript',\n  'cilk-c': 'cilkc',\n  'cilk-cpp': 'cilkcpp',\n  cilk: 'cilkcpp',\n  coffee: 'coffeescript',\n  conc: 'concurnas',\n  jinja2: 'django',\n  'dns-zone': 'dns-zone-file',\n  dockerfile: 'docker',\n  gv: 'dot',\n  eta: 'ejs',\n  xlsx: 'excel-formula',\n  xls: 'excel-formula',\n  gamemakerlanguage: 'gml',\n  po: 'gettext',\n  gni: 'gn',\n  ld: 'linker-script',\n  'go-mod': 'go-module',\n  hbs: 'handlebars',\n  mustache: 'handlebars',\n  hs: 'haskell',\n  idr: 'idris',\n  gitignore: 'ignore',\n  hgignore: 'ignore',\n  npmignore: 'ignore',\n  webmanifest: 'json',\n  kt: 'kotlin',\n  kts: 'kotlin',\n  kum: 'kumir',\n  tex: 'latex',\n  context: 'latex',\n  ly: 'lilypond',\n  emacs: 'lisp',\n  elisp: 'lisp',\n  'emacs-lisp': 'lisp',\n  md: 'markdown',\n  moon: 'moonscript',\n  n4jsd: 'n4js',\n  nani: 'naniscript',\n  objc: 'objectivec',\n  qasm: 'openqasm',\n  objectpascal: 'pascal',\n  px: 'pcaxis',\n  pcode: 'peoplecode',\n  plantuml: 'plant-uml',\n  pq: 'powerquery',\n  mscript: 'powerquery',\n  pbfasm: 'purebasic',\n  purs: 'purescript',\n  py: 'python',\n  qs: 'qsharp',\n  rkt: 'racket',\n  razor: 'cshtml',\n  rpy: 'renpy',\n  res: 'rescript',\n  robot: 'robotframework',\n  rb: 'ruby',\n  'sh-session': 'shell-session',\n  shellsession: 'shell-session',\n  smlnj: 'sml',\n  sol: 'solidity',\n  sln: 'solution-file',\n  rq: 'sparql',\n  sclang: 'supercollider',\n  t4: 't4-cs',\n  trickle: 'tremor',\n  troy: 'tremor',\n  trig: 'turtle',\n  ts: 'typescript',\n  tsconfig: 'typoscript',\n  uscript: 'unrealscript',\n  uc: 'unrealscript',\n  url: 'uri',\n  vb: 'visual-basic',\n  vba: 'visual-basic',\n  webidl: 'web-idl',\n  mathematica: 'wolfram',\n  nb: 'wolfram',\n  wl: 'wolfram',\n  xeoracube: 'xeora',\n  yml: 'yaml',\n};\n\n// The `depTreeCache` is used to cache the dependency tree for each language,\n// preventing duplicate calculations and avoiding repeated warning messages.\nconst depTreeCache = {};\n\n/**\n * PrismJs language dependencies required a specific order to load.\n * Try to check and print a warning message if some dependencies missing or in wrong order.\n * @param {*} lang current lang to check dependencies\n */\nexport default function checkLangDependenciesAllLoaded(lang) {\n  if (!lang) {\n    return;\n  }\n\n  lang = lang_aliases[lang] || lang;\n\n  const validLang = lang_dependencies[lang];\n  if (!validLang) {\n    return;\n  }\n\n  if (!depTreeCache[lang]) {\n    /**\n     * The dummy node constructs the dependency tree as the root\n     * and maintains the final global loading status (dummy.loaded) for current lang.\n     */\n    const dummy = {\n      cur: '',\n      loaded: true,\n      dependencies: [],\n    };\n\n    buildAndCheckDepTree(lang, dummy, dummy);\n\n    const depTree = dummy.dependencies[0];\n    depTreeCache[lang] = depTree;\n\n    if (!dummy.loaded) {\n      const prettyOutput = prettryPrint(depTree, 1);\n      // eslint-disable-next-line no-console\n      console.warn(\n        `The language '${lang}' required dependencies for code block highlighting are not satisfied.`,\n        `Priority dependencies from low to high, consider to place all the necessary dependencie by priority (higher first): \\n`,\n        prettyOutput,\n      );\n    }\n  }\n}\n\nconst buildAndCheckDepTree = (lang, parent, dummy) => {\n  if (!lang) {\n    return;\n  }\n  const cur = { cur: lang, loaded: true, dependencies: [] };\n  let deps = lang_dependencies[lang] || [];\n\n  if (!(lang in Prism.languages)) {\n    dummy.loaded = false;\n    cur.loaded = false;\n  }\n\n  if (typeof deps === 'string') {\n    deps = [deps];\n  }\n\n  deps.forEach(dep => {\n    buildAndCheckDepTree(dep, cur, dummy);\n  });\n\n  parent.dependencies.push(cur);\n};\n\nconst prettryPrint = (depTree, level) => {\n  let cur = `${'  '.repeat(level * 3)} ${depTree.cur} ${depTree.loaded ? '(+)' : '(-)'}`;\n  if (depTree.dependencies.length) {\n    depTree.dependencies.forEach(dep => {\n      cur += prettryPrint(dep, level + 1);\n    });\n  }\n  return '\\n' + cur;\n};\n"
  },
  {
    "path": "src/core/virtual-routes/exact-match.js",
    "content": "/**\n * Adds beginning of input (^) and end of input ($) assertions if needed into a regex string\n * @param {string} matcher the string to match\n * @returns {string}\n */\nexport function makeExactMatcher(matcher) {\n  const matcherWithBeginningOfInput = matcher.startsWith('^')\n    ? matcher\n    : `^${matcher}`;\n\n  const matcherWithBeginningAndEndOfInput =\n    matcherWithBeginningOfInput.endsWith('$')\n      ? matcherWithBeginningOfInput\n      : `${matcherWithBeginningOfInput}$`;\n\n  return matcherWithBeginningAndEndOfInput;\n}\n"
  },
  {
    "path": "src/core/virtual-routes/index.js",
    "content": "import { makeExactMatcher } from './exact-match.js';\nimport { createNextFunction } from './next.js';\n\n/** @typedef {import('../Docsify.js').Constructor} Constructor */\n\n/** @typedef {Record<string, string | VirtualRouteHandler>} VirtualRoutesMap */\n/** @typedef {(route: string, match: RegExpMatchArray | null, next?: (content: string | void | Promise<string | void>) => void) => string | void | Promise<string | void> } VirtualRouteHandler */\n\n/**\n * Allows users/plugins to introduce dynamically created content into their docsify\n * websites. https://github.com/docsifyjs/docsify/issues/1737\n *\n * For instance:\n *\n * ```js\n * window.$docsify = {\n *   routes: {\n *     '/items/(.+)': function (route, matched) {\n *       return `\n *         # Item Page: ${matched[1]}\n *         This is an item\n *       `;\n *     }\n *   }\n * }\n * ```\n *\n * @template {Constructor} T\n * @param {T} Base - The class to extend\n */\nexport function VirtualRoutes(Base) {\n  return class VirtualRoutes extends Base {\n    /**\n     * Gets the Routes object from the configuration\n     * @returns {VirtualRoutesMap}\n     */\n    routes() {\n      return this.config.routes || {};\n    }\n\n    /**\n     * Attempts to match the given path with a virtual route.\n     * @param {string} path the path of the route to match\n     * @returns {PromiseLike<string | null>} resolves to string if route was matched, otherwise null\n     */\n    matchVirtualRoute(path) {\n      const virtualRoutes = this.routes();\n      const virtualRoutePaths = Object.keys(virtualRoutes);\n\n      /** @type {(value: string | null) => any} */\n      let done = () => null;\n\n      /**\n       * This is a tail recursion that iterates over all the available routes.\n       * It can result in one of two ways:\n       * 1. Call itself (essentially reviewing the next route)\n       * 2. Call the \"done\" callback with the result (either the contents, or \"null\" if no match was found)\n       */\n      function asyncMatchNextRoute() {\n        const virtualRoutePath = virtualRoutePaths.shift();\n        if (!virtualRoutePath) {\n          return done(null);\n        }\n\n        const matcher = makeExactMatcher(virtualRoutePath);\n        const matched = path.match(matcher);\n\n        if (!matched) {\n          return asyncMatchNextRoute();\n        }\n\n        const virtualRouteContentOrFn = virtualRoutes[virtualRoutePath];\n\n        if (typeof virtualRouteContentOrFn === 'string') {\n          const contents = virtualRouteContentOrFn;\n          return done(contents);\n        }\n\n        if (typeof virtualRouteContentOrFn === 'function') {\n          const fn = virtualRouteContentOrFn;\n\n          const [next, onNext] = createNextFunction();\n          onNext(contents => {\n            if (typeof contents === 'string') {\n              return done(contents);\n            } else if (contents === false) {\n              return done(null);\n            } else {\n              return asyncMatchNextRoute();\n            }\n          });\n\n          if (fn.length <= 2) {\n            const returnedValue = fn(path, matched);\n            return next(returnedValue);\n          } else {\n            return fn(path, matched, next);\n          }\n        }\n\n        return asyncMatchNextRoute();\n      }\n\n      return {\n        // @ts-expect-error types are screwed here\n        then(cb) {\n          // @ts-expect-error types are screwed here\n          done = cb;\n          asyncMatchNextRoute();\n        },\n      };\n    }\n  };\n}\n"
  },
  {
    "path": "src/core/virtual-routes/next.js",
    "content": "/** @typedef {(value: any) => void} CB */\n/** @typedef {(cb: CB) => void} OnNext */\n/** @typedef {(value: any) => void} NextFunction */\n\n/**\n * Creates a pair of a function and an event emitter.\n * When the function is called, the event emitter calls the given callback with the value that was passed to the function.\n * @returns {[NextFunction, OnNext]}\n */\nexport function createNextFunction() {\n  /** @type {CB} */\n  let storedCb = () => {};\n\n  function next(value) {\n    storedCb(value);\n  }\n\n  function onNext(cb) {\n    storedCb = cb;\n  }\n\n  return [next, onNext];\n}\n"
  },
  {
    "path": "src/plugins/disqus.js",
    "content": "const fixedPath = location.href.replace('/-/', '/#/');\nif (fixedPath !== location.href) {\n  location.href = fixedPath;\n}\n\nconst window = /** @type {any} */ (globalThis);\n\nfunction install(hook, vm) {\n  const dom = Docsify.dom;\n  const disqus = vm.config.disqus;\n  if (!disqus) {\n    throw Error('$docsify.disqus is required');\n  }\n\n  hook.init(_ => {\n    const script = /** @type {HTMLScriptElement} */ (dom.create('script'));\n\n    script.async = true;\n    script.src = `https://${disqus}.disqus.com/embed.js`;\n    script.setAttribute('data-timestamp', String(Number(new Date())));\n    dom.appendTo(dom.body, script);\n  });\n\n  hook.mounted(_ => {\n    const div = dom.create('div');\n    div.id = 'disqus_thread';\n    const main = dom.getNode('#main');\n    div.style = `width: ${main.clientWidth}px; margin: 0 auto 20px;`;\n    dom.appendTo(dom.find('.content'), div);\n\n    window.disqus_config = function () {\n      this.page.url = location.origin + '/-' + vm.route.path;\n      this.page.identifier = vm.route.path;\n      this.page.title = document.title;\n    };\n  });\n\n  hook.doneEach(_ => {\n    if (typeof window.DISQUS !== 'undefined') {\n      window.DISQUS.reset({\n        reload: true,\n        config() {\n          this.page.url = location.origin + '/-' + vm.route.path;\n          this.page.identifier = vm.route.path;\n          this.page.title = document.title;\n        },\n      });\n    }\n  });\n}\n\nwindow.$docsify = window.$docsify || {};\nwindow.$docsify.plugins = [install, ...(window.$docsify.plugins || [])];\n\nexport {};\n"
  },
  {
    "path": "src/plugins/emoji.js",
    "content": "import emojiData from '../core/render/emoji-data.js';\n\nconst window = /** @type {any} */ (globalThis);\n\n// Deprecation notice\nif (window && window.console) {\n  // eslint-disable-next-line no-console\n  console.info('Docsify emoji plugin has been deprecated as of v4.13');\n}\n\n// Emoji from GitHub API\nwindow.emojify = function (match, $1) {\n  return $1 in emojiData.data\n    ? /* html */ `<img src=\"${emojiData.baseURL}${emojiData.data[$1]}\" alt=\"${$1}\" class=\"emoji\" />`\n    : match;\n};\n"
  },
  {
    "path": "src/plugins/external-script.js",
    "content": "const window = /** @type {any} */ (globalThis);\n\nfunction handleExternalScript() {\n  const container = Docsify.dom.getNode('#main');\n  const scripts = /** @type {HTMLScriptElement[]} */ (\n    Docsify.dom.findAll(container, 'script')\n  );\n\n  for (const script of scripts) {\n    if (script.src) {\n      const newScript = document.createElement('script');\n\n      Array.from(script.attributes).forEach(attribute => {\n        newScript[attribute.name] = attribute.value;\n      });\n\n      script.before(newScript);\n      script.remove();\n    }\n  }\n}\n\nconst install = function (hook) {\n  hook.doneEach(handleExternalScript);\n};\n\nwindow.$docsify = window.$docsify || {};\nwindow.$docsify.plugins = [install, ...(window.$docsify.plugins || [])];\n\nexport {};\n"
  },
  {
    "path": "src/plugins/front-matter/index.js",
    "content": "import parser from './parser.js';\n\nconst install = function (hook, vm) {\n  // Used to remove front matter from embedded pages if installed.\n  vm.config.frontMatter = {};\n  vm.config.frontMatter.installed = true;\n  vm.config.frontMatter.parseMarkdown = function (content) {\n    const { body } = parser(content);\n    return body;\n  };\n\n  hook.beforeEach(content => {\n    const { attributes, body } = parser(content);\n\n    vm.frontmatter = attributes;\n\n    return body;\n  });\n};\n\nwindow.$docsify = window.$docsify || {};\nwindow.$docsify.plugins = [install, ...(window.$docsify?.plugins || [])];\n"
  },
  {
    "path": "src/plugins/front-matter/parser.js",
    "content": "/**\n * Fork https://github.com/egoist/docute/blob/master/src/utils/front-matter.js\n */\n/* eslint-disable */\nimport parser from './yaml.js';\n\nconst optionalByteOrderMark = '\\\\ufeff?';\nconst pattern =\n  '^(' +\n  optionalByteOrderMark +\n  '(= yaml =|---)' +\n  '$([\\\\s\\\\S]*?)' +\n  '(?:\\\\2|\\\\.\\\\.\\\\.)' +\n  '$' +\n  '' +\n  '(?:\\\\n)?)';\n// NOTE: If this pattern uses the 'g' flag the `regex` variable definition will\n// need to be moved down into the functions that use it.\nconst regex = new RegExp(pattern, 'm');\n\nfunction extractor(string) {\n  string = string || '';\n\n  const lines = string.split(/(\\r?\\n)/);\n  if (lines[0] && /= yaml =|---/.test(lines[0])) {\n    return parse(string);\n  } else {\n    return { attributes: {}, body: string };\n  }\n}\n\nfunction parse(string) {\n  const match = regex.exec(string);\n\n  if (!match) {\n    return {\n      attributes: {},\n      body: string,\n    };\n  }\n\n  const yaml = match[match.length - 1].replace(/^\\s+|\\s+$/g, '');\n  const attributes = parser(yaml) || {};\n  const body = string.replace(match[0], '');\n\n  return { attributes: attributes, body: body, frontmatter: yaml };\n}\n\nexport default extractor;\n"
  },
  {
    "path": "src/plugins/front-matter/yaml.js",
    "content": "// @ts-nocheck\n/**\n * Forked from https://github.com/egoist/docute/blob/master/src/utils/yaml.js\n */\n/* eslint-disable */\n/*\nYAML parser for Javascript\nAuthor: Diogo Costa\nThis program is released under the MIT License as follows:\nCopyright (c) 2011 Diogo Costa (costa.h4evr@gmail.com)\nPermission is hereby granted, free of charge, to any person obtaining a copy\n of this software and associated documentation files (the \"Software\"), to deal\n in the Software without restriction, including without limitation the rights\n to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n copies of the Software, and to permit persons to whom the Software is\n furnished to do so, subject to the following conditions:\nThe above copyright notice and this permission notice shall be included in\n all copies or substantial portions of the Software.\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n THE SOFTWARE.\n*/\n\n/**\n * @name YAML\n * @namespace\n */\n\nvar errors = [],\n  reference_blocks = [],\n  processing_time = 0,\n  regex = {\n    regLevel: new RegExp('^([\\\\s\\\\-]+)'),\n    invalidLine: new RegExp('^\\\\-\\\\-\\\\-|^\\\\.\\\\.\\\\.|^\\\\s*#.*|^\\\\s*$'),\n    dashesString: new RegExp('^\\\\s*\\\\\"([^\\\\\"]*)\\\\\"\\\\s*$'),\n    quotesString: new RegExp(\"^\\\\s*\\\\'([^\\\\']*)\\\\'\\\\s*$\"),\n    float: new RegExp('^[+-]?[0-9]+\\\\.[0-9]+(e[+-]?[0-9]+(\\\\.[0-9]+)?)?$'),\n    integer: new RegExp('^[+-]?[0-9]+$'),\n    array: new RegExp('\\\\[\\\\s*(.*)\\\\s*\\\\]'),\n    map: new RegExp('\\\\{\\\\s*(.*)\\\\s*\\\\}'),\n    key_value: new RegExp('([a-z0-9_-][ a-z0-9_-]*):( .+)', 'i'),\n    single_key_value: new RegExp('^([a-z0-9_-][ a-z0-9_-]*):( .+?)$', 'i'),\n    key: new RegExp('([a-z0-9_-][ a-z0-9_-]+):( .+)?', 'i'),\n    item: new RegExp('^-\\\\s+'),\n    trim: new RegExp('^\\\\s+|\\\\s+$'),\n    comment: new RegExp(\n      '([^\\\\\\'\\\\\"#]+([\\\\\\'\\\\\"][^\\\\\\'\\\\\"]*[\\\\\\'\\\\\"])*)*(#.*)?',\n    ),\n  };\n\n/**\n * @class A block of lines of a given level.\n * @param {int} lvl The block's level.\n */\nfunction Block(lvl) {\n  return {\n    /* The block's parent */\n    parent: null,\n    /* Number of children */\n    length: 0,\n    /* Block's level */\n    level: lvl,\n    /* Lines of code to process */\n    lines: [],\n    /* Blocks with greater level */\n    children: [],\n    /* Add a block to the children collection */\n    addChild: function (obj) {\n      this.children.push(obj);\n      obj.parent = this;\n      ++this.length;\n    },\n  };\n}\n\nfunction parser(str) {\n  var regLevel = regex['regLevel'];\n  var invalidLine = regex['invalidLine'];\n  var lines = str.split('\\n');\n  var m;\n  var level = 0,\n    curLevel = 0;\n\n  var blocks = [];\n\n  var result = new Block(-1);\n  var currentBlock = new Block(0);\n  result.addChild(currentBlock);\n  var levels = [];\n  var line = '';\n\n  blocks.push(currentBlock);\n  levels.push(level);\n\n  for (var i = 0, len = lines.length; i < len; ++i) {\n    line = lines[i];\n\n    if (line.match(invalidLine)) {\n      continue;\n    }\n\n    if ((m = regLevel.exec(line))) {\n      level = m[1].length;\n    } else level = 0;\n\n    if (level > curLevel) {\n      var oldBlock = currentBlock;\n      currentBlock = new Block(level);\n      oldBlock.addChild(currentBlock);\n      blocks.push(currentBlock);\n      levels.push(level);\n    } else if (level < curLevel) {\n      var added = false;\n\n      var k = levels.length - 1;\n      for (; k >= 0; --k) {\n        if (levels[k] == level) {\n          currentBlock = new Block(level);\n          blocks.push(currentBlock);\n          levels.push(level);\n          if (blocks[k].parent != null) blocks[k].parent.addChild(currentBlock);\n          added = true;\n          break;\n        }\n      }\n\n      if (!added) {\n        errors.push('Error: Invalid indentation at line ' + i + ': ' + line);\n        return;\n      }\n    }\n\n    currentBlock.lines.push(line.replace(regex['trim'], ''));\n    curLevel = level;\n  }\n\n  return result;\n}\n\nfunction processValue(val) {\n  val = val.replace(regex['trim'], '');\n  var m = null;\n\n  if (val == 'true') {\n    return true;\n  } else if (val == 'false') {\n    return false;\n  } else if (val == '.NaN') {\n    return Number.NaN;\n  } else if (val == 'null') {\n    return null;\n  } else if (val == '.inf') {\n    return Number.POSITIVE_INFINITY;\n  } else if (val == '-.inf') {\n    return Number.NEGATIVE_INFINITY;\n  } else if ((m = val.match(regex['dashesString']))) {\n    return m[1];\n  } else if ((m = val.match(regex['quotesString']))) {\n    return m[1];\n  } else if ((m = val.match(regex['float']))) {\n    return parseFloat(m[0]);\n  } else if ((m = val.match(regex['integer']))) {\n    return parseInt(m[0]);\n  } else if (!isNaN((m = Date.parse(val)))) {\n    return new Date(m);\n  } else if ((m = val.match(regex['single_key_value']))) {\n    var res = {};\n    res[m[1]] = processValue(m[2]);\n    return res;\n  } else if ((m = val.match(regex['array']))) {\n    var count = 0,\n      c = ' ';\n    var res = [];\n    var content = '';\n    var str = false;\n    for (var j = 0, lenJ = m[1].length; j < lenJ; ++j) {\n      c = m[1][j];\n      if (c == \"'\" || c == '\"') {\n        if (str === false) {\n          str = c;\n          content += c;\n          continue;\n        } else if ((c == \"'\" && str == \"'\") || (c == '\"' && str == '\"')) {\n          str = false;\n          content += c;\n          continue;\n        }\n      } else if (str === false && (c == '[' || c == '{')) {\n        ++count;\n      } else if (str === false && (c == ']' || c == '}')) {\n        --count;\n      } else if (str === false && count == 0 && c == ',') {\n        res.push(processValue(content));\n        content = '';\n        continue;\n      }\n\n      content += c;\n    }\n\n    if (content.length > 0) res.push(processValue(content));\n    return res;\n  } else if ((m = val.match(regex['map']))) {\n    var count = 0,\n      c = ' ';\n    var res = [];\n    var content = '';\n    var str = false;\n    for (var j = 0, lenJ = m[1].length; j < lenJ; ++j) {\n      c = m[1][j];\n      if (c == \"'\" || c == '\"') {\n        if (str === false) {\n          str = c;\n          content += c;\n          continue;\n        } else if ((c == \"'\" && str == \"'\") || (c == '\"' && str == '\"')) {\n          str = false;\n          content += c;\n          continue;\n        }\n      } else if (str === false && (c == '[' || c == '{')) {\n        ++count;\n      } else if (str === false && (c == ']' || c == '}')) {\n        --count;\n      } else if (str === false && count == 0 && c == ',') {\n        res.push(content);\n        content = '';\n        continue;\n      }\n\n      content += c;\n    }\n\n    if (content.length > 0) res.push(content);\n\n    var newRes = {};\n    for (var j = 0, lenJ = res.length; j < lenJ; ++j) {\n      if ((m = res[j].match(regex['key_value']))) {\n        newRes[m[1]] = processValue(m[2]);\n      }\n    }\n\n    return newRes;\n  } else return val;\n}\n\nfunction processFoldedBlock(block) {\n  var lines = block.lines;\n  var children = block.children;\n  var str = lines.join(' ');\n  var chunks = [str];\n  for (var i = 0, len = children.length; i < len; ++i) {\n    chunks.push(processFoldedBlock(children[i]));\n  }\n  return chunks.join('\\n');\n}\n\nfunction processLiteralBlock(block) {\n  var lines = block.lines;\n  var children = block.children;\n  var str = lines.join('\\n');\n  for (var i = 0, len = children.length; i < len; ++i) {\n    str += processLiteralBlock(children[i]);\n  }\n  return str;\n}\n\nfunction processBlock(blocks) {\n  var m = null;\n  var res = {};\n  var lines = null;\n  var children = null;\n  var currentObj = null;\n\n  var level = -1;\n\n  var processedBlocks = [];\n\n  var isMap = true;\n\n  for (var j = 0, lenJ = blocks.length; j < lenJ; ++j) {\n    if (level != -1 && level != blocks[j].level) continue;\n\n    processedBlocks.push(j);\n\n    level = blocks[j].level;\n    lines = blocks[j].lines;\n    children = blocks[j].children;\n    currentObj = null;\n\n    for (var i = 0, len = lines.length; i < len; ++i) {\n      var line = lines[i];\n\n      if ((m = line.match(regex['key']))) {\n        var key = m[1];\n\n        if (key[0] == '-') {\n          key = key.replace(regex['item'], '');\n          if (isMap) {\n            isMap = false;\n            if (typeof res.length === 'undefined') {\n              res = [];\n            }\n          }\n          if (currentObj != null) res.push(currentObj);\n          currentObj = {};\n          isMap = true;\n        }\n\n        if (typeof m[2] != 'undefined') {\n          var value = m[2].replace(regex['trim'], '');\n          if (value[0] == '&') {\n            var nb = processBlock(children);\n            if (currentObj != null) currentObj[key] = nb;\n            else res[key] = nb;\n            reference_blocks[value.substr(1)] = nb;\n          } else if (value[0] == '|') {\n            if (currentObj != null)\n              currentObj[key] = processLiteralBlock(children.shift());\n            else res[key] = processLiteralBlock(children.shift());\n          } else if (value[0] == '*') {\n            var v = value.substr(1);\n            var no = {};\n\n            if (typeof reference_blocks[v] == 'undefined') {\n              errors.push(\"Reference '\" + v + \"' not found!\");\n            } else {\n              for (var k in reference_blocks[v]) {\n                no[k] = reference_blocks[v][k];\n              }\n\n              if (currentObj != null) currentObj[key] = no;\n              else res[key] = no;\n            }\n          } else if (value[0] == '>') {\n            if (currentObj != null)\n              currentObj[key] = processFoldedBlock(children.shift());\n            else res[key] = processFoldedBlock(children.shift());\n          } else {\n            if (currentObj != null) currentObj[key] = processValue(value);\n            else res[key] = processValue(value);\n          }\n        } else {\n          if (currentObj != null) currentObj[key] = processBlock(children);\n          else res[key] = processBlock(children);\n        }\n      } else if (line.match(/^-\\s*$/)) {\n        if (isMap) {\n          isMap = false;\n          if (typeof res.length === 'undefined') {\n            res = [];\n          }\n        }\n        if (currentObj != null) res.push(currentObj);\n        currentObj = {};\n        isMap = true;\n        continue;\n      } else if ((m = line.match(/^-\\s*(.*)/))) {\n        if (currentObj != null) currentObj.push(processValue(m[1]));\n        else {\n          if (isMap) {\n            isMap = false;\n            if (typeof res.length === 'undefined') {\n              res = [];\n            }\n          }\n          res.push(processValue(m[1]));\n        }\n        continue;\n      }\n    }\n\n    if (currentObj != null) {\n      if (isMap) {\n        isMap = false;\n        if (typeof res.length === 'undefined') {\n          res = [];\n        }\n      }\n      res.push(currentObj);\n    }\n  }\n\n  for (var j = processedBlocks.length - 1; j >= 0; --j) {\n    blocks.splice.call(blocks, processedBlocks[j], 1);\n  }\n\n  return res;\n}\n\nfunction semanticAnalysis(blocks) {\n  var res = processBlock(blocks.children);\n  return res;\n}\n\nfunction preProcess(src) {\n  var m;\n  var lines = src.split('\\n');\n\n  var r = regex['comment'];\n\n  for (var i in lines) {\n    if ((m = lines[i].match(r))) {\n      /*                var cmt = \"\";\n            if(typeof m[3] != \"undefined\")\n                lines[i] = m[1];\n            else if(typeof m[3] != \"undefined\")\n                lines[i] = m[3];\n            else\n                lines[i] = \"\";\n                */\n      if (typeof m[3] !== 'undefined') {\n        lines[i] = m[0].substr(0, m[0].length - m[3].length);\n      }\n    }\n  }\n\n  return lines.join('\\n');\n}\n\nfunction load(str) {\n  errors = [];\n  reference_blocks = [];\n  processing_time = new Date().getTime();\n  var pre = preProcess(str);\n  var doc = parser(pre);\n  var res = semanticAnalysis(doc);\n  processing_time = new Date().getTime() - processing_time;\n\n  return res;\n}\n\nexport default load;\n"
  },
  {
    "path": "src/plugins/ga.js",
    "content": "// From https://github.com/egoist/vue-ga/blob/master/src/index.js\nfunction appendScript() {\n  const script = document.createElement('script');\n  script.async = true;\n  script.src = 'https://www.google-analytics.com/analytics.js';\n  document.body.appendChild(script);\n}\n\nconst window = /** @type {any} */ (globalThis);\n\nfunction init(id) {\n  appendScript();\n  window.ga =\n    window.ga ||\n    function () {\n      (window.ga.q = window.ga.q || []).push(arguments);\n    };\n\n  window.ga.l = Number(new Date());\n  window.ga('create', id, 'auto');\n}\n\nfunction collect() {\n  if (!window.ga) {\n    init(window.$docsify.ga);\n  }\n  window.ga('set', 'page', location.hash);\n  window.ga('send', 'pageview');\n}\n\nconst install = function (hook) {\n  if (!window.$docsify.ga) {\n    // eslint-disable-next-line no-console\n    console.error('[Docsify] ga is required.');\n    return;\n  }\n\n  hook.beforeEach(collect);\n};\n\nwindow.$docsify = window.$docsify || {};\nwindow.$docsify.plugins = [install, ...(window.$docsify?.plugins || [])];\n\nexport {};\n"
  },
  {
    "path": "src/plugins/gitalk.js",
    "content": "const window = /** @type {any} */ (globalThis);\n\nfunction install(hook) {\n  const dom = Docsify.dom;\n\n  hook.mounted(_ => {\n    const div = dom.create('div');\n    div.id = 'gitalk-container';\n    const main = dom.getNode('#main');\n    div.style = `width: ${main.clientWidth}px; margin: 0 auto 20px;`;\n    dom.appendTo(dom.find('.content'), div);\n  });\n\n  hook.doneEach(_ => {\n    const el = /** @type {HTMLElement} */ (\n      document.getElementById('gitalk-container')\n    );\n    while (el.hasChildNodes()) {\n      el.removeChild(/** @type {Node} */ (el.firstChild));\n    }\n\n    window.gitalk.render('gitalk-container');\n  });\n}\n\nwindow.$docsify = window.$docsify || {};\nwindow.$docsify.plugins = [install, ...(window.$docsify.plugins || [])];\n\nexport {};\n"
  },
  {
    "path": "src/plugins/gtag.js",
    "content": "// From ./ga.js\n\nfunction appendScript(id) {\n  const script = document.createElement('script');\n  script.async = true;\n  script.src = 'https://www.googletagmanager.com/gtag/js?id=' + id;\n  document.body.appendChild(script);\n}\n\nconst window = /** @type {any} */ (globalThis);\n\n// global site tag instance initialized\nfunction initGlobalSiteTag(id) {\n  appendScript(id);\n\n  window.dataLayer = window.dataLayer || [];\n  window.gtag =\n    window.gtag ||\n    function () {\n      window.dataLayer.push(arguments);\n    };\n\n  window.gtag('js', new Date());\n  window.gtag('config', id);\n}\n\n// add additional products to your tag\n// https://developers.google.com/tag-platform/gtagjs/install\nfunction initAdditionalTag(id) {\n  window.gtag('config', id);\n}\n\nfunction init(ids) {\n  if (Array.isArray(ids)) {\n    // set the first id to be a global site tag\n    initGlobalSiteTag(ids[0]);\n\n    // the rest ids\n    ids.forEach((id, index) => {\n      if (index > 0) {\n        initAdditionalTag(id);\n      }\n    });\n  } else {\n    initGlobalSiteTag(ids);\n  }\n}\n\nfunction collect() {\n  if (!window.gtag) {\n    init(window.$docsify.gtag);\n  }\n\n  // usage: https://developers.google.com/analytics/devguides/collection/gtagjs/pages\n  window.gtag('event', 'page_view', {\n    page_title: document.title,\n    page_location: location.href,\n    page_path: location.pathname,\n  });\n}\n\nconst install = function (hook) {\n  if (!window.$docsify.gtag) {\n    // eslint-disable-next-line no-console\n    console.error('[Docsify] gtag is required.');\n    return;\n  }\n\n  hook.beforeEach(collect);\n};\n\nwindow.$docsify = window.$docsify || {};\nwindow.$docsify.plugins = [install, ...(window.$docsify.plugins || [])];\n\nexport {};\n"
  },
  {
    "path": "src/plugins/matomo.js",
    "content": "function appendScript(options) {\n  const script = document.createElement('script');\n  script.async = true;\n  script.src = options.host + '/matomo.js';\n  document.body.appendChild(script);\n}\n\nconst window = /** @type {any} */ (globalThis);\n\nfunction init(options) {\n  window._paq = window._paq || [];\n  window._paq.push(['trackPageView']);\n  window._paq.push(['enableLinkTracking']);\n  setTimeout(() => {\n    appendScript(options);\n    window._paq.push(['setTrackerUrl', options.host + '/matomo.php']);\n    window._paq.push(['setSiteId', String(options.id)]);\n  }, 0);\n}\n\nfunction collect() {\n  if (!window._paq) {\n    init(window.$docsify.matomo);\n  }\n  window._paq.push(['setCustomUrl', window.location.hash.substr(1)]);\n  window._paq.push(['setDocumentTitle', document.title]);\n  window._paq.push(['trackPageView']);\n}\n\nconst install = function (hook) {\n  if (!window.$docsify.matomo) {\n    // eslint-disable-next-line no-console\n    console.error('[Docsify] matomo is required.');\n    return;\n  }\n\n  hook.beforeEach(collect);\n};\n\nwindow.$docsify = window.$docsify || {};\nwindow.$docsify.plugins = [install, ...(window.$docsify.plugins || [])];\n\nexport {};\n"
  },
  {
    "path": "src/plugins/search/component.js",
    "content": "import { escapeHtml, search } from './search.js';\nimport cssText from './style.css';\n\nlet NO_DATA_TEXT = '';\n\nfunction tpl(vm, defaultValue = '') {\n  const { insertAfter, insertBefore } = vm.config?.search || {};\n  const html = /* html */ `\n    <div class=\"input-wrap\">\n      <input type=\"search\" value=\"${defaultValue}\" required aria-keyshortcuts=\"/ control+k meta+k\" />\n      <button class=\"clear-button\" title=\"Clear search\">\n        <span class=\"visually-hidden\">Clear search</span>\n      </button>\n      <div class=\"kbd-group\">\n        <kbd title=\"Press / to search\">/</kbd>\n        <kbd title=\"Press Control+K to search\">⌃K</kbd>\n      </div>\n    </div>\n    <p class=\"results-status\" aria-live=\"polite\"></p>\n    <div class=\"results-panel\"></div>\n  `;\n  const sidebarElm = Docsify.dom.find('.sidebar');\n  const searchElm = Docsify.dom.create('section', html);\n  const insertElm = /** @type {HTMLElement} */ (\n    sidebarElm.querySelector(\n      `:scope ${insertAfter || insertBefore || '> :first-child'}`,\n    )\n  );\n\n  searchElm.classList.add('search');\n  searchElm.setAttribute('role', 'search');\n  sidebarElm.insertBefore(\n    searchElm,\n    insertAfter ? insertElm.nextSibling : insertElm,\n  );\n}\n\nfunction doSearch(value) {\n  const $search = Docsify.dom.find('.search');\n  const $panel = Docsify.dom.find($search, '.results-panel');\n  const $status = Docsify.dom.find('.search .results-status');\n\n  if (!value) {\n    $panel.innerHTML = '';\n    $status.textContent = '';\n\n    return;\n  }\n\n  const matches = search(value);\n\n  let html = '';\n  matches.forEach((post, i) => {\n    const content = post.content ? `...${post.content}...` : '';\n    const title = (post.title || '').replace(/<[^>]+>/g, '');\n    html += /* html */ `\n      <div class=\"matching-post\" aria-label=\"search result ${i + 1}\">\n        <a href=\"${post.url}\" title=\"${title}\">\n          <p class=\"title clamp-1\">${post.title}</p>\n          <p class=\"content clamp-2\">${content}</p>\n        </a>\n      </div>\n    `;\n  });\n\n  $panel.innerHTML = html || '';\n  $status.textContent = matches.length\n    ? `Found ${matches.length} results`\n    : NO_DATA_TEXT;\n}\n\nfunction bindEvents() {\n  const $search = Docsify.dom.find('.search');\n  const $input = /** @type {HTMLInputElement} */ (\n    Docsify.dom.find($search, 'input')\n  );\n  const $clear = Docsify.dom.find($search, '.clear-button');\n\n  let timeId;\n\n  /**\n   * Prevent to Fold sidebar.\n   *\n   * When searching on the mobile end,\n   * the sidebar is collapsed when you click the INPUT box,\n   * making it impossible to search.\n   */\n  Docsify.dom.on(\n    $search,\n    'click',\n    e =>\n      ['A', 'H2', 'P', 'EM'].indexOf(e.target.tagName) === -1 &&\n      e.stopPropagation(),\n  );\n  Docsify.dom.on($input, 'input', e => {\n    clearTimeout(timeId);\n    timeId = setTimeout(\n      _ => doSearch(/** @type {HTMLInputElement} */ (e.target).value.trim()),\n      100,\n    );\n  });\n  Docsify.dom.on($clear, 'click', e => {\n    $input.value = '';\n    doSearch();\n  });\n}\n\nfunction updatePlaceholder(text, path) {\n  const $input = /** @type {HTMLInputElement | null} */ (\n    Docsify.dom.getNode('.search input[type=\"search\"]')\n  );\n\n  if (!$input) {\n    return;\n  }\n\n  if (typeof text === 'string') {\n    $input.placeholder = text;\n  } else {\n    const match = Object.keys(text).filter(key => path.indexOf(key) > -1)[0];\n    $input.placeholder = text[match];\n  }\n}\n\nfunction updateNoData(text, path) {\n  if (typeof text === 'string') {\n    NO_DATA_TEXT = text;\n  } else {\n    const match = Object.keys(text).filter(key => path.indexOf(key) > -1)[0];\n    NO_DATA_TEXT = text[match];\n  }\n}\n\nexport function init(opts, vm) {\n  const sidebarElm = Docsify.dom.find('.sidebar');\n\n  if (!sidebarElm) {\n    return;\n  }\n\n  const keywords = vm.router.parse().query.s || '';\n\n  Docsify.dom.style(cssText);\n  tpl(vm, escapeHtml(keywords));\n  bindEvents();\n  keywords && setTimeout(_ => doSearch(keywords), 500);\n}\n\nexport function update(opts, vm) {\n  updatePlaceholder(opts.placeholder, vm.route.path);\n  updateNoData(opts.noData, vm.route.path);\n}\n"
  },
  {
    "path": "src/plugins/search/index.js",
    "content": "import {\n  init as initComponent,\n  update as updateComponent,\n} from './component.js';\nimport { init as initSearch } from './search.js';\n\n/**\n * @type {{\n *   placeholder: string;\n *   noData: string;\n *   paths: string[] | 'auto';\n *   depth: number;\n *   maxAge: number;\n *   namespace?: string;\n *   pathNamespaces?: RegExp | string[];\n *   keyBindings: string[];\n *   insertAfter?: string;\n *   insertBefore?: string;\n * }} */\nconst CONFIG = {\n  placeholder: 'Type to search',\n  noData: 'No Results!',\n  paths: 'auto',\n  depth: 2,\n  maxAge: 86400000, // 1 day\n  namespace: undefined,\n  pathNamespaces: undefined,\n  keyBindings: ['/', 'meta+k', 'ctrl+k'],\n  insertAfter: undefined, // CSS selector\n  insertBefore: undefined, // CSS selector\n};\n\nconst install = function (hook, vm) {\n  const { util } = Docsify;\n  const opts = vm.config.search || CONFIG;\n\n  if (Array.isArray(opts)) {\n    CONFIG.paths = opts;\n  } else if (typeof opts === 'object') {\n    CONFIG.paths = Array.isArray(opts.paths) ? opts.paths : 'auto';\n    CONFIG.maxAge = util.isPrimitive(opts.maxAge) ? opts.maxAge : CONFIG.maxAge;\n    CONFIG.placeholder = opts.placeholder || CONFIG.placeholder;\n    CONFIG.noData = opts.noData || CONFIG.noData;\n    CONFIG.depth = opts.depth || CONFIG.depth;\n    CONFIG.namespace = opts.namespace || CONFIG.namespace;\n    CONFIG.pathNamespaces = opts.pathNamespaces || CONFIG.pathNamespaces;\n    CONFIG.keyBindings = opts.keyBindings || CONFIG.keyBindings;\n  }\n\n  const isAuto = CONFIG.paths === 'auto';\n\n  hook.init(() => {\n    const { keyBindings } = vm.config;\n\n    // Add key bindings\n    if (keyBindings.constructor === Object) {\n      keyBindings.focusSearch = {\n        bindings: CONFIG.keyBindings,\n        callback(e) {\n          const sidebarElm = document.querySelector('.sidebar');\n          const sidebarToggleElm = /** @type {HTMLElement} */ (\n            document.querySelector('.sidebar-toggle')\n          );\n          const searchElm = /** @type {HTMLInputElement | null} */ (\n            sidebarElm?.querySelector('input[type=\"search\"]')\n          );\n          const isSidebarHidden =\n            (sidebarElm?.getBoundingClientRect().x ?? 0) < 0;\n\n          isSidebarHidden && sidebarToggleElm?.click();\n\n          setTimeout(() => searchElm?.focus(), isSidebarHidden ? 250 : 0);\n        },\n      };\n    }\n  });\n  hook.mounted(_ => {\n    initComponent(CONFIG, vm);\n    !isAuto && initSearch(CONFIG, vm);\n  });\n  hook.doneEach(_ => {\n    updateComponent(CONFIG, vm);\n    isAuto && initSearch(CONFIG, vm);\n  });\n};\n\nwindow.$docsify = window.$docsify || {};\nwindow.$docsify.plugins = [install, ...(window.$docsify.plugins || [])];\n"
  },
  {
    "path": "src/plugins/search/markdown-to-txt.js",
    "content": "/**\n * This is a function to convert markdown to txt based on markedjs v13+.\n * Copies the escape/unescape functions from [lodash](https://www.npmjs.com/package/lodash) instead import to reduce the size.\n */\nimport { marked } from 'marked';\n\nconst reEscapedHtml = /&(?:amp|lt|gt|quot|#(0+)?39);/g;\nconst reHasEscapedHtml = RegExp(reEscapedHtml.source);\nconst htmlUnescapes = {\n  '&amp;': '&',\n  '&lt;': '<',\n  '&gt;': '>',\n  '&quot;': '\"',\n  '&#39;': \"'\",\n};\n\nfunction unescape(string) {\n  return string && reHasEscapedHtml.test(string)\n    ? string.replace(reEscapedHtml, entity => htmlUnescapes[entity] || \"'\")\n    : string || '';\n}\n\nconst reUnescapedHtml = /[&<>\"']/g;\nconst reHasUnescapedHtml = RegExp(reUnescapedHtml.source);\nconst htmlEscapes = {\n  '&': '&amp;',\n  '<': '&lt;',\n  '>': '&gt;',\n  '\"': '&quot;',\n  \"'\": '&#39;',\n};\n\nfunction escape(string) {\n  return string && reHasUnescapedHtml.test(string)\n    ? string.replace(reUnescapedHtml, chr => htmlEscapes[chr])\n    : string || '';\n}\n\nfunction helpersCleanup(string) {\n  return string && string.replace('!>', '').replace('?>', '');\n}\n\nconst markdownToTxtRenderer = {\n  space() {\n    return '';\n  },\n\n  code({ text }) {\n    const code = text.replace(/\\n$/, '');\n    return escape(code);\n  },\n\n  blockquote({ tokens }) {\n    return this.parser?.parse(tokens) || '';\n  },\n\n  html() {\n    return '';\n  },\n\n  heading({ tokens }) {\n    return this.parser?.parseInline(tokens) || '';\n  },\n\n  hr() {\n    return '';\n  },\n\n  list(token) {\n    let body = '';\n    for (let j = 0; j < token.items.length; j++) {\n      const item = token.items[j];\n      body += this.listitem?.(item);\n    }\n\n    return body;\n  },\n\n  listitem(item) {\n    let itemBody = '';\n    if (item.task) {\n      const checkbox = this.checkbox?.({\n        checked: !!item.checked,\n      });\n      if (item.loose) {\n        if (item.tokens.length > 0 && item.tokens[0].type === 'paragraph') {\n          item.tokens[0].text = checkbox + ' ' + item.tokens[0].text;\n          if (\n            item.tokens[0].tokens &&\n            item.tokens[0].tokens.length > 0 &&\n            item.tokens[0].tokens[0].type === 'text'\n          ) {\n            item.tokens[0].tokens[0].text =\n              checkbox + ' ' + item.tokens[0].tokens[0].text;\n          }\n        } else {\n          item.tokens.unshift({\n            type: 'text',\n            raw: checkbox + ' ',\n            text: checkbox + ' ',\n          });\n        }\n      } else {\n        itemBody += checkbox + ' ';\n      }\n    }\n\n    itemBody += this.parser?.parse(item.tokens, !!item.loose);\n\n    return `${itemBody || ''}`;\n  },\n\n  /**\n   * @param {{checked: boolean}} options\n   * @return {string}\n   */\n  checkbox(options) {\n    return '';\n  },\n\n  paragraph({ tokens }) {\n    return this.parser?.parseInline(tokens) || '';\n  },\n\n  table(token) {\n    let header = '';\n\n    let cell = '';\n    for (let j = 0; j < token.header.length; j++) {\n      cell += this.tablecell?.(token.header[j]);\n    }\n    header += this.tablerow?.({ text: cell });\n\n    let body = '';\n    for (let j = 0; j < token.rows.length; j++) {\n      const row = token.rows[j];\n\n      cell = '';\n      for (let k = 0; k < row.length; k++) {\n        cell += this.tablecell?.(row[k]);\n      }\n\n      body += this.tablerow?.({ text: cell });\n    }\n\n    return header + ' ' + body;\n  },\n\n  tablerow({ text }) {\n    return text;\n  },\n\n  tablecell(token) {\n    return this.parser?.parseInline(token.tokens) || '';\n  },\n\n  strong({ text }) {\n    return text;\n  },\n\n  em({ tokens }) {\n    return this.parser?.parseInline(tokens) || '';\n  },\n\n  codespan({ text }) {\n    return text;\n  },\n\n  br() {\n    return ' ';\n  },\n\n  del({ tokens }) {\n    return this.parser?.parseInline(tokens);\n  },\n\n  link({ tokens, href, title }) {\n    // Remain the href and title attributes for searching, so is the image\n    // e.g. [filename](_media/example.js ':include :type=code :fragment=demo')\n    // Result: filename _media/example.js :include :type=code :fragment=demo\n    return `${this.parser?.parseInline(tokens) || ''} ${href || ''} ${title || ''}`;\n  },\n\n  image({ title, text, href }) {\n    return `${text || ''} ${href || ''} ${title || ''}`;\n  },\n\n  text(token) {\n    return token.tokens\n      ? this.parser?.parseInline(token.tokens) || ''\n      : token.text || '';\n  },\n};\nconst _marked = marked.setOptions({\n  // @ts-expect-error missing properties\n  renderer: markdownToTxtRenderer,\n});\n\nexport function markdownToTxt(markdown) {\n  const unmarked = _marked.parse(markdown);\n  const unescaped = unescape(unmarked);\n  const helpersCleaned = helpersCleanup(unescaped);\n  return helpersCleaned.trim();\n}\n\nexport default markdownToTxt;\n"
  },
  {
    "path": "src/plugins/search/search.js",
    "content": "import {\n  getAndRemoveConfig,\n  getAndRemoveDocsifyIgnoreConfig,\n  removeAtag,\n} from '../../core/render/utils.js';\nimport { markdownToTxt } from './markdown-to-txt.js';\nimport Dexie from 'dexie';\n\nlet INDEXES = [];\n\nconst db = new Dexie('docsify');\ndb.version(1).stores({\n  search: 'slug, title, body, path, indexKey',\n  expires: 'key, value',\n});\n\nasync function saveData(maxAge, expireKey) {\n  INDEXES = Object.values(INDEXES).flatMap(innerData =>\n    Object.values(innerData),\n  );\n  await /** @type {any} */ (db).search.bulkPut(INDEXES);\n  await /** @type {any} */ (db).expires.put({\n    key: expireKey,\n    value: Date.now() + maxAge,\n  });\n}\n\nasync function getData(key, isExpireKey = false) {\n  if (isExpireKey) {\n    const item = await /** @type {any} */ (db).expires.get(key);\n    return item ? item.value : 0;\n  }\n\n  const item = await /** @type {any} */ (db).search\n    .where({ indexKey: key })\n    .toArray();\n  return item ? item : null;\n}\n\nconst LOCAL_STORAGE = {\n  EXPIRE_KEY: 'docsify.search.expires',\n  INDEX_KEY: 'docsify.search.index',\n};\n\nfunction resolveExpireKey(namespace) {\n  return namespace\n    ? `${LOCAL_STORAGE.EXPIRE_KEY}/${namespace}`\n    : LOCAL_STORAGE.EXPIRE_KEY;\n}\n\nfunction resolveIndexKey(namespace) {\n  return namespace\n    ? `${LOCAL_STORAGE.INDEX_KEY}/${namespace}`\n    : LOCAL_STORAGE.INDEX_KEY;\n}\n\nexport function escapeHtml(string) {\n  const entityMap = {\n    '&': '&amp;',\n    '<': '&lt;',\n    '>': '&gt;',\n    '\"': '&quot;',\n    \"'\": '&#39;',\n  };\n\n  return String(string).replace(/[&<>\"']/g, s => entityMap[s]);\n}\n\nfunction getAllPaths(router) {\n  const paths = [];\n\n  Docsify.dom\n    .findAll('.sidebar-nav a:not(.section-link):not([data-nosearch])')\n    .forEach(node => {\n      const href = /** @type {HTMLAnchorElement} */ (node).href;\n      const originHref = /** @type {HTMLAnchorElement} */ (node).getAttribute(\n        'href',\n      );\n      const path = router.parse(href).path;\n\n      if (\n        path &&\n        paths.indexOf(path) === -1 &&\n        !Docsify.util.isAbsolutePath(originHref)\n      ) {\n        paths.push(path);\n      }\n    });\n\n  return paths;\n}\n\nfunction getTableData(token) {\n  if (!token.text && token.type === 'table') {\n    token.rows.unshift(token.header);\n    token.text = token.rows\n      .map(columns => columns.map(r => r.text).join(' | '))\n      .join(' |\\n ');\n  }\n  return token.text;\n}\n\nfunction getListData(token) {\n  if (!token.text && token.type === 'list') {\n    token.text = token.raw;\n  }\n  return token.text;\n}\n\nexport function genIndex(path, content = '', router, depth, indexKey) {\n  const tokens = window.marked.lexer(content);\n  const slugify = window.Docsify.slugify;\n  /** @type {Record<string, any>} */\n  const index = {};\n  let slug;\n  let title = '';\n\n  tokens.forEach((token, tokenIndex) => {\n    if (token.type === 'heading' && token.depth <= depth) {\n      const { str, config } = getAndRemoveConfig(token.text);\n\n      slug = router.toURL(path, { id: slugify(config.id || token.text) });\n\n      if (str) {\n        title = getAndRemoveDocsifyIgnoreConfig(str).content;\n        title = removeAtag(title.trim());\n      }\n\n      index[slug] = {\n        slug,\n        title: title,\n        body: '',\n        path: path,\n        indexKey: indexKey,\n      };\n    } else {\n      if (tokenIndex === 0) {\n        slug = router.toURL(path);\n        index[slug] = {\n          slug,\n          title: path !== '/' ? path.slice(1) : 'Home Page',\n          body: markdownToTxt(/** @type {any} */ (token).text || ''),\n          path: path,\n          indexKey: indexKey,\n        };\n      }\n\n      if (!slug) {\n        return;\n      }\n\n      if (!index[slug]) {\n        index[slug] = { slug, title: '', body: '' };\n      } else if (index[slug].body) {\n        // @ts-expect-error\n        token.text = getTableData(token);\n        // @ts-expect-error\n        token.text = getListData(token);\n\n        // @ts-expect-error\n        index[slug].body += '\\n' + markdownToTxt(token.text || '');\n      } else {\n        // @ts-expect-error\n        token.text = getTableData(token);\n        // @ts-expect-error\n        token.text = getListData(token);\n\n        // @ts-expect-error\n        index[slug].body = markdownToTxt(token.text || '');\n      }\n\n      index[slug].path = path;\n      index[slug].indexKey = indexKey;\n    }\n  });\n  slugify.clear();\n  return index;\n}\n\nexport function ignoreDiacriticalMarks(keyword) {\n  if (keyword && keyword.normalize) {\n    return keyword.normalize('NFD').replace(/[\\u0300-\\u036f]/g, '');\n  }\n  return keyword;\n}\n\n/**\n * @param {String} query Search query\n * @returns {Array} Array of results\n */\nexport function search(query) {\n  const matchingResults = [];\n\n  query = query.trim();\n  let keywords = query.split(/[\\s\\-，\\\\/]+/);\n  if (keywords.length !== 1) {\n    keywords = [query, ...keywords];\n  }\n\n  for (const post of INDEXES) {\n    let matchesScore = 0;\n    let resultStr = '';\n    let handlePostTitle = '';\n    let handlePostContent = '';\n    const postTitle = post.title && post.title.trim();\n    const postContent = post.body && post.body.trim();\n    const postUrl = post.slug || '';\n\n    if (postTitle) {\n      keywords.forEach(keyword => {\n        // From https://github.com/sindresorhus/escape-string-regexp\n        const regEx = new RegExp(\n          escapeHtml(ignoreDiacriticalMarks(keyword)).replace(\n            /[|\\\\{}()[\\]^$+*?.]/g,\n            '\\\\$&',\n          ),\n          'gi',\n        );\n        let indexTitle = -1;\n        let indexContent = -1;\n        handlePostTitle = postTitle\n          ? escapeHtml(ignoreDiacriticalMarks(postTitle))\n          : postTitle;\n        handlePostContent = postContent\n          ? escapeHtml(ignoreDiacriticalMarks(postContent))\n          : postContent;\n\n        indexTitle = postTitle ? handlePostTitle.search(regEx) : -1;\n        indexContent = postContent ? handlePostContent.search(regEx) : -1;\n\n        if (indexTitle >= 0 || indexContent >= 0) {\n          matchesScore += indexTitle >= 0 ? 3 : indexContent >= 0 ? 2 : 0;\n          if (indexContent < 0) {\n            indexContent = 0;\n          }\n\n          let start = 0;\n          let end = 0;\n\n          start = indexContent < 11 ? 0 : indexContent - 10;\n          end = start === 0 ? 100 : indexContent + keyword.length + 90;\n\n          if (handlePostContent && end > handlePostContent.length) {\n            end = handlePostContent.length;\n          }\n\n          const matchContent =\n            handlePostContent &&\n            handlePostContent\n              .substring(start, end)\n              .replace(regEx, word => /* html */ `<mark>${word}</mark>`);\n\n          resultStr += matchContent;\n        }\n      });\n\n      if (matchesScore > 0) {\n        const matchingPost = {\n          title: handlePostTitle,\n          content: postContent ? resultStr : '',\n          url: postUrl,\n          score: matchesScore,\n        };\n\n        matchingResults.push(matchingPost);\n      }\n    }\n  }\n\n  return matchingResults.sort((r1, r2) => r2.score - r1.score);\n}\n\nexport async function init(config, vm) {\n  const isAuto = config.paths === 'auto';\n  const paths = isAuto ? getAllPaths(vm.router) : config.paths;\n\n  let namespaceSuffix = '';\n\n  // only in auto mode\n  if (paths.length && isAuto && config.pathNamespaces) {\n    const path = paths[0];\n\n    if (Array.isArray(config.pathNamespaces)) {\n      namespaceSuffix =\n        config.pathNamespaces.filter(\n          prefix => path.slice(0, prefix.length) === prefix,\n        )[0] || namespaceSuffix;\n    } else if (config.pathNamespaces instanceof RegExp) {\n      const matches = path.match(config.pathNamespaces);\n\n      if (matches) {\n        namespaceSuffix = matches[0];\n      }\n    }\n    const isExistHome = paths.indexOf(namespaceSuffix + '/') === -1;\n    const isExistReadme = paths.indexOf(namespaceSuffix + '/README') === -1;\n    if (isExistHome && isExistReadme) {\n      paths.unshift(namespaceSuffix + '/');\n    }\n  } else if (paths.indexOf('/') === -1 && paths.indexOf('/README') === -1) {\n    paths.unshift('/');\n  }\n\n  const expireKey = resolveExpireKey(config.namespace) + namespaceSuffix;\n  const indexKey = resolveIndexKey(config.namespace) + namespaceSuffix;\n\n  const isExpired = (await getData(expireKey, true)) < Date.now();\n\n  INDEXES = await getData(indexKey);\n\n  if (isExpired) {\n    INDEXES = [];\n  } else if (!isAuto) {\n    return;\n  }\n\n  const len = paths.length;\n  let count = 0;\n\n  paths.forEach(path => {\n    const pathExists = Array.isArray(INDEXES)\n      ? INDEXES.some(obj => obj.path === path)\n      : false;\n    if (pathExists) {\n      return count++;\n    }\n\n    Docsify.get(vm.router.getFile(path), false, vm.config.requestHeaders).then(\n      async result => {\n        INDEXES[path] = genIndex(\n          path,\n          result,\n          vm.router,\n          config.depth,\n          indexKey,\n        );\n        if (len === ++count) {\n          await saveData(config.maxAge, expireKey);\n        }\n      },\n    );\n  });\n}\n"
  },
  {
    "path": "src/plugins/search/style.css",
    "content": "/* prettier-ignore */\n:root {\n  --plugin-search-input-bg           : var(--form-element-bg);\n  --plugin-search-input-border-color : var(--sidebar-border-color);\n  --plugin-search-input-border-radius: var(--form-element-border-radius);\n  --plugin-search-input-color        : var(--form-element-color);\n  --plugin-search-kbd-bg             : var(--color-bg);\n  --plugin-search-kbd-border         : 1px solid var(--color-mono-3);\n  --plugin-search-kbd-border-radius  : 4px;\n  --plugin-search-kbd-color          : var(--color-mono-5);\n  --plugin-search-margin             : 10px;\n  --plugin-search-reset-bg           : var(--theme-color);\n  --plugin-search-reset-border       : transparent;\n  --plugin-search-reset-border-radius: var(--border-radius);\n  --plugin-search-reset-color        : #fff;\n}\n\n.search {\n  margin: var(--plugin-search-margin);\n}\n\n/* Input */\n/* ================================== */\n.search .input-wrap {\n  position: relative;\n}\n\n.search input {\n  width: 100%;\n  padding-inline-end: 36px;\n  border: 1px solid var(--plugin-search-input-border-color);\n  border-radius: var(--plugin-search-input-border-radius);\n  background: var(--plugin-search-input-bg);\n  color: var(--plugin-search-input-color);\n}\n\n.search input::-webkit-search-decoration,\n.search input::-webkit-search-cancel-button {\n  appearance: none;\n}\n\n.search .clear-button,\n.search .kbd-group {\n  visibility: hidden;\n  display: flex;\n  gap: 0.15em;\n  position: absolute;\n  right: 7px;\n  top: 50%;\n  opacity: 0;\n  translate: 0 -50%;\n  transition-property: opacity, visibility;\n  transition-duration: var(--duration-medium);\n}\n\n/* Note: invalid = empty, valid = not empty */\n.search input:valid ~ .clear-button,\n.search input:invalid:where(:focus, :hover) ~ .kbd-group,\n.search .kbd-group:hover {\n  visibility: visible;\n  opacity: 1;\n}\n\n.search .clear-button {\n  --_button-size: 20px;\n  --_content-size: 12px;\n\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: var(--_button-size);\n  width: var(--_button-size);\n  border: var(--plugin-search-reset-border);\n  border-radius: var(--plugin-search-reset-border-radius);\n  background: var(--plugin-search-reset-bg);\n  cursor: pointer;\n}\n\n.search .clear-button::before,\n.search .clear-button::after {\n  content: '';\n  position: absolute;\n  height: 2px;\n  width: var(--_content-size);\n  color: var(--plugin-search-reset-color);\n  background: var(--plugin-search-reset-color);\n}\n\n.search .clear-button::before {\n  rotate: 45deg;\n}\n\n.search .clear-button::after {\n  rotate: -45deg;\n}\n\n.search kbd {\n  border: var(--plugin-search-kbd-border);\n  border-radius: var(--plugin-search-kbd-border-radius);\n  background: var(--plugin-search-kbd-bg);\n  color: var(--plugin-search-kbd-color);\n  font-size: var(--font-size-s);\n}\n\n/* Results */\n/* ================================== */\n.search a:hover {\n  color: var(--theme-color);\n}\n\n.search .results-panel:empty {\n  display: none;\n}\n\n/* Hide other sidebar items when results are shown */\n.search:has(.results-panel:not(:empty)) ~ * {\n  display: none;\n}\n\n/* Dim other sidebar items when no results are found */\n.search:where(:has(input:valid:focus), :has(.results-panel::empty)) ~ * {\n  opacity: 0.2;\n}\n\n.search .matching-post {\n  overflow: hidden;\n  padding: 1em 0 1.2em 0;\n  border-bottom: 1px solid var(--color-mono-2);\n}\n\n.search .matching-post:hover a {\n  text-decoration-color: transparent;\n}\n\n.search .matching-post:hover .title {\n  text-decoration: inherit;\n  text-decoration-color: var(--link-underline-color-hover);\n}\n\n.search .matching-post .title {\n  margin: 0 0 0.5em 0;\n  line-height: 1.4;\n}\n\n.search .matching-post .content {\n  margin: 0;\n  color: var(--color-mono-6);\n  font-size: var(--font-size-s);\n}\n\n.search .results-status {\n  margin-bottom: 0;\n  color: var(--color-mono-6);\n  font-size: var(--font-size-s);\n}\n\n.search .results-status:empty {\n  display: none;\n}\n"
  },
  {
    "path": "src/plugins/zoom-image.js",
    "content": "import mediumZoom from 'medium-zoom';\n\nfunction install(hook) {\n  let zoom;\n\n  hook.doneEach(_ => {\n    let elms = /** @type {HTMLElement[]} */ (\n      Array.from(\n        document.querySelectorAll(\n          '.markdown-section img:not(.emoji):not([data-no-zoom])',\n        ),\n      )\n    );\n\n    Docsify.dom.style(\n      `.medium-zoom-image--opened,.medium-zoom-overlay{z-index:999}`,\n    );\n\n    elms = elms.filter(elm => !elm.matches('a img'));\n\n    if (zoom) {\n      zoom.detach();\n    }\n\n    zoom = mediumZoom(elms, { background: 'var(--color-bg)' });\n  });\n}\n\nwindow.$docsify = window.$docsify || {};\nwindow.$docsify.plugins = [install, ...(window.$docsify.plugins || [])];\n"
  },
  {
    "path": "test/README.md",
    "content": "# Docsify Testing\n\n## Environment\n\n- [Jest](https://jestjs.io): A test framework used for assertions, mocks, spies, etc.\n- [Playwright](https://playwright.dev): A test automation tool for launching browsers and manipulating the DOM.\n\n## Test files\n\n- Unit tests located in `/test/unit/` and use [Jest](https://jestjs.io).\n- Integration tests are located in `/test/integration/` and use [Jest](https://jestjs.io).\n- E2E tests are located in `/test/e2e/` and use [Jest](https://jestjs.io) + [Playwright](https://playwright.dev).\n\n## CLI commands\n\n```bash\n# Run all tests\nnpm t\n\n# Run test types\nnpm run test:e2e\nnpm run test:integration\nnpm run test:unit\n```\n\n### Unit / Integration (Jest)\n\n```bash\n# Run test file(s)\nnpm run test:unit -- -i ./path/to/file.test.js\nnpm run test:unit -- -i ./path/to/*.test.js\n\n# Run test name(s)\nnpm run test:unit -- -t \"my test\"\n\n# Run test name(s) in file\nnpm run test:unit -- -i ./path/to/file.test.js -t \"my test\"\n\n# ------------------------------------------------------------------------------\n\n# Update snapshots\nnpm run test:unit -- -u\n\n# Update snapshots for test file(s)\nnpm run test:unit -- -u -i ./path/to/file.test.js\nnpm run test:unit -- -u -i ./path/to/*.test.js\n\n# Update snapshots for test name(s)\nnpm run test:unit -- -u -t \"my test\"\n\n# Update snapshots for test name(s) in file\nnpm run test:unit -- -u -i ./path/to/file.test.js -t \"my test\"\n```\n\n### E2E (Playwright)\n\n```bash\n# Run test file(s)\nnpm run test:e2e -- ./path/to/file.test.js\nnpm run test:e2e -- ./path/to/*.test.js\n\n# Run test name(s)\nnpm run test:e2e -- -g \"my test\"\n\n# Run test name(s) in file\nnpm run test:e2e -- ./path/to/file.test.js -g \"my test\"\n\n# ------------------------------------------------------------------------------\n\n# Update snapshots\nnpm run test:e2e -- -u\n\n# Update snapshots for test file(s)\nnpm run test:e2e -- -u ./path/to/file.test.js\nnpm run test:e2e -- -u ./path/to/*.test.js\n\n# Update snapshots for test name(s)\nnpm run test:e2e -- -u -g \"my test\"\n\n# Update snapshots for test name(s) in file\nnpm run test:e2e -- -u ./path/to/file.test.js -g \"my test\"\n```\n"
  },
  {
    "path": "test/config/jest.setup-tests.js",
    "content": "/* global afterEach, beforeAll, beforeEach */\n\nimport _mock from 'xhr-mock';\n\nconst mock = _mock.default;\n\nconst sideEffects = {\n  document: {\n    addEventListener: {\n      fn: document.addEventListener,\n      refs: [],\n    },\n    keys: Object.keys(document),\n  },\n  window: {\n    addEventListener: {\n      fn: window.addEventListener,\n      refs: [],\n    },\n    keys: Object.keys(window),\n  },\n};\n\nclass IntersectionObserver {\n  constructor() {}\n\n  root = null;\n  rootMargin = '';\n  thresholds = [];\n\n  disconnect() {\n    return null;\n  }\n\n  observe() {\n    return null;\n  }\n\n  takeRecords() {\n    return [];\n  }\n\n  unobserve() {\n    return null;\n  }\n}\n\n// Lifecycle Hooks\n// =============================================================================\nbeforeAll(async () => {\n  // Spy addEventListener\n  ['document', 'window'].forEach(obj => {\n    const fn = sideEffects[obj].addEventListener.fn;\n    const refs = sideEffects[obj].addEventListener.refs;\n\n    function addEventListenerSpy(type, listener, options) {\n      // Store listener reference so it can be removed during reset\n      refs.push({ type, listener, options });\n      // Call original window.addEventListener\n      fn(type, listener, options);\n    }\n\n    // Add to default key array to prevent removal during reset\n    sideEffects[obj].keys.push('addEventListener');\n\n    // Replace addEventListener with mock\n    global[obj].addEventListener = addEventListenerSpy;\n  });\n\n  if (!global.HTMLElement.prototype.scrollIntoView) {\n    Object.defineProperty(global.HTMLElement.prototype, 'scrollIntoView', {\n      configurable: true,\n      writable: true,\n      value: () => {},\n    });\n  }\n});\n\nbeforeEach(async () => {\n  const rootElm = document.documentElement;\n\n  // Reset JSDOM\n  // -----------------------------------------------------------------------------\n  // This attempts to remove side effects from tests, however it does not reset\n  // all changes made to globals like the window and document objects. Tests\n  // requiring a full JSDOM reset should be stored in separate files, which is\n  // only way to do a complete JSDOM reset with Jest.\n\n  // Remove attributes on root element\n  [...rootElm.attributes].forEach(attr => rootElm.removeAttribute(attr.name));\n\n  // Remove elements (faster than setting innerHTML)\n  while (rootElm.firstChild) {\n    rootElm.removeChild(rootElm.firstChild);\n  }\n\n  // Remove global listeners and keys\n  ['document', 'window'].forEach(obj => {\n    const refs = sideEffects[obj].addEventListener.refs;\n\n    // Listeners\n    while (refs.length) {\n      const { type, listener, options } = refs.pop();\n      global[obj].removeEventListener(type, listener, options);\n    }\n\n    // Keys\n    Object.keys(global[obj])\n      .filter(key => !sideEffects[obj].keys.includes(key))\n      .forEach(key => {\n        delete global[obj][key];\n      });\n  });\n\n  // Restore base elements\n  rootElm.innerHTML = '<head></head><body></body>';\n\n  // Mock IntersectionObserver\n  // -----------------------------------------------------------------------------\n  [global, window].forEach(\n    obj => (obj.IntersectionObserver = IntersectionObserver),\n  );\n});\n\nafterEach(async () => {\n  // Restore the global XMLHttpRequest object to its original state\n  mock.teardown();\n});\n"
  },
  {
    "path": "test/config/jest.setup.js",
    "content": "import { startServer } from './server.js';\n\nexport default async () => {\n  await startServer();\n};\n"
  },
  {
    "path": "test/config/jest.teardown.js",
    "content": "import { stopServer } from './server.js';\n\nexport default async () => {\n  stopServer();\n};\n"
  },
  {
    "path": "test/config/playwright.setup.js",
    "content": "import { startServer } from './server.js';\n\nexport default async config => {\n  startServer();\n};\n"
  },
  {
    "path": "test/config/playwright.teardown.js",
    "content": "import { stopServer } from './server.js';\n\nexport default async config => {\n  stopServer();\n};\n"
  },
  {
    "path": "test/config/server.js",
    "content": "import * as process from 'node:process';\nimport { create } from 'browser-sync';\nimport { testConfig } from '../../server.configs.js';\n\nconst bsServer = create();\n\nexport async function startServer() {\n  // Wait for server to start\n  return new Promise(resolve => {\n    const settings = testConfig;\n\n    console.log('\\n');\n\n    bsServer.init(settings, () => {\n      // Exit process if specified port is not available. BrowserSync\n      // auto-selects a new port if the specified port is unavailable. This is\n      // problematic for testing and CI/CD.\n      if (bsServer.getOption('port') !== settings.port) {\n        console.log(\n          `\\nPort ${settings.port} not available. Exiting process.\\n`,\n        );\n        process.exit(0);\n      }\n\n      resolve(bsServer);\n    });\n  });\n}\n\nexport function stopServer() {\n  bsServer.exit();\n}\n"
  },
  {
    "path": "test/consume-types/README.md",
    "content": "# Vanilla ESM TypeScript Example\n\nThis is an example of a project that imports Docsify via plain ESM in browser,\nwith type checking with TypeScript to prove that type definitions are working.\n\nThis ensures that downstream projects that consume Docsify via ESM get\nintellisense in their IDE (f.e. `Go To Definition` in VS Code will take the user\nto Docsify source code).\n\nThis example is not using a build tool, but relying on native ESM in the\nbrowser, using an `importmap` script (`<script type=\"importmap\">` in\n`index.html`) to tell the browser to find dependencies in node_modules.\n\n## Commands\n\n### `npm run typecheck`\n\nRun type checking for the project.\n\n### `npm run serve`\n\nServe the example at http://localhost:5500 to verify vanilla ESM usage works.\n\n## Notes\n\n- We use a service worker to fix Docsify dependencies that use non-standard\n  import paths (without file extensions). See `sw.js` for more details.\n- We use `prism.js` in the `importmap` to map PrismJS from CommonJS to ESM. See\n  `prism.js` for more details.\n- We use `await import()` instead of `import` statements when importing Docsify\n  to ensure we load PrismJS and create a global `Prism` variable before Docsify is\n  imported. See `example.js` for more details.\n"
  },
  {
    "path": "test/consume-types/example.js",
    "content": "// This file demonstrates consuming Docsify as an ES Module with TypeScript types.\n// Things to try:\n// - Running 'Go to Definition' on `Docsify` should take you to the source JS\n//   file, not only the declaration file.\n// - Import from any subpath, and the type definitions should be visible.\n\n// Import the service worker registration script. This is needed to re-map\n// imports when using ES Modules in the browser, as some of Docsify's\n// dependencies are not available as ES Modules by default (CommonJS) or have\n// non-standard import paths like 'some-lib/some-subpath' without a file\n// extension, or expecting to import `index.js` from a folder, f.e.\n// 'some-lib/some-subpath.js' or 'some-lib/some-subpath/index.js'.\n// FIXME Replace ancient non-ESM dependencies with modern libraries.\nimport './register-sw.js';\n\n// FIXME hack: import prismjs first to ensure global Prism is set up, as\n// Docsify's graph (the import to\n// 'prismjs/components/prism-markup-templating.js') depends on Prism being\n// global first.\nawait import('prismjs');\n\n// Import Docsify *after* prismjs or there will be a runtime error. See the\n// importmap, and prism.js for more details.\n//\n// NOTE: we have to use `await import` instead of `import` statements because\n// Prism being global is not statically analyzable by the ES Module system so it\n// will try to execute Docsify's graph before prism.js has finished executed.\n// This is very odd, I didn't think this was possible.\nconst { Docsify } = await import('docsify');\n\nconst d = new Docsify({\n  el: '#app',\n  name: 'Vanilla ESM TypeScript Example',\n  themeColor: 'deeppink',\n  hideSidebar: false,\n\n  // @ts-expect-error invalid property to test that type checking works\n  blahblah: 123,\n});\n\nconsole.log(d);\n\n// @ts-expect-error global types not available to ESM\nwindow.Docsify;\n\nexport {};\n"
  },
  {
    "path": "test/consume-types/index.html",
    "content": "<head>\n  <meta charset=\"utf-8\" />\n  <meta\n    name=\"viewport\"\n    content=\"width=device-width, initial-scale=1, viewport-fit=cover\"\n  />\n\n  <link rel=\"stylesheet\" href=\"/node_modules/docsify/dist/themes/core.css\" />\n\n  <style></style>\n\n  <script type=\"importmap\">\n    {\n      \"imports\": {\n        \"docsify\": \"/node_modules/docsify/src/core/Docsify.js\",\n        \"docsify/\": \"/node_modules/docsify/\",\n        \"prismjs\": \"/prism.js\",\n        \"prismjs/\": \"/node_modules/prismjs/\",\n        \"marked\": \"/node_modules/marked/lib/marked.esm.js\",\n        \"marked/\": \"/node_modules/marked/\",\n        \"tinydate\": \"/node_modules/tinydate/dist/tinydate.mjs\",\n        \"common-tags\": \"/node_modules/common-tags/es/index.js\"\n      }\n    }\n  </script>\n\n  <script></script>\n\n  <script src=\"./example.js\" type=\"module\"></script>\n</head>\n\n<body>\n  <div id=\"app\"></div>\n</body>\n"
  },
  {
    "path": "test/consume-types/package.json",
    "content": "{\n  \"scripts\": {\n    \"typecheck\": \"tsc --noEmit\",\n    \"serve\": \"five-server . --open=false --ignorePattern=node_modules\"\n  },\n  \"dependencies\": {\n    \"docsify\": \"file:../../\"\n  },\n  \"devDependencies\": {\n    \"five-server\": \"^0.4.5\",\n    \"typescript\": \"^5.9.3\"\n  }\n}\n"
  },
  {
    "path": "test/consume-types/prism.js",
    "content": "// Small ESM wrapper, used in the importmap for 'prismjs', to import PrismJS by\n// tricking its CommonJS format to see module.exports.\n\nconst __prismCjs = await fetch('/node_modules/prismjs/prism.js').then(res =>\n  res.text(),\n);\n\n// Emulate CommonJS environment\nconst module = { exports: {} };\n// eslint-disable-next-line no-unused-vars\nconst exports = module.exports;\n\n// Evaluate the CommonJS code with module.exports in scope\neval(__prismCjs);\n\n// Export the Prism object\nconst _Prism = module.exports;\nexport default _Prism;\n\n// Also make Prism global because Docsify expects it by importing from\n// 'prismjs/components/prism-markup-templating.js' in the compiler.\n// @ts-expect-error FIXME get rid of this ugly global dependency hack in Docsify.\nwindow.Prism = _Prism;\n\n// @ts-expect-error\n// eslint-disable-next-line no-undef\nconsole.log('Prism loaded:', Prism);\n"
  },
  {
    "path": "test/consume-types/register-sw.js",
    "content": "// This whole thing is ugly. It is only so that we can fix improper import\n// statements in libraries from node_modules. See sw.js for how we re-map the\n// import URLs.\n// The convoluted code here is so that we will force the app to use a new\n// service worker if in dev mode we update the service worker code in sw.js\n\nlet reloadQueued = false;\nfunction queueReload() {\n  if (reloadQueued) {\n    return;\n  }\n  reloadQueued = true;\n  // Use location.reload() after a so any late controllerchange still settles.\n  window.location.reload();\n}\n\n// Register first.\nconst registration = await navigator.serviceWorker.register('/sw.js', {\n  scope: '/',\n  type: 'module',\n  updateViaCache: 'none',\n});\n\n// If there is already a waiting worker, wait for it to claim this client.\nif (registration.waiting) {\n  const sw = registration.waiting;\n\n  await new Promise(resolve => {\n    sw.addEventListener('statechange', () => {\n      // Wait until activated.\n      if (sw.state === 'activated') {\n        if (sw === navigator.serviceWorker.controller) {\n          resolve(void 0);\n        } else {\n          // If the new SW activated but is not controlling yet, it changed? Not sure if this can actuall happen.\n          queueReload();\n        }\n      }\n    });\n  });\n}\n\nif (navigator.serviceWorker.controller) {\n  navigator.serviceWorker.addEventListener('controllerchange', () => {\n    queueReload();\n  });\n} else {\n  // First-ever load: wait for controllerchange\n  await new Promise(resolve => {\n    navigator.serviceWorker.addEventListener('controllerchange', resolve, {\n      once: true,\n    });\n  });\n}\n\n// Track new installs.\nregistration.addEventListener('updatefound', () => {\n  const sw = registration.installing;\n  if (!sw) {\n    return;\n  }\n  sw.addEventListener('statechange', () => {\n    // When installed AND there is already a controller, a reload will let new SW control.\n    if (sw.state === 'installed' && navigator.serviceWorker.controller) {\n      // If skipWaiting ran inside new SW, controllerchange may fire soon; still queue.\n      queueReload();\n    }\n  });\n});\n\n// Force update check after listeners.\n// For purposes of testing the example, force update on page load so we\n// always test the latest service worker.\nawait registration.update();\n\n// Wait until there is an active worker (first load).\nawait navigator.serviceWorker.ready;\n\nif (reloadQueued) {\n  await new Promise(() => {});\n}\nconsole.log(\n  'Service worker ready and controlling. Continue app bootstrap here.',\n);\n\nexport {};\n"
  },
  {
    "path": "test/consume-types/sw.js",
    "content": "// The purpose of this service worker is to help with loading\n// node_modules modules when using ES modules in the browser.\n//\n// Specifically, this service worker helps with non-standard module paths\n// that do not include file extensions, such as:\n//\n//   /node_modules/some-lib/foo/bar\n//   /node_modules/some-lib/foo/bar/\n//\n// In these cases, the service worker will try to resolve them to actual files\n// by appending \".js\" or \"/index.js\" as needed.\n//\n// This service worker only handles requests under /node_modules/.\n// All other requests are passed through unmodified.\n\nexport {};\n\nconst scope = /** @type {ServiceWorkerGlobalScope} */ (\n  /** @type {any} */ (self)\n);\n\nscope.addEventListener('install', event => {\n  // Always activate worker immediately, for purposes of testing.\n  event.waitUntil(scope.skipWaiting());\n});\n\nscope.addEventListener('activate', event => {\n  // Always activate worker immediately, for purposes of testing.\n  event.waitUntil(scope.clients.claim());\n});\n\nscope.addEventListener('fetch', event => {\n  const url = new URL(event.request.url);\n\n  // Don't handle non-node_modules paths\n  if (!url.pathname.startsWith('/node_modules/')) {\n    event.respondWith(fetch(url.href));\n    return;\n  }\n\n  // 6\n  // Special handling for non-standard module paths in node_modules\n\n  const parts = url.pathname.split('/');\n  const fileName = /** @type {string} */ (parts.pop());\n  const ext = fileName.includes('.') ? fileName.split('.').pop() : '';\n\n  // Handle imports like 'some-lib/foo/bar' without an extension.\n  if (fileName !== '' && ext === '') {\n    event.respondWith(\n      // eslint-disable-next-line no-async-promise-executor\n      new Promise(async resolve => {\n        try {\n          // First try adding .js\n          const response = await tryJs();\n          const mimeType = response.headers.get('Content-Type') || '';\n          if (response.ok && mimeType.includes('javascript')) {\n            resolve(response);\n          } else {\n            throw new Error('Not JS');\n          }\n        } catch {\n          // If that fails, try adding /index.js\n          resolve(await tryIndexJs());\n        }\n\n        async function tryJs() {\n          const tryJs = new URL(url);\n          tryJs.href += '.js';\n          const response = await fetch(tryJs);\n          return response;\n        }\n\n        async function tryIndexJs() {\n          const tryIndexJs = new URL(url);\n          tryIndexJs.href += '/index.js';\n          const response = await fetch(tryIndexJs);\n          return response;\n        }\n      }),\n    );\n\n    return;\n  }\n\n  // Handle imports like 'some-lib/foo/bar/' (ending with a slash).\n  if (fileName === '') {\n    // adding index.js\n    const tryIndexJs = new URL(url);\n    tryIndexJs.href += 'index.js';\n\n    event.respondWith(fetch(tryIndexJs));\n\n    return;\n  }\n\n  // For all other cases, just fetch normally.\n  event.respondWith(fetch(url));\n});\n"
  },
  {
    "path": "test/consume-types/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"allowJs\": true,\n    \"checkJs\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"target\": \"esnext\",\n    \"strict\": true,\n    \"noEmit\": true,\n    \"lib\": [\"DOM\", \"ESNext\", \"WebWorker\"],\n    \"skipLibCheck\": true,\n    \"skipDefaultLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "test/e2e/configuration.test.js",
    "content": "/* global fail */\nimport docsifyInit from '../helpers/docsify-init.js';\nimport { test, expect } from './fixtures/docsify-init-fixture.js';\n\ntest.describe('Configuration options', () => {\n  test.describe('catchPluginErrors', () => {\n    test('true (handles uncaught errors)', async ({ page }) => {\n      let consoleMsg, errorMsg;\n\n      page.on('console', msg => (consoleMsg = msg.text()));\n      page.on('pageerror', err => (errorMsg = err.message));\n\n      await docsifyInit({\n        config: {\n          catchPluginErrors: true,\n          plugins: [\n            function (hook, vm) {\n              hook.init(function () {\n                fail();\n              });\n              hook.beforeEach(markdown => {\n                return `${markdown}\\n\\nbeforeEach`;\n              });\n            },\n          ],\n        },\n        markdown: {\n          homepage: '# Hello World',\n        },\n        // _logHTML: true,\n      });\n\n      const mainElm = page.locator('#main');\n\n      expect(errorMsg).toBeUndefined();\n      expect(consoleMsg).toContain('Docsify plugin error');\n      await expect(mainElm).toContainText('Hello World');\n      await expect(mainElm).toContainText('beforeEach');\n    });\n\n    test('false (throws uncaught errors)', async ({ page }) => {\n      let consoleMsg, errorMsg;\n\n      page.on('console', msg => {\n        const text = msg.text();\n        if (text.startsWith('DEPRECATION:')) {\n          return;\n        } // ignore expected deprecation warnings\n        consoleMsg = text;\n      });\n      page.on('pageerror', err => (errorMsg = err.message));\n\n      await docsifyInit({\n        config: {\n          catchPluginErrors: false,\n          plugins: [\n            function (hook, vm) {\n              hook.ready(function () {\n                fail();\n              });\n            },\n          ],\n        },\n        markdown: {\n          homepage: '# Hello World',\n        },\n        // _logHTML: true,\n      });\n\n      expect(consoleMsg).toBeUndefined();\n      expect(errorMsg).toContain('fail');\n    });\n  });\n\n  test.describe('notFoundPage', () => {\n    test.describe('renders default error content', () => {\n      test.beforeEach(async ({ page }) => {\n        await page.route('README.md', async route => {\n          await route.fulfill({\n            status: 500,\n          });\n        });\n      });\n\n      test('false', async ({ page }) => {\n        await docsifyInit({\n          config: {\n            notFoundPage: false,\n          },\n        });\n\n        await expect(page.locator('#main')).toContainText('500');\n      });\n\n      test('true with non-404 error', async ({ page }) => {\n        await docsifyInit({\n          config: {\n            notFoundPage: true,\n          },\n          routes: {\n            '_404.md': '',\n          },\n        });\n\n        await expect(page.locator('#main')).toContainText('500');\n      });\n\n      test('string with non-404 error', async ({ page }) => {\n        await docsifyInit({\n          config: {\n            notFoundPage: '404.md',\n          },\n          routes: {\n            '404.md': '',\n          },\n        });\n\n        await expect(page.locator('#main')).toContainText('500');\n      });\n    });\n\n    test('true: renders _404.md page', async ({ page }) => {\n      const expectText = 'Pass';\n\n      await docsifyInit({\n        config: {\n          notFoundPage: true,\n        },\n        routes: {\n          '_404.md': expectText,\n        },\n      });\n\n      await page.evaluate(() => (window.location.hash = '#/fail'));\n      await expect(page.locator('#main')).toContainText(expectText);\n    });\n\n    test('string: renders specified 404 page', async ({ page }) => {\n      const expectText = 'Pass';\n\n      await docsifyInit({\n        config: {\n          notFoundPage: '404.md',\n        },\n        routes: {\n          '404.md': expectText,\n        },\n      });\n\n      await page.evaluate(() => (window.location.hash = '#/fail'));\n      await expect(page.locator('#main')).toContainText(expectText);\n    });\n  });\n});\n\ntest.describe('keyBindings', () => {\n  test('handles toggleSidebar binding (default)', async ({ page }) => {\n    const docsifyInitConfig = {\n      markdown: {\n        homepage: `\n          # Heading 1\n        `,\n      },\n    };\n\n    await docsifyInit(docsifyInitConfig);\n\n    const sidebarElm = page.locator('.sidebar');\n\n    await expect(sidebarElm).toHaveClass(/show/);\n    await page.keyboard.press('\\\\');\n    await expect(sidebarElm).not.toHaveClass(/show/);\n  });\n\n  test('handles custom binding', async ({ page }) => {\n    const docsifyInitConfig = {\n      config: {\n        keyBindings: {\n          customBinding: {\n            bindings: 'z',\n            callback(e) {\n              const elm = document.querySelector('main input[type=\"text\"]');\n\n              elm.value = 'foo';\n            },\n          },\n        },\n      },\n      markdown: {\n        homepage: `\n          <input type=\"text\">\n        `,\n      },\n    };\n\n    const inputElm = page.locator('main input[type=\"text\"]');\n\n    await docsifyInit(docsifyInitConfig);\n\n    await expect(inputElm).toHaveValue('');\n    await page.keyboard.press('z');\n    await expect(inputElm).toHaveValue('foo');\n  });\n\n  test('ignores event when focused on text input elements', async ({\n    page,\n  }) => {\n    const docsifyInitConfig = {\n      config: {\n        keyBindings: {\n          customBinding: {\n            bindings: 'z',\n            callback(e) {\n              document.body.setAttribute('data-foo', '');\n            },\n          },\n        },\n      },\n      markdown: {\n        homepage: `\n          <input type=\"text\">\n          <select>\n            <option value=\"a\" selected>a</option>\n            <option value=\"z\">z</option>\n          </select>\n          <textarea></textarea>\n        `,\n      },\n    };\n\n    const bodyElm = page.locator('body');\n    const inputElm = page.locator('input[type=\"text\"]');\n    const selectElm = page.locator('select');\n    const textareaElm = page.locator('textarea');\n\n    await docsifyInit(docsifyInitConfig);\n\n    await inputElm.focus();\n    await expect(inputElm).toHaveValue('');\n    await page.keyboard.press('z');\n    await expect(inputElm).toHaveValue('z');\n    await inputElm.blur();\n\n    await textareaElm.focus();\n    await expect(textareaElm).toHaveValue('');\n    await page.keyboard.press('z');\n    await expect(textareaElm).toHaveValue('z');\n    await textareaElm.blur();\n\n    await selectElm.focus();\n    await page.keyboard.press('z');\n    await expect(selectElm).toHaveValue('z');\n    await selectElm.blur();\n\n    await expect(bodyElm).not.toHaveAttribute('data-foo');\n    await page.keyboard.press('z');\n    await expect(bodyElm).toHaveAttribute('data-foo');\n  });\n});\n"
  },
  {
    "path": "test/e2e/example.test.js",
    "content": "import docsifyInit from '../helpers/docsify-init.js';\nimport { test, expect } from './fixtures/docsify-init-fixture.js';\n\ntest.describe('Creating a Docsify site (e2e tests in Playwright)', () => {\n  test('manual docsify site using playwright methods', async ({ page }) => {\n    // Add docsify target element\n    await page.setContent('<div id=\"app\"></div>');\n\n    // Set docsify configuration\n    await page.evaluate(() => {\n      window.$docsify = {\n        el: '#app',\n        themeColor: 'red',\n      };\n    });\n\n    // Inject docsify theme\n    await page.addStyleTag({ url: '/dist/themes/core.css' });\n\n    // Inject docsify.js\n    await page.addScriptTag({ url: '/dist/docsify.js' });\n\n    // Wait for docsify to initialize\n    await page.locator('#main').waitFor();\n\n    // Create handle for JavaScript object in browser\n    const $docsify = await page.evaluate(() => window.$docsify);\n    // const $docsify = await page.evaluateHandle(() => window.$docsify);\n\n    // Test object property and value\n    expect($docsify).toHaveProperty('themeColor', 'red');\n  });\n\n  test('Docsify /docs/ site using docsifyInit()', async ({ page }) => {\n    // Load custom docsify\n    // (See ./helpers/docsifyInit.js for details)\n    await docsifyInit({\n      // _logHTML: true,\n    });\n\n    // Verify docsifyInitConfig.markdown content was rendered\n    const mainElm = page.locator('#main');\n    await expect(mainElm).toHaveCount(1);\n    await expect(mainElm).toContainText(\n      'A magical documentation site generator',\n    );\n  });\n\n  test('custom docsify site using docsifyInit()', async ({ page }) => {\n    const docsifyInitConfig = {\n      config: {\n        name: 'Docsify Name',\n        themeColor: 'red',\n      },\n      markdown: {\n        coverpage: `\n          # Docsify Test\n\n          > Testing a magical documentation site generator\n\n          [GitHub](https://github.com/docsifyjs/docsify/)\n        `,\n        homepage: `\n          # Hello World\n\n          This is the homepage.\n        `,\n        navbar: `\n          - [docsify.js.org](https://docsify.js.org/#/)\n        `,\n        sidebar: `\n          - [Test Page](test)\n        `,\n      },\n      routes: {\n        'test.md': `\n          # Test Page\n\n          This is a custom route.\n        `,\n        'data-test-scripturls.js': `\n          document.body.setAttribute('data-test-scripturls', 'pass');\n        `,\n      },\n      script: `\n        document.body.setAttribute('data-test-script', 'pass');\n      `,\n      scriptURLs: [\n        // docsifyInit() route\n        'data-test-scripturls.js',\n        // Server route\n        '/dist/plugins/search.js',\n      ],\n      style: `\n        body {\n          background: red !important;\n        }\n      `,\n      styleURLs: ['/dist/themes/core.css'],\n    };\n\n    await docsifyInit({\n      ...docsifyInitConfig,\n      // _logHTML: true,\n    });\n\n    const $docsify = await page.evaluate(() => window.$docsify);\n\n    // Verify config options\n    expect(typeof $docsify).toEqual('object');\n    expect($docsify).toHaveProperty('themeColor', 'red');\n    await expect(page.locator('.app-name')).toHaveText('Docsify Name');\n\n    // Verify docsifyInitConfig.markdown content was rendered\n    await expect(page.locator('section.cover h1')).toHaveText('Docsify Test'); // Coverpage\n    await expect(page.locator('nav.app-nav')).toHaveText('docsify.js.org'); // Navbar\n    await expect(page.locator('aside.sidebar')).toContainText('Test Page'); // Sidebar\n    await expect(page.locator('#main')).toContainText('This is the homepage'); // Homepage\n\n    // Verify docsifyInitConfig.scriptURLs were added to the DOM\n    for (const scriptURL of docsifyInitConfig.scriptURLs) {\n      await expect(page.locator(`script[src$=\"${scriptURL}\"]`)).toHaveCount(1);\n    }\n\n    // Verify docsifyInitConfig.scriptURLs were executed\n    await expect(page.locator('body[data-test-scripturls]')).toHaveCount(1);\n    await expect(page.locator('.search input[type=\"search\"]')).toHaveCount(1);\n\n    // Verify docsifyInitConfig.script was added to the DOM\n    expect(\n      await page.evaluate(\n        scriptText => {\n          return [...document.querySelectorAll('script')].some(\n            elm => elm.textContent.replace(/\\s+/g, '') === scriptText,\n          );\n        },\n        docsifyInitConfig.script.replace(/\\s+/g, ''),\n      ),\n    ).toBe(true);\n\n    // Verify docsifyInitConfig.script was executed\n    await expect(page.locator('body[data-test-script]')).toHaveCount(1);\n\n    // Verify docsifyInitConfig.styleURLs were added to the DOM\n    for (const styleURL of docsifyInitConfig.styleURLs) {\n      await expect(\n        page.locator(`link[rel*=\"stylesheet\"][href$=\"${styleURL}\"]`),\n      ).toHaveCount(1);\n    }\n\n    // Verify docsifyInitConfig.style was added to the DOM\n    expect(\n      await page.evaluate(\n        styleText => {\n          return [...document.querySelectorAll('style')].some(\n            elm => elm.textContent.replace(/\\s+/g, '') === styleText,\n          );\n        },\n        docsifyInitConfig.style.replace(/\\s+/g, ''),\n      ),\n    ).toBe(true);\n\n    // Verify docsify navigation and docsifyInitConfig.routes\n    await page.click('a[href=\"#/test\"]');\n    expect(page.url()).toMatch(/\\/test$/);\n    await expect(page.locator('#main')).toContainText('This is a custom route');\n  });\n\n  // test.fixme('image snapshots', async ({ page }) => {\n  //   await docsifyInit({\n  //     config: {\n  //       name: 'Docsify Test',\n  //     },\n  //     markdown: {\n  //       homepage: `\n  //         # The Cosmos Awaits\n\n  //         [Carl Sagan](https://en.wikipedia.org/wiki/Carl_Sagan)\n\n  //         Cosmic ocean take root and flourish decipherment hundreds of thousands\n  //         dream of the mind's eye courage of our questions. At the edge of forever\n  //         network of wormholes ship of the imagination two ghostly white figures\n  //         in coveralls and helmets are softly dancing are creatures of the cosmos\n  //         the only home we've ever known? How far away emerged into consciousness\n  //         bits of moving fluff gathered by gravity with pretty stories for which\n  //         there's little good evidence vanquish the impossible.\n\n  //         The ash of stellar alchemy permanence of the stars shores of the cosmic\n  //         ocean billions upon billions Drake Equation finite but unbounded.\n  //         Hundreds of thousands cosmic ocean hearts of the stars Hypatia invent\n  //         the universe hearts of the stars? Realm of the galaxies muse about dream\n  //         of the mind's eye hundreds of thousands the only home we've ever known\n  //         how far away. Extraordinary claims require extraordinary evidence\n  //         citizens of distant epochs invent the universe as a patch of light the\n  //         carbon in our apple pies gathered by gravity.\n\n  //         Billions upon billions gathered by gravity white dwarf intelligent\n  //         beings vanquish the impossible descended from astronomers. A still more\n  //         glorious dawn awaits cosmic ocean star stuff harvesting star light the\n  //         sky calls to us kindling the energy hidden in matter rich in heavy\n  //         atoms. A mote of dust suspended in a sunbeam across the centuries the\n  //         only home we've ever known bits of moving fluff a very small stage in a\n  //         vast cosmic arena courage of our questions.\n\n  //         Euclid the only home we've ever known realm of the galaxies trillion\n  //         radio telescope Apollonius of Perga. The carbon in our apple pies invent\n  //         the universe muse about stirred by starlight great turbulent clouds\n  //         emerged into consciousness? Invent the universe vastness is bearable\n  //         only through love a still more glorious dawn awaits descended from\n  //         astronomers as a patch of light the sky calls to us. Great turbulent\n  //         clouds citizens of distant epochs invent the universe two ghostly white\n  //         figures in coveralls and helmets are softly dancing courage of our\n  //         questions rich in heavy atoms and billions upon billions upon billions\n  //         upon billions upon billions upon billions upon billions.\n  //       `,\n  //     },\n  //     styleURLs: [`/dist/themes/core.css`],\n  //     // _logHTML: true,\n  //   });\n\n  //   // Viewport screenshot\n  //   const viewportShot = await page.screenshot();\n  //   expect(viewportShot).toMatchSnapshot('viewport.png');\n\n  //   // Element screenshot\n  //   const elmHandle = await page.locator('h1').first();\n  //   const elmShot = await elmHandle.screenshot();\n  //   expect(elmShot).toMatchSnapshot('element.png');\n  // });\n});\n"
  },
  {
    "path": "test/e2e/fixtures/docsify-init-fixture.js",
    "content": "import { test as _test, expect as _expect } from '@playwright/test';\n\nexport const test = _test.extend({\n  page: async ({ page }, use) => {\n    global.page = page;\n\n    // Navigate to a real URL by default\n    // Playwright tests are executed on \"about:blank\" by default, which will\n    // cause operations that require the window location to be a valid URL to\n    // fail (e.g. AJAX requests). Navigating to a blank document with a real\n    // URL solved this problem.\n    await page.goto('/_blank.html');\n    await use(page);\n  },\n});\n\nexport const expect = _expect;\n"
  },
  {
    "path": "test/e2e/gtag.test.js",
    "content": "import docsifyInit from '../helpers/docsify-init.js';\nimport { test, expect } from './fixtures/docsify-init-fixture.js';\n\nconst gtagList = [\n  'AW-YYYYYY', // Google Ads\n  'DC-ZZZZZZ', // Floodlight\n  'G-XXXXXX', // Google Analytics 4 (GA4)\n  'UA-XXXXXX', // Google Universal Analytics (GA3)\n];\n\ntest.describe('Gtag Plugin Tests', () => {\n  // page request listened, print collect url\n  function pageRequestListened(page) {\n    page.on('request', request => {\n      if (request.url().indexOf('www.google-analytics.com') !== -1) {\n        // console.log(request.url());\n      }\n    });\n\n    page.on('response', response => {\n      const request = response.request();\n      // googleads.g.doubleclick.net\n      // www.google-analytics.com\n      // www.googletagmanager.com\n      const reg =\n        /googleads\\.g\\.doubleclick\\.net|www\\.google-analytics\\.com|www\\.googletagmanager\\.com/g;\n      if (request.url().match(reg)) {\n        // console.log(request.url(), response.status());\n      }\n    });\n  }\n\n  test('single gtag', async ({ page }) => {\n    pageRequestListened(page);\n\n    const docsifyInitConfig = {\n      config: {\n        gtag: gtagList[0],\n      },\n      scriptURLs: ['/dist/plugins/gtag.js'],\n      styleURLs: ['/dist/themes/core.css'],\n    };\n\n    await docsifyInit({\n      ...docsifyInitConfig,\n    });\n\n    const $docsify = await page.evaluate(() => window.$docsify);\n\n    // Verify config options\n    expect(typeof $docsify).toEqual('object');\n\n    // console.log($docsify.gtag, $docsify.gtag === '');\n\n    // Tests\n    expect($docsify.gtag).not.toEqual('');\n  });\n\n  test('multi gtag', async ({ page }) => {\n    pageRequestListened(page);\n\n    const docsifyInitConfig = {\n      config: {\n        gtag: gtagList,\n      },\n      scriptURLs: ['/dist/plugins/gtag.js'],\n      styleURLs: ['/dist/themes/core.css'],\n    };\n\n    await docsifyInit({\n      ...docsifyInitConfig,\n    });\n\n    const $docsify = await page.evaluate(() => window.$docsify);\n\n    // Verify config options\n    expect(typeof $docsify).toEqual('object');\n\n    // console.log($docsify.gtag, $docsify.gtag === '');\n\n    // Tests\n    expect($docsify.gtag).not.toEqual('');\n  });\n});\n"
  },
  {
    "path": "test/e2e/index-file.test.js",
    "content": "import docsifyInit from '../helpers/docsify-init.js';\nimport { test, expect } from './fixtures/docsify-init-fixture.js';\n\ntest.describe('Index file hosting', () => {\n  const sharedOptions = {\n    config: {\n      basePath: '/index.html#/',\n    },\n    testURL: '/index.html#/',\n  };\n\n  test('should serve from index file', async ({ page }) => {\n    await docsifyInit(sharedOptions);\n    await expect(page.locator('#main')).toContainText(\n      'A magical documentation site generator',\n    );\n    expect(page.url()).toMatch(/index\\.html#\\/$/);\n  });\n\n  test('should use index file links in sidebar from index file hosting', async ({\n    page,\n  }) => {\n    await docsifyInit(sharedOptions);\n    await page.click('a[href=\"#/quickstart\"]');\n    await expect(page.locator('#main')).toContainText('Quick start');\n    expect(page.url()).toMatch(/index\\.html#\\/quickstart$/);\n  });\n});\n"
  },
  {
    "path": "test/e2e/plugins.test.js",
    "content": "import docsifyInit from '../helpers/docsify-init.js';\nimport { waitForFunction } from '../helpers/wait-for.js';\nimport { test, expect } from './fixtures/docsify-init-fixture.js';\n\ntest.describe('Plugins', () => {\n  test('Hook order', async ({ page }) => {\n    const consoleMsgs = [];\n    const expectedMsgs = [\n      'init',\n      'mounted',\n      'beforeEach-async',\n      'beforeEach',\n      'afterEach-async',\n      'afterEach',\n      'doneEach',\n      'ready',\n    ];\n\n    page.on('console', msg => {\n      const text = msg.text();\n      if (text.startsWith('DEPRECATION:')) {\n        return;\n      } // ignore expected deprecation warnings\n      consoleMsgs.push(text);\n    });\n\n    await docsifyInit({\n      config: {\n        plugins: [\n          function (hook, vm) {\n            hook.init(() => {\n              console.log('init');\n            });\n\n            hook.mounted(() => {\n              console.log('mounted');\n            });\n\n            hook.beforeEach((markdown, next) => {\n              setTimeout(() => {\n                console.log('beforeEach-async');\n                next(markdown);\n              }, 100);\n            });\n\n            hook.beforeEach(markdown => {\n              console.log('beforeEach');\n              return markdown;\n            });\n\n            hook.afterEach((html, next) => {\n              setTimeout(() => {\n                console.log('afterEach-async');\n                next(html);\n              }, 100);\n            });\n\n            hook.afterEach(html => {\n              console.log('afterEach');\n              return html;\n            });\n\n            hook.doneEach(() => {\n              console.log('doneEach');\n            });\n\n            hook.ready(() => {\n              console.log('ready');\n            });\n          },\n        ],\n      },\n      markdown: {\n        homepage: '# Hello World',\n      },\n      // _logHTML: true,\n    });\n\n    expect(consoleMsgs).toEqual(expectedMsgs);\n  });\n\n  test.describe('beforeEach()', () => {\n    test('return value', async ({ page }) => {\n      await docsifyInit({\n        config: {\n          plugins: [\n            function (hook, vm) {\n              hook.beforeEach(markdown => {\n                return 'beforeEach';\n              });\n            },\n          ],\n        },\n        // _logHTML: true,\n      });\n\n      await expect(page.locator('#main')).toContainText('beforeEach');\n    });\n\n    test('async return value', async ({ page }) => {\n      await docsifyInit({\n        config: {\n          plugins: [\n            function (hook, vm) {\n              hook.beforeEach((markdown, next) => {\n                setTimeout(() => {\n                  next('beforeEach');\n                }, 100);\n              });\n            },\n          ],\n        },\n        markdown: {\n          homepage: '# Hello World',\n        },\n        // _logHTML: true,\n      });\n\n      await expect(page.locator('#main')).toContainText('beforeEach');\n    });\n  });\n\n  test.describe('afterEach()', () => {\n    test('return value', async ({ page }) => {\n      await docsifyInit({\n        config: {\n          plugins: [\n            function (hook, vm) {\n              hook.afterEach(html => {\n                return '<p>afterEach</p>';\n              });\n            },\n          ],\n        },\n        markdown: {\n          homepage: '# Hello World',\n        },\n        // _logHTML: true,\n      });\n\n      await expect(page.locator('#main')).toContainText('afterEach');\n    });\n\n    test('async return value', async ({ page }) => {\n      await docsifyInit({\n        config: {\n          plugins: [\n            function (hook, vm) {\n              hook.afterEach((html, next) => {\n                setTimeout(() => {\n                  next('<p>afterEach</p>');\n                }, 100);\n              });\n            },\n          ],\n        },\n        markdown: {\n          homepage: '# Hello World',\n        },\n        // _logHTML: true,\n      });\n\n      await expect(page.locator('#main')).toContainText('afterEach');\n    });\n  });\n\n  test.describe('route data accessible to plugins', () => {\n    let routeData = null;\n\n    test.beforeEach(async ({ page }) => {\n      // Store route data set via plugin hook (below)\n      page.on('console', async msg => {\n        for (const arg of msg.args()) {\n          const val = await arg.jsonValue();\n          if (typeof val === 'string' && val.startsWith('DEPRECATION:')) {\n            continue;\n          }\n          try {\n            const obj = typeof val === 'string' ? JSON.parse(val) : val;\n            obj && obj.response && (routeData = obj);\n          } catch {\n            // ignore non-JSON console messages\n          }\n        }\n      });\n    });\n\n    test.afterEach(async ({ page }) => {\n      routeData = null;\n    });\n\n    test('success (200)', async ({ page }) => {\n      await docsifyInit({\n        config: {\n          plugins: [\n            function (hook, vm) {\n              hook.doneEach(html => {\n                console.log(JSON.stringify(vm.route));\n              });\n            },\n          ],\n        },\n      });\n\n      expect(routeData).toHaveProperty('response');\n      expect(routeData.response).toHaveProperty('ok', true);\n      expect(routeData.response).toHaveProperty('status', 200);\n      expect(routeData.response).toHaveProperty('statusText', 'OK');\n    });\n\n    test('fail (404)', async ({ page }) => {\n      const link404Elm = page.locator('a[href*=\"404\"]');\n\n      await docsifyInit({\n        markdown: {\n          sidebar: `\n            - [404](404.md)\n          `,\n        },\n        config: {\n          plugins: [\n            function (hook, vm) {\n              hook.doneEach(html => {\n                console.log(JSON.stringify(vm.route));\n              });\n            },\n          ],\n        },\n      });\n\n      await link404Elm.click();\n      await waitForFunction(() => routeData?.response?.status === 404);\n\n      expect(routeData).toHaveProperty('response');\n      expect(routeData.response).toHaveProperty('ok', false);\n      expect(routeData.response).toHaveProperty('status', 404);\n      expect(routeData.response).toHaveProperty('statusText', 'Not Found');\n    });\n  });\n});\n"
  },
  {
    "path": "test/e2e/search.test.js",
    "content": "import docsifyInit from '../helpers/docsify-init.js';\nimport { test, expect } from './fixtures/docsify-init-fixture.js';\n\ntest.describe('Search Plugin Tests', () => {\n  test('search readme', async ({ page }) => {\n    const docsifyInitConfig = {\n      markdown: {\n        homepage: `\n          # Hello World\n\n          This is the homepage.\n        `,\n        sidebar: `\n          - [Test Page](test)\n        `,\n      },\n      routes: {\n        '/test.md': `\n          # Test Page\n\n          This is a custom route.\n        `,\n      },\n      scriptURLs: ['/dist/plugins/search.js'],\n    };\n\n    const searchFieldElm = page.locator('input[type=search]');\n    const resultsHeadingElm = page.locator('.results-panel .title');\n\n    await docsifyInit(docsifyInitConfig);\n\n    await searchFieldElm.fill('hello');\n    await expect(resultsHeadingElm).toHaveText('Hello World');\n    await page.click('.clear-button');\n    await searchFieldElm.fill('test');\n    await expect(resultsHeadingElm).toHaveText('Test Page');\n  });\n\n  test('search ignore title', async ({ page }) => {\n    const docsifyInitConfig = {\n      markdown: {\n        homepage: `\n          # Hello World\n\n          This is the homepage.\n        `,\n        sidebar: `\n          - [Home page](/)\n          - [GitHub Pages](github)\n        `,\n      },\n      routes: {\n        '/github.md': `\n            # GitHub Pages\n\n            This is the GitHub Pages.\n\n            ## GitHub Pages ignore1 <!-- {docsify-ignore} -->\n\n            There're three places to populate your docs for your GitHub repository1.\n\n            ## GitHub Pages ignore2 {docsify-ignore}\n\n            There're three places to populate your docs for your GitHub repository2.\n          `,\n      },\n      scriptURLs: ['/dist/plugins/search.js'],\n    };\n\n    const searchFieldElm = page.locator('input[type=search]');\n    const resultsHeadingElm = page.locator('.results-panel .title');\n\n    await docsifyInit(docsifyInitConfig);\n\n    await searchFieldElm.fill('repository1');\n    await expect(resultsHeadingElm).toHaveText('GitHub Pages ignore1');\n    await page.click('.clear-button');\n    await searchFieldElm.fill('repository2');\n    await expect(resultsHeadingElm).toHaveText('GitHub Pages ignore2');\n  });\n\n  test('search only one homepage', async ({ page }) => {\n    const docsifyInitConfig = {\n      markdown: {\n        sidebar: `\n          - [README](README)\n          - [Test Page](test)\n        `,\n      },\n      routes: {\n        '/README.md': `\n          # Hello World\n\n          This is the homepage.\n        `,\n        '/test.md': `\n          # Test Page\n\n          This is a custom route.\n        `,\n      },\n      scriptURLs: ['/dist/plugins/search.js'],\n    };\n\n    const searchFieldElm = page.locator('input[type=search]');\n    const resultsHeadingElm = page.locator('.results-panel .title');\n    const resultElm = page.locator('.matching-post');\n\n    await docsifyInit(docsifyInitConfig);\n\n    await searchFieldElm.fill('hello');\n    await expect(resultElm).toHaveCount(1);\n    await expect(resultsHeadingElm).toHaveText('Hello World');\n    await page.click('.clear-button');\n    await searchFieldElm.fill('test');\n    await expect(resultsHeadingElm).toHaveText('Test Page');\n  });\n\n  test('search ignore diacritical marks', async ({ page }) => {\n    const docsifyInitConfig = {\n      markdown: {\n        homepage: `\n          # Qué es\n\n          docsify genera su sitio web de documentación sobre la marcha. A diferencia de GitBook, no genera archivos estáticos html. En cambio, carga y analiza de forma inteligente sus archivos de Markdown y los muestra como sitio web. Todo lo que necesita hacer es crear un index.html para comenzar y desplegarlo en GitHub Pages.\n        `,\n      },\n      scriptURLs: ['/dist/plugins/search.js'],\n    };\n\n    const searchFieldElm = page.locator('input[type=search]');\n    const resultsHeadingElm = page.locator('.results-panel .title');\n\n    await docsifyInit(docsifyInitConfig);\n\n    await searchFieldElm.fill('documentacion');\n    await expect(resultsHeadingElm).toHaveText('Que es');\n    await page.click('.clear-button');\n    await searchFieldElm.fill('estáticos');\n    await expect(resultsHeadingElm).toHaveText('Que es');\n  });\n\n  test('search when there is no title', async ({ page }) => {\n    const docsifyInitConfig = {\n      markdown: {\n        homepage: `\n          This is some description. We assume autoHeader added the # Title. A long paragraph.\n        `,\n        sidebar: `\n          - [Changelog](changelog)\n        `,\n      },\n      routes: {\n        '/changelog.md': `\n          feat: Support search when there is no title\n\n          ## Changelog Title\n\n          hello, this is a changelog\n        `,\n      },\n      scriptURLs: ['/dist/plugins/search.js'],\n    };\n\n    const searchFieldElm = page.locator('input[type=search]');\n    const resultsHeadingElm = page.locator('.results-panel .title');\n\n    await docsifyInit(docsifyInitConfig);\n\n    await searchFieldElm.fill('paragraph');\n    await expect(resultsHeadingElm).toHaveText('Home Page');\n    await page.click('.clear-button');\n    await searchFieldElm.fill('Support');\n    await expect(resultsHeadingElm).toHaveText('changelog');\n    await page.click('.clear-button');\n    await searchFieldElm.fill('hello');\n    await expect(resultsHeadingElm).toHaveText('Changelog Title');\n  });\n\n  test('search when there is no body', async ({ page }) => {\n    const docsifyInitConfig = {\n      markdown: {\n        homepage: `\n          # EmptyContent\n          ---\n          ---\n        `,\n      },\n      scriptURLs: ['/dist/plugins/search.js'],\n    };\n\n    const searchFieldElm = page.locator('input[type=search]');\n    const resultsHeadingElm = page.locator('.results-panel .title');\n\n    await docsifyInit(docsifyInitConfig);\n\n    await searchFieldElm.fill('empty');\n    await expect(resultsHeadingElm).toHaveText('EmptyContent');\n  });\n\n  test('handles default focusSearch binding', async ({ page }) => {\n    const docsifyInitConfig = {\n      scriptURLs: ['/dist/plugins/search.js'],\n    };\n\n    const searchFieldElm = page.locator('input[type=\"search\"]');\n\n    await docsifyInit(docsifyInitConfig);\n\n    await expect(searchFieldElm).not.toBeFocused();\n    await page.keyboard.press('/');\n    await expect(searchFieldElm).toBeFocused();\n  });\n\n  test('handles custom focusSearch binding', async ({ page }) => {\n    const docsifyInitConfig = {\n      config: {\n        search: {\n          keyBindings: ['z'],\n        },\n      },\n      scriptURLs: ['/dist/plugins/search.js'],\n    };\n\n    const searchFieldElm = page.locator('input[type=\"search\"]');\n\n    await docsifyInit(docsifyInitConfig);\n\n    await expect(searchFieldElm).not.toBeFocused();\n    await page.keyboard.press('/');\n    await expect(searchFieldElm).not.toBeFocused();\n    await page.keyboard.press('z');\n    await expect(searchFieldElm).toBeFocused();\n  });\n  test('search result should remove markdown code block', async ({ page }) => {\n    const docsifyInitConfig = {\n      markdown: {\n        homepage: `\n# Hello World\n\nsearchHere\n\\`\\`\\`js\nconsole.log('Hello World');\n\\`\\`\\`\n        `,\n      },\n      scriptURLs: ['/dist/plugins/search.js'],\n    };\n\n    const searchFieldElm = page.locator('input[type=search]');\n    const resultsHeadingElm = page.locator('.results-panel .content');\n\n    await docsifyInit(docsifyInitConfig);\n    await searchFieldElm.fill('searchHere');\n    // there is a newline after searchHere and the markdown part ```js ``` it should be removed\n    expect(await resultsHeadingElm.textContent()).toContain(\n      \"...searchHere\\nconsole.log('Hello World');...\",\n    );\n  });\n\n  test('search result should remove file markdown and keep href attribution for files', async ({\n    page,\n  }) => {\n    const docsifyInitConfig = {\n      markdown: {\n        homepage: `\n# Hello World\n![filename](_media/example.js ':include :type=code :fragment=demo')\n        `,\n      },\n      scriptURLs: ['/dist/plugins/search.js'],\n    };\n\n    const searchFieldElm = page.locator('input[type=search]');\n    const resultsHeadingElm = page.locator('.results-panel .content');\n\n    await docsifyInit(docsifyInitConfig);\n    await searchFieldElm.fill('filename');\n    expect(await resultsHeadingElm.textContent()).toContain(\n      '...filename _media/example.js :include :type=code :fragment=demo...',\n    );\n  });\n\n  test('search result should remove checkbox markdown and keep related values', async ({\n    page,\n  }) => {\n    const docsifyInitConfig = {\n      markdown: {\n        homepage: `\n# Hello World\n\n- [ ] Task 1\n- [x] SearchHere\n- [ ] Task 3\n          `,\n      },\n      scriptURLs: ['/dist/plugins/search.js'],\n    };\n\n    const searchFieldElm = page.locator('input[type=search]');\n    const resultsHeadingElm = page.locator('.results-panel .content');\n\n    await docsifyInit(docsifyInitConfig);\n    await searchFieldElm.fill('SearchHere');\n    // remove the checkbox markdown and keep the related values\n    expect(await resultsHeadingElm.textContent()).toContain(\n      '...Task 1 SearchHere Task 3...',\n    );\n  });\n\n  test('search result should remove docsify self helper markdown and keep related values', async ({\n    page,\n  }) => {\n    const docsifyInitConfig = {\n      markdown: {\n        homepage: `\n# Hello World\n\n!> SearchHere to check it!\n\n          `,\n      },\n      scriptURLs: ['/dist/plugins/search.js'],\n    };\n\n    const searchFieldElm = page.locator('input[type=search]');\n    const resultsHeadingElm = page.locator('.results-panel .content');\n\n    await docsifyInit(docsifyInitConfig);\n    await searchFieldElm.fill('SearchHere');\n    // remove the helper markdown and keep the related values\n    expect(await resultsHeadingElm.textContent()).toContain(\n      '...SearchHere to check it!...',\n    );\n  });\n});\n"
  },
  {
    "path": "test/e2e/security.test.js",
    "content": "import docsifyInit from '../helpers/docsify-init.js';\nimport { test, expect } from './fixtures/docsify-init-fixture.js';\n\ntest.describe('Security - Cross Site Scripting (XSS)', () => {\n  const sharedOptions = {\n    markdown: {\n      homepage: '# Hello World',\n    },\n    routes: {\n      'test.md': '# Test Page',\n    },\n  };\n  const slashStrings = ['//', '///'];\n\n  for (const slashString of slashStrings) {\n    const hash = `#${slashString}domain.com/file.md`;\n\n    test(`should not load remote content from hash (${hash})`, async ({\n      page,\n    }) => {\n      const mainElm = page.locator('#main');\n\n      await docsifyInit(sharedOptions);\n      await expect(mainElm).toContainText('Hello World');\n      await page.evaluate(() => (location.hash = '#/test'));\n      await expect(mainElm).toContainText('Test Page');\n      await page.evaluate(newHash => {\n        location.hash = newHash;\n      }, hash);\n      await expect(mainElm).toContainText('Hello World');\n      expect(page.url()).toMatch(/#\\/$/);\n    });\n  }\n});\n"
  },
  {
    "path": "test/e2e/sidebar.test.js",
    "content": "import docsifyInit from '../helpers/docsify-init.js';\nimport { test, expect } from './fixtures/docsify-init-fixture.js';\n\n// Suite\n// -----------------------------------------------------------------------------\ntest.describe('Sidebar Tests', () => {\n  // Tests\n  // ---------------------------------------------------------------------------\n  test('Active Test', async ({ page }) => {\n    const docsifyInitConfig = {\n      markdown: {\n        sidebar: `\n          - [Test Space](test%20space)\n          - [Test _](test_foo)\n          - [Test -](test-foo)\n          - [Test .](test.foo)\n          - [Test >](test>foo)\n          - [Test](test)\n        `,\n      },\n      routes: {\n        '/test space.md': `\n          # Test Space\n        `,\n        '/test_foo.md': `\n          # Test _\n        `,\n        '/test-foo.md': `\n          # Test -\n        `,\n        '/test.foo.md': `\n          # Test .\n        `,\n        '/test>foo.md': `\n          # Test >\n        `,\n        '/test.md': `\n          # Test page\n        `,\n      },\n    };\n\n    const activeLinkElm = page.locator('.sidebar-nav li[class=active]');\n\n    await docsifyInit(docsifyInitConfig);\n\n    await page.click('a[href=\"#/test\"]');\n    await expect(activeLinkElm).toHaveText('Test');\n    expect(page.url()).toMatch(/\\/test$/);\n\n    await page.click('a[href=\"#/test%20space\"]');\n    await expect(activeLinkElm).toHaveText('Test Space');\n    expect(page.url()).toMatch(/\\/test%20space$/);\n\n    await page.click('a[href=\"#/test_foo\"]');\n    await expect(activeLinkElm).toHaveText('Test _');\n    expect(page.url()).toMatch(/\\/test_foo$/);\n\n    await page.click('a[href=\"#/test-foo\"]');\n    await expect(activeLinkElm).toHaveText('Test -');\n    expect(page.url()).toMatch(/\\/test-foo$/);\n\n    await page.click('a[href=\"#/test.foo\"]');\n    await expect(activeLinkElm).toHaveText('Test .');\n    expect(page.url()).toMatch(/\\/test.foo$/);\n\n    await page.click('a[href=\"#/test>foo\"]');\n    await expect(activeLinkElm).toHaveText('Test >');\n    expect(page.url()).toMatch(/\\/test%3Efoo$/);\n  });\n});\n\ntest.describe('Configuration: autoHeader', () => {\n  test('autoHeader=false', async ({ page }) => {\n    const docsifyInitConfig = {\n      config: {\n        loadSidebar: '_sidebar.md',\n        autoHeader: false,\n      },\n      markdown: {\n        sidebar: `\n            - [QuickStartAutoHeader](quickstart.md)\n          `,\n      },\n      routes: {\n        '/quickstart.md': `\n            the content of quickstart space\n            ## In the main content there is no h1\n          `,\n      },\n    };\n\n    await docsifyInit(docsifyInitConfig);\n\n    await page.click('a[href=\"#/quickstart\"]');\n    expect(page.url()).toMatch(/\\/quickstart$/);\n    // not heading\n    await expect(page.locator('#quickstart')).toBeHidden();\n  });\n\n  test('autoHeader=true', async ({ page }) => {\n    const docsifyInitConfig = {\n      config: {\n        loadSidebar: '_sidebar.md',\n        autoHeader: true,\n      },\n      markdown: {\n        sidebar: `\n            - [QuickStartAutoHeader](quickstart.md )\n          `,\n      },\n      routes: {\n        '/quickstart.md': `\n            the content of quickstart space\n            ## In the main content there is no h1\n          `,\n      },\n    };\n\n    await docsifyInit(docsifyInitConfig);\n\n    await page.click('a[href=\"#/quickstart\"]');\n    expect(page.url()).toMatch(/\\/quickstart$/);\n\n    // auto generate default heading id\n    const autoHeader = page.locator('#quickstartautoheader');\n    expect(await autoHeader.innerText()).toContain('QuickStartAutoHeader');\n  });\n\n  test('autoHeader=true and custom headingId', async ({ page }) => {\n    const docsifyInitConfig = {\n      config: {\n        loadSidebar: '_sidebar.md',\n        autoHeader: true,\n      },\n      markdown: {\n        sidebar: `\n            - [QuickStartAutoHeader](quickstart.md \":id=quickstartId\")\n          `,\n      },\n      routes: {\n        '/quickstart.md': `\n            the content of quickstart space\n            ## In the main content there is no h1\n          `,\n      },\n    };\n\n    await docsifyInit(docsifyInitConfig);\n\n    await page.click('a[href=\"#/quickstart\"]');\n    expect(page.url()).toMatch(/\\/quickstart$/);\n    // auto generate custom heading id\n    const autoHeader = page.locator('#quickstartId');\n    expect(await autoHeader.innerText()).toContain('QuickStartAutoHeader');\n  });\n});\n"
  },
  {
    "path": "test/e2e/virtual-routes.test.js",
    "content": "import docsifyInit from '../helpers/docsify-init.js';\nimport { test, expect } from './fixtures/docsify-init-fixture.js';\n\n/**\n * Navigate to a specific route in the site\n * @param {import('playwright-core').Page} page the playwright page instance from the test\n * @param {string} route the route you want to navigate to\n */\nasync function navigateToRoute(page, route) {\n  await page.evaluate(r => (window.location.hash = r), route);\n  await page.waitForLoadState('load');\n}\n\ntest.describe('Virtual Routes - Generate Dynamic Content via Config', () => {\n  test.describe('Different Types of Virtual Routes', () => {\n    test('rendering virtual routes specified as string', async ({ page }) => {\n      const routes = {\n        '/my-awesome-route': '# My Awesome Route',\n      };\n\n      await docsifyInit({\n        config: {\n          routes,\n        },\n      });\n\n      await navigateToRoute(page, '/my-awesome-route');\n\n      const titleElm = page.locator('#main h1');\n      await expect(titleElm).toContainText('My Awesome Route');\n    });\n\n    test('rendering virtual routes specified as functions', async ({\n      page,\n    }) => {\n      const routes = {\n        '/my-awesome-function-route': function () {\n          return '# My Awesome Function Route';\n        },\n      };\n\n      await docsifyInit({\n        config: {\n          routes,\n        },\n      });\n\n      await navigateToRoute(page, '/my-awesome-function-route');\n\n      const titleElm = page.locator('#main h1');\n      await expect(titleElm).toContainText('My Awesome Function Route');\n    });\n\n    test('rendering virtual routes specified functions that use the \"next\" callback', async ({\n      page,\n    }) => {\n      const routes = {\n        '/my-awesome-async-function-route': async function (\n          route,\n          matched,\n          next,\n        ) {\n          setTimeout(() => next('# My Awesome Function Route'), 100);\n        },\n      };\n\n      await docsifyInit({\n        config: {\n          routes,\n        },\n      });\n\n      await navigateToRoute(page, '/my-awesome-async-function-route');\n\n      const titleElm = page.locator('#main h1');\n      await expect(titleElm).toContainText('My Awesome Function Route');\n    });\n  });\n\n  test.describe('Routes with Regex Matches', () => {\n    test('rendering virtual routes with regex matches', async ({ page }) => {\n      const routes = {\n        '/items/(.*)': '# Item Page',\n      };\n\n      await docsifyInit({\n        config: {\n          routes,\n        },\n      });\n\n      await navigateToRoute(page, '/items/banana');\n\n      const titleElm = page.locator('#main h1');\n      await expect(titleElm).toContainText('Item Page');\n    });\n\n    test('virtual route functions should get the route as first parameter', async ({\n      page,\n    }) => {\n      const routes = {\n        '/pets/(.*)': route => '# Route: /pets/dog',\n      };\n\n      await docsifyInit({\n        config: {\n          routes,\n        },\n      });\n\n      await navigateToRoute(page, '/pets/dog');\n\n      const titleElm = page.locator('#main h1');\n      await expect(titleElm).toContainText('Route: /pets/dog');\n    });\n\n    test('virtual route functions should get the matched array as second parameter', async ({\n      page,\n    }) => {\n      const routes = {\n        '/pets/(.*)'(_, matched) {\n          return `# Pets Page (${matched[1]})`;\n        },\n      };\n\n      await docsifyInit({\n        config: {\n          routes,\n        },\n      });\n\n      await navigateToRoute(page, '/pets/cat');\n\n      const titleElm = page.locator('#main h1');\n      await expect(titleElm).toContainText('Pets Page (cat)');\n    });\n  });\n\n  test.describe('Route Matching Specifics', () => {\n    test('routes should be exact match if no regex was passed', async ({\n      page,\n    }) => {\n      const routes = {\n        '/my': '# Incorrect Route - only prefix',\n        '/route': '# Incorrect Route - only postfix',\n        '/my/route': '# Correct Route',\n      };\n\n      await docsifyInit({\n        config: {\n          routes,\n        },\n      });\n\n      await navigateToRoute(page, '/my/route');\n\n      const titleElm = page.locator('#main h1');\n      await expect(titleElm).toContainText('Correct Route');\n    });\n\n    test('if there are two routes that match, the first one should be taken', async ({\n      page,\n    }) => {\n      const routes = {\n        '/multiple/(.+)': '# First Match',\n        '/multiple/(.*)': '# Second Match',\n      };\n\n      await docsifyInit({\n        config: {\n          routes,\n        },\n      });\n\n      await navigateToRoute(page, '/multiple/matches');\n\n      const titleElm = page.locator('#main h1');\n      await expect(titleElm).toContainText('First Match');\n    });\n\n    test('prefer virtual route over a real file, if a virtual route exists', async ({\n      page,\n    }) => {\n      const routes = {\n        '/': '# Virtual Homepage',\n      };\n\n      await docsifyInit({\n        markdown: {\n          homepage: '# Real File Homepage',\n        },\n        config: {\n          routes,\n        },\n      });\n\n      const titleElm = page.locator('#main h1');\n      await expect(titleElm).toContainText('Virtual Homepage');\n    });\n\n    test('fallback to default routing if no route was matched', async ({\n      page,\n    }) => {\n      const routes = {\n        '/a': '# A',\n        '/b': '# B',\n        '/c': '# C',\n      };\n\n      await docsifyInit({\n        markdown: {\n          homepage: '# Real File Homepage',\n        },\n        config: {\n          routes,\n        },\n      });\n\n      await navigateToRoute(page, '/d');\n\n      const mainElm = page.locator('#main');\n      await expect(mainElm).toContainText('404 - Not Found');\n    });\n\n    test('skip routes that returned a falsy value that is not a boolean', async ({\n      page,\n    }) => {\n      const routes = {\n        '/multiple/(.+)': () => null,\n        '/multiple/(.*)': () => undefined,\n        '/multiple/.+': () => 0,\n        '/multiple/.*': () => '# Last Match',\n      };\n\n      await docsifyInit({\n        config: {\n          routes,\n        },\n      });\n\n      await navigateToRoute(page, '/multiple/matches');\n\n      const titleElm = page.locator('#main h1');\n      await expect(titleElm).toContainText('Last Match');\n    });\n\n    test('abort virtual routes and not try the next one, if any matched route returned an explicit \"false\" boolean', async ({\n      page,\n    }) => {\n      const routes = {\n        '/multiple/(.+)': () => false,\n        '/multiple/(.*)': () => \"# You Shouldn't See Me\",\n      };\n\n      await docsifyInit({\n        config: {\n          routes,\n        },\n      });\n\n      await navigateToRoute(page, '/multiple/matches');\n\n      const mainElm = page.locator('#main');\n      await expect(mainElm).toContainText('404 - Not Found');\n    });\n\n    test('skip routes that are not a valid string or function', async ({\n      page,\n    }) => {\n      const routes = {\n        '/multiple/(.+)': 123,\n        '/multiple/(.*)': false,\n        '/multiple/.+': null,\n        '/multiple/..+': [],\n        '/multiple/..*': {},\n        '/multiple/.*': '# Last Match',\n      };\n\n      await docsifyInit({\n        config: {\n          routes,\n        },\n      });\n\n      await navigateToRoute(page, '/multiple/matches');\n\n      const titleElm = page.locator('#main h1');\n      await expect(titleElm).toContainText('Last Match');\n    });\n  });\n});\n"
  },
  {
    "path": "test/e2e/vue.test.js",
    "content": "import { stripIndent } from 'common-tags';\nimport docsifyInit from '../helpers/docsify-init.js';\nimport { test, expect } from './fixtures/docsify-init-fixture.js';\n\nconst vueURL = '/node_modules/vue/dist/vue.global.js';\n\ntest.describe('Vue.js Compatibility', () => {\n  function getSharedConfig() {\n    const config = {\n      config: {\n        vueComponents: {\n          'button-counter': {\n            template: `\n              <button @click=\"counter++\">{{ counter }}</button>\n            `,\n            data: function () {\n              return {\n                counter: 0,\n              };\n            },\n          },\n        },\n        vueGlobalOptions: {\n          data: () => ({\n            counter: 0,\n            msg: 'vueglobaloptions',\n          }),\n        },\n        vueMounts: {\n          '#vuemounts': {\n            data() {\n              return {\n                counter: 0,\n                msg: 'vuemounts',\n              };\n            },\n          },\n        },\n      },\n      markdown: {\n        homepage: stripIndent`\n          <div id=\"vuefor\"><span v-for=\"i in 5\">{{ i }}</span></div>\n\n          <button-counter id=\"vuecomponent\">---</button-counter>\n\n          <div id=\"vueglobaloptions\">\n            <p v-text=\"msg\">---</p>\n            <button @click=\"counter += 1\">+</button>\n            <span>{{ counter }}<span>\n          </div>\n\n          <div id=\"vuemounts\">\n            <p v-text=\"msg\">---</p>\n            <button @click=\"counter += 1\">+</button>\n            <span>{{ counter }}<span>\n          </div>\n\n          <div id=\"vuescript\">\n            <p v-text=\"msg\">---</p>\n            <button @click=\"counter += 1\">+</button>\n            <span>{{ counter }}<span>\n          </div>\n\n          <script>\n            const vueConfig = {\n              data() {\n                return {\n                  counter: 0,\n                  msg: 'vuescript'\n                }\n              },\n            }\n            const vueMountElm = '#vuescript';\n\n            Vue.createApp(vueConfig).mount(vueMountElm);\n          </script>\n        `,\n      },\n    };\n\n    return config;\n  }\n\n  // Tests\n  // ----------------------------------------------------------------------------\n  test('Parse templates and render content when import Vue resources', async ({\n    page,\n  }) => {\n    const docsifyInitConfig = {\n      config: {},\n      markdown: {\n        homepage: stripIndent`\n            <div id=\"vuefor\"><span v-for=\"i in 5\">{{ i }}</span></div>\n          `,\n      },\n    };\n\n    docsifyInitConfig.scriptURLs = vueURL;\n\n    await docsifyInit(docsifyInitConfig);\n    await expect(page.locator('#vuefor')).toHaveText('12345');\n  });\n\n  for (const executeScript of [true, undefined]) {\n    test(`renders content when executeScript is ${executeScript}`, async ({\n      page,\n    }) => {\n      const docsifyInitConfig = getSharedConfig();\n\n      docsifyInitConfig.config.executeScript = executeScript;\n      docsifyInitConfig.scriptURLs = vueURL;\n\n      await docsifyInit(docsifyInitConfig);\n\n      // Static\n      await expect(page.locator('#vuefor')).toHaveText('12345');\n      await expect(page.locator('#vuecomponent')).toHaveText('0');\n      await expect(page.locator('#vueglobaloptions p')).toHaveText(\n        'vueglobaloptions',\n      );\n      await expect(page.locator('#vueglobaloptions > span')).toHaveText('0');\n      await expect(page.locator('#vuemounts p')).toHaveText('vuemounts');\n      await expect(page.locator('#vuemounts > span')).toHaveText('0');\n      await expect(page.locator('#vuescript p')).toHaveText('vuescript');\n      await expect(page.locator('#vuescript > span')).toHaveText('0');\n\n      // Reactive\n      await page.click('#vuecomponent');\n      await expect(page.locator('#vuecomponent')).toHaveText('1');\n      await page.click('#vueglobaloptions button');\n      await expect(page.locator('#vueglobaloptions > span')).toHaveText('1');\n      await page.click('#vuemounts button');\n      await expect(page.locator('#vuemounts > span')).toHaveText('1');\n      await page.click('#vuescript button');\n      await expect(page.locator('#vuescript > span')).toHaveText('1');\n    });\n  }\n\n  test('ignores content when Vue is not present', async ({ page }) => {\n    const docsifyInitConfig = getSharedConfig();\n\n    await docsifyInit(docsifyInitConfig);\n    await page.evaluate(() => 'Vue' in window === false);\n    await expect(page.locator('#vuefor')).toHaveText('{{ i }}');\n    await expect(page.locator('#vuecomponent')).toHaveText('---');\n    await expect(page.locator('#vueglobaloptions p')).toHaveText('---');\n    await expect(page.locator('#vuemounts p')).toHaveText('---');\n    await expect(page.locator('#vuescript p')).toHaveText('---');\n  });\n\n  test('ignores content when vueGlobalOptions is undefined', async ({\n    page,\n  }) => {\n    const docsifyInitConfig = getSharedConfig();\n\n    docsifyInitConfig.config.vueGlobalOptions = undefined;\n    docsifyInitConfig.scriptURLs = vueURL;\n\n    await docsifyInit(docsifyInitConfig);\n    await expect(page.locator('#vuefor')).toHaveText('12345');\n    await expect(page.locator('#vuecomponent')).toHaveText('0');\n    await expect(page.locator('#vuecomponent')).toHaveText('0');\n    // eslint-disable-next-line playwright/prefer-web-first-assertions\n    expect(await page.locator('#vueglobaloptions p').innerText()).toBe('');\n    await expect(page.locator('#vuemounts p')).toHaveText('vuemounts');\n    await expect(page.locator('#vuescript p')).toHaveText('vuescript');\n  });\n\n  test('ignores content when vueMounts is undefined', async ({ page }) => {\n    const docsifyInitConfig = getSharedConfig();\n\n    docsifyInitConfig.config.vueMounts['#vuemounts'] = undefined;\n    docsifyInitConfig.scriptURLs = vueURL;\n\n    await docsifyInit(docsifyInitConfig);\n    await expect(page.locator('#vuefor')).toHaveText('12345');\n    await expect(page.locator('#vuecomponent')).toHaveText('0');\n    await expect(page.locator('#vueglobaloptions p')).toHaveText(\n      'vueglobaloptions',\n    );\n    await expect(page.locator('#vuemounts p')).toHaveText('vueglobaloptions');\n    await expect(page.locator('#vuescript p')).toHaveText('vuescript');\n  });\n\n  test('ignores <script> when executeScript is false', async ({ page }) => {\n    const docsifyInitConfig = getSharedConfig();\n\n    docsifyInitConfig.config.executeScript = false;\n    docsifyInitConfig.scriptURLs = vueURL;\n\n    await docsifyInit(docsifyInitConfig);\n    await expect(page.locator('#vuescript p')).toHaveText('vueglobaloptions');\n  });\n});\n"
  },
  {
    "path": "test/helpers/docsify-init.js",
    "content": "/* globals page */\nimport _mock, { proxy } from 'xhr-mock';\n\nimport axios from 'axios';\nimport prettier from 'prettier';\nimport { stripIndent } from 'common-tags';\nimport { waitForSelector } from './wait-for.js';\n\nconst mock = _mock.default;\nconst docsifyPATH = '../../dist/docsify.js'; // JSDOM\nconst docsifyURL = '/dist/docsify.js'; // Playwright\n\n/**\n * Jest / Playwright helper for creating custom docsify test sites\n *\n * @param {Object} options options object\n * @param {Function|Object} [options.config] docsify configuration (merged with default)\n * @param {String} [options.html] HTML content to use for docsify `index.html` page\n * @param {Object} [options.markdown] Docsify markdown content\n * @param {String} [options.markdown.coverpage] coverpage markdown\n * @param {String} [options.markdown.homepage] homepage markdown\n * @param {String} [options.markdown.navbar] navbar markdown\n * @param {String} [options.markdown.sidebar] sidebar markdown\n * @param {Object} [options.routes] custom routes defined as `{ pathOrGlob: responseText }`\n * @param {String} [options.script] JS to inject via <script> tag\n * @param {String|String[]} [options.scriptURLs] External JS to inject via <script src=\"...\"> tag(s)\n * @param {String} [options.style] CSS to inject via <style> tag\n * @param {String|String[]} [options.styleURLs=['/dist/themes/core.css']] External CSS to inject via <link rel=\"stylesheet\"> tag(s)\n * @param {String} [options.testURL] URL to set as window.location.href\n * @param {String} [options.waitForSelector='#main'] Element to wait for before returning promise\n * @param {Boolean|Object|String} [options._logHTML] Logs HTML to console after initialization. Accepts CSS selector.\n * @param {Boolean} [options._logHTML.format=true] Formats HTML output\n * @param {String} [options._logHTML.selector='html'] CSS selector(s) to match and log HTML for\n * @returns {Promise}\n */\nasync function docsifyInit(options = {}) {\n  const isJSDOM = 'window' in global;\n  const isPlaywright = 'page' in global;\n\n  const defaults = {\n    config: {\n      basePath: process.env.TEST_HOST,\n      el: '#app',\n    },\n    html: `\n      <!DOCTYPE html>\n      <html>\n        <head>\n          <meta charset=\"UTF-8\" />\n        </head>\n        <body>\n          <div id=\"app\"></div>\n        </body>\n      </html>\n    `,\n    markdown: {\n      coverpage: '',\n      homepage: '',\n      navbar: '',\n      sidebar: '',\n    },\n    routes: {},\n    script: '',\n    scriptURLs: [],\n    style: '',\n    styleURLs: [],\n    testURL: `${process.env.TEST_HOST}/docsify-init.html`,\n    waitForSelector: '#main > *:first-child',\n  };\n  const settings = {\n    ...defaults,\n    ...options,\n    get config() {\n      const sharedConfig = {\n        ...defaults.config,\n        // Conditionally set options but allow explicit overrides\n        coverpage: Boolean(settings.markdown.coverpage),\n        loadNavbar: Boolean(settings.markdown.navbar),\n        loadSidebar: Boolean(settings.markdown.sidebar),\n      };\n\n      const updateBasePath = config => {\n        if (config.basePath) {\n          config.basePath = new URL(\n            config.basePath,\n            process.env.TEST_HOST,\n          ).href;\n        }\n      };\n\n      // Config as function\n      if (typeof options.config === 'function') {\n        return function (vm) {\n          const config = { ...sharedConfig, ...options.config(vm) };\n\n          updateBasePath(config);\n\n          return config;\n        };\n      }\n      // Config as object\n      else {\n        const config = { ...sharedConfig, ...options.config };\n\n        updateBasePath(config);\n\n        return config;\n      }\n    },\n    get markdown() {\n      return Object.fromEntries(\n        Object.entries({\n          ...defaults.markdown,\n          ...options.markdown,\n        })\n          .filter(([key, markdown]) => key && markdown)\n          .map(([key, markdown]) => [key, stripIndent`${markdown}`]),\n      );\n    },\n    get routes() {\n      const helperRoutes = {\n        [settings.testURL]: stripIndent`${settings.html}`,\n        'README.md': settings.markdown.homepage,\n        '_coverpage.md': settings.markdown.coverpage,\n        '_navbar.md': settings.markdown.navbar,\n        '_sidebar.md': settings.markdown.sidebar,\n      };\n\n      const finalRoutes = Object.fromEntries(\n        Object.entries({\n          ...helperRoutes,\n          ...options.routes,\n        })\n          // Remove items with falsey responseText\n          .filter(([url, responseText]) => url && responseText)\n          .map(([url, responseText]) => [\n            // Convert relative to absolute URL\n            new URL(url, settings.config.basePath || process.env.TEST_HOST)\n              .href,\n            // Strip indentation from responseText\n            stripIndent`${responseText}`,\n          ]),\n      );\n\n      return finalRoutes;\n    },\n    // Merge scripts and remove duplicates\n    scriptURLs: []\n      .concat(options.scriptURLs || '')\n      .filter(url => url)\n      .map(url => new URL(url, process.env.TEST_HOST).href),\n    styleURLs: []\n      .concat(options.styleURLs || '')\n      .filter(url => url)\n      .map(url => new URL(url, process.env.TEST_HOST).href),\n  };\n\n  // Routes\n  const contentTypes = {\n    css: 'text/css',\n    html: 'text/html',\n    js: 'application/javascript',\n    json: 'application/json',\n    md: 'text/markdown',\n  };\n  const reFileExtentionFromURL = new RegExp(\n    '(?:.)(' + Object.keys(contentTypes).join('|') + ')(?:[?#].*)?$',\n    'i',\n  );\n\n  if (isJSDOM) {\n    // Replace the global XMLHttpRequest object\n    mock.setup();\n  }\n\n  for (let [urlGlob, response] of Object.entries(settings.routes)) {\n    const fileExtension = (urlGlob.match(reFileExtentionFromURL) || [])[1];\n    const contentType = contentTypes[fileExtension];\n\n    if (typeof response === 'string') {\n      response = {\n        status: 200,\n        body: response,\n      };\n    }\n\n    // Specifying contentType required for Webkit\n    response.contentType = response.contentType || contentType || '';\n\n    if (isJSDOM) {\n      mock.get(urlGlob, (req, res) => {\n        return res\n          .status(response.status)\n          .body(settings.routes[urlGlob])\n          .header('Content-Type', contentType);\n      });\n    } else {\n      await page.route(urlGlob, route => route.fulfill(response));\n    }\n  }\n\n  if (isJSDOM) {\n    // Proxy unhandled requests to real server(s)\n    mock.use(proxy);\n  }\n\n  // Set test URL / HTML\n  if (isJSDOM) {\n    window.history.pushState({}, 'docsify', settings.testURL);\n    document.documentElement.innerHTML = settings.html;\n  } else if (isPlaywright) {\n    await page.goto(settings.testURL);\n    await page.waitForLoadState();\n  }\n\n  // Docsify configuration\n  if (isJSDOM) {\n    window.$docsify = settings.config;\n  } else if (isPlaywright) {\n    // Convert config functions to strings\n    const configString = JSON.stringify(settings.config, (key, val) =>\n      typeof val === 'function' ? `__FN__${val.toString()}` : val,\n    );\n\n    await page.evaluate(config => {\n      // Restore config functions from strings\n      const configObj = JSON.parse(config, (key, val) => {\n        if (/^__FN__/.test(val)) {\n          let source = val.split('__FN__')[1];\n\n          // f.e. `foo() {}` or `'bar!?'() {}` without the `function ` prefix\n          const isConcise =\n            !source.includes('function') && !source.includes('=>');\n\n          if (isConcise) {\n            source = `{ ${source} }`;\n          } else {\n            source = `{ _: ${source} }`;\n          }\n\n          return new Function(/* js */ `\n            const o = ${source}\n            const keys = Object.keys(o)\n            return o[keys[0]]\n          `)();\n        } else {\n          return val;\n        }\n      });\n\n      window.$docsify = configObj;\n    }, configString);\n  }\n\n  // Style URLs\n  if (isJSDOM) {\n    for (const url of settings.styleURLs) {\n      const linkElm = document.createElement('link');\n\n      linkElm.setAttribute('rel', 'stylesheet');\n      linkElm.setAttribute('type', 'text/css');\n      linkElm.setAttribute('href', url);\n      document.head.appendChild(linkElm);\n    }\n  } else if (isPlaywright) {\n    await Promise.all(settings.styleURLs.map(url => page.addStyleTag({ url })));\n  }\n\n  // JavaScript URLs\n  if (isJSDOM) {\n    for (const url of settings.scriptURLs) {\n      const responseText = settings.routes[url] || (await axios.get(url)).data;\n      const scriptElm = document.createElement('script');\n\n      scriptElm.setAttribute('data-src', url);\n      scriptElm.textContent = stripIndent`${responseText}`;\n      document.head.appendChild(scriptElm);\n    }\n\n    const isDocsifyLoaded = 'Docsify' in window;\n\n    if (!isDocsifyLoaded) {\n      await import(docsifyPATH);\n    }\n  } else if (isPlaywright) {\n    for (const url of settings.scriptURLs) {\n      await page.addScriptTag({ url });\n    }\n\n    const isDocsifyLoaded = await page.evaluate(() => 'Docsify' in window);\n\n    if (!isDocsifyLoaded) {\n      await page.addScriptTag({ url: docsifyURL });\n    }\n  }\n\n  // Style\n  if (settings.style) {\n    if (isJSDOM) {\n      const headElm = document.querySelector('head');\n      const styleElm = document.createElement('style');\n\n      styleElm.textContent = stripIndent`${settings.style}`;\n      headElm.appendChild(styleElm);\n    } else if (isPlaywright) {\n      await page.evaluate(\n        data => {\n          const headElm = document.querySelector('head');\n          const styleElm = document.createElement('style');\n\n          styleElm.textContent = data;\n          headElm.appendChild(styleElm);\n        },\n        stripIndent`${settings.style}`,\n      );\n    }\n  }\n\n  // JavaScript\n  if (settings.script) {\n    if (isJSDOM) {\n      const scriptElm = document.createElement('script');\n\n      scriptElm.textContent = stripIndent`${settings.script}`;\n      document.head.appendChild(scriptElm);\n    } else if (isPlaywright) {\n      await page.addScriptTag({ content: settings.script });\n    }\n  }\n\n  // Docsify \"Ready\"\n  if (isJSDOM) {\n    await waitForSelector(settings.waitForSelector);\n  } else if (isPlaywright) {\n    // await page.waitForSelector(settings.waitForSelector);\n    await page.locator(settings.waitForSelector).waitFor();\n    await page.waitForLoadState('load');\n  }\n\n  // Log HTML to console\n  if (settings._logHTML) {\n    const selector =\n      typeof settings._logHTML === 'string'\n        ? settings._logHTML\n        : settings._logHTML.selector;\n\n    let htmlArr = [];\n\n    if (selector) {\n      if (isJSDOM) {\n        htmlArr = [...document.querySelectorAll(selector)].map(\n          elm => elm.outerHTML,\n        );\n      } else {\n        htmlArr = await page.evaluateAll(selector, elms =>\n          elms.map(e => e.outerHTML),\n        );\n      }\n    } else {\n      htmlArr = [\n        isJSDOM ? document.documentElement.outerHTML : await page.content(),\n      ];\n    }\n\n    if (htmlArr.length) {\n      htmlArr.forEach(html => {\n        if (settings._logHTML.format !== false) {\n          html = prettier.format(html, { parser: 'html' });\n        }\n\n        console.log(html);\n      });\n    } else {\n      console.warn(`docsify-init(): unable to match selector '${selector}'`);\n    }\n  }\n\n  return Promise.resolve();\n}\n\nexport default docsifyInit;\n"
  },
  {
    "path": "test/helpers/wait-for.js",
    "content": "const defaults = {\n  delay: 100,\n  timeout: 4000,\n};\n\n/**\n * Waits for specified function to resolve to a truthy value.\n *\n * @param {Function} fn function to be evaluated until truthy\n * @param {*} arg optional argument to pass to `fn`\n * @param {Object} options optional parameters\n * @param {number} options.delay delay between fn invocations\n * @param {number} options.timeout timeout in milliseconds\n * @returns {Promise} promise which resolves to the truthy fn return value or\n * rejects to an error object or last non-truthy fn return value\n */\nfunction waitForFunction(fn, arg, options = {}) {\n  const settings = {\n    ...defaults,\n    ...options,\n  };\n\n  return new Promise((resolve, reject) => {\n    let timeElapsed = 0;\n    let lastError;\n\n    const int = setInterval(() => {\n      let result;\n\n      try {\n        result = fn(arg);\n      } catch (err) {\n        lastError = err;\n      }\n\n      if (result) {\n        clearInterval(int);\n        resolve(result);\n      }\n\n      timeElapsed += settings.delay;\n\n      if (timeElapsed >= settings.timeout) {\n        console.error(\n          `\\nwaitForFunction did not return a truthy value within ${settings.timeout} ms.\\n`,\n        );\n        reject(lastError || result);\n      }\n    }, settings.delay);\n  });\n}\n\n/**\n * Waits for specified CSS selector to be located in the DOM\n *\n * @param {String} cssSelector CSS selector to query for\n * @param {Object} options optional parameters\n * @param {number} options.delay delay between checks\n * @param {number} options.timeout timeout in milliseconds\n * @returns {Promise} promise which resolves to first matching element\n */\nfunction waitForSelector(cssSelector, options = {}) {\n  const settings = {\n    ...defaults,\n    ...options,\n  };\n\n  return new Promise((resolve, reject) => {\n    let timeElapsed = 0;\n\n    const int = setInterval(() => {\n      const elm = document.querySelector(cssSelector);\n      if (elm) {\n        clearInterval(int);\n        resolve(elm);\n      }\n\n      timeElapsed += settings.delay;\n\n      if (timeElapsed >= settings.timeout) {\n        const msg = `waitForSelector did not match CSS selector '${cssSelector}' (${settings.timeout} ms)`;\n\n        reject(msg);\n      }\n    }, settings.delay);\n  });\n}\n\n/**\n * Waits for specified CSS selector to contain text content\n *\n * @param {String} cssSelector CSS selector to query for\n * @param {String} text text to match\n * @param {Object} options optional parameters\n * @param {number} options.delay delay between checks\n * @param {number} options.timeout timeout in milliseconds\n * @returns {Promise} promise which resolves to first matching element that contains specified text\n */\nfunction waitForText(cssSelector, text, options = {}) {\n  const settings = {\n    ...defaults,\n    ...options,\n  };\n\n  return new Promise((resolve, reject) => {\n    let timeElapsed = 0;\n\n    waitForSelector(cssSelector, settings)\n      .then(elm => {\n        const int = setInterval(() => {\n          const isMatch = elm.textContent.includes(text);\n\n          if (isMatch) {\n            clearInterval(int);\n            resolve(true);\n          }\n\n          timeElapsed += settings.delay;\n\n          if (timeElapsed >= settings.timeout) {\n            const msg = `waitForText did not find '${text}' in CSS selector '${cssSelector}' (${settings.timeout} ms): '${elm.textContent}'`;\n\n            reject(msg);\n          }\n        }, settings.delay);\n      })\n      .catch(() => {\n        const msg = `waitForText did not match CSS selector '${cssSelector}' (${settings.timeout} ms)`;\n\n        reject(msg);\n      });\n  });\n}\n\nexport { waitForFunction, waitForSelector, waitForText };\n"
  },
  {
    "path": "test/integration/__snapshots__/docs.test.js.snap",
    "content": "// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing\n\nexports[`Docs Site coverpage renders and is unchanged 1`] = `\n\"<section class=\"cover show\" role=\"complementary\" aria-label=\"cover\">\n      <div class=\"mask\"></div>\n      <div class=\"cover-main\"><!-- markdownlint-disable first-line-h1 -->\n\n<p><img src=\"http://127.0.0.1:4000/_media/icon.svg\" data-origin=\"_media/icon.svg\" alt=\"logo\"></p><h1 id=\"docsify\" tabindex=\"-1\"><a href=\"#/?id=docsify\" data-id=\"docsify\" class=\"anchor\"><span>docsify <small>5.0.0-rc.4</small></span></a></h1><blockquote><p>A magical documentation site generator</p></blockquote><ul><li>Simple and lightweight</li><li>No statically built HTML files</li><li>Multiple themes</li></ul><p><a href=\"#/?id=docsify\" class=\"button primary\">Get Started</a>\n<a href=\"https://github.com/docsifyjs/docsify/\" target=\"_blank\" rel=\"noopener\" class=\"button secondary\">GitHub</a></p><!-- ![color](#f0f0f0) -->\n<!-- ![](/_media/icon.svg) -->\n</div>\n    </section>\"\n`;\n\nexports[`Docs Site navbar renders and is unchanged 1`] = `\n\"<nav class=\"app-nav\" aria-label=\"secondary\"><!-- markdownlint-disable first-line-h1 -->\n\n<ul><li><p>Translations</p><ul><li><a href=\"#/\">English</a></li><li><a href=\"#/zh-cn/\">简体中文</a></li></ul></li></ul></nav>\"\n`;\n\nexports[`Docs Site sidebar renders and is unchanged 1`] = `\n\"<aside id=\"__sidebar\" class=\"sidebar show\" tabindex=\"-1\" role=\"none\">\n      \n      <div class=\"sidebar-nav\" role=\"navigation\" aria-label=\"primary\"><!-- markdownlint-disable first-line-h1 -->\n\n<ul><li><p>Getting started</p><ul><li><a href=\"#/quickstart\" class=\"page-link\">Quick start</a></li><li><a href=\"#/adding-pages\" class=\"page-link\">Adding pages</a></li><li><a href=\"#/cover\" class=\"page-link\">Cover page</a></li><li><a href=\"#/custom-navbar\" class=\"page-link\">Custom navbar</a></li></ul></li><li><p>Customization</p><ul><li><a href=\"#/configuration\" class=\"page-link\">Configuration</a></li><li><a href=\"#/themes\" class=\"page-link\">Themes</a></li><li><a href=\"#/plugins\" class=\"page-link\">List of Plugins</a></li><li><a href=\"#/write-a-plugin\" class=\"page-link\">Write a Plugin</a></li><li><a href=\"#/markdown\" class=\"page-link\">Markdown configuration</a></li><li><a href=\"#/language-highlight\" class=\"page-link\">Language highlighting</a></li><li><a href=\"#/emoji\" class=\"page-link\">Emoji</a></li></ul></li><li><p>Guide</p><ul><li><a href=\"#/deploy\" class=\"page-link\">Deploy</a></li><li><a href=\"#/helpers\" class=\"page-link\">Helpers</a></li><li><a href=\"#/vue\" class=\"page-link\">Vue compatibility</a></li><li><a href=\"#/cdn\" class=\"page-link\">CDN</a></li><li><a href=\"#/pwa\" class=\"page-link\">Offline Mode (PWA)</a></li><li><a href=\"#/embed-files\" class=\"page-link\">Embed Files</a></li><li><a href=\"#/ui-kit\" class=\"page-link\">UI Kit</a></li></ul></li><li><p>Upgrading</p><ul><li><a href=\"#/v5-upgrade\" class=\"page-link\">v4 to v5</a></li></ul></li></ul><ul><li><a href=\"#/awesome\" class=\"page-link\">Awesome docsify</a></li><li><a href=\"#/changelog\" class=\"page-link\">Changelog</a></li></ul></div>\n    </aside>\"\n`;\n"
  },
  {
    "path": "test/integration/__snapshots__/emoji.test.js.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Emoji Ignores all emoji shorthand codes (noEmoji:true) 1`] = `\"<p>:smile:</p><p>:smile::smile:</p><p>:smile: :smile:</p><p>:smile::smile::smile:</p><p>:smile: :smile: :smile:</p><p>text:smile:</p><p>:smile:text</p><p>text:smile:text</p>\"`;\n\nexports[`Emoji Ignores emoji shorthand codes in URIs 1`] = `\"<p>Url <a href=\"https://docsify.js.org/:foo:/\" target=\"_blank\" rel=\"noopener\">https://docsify.js.org/:foo:/</a> <a href=\"http://docsify.js.org/:100:/\" target=\"_blank\" rel=\"noopener\">http://docsify.js.org/:100:/</a> <a href=\"ftp://docsify.js.org/:smile:/\" target=\"_blank\" rel=\"noopener\">ftp://docsify.js.org/:smile:/</a></p>\"`;\n\nexports[`Emoji Ignores emoji shorthand codes in URIs while handling anchor content 1`] = `\"<p>Achor tags <a href=\"http://docsify.js.org/:100:/\" target=\"_blank\" rel=\"noopener\"><img src=\"https://github.githubassets.com/images/icons/emoji/unicode/1f4af.png?v8.png\" alt=\"100\" class=\"emoji\" loading=\"lazy\"></a></p>\"`;\n\nexports[`Emoji Ignores emoji shorthand codes in code, pre, script, and template tags 1`] = `\n\"<pre>:100:</pre>\n\n<p><code>:100:</code></p><script>\n  var test = ':100:';\n</script>\n\n<template>\n  <p>:100</p>\n</template>\"\n`;\n\nexports[`Emoji Ignores emoji shorthand codes in comments 1`] = `\"<p>Text <!-- :foo: :100: --></p>\"`;\n\nexports[`Emoji Ignores emoji shorthand codes in html attributes 1`] = `\"<p><a href=\"http://domain.com/:smile:/\"> <img src=\"http://domain.com/:smile:/file.png\"> <script src=\"http://domain.com/:smile:/file.js\"></script></a></p>\"`;\n\nexports[`Emoji Ignores emoji shorthand codes in style url() values 1`] = `\"<style>@import url(http://domain.com/:smile/file.css);</style>\"`;\n\nexports[`Emoji Ignores unmatched emoji shorthand codes 1`] = `\"<p>hh:mm</p><p>hh:mm:ss</p><p>Namespace::SubNameSpace</p><p>Namespace::SubNameSpace::Class</p><p>2014-12-29T16:11:20+00:00</p>\"`;\n\nexports[`Emoji Renders GitHub emoji images (nativeEmoji:false) 1`] = `\"<p><img src=\"https://github.githubassets.com/images/icons/emoji/unicode/1f604.png?v8.png\" alt=\"smile\" class=\"emoji\" loading=\"lazy\"></p><p><img src=\"https://github.githubassets.com/images/icons/emoji/unicode/1f604.png?v8.png\" alt=\"smile\" class=\"emoji\" loading=\"lazy\"><img src=\"https://github.githubassets.com/images/icons/emoji/unicode/1f604.png?v8.png\" alt=\"smile\" class=\"emoji\" loading=\"lazy\"></p><p><img src=\"https://github.githubassets.com/images/icons/emoji/unicode/1f604.png?v8.png\" alt=\"smile\" class=\"emoji\" loading=\"lazy\"> <img src=\"https://github.githubassets.com/images/icons/emoji/unicode/1f604.png?v8.png\" alt=\"smile\" class=\"emoji\" loading=\"lazy\"></p><p><img src=\"https://github.githubassets.com/images/icons/emoji/unicode/1f604.png?v8.png\" alt=\"smile\" class=\"emoji\" loading=\"lazy\"><img src=\"https://github.githubassets.com/images/icons/emoji/unicode/1f604.png?v8.png\" alt=\"smile\" class=\"emoji\" loading=\"lazy\"><img src=\"https://github.githubassets.com/images/icons/emoji/unicode/1f604.png?v8.png\" alt=\"smile\" class=\"emoji\" loading=\"lazy\"></p><p><img src=\"https://github.githubassets.com/images/icons/emoji/unicode/1f604.png?v8.png\" alt=\"smile\" class=\"emoji\" loading=\"lazy\"> <img src=\"https://github.githubassets.com/images/icons/emoji/unicode/1f604.png?v8.png\" alt=\"smile\" class=\"emoji\" loading=\"lazy\"> <img src=\"https://github.githubassets.com/images/icons/emoji/unicode/1f604.png?v8.png\" alt=\"smile\" class=\"emoji\" loading=\"lazy\"></p><p>text<img src=\"https://github.githubassets.com/images/icons/emoji/unicode/1f604.png?v8.png\" alt=\"smile\" class=\"emoji\" loading=\"lazy\"></p><p><img src=\"https://github.githubassets.com/images/icons/emoji/unicode/1f604.png?v8.png\" alt=\"smile\" class=\"emoji\" loading=\"lazy\">text</p><p>text<img src=\"https://github.githubassets.com/images/icons/emoji/unicode/1f604.png?v8.png\" alt=\"smile\" class=\"emoji\" loading=\"lazy\">text</p>\"`;\n\nexports[`Emoji Renders native emoji characters (nativeEmoji:true) 1`] = `\"<p><span class=\"emoji\">😄︎</span></p><p><span class=\"emoji\">😄︎</span><span class=\"emoji\">😄︎</span></p><p><span class=\"emoji\">😄︎</span> <span class=\"emoji\">😄︎</span></p><p><span class=\"emoji\">😄︎</span><span class=\"emoji\">😄︎</span><span class=\"emoji\">😄︎</span></p><p><span class=\"emoji\">😄︎</span> <span class=\"emoji\">😄︎</span> <span class=\"emoji\">😄︎</span></p><p>text<span class=\"emoji\">😄︎</span></p><p><span class=\"emoji\">😄︎</span>text</p><p>text<span class=\"emoji\">😄︎</span>text</p>\"`;\n"
  },
  {
    "path": "test/integration/docs.test.js",
    "content": "import { jest } from '@jest/globals';\nimport docsifyInit from '../helpers/docsify-init.js';\n\n// Suite\n// -----------------------------------------------------------------------------\ndescribe('Docs Site', function () {\n  // Tests\n  // ---------------------------------------------------------------------------\n  test('coverpage renders and is unchanged', async () => {\n    // Override Math.random implementation to prevent random gradient values\n    // used as background image from causing test to fail\n    const mathSpy = jest.spyOn(Math, 'random').mockReturnValue(0.5);\n\n    await docsifyInit({\n      config: {\n        coverpage: '_coverpage.md',\n      },\n      markdown: {\n        homepage: '# Hello World',\n      },\n      waitForSelector: '.cover-main > *',\n    });\n\n    const coverpageElm = document.querySelector('section.cover');\n\n    // Test snapshots\n    expect(mathSpy).toHaveBeenCalled();\n    expect(coverpageElm).not.toBeNull();\n    expect(coverpageElm.outerHTML).toMatchSnapshot();\n  });\n\n  test('sidebar renders and is unchanged', async () => {\n    await docsifyInit({\n      config: {\n        loadSidebar: '_sidebar.md',\n      },\n      markdown: {\n        homepage: '# Hello World',\n      },\n      waitForSelector: '.sidebar-nav > ul',\n    });\n\n    const sidebarElm = document.querySelector('.sidebar');\n\n    // Test snapshots\n    expect(sidebarElm).not.toBeNull();\n    expect(sidebarElm.outerHTML).toMatchSnapshot();\n  });\n\n  test('navbar renders and is unchanged', async () => {\n    await docsifyInit({\n      config: {\n        loadNavbar: '_navbar.md',\n      },\n      markdown: {\n        homepage: '# Hello World',\n      },\n      waitForSelector: '.app-nav > ul',\n    });\n\n    const navbarElm = document.querySelector('nav.app-nav');\n\n    // Test snapshots\n    expect(navbarElm).not.toBeNull();\n    expect(navbarElm.outerHTML).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "test/integration/docsify.test.js",
    "content": "import { jest } from '@jest/globals';\nimport docsifyInit from '../helpers/docsify-init.js';\n\n// Suite\n// -----------------------------------------------------------------------------\ndescribe('Docsify', function () {\n  // Tests\n  // ---------------------------------------------------------------------------\n  test('allows $docsify configuration to be a function', async () => {\n    const testConfig = jest.fn(vm => {\n      expect(vm).toBeInstanceOf(Object);\n      expect(vm.constructor.name).toBe('Docsify');\n      expect(vm.$fetch).toBeInstanceOf(Function);\n      expect(vm.route).toBeInstanceOf(Object);\n    });\n\n    await docsifyInit({\n      config: testConfig,\n    });\n\n    expect(typeof Docsify).toBe('object');\n    expect(testConfig).toHaveBeenCalled();\n  });\n\n  test('provides the hooks and vm API to plugins', async () => {\n    const testConfig = jest.fn(vm => {\n      const vm1 = vm;\n\n      return {\n        plugins: [\n          function (hook, vm2) {\n            expect(vm1).toEqual(vm2);\n\n            expect(hook.init).toBeInstanceOf(Function);\n            expect(hook.beforeEach).toBeInstanceOf(Function);\n            expect(hook.afterEach).toBeInstanceOf(Function);\n            expect(hook.doneEach).toBeInstanceOf(Function);\n            expect(hook.mounted).toBeInstanceOf(Function);\n            expect(hook.ready).toBeInstanceOf(Function);\n          },\n        ],\n      };\n    });\n\n    await docsifyInit({\n      config: testConfig,\n    });\n\n    expect(typeof Docsify).toBe('object');\n    expect(testConfig).toHaveBeenCalled();\n  });\n});\n"
  },
  {
    "path": "test/integration/emoji.test.js",
    "content": "import docsifyInit from '../helpers/docsify-init.js';\n\n// Suite\n// -----------------------------------------------------------------------------\ndescribe('Emoji', function () {\n  // Tests\n  // ---------------------------------------------------------------------------\n  const emojiMarkdown = `\n    :smile:\n\n    :smile::smile:\n\n    :smile: :smile:\n\n    :smile::smile::smile:\n\n    :smile: :smile: :smile:\n\n    text:smile:\n\n    :smile:text\n\n    text:smile:text\n  `;\n\n  test('Renders native emoji characters (nativeEmoji:true)', async () => {\n    await docsifyInit({\n      config: {\n        nativeEmoji: true,\n      },\n      markdown: {\n        homepage: emojiMarkdown,\n      },\n      // _logHTML: true,\n    });\n\n    const mainElm = document.querySelector('#main');\n\n    expect(mainElm.innerHTML).toMatchSnapshot();\n  });\n\n  test('Renders GitHub emoji images (nativeEmoji:false)', async () => {\n    await docsifyInit({\n      config: {\n        nativeEmoji: false,\n      },\n      markdown: {\n        homepage: emojiMarkdown,\n      },\n      // _logHTML: true,\n    });\n\n    const mainElm = document.querySelector('#main');\n\n    expect(mainElm.innerHTML).toMatchSnapshot();\n  });\n\n  test('Ignores all emoji shorthand codes (noEmoji:true)', async () => {\n    await docsifyInit({\n      config: {\n        noEmoji: true,\n      },\n      markdown: {\n        homepage: emojiMarkdown,\n      },\n      // _logHTML: true,\n    });\n\n    const mainElm = document.querySelector('#main');\n\n    expect(mainElm.innerHTML).toMatchSnapshot();\n  });\n\n  test('Ignores unmatched emoji shorthand codes', async () => {\n    await docsifyInit({\n      markdown: {\n        homepage: `\n          hh:mm\n\n          hh:mm:ss\n\n          Namespace::SubNameSpace\n\n          Namespace::SubNameSpace::Class\n\n          2014-12-29T16:11:20+00:00\n        `,\n      },\n      // _logHTML: true,\n    });\n\n    const mainElm = document.querySelector('#main');\n\n    expect(mainElm.innerHTML).toMatchSnapshot();\n  });\n\n  test('Ignores emoji shorthand codes in comments', async () => {\n    await docsifyInit({\n      markdown: {\n        homepage: 'Text <!-- :foo: :100: -->',\n      },\n      // _logHTML: true,\n    });\n\n    const mainElm = document.querySelector('#main');\n\n    expect(mainElm.innerHTML).toMatchSnapshot();\n  });\n\n  test('Ignores emoji shorthand codes in URIs', async () => {\n    await docsifyInit({\n      markdown: {\n        homepage:\n          'Url https://docsify.js.org/:foo:/ http://docsify.js.org/:100:/ ftp://docsify.js.org/:smile:/',\n      },\n      // _logHTML: true,\n    });\n\n    const mainElm = document.querySelector('#main');\n\n    expect(mainElm.innerHTML).toMatchSnapshot();\n  });\n\n  test('Ignores emoji shorthand codes in URIs while handling anchor content', async () => {\n    await docsifyInit({\n      markdown: {\n        homepage: 'Achor tags [:100:](http://docsify.js.org/:100:/)',\n      },\n      // _logHTML: true,\n    });\n\n    const mainElm = document.querySelector('#main');\n\n    expect(mainElm.innerHTML).toMatchSnapshot();\n  });\n\n  test('Ignores emoji shorthand codes in html attributes', async () => {\n    await docsifyInit({\n      markdown: {\n        homepage:\n          /* html */ '<a href=\"http://domain.com/:smile:/\"> <img src=\\'http://domain.com/:smile:/file.png\\'> <script src=http://domain.com/:smile:/file.js></script>',\n      },\n      // _logHTML: true,\n    });\n\n    const mainElm = document.querySelector('#main');\n\n    expect(mainElm.innerHTML).toMatchSnapshot();\n  });\n\n  test('Ignores emoji shorthand codes in style url() values', async () => {\n    await docsifyInit({\n      markdown: {\n        homepage:\n          /* html */ '<style>@import url(http://domain.com/:smile/file.css);</style>',\n      },\n      // _logHTML: true,\n    });\n\n    const mainElm = document.querySelector('#main');\n\n    expect(mainElm.innerHTML).toMatchSnapshot();\n  });\n\n  test('Ignores emoji shorthand codes in code, pre, script, and template tags', async () => {\n    await docsifyInit({\n      markdown: {\n        homepage: /* html */ `\n          <pre>:100:</pre>\n\n          <code>:100:</code>\n\n          <script>\n            var test = ':100:';\n          </script>\n\n          <template>\n            <p>:100</p>\n          </template>\n        `,\n      },\n      // _logHTML: true,\n    });\n\n    const mainElm = document.querySelector('#main');\n\n    expect(mainElm.innerHTML).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "test/integration/example.test.js",
    "content": "import { waitForFunction, waitForText } from '../helpers/wait-for.js';\nimport docsifyInit from '../helpers/docsify-init.js';\n\ndescribe('Creating a Docsify site (integration tests in Jest)', function () {\n  test('Docsify /docs/ site using docsifyInit()', async () => {\n    await docsifyInit({\n      // _logHTML: true,\n    });\n\n    // Verify config options\n    expect(typeof window.$docsify).toBe('object');\n\n    // Verify options.markdown content was rendered\n    expect(document.querySelector('#main').textContent).toContain(\n      'A magical documentation site generator',\n    );\n  });\n\n  test('kitchen sink docsify site using docsifyInit()', async () => {\n    const docsifyInitConfig = {\n      config: {\n        name: 'Docsify Name',\n      },\n      markdown: {\n        coverpage: `\n          # Docsify Test\n\n          > Testing a magical documentation site generator\n\n          [GitHub](https://github.com/docsifyjs/docsify/)\n        `,\n        homepage: `\n          # Hello World\n\n          This is the homepage.\n        `,\n        navbar: `\n          - [docsify.js.org](https://docsify.js.org/#/)\n        `,\n        sidebar: `\n          - [Test Page](test)\n        `,\n      },\n      routes: {\n        'test.md': `\n          # Test Page\n\n          This is a custom route.\n        `,\n        'data-test-scripturls.js': `\n          document.body.setAttribute('data-test-scripturls', 'pass');\n        `,\n      },\n      script: `\n        document.body.setAttribute('data-test-script', 'pass');\n      `,\n      scriptURLs: [\n        // docsifyInit() route\n        'data-test-scripturls.js',\n      ],\n      style: `\n        body {\n          background: red !important;\n        }\n      `,\n      styleURLs: ['/dist/themes/core.css'],\n    };\n\n    await docsifyInit({\n      ...docsifyInitConfig,\n      // _logHTML: true,\n    });\n\n    // Verify config options\n    expect(typeof window.$docsify).toBe('object');\n    expect(document.querySelector('.app-name').textContent).toContain(\n      'Docsify Name',\n    );\n\n    // Verify docsifyInitConfig.markdown content was rendered\n    Object.entries({\n      'section.cover': 'Docsify Test', // Coverpage\n      'nav.app-nav': 'docsify.js.org', // Navbar\n      'aside.sidebar': 'Test Page', // Sidebar\n      '#main': 'This is the homepage', // Homepage\n    }).forEach(([selector, content]) => {\n      expect(document.querySelector(selector).textContent).toContain(content);\n    });\n\n    // Verify docsifyInitConfig.scriptURLs were added to the DOM\n    for (const scriptURL of docsifyInitConfig.scriptURLs) {\n      const matchElm = document.querySelector(\n        `script[data-src$=\"${scriptURL}\"]`,\n      );\n      expect(matchElm).toBeTruthy();\n    }\n\n    // Verify docsifyInitConfig.scriptURLs were executed\n    expect(document.body.hasAttribute('data-test-scripturls')).toBe(true);\n\n    // Verify docsifyInitConfig.script was added to the DOM\n    expect(\n      [...document.querySelectorAll('script')].some(\n        elm =>\n          elm.textContent.replace(/\\s+/g, '') ===\n          docsifyInitConfig.script.replace(/\\s+/g, ''),\n      ),\n    ).toBe(true);\n\n    // Verify docsifyInitConfig.script was executed\n    expect(document.body.hasAttribute('data-test-script')).toBe(true);\n\n    // Verify docsifyInitConfig.styleURLs were added to the DOM\n    for (const styleURL of docsifyInitConfig.styleURLs) {\n      const matchElm = document.querySelector(\n        `link[rel*=\"stylesheet\"][href$=\"${styleURL}\"]`,\n      );\n      expect(matchElm).toBeTruthy();\n    }\n\n    // Verify docsifyInitConfig.style was added to the DOM\n    expect(\n      [...document.querySelectorAll('style')].some(\n        elm =>\n          elm.textContent.replace(/\\s+/g, '') ===\n          docsifyInitConfig.style.replace(/\\s+/g, ''),\n      ),\n    ).toBe(true);\n\n    // Verify docsify navigation and docsifyInitConfig.routes\n    document.querySelector('a[href=\"#/test\"]').click();\n    expect(\n      await waitForFunction(() => /#\\/test$/.test(window.location.href)),\n    ).toBeTruthy();\n    expect(await waitForText('#main', 'This is a custom route')).toBeTruthy();\n  });\n\n  test('embed file code fragment renders', async () => {\n    await docsifyInit({\n      markdown: {\n        homepage: `\n          # Embed Test\n\n          [filename](_media/example1.js ':include :type=code :fragment=demo')\n        `,\n      },\n      routes: {\n        '_media/example1.js': `\n            let myURL = 'https://api.example.com/data';\n            /// [demo]\n            const result = fetch(myURL)\n              .then(response => {\n                return response.json();\n              })\n              .then(myJson => {\n                console.log(JSON.stringify(myJson));\n              });\n            /// [demo]\n            result.then(console.log).catch(console.error);\n        `,\n      },\n    });\n\n    // Wait for the embedded fragment to be fetched and rendered into #main\n    expect(\n      await waitForText('#main', 'console.log(JSON.stringify(myJson));'),\n    ).toBeTruthy();\n\n    const mainText = document.querySelector('#main').textContent;\n    expect(mainText).not.toContain('https://api.example.com/data');\n    expect(mainText).not.toContain(\n      'result.then(console.log).catch(console.error);',\n    );\n  });\n\n  test('embed file full line fragment identifier', async () => {\n    await docsifyInit({\n      markdown: {\n        homepage: `\n          # Embed Test\n\n          [filename](_media/example1.html ':include :type=code :fragment=demo :omitFragmentLine')\n        `,\n      },\n      routes: {\n        '_media/example1.html': `\n            <script>\n            let myURL = 'https://api.example.com/data';\n            /// [demo] Full line fragment identifier (all of these words here should not be included in fragment)\n            const result = fetch(myURL)\n              .then(response => {\n                return response.json();\n              })\n              .then(myJson => {\n                console.log(JSON.stringify(myJson));\n              });\n            <!-- /// [demo] -->\n            result.then(console.log).catch(console.error);\n            </script>\n        `,\n      },\n    });\n\n    // Wait for the embedded fragment to be fetched and rendered into #main\n    expect(\n      await waitForText('#main', 'console.log(JSON.stringify(myJson));'),\n    ).toBeTruthy();\n\n    const mainText = document.querySelector('#main').textContent;\n    expect(mainText).not.toContain('https://api.example.com/data');\n    expect(mainText).not.toContain('Full line fragment identifier');\n    expect(mainText).not.toContain('-->');\n    expect(mainText).not.toContain(\n      'result.then(console.log).catch(console.error);',\n    );\n  });\n\n  test('embed multiple file code fragments', async () => {\n    await docsifyInit({\n      markdown: {\n        homepage: `\n          # Embed Test\n\n          [filename](_media/example1.js ':include :type=code :fragment=demo')\n\n          [filename](_media/example2.js \":include :type=code :fragment=something\")\n\n          # Text between\n\n          [filename](_media/example3.js ':include :fragment=something_else_not_code')\n          \n          [filename](_media/example4.js ':include :fragment=demo')\n          \n          # Text after\n        `,\n      },\n      routes: {\n        '_media/example1.js': `\n            let example1 = 1;\n            /// [demo]\n            example1 += 10;\n            /// [demo]\n            console.log(example1);`,\n        '_media/example2.js': `\n            let example1 = 1;\n            ### [something]\n            example2 += 10;\n            ### [something]\n            console.log(example2);`,\n        '_media/example3.js': `\n            let example3 = 1;\n            ### [something_else_not_code]\n            example3 += 10;\n            /// [something_else_not_code]\n            console.log(example3);`,\n        '_media/example4.js': `\n            let example4 = 1;\n            ### No fragment here\n            example4 += 10;\n            /// No fragment here\n            console.log(example4);`,\n      },\n    });\n\n    expect(await waitForText('#main', 'example1 += 10;')).toBeTruthy();\n    expect(await waitForText('#main', 'example2 += 10;')).toBeTruthy();\n    expect(await waitForText('#main', 'example3 += 10;')).toBeTruthy();\n\n    const mainText = document.querySelector('#main').textContent;\n    expect(mainText).toContain('Text between');\n    expect(mainText).toContain('Text after');\n    expect(mainText).not.toContain('let example1 = 1;');\n    expect(mainText).not.toContain('let example2 = 1;');\n    expect(mainText).not.toContain('let example3 = 1;');\n    expect(mainText).not.toContain('console.log(example1);');\n    expect(mainText).not.toContain('console.log(example2);');\n    expect(mainText).not.toContain('console.log(example3);');\n    expect(mainText).not.toContain('console.log(example4);');\n    expect(mainText).not.toContain('example4 += 10;');\n    expect(mainText).not.toContain('No fragment here');\n  });\n\n  test('embed file table cell', async () => {\n    await docsifyInit({\n      markdown: {\n        homepage: `\n          # Embed Test\n\nCommand | Description | Parameters\n---: | --- | ---\n**Something** | |\n\\`do-something\\` | Does something. | [include content](_media/content.md ':include')\n**Something else** | |\n\\`etc.\\` | Etc. | |\n        `,\n      },\n      routes: {\n        '_media/content.md': `this is include content`,\n      },\n    });\n\n    const mainText = document.querySelector('#main').textContent;\n    expect(mainText).toContain('Something');\n    expect(mainText).toContain('this is include content');\n  });\n});\n"
  },
  {
    "path": "test/integration/global-apis.test.js",
    "content": "import initGlobalAPI from '../../src/core/global-api.js';\n\n// Suite\n// -----------------------------------------------------------------------------\ndescribe('Global APIs', function () {\n  // Tests\n  // ---------------------------------------------------------------------------\n  test('APIs are available', () => {\n    initGlobalAPI();\n\n    expect(typeof window.Docsify).toBe('object');\n    expect(typeof window.Docsify.util).toBe('object');\n    expect(typeof window.Docsify.dom).toBe('object');\n    expect(typeof window.Docsify.get).toBe('function');\n    expect(typeof window.Docsify.slugify).toBe('function');\n    expect(typeof window.Docsify.version).toBe('string');\n    expect(typeof window.DocsifyCompiler).toBe('function');\n    expect(typeof window.marked).toBe('function');\n    expect(typeof window.Prism).toBe('object');\n  });\n});\n"
  },
  {
    "path": "test/integration/render.test.js",
    "content": "import { stripIndent } from 'common-tags';\nimport docsifyInit from '../helpers/docsify-init.js';\nimport { waitForText } from '../helpers/wait-for.js';\n\n// Suite\n// -----------------------------------------------------------------------------\ndescribe('render', function () {\n  // Helpers\n  // ---------------------------------------------------------------------------\n  describe('callouts', () => {\n    beforeEach(async () => {\n      await docsifyInit();\n    });\n\n    test('caution', () => {\n      const output = window.marked('> [!CAUTION]\\n> Text');\n\n      expect(output).toMatchInlineSnapshot(`\n\"<div class=\"callout caution\"><p>\nText</p></div>\"\n`);\n    });\n\n    test('important', () => {\n      const output = window.marked('> [!IMPORTANT]\\n> Text');\n\n      expect(output).toMatchInlineSnapshot(`\n\"<div class=\"callout important\"><p>\nText</p></div>\"\n`);\n    });\n\n    test('note', () => {\n      const output = window.marked('> [!NOTE]\\n> Text');\n\n      expect(output).toMatchInlineSnapshot(`\n\"<div class=\"callout note\"><p>\nText</p></div>\"\n`);\n    });\n\n    test('tip', () => {\n      const output = window.marked('> [!TIP]\\n> Text');\n\n      expect(output).toMatchInlineSnapshot(`\n\"<div class=\"callout tip\"><p>\nText</p></div>\"\n`);\n    });\n\n    test('warning', () => {\n      const output = window.marked('> [!WARNING]\\n> Text');\n\n      expect(output).toMatchInlineSnapshot(`\n\"<div class=\"callout warning\"><p>\nText</p></div>\"\n`);\n    });\n\n    test('important (legacy)', () => {\n      const output = window.marked('!> Important content');\n\n      expect(output).toMatchInlineSnapshot(\n        `\"<p class=\"callout important\">Important content</p>\"`,\n      );\n    });\n\n    test('tip (legacy)', () => {\n      const output = window.marked('?> General tip');\n\n      expect(output).toMatchInlineSnapshot(\n        `\"<p class=\"callout tip\">General tip</p>\"`,\n      );\n    });\n  });\n\n  // Lists\n  // ---------------------------------------------------------------------------\n  describe('lists', function () {\n    beforeEach(async () => {\n      await docsifyInit();\n    });\n\n    test('as unordered task list', async function () {\n      const output = window.marked(stripIndent`\n        - [x] Task 1\n        - [ ] Task 2\n        - [ ] Task 3\n      `);\n\n      expect(output).toMatchInlineSnapshot(\n        '\"<ul class=\"task-list\"><li class=\"task-list-item\"><label><input checked=\"\" disabled=\"\" type=\"checkbox\"> Task 1</label></li><li class=\"task-list-item\"><label><input disabled=\"\" type=\"checkbox\"> Task 2</label></li><li class=\"task-list-item\"><label><input disabled=\"\" type=\"checkbox\"> Task 3</label></li></ul>\"',\n      );\n    });\n\n    test('as ordered task list', async function () {\n      const output = window.marked(stripIndent`\n        1. [ ] Task 1\n        2. [x] Task 2\n      `);\n\n      expect(output).toMatchInlineSnapshot(\n        '\"<ol class=\"task-list\"><li class=\"task-list-item\"><label><input disabled=\"\" type=\"checkbox\"> Task 1</label></li><li class=\"task-list-item\"><label><input checked=\"\" disabled=\"\" type=\"checkbox\"> Task 2</label></li></ol>\"',\n      );\n    });\n\n    test('normal unordered', async function () {\n      const output = window.marked(stripIndent`\n        - [linktext](link)\n        - just text\n      `);\n\n      expect(output).toMatchInlineSnapshot(\n        '\"<ul ><li><a href=\"#/link\" >linktext</a></li><li>just text</li></ul>\"',\n      );\n    });\n\n    test('unordered with custom start', async function () {\n      const output = window.marked(stripIndent`\n        1. first\n        2. second\n\n        text\n\n        3. third\n      `);\n\n      expect(output).toMatchInlineSnapshot(\n        '\"<ol ><li>first</li><li>second</li></ol><p>text</p><ol start=\"3\"><li>third</li></ol>\"',\n      );\n    });\n\n    test('nested', async function () {\n      const output = window.marked(stripIndent`\n        - 1\n        - 2\n          - 2 a\n          - 2 b\n        - 3\n      `);\n\n      expect(output).toMatchInlineSnapshot(\n        '\"<ul ><li>1</li><li>2<ul ><li>2 a</li><li>2 b</li></ul></li><li>3</li></ul>\"',\n      );\n    });\n  });\n\n  // Images\n  // ---------------------------------------------------------------------------\n  describe('images', function () {\n    beforeEach(async () => {\n      await docsifyInit();\n    });\n\n    test('regular', async function () {\n      const output = window.marked('![alt text](http://imageUrl)');\n\n      expect(output).toMatchInlineSnapshot(\n        '\"<p><img src=\"http://imageUrl\" data-origin=\"http://imageUrl\" alt=\"alt text\"  /></p>\"',\n      );\n    });\n\n    test('class', async function () {\n      const output = window.marked(\n        \"![alt text](http://imageUrl ':class=someCssClass')\",\n      );\n\n      expect(output).toMatchInlineSnapshot(\n        '\"<p><img src=\"http://imageUrl\" data-origin=\"http://imageUrl\" alt=\"alt text\" class=\"someCssClass\" /></p>\"',\n      );\n    });\n\n    test('id', async function () {\n      const output = window.marked(\n        \"![alt text](http://imageUrl ':id=someCssID')\",\n      );\n\n      expect(output).toMatchInlineSnapshot(\n        '\"<p><img src=\"http://imageUrl\" data-origin=\"http://imageUrl\" alt=\"alt text\" id=\"someCssID\" /></p>\"',\n      );\n    });\n\n    test('no-zoom', async function () {\n      const output = window.marked(\"![alt text](http://imageUrl ':no-zoom')\");\n\n      expect(output).toMatchInlineSnapshot(\n        '\"<p><img src=\"http://imageUrl\" data-origin=\"http://imageUrl\" alt=\"alt text\" data-no-zoom /></p>\"',\n      );\n    });\n\n    test('width and height', async function () {\n      const output = window.marked(\n        \"![alt text](http://imageUrl ':size=WIDTHxHEIGHT')\",\n      );\n\n      expect(output).toMatchInlineSnapshot(\n        '\"<p><img src=\"http://imageUrl\" data-origin=\"http://imageUrl\" alt=\"alt text\" width=\"WIDTH\" height=\"HEIGHT\" /></p>\"',\n      );\n    });\n\n    test('width', async function () {\n      const output = window.marked(\"![alt text](http://imageUrl ':size=50')\");\n\n      expect(output).toMatchInlineSnapshot(\n        '\"<p><img src=\"http://imageUrl\" data-origin=\"http://imageUrl\" alt=\"alt text\" width=\"50\" /></p>\"',\n      );\n    });\n  });\n\n  // Headings\n  // ---------------------------------------------------------------------------\n  describe('headings', function () {\n    beforeEach(async () => {\n      await docsifyInit();\n    });\n\n    test('h1', async function () {\n      const output = window.marked('# h1 tag');\n\n      expect(output).toMatchInlineSnapshot(\n        '\"<h1 id=\"h1-tag\" tabindex=\"-1\"><a href=\"#/?id=h1-tag\" data-id=\"h1-tag\" class=\"anchor\"><span>h1 tag</span></a></h1>\"',\n      );\n    });\n\n    test('h2', async function () {\n      const output = window.marked('## h2 tag');\n\n      expect(output).toMatchInlineSnapshot(\n        '\"<h2 id=\"h2-tag\" tabindex=\"-1\"><a href=\"#/?id=h2-tag\" data-id=\"h2-tag\" class=\"anchor\"><span>h2 tag</span></a></h2>\"',\n      );\n    });\n\n    test('h3', async function () {\n      const output = window.marked('### h3 tag');\n\n      expect(output).toMatchInlineSnapshot(\n        '\"<h3 id=\"h3-tag\" tabindex=\"-1\"><a href=\"#/?id=h3-tag\" data-id=\"h3-tag\" class=\"anchor\"><span>h3 tag</span></a></h3>\"',\n      );\n    });\n\n    test('h4', async function () {\n      const output = window.marked('#### h4 tag');\n\n      expect(output).toMatchInlineSnapshot(\n        '\"<h4 id=\"h4-tag\" tabindex=\"-1\"><a href=\"#/?id=h4-tag\" data-id=\"h4-tag\" class=\"anchor\"><span>h4 tag</span></a></h4>\"',\n      );\n    });\n\n    test('h5', async function () {\n      const output = window.marked('##### h5 tag');\n\n      expect(output).toMatchInlineSnapshot(\n        '\"<h5 id=\"h5-tag\" tabindex=\"-1\"><a href=\"#/?id=h5-tag\" data-id=\"h5-tag\" class=\"anchor\"><span>h5 tag</span></a></h5>\"',\n      );\n    });\n\n    test('h6', async function () {\n      const output = window.marked('###### h6 tag');\n\n      expect(output).toMatchInlineSnapshot(\n        '\"<h6 id=\"h6-tag\" tabindex=\"-1\"><a href=\"#/?id=h6-tag\" data-id=\"h6-tag\" class=\"anchor\"><span>h6 tag</span></a></h6>\"',\n      );\n    });\n  });\n\n  // Links\n  // ---------------------------------------------------------------------------\n  describe('link', function () {\n    beforeEach(async () => {\n      await docsifyInit();\n    });\n\n    test('regular', async function () {\n      const output = window.marked('[alt text](http://url)');\n\n      expect(output).toMatchInlineSnapshot(\n        `\"<p><a href=\"http://url\" target=\"_blank\" rel=\"noopener\">alt text</a></p>\"`,\n      );\n    });\n\n    test('linkrel', async function () {\n      // const { docsify } = await init('default', {\n      //   externalLinkTarget: '_blank',\n      //   externalLinkRel: 'noopener',\n      // });\n      const output = window.marked('[alt text](http://www.example.com)');\n\n      expect(output).toMatchInlineSnapshot(\n        `\"<p><a href=\"http://www.example.com\" target=\"_blank\" rel=\"noopener\">alt text</a></p>\"`,\n      );\n    });\n\n    test('disabled', async function () {\n      const output = window.marked(\"[alt text](http://url ':disabled')\");\n\n      expect(output).toMatchInlineSnapshot(\n        `\"<p><a href=\"javascript:void(0)\" target=\"_blank\" rel=\"noopener\" disabled>alt text</a></p>\"`,\n      );\n    });\n\n    test('target for absolute path', async function () {\n      const output = window.marked(\"[alt text](http://url ':target=_self')\");\n\n      expect(output).toMatchInlineSnapshot(\n        `\"<p><a href=\"http://url\" target=\"_self\">alt text</a></p>\"`,\n      );\n    });\n\n    test('target for relative path', async function () {\n      const output = window.marked(\"[alt text](/url ':target=_blank')\");\n\n      expect(output).toMatchInlineSnapshot(\n        '\"<p><a href=\"#/url\" target=\"_blank\">alt text</a></p>\"',\n      );\n    });\n\n    test('class', async function () {\n      const output = window.marked(\n        \"[alt text](http://url ':class=someCssClass')\",\n      );\n\n      expect(output).toMatchInlineSnapshot(\n        `\"<p><a href=\"http://url\" target=\"_blank\" rel=\"noopener\" class=\"someCssClass\">alt text</a></p>\"`,\n      );\n    });\n\n    test('multi class config', async function () {\n      const output = window.marked(\n        \"[alt text](http://url ':class=someCssClass :class=anotherCssClass')\",\n      );\n\n      expect(output).toMatchInlineSnapshot(\n        `\"<p><a href=\"http://url\" target=\"_blank\" rel=\"noopener\" class=\"someCssClass anotherCssClass\">alt text</a></p>\"`,\n      );\n    });\n\n    test('id', async function () {\n      const output = window.marked(\"[alt text](http://url ':id=someCssID')\");\n\n      expect(output).toMatchInlineSnapshot(\n        `\"<p><a href=\"http://url\" target=\"_blank\" rel=\"noopener\" id=\"someCssID\">alt text</a></p>\"`,\n      );\n    });\n  });\n\n  // Skip Link\n  // ---------------------------------------------------------------------------\n  describe('skip link', () => {\n    test('renders default skip link and label', async () => {\n      await docsifyInit();\n\n      const elm = document.getElementById('skip-to-content');\n      const expectText = 'Skip to main content';\n\n      expect(elm.textContent).toBe(expectText);\n      expect(elm.outerHTML).toMatchInlineSnapshot(\n        `\"<button type=\"button\" id=\"skip-to-content\" class=\"primary\">Skip to main content</button>\"`,\n      );\n    });\n\n    test('renders custom label from config string', async () => {\n      const expectText = 'test';\n\n      await docsifyInit({\n        config: {\n          skipLink: expectText,\n        },\n      });\n\n      const elm = document.getElementById('skip-to-content');\n\n      expect(elm.textContent).toBe(expectText);\n    });\n\n    test('renders custom label from config object', async () => {\n      const getSkipLinkText = () =>\n        document.getElementById('skip-to-content').textContent;\n\n      await docsifyInit({\n        config: {\n          skipLink: {\n            '/dir1/dir2/': 'baz',\n            '/dir1/': 'bar',\n          },\n        },\n      });\n\n      window.location.hash = '/dir1/dir2/';\n      await waitForText('#skip-to-content', 'baz');\n      expect(getSkipLinkText()).toBe('baz');\n\n      window.location.hash = '/dir1/';\n      await waitForText('#skip-to-content', 'bar');\n      expect(getSkipLinkText()).toBe('bar');\n\n      // Fallback to default\n      window.location.hash = '';\n      await waitForText('#skip-to-content', 'Skip to main content');\n      expect(getSkipLinkText()).toBe('Skip to main content');\n    });\n\n    test('does not render skip link when false', async () => {\n      await docsifyInit({\n        config: {\n          skipLink: false,\n        },\n      });\n      const elm = document.getElementById('skip-to-content') || false;\n\n      expect(elm).toBe(false);\n    });\n  });\n});\n"
  },
  {
    "path": "test/integration/sidebar.test.js",
    "content": "import docsifyInit from '../helpers/docsify-init.js';\n\ndescribe('Test sidebar render toc structure', function () {\n  test('Render sidebar with loadSidebar=true and the _sidebar.md file', async () => {\n    await docsifyInit({\n      config: {\n        loadSidebar: '_sidebar.md',\n      },\n      markdown: {\n        homepage: '# Hello World',\n        sidebar: `\n        - Getting started\n            - [Level1](QuickStart.md)\n                - [Level2](QuickStart2.md)\n        `,\n      },\n      waitForSelector: '.sidebar-nav > ul',\n    });\n\n    const sidebarElm = document.querySelector('.sidebar');\n    /**\n     * Expected render result\n     * ==========================\n     *<ul>\n     *   <li>\n     *       Getting started\n     *       <ul>\n     *           <li>\n     *               <a href=\"#/QuickStart\" title=\"Level1\">Level1</a>\n     *               <ul>\n     *               <li><a href=\"#/QuickStart2\" title=\"Level2\">Level2</a></li>\n     *               </ul>\n     *           </li>\n     *       </ul>\n     *   </li>\n     * </ul>\n     */\n\n    const GettingStarted = document.querySelector('.sidebar-nav > ul > li');\n    const level1_Elm = document.querySelector(\n      '.sidebar-nav > ul > li > ul> li',\n    );\n    const level1_A_tag = level1_Elm.querySelector('a');\n\n    const level2_Elm = level1_Elm.querySelector(' ul > li ');\n\n    const level2_A_tag = level2_Elm.querySelector('a');\n\n    expect(sidebarElm).not.toBeNull();\n    expect(GettingStarted).not.toBeNull();\n    expect(level1_Elm).not.toBeNull();\n    expect(level1_A_tag).not.toBeNull();\n    expect(level2_Elm).not.toBeNull();\n    expect(level2_A_tag).not.toBeNull();\n    expect(level1_A_tag.textContent).toContain('Level1');\n    expect(level2_A_tag.textContent).toContain('Level2');\n  });\n\n  test('Render sidebar with loadSidebar=false should be same to loadSidebar=true sidebar structure', async () => {\n    await docsifyInit({\n      config: {\n        loadSidebar: false,\n      },\n      markdown: {\n        homepage: `\n        # Getting started \n           some thing\n        ## Level1 \n            foo\n        ### Level2 \n            bar\n        `,\n      },\n      waitForSelector: '.sidebar-nav > ul',\n    });\n\n    const sidebarElm = document.querySelector('.sidebar');\n    /**\n     * Expected render result\n     * ==========================\n     *<ul>\n     *   <li>\n     *       Getting started\n     *       <ul>\n     *           <li>\n     *               <a href=\"#/QuickStart\" title=\"Level1\">Level1</a>\n     *               <ul>\n     *               <li><a href=\"#/QuickStart2\" title=\"Level2\">Level2</a></li>\n     *               </ul>\n     *           </li>\n     *       </ul>\n     *   </li>\n     * </ul>\n     */\n\n    const appSubSidebarTargetElm = document.querySelector('.sidebar-nav > ul');\n    expect(appSubSidebarTargetElm).not.toBeNull();\n    const ulClass = appSubSidebarTargetElm.className;\n    // the sidebar-nav > ul should have the class app-sub-sidebar\n    expect(ulClass).toContain('app-sub-sidebar');\n\n    const GettingStarted = document.querySelector('.sidebar-nav > ul > li');\n    const level1_Elm = document.querySelector(\n      '.sidebar-nav > ul > li > ul> li',\n    );\n    const level1_A_tag = level1_Elm.querySelector('a');\n\n    const level2_Elm = level1_Elm.querySelector(' ul > li ');\n\n    const level2_A_tag = level2_Elm.querySelector('a');\n\n    expect(sidebarElm).not.toBeNull();\n    expect(GettingStarted).not.toBeNull();\n    expect(level1_Elm).not.toBeNull();\n    expect(level1_A_tag).not.toBeNull();\n    expect(level2_Elm).not.toBeNull();\n    expect(level2_A_tag).not.toBeNull();\n    expect(level1_A_tag.textContent).toContain('Level1');\n    expect(level2_A_tag.textContent).toContain('Level2');\n  });\n});\n"
  },
  {
    "path": "test/unit/core-util.test.js",
    "content": "import { isExternal } from '../../src/core/util/index.js';\n\n// Core util\n// -----------------------------------------------------------------------------\ndescribe('core/util', () => {\n  // isExternal()\n  // ---------------------------------------------------------------------------\n  describe('isExternal()', () => {\n    // cases non external\n    test('non external local url with one /', () => {\n      const result = isExternal(`/${location.host}/docsify/demo.md`);\n\n      expect(result).toBeFalsy();\n    });\n\n    test('non external local url with two //', () => {\n      const result = isExternal(`//${location.host}/docsify/demo.md`);\n\n      expect(result).toBeFalsy();\n    });\n\n    test('non external local url with three ///', () => {\n      const result = isExternal(`///${location.host}/docsify/demo.md`);\n\n      expect(result).toBeFalsy();\n    });\n\n    test('non external local url with more /', () => {\n      const result = isExternal(\n        `//////////////////${location.host}/docsify/demo.md`,\n      );\n\n      expect(result).toBeFalsy();\n    });\n\n    test('non external url with one /', () => {\n      const result = isExternal('/example.github.io/docsify/demo.md');\n\n      expect(result).toBeFalsy();\n    });\n\n    // cases is external\n    test('external url with two //', () => {\n      const result = isExternal('/docsify/demo.md');\n\n      expect(result).toBeFalsy();\n    });\n\n    test('external url with three ///', () => {\n      const result = isExternal('///example.github.io/docsify/demo.md');\n\n      expect(result).toBeTruthy();\n    });\n\n    test('external url with more /', () => {\n      const result = isExternal(\n        '//////////////////example.github.io/docsify/demo.md',\n      );\n\n      expect(result).toBeTruthy();\n    });\n\n    test('external url with one \\\\', () => {\n      const result = isExternal('/\\\\example.github.io/docsify/demo.md');\n\n      expect(result).toBeTruthy();\n    });\n\n    test('external url with two \\\\', () => {\n      const result = isExternal('/\\\\\\\\example.github.io/docsify/demo.md');\n\n      expect(result).toBeTruthy();\n    });\n  });\n});\n"
  },
  {
    "path": "test/unit/render-util.test.js",
    "content": "import {\n  removeAtag,\n  getAndRemoveConfig,\n  getAndRemoveDocsifyIgnoreConfig,\n} from '../../src/core/render/utils.js';\nimport { tree } from '../../src/core/render/tpl.js';\nimport { slugify } from '../../src/core/render/slugify.js';\n\n// Suite\n// -----------------------------------------------------------------------------\ndescribe('core/render/utils', () => {\n  // removeAtag()\n  // ---------------------------------------------------------------------------\n  describe('removeAtag()', () => {\n    test('removeAtag from a link', () => {\n      const result = removeAtag('<a href=\"www.example.com\">content</a>');\n\n      expect(result).toBe('content');\n    });\n  });\n\n  // getAndRemoveDocsifyIgnoreConfig()\n  // ---------------------------------------------------------------------------\n  describe('getAndRemoveDocsifyIgnoreConfig()', () => {\n    test('getAndRemoveDocsifyIgnoreConfig from <!-- {docsify-ignore} -->', () => {\n      const { content, ignoreAllSubs, ignoreSubHeading } =\n        getAndRemoveDocsifyIgnoreConfig(\n          'My Ignore Title<!-- {docsify-ignore} -->',\n        );\n      expect(content).toBe('My Ignore Title');\n      expect(ignoreSubHeading).toBeTruthy();\n      expect(ignoreAllSubs === undefined).toBeTruthy();\n    });\n\n    test('getAndRemoveDocsifyIgnoreConfig from <!-- {docsify-ignore-all} -->', () => {\n      const { content, ignoreAllSubs, ignoreSubHeading } =\n        getAndRemoveDocsifyIgnoreConfig(\n          'My Ignore Title<!-- {docsify-ignore-all} -->',\n        );\n      expect(content).toBe('My Ignore Title');\n      expect(ignoreAllSubs).toBeTruthy();\n      expect(ignoreSubHeading === undefined).toBeTruthy();\n    });\n\n    test('getAndRemoveDocsifyIgnoreConfig from {docsify-ignore}', () => {\n      const { content, ignoreAllSubs, ignoreSubHeading } =\n        getAndRemoveDocsifyIgnoreConfig('My Ignore Title{docsify-ignore}');\n      expect(content).toBe('My Ignore Title');\n      expect(ignoreSubHeading).toBeTruthy();\n      expect(ignoreAllSubs === undefined).toBeTruthy();\n    });\n\n    test('getAndRemoveDocsifyIgnoreConfig from {docsify-ignore-all}', () => {\n      const { content, ignoreAllSubs, ignoreSubHeading } =\n        getAndRemoveDocsifyIgnoreConfig('My Ignore Title{docsify-ignore-all}');\n      expect(content).toBe('My Ignore Title');\n      expect(ignoreAllSubs).toBeTruthy();\n      expect(ignoreSubHeading === undefined).toBeTruthy();\n    });\n  });\n\n  // getAndRemoveConfig()\n  // ---------------------------------------------------------------------------\n  describe('getAndRemoveConfig()', () => {\n    test('parse simple config', () => {\n      const result = getAndRemoveConfig(\n        \"[filename](_media/example.md ':include')\",\n      );\n\n      expect(result).toMatchObject({\n        config: {},\n        str: \"[filename](_media/example.md ':include')\",\n      });\n    });\n\n    test('parse config with arguments', () => {\n      const result = getAndRemoveConfig(\n        \"[filename](_media/example.md ':include :foo=bar :baz test')\",\n      );\n\n      expect(result).toMatchObject({\n        config: {\n          foo: 'bar',\n          baz: true,\n        },\n        str: \"[filename](_media/example.md ':include test')\",\n      });\n    });\n\n    test('parse config with key arguments img', () => {\n      const result = getAndRemoveConfig(\n        \"![logo](https://docsify.js.org/_media/icon.svg ' :size=50x100 ')\",\n      );\n\n      expect(result).toMatchObject({\n        config: {\n          size: '50x100',\n        },\n        str: \"![logo](https://docsify.js.org/_media/icon.svg ' ')\",\n      });\n    });\n\n    test('parse config with key arguments', () => {\n      const result = getAndRemoveConfig(\n        \"[filename](_media/example.md ' :class=foo ')\",\n      );\n\n      expect(result).toMatchObject({\n        config: {\n          class: 'foo',\n        },\n        str: \"[filename](_media/example.md ' ')\",\n      });\n    });\n\n    test('parse config with same key arguments', () => {\n      const result = getAndRemoveConfig(\n        \"[filename](_media/example.md ' :class=foo :class=bar :bb=aa ')\",\n      );\n\n      expect(result).toMatchObject({\n        config: {\n          class: ['foo', 'bar'],\n        },\n        str: \"[filename](_media/example.md ' ')\",\n      });\n    });\n\n    test('parse config with double quotes', () => {\n      const result = getAndRemoveConfig(\n        '[filename](_media/example.md \":include\")',\n      );\n\n      expect(result).toMatchObject({\n        config: {},\n        str: '[filename](_media/example.md \":include\")',\n      });\n    });\n  });\n});\n\ndescribe('core/render/tpl', () => {\n  test('remove html tag in tree', () => {\n    const result = tree([\n      {\n        level: 2,\n        slug: '#/cover?id=basic-usage',\n        title: '<span style=\"color:red\">Basic usage</span>',\n      },\n      {\n        level: 2,\n        slug: '#/cover?id=custom-background',\n        title: 'Custom background',\n      },\n      {\n        level: 2,\n        slug: '#/cover?id=test',\n        title:\n          '<img src=\"/docs/_media/favicon.ico\" data-origin=\"/_media/favicon.ico\" alt=\"ico\">Test',\n      },\n    ]);\n\n    expect(result).toBe(\n      /* html */ '<ul class=\"app-sub-sidebar\"><li><a class=\"section-link\" href=\"#/cover?id=basic-usage\" title=\"Basic usage\"><span style=\"color:red\">Basic usage</span></a></li><li><a class=\"section-link\" href=\"#/cover?id=custom-background\" title=\"Custom background\">Custom background</a></li><li><a class=\"section-link\" href=\"#/cover?id=test\" title=\"Test\"><img src=\"/docs/_media/favicon.ico\" data-origin=\"/_media/favicon.ico\" alt=\"ico\">Test</a></li></ul>',\n    );\n  });\n});\n\ndescribe('core/render/slugify', () => {\n  test('slugify()', () => {\n    const htmlStrippedSlug = slugify(\n      'Bla bla bla <svg aria-label=\"broken\" class=\"broken\" viewPort=\"0 0 1 1\"><circle cx=\"0.5\" cy=\"0.5\"/></svg>',\n    );\n    expect(htmlStrippedSlug).toBe('bla-bla-bla-');\n\n    const nestedHtmlStrippedSlug = slugify(\n      'Another <span style=\"font-size: 1.2em\" class=\"foo bar baz\">broken <span class=\"aaa\">example</span></span>',\n    );\n    expect(nestedHtmlStrippedSlug).toBe('another-broken-example');\n\n    const emojiRemovedSlug = slugify('emoji test ⚠️🔥✅');\n    expect(emojiRemovedSlug).toBe('emoji-test-');\n\n    const multiSpaceSlug = slugify('Title    with   multiple spaces');\n    expect(multiSpaceSlug).toBe('title----with---multiple-spaces');\n\n    const numberLeadingSlug = slugify('123abc');\n    expect(numberLeadingSlug).toBe('_123abc');\n\n    const firstDuplicate = slugify('duplicate');\n    expect(firstDuplicate).toBe('duplicate');\n\n    const secondDuplicate = slugify('duplicate');\n    expect(secondDuplicate).toBe('duplicate-1');\n\n    const thirdDuplicate = slugify('duplicate');\n    expect(thirdDuplicate).toBe('duplicate-2');\n\n    const mixedCaseSlug = slugify('This Is Mixed CASE');\n    expect(mixedCaseSlug).toBe('this-is-mixed-case');\n\n    const chinesePreservedSlug = slugify('你好 world');\n    expect(chinesePreservedSlug).toBe('你好-world');\n\n    const specialCharSlug = slugify('C++ vs. Java & Python!');\n    expect(specialCharSlug).toBe('c-vs-java--python');\n\n    const docsifyIgnoreSlug = slugify(\n      'Ignore Heading <!-- {docsify-ignore} -->',\n    );\n    expect(docsifyIgnoreSlug).toBe('ignore-heading-');\n\n    const quoteCleanedSlug = slugify('\"The content\"');\n    expect(quoteCleanedSlug).toBe('the-content');\n  });\n});\n"
  },
  {
    "path": "test/unit/router-history-base.test.js",
    "content": "import { History } from '../../src/core/router/history/base.js';\n\nclass MockHistory extends History {\n  parse(path) {\n    return { path };\n  }\n}\n\n// Suite\n// -----------------------------------------------------------------------------\ndescribe('router/history/base', () => {\n  // Setup & Teardown\n  // ---------------------------------------------------------------------------\n  let history;\n\n  // resolvePath: true\n  // ---------------------------------------------------------------------------\n  describe('relativePath: true', () => {\n    // Setup & Teardown\n    // -------------------------------------------------------------------------\n    beforeEach(() => {\n      history = new MockHistory({ relativePath: true });\n    });\n\n    // Tests\n    // -------------------------------------------------------------------------\n    test('toURL', () => {\n      const url = history.toURL('guide.md', {}, '/zh-ch/');\n\n      expect(url).toBe('/zh-ch/guide');\n    });\n\n    test('toURL with double dot', () => {\n      const url = history.toURL('../README.md', {}, '/zh-ch/');\n\n      expect(url).toBe('/README');\n    });\n\n    test('toURL child path', () => {\n      const url = history.toURL('config/example.md', {}, '/zh-ch/');\n\n      expect(url).toBe('/zh-ch/config/example');\n    });\n\n    test('toURL absolute path', () => {\n      const url = history.toURL('/README', {}, '/zh-ch/');\n\n      expect(url).toBe('/README');\n    });\n  });\n\n  // resolvePath: false\n  // ---------------------------------------------------------------------------\n  describe('relativePath: false', () => {\n    // Setup & Teardown\n    // -------------------------------------------------------------------------\n    beforeEach(() => {\n      history = new MockHistory({ relativePath: false });\n    });\n\n    // Tests\n    // -------------------------------------------------------------------------\n    test('toURL', () => {\n      const url = history.toURL('README', {}, '/zh-ch/');\n\n      expect(url).toBe('/README');\n    });\n  });\n\n  // getFile test\n  // ---------------------------------------------------------------------------\n  describe('getFile', () => {\n    // Tests\n    // -------------------------------------------------------------------------\n    test('path is url', () => {\n      const file = history.getFile('https://some/raw/url/README.md');\n\n      expect(file).toBe('https://some/raw/url/README.md');\n    });\n    test('path is url, but ext is .html', () => {\n      const file = history.getFile('https://foo.com/index.html');\n\n      expect(file).toBe('https://foo.com/index.html');\n    });\n    test('path is url, but with parameters', () => {\n      const file = history.getFile(\n        'https://some/raw/url/README.md?token=Mytoken',\n      );\n\n      expect(file).toBe('https://some/raw/url/README.md?token=Mytoken');\n    });\n    test('path is url, but ext is different', () => {\n      history = new MockHistory({ ext: '.ext' });\n\n      const file = history.getFile('https://some/raw/url/README.md');\n\n      expect(file).toBe('https://some/raw/url/README.md.ext');\n    });\n  });\n});\n"
  },
  {
    "path": "test/unit/router-util.test.js",
    "content": "import { resolvePath } from '../../src/core/util/index.js';\n\n// Suite\n// -----------------------------------------------------------------------------\ndescribe('router/util', () => {\n  // resolvePath()\n  // ---------------------------------------------------------------------------\n  describe('resolvePath()', () => {\n    test('resolvePath with filename', () => {\n      const result = resolvePath('hello.md');\n\n      expect(result).toBe('/hello.md');\n    });\n\n    test('resolvePath with ./', () => {\n      const result = resolvePath('./hello.md');\n\n      expect(result).toBe('/hello.md');\n    });\n\n    test('resolvePath with ../', () => {\n      const result = resolvePath('test/../hello.md');\n\n      expect(result).toBe('/hello.md');\n    });\n  });\n});\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"allowJs\": true,\n    \"checkJs\": true,\n\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"target\": \"es2023\",\n    \"lib\": [\"ES2023\", \"DOM\", \"DOM.AsyncIterable\", \"DOM.Iterable\"],\n    \"declaration\": true,\n    \"declarationMap\": true, // This (along with also shipping src/, not only dist/), makes \"Go To Definition\" go to the source files in IDEs for a better DX.\n    \"emitDeclarationOnly\": true,\n    \"resolveJsonModule\": true,\n\n    // Output declarations next to source files (the default when outDir is not\n    // defined), rather than in dist/, to avoid conflicting with the global\n    // build in dist/.\n    // \"outDir\": \"dist/\",\n\n    \"strict\": true,\n    \"skipLibCheck\": true,\n    \"skipDefaultLibCheck\": true,\n\n    // TODO: Remove once tinydate import is refactored to non-default, or replace/delete non-standards-aligned libs.\n    // Enables default import interop for CJS modules like tinydate\n    \"allowSyntheticDefaultImports\": true,\n\n    // TODO: Remove this once all implicit any errors are fixed with proper JSDoc types\n    // Currently suppressing ~600 implicit any errors across the codebase\n    // See: https://github.com/docsifyjs/docsify/pull/2392\n    \"noImplicitAny\": false\n  },\n  \"include\": [\"src/**/*.js\", \"src/core/modules.ts\", \"src/core/globals.ts\"]\n}\n"
  },
  {
    "path": "vercel.json",
    "content": "{\n  \"headers\": [\n    {\n      \"source\": \"/(.*)\",\n      \"headers\": [{ \"key\": \"x-robots-tag\", \"value\": \"noindex\" }]\n    }\n  ],\n  \"redirects\": [{ \"source\": \"/\", \"destination\": \"/preview/\" }],\n  \"rewrites\": [\n    { \"source\": \"/preview/CHANGELOG.md\", \"destination\": \"/CHANGELOG.md\" },\n    { \"source\": \"/preview/:path*\", \"destination\": \"/docs/:path*\" }\n  ]\n}\n"
  }
]