{/*v=====----------------------< Zoom Controls >---------------------=====v*/}
{
});
const handleBrewRendererPageChange = useCallback((pageNumber)=>{
- updateState({ currentBrewRendererPageNum: pageNumber });
+ setState((prevState)=>({
+ currentBrewRendererPageNum : pageNumber,
+ ...prevState }));
}, []);
const handleControlKeys = (e)=>{
diff --git a/package-lock.json b/package-lock.json
index da1759b43..6cf876186 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -19,7 +19,7 @@
"classnames": "^2.5.1",
"codemirror": "^5.65.6",
"cookie-parser": "^1.4.7",
- "core-js": "^3.39.0",
+ "core-js": "^3.40.0",
"cors": "^2.8.5",
"create-react-class": "^15.7.0",
"dedent-tabs": "^0.10.3",
@@ -28,7 +28,7 @@
"express": "^4.21.2",
"express-async-handler": "^1.2.0",
"express-static-gzip": "2.2.0",
- "fs-extra": "11.2.0",
+ "fs-extra": "11.3.0",
"idb-keyval": "^6.2.1",
"js-yaml": "^4.1.0",
"jwt-simple": "^0.5.6",
@@ -41,13 +41,13 @@
"marked-smartypants-lite": "^1.0.2",
"markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.30.1",
- "mongoose": "^8.9.3",
+ "mongoose": "^8.9.5",
"nanoid": "5.0.9",
"nconf": "^0.12.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-frame-component": "^4.1.3",
- "react-router": "^7.1.1",
+ "react-router": "^7.1.3",
"sanitize-filename": "1.6.3",
"superagent": "^10.1.1",
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
@@ -55,17 +55,17 @@
"devDependencies": {
"@stylistic/stylelint-plugin": "^3.1.1",
"babel-plugin-transform-import-meta": "^2.3.2",
- "eslint": "^9.17.0",
- "eslint-plugin-jest": "^28.10.0",
- "eslint-plugin-react": "^7.37.3",
+ "eslint": "^9.18.0",
+ "eslint-plugin-jest": "^28.11.0",
+ "eslint-plugin-react": "^7.37.4",
"globals": "^15.14.0",
"jest": "^29.7.0",
"jest-expect-message": "^1.1.3",
"jsdom-global": "^3.0.2",
"postcss-less": "^6.0.0",
- "stylelint": "^16.12.0",
- "stylelint-config-recess-order": "^5.1.1",
- "stylelint-config-recommended": "^14.0.1",
+ "stylelint": "^16.13.2",
+ "stylelint-config-recess-order": "^6.0.0",
+ "stylelint-config-recommended": "^15.0.0",
"supertest": "^7.0.0"
},
"engines": {
@@ -1868,10 +1868,13 @@
}
},
"node_modules/@eslint/core": {
- "version": "0.9.0",
- "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.0.tgz",
- "integrity": "sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==",
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz",
+ "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==",
"dev": true,
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
@@ -1912,9 +1915,9 @@
}
},
"node_modules/@eslint/js": {
- "version": "9.17.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz",
- "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==",
+ "version": "9.18.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz",
+ "integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==",
"dev": true,
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1930,11 +1933,12 @@
}
},
"node_modules/@eslint/plugin-kit": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz",
- "integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==",
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz",
+ "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==",
"dev": true,
"dependencies": {
+ "@eslint/core": "^0.10.0",
"levn": "^0.4.1"
},
"engines": {
@@ -2671,6 +2675,39 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@keyv/serialize": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.0.2.tgz",
+ "integrity": "sha512-+E/LyaAeuABniD/RvUezWVXKpeuvwLEA9//nE9952zBaOdBd2mQ3pPoM8cUe2X6IcMByfuSLzmYqnYshG60+HQ==",
+ "dev": true,
+ "dependencies": {
+ "buffer": "^6.0.3"
+ }
+ },
+ "node_modules/@keyv/serialize/node_modules/buffer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+ "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
+ }
+ },
"node_modules/@mongodb-js/saslprep": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz",
@@ -4194,6 +4231,25 @@
"node": ">=0.10.0"
}
},
+ "node_modules/cacheable": {
+ "version": "1.8.7",
+ "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.8.7.tgz",
+ "integrity": "sha512-AbfG7dAuYNjYxFUtL1lAqmlWdxczCJ47w7cFjhGcnGnUdwSo6VgmSojfoW3tUI12HUkgTJ5kqj78yyq6TsFtlg==",
+ "dev": true,
+ "dependencies": {
+ "hookified": "^1.6.0",
+ "keyv": "^5.2.3"
+ }
+ },
+ "node_modules/cacheable/node_modules/keyv": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.2.3.tgz",
+ "integrity": "sha512-AGKecUfzrowabUv0bH1RIR5Vf7w+l4S3xtQAypKaUpTdIR1EbrAcTxHCrpo9Q+IWeUlFE2palRtgIQcgm+PQJw==",
+ "dev": true,
+ "dependencies": {
+ "@keyv/serialize": "^1.0.2"
+ }
+ },
"node_modules/cached-path-relative": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.1.0.tgz",
@@ -4619,11 +4675,10 @@
}
},
"node_modules/core-js": {
- "version": "3.39.0",
- "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.39.0.tgz",
- "integrity": "sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==",
+ "version": "3.40.0",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.40.0.tgz",
+ "integrity": "sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==",
"hasInstallScript": true,
- "license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
@@ -4848,13 +4903,12 @@
}
},
"node_modules/css-tree": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.0.1.tgz",
- "integrity": "sha512-8Fxxv+tGhORlshCdCwnNJytvlvq46sOLSYEx2ZIGurahWvMucSRnyjPA3AmrMq4VPRYbHVpWj5VkiVasrM2H4Q==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz",
+ "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "mdn-data": "2.12.1",
+ "mdn-data": "2.12.2",
"source-map-js": "^1.0.1"
},
"engines": {
@@ -5596,18 +5650,18 @@
"license": "MIT"
},
"node_modules/eslint": {
- "version": "9.17.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz",
- "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==",
+ "version": "9.18.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz",
+ "integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.19.0",
- "@eslint/core": "^0.9.0",
+ "@eslint/core": "^0.10.0",
"@eslint/eslintrc": "^3.2.0",
- "@eslint/js": "9.17.0",
- "@eslint/plugin-kit": "^0.2.3",
+ "@eslint/js": "9.18.0",
+ "@eslint/plugin-kit": "^0.2.5",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.1",
@@ -5655,9 +5709,9 @@
}
},
"node_modules/eslint-plugin-jest": {
- "version": "28.10.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.10.0.tgz",
- "integrity": "sha512-hyMWUxkBH99HpXT3p8hc7REbEZK3D+nk8vHXGgpB+XXsi0gO4PxMSP+pjfUzb67GnV9yawV9a53eUmcde1CCZA==",
+ "version": "28.11.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.11.0.tgz",
+ "integrity": "sha512-QAfipLcNCWLVocVbZW8GimKn5p5iiMcgGbRzz8z/P5q7xw+cNEpYqyzFMtIF/ZgF2HLOyy+dYBut+DoYolvqig==",
"dev": true,
"dependencies": {
"@typescript-eslint/utils": "^6.0.0 || ^7.0.0 || ^8.0.0"
@@ -5680,9 +5734,9 @@
}
},
"node_modules/eslint-plugin-react": {
- "version": "7.37.3",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.3.tgz",
- "integrity": "sha512-DomWuTQPFYZwF/7c9W2fkKkStqZmBd3uugfqBYLdkZ3Hii23WzZuOLUskGxB8qkSKqftxEeGL1TB2kMhrce0jA==",
+ "version": "7.37.4",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.4.tgz",
+ "integrity": "sha512-BGP0jRmfYyvOyvMoRX/uoUeW+GqNj9y16bPQzqAHf3AYII/tDs+jMN0dBVkl88/OZwNGwrVFxE7riHsXVfy/LQ==",
"dev": true,
"dependencies": {
"array-includes": "^3.1.8",
@@ -6194,17 +6248,16 @@
"license": "MIT"
},
"node_modules/fast-glob": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
- "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
"dev": true,
- "license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
"glob-parent": "^5.1.2",
"merge2": "^1.3.0",
- "micromatch": "^4.0.4"
+ "micromatch": "^4.0.8"
},
"engines": {
"node": ">=8.6.0"
@@ -6373,11 +6426,10 @@
}
},
"node_modules/flatted": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
- "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
- "dev": true,
- "license": "ISC"
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz",
+ "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==",
+ "dev": true
},
"node_modules/for-each": {
"version": "0.3.3",
@@ -6455,10 +6507,9 @@
}
},
"node_modules/fs-extra": {
- "version": "11.2.0",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
- "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==",
- "license": "MIT",
+ "version": "11.3.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz",
+ "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
@@ -7075,6 +7126,12 @@
"minimalistic-crypto-utils": "^1.0.1"
}
},
+ "node_modules/hookified": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.6.0.tgz",
+ "integrity": "sha512-se7cpwTA+iA/eY548Bu03JJqBiEZAqU2jnyKdj5B5qurtBg64CZGHTgqCv4Yh7NWu6FGI09W61MCq+NoPj9GXA==",
+ "dev": true
+ },
"node_modules/html-encoding-sniffer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
@@ -9862,11 +9919,10 @@
}
},
"node_modules/mdn-data": {
- "version": "2.12.1",
- "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.1.tgz",
- "integrity": "sha512-rsfnCbOHjqrhWxwt5/wtSLzpoKTzW7OXdT5lLOIH1OTYhWu9rRJveGq0sKvDZODABH7RX+uoR+DYcpFnq4Tf6Q==",
- "dev": true,
- "license": "CC0-1.0"
+ "version": "2.12.2",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz",
+ "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==",
+ "dev": true
},
"node_modules/media-typer": {
"version": "0.3.0",
@@ -10169,9 +10225,9 @@
}
},
"node_modules/mongoose": {
- "version": "8.9.3",
- "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.9.3.tgz",
- "integrity": "sha512-G50GNPdMqhoiRAJ/24GYAzg13yxXDD3FOOFeYiFwtHmHpAJem3hxbYIxAhLJGWbYEiUZL0qFMu2LXYkgGAmo+Q==",
+ "version": "8.9.5",
+ "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.9.5.tgz",
+ "integrity": "sha512-SPhOrgBm0nKV3b+IIHGqpUTOmgVL5Z3OO9AwkFEmvOZznXTvplbomstCnPOGAyungtRXE5pJTgKpKcZTdjeESg==",
"dependencies": {
"bson": "^6.10.1",
"kareem": "2.6.3",
@@ -11598,9 +11654,9 @@
"license": "MIT"
},
"node_modules/react-router": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.1.1.tgz",
- "integrity": "sha512-39sXJkftkKWRZ2oJtHhCxmoCrBCULr/HAH4IT5DHlgu/Q0FCPV0S4Lx+abjDTx/74xoZzNYDYbOZWlJjruyuDQ==",
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.1.3.tgz",
+ "integrity": "sha512-EezYymLY6Guk/zLQ2vRA8WvdUhWFEj5fcE3RfWihhxXBW7+cd1LsIiA3lmx+KCmneAGQuyBv820o44L2+TtkSA==",
"dependencies": {
"@types/cookie": "^0.6.0",
"cookie": "^1.0.1",
@@ -12982,9 +13038,9 @@
"license": "ISC"
},
"node_modules/stylelint": {
- "version": "16.12.0",
- "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.12.0.tgz",
- "integrity": "sha512-F8zZ3L/rBpuoBZRvI4JVT20ZanPLXfQLzMOZg1tzPflRVh9mKpOZ8qcSIhh1my3FjAjZWG4T2POwGnmn6a6hbg==",
+ "version": "16.13.2",
+ "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.13.2.tgz",
+ "integrity": "sha512-wDlgh0mRO9RtSa3TdidqHd0nOG8MmUyVKl+dxA6C1j8aZRzpNeEgdhFmU5y4sZx4Fc6r46p0fI7p1vR5O2DZqA==",
"dev": true,
"funding": [
{
@@ -13006,16 +13062,16 @@
"colord": "^2.9.3",
"cosmiconfig": "^9.0.0",
"css-functions-list": "^3.2.3",
- "css-tree": "^3.0.1",
+ "css-tree": "^3.1.0",
"debug": "^4.3.7",
- "fast-glob": "^3.3.2",
+ "fast-glob": "^3.3.3",
"fastest-levenshtein": "^1.0.16",
- "file-entry-cache": "^9.1.0",
+ "file-entry-cache": "^10.0.5",
"global-modules": "^2.0.0",
"globby": "^11.1.0",
"globjoin": "^0.1.4",
"html-tags": "^3.3.1",
- "ignore": "^6.0.2",
+ "ignore": "^7.0.1",
"imurmurhash": "^0.1.4",
"is-plain-object": "^5.0.0",
"known-css-properties": "^0.35.0",
@@ -13044,9 +13100,9 @@
}
},
"node_modules/stylelint-config-recess-order": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/stylelint-config-recess-order/-/stylelint-config-recess-order-5.1.1.tgz",
- "integrity": "sha512-eDAHWVBelzDbMbdMj15pSw0Ycykv5eLeriJdbGCp0zd44yvhgZLI+wyVHegzXp5NrstxTPSxl0fuOVKdMm0XLA==",
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/stylelint-config-recess-order/-/stylelint-config-recess-order-6.0.0.tgz",
+ "integrity": "sha512-1KqrttqpIrCYFAVQ1/bbgXo7EvvcjmkxxmnzVr+U66Xr2OlrNZqQ5+44Tmct6grCWY6wGTIBh2tSANqcmwIM2g==",
"dev": true,
"dependencies": {
"stylelint-order": "^6.0.4"
@@ -13056,9 +13112,9 @@
}
},
"node_modules/stylelint-config-recommended": {
- "version": "14.0.1",
- "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-14.0.1.tgz",
- "integrity": "sha512-bLvc1WOz/14aPImu/cufKAZYfXs/A/owZfSMZ4N+16WGXLoX5lOir53M6odBxvhgmgdxCVnNySJmZKx73T93cg==",
+ "version": "15.0.0",
+ "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-15.0.0.tgz",
+ "integrity": "sha512-9LejMFsat7L+NXttdHdTq94byn25TD+82bzGRiV1Pgasl99pWnwipXS5DguTpp3nP1XjvLXVnEJIuYBfsRjRkA==",
"dev": true,
"funding": [
{
@@ -13070,12 +13126,11 @@
"url": "https://github.com/sponsors/stylelint"
}
],
- "license": "MIT",
"engines": {
"node": ">=18.12.0"
},
"peerDependencies": {
- "stylelint": "^16.1.0"
+ "stylelint": "^16.13.0"
}
},
"node_modules/stylelint-order": {
@@ -13147,34 +13202,29 @@
"license": "MIT"
},
"node_modules/stylelint/node_modules/file-entry-cache": {
- "version": "9.1.0",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-9.1.0.tgz",
- "integrity": "sha512-/pqPFG+FdxWQj+/WSuzXSDaNzxgTLr/OrR1QuqfEZzDakpdYE70PwUxL7BPUa8hpjbvY1+qvCl8k+8Tq34xJgg==",
+ "version": "10.0.5",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.0.5.tgz",
+ "integrity": "sha512-umpQsJrBNsdMDgreSryMEXvJh66XeLtZUwA8Gj7rHGearGufUFv6rB/bcXRFsiGWw/VeSUgUofF4Rf2UKEOrTA==",
"dev": true,
"dependencies": {
- "flat-cache": "^5.0.0"
- },
- "engines": {
- "node": ">=18"
+ "flat-cache": "^6.1.5"
}
},
"node_modules/stylelint/node_modules/flat-cache": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-5.0.0.tgz",
- "integrity": "sha512-JrqFmyUl2PnPi1OvLyTVHnQvwQ0S+e6lGSwu8OkAZlSaNIZciTY2H/cOOROxsBA1m/LZNHDsqAgDZt6akWcjsQ==",
+ "version": "6.1.5",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.5.tgz",
+ "integrity": "sha512-QR+2kN38f8nMfiIQ1LHYjuDEmZNZVjxuxY+HufbS3BW0EX01Q5OnH7iduOYRutmgiXb797HAKcXUeXrvRjjgSQ==",
"dev": true,
"dependencies": {
- "flatted": "^3.3.1",
- "keyv": "^4.5.4"
- },
- "engines": {
- "node": ">=18"
+ "cacheable": "^1.8.7",
+ "flatted": "^3.3.2",
+ "hookified": "^1.6.0"
}
},
"node_modules/stylelint/node_modules/ignore": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-6.0.2.tgz",
- "integrity": "sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==",
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.3.tgz",
+ "integrity": "sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA==",
"dev": true,
"engines": {
"node": ">= 4"
diff --git a/package.json b/package.json
index 7acffcda8..40f6aad81 100644
--- a/package.json
+++ b/package.json
@@ -39,6 +39,7 @@
"test:definition-lists": "jest tests/markdown/definition-lists.test.js --verbose --noStackTrace",
"test:hard-breaks": "jest tests/markdown/hard-breaks.test.js --verbose --noStackTrace",
"test:non-breaking-spaces": "jest tests/markdown/non-breaking-spaces.test.js --verbose --noStackTrace",
+ "test:paragraph-justification": "jest tests/markdown/paragraph-justification.test.js --verbose --noStackTrace",
"test:emojis": "jest tests/markdown/emojis.test.js --verbose --noStackTrace",
"test:route": "jest tests/routes/static-pages.test.js --verbose",
"test:safehtml": "jest tests/html/safeHTML.test.js --verbose",
@@ -93,7 +94,7 @@
"classnames": "^2.5.1",
"codemirror": "^5.65.6",
"cookie-parser": "^1.4.7",
- "core-js": "^3.39.0",
+ "core-js": "^3.40.0",
"cors": "^2.8.5",
"create-react-class": "^15.7.0",
"dedent-tabs": "^0.10.3",
@@ -102,7 +103,7 @@
"express": "^4.21.2",
"express-async-handler": "^1.2.0",
"express-static-gzip": "2.2.0",
- "fs-extra": "11.2.0",
+ "fs-extra": "11.3.0",
"idb-keyval": "^6.2.1",
"js-yaml": "^4.1.0",
"jwt-simple": "^0.5.6",
@@ -115,13 +116,13 @@
"marked-smartypants-lite": "^1.0.2",
"markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.30.1",
- "mongoose": "^8.9.3",
+ "mongoose": "^8.9.5",
"nanoid": "5.0.9",
"nconf": "^0.12.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-frame-component": "^4.1.3",
- "react-router": "^7.1.1",
+ "react-router": "^7.1.3",
"sanitize-filename": "1.6.3",
"superagent": "^10.1.1",
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
@@ -129,17 +130,17 @@
"devDependencies": {
"@stylistic/stylelint-plugin": "^3.1.1",
"babel-plugin-transform-import-meta": "^2.3.2",
- "eslint": "^9.17.0",
- "eslint-plugin-jest": "^28.10.0",
- "eslint-plugin-react": "^7.37.3",
+ "eslint": "^9.18.0",
+ "eslint-plugin-jest": "^28.11.0",
+ "eslint-plugin-react": "^7.37.4",
"globals": "^15.14.0",
"jest": "^29.7.0",
"jest-expect-message": "^1.1.3",
"jsdom-global": "^3.0.2",
"postcss-less": "^6.0.0",
- "stylelint": "^16.12.0",
- "stylelint-config-recess-order": "^5.1.1",
- "stylelint-config-recommended": "^14.0.1",
+ "stylelint": "^16.13.2",
+ "stylelint-config-recess-order": "^6.0.0",
+ "stylelint-config-recommended": "^15.0.0",
"supertest": "^7.0.0"
}
}
diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js
index 852243d81..99766b536 100644
--- a/shared/naturalcrit/markdown.js
+++ b/shared/naturalcrit/markdown.js
@@ -1,7 +1,8 @@
+/* eslint-disable max-depth */
/* eslint-disable max-lines */
import _ from 'lodash';
import { Parser as MathParser } from 'expr-eval';
-import { marked as Marked } from 'marked';
+import { marked as Marked } from 'marked';
import MarkedExtendedTables from 'marked-extended-tables';
import { markedSmartypantsLite as MarkedSmartypantsLite } from 'marked-smartypants-lite';
import { gfmHeadingId as MarkedGFMHeadingId, resetHeadings as MarkedGFMResetHeadingIDs } from 'marked-gfm-heading-id';
@@ -172,8 +173,8 @@ const mustacheSpans = {
return ``${key}="${value}"`).join(' ')}` : ''}` +
+ `${tags.styles ? ` style="${Object.entries(tags.styles).map(([key, value])=>`${key}:${value};`).join(' ')}"` : ''}` +
+ `${tags.attributes ? ` ${Object.entries(tags.attributes).map(([key, value])=>`${key}="${value}"`).join(' ')}` : ''}` +
`>${this.parser.parseInline(token.tokens)} `; // parseInline to turn child tokens into HTML
}
};
@@ -228,7 +229,7 @@ const mustacheDivs = {
return ``${key}="${value}"`).join(' ')}` : ''}` +
`>${this.parser.parse(token.tokens)}
`; // parse to turn child tokens into HTML
}
@@ -265,18 +266,13 @@ const mustacheInjectInline = {
const text = this.parser.parseInline([token]);
const originalTags = extractHTMLStyleTags(text);
const injectedTags = token.injectedTags;
- const tags = {
- id : injectedTags.id || originalTags.id || null,
- classes : [originalTags.classes, injectedTags.classes].join(' ').trim() || null,
- styles : [originalTags.styles, injectedTags.styles].join(' ').trim() || null,
- attributes : Object.assign(originalTags.attributes ?? {}, injectedTags.attributes ?? {})
- };
+ const tags = mergeHTMLTags(originalTags, injectedTags);
const openingTag = /(<[^\s<>]+)[^\n<>]*(>.*)/s.exec(text);
if(openingTag) {
return `${openingTag[1]}` +
`${tags.classes ? ` class="${tags.classes}"` : ''}` +
`${tags.id ? ` id="${tags.id}"` : ''}` +
- `${tags.styles ? ` style="${tags.styles}"` : ''}` +
+ `${!_.isEmpty(tags.styles) ? ` style="${Object.entries(tags.styles).map(([key, value])=>`${key}:${value};`).join(' ')}"` : ''}` +
`${!_.isEmpty(tags.attributes) ? ` ${Object.entries(tags.attributes).map(([key, value])=>`${key}="${value}"`).join(' ')}` : ''}` +
`${openingTag[2]}`; // parse to turn child tokens into HTML
}
@@ -314,18 +310,13 @@ const mustacheInjectBlock = {
const text = this.parser.parse([token]);
const originalTags = extractHTMLStyleTags(text);
const injectedTags = token.injectedTags;
- const tags = {
- id : injectedTags.id || originalTags.id || null,
- classes : [originalTags.classes, injectedTags.classes].join(' ').trim() || null,
- styles : [originalTags.styles, injectedTags.styles].join(' ').trim() || null,
- attributes : Object.assign(originalTags.attributes ?? {}, injectedTags.attributes ?? {})
- };
+ const tags = mergeHTMLTags(originalTags, injectedTags);
const openingTag = /(<[^\s<>]+)[^\n<>]*(>.*)/s.exec(text);
if(openingTag) {
return `${openingTag[1]}` +
`${tags.classes ? ` class="${tags.classes}"` : ''}` +
`${tags.id ? ` id="${tags.id}"` : ''}` +
- `${tags.styles ? ` style="${tags.styles}"` : ''}` +
+ `${!_.isEmpty(tags.styles) ? ` style="${Object.entries(tags.styles).map(([key, value])=>`${key}:${value};`).join(' ')}"` : ''}` +
`${!_.isEmpty(tags.attributes) ? ` ${Object.entries(tags.attributes).map(([key, value])=>`${key}="${value}"`).join(' ')}` : ''}` +
`${openingTag[2]}`; // parse to turn child tokens into HTML
}
@@ -370,6 +361,43 @@ const superSubScripts = {
}
};
+
+const justifiedParagraphClasses = [];
+justifiedParagraphClasses[2] = 'Left';
+justifiedParagraphClasses[4] = 'Right';
+justifiedParagraphClasses[6] = 'Center';
+
+const justifiedParagraphs = {
+ name : 'justifiedParagraphs',
+ level : 'block',
+ start(src) {
+ return src.match(/\n(?:-:|:-|-:) {1}/m)?.index;
+ }, // Hint to Marked.js to stop and check for a match
+ tokenizer(src, tokens) {
+ const regex = /^(((:-))|((-:))|((:-:))) .+(\n(([^\n].*\n)*(\n|$))|$)/ygm;
+ const match = regex.exec(src);
+ if(match?.length) {
+ let whichJustify;
+ if(match[2]?.length) whichJustify = 2;
+ if(match[4]?.length) whichJustify = 4;
+ if(match[6]?.length) whichJustify = 6;
+ return {
+ type : 'justifiedParagraphs', // Should match "name" above
+ raw : match[0], // Text to consume from the source
+ length : match[whichJustify].length,
+ text : match[0].slice(match[whichJustify].length),
+ class : justifiedParagraphClasses[whichJustify],
+ tokens : this.lexer.inlineTokens(match[0].slice(match[whichJustify].length + 1))
+ };
+ }
+ },
+ renderer(token) {
+ return `${this.parser.parseInline(token.tokens)}
`;
+ }
+
+};
+
+
const forcedParagraphBreaks = {
name : 'hardBreaks',
level : 'block',
@@ -680,7 +708,7 @@ function MarkedVariables() {
}
if(match[8]) { // Inline Definition
const label = match[10] ? match[10].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
- let content = match[11] ? match[11].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
+ let content = match[11] || null;
// In case of nested (), find the correct matching end )
let level = 0;
@@ -696,10 +724,8 @@ function MarkedVariables() {
break;
}
}
- if(i > -1) {
- combinedRegex.lastIndex = combinedRegex.lastIndex - (content.length - i);
- content = content.slice(0, i).trim().replace(/\s+/g, ' ');
- }
+ combinedRegex.lastIndex = combinedRegex.lastIndex - (content.length - i);
+ content = content.slice(0, i).trim().replace(/\s+/g, ' ');
varsQueue.push(
{ type : 'varDefBlock',
@@ -769,7 +795,7 @@ const tableTerminators = [
];
Marked.use(MarkedVariables());
-Marked.use({ extensions : [definitionListsMultiLine, definitionListsSingleLine, forcedParagraphBreaks,
+Marked.use({ extensions : [justifiedParagraphs, definitionListsMultiLine, definitionListsSingleLine, forcedParagraphBreaks,
nonbreakingSpaces, superSubScripts, mustacheSpans, mustacheDivs, mustacheInjectInline] });
Marked.use(mustacheInjectBlock);
Marked.use({ renderer: renderer, tokenizer: tokenizer, mangle: false });
@@ -835,15 +861,20 @@ const processStyleTags = (string)=>{
const index = attr.indexOf('=');
let [key, value] = [attr.substring(0, index), attr.substring(index + 1)];
value = value.replace(/"/g, '');
- obj[key] = value;
+ obj[key.trim()] = value.trim();
return obj;
}, {}) || null;
- const styles = tags?.length ? tags.map((tag)=>tag.replace(/:"?([^"]*)"?/g, ':$1;').trim()).join(' ') : null;
+ const styles = tags?.length ? tags.reduce((styleObj, style) => {
+ const index = style.indexOf(':');
+ const [key, value] = [style.substring(0, index), style.substring(index + 1)];
+ styleObj[key.trim()] = value.replace(/"?([^"]*)"?/g, '$1').trim();
+ return styleObj;
+ }, {}) : null;
return {
id : id,
classes : classes,
- styles : styles,
+ styles : _.isEmpty(styles) ? null : styles,
attributes : _.isEmpty(attributes) ? null : attributes
};
};
@@ -853,25 +884,40 @@ const extractHTMLStyleTags = (htmlString)=>{
const firstElementOnly = htmlString.split('>')[0];
const id = firstElementOnly.match(/id="([^"]*)"/)?.[1] || null;
const classes = firstElementOnly.match(/class="([^"]*)"/)?.[1] || null;
- const styles = firstElementOnly.match(/style="([^"]*)"/)?.[1] || null;
+ const styles = firstElementOnly.match(/style="([^"]*)"/)?.[1]
+ ?.split(';').reduce((styleObj, style) => {
+ if (style.trim() === '') return styleObj;
+ const index = style.indexOf(':');
+ const [key, value] = [style.substring(0, index), style.substring(index + 1)];
+ styleObj[key.trim()] = value.trim();
+ return styleObj;
+ }, {}) || null;
const attributes = firstElementOnly.match(/[a-zA-Z]+="[^"]*"/g)
?.filter((attr)=>!attr.startsWith('class="') && !attr.startsWith('style="') && !attr.startsWith('id="'))
.reduce((obj, attr)=>{
const index = attr.indexOf('=');
let [key, value] = [attr.substring(0, index), attr.substring(index + 1)];
- value = value.replace(/"/g, '');
- obj[key] = value;
+ obj[key.trim()] = value.replace(/"/g, '');
return obj;
}, {}) || null;
return {
id : id,
classes : classes,
- styles : styles,
+ styles : _.isEmpty(styles) ? null : styles,
attributes : _.isEmpty(attributes) ? null : attributes
};
};
+const mergeHTMLTags = (originalTags, newTags) => {
+ return {
+ id : newTags.id || originalTags.id || null,
+ classes : [originalTags.classes, newTags.classes].join(' ').trim() || null,
+ styles : Object.assign(originalTags.styles ?? {}, newTags.styles ?? {}),
+ attributes : Object.assign(originalTags.attributes ?? {}, newTags.attributes ?? {})
+ };
+};
+
const globalVarsList = {};
let varsQueue = [];
let globalPageNumber = 0;
diff --git a/tests/markdown/mustache-syntax.test.js b/tests/markdown/mustache-syntax.test.js
index 261a5fd32..d17518411 100644
--- a/tests/markdown/mustache-syntax.test.js
+++ b/tests/markdown/mustache-syntax.test.js
@@ -300,7 +300,7 @@ describe('Injection: When an injection tag follows an element', ()=>{
it('Renders a span "text" with its own styles, appended with injected styles', function() {
const source = '{{color:blue,height:10px text}}{width:10px,color:red}';
const rendered = Markdown.render(source);
- expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('text ');
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('text ');
});
it('Renders a span "text" with its own classes, appended with injected classes', function() {
@@ -429,7 +429,7 @@ describe('Injection: When an injection tag follows an element', ()=>{
}}
{width:10px,color:red}`;
const rendered = Markdown.render(source).trimReturns();
- expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('');
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('');
});
it('Renders a span "text" with its own classes, appended with injected classes', function() {
diff --git a/tests/markdown/paragraph-justification.test.js b/tests/markdown/paragraph-justification.test.js
new file mode 100644
index 000000000..48b311e85
--- /dev/null
+++ b/tests/markdown/paragraph-justification.test.js
@@ -0,0 +1,27 @@
+/* eslint-disable max-lines */
+
+import Markdown from 'naturalcrit/markdown.js';
+
+describe('Justification', ()=>{
+ test('Left Justify', function() {
+ const source = ':- Hello';
+ const rendered = Markdown.render(source);
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`Hello
`);
+ });
+ test('Right Justify', function() {
+ const source = '-: Hello';
+ const rendered = Markdown.render(source);
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`Hello
`);
+ });
+ test('Center Justify', function() {
+ const source = ':-: Hello';
+ const rendered = Markdown.render(source);
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`Hello
`);
+ });
+
+ test('Ignored inside a code block', function() {
+ const source = '```\n\n:- Hello\n\n```\n';
+ const rendered = Markdown.render(source);
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`\n:- Hello\n \n`);
+ });
+});
diff --git a/tests/markdown/variables.test.js b/tests/markdown/variables.test.js
index be16e8a22..41259da7e 100644
--- a/tests/markdown/variables.test.js
+++ b/tests/markdown/variables.test.js
@@ -402,4 +402,12 @@ describe('Variable names that are subsets of other names', ()=>{
const rendered = Markdown.render(source).trimReturns();
expect(rendered).toBe('14
');
});
+});
+
+describe('Regression Tests', ()=>{
+ it('Don\'t Eat all the parentheticals!', function() {
+ const source='\n| title 1 | title 2 | title 3 | title 4|\n|-----------|---------|---------|--------|\n|[foo](bar) | Ipsum | ) | ) |\n';
+ const rendered = Markdown.render(source).trimReturns();
+ expect(rendered).toBe('title 1 title 2 title 3 title 4 foo Ipsum ) )
');
+ });
});
\ No newline at end of file
diff --git a/themes/V3/Blank/style.less b/themes/V3/Blank/style.less
index 4f838a5d1..65eeee683 100644
--- a/themes/V3/Blank/style.less
+++ b/themes/V3/Blank/style.less
@@ -505,4 +505,4 @@ body { counter-reset : page-numbers 0; }
counter-increment : page-numbers;
}
-}
\ No newline at end of file
+}