diff --git a/client/homebrew/brewRenderer/brewRenderer.jsx b/client/homebrew/brewRenderer/brewRenderer.jsx
index 21c0608bd..c83a2029b 100644
--- a/client/homebrew/brewRenderer/brewRenderer.jsx
+++ b/client/homebrew/brewRenderer/brewRenderer.jsx
@@ -186,7 +186,7 @@ const BrewRenderer = (props)=>{
} else {
if(pageText.startsWith('\\page')) {
const firstLineTokens = Markdown.marked.lexer(pageText.split('\n', 1)[0])[0].tokens;
- const injectedTags = firstLineTokens.find((obj)=>obj.injectedTags !== undefined)?.injectedTags;
+ const injectedTags = firstLineTokens?.find((obj)=>obj.injectedTags !== undefined)?.injectedTags;
if(injectedTags) {
styles = { ...styles, ...injectedTags.styles };
styles = _.mapKeys(styles, (v, k)=>k.startsWith('--') ? k : _.camelCase(k)); // Convert CSS to camelCase for React
diff --git a/package-lock.json b/package-lock.json
index 69cc1cade..2e3af673a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,11 +10,11 @@
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
- "@babel/core": "^7.26.9",
- "@babel/plugin-transform-runtime": "^7.26.9",
+ "@babel/core": "^7.26.10",
+ "@babel/plugin-transform-runtime": "^7.26.10",
"@babel/preset-env": "^7.26.9",
"@babel/preset-react": "^7.26.3",
- "@googleapis/drive": "^8.16.0",
+ "@googleapis/drive": "^11.0.0",
"body-parser": "^1.20.2",
"classnames": "^2.5.1",
"codemirror": "^5.65.6",
@@ -33,7 +33,7 @@
"jwt-simple": "^0.5.6",
"less": "^3.13.1",
"lodash": "^4.17.21",
- "marked": "14.0.0",
+ "marked": "15.0.0",
"marked-emoji": "^2.0.0",
"marked-extended-tables": "^2.0.1",
"marked-gfm-heading-id": "^4.0.1",
@@ -41,21 +41,23 @@
"marked-subsuper-text": "^1.0.3",
"markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.30.1",
- "mongoose": "^8.12.1",
- "nanoid": "5.1.3",
+ "mongoose": "^8.13.0",
+ "nanoid": "5.1.5",
"nconf": "^0.12.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-frame-component": "^4.1.3",
- "react-router": "^7.3.0",
+ "react-router": "^7.4.0",
+ "romans": "^3.0.0",
"sanitize-filename": "1.6.3",
- "superagent": "^10.1.1",
- "vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
+ "superagent": "^10.2.0",
+ "vitreum": "git+https://git@github.com/calculuschild/vitreum.git",
+ "written-number": "^0.11.1"
},
"devDependencies": {
"@stylistic/stylelint-plugin": "^3.1.2",
"babel-plugin-transform-import-meta": "^2.3.2",
- "eslint": "^9.22.0",
+ "eslint": "^9.23.0",
"eslint-plugin-jest": "^28.11.0",
"eslint-plugin-react": "^7.37.4",
"globals": "^16.0.0",
@@ -63,10 +65,10 @@
"jest-expect-message": "^1.1.3",
"jsdom-global": "^3.0.2",
"postcss-less": "^6.0.0",
- "stylelint": "^16.16.0",
+ "stylelint": "^16.17.0",
"stylelint-config-recess-order": "^6.0.0",
"stylelint-config-recommended": "^15.0.0",
- "supertest": "^7.0.0"
+ "supertest": "^7.1.0"
},
"engines": {
"node": "^20.18.x",
@@ -110,21 +112,21 @@
}
},
"node_modules/@babel/core": {
- "version": "7.26.9",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.9.tgz",
- "integrity": "sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==",
+ "version": "7.26.10",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz",
+ "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==",
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.26.2",
- "@babel/generator": "^7.26.9",
+ "@babel/generator": "^7.26.10",
"@babel/helper-compilation-targets": "^7.26.5",
"@babel/helper-module-transforms": "^7.26.0",
- "@babel/helpers": "^7.26.9",
- "@babel/parser": "^7.26.9",
+ "@babel/helpers": "^7.26.10",
+ "@babel/parser": "^7.26.10",
"@babel/template": "^7.26.9",
- "@babel/traverse": "^7.26.9",
- "@babel/types": "^7.26.9",
+ "@babel/traverse": "^7.26.10",
+ "@babel/types": "^7.26.10",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@@ -140,13 +142,13 @@
}
},
"node_modules/@babel/generator": {
- "version": "7.26.9",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.9.tgz",
- "integrity": "sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==",
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz",
+ "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==",
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.26.9",
- "@babel/types": "^7.26.9",
+ "@babel/parser": "^7.27.0",
+ "@babel/types": "^7.27.0",
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25",
"jsesc": "^3.0.2"
@@ -389,12 +391,12 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.26.9",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz",
- "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==",
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz",
+ "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==",
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.26.9"
+ "@babel/types": "^7.27.0"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -1406,15 +1408,15 @@
}
},
"node_modules/@babel/plugin-transform-runtime": {
- "version": "7.26.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.26.9.tgz",
- "integrity": "sha512-Jf+8y9wXQbbxvVYTM8gO5oEF2POdNji0NMltEkG7FtmzD9PVz7/lxpqSdTvwsjTMU5HIHuDVNf2SOxLkWi+wPQ==",
+ "version": "7.26.10",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.26.10.tgz",
+ "integrity": "sha512-NWaL2qG6HRpONTnj4JvDU6th4jYeZOJgu3QhmFTCihib0ermtOJqktA5BduGm3suhhVe9EMP9c9+mfJ/I9slqw==",
"license": "MIT",
"dependencies": {
"@babel/helper-module-imports": "^7.25.9",
"@babel/helper-plugin-utils": "^7.26.5",
"babel-plugin-polyfill-corejs2": "^0.4.10",
- "babel-plugin-polyfill-corejs3": "^0.10.6",
+ "babel-plugin-polyfill-corejs3": "^0.11.0",
"babel-plugin-polyfill-regenerator": "^0.6.1",
"semver": "^6.3.1"
},
@@ -1640,19 +1642,6 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-corejs3": {
- "version": "0.11.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz",
- "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==",
- "license": "MIT",
- "dependencies": {
- "@babel/helper-define-polyfill-provider": "^0.6.3",
- "core-js-compat": "^3.40.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
- }
- },
"node_modules/@babel/preset-modules": {
"version": "0.1.6-no-external-plugins",
"resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz",
@@ -1700,30 +1689,30 @@
}
},
"node_modules/@babel/template": {
- "version": "7.26.9",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz",
- "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==",
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz",
+ "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.26.2",
- "@babel/parser": "^7.26.9",
- "@babel/types": "^7.26.9"
+ "@babel/parser": "^7.27.0",
+ "@babel/types": "^7.27.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
- "version": "7.26.9",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.9.tgz",
- "integrity": "sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==",
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz",
+ "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.26.2",
- "@babel/generator": "^7.26.9",
- "@babel/parser": "^7.26.9",
- "@babel/template": "^7.26.9",
- "@babel/types": "^7.26.9",
+ "@babel/generator": "^7.27.0",
+ "@babel/parser": "^7.27.0",
+ "@babel/template": "^7.27.0",
+ "@babel/types": "^7.27.0",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
@@ -1741,9 +1730,9 @@
}
},
"node_modules/@babel/types": {
- "version": "7.26.10",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz",
- "integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==",
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz",
+ "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.25.9",
@@ -1878,9 +1867,9 @@
}
},
"node_modules/@eslint/config-helpers": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.1.0.tgz",
- "integrity": "sha512-kLrdPDJE1ckPo94kmPPf9Hfd0DU0Jw6oKYrhe+pwSC0iTUInmTa+w6fw8sGgcfkFJGNdWOUeOaDM4quW4a7OkA==",
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.0.tgz",
+ "integrity": "sha512-yJLLmLexii32mGrhW29qvU3QBVTu0GUmEf/J4XsBtVhp4JkIUFN/BjWqTF63yRvGApIDpZm5fa97LtYtINmfeQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -1901,9 +1890,9 @@
}
},
"node_modules/@eslint/eslintrc": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.0.tgz",
- "integrity": "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==",
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
+ "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1938,9 +1927,9 @@
}
},
"node_modules/@eslint/js": {
- "version": "9.22.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.22.0.tgz",
- "integrity": "sha512-vLFajx9o8d1/oL2ZkpMYbkLv8nDB6yaIwFNt7nI4+I80U/z03SxmfOMsLbvWr3p7C+Wnoh//aOu2pQW8cS0HCQ==",
+ "version": "9.23.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.23.0.tgz",
+ "integrity": "sha512-35MJ8vCPU0ZMxo7zfev2pypqTwWTofFZO6m4KAtdoFhRpLJUpHTZZ+KB3C7Hb1d7bULYwO4lJXGCi5Se+8OMbw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1972,9 +1961,9 @@
}
},
"node_modules/@googleapis/drive": {
- "version": "8.16.0",
- "resolved": "https://registry.npmjs.org/@googleapis/drive/-/drive-8.16.0.tgz",
- "integrity": "sha512-Xi2mMrUTQ+gsfyouRGd0pfnL+jjg4n4sjKsJruM1y4DknuRfdSBTk5E//WrL0YJ/CqpcBgyd7L8DvaPRtxZD3Q==",
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/@googleapis/drive/-/drive-11.0.0.tgz",
+ "integrity": "sha512-vhkl6/MZ8k5h5XOyenWOD4ys+SNdb8wKYvzU6OBGFx/TzJyHRm4JwgvE8uVDFU6efzNRS0mOiNRfY6nrmHOTtg==",
"license": "Apache-2.0",
"dependencies": {
"googleapis-common": "^7.0.0"
@@ -3140,9 +3129,9 @@
}
},
"node_modules/acorn": {
- "version": "8.14.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
- "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
+ "version": "8.14.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
+ "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
"dev": true,
"license": "MIT",
"bin": {
@@ -3704,12 +3693,13 @@
}
},
"node_modules/babel-plugin-polyfill-corejs3": {
- "version": "0.10.6",
- "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz",
- "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==",
+ "version": "0.11.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz",
+ "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==",
+ "license": "MIT",
"dependencies": {
- "@babel/helper-define-polyfill-provider": "^0.6.2",
- "core-js-compat": "^3.38.0"
+ "@babel/helper-define-polyfill-provider": "^0.6.3",
+ "core-js-compat": "^3.40.0"
},
"peerDependencies": {
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
@@ -5697,19 +5687,19 @@
"license": "MIT"
},
"node_modules/eslint": {
- "version": "9.22.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.22.0.tgz",
- "integrity": "sha512-9V/QURhsRN40xuHXWjV64yvrzMjcz7ZyNoF2jJFmy9j/SLk0u1OLSZgXi28MrXjymnjEGSR80WCdab3RGMDveQ==",
+ "version": "9.23.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.23.0.tgz",
+ "integrity": "sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.19.2",
- "@eslint/config-helpers": "^0.1.0",
+ "@eslint/config-helpers": "^0.2.0",
"@eslint/core": "^0.12.0",
- "@eslint/eslintrc": "^3.3.0",
- "@eslint/js": "9.22.0",
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "9.23.0",
"@eslint/plugin-kit": "^0.2.7",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
@@ -9904,9 +9894,9 @@
}
},
"node_modules/marked": {
- "version": "14.0.0",
- "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz",
- "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==",
+ "version": "15.0.0",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.0.tgz",
+ "integrity": "sha512-0mouKmBROJv/WSHJBPZZyYofUgawMChnD5je/g+aOBXsHDjb/IsnTQj7mnhQZu+qPJmRQ0ecX3mLGEUm3BgwYA==",
"license": "MIT",
"bin": {
"marked": "bin/marked.js"
@@ -10284,9 +10274,9 @@
}
},
"node_modules/mongodb-connection-string-url/node_modules/tr46": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz",
- "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==",
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.0.tgz",
+ "integrity": "sha512-IUWnUK7ADYR5Sl1fZlO1INDUhVhatWl7BtJWsIhwJ0UAK7ilzzIa8uIqOO/aYVWHZPJkKbEL+362wrzoeRF7bw==",
"license": "MIT",
"dependencies": {
"punycode": "^2.3.1"
@@ -10305,12 +10295,12 @@
}
},
"node_modules/mongodb-connection-string-url/node_modules/whatwg-url": {
- "version": "14.1.1",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.1.tgz",
- "integrity": "sha512-mDGf9diDad/giZ/Sm9Xi2YcyzaFpbdLpJPr+E9fSkyQ7KpQD4SdFcugkRQYzhmfI4KeV4Qpnn2sKPdo+kmsgRQ==",
+ "version": "14.2.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
+ "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
"license": "MIT",
"dependencies": {
- "tr46": "^5.0.0",
+ "tr46": "^5.1.0",
"webidl-conversions": "^7.0.0"
},
"engines": {
@@ -10318,14 +10308,14 @@
}
},
"node_modules/mongoose": {
- "version": "8.12.1",
- "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.12.1.tgz",
- "integrity": "sha512-UW22y8QFVYmrb36hm8cGncfn4ARc/XsYWQwRTaj0gxtQk1rDuhzDO1eBantS+hTTatfAIS96LlRCJrcNHvW5+Q==",
+ "version": "8.13.0",
+ "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.13.0.tgz",
+ "integrity": "sha512-e/iYV1mPeOkg+SWAMHzt3t42/EZyER3OB1H2pjP9C3vQ+Qb5DMeV9Kb+YCUycKgScA3fbwL7dKG4EpinGlg21g==",
"license": "MIT",
"dependencies": {
"bson": "^6.10.3",
"kareem": "2.6.3",
- "mongodb": "~6.14.0",
+ "mongodb": "~6.15.0",
"mpath": "0.9.0",
"mquery": "5.0.0",
"ms": "2.1.3",
@@ -10401,9 +10391,9 @@
}
},
"node_modules/mongoose/node_modules/mongodb": {
- "version": "6.14.2",
- "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.14.2.tgz",
- "integrity": "sha512-kMEHNo0F3P6QKDq17zcDuPeaywK/YaJVCEQRzPF3TOM/Bl9MFg64YE5Tu7ifj37qZJMhwU1tl2Ioivws5gRG5Q==",
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.15.0.tgz",
+ "integrity": "sha512-ifBhQ0rRzHDzqp9jAQP6OwHSH7dbYIQjD3SbJs9YYk9AikKEettW/9s/tbSFDTpXcRbF+u1aLrhHxDFaYtZpFQ==",
"license": "Apache-2.0",
"dependencies": {
"@mongodb-js/saslprep": "^1.1.9",
@@ -10480,9 +10470,9 @@
"optional": true
},
"node_modules/nanoid": {
- "version": "5.1.3",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.3.tgz",
- "integrity": "sha512-zAbEOEr7u2CbxwoMRlz/pNSpRP0FdAU4pRaYunCdEezWohXFs+a0Xw7RfkKaezMsmSM1vttcLthJtwRnVtOfHQ==",
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz",
+ "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==",
"funding": [
{
"type": "github",
@@ -11753,9 +11743,9 @@
"license": "MIT"
},
"node_modules/react-router": {
- "version": "7.3.0",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.3.0.tgz",
- "integrity": "sha512-466f2W7HIWaNXTKM5nHTqNxLrHTyXybm7R0eBlVSt0k/u55tTCDO194OIx/NrYD4TS5SXKTNekXfT37kMKUjgw==",
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.4.0.tgz",
+ "integrity": "sha512-Y2g5ObjkvX3VFeVt+0CIPuYd9PpgqCslG7ASSIdN73LwA1nNWzcMLaoMRJfP3prZFI92svxFwbn7XkLJ+UPQ6A==",
"license": "MIT",
"dependencies": {
"@types/cookie": "^0.6.0",
@@ -12132,6 +12122,12 @@
"inherits": "^2.0.1"
}
},
+ "node_modules/romans": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/romans/-/romans-3.0.0.tgz",
+ "integrity": "sha512-7DDsAfhtpRr/ZFQXiHDrC3Pe00agcAsFiNt5nNx4ZAQlsc6yJG0mvXA5WAvO8YZyOg349twm2GYhHLw7rCXAzw==",
+ "license": "MIT"
+ },
"node_modules/rrweb-cssom": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz",
@@ -13139,9 +13135,9 @@
"license": "ISC"
},
"node_modules/stylelint": {
- "version": "16.16.0",
- "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.16.0.tgz",
- "integrity": "sha512-40X5UOb/0CEFnZVEHyN260HlSSUxPES+arrUphOumGWgXERHfwCD0kNBVILgQSij8iliYVwlc0V7M5bcLP9vPg==",
+ "version": "16.17.0",
+ "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.17.0.tgz",
+ "integrity": "sha512-I9OwVIWRMqVm2Br5iTbrfSqGRPWQUlvm6oXO1xZuYYu0Gpduy67N8wXOZv15p6E/JdlZiAtQaIoLKZEWk5hrjw==",
"dev": true,
"funding": [
{
@@ -13395,9 +13391,10 @@
}
},
"node_modules/superagent": {
- "version": "10.1.1",
- "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.1.1.tgz",
- "integrity": "sha512-9pIwrHrOj3uAnqg9gDlW7EA2xv+N5au/dSM0kM22HTqmUu8jBxNT+8uA7tA3UoCnmiqzpSbu8rasIUZvbyamMQ==",
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.0.tgz",
+ "integrity": "sha512-IKeoGox6oG9zyDeizaezkJ2/aK0wc5la9st7WsAKyrAkfJ56W3whVbVtF68k6wuc87/y9T85NyON5FLz7Mrzzw==",
+ "license": "MIT",
"dependencies": {
"component-emitter": "^1.3.0",
"cookiejar": "^2.1.4",
@@ -13426,9 +13423,9 @@
}
},
"node_modules/supertest": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.0.0.tgz",
- "integrity": "sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA==",
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.0.tgz",
+ "integrity": "sha512-5QeSO8hSrKghtcWEoPiO036fxH0Ii2wVQfFZSP0oqQhmjk8bOLhDFXr4JrvaFmPuEWUoq4znY3uSi8UzLKxGqw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -14915,6 +14912,12 @@
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
}
},
+ "node_modules/written-number": {
+ "version": "0.11.1",
+ "resolved": "https://registry.npmjs.org/written-number/-/written-number-0.11.1.tgz",
+ "integrity": "sha512-LhQ68uUnzHH0bwm/QiGA9JwqgadSDOwqB2AIs/LBsrOY6ScqVXKRN2slTCeKAhstDBJ/Of/Yxcjn0pnQmVlmtg==",
+ "license": "MIT"
+ },
"node_modules/ws": {
"version": "7.5.10",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
diff --git a/package.json b/package.json
index 96528b786..8e88a3fd4 100644
--- a/package.json
+++ b/package.json
@@ -84,11 +84,11 @@
]
},
"dependencies": {
- "@babel/core": "^7.26.9",
- "@babel/plugin-transform-runtime": "^7.26.9",
+ "@babel/core": "^7.26.10",
+ "@babel/plugin-transform-runtime": "^7.26.10",
"@babel/preset-env": "^7.26.9",
"@babel/preset-react": "^7.26.3",
- "@googleapis/drive": "^8.16.0",
+ "@googleapis/drive": "^11.0.0",
"body-parser": "^1.20.2",
"classnames": "^2.5.1",
"codemirror": "^5.65.6",
@@ -107,7 +107,7 @@
"jwt-simple": "^0.5.6",
"less": "^3.13.1",
"lodash": "^4.17.21",
- "marked": "14.0.0",
+ "marked": "15.0.0",
"marked-emoji": "^2.0.0",
"marked-extended-tables": "^2.0.1",
"marked-gfm-heading-id": "^4.0.1",
@@ -115,21 +115,23 @@
"marked-subsuper-text": "^1.0.3",
"markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.30.1",
- "mongoose": "^8.12.1",
- "nanoid": "5.1.3",
+ "mongoose": "^8.13.0",
+ "nanoid": "5.1.5",
"nconf": "^0.12.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-frame-component": "^4.1.3",
- "react-router": "^7.3.0",
+ "react-router": "^7.4.0",
+ "romans": "^3.0.0",
"sanitize-filename": "1.6.3",
- "superagent": "^10.1.1",
- "vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
+ "superagent": "^10.2.0",
+ "vitreum": "git+https://git@github.com/calculuschild/vitreum.git",
+ "written-number": "^0.11.1"
},
"devDependencies": {
"@stylistic/stylelint-plugin": "^3.1.2",
"babel-plugin-transform-import-meta": "^2.3.2",
- "eslint": "^9.22.0",
+ "eslint": "^9.23.0",
"eslint-plugin-jest": "^28.11.0",
"eslint-plugin-react": "^7.37.4",
"globals": "^16.0.0",
@@ -137,9 +139,9 @@
"jest-expect-message": "^1.1.3",
"jsdom-global": "^3.0.2",
"postcss-less": "^6.0.0",
- "stylelint": "^16.16.0",
+ "stylelint": "^16.17.0",
"stylelint-config-recess-order": "^6.0.0",
"stylelint-config-recommended": "^15.0.0",
- "supertest": "^7.0.0"
+ "supertest": "^7.1.0"
}
}
diff --git a/server/app.js b/server/app.js
index 079f5e03c..6bd0c7c9a 100644
--- a/server/app.js
+++ b/server/app.js
@@ -69,14 +69,11 @@ const corsOptions = {
'https://homebrewery-stage.herokuapp.com',
];
- if(isLocalEnvironment) {
- const localNetworkRegex = /^http:\/\/(localhost|127\.0\.0\.1|10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+|172\.(1[6-9]|2\d|3[0-1])\.\d+\.\d+):\d+$/;
- allowedOrigins.push(localNetworkRegex);
- }
+ const localNetworkRegex = /^http:\/\/(localhost|127\.0\.0\.1|10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+|172\.(1[6-9]|2\d|3[0-1])\.\d+\.\d+):\d+$/;
const herokuRegex = /^https:\/\/(?:homebrewery-pr-\d+\.herokuapp\.com|naturalcrit-pr-\d+\.herokuapp\.com)$/; // Matches any Heroku app
- if(!origin || allowedOrigins.includes(origin) || herokuRegex.test(origin)) {
+ if(!origin || allowedOrigins.includes(origin) || herokuRegex.test(origin) || (isLocalEnvironment && localNetworkRegex.test(origin))) {
callback(null, true);
} else {
console.log(origin, 'not allowed');
diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js
index a8b877f4b..6391b511b 100644
--- a/shared/naturalcrit/markdown.js
+++ b/shared/naturalcrit/markdown.js
@@ -8,6 +8,8 @@ import { markedSmartypantsLite as MarkedSmartypantsLite }
import { gfmHeadingId as MarkedGFMHeadingId, resetHeadings as MarkedGFMResetHeadingIDs } from 'marked-gfm-heading-id';
import { markedEmoji as MarkedEmojis } from 'marked-emoji';
import MarkedSubSuperText from 'marked-subsuper-text';
+import { romanize } from 'romans';
+import writtenNumber from 'written-number';
//Icon fonts included so they can appear in emoji autosuggest dropdown
import diceFont from '../../themes/fonts/iconFonts/diceFont.js';
@@ -59,6 +61,53 @@ mathParser.functions.signed = function (a) {
if(a >= 0) return `+${a}`;
return `${a}`;
};
+// Add Roman numeral functions
+mathParser.functions.toRomans = function (a) {
+ return romanize(a);
+};
+mathParser.functions.toRomansUpper = function (a) {
+ return romanize(a).toUpperCase();
+};
+mathParser.functions.toRomansLower = function (a) {
+ return romanize(a).toLowerCase();
+};
+// Add character functions
+mathParser.functions.toChar = function (a) {
+ if(a <= 0) return a;
+ const genChars = function (i) {
+ return (i > 26 ? genChars(Math.floor((i - 1) / 26)) : '') + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[(i - 1) % 26];
+ };
+ return genChars(a);
+};
+mathParser.functions.toCharUpper = function (a) {
+ return mathParser.functions.toChar(a).toUpperCase();
+};
+mathParser.functions.toCharLower = function (a) {
+ return mathParser.functions.toChar(a).toLowerCase();
+};
+// Add word functions
+mathParser.functions.toWords = function (a) {
+ return writtenNumber(a);
+};
+mathParser.functions.toWordsUpper = function (a) {
+ return mathParser.functions.toWords(a).toUpperCase();
+};
+mathParser.functions.toWordsLower = function (a) {
+ return mathParser.functions.toWords(a).toLowerCase();
+};
+mathParser.functions.toWordsCaps = function (a) {
+ const words = mathParser.functions.toWords(a).split(' ');
+ return words.map((word)=>{
+ return word.replace(/(?:^|\b|\s)(\w)/g, function(w, index) {
+ return index === 0 ? w.toLowerCase() : w.toUpperCase();
+ });
+ }).join(' ');
+};
+
+// Normalize variable names; trim edge spaces and shorten blocks of whitespace to 1 space
+const normalizeVarNames = (label)=>{
+ return label.trim().replace(/\s+/g, ' ');
+};
//Processes the markdown within an HTML block if it's just a class-wrapper
renderer.html = function (token) {
@@ -99,7 +148,7 @@ renderer.link = function (token) {
}
let out = `-------------------=====v//
const mathRegex = /[a-z]+\(|[+\-*/^(),]/g;
@@ -664,8 +713,8 @@ function MarkedVariables() {
});
}
if(match[3]) { // Block Definition
- const label = match[4] ? match[4].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
- const content = match[5] ? match[5].trim().replace(/[ \t]+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
+ const label = match[4] ? normalizeVarNames(match[4]) : null;
+ const content = match[5] ? match[5].trim().replace(/[ \t]+/g, ' ') : null; // Normalize text content (except newlines for block-level content)
varsQueue.push(
{ type : 'varDefBlock',
@@ -674,7 +723,7 @@ function MarkedVariables() {
});
}
if(match[6]) { // Block Call
- const label = match[7] ? match[7].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
+ const label = match[7] ? normalizeVarNames(match[7]) : null;
varsQueue.push(
{ type : 'varCallBlock',
@@ -683,7 +732,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
+ const label = match[10] ? normalizeVarNames(match[10]) : null;
let content = match[11] || null;
// In case of nested (), find the correct matching end )
@@ -715,7 +764,7 @@ function MarkedVariables() {
});
}
if(match[12]) { // Inline Call
- const label = match[13] ? match[13].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
+ const label = match[13] ? normalizeVarNames(match[13]) : null;
varsQueue.push(
{ type : 'varCallInline',
@@ -902,7 +951,13 @@ let globalPageNumber = 0;
const Markdown = {
marked : Marked,
render : (rawBrewText, pageNumber=0)=>{
- globalVarsList[pageNumber] = {}; //Reset global links for current page, to ensure values are parsed in order
+ const lastPageNumber = pageNumber > 0 ? globalVarsList[pageNumber - 1].HB_pageNumber.content : 0;
+ globalVarsList[pageNumber] = { //Reset global links for current page, to ensure values are parsed in order
+ 'HB_pageNumber' : { //Add document variables for this page
+ content : !isNaN(Number(lastPageNumber)) ? Number(lastPageNumber) + 1 : lastPageNumber,
+ resolved : true
+ }
+ };
varsQueue = []; //Could move into MarkedVariables()
globalPageNumber = pageNumber;
if(pageNumber==0) {
diff --git a/tests/markdown/non-breaking-spaces.test.js b/tests/markdown/non-breaking-spaces.test.js
index c4f36554a..83fb55509 100644
--- a/tests/markdown/non-breaking-spaces.test.js
+++ b/tests/markdown/non-breaking-spaces.test.js
@@ -54,19 +54,19 @@ describe('Non-Breaking Spaces', ()=>{
test('I am actually a single-line definition list!', function() {
const source = 'Term ::> Definition 1\n';
const rendered = Markdown.render(source).trim();
- expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(` two one two 1 2 100 101 a a
`);
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
`);
});
test('I am actually a definition list!', function() {
const source = 'Term\n::> Definition 1\n';
const rendered = Markdown.render(source).trim();
- expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
`);
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
`);
});
test('I am actually a two-term definition list!', function() {
const source = 'Term\n::> Definition 1\n::>> Definition 2';
const rendered = Markdown.render(source).trim();
- expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
`);
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
`);
});
});
diff --git a/tests/markdown/variables.test.js b/tests/markdown/variables.test.js
index 41259da7e..ea98fe56c 100644
--- a/tests/markdown/variables.test.js
+++ b/tests/markdown/variables.test.js
@@ -370,6 +370,30 @@ describe('Cross-page variables', ()=>{
const rendered = renderAllPages([source0, source1]).join('\n\\page\n').trimReturns();
expect(rendered, `Input:\n${[source0, source1].join('\n\\page\n')}`, { showPrefix: false }).toBe('
| title 1 | title 2 | title 3 | title 4 |
|---|---|---|---|
| foo | Ipsum | ) | ) |




Positive: +
Negative: -
'); + }); + + it('Signed Test', function() { + const source = `[a]: 13\n\n[b]: -11\n\nPositive: $[signed(a)]\n\nNegative: $[signed(b)]`; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered).toBe('Positive: +13
Negative: -11
'); + }); + + it('Roman Numerals Test', function() { + const source = `[a]: 18\n\nRoman Numeral: $[toRomans(a)]`; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered).toBe('Roman Numeral: XVIII
'); + }); + + it('Roman Numerals Test - Uppercase', function() { + const source = `[a]: 18\n\nRoman Numeral: $[toRomansUpper(a)]`; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered).toBe('Roman Numeral: XVIII
'); + }); + + it('Roman Numerals Test - Lowercase', function() { + const source = `[a]: 18\n\nRoman Numeral: $[toRomansLower(a)]`; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered).toBe('Roman Numeral: xviii
'); + }); + + it('Number to Characters Test', function() { + const source = `[a]: 18\n\n[b]: 39\n\nCharacters: $[toChar(a)] $[toChar(b)]`; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered).toBe('Characters: R AM
'); + }); + + it('Number to Characters Test - Uppercase', function() { + const source = `[a]: 18\n\n[b]: 39\n\nCharacters: $[toCharUpper(a)] $[toCharUpper(b)]`; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered).toBe('Characters: R AM
'); + }); + + it('Number to Characters Test - Lowercase', function() { + const source = `[a]: 18\n\n[b]: 39\n\nCharacters: $[toCharLower(a)] $[toCharLower(b)]`; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered).toBe('Characters: r am
'); + }); + + it('Number to Words Test', function() { + const source = `[a]: 80085\n\nWords: $[toWords(a)]`; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered).toBe('Words: eighty thousand and eighty-five
'); + }); + + it('Number to Words Test - Uppercase', function() { + const source = `[a]: 80085\n\nWords: $[toWordsUpper(a)]`; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered).toBe('Words: EIGHTY THOUSAND AND EIGHTY-FIVE
'); + }); + + it('Number to Words Test - Lowercase', function() { + const source = `[a]: 80085\n\nWords: $[toWordsLower(a)]`; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered).toBe('Words: eighty thousand and eighty-five
'); + }); + + it('Number to Words Test - Capitalized', function() { + const source = `[a]: 80085\n\nWords: $[toWordsCaps(a)]`; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered).toBe('Words: Eighty Thousand And Eighty-Five
'); + }); }); \ No newline at end of file diff --git a/themes/Legacy/5ePHB/style.less b/themes/Legacy/5ePHB/style.less index 56d1bed94..41524c74c 100644 --- a/themes/Legacy/5ePHB/style.less +++ b/themes/Legacy/5ePHB/style.less @@ -13,13 +13,13 @@ body { counter-reset : phb-page-numbers; } * { -webkit-print-color-adjust : exact; } .useSansSerif() { - font-family : "ScalySans"; + font-family : 'ScalySans'; em { - font-family : "ScalySans"; + font-family : 'ScalySans'; font-style : italic; } strong { - font-family : "ScalySans"; + font-family : 'ScalySans'; font-weight : 800; letter-spacing : -0.02em; } @@ -46,7 +46,7 @@ body { counter-reset : phb-page-numbers; } padding : 1.0cm 1.7cm; padding-bottom : 1.5cm; overflow : hidden; - font-family : "BookSanity"; + font-family : 'BookSanity'; font-size : 0.317cm; counter-increment : phb-page-numbers; background-color : @background; @@ -106,7 +106,7 @@ body { counter-reset : phb-page-numbers; } h1,h2,h3,h4 { margin-top : 0.2em; margin-bottom : 0.2em; - font-family : "MrJeeves"; + font-family : 'MrJeeves'; font-weight : 800; color : @headerText; } @@ -117,7 +117,7 @@ body { counter-reset : phb-page-numbers; } -moz-column-span : all; & + p::first-letter { float : left; - font-family : "Solberry"; + font-family : 'Solberry'; font-size : 10em; line-height : 0.795em; color : #222222; @@ -134,7 +134,7 @@ body { counter-reset : phb-page-numbers; } } h5 { margin-bottom : 0.2em; - font-family : "ScalySansSmallCaps"; + font-family : 'ScalySansSmallCaps'; font-size : 0.423cm; font-weight : 900; } @@ -200,7 +200,7 @@ body { counter-reset : phb-page-numbers; } & + p { padding-bottom : 0px; } } h3 { - font-family : "ScalySans"; + font-family : 'ScalySans'; font-weight : normal; border-bottom : 1px solid @headerText; } @@ -380,7 +380,7 @@ body { counter-reset : phb-page-numbers; } // ************************************/ .phb .descriptive { margin-bottom : 1em; - font-family : "ScalySans"; + font-family : 'ScalySans'; background-color : #FAF7EA; border-style : solid; border-width : 7px; @@ -394,11 +394,11 @@ body { counter-reset : phb-page-numbers; } } p + p { padding-top : 0.8em; } em { - font-family : "ScalySans"; + font-family : 'ScalySans'; font-style : italic; } strong { - font-family : "ScalySans"; + font-family : 'ScalySans'; font-weight : 800; letter-spacing : -0.02em; } @@ -411,7 +411,7 @@ body { counter-reset : phb-page-numbers; } .phb { .artist { position : absolute; - font-family : "WalterTurncoat"; + font-family : 'WalterTurncoat'; font-size : 0.27cm; color : @captionText; text-align : center; @@ -421,7 +421,7 @@ body { counter-reset : phb-page-numbers; } text-indent : unset; } h5 { - font-family : "WalterTurncoat"; + font-family : 'WalterTurncoat'; font-size : 1.3em; } a { diff --git a/themes/V3/5eDMG/style.less b/themes/V3/5eDMG/style.less index cbc3fa890..d79533c2c 100644 --- a/themes/V3/5eDMG/style.less +++ b/themes/V3/5eDMG/style.less @@ -7,7 +7,7 @@ } .page { - background-image : url("/assets/DMG_background.png"); + background-image : url('/assets/DMG_background.png'); background-size : cover; /* TABLES WITHIN NOTES */ @@ -23,7 +23,7 @@ &::after { height : 58px; - background-image : url("/assets/DMG_footerAccent.png"); + background-image : url('/assets/DMG_footerAccent.png'); } .footnote { bottom : 40px; } diff --git a/themes/V3/5ePHB/snippets.js b/themes/V3/5ePHB/snippets.js index dbcdc6f2a..27ea62bea 100644 --- a/themes/V3/5ePHB/snippets.js +++ b/themes/V3/5ePHB/snippets.js @@ -6,164 +6,12 @@ const MonsterBlockGen = require('./snippets/monsterblock.gen.js'); const scriptGen = require('./snippets/script.gen.js'); const ClassFeatureGen = require('./snippets/classfeature.gen.js'); const CoverPageGen = require('./snippets/coverpage.gen.js'); -const TableOfContentsGen = require('./snippets/tableOfContents.gen.js'); -const indexGen = require('./snippets/index.gen.js'); const QuoteGen = require('./snippets/quote.gen.js'); const dedent = require('dedent-tabs').default; module.exports = [ - - { - groupName : 'Text Editor', - icon : 'fas fa-pencil-alt', - view : 'text', - snippets : [ - { - name : 'Table of Contents', - icon : 'fas fa-book', - gen : TableOfContentsGen, - experimental : true, - subsnippets : [ - { - name : 'Generate Table of Contents', - icon : 'fas fa-book', - gen : TableOfContentsGen, - experimental : true - }, - { - name : 'Table of Contents Individual Inclusion', - icon : 'fas fa-book', - gen : dedent `\n{{tocInclude# CHANGE # to your header level - }}\n`, - subsnippets : [ - { - name : 'Individual Inclusion H1', - icon : 'fas fa-book', - gen : dedent `\n{{tocIncludeH1 \n - }}\n`, - }, - { - name : 'Individual Inclusion H2', - icon : 'fas fa-book', - gen : dedent `\n{{tocIncludeH2 \n - }}\n`, - }, - { - name : 'Individual Inclusion H3', - icon : 'fas fa-book', - gen : dedent `\n{{tocIncludeH3 \n - }}\n`, - }, - { - name : 'Individual Inclusion H4', - icon : 'fas fa-book', - gen : dedent `\n{{tocIncludeH4 \n - }}\n`, - }, - { - name : 'Individual Inclusion H5', - icon : 'fas fa-book', - gen : dedent `\n{{tocIncludeH5 \n - }}\n`, - }, - { - name : 'Individual Inclusion H6', - icon : 'fas fa-book', - gen : dedent `\n{{tocIncludeH6 \n - }}\n`, - } - ] - }, - { - name : 'Table of Contents Range Inclusion', - icon : 'fas fa-book', - gen : dedent `\n{{tocDepthH3 - }}\n`, - subsnippets : [ - { - name : 'Include in ToC up to H3', - icon : 'fas fa-dice-three', - gen : dedent `\n{{tocDepthH3 - }}\n`, - - }, - { - name : 'Include in ToC up to H4', - icon : 'fas fa-dice-four', - gen : dedent `\n{{tocDepthH4 - }}\n`, - }, - { - name : 'Include in ToC up to H5', - icon : 'fas fa-dice-five', - gen : dedent `\n{{tocDepthH5 - }}\n`, - }, - { - name : 'Include in ToC up to H6', - icon : 'fas fa-dice-six', - gen : dedent `\n{{tocDepthH6 - }}\n`, - }, - ] - }, - { - name : 'Table of Contents Individual Exclusion', - icon : 'fas fa-book', - gen : dedent `\n{{tocExcludeH1 \n - }}\n`, - subsnippets : [ - { - name : 'Individual Exclusion H1', - icon : 'fas fa-book', - gen : dedent `\n{{tocExcludeH1 \n - }}\n`, - }, - { - name : 'Individual Exclusion H2', - icon : 'fas fa-book', - gen : dedent `\n{{tocExcludeH2 \n - }}\n`, - }, - { - name : 'Individual Exclusion H3', - icon : 'fas fa-book', - gen : dedent `\n{{tocExcludeH3 \n - }}\n`, - }, - { - name : 'Individual Exclusion H4', - icon : 'fas fa-book', - gen : dedent `\n{{tocExcludeH4 \n - }}\n`, - }, - { - name : 'Individual Exclusion H5', - icon : 'fas fa-book', - gen : dedent `\n{{tocExcludeH5 \n - }}\n`, - }, - { - name : 'Individual Exclusion H6', - icon : 'fas fa-book', - gen : dedent `\n{{tocExcludeH6 \n - }}\n`, - }, - ] - }, - - ] - }, - { - name : 'Index', - icon : 'fas fa-bars', - gen : indexGen, - experimental : true - } - ] - }, { groupName : 'Style Editor', icon : 'fas fa-pencil-alt', @@ -192,70 +40,9 @@ module.exports = [ line-height: 1em; }\n\n` }, - { - name : 'Table of Contents Toggles', - icon : 'fas fa-book', - subsnippets : [ - { - name : 'Enable H1-H4 all pages', - icon : 'fas fa-dice-four', - gen : `.page {\n\th4 {--TOC: include; }\n}\n\n`, - }, - { - name : 'Enable H1-H5 all pages', - icon : 'fas fa-dice-five', - gen : `.page {\n\th4, h5 {--TOC: include; }\n}\n\n`, - }, - { - name : 'Enable H1-H6 all pages', - icon : 'fas fa-dice-six', - gen : `.page {\n\th4, h5, h6 {--TOC: include; }\n}\n\n`, - }, - ] - } ] }, - - /*********************** IMAGES *******************/ - { - groupName : 'Images', - icon : 'fas fa-images', - view : 'text', - snippets : [ - { - name : 'Image', - icon : 'fas fa-image', - gen : dedent` -  {width:325px,mix-blend-mode:multiply} - - {{artist,position:relative,top:-230px,left:10px,margin-bottom:-30px - ##### Cat Warrior - [Kyoung Hwan Kim](https://www.artstation.com/tahra) - }}` - }, - { - name : 'Background Image', - icon : 'fas fa-tree', - gen : dedent` -  {position:absolute,top:50px,right:30px,width:280px} - - {{artist,top:80px,right:30px - ##### Homebrew Mug - [naturalcrit](https://homebrew.naturalcrit.com) - }}` - }, - { - name : 'Watermark', - icon : 'fas fa-id-card', - gen : dedent` - {{watermark Homebrewery}}\n` - }, - ] - }, - - /************************* PHB ********************/ - { groupName : 'PHB', icon : 'fas fa-book', @@ -450,9 +237,6 @@ module.exports = [ ] }, - - - /**************** PAGE *************/ { diff --git a/themes/V3/5ePHB/style.less b/themes/V3/5ePHB/style.less index 93cfdf719..555866ba4 100644 --- a/themes/V3/5ePHB/style.less +++ b/themes/V3/5ePHB/style.less @@ -791,47 +791,8 @@ // * TABLE OF CONTENTS // *****************************/ -// Default Exclusions -// Anything not excluded is included, default Headers are H1, H2, and H3. -h4, -h5, -h6, -.page:has(.frontCover), -.page:has(.backCover), -.page:has(.insideCover), -.monster, -.noToC, -.toc { --TOC : exclude; } - - -// Brew level default inclusion changes. -// These add Headers 'back' to inclusion. - -//NOTE: DO NOT USE :HAS WITH .PAGES!!! EXTREMELY SLOW TO RENDER ON LARGE DOCS! - -// Block level inclusion changes -// These include either a single (include) or a range (depth) -.tocIncludeH1 h1 {--TOC : include; } -.tocIncludeH2 h2 {--TOC : include; } -.tocIncludeH3 h3 {--TOC : include; } -.tocIncludeH4 h4 {--TOC : include; } -.tocIncludeH5 h5 {--TOC : include; } -.tocIncludeH6 h6 {--TOC : include; } - -.tocDepthH2 :is(h1, h2) {--TOC : include; } -.tocDepthH3 :is(h1, h2, h3) {--TOC : include; } -.tocDepthH4 :is(h1, h2, h3, h4) {--TOC : include; } -.tocDepthH5 :is(h1, h2, h3, h4, h5) {--TOC : include; } -.tocDepthH6 :is(h1, h2, h3, h4, h5, h6) {--TOC : include; } - -// Block level exclusion changes -// These exclude a single block level -.tocExcludeH1 h1 {--TOC : exclude; } -.tocExcludeH2 h2 {--TOC : exclude; } -.tocExcludeH3 h3 {--TOC : exclude; } -.tocExcludeH4 h4 {--TOC : exclude; } -.tocExcludeH5 h5 {--TOC : exclude; } -.tocExcludeH6 h6 {--TOC : exclude; } +// Additional Default Exclusions +.monster { --TOC : exclude; } .page:has(.partCover) { --TOC : exclude; @@ -964,6 +925,7 @@ h6, } } } + // ***************************** // * INDEX // *****************************/ diff --git a/themes/V3/Blank/snippets.js b/themes/V3/Blank/snippets.js index 380daa37a..c5198fd87 100644 --- a/themes/V3/Blank/snippets.js +++ b/themes/V3/Blank/snippets.js @@ -4,6 +4,8 @@ const WatercolorGen = require('./snippets/watercolor.gen.js'); const ImageMaskGen = require('./snippets/imageMask.gen.js'); const FooterGen = require('./snippets/footer.gen.js'); const dedent = require('dedent-tabs').default; +const TableOfContentsGen = require('./snippets/tableOfContents.gen.js'); +const indexGen = require('./snippets/index.gen.js'); module.exports = [ @@ -36,6 +38,11 @@ module.exports = [ icon : 'fas fa-sort-numeric-down', gen : '{{pageNumber,auto}}\n' }, + { + name : 'Variable Auto Page Number', + icon : 'fas fa-sort-numeric-down', + gen : '{{pageNumber $[HB_pageNumber]}}\n' + }, { name : 'Skip Page Number Increment this Page', icon : 'fas fa-xmark', @@ -141,7 +148,53 @@ module.exports = [ [Homebrewery.Naturalcrit.com](https://homebrewery.naturalcrit.com) }}\n\n`; }, - } + }, + { + name : 'Table of Contents', + icon : 'fas fa-book', + gen : TableOfContentsGen, + experimental : true, + subsnippets : [ + { + name : 'Table of Contents', + icon : 'fas fa-book', + gen : TableOfContentsGen, + experimental : true + }, + { + name : 'Include in ToC up to H3', + icon : 'fas fa-dice-three', + gen : dedent `\n{{tocDepthH3 + }}\n`, + + }, + { + name : 'Include in ToC up to H4', + icon : 'fas fa-dice-four', + gen : dedent `\n{{tocDepthH4 + }}\n`, + }, + { + name : 'Include in ToC up to H5', + icon : 'fas fa-dice-five', + gen : dedent `\n{{tocDepthH5 + }}\n`, + }, + { + name : 'Include in ToC up to H6', + icon : 'fas fa-dice-six', + gen : dedent `\n{{tocDepthH6 + }}\n`, + } + ] + }, + { + name : 'Index', + icon : 'fas fa-bars', + gen : indexGen, + experimental : true + }, + ] }, { @@ -153,7 +206,7 @@ module.exports = [ name : 'Add Comment', icon : 'fas fa-code', gen : '/* This is a comment that will not be rendered into your brew. */' - }, + } ] }, diff --git a/themes/V3/5ePHB/snippets/index.gen.js b/themes/V3/Blank/snippets/index.gen.js similarity index 100% rename from themes/V3/5ePHB/snippets/index.gen.js rename to themes/V3/Blank/snippets/index.gen.js diff --git a/themes/V3/5ePHB/snippets/tableOfContents.gen.js b/themes/V3/Blank/snippets/tableOfContents.gen.js similarity index 100% rename from themes/V3/5ePHB/snippets/tableOfContents.gen.js rename to themes/V3/Blank/snippets/tableOfContents.gen.js diff --git a/themes/V3/Blank/style.less b/themes/V3/Blank/style.less index 9f2bd498e..01b85326f 100644 --- a/themes/V3/Blank/style.less +++ b/themes/V3/Blank/style.less @@ -5,6 +5,7 @@ @import (less) './themes/fonts/iconFonts/diceFont.less'; @import (less) './themes/fonts/iconFonts/gameIcons.less'; @import (less) './themes/fonts/iconFonts/fontAwesome.less'; +@import (less) './themes/fonts/Journal/fonts.less'; :root { //Colors @@ -496,3 +497,117 @@ body { counter-reset : page-numbers 0; } &:not(:has(.skipCounting)) { counter-increment : page-numbers; } } + +// ***************************** +// * INDEX +// *****************************/ +.page { + .index { + + ul ul { margin : 0; } + + ul { + padding-left : 0; + text-indent : 0; + list-style-type : none; + } + + & > ul > li { + padding-left : 1.5em; + text-indent : -1.5em; + } + } +} + +// ***************************** +// * TABLE OF CONTENTS +// *****************************/ + +// Default Exclusions +// Anything not exlcuded is included, default Headers are H1, H2, and H3. +h4, +h5, +h6, +.page:has(.frontCover), +.page:has(.backCover), +.page:has(.insideCover), +.noToC, +.toc { --TOC : exclude; } + +.tocDepthH2 :is(h1, h2) {--TOC : include; } +.tocDepthH3 :is(h1, h2, h3) {--TOC : include; } +.tocDepthH4 :is(h1, h2, h3, h4) {--TOC : include; } +.tocDepthH5 :is(h1, h2, h3, h4, h5) {--TOC : include; } +.tocDepthH6 :is(h1, h2, h3, h4, h5, h6) {--TOC : include; } + +.tocIncludeH1 h1 {--TOC : include; } +.tocIncludeH2 h2 {--TOC : include; } +.tocIncludeH3 h3 {--TOC : include; } +.tocIncludeH4 h4 {--TOC : include; } +.tocIncludeH5 h5 {--TOC : include; } +.tocIncludeH6 h6 {--TOC : include; } + +.page { + &:has(.toc)::after { display : none; } + .toc { + -webkit-column-break-inside : avoid; + page-break-inside : avoid; + break-inside : avoid; + h1 { + margin-bottom : 0.3cm; + text-align : center; + } + a { + display : inline; + color : inherit; + text-decoration : none; + &:hover { text-decoration : underline; } + } + h4 { + margin-top : 0.2cm; + line-height : 0.4cm; + & + ul li { line-height : 1.2em; } + } + ul { + padding-left : 0; + margin-top : 0; + list-style-type : none; + a { + display : flex; + flex-flow : row nowrap; + justify-content : space-between; + width : 100%; + } + li + li h3 { + margin-top : 0.26cm; + line-height : 1em; + } + h3 span:first-child::after { border : none; } + span { + display : contents; + &:first-child::after { + bottom : 0.08cm; + flex : 1; + margin-right : 0.16cm; + margin-bottom : 0.08cm; + margin-left : 0.08cm; /* Spacing before dot leaders */ + content : ''; + border-bottom : 0.05cm dotted #000000; + } + &:last-child { + display : inline-block; + align-self : flex-end; + font-size : 0.34cm; + font-weight : normal; + } + } + ul { /* List indent */ + margin-left : 1em; + } + } + &.wide { + .useColumns(0.96, @fillMode: balance); + } + } + .toc.wide li { break-inside : auto; } +} diff --git a/themes/V3/Journal/style.less b/themes/V3/Journal/style.less index bddefb749..74c976f47 100644 --- a/themes/V3/Journal/style.less +++ b/themes/V3/Journal/style.less @@ -12,7 +12,7 @@ } .useSansSerif() { - font-family : "PermanentMarker"; + font-family : 'PermanentMarker'; font-size : 0.3cm; line-height : 1.2em; color : var(--HB_Color_Text2); @@ -29,7 +29,7 @@ .page { padding : 2.1cm 1.9cm 1.7cm 3.8cm; - background-image : url("/assets/Journal/Background1.webp"); + background-image : url('/assets/Journal/Background1.webp'); background-repeat : no-repeat; background-size : 200% 100%; filter : drop-shadow(1px 4px 14px black); @@ -39,7 +39,7 @@ background-position : right; } &:nth-of-type(2) { - background-image : url("/assets/Journal/Background2.webp"); //Only first page should show ribbon + background-image : url('/assets/Journal/Background2.webp'); //Only first page should show ribbon } & .columnWrapper { @@ -51,7 +51,7 @@ // * BASE // *****************************/ .page { - font-family : "ReenieBeanie"; + font-family : 'ReenieBeanie'; font-size : 0.53cm; line-height : 0.8em; color : var(--HB_Color_Text); @@ -71,7 +71,7 @@ // * HEADERS // *****************************/ h1,h2,h3,h4,h5 { - font-family : "FrederickaTheGreat"; + font-family : 'FrederickaTheGreat'; font-weight : unset; color : var(--HB_Color_HeaderText); } @@ -89,7 +89,7 @@ margin-right : 0.1em; margin-bottom : -20px; margin-left : -40px; - font-family : "FrederickaTheGreat"; + font-family : 'FrederickaTheGreat'; font-size : 1.9em; line-height : 1em; } @@ -114,7 +114,7 @@ &:nth-of-type(3n) { transform : rotate(-1.5deg); } } h5 { - font-family : "PermanentMarker"; + font-family : 'PermanentMarker'; font-size : 0.4cm; font-weight : bold; line-height : 0.951em; //Font is misaligned. Shift up slightly @@ -146,14 +146,14 @@ .note { .useSansSerif(); padding : 0.2cm; - background-image : url("/assets/Journal/HashMarks.png"), + background-image : url('/assets/Journal/HashMarks.png'), linear-gradient(to bottom right, #FF000000, #A36A4E14, #41212100); background-repeat : no-repeat; background-position : center; background-size : 120% 120%; border-style : solid; border-width : 1px; - border-image-source : url("/assets/Journal/Border1.png"); + border-image-source : url('/assets/Journal/Border1.png'); border-image-slice : 18 18 18 18; border-image-width : 6px 6px 6px 6px; border-image-outset : 5px 5px 5px 5px; @@ -173,7 +173,7 @@ .descriptive { .useSansSerif(); padding : 0.2cm; - background-image : url("/assets/Journal/HashMarks.png"), + background-image : url('/assets/Journal/HashMarks.png'), linear-gradient(to bottom right, #FF000000, #41212114, #41212100); background-repeat : no-repeat; background-position : center; @@ -201,7 +201,7 @@ .artist { position : absolute; width : auto; - font-family : "WalterTurncoat"; + font-family : 'WalterTurncoat'; font-size : 0.27cm; color : var(--HB_Color_CaptionText); text-align : center; @@ -211,7 +211,7 @@ text-indent : unset; } h5 { - font-family : "WalterTurncoat"; + font-family : 'WalterTurncoat'; font-size : 1.3em; } a { @@ -309,7 +309,7 @@ .pageNumber { right : 3cm; bottom : 1.25cm; - font-family : "FrederickaTheGreat"; + font-family : 'FrederickaTheGreat'; color : var(--HB_Color_HeaderText); } .footnote { @@ -370,7 +370,7 @@ .page .spellList { .useSansSerif(); - font-family : "PermanentMarker"; + font-family : 'PermanentMarker'; column-count : 2; ul + h5 { margin-top : 15px; } ul { @@ -439,7 +439,7 @@ &:last-child { width : 1%; padding-left : 0.06cm; /* Spacing after dot leaders */ - font-family : "ReenieBeanie"; + font-family : 'ReenieBeanie'; font-size : 0.34cm; font-weight : normal; vertical-align : bottom; /* Keep page number bottom-aligned */