From 6067bdb24e6729d2249c9685f4f1c514c3167d91 Mon Sep 17 00:00:00 2001 From: undergroundwires Date: Sun, 25 Sep 2022 23:25:43 +0200 Subject: [PATCH] Improve documentation support with markdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rework documentation URLs as inline markdown. Redesign documentations with markdown text. Redesign way to document scripts/categories and present the documentation. Documentation is showed in an expandable box instead of tooltip. This is to allow writing longer documentation (tooltips are meant to be used for short text) and have better experience on mobile. If a node (script/category) has documentation it's now shown with single information icon (ℹ) aligned to right. Add support for rendering documentation as markdown. It automatically converts plain URLs to URLs with display names (e.g. https://docs.microsoft.com/..) will be rendered automatically like "docs.microsoft.com - Windows 11 Privacy...". --- .eslintignore | 1 + docs/collection-files.md | 7 +- package-lock.json | 372 ++++++++++++++---- package.json | 1 + src/application/Parser/CategoryParser.ts | 4 +- src/application/Parser/DocumentationParser.ts | 64 ++- src/application/Parser/Script/ScriptParser.ts | 4 +- .../collections/collection.yaml.d.ts | 4 +- src/domain/Category.ts | 2 +- src/domain/IDocumentable.ts | 2 +- src/domain/IScript.ts | 2 +- src/domain/Script.ts | 2 +- .../assets/icons/external-link.svg | 6 + src/presentation/assets/styles/_globals.scss | 3 +- .../styles/third-party-extensions/_tree.scss | 11 +- .../View/ScriptsTree/ScriptNodeParser.ts | 4 +- .../SelectableTree/LiquorTree/LiquorTree.d.ts | 2 +- .../LiquorTree/NodeWrapper/NodeTranslator.ts | 4 +- .../Node/Documentation/Documentable.vue | 71 ++++ .../Node/Documentation/DocumentationText.vue | 119 ++++++ .../Node/Documentation/MarkdownRenderer.ts | 156 ++++++++ .../ToggleDocumentationButton.vue | 47 +++ .../SelectableTree/Node/DocumentationUrls.vue | 45 --- .../ScriptsTree/SelectableTree/Node/INode.ts | 2 +- .../ScriptsTree/SelectableTree/Node/Node.vue | 30 +- .../NoDeadDocumentationUrls.spec.ts | 6 +- .../Documentation/MarkdownRenderer.spec.ts | 46 +++ .../application/Parser/CategoryParser.spec.ts | 6 +- .../Parser/DocumentationParser.spec.ts | 50 ++- .../Parser/Script/ScriptParser.spec.ts | 6 +- tests/unit/domain/Script.spec.ts | 14 +- .../View/Tree/ScriptNodeParser.spec.ts | 4 +- .../NodeWrapper/NodePredicateFilter.spec.ts | 6 +- .../NodeWrapper/NodeStateUpdater.spec.ts | 38 +- .../NodeWrapper/NodeTranslator.spec.ts | 12 +- .../Documentation/MarkdownRenderer.spec.ts | 69 ++++ .../Node/Reverter/ReverterFactory.spec.ts | 2 +- tests/unit/shared/Stubs/CategoryDataStub.ts | 6 +- tests/unit/shared/Stubs/CategoryStub.ts | 2 +- tests/unit/shared/Stubs/ScriptDataStub.ts | 4 +- tests/unit/shared/Stubs/ScriptStub.ts | 2 +- 41 files changed, 973 insertions(+), 265 deletions(-) create mode 100644 .eslintignore create mode 100644 src/presentation/assets/icons/external-link.svg create mode 100644 src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Documentation/Documentable.vue create mode 100644 src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Documentation/DocumentationText.vue create mode 100644 src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Documentation/MarkdownRenderer.ts create mode 100644 src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Documentation/ToggleDocumentationButton.vue delete mode 100644 src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/DocumentationUrls.vue create mode 100644 tests/integration/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Documentation/MarkdownRenderer.spec.ts create mode 100644 tests/unit/presentation/components/Scripts/View/Tree/SelectableTree/Node/Documentation/MarkdownRenderer.spec.ts diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..77738287 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +dist/ \ No newline at end of file diff --git a/docs/collection-files.md b/docs/collection-files.md index 662f1f8a..1a270e29 100644 --- a/docs/collection-files.md +++ b/docs/collection-files.md @@ -41,6 +41,9 @@ - `children: [` ***[`Category`](#Category)*** `|` [***`Script`***](#Script) `, ... ]` (**required**) - ❗ Category must consist of at least one subcategory or script. - Children can be combination of scripts and subcategories. +- `docs`: *`string`* | `[`*`string`*`, ... ]` + - Documentation pieces related to the category. + - Rendered as markdown. ### `Script` @@ -71,8 +74,8 @@ - A shared function or sequence of functions to call (called in order) - ❗ If not defined `code` must be defined - `docs`: *`string`* | `[`*`string`*`, ... ]` - - Single documentation URL or list of URLs for those who wants to learn more about the script - - E.g. `https://docs.microsoft.com/en-us/windows-server/` + - Documentation pieces related to the script. + - Rendered as markdown. - `recommend`: `"standard"` | `"strict"` | `undefined` (default) - If not defined then the script will not be recommended - If defined it can be either diff --git a/package-lock.json b/package-lock.json index 5f5f7bff..e8a7206c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "file-saver": "^2.0.5", "install": "^0.13.0", "liquor-tree": "^0.2.70", + "markdown-it": "^13.0.1", "npm": "^8.5.3", "v-tooltip": "2.1.3", "vue": "^2.6.14", @@ -1822,6 +1823,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, "optional": true, "engines": { "node": ">=0.1.90" @@ -2153,7 +2155,8 @@ "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==" + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "dev": true }, "node_modules/@hapi/address": { "version": "2.1.4", @@ -2404,6 +2407,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "dev": true, "dependencies": { "@gar/promisify": "^1.0.1", "semver": "^7.3.5" @@ -2413,6 +2417,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "dev": true, "dependencies": { "mkdirp": "^1.0.4", "rimraf": "^3.0.2" @@ -2425,6 +2430,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -4274,7 +4280,8 @@ "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true }, "node_modules/accepts": { "version": "1.3.8", @@ -4389,6 +4396,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -4519,6 +4527,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "engines": { "node": ">=8" } @@ -4527,6 +4536,7 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -4652,8 +4662,7 @@ "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/aria-query": { "version": "5.0.2", @@ -5067,7 +5076,8 @@ "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==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "node_modules/base": { "version": "0.11.2", @@ -5408,6 +5418,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5790,6 +5801,7 @@ "version": "15.3.0", "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "dev": true, "dependencies": { "@npmcli/fs": "^1.0.0", "@npmcli/move-file": "^1.0.1", @@ -5818,6 +5830,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -6005,6 +6018,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -6109,6 +6123,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, "engines": { "node": ">=10" } @@ -6234,6 +6249,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, "engines": { "node": ">=6" } @@ -6369,6 +6385,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", + "dev": true, "dependencies": { "string-width": "^4.2.0" }, @@ -6487,6 +6504,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, "dependencies": { "color-name": "1.1.3" } @@ -6494,7 +6512,8 @@ "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true }, "node_modules/colord": { "version": "2.9.3", @@ -6616,7 +6635,8 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true }, "node_modules/concat-stream": { "version": "2.0.0", @@ -8971,6 +8991,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, "engines": { "node": ">=0.8.0" } @@ -10897,6 +10918,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, "dependencies": { "minipass": "^3.0.0" }, @@ -10949,7 +10971,8 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "node_modules/fsevents": { "version": "2.3.2", @@ -11122,6 +11145,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -11274,7 +11298,8 @@ "node_modules/graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true }, "node_modules/graceful-readlink": { "version": "1.0.1", @@ -11381,6 +11406,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, "engines": { "node": ">=4" } @@ -11579,6 +11605,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -12003,6 +12030,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, "engines": { "node": ">=0.8.19" } @@ -12011,6 +12039,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, "engines": { "node": ">=8" } @@ -12018,12 +12047,14 @@ "node_modules/infer-owner": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==" + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -12032,12 +12063,14 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "node_modules/ini": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true, "engines": { "node": ">=10" } @@ -12320,6 +12353,7 @@ "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, "engines": { "node": ">=8" } @@ -12569,7 +12603,8 @@ "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true }, "node_modules/is-unicode-supported": { "version": "0.1.0", @@ -12652,7 +12687,8 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true }, "node_modules/isobject": { "version": "3.0.1", @@ -13072,7 +13108,8 @@ "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true }, "node_modules/json-schema": { "version": "0.4.0", @@ -13404,10 +13441,9 @@ "dev": true }, "node_modules/linkify-it": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", - "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", - "dev": true, + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", + "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", "dependencies": { "uc.micro": "^1.0.1" } @@ -13788,6 +13824,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -13853,14 +13890,13 @@ } }, "node_modules/markdown-it": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", - "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", - "dev": true, + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.1.tgz", + "integrity": "sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==", "dependencies": { "argparse": "^2.0.1", - "entities": "~2.1.0", - "linkify-it": "^3.0.1", + "entities": "~3.0.1", + "linkify-it": "^4.0.1", "mdurl": "^1.0.1", "uc.micro": "^1.0.5" }, @@ -13868,6 +13904,17 @@ "markdown-it": "bin/markdown-it.js" } }, + "node_modules/markdown-it/node_modules/entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/markdownlint": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.25.1.tgz", @@ -13931,6 +13978,31 @@ "integrity": "sha512-oEacRUVeTJ5D5hW1UYd2qExYI0oELdYK72k1TKGvIeYJIbqQWAz476NAc7LNixSySUhcNl++d02DvX0ccDk9/w==", "dev": true }, + "node_modules/markdownlint/node_modules/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "dev": true, + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "node_modules/markdownlint/node_modules/markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, "node_modules/matcher": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", @@ -14102,8 +14174,7 @@ "node_modules/mdurl": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", - "dev": true + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" }, "node_modules/media-typer": { "version": "0.3.0", @@ -14808,6 +14879,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -14818,12 +14890,14 @@ "node_modules/minimist": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true }, "node_modules/minipass": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz", "integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==", + "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -14835,6 +14909,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, "dependencies": { "minipass": "^3.0.0" }, @@ -14846,6 +14921,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, "dependencies": { "minipass": "^3.0.0" }, @@ -14857,6 +14933,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, "dependencies": { "minipass": "^3.0.0" }, @@ -14868,6 +14945,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" @@ -14998,6 +15076,7 @@ "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, "dependencies": { "minimist": "^1.2.6" }, @@ -15633,7 +15712,8 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "node_modules/multicast-dns": { "version": "7.2.5", @@ -15929,6 +16009,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "dev": true, "dependencies": { "abbrev": "^1.0.0" }, @@ -18625,6 +18706,7 @@ "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, "dependencies": { "wrappy": "1" } @@ -18674,6 +18756,7 @@ "version": "1.5.2", "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, "bin": { "opener": "bin/opener-bin.js" } @@ -18902,6 +18985,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, "dependencies": { "aggregate-error": "^3.0.0" }, @@ -19174,6 +19258,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -20217,7 +20302,8 @@ "node_modules/promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==" + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true }, "node_modules/propose": { "version": "0.0.5", @@ -22038,6 +22124,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, "dependencies": { "glob": "^7.1.3" }, @@ -22302,6 +22389,7 @@ "version": "7.3.7", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -22651,7 +22739,8 @@ "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true }, "node_modules/sirv": { "version": "1.0.19", @@ -23088,6 +23177,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "dev": true, "dependencies": { "minipass": "^3.1.1" }, @@ -23318,6 +23408,7 @@ "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, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -23330,7 +23421,8 @@ "node_modules/string-width/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==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true }, "node_modules/string.prototype.trimend": { "version": "1.0.5", @@ -23364,6 +23456,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -23451,6 +23544,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -23613,6 +23707,7 @@ "version": "6.1.11", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", + "dev": true, "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -23629,6 +23724,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -23739,7 +23835,8 @@ "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true }, "node_modules/thenify": { "version": "3.3.1", @@ -24391,6 +24488,7 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, "dependencies": { "is-typedarray": "^1.0.0" } @@ -24411,8 +24509,7 @@ "node_modules/uc.micro": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", - "dev": true + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" }, "node_modules/un-eval": { "version": "1.2.0", @@ -24778,6 +24875,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, "dependencies": { "unique-slug": "^2.0.0" } @@ -24786,6 +24884,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "dev": true, "dependencies": { "imurmurhash": "^0.1.4" } @@ -28017,6 +28116,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -28206,12 +28306,14 @@ "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==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true }, "node_modules/write-file-atomic": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, "dependencies": { "imurmurhash": "^0.1.4", "is-typedarray": "^1.0.0", @@ -28300,7 +28402,8 @@ "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true }, "node_modules/yaml": { "version": "1.10.2", @@ -29768,6 +29871,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, "optional": true }, "@cypress/request": { @@ -30023,7 +30127,8 @@ "@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==" + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "dev": true }, "@hapi/address": { "version": "2.1.4", @@ -30230,6 +30335,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "dev": true, "requires": { "@gar/promisify": "^1.0.1", "semver": "^7.3.5" @@ -30239,6 +30345,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "dev": true, "requires": { "mkdirp": "^1.0.4", "rimraf": "^3.0.2" @@ -30247,7 +30354,8 @@ "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true } } }, @@ -31766,7 +31874,8 @@ "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true }, "accepts": { "version": "1.3.8", @@ -31852,6 +31961,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, "requires": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -31945,12 +32055,14 @@ "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -32049,8 +32161,7 @@ "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "aria-query": { "version": "5.0.2", @@ -32362,7 +32473,8 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "base": { "version": "0.11.2", @@ -32634,6 +32746,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -32937,6 +33050,7 @@ "version": "15.3.0", "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "dev": true, "requires": { "@npmcli/fs": "^1.0.0", "@npmcli/move-file": "^1.0.1", @@ -32961,7 +33075,8 @@ "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true } } }, @@ -33101,6 +33216,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -33175,7 +33291,8 @@ "chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true }, "chrome-trace-event": { "version": "1.0.3", @@ -33277,7 +33394,8 @@ "clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true }, "cli-boxes": { "version": "2.2.1", @@ -33369,6 +33487,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", + "dev": true, "requires": { "@colors/colors": "1.5.0", "string-width": "^4.2.0" @@ -33460,6 +33579,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, "requires": { "color-name": "1.1.3" } @@ -33467,7 +33587,8 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true }, "colord": { "version": "2.9.3", @@ -33570,7 +33691,8 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true }, "concat-stream": { "version": "2.0.0", @@ -35423,7 +35545,8 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true }, "escodegen": { "version": "2.0.0", @@ -36901,6 +37024,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, "requires": { "minipass": "^3.0.0" } @@ -36952,7 +37076,8 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "fsevents": { "version": "2.3.2", @@ -37079,6 +37204,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -37192,7 +37318,8 @@ "graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true }, "graceful-readlink": { "version": "1.0.1", @@ -37272,7 +37399,8 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true }, "has-property-descriptors": { "version": "1.0.0", @@ -37418,6 +37546,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, "requires": { "lru-cache": "^6.0.0" } @@ -37729,22 +37858,26 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true }, "indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true }, "infer-owner": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==" + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -37753,12 +37886,14 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "ini": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==" + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true }, "install": { "version": "0.13.0", @@ -37962,7 +38097,8 @@ "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==" + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true }, "is-glob": { "version": "4.0.3", @@ -38125,7 +38261,8 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true }, "is-unicode-supported": { "version": "0.1.0", @@ -38184,7 +38321,8 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true }, "isobject": { "version": "3.0.1", @@ -38518,7 +38656,8 @@ "json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true }, "json-schema": { "version": "0.4.0", @@ -38799,10 +38938,9 @@ "dev": true }, "linkify-it": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", - "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", - "dev": true, + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", + "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", "requires": { "uc.micro": "^1.0.1" } @@ -39101,6 +39239,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, "requires": { "yallist": "^4.0.0" } @@ -39147,16 +39286,22 @@ } }, "markdown-it": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", - "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", - "dev": true, + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.1.tgz", + "integrity": "sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==", "requires": { "argparse": "^2.0.1", - "entities": "~2.1.0", - "linkify-it": "^3.0.1", + "entities": "~3.0.1", + "linkify-it": "^4.0.1", "mdurl": "^1.0.1", "uc.micro": "^1.0.5" + }, + "dependencies": { + "entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==" + } } }, "markdownlint": { @@ -39166,6 +39311,30 @@ "dev": true, "requires": { "markdown-it": "12.3.2" + }, + "dependencies": { + "linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "dev": true, + "requires": { + "uc.micro": "^1.0.1" + } + }, + "markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "dev": true, + "requires": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + } + } } }, "markdownlint-cli": { @@ -39343,8 +39512,7 @@ "mdurl": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", - "dev": true + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" }, "media-typer": { "version": "0.3.0", @@ -39779,6 +39947,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -39786,12 +39955,14 @@ "minimist": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true }, "minipass": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz", "integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==", + "dev": true, "requires": { "yallist": "^4.0.0" } @@ -39800,6 +39971,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, "requires": { "minipass": "^3.0.0" } @@ -39808,6 +39980,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, "requires": { "minipass": "^3.0.0" } @@ -39816,6 +39989,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, "requires": { "minipass": "^3.0.0" } @@ -39824,6 +39998,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, "requires": { "minipass": "^3.0.0", "yallist": "^4.0.0" @@ -39945,6 +40120,7 @@ "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, "requires": { "minimist": "^1.2.6" } @@ -40445,7 +40621,8 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "multicast-dns": { "version": "7.2.5", @@ -40705,6 +40882,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "dev": true, "requires": { "abbrev": "^1.0.0" } @@ -42543,6 +42721,7 @@ "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, "requires": { "wrappy": "1" } @@ -42578,7 +42757,8 @@ "opener": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", - "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==" + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true }, "optionator": { "version": "0.9.1", @@ -42745,6 +42925,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, "requires": { "aggregate-error": "^3.0.0" } @@ -42976,7 +43157,8 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true }, "path-key": { "version": "3.1.1", @@ -43685,7 +43867,8 @@ "promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==" + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true }, "propose": { "version": "0.0.5", @@ -45117,6 +45300,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, "requires": { "glob": "^7.1.3" } @@ -45304,6 +45488,7 @@ "version": "7.3.7", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, "requires": { "lru-cache": "^6.0.0" } @@ -45596,7 +45781,8 @@ "signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true }, "sirv": { "version": "1.0.19", @@ -45963,6 +46149,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "dev": true, "requires": { "minipass": "^3.1.1" } @@ -46161,6 +46348,7 @@ "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, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -46170,7 +46358,8 @@ "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==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true } } }, @@ -46200,6 +46389,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -46257,6 +46447,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -46380,6 +46571,7 @@ "version": "6.1.11", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", + "dev": true, "requires": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -46392,7 +46584,8 @@ "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true } } }, @@ -46468,7 +46661,8 @@ "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true }, "thenify": { "version": "3.3.1", @@ -46980,6 +47174,7 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, "requires": { "is-typedarray": "^1.0.0" } @@ -46993,8 +47188,7 @@ "uc.micro": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", - "dev": true + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" }, "un-eval": { "version": "1.2.0", @@ -47258,6 +47452,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, "requires": { "unique-slug": "^2.0.0" } @@ -47266,6 +47461,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "dev": true, "requires": { "imurmurhash": "^0.1.4" } @@ -49808,6 +50004,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "requires": { "isexe": "^2.0.0" } @@ -49959,12 +50156,14 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true }, "write-file-atomic": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, "requires": { "imurmurhash": "^0.1.4", "is-typedarray": "^1.0.0", @@ -50024,7 +50223,8 @@ "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true }, "yaml": { "version": "1.10.2", diff --git a/package.json b/package.json index 8a7effb5..dd77845e 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "file-saver": "^2.0.5", "install": "^0.13.0", "liquor-tree": "^0.2.70", + "markdown-it": "^13.0.1", "npm": "^8.5.3", "v-tooltip": "2.1.3", "vue": "^2.6.14", diff --git a/src/application/Parser/CategoryParser.ts b/src/application/Parser/CategoryParser.ts index a83fd830..10f8d2ef 100644 --- a/src/application/Parser/CategoryParser.ts +++ b/src/application/Parser/CategoryParser.ts @@ -5,7 +5,7 @@ import { Script } from '@/domain/Script'; import { Category } from '@/domain/Category'; import { NodeValidator } from '@/application/Parser/NodeValidation/NodeValidator'; import { NodeType } from '@/application/Parser/NodeValidation/NodeType'; -import { parseDocUrls } from './DocumentationParser'; +import { parseDocs } from './DocumentationParser'; import { ICategoryCollectionParseContext } from './Script/ICategoryCollectionParseContext'; import { parseScript } from './Script/ScriptParser'; @@ -50,7 +50,7 @@ function parseCategoryRecursively(context: ICategoryParseContext): Category { return context.factory( /* id: */ categoryIdCounter++, /* name: */ context.categoryData.category, - /* docs: */ parseDocUrls(context.categoryData), + /* docs: */ parseDocs(context.categoryData), /* categories: */ children.subCategories, /* scripts: */ children.subScripts, ); diff --git a/src/application/Parser/DocumentationParser.ts b/src/application/Parser/DocumentationParser.ts index a3a9a931..0639dfad 100644 --- a/src/application/Parser/DocumentationParser.ts +++ b/src/application/Parser/DocumentationParser.ts @@ -1,64 +1,58 @@ -import type { DocumentableData, DocumentationUrlsData } from '@/application/collections/'; +import type { DocumentableData, DocumentationData } from '@/application/collections/'; -export function parseDocUrls(documentable: DocumentableData): ReadonlyArray { +export function parseDocs(documentable: DocumentableData): readonly string[] { if (!documentable) { throw new Error('missing documentable'); } const { docs } = documentable; - if (!docs || !docs.length) { + if (!docs) { return []; } - let result = new DocumentationUrlContainer(); + let result = new DocumentationContainer(); result = addDocs(docs, result); return result.getAll(); } function addDocs( - docs: DocumentationUrlsData, - urls: DocumentationUrlContainer, -): DocumentationUrlContainer { + docs: DocumentationData, + container: DocumentationContainer, +): DocumentationContainer { if (docs instanceof Array) { - urls.addUrls(docs); + if (docs.length > 0) { + container.addParts(docs); + } } else if (typeof docs === 'string') { - urls.addUrl(docs); + container.addPart(docs); } else { - throw new Error('Docs field (documentation url) must a string or array of strings'); + throwInvalidType(); } - return urls; + return container; } -class DocumentationUrlContainer { - private readonly urls = new Array(); +class DocumentationContainer { + private readonly parts = new Array(); - public addUrl(url: string) { - validateUrl(url); - this.urls.push(url); + public addPart(documentation: string) { + if (!documentation) { + throw Error('missing documentation'); + } + if (typeof documentation !== 'string') { + throwInvalidType(); + } + this.parts.push(documentation); } - public addUrls(urls: readonly string[]) { - for (const url of urls) { - if (typeof url !== 'string') { - throw new Error('Docs field (documentation url) must be an array of strings'); - } - this.addUrl(url); + public addParts(parts: readonly string[]) { + for (const part of parts) { + this.addPart(part); } } public getAll(): ReadonlyArray { - return this.urls; + return this.parts; } } -function validateUrl(docUrl: string): void { - if (!docUrl) { - throw new Error('Documentation url is null or empty'); - } - if (docUrl.includes('\n')) { - throw new Error('Documentation url cannot be multi-lined.'); - } - const validUrlRegex = /(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/g; - const res = docUrl.match(validUrlRegex); - if (res == null) { - throw new Error(`Invalid documentation url: ${docUrl}`); - } +function throwInvalidType() { + throw new Error('docs field (documentation) must be an array of strings'); } diff --git a/src/application/Parser/Script/ScriptParser.ts b/src/application/Parser/Script/ScriptParser.ts index 9bdee0cd..99a12a13 100644 --- a/src/application/Parser/Script/ScriptParser.ts +++ b/src/application/Parser/Script/ScriptParser.ts @@ -3,7 +3,7 @@ import { Script } from '@/domain/Script'; import { RecommendationLevel } from '@/domain/RecommendationLevel'; import { IScriptCode } from '@/domain/IScriptCode'; import { ScriptCode } from '@/domain/ScriptCode'; -import { parseDocUrls } from '../DocumentationParser'; +import { parseDocs } from '../DocumentationParser'; import { createEnumParser, IEnumParser } from '../../Common/Enum'; import { NodeType } from '../NodeValidation/NodeType'; import { NodeValidator } from '../NodeValidation/NodeValidator'; @@ -23,7 +23,7 @@ export function parseScript( const script = scriptFactory( /* name: */ data.name, /* code: */ parseCode(data, context), - /* docs: */ parseDocUrls(data), + /* docs: */ parseDocs(data), /* level: */ parseLevel(data.recommend, levelParser), ); return script; diff --git a/src/application/collections/collection.yaml.d.ts b/src/application/collections/collection.yaml.d.ts index 2c3a1cf9..6e7f0bce 100644 --- a/src/application/collections/collection.yaml.d.ts +++ b/src/application/collections/collection.yaml.d.ts @@ -12,10 +12,10 @@ declare module '@/application/collections/*' { } export type CategoryOrScriptData = CategoryData | ScriptData; - export type DocumentationUrlsData = ReadonlyArray | string; + export type DocumentationData = ReadonlyArray | string; export interface DocumentableData { - readonly docs?: DocumentationUrlsData; + readonly docs?: DocumentationData; } export interface InstructionHolder { diff --git a/src/domain/Category.ts b/src/domain/Category.ts index d094bcc4..9e9e35e6 100644 --- a/src/domain/Category.ts +++ b/src/domain/Category.ts @@ -8,7 +8,7 @@ export class Category extends BaseEntity implements ICategory { constructor( id: number, public readonly name: string, - public readonly documentationUrls: ReadonlyArray, + public readonly docs: ReadonlyArray, public readonly subCategories?: ReadonlyArray, public readonly scripts?: ReadonlyArray, ) { diff --git a/src/domain/IDocumentable.ts b/src/domain/IDocumentable.ts index e21f59b6..76c253cf 100644 --- a/src/domain/IDocumentable.ts +++ b/src/domain/IDocumentable.ts @@ -1,3 +1,3 @@ export interface IDocumentable { - readonly documentationUrls: ReadonlyArray; + readonly docs: ReadonlyArray; } diff --git a/src/domain/IScript.ts b/src/domain/IScript.ts index a6d00d53..a02fcc06 100644 --- a/src/domain/IScript.ts +++ b/src/domain/IScript.ts @@ -6,7 +6,7 @@ import { IScriptCode } from './IScriptCode'; export interface IScript extends IEntity, IDocumentable { readonly name: string; readonly level?: RecommendationLevel; - readonly documentationUrls: ReadonlyArray; + readonly docs: ReadonlyArray; readonly code: IScriptCode; canRevert(): boolean; } diff --git a/src/domain/Script.ts b/src/domain/Script.ts index 1e65c7e2..6f8d5ebc 100644 --- a/src/domain/Script.ts +++ b/src/domain/Script.ts @@ -7,7 +7,7 @@ export class Script extends BaseEntity implements IScript { constructor( public readonly name: string, public readonly code: IScriptCode, - public readonly documentationUrls: ReadonlyArray, + public readonly docs: ReadonlyArray, public readonly level?: RecommendationLevel, ) { super(name); diff --git a/src/presentation/assets/icons/external-link.svg b/src/presentation/assets/icons/external-link.svg new file mode 100644 index 00000000..40bcd506 --- /dev/null +++ b/src/presentation/assets/icons/external-link.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/presentation/assets/styles/_globals.scss b/src/presentation/assets/styles/_globals.scss index ba24dfa9..6d3dcf05 100644 --- a/src/presentation/assets/styles/_globals.scss +++ b/src/presentation/assets/styles/_globals.scss @@ -10,12 +10,13 @@ box-sizing: border-box; } +$globals-color-hover: $color-primary; a { color:inherit; text-decoration: underline; cursor: pointer; @include hover-or-touch { - color: $color-primary; + color: $globals-color-hover; } } diff --git a/src/presentation/assets/styles/third-party-extensions/_tree.scss b/src/presentation/assets/styles/third-party-extensions/_tree.scss index 78a5a339..fc80dd44 100644 --- a/src/presentation/assets/styles/third-party-extensions/_tree.scss +++ b/src/presentation/assets/styles/third-party-extensions/_tree.scss @@ -19,10 +19,13 @@ $color-node-checkbox-tick-checked : $color-on-secondary; &-node { white-space: normal !important; > .tree-content { - > .tree-anchor > span { - color: $color-node-fg; - text-transform: uppercase; - font-size: 1.5em; + > .tree-anchor { + > span { + color: $color-node-fg; + text-transform: uppercase; + font-size: 1.5em; + } + display: block; // so it takes full width to allow aligning items inside } @include hover-or-touch { background: $color-node-hover-bg !important; diff --git a/src/presentation/components/Scripts/View/ScriptsTree/ScriptNodeParser.ts b/src/presentation/components/Scripts/View/ScriptsTree/ScriptNodeParser.ts index d99a543b..d099dc2e 100644 --- a/src/presentation/components/Scripts/View/ScriptsTree/ScriptNodeParser.ts +++ b/src/presentation/components/Scripts/View/ScriptsTree/ScriptNodeParser.ts @@ -64,7 +64,7 @@ function convertCategoryToNode( type: NodeType.Category, text: category.name, children, - documentationUrls: category.documentationUrls, + docs: category.docs, isReversible: children && children.every((child) => child.isReversible), }; } @@ -75,7 +75,7 @@ function convertScriptToNode(script: IScript): INode { type: NodeType.Script, text: script.name, children: undefined, - documentationUrls: script.documentationUrls, + docs: script.docs, isReversible: script.canRevert(), }; } diff --git a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/LiquorTree/LiquorTree.d.ts b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/LiquorTree/LiquorTree.d.ts index f5cd9011..9ec7e3ae 100644 --- a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/LiquorTree/LiquorTree.d.ts +++ b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/LiquorTree/LiquorTree.d.ts @@ -13,7 +13,7 @@ declare module 'liquor-tree' { } export interface ICustomLiquorTreeData { type: number; - documentationUrls: ReadonlyArray; + docs: ReadonlyArray; isReversible: boolean; } // https://github.com/amsik/liquor-tree/blob/master/src/lib/Node.js diff --git a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/LiquorTree/NodeWrapper/NodeTranslator.ts b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/LiquorTree/NodeWrapper/NodeTranslator.ts index bb32ecdf..464f271d 100644 --- a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/LiquorTree/NodeWrapper/NodeTranslator.ts +++ b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/LiquorTree/NodeWrapper/NodeTranslator.ts @@ -11,7 +11,7 @@ export function convertExistingToNode(liquorTreeNode: ILiquorTreeExistingNode): text: liquorTreeNode.data.text, // selected: liquorTreeNode.states && liquorTreeNode.states.checked, children: convertChildren(liquorTreeNode.children, convertExistingToNode), - documentationUrls: liquorTreeNode.data.documentationUrls, + docs: liquorTreeNode.data.docs, isReversible: liquorTreeNode.data.isReversible, }; } @@ -27,7 +27,7 @@ export function toNewLiquorTreeNode(node: INode): ILiquorTreeNewNode { }, children: convertChildren(node.children, toNewLiquorTreeNode), data: { - documentationUrls: node.documentationUrls, + docs: node.docs, isReversible: node.isReversible, type: node.type, }, diff --git a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Documentation/Documentable.vue b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Documentation/Documentable.vue new file mode 100644 index 00000000..c14138eb --- /dev/null +++ b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Documentation/Documentable.vue @@ -0,0 +1,71 @@ + + + + + diff --git a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Documentation/DocumentationText.vue b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Documentation/DocumentationText.vue new file mode 100644 index 00000000..f8c83dc6 --- /dev/null +++ b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Documentation/DocumentationText.vue @@ -0,0 +1,119 @@ + + + + + diff --git a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Documentation/MarkdownRenderer.ts b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Documentation/MarkdownRenderer.ts new file mode 100644 index 00000000..8edce915 --- /dev/null +++ b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Documentation/MarkdownRenderer.ts @@ -0,0 +1,156 @@ +import MarkdownIt from 'markdown-it'; +import Renderer from 'markdown-it/lib/renderer'; +import Token from 'markdown-it/lib/token'; + +export function createRenderer(): IRenderer { + const md = new MarkdownIt({ + linkify: true, // Auto-convert URL-like text to links. + breaks: false, // Do not convert single '\n's into
. + }); + openUrlsInNewTab(md); + return { + render: (markdown: string) => { + markdown = beatifyAutoLinks(markdown); + return md.render(markdown); + }, + }; +} + +export interface IRenderer { + render(markdown: string): string; +} + +function beatifyAutoLinks(content: string): string { + if (!content) { + return content; + } + return content.replaceAll(/(? { + return toReadableLink(urlMatch); + }); +} + +function toReadableLink(url: string): string { + const parts = new URL(url); + let displayName = toReadableHostName(parts.hostname); + const pageName = extractPageName(parts); + if (pageName) { + displayName += ` - ${truncateRight(capitalizeEachLetter(pageName), 50)}`; + } + return `[${displayName}](${parts.href})`; +} + +function toReadableHostName(hostname: string): string { + const wwwStripped = hostname.replace(/^(www\.)/, ''); + const truncated = truncateLeft(wwwStripped, 30); + return truncated; +} + +function extractPageName(parts: URL): string | undefined { + const path = toReadablePath(parts.pathname); + if (path) { + return path; + } + return toReadableQuery(parts); +} + +function toReadableQuery(parts: URL): string | undefined { + const queryValues = [...parts.searchParams.values()]; + if (queryValues.length === 0) { + return undefined; + } + return selectMostDescriptiveName(queryValues); +} + +function truncateLeft(phrase: string, threshold: number): string { + return phrase.length > threshold ? `…${phrase.substring(phrase.length - threshold, phrase.length)}` : phrase; +} + +function isDigit(value: string): boolean { + return /^\d+$/.test(value); +} + +function toReadablePath(path: string): string | undefined { + const decodedPath = decodeURI(path); // Fixes e.g. %20 to whitespaces + const pathPart = selectMostDescriptiveName(decodedPath.split('/')); + if (!pathPart) { + return undefined; + } + const extensionStripped = removeTrailingExtension(pathPart); + const humanlyTokenized = extensionStripped.replaceAll(/[-_]/g, ' '); + return humanlyTokenized; +} + +function removeTrailingExtension(value: string): string { + const parts = value.split('.'); + if (parts.length === 1) { + return value; + } + if (parts.at(-1).length > 9) { + return value; // Heuristically web file extension is no longer than 9 chars (e.g. "html") + } + return parts.slice(0, -1).join('.'); +} + +function capitalizeEachLetter(phrase: string): string { + return phrase + .split(' ') + .map((word) => capitalizeFirstLetter(word)) + .join(' '); + function capitalizeFirstLetter(str: string): string { + return str.charAt(0).toUpperCase() + str.slice(1); + } +} + +function truncateRight(phrase: string, threshold: number): string { + return phrase.length > threshold ? `${phrase.substring(0, threshold)}…` : phrase; +} + +function selectMostDescriptiveName(parts: readonly string[]): string | undefined { + const goodParts = parts.filter(isGoodPathPart); + if (goodParts.length === 0) { + return undefined; + } + const longestGoodPart = goodParts.reduce((a, b) => (a.length > b.length ? a : b)); + return longestGoodPart; +} + +function isGoodPathPart(part: string): boolean { + return part + && !isDigit(part) // E.g. article numbers, issue numbers + && part.length > 2 // This is often non-human categories like T5 etc. + && !/^index(?:\.\S{0,10}$|$)/.test(part) // E.g. index.html + && !/^[A-Za-z]{2,4}([_-][A-Za-z]{4})?([_-]([A-Za-z]{2}|[0-9]{3}))$/.test(part) // Locale string e.g. fr-FR + && !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(part) // GUID + && !/^[0-9a-f]{40}$/.test(part); // Git SHA (e.g. GitHub links) +} + +function openUrlsInNewTab(md: MarkdownIt) { + // https://github.com/markdown-it/markdown-it/blob/12.2.0/docs/architecture.md#renderer + const defaultRender = getDefaultRenderer(md, 'link_open'); + md.renderer.rules.link_open = (tokens, idx, options, env, self) => { + const token = tokens[idx]; + if (!getTokenAttributeValue(token, 'target')) { + token.attrPush(['target', '_blank']); + } + return defaultRender(tokens, idx, options, env, self); + }; +} + +function getDefaultRenderer(md: MarkdownIt, ruleName: string): Renderer.RenderRule { + const renderer = md.renderer.rules[ruleName]; + if (renderer) { + return renderer; + } + return (tokens, idx, options, _env, self) => { + return self.renderToken(tokens, idx, options); + }; +} + +function getTokenAttributeValue(token: Token, attributeName: string): string | undefined { + const attributeIndex = token.attrIndex(attributeName); + if (attributeIndex < 0) { + return undefined; + } + const value = token.attrs[attributeIndex][1]; + return value; +} diff --git a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Documentation/ToggleDocumentationButton.vue b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Documentation/ToggleDocumentationButton.vue new file mode 100644 index 00000000..0ed01ed7 --- /dev/null +++ b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Documentation/ToggleDocumentationButton.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/DocumentationUrls.vue b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/DocumentationUrls.vue deleted file mode 100644 index 8f1623dd..00000000 --- a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/DocumentationUrls.vue +++ /dev/null @@ -1,45 +0,0 @@ - - - - - diff --git a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/INode.ts b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/INode.ts index a322dca4..40e6b31c 100644 --- a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/INode.ts +++ b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/INode.ts @@ -7,7 +7,7 @@ export interface INode { readonly id: string; readonly text: string; readonly isReversible: boolean; - readonly documentationUrls: ReadonlyArray; + readonly docs: ReadonlyArray; readonly children?: ReadonlyArray; readonly type: NodeType; } diff --git a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Node.vue b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Node.vue index aa690a8d..fee0e675 100644 --- a/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Node.vue +++ b/src/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Node.vue @@ -1,31 +1,29 @@ @@ -33,7 +31,7 @@ export default class Node extends Vue { @use "@/presentation/assets/styles/main" as *; #node { - display:flex; + display: flex; flex-direction: row; flex-wrap: wrap; .text { diff --git a/tests/integration/application/collections/NoDeadDocumentationUrls.spec.ts b/tests/integration/application/collections/NoDeadDocumentationUrls.spec.ts index d56307d0..134569ec 100644 --- a/tests/integration/application/collections/NoDeadDocumentationUrls.spec.ts +++ b/tests/integration/application/collections/NoDeadDocumentationUrls.spec.ts @@ -36,10 +36,14 @@ function collectUniqueUrls(app: IApplication): string[] { return app .collections .flatMap((a) => a.getAllScripts()) - .flatMap((script) => script.documentationUrls) + .flatMap((script) => script.docs?.flatMap((doc) => parseUrls(doc))) .filter((url, index, array) => array.indexOf(url) === index); } +function parseUrls(text: string): string[] { + return text?.match(/\bhttps?:\/\/\S+/gi) ?? []; +} + function printUrls(statuses: IUrlStatus[]): string { /* eslint-disable prefer-template */ return '\n' diff --git a/tests/integration/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Documentation/MarkdownRenderer.spec.ts b/tests/integration/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Documentation/MarkdownRenderer.spec.ts new file mode 100644 index 00000000..1500b4c9 --- /dev/null +++ b/tests/integration/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Documentation/MarkdownRenderer.spec.ts @@ -0,0 +1,46 @@ +import 'mocha'; +import { expect } from 'chai'; +import { parseApplication } from '@/application/Parser/ApplicationParser'; +import { OperatingSystem } from '@/domain/OperatingSystem'; +import { createRenderer } from '@/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Documentation/MarkdownRenderer'; + +describe('MarkdownRenderer', () => { + describe('can render all docs', () => { + // arrange + const renderer = createRenderer(); + for (const node of collectAllDocumentableNodes()) { + it(`${node.nodeLabel}`, () => { + // act + const html = renderer.render(node.docs); + // assert + expect(isValidHtml(html)); + }); + } + }); +}); + +interface IDocumentableNode { + nodeLabel: string + docs: string +} +function* collectAllDocumentableNodes(): Generator { + const app = parseApplication(); + for (const collection of app.collections) { + const documentableNodes = [ + ...collection.getAllScripts(), + ...collection.getAllCategories(), + ]; + for (const node of documentableNodes) { + const documentable: IDocumentableNode = { + nodeLabel: `${OperatingSystem[collection.os]} | ${node.name} (${node.id})`, + docs: node.docs.join('\n'), + }; + yield documentable; + } + } +} + +function isValidHtml(value: string): boolean { + const doc = new window.DOMParser().parseFromString(value, 'text/html'); + return Array.from(doc.body.childNodes).some((node) => node.nodeType === 1); +} diff --git a/tests/unit/application/Parser/CategoryParser.spec.ts b/tests/unit/application/Parser/CategoryParser.spec.ts index 3e9d0004..5c0fb465 100644 --- a/tests/unit/application/Parser/CategoryParser.spec.ts +++ b/tests/unit/application/Parser/CategoryParser.spec.ts @@ -3,7 +3,7 @@ import { expect } from 'chai'; import type { CategoryData, CategoryOrScriptData } from '@/application/collections/'; import { CategoryFactoryType, parseCategory } from '@/application/Parser/CategoryParser'; import { parseScript } from '@/application/Parser/Script/ScriptParser'; -import { parseDocUrls } from '@/application/Parser/DocumentationParser'; +import { parseDocs } from '@/application/Parser/DocumentationParser'; import { ScriptCompilerStub } from '@tests/unit/shared/Stubs/ScriptCompilerStub'; import { ScriptDataStub } from '@tests/unit/shared/Stubs/ScriptDataStub'; import { CategoryCollectionParseContextStub } from '@tests/unit/shared/Stubs/CategoryCollectionParseContextStub'; @@ -157,14 +157,14 @@ describe('CategoryParser', () => { it('returns expected docs', () => { // arrange const url = 'https://privacy.sexy'; - const expected = parseDocUrls({ docs: url }); + const expected = parseDocs({ docs: url }); const category = new CategoryDataStub() .withDocs(url); // act const actual = new TestBuilder() .withData(category) .parseCategory() - .documentationUrls; + .docs; // assert expect(actual).to.deep.equal(expected); }); diff --git a/tests/unit/application/Parser/DocumentationParser.spec.ts b/tests/unit/application/Parser/DocumentationParser.spec.ts index b222faed..44b671d0 100644 --- a/tests/unit/application/Parser/DocumentationParser.spec.ts +++ b/tests/unit/application/Parser/DocumentationParser.spec.ts @@ -1,26 +1,60 @@ import 'mocha'; import { expect } from 'chai'; import type { DocumentableData } from '@/application/collections/'; -import { parseDocUrls } from '@/application/Parser/DocumentationParser'; -import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests'; +import { parseDocs } from '@/application/Parser/DocumentationParser'; +import { itEachAbsentObjectValue, itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests'; describe('DocumentationParser', () => { - describe('parseDocUrls', () => { - describe('throws when absent', () => { + describe('parseDocs', () => { + describe('throws when node is missing', () => { itEachAbsentObjectValue((absentValue) => { // arrange const expectedError = 'missing documentable'; // act - const act = () => parseDocUrls(absentValue); + const act = () => parseDocs(absentValue); // assert expect(act).to.throw(expectedError); }); }); + describe('throws when single documentation is missing', () => { + itEachAbsentStringValue((absentValue) => { + // arrange + const expectedError = 'missing documentation'; + const node: DocumentableData = { docs: ['non empty doc 1', absentValue] }; + // act + const act = () => parseDocs(node); + // assert + expect(act).to.throw(expectedError); + }); + }); + describe('throws when type is unexpected', () => { + // arrange + const expectedTypeError = 'docs field (documentation) must be an array of strings'; + const wrongTypedValue = 22 as never; + const testCases: ReadonlyArray<{ name: string, node: DocumentableData }> = [ + { + name: 'given docs', + node: { docs: wrongTypedValue }, + }, + { + name: 'single doc', + node: { docs: ['non empty doc 1', wrongTypedValue] }, + }, + ]; + for (const testCase of testCases) { + it(testCase.name, () => { + // act + const act = () => parseDocs(testCase.node); + // assert + expect(act).to.throw(expectedTypeError); + }); + } + }); it('returns empty when empty', () => { // arrange const empty: DocumentableData = { }; // act - const actual = parseDocUrls(empty); + const actual = parseDocs(empty); // assert expect(actual).to.have.lengthOf(0); }); @@ -30,7 +64,7 @@ describe('DocumentationParser', () => { const expected = [url]; const sut: DocumentableData = { docs: url }; // act - const actual = parseDocUrls(sut); + const actual = parseDocs(sut); // assert expect(actual).to.deep.equal(expected); }); @@ -39,7 +73,7 @@ describe('DocumentationParser', () => { const expected = ['https://privacy.sexy', 'https://github.com/undergroundwires/privacy.sexy']; const sut: DocumentableData = { docs: expected }; // act - const actual = parseDocUrls(sut); + const actual = parseDocs(sut); // assert expect(actual).to.deep.equal(expected); }); diff --git a/tests/unit/application/Parser/Script/ScriptParser.spec.ts b/tests/unit/application/Parser/Script/ScriptParser.spec.ts index aa65b557..5e8f6e31 100644 --- a/tests/unit/application/Parser/Script/ScriptParser.spec.ts +++ b/tests/unit/application/Parser/Script/ScriptParser.spec.ts @@ -2,7 +2,7 @@ import 'mocha'; import { expect } from 'chai'; import type { ScriptData } from '@/application/collections/'; import { parseScript, ScriptFactoryType } from '@/application/Parser/Script/ScriptParser'; -import { parseDocUrls } from '@/application/Parser/DocumentationParser'; +import { parseDocs } from '@/application/Parser/DocumentationParser'; import { RecommendationLevel } from '@/domain/RecommendationLevel'; import { ICategoryCollectionParseContext } from '@/application/Parser/Script/ICategoryCollectionParseContext'; import { itEachAbsentObjectValue, itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests'; @@ -36,13 +36,13 @@ describe('ScriptParser', () => { const docs = ['https://expected-doc1.com', 'https://expected-doc2.com']; const script = ScriptDataStub.createWithCode() .withDocs(docs); - const expected = parseDocUrls(script); + const expected = parseDocs(script); // act const actual = new TestBuilder() .withData(script) .parseScript(); // assert - expect(actual.documentationUrls).to.deep.equal(expected); + expect(actual.docs).to.deep.equal(expected); }); describe('level', () => { describe('accepts absent level', () => { diff --git a/tests/unit/domain/Script.spec.ts b/tests/unit/domain/Script.spec.ts index 6091770a..5e3043a7 100644 --- a/tests/unit/domain/Script.spec.ts +++ b/tests/unit/domain/Script.spec.ts @@ -92,15 +92,15 @@ describe('Script', () => { } }); }); - describe('documentationUrls', () => { + describe('docs', () => { it('sets as expected', () => { // arrange const expected = ['doc1', 'doc2']; // act const sut = new ScriptBuilder() - .withDocumentationUrls(expected) + .withDocs(expected) .build(); - const actual = sut.documentationUrls; + const actual = sut.docs; // assert expect(actual).to.equal(expected); }); @@ -115,7 +115,7 @@ class ScriptBuilder { private level = RecommendationLevel.Standard; - private documentationUrls: readonly string[]; + private docs: readonly string[] = undefined; public withCodes(code: string, revertCode = ''): ScriptBuilder { this.code = new ScriptCodeStub() @@ -139,8 +139,8 @@ class ScriptBuilder { return this; } - public withDocumentationUrls(urls: readonly string[]): ScriptBuilder { - this.documentationUrls = urls; + public withDocs(urls: readonly string[]): ScriptBuilder { + this.docs = urls; return this; } @@ -148,7 +148,7 @@ class ScriptBuilder { return new Script( this.name, this.code, - this.documentationUrls, + this.docs, this.level, ); } diff --git a/tests/unit/presentation/components/Scripts/View/Tree/ScriptNodeParser.spec.ts b/tests/unit/presentation/components/Scripts/View/Tree/ScriptNodeParser.spec.ts index 81bfc6b2..324836b3 100644 --- a/tests/unit/presentation/components/Scripts/View/Tree/ScriptNodeParser.spec.ts +++ b/tests/unit/presentation/components/Scripts/View/Tree/ScriptNodeParser.spec.ts @@ -90,7 +90,7 @@ function isReversible(category: ICategory): boolean { function expectSameCategory(node: INode, category: ICategory): void { expect(node.type).to.equal(NodeType.Category, getErrorMessage('type')); expect(node.id).to.equal(getCategoryNodeId(category), getErrorMessage('id')); - expect(node.documentationUrls).to.equal(category.documentationUrls, getErrorMessage('documentationUrls')); + expect(node.docs).to.equal(category.docs, getErrorMessage('docs')); expect(node.text).to.equal(category.name, getErrorMessage('name')); expect(node.isReversible).to.equal(isReversible(category), getErrorMessage('isReversible')); expect(node.children).to.have.lengthOf(category.scripts.length || category.subCategories.length, getErrorMessage('name')); @@ -110,7 +110,7 @@ function expectSameCategory(node: INode, category: ICategory): void { function expectSameScript(node: INode, script: IScript): void { expect(node.type).to.equal(NodeType.Script, getErrorMessage('type')); expect(node.id).to.equal(getScriptNodeId(script), getErrorMessage('id')); - expect(node.documentationUrls).to.equal(script.documentationUrls, getErrorMessage('documentationUrls')); + expect(node.docs).to.equal(script.docs, getErrorMessage('docs')); expect(node.text).to.equal(script.name, getErrorMessage('name')); expect(node.isReversible).to.equal(script.canRevert(), getErrorMessage('canRevert')); expect(node.children).to.equal(undefined); diff --git a/tests/unit/presentation/components/Scripts/View/Tree/SelectableTree/LiquorTree/NodeWrapper/NodePredicateFilter.spec.ts b/tests/unit/presentation/components/Scripts/View/Tree/SelectableTree/LiquorTree/NodeWrapper/NodePredicateFilter.spec.ts index fb3d1187..6975c01c 100644 --- a/tests/unit/presentation/components/Scripts/View/Tree/SelectableTree/LiquorTree/NodeWrapper/NodePredicateFilter.spec.ts +++ b/tests/unit/presentation/components/Scripts/View/Tree/SelectableTree/LiquorTree/NodeWrapper/NodePredicateFilter.spec.ts @@ -12,7 +12,7 @@ describe('NodePredicateFilter', () => { data: { text: 'script-text', type: NodeType.Script, - documentationUrls: [], + docs: [], isReversible: false, }, states: undefined, @@ -22,7 +22,7 @@ describe('NodePredicateFilter', () => { id: 'script', text: 'script-text', isReversible: false, - documentationUrls: [], + docs: [], children: [], type: NodeType.Script, }; @@ -54,7 +54,7 @@ function getExistingNode(): ILiquorTreeExistingNode { data: { text: 'script-text', type: NodeType.Script, - documentationUrls: [], + docs: [], isReversible: false, }, states: undefined, diff --git a/tests/unit/presentation/components/Scripts/View/Tree/SelectableTree/LiquorTree/NodeWrapper/NodeStateUpdater.spec.ts b/tests/unit/presentation/components/Scripts/View/Tree/SelectableTree/LiquorTree/NodeWrapper/NodeStateUpdater.spec.ts index beae7dcd..f9462903 100644 --- a/tests/unit/presentation/components/Scripts/View/Tree/SelectableTree/LiquorTree/NodeWrapper/NodeStateUpdater.spec.ts +++ b/tests/unit/presentation/components/Scripts/View/Tree/SelectableTree/LiquorTree/NodeWrapper/NodeStateUpdater.spec.ts @@ -32,16 +32,16 @@ describe('NodeStateUpdater', () => { // arrange const node = { id: '1', - data: { type: NodeType.Category, documentationUrls: [], isReversible: false }, + data: { type: NodeType.Category, docs: [], isReversible: false }, children: [ { id: '2', - data: { type: NodeType.Category, documentationUrls: [], isReversible: false }, + data: { type: NodeType.Category, docs: [], isReversible: false }, children: [getScriptNode('a'), getScriptNode('b')], }, { id: '3', - data: { type: NodeType.Category, documentationUrls: [], isReversible: false }, + data: { type: NodeType.Category, docs: [], isReversible: false }, children: [getScriptNode('c')], }, ], @@ -56,16 +56,16 @@ describe('NodeStateUpdater', () => { // arrange const node = { id: '1', - data: { type: NodeType.Category, documentationUrls: [], isReversible: false }, + data: { type: NodeType.Category, docs: [], isReversible: false }, children: [ { id: '2', - data: { type: NodeType.Category, documentationUrls: [], isReversible: false }, + data: { type: NodeType.Category, docs: [], isReversible: false }, children: [getScriptNode('a'), getScriptNode('b')], }, { id: '3', - data: { type: NodeType.Category, documentationUrls: [], isReversible: false }, + data: { type: NodeType.Category, docs: [], isReversible: false }, children: [getScriptNode('c')], }, ], @@ -80,16 +80,16 @@ describe('NodeStateUpdater', () => { // arrange const node = { id: '1', - data: { type: NodeType.Category, documentationUrls: [], isReversible: false }, + data: { type: NodeType.Category, docs: [], isReversible: false }, children: [ { id: '2', - data: { type: NodeType.Category, documentationUrls: [], isReversible: false }, + data: { type: NodeType.Category, docs: [], isReversible: false }, children: [getScriptNode('a'), getScriptNode('b')], }, { id: '3', - data: { type: NodeType.Category, documentationUrls: [], isReversible: false }, + data: { type: NodeType.Category, docs: [], isReversible: false }, children: [getScriptNode('c')], }, ], @@ -128,16 +128,16 @@ describe('NodeStateUpdater', () => { // arrange const node = { id: '1', - data: { type: NodeType.Category, documentationUrls: [], isReversible: false }, + data: { type: NodeType.Category, docs: [], isReversible: false }, children: [ { id: '2', - data: { type: NodeType.Category, documentationUrls: [], isReversible: false }, + data: { type: NodeType.Category, docs: [], isReversible: false }, children: [getScriptNode('a'), getScriptNode('b')], }, { id: '3', - data: { type: NodeType.Category, documentationUrls: [], isReversible: false }, + data: { type: NodeType.Category, docs: [], isReversible: false }, children: [getScriptNode('c')], }, ], @@ -152,16 +152,16 @@ describe('NodeStateUpdater', () => { // arrange const node = { id: '1', - data: { type: NodeType.Category, documentationUrls: [], isReversible: false }, + data: { type: NodeType.Category, docs: [], isReversible: false }, children: [ { id: '2', - data: { type: NodeType.Category, documentationUrls: [], isReversible: false }, + data: { type: NodeType.Category, docs: [], isReversible: false }, children: [getScriptNode('a'), getScriptNode('b')], }, { id: '3', - data: { type: NodeType.Category, documentationUrls: [], isReversible: false }, + data: { type: NodeType.Category, docs: [], isReversible: false }, children: [getScriptNode('c')], }, ], @@ -176,16 +176,16 @@ describe('NodeStateUpdater', () => { // arrange const node = { id: '1', - data: { type: NodeType.Category, documentationUrls: [], isReversible: false }, + data: { type: NodeType.Category, docs: [], isReversible: false }, children: [ { id: '2', - data: { type: NodeType.Category, documentationUrls: [], isReversible: false }, + data: { type: NodeType.Category, docs: [], isReversible: false }, children: [getScriptNode('a'), getScriptNode('b')], }, { id: '3', - data: { type: NodeType.Category, documentationUrls: [], isReversible: false }, + data: { type: NodeType.Category, docs: [], isReversible: false }, children: [getScriptNode('c')], }, ], @@ -204,7 +204,7 @@ describe('NodeStateUpdater', () => { id: scriptNodeId, data: { type: NodeType.Script, - documentationUrls: [], + docs: [], isReversible: false, }, children: [], diff --git a/tests/unit/presentation/components/Scripts/View/Tree/SelectableTree/LiquorTree/NodeWrapper/NodeTranslator.spec.ts b/tests/unit/presentation/components/Scripts/View/Tree/SelectableTree/LiquorTree/NodeWrapper/NodeTranslator.spec.ts index d71c5411..d5193aad 100644 --- a/tests/unit/presentation/components/Scripts/View/Tree/SelectableTree/LiquorTree/NodeWrapper/NodeTranslator.spec.ts +++ b/tests/unit/presentation/components/Scripts/View/Tree/SelectableTree/LiquorTree/NodeWrapper/NodeTranslator.spec.ts @@ -33,20 +33,20 @@ function getNode(): INode { text: 'parentcategory', isReversible: true, type: NodeType.Category, - documentationUrls: ['parentcategory-url1', 'parentcategory-url2'], + docs: ['parentcategory-doc1', 'parentcategory-doc2'], children: [ { id: '2', text: 'subcategory', isReversible: true, - documentationUrls: ['subcategory-url1', 'subcategory-url2'], + docs: ['subcategory-doc1', 'subcategory-doc2'], type: NodeType.Category, children: [ { id: 'script1', text: 'cool script 1', isReversible: true, - documentationUrls: ['script1url1', 'script1url2'], + docs: ['script1-doc1', 'script1-doc2'], children: [], type: NodeType.Script, }, @@ -54,7 +54,7 @@ function getNode(): INode { id: 'script2', text: 'cool script 2', isReversible: true, - documentationUrls: ['script2url1', 'script2url2'], + docs: ['script2-doc1', 'script2-doc2'], children: [], type: NodeType.Script, }], @@ -66,7 +66,7 @@ function getExpectedExistingNodeData(node: INode): ILiquorTreeNodeData { return { text: node.text, type: node.type, - documentationUrls: node.documentationUrls, + docs: node.docs, isReversible: node.isReversible, }; } @@ -74,7 +74,7 @@ function getExpectedExistingNodeData(node: INode): ILiquorTreeNodeData { function getExpectedNewNodeData(node: INode): ICustomLiquorTreeData { return { type: node.type, - documentationUrls: node.documentationUrls, + docs: node.docs, isReversible: node.isReversible, }; } diff --git a/tests/unit/presentation/components/Scripts/View/Tree/SelectableTree/Node/Documentation/MarkdownRenderer.spec.ts b/tests/unit/presentation/components/Scripts/View/Tree/SelectableTree/Node/Documentation/MarkdownRenderer.spec.ts new file mode 100644 index 00000000..d6169c44 --- /dev/null +++ b/tests/unit/presentation/components/Scripts/View/Tree/SelectableTree/Node/Documentation/MarkdownRenderer.spec.ts @@ -0,0 +1,69 @@ +import 'mocha'; +import { expect } from 'chai'; +import { createRenderer } from '@/presentation/components/Scripts/View/ScriptsTree/SelectableTree/Node/Documentation/MarkdownRenderer'; + +describe('MarkdownRenderer', () => { + describe('createRenderer', () => { + it('can create', () => { + // arrange & act + const renderer = createRenderer(); + // assert + expect(renderer !== undefined); + }); + it('opens URLs in new tab', () => { + // arrange + const renderer = createRenderer(); + const markdown = '[undergroundwires.dev](https://undergroundwires.dev)'; + // act + const htmlString = renderer.render(markdown); + // assert + const html = parseHtml(htmlString); + const aElement = html.getElementsByTagName('a')[0]; + const href = aElement.getAttribute('target'); + expect(href).to.equal('_blank'); + }); + it('does not convert single linebreak to
', () => { + // arrange + const renderer = createRenderer(); + const markdown = 'Text with\nSingle\nLinebreaks'; + // act + const htmlString = renderer.render(markdown); + // assert + const html = parseHtml(htmlString); + const totalBrElements = html.getElementsByTagName('br').length; + expect(totalBrElements).to.equal(0); + }); + it('creates links for plain URL', () => { + // arrange + const renderer = createRenderer(); + const expectedUrl = 'https://privacy.sexy/'; + const markdown = `Visit ${expectedUrl} now!`; + // act + const htmlString = renderer.render(markdown); + // assert + const html = parseHtml(htmlString); + const aElement = html.getElementsByTagName('a')[0]; + const href = aElement.getAttribute('href'); + expect(href).to.equal(expectedUrl); + }); + it('it generates beautiful labels for auto-linkified URL', () => { + // arrange + const renderer = createRenderer(); + const url = 'https://privacy.sexy'; + const expectedText = 'privacy.sexy'; + const markdown = `Visit ${url} now!`; + // act + const htmlString = renderer.render(markdown); + // assert + const html = parseHtml(htmlString); + const aElement = html.getElementsByTagName('a')[0]; + expect(aElement.text).to.equal(expectedText); + }); + }); +}); + +function parseHtml(htmlString: string): Document { + const parser = new window.DOMParser(); + const htmlDoc = parser.parseFromString(htmlString, 'text/html'); + return htmlDoc; +} diff --git a/tests/unit/presentation/components/Scripts/View/Tree/SelectableTree/Node/Reverter/ReverterFactory.spec.ts b/tests/unit/presentation/components/Scripts/View/Tree/SelectableTree/Node/Reverter/ReverterFactory.spec.ts index aa5e73f4..c521d295 100644 --- a/tests/unit/presentation/components/Scripts/View/Tree/SelectableTree/Node/Reverter/ReverterFactory.spec.ts +++ b/tests/unit/presentation/components/Scripts/View/Tree/SelectableTree/Node/Reverter/ReverterFactory.spec.ts @@ -39,7 +39,7 @@ describe('ReverterFactory', () => { id: nodeId, text: 'text', isReversible: false, - documentationUrls: [], + docs: [], children: [], type, }; diff --git a/tests/unit/shared/Stubs/CategoryDataStub.ts b/tests/unit/shared/Stubs/CategoryDataStub.ts index 03f71541..6b7e6f6e 100644 --- a/tests/unit/shared/Stubs/CategoryDataStub.ts +++ b/tests/unit/shared/Stubs/CategoryDataStub.ts @@ -1,4 +1,4 @@ -import type { CategoryData, CategoryOrScriptData, DocumentationUrlsData } from '@/application/collections/'; +import type { CategoryData, CategoryOrScriptData, DocumentationData } from '@/application/collections/'; import { ScriptDataStub } from './ScriptDataStub'; export class CategoryDataStub implements CategoryData { @@ -6,7 +6,7 @@ export class CategoryDataStub implements CategoryData { public category = 'category name'; - public docs?: DocumentationUrlsData; + public docs?: DocumentationData; public withChildren(children: readonly CategoryOrScriptData[]) { this.children = children; @@ -18,7 +18,7 @@ export class CategoryDataStub implements CategoryData { return this; } - public withDocs(docs: DocumentationUrlsData) { + public withDocs(docs: DocumentationData) { this.docs = docs; return this; } diff --git a/tests/unit/shared/Stubs/CategoryStub.ts b/tests/unit/shared/Stubs/CategoryStub.ts index 7ef88fd1..8a931c44 100644 --- a/tests/unit/shared/Stubs/CategoryStub.ts +++ b/tests/unit/shared/Stubs/CategoryStub.ts @@ -9,7 +9,7 @@ export class CategoryStub extends BaseEntity implements ICategory { public readonly scripts = new Array(); - public readonly documentationUrls = new Array(); + public readonly docs = new Array(); public constructor(id: number) { super(id); diff --git a/tests/unit/shared/Stubs/ScriptDataStub.ts b/tests/unit/shared/Stubs/ScriptDataStub.ts index 723acc2a..b4edebe5 100644 --- a/tests/unit/shared/Stubs/ScriptDataStub.ts +++ b/tests/unit/shared/Stubs/ScriptDataStub.ts @@ -33,7 +33,7 @@ export class ScriptDataStub implements ScriptData { public recommend = RecommendationLevel[RecommendationLevel.Standard].toLowerCase(); - public docs = ['hello.com']; + public docs?: readonly string[] = ['hello.com']; private constructor() { /* use static methods for constructing */ } @@ -42,7 +42,7 @@ export class ScriptDataStub implements ScriptData { return this; } - public withDocs(docs: string[]): ScriptDataStub { + public withDocs(docs: readonly string[]): ScriptDataStub { this.docs = docs; return this; } diff --git a/tests/unit/shared/Stubs/ScriptStub.ts b/tests/unit/shared/Stubs/ScriptStub.ts index e2cb8c0f..6eab8de8 100644 --- a/tests/unit/shared/Stubs/ScriptStub.ts +++ b/tests/unit/shared/Stubs/ScriptStub.ts @@ -11,7 +11,7 @@ export class ScriptStub extends BaseEntity implements IScript { revert: `REM revert-code (${this.id})`, }; - public readonly documentationUrls = new Array(); + public readonly docs = new Array(); public level? = RecommendationLevel.Standard;