diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6eabff0b5f..9fdd5e0277 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 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/docs/configuration.md b/docs/configuration.md index 3901501898..d9cdfd05b1 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -456,7 +456,7 @@ window.$docsify = { ## name -- Type: `String` +- Type: `Boolean | String` Website name as it appears in the sidebar. @@ -474,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` @@ -652,6 +668,10 @@ window.$docsify = { }; ``` +## plugins + +See [Plugins](./plugins.md). + ## relativePath - Type: `Boolean` @@ -706,6 +726,8 @@ window.$docsify = { }; ``` +If undefined or empty, no GitHub corner will be displayed. + ## requestHeaders - Type: `Object` @@ -887,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', }; ``` @@ -912,6 +934,13 @@ window.$docsify = { }; ``` +```js +// Use default +window.$docsify = { + skipLink: true, // "Skip to main content" +}; +``` + ## subMaxLevel - Type: `Number` 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/package-lock.json b/package-lock.json index 35516aa18e..4e287cda5e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,9 @@ "@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", @@ -53,6 +55,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" }, @@ -4576,6 +4579,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", @@ -4670,6 +4680,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", @@ -18207,11 +18224,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 48c6cf2526..87750ec766 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": "^16.0.0", "medium-zoom": "^1.1.0", "opencollective-postinstall": "^2.0.2", "prismjs": "^1.29.0", @@ -51,10 +55,11 @@ "@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", @@ -69,7 +74,6 @@ "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", @@ -78,6 +82,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<DocsifyConfig>} 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 8f165518aa..4fd85c2e29 100644 --- a/src/core/config.js +++ b/src/core/config.js @@ -1,82 +1,142 @@ 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( - { - alias: {}, - auto2top: false, - autoHeader: false, - basePath: '', - catchPluginErrors: true, - cornerExternalLinkTarget: '_blank', - coverpage: '', - el: '#app', - executeScript: null, - ext: '.md', - externalLinkRel: 'noopener', - externalLinkTarget: '_blank', - fallbackLanguages: null, - fallbackDefaultLanguage: '', - formatUpdated: '', - hideSidebar: false, - homepage: 'README.md', - keyBindings: {}, - loadNavbar: null, - loadSidebar: null, - logo: false, - markdown: null, - maxLevel: 6, - mergeNavbar: false, - name: '', - nameLink: window.location.pathname, - nativeEmoji: false, - noCompileLinks: [], - noEmoji: false, - notFoundPage: false, - onlyCover: false, - plugins: [], - relativePath: false, - repo: '', - requestHeaders: {}, - routerMode: 'hash', - routes: {}, - skipLink: 'Skip to main content', - 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. - <style> - :root { - --theme-color: deeppink; - } - </style> - `).trim(), - ); - } - }, - }, +const defaultDocsifyConfig = () => ({ + alias: /** @type {Record<string, string>} */ ({}), + 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<string, TODO> | 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<string, string>} */ ( + false + ), + onlyCover: false, + plugins: /** @type {Plugin[]} */ ([]), + relativePath: false, + repo: /** @type {string} */ (''), + requestHeaders: /** @type {Record<string, string>} */ ({}), + routerMode: /** @type {'hash' | 'history'} */ 'hash', + routes: /** @type {Record<string, string | RouteHandler>} */ ({}), + skipLink: /** @type {false | string | Record<string, string>} */ ( + 'Skip to main content' + ), + subMaxLevel: 0, + vueComponents: /** @type {Record<string, TODO>} */ ({}), + vueGlobalOptions: /** @type {Record<string, TODO>} */ ({}), + vueMounts: /** @type {Record<string, TODO>} */ ({}), + + // 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. + <style> + :root { + --theme-color: deeppink; + } + </style> + `).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. + <style> + :root { + --scroll-padding-top: 10px; + } + </style> + `).trim(), + ); + } + }, + __topMargin: 0, +}); + +/** @typedef {ReturnType<typeof defaultDocsifyConfig>} DocsifyConfig */ + +/** + * @param {import('./Docsify.js').Docsify} vm + * @param {Partial<DocsifyConfig>} config + * @returns {DocsifyConfig} + */ +export default function (vm, config = {}) { + if (window.$docsify) { + // eslint-disable-next-line no-console + console.warn( + 'DEPRECATION: The global $docsify config variable is deprecated. See the latest getting started docs. https://docsify.js.org/#/quickstart', + ); + } + + 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 @@ -86,8 +146,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(); @@ -107,10 +169,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; } } @@ -128,15 +200,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 12fa3c78c7..ed51481652 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,7 +53,7 @@ export function Events(Base) { const coverElm = dom.find('section.cover'); if (!coverElm) { - dom.toggleClass(dom.body, 'add', 'sticky'); + dom.body.classList.add('sticky'); return; } @@ -61,7 +61,7 @@ export function Events(Base) { const isIntersecting = entries[0].isIntersecting; const op = isIntersecting ? 'remove' : 'add'; - dom.toggleClass(dom.body, op, 'sticky'); + dom.body.classList[op]('sticky'); }); observer.observe(coverElm); @@ -165,32 +165,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 +201,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 +246,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 +272,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(); }); @@ -366,7 +373,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 +414,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 +443,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 +454,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 +497,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 +530,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 +542,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; @@ -549,7 +561,7 @@ export function Events(Base) { // Set aria-expanded attribute ariaElms.forEach(toggleElm => { const expanded = force ?? sidebarElm.classList.contains('show'); - toggleElm.setAttribute('aria-expanded', expanded); + toggleElm.setAttribute('aria-expanded', String(expanded)); toggleElm.setAttribute( 'aria-label', expanded ? 'Hide primary navigation' : 'Show primary navigation', @@ -570,8 +582,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) { @@ -604,6 +616,9 @@ export function Events(Base) { } // Browsers w/o native scrollend event support (Safari) else { + /** @type {any} */ + let scrollTimer; + const callback = () => { clearTimeout(scrollTimer); @@ -613,8 +628,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 eb625fb02d..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,6 +238,11 @@ export function Fetch(Base) { } } + /** + * @param {any} path + * @param {any} qs + * @param {any} [cb] + */ _fetchFallbackPage(path, qs, cb = noop) { const { requestHeaders, @@ -227,12 +267,17 @@ export function Fetch(Base) { 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), ); @@ -245,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; @@ -255,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; @@ -266,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<DocsifyConfig> + | (Partial<DocsifyConfig> & ((config: Docsify) => Partial<DocsifyConfig>)); + +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<string, Function[]>} */ _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 5eb32fbce9..1d5dd54d66 100644 --- a/src/core/render/compiler.js +++ b/src/core/render/compiler.js @@ -96,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); @@ -179,6 +179,7 @@ export class Compiler { origin.listitem = taskListItemCompiler({ renderer }); origin.tablecell = tableCellCompiler({ renderer }); + // @ts-expect-error renderer.origin = origin; return renderer; @@ -265,6 +266,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/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/embed.js b/src/core/render/embed.js index 95055e9fa8..73fe7de19f 100644 --- a/src/core/render/embed.js +++ b/src/core/render/embed.js @@ -61,10 +61,9 @@ 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) { @@ -91,10 +90,10 @@ function walkFetchEmbed({ embedTokens, compile, fetch }, cb) { text: /* html */ `<div class="mermaid">\n${text}\n</div>`, }, ]; - embedToken.links = {}; + /** @type {any} */ (embedToken).links = {}; } else { embedToken = [{ type: 'html', text }]; - embedToken.links = {}; + /** @type {any} */ (embedToken).links = {}; } } 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/utils.js b/src/core/render/utils.js index 66796de1e0..f402118043 100644 --- a/src/core/render/utils.js +++ b/src/core/render/utils.js @@ -16,9 +16,10 @@ * * @param {string} str The string to parse. * - * @return {{str: string, config: object}} The original string formatted, and parsed object, { str, config }. + * @return {{str: string, config: Record<string, string | string[]>}} The original string formatted, and parsed object, { str, config }. */ export function getAndRemoveConfig(str = '') { + /** @type {Record<string, string | string[]>} */ const config = {}; if (str) { @@ -36,7 +37,8 @@ export function getAndRemoveConfig(str = '') { if (!Array.isArray(config[key]) && value !== config[key]) { config[key] = [config[key]]; } - config[key].includes(value) || config[key].push(value); + config[key].includes(value) || + /** @type {string[]} */ (config[key]).push(value); } else { config[key] = value; } @@ -86,5 +88,9 @@ export function getAndRemoveDocsifyIgnoreConfig(content = '') { ignoreAllSubs = true; } - return { content, ignoreAllSubs, ignoreSubHeading }; + return /** @type {{content: string, ignoreAllSubs: boolean, ignoreSubHeading: boolean}} */ ({ + content, + ignoreAllSubs, + ignoreSubHeading, + }); } diff --git a/src/core/router/history/base.js b/src/core/router/history/base.js index 352aa0f03c..22f2276f49 100644 --- a/src/core/router/history/base.js +++ b/src/core/router/history/base.js @@ -51,7 +51,11 @@ export class History { return this.config.basePath; } - getFile(path = this.getCurrentPath(), isRelative) { + /** + * @param {string} path + * @param {boolean} isRelative + */ + getFile(path = this.getCurrentPath(), isRelative = false) { const { config } = this; const base = this.getBasePath(); const ext = typeof config.ext === 'string' ? config.ext : '.md'; @@ -72,11 +76,24 @@ export class History { cb(); } - getCurrentPath() {} + /** + * @return {string} + */ + getCurrentPath() { + throw new Error('Subclass should implement'); + } - normalize() {} + normalize() { + throw new Error('Subclass should implement'); + } - parse() {} + /** + * @param {string} path + * @return {import('../index.js').Route} { path, query, file, response } + */ + parse(path) { + throw new Error('Subclass should implement'); + } toURL(path, params, currentRoute) { const local = currentRoute && path[0] === '#'; diff --git a/src/core/router/history/hash.js b/src/core/router/history/hash.js index 4285ea4c3d..8bd091d53d 100644 --- a/src/core/router/history/hash.js +++ b/src/core/router/history/hash.js @@ -34,7 +34,7 @@ export class HashHistory extends History { return index === -1 ? '' : href.slice(index + 1); } - /** @param {((params: {source: TODO}) => void)} [cb] */ + /** @param {(params: {source: any, event?: any}) => void} [cb] */ onchange(cb = noop) { // The hashchange event does not tell us if it originated from // a clicked link or by moving back/forward in the history; @@ -80,8 +80,8 @@ export class HashHistory extends History { /** * Parse the url - * @param {string} [path=location.href] URL to be parsed - * @return {object} { path, query } + * @param {string} path URL to be parsed + * @return {import('../index.js').Route} { path, query, file, response } */ parse(path = location.href) { let query = ''; diff --git a/src/core/router/history/html5.js b/src/core/router/history/html5.js index 49c1aac5eb..9903e46986 100644 --- a/src/core/router/history/html5.js +++ b/src/core/router/history/html5.js @@ -17,6 +17,7 @@ export class HTML5History extends History { return (path || '/') + window.location.search + window.location.hash; } + /** @param {(params: any) => void} [cb] */ onchange(cb = noop) { on('click', e => { const el = e.target.tagName === 'A' ? e.target : e.target.parentNode; @@ -37,7 +38,7 @@ export class HTML5History extends History { /** * Parse the url * @param {string} [path=location.href] URL to be parsed - * @return {object} { path, query } + * @return {import('../index.js').Route} { path, query, file, response } */ parse(path = location.href) { let query = ''; diff --git a/src/core/router/index.js b/src/core/router/index.js index cb4f8674cd..f7b985314c 100644 --- a/src/core/router/index.js +++ b/src/core/router/index.js @@ -5,11 +5,14 @@ import { HTML5History } from './history/html5.js'; /** * @typedef {{ - * path?: string + * path: string + * query: Record<string, string>; + * file: string; + * response: {} * }} Route */ -/** @type {Route} */ +/** @type {Partial<Route>} */ let lastRoute = {}; /** @typedef {import('../Docsify.js').Constructor} Constructor */ @@ -20,12 +23,13 @@ let lastRoute = {}; */ export function Router(Base) { return class Router extends Base { + /** @type {Partial<Route>} */ route = {}; updateRender() { - this.router.normalize(); - this.route = this.router.parse(); - dom.body.setAttribute('data-page', this.route.file); + this.router?.normalize(); + this.route = this.router?.parse() ?? {}; + dom.body.setAttribute('data-page', this.route.file ?? ''); } initRouter() { diff --git a/src/core/router/util.js b/src/core/router/util.js index f795063bcf..54118b82df 100644 --- a/src/core/router/util.js +++ b/src/core/router/util.js @@ -3,7 +3,12 @@ import { cached } from '../util/core.js'; const decode = decodeURIComponent; const encode = encodeURIComponent; +/** + * @param {string} query + * @return {Record<string, string>} + */ export function parseQuery(query) { + /** @type {Record<string, string>} */ const res = {}; query = query.trim().replace(/^(\?|#|&)/, ''); diff --git a/src/core/util/ajax.js b/src/core/util/ajax.js index 1a0edd107e..37afe1401c 100644 --- a/src/core/util/ajax.js +++ b/src/core/util/ajax.js @@ -20,6 +20,11 @@ const cache = {}; // TODO update to using fetch() + Streams API instead of XMLHttpRequest. See an // example of download progress calculation using fetch() here: // https://streams.spec.whatwg.org/demos/ +/** + * @param {string} url + * @param {boolean} [hasBar] + * @param {Record<string, string>} [headers] + */ export function get(url, hasBar = false, headers = {}) { const xhr = new XMLHttpRequest(); const cached = cache[url]; diff --git a/src/core/util/core.js b/src/core/util/core.js index fc61809422..04f3fbdd12 100644 --- a/src/core/util/core.js +++ b/src/core/util/core.js @@ -48,10 +48,11 @@ export function isFn(obj) { /** * Check if url is external - * @param {String} string url + * @param {String} url url * @returns {Boolean} True if the passed-in url is external */ export function isExternal(url) { + /** @type {any} */ const match = url.match( /^([^:/?#]+:)?(?:\/{2,}([^/?#]*))?([^?#]+)?(\?[^#]*)?(#.*)?/, ); diff --git a/src/core/util/dom.js b/src/core/util/dom.js index 3bd794ee5b..84f1bb5299 100644 --- a/src/core/util/dom.js +++ b/src/core/util/dom.js @@ -1,12 +1,13 @@ import { isFn } from '../util/core.js'; +/** @type {Record<string, Element>} */ const cacheNode = {}; /** * Get Node * @param {String|Element} el A DOM element * @param {Boolean} noCache Flag to use or not use the cache - * @return {HTMLElement | SVGElement} The found node element + * @return {Element} The found node element */ export function getNode(el, noCache = false) { if (typeof el === 'string') { @@ -26,6 +27,11 @@ export function getNode(el, noCache = false) { * @param {*} content the content to be rendered as HTML * @param {*} replace To replace the content (true) or insert instead (false) , default is false */ +/** + * @param {string|Element} el + * @param {string} content + * @param {boolean} [replace] + */ export function setHTML(el, content, replace) { const node = getNode(el); if (node) { @@ -40,54 +46,87 @@ export const body = $.body; export const head = $.head; /** - * Find elements - * @param {String|Element} el The root element where to perform the search from - * @param {Element} node The query + * Find the first matching element + * @param {string|Element} el The root element on which to perform the query + * from, or a query string to query from `document`. + * @param {string} [query] The query string to use on `el` if `el` is an + * element. * @returns {Element} The found DOM element * @example * find('nav') => document.querySelector('nav') * find(nav, 'a') => nav.querySelector('a') */ -export function find(el, node) { - return node ? el.querySelector(node) : $.querySelector(el); +export function find(el, query = ':is()') { + return /** @type {Element} */ ( + typeof el !== 'string' ? el.querySelector(query) : $.querySelector(el) + ); } /** - * Find all elements - * @param {String|Element} el The root element where to perform the search from - * @param {Element} node The query + * Find all matching elements + * @param {string|Element} el The root element on which to perform the query + * from, or a query string to query from `document`. + * @param {string} [query] The query string to use on `el` if `el` is an + * element. * @returns {Array<Element>} An array of DOM elements * @example * findAll('a') => Array.from(document.querySelectorAll('a')) * findAll(nav, 'a') => Array.from(nav.querySelectorAll('a')) */ -export function findAll(el, node) { - return Array.from(node ? el.querySelectorAll(node) : $.querySelectorAll(el)); +export function findAll(el, query = ':is()') { + return Array.from( + typeof el !== 'string' + ? el.querySelectorAll(query) + : $.querySelectorAll(el), + ); } +/** + * @param {string} node + * @param {string} [tpl] + * @returns {HTMLElement} + */ export function create(node, tpl) { - node = $.createElement(node); + const element = $.createElement(node); if (tpl) { - node.innerHTML = tpl; + element.innerHTML = tpl; } - return node; + return element; } +/** + * @param {Element} target + * @param {Element} el + */ export function appendTo(target, el) { return target.appendChild(el); } +/** + * @param {Element} target + * @param {Element} el + */ export function before(target, el) { return target.insertBefore(el, target.children[0]); } +/** + * @param {any} el + * @param {any} type + * @param {any} [handler] + */ export function on(el, type, handler) { isFn(type) ? window.addEventListener(el, type) : el.addEventListener(type, handler); } +/** + * @param {any} el + * @param {any} type + * @param {any} [handler] + */ export function off(el, type, handler) { isFn(type) ? window.removeEventListener(el, type) @@ -95,27 +134,16 @@ export function off(el, type, handler) { } /** - * Toggle class - * @param {Element|null} el The element that needs the class to be toggled - * @param {string} type The type of action to be performed on the classList (toggle by default) - * @param {String} val Name of the class to be toggled - * @void - * @example - * toggleClass(el, 'active') => el.classList.toggle('active') - * toggleClass(el, 'add', 'active') => el.classList.add('active') + * @param {string} content */ -export function toggleClass(el, type, val) { - el && el.classList[val ? type : 'toggle'](val || type); -} - export function style(content) { - appendTo(head, create('style', content)); + appendTo(head, /** @type {Element} */ (create('style', content))); } /** * Fork https://github.com/bendrucker/document-ready/blob/master/index.js - * @param {Function} callback The callbacack to be called when the page is loaded - * @returns {Number|void} If the page is already loaded returns the result of the setTimeout callback, + * @param {(event: Event) => void} callback The callbacack to be called when the page is loaded + * @returns {number|void} If the page is already loaded returns the result of the setTimeout callback, * otherwise it only attaches the callback to the DOMContentLoaded event */ export function documentReady(callback, doc = document) { diff --git a/src/core/util/prism.js b/src/core/util/prism.js index 86ae9ab585..3f381701ee 100644 --- a/src/core/util/prism.js +++ b/src/core/util/prism.js @@ -1,4 +1,4 @@ -import Prism from 'prismjs'; +import * as Prism from 'prismjs'; /** * * The dependencies map which syncs from @@ -292,7 +292,7 @@ const prettryPrint = (depTree, level) => { let cur = `${' '.repeat(level * 3)} ${depTree.cur} ${depTree.loaded ? '(+)' : '(-)'}`; if (depTree.dependencies.length) { depTree.dependencies.forEach(dep => { - cur += prettryPrint(dep, level + 1, cur); + cur += prettryPrint(dep, level + 1); }); } return '\n' + cur; diff --git a/src/core/virtual-routes/index.js b/src/core/virtual-routes/index.js index faea43b9c0..a164d57a39 100644 --- a/src/core/virtual-routes/index.js +++ b/src/core/virtual-routes/index.js @@ -4,7 +4,7 @@ import { createNextFunction } from './next.js'; /** @typedef {import('../Docsify.js').Constructor} Constructor */ /** @typedef {Record<string, string | VirtualRouteHandler>} VirtualRoutesMap */ -/** @typedef {(route: string, match: RegExpMatchArray | null) => string | void | Promise<string | void> } VirtualRouteHandler */ +/** @typedef {(route: string, match: RegExpMatchArray | null, next?: (content: string | void | Promise<string | void>) => void) => string | void | Promise<string | void> } VirtualRouteHandler */ /** * Allows users/plugins to introduce dynamically created content into their docsify @@ -25,7 +25,7 @@ import { createNextFunction } from './next.js'; * } * ``` * - * @template {!Constructor} T + * @template {Constructor} T * @param {T} Base - The class to extend */ export function VirtualRoutes(Base) { @@ -41,12 +41,13 @@ export function VirtualRoutes(Base) { /** * Attempts to match the given path with a virtual route. * @param {string} path the path of the route to match - * @returns {Promise<string | null>} resolves to string if route was matched, otherwise null + * @returns {PromiseLike<string | null>} resolves to string if route was matched, otherwise null */ matchVirtualRoute(path) { const virtualRoutes = this.routes(); const virtualRoutePaths = Object.keys(virtualRoutes); + /** @type {(value: string | null) => any} */ let done = () => null; /** @@ -101,7 +102,9 @@ export function VirtualRoutes(Base) { } return { + // @ts-expect-error types are screwed here then(cb) { + // @ts-expect-error types are screwed here done = cb; asyncMatchNextRoute(); }, diff --git a/src/core/virtual-routes/next.js b/src/core/virtual-routes/next.js index ec53ece46f..21d5c57b82 100644 --- a/src/core/virtual-routes/next.js +++ b/src/core/virtual-routes/next.js @@ -9,7 +9,7 @@ */ export function createNextFunction() { /** @type {CB} */ - let storedCb = () => null; + let storedCb = () => {}; function next(value) { storedCb(value); diff --git a/src/plugins/disqus.js b/src/plugins/disqus.js index dd19d5c6b4..b6b38681b2 100644 --- a/src/plugins/disqus.js +++ b/src/plugins/disqus.js @@ -3,6 +3,8 @@ if (fixedPath !== location.href) { location.href = fixedPath; } +const window = /** @type {any} */ (globalThis); + function install(hook, vm) { const dom = Docsify.dom; const disqus = vm.config.disqus; @@ -11,11 +13,11 @@ function install(hook, vm) { } hook.init(_ => { - const script = dom.create('script'); + const script = /** @type {HTMLScriptElement} */ (dom.create('script')); script.async = true; script.src = `https://${disqus}.disqus.com/embed.js`; - script.setAttribute('data-timestamp', Number(new Date())); + script.setAttribute('data-timestamp', String(Number(new Date()))); dom.appendTo(dom.body, script); }); @@ -48,4 +50,6 @@ function install(hook, vm) { } window.$docsify = window.$docsify || {}; -$docsify.plugins = [install, ...($docsify.plugins || [])]; +window.$docsify.plugins = [install, ...(window.$docsify.plugins || [])]; + +export {}; diff --git a/src/plugins/emoji.js b/src/plugins/emoji.js index 7d71a9980d..10f9fa0eb7 100644 --- a/src/plugins/emoji.js +++ b/src/plugins/emoji.js @@ -1,5 +1,7 @@ import emojiData from '../core/render/emoji-data.js'; +const window = /** @type {any} */ (globalThis); + // Deprecation notice if (window && window.console) { // eslint-disable-next-line no-console diff --git a/src/plugins/external-script.js b/src/plugins/external-script.js index fcdeca7069..992790fc8a 100644 --- a/src/plugins/external-script.js +++ b/src/plugins/external-script.js @@ -1,6 +1,10 @@ +const window = /** @type {any} */ (globalThis); + function handleExternalScript() { const container = Docsify.dom.getNode('#main'); - const scripts = Docsify.dom.findAll(container, 'script'); + const scripts = /** @type {HTMLScriptElement[]} */ ( + Docsify.dom.findAll(container, 'script') + ); for (const script of scripts) { if (script.src) { @@ -10,8 +14,8 @@ function handleExternalScript() { newScript[attribute.name] = attribute.value; }); - script.parentNode.insertBefore(newScript, script); - script.parentNode.removeChild(script); + script.before(newScript); + script.remove(); } } } @@ -21,4 +25,6 @@ const install = function (hook) { }; window.$docsify = window.$docsify || {}; -$docsify.plugins = [install, ...($docsify.plugins || [])]; +window.$docsify.plugins = [install, ...(window.$docsify.plugins || [])]; + +export {}; diff --git a/src/plugins/front-matter/index.js b/src/plugins/front-matter/index.js index ffe7986fcb..1764698117 100644 --- a/src/plugins/front-matter/index.js +++ b/src/plugins/front-matter/index.js @@ -19,4 +19,4 @@ const install = function (hook, vm) { }; window.$docsify = window.$docsify || {}; -$docsify.plugins = [install, ...($docsify.plugins || [])]; +window.$docsify.plugins = [install, ...(window.$docsify?.plugins || [])]; diff --git a/src/plugins/front-matter/yaml.js b/src/plugins/front-matter/yaml.js index 3ac8523b57..83c33d657d 100644 --- a/src/plugins/front-matter/yaml.js +++ b/src/plugins/front-matter/yaml.js @@ -1,3 +1,4 @@ +// @ts-nocheck /** * Forked from https://github.com/egoist/docute/blob/master/src/utils/yaml.js */ @@ -54,7 +55,6 @@ var errors = [], /** * @class A block of lines of a given level. * @param {int} lvl The block's level. - * @private */ function Block(lvl) { return { diff --git a/src/plugins/ga.js b/src/plugins/ga.js index c1c130c110..4716aebdab 100644 --- a/src/plugins/ga.js +++ b/src/plugins/ga.js @@ -6,6 +6,8 @@ function appendScript() { document.body.appendChild(script); } +const window = /** @type {any} */ (globalThis); + function init(id) { appendScript(); window.ga = @@ -20,15 +22,14 @@ function init(id) { function collect() { if (!window.ga) { - init($docsify.ga); + init(window.$docsify.ga); } - window.ga('set', 'page', location.hash); window.ga('send', 'pageview'); } const install = function (hook) { - if (!$docsify.ga) { + if (!window.$docsify.ga) { // eslint-disable-next-line no-console console.error('[Docsify] ga is required.'); return; @@ -38,4 +39,6 @@ const install = function (hook) { }; window.$docsify = window.$docsify || {}; -$docsify.plugins = [install, ...($docsify.plugins || [])]; +window.$docsify.plugins = [install, ...(window.$docsify?.plugins || [])]; + +export {}; diff --git a/src/plugins/gitalk.js b/src/plugins/gitalk.js index 627dcef3b0..abf357442c 100644 --- a/src/plugins/gitalk.js +++ b/src/plugins/gitalk.js @@ -1,4 +1,5 @@ -/* global gitalk */ +const window = /** @type {any} */ (globalThis); + function install(hook) { const dom = Docsify.dom; @@ -11,14 +12,18 @@ function install(hook) { }); hook.doneEach(_ => { - const el = document.getElementById('gitalk-container'); + const el = /** @type {HTMLElement} */ ( + document.getElementById('gitalk-container') + ); while (el.hasChildNodes()) { - el.removeChild(el.firstChild); + el.removeChild(/** @type {Node} */ (el.firstChild)); } - gitalk.render('gitalk-container'); + window.gitalk.render('gitalk-container'); }); } window.$docsify = window.$docsify || {}; -$docsify.plugins = [install, ...($docsify.plugins || [])]; +window.$docsify.plugins = [install, ...(window.$docsify.plugins || [])]; + +export {}; diff --git a/src/plugins/gtag.js b/src/plugins/gtag.js index aae78b6fa9..2a4298af11 100644 --- a/src/plugins/gtag.js +++ b/src/plugins/gtag.js @@ -7,6 +7,8 @@ function appendScript(id) { document.body.appendChild(script); } +const window = /** @type {any} */ (globalThis); + // global site tag instance initialized function initGlobalSiteTag(id) { appendScript(id); @@ -46,7 +48,7 @@ function init(ids) { function collect() { if (!window.gtag) { - init($docsify.gtag); + init(window.$docsify.gtag); } // usage: https://developers.google.com/analytics/devguides/collection/gtagjs/pages @@ -58,7 +60,7 @@ function collect() { } const install = function (hook) { - if (!$docsify.gtag) { + if (!window.$docsify.gtag) { // eslint-disable-next-line no-console console.error('[Docsify] gtag is required.'); return; @@ -68,4 +70,6 @@ const install = function (hook) { }; window.$docsify = window.$docsify || {}; -$docsify.plugins = [install, ...($docsify.plugins || [])]; +window.$docsify.plugins = [install, ...(window.$docsify.plugins || [])]; + +export {}; diff --git a/src/plugins/matomo.js b/src/plugins/matomo.js index e4ed911ce5..478e31dc5d 100644 --- a/src/plugins/matomo.js +++ b/src/plugins/matomo.js @@ -5,6 +5,8 @@ function appendScript(options) { document.body.appendChild(script); } +const window = /** @type {any} */ (globalThis); + function init(options) { window._paq = window._paq || []; window._paq.push(['trackPageView']); @@ -18,16 +20,15 @@ function init(options) { function collect() { if (!window._paq) { - init($docsify.matomo); + init(window.$docsify.matomo); } - window._paq.push(['setCustomUrl', window.location.hash.substr(1)]); window._paq.push(['setDocumentTitle', document.title]); window._paq.push(['trackPageView']); } const install = function (hook) { - if (!$docsify.matomo) { + if (!window.$docsify.matomo) { // eslint-disable-next-line no-console console.error('[Docsify] matomo is required.'); return; @@ -37,4 +38,6 @@ const install = function (hook) { }; window.$docsify = window.$docsify || {}; -$docsify.plugins = [install, ...($docsify.plugins || [])]; +window.$docsify.plugins = [install, ...(window.$docsify.plugins || [])]; + +export {}; diff --git a/src/plugins/search/component.js b/src/plugins/search/component.js index a382f8e7c8..8d447c9cb1 100644 --- a/src/plugins/search/component.js +++ b/src/plugins/search/component.js @@ -21,8 +21,10 @@ function tpl(vm, defaultValue = '') { `; const sidebarElm = Docsify.dom.find('.sidebar'); const searchElm = Docsify.dom.create('section', html); - const insertElm = sidebarElm.querySelector( - `:scope ${insertAfter || insertBefore || '> :first-child'}`, + const insertElm = /** @type {HTMLElement} */ ( + sidebarElm.querySelector( + `:scope ${insertAfter || insertBefore || '> :first-child'}`, + ) ); searchElm.classList.add('search'); @@ -69,7 +71,9 @@ function doSearch(value) { function bindEvents() { const $search = Docsify.dom.find('.search'); - const $input = Docsify.dom.find($search, 'input'); + const $input = /** @type {HTMLInputElement} */ ( + Docsify.dom.find($search, 'input') + ); const $clear = Docsify.dom.find($search, '.clear-button'); let timeId; @@ -90,7 +94,10 @@ function bindEvents() { ); Docsify.dom.on($input, 'input', e => { clearTimeout(timeId); - timeId = setTimeout(_ => doSearch(e.target.value.trim()), 100); + timeId = setTimeout( + _ => doSearch(/** @type {HTMLInputElement} */ (e.target).value.trim()), + 100, + ); }); Docsify.dom.on($clear, 'click', e => { $input.value = ''; @@ -99,7 +106,9 @@ function bindEvents() { } function updatePlaceholder(text, path) { - const $input = Docsify.dom.getNode('.search input[type="search"]'); + const $input = /** @type {HTMLInputElement | null} */ ( + Docsify.dom.getNode('.search input[type="search"]') + ); if (!$input) { return; diff --git a/src/plugins/search/index.js b/src/plugins/search/index.js index 157fa43c44..f6ef34df01 100644 --- a/src/plugins/search/index.js +++ b/src/plugins/search/index.js @@ -4,6 +4,19 @@ import { } from './component.js'; import { init as initSearch } from './search.js'; +/** + * @type {{ + * placeholder: string; + * noData: string; + * paths: string[] | 'auto'; + * depth: number; + * maxAge: number; + * namespace?: string; + * pathNamespaces?: RegExp | string[]; + * keyBindings: string[]; + * insertAfter?: string; + * insertBefore?: string; + * }} */ const CONFIG = { placeholder: 'Type to search', noData: 'No Results!', @@ -45,9 +58,14 @@ const install = function (hook, vm) { bindings: CONFIG.keyBindings, callback(e) { const sidebarElm = document.querySelector('.sidebar'); - const sidebarToggleElm = document.querySelector('.sidebar-toggle'); - const searchElm = sidebarElm?.querySelector('input[type="search"]'); - const isSidebarHidden = sidebarElm?.getBoundingClientRect().x < 0; + const sidebarToggleElm = /** @type {HTMLElement} */ ( + document.querySelector('.sidebar-toggle') + ); + const searchElm = /** @type {HTMLInputElement | null} */ ( + sidebarElm?.querySelector('input[type="search"]') + ); + const isSidebarHidden = + (sidebarElm?.getBoundingClientRect().x ?? 0) < 0; isSidebarHidden && sidebarToggleElm?.click(); @@ -67,4 +85,4 @@ const install = function (hook, vm) { }; window.$docsify = window.$docsify || {}; -$docsify.plugins = [install, ...($docsify.plugins || [])]; +window.$docsify.plugins = [install, ...(window.$docsify.plugins || [])]; diff --git a/src/plugins/search/markdown-to-txt.js b/src/plugins/search/markdown-to-txt.js index c98459f2f8..4068c071b9 100644 --- a/src/plugins/search/markdown-to-txt.js +++ b/src/plugins/search/markdown-to-txt.js @@ -79,7 +79,9 @@ const markdownToTxtRenderer = { listitem(item) { let itemBody = ''; if (item.task) { - const checkbox = this.checkbox?.({ checked: !!item.checked }); + const checkbox = this.checkbox?.({ + checked: !!item.checked, + }); if (item.loose) { if (item.tokens.length > 0 && item.tokens[0].type === 'paragraph') { item.tokens[0].text = checkbox + ' ' + item.tokens[0].text; @@ -108,7 +110,11 @@ const markdownToTxtRenderer = { return `${itemBody || ''}`; }, - checkbox() { + /** + * @param {{checked: boolean}} options + * @return {string} + */ + checkbox(options) { return ''; }, @@ -185,7 +191,10 @@ const markdownToTxtRenderer = { : token.text || ''; }, }; -const _marked = marked.setOptions({ renderer: markdownToTxtRenderer }); +const _marked = marked.setOptions({ + // @ts-expect-error missing properties + renderer: markdownToTxtRenderer, +}); export function markdownToTxt(markdown) { const unmarked = _marked.parse(markdown); diff --git a/src/plugins/search/search.js b/src/plugins/search/search.js index bb0dfb55d6..6e3b2e0704 100644 --- a/src/plugins/search/search.js +++ b/src/plugins/search/search.js @@ -18,17 +18,22 @@ async function saveData(maxAge, expireKey) { INDEXES = Object.values(INDEXES).flatMap(innerData => Object.values(innerData), ); - await db.search.bulkPut(INDEXES); - await db.expires.put({ key: expireKey, value: Date.now() + maxAge }); + await /** @type {any} */ (db).search.bulkPut(INDEXES); + await /** @type {any} */ (db).expires.put({ + key: expireKey, + value: Date.now() + maxAge, + }); } async function getData(key, isExpireKey = false) { if (isExpireKey) { - const item = await db.expires.get(key); + const item = await /** @type {any} */ (db).expires.get(key); return item ? item.value : 0; } - const item = await db.search.where({ indexKey: key }).toArray(); + const item = await /** @type {any} */ (db).search + .where({ indexKey: key }) + .toArray(); return item ? item : null; } @@ -67,8 +72,10 @@ function getAllPaths(router) { Docsify.dom .findAll('.sidebar-nav a:not(.section-link):not([data-nosearch])') .forEach(node => { - const href = node.href; - const originHref = node.getAttribute('href'); + const href = /** @type {HTMLAnchorElement} */ (node).href; + const originHref = /** @type {HTMLAnchorElement} */ (node).getAttribute( + 'href', + ); const path = router.parse(href).path; if ( @@ -103,7 +110,8 @@ function getListData(token) { export function genIndex(path, content = '', router, depth, indexKey) { const tokens = window.marked.lexer(content); const slugify = window.Docsify.slugify; - const index = []; + /** @type {Record<string, any>} */ + const index = {}; let slug; let title = ''; @@ -131,7 +139,7 @@ export function genIndex(path, content = '', router, depth, indexKey) { index[slug] = { slug, title: path !== '/' ? path.slice(1) : 'Home Page', - body: markdownToTxt(token.text || ''), + body: markdownToTxt(/** @type {any} */ (token).text || ''), path: path, indexKey: indexKey, }; @@ -144,14 +152,20 @@ export function genIndex(path, content = '', router, depth, indexKey) { if (!index[slug]) { index[slug] = { slug, title: '', body: '' }; } else if (index[slug].body) { + // @ts-expect-error token.text = getTableData(token); + // @ts-expect-error token.text = getListData(token); + // @ts-expect-error index[slug].body += '\n' + markdownToTxt(token.text || ''); } else { + // @ts-expect-error token.text = getTableData(token); + // @ts-expect-error token.text = getListData(token); + // @ts-expect-error index[slug].body = markdownToTxt(token.text || ''); } diff --git a/src/plugins/zoom-image.js b/src/plugins/zoom-image.js index 52e1ebef7e..6b4d0e8f5b 100644 --- a/src/plugins/zoom-image.js +++ b/src/plugins/zoom-image.js @@ -4,10 +4,12 @@ function install(hook) { let zoom; hook.doneEach(_ => { - let elms = Array.from( - document.querySelectorAll( - '.markdown-section img:not(.emoji):not([data-no-zoom])', - ), + let elms = /** @type {HTMLElement[]} */ ( + Array.from( + document.querySelectorAll( + '.markdown-section img:not(.emoji):not([data-no-zoom])', + ), + ) ); Docsify.dom.style( @@ -25,4 +27,4 @@ function install(hook) { } window.$docsify = window.$docsify || {}; -$docsify.plugins = [install, ...($docsify.plugins || [])]; +window.$docsify.plugins = [install, ...(window.$docsify.plugins || [])]; diff --git a/test/consume-types/README.md b/test/consume-types/README.md new file mode 100644 index 0000000000..2fd30ae1ba --- /dev/null +++ b/test/consume-types/README.md @@ -0,0 +1,32 @@ +# Vanilla ESM TypeScript Example + +This is an example of a project that imports Docsify via plain ESM in browser, +with type checking with TypeScript to prove that type definitions are working. + +This ensures that downstream projects that consume Docsify via ESM get +intellisense in their IDE (f.e. `Go To Definition` in VS Code will take the user +to Docsify source code). + +This example is not using a build tool, but relying on native ESM in the +browser, using an `importmap` script (`<script type="importmap">` in +`index.html`) to tell the browser to find dependencies in node_modules. + +## Commands + +### `npm run typecheck` + +Run type checking for the project. + +### `npm run serve` + +Serve the example at http://localhost:5500 to verify vanilla ESM usage works. + +## Notes + +- We use a service worker to fix Docsify dependencies that use non-standard + import paths (without file extensions). See `sw.js` for more details. +- We use `prism.js` in the `importmap` to map PrismJS from CommonJS to ESM. See + `prism.js` for more details. +- We use `await import()` instead of `import` statements when importing Docsify + to ensure we load PrismJS and create a global `Prism` variable before Docsify is + imported. See `example.js` for more details. diff --git a/test/consume-types/example.js b/test/consume-types/example.js new file mode 100644 index 0000000000..787dbcab60 --- /dev/null +++ b/test/consume-types/example.js @@ -0,0 +1,46 @@ +// This file demonstrates consuming Docsify as an ES Module with TypeScript types. +// Things to try: +// - Running 'Go to Definition' on `Docsify` should take you to the source JS +// file, not only the declaration file. +// - Import from any subpath, and the type definitions should be visible. + +// Import the service worker registration script. This is needed to re-map +// imports when using ES Modules in the browser, as some of Docsify's +// dependencies are not available as ES Modules by default (CommonJS) or have +// non-standard import paths like 'some-lib/some-subpath' without a file +// extension, or expecting to import `index.js` from a folder, f.e. +// 'some-lib/some-subpath.js' or 'some-lib/some-subpath/index.js'. +// FIXME Replace ancient non-ESM dependencies with modern libraries. +import './register-sw.js'; + +// FIXME hack: import prismjs first to ensure global Prism is set up, as +// Docsify's graph (the import to +// 'prismjs/components/prism-markup-templating.js') depends on Prism being +// global first. +await import('prismjs'); + +// Import Docsify *after* prismjs or there will be a runtime error. See the +// importmap, and prism.js for more details. +// +// NOTE: we have to use `await import` instead of `import` statements because +// Prism being global is not statically analyzable by the ES Module system so it +// will try to execute Docsify's graph before prism.js has finished executed. +// This is very odd, I didn't think this was possible. +const { Docsify } = await import('docsify'); + +const d = new Docsify({ + el: '#app', + name: 'Vanilla ESM TypeScript Example', + themeColor: 'deeppink', + hideSidebar: false, + + // @ts-expect-error invalid property to test that type checking works + blahblah: 123, +}); + +console.log(d); + +// @ts-expect-error global types not available to ESM +window.Docsify; + +export {}; diff --git a/test/consume-types/index.html b/test/consume-types/index.html new file mode 100644 index 0000000000..975d10bee3 --- /dev/null +++ b/test/consume-types/index.html @@ -0,0 +1,34 @@ +<head> + <meta charset="utf-8" /> + <meta + name="viewport" + content="width=device-width, initial-scale=1, viewport-fit=cover" + /> + + <link rel="stylesheet" href="/node_modules/docsify/dist/themes/core.css" /> + + <style></style> + + <script type="importmap"> + { + "imports": { + "docsify": "/node_modules/docsify/src/core/Docsify.js", + "docsify/": "/node_modules/docsify/", + "prismjs": "/prism.js", + "prismjs/": "/node_modules/prismjs/", + "marked": "/node_modules/marked/lib/marked.esm.js", + "marked/": "/node_modules/marked/", + "tinydate": "/node_modules/tinydate/dist/tinydate.mjs", + "common-tags": "/node_modules/common-tags/es/index.js" + } + } + </script> + + <script></script> + + <script src="./example.js" type="module"></script> +</head> + +<body> + <div id="app"></div> +</body> diff --git a/test/consume-types/package-lock.json b/test/consume-types/package-lock.json new file mode 100644 index 0000000000..01e333b6ec --- /dev/null +++ b/test/consume-types/package-lock.json @@ -0,0 +1,2276 @@ +{ + "name": "consume-types", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "docsify": "file:../../" + }, + "devDependencies": { + "five-server": "^0.4.5", + "typescript": "^5.9.3" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@html-validate/stylish": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@html-validate/stylish/-/stylish-4.3.0.tgz", + "integrity": "sha512-eUfvKpRJg5TvzSfTf2EovrQoTKjkRnPUOUnXVJ2cQ4GbC/bQw98oxN+DdSf+HxOBK00YOhsP52xWdJPV1o4n5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^4.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@sidvind/better-ajv-errors": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@sidvind/better-ajv-errors/-/better-ajv-errors-2.1.3.tgz", + "integrity": "sha512-lWuod/rh7Xz5uXiEGSfm2Sd5PG7K/6yJfoAZVqzsEswjPJhUz15R7Gn/o8RczA041QS15hBd/BCSeu9vwPArkA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@babel/code-frame": "^7.16.0", + "chalk": "^4.1.0" + }, + "engines": { + "node": ">= 16.14" + }, + "peerDependencies": { + "ajv": "4.11.8 - 8" + } + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz", + "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@yandeu/open-cjs": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@yandeu/open-cjs/-/open-cjs-0.0.3.tgz", + "integrity": "sha512-TbwJoKM8vybqT/8PjISQO+uS2Bjkn9kQm2Sd/eGmiacZe5DBO7teSejwq45TxjMSbFf64yP06nvjqefmpk+gdA==", + "dev": true, + "license": "MIT" + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "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/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "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/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/common-tags": { + "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==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dexie": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/dexie/-/dexie-4.2.1.tgz", + "integrity": "sha512-Ckej0NS6jxQ4Po3OrSQBFddayRhTCic2DoCAG5zacOfOVB9P2Q5Xc5uL/nVa7ZVs+HdMnvUPzLFCB/JwpB6Csg==", + "license": "Apache-2.0" + }, + "node_modules/docsify": { + "version": "5.0.0-rc.3", + "resolved": "file:../..", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "common-tags": "^1.8.0", + "dexie": "^4.0.11", + "marked": "^16.0.0", + "medium-zoom": "^1.1.0", + "opencollective-postinstall": "^2.0.2", + "prismjs": "^1.29.0", + "tinydate": "^1.3.0" + }, + "engines": { + "node": ">=20.11.0" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/five-server": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/five-server/-/five-server-0.4.5.tgz", + "integrity": "sha512-EVAuwMdXHuRsHXG/jUthQtLP6G0j7MbkgaU1AXJInkJUOuNVz0vEVGe87YgdrA+mZE1ZVHFUmdi+B78kRFNQqw==", + "dev": true, + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@yandeu/open-cjs": "^0.0.3", + "chokidar": "^4.0.3", + "cors": "^2.8.5", + "debug": "^4.3.1", + "express": "^5.1.0", + "html-validate": "^7.1.1", + "mime-types": "~2.1.24", + "node-html-parser": "~5.4.1", + "parseurl": "~1.3.3", + "selfsigned": "^2.0.0", + "ws": "^8.2.0" + }, + "bin": { + "five-server": "lib/bin.js", + "live-server": "lib/bin.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/yandeu" + } + }, + "node_modules/foreground-child": { + "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.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/html-validate": { + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/html-validate/-/html-validate-7.18.1.tgz", + "integrity": "sha512-K5jb0h/xAoeR8sJqyR0n/QaKL7rdT88sPCtN+Pvtyn5JUU+nidQe2gBB09WRzPTcQtPXBj4QxBUH5IA2tt8JQg==", + "dev": true, + "license": "MIT", + "workspaces": [ + "docs" + ], + "dependencies": { + "@babel/code-frame": "^7.10.0", + "@html-validate/stylish": "^4.0.1", + "@sidvind/better-ajv-errors": "^2.0.0", + "acorn-walk": "^8.0.0", + "ajv": "^8.0.0", + "deepmerge": "^4.2.0", + "espree": "^9.0.0", + "glob": "^10.0.0", + "ignore": "^5.0.0", + "kleur": "^4.1.0", + "minimist": "^1.2.0", + "prompts": "^2.0.0", + "semver": "^7.0.0" + }, + "bin": { + "html-validate": "bin/html-validate.js" + }, + "engines": { + "node": ">= 14.0" + }, + "peerDependencies": { + "jest": "^25.1 || ^26 || ^27.1 || ^28.1.3 || ^29.0.3", + "jest-diff": "^25.1 || ^26 || ^27.1 || ^28.1.3 || ^29.0.3", + "jest-snapshot": "^25.1 || ^26 || ^27.1 || ^28.1.3 || ^29.0.3" + }, + "peerDependenciesMeta": { + "jest": { + "optional": true + }, + "jest-diff": { + "optional": true + }, + "jest-snapshot": { + "optional": true + } + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "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/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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/marked": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/medium-zoom": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/medium-zoom/-/medium-zoom-1.1.0.tgz", + "integrity": "sha512-ewyDsp7k4InCUp3jRmwHBRFGyjBimKps/AJLjRSox+2q/2H4p/PNpQf+pwONWlJiOudkBXtbdmVbFjqyybfTmQ==", + "license": "MIT" + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-html-parser": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-5.4.2.tgz", + "integrity": "sha512-RaBPP3+51hPne/OolXxcz89iYvQvKOydaqoePpOgXcrOKZhjVIzmpKZz+Hd/RBO2/zN2q6CNJhQzucVz+u3Jyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-select": "^4.2.1", + "he": "1.2.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "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" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/opencollective-postinstall": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", + "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", + "license": "MIT", + "bin": { + "opencollective-postinstall": "index.js" + } + }, + "node_modules/package-json-from-dist": { + "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/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prompts/node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", + "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.7.0", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "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" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/send/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "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": { + "es-errors": "^1.3.0", + "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" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tinydate": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinydate/-/tinydate-1.3.0.tgz", + "integrity": "sha512-7cR8rLy2QhYHpsBDBVYnnWXm8uRTr38RoZakFSW7Bs7PzfMPNZthuMLkwqZv7MTu8lhQ91cOFYS5a7iFj2oR3w==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/test/consume-types/package.json b/test/consume-types/package.json new file mode 100644 index 0000000000..83ab9314f4 --- /dev/null +++ b/test/consume-types/package.json @@ -0,0 +1,13 @@ +{ + "scripts": { + "typecheck": "tsc --noEmit", + "serve": "five-server . --open=false --ignorePattern=node_modules" + }, + "dependencies": { + "docsify": "file:../../" + }, + "devDependencies": { + "five-server": "^0.4.5", + "typescript": "^5.9.3" + } +} diff --git a/test/consume-types/prism.js b/test/consume-types/prism.js new file mode 100644 index 0000000000..9fa1050498 --- /dev/null +++ b/test/consume-types/prism.js @@ -0,0 +1,27 @@ +// Small ESM wrapper, used in the importmap for 'prismjs', to import PrismJS by +// tricking its CommonJS format to see module.exports. + +const __prismCjs = await fetch('/node_modules/prismjs/prism.js').then(res => + res.text(), +); + +// Emulate CommonJS environment +const module = { exports: {} }; +// eslint-disable-next-line no-unused-vars +const exports = module.exports; + +// Evaluate the CommonJS code with module.exports in scope +eval(__prismCjs); + +// Export the Prism object +const _Prism = module.exports; +export default _Prism; + +// Also make Prism global because Docsify expects it by importing from +// 'prismjs/components/prism-markup-templating.js' in the compiler. +// @ts-expect-error FIXME get rid of this ugly global dependency hack in Docsify. +window.Prism = _Prism; + +// @ts-expect-error +// eslint-disable-next-line no-undef +console.log('Prism loaded:', Prism); diff --git a/test/consume-types/register-sw.js b/test/consume-types/register-sw.js new file mode 100644 index 0000000000..566c14d0bf --- /dev/null +++ b/test/consume-types/register-sw.js @@ -0,0 +1,86 @@ +// This whole thing is ugly. It is only so that we can fix improper import +// statements in libraries from node_modules. See sw.js for how we re-map the +// import URLs. +// The convoluted code here is so that we will force the app to use a new +// service worker if in dev mode we update the service worker code in sw.js + +let reloadQueued = false; +function queueReload() { + if (reloadQueued) { + return; + } + reloadQueued = true; + // Use location.reload() after a so any late controllerchange still settles. + window.location.reload(); +} + +// Register first. +const registration = await navigator.serviceWorker.register('/sw.js', { + scope: '/', + type: 'module', + updateViaCache: 'none', +}); + +// If there is already a waiting worker, wait for it to claim this client. +if (registration.waiting) { + const sw = registration.waiting; + + await new Promise(resolve => { + sw.addEventListener('statechange', () => { + // Wait until activated. + if (sw.state === 'activated') { + if (sw === navigator.serviceWorker.controller) { + resolve(void 0); + } else { + // If the new SW activated but is not controlling yet, it changed? Not sure if this can actuall happen. + queueReload(); + } + } + }); + }); +} + +if (navigator.serviceWorker.controller) { + navigator.serviceWorker.addEventListener('controllerchange', () => { + queueReload(); + }); +} else { + // First-ever load: wait for controllerchange + await new Promise(resolve => { + navigator.serviceWorker.addEventListener('controllerchange', resolve, { + once: true, + }); + }); +} + +// Track new installs. +registration.addEventListener('updatefound', () => { + const sw = registration.installing; + if (!sw) { + return; + } + sw.addEventListener('statechange', () => { + // When installed AND there is already a controller, a reload will let new SW control. + if (sw.state === 'installed' && navigator.serviceWorker.controller) { + // If skipWaiting ran inside new SW, controllerchange may fire soon; still queue. + queueReload(); + } + }); +}); + +// Force update check after listeners. +// For purposes of testing the example, force update on page load so we +// always test the latest service worker. +await registration.update(); + +// Wait until there is an active worker (first load). +await navigator.serviceWorker.ready; + +if (reloadQueued) { + await new Promise(() => {}); +} +console.log( + 'Service worker ready and controlling. Continue app bootstrap here.', +); + +export {}; diff --git a/test/consume-types/sw.js b/test/consume-types/sw.js new file mode 100644 index 0000000000..ea14cb925c --- /dev/null +++ b/test/consume-types/sw.js @@ -0,0 +1,99 @@ +// The purpose of this service worker is to help with loading +// node_modules modules when using ES modules in the browser. +// +// Specifically, this service worker helps with non-standard module paths +// that do not include file extensions, such as: +// +// /node_modules/some-lib/foo/bar +// /node_modules/some-lib/foo/bar/ +// +// In these cases, the service worker will try to resolve them to actual files +// by appending ".js" or "/index.js" as needed. +// +// This service worker only handles requests under /node_modules/. +// All other requests are passed through unmodified. + +export {}; + +const scope = /** @type {ServiceWorkerGlobalScope} */ ( + /** @type {any} */ (self) +); + +scope.addEventListener('install', event => { + // Always activate worker immediately, for purposes of testing. + event.waitUntil(scope.skipWaiting()); +}); + +scope.addEventListener('activate', event => { + // Always activate worker immediately, for purposes of testing. + event.waitUntil(scope.clients.claim()); +}); + +scope.addEventListener('fetch', event => { + const url = new URL(event.request.url); + + // Don't handle non-node_modules paths + if (!url.pathname.startsWith('/node_modules/')) { + event.respondWith(fetch(url.href)); + return; + } + + // 6 + // Special handling for non-standard module paths in node_modules + + const parts = url.pathname.split('/'); + const fileName = /** @type {string} */ (parts.pop()); + const ext = fileName.includes('.') ? fileName.split('.').pop() : ''; + + // Handle imports like 'some-lib/foo/bar' without an extension. + if (fileName !== '' && ext === '') { + event.respondWith( + // eslint-disable-next-line no-async-promise-executor + new Promise(async resolve => { + try { + // First try adding .js + const response = await tryJs(); + const mimeType = response.headers.get('Content-Type') || ''; + if (response.ok && mimeType.includes('javascript')) { + resolve(response); + } else { + throw new Error('Not JS'); + } + } catch { + // If that fails, try adding /index.js + resolve(await tryIndexJs()); + } + + async function tryJs() { + const tryJs = new URL(url); + tryJs.href += '.js'; + const response = await fetch(tryJs); + return response; + } + + async function tryIndexJs() { + const tryIndexJs = new URL(url); + tryIndexJs.href += '/index.js'; + const response = await fetch(tryIndexJs); + return response; + } + }), + ); + + return; + } + + // Handle imports like 'some-lib/foo/bar/' (ending with a slash). + if (fileName === '') { + // adding index.js + const tryIndexJs = new URL(url); + tryIndexJs.href += 'index.js'; + + event.respondWith(fetch(tryIndexJs)); + + return; + } + + // For all other cases, just fetch normally. + event.respondWith(fetch(url)); +}); diff --git a/test/consume-types/tsconfig.json b/test/consume-types/tsconfig.json new file mode 100644 index 0000000000..d05ee2182a --- /dev/null +++ b/test/consume-types/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "module": "esnext", + "moduleResolution": "node", + "target": "esnext", + "strict": true, + "noEmit": true, + "lib": ["DOM", "ESNext", "WebWorker"], + "skipLibCheck": true, + "skipDefaultLibCheck": true + } +} diff --git a/test/e2e/configuration.test.js b/test/e2e/configuration.test.js index d081d95eda..10af07faf0 100644 --- a/test/e2e/configuration.test.js +++ b/test/e2e/configuration.test.js @@ -41,7 +41,13 @@ test.describe('Configuration options', () => { test('false (throws uncaught errors)', async ({ page }) => { let consoleMsg, errorMsg; - page.on('console', msg => (consoleMsg = msg.text())); + page.on('console', msg => { + const text = msg.text(); + if (text.startsWith('DEPRECATION:')) { + return; + } // ignore expected deprecation warnings + consoleMsg = text; + }); page.on('pageerror', err => (errorMsg = err.message)); await docsifyInit({ diff --git a/test/e2e/plugins.test.js b/test/e2e/plugins.test.js index 68534830df..b1e53d729a 100644 --- a/test/e2e/plugins.test.js +++ b/test/e2e/plugins.test.js @@ -16,7 +16,13 @@ test.describe('Plugins', () => { 'ready', ]; - page.on('console', msg => consoleMsgs.push(msg.text())); + page.on('console', msg => { + const text = msg.text(); + if (text.startsWith('DEPRECATION:')) { + return; + } // ignore expected deprecation warnings + consoleMsgs.push(text); + }); await docsifyInit({ config: { @@ -166,8 +172,15 @@ test.describe('Plugins', () => { page.on('console', async msg => { for (const arg of msg.args()) { const val = await arg.jsonValue(); - const obj = JSON.parse(val); - obj.response && (routeData = obj); + if (typeof val === 'string' && val.startsWith('DEPRECATION:')) { + continue; + } + try { + const obj = typeof val === 'string' ? JSON.parse(val) : val; + obj && obj.response && (routeData = obj); + } catch { + // ignore non-JSON console messages + } } }); }); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000000..ef671d3627 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + + "module": "esnext", + "moduleResolution": "node", + "target": "es2023", + "lib": ["ES2023", "DOM", "DOM.AsyncIterable", "DOM.Iterable"], + "declaration": true, + "declarationMap": true, // This (along with also shipping src/, not only dist/), makes "Go To Definition" go to the source files in IDEs for a better DX. + "emitDeclarationOnly": true, + "resolveJsonModule": true, + + // Output declarations next to source files (the default when outDir is not + // defined), rather than in dist/, to avoid conflicting with the global + // build in dist/. + // "outDir": "dist/", + + "strict": true, + "skipLibCheck": true, + "skipDefaultLibCheck": true, + + // TODO: Remove once tinydate import is refactored to non-default, or replace/delete non-standards-aligned libs. + // Enables default import interop for CJS modules like tinydate + "allowSyntheticDefaultImports": true, + + // TODO: Remove this once all implicit any errors are fixed with proper JSDoc types + // Currently suppressing ~600 implicit any errors across the codebase + // See: https://github.com/docsifyjs/docsify/pull/2392 + "noImplicitAny": false + }, + "include": ["src/**/*.js", "src/core/modules.ts", "src/core/globals.ts"] +}