diff --git a/.github/workflows/crowdin.yml b/.github/workflows/crowdin.yml
index a820881654..e51fa9c40b 100644
--- a/.github/workflows/crowdin.yml
+++ b/.github/workflows/crowdin.yml
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
- name: Crowdin push
uses: crowdin/github-action@v2
diff --git a/.github/workflows/emoji.yml b/.github/workflows/emoji.yml
index 77fb6ab1ca..6d89134da9 100644
--- a/.github/workflows/emoji.yml
+++ b/.github/workflows/emoji.yml
@@ -10,9 +10,9 @@ jobs:
if: github.repository == 'docsifyjs/docsify'
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- - uses: actions/setup-node@v4
+ - uses: actions/setup-node@v6
with:
node-version: latest
cache: 'npm'
@@ -25,7 +25,7 @@ jobs:
- name: Commit
id: auto-commit-action
- uses: stefanzweifel/git-auto-commit-action@v6
+ uses: stefanzweifel/git-auto-commit-action@v7
with:
commit_message: 'chore: Sync emoji data with GitHub emoji API'
branch: sync-emoji
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 67c6c0f00d..1a66eb1fb3 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -14,9 +14,9 @@ jobs:
matrix:
node-version: ['lts/*']
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Setup Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
@@ -35,9 +35,9 @@ jobs:
node-version: ['lts/*']
os: ['macos-latest', 'ubuntu-latest', 'windows-latest']
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Setup Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
@@ -49,6 +49,8 @@ jobs:
run: npm run test:unit -- --ci --runInBand
- name: Integration Tests
run: npm run test:integration -- --ci --runInBand
+ - name: Consumption Tests
+ run: npm run test:consume-types
test-playwright:
runs-on: ubuntu-latest
@@ -56,9 +58,9 @@ jobs:
matrix:
node-version: ['lts/*']
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Setup Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
@@ -71,7 +73,7 @@ jobs:
- name: E2E Tests (Playwright)
run: npm run test:e2e
- name: Store artifacts
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
if: failure()
with:
name: ${{ matrix.os }}-${{ matrix.node-version }}-artifacts
diff --git a/.gitignore b/.gitignore
index 2fcd3279a2..462c5abfa2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,8 +4,6 @@
.vercel
_playwright-report
_playwright-results
-dist
-lib
node_modules
# Files
@@ -14,3 +12,10 @@ node_modules
# Exceptions
!.gitkeep
+
+# Output folder for the global build only
+dist
+
+# TypeScript declaration files for standard ESM consumption
+src/**/*.d.ts
+src/**/*.d.ts.map
diff --git a/.prettierignore b/.prettierignore
index 6906e9d65e..2983d21759 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -8,4 +8,4 @@ _vars.css
_vars-advanced.css
CHANGELOG.md
emoji-data.*
-HISTORY.md
\ No newline at end of file
+HISTORY.md
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 39497bbfc1..45d2e18043 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,4 +1,5 @@
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
- "cSpell.words": ["coverpage"]
+ "cSpell.words": ["coverpage"],
+ "typescript.tsdk": "node_modules/typescript/lib"
}
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 160719a925..ea63c9c7d9 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -9,7 +9,7 @@ We welcome any type of contribution, not only code. You can help with
- **QA**: file bug reports, the more details you can give the better (e.g. screenshots with the console open)
- **Marketing**: writing blog posts, howto's, printing stickers, ...
- **Community**: presenting the project at meetups, organizing a dedicated meetup for the local community, ...
-- **Code**: take a look at the [open issues](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.
+- **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.
- **Money**: we welcome financial contributions in full transparency on our [open collective](https://opencollective.com/docsify).
## Your First Contribution
@@ -70,7 +70,7 @@ Anyone can file an expense. If the expense makes sense for the development of th
## Questions
-If you have any questions, create an [issue](issue) (protip: do a quick search first to see if someone else didn't ask the same question before!).
+If 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!).
You can also reach us at hello@docsify.opencollective.com.
## Credits
diff --git a/README.md b/README.md
index 31d759d34e..192a8c00f4 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,7 @@ Docsify turns one or more Markdown files into a Website, with no build process r
## Features
-- No statically built html files
+- No statically built HTML files
- Simple and lightweight
- Smart full-text search plugin
- Multiple themes
diff --git a/docs/README.md b/docs/README.md
index 1486fa48b0..48fb465889 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -4,13 +4,11 @@
## What it is
-Docsify generates your documentation website on the fly. Unlike GitBook, it does not generate static html files. Instead, it smartly loads and parses your Markdown files and displays them as a website. To start using it, all you need to do is create an `index.html` and [deploy it on GitHub Pages](deploy.md).
-
-See the [Quick start](quickstart.md) guide for more details.
+Docsify 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).
## Features
-- No statically built html files
+- No statically built HTML files
- Simple and lightweight
- Smart full-text search plugin
- Multiple themes
diff --git a/docs/_sidebar.md b/docs/_sidebar.md
index 86ad48f6b2..8344e6873d 100644
--- a/docs/_sidebar.md
+++ b/docs/_sidebar.md
@@ -27,5 +27,9 @@
- [Embed Files](embed-files.md)
- [UI Kit](ui-kit.md)
+- Upgrading
+
+ - [v4 to v5](v5-upgrade.md)
+
* [Awesome docsify](awesome.md)
* [Changelog](changelog.md)
diff --git a/docs/configuration.md b/docs/configuration.md
index 53810a35a8..d9cdfd05b1 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -240,6 +240,22 @@ window.$docsify = {
};
```
+## fallbackDefaultLanguage
+
+- Type: `String`
+- Default: `''`
+
+When a page is requested and it doesn't exist for the given locale, Docsify will fallback to the language specified by this option.
+
+For 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`.
+
+```js
+window.$docsify = {
+ fallbackLanguages: ['fr', 'de'],
+ fallbackDefaultLanguage: 'zh-cn', // default: ''
+};
+```
+
## formatUpdated
- Type: `String|Function`
@@ -262,7 +278,7 @@ window.$docsify = {
## hideSidebar
- Type : `Boolean`
-- Default: `true`
+- Default: `false`
This option will completely hide your sidebar and won't render any content on the side.
@@ -440,7 +456,7 @@ window.$docsify = {
## name
-- Type: `String`
+- Type: `Boolean | String`
Website name as it appears in the sidebar.
@@ -458,6 +474,22 @@ window.$docsify = {
};
```
+If `true`, the website name will be inferred from the document's `
` tag.
+
+```js
+window.$docsify = {
+ name: true,
+};
+```
+
+If `false` or empty, no name will be displayed.
+
+```js
+window.$docsify = {
+ name: false,
+};
+```
+
## nameLink
- Type: `String`
@@ -636,6 +668,10 @@ window.$docsify = {
};
```
+## plugins
+
+See [Plugins](./plugins.md).
+
## relativePath
- Type: `Boolean`
@@ -690,6 +726,8 @@ window.$docsify = {
};
```
+If undefined or empty, no GitHub corner will be displayed.
+
## requestHeaders
- Type: `Object`
@@ -871,9 +909,9 @@ window.$docsify = {
Determines if/how the site's [skip navigation link](https://webaim.org/techniques/skipnav/) will be rendered.
```js
-// Render skip link for all routes (default)
+// Render skip link for all routes
window.$docsify = {
- skipLink: 'Skip to main content',
+ skipLink: 'Skip to content',
};
```
@@ -896,6 +934,13 @@ window.$docsify = {
};
```
+```js
+// Use default
+window.$docsify = {
+ skipLink: true, // "Skip to main content"
+};
+```
+
## subMaxLevel
- Type: `Number`
diff --git a/docs/deploy.md b/docs/deploy.md
index e0cd152b9b..573d396e77 100644
--- a/docs/deploy.md
+++ b/docs/deploy.md
@@ -213,3 +213,30 @@ You can deploy **Docsify** as a Static Site on [Kinsta](https://kinsta.com/stati
- Publish directory: `docs`
6. Click the **Create site**.
+
+## DeployHQ
+
+[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).
+
+> [!IMPORTANT] DeployHQ does not host your site. It automates deploying your Docsify files to your chosen hosting provider or server.
+
+To deploy your Docsify site using DeployHQ:
+
+1. Sign up for a [DeployHQ account](https://www.deployhq.com/) and verify your email.
+
+2. 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.
+
+3. Add a server and enter your server details:
+
+ - Give your server a name
+ - Select your protocol (SSH/SFTP, FTP, or cloud platform)
+ - Enter your server hostname, username, and password/SSH key
+ - Set **Deployment Path** to your web root (e.g., `public_html/`)
+
+4. 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/`.
+
+5. Click **Deploy Project**, then select your server and click **Deploy** to start your first deployment.
+
+Your 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.
+
+For more information on advanced deployment features, see [DeployHQ's documentation](https://www.deployhq.com/support).
diff --git a/docs/embed-files.md b/docs/embed-files.md
index 175e95d2de..e9241c4789 100644
--- a/docs/embed-files.md
+++ b/docs/embed-files.md
@@ -65,9 +65,21 @@ Sometimes you don't want to embed a whole file. Maybe because you need just a fe
```
In your code file you need to surround the fragment between `/// [demo]` lines (before and after the fragment).
-Alternatively you can use `### [demo]`.
+Alternatively 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. `` in your source file and `:omitFragmentLine` will make the `-->` not show up in your Docsify code fragment section.
-Example:
+Example: In the source file \_media/example.js, `/// [demo]` identifiers have been included:
+
+```markdown
+[filename](_media/example.js ':include :type=code')
+```
+
+[filename](_media/example.js ':include :type=code')
+
+Adding the `:fragment=demo` results in the following:
+
+```markdown
+[filename](_media/example.js ':include :type=code :fragment=demo')
+```
[filename](_media/example.js ':include :type=code :fragment=demo')
diff --git a/docs/plugins.md b/docs/plugins.md
index 2cbdff0f82..a656bc1fd8 100644
--- a/docs/plugins.md
+++ b/docs/plugins.md
@@ -1,5 +1,9 @@
# List of Plugins
+These are built-in and external plugins for Docsify.
+
+See also how to [Write a Plugin](./write-a-plugin.md).
+
## Full text search
By 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.
@@ -25,7 +29,7 @@ By default, the hyperlink on the current page is recognized and the content is s
insertBefore: '.sidebar-nav', // CSS selector in .sidebar scope
maxAge: 86400000, // Expiration time, the default one day
- paths: [], // or 'auto'
+ paths: [], // string[] of files to search in, or 'auto' for discovery based on your sidebar
placeholder: 'Type to search',
// Localization
diff --git a/docs/v5-upgrade.md b/docs/v5-upgrade.md
new file mode 100644
index 0000000000..cd30ce455d
--- /dev/null
+++ b/docs/v5-upgrade.md
@@ -0,0 +1,129 @@
+# Upgrading v4 to v5
+
+The 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.
+
+## Before You Begin
+
+Some older Docsify sites may use non-version-locked URLs like:
+
+```html
+
+```
+
+If 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.
+
+## Step-by-Step Instructions
+
+### 1. Update the Theme CSS
+
+**Replace the theme (v4):**
+
+```html
+
+
+
+```
+
+**With this (v5):**
+
+```html
+
+
+
+
+```
+
+**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.
+
+View [Themes](themes.md) for more details.
+
+### 2. Add Optional Body Class (for styling)
+
+**Update your opening body tag:**
+
+```html
+
+```
+
+This adds a chevron indicator to the sidebar. You can omit this if you prefer.
+
+View [Theme Classes](themes.md?id=classes) for more details.
+
+### 3. Update the Main Docsify Script
+
+**Change:**
+
+```html
+
+
+
+```
+
+**To:**
+
+```html
+
+```
+
+### 4. Update Plugin URLs
+
+**Search Plugin:**
+
+```html
+
+
+
+
+
+
+
+```
+
+**Zoom Plugin:**
+
+```html
+
+
+
+
+
+
+
+```
+
+**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:
+
+- Change `/lib/plugins/` to `/dist/plugins/`
+- Update version from `@4` (or non-versioned) to `@5`
+- Example: `//cdn.jsdelivr.net/npm/docsify/lib/plugins/emoji.min.js` becomes `//cdn.jsdelivr.net/npm/docsify@5/dist/plugins/emoji.min.js`
+
+## Key Differences Summary
+
+- **CDN Path**: Changed from `/lib/` to `/dist/`
+- **Version**: Updated from `@4` to `@5`
+- **Themes**: v5 uses a core theme (with optional add-ons available)
+- **Plugin Names**: `zoom-image` → `zoom`
+
+## Additional Notes
+
+- Your configuration in `window.$docsify` stays the same
+- All your markdown content remains unchanged
+- The upgrade is non-breaking for most sites (however, legacy browsers like Internet Explorer 11 are no longer supported)
+- To maintain the same visual styling as Docsify v4, the [Vue theme (Add-on)](themes.md?id=vue-theme-add-on) is available
+- Custom CSS targeting v4 theme-specific classes or elements may need to be updated for v5
+- The v5 core theme can be customized using CSS variables - view [Theme Customization](themes.md?id=customization) for more details
+
+That's it! Your Docsify site should now be running on v5.
diff --git a/package-lock.json b/package-lock.json
index b66eb7e5fa..7d450a88ca 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,7 +10,9 @@
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
+ "common-tags": "^1.8.0",
"dexie": "^4.0.11",
+ "marked": "^17.0.1",
"medium-zoom": "^1.1.0",
"opencollective-postinstall": "^2.0.2",
"prismjs": "^1.29.0",
@@ -20,39 +22,40 @@
"@babel/eslint-parser": "^7.24.5",
"@babel/preset-env": "^7.11.5",
"@eslint/js": "^10.0.0",
- "@playwright/test": "^1.44.0",
+ "@playwright/test": "^1.57.0",
"@rollup/plugin-babel": "^6.0.4",
- "@rollup/plugin-commonjs": "^28.0.1",
+ "@rollup/plugin-commonjs": "^29.0.0",
"@rollup/plugin-node-resolve": "^16.0.0",
"@rollup/plugin-replace": "^6.0.1",
"@rollup/plugin-terser": "^0.4.3",
+ "@types/common-tags": "^1.8.4",
"@types/eslint": "^8.40.2",
+ "@types/prismjs": "^1.26.5",
"axios": "^1.5.0",
"browser-sync": "^3.0.2",
- "common-tags": "^1.8.0",
"conventional-changelog-cli": "^3.0.0",
"cross-env": "^10.0.0",
"cssnano": "^7.0.1",
"eslint": "^9.3.0",
"eslint-config-prettier": "^10.0.1",
- "eslint-plugin-jest": "^28.5.0",
+ "eslint-plugin-jest": "^29.2.1",
"eslint-plugin-playwright": "^2.1.0",
"eslint-plugin-prettier": "^5.1.3",
- "glob": "^11.0.1",
+ "glob": "^13.0.0",
"globals": "^16.0.0",
"husky": "^9.0.11",
"jest": "^30.0.4",
"jest-environment-jsdom": "^30.0.5",
"lint-staged": "^16.1.0",
- "marked": "^16.0.0",
"npm-run-all": "^4.1.5",
"postcss-cli": "^11.0.0",
"postcss-import": "^16.1.0",
"postcss-nesting": "^13.0.0",
"prettier": "^3.2.5",
- "rimraf": "^5.0.7",
+ "rimraf": "^6.1.0",
"rollup": "^4.17.2",
"rollup-plugin-import-css": "^4.0.1",
+ "typescript": "^5.9.3",
"vue": "^3.4.27",
"xhr-mock": "^2.5.1"
},
@@ -2039,16 +2042,20 @@
"license": "MIT"
},
"node_modules/@eslint-community/eslint-utils": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
- "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
+ "version": "4.9.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
+ "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "eslint-visitor-keys": "^3.3.0"
+ "eslint-visitor-keys": "^3.4.3"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
"peerDependencies": {
"eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
}
@@ -2176,6 +2183,29 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@isaacs/balanced-match": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
+ "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/@isaacs/brace-expansion": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
+ "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@isaacs/balanced-match": "^4.0.1"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@@ -2262,10 +2292,11 @@
}
},
"node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
- "version": "3.14.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
- "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
+ "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
@@ -3230,10 +3261,11 @@
"dev": true
},
"node_modules/@jest/reporters/node_modules/glob": {
- "version": "10.4.5",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
- "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"dev": true,
+ "license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
@@ -4044,12 +4076,13 @@
}
},
"node_modules/@playwright/test": {
- "version": "1.45.1",
- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.1.tgz",
- "integrity": "sha512-Wo1bWTzQvGA7LyKGIZc8nFSTFf2TkthGIFBR+QVNilvwouGzFd4PYukZe3rvf5PSqjHi1+1NyKSDZKcQWETzaA==",
+ "version": "1.57.0",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz",
+ "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==",
"dev": true,
+ "license": "Apache-2.0",
"dependencies": {
- "playwright": "1.45.1"
+ "playwright": "1.57.0"
},
"bin": {
"playwright": "cli.js"
@@ -4085,10 +4118,11 @@
}
},
"node_modules/@rollup/plugin-commonjs": {
- "version": "28.0.1",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.1.tgz",
- "integrity": "sha512-+tNWdlWKbpB3WgBN7ijjYkq9X5uhjmcvyjEght4NmH5fAU++zfQzAJ6wumLS+dNcvwEZhKx2Z+skY8m7v0wGSA==",
+ "version": "29.0.0",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-29.0.0.tgz",
+ "integrity": "sha512-U2YHaxR2cU/yAiwKJtJRhnyLk7cifnQw0zUpISsocBDoHDJn+HTV74ABqnwr5bEgWUwFZC9oFL6wLe21lHu5eQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@rollup/pluginutils": "^5.0.1",
"commondir": "^1.0.1",
@@ -4551,6 +4585,13 @@
"@babel/types": "^7.20.7"
}
},
+ "node_modules/@types/common-tags": {
+ "version": "1.8.4",
+ "resolved": "https://registry.npmjs.org/@types/common-tags/-/common-tags-1.8.4.tgz",
+ "integrity": "sha512-S+1hLDJPjWNDhcGxsxEbepzaxWqURP/o+3cP4aa2w7yBXgdcmKGQtZzP8JbyfOd0m+33nh+8+kvxYE2UJtBDkg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
@@ -4645,6 +4686,13 @@
"integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
"dev": true
},
+ "node_modules/@types/prismjs": {
+ "version": "1.26.5",
+ "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz",
+ "integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/resolve": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
@@ -4679,30 +4727,71 @@
"integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
"dev": true
},
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.48.0.tgz",
+ "integrity": "sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.48.0",
+ "@typescript-eslint/types": "^8.48.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
"node_modules/@typescript-eslint/scope-manager": {
- "version": "7.15.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.15.0.tgz",
- "integrity": "sha512-Q/1yrF/XbxOTvttNVPihxh1b9fxamjEoz2Os/Pe38OHwxC24CyCqXxGTOdpb4lt6HYtqw9HetA/Rf6gDGaMPlw==",
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.48.0.tgz",
+ "integrity": "sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "7.15.0",
- "@typescript-eslint/visitor-keys": "7.15.0"
+ "@typescript-eslint/types": "8.48.0",
+ "@typescript-eslint/visitor-keys": "8.48.0"
},
"engines": {
- "node": "^18.18.0 || >=20.0.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.0.tgz",
+ "integrity": "sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/types": {
- "version": "7.15.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.15.0.tgz",
- "integrity": "sha512-aV1+B1+ySXbQH0pLK0rx66I3IkiZNidYobyfn0WFsdGhSXw+P3YOqeTq5GED458SfB24tg+ux3S+9g118hjlTw==",
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.0.tgz",
+ "integrity": "sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==",
"dev": true,
+ "license": "MIT",
"engines": {
- "node": "^18.18.0 || >=20.0.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
@@ -4710,67 +4799,49 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "7.15.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.15.0.tgz",
- "integrity": "sha512-gjyB/rHAopL/XxfmYThQbXbzRMGhZzGw6KpcMbfe8Q3nNQKStpxnUKeXb0KiN/fFDR42Z43szs6rY7eHk0zdGQ==",
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.0.tgz",
+ "integrity": "sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "7.15.0",
- "@typescript-eslint/visitor-keys": "7.15.0",
+ "@typescript-eslint/project-service": "8.48.0",
+ "@typescript-eslint/tsconfig-utils": "8.48.0",
+ "@typescript-eslint/types": "8.48.0",
+ "@typescript-eslint/visitor-keys": "8.48.0",
"debug": "^4.3.4",
- "globby": "^11.1.0",
- "is-glob": "^4.0.3",
"minimatch": "^9.0.4",
"semver": "^7.6.0",
- "ts-api-utils": "^1.3.0"
+ "tinyglobby": "^0.2.15",
+ "ts-api-utils": "^2.1.0"
},
"engines": {
- "node": "^18.18.0 || >=20.0.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
- "node_modules/@typescript-eslint/typescript-estree/node_modules/globby": {
- "version": "11.1.0",
- "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
- "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
- "dev": true,
- "dependencies": {
- "array-union": "^2.1.0",
- "dir-glob": "^3.0.1",
- "fast-glob": "^3.2.9",
- "ignore": "^5.2.0",
- "merge2": "^1.4.1",
- "slash": "^3.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"dev": true,
+ "license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
@@ -4782,10 +4853,11 @@
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
- "version": "7.6.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
- "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
"dev": true,
+ "license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
@@ -4793,17 +4865,42 @@
"node": ">=10"
}
},
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.48.0.tgz",
+ "integrity": "sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.7.0",
+ "@typescript-eslint/scope-manager": "8.48.0",
+ "@typescript-eslint/types": "8.48.0",
+ "@typescript-eslint/typescript-estree": "8.48.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "7.15.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.15.0.tgz",
- "integrity": "sha512-Hqgy/ETgpt2L5xueA/zHHIl4fJI2O4XUE9l4+OIfbJIRSnTJb/QscncdqqZzofQegIJugRIF57OJea1khw2SDw==",
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.0.tgz",
+ "integrity": "sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "7.15.0",
- "eslint-visitor-keys": "^3.4.3"
+ "@typescript-eslint/types": "8.48.0",
+ "eslint-visitor-keys": "^4.2.1"
},
"engines": {
- "node": "^18.18.0 || >=20.0.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
@@ -4811,12 +4908,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
- "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
"dev": true,
+ "license": "Apache-2.0",
"engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
@@ -5321,15 +5419,6 @@
"integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==",
"dev": true
},
- "node_modules/array-union": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
- "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/arraybuffer.prototype.slice": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz",
@@ -5981,6 +6070,23 @@
"node": ">= 0.4"
}
},
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -6367,7 +6473,6 @@
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz",
"integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==",
- "dev": true,
"engines": {
"node": ">=4.0.0"
}
@@ -7200,27 +7305,6 @@
"integrity": "sha512-SOKO002EqlvBYYKQSew3iymBoN2EQ4BDw/3yprjh7kAfFzjBYkaMNa/pZvcA7HSWlcKSQb9XhPe3wKyQ0x4A8A==",
"license": "Apache-2.0"
},
- "node_modules/dir-glob": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
- "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
- "dev": true,
- "dependencies": {
- "path-type": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/dir-glob/node_modules/path-type": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
- "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
@@ -7783,19 +7867,20 @@
}
},
"node_modules/eslint-plugin-jest": {
- "version": "28.6.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.6.0.tgz",
- "integrity": "sha512-YG28E1/MIKwnz+e2H7VwYPzHUYU4aMa19w0yGcwXnnmJH6EfgHahTJ2un3IyraUxNfnz/KUhJAFXNNwWPo12tg==",
+ "version": "29.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-29.2.1.tgz",
+ "integrity": "sha512-0WLIezrIxitUGbjMIGwznVzSIp0uFJV0PZ2fiSvpyVcxe+QMXKUt7MRhUpzdbctnnLwiOTOFkACplgB0wAglFw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@typescript-eslint/utils": "^6.0.0 || ^7.0.0"
+ "@typescript-eslint/utils": "^8.0.0"
},
"engines": {
- "node": "^16.10.0 || ^18.12.0 || >=20.0.0"
+ "node": "^20.12.0 || ^22.0.0 || >=24.0.0"
},
"peerDependencies": {
- "@typescript-eslint/eslint-plugin": "^6.0.0 || ^7.0.0",
- "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0",
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
+ "eslint": "^8.57.0 || ^9.0.0",
"jest": "*"
},
"peerDependenciesMeta": {
@@ -7807,28 +7892,6 @@
}
}
},
- "node_modules/eslint-plugin-jest/node_modules/@typescript-eslint/utils": {
- "version": "7.15.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.15.0.tgz",
- "integrity": "sha512-hfDMDqaqOqsUVGiEPSMLR/AjTSCsmJwjpKkYQRo1FNbmW4tBwBspYDwO9eh7sKSTwMQgBw9/T4DHudPaqshRWA==",
- "dev": true,
- "dependencies": {
- "@eslint-community/eslint-utils": "^4.4.0",
- "@typescript-eslint/scope-manager": "7.15.0",
- "@typescript-eslint/types": "7.15.0",
- "@typescript-eslint/typescript-estree": "7.15.0"
- },
- "engines": {
- "node": "^18.18.0 || >=20.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.56.0"
- }
- },
"node_modules/eslint-plugin-playwright": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-playwright/-/eslint-plugin-playwright-2.1.0.tgz",
@@ -8614,12 +8677,13 @@
}
},
"node_modules/foreground-child": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz",
- "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==",
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"dev": true,
+ "license": "ISC",
"dependencies": {
- "cross-spawn": "^7.0.0",
+ "cross-spawn": "^7.0.6",
"signal-exit": "^4.0.1"
},
"engines": {
@@ -9036,22 +9100,16 @@
}
},
"node_modules/glob": {
- "version": "11.0.1",
- "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz",
- "integrity": "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==",
+ "version": "13.0.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz",
+ "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==",
"dev": true,
- "license": "ISC",
+ "license": "BlueOak-1.0.0",
"dependencies": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^4.0.1",
- "minimatch": "^10.0.0",
+ "minimatch": "^10.1.1",
"minipass": "^7.1.2",
- "package-json-from-dist": "^1.0.0",
"path-scurry": "^2.0.0"
},
- "bin": {
- "glob": "dist/esm/bin.mjs"
- },
"engines": {
"node": "20 || >=22"
},
@@ -9071,24 +9129,14 @@
"node": ">= 6"
}
},
- "node_modules/glob/node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
"node_modules/glob/node_modules/minimatch": {
- "version": "10.0.1",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz",
- "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==",
+ "version": "10.1.1",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz",
+ "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==",
"dev": true,
- "license": "ISC",
+ "license": "BlueOak-1.0.0",
"dependencies": {
- "brace-expansion": "^2.0.1"
+ "@isaacs/brace-expansion": "^5.0.0"
},
"engines": {
"node": "20 || >=22"
@@ -10048,9 +10096,9 @@
}
},
"node_modules/jackspeak": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz",
- "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz",
+ "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
@@ -10785,10 +10833,11 @@
"dev": true
},
"node_modules/jest-config/node_modules/glob": {
- "version": "10.4.5",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
- "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"dev": true,
+ "license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
@@ -12536,10 +12585,11 @@
"dev": true
},
"node_modules/jest-runtime/node_modules/glob": {
- "version": "10.4.5",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
- "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"dev": true,
+ "license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
@@ -13640,10 +13690,11 @@
"dev": true
},
"node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
@@ -14350,10 +14401,10 @@
}
},
"node_modules/marked": {
- "version": "16.0.0",
- "resolved": "https://registry.npmjs.org/marked/-/marked-16.0.0.tgz",
- "integrity": "sha512-MUKMXDjsD/eptB7GPzxo4xcnLS6oo7/RHimUMHEDRhUooPwmN9BEpMl7AEOJv3bmso169wHI2wUF9VQgL7zfmA==",
- "dev": true,
+ "version": "17.0.1",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.1.tgz",
+ "integrity": "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==",
+ "license": "MIT",
"bin": {
"marked": "bin/marked.js"
},
@@ -14641,10 +14692,11 @@
}
},
"node_modules/min-document": {
- "version": "2.19.0",
- "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz",
- "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==",
+ "version": "2.19.2",
+ "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.2.tgz",
+ "integrity": "sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"dom-walk": "^0.1.0"
}
@@ -14990,10 +15042,11 @@
}
},
"node_modules/object-inspect": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
- "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">= 0.4"
},
@@ -15141,10 +15194,11 @@
}
},
"node_modules/package-json-from-dist": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz",
- "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==",
- "dev": true
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0"
},
"node_modules/parent-module": {
"version": "1.0.1",
@@ -15396,12 +15450,13 @@
}
},
"node_modules/playwright": {
- "version": "1.45.1",
- "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.1.tgz",
- "integrity": "sha512-Hjrgae4kpSQBr98nhCj3IScxVeVUixqj+5oyif8TdIn2opTCPEzqAqNMeK42i3cWDCVu9MI+ZsGWw+gVR4ISBg==",
+ "version": "1.57.0",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz",
+ "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==",
"dev": true,
+ "license": "Apache-2.0",
"dependencies": {
- "playwright-core": "1.45.1"
+ "playwright-core": "1.57.0"
},
"bin": {
"playwright": "cli.js"
@@ -15414,10 +15469,11 @@
}
},
"node_modules/playwright-core": {
- "version": "1.45.1",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.1.tgz",
- "integrity": "sha512-LF4CUUtrUu2TCpDw4mcrAIuYrEjVDfT1cHbJMfwnE2+1b8PZcFzPNgvZCvq2JfQ4aTjRCCHw5EJ2tmr2NSzdPg==",
+ "version": "1.57.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz",
+ "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==",
"dev": true,
+ "license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
@@ -15431,6 +15487,7 @@
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
+ "license": "MIT",
"optional": true,
"os": [
"darwin"
@@ -16234,12 +16291,13 @@
]
},
"node_modules/qs": {
- "version": "6.12.2",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.2.tgz",
- "integrity": "sha512-x+NLUpx9SYrcwXtX7ob1gnkSems4i/mGZX5SlYxwIau6RrUSODO89TR/XDGGpn5RPWSYIB+aSfuSlV5+CmbTBg==",
+ "version": "6.14.1",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
+ "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
"dev": true,
+ "license": "BSD-3-Clause",
"dependencies": {
- "side-channel": "^1.0.6"
+ "side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
@@ -16736,105 +16794,60 @@
"license": "MIT"
},
"node_modules/rimraf": {
- "version": "5.0.7",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.7.tgz",
- "integrity": "sha512-nV6YcJo5wbLW77m+8KjH8aB/7/rxQy9SZ0HY5shnwULfS+9nmTtVXAJET5NdZmCzA4fPI/Hm1wo/Po/4mopOdg==",
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.0.tgz",
+ "integrity": "sha512-DxdlA1bdNzkZK7JiNWH+BAx1x4tEJWoTofIopFo6qWUU94jYrFZ0ubY05TqH3nWPJ1nKa1JWVFDINZ3fnrle/A==",
"dev": true,
+ "license": "BlueOak-1.0.0",
"dependencies": {
- "glob": "^10.3.7"
+ "glob": "^11.0.3",
+ "package-json-from-dist": "^1.0.1"
},
"bin": {
"rimraf": "dist/esm/bin.mjs"
},
"engines": {
- "node": ">=14.18"
+ "node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/rimraf/node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
"node_modules/rimraf/node_modules/glob": {
- "version": "10.4.5",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
- "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz",
+ "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==",
"dev": true,
- "license": "ISC",
+ "license": "BlueOak-1.0.0",
"dependencies": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^3.1.2",
- "minimatch": "^9.0.4",
+ "foreground-child": "^3.3.1",
+ "jackspeak": "^4.1.1",
+ "minimatch": "^10.1.1",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
- "path-scurry": "^1.11.1"
+ "path-scurry": "^2.0.0"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/rimraf/node_modules/jackspeak": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
- "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "@isaacs/cliui": "^8.0.2"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- },
- "optionalDependencies": {
- "@pkgjs/parseargs": "^0.11.0"
- }
- },
- "node_modules/rimraf/node_modules/lru-cache": {
- "version": "10.4.3",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
- "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/rimraf/node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
"engines": {
- "node": ">=16 || 14 >=14.17"
+ "node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/rimraf/node_modules/path-scurry": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
- "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "node_modules/rimraf/node_modules/minimatch": {
+ "version": "10.1.1",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz",
+ "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
- "lru-cache": "^10.2.0",
- "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ "@isaacs/brace-expansion": "^5.0.0"
},
"engines": {
- "node": ">=16 || 14 >=14.18"
+ "node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
@@ -17262,15 +17275,73 @@
}
},
"node_modules/side-channel": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
- "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "call-bind": "^1.0.7",
"es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.4",
- "object-inspect": "^1.13.1"
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
@@ -18067,6 +18138,54 @@
"node": ">=4"
}
},
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyglobby/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tinyglobby/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/tldts": {
"version": "6.1.86",
"resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
@@ -18150,15 +18269,16 @@
}
},
"node_modules/ts-api-utils": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
- "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
+ "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
"dev": true,
+ "license": "MIT",
"engines": {
- "node": ">=16"
+ "node": ">=18.12"
},
"peerDependencies": {
- "typescript": ">=4.2.0"
+ "typescript": ">=4.8.4"
}
},
"node_modules/tslib": {
@@ -18274,11 +18394,11 @@
}
},
"node_modules/typescript": {
- "version": "5.5.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
- "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==",
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
- "peer": true,
+ "license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
diff --git a/package.json b/package.json
index 9b9595304d..385dca7fb3 100644
--- a/package.json
+++ b/package.json
@@ -24,18 +24,22 @@
},
"type": "module",
"main": "dist/docsify.js",
+ "types": "src/core/Docsify.d.ts",
"exports": {
".": "./src/core/Docsify.js",
"./*": "./*"
},
"files": [
- "dist"
+ "dist",
+ "src"
],
"lint-staged": {
"*.js": "eslint --fix"
},
"dependencies": {
+ "common-tags": "^1.8.0",
"dexie": "^4.0.11",
+ "marked": "^17.0.1",
"medium-zoom": "^1.1.0",
"opencollective-postinstall": "^2.0.2",
"prismjs": "^1.29.0",
@@ -45,39 +49,40 @@
"@babel/eslint-parser": "^7.24.5",
"@babel/preset-env": "^7.11.5",
"@eslint/js": "^10.0.0",
- "@playwright/test": "^1.44.0",
+ "@playwright/test": "^1.57.0",
"@rollup/plugin-babel": "^6.0.4",
- "@rollup/plugin-commonjs": "^28.0.1",
+ "@rollup/plugin-commonjs": "^29.0.0",
"@rollup/plugin-node-resolve": "^16.0.0",
"@rollup/plugin-replace": "^6.0.1",
"@rollup/plugin-terser": "^0.4.3",
+ "@types/common-tags": "^1.8.4",
"@types/eslint": "^8.40.2",
+ "@types/prismjs": "^1.26.5",
"axios": "^1.5.0",
"browser-sync": "^3.0.2",
- "common-tags": "^1.8.0",
"conventional-changelog-cli": "^3.0.0",
"cross-env": "^10.0.0",
"cssnano": "^7.0.1",
"eslint": "^9.3.0",
"eslint-config-prettier": "^10.0.1",
- "eslint-plugin-jest": "^28.5.0",
+ "eslint-plugin-jest": "^29.2.1",
"eslint-plugin-playwright": "^2.1.0",
"eslint-plugin-prettier": "^5.1.3",
- "glob": "^11.0.1",
+ "glob": "^13.0.0",
"globals": "^16.0.0",
"husky": "^9.0.11",
"jest": "^30.0.4",
"jest-environment-jsdom": "^30.0.5",
"lint-staged": "^16.1.0",
- "marked": "^16.0.0",
"npm-run-all": "^4.1.5",
"postcss-cli": "^11.0.0",
"postcss-import": "^16.1.0",
"postcss-nesting": "^13.0.0",
"prettier": "^3.2.5",
- "rimraf": "^5.0.7",
+ "rimraf": "^6.1.0",
"rollup": "^4.17.2",
"rollup-plugin-import-css": "^4.0.1",
+ "typescript": "^5.9.3",
"vue": "^3.4.27",
"xhr-mock": "^2.5.1"
},
@@ -87,8 +92,9 @@
"build:css:min": "cross-env NODE_ENV='production' npm run build:css -- --ext .min.css",
"build:emoji": "node ./build/emoji.js",
"build:js": "rollup -c",
- "build": "run-s clean build:js build:css build:css:min build:cover",
- "clean": "rimraf --glob dist/** themes/** _playwright*/**",
+ "build:types": "tsc",
+ "build": "run-s clean build:types build:js build:css build:css:min build:cover",
+ "clean": "rimraf --glob \"dist/**\" \"themes/**\" \"_playwright*/**\" \"src/**/*.d.ts\" \"src/**/*.d.ts.map\"",
"dev": "run-p serve:dev watch:*",
"docker:build:test": "npm run docker:cli -- build:test",
"docker:build": "docker build -f Dockerfile -t docsify-test:local .",
@@ -111,11 +117,15 @@
"test:e2e": "playwright test",
"test:e2e:chromium": "playwright test --project='chromium'",
"test:e2e:ui": "playwright test --ui",
+ "test:e2e:consume-types": "echo TODO: test the consume-types example with ESM modules",
"test:integration": "npm run test:jest -- --selectProjects integration",
"test:jest": "cross-env NODE_OPTIONS=--experimental-vm-modules jest",
"test:unit": "npm run test:jest -- --selectProjects unit",
"test:update:snapshot": "npm run test:jest -- --updateSnapshot",
- "test": "run-s test:jest test:e2e",
+ "test:consume-types": "cd test/consume-types && npm clean-install --install-links && npm run typecheck",
+ "test": "run-s test:jest test:e2e test:consume-types",
+ "typecheck": "tsc --noEmit",
+ "typecheck:watch": "tsc --noEmit --watch",
"watch:css": "run-p 'build:css -- --watch' 'build:css:min -- --watch'",
"watch:js": "npm run build:js -- --watch"
}
diff --git a/src/core/Docsify.js b/src/core/Docsify.js
index dbf7755ce9..f00268a763 100644
--- a/src/core/Docsify.js
+++ b/src/core/Docsify.js
@@ -1,3 +1,4 @@
+import prism from 'prismjs';
import { Router } from './router/index.js';
import { Render } from './render/index.js';
import { Fetch } from './fetch/index.js';
@@ -8,16 +9,29 @@ import config from './config.js';
import { isFn } from './util/core.js';
import { Lifecycle } from './init/lifecycle.js';
+export { prism };
+export { marked } from 'marked';
+export * as util from './util/index.js';
+export * as dom from './util/dom.js';
+export { Compiler } from './render/compiler.js';
+export { slugify } from './render/slugify.js';
+export { get } from './util/ajax.js';
+
/** @typedef {new (...args: any[]) => any} Constructor */
+/** @typedef {import('./config.js').DocsifyConfig} DocsifyConfig */
export class Docsify extends Fetch(
Events(Render(VirtualRoutes(Router(Lifecycle(Object))))),
) {
- config = config(this);
+ /** @type {DocsifyConfig} */
+ config;
- constructor() {
+ /** @param {Partial} conf */
+ constructor(conf = {}) {
super();
+ this.config = config(this, conf);
+
this.initLifecycle(); // Init hooks
this.initPlugin(); // Install plugins
this.callHook('init');
@@ -45,3 +59,5 @@ export class Docsify extends Fetch(
});
}
}
+
+export const version = '__VERSION__';
diff --git a/src/core/config.js b/src/core/config.js
index bfcd0d0c27..f433b33d06 100644
--- a/src/core/config.js
+++ b/src/core/config.js
@@ -1,73 +1,135 @@
import { stripIndent } from 'common-tags';
import { hyphenate, isPrimitive } from './util/core.js';
+/** @import { Docsify } from './Docsify.js' */
+/** @import { Hooks } from './init/lifecycle.js' */
const currentScript = document.currentScript;
-/** @param {import('./Docsify.js').Docsify} vm */
-export default function (vm) {
- const config = Object.assign(
- {
- auto2top: false,
- autoHeader: false,
- basePath: '',
- catchPluginErrors: true,
- cornerExternalLinkTarget: '_blank',
- coverpage: '',
- el: '#app',
- executeScript: null,
- ext: '.md',
- externalLinkRel: 'noopener',
- externalLinkTarget: '_blank',
- formatUpdated: '',
- ga: '',
- homepage: 'README.md',
- loadNavbar: null,
- loadSidebar: null,
- maxLevel: 6,
- mergeNavbar: false,
- name: '',
- nameLink: window.location.pathname,
- nativeEmoji: false,
- noCompileLinks: [],
- noEmoji: false,
- notFoundPage: false,
- plugins: [],
- relativePath: false,
- repo: '',
- routes: {},
- routerMode: 'hash',
- subMaxLevel: 0,
- // themeColor: '',
- topMargin: 0,
-
- // Deprecations //////////////////
-
- __themeColor: '',
- get themeColor() {
- return this.__themeColor;
- },
- set themeColor(value) {
- if (value) {
- this.__themeColor = value;
-
- // eslint-disable-next-line no-console
- console.warn(
- stripIndent(`
- $docsify.themeColor is deprecated. Use the "--theme-color" theme property to set your theme color.
-
- `).trim(),
- );
- }
- },
- },
+const defaultDocsifyConfig = () => ({
+ alias: /** @type {Record} */ ({}),
+ auto2top: false,
+ autoHeader: false,
+ basePath: '',
+ catchPluginErrors: true,
+ cornerExternalLinkTarget:
+ /** @type {'_blank' | '_self' | '_parent' | '_top' | '_unfencedTop'} */ (
+ '_blank'
+ ),
+ coverpage: /** @type {boolean | string} */ (''),
+ el: '#app',
+ executeScript: /** @type {null | boolean} */ (null),
+ ext: '.md',
+ externalLinkRel: /** @type {'noopener' | string} */ ('noopener'), // TODO string union type based on spec
+ externalLinkTarget:
+ /** @type {'_blank' | '_self' | '_parent' | '_top' | '_unfencedTop'} */ (
+ '_blank'
+ ),
+ fallbackLanguages: /** @type {null | string[]} */ (null),
+ fallbackDefaultLanguage: '',
+ formatUpdated: /** @type {string | ((updatedAt: string) => string)} */ (''),
+ /** For the frontmatter plugin. */
+ frontMatter: /** @type {Record | null} */ (null),
+ hideSidebar: false,
+ homepage: 'README.md',
+ keyBindings:
+ /** @type {false | { [commandName: string]: { bindings: string[]; callback: Function } }} */ ({}),
+ loadNavbar: /** @type {null | boolean | string} */ (null),
+ loadSidebar: /** @type {null | boolean | string} */ (null),
+ logo: false,
+ markdown: null,
+ maxLevel: 6,
+ mergeNavbar: false,
+ name: /** @type {boolean | string} */ (''),
+ nameLink: window.location.pathname,
+ nativeEmoji: false,
+ noCompileLinks: /** @type {string[]} */ ([]),
+ noEmoji: false,
+ notFoundPage: /** @type {boolean | string | Record} */ (
+ false
+ ),
+ onlyCover: false,
+ plugins: /** @type {Plugin[]} */ ([]),
+ relativePath: false,
+ repo: /** @type {string} */ (''),
+ requestHeaders: /** @type {Record} */ ({}),
+ routerMode: /** @type {'hash' | 'history'} */ 'hash',
+ routes: /** @type {Record} */ ({}),
+ skipLink: /** @type {false | string | Record} */ (
+ 'Skip to main content'
+ ),
+ subMaxLevel: 0,
+ vueComponents: /** @type {Record} */ ({}),
+ vueGlobalOptions: /** @type {Record} */ ({}),
+ vueMounts: /** @type {Record} */ ({}),
+
+ // Deprecations //////////////////
+
+ /** @deprecated */
+ get themeColor() {
+ return this.__themeColor;
+ },
+ set themeColor(value) {
+ if (value) {
+ this.__themeColor = value;
+
+ // eslint-disable-next-line no-console
+ console.warn(
+ stripIndent(`
+ $docsify.themeColor is deprecated as of v5. Use the "--theme-color" CSS property to customize your theme color.
+
+ `).trim(),
+ );
+ }
+ },
+ __themeColor: '',
+
+ /** @deprecated */
+ get topMargin() {
+ return this.__topMargin;
+ },
+ set topMargin(value) {
+ if (value) {
+ this.__topMargin = value;
+
+ // eslint-disable-next-line no-console
+ console.warn(
+ stripIndent(`
+ $docsify.topMargin is deprecated as of v5. Use the "--scroll-padding-top" CSS property to specify a scroll margin when using a sticky navbar.
+
+ `).trim(),
+ );
+ }
+ },
+ __topMargin: 0,
+});
+
+/** @typedef {ReturnType} DocsifyConfig */
+
+/**
+ * @param {import('./Docsify.js').Docsify} vm
+ * @param {Partial} config
+ * @returns {DocsifyConfig}
+ */
+export default function (vm, config = {}) {
+ config = Object.assign(
+ defaultDocsifyConfig(),
+
+ // Handle non-function configs no matter what (f.e. plugins assign options onto it)
+ window.$docsify,
- typeof window.$docsify === 'function'
- ? window.$docsify(vm)
- : window.$docsify,
+ // Also handle function config (the app can specificy a function, and plugins will assign options onto it)
+ typeof window.$docsify === 'function' ? window.$docsify(vm) : undefined,
+
+ // Finally, user config passed directly to the instance has priority.
+ config,
);
// Merge default and user-specified key bindings
@@ -77,8 +139,10 @@ export default function (vm) {
{
toggleSidebar: {
bindings: ['\\'],
- callback(e) {
- const toggleElm = document.querySelector('.sidebar-toggle-button');
+ callback(/** @type {KeyboardEvent} */ e) {
+ const toggleElm = /** @type {HTMLElement} */ (
+ document.querySelector('.sidebar-toggle-button')
+ );
if (toggleElm) {
toggleElm.click();
@@ -98,10 +162,20 @@ export default function (vm) {
)[0];
if (script) {
- for (const prop of Object.keys(config)) {
+ for (const prop of /** @type {(keyof DocsifyConfig)[]} */ (
+ Object.keys(config)
+ )) {
const val = script.getAttribute('data-' + hyphenate(prop));
if (isPrimitive(val)) {
+ // eslint-disable-next-line no-console
+ console.warn(
+ `DEPRECATION: data-* attributes on the docsify global script (f.e. ${
+ 'data-' + hyphenate(prop)
+ }) are deprecated.`,
+ );
+
+ // @ts-expect-error too dynamic for TS
config[prop] = val === '' ? true : val;
}
}
@@ -119,15 +193,30 @@ export default function (vm) {
config.coverpage = '_coverpage' + config.ext;
}
- if (config.repo === true) {
- config.repo = '';
- }
-
if (config.name === true) {
config.name = '';
}
- window.$docsify = config;
-
- return config;
+ return /** @type {DocsifyConfig} */ (config);
}
+
+/** @typedef {any} TODO */
+
+/** @typedef {(hooks: Hooks, vm: Docsify) => void} Plugin */
+
+/**
+ @typedef {(
+ ((route: string, matched: RegExpMatchArray) => string) |
+ ((route: string, matched: RegExpMatchArray, next: (markdown?: string) => void) => void)
+ )} RouteHandler - Given a route, provides the markdown to render for that route.
+ */
+
+/**
+@typedef {
+ {
+ subMaxLevel: number,
+ themeColor: string,
+ topMargin: number,
+ }
+} DocsifyConfigOld
+*/
diff --git a/src/core/event/index.js b/src/core/event/index.js
index e14ecf365d..f92c38e5b1 100644
--- a/src/core/event/index.js
+++ b/src/core/event/index.js
@@ -10,8 +10,8 @@ import { stripUrlExceptId } from '../router/util.js';
*/
export function Events(Base) {
return class Events extends Base {
- #intersectionObserver;
- #isScrolling;
+ #intersectionObserver = new IntersectionObserver(() => {});
+ #isScrolling = false;
#title = dom.$.title;
// Initialization
@@ -53,16 +53,19 @@ export function Events(Base) {
const coverElm = dom.find('section.cover');
if (!coverElm) {
- dom.toggleClass(dom.body, 'add', 'sticky');
+ dom.body.classList.add('sticky');
return;
}
- const observer = new IntersectionObserver(entries => {
- const isIntersecting = entries[0].isIntersecting;
- const op = isIntersecting ? 'remove' : 'add';
+ const observer = new IntersectionObserver(
+ entries => {
+ const isIntersecting = entries[0].isIntersecting;
+ const op = isIntersecting ? 'remove' : 'add';
- dom.toggleClass(dom.body, op, 'sticky');
- });
+ dom.body.classList[op]('sticky');
+ },
+ { threshold: 0.01 },
+ );
observer.observe(coverElm);
}
@@ -73,7 +76,7 @@ export function Events(Base) {
* @void
*/
#initHeadings() {
- const headingElms = dom.findAll('#main :where(h1, h2, h3, h4, h5)');
+ const headingElms = dom.findAll('#main :where(h1, h2, h3, h4, h5, h6)');
const headingsInView = new Set();
let isInitialLoad = true;
@@ -165,32 +168,35 @@ export function Events(Base) {
// Convert key sequences to sorted arrays (modifiers first)
// Ex: ['alt+t', 't+ctrl'] => [['alt', 't'], ['ctrl', 't']]
- bindingConfig.bindings = bindingConfig.bindings.map(keys => {
- const sortedKeys = [[], []]; // Modifier keys, non-modifier keys
+ bindingConfig.bindings = bindingConfig.bindings.map(
+ (/** @type {string | string[]} */ keys) => {
+ /** @type {string[][]} */
+ const sortedKeys = [[], []]; // Modifier keys, non-modifier keys
- if (typeof keys === 'string') {
- keys = keys.split('+');
- }
+ if (typeof keys === 'string') {
+ keys = keys.split('+');
+ }
- keys.forEach(key => {
- const isModifierKey = modifierKeys.includes(key);
- const targetArray = sortedKeys[isModifierKey ? 0 : 1];
- const newKeyValue = key.trim().toLowerCase();
+ keys.forEach(key => {
+ const isModifierKey = modifierKeys.includes(key);
+ const targetArray = sortedKeys[isModifierKey ? 0 : 1];
+ const newKeyValue = key.trim().toLowerCase();
- targetArray.push(newKeyValue);
- });
+ targetArray.push(newKeyValue);
+ });
- sortedKeys.forEach(arr => arr.sort());
+ sortedKeys.forEach(arr => arr.sort());
- return sortedKeys.flat();
- });
+ return sortedKeys.flat();
+ },
+ );
});
// Handle keyboard events
- dom.on('keydown', e => {
- const isTextEntry = document.activeElement.matches(
- 'input, select, textarea',
- );
+ dom.on('keydown', (/** @type {KeyboardEvent} */ e) => {
+ const isTextEntry = /** @type {HTMLElement} */ (
+ document.activeElement
+ ).matches('input, select, textarea');
if (isTextEntry) {
return;
@@ -198,15 +204,16 @@ export function Events(Base) {
const bindingConfigs = Object.values(keyBindings || []);
const matchingConfigs = bindingConfigs.filter(
- ({ bindings }) =>
+ (/** @type {{ bindings: string[][] }} */ { bindings }) =>
bindings &&
// bindings: [['alt', 't'], ['ctrl', 't']]
- bindings.some(keys =>
+ bindings.some((/** @type {string[]} */ keys) =>
// keys: ['alt', 't']
keys.every(
// k: 'alt'
k =>
- (modifierKeys.includes(k) && e[k + 'Key']) ||
+ (modifierKeys.includes(k) &&
+ e[/** @type {keyof KeyboardEvent} */ (k + 'Key')]) ||
e.key === k || // Ex: " ", "a"
e.code.toLowerCase() === k || // "space"
e.code.toLowerCase() === `key${k}`, // "keya"
@@ -242,13 +249,15 @@ export function Events(Base) {
});
// Collapse toggle
- dom.on(sidebarElm, 'click', ({ target }) => {
- const linkElm = target.closest('a');
- const linkParent = linkElm?.closest('li');
+ dom.on(sidebarElm, 'click', (/** @type {MouseEvent} */ { target }) => {
+ const linkElm = /** @type {HTMLElement} */ (target).closest('a');
+ const linkParent = /** @type {HTMLLIElement} */ (
+ linkElm?.closest('li')
+ );
const hasSubSidebar = linkParent?.querySelector('.app-sub-sidebar');
if (hasSubSidebar) {
- dom.toggleClass(linkParent, 'collapse');
+ linkParent.classList.toggle('collapse');
}
});
}
@@ -266,19 +275,20 @@ export function Events(Base) {
return;
}
+ /** @type {HTMLElement | null} */
let lastContentFocusElm;
// Store last focused content element (restored via #toggleSidebar)
- dom.on(contentElm, 'focusin', e => {
+ dom.on(contentElm, 'focusin', (/** @type {FocusEvent} */ e) => {
const focusAttr = 'data-restore-focus';
lastContentFocusElm?.removeAttribute(focusAttr);
- lastContentFocusElm = e.target;
+ lastContentFocusElm = /** @type {HTMLElement} */ (e.target);
lastContentFocusElm.setAttribute(focusAttr, '');
});
// Toggle sidebar
- dom.on(toggleElm, 'click', e => {
+ dom.on(toggleElm, 'click', (/** @type {MouseEvent} */ e) => {
e.stopPropagation();
this.#toggleSidebar();
});
@@ -351,7 +361,7 @@ export function Events(Base) {
// Anchor link
if (query.id) {
const headingElm = dom.find(
- `.markdown-section :where(h1, h2, h3, h4, h5)[id="${query.id}"]`,
+ `.markdown-section :where(h1, h2, h3, h4, h5, h6)[id="${query.id}"]`,
);
if (headingElm) {
@@ -366,7 +376,8 @@ export function Events(Base) {
else if (source === 'navigate') {
// Scroll to top
if (auto2top) {
- document.scrollingElement.scrollTop = topMargin ?? 0;
+ /** @type {Element} */ (document.scrollingElement).scrollTop =
+ topMargin ?? 0;
}
}
}
@@ -406,13 +417,15 @@ export function Events(Base) {
...options,
};
const { query } = this.route;
- const focusEl = query.id
- ? // Heading ID
- dom.find(`#${query.id}`)
- : // First heading
- dom.find('#main :where(h1, h2, h3, h4, h5, h6)') ||
- // Content container
- dom.find('#main');
+ const focusEl = /** @type {HTMLElement|null} */ (
+ query.id
+ ? // Heading ID
+ dom.find(`#${query.id}`)
+ : // First heading
+ dom.find('#main :where(h1, h2, h3, h4, h5, h6)') ||
+ // Content container
+ dom.find('#main')
+ );
// Move focus to content area
if (focusEl) {
@@ -433,10 +446,6 @@ export function Events(Base) {
/**
* Marks the active app nav item
- *
- * @param {string} [href] Matching element HREF value. If unspecified,
- * defaults to the current path (without query params)
- * @void
*/
#markAppNavActiveElm() {
const href = decodeURIComponent(this.router.toURL(this.route.path));
@@ -448,13 +457,16 @@ export function Events(Base) {
return;
}
- const newActive = dom
- .findAll(navElm, 'a')
+ const newActive = /** @type {HTMLAnchorElement[]} */ (
+ dom.findAll(navElm, 'a')
+ )
.sort((a, b) => b.href.length - a.href.length)
.find(
a =>
- href.includes(a.getAttribute('href')) ||
- href.includes(decodeURI(a.getAttribute('href'))),
+ href.includes(/** @type {string} */ (a.getAttribute('href'))) ||
+ href.includes(
+ decodeURI(/** @type {string} */ (a.getAttribute('href'))),
+ ),
)
?.closest('li');
const oldActive = dom.find(navElm, 'li.active');
@@ -488,7 +500,7 @@ export function Events(Base) {
const newActive = dom
.find(
sidebar,
- `a[href="${href}"], a[href="${decodeURIComponent(href)}"]`,
+ `a[href="${href}"], a[href="${decodeURIComponent(/** @type {string} */ (href))}"]`,
)
?.closest('li');
@@ -521,7 +533,7 @@ export function Events(Base) {
const newPage = dom
.find(
sidebar,
- `a[href="${path}"], a[href="${decodeURIComponent(path)}"]`,
+ `a[href="${path}"], a[href="${decodeURIComponent(/** @type {string} */ (path))}"]`,
)
?.closest('li');
@@ -533,8 +545,11 @@ export function Events(Base) {
return newPage;
}
+ /**
+ * @param {boolean} [force]
+ */
#toggleSidebar(force) {
- const sidebarElm = dom.find('.sidebar');
+ const sidebarElm = /** @type {HTMLElement|null} */ (dom.find('.sidebar'));
if (!sidebarElm) {
return;
@@ -548,9 +563,11 @@ export function Events(Base) {
// Set aria-expanded attribute
ariaElms.forEach(toggleElm => {
+ const expanded = force ?? sidebarElm.classList.contains('show');
+ toggleElm.setAttribute('aria-expanded', String(expanded));
toggleElm.setAttribute(
- 'aria-expanded',
- force ?? sidebarElm.classList.contains('show'),
+ 'aria-label',
+ expanded ? 'Hide primary navigation' : 'Show primary navigation',
);
});
@@ -568,8 +585,8 @@ export function Events(Base) {
}
// Restore focus
else {
- const restoreElm = document.querySelector(
- 'main > .content [data-restore-focus]',
+ const restoreElm = /** @type {HTMLElement|null} */ (
+ document.querySelector('main > .content [data-restore-focus]')
);
if (restoreElm) {
@@ -602,6 +619,9 @@ export function Events(Base) {
}
// Browsers w/o native scrollend event support (Safari)
else {
+ /** @type {any} */
+ let scrollTimer;
+
const callback = () => {
clearTimeout(scrollTimer);
@@ -611,8 +631,6 @@ export function Events(Base) {
}, 100);
};
- let scrollTimer;
-
document.addEventListener('scroll', callback, false);
}
},
diff --git a/src/core/fetch/index.js b/src/core/fetch/index.js
index be4e21409c..3dcc0caf9d 100644
--- a/src/core/fetch/index.js
+++ b/src/core/fetch/index.js
@@ -10,6 +10,14 @@ import { get } from '../util/ajax.js';
*/
export function Fetch(Base) {
return class Fetch extends Base {
+ /**
+ * @param {any} path
+ * @param {any} qs
+ * @param {any} file
+ * @param {any} next
+ * @param {any} vm
+ * @param {any} [first]
+ */
#loadNested(path, qs, file, next, vm, first) {
path = first ? path : path.replace(/\/$/, '');
path = getParentPath(path);
@@ -25,16 +33,25 @@ export function Fetch(Base) {
).then(next, _error => this.#loadNested(path, qs, file, next, vm));
}
+ /** @type {any} */
#last;
#abort = () => this.#last && this.#last.abort && this.#last.abort();
+ /**
+ * @param {any} url
+ * @param {any} requestHeaders
+ */
#request = (url, requestHeaders) => {
this.#abort();
this.#last = get(url, true, requestHeaders);
return this.#last;
};
+ /**
+ * @param {any} path
+ * @param {any} config
+ */
#get404Path = (path, config) => {
const { notFoundPage, ext } = config;
const defaultPath = '_404' + (ext || '.md');
@@ -64,8 +81,17 @@ export function Fetch(Base) {
return path404;
};
+ /**
+ * @param {any} path
+ * @param {any} qs
+ * @param {any} loadSidebar
+ * @param {any} cb
+ */
_loadSideAndNav(path, qs, loadSidebar, cb) {
return () => {
+ /**
+ * @param {any} result
+ */
const renderSidebar = result => {
this._renderSidebar(result);
cb();
@@ -73,7 +99,7 @@ export function Fetch(Base) {
if (!loadSidebar) {
// Although, we don't load sidebar from sidebar file, we still need call the render to auto generate sidebar from headings toc
- renderSidebar();
+ renderSidebar(null);
return;
}
@@ -103,6 +129,11 @@ export function Fetch(Base) {
this.isHTML = /\.html$/g.test(file);
// create a handler that should be called if content was fetched successfully
+ /**
+ * @param {any} text
+ * @param {any} [opt]
+ * @param {any} [response]
+ */
const contentFetched = (text, opt, response) => {
this.route.response = response;
this._renderMain(
@@ -113,6 +144,10 @@ export function Fetch(Base) {
};
// and a handler that is called if content failed to fetch
+ /**
+ * @param {any} _error
+ * @param {any} [response]
+ */
const contentFailedToFetch = (_error, response) => {
this.route.response = response;
this._fetchFallbackPage(path, qs, cb) || this._fetch404(file, qs, cb);
@@ -120,7 +155,7 @@ export function Fetch(Base) {
// attempt to fetch content from a virtual route, and fallback to fetching the actual file
if (!this.isRemoteUrl) {
- this.matchVirtualRoute(path).then(contents => {
+ this.matchVirtualRoute(path).then((/** @type {any} */ contents) => {
if (typeof contents === 'string') {
contentFetched(contents);
} else {
@@ -144,7 +179,7 @@ export function Fetch(Base) {
path,
qs,
loadNavbar,
- text => this._renderNav(text),
+ (/** @type {string} */ text) => this._renderNav(text),
this,
true,
);
@@ -203,8 +238,18 @@ export function Fetch(Base) {
}
}
+ /**
+ * @param {any} path
+ * @param {any} qs
+ * @param {any} [cb]
+ */
_fetchFallbackPage(path, qs, cb = noop) {
- const { requestHeaders, fallbackLanguages, loadSidebar } = this.config;
+ const {
+ requestHeaders,
+ fallbackLanguages,
+ fallbackDefaultLanguage,
+ loadSidebar,
+ } = this.config;
if (!fallbackLanguages) {
return false;
@@ -217,17 +262,22 @@ export function Fetch(Base) {
}
const newPath = this.router.getFile(
- path.replace(new RegExp(`^/${local}`), ''),
+ path.replace(new RegExp(`^/${local}`), fallbackDefaultLanguage),
);
const req = this.#request(newPath + qs, requestHeaders);
req.then(
+ /**
+ * @param {any} text
+ * @param {any} [opt]
+ */
(text, opt) =>
this._renderMain(
text,
opt,
this._loadSideAndNav(path, qs, loadSidebar, cb),
),
+ /** @param {any} _error */
_error => this._fetch404(path, qs, cb),
);
@@ -240,7 +290,6 @@ export function Fetch(Base) {
* @param {*} qs TODO: define
* @param {Function} cb Callback
* @returns {Boolean} True if the requested page is not found
- * @private
*/
_fetch404(path, qs, cb = noop) {
const { loadSidebar, requestHeaders, notFoundPage } = this.config;
@@ -250,7 +299,12 @@ export function Fetch(Base) {
const path404 = this.#get404Path(path, this.config);
this.#request(this.router.getFile(path404), requestHeaders).then(
+ /**
+ * @param {any} text
+ * @param {any} [opt]
+ */
(text, opt) => this._renderMain(text, opt, fnLoadSideAndNav),
+ /** @param {any} _error */
_error => this._renderMain(null, {}, fnLoadSideAndNav),
);
return true;
@@ -261,7 +315,7 @@ export function Fetch(Base) {
}
initFetch() {
- this.$fetch(_ => this.callHook('ready'));
+ this.$fetch(() => this.callHook('ready'));
}
};
}
diff --git a/src/core/global-api.js b/src/core/global-api.js
index 8a571d84ac..1b48cb43b1 100644
--- a/src/core/global-api.js
+++ b/src/core/global-api.js
@@ -1,4 +1,4 @@
-import prism from 'prismjs';
+import * as prism from 'prismjs';
import { marked } from 'marked';
import * as util from './util/index.js';
import * as dom from './util/dom.js';
@@ -6,10 +6,6 @@ import { Compiler } from './render/compiler.js';
import { slugify } from './render/slugify.js';
import { get } from './util/ajax.js';
-// TODO This is deprecated, kept for backwards compatibility. Remove in a
-// major release. We'll tell people to get everything from the DOCSIFY global
-// when using the global build, but we'll highly recommend for them to import
-// from the ESM build (f.e. dist/docsify.esm.js and dist/docsify.min.esm.js).
export default function initGlobalAPI() {
window.Docsify = {
util,
diff --git a/src/core/globals.ts b/src/core/globals.ts
new file mode 100644
index 0000000000..cf0f795211
--- /dev/null
+++ b/src/core/globals.ts
@@ -0,0 +1,42 @@
+import prism from 'prismjs';
+import { marked as _marked } from 'marked';
+import * as util from './util/index.js';
+import * as dom from './util/dom.js';
+import { Compiler } from './render/compiler.js';
+import { slugify } from './render/slugify.js';
+import { get } from './util/ajax.js';
+import { DocsifyConfig } from './config.js';
+import { Docsify } from './Docsify.js';
+
+type DocsifyConfigOrFn =
+ | Partial
+ | (Partial & ((config: Docsify) => Partial));
+
+declare global {
+ interface Window {
+ $docsify?: DocsifyConfigOrFn;
+
+ Docsify: {
+ util: typeof util;
+ dom: typeof dom;
+ get: typeof get;
+ slugify: typeof slugify;
+ version: string;
+ };
+ DocsifyCompiler: typeof Compiler;
+ marked: typeof _marked;
+ Prism: typeof prism;
+ Vue: any; // TODO Get Vue types and apply them here
+
+ __current_docsify_compiler__?: Compiler;
+ }
+
+ const $docsify: Window['$docsify'];
+
+ const Docsify: Window['Docsify'];
+ const DocsifyCompiler: Window['DocsifyCompiler'];
+ const marked: Window['marked'];
+ // @ts-expect-error Prism types are wonky
+ const Prism: Window['Prism'];
+ const Vue: Window['Vue'];
+}
diff --git a/src/core/index.js b/src/core/index.js
index 897ac66b3a..43fb5b9c7e 100644
--- a/src/core/index.js
+++ b/src/core/index.js
@@ -2,10 +2,6 @@ import { documentReady } from './util/dom.js';
import { Docsify } from './Docsify.js';
import initGlobalAPI from './global-api.js';
-// TODO This global API and auto-running Docsify will be deprecated, and removed
-// in a major release. Instead we'll tell users to use `new Docsify()` to create
-// and manage their instance(s).
-
/**
* Global API
*/
diff --git a/src/core/init/lifecycle.js b/src/core/init/lifecycle.js
index 7174079afb..de11f84c70 100644
--- a/src/core/init/lifecycle.js
+++ b/src/core/init/lifecycle.js
@@ -8,8 +8,10 @@ import { noop } from '../util/core.js';
*/
export function Lifecycle(Base) {
return class Lifecycle extends Base {
+ /** @type {Record} */
_hooks = {};
- _lifecycle = {};
+
+ _lifecycle = /** @type {Hooks} */ ({});
initLifecycle() {
const hooks = [
@@ -21,16 +23,25 @@ export function Lifecycle(Base) {
'ready',
];
- hooks.forEach(hook => {
+ hooks.forEach((/** @type {string} */ hook) => {
+ /** @type {Function[]} */
const arr = (this._hooks[hook] = []);
- this._lifecycle[hook] = fn => arr.push(fn);
+ this._lifecycle[hook] = (/** @type {Function} */ fn) => arr.push(fn);
});
}
+ /**
+ * @param {string} hookName
+ * @param {any} [data]
+ * @param {Function} [next]
+ */
callHook(hookName, data, next = noop) {
const queue = this._hooks[hookName];
const catchPluginErrors = this.config.catchPluginErrors;
+ /**
+ * @param {number} index
+ */
const step = function (index) {
const hookFn = queue[index];
@@ -40,9 +51,11 @@ export function Lifecycle(Base) {
const errTitle = 'Docsify plugin error';
if (hookFn.length === 2) {
+ // FIXME this does not catch async errors. We can support async
+ // functions for this, or add a second arg to next() functions.
try {
- hookFn(data, result => {
- data = result;
+ hookFn(data, (/** @type {string} */ result) => {
+ data = result === undefined ? data : result;
step(index + 1);
});
} catch (err) {
@@ -81,3 +94,20 @@ export function Lifecycle(Base) {
}
};
}
+
+/**
+@typedef {{
+ init(): void
+ mounted(): void
+ beforeEach: (
+ ((markdown: string) => string) |
+ ((markdown: string, next: (markdown?: string) => void) => void)
+ )
+ afterEach: (
+ ((html: string) => string) |
+ ((html: string, next: (html?: string) => void) => void)
+ )
+ doneEach(): void
+ ready(): void
+}} Hooks
+*/
diff --git a/src/core/module.js b/src/core/module.js
new file mode 100644
index 0000000000..4206ebc246
--- /dev/null
+++ b/src/core/module.js
@@ -0,0 +1 @@
+export * from './Docsify.js';
diff --git a/src/core/module.min.js b/src/core/module.min.js
new file mode 100644
index 0000000000..c08b05d104
--- /dev/null
+++ b/src/core/module.min.js
@@ -0,0 +1,2 @@
+// This file exists for type declaration output.
+export * from './Docsify.js';
diff --git a/src/core/modules.ts b/src/core/modules.ts
new file mode 100644
index 0000000000..55ba6a0820
--- /dev/null
+++ b/src/core/modules.ts
@@ -0,0 +1,4 @@
+declare module '*.css' {
+ const cssText: string;
+ export default cssText;
+}
diff --git a/src/core/render/compiler.js b/src/core/render/compiler.js
index 9833482cca..ef15709108 100644
--- a/src/core/render/compiler.js
+++ b/src/core/render/compiler.js
@@ -15,6 +15,7 @@ import { taskListCompiler } from './compiler/taskList.js';
import { taskListItemCompiler } from './compiler/taskListItem.js';
import { linkCompiler } from './compiler/link.js';
import { compileMedia } from './compiler/media.js';
+import { tableCellCompiler } from './compiler/tableCell.js';
const cachedLinks = {};
@@ -95,7 +96,7 @@ export class Compiler {
* @param {string} href The href to the file to embed in the page.
* @param {string} title Title of the link used to make the embed.
*
- * @return {type} Return value description.
+ * @return {any} Return value description.
*/
compileEmbed(href, title) {
const { str, config } = getAndRemoveConfig(title);
@@ -134,6 +135,7 @@ export class Compiler {
}
embed.fragment = config.fragment;
+ embed.omitFragmentLine = config.omitFragmentLine;
return embed;
}
@@ -176,7 +178,9 @@ export class Compiler {
origin.image = imageCompiler({ renderer, contentBase, router });
origin.list = taskListCompiler({ renderer });
origin.listitem = taskListItemCompiler({ renderer });
+ origin.tablecell = tableCellCompiler({ renderer });
+ // @ts-expect-error
renderer.origin = origin;
return renderer;
@@ -263,6 +267,7 @@ export class Compiler {
text: text,
tokens: [{ type: 'text', raw: text, text: text }],
};
+ // @ts-expect-error
return this.renderer.heading(tokenHeading);
}
diff --git a/src/core/render/compiler/blockquote.js b/src/core/render/compiler/blockquote.js
index 5f00951dea..8fc2607e18 100644
--- a/src/core/render/compiler/blockquote.js
+++ b/src/core/render/compiler/blockquote.js
@@ -1,36 +1,45 @@
export const blockquoteCompiler = ({ renderer }) =>
(renderer.blockquote = function ({ tokens }) {
- const calloutData =
- tokens[0].type === 'paragraph' &&
- // 0: Match "[!TIP] My Title"
- // 1: Mark "[!TIP]"
- // 2: Type "TIP"
- tokens[0].raw.match(/^(\[!(\w+)\])/);
-
let openTag = '';
let closeTag = ' ';
- if (calloutData) {
- const calloutMark = calloutData[1]; // "[!TIP]"
- const calloutType = calloutData[2].toLowerCase(); // "tip"
- const token = tokens[0].tokens[0];
+ // Find the first paragraph token in the blockquote
+ const firstParagraphIndex = tokens.findIndex(t => t.type === 'paragraph');
+ const firstParagraph = tokens[firstParagraphIndex];
- // Remove callout mark from tokens
- ['raw', 'text'].forEach(key => {
- token[key] = token[key].replace(calloutMark, '').trimStart();
- });
+ if (firstParagraph) {
+ // Check if the paragraph starts with a callout like [!TIP] or [!NOTE]
+ const calloutData = firstParagraph.raw.match(/^(\[!(\w+)\])/);
- // Remove empty paragraph
- if (tokens.length > 1 && !token.raw.trim()) {
- tokens = tokens.slice(1);
- }
+ if (calloutData) {
+ const calloutMark = calloutData[1]; // "[!TIP]"
+ const calloutType = calloutData[2].toLowerCase(); // "tip"
+
+ // Remove the callout mark from the paragraph raw text
+ firstParagraph.raw = firstParagraph.raw
+ .replace(calloutMark, '')
+ .trimStart();
+ if (firstParagraph.tokens && firstParagraph.tokens.length > 0) {
+ firstParagraph.tokens.forEach(t => {
+ if (t.raw) {
+ t.raw = t.raw.replace(calloutMark, '');
+ }
+ if (t.text) {
+ t.text = t.text.replace(calloutMark, '');
+ }
+ });
+ }
- openTag = ``;
- closeTag = `
`;
+ // If the first paragraph is now empty after removing [!TIP], remove it
+ if (!firstParagraph.raw.trim()) {
+ tokens.splice(firstParagraphIndex, 1);
+ }
+
+ openTag = ``;
+ closeTag = `
`;
+ }
}
const body = this.parser.parse(tokens);
- const html = `${openTag}${body}${closeTag}`;
-
- return html;
+ return `${openTag}${body}${closeTag}`;
});
diff --git a/src/core/render/compiler/code.js b/src/core/render/compiler/code.js
index b63b85aa1b..bfe57413b9 100644
--- a/src/core/render/compiler/code.js
+++ b/src/core/render/compiler/code.js
@@ -1,4 +1,4 @@
-import Prism from 'prismjs';
+import * as Prism from 'prismjs';
// See https://github.com/PrismJS/prism/pull/1367
import 'prismjs/components/prism-markup-templating.js';
import checkLangDependenciesAllLoaded from '../../util/prism.js';
diff --git a/src/core/render/compiler/image.js b/src/core/render/compiler/image.js
index 6e9b20f897..d113516b61 100644
--- a/src/core/render/compiler/image.js
+++ b/src/core/render/compiler/image.js
@@ -18,7 +18,7 @@ export const imageCompiler = ({ renderer, contentBase, router }) =>
}
if (config.size) {
- const [width, height] = config.size.split('x');
+ const [width, height] = /** @type {string} */ (config.size).split('x');
if (height) {
attrs.push(`width="${width}" height="${height}"`);
} else {
diff --git a/src/core/render/compiler/media.js b/src/core/render/compiler/media.js
index d12b1fb0dc..3fa3cd799b 100644
--- a/src/core/render/compiler/media.js
+++ b/src/core/render/compiler/media.js
@@ -18,12 +18,12 @@ export const compileMedia = {
},
video(url, title) {
return {
- html: `Not Support `,
+ html: `Not Supported `,
};
},
audio(url, title) {
return {
- html: `Not Support `,
+ html: `Not Supported `,
};
},
code(url, title) {
diff --git a/src/core/render/compiler/tableCell.js b/src/core/render/compiler/tableCell.js
new file mode 100644
index 0000000000..063ed6682a
--- /dev/null
+++ b/src/core/render/compiler/tableCell.js
@@ -0,0 +1,15 @@
+export const tableCellCompiler = ({ renderer }) =>
+ (renderer.tablecell = function (token) {
+ let content;
+
+ if (token.embedTokens && token.embedTokens.length > 0) {
+ content = this.parser.parse(token.embedTokens);
+ } else {
+ content = this.parser.parseInline(token.tokens);
+ }
+
+ const type = token.header ? 'th' : 'td';
+ const tag = token.align ? `<${type} align="${token.align}">` : `<${type}>`;
+
+ return tag + content + `${type}>\n`;
+ });
diff --git a/src/core/render/compiler/taskListItem.js b/src/core/render/compiler/taskListItem.js
index 5978db7649..4c0019b251 100644
--- a/src/core/render/compiler/taskListItem.js
+++ b/src/core/render/compiler/taskListItem.js
@@ -21,8 +21,6 @@ export const taskListItemCompiler = ({ renderer }) =>
text: checkbox + ' ',
});
}
- } else {
- text += checkbox + ' ';
}
}
diff --git a/src/core/render/embed.js b/src/core/render/embed.js
index 8759f13d47..8a55005341 100644
--- a/src/core/render/embed.js
+++ b/src/core/render/embed.js
@@ -12,16 +12,22 @@ const cached = {};
*
* @param {string} text - The input text that may contain embedded fragments.
* @param {string} fragment - The fragment identifier to search for.
- * @returns {string} - The extracted and demented content, or an empty string if not found.
+ * @param {boolean} fullLine - Boolean flag to enable full-line matching of fragment identifiers.
+ * @returns {string} - The extracted and dedented content, or an empty string if not found.
*/
-function extractFragmentContent(text, fragment) {
+function extractFragmentContent(text, fragment, fullLine) {
if (!fragment) {
return text;
}
-
+ let fragmentRegex = `(?:###|\\/\\/\\/)\\s*\\[${fragment}\\]`;
+ const contentRegex = `[\\s\\S]*?`;
+ if (fullLine) {
+ // Match full line containing fragment identifier (e.g. /// [demo])
+ fragmentRegex = `.*${fragmentRegex}.*\n`;
+ }
const pattern = new RegExp(
- `(?:###|\\/\\/\\/)\\s*\\[${fragment}\\]([\\s\\S]*?)(?:###|\\/\\/\\/)\\s*\\[${fragment}\\]`,
- );
+ `(?:${fragmentRegex})(${contentRegex})(?:${fragmentRegex})`,
+ ); // content is the capture group
const match = text.match(pattern);
return stripIndent((match || [])[1] || '').trim();
}
@@ -61,20 +67,27 @@ function walkFetchEmbed({ embedTokens, compile, fetch }, cb) {
});
// This may contain YAML front matter and will need to be stripped.
- const frontMatterInstalled =
- ($docsify.frontMatter || {}).installed || false;
- if (frontMatterInstalled === true) {
- text = $docsify.frontMatter.parseMarkdown(text);
+ const frontMatterInstalled = $docsify?.frontMatter?.installed;
+ if (frontMatterInstalled) {
+ text = $docsify.frontMatter?.parseMarkdown(text);
}
if (currentToken.embed.fragment) {
- text = extractFragmentContent(text, currentToken.embed.fragment);
+ text = extractFragmentContent(
+ text,
+ currentToken.embed.fragment,
+ currentToken.embed.omitFragmentLine,
+ );
}
embedToken = compile.lexer(text);
} else if (currentToken.embed.type === 'code') {
if (currentToken.embed.fragment) {
- text = extractFragmentContent(text, currentToken.embed.fragment);
+ text = extractFragmentContent(
+ text,
+ currentToken.embed.fragment,
+ currentToken.embed.omitFragmentLine,
+ );
}
embedToken = compile.lexer(
@@ -91,14 +104,21 @@ function walkFetchEmbed({ embedTokens, compile, fetch }, cb) {
text: /* html */ `\n${text}\n
`,
},
];
- embedToken.links = {};
+ /** @type {any} */ (embedToken).links = {};
} else {
embedToken = [{ type: 'html', text }];
- embedToken.links = {};
+ /** @type {any} */ (embedToken).links = {};
}
}
- cb({ token: currentToken, embedToken });
+ cb({
+ token: currentToken,
+ embedToken,
+ rowIndex: currentToken.rowIndex,
+ cellIndex: currentToken.cellIndex,
+ tokenRef: currentToken.tokenRef,
+ });
+
if (++count >= embedTokens.length) {
cb({});
}
@@ -126,23 +146,45 @@ export function prerenderEmbed({ compiler, raw = '', fetch }, done) {
const linkRE = compile.Lexer.rules.inline.normal.link;
const links = tokens.links;
+ const linkMatcher = new RegExp(linkRE.source, 'g');
+
tokens.forEach((token, index) => {
if (token.type === 'paragraph') {
token.text = token.text.replace(
- new RegExp(linkRE.source, 'g'),
+ linkMatcher,
(src, filename, href, title) => {
const embed = compiler.compileEmbed(href, title);
-
if (embed) {
embedTokens.push({
index,
+ tokenRef: token,
embed,
});
}
-
return src;
},
);
+ } else if (token.type === 'table') {
+ token.rows.forEach((row, rowIndex) => {
+ row.forEach((cell, cellIndex) => {
+ cell.text = cell.text.replace(
+ linkMatcher,
+ (src, filename, href, title) => {
+ const embed = compiler.compileEmbed(href, title);
+ if (embed) {
+ embedTokens.push({
+ index,
+ tokenRef: token,
+ rowIndex,
+ cellIndex,
+ embed,
+ });
+ }
+ return src;
+ },
+ );
+ });
+ });
}
});
@@ -150,27 +192,36 @@ export function prerenderEmbed({ compiler, raw = '', fetch }, done) {
// so that we know where to insert the embedded tokens as they
// are returned
const moves = [];
- walkFetchEmbed({ compile, embedTokens, fetch }, ({ embedToken, token }) => {
- if (token) {
- // iterate through the array of previously inserted tokens
- // to determine where the current embedded tokens should be inserted
- let index = token.index;
- moves.forEach(pos => {
- if (index > pos.start) {
- index += pos.length;
- }
- });
+ walkFetchEmbed(
+ { compile, embedTokens, fetch },
+ ({ embedToken, token, rowIndex, cellIndex, tokenRef }) => {
+ if (token) {
+ if (typeof rowIndex === 'number' && typeof cellIndex === 'number') {
+ const cell = tokenRef.rows[rowIndex][cellIndex];
+
+ cell.embedTokens = embedToken;
+ } else {
+ // iterate through the array of previously inserted tokens
+ // to determine where the current embedded tokens should be inserted
+ let index = token.index;
+ moves.forEach(pos => {
+ if (index > pos.start) {
+ index += pos.length;
+ }
+ });
- Object.assign(links, embedToken.links);
+ Object.assign(links, embedToken.links);
- tokens = tokens
- .slice(0, index)
- .concat(embedToken, tokens.slice(index + 1));
- moves.push({ start: index, length: embedToken.length - 1 });
- } else {
- cached[raw] = tokens.concat();
- tokens.links = cached[raw].links = links;
- done(tokens);
- }
- });
+ tokens = tokens
+ .slice(0, index)
+ .concat(embedToken, tokens.slice(index + 1));
+ moves.push({ start: index, length: embedToken.length - 1 });
+ }
+ } else {
+ cached[raw] = tokens.concat();
+ tokens.links = cached[raw].links = links;
+ done(tokens);
+ }
+ },
+ );
}
diff --git a/src/core/render/index.js b/src/core/render/index.js
index 85b6e7200f..9e546a3d01 100644
--- a/src/core/render/index.js
+++ b/src/core/render/index.js
@@ -9,18 +9,28 @@ import { prerenderEmbed } from './embed.js';
/** @typedef {import('../Docsify.js').Constructor} Constructor */
+// TODO replace with Vue types if available
+/** @typedef {{ _isVue?: boolean, $destroy?: () => void }} Vue2Instance */
+/** @typedef {{ __vue__?: Vue2Instance }} WithVue2 */
+/** @typedef {{ __v_skip?: boolean }} VNode3 */
+/** @typedef {{ _vnode?: VNode3, __vue_app__?: { unmount: () => void } }} WithVue3 */
+/** @typedef {Element & WithVue2 & WithVue3} VueMountElement */
+
/**
* @template {!Constructor} T
* @param {T} Base - The class to extend
*/
export function Render(Base) {
return class Render extends Base {
+ /** @type {Compiler | undefined} */
+ compiler;
#vueGlobalData;
#addTextAsTitleAttribute(cssSelector) {
dom.findAll(cssSelector).forEach(elm => {
- if (!elm.title && elm.innerText) {
- elm.title = elm.innerText;
+ const e = /** @type {HTMLElement} */ (elm);
+ if (!e.title && e.innerText) {
+ e.title = e.innerText;
}
});
}
@@ -28,12 +38,14 @@ export function Render(Base) {
#executeScript() {
const script = dom
.findAll('.markdown-section>script')
- .filter(s => !/template/.test(s.type))[0];
+ .filter(
+ s => !/template/.test(/** @type {HTMLScriptElement} */ (s).type),
+ )[0];
if (!script) {
return false;
}
- const code = script.innerText.trim();
+ const code = /** @type {HTMLElement} */ (script).innerText.trim();
if (!code) {
return false;
}
@@ -60,6 +72,9 @@ export function Render(Base) {
window.Vue.version &&
Number(window.Vue.version.charAt(0));
+ /**
+ * @param {VueMountElement} elm
+ */
const isMountedVue = elm => {
const isVue2 = Boolean(elm.__vue__ && elm.__vue__._isVue);
const isVue3 = Boolean(elm._vnode && elm._vnode.__v_skip);
@@ -75,9 +90,9 @@ export function Render(Base) {
// Destroy/unmount existing Vue instances
for (const mountedElm of mountedElms) {
if (vueVersion === 2) {
- mountedElm.__vue__.$destroy();
+ /** @type {VueMountElement} */ (mountedElm).__vue__?.$destroy?.();
} else if (vueVersion === 3) {
- mountedElm.__vue_app__.unmount();
+ /** @type {VueMountElement} */ (mountedElm).__vue_app__?.unmount();
}
}
}
@@ -159,12 +174,16 @@ export function Render(Base) {
.filter(elm => !vueMountData.some(([e, c]) => e === elm))
// Detect Vue content
.filter(elm => {
+ const selector = vueComponentNames.join(',');
+ const hasComponents = selector
+ ? Boolean(elm.querySelector(selector))
+ : false;
const isVueMount =
// is a component
elm.tagName.toLowerCase() in
(docsifyConfig.vueComponents || {}) ||
// has a component(s)
- elm.querySelector(vueComponentNames.join(',') || null) ||
+ hasComponents ||
// has curly braces
reHasBraces.test(elm.outerHTML) ||
// has content directive
@@ -281,24 +300,30 @@ export function Render(Base) {
const sidebarToggleEl = dom.getNode('button.sidebar-toggle');
if (hideSidebar) {
- sidebarEl?.remove(sidebarEl);
- sidebarToggleEl?.remove(sidebarToggleEl);
+ sidebarEl?.remove();
+ sidebarToggleEl?.remove();
return null;
}
+ if (!this.compiler) {
+ throw new Error('Compiler is not initialized');
+ }
+
dom.setHTML('.sidebar-nav', this.compiler.sidebar(text, maxLevel));
- sidebarToggleEl.setAttribute('aria-expanded', !isMobile());
+ sidebarToggleEl.setAttribute('aria-expanded', String(!isMobile()));
const activeElmHref = this.router.toURL(this.route.path);
- const activeEl = dom.find(`.sidebar-nav a[href="${activeElmHref}"]`);
+ const activeEl = /** @type {HTMLElement | null} */ (
+ dom.find(`.sidebar-nav a[href="${activeElmHref}"]`)
+ );
this.#addTextAsTitleAttribute('.sidebar-nav a');
if (loadSidebar && activeEl) {
- activeEl.parentNode.innerHTML +=
- this.compiler.subSidebar(subMaxLevel) || '';
+ const parent = /** @type {HTMLElement} */ (activeEl.parentElement);
+ parent.innerHTML += this.compiler.subSidebar(subMaxLevel) || '';
} else {
this.compiler.resetToc();
}
@@ -335,6 +360,9 @@ export function Render(Base) {
});
}
+ /**
+ * @param {HTMLElement | null} activeEl
+ */
_bindEventOnRendered(activeEl) {
const { autoHeader } = this.config;
@@ -345,7 +373,10 @@ export function Render(Base) {
const hasH1 = main.querySelector('h1');
if (!hasH1) {
- const h1HTML = this.compiler.header(activeEl.innerText, 1);
+ const h1HTML = /** @type {Compiler} */ (this.compiler).header(
+ activeEl.innerText,
+ 1,
+ );
const h1Node = dom.create('div', h1HTML).firstElementChild;
if (h1Node) {
@@ -360,7 +391,7 @@ export function Render(Base) {
return;
}
- const html = this.compiler.compile(text);
+ const html = /** @type {Compiler} */ (this.compiler).compile(text);
['.app-nav', '.app-nav-merged'].forEach(selector => {
dom.setHTML(selector, html);
@@ -400,11 +431,12 @@ export function Render(Base) {
} else {
prerenderEmbed(
{
- compiler: this.compiler,
+ compiler: /** @type {Compiler} */ (this.compiler),
raw: result,
+ fetch: undefined,
},
tokens => {
- html = this.compiler.compile(tokens);
+ html = /** @type {Compiler} */ (this.compiler).compile(tokens);
callback();
},
);
@@ -417,20 +449,18 @@ export function Render(Base) {
const rootElm = document.documentElement;
const coverBg = getComputedStyle(rootElm).getPropertyValue('--cover-bg');
- dom.toggleClass(
- dom.getNode('main'),
- coverOnly ? 'add' : 'remove',
- 'hidden',
- );
+ dom.getNode('main').classList[coverOnly ? 'add' : 'remove']('hidden');
if (!text) {
- dom.toggleClass(el, 'remove', 'show');
+ el.classList.remove('show');
return;
}
- dom.toggleClass(el, 'add', 'show');
+ el.classList.add('show');
- let html = this.coverIsHTML ? text : this.compiler.cover(text);
+ let html = this.coverIsHTML
+ ? text
+ : /** @type {Compiler} */ (this.compiler).cover(text);
if (!coverBg) {
const mdBgMatch = html
@@ -585,13 +615,17 @@ export function Render(Base) {
}
if (config.themeColor) {
- dom.$.head.appendChild(
- dom.create('div', tpl.theme(config.themeColor)).firstElementChild,
- );
+ const themeNode = dom.create(
+ 'div',
+ tpl.theme(config.themeColor),
+ ).firstElementChild;
+ if (themeNode) {
+ dom.$.head.appendChild(themeNode);
+ }
}
this._updateRender();
- dom.toggleClass(dom.body, 'ready');
+ dom.body.classList.add('ready');
}
};
}
diff --git a/src/core/render/tpl.js b/src/core/render/tpl.js
index eb208dde03..88b732a78f 100644
--- a/src/core/render/tpl.js
+++ b/src/core/render/tpl.js
@@ -43,7 +43,7 @@ export function main(config) {
? ''
: `