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 066711c41..d252c7120 100644
--- a/client/homebrew/editor/snippetbar/snippetbar.jsx
+++ b/client/homebrew/editor/snippetbar/snippetbar.jsx
@@ -217,19 +217,11 @@ const Snippetbar = createClass({
renderEditorButtons : function(){
if(!this.props.showEditButtons) return;
- const foldButtons = <>
-
-
+ return (
+
+ {this.props.view !== 'meta' && <>
@@ -245,14 +237,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 a7202c428..33242174b 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/package-lock.json b/package-lock.json
index 226333ec6..06004a985 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,7 +13,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",
@@ -23,7 +23,7 @@
"dedent-tabs": "^0.10.3",
"dompurify": "^3.2.2",
"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,7 +39,7 @@
"marked-smartypants-lite": "^1.0.2",
"markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.30.1",
- "mongoose": "^8.8.3",
+ "mongoose": "^8.8.4",
"nanoid": "5.0.9",
"nconf": "^0.12.1",
"react": "^18.3.1",
@@ -1660,9 +1660,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",
@@ -6269,9 +6270,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 +6294,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 +6309,10 @@
},
"engines": {
"node": ">= 0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/express-async-handler": {
@@ -10902,9 +10908,9 @@
}
},
"node_modules/mongoose": {
- "version": "8.8.3",
- "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.8.3.tgz",
- "integrity": "sha512-/I4n/DcXqXyIiLRfAmUIiTjj3vXfeISke8dt4U4Y8Wfm074Wa6sXnQrXN49NFOFf2mM1kUdOXryoBvkuCnr+Qw==",
+ "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",
@@ -11772,9 +11778,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",
diff --git a/package.json b/package.json
index 84947d40c..ff89c83af 100644
--- a/package.json
+++ b/package.json
@@ -85,7 +85,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",
@@ -95,7 +95,7 @@
"dedent-tabs": "^0.10.3",
"dompurify": "^3.2.2",
"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",
@@ -111,7 +111,7 @@
"marked-smartypants-lite": "^1.0.2",
"markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.30.1",
- "mongoose": "^8.8.3",
+ "mongoose": "^8.8.4",
"nanoid": "5.0.9",
"nconf": "^0.12.1",
"react": "^18.3.1",
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 b0532e392..b3d1930f7 100644
--- a/server/homebrew.api.js
+++ b/server/homebrew.api.js
@@ -89,76 +89,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 0af9863aa..320c17579 100644
--- a/server/homebrew.api.spec.js
+++ b/server/homebrew.api.spec.js
@@ -298,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()=>{
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;
}
}