diff --git a/package-lock.json b/package-lock.json index 6fc9545d..de999e38 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,19 @@ "version": "7.6.0", "license": "Apache-2.0", "dependencies": { - "imgix-url-builder": "^0.0.4" + "hast": "^1.0.0", + "hast-util-select": "^6.0.2", + "hast-util-to-html": "^9.0.1", + "imgix-url-builder": "^0.0.4", + "mdast-util-to-string": "^4.0.0", + "rehype-minify-whitespace": "^6.0.0", + "rehype-parse": "^9.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.0", + "unified": "^11.0.5", + "unist-util-remove": "^4.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.2" }, "devDependencies": { "@prismicio/mock": "^0.3.1", @@ -1192,7 +1204,6 @@ "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.9.tgz", "integrity": "sha512-8Hz50m2eoS56ldRlepxSBa6PWEVCtzUo/92HgLc2qTMnotJNIm7xP+UZhyWoYsyOdd5dxZ+NZLb24rsKyFs2ow==", - "dev": true, "dependencies": { "@types/ms": "*" } @@ -1203,6 +1214,15 @@ "integrity": "sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==", "dev": true }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -1239,8 +1259,7 @@ "node_modules/@types/ms": { "version": "0.7.32", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.32.tgz", - "integrity": "sha512-xPSg0jm4mqgEkNhowKgZFBNtwoEwF6gJ4Dhww+GFpm3IgtNseHQZ5IqdNwnquZEoANxyDAKDRAdVo4Z72VvD/g==", - "dev": true + "integrity": "sha512-xPSg0jm4mqgEkNhowKgZFBNtwoEwF6gJ4Dhww+GFpm3IgtNseHQZ5IqdNwnquZEoANxyDAKDRAdVo4Z72VvD/g==" }, "node_modules/@types/node": { "version": "20.8.2", @@ -1272,8 +1291,7 @@ "node_modules/@types/unist": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.8.tgz", - "integrity": "sha512-d0XxK3YTObnWVp6rZuev3c49+j4Lo8g4L1ZRm9z5L0xpoZycUPshHgczK5gsUMaZOstjVYYi09p5gYvUtfChYw==", - "dev": true + "integrity": "sha512-d0XxK3YTObnWVp6rZuev3c49+j4Lo8g4L1ZRm9z5L0xpoZycUPshHgczK5gsUMaZOstjVYYi09p5gYvUtfChYw==" }, "node_modules/@types/uuid": { "version": "9.0.4", @@ -1471,6 +1489,12 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "license": "ISC" + }, "node_modules/@vitest/coverage-v8": { "version": "0.34.6", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-0.34.6.tgz", @@ -1783,6 +1807,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1809,6 +1843,16 @@ } ] }, + "node_modules/bcp-47-match": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", + "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/big-integer": { "version": "1.6.51", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", @@ -1844,6 +1888,12 @@ "readable-stream": "^3.4.0" } }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, "node_modules/bplist-parser": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", @@ -2010,6 +2060,16 @@ "upper-case-first": "^2.0.2" } }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chai": { "version": "4.3.10", "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz", @@ -2066,7 +2126,26 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", - "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -2250,6 +2329,16 @@ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/comment-parser": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.0.tgz", @@ -2597,6 +2686,22 @@ "node": ">= 8" } }, + "node_modules/css-selector-parser": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-3.0.5.tgz", + "integrity": "sha512-3itoDFbKUNx1eKmVpYMFyqKX04Ww9osZ+dLgrk6GEv6KMVeXUhUnp4I5X+evw+u3ZxVU6RFXSSRxlTeMh8bA+g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, "node_modules/dargs": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", @@ -2628,7 +2733,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -2679,7 +2783,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", - "dev": true, "dependencies": { "character-entities": "^2.0.0" }, @@ -2774,20 +2877,10 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, "engines": { "node": ">=6" } }, - "node_modules/detect-indent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", - "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -2797,6 +2890,19 @@ "node": ">=8" } }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/diff": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", @@ -2827,6 +2933,19 @@ "node": ">=8" } }, + "node_modules/direction": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz", + "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==", + "license": "MIT", + "bin": { + "direction": "cli.js" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -2941,6 +3060,18 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -3380,6 +3511,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -4059,6 +4196,267 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hast": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hast/-/hast-1.0.0.tgz", + "integrity": "sha512-vFUqlRV5C+xqP76Wwq2SrM0kipnmpxJm7OfvVXpB35Fp+Fn4MV+ozr+JZr5qFvyR1q/U+Foim2x+3P+x9S1PLA==", + "deprecated": "Renamed to rehype", + "license": "MIT" + }, + "node_modules/hast-util-embedded": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-embedded/-/hast-util-embedded-3.0.0.tgz", + "integrity": "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.1.tgz", + "integrity": "sha512-RXQBLMl9kjKVNkJTIO6bZyb2n+cUH8LFaSSzo82jiLT6Tfc+Pt7VQCS+/h3YwG4jaNE2TA2sdJisGWR+aJrp0g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz", + "integrity": "sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^8.0.0", + "property-information": "^6.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==", + "license": "MIT" + }, + "node_modules/hast-util-has-property": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", + "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.0.4.tgz", + "integrity": "sha512-LHE65TD2YiNsHD3YuXcKPHXPLuYh/gjp12mOfU8jxSrm1f/yJpsb0F/KKljS6U9LJoP0Ux+tCe8iJ2AsPzTdgA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==", + "license": "MIT" + }, + "node_modules/hast-util-select": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-6.0.2.tgz", + "integrity": "sha512-hT/SD/d/Meu+iobvgkffo1QecV8WeKWxwsNMzcTJsKw1cKTQKSR/7ArJeURLNJF9HDjp9nVoORyNNJxrvBye8Q==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "bcp-47-match": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "css-selector-parser": "^3.0.0", + "devlop": "^1.0.0", + "direction": "^2.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-to-string": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "not": "^0.1.0", + "nth-check": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-select/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==", + "license": "MIT" + }, + "node_modules/hast-util-to-html": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.1.tgz", + "integrity": "sha512-hZOofyZANbyWo+9RP75xIDV/gq+OUKx+T46IlwERnKmfpwp81XBFbT9mi26ws+SJchA4RVUQwIBJpqEOBhMzEQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-raw": "^9.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==", + "license": "MIT" + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", + "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.0.tgz", + "integrity": "sha512-OGkAxX1Ua3cbcW6EJ5pT/tslVb90uViVkcJ4ZZIMW/R33DX/AkcJcRrPebPwJkHYwlDHXz4aIwvAAaAdtrACFA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-8.0.0.tgz", + "integrity": "sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/header-case": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", @@ -4093,6 +4491,16 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/human-signals": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", @@ -5116,11 +5524,12 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-to-string": { + "node_modules/mdast-util-from-markdown/node_modules/mdast-util-to-string": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", "dev": true, + "license": "MIT", "dependencies": { "@types/mdast": "^3.0.0" }, @@ -5129,58 +5538,199 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/meow": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", - "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", - "dev": true, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "license": "MIT", "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - }, - "engines": { - "node": ">=10" + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/meow/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, + "node_modules/mdast-util-to-hast/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" + "@types/unist": "*" } }, - "node_modules/meow/node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "node_modules/meow/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, + "node_modules/mdast-util-to-hast/node_modules/micromark-util-character": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-to-hast/node_modules/micromark-util-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz", + "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-to-hast/node_modules/micromark-util-sanitize-uri": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz", + "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/mdast-util-to-hast/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-to-hast/node_modules/micromark-util-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz", + "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/meow": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", + "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", + "dev": true, + "dependencies": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/meow/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/meow/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, "engines": { "node": ">=8" } @@ -5878,8 +6428,7 @@ "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==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/msw": { "version": "1.3.2", @@ -6143,6 +6692,11 @@ "node": ">=0.10.0" } }, + "node_modules/not": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/not/-/not-0.1.0.tgz", + "integrity": "sha512-5PDmaAsVfnWUgTUbJ3ERwn7u79Z0dYxN9ErxCpVJJqe2RK0PJ3z+iFUxuqjwtlDDegXvtWoxD/3Fzxox7tFGWA==" + }, "node_modules/npm-run-path": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", @@ -6170,6 +6724,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -6411,6 +6977,18 @@ "node": ">=4" } }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "license": "MIT", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/pascal-case": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", @@ -6645,6 +7223,16 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, + "node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", @@ -6823,61 +7411,642 @@ "node": ">=4" } }, - "node_modules/read-pkg/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", - "dev": true, - "engines": { - "node": ">=4" + "node_modules/read-pkg/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/redent/node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/rehype-minify-whitespace": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/rehype-minify-whitespace/-/rehype-minify-whitespace-6.0.0.tgz", + "integrity": "sha512-i9It4YHR0Sf3GsnlR5jFUKXRr9oayvEk9GKQUkwZv6hs70OH9q3OCZrq9PpLvIGKt3W+JxBOxCidNVpH/6rWdA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-parse": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.0.tgz", + "integrity": "sha512-WG7nfvmWWkCR++KEkZevZb/uw41E8TsH4DsY9UxsTbIXCVGbAs4S+r8FrQ+OtH5EEQAs+5UxKC42VinkmpA1Yw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-html": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/remark-parse/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==", + "license": "MIT" + }, + "node_modules/remark-parse/node_modules/mdast-util-from-markdown": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.1.tgz", + "integrity": "sha512-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse/node_modules/micromark": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz", + "integrity": "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-core-commonmark": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.1.tgz", + "integrity": "sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-factory-destination": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz", + "integrity": "sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-factory-label": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.0.tgz", + "integrity": "sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-factory-space": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", + "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-factory-title": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.0.tgz", + "integrity": "sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-factory-whitespace": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.0.tgz", + "integrity": "sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-util-character": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-util-chunked": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.0.tgz", + "integrity": "sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-util-classify-character": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.0.tgz", + "integrity": "sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-util-combine-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.0.tgz", + "integrity": "sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.1.tgz", + "integrity": "sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-util-decode-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.0.tgz", + "integrity": "sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-util-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz", + "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/remark-parse/node_modules/micromark-util-html-tag-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.0.tgz", + "integrity": "sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/remark-parse/node_modules/micromark-util-normalize-identifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.0.tgz", + "integrity": "sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-util-resolve-all": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.0.tgz", + "integrity": "sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-util-sanitize-uri": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz", + "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/remark-parse/node_modules/micromark-util-subtokenize": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.1.tgz", + "integrity": "sha512-jZNtiFl/1aY73yS3UGQkutD0UbhTt68qnRpw2Pifmz5wV9h8gOVsN70v+Lq/f1rKaU/W8pxRe8y8Q9FX1AOe1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/read-pkg/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } + "node_modules/remark-parse/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, + "node_modules/remark-parse/node_modules/micromark-util-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz", + "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/remark-parse/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "@types/unist": "^3.0.0" }, - "engines": { - "node": ">= 6" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, + "node_modules/remark-rehype": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.0.tgz", + "integrity": "sha512-z3tJrAs2kIs1AqIIy6pzHmAHlF1hWQ+OdY4/hv+Wxe35EhyLKcajL33iUEn3ScxtFox9nUvRufR/Zre8Q08H/g==", + "license": "MIT", "dependencies": { - "picomatch": "^2.2.1" + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" }, - "engines": { - "node": ">=8.10.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, + "node_modules/remark-rehype/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">=8" + "@types/unist": "*" } }, "node_modules/require-directory": { @@ -7253,6 +8422,16 @@ "deprecated": "Please use @jridgewell/sourcemap-codec instead", "dev": true }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -7384,6 +8563,16 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/standard-version/node_modules/detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/standard-version/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -7454,6 +8643,20 @@ "node": ">=8" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/stringify-package": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/stringify-package/-/stringify-package-1.0.1.tgz", @@ -7494,18 +8697,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -7689,6 +8880,16 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "dev": true }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", @@ -7698,6 +8899,16 @@ "node": ">=8" } }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ts-api-utils": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", @@ -7793,6 +9004,102 @@ "node": ">=0.8.0" } }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unified/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==", + "license": "MIT" + }, + "node_modules/unified/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==", + "license": "MIT" + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==", + "license": "MIT" + }, + "node_modules/unist-util-remove": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove/-/unist-util-remove-4.0.0.tgz", + "integrity": "sha512-b4gokeGId57UVRX/eVKej5gXqGlc9+trkORhFJpu9raqZkZhU0zm8Doi05+HaiBsMEIJowL+2WtQ5ItjsngPXg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==", + "license": "MIT" + }, "node_modules/unist-util-stringify-position": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", @@ -7806,6 +9113,47 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==", + "license": "MIT" + }, + "node_modules/unist-util-visit/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==", + "license": "MIT" + }, "node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -7926,6 +9274,93 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/vfile": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.2.tgz", + "integrity": "sha512-zND7NlS8rJYb/sPqkb13ZvbbUoExdbi4w3SfRrMq6R3FvnLQmmfpajJNITuuYm6AZ5uao9vy4BAos3EXBPf2rg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.2.tgz", + "integrity": "sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==", + "license": "MIT" + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==", + "license": "MIT" + }, + "node_modules/vfile-message/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile/node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==", + "license": "MIT" + }, + "node_modules/vfile/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vite": { "version": "4.4.11", "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.11.tgz", @@ -8547,6 +9982,16 @@ "@zxing/text-encoding": "0.9.0" } }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/web-streams-polyfill": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", @@ -8752,6 +10197,16 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/package.json b/package.json index 052679f2..cfc4a7b4 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,19 @@ "test": "npm run lint && npm run types && npm run unit && npm run build && npm run size" }, "dependencies": { - "imgix-url-builder": "^0.0.4" + "hast": "^1.0.0", + "hast-util-select": "^6.0.2", + "hast-util-to-html": "^9.0.1", + "imgix-url-builder": "^0.0.4", + "mdast-util-to-string": "^4.0.0", + "rehype-minify-whitespace": "^6.0.0", + "rehype-parse": "^9.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.0", + "unified": "^11.0.5", + "unist-util-remove": "^4.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.2" }, "devDependencies": { "@prismicio/mock": "^0.3.1", diff --git a/src/errors/PrismicRichTextError.ts b/src/errors/PrismicRichTextError.ts new file mode 100644 index 00000000..06165244 --- /dev/null +++ b/src/errors/PrismicRichTextError.ts @@ -0,0 +1 @@ +export class PrismicRichTextError extends Error {} diff --git a/src/errors/PrismicRichTextSerializerError.ts b/src/errors/PrismicRichTextSerializerError.ts new file mode 100644 index 00000000..fc67c3a8 --- /dev/null +++ b/src/errors/PrismicRichTextSerializerError.ts @@ -0,0 +1,3 @@ +import { PrismicRichTextError } from "./PrismicRichTextError"; + +export class PrismicRichTextSerializerError extends PrismicRichTextError {} diff --git a/src/helpers/asHTML.ts b/src/helpers/asHTML.ts index def12591..28a62d95 100644 --- a/src/helpers/asHTML.ts +++ b/src/helpers/asHTML.ts @@ -168,7 +168,7 @@ const createHTMLRichTextSerializer = ( return ( ( nodeSerializerOrShorthand as HTMLStrictRichTextMapSerializer[BlockType] - )(payload) || defaultWithShorthand(payload) + )?.(payload) || defaultWithShorthand(payload) ); }) as NonNullable; } diff --git a/src/helpers/unstable_htmlAsRichText.ts b/src/helpers/unstable_htmlAsRichText.ts new file mode 100644 index 00000000..019a7662 --- /dev/null +++ b/src/helpers/unstable_htmlAsRichText.ts @@ -0,0 +1,121 @@ +import { Element } from "hast"; +import rehypeParse from "rehype-parse"; +import { unified } from "unified"; + +import { RTPartialInlineNode } from "../lib/RichTextFieldBuilder"; +import { RehypeRichTextConfig, rehypeRichText } from "../lib/rehypeRichText"; + +import { + RTInlineNode, + RTNode, + RichTextField, + RichTextNodeTypes, +} from "../types/value/richText"; + +// Used for TSDocs only. +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import type { HTMLRichTextMapSerializer, asHTML } from "./asHTML"; + +/** + * A shorthand definition for {@link RichTextHTMLMapSerializer} rich text node + * types. + * + * @remarks + * The `label` rich text node type is not available as is. Use an object + * containing your label name to convert to label nodes instead. For example: + * `u: { label: "underline" }` + * @remarks + * The `span` rich text node type is not available as it is not relevant in the + * context of going from HTML to Prismic rich text. + */ +export type RichTextHTMLMapSerializerShorthand = + | Exclude + | { label: string }; + +/** + * The payload provided to a {@link RichTextHTMLMapSerializerFunction}. + */ +type RichTextHTMLMapSerializerFunctionPayload = { + /** + * The hast {@link Element} node to serialize. + */ + node: Element; + + /** + * Additional context information to help with the serialization. + */ + context: { + /** + * The list type of the last list node encountered if any. + */ + listType?: "group-list-item" | "group-o-list-item"; + }; +}; + +/** + * Serializes a hast {@link Element} node to a + * {@link RichTextHTMLMapSerializerShorthand} or a rich text node. + * + * @remarks + * Serializing to a rich text node directly is not recommended and is only + * available as an escape hatch. Prefer returning a + * {@link RichTextHTMLMapSerializerShorthand} instead. + */ +export type RichTextHTMLMapSerializerFunction = ( + payload: RichTextHTMLMapSerializerFunctionPayload, +) => + | RichTextHTMLMapSerializerShorthand + | RTNode + | RTInlineNode + | RTPartialInlineNode + | undefined; + +/** + * Serializes a hast {@link Element} node matching the given HTML tag name or CSS + * selector to a Prismic rich text node. + * + * @remarks + * This serializer is used to serialize HTML to Prismic rich text. When + * serializing Prismic rich text to HTML with {@link asHTML}, use the + * {@link HTMLRichTextMapSerializer} type instead. + */ +export type RichTextHTMLMapSerializer = Record< + string, + RichTextHTMLMapSerializerShorthand | RichTextHTMLMapSerializerFunction +>; + +/** + * Configuration that determines the output of {@link htmlAsRichText}. + */ +export type HTMLAsRichTextConfig = RehypeRichTextConfig; + +/** + * The return type of {@link htmlAsRichText}. + */ +type HTMLAsRichTextReturnType = { + result: RichTextField; + warnings: string[]; +}; + +/** + * Converts an HTML string to a rich text field. + * + * @param html - An HTML string. + * @param config - Configuration that determines the output the function. + * + * @returns `html` as rich text. + * + * @experimental Names and implementations may change in the future. + * `unstable_htmlAsRichText()` does not follow SemVer. + */ +export const unstable_htmlAsRichText = ( + html: string, + config?: HTMLAsRichTextConfig, +): HTMLAsRichTextReturnType => { + const { result, messages } = unified() + .use(rehypeParse, { emitParseErrors: true, missingDoctype: 0 }) + .use(rehypeRichText, config) + .processSync(html); + + return { result, warnings: messages.map((message) => message.toString()) }; +}; diff --git a/src/helpers/unstable_markdownAsRichText.ts b/src/helpers/unstable_markdownAsRichText.ts new file mode 100644 index 00000000..66eff1ef --- /dev/null +++ b/src/helpers/unstable_markdownAsRichText.ts @@ -0,0 +1,52 @@ +import remarkParse from "remark-parse"; +import remarkRehype from "remark-rehype"; +import { unified } from "unified"; + +import { RehypeRichTextConfig, rehypeRichText } from "../lib/rehypeRichText"; + +import { RichTextField } from "../types/value/richText"; + +// Used for TSDocs only. +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import type { unstable_htmlAsRichText } from "./unstable_htmlAsRichText"; + +/** + * Configuration that determines the output of {@link markdownAsRichText}. + */ +export type MarkdownAsRichTextConfig = RehypeRichTextConfig; + +/** + * The return type of {@link markdownAsRichText}. + */ +type MarkdownAsRichTextReturnType = { + result: RichTextField; + warnings: string[]; +}; + +/** + * Converts a markdown string to a rich text field. + * + * @remarks + * To convert markdown to a rich text field, this function first converts it to + * HTML. It's essentially a sugar above {@link unstable_htmlAsRichText}. + * + * @param markdown - A markdown string. + * @param config - Configuration that determines the output of the function. + * + * @returns `markdown` as rich text. + * + * @experimental Names and implementations may change in the future. + * `unstable_markdownAsRichText()` does not follow SemVer. + */ +export const unstable_markdownAsRichText = ( + markdown: string, + config?: MarkdownAsRichTextConfig, +): MarkdownAsRichTextReturnType => { + const { result, messages } = unified() + .use(remarkParse) + .use(remarkRehype) + .use(rehypeRichText, config) + .processSync(markdown); + + return { result, warnings: messages.map((message) => message.toString()) }; +}; diff --git a/src/index.ts b/src/index.ts index 49658725..6f622942 100644 --- a/src/index.ts +++ b/src/index.ts @@ -80,6 +80,8 @@ export { mapSliceZone, unstable_mapSliceZone }; // Conversion helper. export { documentToLinkField } from "./helpers/documentToLinkField"; +export { unstable_htmlAsRichText } from "./helpers/unstable_htmlAsRichText"; +export { unstable_markdownAsRichText } from "./helpers/unstable_markdownAsRichText"; export type { LinkResolverFunction } from "./helpers/asLink"; export type { AsLinkAttrsConfig } from "./helpers/asLinkAttrs"; @@ -101,10 +103,13 @@ export type { }; export type { HTMLRichTextSerializer } from "./helpers/asHTML"; +export type { RichTextHTMLMapSerializer } from "./helpers/unstable_htmlAsRichText"; + //============================================================================= // Errors - Custom errors for Prismic APIs. //============================================================================= +// Client Errors export { PrismicError } from "./errors/PrismicError"; export { ForbiddenError } from "./errors/ForbiddenError"; export { NotFoundError } from "./errors/NotFoundError"; @@ -114,6 +119,10 @@ export { PreviewTokenExpiredError } from "./errors/PreviewTokenExpired"; export { ParsingError } from "./errors/ParsingError"; export { RepositoryNotFoundError } from "./errors/RepositoryNotFoundError"; +// Rich Text Errors +export { PrismicRichTextError } from "./errors/PrismicRichTextError"; +export { PrismicRichTextSerializerError } from "./errors/PrismicRichTextSerializerError"; + //============================================================================= // Types - Types representing Prismic content, models, and API payloads. //============================================================================= @@ -171,6 +180,7 @@ export type { RTBlockNode, RTInlineNode, RTAnyNode, + RichTextNodeTypes, } from "./types/value/richText"; export type { TitleField } from "./types/value/title"; diff --git a/src/lib/RichTextFieldBuilder.ts b/src/lib/RichTextFieldBuilder.ts new file mode 100644 index 00000000..d0ce1bbf --- /dev/null +++ b/src/lib/RichTextFieldBuilder.ts @@ -0,0 +1,164 @@ +import { + RTInlineNode, + RTLinkNode, + RTNode, + RTTextNode, + RichTextField, +} from "../types/value/richText"; + +import { PrismicRichTextError } from "../errors/PrismicRichTextError"; + +/** + * Omits keys from a type, distributing the operation over a union. + * + * Taken from the `type-fest` package. + * + * @see https://github.com/sindresorhus/type-fest/blob/8a45ba048767aaffcebc7d190172d814a739feb0/source/distributed-omit.d.ts + */ +type DistributedOmit< + ObjectType, + KeyType extends keyof ObjectType, +> = ObjectType extends unknown ? Omit : never; + +/** + * An inline node without its `start` and `end` properties. + */ +export type RTPartialInlineNode = DistributedOmit< + RTInlineNode, + "start" | "end" +>; + +export class RichTextFieldBuilder { + #nodes: RTNode[] = []; + + get #last(): RTNode | undefined { + return this.#nodes[this.#nodes.length - 1]; + } + + appendNode(node: RTNode): void { + this.cleanupLast(); + + this.#nodes.push(node); + } + + appendTextNode( + type: RTTextNode["type"], + direction: "ltr" | "rtl" = "ltr", + ): void { + this.appendNode({ + type, + text: "", + spans: [], + direction, + }); + } + + appendSpan(span: RTInlineNode): void; + appendSpan(partialSpan: RTPartialInlineNode, length: number): void; + appendSpan( + spanOrPartialSpan: RTInlineNode | RTPartialInlineNode, + length?: number, + ): void { + if (!this.isTextNode(this.#last)) { + throw new PrismicRichTextError("Cannot add span to non-text last node"); + } + + let span: RTInlineNode; + if ("start" in spanOrPartialSpan && "end" in spanOrPartialSpan) { + span = spanOrPartialSpan; + } else { + const lastLength = this.#last.text.length; + + span = { + ...spanOrPartialSpan, + start: lastLength, + end: lastLength + length!, + }; + } + + let lastIdenticalSpanIndex = -1; + for (let i = this.#last.spans.length - 1; i >= 0; i--) { + const lastSpan = this.#last.spans[i]; + + if (span.type === "strong" || span.type === "em") { + if (lastSpan.type === span.type) { + lastIdenticalSpanIndex = i; + break; + } + } else if (span.type === "label") { + if ( + lastSpan.type === "label" && + lastSpan.data.label === span.data.label + ) { + lastIdenticalSpanIndex = i; + break; + } + } else if (span.type === "hyperlink") { + if (lastSpan.type === "hyperlink") { + // Check that all data fields are the same. Since it's not a + // deep equal operation, this doesn't guarantee a perfect + // match with content relationship. + const isSameLink = ( + Object.entries(span.data) as [keyof RTLinkNode["data"], unknown][] + ).every(([key, value]) => lastSpan.data[key] === value); + + if (isSameLink) { + lastIdenticalSpanIndex = i; + break; + } + } + } + } + + // Prefer to extend the last span of the same type end + // position if it ends at the start of the new span. + if (this.#last.spans[lastIdenticalSpanIndex]?.end === span.start) { + this.#last.spans[lastIdenticalSpanIndex].end = span.end; + } else { + this.#last.spans.push(span); + } + } + + appendText(text: string): void { + if (!this.isTextNode(this.#last)) { + throw new PrismicRichTextError( + "Cannot append text to non-text last node", + ); + } + + if (this.#last.text) { + this.#last.text += text; + } else { + this.#last.text = text.trimStart(); + } + } + + build(): RichTextField { + // Ensure that the last text node is trimmed. + this.cleanupLast(); + + // Because `RichTextField` is defined as a non-empty + // array, we have to cast `RTNode[]` to `RichTextField`. + return this.#nodes as RichTextField; + } + + /** + * Cleans up the current last node. + */ + private cleanupLast(): void { + if (this.isTextNode(this.#last)) { + this.#last.text = this.#last.text.trimEnd(); + } + } + + /** + * Determines if a node is a text node. + * + * @param node - rich text node to check. + * + * @returns `true` if `node` is a text node, `false` otherwise. + */ + private isTextNode(node?: RTNode): node is RTTextNode { + return !!node && node.type !== "image" && node.type !== "embed"; + } +} diff --git a/src/lib/filterRichTextField.ts b/src/lib/filterRichTextField.ts new file mode 100644 index 00000000..763cd464 --- /dev/null +++ b/src/lib/filterRichTextField.ts @@ -0,0 +1,159 @@ +import { CustomTypeModelRichTextField } from "../types/model/richText"; +import { LinkType } from "../types/value/link"; +import { + RTImageNode, + RTNode, + RTTextNode, + RichTextField, + RichTextNodeType, + RichTextNodeTypes, +} from "../types/value/richText"; +import { TitleField } from "../types/value/title"; + +/** + * Get allowed nodes types from a rich text or title field custom type model. + * + * @param model - A rich text or title field custom type model. + * + * @returns An array of allowed nodes types. + */ +const getAllowedNodeTypes = ( + model: CustomTypeModelRichTextField, +): RichTextNodeTypes[] => { + const allowedNodeTypes: RichTextNodeTypes[] = []; // An empty array means no nodes are allowed by default. + + if (model.config) { + if ("multi" in model.config && model.config.multi) { + allowedNodeTypes.push( + ...(model.config.multi.split(",") as RichTextNodeTypes[]), + ); + } else if ("single" in model.config && model.config.single) { + allowedNodeTypes.push( + ...(model.config.single.split(",") as RichTextNodeTypes[]), + ); + } + + if (model.config.labels?.length) { + allowedNodeTypes.push("label"); + } + } + + return allowedNodeTypes; +}; + +/** + * Filter an image node based on given model. + * + * @param node - An image node to filter. + * @param model - A rich text or title field custom type model. + * + * @returns A filtered image node based on the given model. + */ +const filterImageNode = ( + node: RTImageNode, + model: CustomTypeModelRichTextField, +): RTImageNode => { + if ( + !model.config?.allowTargetBlank && + node.linkTo?.link_type === LinkType.Web + ) { + return { + ...node, + linkTo: { + ...node.linkTo, + target: undefined, + }, + }; + } + + return node; +}; + +/** + * Filter a text node based on given model. + * + * @param node - A text node to filter. + * @param model - A rich text or title field custom type model. + * @param allowedNodeTypes - An array of allowed nodes types. + * + * @returns A filtered text node based on the given model. + */ +const filterTextNode = ( + node: RTTextNode, + model: CustomTypeModelRichTextField, + allowedNodeTypes: RichTextNodeTypes[], +): RTTextNode => { + const filteredNode: RTTextNode = { + ...node, + spans: [], + }; + + for (let i = 0; i < node.spans.length; i++) { + const span = node.spans[i]; + + if (allowedNodeTypes.includes(span.type)) { + if (span.type === RichTextNodeType.hyperlink) { + if ( + !model.config?.allowTargetBlank && + span.data.link_type === LinkType.Web + ) { + filteredNode.spans.push({ + ...span, + data: { + ...span.data, + target: undefined, + }, + }); + } else { + filteredNode.spans.push(span); + } + } else if (span.type === RichTextNodeType.label) { + if (model.config?.labels?.includes(span.data.label)) { + filteredNode.spans.push(span); + } + } else { + filteredNode.spans.push(span); + } + } + } + + return filteredNode; +}; + +/** + * Filter a rich text field based on given model. + * + * @param richTextField - A rich text or title field from Prismic. + * @param model - A rich text or title field custom type model. + * + * @returns A rich text or title field filtered based on the given model. + */ +export const filterRichTextField = ( + richTextField: Field, + model: CustomTypeModelRichTextField, +): Field => { + const nodes: RTNode[] = []; + + const allowedNodeTypes = getAllowedNodeTypes(model); + + for ( + let i = 0; + // Only process the first node if it's a "single" node rich text type + i < (model.config && "multi" in model.config ? richTextField.length : 1); + i++ + ) { + const node = richTextField[i]; + + if (allowedNodeTypes.includes(node.type)) { + if (node.type === RichTextNodeType.image) { + nodes.push(filterImageNode(node, model)); + } else if (node.type === RichTextNodeType.embed) { + nodes.push(node); + } else { + nodes.push(filterTextNode(node, model, allowedNodeTypes)); + } + } + } + + return nodes as Field; +}; diff --git a/src/lib/hastSerializerHelpers.ts b/src/lib/hastSerializerHelpers.ts new file mode 100644 index 00000000..44979fc3 --- /dev/null +++ b/src/lib/hastSerializerHelpers.ts @@ -0,0 +1,153 @@ +import { Element } from "hast"; +import { toHtml } from "hast-util-to-html"; + +import { OEmbedType } from "../types/value/embed"; +import { LinkType } from "../types/value/link"; +import { RTEmbedNode, RTImageNode, RTLinkNode } from "../types/value/richText"; + +import { PrismicRichTextSerializerError } from "../errors/PrismicRichTextSerializerError"; + +import { RTPartialInlineNode } from "./RichTextFieldBuilder"; + +/** + * Serializes a hast {@link Element} node to a {@link RTImageNode}. + * + * @param node - A hast node to serialize. + * + * @returns Equivalent {@link RTImageNode}. + */ +export const serializeImage = (node: Element): RTImageNode => { + const src = node.properties?.src as string | undefined; + + if (!src) { + throw new PrismicRichTextSerializerError( + "Element of type `img` is missing an `src` attribute", + ); + } + + const url = new URL(src); + + let width = node.properties?.width as number | undefined; + let height = node.properties?.height as number | undefined; + let x = 0; + let y = 0; + let zoom = 1; + + // Attempt to infer the image dimensions from the URL imgix parameters. + if (url.hostname === "images.prismic.io") { + if (url.searchParams.has("w")) { + width = Number(url.searchParams.get("w")); + } + + if (url.searchParams.has("h")) { + height = Number(url.searchParams.get("h")); + } + + if (url.searchParams.has("rect")) { + const [rectX, rextY, rectW, _rectH] = url.searchParams + .get("rect")! + .split(","); + + x = Number(rectX); + y = Number(rextY); + + // This is not perfect but it's supposed to work on images without constraints. + if (width) { + zoom = Math.max(1, width / Number(rectW)); + } + } + } + + return { + type: "image", + id: "", + url: src, + alt: (node.properties?.alt as string) ?? "", + copyright: (node.properties?.copyright as string) ?? null, + // This is not accurate. We're doing the casting because it seems the migration API + // accepts `undefined` values for those fields which is more convenient to us here. + // See: https://github.com/prismicio/prismic-client/pull/342#discussion_r1683650185 + dimensions: { width: width as number, height: height as number }, + edit: { x, y, zoom, background: "transparent" }, + }; +}; + +/** + * Serializes a hast {@link Element} node to a {@link RTEmbedNode}. + * + * @param node - A hast node to serialize. + * + * @returns Equivalent {@link RTEmbedNode}. + */ +export const serializeEmbed = (node: Element): RTEmbedNode => { + const src = node.properties?.src as string | undefined; + + if (!src) { + throw new PrismicRichTextSerializerError( + "Element of type `embed` is missing an `src` attribute", + ); + } + + const oembedBase = { + version: "1.0", + embed_url: src, + html: toHtml(node), + title: node.properties?.title as string | undefined, + }; + + const width = node.properties?.width as number | undefined; + const height = node.properties?.height as number | undefined; + + // Remove the children of the embed node as we don't want to process them. + node.children = []; + + if (width && height) { + return { + type: "embed", + oembed: { ...oembedBase, type: OEmbedType.Rich, width, height }, + }; + } else { + return { + type: "embed", + oembed: { ...oembedBase, type: OEmbedType.Link }, + }; + } +}; + +/** + * Serializes a hast {@link Element} node to a {@link RTPartialInlineNode}. + * + * @param node - A hast node to serialize. + * @param rtPartialInlineNode - A partial rich text node. + * + * @returns Equivalent {@link RTPartialInlineNode}. + */ +export const serializeSpan = ( + node: Element, + rtPartialInlineNode: + | RTPartialInlineNode + | Omit, +): RTPartialInlineNode => { + if ( + rtPartialInlineNode.type === "hyperlink" && + !("data" in rtPartialInlineNode) + ) { + const url = node.properties?.href as string | undefined; + if (!url) { + throw new PrismicRichTextSerializerError( + "Element of type `hyperlink` is missing an `href` attribute", + ); + } + + return { + type: "hyperlink", + data: { + link_type: LinkType.Web, + url, + target: node.properties?.target as string | undefined, + }, + }; + } + + return rtPartialInlineNode; +}; diff --git a/src/lib/hastToRichText.ts b/src/lib/hastToRichText.ts new file mode 100644 index 00000000..6aff86a7 --- /dev/null +++ b/src/lib/hastToRichText.ts @@ -0,0 +1,294 @@ +import { Element, Root } from "hast"; +import { whitespace } from "hast-util-whitespace"; +import { toString } from "mdast-util-to-string"; +import { visit } from "unist-util-visit"; +import { VFile } from "vfile"; + +import { + RTBlockNode, + RTInlineNode, + RTLabelNode, + RTTextNode, + RichTextField, + RichTextNodeType, +} from "../types/value/richText"; + +import { PrismicRichTextSerializerError } from "../errors/PrismicRichTextSerializerError"; + +import { + RichTextHTMLMapSerializer, + RichTextHTMLMapSerializerFunction, + RichTextHTMLMapSerializerShorthand, +} from "../helpers/unstable_htmlAsRichText"; + +import { RichTextFieldBuilder } from "./RichTextFieldBuilder"; +import { + serializeEmbed, + serializeImage, + serializeSpan, +} from "./hastSerializerHelpers"; + +/** + * Pick keys from a type, distributing the operation over a union. + * + * Taken from the `type-fest` package. + * + * @see https://github.com/sindresorhus/type-fest/blob/8a45ba048767aaffcebc7d190172d814a739feb0/source/distributed-pick.d.ts + */ +type DistributedPick< + ObjectType, + KeyType extends keyof ObjectType, +> = ObjectType extends unknown ? Pick : never; + +const DEFAULT_SERIALIZER: RichTextHTMLMapSerializer = { + h1: "heading1", + h2: "heading2", + h3: "heading3", + h4: "heading4", + h5: "heading5", + h6: "heading6", + p: "paragraph", + pre: "preformatted", + strong: "strong", + em: "em", + li: ({ context }) => + context.listType === "group-o-list-item" ? "o-list-item" : "list-item", + ul: "group-list-item", + ol: "group-o-list-item", + img: "image", + iframe: "embed", + a: "hyperlink", +}; + +const VFILE_RULE = "failed-to-serialize-node"; +const VFILE_SOURCE = "prismic"; + +/** + * Configuration that determines the output of `toRichText`. + */ +export type HASTToRichTextConfig = { + /** + * An optional HTML to rich text serializer. Will be merged with the default + * HTML to rich text serializer. + */ + serializer?: RichTextHTMLMapSerializer; + + /** + * Whether or not the text processed should be marked as right-to-left. + * + * @defaultValue `false` + */ + direction?: "ltr" | "rtl"; +}; + +/** + * Transfor a hast tree to a rich text field. + * + * @param tree - The hast tree to transform. + * @param file - The vfile to attach warnings to. + * @param config - Configuration that determines the output of the function. + * + * @returns The rich text field equivalent of the provided hast tree. + */ +export const hastToRichText = ( + tree: Root | Element, + file: VFile, + config?: HASTToRichTextConfig, +): RichTextField => { + const builder = new RichTextFieldBuilder(); + + // Merge the default serializer with the user-provided one. + const serializer = { + ...DEFAULT_SERIALIZER, + ...config?.serializer, + }; + + // Keep track of the last text node type to append text nodes to in + // case of an image or an embed node is present inside a paragraph. + let lastRTTextNodeType: RTTextNode["type"] = RichTextNodeType.paragraph; + + // Keep track of the last list type to know whether we need to append + // `list-item` or `o-list-item` nodes. + let lastListType: "group-list-item" | "group-o-list-item" | undefined = + undefined; + + visit(tree, (node) => { + if (node.type === "element") { + // Transforms line break elements to line breaks + if (node.tagName === "br") { + try { + builder.appendText("\n"); + } catch (error) { + // noop + } + } + + // Resolves the serializer responsible for the current node. + let serializerOrShorthand: + | RichTextHTMLMapSerializerShorthand + | RichTextHTMLMapSerializerFunction; + + // We give priority to CSS selectors over tag names. + if (node.matchesSerializer && node.matchesSerializer in serializer) { + serializerOrShorthand = serializer[node.matchesSerializer]; + } else if (node.tagName in serializer) { + serializerOrShorthand = serializer[node.tagName]; + } else { + return; + } + + let shorthand: RichTextHTMLMapSerializerShorthand; + if (typeof serializerOrShorthand === "function") { + const shorthandOrNode = serializerOrShorthand({ + node, + context: { listType: lastListType }, + }); + + if (!shorthandOrNode) { + // Exit on unhandled node. + return; + } else if ( + typeof shorthandOrNode === "object" && + "type" in shorthandOrNode + ) { + // When the serializer returns a rich text node, we append it and return. + switch (shorthandOrNode.type) { + case RichTextNodeType.strong: + case RichTextNodeType.em: + case RichTextNodeType.label: + case RichTextNodeType.hyperlink: { + const length = toString(node).length; + + try { + builder.appendSpan(shorthandOrNode, length); + } catch (error) { + // Happens when we extract an image/embed node inside an RTTextNode and that + // the next children is a span. The last RT node type is then an image/embed + // node, so we need to resume a new RT text node. + builder.appendTextNode(lastRTTextNodeType, config?.direction); + builder.appendSpan(shorthandOrNode, length); + } + + return; + } + + case RichTextNodeType.image: + case RichTextNodeType.embed: + builder.appendNode(shorthandOrNode); + + return; + + default: + lastRTTextNodeType = shorthandOrNode.type; + builder.appendNode(shorthandOrNode); + + return; + } + } + + // Else it's a shorthand. + shorthand = shorthandOrNode; + } else { + shorthand = serializerOrShorthand; + } + + let match: + | DistributedPick< + RTBlockNode | Exclude, + "type" + > + | { type: "label"; data: { label: string } }; + if (typeof shorthand === "string") { + match = { type: shorthand }; + } else { + match = { type: RichTextNodeType.label, data: shorthand }; + } + + try { + switch (match.type) { + case RichTextNodeType.heading1: + case RichTextNodeType.heading2: + case RichTextNodeType.heading3: + case RichTextNodeType.heading4: + case RichTextNodeType.heading5: + case RichTextNodeType.heading6: + case RichTextNodeType.paragraph: + case RichTextNodeType.preformatted: + case RichTextNodeType.listItem: + case RichTextNodeType.oListItem: + lastRTTextNodeType = match.type; + builder.appendTextNode(match.type, config?.direction); + break; + + case RichTextNodeType.list: + case RichTextNodeType.oList: + lastListType = match.type; + break; + + case RichTextNodeType.image: + builder.appendNode(serializeImage(node)); + break; + + case RichTextNodeType.embed: + builder.appendNode(serializeEmbed(node)); + break; + + case RichTextNodeType.strong: + case RichTextNodeType.em: + case RichTextNodeType.label: + case RichTextNodeType.hyperlink: { + const span = serializeSpan(node, match); + const length = toString(node).length; + + try { + builder.appendSpan(span, length); + } catch (error) { + // Happens when we extract an image/embed node inside an RTTextNode and that + // the next children is a span. The last RT node type is then an image/embed + // node, so we need to resume a new RT text node. + builder.appendTextNode(lastRTTextNodeType, config?.direction); + builder.appendSpan(span, length); + } + break; + } + + default: + throw new Error( + `Unknown rich text node type: \`${ + (match as { type: string }).type + }\``, + ); + } + } catch (error) { + if (error instanceof PrismicRichTextSerializerError) { + file.message(error.message, { + cause: error, + place: node.position, + ruleId: VFILE_RULE, + source: VFILE_SOURCE, + }); + + return; + } + + throw error; + } + } else if (node.type === "text") { + if (!whitespace(node)) { + try { + builder.appendText(node.value); + } catch (error) { + // Happens when we extract an image/embed node inside an RTTextNode. The last RT + // node type is then an image/embed node, so we need to resume a new RT text node. + builder.appendTextNode(lastRTTextNodeType, config?.direction); + builder.appendText(node.value); + } + } + } + + // We ignore the following node types: + // root, doctype, comment, raw + }); + + return builder.build(); +}; diff --git a/src/lib/rehypeRichText.ts b/src/lib/rehypeRichText.ts new file mode 100644 index 00000000..0f415c7c --- /dev/null +++ b/src/lib/rehypeRichText.ts @@ -0,0 +1,183 @@ +import { Element, Root } from "hast"; +import { select, selectAll } from "hast-util-select"; +import rehypeMinifyWhitespace from "rehype-minify-whitespace"; +import { Plugin, Processor } from "unified"; +import { remove } from "unist-util-remove"; +import { SKIP, visit } from "unist-util-visit"; +import { VFile } from "vfile"; + +import { CustomTypeModelRichTextField } from "../types/model/richText"; +import { RichTextField } from "../types/value/richText"; + +import { filterRichTextField } from "./filterRichTextField"; +import { HASTToRichTextConfig, hastToRichText } from "./hastToRichText"; + +/** + * Configuration options for {@link rehypeRichText}. + */ +export type RehypeRichTextConfig = { + /** + * A CSS selector that targets the section of the document to convert to rich + * text. + * + * @example `div.post` + * + * @defaultValue `":root"` - The top-level element of the document. + */ + container?: string; + + /** + * A list of CSS selectors to exclude matching nodes from the document to + * process. + * + * @example `[".hidden", "aside"]` + * + * @defaultValue `[]` - No nodes are excluded. + */ + exclude?: string[]; + + /** + * A list of CSS selectors to include only matching nodes from the document to + * process. + * + * @example `["p", "img"]` + * + * @defaultValue `[]` - All nodes are included. + */ + include?: string[]; + + /** + * A rich text or title field model definition. When provided the serializer + * will filter out any node types not allowed by the model. + * + * @defaultValue `undefined` - No filtering is applied. + */ + model?: CustomTypeModelRichTextField; +} & HASTToRichTextConfig; + +/** + * A unified plugin that compiles a hast tree to a Prismic rich text field. + * + * @param config - Configuration options for the rehype rich text processor. + */ +// unified requires the function to be typed directly with the +// `Plugin` type to properly infer return types on processors. +export const rehypeRichText: Plugin< + [config?: RehypeRichTextConfig], + Root, + RichTextField +> = function rehypeRichText(config) { + // This is a bit dirty, but it seems like that's how rehype intends + // it to be due to JSDocs limitations(?), see: + // https://github.com/rehypejs/rehype/blob/f6912ac680704f1ef4b558ac57cbf0dd62ed0892/packages/rehype-stringify/lib/index.js#L18-L20 + // eslint-disable-next-line @typescript-eslint/no-this-alias + const self = this as unknown as Processor< + undefined, + undefined, + undefined, + Root, + RichTextField + >; + + // We need to exclude nodes _before_ we minify the tree as excluding + // nodes could end up in more whitespaces to trim. + self.use(() => { + return (tree: Root) => { + // Extract container node if any is specified. + if (config?.container) { + const element = select(config.container, tree); + + if (!element) { + throw new Error( + `No container matching \`${config?.container}\` could be found in the input AST.`, + ); + } + + // We cannot reassign the tree itself, so we instead replace + // its children with the found element. + tree.children = [element]; + } + + // Remove excluded nodes if any are specified + if (config?.exclude) { + // We join selector to only run one query + const nodesToExclude = selectAll(config.exclude.join(", "), tree); + + remove(tree, (node) => nodesToExclude.includes(node as Element)); + } + + // Include only nodes to include + if (config?.include) { + const nodesToInclude: Element[] = []; + + // We join selector to only run one query + const selector = config.include.join(", "); + const rawNodesToInclude = selectAll(selector, tree); + + // We walk the tree to exclude matching nodes that are children of other matching nodes. + visit(tree, (node) => { + if (rawNodesToInclude.includes(node as Element)) { + nodesToInclude.push(node as Element); + + // Stop traversing this part of the tree since we found its matching parent node. + return SKIP; + } + }); + + // We cannot reassign the tree itself, so we instead replace + // its children with the found element. + tree.children = nodesToInclude; + } + + // Mark nodes matching CSS selectors + if (config?.serializer) { + for (const key in config.serializer) { + // HTML tag names are single word, lowercase strings, a-z and 1-6 (headings). + // Here we want to match anything that's not a valid HTML tag name and treat + // it as a CSS selector. See: https://regex101.com/r/LILLWH/1 + if (!/^[a-z]+[1-6]?$/.test(key)) { + const matches = selectAll(key, tree); + + for (let i = 0; i < matches.length; i++) { + matches[i].matchesSerializer = key; + } + } + } + } + }; + }); + + // `rehypeRichText` _depends_ on `rehypeMinifyWhitespace`, that's why it's + // registered within the plugin rather than on the processor directly. + self.use(rehypeMinifyWhitespace); + + self.compiler = compiler; + + function compiler(tree: Root, file: VFile): RichTextField { + const richTextField = hastToRichText(tree, file, config); + + if (config?.model) { + return filterRichTextField(richTextField, config.model); + } + + return richTextField; + } +}; + +declare module "unified" { + // Register unified processor the result type. + interface CompileResultMap { + RichTextField: RichTextField; + } +} + +declare module "hast" { + // Extend hast node with a rich text value. + interface Element { + /** + * A serializer this node matches to. Nodes are marked with this property + * when they match a CSS selector from the serializer map. + */ + matchesSerializer?: string; + } +} diff --git a/src/richtext/index.ts b/src/richtext/index.ts index 7085bb21..556abeec 100644 --- a/src/richtext/index.ts +++ b/src/richtext/index.ts @@ -4,6 +4,7 @@ export { asText } from "./asText"; export { serialize } from "./serialize"; export { wrapMapSerializer } from "./wrapMapSerializer"; export { composeSerializers } from "./composeSerializers"; +export { filterRichTextField } from "../lib/filterRichTextField"; export { RichTextNodeType as Element } from "../types/value/richText"; diff --git a/src/richtext/types.ts b/src/richtext/types.ts index 7b2e4b52..95aac55f 100644 --- a/src/richtext/types.ts +++ b/src/richtext/types.ts @@ -20,6 +20,7 @@ import { RTSpanNode, RTStrongNode, RichTextNodeType, + RichTextNodeTypes, } from "../types/value/richText"; // Serializers @@ -32,7 +33,7 @@ import { * @see Templating rich text and title fields from Prismic {@link https://prismic.io/docs/technologies/templating-rich-text-and-title-fields-javascript} */ export type RichTextFunctionSerializer = ( - type: (typeof RichTextNodeType)[keyof typeof RichTextNodeType], + type: RichTextNodeTypes, node: RTAnyNode, text: string | undefined, children: ReturnType[], @@ -138,7 +139,7 @@ export interface Tree { export interface TreeNode { key: string; - type: (typeof RichTextNodeType)[keyof typeof RichTextNodeType]; + type: RichTextNodeTypes; text?: string; node: RTAnyNode; children: TreeNode[]; diff --git a/src/types/model/richText.ts b/src/types/model/richText.ts index 3c42ea53..ba68c68c 100644 --- a/src/types/model/richText.ts +++ b/src/types/model/richText.ts @@ -21,6 +21,8 @@ export interface CustomTypeModelRichTextMultiField { placeholder?: string; allowTargetBlank?: boolean; multi?: string; + // @prismicio/types-internal types `labels` as readonly. + labels?: readonly string[]; }; } @@ -36,5 +38,7 @@ export interface CustomTypeModelRichTextSingleField { placeholder?: string; allowTargetBlank?: boolean; single?: string; + // @prismicio/types-internal types `labels` as readonly. + labels?: readonly string[]; }; } diff --git a/src/types/value/richText.ts b/src/types/value/richText.ts index cf9a378c..85af0e8a 100644 --- a/src/types/value/richText.ts +++ b/src/types/value/richText.ts @@ -6,7 +6,7 @@ import type { FilledLinkToWebField } from "./link"; import type { FilledLinkToMediaField } from "./linkToMedia"; /** - * Types for RichTextNodes + * Types enum for RichTextNodes * * @see More details: {@link https://prismic.io/docs/rich-text-title} */ @@ -32,6 +32,14 @@ export const RichTextNodeType = { span: "span", } as const; +/** + * Types for RichTextNodes + * + * @see More details: {@link https://prismic.io/docs/rich-text-title} + */ +export type RichTextNodeTypes = + (typeof RichTextNodeType)[keyof typeof RichTextNodeType]; + // Text nodes /** @@ -40,6 +48,7 @@ export const RichTextNodeType = { export interface RTTextNodeBase { text: string; spans: RTInlineNode[]; + direction?: "ltr" | "rtl"; } /** diff --git a/test/__snapshots__/helpers-unstable_htmlAsRichText.test.ts.snap b/test/__snapshots__/helpers-unstable_htmlAsRichText.test.ts.snap new file mode 100644 index 00000000..fc35e543 --- /dev/null +++ b/test/__snapshots__/helpers-unstable_htmlAsRichText.test.ts.snap @@ -0,0 +1,1137 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`transforms HTML to rich text > basic > empty 1`] = `[]`; + +exports[`transforms HTML to rich text > basic > innerHTML 1`] = ` +[ + { + "direction": "ltr", + "spans": [ + { + "end": 11, + "start": 6, + "type": "strong", + }, + ], + "text": "lorem ipsum dolor sit amet", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > basic > multiple tags 1`] = ` +[ + { + "direction": "ltr", + "spans": [], + "text": "lorem ipsum dolor sit amet", + "type": "heading1", + }, + { + "direction": "ltr", + "spans": [], + "text": "consectetur adipiscing elit", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > basic > single tag 1`] = ` +[ + { + "direction": "ltr", + "spans": [], + "text": "lorem ipsum dolor sit amet", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > configuration > container > converts only the given container 1`] = ` +[ + { + "direction": "ltr", + "spans": [], + "text": "consectetur adipiscing elit", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > configuration > direction > marks text as left-to-right 1`] = ` +[ + { + "direction": "ltr", + "spans": [], + "text": "lorem ipsum dolor sit amet", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > configuration > direction > marks text as right-to-left 1`] = ` +[ + { + "direction": "rtl", + "spans": [], + "text": "lorem ipsum dolor sit amet", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > configuration > exclude > excludes the given complex selectors 1`] = ` +[ + { + "direction": "ltr", + "spans": [], + "text": "ipsum dolor sit amet", + "type": "heading1", + }, + { + "direction": "ltr", + "spans": [ + { + "data": { + "link_type": "Web", + "target": undefined, + "url": "#", + }, + "end": 11, + "start": 0, + "type": "hyperlink", + }, + ], + "text": "consectetur adipiscing elit", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > configuration > exclude > excludes the given selectors 1`] = ` +[ + { + "direction": "ltr", + "spans": [], + "text": "consectetur adipiscing elit", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > configuration > include > dedupes matches that are child of other matches 1`] = ` +[ + { + "direction": "ltr", + "spans": [], + "text": "lorem ipsum dolor sit amet", + "type": "paragraph", + }, + { + "direction": "ltr", + "spans": [], + "text": "consectetur adipiscing elit", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > configuration > include > includes only the given complex selectors 1`] = ` +[ + { + "direction": "ltr", + "spans": [], + "text": "consectetur adipiscing elit", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > configuration > include > includes only the given selectors 1`] = ` +[ + { + "direction": "ltr", + "spans": [], + "text": "lorem ipsum dolor sit amet", + "type": "heading1", + }, +] +`; + +exports[`transforms HTML to rich text > configuration > model > filters output according to multi type model 1`] = ` +[ + { + "direction": "ltr", + "spans": [ + { + "end": 22, + "start": 12, + "type": "strong", + }, + ], + "text": "consectetur adipiscing elit", + "type": "paragraph", + }, + { + "direction": "ltr", + "spans": [], + "text": "sed do eiusmod tempor", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > configuration > model > filters output according to single type model 1`] = ` +[ + { + "direction": "ltr", + "spans": [ + { + "end": 11, + "start": 6, + "type": "strong", + }, + ], + "text": "lorem ipsum dolor sit amet", + "type": "heading1", + }, +] +`; + +exports[`transforms HTML to rich text > configuration > serializer > complex selector 1`] = ` +[ + { + "direction": "ltr", + "spans": [], + "text": "lorem ipsum dolor sit amet", + "type": "heading1", + }, + { + "direction": "ltr", + "spans": [], + "text": "consectetur adipiscing elit", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > configuration > serializer > function serializer > basic 1`] = ` +[ + { + "direction": "ltr", + "spans": [], + "text": "lorem ipsum dolor sit amet", + "type": "heading1", + }, + { + "direction": "ltr", + "spans": [], + "text": "consectetur adipiscing elit", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > configuration > serializer > function serializer > embed node 1`] = ` +[ + { + "oembed": { + "embed_url": "bar", + "height": 0, + "html": "baz", + "title": "qux", + "type": "rich", + "version": "1.0", + "width": 0, + }, + "type": "embed", + }, +] +`; + +exports[`transforms HTML to rich text > configuration > serializer > function serializer > image node 1`] = ` +[ + { + "alt": "qux", + "copyright": null, + "dimensions": { + "height": 0, + "width": 0, + }, + "edit": { + "background": "transparent", + "x": 0, + "y": 0, + "zoom": 1, + }, + "id": "bar", + "type": "image", + "url": "baz", + }, +] +`; + +exports[`transforms HTML to rich text > configuration > serializer > function serializer > span nodes > full 1`] = ` +[ + { + "direction": "ltr", + "spans": [ + { + "end": 5, + "start": 0, + "type": "strong", + }, + ], + "text": "lorem ipsum dolor sit amet", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > configuration > serializer > function serializer > span nodes > partial 1`] = ` +[ + { + "direction": "ltr", + "spans": [ + { + "end": 11, + "start": 6, + "type": "strong", + }, + ], + "text": "lorem ipsum dolor sit amet", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > configuration > serializer > function serializer > span nodes > with extracted image node > full 1`] = ` +[ + { + "direction": "ltr", + "spans": [], + "text": "lorem ipsum", + "type": "paragraph", + }, + { + "alt": "bar", + "copyright": null, + "dimensions": { + "height": undefined, + "width": undefined, + }, + "edit": { + "background": "transparent", + "x": 0, + "y": 0, + "zoom": 1, + }, + "id": "", + "type": "image", + "url": "https://example.com/foo.png", + }, + { + "direction": "ltr", + "spans": [ + { + "end": 9, + "start": 6, + "type": "strong", + }, + ], + "text": "dolor sit amet", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > configuration > serializer > function serializer > span nodes > with extracted image node > partial 1`] = ` +[ + { + "direction": "ltr", + "spans": [], + "text": "lorem ipsum", + "type": "paragraph", + }, + { + "alt": "bar", + "copyright": null, + "dimensions": { + "height": undefined, + "width": undefined, + }, + "edit": { + "background": "transparent", + "x": 0, + "y": 0, + "zoom": 1, + }, + "id": "", + "type": "image", + "url": "https://example.com/foo.png", + }, + { + "direction": "ltr", + "spans": [ + { + "end": 5, + "start": 0, + "type": "strong", + }, + ], + "text": "dolor sit amet", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > configuration > serializer > function serializer > text nodes 1`] = ` +[ + { + "spans": [], + "text": "lorem ipsum dolor sit amet", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > configuration > serializer > function serializer > undefined 1`] = ` +[ + { + "direction": "ltr", + "spans": [], + "text": "lorem ipsum dolor sit amet", + "type": "paragraph", + }, + { + "direction": "ltr", + "spans": [], + "text": "consectetur adipiscing elit", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > configuration > serializer > selector 1`] = ` +[ + { + "direction": "ltr", + "spans": [], + "text": "lorem ipsum dolor sit amet", + "type": "heading1", + }, + { + "direction": "ltr", + "spans": [], + "text": "consectetur adipiscing elit", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > configuration > serializer > tag name 1`] = ` +[ + { + "direction": "ltr", + "spans": [], + "text": "lorem ipsum dolor sit amet", + "type": "heading1", + }, +] +`; + +exports[`transforms HTML to rich text > embed > custom 1`] = ` +[ + { + "oembed": { + "embed_url": "https://twitter.com/li_hbr/status/1803718142222282829?ref_src=twsrc%5Etfw", + "html": "

Slack bot that uses AI to tl;dr; threads for you, anyone?

— Lucie (@li_hbr) June 20, 2024
", + "title": undefined, + "type": "link", + "version": "1.0", + }, + "type": "embed", + }, +] +`; + +exports[`transforms HTML to rich text > embed > iframe 1`] = ` +[ + { + "oembed": { + "embed_url": "https://www.youtube.com/embed/wkS1bf7BLjs?feature=oembed", + "height": 150, + "html": "", + "title": "幾田りら「ハミング」Official Music Video", + "type": "rich", + "version": "1.0", + "width": 200, + }, + "type": "embed", + }, +] +`; + +exports[`transforms HTML to rich text > embed > missing src 1`] = `[]`; + +exports[`transforms HTML to rich text > image > empty alt 1`] = ` +[ + { + "alt": "", + "copyright": null, + "dimensions": { + "height": undefined, + "width": undefined, + }, + "edit": { + "background": "transparent", + "x": 0, + "y": 0, + "zoom": 1, + }, + "id": "", + "type": "image", + "url": "https://example.com/foo.png", + }, +] +`; + +exports[`transforms HTML to rich text > image > extracts image in text nodes and resume previous text node > adjacent spans (respects direction) 1`] = ` +[ + { + "direction": "rtl", + "spans": [ + { + "end": 11, + "start": 6, + "type": "strong", + }, + ], + "text": "lorem ipsum", + "type": "paragraph", + }, + { + "alt": "bar", + "copyright": null, + "dimensions": { + "height": undefined, + "width": undefined, + }, + "edit": { + "background": "transparent", + "x": 0, + "y": 0, + "zoom": 1, + }, + "id": "", + "type": "image", + "url": "https://example.com/foo.png", + }, + { + "direction": "rtl", + "spans": [ + { + "end": 5, + "start": 0, + "type": "em", + }, + ], + "text": "dolor sit amet", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > image > extracts image in text nodes and resume previous text node > adjacent spans 1`] = ` +[ + { + "direction": "ltr", + "spans": [ + { + "end": 11, + "start": 6, + "type": "strong", + }, + ], + "text": "lorem ipsum", + "type": "paragraph", + }, + { + "alt": "bar", + "copyright": null, + "dimensions": { + "height": undefined, + "width": undefined, + }, + "edit": { + "background": "transparent", + "x": 0, + "y": 0, + "zoom": 1, + }, + "id": "", + "type": "image", + "url": "https://example.com/foo.png", + }, + { + "direction": "ltr", + "spans": [ + { + "end": 5, + "start": 0, + "type": "em", + }, + ], + "text": "dolor sit amet", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > image > extracts image in text nodes and resume previous text node > basic 1`] = ` +[ + { + "direction": "ltr", + "spans": [], + "text": "lorem ipsum", + "type": "paragraph", + }, + { + "alt": "bar", + "copyright": null, + "dimensions": { + "height": undefined, + "width": undefined, + }, + "edit": { + "background": "transparent", + "x": 0, + "y": 0, + "zoom": 1, + }, + "id": "", + "type": "image", + "url": "https://example.com/foo.png", + }, + { + "direction": "ltr", + "spans": [], + "text": "dolor sit amet", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > image > extracts image in text nodes and resume previous text node > spans 1`] = ` +[ + { + "direction": "ltr", + "spans": [ + { + "end": 5, + "start": 0, + "type": "strong", + }, + ], + "text": "lorem ipsum", + "type": "paragraph", + }, + { + "alt": "bar", + "copyright": null, + "dimensions": { + "height": undefined, + "width": undefined, + }, + "edit": { + "background": "transparent", + "x": 0, + "y": 0, + "zoom": 1, + }, + "id": "", + "type": "image", + "url": "https://example.com/foo.png", + }, + { + "direction": "ltr", + "spans": [ + { + "end": 9, + "start": 6, + "type": "em", + }, + ], + "text": "dolor sit amet", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > image > missing alt 1`] = ` +[ + { + "alt": "", + "copyright": null, + "dimensions": { + "height": undefined, + "width": undefined, + }, + "edit": { + "background": "transparent", + "x": 0, + "y": 0, + "zoom": 1, + }, + "id": "", + "type": "image", + "url": "https://example.com/foo.png", + }, +] +`; + +exports[`transforms HTML to rich text > image > missing src 1`] = `[]`; + +exports[`transforms HTML to rich text > image > non-prismic 1`] = ` +[ + { + "alt": "foo", + "copyright": null, + "dimensions": { + "height": undefined, + "width": undefined, + }, + "edit": { + "background": "transparent", + "x": 0, + "y": 0, + "zoom": 1, + }, + "id": "", + "type": "image", + "url": "https://example.com/foo.png", + }, +] +`; + +exports[`transforms HTML to rich text > image > prismic 1`] = ` +[ + { + "alt": "foo", + "copyright": null, + "dimensions": { + "height": 1602, + "width": 2400, + }, + "edit": { + "background": "transparent", + "x": 399, + "y": 259, + "zoom": 1.5, + }, + "id": "", + "type": "image", + "url": "https://images.prismic.io/200629-sms-hoy/f0a757f6-770d-4eb8-a08b-f1727f1a58e4_guilherme-romano-KI2KaOeT670-unsplash.jpg?auto=format%2Ccompress&rect=399%2C259%2C1600%2C1068&w=2400&h=1602", + }, +] +`; + +exports[`transforms HTML to rich text > lists > list-item 1`] = ` +[ + { + "direction": "ltr", + "spans": [], + "text": "lorem ipsum dolor sit amet", + "type": "list-item", + }, + { + "direction": "ltr", + "spans": [], + "text": "consectetur adipiscing elit", + "type": "list-item", + }, +] +`; + +exports[`transforms HTML to rich text > lists > list-item and o-list-item 1`] = ` +[ + { + "direction": "ltr", + "spans": [], + "text": "lorem ipsum dolor sit amet", + "type": "o-list-item", + }, + { + "direction": "ltr", + "spans": [], + "text": "consectetur adipiscing elit", + "type": "o-list-item", + }, + { + "direction": "ltr", + "spans": [], + "text": "sed do eiusmod tempor incididunt", + "type": "list-item", + }, + { + "direction": "ltr", + "spans": [], + "text": "ut labore et dolore magna aliqua", + "type": "list-item", + }, +] +`; + +exports[`transforms HTML to rich text > lists > nested list-item and o-list-item 1`] = ` +[ + { + "direction": "ltr", + "spans": [], + "text": "lorem ipsum dolor sit amet", + "type": "list-item", + }, + { + "direction": "ltr", + "spans": [], + "text": "consectetur adipiscing elit", + "type": "o-list-item", + }, + { + "direction": "ltr", + "spans": [], + "text": "sed do eiusmod tempor incididunt", + "type": "o-list-item", + }, + { + "direction": "ltr", + "spans": [], + "text": "ut labore et dolore magna aliqua", + "type": "o-list-item", + }, +] +`; + +exports[`transforms HTML to rich text > lists > o-list-item 1`] = ` +[ + { + "direction": "ltr", + "spans": [], + "text": "lorem ipsum dolor sit amet", + "type": "o-list-item", + }, + { + "direction": "ltr", + "spans": [], + "text": "consectetur adipiscing elit", + "type": "o-list-item", + }, +] +`; + +exports[`transforms HTML to rich text > spans > directly adjacent spans > compacts similar > hyperlink 1`] = ` +[ + { + "direction": "ltr", + "spans": [ + { + "data": { + "link_type": "Web", + "target": undefined, + "url": "https://prismic.io", + }, + "end": 17, + "start": 6, + "type": "hyperlink", + }, + ], + "text": "lorem ipsum dolor sit amet", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > spans > directly adjacent spans > compacts similar > label 1`] = ` +[ + { + "direction": "ltr", + "spans": [ + { + "data": { + "label": "underline", + }, + "end": 17, + "start": 6, + "type": "label", + }, + ], + "text": "lorem ipsum dolor sit amet", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > spans > directly adjacent spans > compacts similar > nested spans 1`] = ` +[ + { + "direction": "ltr", + "spans": [ + { + "end": 11, + "start": 6, + "type": "strong", + }, + { + "end": 17, + "start": 9, + "type": "em", + }, + ], + "text": "lorem ipsum dolor sit amet", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > spans > directly adjacent spans > compacts similar > strong, em 1`] = ` +[ + { + "direction": "ltr", + "spans": [ + { + "end": 17, + "start": 6, + "type": "strong", + }, + { + "end": 38, + "start": 22, + "type": "em", + }, + ], + "text": "lorem ipsum dolor sit amet consectetur adipiscing elit", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > spans > directly adjacent spans > does not compact different > hyperlink 1`] = ` +[ + { + "direction": "ltr", + "spans": [ + { + "data": { + "link_type": "Web", + "target": undefined, + "url": "https://prismic.io", + }, + "end": 11, + "start": 6, + "type": "hyperlink", + }, + { + "data": { + "link_type": "Web", + "target": undefined, + "url": "https://google.com", + }, + "end": 17, + "start": 11, + "type": "hyperlink", + }, + ], + "text": "lorem ipsum dolor sit amet", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > spans > directly adjacent spans > does not compact different > label 1`] = ` +[ + { + "direction": "ltr", + "spans": [ + { + "data": { + "label": "underline", + }, + "end": 11, + "start": 6, + "type": "label", + }, + { + "data": { + "label": "strikethrough", + }, + "end": 17, + "start": 11, + "type": "label", + }, + ], + "text": "lorem ipsum dolor sit amet", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > spans > hyperlink 1`] = ` +[ + { + "direction": "ltr", + "spans": [ + { + "data": { + "link_type": "Web", + "target": undefined, + "url": "https://prismic.io", + }, + "end": 11, + "start": 6, + "type": "hyperlink", + }, + { + "data": { + "link_type": "Web", + "target": "_blank", + "url": "https://prismic.io", + }, + "end": 21, + "start": 18, + "type": "hyperlink", + }, + ], + "text": "lorem ipsum dolor sit amet", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > spans > hyperlink missing href 1`] = ` +[ + { + "direction": "ltr", + "spans": [], + "text": "lorem ipsum dolor sit amet", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > spans > label 1`] = ` +[ + { + "direction": "ltr", + "spans": [ + { + "data": { + "label": "underline", + }, + "end": 11, + "start": 6, + "type": "label", + }, + ], + "text": "lorem ipsum dolor sit amet", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > spans > nested spans 1`] = ` +[ + { + "direction": "ltr", + "spans": [ + { + "end": 17, + "start": 6, + "type": "strong", + }, + { + "end": 17, + "start": 9, + "type": "em", + }, + { + "data": { + "link_type": "Web", + "target": undefined, + "url": "https://prismic.io", + }, + "end": 17, + "start": 12, + "type": "hyperlink", + }, + ], + "text": "lorem ipsum dolor sit amet", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > spans > strong, em 1`] = ` +[ + { + "direction": "ltr", + "spans": [ + { + "end": 11, + "start": 6, + "type": "strong", + }, + { + "end": 21, + "start": 18, + "type": "em", + }, + ], + "text": "lorem ipsum dolor sit amet", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > whistespaces > ignores wild \`
\` 1`] = ` +[ + { + "direction": "ltr", + "spans": [], + "text": "lorem ipsum dolor sit amet", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > whistespaces > strips complex indentation 1`] = ` +[ + { + "direction": "ltr", + "spans": [], + "text": "lorem ipsum dolor sit amet consectetur adipiscing elit", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > whistespaces > strips indentation 1`] = ` +[ + { + "direction": "ltr", + "spans": [], + "text": "lorem ipsum dolor sit amet", + "type": "paragraph", + }, +] +`; + +exports[`transforms HTML to rich text > whistespaces > treats \`
\` as new lines 1`] = ` +[ + { + "direction": "ltr", + "spans": [], + "text": "lorem ipsum dolor sit amet +consectetur adipiscing elit", + "type": "paragraph", + }, +] +`; diff --git a/test/__snapshots__/helpers-unstable_markdownAsRichText.test.ts.snap b/test/__snapshots__/helpers-unstable_markdownAsRichText.test.ts.snap new file mode 100644 index 00000000..f70dc0c8 --- /dev/null +++ b/test/__snapshots__/helpers-unstable_markdownAsRichText.test.ts.snap @@ -0,0 +1,31 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`transforms markdown to rich text > basic > empty 1`] = `[]`; + +exports[`transforms markdown to rich text > basic > multiple tags 1`] = ` +[ + { + "direction": "ltr", + "spans": [], + "text": "lorem ipsum dolor sit amet", + "type": "heading1", + }, + { + "direction": "ltr", + "spans": [], + "text": "consectetur adipiscing elit", + "type": "preformatted", + }, +] +`; + +exports[`transforms markdown to rich text > basic > single tag 1`] = ` +[ + { + "direction": "ltr", + "spans": [], + "text": "lorem ipsum dolor sit amet", + "type": "paragraph", + }, +] +`; diff --git a/test/__testutils__/testAsRichTextHelper.ts b/test/__testutils__/testAsRichTextHelper.ts new file mode 100644 index 00000000..30dcf307 --- /dev/null +++ b/test/__testutils__/testAsRichTextHelper.ts @@ -0,0 +1,111 @@ +import { expect, it } from "vitest"; + +import { LinkType, asHTML } from "../../src"; +import { + unstable_htmlAsRichText, + unstable_markdownAsRichText, +} from "../../src"; +import type { HTMLAsRichTextConfig } from "../../src/helpers/unstable_htmlAsRichText"; +import type { MarkdownAsRichTextConfig } from "../../src/helpers/unstable_markdownAsRichText"; + +type TestAsRichTextHelperArgs = { + input: string; + + config?: TConfig; + + /** + * Warnings that are expected to be present in the output. + */ + expectWarnings?: string[]; + + /** + * The rich text format is a lossy representation of HTML. Namely it does not + * preserves indentation and applies some optimizations to the output such as + * merging directly adjacent identical spans. + * + * By default, the test suite will expect the HTML representation of the + * output to exactly match the input. This flag can be used to tell it to + * expect the output to not match the input instead. + */ + expectHTMLToMatchInputExactly?: boolean; +}; + +const testAsRichTextHelperFactory = < + THelper extends + | typeof unstable_htmlAsRichText + | typeof unstable_markdownAsRichText, +>( + description: string, + args: TestAsRichTextHelperArgs< + THelper extends typeof unstable_htmlAsRichText + ? HTMLAsRichTextConfig + : THelper extends typeof unstable_markdownAsRichText + ? MarkdownAsRichTextConfig + : undefined + >, + helper: THelper, +): void => { + it(description, () => { + const output = helper(args.input, args.config); + + expect(output.result).toMatchSnapshot(); + + const outputAsHTML = asHTML(output.result, { + serializer: { + // A simplified hyperlink serializer so that we don't have + // to append `rel="noopener noreferrer"` to every link in + // the test cases. + hyperlink: ({ node, children }) => { + const maybeTarget = + node.data.link_type === LinkType.Web && node.data.target + ? ` target="${node.data.target}"` + : ""; + + return `${children}`; + }, + // A simplified image serializer so that we don't have to + // wrap the images in a `div` element like the default + // serializer does. + image: ({ node }) => `${node.alt}`, + // A simplified embed serializer so that we don't have to + // wrap the embeds in a `div` element with the various data + // attributes the default serializer applies. + embed: ({ node }) => node.oembed.html, + }, + }); + + expect(output.warnings.sort()).toStrictEqual( + args.expectWarnings?.sort() ?? [], + ); + + if ( + typeof args.expectHTMLToMatchInputExactly === "undefined" || + args.expectHTMLToMatchInputExactly + ) { + expect(outputAsHTML).toBe(args.input); + } else { + expect(outputAsHTML).not.toBe(args.input); + } + }); +}; + +export const testHTMLAsRichTextHelper = ( + description: string, + args: TestAsRichTextHelperArgs, +): void => { + testAsRichTextHelperFactory(description, args, unstable_htmlAsRichText); +}; + +export const testMarkdownAsRichTextHelper = ( + description: string, + args: Omit< + TestAsRichTextHelperArgs, + "expectHTMLToMatchInputExactly" + >, +): void => { + testAsRichTextHelperFactory( + description, + { ...args, expectHTMLToMatchInputExactly: !args.input }, + unstable_markdownAsRichText, + ); +}; diff --git a/test/buildQueryURL.test.ts b/test/buildQueryURL.test.ts index 8b4ce1b3..d86f26bd 100644 --- a/test/buildQueryURL.test.ts +++ b/test/buildQueryURL.test.ts @@ -288,7 +288,13 @@ it("warns if NODE_ENV is development and an array of strings is provided to `ord orderings: ["orderings"], }); - expect(consoleWarnSpy).toHaveBeenCalledWith( + prismic.buildQueryURL(endpoint, { + ref: "ref", + orderings: ["orderings desc"], + }); + + expect(consoleWarnSpy).toHaveBeenNthCalledWith( + 2, expect.stringMatching(/orderings-must-be-an-array-of-objects/i), ); diff --git a/test/helpers-unstable_htmlAsRichText.test.ts b/test/helpers-unstable_htmlAsRichText.test.ts new file mode 100644 index 00000000..34a230dc --- /dev/null +++ b/test/helpers-unstable_htmlAsRichText.test.ts @@ -0,0 +1,491 @@ +import { describe, expect, it } from "vitest"; + +import { testHTMLAsRichTextHelper } from "./__testutils__/testAsRichTextHelper"; + +import { unstable_htmlAsRichText } from "../src"; + +describe("transforms HTML to rich text", () => { + describe("basic", () => { + testHTMLAsRichTextHelper("empty", { + input: /* html */ ``, + }); + + testHTMLAsRichTextHelper("single tag", { + input: /* html */ `

lorem ipsum dolor sit amet

`, + }); + + testHTMLAsRichTextHelper("multiple tags", { + input: /* html */ `

lorem ipsum dolor sit amet

consectetur adipiscing elit

`, + }); + + testHTMLAsRichTextHelper("innerHTML", { + input: /* html */ `lorem ipsum dolor sit amet`, + expectHTMLToMatchInputExactly: false, + }); + }); + + describe("spans", () => { + testHTMLAsRichTextHelper("strong, em", { + input: /* html */ `

lorem ipsum dolor sit amet

`, + }); + + testHTMLAsRichTextHelper("label", { + input: /* html */ `

lorem ipsum dolor sit amet

`, + config: { serializer: { "span.underline": { label: "underline" } } }, + }); + + testHTMLAsRichTextHelper("hyperlink", { + input: /* html */ `

lorem ipsum dolor sit amet

`, + }); + + testHTMLAsRichTextHelper("hyperlink missing href", { + input: /* html */ `

lorem ipsum dolor sit amet

`, + expectWarnings: [ + "1:10-1:22: Element of type `hyperlink` is missing an `href` attribute", + ], + expectHTMLToMatchInputExactly: false, + }); + + testHTMLAsRichTextHelper("nested spans", { + input: /* html */ `

lorem ipsum dolor sit amet

`, + }); + + describe("directly adjacent spans", () => { + describe("compacts similar", () => { + testHTMLAsRichTextHelper("strong, em", { + input: /* html */ `

lorem ipsum dolor sit amet consectetur adipiscing elit

`, + // `strong` and `em` tags will be merged into single ones. + expectHTMLToMatchInputExactly: false, + }); + + testHTMLAsRichTextHelper("hyperlink", { + input: /* html */ `

lorem ipsum dolor sit amet

`, + // `a` tags will be merged into single ones. + expectHTMLToMatchInputExactly: false, + }); + + testHTMLAsRichTextHelper("label", { + input: /* html */ `

lorem ipsum dolor sit amet

`, + config: { serializer: { "span.underline": { label: "underline" } } }, + // `span.underline` tags will be merged into single ones. + expectHTMLToMatchInputExactly: false, + }); + + testHTMLAsRichTextHelper("nested spans", { + input: /* html */ `

lorem ipsum dolor sit amet

`, + }); + }); + + describe("does not compact different", () => { + testHTMLAsRichTextHelper("hyperlink", { + input: /* html */ `

lorem ipsum dolor sit amet

`, + }); + + testHTMLAsRichTextHelper("label", { + input: /* html */ `

lorem ipsum dolor sit amet

`, + config: { + serializer: { + "span.underline": { label: "underline" }, + "span.strikethrough": { label: "strikethrough" }, + }, + }, + }); + }); + }); + }); + + describe("lists", () => { + testHTMLAsRichTextHelper("list-item", { + input: /* html */ `
  • lorem ipsum dolor sit amet
  • consectetur adipiscing elit
`, + }); + + testHTMLAsRichTextHelper("o-list-item", { + input: /* html */ `
  1. lorem ipsum dolor sit amet
  2. consectetur adipiscing elit
`, + }); + + testHTMLAsRichTextHelper("list-item and o-list-item", { + input: /* html */ `
  1. lorem ipsum dolor sit amet
  2. consectetur adipiscing elit
  • sed do eiusmod tempor incididunt
  • ut labore et dolore magna aliqua
`, + }); + + // We expect the last list item to be an `o-list-item` because we don't support nested lists. + testHTMLAsRichTextHelper("nested list-item and o-list-item", { + input: /* html */ `
  • lorem ipsum dolor sit amet
    1. consectetur adipiscing elit
    2. sed do eiusmod tempor incididunt
  • ut labore et dolore magna aliqua
`, + expectHTMLToMatchInputExactly: false, + }); + }); + + describe("image", () => { + testHTMLAsRichTextHelper("non-prismic", { + input: /* html */ `foo`, + }); + + testHTMLAsRichTextHelper("prismic", { + input: /* html */ `foo`, + }); + + testHTMLAsRichTextHelper("empty alt", { + input: /* html */ ``, + }); + + testHTMLAsRichTextHelper("missing alt", { + input: /* html */ ``, + expectHTMLToMatchInputExactly: false, + }); + + testHTMLAsRichTextHelper("missing src", { + input: /* html */ ``, + expectWarnings: [ + "1:1-1:8: Element of type `img` is missing an `src` attribute", + ], + expectHTMLToMatchInputExactly: false, + }); + + describe("extracts image in text nodes and resume previous text node", () => { + testHTMLAsRichTextHelper("basic", { + input: /* html */ `

lorem ipsum bar dolor sit amet

`, + expectHTMLToMatchInputExactly: false, + }); + + testHTMLAsRichTextHelper("spans", { + input: /* html */ `

lorem ipsum bar dolor sit amet

`, + expectHTMLToMatchInputExactly: false, + }); + + testHTMLAsRichTextHelper("adjacent spans", { + input: /* html */ `

lorem ipsum bar dolor sit amet

`, + expectHTMLToMatchInputExactly: false, + }); + + testHTMLAsRichTextHelper("adjacent spans (respects direction)", { + input: /* html */ `

lorem ipsum bar dolor sit amet

`, + config: { direction: "rtl" }, + expectHTMLToMatchInputExactly: false, + }); + }); + }); + + describe("embed", () => { + testHTMLAsRichTextHelper("iframe", { + input: /* html */ ``, + }); + + // TODO: Update with function serializer to remove the first link + testHTMLAsRichTextHelper("custom", { + input: /* html */ ``, + config: { serializer: { blockquote: "embed" } }, + }); + + testHTMLAsRichTextHelper("missing src", { + input: /* html */ ``, + expectWarnings: [ + "1:1-1:18: Element of type `embed` is missing an `src` attribute", + ], + expectHTMLToMatchInputExactly: false, + }); + }); + + describe("configuration", () => { + describe("serializer", () => { + testHTMLAsRichTextHelper("tag name", { + input: /* html */ `

lorem ipsum dolor sit amet

`, + config: { + serializer: { + p: "heading1", + }, + }, + expectHTMLToMatchInputExactly: false, + }); + + testHTMLAsRichTextHelper("selector", { + input: /* html */ `

lorem ipsum dolor sit amet

consectetur adipiscing elit

`, + config: { + serializer: { + "#foo": "heading1", + }, + }, + expectHTMLToMatchInputExactly: false, + }); + + testHTMLAsRichTextHelper("complex selector", { + input: /* html */ ` +

lorem ipsum dolor sit amet

+

consectetur adipiscing elit

`, + config: { + serializer: { + "article#foo > p": "heading1", + }, + }, + expectHTMLToMatchInputExactly: false, + }); + + describe("shorthand serializer", () => { + it("throws on invalide rich text node type", () => { + expect(() => + unstable_htmlAsRichText( + /* html */ `

lorem ipsum dolor sit amet

`, + { + serializer: { p: "foo" as "paragraph" }, + }, + ), + ).toThrowErrorMatchingInlineSnapshot( + '"Unknown rich text node type: `foo`"', + ); + }); + }); + + describe("function serializer", () => { + testHTMLAsRichTextHelper("basic", { + input: /* html */ `

lorem ipsum dolor sit amet

consectetur adipiscing elit

`, + config: { + serializer: { + p: ({ node }) => + "dataHeading" in node.properties ? "heading1" : "paragraph", + }, + }, + expectHTMLToMatchInputExactly: false, + }); + + testHTMLAsRichTextHelper("undefined", { + input: /* html */ `

lorem ipsum dolor sit amet

consectetur adipiscing elit

`, + config: { + serializer: { + p: ({ node }) => + "dataHeading" in node.properties ? undefined : "paragraph", + }, + }, + expectHTMLToMatchInputExactly: false, + }); + + testHTMLAsRichTextHelper("text nodes", { + input: /* html */ `
lorem ipsum dolor sit amet
`, + config: { + serializer: { + div: () => ({ type: "paragraph", text: "", spans: [] }), + }, + }, + expectHTMLToMatchInputExactly: false, + }); + + testHTMLAsRichTextHelper("image node", { + input: /* html */ ``, + config: { + serializer: { + foo: () => ({ + type: "image", + id: "bar", + url: "baz", + alt: "qux", + copyright: null, + dimensions: { width: 0, height: 0 }, + edit: { x: 0, y: 0, zoom: 1, background: "transparent" }, + }), + }, + }, + expectHTMLToMatchInputExactly: false, + }); + + testHTMLAsRichTextHelper("embed node", { + input: /* html */ ``, + config: { + serializer: { + foo: () => ({ + type: "embed", + oembed: { + version: "1.0", + embed_url: "bar", + html: "baz", + title: "qux", + type: "rich", + width: 0, + height: 0, + }, + }), + }, + }, + expectHTMLToMatchInputExactly: false, + }); + + describe("span nodes", () => { + testHTMLAsRichTextHelper("partial", { + input: /* html */ `

lorem ipsum dolor sit amet

`, + config: { + serializer: { + span: () => ({ type: "strong" }), + }, + }, + expectHTMLToMatchInputExactly: false, + }); + + testHTMLAsRichTextHelper("full", { + input: /* html */ `

lorem ipsum dolor sit amet

`, + config: { + serializer: { + span: () => ({ type: "strong", start: 0, end: 5 }), + }, + }, + expectHTMLToMatchInputExactly: false, + }); + + describe("with extracted image node", () => { + testHTMLAsRichTextHelper("partial", { + input: /* html */ `

lorem ipsum bar dolor sit amet

`, + config: { + serializer: { + span: () => ({ type: "strong" }), + }, + }, + expectHTMLToMatchInputExactly: false, + }); + + testHTMLAsRichTextHelper("full", { + input: /* html */ `

lorem ipsum bar dolor sit amet

`, + config: { + serializer: { + span: () => ({ type: "strong", start: 6, end: 9 }), + }, + }, + expectHTMLToMatchInputExactly: false, + }); + }); + }); + }); + }); + + describe("container", () => { + testHTMLAsRichTextHelper("converts only the given container", { + input: /* html */ ` +

lorem ipsum dolor sit amet

+

consectetur adipiscing elit

`, + config: { container: "article#bar" }, + expectHTMLToMatchInputExactly: false, + }); + + it("throws when the container cannot be found", () => { + expect(() => + unstable_htmlAsRichText("", { container: "article#baz" }), + ).toThrowErrorMatchingInlineSnapshot( + '"No container matching `article#baz` could be found in the input AST."', + ); + }); + }); + + describe("exclude", () => { + testHTMLAsRichTextHelper("excludes the given selectors", { + input: /* html */ ` +

lorem ipsum dolor sit amet

+

consectetur adipiscing elit

`, + config: { exclude: ["h1"] }, + expectHTMLToMatchInputExactly: false, + }); + + testHTMLAsRichTextHelper("excludes the given complex selectors", { + input: /* html */ ` +

lorem ipsum dolor sit amet

+

consectetur adipiscing elit

`, + config: { exclude: ["h1 > a"] }, + expectHTMLToMatchInputExactly: false, + }); + }); + + describe("include", () => { + testHTMLAsRichTextHelper("includes only the given selectors", { + input: /* html */ ` +

lorem ipsum dolor sit amet

+

consectetur adipiscing elit

`, + config: { include: ["h1"] }, + expectHTMLToMatchInputExactly: false, + }); + + testHTMLAsRichTextHelper("includes only the given complex selectors", { + input: /* html */ ` +

lorem ipsum dolor sit amet

+

consectetur adipiscing elit

`, + config: { include: ["article#bar > p"] }, + expectHTMLToMatchInputExactly: false, + }); + + testHTMLAsRichTextHelper( + "dedupes matches that are child of other matches", + { + input: /* html */ ` +

lorem ipsum dolor sit amet

+

consectetur adipiscing elit

`, + config: { include: ["article#bar", "p"] }, + expectHTMLToMatchInputExactly: false, + }, + ); + }); + + describe("model", () => { + testHTMLAsRichTextHelper( + "filters output according to single type model", + { + input: /* html */ `

lorem ipsum dolor sit amet

consectetur adipiscing elit

`, + config: { + model: { + type: "StructuredText", + config: { single: "heading1,strong" }, + }, + }, + expectHTMLToMatchInputExactly: false, + }, + ); + + testHTMLAsRichTextHelper("filters output according to multi type model", { + input: /* html */ ` +

lorem ipsum dolor sit amet

+

consectetur adipiscing elit

+

sed do eiusmod tempor

`, + config: { + model: { + type: "StructuredText", + config: { multi: "paragraph,strong" }, + }, + }, + expectHTMLToMatchInputExactly: false, + }); + }); + + describe("direction", () => { + testHTMLAsRichTextHelper("marks text as left-to-right", { + input: /* html */ `

lorem ipsum dolor sit amet

`, + config: { direction: "ltr" }, + }); + + testHTMLAsRichTextHelper("marks text as right-to-left", { + input: /* html */ `

lorem ipsum dolor sit amet

`, + config: { direction: "rtl" }, + }); + }); + }); + + describe("whistespaces", () => { + testHTMLAsRichTextHelper("treats `
` as new lines", { + input: /* html */ `

lorem ipsum dolor sit amet
consectetur adipiscing elit

`, + }); + + testHTMLAsRichTextHelper("ignores wild `
`", { + input: /* html */ `

lorem ipsum dolor sit amet

`, + expectHTMLToMatchInputExactly: false, + }); + + testHTMLAsRichTextHelper("strips indentation", { + input: /* html */ ` +

+ lorem ipsum dolor sit amet +

+ `, + expectHTMLToMatchInputExactly: false, + }); + + testHTMLAsRichTextHelper("strips complex indentation", { + input: /* html */ ` +

+ lorem ipsum dolor sit amet + consectetur adipiscing elit +

+ `, + expectHTMLToMatchInputExactly: false, + }); + }); +}); diff --git a/test/helpers-unstable_markdownAsRichText.test.ts b/test/helpers-unstable_markdownAsRichText.test.ts new file mode 100644 index 00000000..857c9e2f --- /dev/null +++ b/test/helpers-unstable_markdownAsRichText.test.ts @@ -0,0 +1,20 @@ +import { describe } from "vitest"; + +import { testMarkdownAsRichTextHelper } from "./__testutils__/testAsRichTextHelper"; + +describe("transforms markdown to rich text", () => { + describe("basic", () => { + testMarkdownAsRichTextHelper("empty", { + input: /* md */ ``, + }); + + testMarkdownAsRichTextHelper("single tag", { + input: /* md */ `lorem ipsum dolor sit amet`, + }); + + testMarkdownAsRichTextHelper("multiple tags", { + input: /* md */ `# lorem ipsum dolor sit amet + consectetur adipiscing elit`, + }); + }); +}); diff --git a/test/lib-filterRichTextField.test.ts b/test/lib-filterRichTextField.test.ts new file mode 100644 index 00000000..20782d23 --- /dev/null +++ b/test/lib-filterRichTextField.test.ts @@ -0,0 +1,260 @@ +import { expect, it } from "vitest"; + +import { createRichTextFixtures } from "./__testutils__/createRichTextFixtures"; + +import { LinkType, RTImageNode, RTParagraphNode, RichTextField } from "../src"; +import { filterRichTextField } from "../src/lib/filterRichTextField"; + +it("filters out node types that aren't allowed by single type model", () => { + const input: RichTextField = [ + { + spans: [], + text: "lorem ipsum dolor sit amet", + type: "heading1", + }, + { + spans: [], + text: "consectetur adipiscing elit", + type: "paragraph", + }, + { + spans: [], + text: "sed do eiusmod tempor", + type: "heading1", + }, + ]; + + const allowedNodeTypes = ["heading1"]; + + const output = filterRichTextField(input, { + type: "StructuredText", + config: { + single: allowedNodeTypes.join(","), + }, + }); + + expect(output).toHaveLength(1); + expect(output.every((node) => allowedNodeTypes.includes(node.type))).toBe( + true, + ); +}); + +it("filters out node types that aren't allowed by multi type model", () => { + const { en: input } = createRichTextFixtures(); + + const allowedNodeTypes = ["paragraph", "embed"]; + + const output = filterRichTextField(input, { + type: "StructuredText", + config: { + multi: allowedNodeTypes.join(","), + }, + }); + + expect(output.length).toBeGreaterThanOrEqual(1); + expect(output.every((node) => allowedNodeTypes.includes(node.type))).toBe( + true, + ); +}); + +it("filters out span node types that aren't allowed by single type model", () => { + const input: RichTextField = [ + { + spans: [ + { + end: 11, + start: 6, + type: "strong", + }, + { + end: 21, + start: 18, + type: "em", + }, + ], + text: "lorem ipsum dolor sit amet", + type: "paragraph", + }, + ]; + + const allowedNodeTypes = ["paragraph", "strong"]; + + const [output] = filterRichTextField(input, { + type: "StructuredText", + config: { + single: allowedNodeTypes.join(","), + }, + }) as RTParagraphNode[]; + + expect(output.spans.length).toBeGreaterThanOrEqual(1); + expect( + output.spans.every((span) => allowedNodeTypes.includes(span.type)), + ).toBe(true); +}); + +it("filters out label span nodes that aren't allowed by single type model", () => { + const input: RichTextField = [ + { + spans: [ + { + end: 11, + start: 6, + type: "label", + data: { label: "code" }, + }, + { + end: 21, + start: 18, + type: "label", + data: { label: "highlight" }, + }, + ], + text: "lorem ipsum dolor sit amet", + type: "paragraph", + }, + ]; + + const allowedLabelTypes = ["code"]; + + const [output] = filterRichTextField(input, { + type: "StructuredText", + config: { + single: "paragraph", + labels: allowedLabelTypes, + }, + }) as RTParagraphNode[]; + + expect(output.spans.length).toBeGreaterThanOrEqual(1); + expect( + output.spans.every( + (span) => + span.type === "label" && allowedLabelTypes.includes(span.data.label), + ), + ).toBe(true); +}); + +it('removes `target: "_blank"` from hyperlink spans when they are not allowed', () => { + const input: RichTextField = [ + { + spans: [ + { + end: 11, + start: 6, + type: "hyperlink", + data: { link_type: LinkType.Web, url: "https://prismic.io" }, + }, + { + end: 21, + start: 18, + type: "hyperlink", + data: { + link_type: LinkType.Web, + url: "https://google.com", + target: "_blank", + }, + }, + ], + text: "lorem ipsum dolor sit amet", + type: "paragraph", + }, + ]; + + const [allowedOutput] = filterRichTextField(input, { + type: "StructuredText", + config: { + single: "paragraph,hyperlink", + allowTargetBlank: true, + }, + }) as RTParagraphNode[]; + + expect(allowedOutput.spans.length).toBeGreaterThanOrEqual(1); + expect( + allowedOutput.spans.every( + (span) => + span.type === "hyperlink" && + span.data.link_type === LinkType.Web && + !span.data.target, + ), + ).toBe(false); + + const [notAllowedOutput] = filterRichTextField(input, { + type: "StructuredText", + config: { + single: "paragraph,hyperlink", + allowTargetBlank: false, + }, + }) as RTParagraphNode[]; + + expect(notAllowedOutput.spans.length).toBeGreaterThanOrEqual(1); + expect( + notAllowedOutput.spans.every( + (span) => + span.type === "hyperlink" && + span.data.link_type === LinkType.Web && + !span.data.target, + ), + ).toBe(true); +}); + +it('removes `target: "_blank"` from image nodes when they are not allowed', () => { + const baseImageNode = { + type: "image", + url: "https://example.com/image.jpg", + id: "", + alt: "", + copyright: "", + dimensions: { height: 100, width: 100 }, + edit: { x: 0, y: 0, zoom: 1, background: "transparent" }, + } as const; + + const input: RichTextField = [ + { + ...baseImageNode, + linkTo: { link_type: LinkType.Web, url: "https://prismic.io" }, + }, + { + ...baseImageNode, + linkTo: { + link_type: LinkType.Web, + url: "https://prismic.io", + target: "_blank", + }, + }, + ]; + + const allowedOutput = filterRichTextField(input, { + type: "StructuredText", + config: { + multi: "image", + allowTargetBlank: true, + }, + }) as RTImageNode[]; + + expect(allowedOutput.length).toBeGreaterThanOrEqual(1); + expect( + allowedOutput.every( + (node) => + node.linkTo && + node.linkTo.link_type === LinkType.Web && + !node.linkTo.target, + ), + ).toBe(false); + + const notAllowedOutput = filterRichTextField(input, { + type: "StructuredText", + config: { + multi: "image", + allowTargetBlank: false, + }, + }) as RTImageNode[]; + + expect(notAllowedOutput.length).toBeGreaterThanOrEqual(1); + expect( + notAllowedOutput.every( + (node) => + node.linkTo && + node.linkTo.link_type === LinkType.Web && + !node.linkTo.target, + ), + ).toBe(true); +});