Compare commits

...

2 commits

10 changed files with 857 additions and 130 deletions

View file

@ -17,6 +17,7 @@
"i18next": "^23.15.2", "i18next": "^23.15.2",
"i18next-browser-languagedetector": "^8.0.0", "i18next-browser-languagedetector": "^8.0.0",
"i18next-resources-to-backend": "^1.2.1", "i18next-resources-to-backend": "^1.2.1",
"juice": "^11.0.0",
"lucide-react": "^0.451.0", "lucide-react": "^0.451.0",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react": "^18.3.1", "react": "^18.3.1",
@ -31,7 +32,9 @@
"validator": "^13.12.0" "validator": "^13.12.0"
}, },
"devDependencies": { "devDependencies": {
"@csstools/postcss-is-pseudo-class": "^5.0.1",
"@eslint/js": "^9.11.1", "@eslint/js": "^9.11.1",
"@tailwindcss/typography": "^0.5.15",
"@types/react": "^18.3.10", "@types/react": "^18.3.10",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@vitejs/plugin-legacy": "^5.4.2", "@vitejs/plugin-legacy": "^5.4.2",
@ -1528,6 +1531,67 @@
"node": ">=6.9.0" "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": { "node_modules/@esbuild/aix-ppc64": {
"version": "0.21.5", "version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
@ -2650,6 +2714,34 @@
"@swc/counter": "^0.1.3" "@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": { "node_modules/@types/estree": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
@ -2772,6 +2864,14 @@
"url": "https://github.com/sponsors/epoberezkin" "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": { "node_modules/ansi-regex": {
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
@ -3066,6 +3166,11 @@
"url": "https://github.com/sponsors/sindresorhus" "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": { "node_modules/brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -3216,6 +3321,46 @@
"node": ">=4" "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": { "node_modules/chokidar": {
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@ -3326,6 +3471,32 @@
"node": ">= 8" "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": { "node_modules/cssesc": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@ -3476,11 +3647,62 @@
"node": ">=0.10.0" "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": { "node_modules/dompurify": {
"version": "3.1.7", "version": "3.1.7",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.7.tgz", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.7.tgz",
"integrity": "sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ==" "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": { "node_modules/downloadjs": {
"version": "1.4.7", "version": "1.4.7",
"resolved": "https://registry.npmjs.org/downloadjs/-/downloadjs-1.4.7.tgz", "resolved": "https://registry.npmjs.org/downloadjs/-/downloadjs-1.4.7.tgz",
@ -3504,6 +3726,29 @@
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true "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": { "node_modules/es-abstract": {
"version": "1.23.3", "version": "1.23.3",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz",
@ -3709,6 +3954,17 @@
"node": ">=6" "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": { "node_modules/escape-string-regexp": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
@ -4515,6 +4771,24 @@
"void-elements": "3.1.0" "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": { "node_modules/i18next": {
"version": "23.16.2", "version": "23.16.2",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.2.tgz", "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.2.tgz",
@ -4553,6 +4827,17 @@
"@babel/runtime": "^7.23.2" "@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": { "node_modules/ignore": {
"version": "5.3.2", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@ -5097,6 +5382,32 @@
"node": ">=4.0" "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": { "node_modules/keyv": {
"version": "4.5.4", "version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "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", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" "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": { "node_modules/lodash.clonedeep": {
"version": "4.5.0", "version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "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", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" "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": { "node_modules/lodash.merge": {
"version": "4.6.2", "version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@ -5218,6 +5541,11 @@
"@jridgewell/sourcemap-codec": "^1.5.0" "@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": { "node_modules/meow": {
"version": "13.2.0", "version": "13.2.0",
"resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz",
@ -5252,6 +5580,17 @@
"node": ">=8.6" "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": { "node_modules/minimatch": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@ -5338,6 +5677,17 @@
"node": ">=0.10.0" "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": { "node_modules/object-assign": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@ -5513,6 +5863,40 @@
"node": ">=6" "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": { "node_modules/path-exists": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@ -6245,6 +6629,11 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/scheduler": {
"version": "0.23.2", "version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
@ -6345,6 +6734,14 @@
"url": "https://github.com/sponsors/isaacs" "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": { "node_modules/source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@ -6859,6 +7256,14 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/unfetch": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/unfetch/-/unfetch-5.0.0.tgz", "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-5.0.0.tgz",
@ -6957,6 +7362,14 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true "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": { "node_modules/validator": {
"version": "13.12.0", "version": "13.12.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz",
@ -7032,6 +7445,130 @@
"node": ">=0.10.0" "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": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View file

@ -20,6 +20,7 @@
"i18next": "^23.15.2", "i18next": "^23.15.2",
"i18next-browser-languagedetector": "^8.0.0", "i18next-browser-languagedetector": "^8.0.0",
"i18next-resources-to-backend": "^1.2.1", "i18next-resources-to-backend": "^1.2.1",
"juice": "^11.0.0",
"lucide-react": "^0.451.0", "lucide-react": "^0.451.0",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react": "^18.3.1", "react": "^18.3.1",
@ -34,7 +35,9 @@
"validator": "^13.12.0" "validator": "^13.12.0"
}, },
"devDependencies": { "devDependencies": {
"@csstools/postcss-is-pseudo-class": "^5.0.1",
"@eslint/js": "^9.11.1", "@eslint/js": "^9.11.1",
"@tailwindcss/typography": "^0.5.15",
"@types/react": "^18.3.10", "@types/react": "^18.3.10",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@vitejs/plugin-legacy": "^5.4.2", "@vitejs/plugin-legacy": "^5.4.2",

View file

@ -1,6 +1,10 @@
export default { export default {
plugins: { plugins: {
tailwindcss: {}, tailwindcss: {},
"./postcss/transform-pseudoclass.js": {},
"@csstools/postcss-is-pseudo-class": {
preserve: true
},
autoprefixer: {}, autoprefixer: {},
"postcss-css-variables": { "postcss-css-variables": {
preserve: true preserve: true

View file

@ -0,0 +1 @@
{}

View 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;

View file

@ -46,7 +46,7 @@ const ThemeProvider = ({ children }) => {
return ( return (
<ThemeContext.Provider <ThemeContext.Provider
value={{ currentTheme, setTheme, areThemesSupported }} value={{ currentTheme, setTheme, areThemesSupported, isDarkMode }}
> >
{children} {children}
</ThemeContext.Provider> </ThemeContext.Provider>

View file

@ -156,8 +156,13 @@ function ComposeContent() {
} }
if (messageData) { if (messageData) {
const message = messageData.messages[messageData.messages.length - 1]; const message = messageData.messages[messageData.messages.length - 1];
const sanitizedBody = DOMPurify.sanitize(message.body, { const inlinedBody = (await import("juice/client")).default(
WHOLE_DOCUMENT: true message.body
);
const sanitizedBody = DOMPurify.sanitize(inlinedBody, {
WHOLE_DOCUMENT: true,
FORBID_TAGS: ["style"],
FORBID_ATTR: ["class"]
}); });
const parsedBody = new DOMParser().parseFromString( const parsedBody = new DOMParser().parseFromString(
sanitizedBody, sanitizedBody,
@ -564,7 +569,7 @@ function ComposeContent() {
cc: ccValues, cc: ccValues,
bcc: bccValues, bcc: bccValues,
subject: subject, 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, inReplyTo: inReplyTo,
attachments: finalAttachments, attachments: finalAttachments,
draftMailbox: draftMailbox, draftMailbox: draftMailbox,
@ -1077,7 +1082,7 @@ function ComposeContent() {
cc: ccValues, cc: ccValues,
bcc: bccValues, bcc: bccValues,
subject: subject, 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, inReplyTo: inReplyTo,
attachments: finalAttachments, attachments: finalAttachments,
draftMailbox: draftMailbox, draftMailbox: draftMailbox,

View file

@ -13,8 +13,8 @@ import {
Trash, Trash,
TriangleAlert TriangleAlert
} from "lucide-react"; } from "lucide-react";
import { useCallback, useContext, useEffect, useRef, useState } from "react"; import { useContext, useEffect, useState } from "react";
import Iframe from "@/components/Iframe.jsx"; import { ThemeContext } from "@/contexts/ThemeContext.jsx";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import DOMPurify from "dompurify"; import DOMPurify from "dompurify";
import { filesize } from "filesize"; import { filesize } from "filesize";
@ -26,10 +26,10 @@ import Loading from "@/components/Loading.jsx";
import download from "downloadjs"; import download from "downloadjs";
function MessageContent() { function MessageContent() {
const iframeRef = useRef({});
const [iframeHeights, setIframeHeights] = useState({});
const { t } = useTranslation(); const { t } = useTranslation();
const { isDarkMode } = useContext(ThemeContext);
const { toast } = useContext(ToastContext); const { toast } = useContext(ToastContext);
const [juice, setJuice] = useState(null);
const [moveShown, setMoveShown] = useState(false); const [moveShown, setMoveShown] = useState(false);
const view = useSelector((state) => state.view.view); const view = useSelector((state) => state.view.view);
const hasMoreThanOneMailbox = useSelector( const hasMoreThanOneMailbox = useSelector(
@ -99,94 +99,70 @@ function MessageContent() {
} }
}, [refresh, loading, dispatch]); }, [refresh, loading, dispatch]);
const replaceCIDsOnIframeLoad = (iframeRefContents, id, attachments) => { const isLightOrDark = (rgbColor) => {
return () => { let [r, g, b] = rgbColor;
const srcElements = let hsp = Math.sqrt(0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b));
iframeRefContents.contentWindow.document.querySelectorAll("[src]"); return hsp > 127.5 ? "light" : "dark";
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 processLinksAndFormsOnIframeLoad = (iframeRefContents) => { const flipLightness = (rgb) => {
return () => { // Convert RGB to HSL
const aElements = let r = rgb[0] / 255;
iframeRefContents.contentWindow.document.querySelectorAll("a"); let g = rgb[1] / 255;
aElements.forEach((aElement) => { let b = rgb[2] / 255;
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 = let max = Math.max(r, g, b);
iframeRefContents.contentWindow.document.querySelectorAll("form"); let min = Math.min(r, g, b);
formElements.forEach((formElement) => { let h,
const currentHashURL = s,
document.location.origin + document.location.pathname + "#"; l = (max + min) / 2;
if (
!formElement.action || if (max === min) {
formElement.action.slice(0, 1) == "#" || h = s = 0; // achromatic
formElement.action.slice(0, currentHashURL.length) == currentHashURL } else {
) { let d = max - min;
// Prevent form submitting s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
formElement.addEventListener("submit", (e) => { switch (max) {
e.preventDefault(); case r:
}); h = (g - b) / d + (g < b ? 6 : 0);
formElement.target = "_self"; break;
} else { case g:
formElement.target = "_parent"; 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 = () => { const getMessageIds = () => {
return messagesToRender return messagesToRender
.slice() .slice()
@ -194,20 +170,6 @@ function MessageContent() {
.map((message) => message.id); .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(() => { useEffect(() => {
if (!loading && messageData && messageData.messages.length > 0) if (!loading && messageData && messageData.messages.length > 0)
document.title = `${ document.title = `${
@ -244,7 +206,17 @@ function MessageContent() {
} }
}, [view, dispatch]); }, [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 />; return <Loading />;
} else if (error) { } else if (error) {
return ( return (
@ -762,6 +734,156 @@ function MessageContent() {
address: "unknown@example.com" address: "unknown@example.com"
}; };
const firstFromAddress = firstFrom.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 ( return (
<div className="border-b-2 border-border" key={id}> <div className="border-b-2 border-border" key={id}>
@ -918,28 +1040,9 @@ function MessageContent() {
</li> </li>
</ul> </ul>
</div> </div>
<Iframe <div
ref={(el) => (iframeRef.current[id] = el)} className="prose prose-email w-full max-w-full rounded-lg mb-2"
onLoad={() => { dangerouslySetInnerHTML={{ __html: emailBody }}
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]
}
/> />
{realAttachments && realAttachments.length > 0 ? ( {realAttachments && realAttachments.length > 0 ? (
<> <>

View file

@ -1,3 +1,5 @@
import typography from "@tailwindcss/typography";
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
export default { export default {
darkMode: ["class"], darkMode: ["class"],
@ -60,9 +62,61 @@ export default {
animation: { animation: {
"accordion-down": "accordion-down 0.2s ease-out", "accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 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],
} }

View file

@ -24,5 +24,8 @@ export default defineConfig({
changeOrigin: true changeOrigin: true
} }
} }
},
build: {
chunkSizeWarningLimit: 600
} }
}); });