diff --git a/changelog.md b/changelog.md
index 1f7815d8d..3736ba9b0 100644
--- a/changelog.md
+++ b/changelog.md
@@ -85,6 +85,52 @@ pre {
## changelog
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
+### Wednesday 11/27/2024 - v3.16.1
+
+{{taskList
+##### 5e-Cleric
+
+* [x] Allow linking to specific HTML IDs via `#ID` at the end of the URL, e.g.: `homebrewery.naturalcrit.com/share/share/a6RCXwaDS58i#p4` to link to Page 4 directly
+
+Fixes issues [#2820](https://github.com/naturalcrit/homebrewery/issues/2820), [#3505](https://github.com/naturalcrit/homebrewery/issues/3505)
+
+* [x] Fix generation of link to certain Google Drive brews
+
+Fixes issue [#3776](https://github.com/naturalcrit/homebrewery/issues/3776)
+
+##### abquintic
+
+* [x] Fix blank pages appearing when pasting text
+
+Fixes issue [#3718](https://github.com/naturalcrit/homebrewery/issues/3718)
+
+##### Gazook89
+
+* [x] Add new brew viewing options to the view toolbar
+- {{fac,single-spread}} {{openSans **SINGLE PAGE**}}
+- {{fac,facing-spread}} {{openSans **TWO PAGE**}}
+- {{fac,flow-spread}} {{openSans **GRID**}}
+
+Fixes issue [#1379](https://github.com/naturalcrit/homebrewery/issues/1379)
+
+* [x] Updates to tag input boxes
+
+##### G-Ambatte
+
+* [x] Admin tools to fix certain corrupted documents
+
+Fixes issue [#3801](https://github.com/naturalcrit/homebrewery/issues/3801)
+
+* [x] Fix print window being affected by document zoom
+
+Fixes issue [#3744](https://github.com/naturalcrit/homebrewery/issues/3744)
+
+
+##### calculuschild, 5e-Cleric, G-Ambatte, Gazook89, abquintic
+
+* [x] Multiple code refactors, cleanups, and security fixes
+}}
+
### Saturday 10/12/2024 - v3.16.0
{{taskList
diff --git a/client/homebrew/brewRenderer/brewRenderer.jsx b/client/homebrew/brewRenderer/brewRenderer.jsx
index 36d263040..5eba86a6a 100644
--- a/client/homebrew/brewRenderer/brewRenderer.jsx
+++ b/client/homebrew/brewRenderer/brewRenderer.jsx
@@ -217,7 +217,7 @@ const BrewRenderer = (props)=>{
}
const renderedStyle = useMemo(()=>renderStyle(), [props.style, props.themeBundle]);
- renderedPages = useMemo(()=>renderPages(), [props.text]);
+ renderedPages = useMemo(()=>renderPages(), [displayOptions.pageShadows, props.text]);
return (
<>
diff --git a/client/homebrew/brewRenderer/toolBar/toolBar.less b/client/homebrew/brewRenderer/toolBar/toolBar.less
index c2f4ff148..c787a6f6b 100644
--- a/client/homebrew/brewRenderer/toolBar/toolBar.less
+++ b/client/homebrew/brewRenderer/toolBar/toolBar.less
@@ -115,10 +115,10 @@
color : #D3D3D3;
accent-color : #D3D3D3;
- &::-webkit-slider-thumb, &::-moz-slider-thumb {
+ &::-webkit-slider-thumb, &::-moz-range-thumb {
width : 5px;
height : 5px;
- cursor : pointer;
+ cursor : ew-resize;
outline : none;
}
diff --git a/client/homebrew/editor/snippetbar/snippetbar.jsx b/client/homebrew/editor/snippetbar/snippetbar.jsx
index 50237b914..f7d9508f8 100644
--- a/client/homebrew/editor/snippetbar/snippetbar.jsx
+++ b/client/homebrew/editor/snippetbar/snippetbar.jsx
@@ -207,19 +207,11 @@ const Snippetbar = createClass({
renderEditorButtons : function(){
if(!this.props.showEditButtons) return;
- const foldButtons = <>
-
-
+ return (
+
+ {this.props.view !== 'meta' && <>
@@ -235,14 +227,21 @@ const Snippetbar = createClass({
- {foldButtons}
-
+
+
+
+
+
+
{this.state.themeSelector && this.renderThemeSelector()}
-
-
+
>}
+
;
+
+ )
},
render : function(){
diff --git a/client/homebrew/editor/snippetbar/snippetbar.less b/client/homebrew/editor/snippetbar/snippetbar.less
index 319cd0cad..7d56dc718 100644
--- a/client/homebrew/editor/snippetbar/snippetbar.less
+++ b/client/homebrew/editor/snippetbar/snippetbar.less
@@ -22,7 +22,7 @@
justify-content : flex-end;
min-width : 225px;
- &:only-child { margin-left : auto; }
+ &:only-child { margin-left : auto;min-width:unset;}
>div {
display : flex;
@@ -38,6 +38,11 @@
line-height : @menuHeight;
text-align : center;
cursor : pointer;
+
+ &.editorTool:not(.active) {
+ cursor:not-allowed;
+ }
+
&:hover,&.selected { background-color : #999999; }
&.text {
.tooltipLeft('Brew Editor');
diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx
index 889fd58c5..46502af23 100644
--- a/client/homebrew/pages/editPage/editPage.jsx
+++ b/client/homebrew/pages/editPage/editPage.jsx
@@ -16,6 +16,7 @@ const PrintNavItem = require('../../navbar/print.navitem.jsx');
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
const Account = require('../../navbar/account.navitem.jsx');
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
+const VaultNavItem = require('../../navbar/vault.navitem.jsx');
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
const Editor = require('../../editor/editor.jsx');
@@ -417,6 +418,7 @@ const EditPage = createClass({
+
diff --git a/client/homebrew/pages/sharePage/sharePage.jsx b/client/homebrew/pages/sharePage/sharePage.jsx
index 2d96e1ce6..04f0e3a6b 100644
--- a/client/homebrew/pages/sharePage/sharePage.jsx
+++ b/client/homebrew/pages/sharePage/sharePage.jsx
@@ -8,6 +8,7 @@ const Navbar = require('../../navbar/navbar.jsx');
const MetadataNav = require('../../navbar/metadata.navitem.jsx');
const PrintNavItem = require('../../navbar/print.navitem.jsx');
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
+const VaultNavItem = require('../../navbar/vault.navitem.jsx');
const Account = require('../../navbar/account.navitem.jsx');
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
@@ -110,6 +111,7 @@ const SharePage = createClass({
>}
+
diff --git a/client/homebrew/pages/vaultPage/vaultPage.less b/client/homebrew/pages/vaultPage/vaultPage.less
index d29a5f4e1..a69bcb33b 100644
--- a/client/homebrew/pages/vaultPage/vaultPage.less
+++ b/client/homebrew/pages/vaultPage/vaultPage.less
@@ -92,49 +92,11 @@
&:invalid { background : rgb(255, 188, 181); }
- &[type='checkbox'] {
- position : relative;
- display : inline-block;
- width : 50px;
- height : 30px;
- font-family : 'WalterTurncoat';
- font-size : 20px;
- font-weight : 800;
- color : white;
- letter-spacing : 2px;
- appearance : none;
- background : red;
- isolation : isolate;
- border-radius : 5px;
-
- &::before,&::after {
- position : absolute;
- inset : 0;
- z-index : 5;
- padding-top : 2px;
- text-align : center;
- }
-
- &::before {
- display : block;
- content : 'No';
- }
-
- &::after {
- display : none;
- content : 'Yes';
- }
-
- &:checked {
- background : green;
-
- &::before { display : none; }
- &::after { display : block; }
- }
- }
+
}
#searchButton {
+ .colorButton(@green);
position : absolute;
right : 20px;
bottom : 0;
@@ -152,7 +114,6 @@
flex-direction : column;
height : 100%;
overflow-y : auto;
- font-family : 'BookInsanityRemake';
font-size : 0.34cm;
h3 {
@@ -356,6 +317,7 @@
}
button {
+ .colorButton(@green);
width : max-content;
&.previousPage { grid-area : previousPage; }
diff --git a/faq.md b/faq.md
index c7254952b..d3b6d24f6 100644
--- a/faq.md
+++ b/faq.md
@@ -69,7 +69,6 @@ pre {
You can check the site status here: [Everyone or Just Me](https://downforeveryoneorjustme.com/homebrewery.naturalcrit.com)
-
### Why am I getting an error when trying to save, and my account is linked to Google?
A sign-in with Google only lasts a year until the authentication expires. You must go [here](https://www.naturalcrit.com/login), click the *Log-out* button, and then sign back in using your Google account.
@@ -82,12 +81,17 @@ If you have linked your account with a Google account, you would change your pas
### Is there a way to restore a previous version of my brew?
-Currently, there is no way to do this through the site yourself. This would take too much of a toll on the amount of storage the homebrewery requires. However, we do have daily backups of our database that we keep for 8 days, and you can contact the moderators on [the subreddit](https://www.reddit.com/r/homebrewery) with your Homebrewery username, the name of the lost brew, and the last known time it was working properly. We can manually look through our backups and restore it if it exists.
+In your brew, there is an icon, :fas_clock_rotate_left:, that button opens up a menu with versions of your brew, stored in order from newer to older, up to a week old. Because of the amount of duplicates this function creates, this information is stored in **your browser**, so if you were to uninstall it or clear your cookies and site data, or change computers, the info will not be there.
+
+Also, we do have daily backups of our database that we keep for 8 days, and you can contact the moderators on [the subreddit](https://www.reddit.com/r/homebrewery) with your Homebrewery username, the name of the lost brew, and the last known time it was working properly. We can manually look through our backups and restore it if it exists.
+
### I worked on a brew for X hours, and suddenly all the text disappeared!
This usually happens if you accidentally drag-select all of your text and then start typing which overwrites the selection. Do not panic, and do not refresh the page or reload your brew quite yet as it is probably auto-saved in this state already. Simply press CTRL+Z as many times as needed to undo your last few changes and you will be back to where you were, then make sure to save your brew in the "good" state.
+You can also load a history version old enough to have all the text, using the :fas_clock_rotate_left: history versions button.
+
\column
### Why is only Chrome supported?
@@ -112,10 +116,7 @@ Once you have an image you would like to use, it is recommended to host it somew
\page
### A particular font does not work for my language, what do I do?
-The fonts used were originally created for use with the English language, though revisions since then have added more support for other languages. They are still not complete sets and may be missing a glyph/character you need. Unfortunately, the volunteer group as it stands at the time of this writing does not have a font guru, so it would be difficult to add more glyphs (especially complicated glyphs). Let us know which glyph is missing on the subreddit, but you may need to search [Google Fonts](https://fonts.google.com) for an alternative font if you need something fast.
-
-### Whenever I click on the "Get PDF" button, instead of getting a download, it opens Print Preview in another tab.
-Yes, this is by design. In the print preview, select "Save as PDF" as the Destination, and then click "Save". There will be a normal download dialog where you can save your brew as a PDF.
+The fonts used were originally created for use with the English language, though revisions since then have added more support for other languages. They are still not complete sets and may be missing a glyph/character you need. Unfortunately, the volunteer group as it stands at the time of this writing does not have a font guru, so it would be difficult to add more glyphs (especially complicated glyphs). Let us know which glyph is missing on the subreddit, but you may need to search [Google Fonts](https://fonts.google.com) for an alternative font if you need something fast.
### I have white borders on the bottom/sides of the print preview.
@@ -126,4 +127,8 @@ The Homebrewery defaults to creating US Letter page sizes. If you are printing
### Typing `#### Adhesion` in the text editor doesn't show the header at all in the completed page?
-Your ad-blocking software is mistakenly assuming your text to be an ad. Whitelist homebrewery.naturalcrit.com in your ad-blocking software.
+Your ad-blocking software is mistakenly assuming your text to be an ad. We recommend whitelisting homebrewery.naturalcrit.com in your ad-blocking software, as we have no ads.
+
+### My username appears as _hidden_ when checking my brews in the Vault, why is that?
+
+Your username is most likely your e-mail adress, and our code is picking that up and protecting your identity. This will remain as is, but you can ask for a name change by contacting the moderators on [the subreddit](https://www.reddit.com/r/homebrewery) with your Homebrewery username, and your desired new name. You will also be asked to provide details about some of your unpublished brews, to verify your identity. No information will be leaked or shared.
diff --git a/package-lock.json b/package-lock.json
index 6b30e4a73..257045fc7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,19 +1,19 @@
{
"name": "homebrewery",
- "version": "3.16.0",
+ "version": "3.16.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "homebrewery",
- "version": "3.16.0",
+ "version": "3.16.1",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@babel/core": "^7.26.0",
"@babel/plugin-transform-runtime": "^7.25.9",
"@babel/preset-env": "^7.26.0",
- "@babel/preset-react": "^7.25.9",
+ "@babel/preset-react": "^7.26.3",
"@googleapis/drive": "^8.14.0",
"body-parser": "^1.20.2",
"classnames": "^2.5.1",
@@ -21,9 +21,9 @@
"cookie-parser": "^1.4.7",
"create-react-class": "^15.7.0",
"dedent-tabs": "^0.10.3",
- "dompurify": "^3.2.1",
+ "dompurify": "^3.2.3",
"expr-eval": "^2.0.2",
- "express": "^4.21.1",
+ "express": "^4.21.2",
"express-async-handler": "^1.2.0",
"express-static-gzip": "2.2.0",
"fs-extra": "11.2.0",
@@ -39,8 +39,8 @@
"marked-smartypants-lite": "^1.0.2",
"markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.30.1",
- "mongoose": "^8.8.2",
- "nanoid": "5.0.8",
+ "mongoose": "^8.8.4",
+ "nanoid": "5.0.9",
"nconf": "^0.12.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@@ -53,15 +53,15 @@
"devDependencies": {
"@stylistic/stylelint-plugin": "^3.1.1",
"babel-plugin-transform-import-meta": "^2.2.1",
- "eslint": "^9.15.0",
+ "eslint": "^9.16.0",
"eslint-plugin-jest": "^28.9.0",
"eslint-plugin-react": "^7.37.2",
- "globals": "^15.12.0",
+ "globals": "^15.13.0",
"jest": "^29.7.0",
"jest-expect-message": "^1.1.3",
"jsdom-global": "^3.0.2",
"postcss-less": "^6.0.0",
- "stylelint": "^16.10.0",
+ "stylelint": "^16.11.0",
"stylelint-config-recess-order": "^5.1.1",
"stylelint-config-recommended": "^14.0.1",
"supertest": "^7.0.0"
@@ -1661,9 +1661,10 @@
}
},
"node_modules/@babel/preset-react": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.25.9.tgz",
- "integrity": "sha512-D3to0uSPiWE7rBrdIICCd0tJSIGpLaaGptna2+w7Pft5xMqLpA1sz99DK5TZ1TjGbdQ/VI1eCSZ06dv3lT4JOw==",
+ "version": "7.26.3",
+ "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.26.3.tgz",
+ "integrity": "sha512-Nl03d6T9ky516DGK2YMxrTqvnpUW63TnJMOMonj+Zae0JiPC5BC9xPMSL6L8fiSpA5vP88qfygavVQvnLp+6Cw==",
+ "license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9",
"@babel/helper-validator-option": "^7.25.9",
@@ -1749,9 +1750,9 @@
"license": "MIT"
},
"node_modules/@csstools/css-parser-algorithms": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.1.tgz",
- "integrity": "sha512-lSquqZCHxDfuTg/Sk2hiS0mcSFCEBuj49JfzPHJogDBT0mGCyY5A1AQzBWngitrp7i1/HAZpIgzF/VjhOEIJIg==",
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz",
+ "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==",
"dev": true,
"funding": [
{
@@ -1763,17 +1764,18 @@
"url": "https://opencollective.com/csstools"
}
],
+ "license": "MIT",
"engines": {
"node": ">=18"
},
"peerDependencies": {
- "@csstools/css-tokenizer": "^3.0.1"
+ "@csstools/css-tokenizer": "^3.0.3"
}
},
"node_modules/@csstools/css-tokenizer": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.1.tgz",
- "integrity": "sha512-UBqaiu7kU0lfvaP982/o3khfXccVlHPWp0/vwwiIgDF0GmqqqxoiXC/6FCjlS9u92f7CoEz6nXKQnrn1kIAkOw==",
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz",
+ "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==",
"dev": true,
"funding": [
{
@@ -1785,6 +1787,7 @@
"url": "https://opencollective.com/csstools"
}
],
+ "license": "MIT",
"engines": {
"node": ">=18"
}
@@ -1812,28 +1815,6 @@
"@csstools/css-tokenizer": "^3.0.1"
}
},
- "node_modules/@csstools/selector-specificity": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-4.0.0.tgz",
- "integrity": "sha512-189nelqtPd8++phaHNwYovKZI0FOzH1vQEE3QhHHkNIGrg5fSs9CbYP3RvfEH5geztnIA9Jwq91wyOIwAW5JIQ==",
- "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": "^6.1.0"
- }
- },
"node_modules/@dual-bundle/import-meta-resolve": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz",
@@ -1929,10 +1910,11 @@
}
},
"node_modules/@eslint/js": {
- "version": "9.15.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.15.0.tgz",
- "integrity": "sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==",
+ "version": "9.16.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.16.0.tgz",
+ "integrity": "sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
@@ -5047,12 +5029,13 @@
}
},
"node_modules/css-tree": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.0.0.tgz",
- "integrity": "sha512-o88DVQ6GzsABn1+6+zo2ct801dBO5OASVyxbbvA2W20ue2puSh/VOuqUj90eUeMSX/xqGqBmOKiRQN7tJOuBXw==",
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.0.1.tgz",
+ "integrity": "sha512-8Fxxv+tGhORlshCdCwnNJytvlvq46sOLSYEx2ZIGurahWvMucSRnyjPA3AmrMq4VPRYbHVpWj5VkiVasrM2H4Q==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "mdn-data": "2.10.0",
+ "mdn-data": "2.12.1",
"source-map-js": "^1.0.1"
},
"engines": {
@@ -5479,9 +5462,9 @@
}
},
"node_modules/dompurify": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.1.tgz",
- "integrity": "sha512-NBHEsc0/kzRYQd+AY6HR6B/IgsqzBABrqJbpCDQII/OK6h7B7LXzweZTDsqSW2LkTRpoxf18YUP+YjGySk6B3w==",
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.3.tgz",
+ "integrity": "sha512-U1U5Hzc2MO0oW3DF+G9qYN0aT7atAou4AgI0XjWz061nyBPbdxkfdhfy5uMgGn6+oLFCfn44ZGbdDqCzVmlOWA==",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
}
@@ -5788,17 +5771,18 @@
"license": "MIT"
},
"node_modules/eslint": {
- "version": "9.15.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.15.0.tgz",
- "integrity": "sha512-7CrWySmIibCgT1Os28lUU6upBshZ+GxybLOrmRzi08kS8MBuO8QA7pXEgYgY5W8vK3e74xv0lpjo9DbaGU9Rkw==",
+ "version": "9.16.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.16.0.tgz",
+ "integrity": "sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA==",
"dev": true,
+ "license": "MIT",
"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/eslintrc": "^3.2.0",
- "@eslint/js": "9.15.0",
+ "@eslint/js": "9.16.0",
"@eslint/plugin-kit": "^0.2.3",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
@@ -6269,9 +6253,10 @@
"license": "MIT"
},
"node_modules/express": {
- "version": "4.21.1",
- "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
- "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
+ "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
+ "license": "MIT",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
@@ -6292,7 +6277,7 @@
"methods": "~1.1.2",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
- "path-to-regexp": "0.1.10",
+ "path-to-regexp": "0.1.12",
"proxy-addr": "~2.0.7",
"qs": "6.13.0",
"range-parser": "~1.2.1",
@@ -6307,6 +6292,10 @@
},
"engines": {
"node": ">= 0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/express-async-handler": {
@@ -6975,10 +6964,11 @@
}
},
"node_modules/globals": {
- "version": "15.12.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-15.12.0.tgz",
- "integrity": "sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==",
+ "version": "15.13.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-15.13.0.tgz",
+ "integrity": "sha512-49TewVEz0UxZjr1WYYsWpPrhyC/B/pA8Bq0fUmet2n+eR7yn0IvNzNaoBwnK6mdkzcN+se7Ez9zUgULTz2QH4g==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=18"
},
@@ -10251,9 +10241,9 @@
}
},
"node_modules/known-css-properties": {
- "version": "0.34.0",
- "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.34.0.tgz",
- "integrity": "sha512-tBECoUqNFbyAY4RrbqsBQqDFpGXAEbdD5QKr8kACx3+rnArmuuR22nKQWKazvp07N9yjTyDZaw/20UIH8tL9DQ==",
+ "version": "0.35.0",
+ "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.35.0.tgz",
+ "integrity": "sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==",
"dev": true,
"license": "MIT"
},
@@ -10585,10 +10575,11 @@
}
},
"node_modules/mdn-data": {
- "version": "2.10.0",
- "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.10.0.tgz",
- "integrity": "sha512-qq7C3EtK3yJXMwz1zAab65pjl+UhohqMOctTgcqjLOWABqmwj+me02LSsCuEUxnst9X1lCBpoE0WArGKgdGDzw==",
- "dev": true
+ "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"
},
"node_modules/media-typer": {
"version": "0.3.0",
@@ -10891,9 +10882,10 @@
}
},
"node_modules/mongoose": {
- "version": "8.8.2",
- "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.8.2.tgz",
- "integrity": "sha512-jCTSqDANfRzk909v4YoZQi7jlGRB2MTvgG+spVBc/BA4tOs1oWJr//V6yYujqNq9UybpOtsSfBqxI0dSOEFJHQ==",
+ "version": "8.8.4",
+ "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.8.4.tgz",
+ "integrity": "sha512-yJbn695qCsqDO+xyPII29x2R7flzXhxCDv09mMZPSGllf0sm4jKw3E9s9uvQ9hjO6bL2xjU8KKowYqcY9eSTMQ==",
+ "license": "MIT",
"dependencies": {
"bson": "^6.7.0",
"kareem": "2.6.3",
@@ -11047,15 +11039,16 @@
"optional": true
},
"node_modules/nanoid": {
- "version": "5.0.8",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.8.tgz",
- "integrity": "sha512-TcJPw+9RV9dibz1hHUzlLVy8N4X9TnwirAjrU08Juo6BNKggzVfP2ZJ/3ZUSq15Xl5i85i+Z89XBO90pB2PghQ==",
+ "version": "5.0.9",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.9.tgz",
+ "integrity": "sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"bin": {
"nanoid": "bin/nanoid.js"
},
@@ -11754,9 +11747,10 @@
}
},
"node_modules/path-to-regexp": {
- "version": "0.1.10",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
- "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
+ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
+ "license": "MIT"
},
"node_modules/path-type": {
"version": "4.0.0",
@@ -11785,9 +11779,10 @@
}
},
"node_modules/picocolors": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
- "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw=="
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
},
"node_modules/picomatch": {
"version": "2.3.1",
@@ -11910,9 +11905,9 @@
}
},
"node_modules/postcss": {
- "version": "8.4.47",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
- "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
+ "version": "8.4.49",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
+ "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
"dev": true,
"funding": [
{
@@ -11928,9 +11923,10 @@
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"dependencies": {
"nanoid": "^3.3.7",
- "picocolors": "^1.1.0",
+ "picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
@@ -13677,9 +13673,9 @@
"license": "ISC"
},
"node_modules/stylelint": {
- "version": "16.10.0",
- "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.10.0.tgz",
- "integrity": "sha512-z/8X2rZ52dt2c0stVwI9QL2AFJhLhbPkyfpDFcizs200V/g7v+UYY6SNcB9hKOLcDDX/yGLDsY/pX08sLkz9xQ==",
+ "version": "16.11.0",
+ "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.11.0.tgz",
+ "integrity": "sha512-zrl4IrKmjJQ+h9FoMp69UMCq5SxeHk0URhxUBj4d3ISzo/DplOFBJZc7t7Dr6otB+1bfbbKNLOmCDpzKSlW+Nw==",
"dev": true,
"funding": [
{
@@ -13691,17 +13687,18 @@
"url": "https://github.com/sponsors/stylelint"
}
],
+ "license": "MIT",
"dependencies": {
- "@csstools/css-parser-algorithms": "^3.0.1",
- "@csstools/css-tokenizer": "^3.0.1",
- "@csstools/media-query-list-parser": "^3.0.1",
- "@csstools/selector-specificity": "^4.0.0",
+ "@csstools/css-parser-algorithms": "^3.0.4",
+ "@csstools/css-tokenizer": "^3.0.3",
+ "@csstools/media-query-list-parser": "^4.0.2",
+ "@csstools/selector-specificity": "^5.0.0",
"@dual-bundle/import-meta-resolve": "^4.1.0",
"balanced-match": "^2.0.0",
"colord": "^2.9.3",
"cosmiconfig": "^9.0.0",
"css-functions-list": "^3.2.3",
- "css-tree": "^3.0.0",
+ "css-tree": "^3.0.1",
"debug": "^4.3.7",
"fast-glob": "^3.3.2",
"fastest-levenshtein": "^1.0.16",
@@ -13713,16 +13710,16 @@
"ignore": "^6.0.2",
"imurmurhash": "^0.1.4",
"is-plain-object": "^5.0.0",
- "known-css-properties": "^0.34.0",
+ "known-css-properties": "^0.35.0",
"mathml-tag-names": "^2.1.3",
"meow": "^13.2.0",
"micromatch": "^4.0.8",
"normalize-path": "^3.0.0",
- "picocolors": "^1.0.1",
- "postcss": "^8.4.47",
+ "picocolors": "^1.1.1",
+ "postcss": "^8.4.49",
"postcss-resolve-nested-selector": "^0.1.6",
"postcss-safe-parser": "^7.0.1",
- "postcss-selector-parser": "^6.1.2",
+ "postcss-selector-parser": "^7.0.0",
"postcss-value-parser": "^4.2.0",
"resolve-from": "^5.0.0",
"string-width": "^4.2.3",
@@ -13787,6 +13784,53 @@
"stylelint": "^14.0.0 || ^15.0.0 || ^16.0.1"
}
},
+ "node_modules/stylelint/node_modules/@csstools/media-query-list-parser": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.2.tgz",
+ "integrity": "sha512-EUos465uvVvMJehckATTlNqGj4UJWkTmdWuDMjqvSUkjGpmOyFZBVwb4knxCm/k2GMTXY+c/5RkdndzFYWeX5A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.4",
+ "@csstools/css-tokenizer": "^3.0.3"
+ }
+ },
+ "node_modules/stylelint/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"
+ }
+ ],
+ "license": "MIT-0",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "postcss-selector-parser": "^7.0.0"
+ }
+ },
"node_modules/stylelint/node_modules/balanced-match": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz",
@@ -13828,6 +13872,20 @@
"node": ">= 4"
}
},
+ "node_modules/stylelint/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,
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/stylelint/node_modules/resolve-from": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
diff --git a/package.json b/package.json
index 6211da07f..9756534f8 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "homebrewery",
"description": "Create authentic looking D&D homebrews using only markdown",
- "version": "3.16.0",
+ "version": "3.16.1",
"type": "module",
"engines": {
"npm": "^10.2.x",
@@ -86,7 +86,7 @@
"@babel/core": "^7.26.0",
"@babel/plugin-transform-runtime": "^7.25.9",
"@babel/preset-env": "^7.26.0",
- "@babel/preset-react": "^7.25.9",
+ "@babel/preset-react": "^7.26.3",
"@googleapis/drive": "^8.14.0",
"body-parser": "^1.20.2",
"classnames": "^2.5.1",
@@ -94,9 +94,9 @@
"cookie-parser": "^1.4.7",
"create-react-class": "^15.7.0",
"dedent-tabs": "^0.10.3",
- "dompurify": "^3.2.1",
+ "dompurify": "^3.2.3",
"expr-eval": "^2.0.2",
- "express": "^4.21.1",
+ "express": "^4.21.2",
"express-async-handler": "^1.2.0",
"express-static-gzip": "2.2.0",
"fs-extra": "11.2.0",
@@ -112,8 +112,8 @@
"marked-smartypants-lite": "^1.0.2",
"markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.30.1",
- "mongoose": "^8.8.2",
- "nanoid": "5.0.8",
+ "mongoose": "^8.8.4",
+ "nanoid": "5.0.9",
"nconf": "^0.12.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@@ -126,15 +126,15 @@
"devDependencies": {
"@stylistic/stylelint-plugin": "^3.1.1",
"babel-plugin-transform-import-meta": "^2.2.1",
- "eslint": "^9.15.0",
+ "eslint": "^9.16.0",
"eslint-plugin-jest": "^28.9.0",
"eslint-plugin-react": "^7.37.2",
- "globals": "^15.12.0",
+ "globals": "^15.13.0",
"jest": "^29.7.0",
"jest-expect-message": "^1.1.3",
"jsdom-global": "^3.0.2",
"postcss-less": "^6.0.0",
- "stylelint": "^16.10.0",
+ "stylelint": "^16.11.0",
"stylelint-config-recess-order": "^5.1.1",
"stylelint-config-recommended": "^14.0.1",
"supertest": "^7.0.0"
diff --git a/server/googleActions.js b/server/googleActions.js
index ec541e5f5..2c2cbac73 100644
--- a/server/googleActions.js
+++ b/server/googleActions.js
@@ -241,8 +241,8 @@ const GoogleActions = {
return obj.data.id;
},
- getGoogleBrew : async (id, accessId, accessType)=>{
- const drive = googleDrive.drive({ version: 'v3', auth: defaultAuth });
+ getGoogleBrew : async (auth = defaultAuth, id, accessId, accessType)=>{
+ const drive = googleDrive.drive({ version: 'v3', auth: auth });
const obj = await drive.files.get({
fileId : id,
diff --git a/server/homebrew.api.js b/server/homebrew.api.js
index b8d4024ad..a75887742 100644
--- a/server/homebrew.api.js
+++ b/server/homebrew.api.js
@@ -87,76 +87,68 @@ const api = {
// Create middleware with the accessType passed in as part of the scope
return async (req, res, next)=>{
// Get relevant IDs for the brew
- const { id, googleId } = api.getId(req);
+ let { id, googleId } = api.getId(req);
const accessMap = {
edit : { editId: id },
share : { shareId: id },
- admin : {
- $or : [
- { editId: id },
- { shareId: id },
- ] }
+ admin : { $or : [{ editId: id }, { shareId: id }] }
};
// Try to find the document in the Homebrewery database -- if it doesn't exist, that's fine.
let stub = await HomebrewModel.get(accessMap[accessType])
.catch((err)=>{
- if(googleId) {
+ if(googleId)
console.warn(`Unable to find document stub for ${accessType}Id ${id}`);
- } else {
+ else
console.warn(err);
- }
});
stub = stub?.toObject();
+ googleId ??= stub?.googleId;
+
+ const isOwner = stub?.authors?.length === 0 || stub?.authors?.[0] === req.account?.username;
+ const isAuthor = stub?.authors?.includes(req.account?.username);
+ const isInvited = stub?.invitedAuthors?.includes(req.account?.username);
+
+ if(accessType === 'edit' && !(isOwner || isAuthor || isInvited)) {
+ const accessError = { name: 'Access Error', status: 401, authors: stub.authors, brewTitle: stub.title, shareId: stub.shareId };
+ if(req.account)
+ throw { ...accessError, message: 'User is not an Author', HBErrorCode: '03' };
+ else
+ throw { ...accessError, message: 'User is not logged in', HBErrorCode: '04' };
+ }
if(stub?.lock?.locked && accessType != 'edit') {
throw { HBErrorCode: '51', code: stub.lock.code, message: stub.lock.shareMessage, brewId: stub.shareId, brewTitle: stub.title };
}
// If there is a google id, try to find the google brew
- if(!stubOnly && (googleId || stub?.googleId)) {
- let googleError;
- const googleBrew = await GoogleActions.getGoogleBrew(googleId || stub?.googleId, id, accessType)
- .catch((err)=>{
- googleError = err;
+ if(!stubOnly && googleId) {
+ const oAuth2Client = isOwner? GoogleActions.authCheck(req.account, res) : undefined;
+
+ const googleBrew = await GoogleActions.getGoogleBrew(oAuth2Client, googleId, id, accessType)
+ .catch((googleError)=>{
+ const reason = googleError.errors?.[0].reason;
+ if(reason == 'notFound')
+ throw { ...googleError, HBErrorCode: '02', authors: stub?.authors, account: req.account?.username };
+ else
+ throw { ...googleError, HBErrorCode: '01' };
});
- // Throw any error caught while attempting to retrieve Google brew.
- if(googleError) {
- const reason = googleError.errors?.[0].reason;
- if(reason == 'notFound') {
- throw { ...googleError, HBErrorCode: '02', authors: stub?.authors, account: req.account?.username };
- } else {
- throw { ...googleError, HBErrorCode: '01' };
- }
- }
+
// Combine the Homebrewery stub with the google brew, or if the stub doesn't exist just use the google brew
stub = stub ? _.assign({ ...api.excludeStubProps(stub), stubbed: true }, api.excludeGoogleProps(googleBrew)) : googleBrew;
}
- const authorsExist = stub?.authors?.length > 0;
- const isAuthor = stub?.authors?.includes(req.account?.username);
- const isInvited = stub?.invitedAuthors?.includes(req.account?.username);
- if(accessType === 'edit' && (authorsExist && !(isAuthor || isInvited))) {
- const accessError = { name: 'Access Error', status: 401 };
- if(req.account){
- throw { ...accessError, message: 'User is not an Author', HBErrorCode: '03', authors: stub.authors, brewTitle: stub.title, shareId: stub.shareId };
- }
- throw { ...accessError, message: 'User is not logged in', HBErrorCode: '04', authors: stub.authors, brewTitle: stub.title, shareId: stub.shareId };
- }
// If after all of that we still don't have a brew, throw an exception
- if(!stub && !stubOnly) {
+ if(!stub)
throw { name: 'BrewLoad Error', message: 'Brew not found', status: 404, HBErrorCode: '05', accessType: accessType, brewId: id };
- }
// Clean up brew: fill in missing fields with defaults / fix old invalid values
- if(stub) {
- stub.tags = stub.tags || undefined; // Clear empty strings
- stub.renderer = stub.renderer || undefined; // Clear empty strings
- stub = _.defaults(stub, DEFAULT_BREW_LOAD); // Fill in blank fields
- }
+ stub.tags = stub.tags || undefined; // Clear empty strings
+ stub.renderer = stub.renderer || undefined; // Clear empty strings
+ stub = _.defaults(stub, DEFAULT_BREW_LOAD); // Fill in blank fields
- req.brew = stub ?? {};
+ req.brew = stub;
next();
};
},
diff --git a/server/homebrew.api.spec.js b/server/homebrew.api.spec.js
index 84ffc3052..8270b1568 100644
--- a/server/homebrew.api.spec.js
+++ b/server/homebrew.api.spec.js
@@ -1,5 +1,7 @@
/* eslint-disable max-lines */
+import { splitTextStyleAndMetadata } from '../shared/helpers.js';
+
describe('Tests for api', ()=>{
let api;
let google;
@@ -296,7 +298,7 @@ describe('Tests for api', ()=>{
expect(next).toHaveBeenCalled();
expect(api.getId).toHaveBeenCalledWith(req);
expect(model.get).toHaveBeenCalledWith({ shareId: '1' });
- expect(google.getGoogleBrew).toHaveBeenCalledWith('2', '1', 'share');
+ expect(google.getGoogleBrew).toHaveBeenCalledWith(undefined, '2', '1', 'share');
});
it('access is denied to a locked brew', async()=>{
@@ -968,4 +970,57 @@ brew`);
expect(res.send).toHaveBeenCalledWith('');
});
});
+ describe('Split Text, Style, and Metadata', ()=>{
+
+ it('basic splitting', async ()=>{
+ const testBrew = {
+ text : '```metadata\n' +
+ 'title: title\n' +
+ 'description: description\n' +
+ 'tags: [ \'tag a\' , \'tag b\' ]\n' +
+ 'systems: [ test system ]\n' +
+ 'renderer: legacy\n' +
+ 'theme: 5ePHB\n' +
+ 'lang: en\n' +
+ '\n' +
+ '```\n' +
+ '\n' +
+ '```css\n' +
+ 'style\n' +
+ 'style\n' +
+ 'style\n' +
+ '```\n' +
+ '\n' +
+ 'text\n'
+ };
+
+ splitTextStyleAndMetadata(testBrew);
+
+ // Metadata
+ expect(testBrew.title).toEqual('title');
+ expect(testBrew.description).toEqual('description');
+ expect(testBrew.tags).toEqual(['tag a', 'tag b']);
+ expect(testBrew.systems).toEqual(['test system']);
+ expect(testBrew.renderer).toEqual('legacy');
+ expect(testBrew.theme).toEqual('5ePHB');
+ expect(testBrew.lang).toEqual('en');
+ // Style
+ expect(testBrew.style).toEqual('style\nstyle\nstyle');
+ // Text
+ expect(testBrew.text).toEqual('text\n');
+ });
+
+ it('convert tags string to array', async ()=>{
+ const testBrew = {
+ text : '```metadata\n' +
+ 'tags: tag a\n' +
+ '```\n\n'
+ };
+
+ splitTextStyleAndMetadata(testBrew);
+
+ // Metadata
+ expect(testBrew.tags).toEqual(['tag a']);
+ });
+ });
});
diff --git a/shared/helpers.js b/shared/helpers.js
index d60da885d..b2190cdcd 100644
--- a/shared/helpers.js
+++ b/shared/helpers.js
@@ -21,6 +21,9 @@ const splitTextStyleAndMetadata = (brew)=>{
brew.snippets = brew.text.slice(11, index - 1);
brew.text = brew.text.slice(index + 5);
}
+
+ // Handle old brews that still have empty strings in the tags metadata
+ if(typeof brew.tags === 'string') brew.tags = brew.tags ? [brew.tags] : [];
};
const printCurrentBrew = ()=>{
diff --git a/shared/naturalcrit/codeEditor/autocompleteEmoji.js b/shared/naturalcrit/codeEditor/autocompleteEmoji.js
index fae373115..c7efa172b 100644
--- a/shared/naturalcrit/codeEditor/autocompleteEmoji.js
+++ b/shared/naturalcrit/codeEditor/autocompleteEmoji.js
@@ -1,7 +1,7 @@
-const diceFont = require('../../../themes/fonts/iconFonts/diceFont.js');
-const elderberryInn = require('../../../themes/fonts/iconFonts/elderberryInn.js');
-const fontAwesome = require('../../../themes/fonts/iconFonts/fontAwesome.js');
-const gameIcons = require('../../../themes/fonts/iconFonts/gameIcons.js');
+import diceFont from '../../../themes/fonts/iconFonts/diceFont.js';
+import elderberryInn from '../../../themes/fonts/iconFonts/elderberryInn.js';
+import fontAwesome from '../../../themes/fonts/iconFonts/fontAwesome.js';
+import gameIcons from '../../../themes/fonts/iconFonts/gameIcons.js';
const emojis = {
...diceFont,
diff --git a/shared/naturalcrit/codeEditor/codeEditor.less b/shared/naturalcrit/codeEditor/codeEditor.less
index cb73b0a88..84a5c63f1 100644
--- a/shared/naturalcrit/codeEditor/codeEditor.less
+++ b/shared/naturalcrit/codeEditor/codeEditor.less
@@ -11,49 +11,54 @@
@import (less) './themes/fonts/iconFonts/fontAwesome.less';
@keyframes sourceMoveAnimation {
- 50% {background-color: red; color: white;}
- 100% {background-color: unset; color: unset;}
+ 50% { color : white;background-color : red;}
+ 100% { color : unset;background-color : unset;}
}
-.codeEditor{
- @media screen and (pointer : coarse) {
- font-size : 16px;
- }
- .CodeMirror-foldmarker {
- font-family: inherit;
- text-shadow: none;
- font-weight: 600;
- color: grey;
- }
+.codeEditor {
+ @media screen and (pointer : coarse) {
+ font-size : 16px;
+ }
+ .CodeMirror-foldmarker {
+ font-family : inherit;
+ font-weight : 600;
+ color : grey;
+ text-shadow : none;
+ }
- .sourceMoveFlash .CodeMirror-line{
- animation-name: sourceMoveAnimation;
- animation-duration: 0.4s;
- }
+ .CodeMirror-foldgutter {
+ cursor : pointer;
+ border-left : 1px solid #EEEEEE;
+ transition : background 0.1s;
+ &:hover { background : #DDDDDD; }
+ }
- .CodeMirror-vscrollbar {
- &::-webkit-scrollbar {
- width: 20px;
- }
- &::-webkit-scrollbar-thumb {
- width: 20px;
- background: linear-gradient(90deg, #858585 15px, #808080 15px);
- }
- }
+ .sourceMoveFlash .CodeMirror-line {
+ animation-name : sourceMoveAnimation;
+ animation-duration : 0.4s;
+ }
+
+ .CodeMirror-vscrollbar {
+ &::-webkit-scrollbar { width : 20px; }
+ &::-webkit-scrollbar-thumb {
+ width : 20px;
+ background : linear-gradient(90deg, #858585 15px, #808080 15px);
+ }
+ }
- //.cm-tab {
- // background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAQAAACOs/baAAAARUlEQVR4nGJgIAG8JkXxUAcCtDWemcGR1lY4MvgzCEKY7jSBjgxBDAG09UEQzAe0AMwMHrSOAwEGRtpaMIwAAAAA//8DAG4ID9EKs6YqAAAAAElFTkSuQmCC) no-repeat right;
- //}
+ //.cm-tab {
+ // background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAQAAACOs/baAAAARUlEQVR4nGJgIAG8JkXxUAcCtDWemcGR1lY4MvgzCEKY7jSBjgxBDAG09UEQzAe0AMwMHrSOAwEGRtpaMIwAAAAA//8DAG4ID9EKs6YqAAAAAElFTkSuQmCC) no-repeat right;
+ //}
- //.cm-trailingspace {
- // .cm-space {
- // background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAQAgMAAABW5NbuAAAACVBMVEVHcEwAAAAAAAAWawmTAAAAA3RSTlMAPBJ6PMxpAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAFUlEQVQI12NgwACcCQysASAEZGAAACMuAX06aCQUAAAAAElFTkSuQmCC) no-repeat right;
- // }
- //}
+ //.cm-trailingspace {
+ // .cm-space {
+ // background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAQAgMAAABW5NbuAAAACVBMVEVHcEwAAAAAAAAWawmTAAAAA3RSTlMAPBJ6PMxpAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAFUlEQVQI12NgwACcCQysASAEZGAAACMuAX06aCQUAAAAAElFTkSuQmCC) no-repeat right;
+ // }
+ //}
}
.emojiPreview {
- font-size: 1.5em;
- line-height: 1.2em;
+ font-size : 1.5em;
+ line-height : 1.2em;
}
\ No newline at end of file
diff --git a/shared/naturalcrit/styles/core.less b/shared/naturalcrit/styles/core.less
index 21a2687bc..3248269c5 100644
--- a/shared/naturalcrit/styles/core.less
+++ b/shared/naturalcrit/styles/core.less
@@ -43,5 +43,6 @@ html,body, #reactRoot{
}
&:disabled{
background-color : @silver !important;
+ cursor:not-allowed;
}
}