diff --git a/package-lock.json b/package-lock.json
index 041d714f71..6c6ab288f2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -66,6 +66,7 @@
"integrity": "sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.26.2",
@@ -1991,6 +1992,7 @@
"url": "https://opencollective.com/csstools"
}
],
+ "peer": true,
"engines": {
"node": "^14 || ^16 || >=18"
},
@@ -2013,6 +2015,7 @@
"url": "https://opencollective.com/csstools"
}
],
+ "peer": true,
"engines": {
"node": "^14 || ^16 || >=18"
}
@@ -4569,6 +4572,7 @@
"integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@octokit/auth-token": "^4.0.0",
"@octokit/graphql": "^7.1.0",
@@ -5321,6 +5325,7 @@
"integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@eslint-community/regexpp": "^4.4.0",
"@typescript-eslint/scope-manager": "5.62.0",
@@ -5356,6 +5361,7 @@
"integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==",
"dev": true,
"license": "BSD-2-Clause",
+ "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "5.62.0",
"@typescript-eslint/types": "5.62.0",
@@ -6267,6 +6273,7 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
"dev": true,
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -6320,6 +6327,7 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -6725,6 +6733,7 @@
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
@@ -6781,6 +6790,7 @@
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -7314,6 +7324,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001688",
"electron-to-chromium": "^1.5.73",
@@ -9477,6 +9488,7 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"dev": true,
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@@ -10424,17 +10436,6 @@
"node": ">= 0.8"
}
},
- "node_modules/encoding": {
- "version": "0.1.13",
- "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
- "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "iconv-lite": "^0.6.2"
- }
- },
"node_modules/end-of-stream": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
@@ -10774,6 +10775,7 @@
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz",
"integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==",
"dev": true,
+ "peer": true,
"dependencies": {
"@babel/code-frame": "7.12.11",
"@eslint/eslintrc": "^0.4.3",
@@ -10921,6 +10923,7 @@
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz",
"integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==",
"dev": true,
+ "peer": true,
"dependencies": {
"array-includes": "^3.1.6",
"array.prototype.flat": "^1.3.1",
@@ -11825,6 +11828,7 @@
"integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"tabbable": "^6.4.0"
}
@@ -12791,10 +12795,20 @@
"license": "ISC"
},
"node_modules/html-entities": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz",
- "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==",
- "dev": true
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz",
+ "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/mdevils"
+ },
+ {
+ "type": "patreon",
+ "url": "https://patreon.com/mdevils"
+ }
+ ],
+ "license": "MIT"
},
"node_modules/html-escaper": {
"version": "2.0.2",
@@ -16069,6 +16083,7 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"dev": true,
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@@ -17058,6 +17073,7 @@
"dev": true,
"hasInstallScript": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@napi-rs/wasm-runtime": "0.2.4",
"@yarnpkg/lockfile": "^1.1.0",
@@ -18098,6 +18114,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -18207,7 +18224,6 @@
"resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-1.6.0.tgz",
"integrity": "sha512-OWgQ9/Pe23MnNJC0PL4uZp8k0EDaUvqpJFSiwFxOLClAhmD7UEisyhO3x5hVsD4xFrjReVTXydlrMes45dJ71w==",
"dev": true,
- "peer": true,
"dependencies": {
"htmlparser2": "^8.0.0",
"js-tokens": "^8.0.0",
@@ -18223,7 +18239,6 @@
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
"dev": true,
- "peer": true,
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.2",
@@ -18243,15 +18258,13 @@
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
- ],
- "peer": true
+ ]
},
"node_modules/postcss-html/node_modules/domhandler": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
"dev": true,
- "peer": true,
"dependencies": {
"domelementtype": "^2.3.0"
},
@@ -18267,7 +18280,6 @@
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
"integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
"dev": true,
- "peer": true,
"dependencies": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
@@ -18282,7 +18294,6 @@
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"dev": true,
- "peer": true,
"engines": {
"node": ">=0.12"
},
@@ -18302,7 +18313,6 @@
"url": "https://github.com/sponsors/fb55"
}
],
- "peer": true,
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
@@ -18314,15 +18324,13 @@
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-8.0.3.tgz",
"integrity": "sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==",
- "dev": true,
- "peer": true
+ "dev": true
},
"node_modules/postcss-html/node_modules/postcss-safe-parser": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz",
"integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==",
"dev": true,
- "peer": true,
"engines": {
"node": ">=12.0"
},
@@ -18766,6 +18774,7 @@
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz",
"integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==",
"dev": true,
+ "peer": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@@ -20886,6 +20895,7 @@
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.2.1.tgz",
"integrity": "sha512-SfIMGFK+4n7XVAyv50CpVfcGYWG4v41y6xG7PqOgQSY8M/PgdK0SQbjWFblxjJZlN9jNq879mB4BCZHJRIJ1hA==",
"dev": true,
+ "peer": true,
"dependencies": {
"@csstools/css-parser-algorithms": "^2.5.0",
"@csstools/css-tokenizer": "^2.2.3",
@@ -21883,6 +21893,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=12"
},
@@ -22195,7 +22206,8 @@
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
"integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==",
- "dev": true
+ "dev": true,
+ "peer": true
},
"node_modules/tsutils": {
"version": "3.21.0",
@@ -22293,6 +22305,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"license": "Apache-2.0",
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -22626,6 +22639,7 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-3.3.11.tgz",
"integrity": "sha512-d4oBctG92CRO1cQfVBZp6WJAs0n8AK4Xf5fNjQCBeKCvMI1efGQ5E3Alt1slFJS9fZuPcFoiAiqFvQlv1X7t/w==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@vue/compiler-dom": "3.3.11",
"@vue/compiler-sfc": "3.3.11",
@@ -22852,6 +22866,7 @@
"integrity": "sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.6",
@@ -22899,6 +22914,7 @@
"integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@discoveryjs/json-ext": "^0.6.1",
"@webpack-cli/configtest": "^3.0.1",
@@ -22980,6 +22996,7 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"dev": true,
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@@ -23083,6 +23100,7 @@
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -23309,6 +23327,7 @@
"version": "2.4.7",
"resolved": "https://registry.npmjs.org/winston/-/winston-2.4.7.tgz",
"integrity": "sha512-vLB4BqzCKDnnZH9PHGoS2ycawueX4HLqENXQitvFHczhgW2vFpSOn31LZtVr1KU8YTw7DS4tM+cqyovxo8taVg==",
+ "peer": true,
"dependencies": {
"async": "^2.6.4",
"colors": "1.0.x",
@@ -25208,6 +25227,7 @@
"fs-extra": "^9.0.1",
"gh-pages": "^6.3.0",
"highlight.js": "^10.4.1",
+ "html-entities": "^2.6.0",
"htmlparser2": "^3.10.1",
"ignore": "^5.1.4",
"js-beautify": "1.14.3",
@@ -25841,6 +25861,7 @@
"integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@jest/core": "^29.7.0",
"@jest/types": "^29.6.3",
@@ -26574,6 +26595,9 @@
"name": "@markbind/vue-components",
"version": "6.1.0",
"license": "MIT",
+ "dependencies": {
+ "html-entities": "^2.6.0"
+ },
"devDependencies": {
"@babel/core": "^7.26.9",
"@babel/plugin-transform-runtime": "^7.26.9",
@@ -27076,6 +27100,7 @@
"integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@jest/transform": "^29.7.0",
"@types/babel__core": "^7.1.14",
@@ -27379,6 +27404,7 @@
"integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@jest/core": "^29.7.0",
"@jest/types": "^29.6.3",
@@ -28307,6 +28333,7 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.9.tgz",
"integrity": "sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==",
"dev": true,
+ "peer": true,
"requires": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.26.2",
@@ -29576,13 +29603,15 @@
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.5.0.tgz",
"integrity": "sha512-abypo6m9re3clXA00eu5syw+oaPHbJTPapu9C4pzNsJ4hdZDzushT50Zhu+iIYXgEe1CxnRMn7ngsbV+MLrlpQ==",
"dev": true,
+ "peer": true,
"requires": {}
},
"@csstools/css-tokenizer": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.2.3.tgz",
"integrity": "sha512-pp//EvZ9dUmGuGtG1p+n17gTHEOqu9jO+FiCUjNN3BDmyhdA2Jq9QsVeR7K8/2QCK17HSsioPlTW9ZkzoWb3Lg==",
- "dev": true
+ "dev": true,
+ "peer": true
},
"@csstools/media-query-list-parser": {
"version": "2.1.7",
@@ -30739,6 +30768,7 @@
"fs-extra": "^9.0.1",
"gh-pages": "^6.3.0",
"highlight.js": "^10.4.1",
+ "html-entities": "^2.6.0",
"htmlparser2": "^3.10.1",
"ignore": "^5.1.4",
"jest": "^29.7.0",
@@ -31152,6 +31182,7 @@
"resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
"integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
"dev": true,
+ "peer": true,
"requires": {
"@jest/core": "^29.7.0",
"@jest/types": "^29.6.3",
@@ -31726,6 +31757,7 @@
"css-loader": "^3.6.0",
"eslint-plugin-vue": "^9.33.0",
"floating-vue": "^5.2.2",
+ "html-entities": "^2.6.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"portal-vue": "^3.0.0",
@@ -32087,6 +32119,7 @@
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
"integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==",
"dev": true,
+ "peer": true,
"requires": {
"@jest/transform": "^29.7.0",
"@types/babel__core": "^7.1.14",
@@ -32288,6 +32321,7 @@
"resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
"integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
"dev": true,
+ "peer": true,
"requires": {
"@jest/core": "^29.7.0",
"@jest/types": "^29.6.3",
@@ -33485,6 +33519,7 @@
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.2.tgz",
"integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==",
"dev": true,
+ "peer": true,
"requires": {
"@octokit/auth-token": "^4.0.0",
"@octokit/graphql": "^7.1.0",
@@ -34113,6 +34148,7 @@
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz",
"integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==",
"dev": true,
+ "peer": true,
"requires": {
"@eslint-community/regexpp": "^4.4.0",
"@typescript-eslint/scope-manager": "5.62.0",
@@ -34131,6 +34167,7 @@
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz",
"integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==",
"dev": true,
+ "peer": true,
"requires": {
"@typescript-eslint/scope-manager": "5.62.0",
"@typescript-eslint/types": "5.62.0",
@@ -34719,7 +34756,8 @@
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
- "dev": true
+ "dev": true,
+ "peer": true
},
"acorn-jsx": {
"version": "5.3.2",
@@ -34758,6 +34796,7 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
+ "peer": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -35035,6 +35074,7 @@
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
"dev": true,
+ "peer": true,
"requires": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
@@ -35078,6 +35118,7 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
+ "peer": true,
"requires": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -35437,6 +35478,7 @@
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz",
"integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==",
"dev": true,
+ "peer": true,
"requires": {
"caniuse-lite": "^1.0.30001688",
"electron-to-chromium": "^1.5.73",
@@ -37001,6 +37043,7 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"dev": true,
+ "peer": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@@ -37702,16 +37745,6 @@
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="
},
- "encoding": {
- "version": "0.1.13",
- "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
- "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
- "dev": true,
- "optional": true,
- "requires": {
- "iconv-lite": "^0.6.2"
- }
- },
"end-of-stream": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
@@ -37969,6 +38002,7 @@
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz",
"integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==",
"dev": true,
+ "peer": true,
"requires": {
"@babel/code-frame": "7.12.11",
"@eslint/eslintrc": "^0.4.3",
@@ -38102,6 +38136,7 @@
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz",
"integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==",
"dev": true,
+ "peer": true,
"requires": {
"array-includes": "^3.1.6",
"array.prototype.flat": "^1.3.1",
@@ -38779,6 +38814,7 @@
"resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.8.0.tgz",
"integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==",
"dev": true,
+ "peer": true,
"requires": {
"tabbable": "^6.4.0"
}
@@ -39461,10 +39497,9 @@
}
},
"html-entities": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz",
- "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==",
- "dev": true
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz",
+ "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ=="
},
"html-escaper": {
"version": "2.0.2",
@@ -43053,6 +43088,7 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"dev": true,
+ "peer": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@@ -43789,6 +43825,7 @@
"resolved": "https://registry.npmjs.org/nx/-/nx-20.8.3.tgz",
"integrity": "sha512-8w815WSMWar3A/LFzwtmEY+E8cVW62lMiFuPDXje+C8O8hFndfvscP56QHNMn2Zdhz3q0+BZUe+se4Em1BKYdA==",
"dev": true,
+ "peer": true,
"requires": {
"@napi-rs/wasm-runtime": "0.2.4",
"@nx/nx-darwin-arm64": "20.8.3",
@@ -44506,6 +44543,7 @@
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "peer": true,
"requires": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -44577,7 +44615,6 @@
"resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-1.6.0.tgz",
"integrity": "sha512-OWgQ9/Pe23MnNJC0PL4uZp8k0EDaUvqpJFSiwFxOLClAhmD7UEisyhO3x5hVsD4xFrjReVTXydlrMes45dJ71w==",
"dev": true,
- "peer": true,
"requires": {
"htmlparser2": "^8.0.0",
"js-tokens": "^8.0.0",
@@ -44590,7 +44627,6 @@
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
"dev": true,
- "peer": true,
"requires": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.2",
@@ -44601,15 +44637,13 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
- "dev": true,
- "peer": true
+ "dev": true
},
"domhandler": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
"dev": true,
- "peer": true,
"requires": {
"domelementtype": "^2.3.0"
}
@@ -44619,7 +44653,6 @@
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
"integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
"dev": true,
- "peer": true,
"requires": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
@@ -44630,15 +44663,13 @@
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
- "dev": true,
- "peer": true
+ "dev": true
},
"htmlparser2": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
"dev": true,
- "peer": true,
"requires": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
@@ -44650,15 +44681,13 @@
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-8.0.3.tgz",
"integrity": "sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==",
- "dev": true,
- "peer": true
+ "dev": true
},
"postcss-safe-parser": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz",
"integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==",
"dev": true,
- "peer": true,
"requires": {}
}
}
@@ -44959,6 +44988,7 @@
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz",
"integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==",
"dev": true,
+ "peer": true,
"requires": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@@ -46508,6 +46538,7 @@
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.2.1.tgz",
"integrity": "sha512-SfIMGFK+4n7XVAyv50CpVfcGYWG4v41y6xG7PqOgQSY8M/PgdK0SQbjWFblxjJZlN9jNq879mB4BCZHJRIJ1hA==",
"dev": true,
+ "peer": true,
"requires": {
"@csstools/css-parser-algorithms": "^2.5.0",
"@csstools/css-tokenizer": "^2.2.3",
@@ -47218,7 +47249,8 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
- "dev": true
+ "dev": true,
+ "peer": true
}
}
},
@@ -47455,7 +47487,8 @@
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
"integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==",
- "dev": true
+ "dev": true,
+ "peer": true
},
"tsutils": {
"version": "3.21.0",
@@ -47527,7 +47560,8 @@
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
- "devOptional": true
+ "devOptional": true,
+ "peer": true
},
"uc.micro": {
"version": "1.0.6",
@@ -47757,6 +47791,7 @@
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.3.11.tgz",
"integrity": "sha512-d4oBctG92CRO1cQfVBZp6WJAs0n8AK4Xf5fNjQCBeKCvMI1efGQ5E3Alt1slFJS9fZuPcFoiAiqFvQlv1X7t/w==",
+ "peer": true,
"requires": {
"@vue/compiler-dom": "3.3.11",
"@vue/compiler-sfc": "3.3.11",
@@ -47922,6 +47957,7 @@
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.98.0.tgz",
"integrity": "sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==",
"dev": true,
+ "peer": true,
"requires": {
"@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.6",
@@ -47959,6 +47995,7 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
+ "peer": true,
"requires": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -48049,6 +48086,7 @@
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz",
"integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==",
"dev": true,
+ "peer": true,
"requires": {
"@discoveryjs/json-ext": "^0.6.1",
"@webpack-cli/configtest": "^3.0.1",
@@ -48091,6 +48129,7 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"dev": true,
+ "peer": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@@ -48229,6 +48268,7 @@
"version": "2.4.7",
"resolved": "https://registry.npmjs.org/winston/-/winston-2.4.7.tgz",
"integrity": "sha512-vLB4BqzCKDnnZH9PHGoS2ycawueX4HLqENXQitvFHczhgW2vFpSOn31LZtVr1KU8YTw7DS4tM+cqyovxo8taVg==",
+ "peer": true,
"requires": {
"async": "^2.6.4",
"colors": "1.0.x",
diff --git a/packages/core/package.json b/packages/core/package.json
index 2ba7e9ef4d..bf00251f1e 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -42,6 +42,7 @@
"fs-extra": "^9.0.1",
"gh-pages": "^6.3.0",
"highlight.js": "^10.4.1",
+ "html-entities": "^2.6.0",
"htmlparser2": "^3.10.1",
"ignore": "^5.1.4",
"js-beautify": "1.14.3",
diff --git a/packages/core/src/html/MdAttributeRenderer.ts b/packages/core/src/html/MdAttributeRenderer.ts
index 442689d883..f454763ff3 100644
--- a/packages/core/src/html/MdAttributeRenderer.ts
+++ b/packages/core/src/html/MdAttributeRenderer.ts
@@ -9,6 +9,8 @@ const _ = {
has,
};
+export type CardStackTagConfig = { name: string; color?: string };
+
/**
* Class that is responsible for rendering markdown-in-attributes
*/
@@ -30,7 +32,7 @@ export class MdAttributeRenderer {
processAttributeWithoutOverride(node: MbNode, attribute: string,
isInline: boolean, slotName = attribute): void {
const hasAttributeSlot = node.children
- && node.children.some(child => getVslotShorthandName(child) === slotName);
+ && node.children.some(child => getVslotShorthandName(child) === slotName);
if (!hasAttributeSlot && _.has(node.attribs, attribute)) {
let rendered;
@@ -167,6 +169,8 @@ export class MdAttributeRenderer {
this.processSlotAttribute(node, 'header', true);
}
+ // eslint-disable-next-line class-methods-use-this
+
/*
* Dropdowns
*/
diff --git a/packages/core/src/html/NodeProcessor.ts b/packages/core/src/html/NodeProcessor.ts
index ee8e8ec8d4..3bb857a6d7 100644
--- a/packages/core/src/html/NodeProcessor.ts
+++ b/packages/core/src/html/NodeProcessor.ts
@@ -27,6 +27,7 @@ import { setHeadingId, assignPanelId } from './headerProcessor';
import { FootnoteProcessor } from './FootnoteProcessor';
import { MbNode, NodeOrText, TextElement } from '../utils/node';
import { processUlNode } from './CustomListIconProcessor';
+import { processCardStackAttributes } from './cardStackProcessor';
const fm = require('fastmatter');
@@ -214,6 +215,9 @@ export class NodeProcessor {
case 'card':
this.mdAttributeRenderer.processCardAttributes(node);
break;
+ case 'cardstack':
+ processCardStackAttributes(node);
+ break;
case 'modal':
this.processModal(node);
break;
diff --git a/packages/core/src/html/cardStackProcessor.ts b/packages/core/src/html/cardStackProcessor.ts
new file mode 100644
index 0000000000..7670cd951c
--- /dev/null
+++ b/packages/core/src/html/cardStackProcessor.ts
@@ -0,0 +1,55 @@
+import { encode } from 'html-entities';
+import { MbNode, NodeOrText } from '../utils/node';
+import { CardStackTagConfig } from './MdAttributeRenderer';
+
+function isTag(child: NodeOrText): child is MbNode {
+ return child.type === 'tag' && (child as MbNode).name === 'tag';
+}
+
+function isTags(child: NodeOrText): child is MbNode {
+ return child.type === 'tag' && (child as MbNode).name === 'tags';
+}
+
+export function processCardStackAttributes(node: MbNode) {
+ // Look for a
child element
+ if (!node.children) {
+ return;
+ }
+
+ const tagsNodeIndex = node.children.findIndex(
+ child => isTags(child),
+ );
+
+ if (tagsNodeIndex === -1) {
+ return;
+ }
+
+ const tagsNode = node.children[tagsNodeIndex] as MbNode;
+ const tagConfigs: Array = [];
+
+ // Parse each element
+ if (tagsNode.children) {
+ tagsNode.children.forEach((child) => {
+ if (isTag(child)) {
+ if (child.attribs?.name) {
+ const config: CardStackTagConfig = {
+ name: child.attribs.name,
+ ...(child.attribs.color && { color: child.attribs.color }),
+ };
+ tagConfigs.push(config);
+ }
+ }
+ });
+ }
+
+ // Add tag-configs as a prop if we found any tags
+ if (tagConfigs.length > 0) {
+ const jsonString = JSON.stringify(tagConfigs);
+ // Replace double quotes with HTML entities to avoid SSR warnings
+ const escapedJson = encode(jsonString);
+ node.attribs['data-tag-configs'] = escapedJson;
+ }
+
+ // Remove the node from the DOM tree
+ node.children.splice(tagsNodeIndex, 1);
+}
diff --git a/packages/core/src/html/codeblockProcessor.ts b/packages/core/src/html/codeblockProcessor.ts
index 85689be6da..55e04f375c 100644
--- a/packages/core/src/html/codeblockProcessor.ts
+++ b/packages/core/src/html/codeblockProcessor.ts
@@ -1,9 +1,9 @@
+import { decode } from 'html-entities';
import cheerio from 'cheerio';
import has from 'lodash/has';
import { NodeOrText, MbNode } from '../utils/node';
import md from '../lib/markdown-it';
-import * as util from '../lib/markdown-it/utils';
const _ = {
has,
@@ -46,7 +46,7 @@ function traverseLinePart(
* so to actually highlight this text, we have to ask to apply at its parent.
*/
- const cleanedText = util.unescapeHtml(node.data);
+ const cleanedText = decode(node.data);
const textLength = cleanedText.length;
resData.numCharsTraversed = textLength;
@@ -129,7 +129,7 @@ function traverseLinePart(
}
} else {
const [start, end] = data.highlightRange;
- const cleaned = util.unescapeHtml(child.data);
+ const cleaned = decode(child.data);
const split = [cleaned.substring(0, start), cleaned.substring(start, end), cleaned.substring(end)];
const [pre, highlighted, post] = split.map(md.utils.escapeHtml);
diff --git a/packages/core/src/lib/markdown-it/utils/index.ts b/packages/core/src/lib/markdown-it/utils/index.ts
deleted file mode 100644
index 589456ce47..0000000000
--- a/packages/core/src/lib/markdown-it/utils/index.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- Extra utility functions related to markdown-it.
- markdown-it library exposes a utility module in markdown-it/utils,
- below are additional functions that can be used as helpers alongside markdown-it/utils
- */
-
-// This mapping is taken from markdown-it/utils, just flipped.
-// Refer to the original file at markdown-it/lib/common/utils.js
-const htmlUnescapedMapping = {
- '&': '&',
- '<': '<',
- '>': '>',
- '"': '"',
- ''': '\'',
-};
-
-// markdown-it/utils have an escapeHtml function, but not the
-// complementary un-escaping function
-
-// Used in highlighting calculations since
-// markdown-it stores text as escaped HTML entities (e.g. <, >).
-// To correctly measure character positions and split text for partial
-// highlights, we must first decode entities back into their real characters.
-
-/**
- * Replaces HTML escape sequences in the input string with their corresponding unescaped characters.
- */
-export function unescapeHtml(str: string) {
- let unescaped = str;
- Object.entries(htmlUnescapedMapping).forEach(([key, value]) => {
- unescaped = unescaped.split(key).join(value);
- });
- return unescaped;
-}
diff --git a/packages/core/test/unit/html/MdAttributeRenderer.test.ts b/packages/core/test/unit/html/MdAttributeRenderer.test.ts
new file mode 100644
index 0000000000..08a7bc102c
--- /dev/null
+++ b/packages/core/test/unit/html/MdAttributeRenderer.test.ts
@@ -0,0 +1,218 @@
+import { processCardStackAttributes } from '../../../src/html/cardStackProcessor';
+import { MbNode, parseHTML } from '../../../src/utils/node';
+
+describe('processCardStackAttributes', () => {
+ it('should do nothing when node has no children', () => {
+ const node: MbNode = {
+ type: 'tag',
+ name: 'cardstack',
+ attribs: {},
+ children: undefined,
+ } as MbNode;
+
+ processCardStackAttributes(node);
+
+ expect(node.attribs['data-tag-configs']).toBeUndefined();
+ });
+
+ it('should do nothing when there is no child element', () => {
+ const html = 'Content';
+ const nodes = parseHTML(html);
+ const cardstackNode = nodes[0] as MbNode;
+
+ processCardStackAttributes(cardstackNode);
+
+ expect(cardstackNode.attribs['data-tag-configs']).toBeUndefined();
+ });
+
+ it('should parse element with single tag config', () => {
+ const html = `
+
+
+
+ Content
+ `;
+ const nodes = parseHTML(html);
+ const cardstackNode = nodes[0] as MbNode;
+
+ processCardStackAttributes(cardstackNode);
+
+ expect(cardstackNode.attribs['data-tag-configs']).toBeDefined();
+ const decodedConfig = cardstackNode.attribs['data-tag-configs']
+ .replace(/"/g, '"');
+ const parsed = JSON.parse(decodedConfig);
+ expect(parsed).toEqual([{ name: 'Success', color: '#28a745' }]);
+ });
+
+ it('should parse element with multiple tag configs', () => {
+ const html = `
+
+
+
+
+
+ Content
+ `;
+ const nodes = parseHTML(html);
+ const cardstackNode = nodes[0] as MbNode;
+
+ processCardStackAttributes(cardstackNode);
+
+ expect(cardstackNode.attribs['data-tag-configs']).toBeDefined();
+ const decodedConfig = cardstackNode.attribs['data-tag-configs']
+ .replace(/"/g, '"');
+ const parsed = JSON.parse(decodedConfig);
+ expect(parsed).toEqual([
+ { name: 'Success', color: '#28a745' },
+ { name: 'Failure', color: '#dc3545' },
+ { name: 'Neutral', color: '#6c757d' },
+ ]);
+ });
+
+ it('should parse tags with Bootstrap color names', () => {
+ const html = `
+
+
+
+
+ Content
+ `;
+ const nodes = parseHTML(html);
+ const cardstackNode = nodes[0] as MbNode;
+
+ processCardStackAttributes(cardstackNode);
+
+ const decodedConfig = cardstackNode.attribs['data-tag-configs']
+ .replace(/"/g, '"');
+ const parsed = JSON.parse(decodedConfig);
+ expect(parsed).toEqual([
+ { name: 'Success', color: 'success' },
+ { name: 'Danger', color: 'danger' },
+ ]);
+ });
+
+ it('should parse tags without color attribute', () => {
+ const html = `
+
+
+
+
+ Content
+ `;
+ const nodes = parseHTML(html);
+ const cardstackNode = nodes[0] as MbNode;
+
+ processCardStackAttributes(cardstackNode);
+
+ const decodedConfig = cardstackNode.attribs['data-tag-configs']
+ .replace(/"/g, '"');
+ const parsed = JSON.parse(decodedConfig);
+ expect(parsed).toEqual([
+ { name: 'Success' },
+ { name: 'Failure', color: '#dc3545' },
+ ]);
+ });
+
+ it('should ignore elements without name attribute', () => {
+ const html = `
+
+
+
+
+ Content
+ `;
+ const nodes = parseHTML(html);
+ const cardstackNode = nodes[0] as MbNode;
+
+ processCardStackAttributes(cardstackNode);
+
+ expect(cardstackNode.attribs['data-tag-configs']).toBeDefined();
+ const decodedConfig = cardstackNode.attribs['data-tag-configs']
+ .replace(/"/g, '"');
+ const parsed = JSON.parse(decodedConfig);
+ expect(parsed).toEqual([{ name: 'Valid', color: '#dc3545' }]);
+ });
+
+ it('should remove the node from the DOM tree', () => {
+ const html = `
+
+
+
+ Content
+ `;
+ const nodes = parseHTML(html);
+ const cardstackNode = nodes[0] as MbNode;
+ const initialChildCount = cardstackNode.children?.length || 0;
+
+ processCardStackAttributes(cardstackNode);
+
+ // Should have removed the element
+ const hasTagsNode = cardstackNode.children?.some(
+ child => child.type === 'tag' && (child as MbNode).name === 'tags',
+ );
+ expect(hasTagsNode).toBe(false);
+ expect((cardstackNode.children?.length || 0)).toBeLessThan(initialChildCount);
+ });
+
+ it('should escape HTML entities in the data attribute', () => {
+ const html = `
+
+
+
+ Content
+ `;
+ const nodes = parseHTML(html);
+ const cardstackNode = nodes[0] as MbNode;
+
+ processCardStackAttributes(cardstackNode);
+
+ const dataAttr = cardstackNode.attribs['data-tag-configs'];
+ // Should contain escaped quotes
+ expect(dataAttr).toContain('"');
+ // Should contain escaped HTML entities for the tag name
+ expect(dataAttr).toContain('<');
+ expect(dataAttr).toContain('>');
+ });
+
+ it('should handle empty element', () => {
+ const html = `
+
+ Content
+ `;
+ const nodes = parseHTML(html);
+ const cardstackNode = nodes[0] as MbNode;
+
+ processCardStackAttributes(cardstackNode);
+
+ // Should remove tags node but not add data-tag-configs since no tags were found
+ expect(cardstackNode.attribs['data-tag-configs']).toBeUndefined();
+ const hasTagsNode = cardstackNode.children?.some(
+ child => child.type === 'tag' && (child as MbNode).name === 'tags',
+ );
+ expect(hasTagsNode).toBe(false);
+ });
+
+ it('should handle with non-tag children', () => {
+ const html = `
+
+
+ Invalid
+
+
+ Content
+ `;
+ const nodes = parseHTML(html);
+ const cardstackNode = nodes[0] as MbNode;
+
+ processCardStackAttributes(cardstackNode);
+
+ const decodedConfig = cardstackNode.attribs['data-tag-configs']
+ .replace(/"/g, '"');
+ const parsed = JSON.parse(decodedConfig);
+ // Should only include the elements, not
+ expect(parsed).toEqual([
+ { name: 'Valid', color: '#28a745' },
+ { name: 'AnotherValid', color: '#dc3545' },
+ ]);
+ });
+});
diff --git a/packages/core/test/unit/lib/markdown-it/utils/index.test.ts b/packages/core/test/unit/lib/markdown-it/utils/index.test.ts
deleted file mode 100644
index 3c48d01cc0..0000000000
--- a/packages/core/test/unit/lib/markdown-it/utils/index.test.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { unescapeHtml } from '../../../../../src/lib/markdown-it/utils';
-
-describe('unescapeHtml', () => {
- test('should unescape HTML entities and handle mixed content', () => {
- const input = 'Hello & welcome to <MarkBind>! &<>"'';
- const expected = 'Hello & welcome to ! &<>"\'';
- const result = unescapeHtml(input);
- expect(result).toBe(expected);
- });
-});
diff --git a/packages/vue-components/package.json b/packages/vue-components/package.json
index d2f667b5da..3cf6a2c805 100644
--- a/packages/vue-components/package.json
+++ b/packages/vue-components/package.json
@@ -44,5 +44,8 @@
"vue-final-modal": "^4.5.5",
"vue-style-loader": "^4.1.3"
},
- "private": true
+ "private": true,
+ "dependencies": {
+ "html-entities": "^2.6.0"
+ }
}
diff --git a/packages/vue-components/src/__tests__/CardStack.spec.js b/packages/vue-components/src/__tests__/CardStack.spec.js
index 8589fe2a9f..dc66604179 100644
--- a/packages/vue-components/src/__tests__/CardStack.spec.js
+++ b/packages/vue-components/src/__tests__/CardStack.spec.js
@@ -42,6 +42,12 @@ const MARKDOWN_CARDS = `
`;
+const CARDS_WITH_CUSTOM_TAGS = `
+
+
+
+`;
+
describe('CardStack', () => {
test('should not hide cards when no filter is provided', async () => {
const wrapper = mount(CardStack, {
@@ -228,4 +234,130 @@ describe('CardStack', () => {
const selectAllBadge = wrapper.find('.select-all-toggle');
expect(selectAllBadge.exists()).toBe(false);
});
+
+ test('should respect custom tag order from tag-configs', async () => {
+ const tagConfigs = JSON.stringify([
+ { name: 'Neutral', color: '#6c757d' },
+ { name: 'Success', color: '#28a745' },
+ { name: 'Failure', color: '#dc3545' },
+ ]);
+ const wrapper = mount(CardStack, {
+ propsData: {
+ dataTagConfigs: tagConfigs.replace(/"/g, '"'),
+ },
+ slots: { default: CARDS_WITH_CUSTOM_TAGS },
+ global: DEFAULT_GLOBAL_MOUNT_OPTIONS,
+ });
+ await wrapper.vm.$nextTick();
+
+ const { tagMapping } = wrapper.vm.cardStackRef;
+ expect(tagMapping.length).toBe(3);
+ expect(tagMapping[0][0]).toBe('Neutral');
+ expect(tagMapping[1][0]).toBe('Success');
+ expect(tagMapping[2][0]).toBe('Failure');
+ });
+
+ test('should apply custom hex colors from tag-configs', async () => {
+ const tagConfigs = JSON.stringify([
+ { name: 'Success', color: '#28a745' },
+ { name: 'Failure', color: '#dc3545' },
+ ]);
+ const wrapper = mount(CardStack, {
+ propsData: {
+ dataTagConfigs: tagConfigs.replace(/"/g, '"'),
+ },
+ slots: { default: CARDS_WITH_CUSTOM_TAGS },
+ global: DEFAULT_GLOBAL_MOUNT_OPTIONS,
+ });
+ await wrapper.vm.$nextTick();
+
+ const { tagMapping } = wrapper.vm.cardStackRef;
+ expect(tagMapping[0][1].badgeColor).toBe('#28a745');
+ expect(tagMapping[1][1].badgeColor).toBe('#dc3545');
+ });
+
+ test('should convert Bootstrap color names to classes', async () => {
+ const tagConfigs = JSON.stringify([
+ { name: 'Success', color: 'success' },
+ { name: 'Failure', color: 'danger' },
+ { name: 'Neutral', color: 'warning' },
+ ]);
+ const wrapper = mount(CardStack, {
+ propsData: {
+ dataTagConfigs: tagConfigs.replace(/"/g, '"'),
+ },
+ slots: { default: CARDS_WITH_CUSTOM_TAGS },
+ global: DEFAULT_GLOBAL_MOUNT_OPTIONS,
+ });
+ await wrapper.vm.$nextTick();
+
+ const { tagMapping } = wrapper.vm.cardStackRef;
+ expect(tagMapping[0][1].badgeColor).toBe('bg-success');
+ expect(tagMapping[1][1].badgeColor).toBe('bg-danger');
+ expect(tagMapping[2][1].badgeColor).toBe('bg-warning text-dark');
+ });
+
+ test('should use default colors for unconfigured tags', async () => {
+ const tagConfigs = JSON.stringify([{ name: 'Success', color: '#28a745' }]);
+ const wrapper = mount(CardStack, {
+ propsData: {
+ dataTagConfigs: tagConfigs.replace(/"/g, '"'),
+ },
+ slots: { default: CARDS_WITH_CUSTOM_TAGS },
+ global: DEFAULT_GLOBAL_MOUNT_OPTIONS,
+ });
+ await wrapper.vm.$nextTick();
+
+ const { tagMapping } = wrapper.vm.cardStackRef;
+ // Success should have custom color
+ expect(tagMapping[0][1].badgeColor).toBe('#28a745');
+ // Other tags should have default Bootstrap colors
+ expect(tagMapping[1][1].badgeColor).toMatch(/^bg-/);
+ expect(tagMapping[2][1].badgeColor).toMatch(/^bg-/);
+ });
+
+ test('should handle invalid tag-configs gracefully', async () => {
+ const wrapper = mount(CardStack, {
+ propsData: {
+ dataTagConfigs: 'invalid-json',
+ },
+ slots: { default: CARDS_WITH_CUSTOM_TAGS },
+ global: DEFAULT_GLOBAL_MOUNT_OPTIONS,
+ });
+ await wrapper.vm.$nextTick();
+
+ // Should still render with default colors
+ const { tagMapping } = wrapper.vm.cardStackRef;
+ expect(tagMapping.length).toBe(3);
+ expect(tagMapping[0][1].badgeColor).toMatch(/^bg-/);
+ });
+
+ test('isBootstrapColor should correctly identify Bootstrap colors', async () => {
+ const wrapper = mount(CardStack, {
+ slots: { default: CARDS_WITH_CUSTOM_TAGS },
+ global: DEFAULT_GLOBAL_MOUNT_OPTIONS,
+ });
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.vm.isBootstrapColor('bg-primary')).toBe(true);
+ expect(wrapper.vm.isBootstrapColor('bg-warning text-dark')).toBe(true);
+ expect(wrapper.vm.isBootstrapColor('#28a745')).toBe(false);
+ expect(wrapper.vm.isBootstrapColor('custom-color')).toBe(false);
+ });
+
+ test('getTextColor should return correct contrast color', async () => {
+ const wrapper = mount(CardStack, {
+ slots: { default: CARDS_WITH_CUSTOM_TAGS },
+ global: DEFAULT_GLOBAL_MOUNT_OPTIONS,
+ });
+ await wrapper.vm.$nextTick();
+
+ // Light background should have dark text
+ expect(wrapper.vm.getTextColor('#ffffff')).toBe('#000');
+ expect(wrapper.vm.getTextColor('#f0f0f0')).toBe('#000');
+
+ // Dark background should have light text
+ expect(wrapper.vm.getTextColor('#000000')).toBe('#fff');
+ expect(wrapper.vm.getTextColor('#333333')).toBe('#fff');
+ });
});
diff --git a/packages/vue-components/src/__tests__/colors.spec.js b/packages/vue-components/src/__tests__/colors.spec.js
new file mode 100644
index 0000000000..02d4755ffa
--- /dev/null
+++ b/packages/vue-components/src/__tests__/colors.spec.js
@@ -0,0 +1,132 @@
+import {
+ BADGE_COLOURS,
+ MIN_TAGS_FOR_SELECT_ALL,
+ isBootstrapColor,
+ getTextColor,
+ normalizeColor,
+} from '../utils/colors';
+
+describe('colors.js', () => {
+ describe('isBootstrapColor', () => {
+ it('should return true for valid Bootstrap color classes', () => {
+ expect(isBootstrapColor('bg-primary')).toBe(true);
+ expect(isBootstrapColor('bg-secondary')).toBe(true);
+ expect(isBootstrapColor('bg-success')).toBe(true);
+ expect(isBootstrapColor('bg-danger')).toBe(true);
+ expect(isBootstrapColor('bg-warning text-dark')).toBe(true);
+ expect(isBootstrapColor('bg-info text-dark')).toBe(true);
+ expect(isBootstrapColor('bg-light text-dark')).toBe(true);
+ expect(isBootstrapColor('bg-dark')).toBe(true);
+ });
+
+ it('should return false for hex colors', () => {
+ expect(isBootstrapColor('#28a745')).toBe(false);
+ expect(isBootstrapColor('#dc3545')).toBe(false);
+ expect(isBootstrapColor('#ffffff')).toBe(false);
+ });
+
+ it('should return false for invalid color values', () => {
+ expect(isBootstrapColor('custom-color')).toBe(false);
+ expect(isBootstrapColor('bg-custom')).toBe(false);
+ expect(isBootstrapColor('')).toBe(false);
+ });
+ });
+
+ describe('getTextColor', () => {
+ it('should return black for light backgrounds', () => {
+ expect(getTextColor('#ffffff')).toBe('#000');
+ expect(getTextColor('#f0f0f0')).toBe('#000');
+ expect(getTextColor('#ffc107')).toBe('#000'); // warning yellow
+ });
+
+ it('should return white for dark backgrounds', () => {
+ expect(getTextColor('#000000')).toBe('#fff');
+ expect(getTextColor('#333333')).toBe('#fff');
+ expect(getTextColor('#dc3545')).toBe('#fff'); // danger red
+ expect(getTextColor('#28a745')).toBe('#fff'); // success green
+ expect(getTextColor('#17a2b8')).toBe('#fff'); // info cyan
+ });
+
+ it('should return black for Bootstrap color classes', () => {
+ expect(getTextColor('bg-primary')).toBe('#000');
+ expect(getTextColor('bg-warning text-dark')).toBe('#000');
+ });
+
+ it('should handle edge cases', () => {
+ expect(getTextColor('')).toBe('#000');
+ expect(getTextColor(null)).toBe('#000');
+ expect(getTextColor(undefined)).toBe('#000');
+ });
+
+ it('should handle colors without # prefix', () => {
+ expect(getTextColor('ffffff')).toBe('#000');
+ expect(getTextColor('000000')).toBe('#fff');
+ });
+ });
+
+ describe('normalizeColor', () => {
+ it('should return null for empty or undefined values', () => {
+ expect(normalizeColor(null)).toBe(null);
+ expect(normalizeColor(undefined)).toBe(null);
+ expect(normalizeColor('')).toBe(null);
+ });
+
+ it('should return hex colors as-is', () => {
+ expect(normalizeColor('#28a745')).toBe('#28a745');
+ expect(normalizeColor('#dc3545')).toBe('#dc3545');
+ expect(normalizeColor('#ffffff')).toBe('#ffffff');
+ });
+
+ it('should convert Bootstrap color names to classes', () => {
+ expect(normalizeColor('primary')).toBe('bg-primary');
+ expect(normalizeColor('secondary')).toBe('bg-secondary');
+ expect(normalizeColor('success')).toBe('bg-success');
+ expect(normalizeColor('danger')).toBe('bg-danger');
+ expect(normalizeColor('dark')).toBe('bg-dark');
+ });
+
+ it('should add text-dark for light Bootstrap colors', () => {
+ expect(normalizeColor('warning')).toBe('bg-warning text-dark');
+ expect(normalizeColor('info')).toBe('bg-info text-dark');
+ expect(normalizeColor('light')).toBe('bg-light text-dark');
+ });
+
+ it('should handle case-insensitive Bootstrap color names', () => {
+ expect(normalizeColor('PRIMARY')).toBe('bg-primary');
+ expect(normalizeColor('Success')).toBe('bg-success');
+ expect(normalizeColor('DANGER')).toBe('bg-danger');
+ expect(normalizeColor('Warning')).toBe('bg-warning text-dark');
+ });
+
+ it('should return colors with bg- prefix as-is', () => {
+ expect(normalizeColor('bg-primary')).toBe('bg-primary');
+ expect(normalizeColor('bg-custom')).toBe('bg-custom');
+ expect(normalizeColor('bg-warning text-dark')).toBe('bg-warning text-dark');
+ });
+
+ it('should treat unknown color names as custom colors', () => {
+ expect(normalizeColor('custom')).toBe('custom');
+ expect(normalizeColor('my-color')).toBe('my-color');
+ });
+ });
+
+ describe('BADGE_COLOURS constant', () => {
+ it('should contain all 8 Bootstrap color variants', () => {
+ expect(BADGE_COLOURS).toHaveLength(8);
+ expect(BADGE_COLOURS).toContain('bg-primary');
+ expect(BADGE_COLOURS).toContain('bg-secondary');
+ expect(BADGE_COLOURS).toContain('bg-success');
+ expect(BADGE_COLOURS).toContain('bg-danger');
+ expect(BADGE_COLOURS).toContain('bg-warning text-dark');
+ expect(BADGE_COLOURS).toContain('bg-info text-dark');
+ expect(BADGE_COLOURS).toContain('bg-light text-dark');
+ expect(BADGE_COLOURS).toContain('bg-dark');
+ });
+ });
+
+ describe('MIN_TAGS_FOR_SELECT_ALL constant', () => {
+ it('should be set to 3', () => {
+ expect(MIN_TAGS_FOR_SELECT_ALL).toBe(3);
+ });
+ });
+});
diff --git a/packages/vue-components/src/cardstack/Card.vue b/packages/vue-components/src/cardstack/Card.vue
index cecc41ef0b..9be1df428f 100644
--- a/packages/vue-components/src/cardstack/Card.vue
+++ b/packages/vue-components/src/cardstack/Card.vue
@@ -22,7 +22,11 @@
{{ key[0] }}
@@ -34,6 +38,7 @@