Compare commits
2 commits
28d3e87d94
...
8c8e26411f
Author | SHA1 | Date | |
---|---|---|---|
8c8e26411f | |||
ba25bc7f6c |
10 changed files with 857 additions and 130 deletions
537
frontend/package-lock.json
generated
537
frontend/package-lock.json
generated
|
@ -17,6 +17,7 @@
|
|||
"i18next": "^23.15.2",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"i18next-resources-to-backend": "^1.2.1",
|
||||
"juice": "^11.0.0",
|
||||
"lucide-react": "^0.451.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^18.3.1",
|
||||
|
@ -31,7 +32,9 @@
|
|||
"validator": "^13.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@csstools/postcss-is-pseudo-class": "^5.0.1",
|
||||
"@eslint/js": "^9.11.1",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@types/react": "^18.3.10",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@vitejs/plugin-legacy": "^5.4.2",
|
||||
|
@ -1528,6 +1531,67 @@
|
|||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/postcss-is-pseudo-class": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-5.0.1.tgz",
|
||||
"integrity": "sha512-JLp3POui4S1auhDR0n8wHd/zTOWmMsmK3nQd3hhL6FhWPaox5W7j1se6zXOG/aP07wV2ww0lxbKYGwbBszOtfQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@csstools/selector-specificity": "^5.0.0",
|
||||
"postcss-selector-parser": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"postcss": "^8.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/postcss-is-pseudo-class/node_modules/@csstools/selector-specificity": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz",
|
||||
"integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"postcss-selector-parser": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/postcss-is-pseudo-class/node_modules/postcss-selector-parser": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz",
|
||||
"integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cssesc": "^3.0.0",
|
||||
"util-deprecate": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
||||
|
@ -2650,6 +2714,34 @@
|
|||
"@swc/counter": "^0.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/typography": {
|
||||
"version": "0.5.15",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.15.tgz",
|
||||
"integrity": "sha512-AqhlCXl+8grUz8uqExv5OTtgpjuVIwFTSXTrh8y9/pw6q2ek7fJ+Y8ZEVw7EB2DCcuCOtEjf9w3+J3rzts01uA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lodash.castarray": "^4.4.0",
|
||||
"lodash.isplainobject": "^4.0.6",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"postcss-selector-parser": "6.0.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": {
|
||||
"version": "6.0.10",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
|
||||
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cssesc": "^3.0.0",
|
||||
"util-deprecate": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
||||
|
@ -2772,6 +2864,14 @@
|
|||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-colors": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
|
||||
"integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
|
||||
|
@ -3066,6 +3166,11 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"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=="
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
|
@ -3216,6 +3321,46 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/cheerio": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz",
|
||||
"integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==",
|
||||
"dependencies": {
|
||||
"cheerio-select": "^2.1.0",
|
||||
"dom-serializer": "^2.0.0",
|
||||
"domhandler": "^5.0.3",
|
||||
"domutils": "^3.1.0",
|
||||
"encoding-sniffer": "^0.2.0",
|
||||
"htmlparser2": "^9.1.0",
|
||||
"parse5": "^7.1.2",
|
||||
"parse5-htmlparser2-tree-adapter": "^7.0.0",
|
||||
"parse5-parser-stream": "^7.1.2",
|
||||
"undici": "^6.19.5",
|
||||
"whatwg-mimetype": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/cheeriojs/cheerio?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/cheerio-select": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
|
||||
"integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
|
||||
"dependencies": {
|
||||
"boolbase": "^1.0.0",
|
||||
"css-select": "^5.1.0",
|
||||
"css-what": "^6.1.0",
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.3",
|
||||
"domutils": "^3.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
|
@ -3326,6 +3471,32 @@
|
|||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/css-select": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
|
||||
"integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
|
||||
"dependencies": {
|
||||
"boolbase": "^1.0.0",
|
||||
"css-what": "^6.1.0",
|
||||
"domhandler": "^5.0.2",
|
||||
"domutils": "^3.0.1",
|
||||
"nth-check": "^2.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/css-what": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
|
||||
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/cssesc": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||
|
@ -3476,11 +3647,62 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-serializer": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
||||
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.2",
|
||||
"entities": "^4.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/domelementtype": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/domhandler": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
|
||||
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "3.1.7",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.7.tgz",
|
||||
"integrity": "sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ=="
|
||||
},
|
||||
"node_modules/domutils": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
|
||||
"integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
|
||||
"dependencies": {
|
||||
"dom-serializer": "^2.0.0",
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/downloadjs": {
|
||||
"version": "1.4.7",
|
||||
"resolved": "https://registry.npmjs.org/downloadjs/-/downloadjs-1.4.7.tgz",
|
||||
|
@ -3504,6 +3726,29 @@
|
|||
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/encoding-sniffer": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz",
|
||||
"integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==",
|
||||
"dependencies": {
|
||||
"iconv-lite": "^0.6.3",
|
||||
"whatwg-encoding": "^3.1.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/encoding-sniffer?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/es-abstract": {
|
||||
"version": "1.23.3",
|
||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz",
|
||||
|
@ -3709,6 +3954,17 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-goat": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-3.0.0.tgz",
|
||||
"integrity": "sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
|
@ -4515,6 +4771,24 @@
|
|||
"void-elements": "3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/htmlparser2": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz",
|
||||
"integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==",
|
||||
"funding": [
|
||||
"https://github.com/fb55/htmlparser2?sponsor=1",
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.3",
|
||||
"domutils": "^3.1.0",
|
||||
"entities": "^4.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "23.16.2",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.2.tgz",
|
||||
|
@ -4553,6 +4827,17 @@
|
|||
"@babel/runtime": "^7.23.2"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||
|
@ -5097,6 +5382,32 @@
|
|||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/juice": {
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/juice/-/juice-11.0.0.tgz",
|
||||
"integrity": "sha512-sGF8hPz9/Wg+YXbaNDqc1Iuoaw+J/P9lBHNQKXAGc9pPNjCd4fyPai0Zxj7MRtdjMr0lcgk5PjEIkP2b8R9F3w==",
|
||||
"dependencies": {
|
||||
"cheerio": "^1.0.0",
|
||||
"commander": "^12.1.0",
|
||||
"mensch": "^0.3.4",
|
||||
"slick": "^1.12.2",
|
||||
"web-resource-inliner": "^7.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"juice": "bin/juice"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.17"
|
||||
}
|
||||
},
|
||||
"node_modules/juice/node_modules/commander": {
|
||||
"version": "12.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
|
||||
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
|
@ -5159,6 +5470,12 @@
|
|||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
|
||||
},
|
||||
"node_modules/lodash.castarray": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
|
||||
"integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash.clonedeep": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||
|
@ -5175,6 +5492,12 @@
|
|||
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="
|
||||
},
|
||||
"node_modules/lodash.isplainobject": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||
|
@ -5218,6 +5541,11 @@
|
|||
"@jridgewell/sourcemap-codec": "^1.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mensch": {
|
||||
"version": "0.3.4",
|
||||
"resolved": "https://registry.npmjs.org/mensch/-/mensch-0.3.4.tgz",
|
||||
"integrity": "sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g=="
|
||||
},
|
||||
"node_modules/meow": {
|
||||
"version": "13.2.0",
|
||||
"resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz",
|
||||
|
@ -5252,6 +5580,17 @@
|
|||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
|
||||
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
|
@ -5338,6 +5677,17 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nth-check": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
||||
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
|
||||
"dependencies": {
|
||||
"boolbase": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
|
@ -5513,6 +5863,40 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/parse5": {
|
||||
"version": "7.2.1",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz",
|
||||
"integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==",
|
||||
"dependencies": {
|
||||
"entities": "^4.5.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/parse5-htmlparser2-tree-adapter": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz",
|
||||
"integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
|
||||
"dependencies": {
|
||||
"domhandler": "^5.0.3",
|
||||
"parse5": "^7.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/parse5-parser-stream": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz",
|
||||
"integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==",
|
||||
"dependencies": {
|
||||
"parse5": "^7.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
|
@ -6245,6 +6629,11 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.23.2",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
||||
|
@ -6345,6 +6734,14 @@
|
|||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/slick": {
|
||||
"version": "1.12.2",
|
||||
"resolved": "https://registry.npmjs.org/slick/-/slick-1.12.2.tgz",
|
||||
"integrity": "sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A==",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
|
@ -6859,6 +7256,14 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "6.20.1",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.20.1.tgz",
|
||||
"integrity": "sha512-AjQF1QsmqfJys+LXfGTNum+qw4S88CojRInG/6t31W/1fk6G59s92bnAvGz5Cmur+kQv2SURXEvvudLmbrE8QA==",
|
||||
"engines": {
|
||||
"node": ">=18.17"
|
||||
}
|
||||
},
|
||||
"node_modules/unfetch": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unfetch/-/unfetch-5.0.0.tgz",
|
||||
|
@ -6957,6 +7362,14 @@
|
|||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/valid-data-url": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/valid-data-url/-/valid-data-url-3.0.1.tgz",
|
||||
"integrity": "sha512-jOWVmzVceKlVVdwjNSenT4PbGghU0SBIizAev8ofZVgivk/TVHXSbNL8LP6M3spZvkR9/QolkyJavGSX5Cs0UA==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/validator": {
|
||||
"version": "13.12.0",
|
||||
"resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz",
|
||||
|
@ -7032,6 +7445,130 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/web-resource-inliner": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/web-resource-inliner/-/web-resource-inliner-7.0.0.tgz",
|
||||
"integrity": "sha512-NlfnGF8MY9ZUwFjyq3vOUBx7KwF8bmE+ywR781SB0nWB6MoMxN4BA8gtgP1KGTZo/O/AyWJz7HZpR704eaj4mg==",
|
||||
"dependencies": {
|
||||
"ansi-colors": "^4.1.1",
|
||||
"escape-goat": "^3.0.0",
|
||||
"htmlparser2": "^5.0.0",
|
||||
"mime": "^2.4.6",
|
||||
"valid-data-url": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/web-resource-inliner/node_modules/dom-serializer": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
|
||||
"integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.0.1",
|
||||
"domhandler": "^4.2.0",
|
||||
"entities": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/web-resource-inliner/node_modules/dom-serializer/node_modules/domhandler": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz",
|
||||
"integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/web-resource-inliner/node_modules/domhandler": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.3.0.tgz",
|
||||
"integrity": "sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/web-resource-inliner/node_modules/domutils": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
|
||||
"integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==",
|
||||
"dependencies": {
|
||||
"dom-serializer": "^1.0.1",
|
||||
"domelementtype": "^2.2.0",
|
||||
"domhandler": "^4.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/web-resource-inliner/node_modules/domutils/node_modules/domhandler": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz",
|
||||
"integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/web-resource-inliner/node_modules/entities": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
|
||||
"integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/web-resource-inliner/node_modules/htmlparser2": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-5.0.1.tgz",
|
||||
"integrity": "sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.0.1",
|
||||
"domhandler": "^3.3.0",
|
||||
"domutils": "^2.4.2",
|
||||
"entities": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/htmlparser2?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-encoding": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
|
||||
"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
|
||||
"dependencies": {
|
||||
"iconv-lite": "0.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-mimetype": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
|
||||
"integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
"i18next": "^23.15.2",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"i18next-resources-to-backend": "^1.2.1",
|
||||
"juice": "^11.0.0",
|
||||
"lucide-react": "^0.451.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^18.3.1",
|
||||
|
@ -34,7 +35,9 @@
|
|||
"validator": "^13.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@csstools/postcss-is-pseudo-class": "^5.0.1",
|
||||
"@eslint/js": "^9.11.1",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@types/react": "^18.3.10",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@vitejs/plugin-legacy": "^5.4.2",
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
"./postcss/transform-pseudoclass.js": {},
|
||||
"@csstools/postcss-is-pseudo-class": {
|
||||
preserve: true
|
||||
},
|
||||
autoprefixer: {},
|
||||
"postcss-css-variables": {
|
||||
preserve: true
|
||||
|
|
1
frontend/postcss/package.json
Normal file
1
frontend/postcss/package.json
Normal file
|
@ -0,0 +1 @@
|
|||
{}
|
17
frontend/postcss/transform-pseudoclass.js
Normal file
17
frontend/postcss/transform-pseudoclass.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
module.exports = (opts = {}) => {
|
||||
return {
|
||||
postcssPlugin: 'postcss-where-to-is-and-prefix',
|
||||
Once(root, { result }) {
|
||||
root.walkRules((rule) => {
|
||||
rule.selector = rule.selector.replace(/\:where\(([^)]*)\)/g, ':is($1)');
|
||||
if (rule.selector.match(/\:is\([^)]*\)/)) {
|
||||
rule.selector = rule.selector + ", " +
|
||||
rule.selector.replace(/\:is\(([^)]*)\)/g, ':-moz-any($1)') + ", " +
|
||||
rule.selector.replace(/\:is\(([^)]*)\)/g, ':-webkit-any($1)')
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.postcss = true;
|
|
@ -46,7 +46,7 @@ const ThemeProvider = ({ children }) => {
|
|||
|
||||
return (
|
||||
<ThemeContext.Provider
|
||||
value={{ currentTheme, setTheme, areThemesSupported }}
|
||||
value={{ currentTheme, setTheme, areThemesSupported, isDarkMode }}
|
||||
>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
|
|
|
@ -156,8 +156,13 @@ function ComposeContent() {
|
|||
}
|
||||
if (messageData) {
|
||||
const message = messageData.messages[messageData.messages.length - 1];
|
||||
const sanitizedBody = DOMPurify.sanitize(message.body, {
|
||||
WHOLE_DOCUMENT: true
|
||||
const inlinedBody = (await import("juice/client")).default(
|
||||
message.body
|
||||
);
|
||||
const sanitizedBody = DOMPurify.sanitize(inlinedBody, {
|
||||
WHOLE_DOCUMENT: true,
|
||||
FORBID_TAGS: ["style"],
|
||||
FORBID_ATTR: ["class"]
|
||||
});
|
||||
const parsedBody = new DOMParser().parseFromString(
|
||||
sanitizedBody,
|
||||
|
@ -564,7 +569,7 @@ function ComposeContent() {
|
|||
cc: ccValues,
|
||||
bcc: bccValues,
|
||||
subject: subject,
|
||||
content: contents,
|
||||
content: `<!DOCTYPE html><html><head><meta name="viewport" content="width=device-width, initial-scale=1.0" /></head><body>${contents}</body></html>`,
|
||||
inReplyTo: inReplyTo,
|
||||
attachments: finalAttachments,
|
||||
draftMailbox: draftMailbox,
|
||||
|
@ -1077,7 +1082,7 @@ function ComposeContent() {
|
|||
cc: ccValues,
|
||||
bcc: bccValues,
|
||||
subject: subject,
|
||||
content: contents,
|
||||
content: `<!DOCTYPE html><html><head><meta name="viewport" content="width=device-width, initial-scale=1.0" /></head><body>${contents}</body></html>`,
|
||||
inReplyTo: inReplyTo,
|
||||
attachments: finalAttachments,
|
||||
draftMailbox: draftMailbox,
|
||||
|
|
|
@ -13,8 +13,8 @@ import {
|
|||
Trash,
|
||||
TriangleAlert
|
||||
} from "lucide-react";
|
||||
import { useCallback, useContext, useEffect, useRef, useState } from "react";
|
||||
import Iframe from "@/components/Iframe.jsx";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { ThemeContext } from "@/contexts/ThemeContext.jsx";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import DOMPurify from "dompurify";
|
||||
import { filesize } from "filesize";
|
||||
|
@ -26,10 +26,10 @@ import Loading from "@/components/Loading.jsx";
|
|||
import download from "downloadjs";
|
||||
|
||||
function MessageContent() {
|
||||
const iframeRef = useRef({});
|
||||
const [iframeHeights, setIframeHeights] = useState({});
|
||||
const { t } = useTranslation();
|
||||
const { isDarkMode } = useContext(ThemeContext);
|
||||
const { toast } = useContext(ToastContext);
|
||||
const [juice, setJuice] = useState(null);
|
||||
const [moveShown, setMoveShown] = useState(false);
|
||||
const view = useSelector((state) => state.view.view);
|
||||
const hasMoreThanOneMailbox = useSelector(
|
||||
|
@ -99,94 +99,70 @@ function MessageContent() {
|
|||
}
|
||||
}, [refresh, loading, dispatch]);
|
||||
|
||||
const replaceCIDsOnIframeLoad = (iframeRefContents, id, attachments) => {
|
||||
return () => {
|
||||
const srcElements =
|
||||
iframeRefContents.contentWindow.document.querySelectorAll("[src]");
|
||||
srcElements.forEach((srcElement) => {
|
||||
if (srcElement.src) {
|
||||
const cidMatch = srcElement.src.match(/^cid:(.+)/);
|
||||
if (cidMatch) {
|
||||
const cid = cidMatch[1];
|
||||
const attachment = attachments.find((attachment) => {
|
||||
return attachment.contentId == cid;
|
||||
});
|
||||
if (attachment) {
|
||||
srcElement.src = `/api/receive/attachment/${attachment.id}`;
|
||||
// Add onload event listener to images with CIDs, so that iframe heights are not broken
|
||||
srcElement.addEventListener("load", () => {
|
||||
resizeOnIframeLoad(iframeRefContents, id)();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
const isLightOrDark = (rgbColor) => {
|
||||
let [r, g, b] = rgbColor;
|
||||
let hsp = Math.sqrt(0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b));
|
||||
return hsp > 127.5 ? "light" : "dark";
|
||||
};
|
||||
|
||||
const processLinksAndFormsOnIframeLoad = (iframeRefContents) => {
|
||||
return () => {
|
||||
const aElements =
|
||||
iframeRefContents.contentWindow.document.querySelectorAll("a");
|
||||
aElements.forEach((aElement) => {
|
||||
const currentHashURL =
|
||||
document.location.origin + document.location.pathname + "#";
|
||||
if (
|
||||
!aElement.href ||
|
||||
aElement.href.slice(0, 1) == "#" ||
|
||||
aElement.href.slice(0, currentHashURL.length) == currentHashURL
|
||||
) {
|
||||
// Prevent changing the URL
|
||||
aElement.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
});
|
||||
aElement.target = "_self";
|
||||
} else {
|
||||
aElement.target = "_parent";
|
||||
}
|
||||
});
|
||||
const flipLightness = (rgb) => {
|
||||
// Convert RGB to HSL
|
||||
let r = rgb[0] / 255;
|
||||
let g = rgb[1] / 255;
|
||||
let b = rgb[2] / 255;
|
||||
|
||||
const formElements =
|
||||
iframeRefContents.contentWindow.document.querySelectorAll("form");
|
||||
formElements.forEach((formElement) => {
|
||||
const currentHashURL =
|
||||
document.location.origin + document.location.pathname + "#";
|
||||
if (
|
||||
!formElement.action ||
|
||||
formElement.action.slice(0, 1) == "#" ||
|
||||
formElement.action.slice(0, currentHashURL.length) == currentHashURL
|
||||
) {
|
||||
// Prevent form submitting
|
||||
formElement.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
});
|
||||
formElement.target = "_self";
|
||||
} else {
|
||||
formElement.target = "_parent";
|
||||
}
|
||||
});
|
||||
};
|
||||
let max = Math.max(r, g, b);
|
||||
let min = Math.min(r, g, b);
|
||||
let h,
|
||||
s,
|
||||
l = (max + min) / 2;
|
||||
|
||||
if (max === min) {
|
||||
h = s = 0; // achromatic
|
||||
} else {
|
||||
let d = max - min;
|
||||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||
switch (max) {
|
||||
case r:
|
||||
h = (g - b) / d + (g < b ? 6 : 0);
|
||||
break;
|
||||
case g:
|
||||
h = (b - r) / d + 2;
|
||||
break;
|
||||
case b:
|
||||
h = (r - g) / d + 4;
|
||||
break;
|
||||
}
|
||||
h /= 6;
|
||||
}
|
||||
|
||||
// Flip lightness
|
||||
l = 1 - l;
|
||||
|
||||
// Convert HSL back to RGB
|
||||
if (s === 0) {
|
||||
r = g = b = l; // achromatic
|
||||
} else {
|
||||
let hue2rgb = function hue2rgb(p, q, t) {
|
||||
if (t < 0) t += 1;
|
||||
if (t > 1) t -= 1;
|
||||
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
||||
if (t < 1 / 2) return q;
|
||||
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
||||
return p;
|
||||
};
|
||||
|
||||
let q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||
let p = 2 * l - q;
|
||||
r = hue2rgb(p, q, h + 1 / 3);
|
||||
g = hue2rgb(p, q, h);
|
||||
b = hue2rgb(p, q, h - 1 / 3);
|
||||
}
|
||||
|
||||
// Return RGB
|
||||
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
|
||||
};
|
||||
|
||||
const resizeOnIframeLoad = useCallback((iframeRefContents, id) => {
|
||||
return () => {
|
||||
const body = iframeRefContents.contentWindow.document.body;
|
||||
const html = iframeRefContents.contentWindow.document.documentElement;
|
||||
const newHeight = Math.max(
|
||||
html.scrollHeight > parseInt(iframeRefContents.height)
|
||||
? html.scrollHeight
|
||||
: 0,
|
||||
body.offsetHeight,
|
||||
html.offsetHeight
|
||||
);
|
||||
setIframeHeights((iframeHeightsO) => {
|
||||
const newIframeHeights = { ...iframeHeightsO };
|
||||
newIframeHeights[id] = newHeight;
|
||||
return newIframeHeights;
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
const getMessageIds = () => {
|
||||
return messagesToRender
|
||||
.slice()
|
||||
|
@ -194,20 +170,6 @@ function MessageContent() {
|
|||
.map((message) => message.id);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const resizeOnIframeLoadAllRefs = () => {
|
||||
Object.keys(iframeRef.current).forEach((refKey) => {
|
||||
resizeOnIframeLoad(iframeRef.current[refKey], refKey)();
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener("resize", resizeOnIframeLoadAllRefs);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", resizeOnIframeLoadAllRefs);
|
||||
};
|
||||
}, [resizeOnIframeLoad]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && messageData && messageData.messages.length > 0)
|
||||
document.title = `${
|
||||
|
@ -244,7 +206,17 @@ function MessageContent() {
|
|||
}
|
||||
}, [view, dispatch]);
|
||||
|
||||
if (loading) {
|
||||
useEffect(() => {
|
||||
const loadJuice = async () => {
|
||||
const newJuice = (await import("juice/client")).default;
|
||||
// Set juice to be a function. The function which returns the function is passed into setJuice
|
||||
setJuice(() => newJuice);
|
||||
};
|
||||
|
||||
loadJuice();
|
||||
}, []);
|
||||
|
||||
if (loading || !juice) {
|
||||
return <Loading />;
|
||||
} else if (error) {
|
||||
return (
|
||||
|
@ -762,6 +734,156 @@ function MessageContent() {
|
|||
address: "unknown@example.com"
|
||||
};
|
||||
const firstFromAddress = firstFrom.address || "unknown@example.com";
|
||||
const inlinedBody = juice(body);
|
||||
const sanitizedBody = DOMPurify.sanitize(inlinedBody, {
|
||||
WHOLE_DOCUMENT: true,
|
||||
FORBID_TAGS: ["style"],
|
||||
FORBID_ATTR: ["class"]
|
||||
});
|
||||
const parsedDocument = new DOMParser().parseFromString(
|
||||
sanitizedBody,
|
||||
"text/html"
|
||||
);
|
||||
const aElements = parsedDocument.querySelectorAll("a");
|
||||
aElements.forEach((aElement) => {
|
||||
const currentHashURL =
|
||||
document.location.origin + document.location.pathname + "#";
|
||||
if (
|
||||
!aElement.href ||
|
||||
aElement.href.slice(0, 1) == "#" ||
|
||||
aElement.href.slice(0, currentHashURL.length) == currentHashURL
|
||||
) {
|
||||
// Prevent changing the URL
|
||||
aElement.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
});
|
||||
aElement.target = "_self";
|
||||
} else {
|
||||
aElement.target = "_parent";
|
||||
}
|
||||
});
|
||||
|
||||
const formElements = parsedDocument.querySelectorAll("form");
|
||||
formElements.forEach((formElement) => {
|
||||
const currentHashURL =
|
||||
document.location.origin + document.location.pathname + "#";
|
||||
if (
|
||||
!formElement.action ||
|
||||
formElement.action.slice(0, 1) == "#" ||
|
||||
formElement.action.slice(0, currentHashURL.length) ==
|
||||
currentHashURL
|
||||
) {
|
||||
// Prevent form submitting
|
||||
formElement.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
});
|
||||
formElement.target = "_self";
|
||||
} else {
|
||||
formElement.target = "_parent";
|
||||
}
|
||||
});
|
||||
|
||||
const srcElements = parsedDocument.querySelectorAll("[src]");
|
||||
srcElements.forEach((srcElement) => {
|
||||
if (srcElement.src) {
|
||||
const cidMatch = srcElement.src.match(/^cid:(.+)/);
|
||||
if (cidMatch) {
|
||||
const cid = cidMatch[1];
|
||||
const attachment = attachments.find((attachment) => {
|
||||
return attachment.contentId == cid;
|
||||
});
|
||||
if (attachment) {
|
||||
srcElement.src = `/api/receive/attachment/${attachment.id}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const allBodyElements = parsedDocument.querySelectorAll("body *");
|
||||
allBodyElements.forEach((element) => {
|
||||
if (element.getAttribute("color") && !element.style.color) {
|
||||
element.style.color = element.getAttribute("color");
|
||||
element.removeAttribute("color");
|
||||
}
|
||||
|
||||
if (
|
||||
element.getAttribute("bgcolor") &&
|
||||
!element.style.backgroundColor
|
||||
) {
|
||||
element.style.backgroundColor = element.getAttribute("bgcolor");
|
||||
element.removeAttribute("bgcolor");
|
||||
}
|
||||
|
||||
if (
|
||||
(element.tagName == "TABLE" ||
|
||||
element.tagName == "TBODY" ||
|
||||
element.tagName == "TR" ||
|
||||
element.tagName == "TH" ||
|
||||
element.tagName == "TD") &&
|
||||
element.getAttribute("width") &&
|
||||
!element.style.width
|
||||
) {
|
||||
element.style.width = element.getAttribute("width");
|
||||
element.removeAttribute("width");
|
||||
}
|
||||
|
||||
if (
|
||||
(element.tagName == "TABLE" ||
|
||||
element.tagName == "TBODY" ||
|
||||
element.tagName == "TR" ||
|
||||
element.tagName == "TH" ||
|
||||
element.tagName == "TD") &&
|
||||
element.getAttribute("height") &&
|
||||
!element.style.height
|
||||
) {
|
||||
element.style.height = element.getAttribute("height");
|
||||
element.removeAttribute("height");
|
||||
}
|
||||
|
||||
const computedStyle = window.getComputedStyle(element);
|
||||
if (
|
||||
element.style.font ||
|
||||
element.style.color ||
|
||||
element.getAttribute("color")
|
||||
) {
|
||||
const parsedColorMatch = computedStyle.color.match(
|
||||
/rgba?\((\d{1,3}), (\d{1,3}), (\d{1,3})(?:, (\d{1,3}))?\)/
|
||||
);
|
||||
if (parsedColorMatch) {
|
||||
const r = parseInt(parsedColorMatch[1]);
|
||||
const g = parseInt(parsedColorMatch[2]);
|
||||
const b = parseInt(parsedColorMatch[3]);
|
||||
if (
|
||||
isLightOrDark([r, g, b]) == (isDarkMode ? "dark" : "light")
|
||||
) {
|
||||
const newColors = flipLightness([r, g, b]);
|
||||
element.style.color = `rgba(${newColors[0]}, ${newColors[1]}, ${newColors[2]}, ${parsedColorMatch[4] ? parsedColorMatch[4] : 255})`;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
element.style.background ||
|
||||
element.style.backgroundColor ||
|
||||
element.getAttribute("bgcolor")
|
||||
) {
|
||||
const parsedColorMatch = computedStyle.backgroundColor.match(
|
||||
/rgba?\((\d{1,3}), (\d{1,3}), (\d{1,3})(?:, (\d{1,3}))?\)/
|
||||
);
|
||||
if (parsedColorMatch) {
|
||||
const r = parseInt(parsedColorMatch[1]);
|
||||
const g = parseInt(parsedColorMatch[2]);
|
||||
const b = parseInt(parsedColorMatch[3]);
|
||||
if (
|
||||
isLightOrDark([r, g, b]) == (isDarkMode ? "light" : "dark")
|
||||
) {
|
||||
const newColors = flipLightness([r, g, b]);
|
||||
element.style.backgroundColor = `rgba(${newColors[0]}, ${newColors[1]}, ${newColors[2]}, ${parsedColorMatch[4] ? parsedColorMatch[4] : 255})`;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const emailBody = parsedDocument.body.innerHTML;
|
||||
|
||||
return (
|
||||
<div className="border-b-2 border-border" key={id}>
|
||||
|
@ -918,28 +1040,9 @@ function MessageContent() {
|
|||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<Iframe
|
||||
ref={(el) => (iframeRef.current[id] = el)}
|
||||
onLoad={() => {
|
||||
setTimeout(() => {
|
||||
replaceCIDsOnIframeLoad(
|
||||
iframeRef.current[id],
|
||||
id,
|
||||
attachments
|
||||
)();
|
||||
processLinksAndFormsOnIframeLoad(iframeRef.current[id])();
|
||||
resizeOnIframeLoad(iframeRef.current[id], id)();
|
||||
}, 0);
|
||||
}}
|
||||
className="bg-white w-full rounded-lg mb-2 overflow-x-auto overflow-y-hidden"
|
||||
srcDoc={DOMPurify.sanitize(body, {
|
||||
WHOLE_DOCUMENT: true
|
||||
})}
|
||||
height={
|
||||
typeof iframeHeights[id] == "undefined"
|
||||
? 500
|
||||
: iframeHeights[id]
|
||||
}
|
||||
<div
|
||||
className="prose prose-email w-full max-w-full rounded-lg mb-2"
|
||||
dangerouslySetInnerHTML={{ __html: emailBody }}
|
||||
/>
|
||||
{realAttachments && realAttachments.length > 0 ? (
|
||||
<>
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import typography from "@tailwindcss/typography";
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
darkMode: ["class"],
|
||||
|
@ -60,9 +62,61 @@ export default {
|
|||
animation: {
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
},
|
||||
typography: {
|
||||
DEFAULT: {
|
||||
css: {
|
||||
color: "hsla(var(--foreground), 1)",
|
||||
a: {
|
||||
color: "hsla(var(--primary), 1)"
|
||||
},
|
||||
strong: {
|
||||
color: "hsla(var(--foreground), 1)"
|
||||
},
|
||||
b: {
|
||||
color: "hsla(var(--foreground), 1)"
|
||||
},
|
||||
blockquote: {
|
||||
color: "hsla(var(--foreground), 1)"
|
||||
},
|
||||
table: {
|
||||
textAlign: null
|
||||
},
|
||||
tbody: {
|
||||
textAlign: null
|
||||
},
|
||||
tr: {
|
||||
textAlign: null
|
||||
},
|
||||
th: {
|
||||
textAlign: null
|
||||
},
|
||||
td: {
|
||||
textAlign: null
|
||||
},
|
||||
"th, td": {
|
||||
textAlign: null
|
||||
}
|
||||
}
|
||||
},
|
||||
email: {
|
||||
css: {
|
||||
code: {
|
||||
"&:before": {
|
||||
content: ""
|
||||
},
|
||||
"&:after": {
|
||||
content: ""
|
||||
},
|
||||
},
|
||||
pre: {
|
||||
backgroundColor: "inherit"
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [],
|
||||
plugins: [typography],
|
||||
}
|
||||
|
||||
|
|
|
@ -24,5 +24,8 @@ export default defineConfig({
|
|||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
},
|
||||
build: {
|
||||
chunkSizeWarningLimit: 600
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue