diff --git a/changelog.md b/changelog.md
index 9d1ddf32d..1f7815d8d 100644
--- a/changelog.md
+++ b/changelog.md
@@ -81,9 +81,85 @@ pre {
}
```
+
## changelog
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
+### Saturday 10/12/2024 - v3.16.0
+
+{{taskList
+##### 5e-Cleric
+
+* [x] Added a new API endpoint `/metadata/:shareId` to fetch metadata about individual brews
+
+Fixes issue [#2638](https://github.com/naturalcrit/homebrewery/issues/2638)
+
+* [x] Added A3, A5, and Card page size snippets under {{openSans **:fas_paintbrush: STYLE TAB :fas_arrow_right: :fas_print: PRINT**}}
+
+* [x] Adjust navbar styling for very long titles
+
+Fixes issue [#2071](https://github.com/naturalcrit/homebrewery/issues/2071)
+
+* [x] Added some sorting options to the {{openSans **VAULT** {{fas,fa-dungeon}}}} page
+
+* [x] Fix `language` property not working in share page
+
+Fixes issue [#3776](https://github.com/naturalcrit/homebrewery/issues/3776)
+
+##### abquintic
+
+* [x] New {{openSans **:fas_pencil: TEXT EDITOR :fas_arrow_right: :fas_bookmark: PAGE NUMBER :fas_arrow_right:**}}
+{{openSans **:fas_xmark: SKIP PAGE NUMBER**}} and {{openSans **:fas_arrow_rotate_left: RESTART PAGE NUMBER**}} snippets for more control over automatic page numbering.
+
+Fixes issue [#513](https://github.com/naturalcrit/homebrewery/issues/513)
+
+* [x] New Table of Contents control options via {{openSans **:fas_pencil: TEXT EDITOR :fas_arrow_right: :fas_book: TABLE OF CONTENTS**}} submenus. By default, H1-H3 is included in the ToC generation, but the new options allow marking `{{blocks}}` to include or exclude specific or ranges of contained headers. Also, a global option to increase the default range of H1-H3 to H1-H4/5/6. After applying these markers, you must regenerate the Table of Contents to see the changes.
+
+* [x] Added a ":fas_lock: SYNC VIEWS" button onto the divider bar. When locked, scrolling on either panel will sync the other panel to the same page.
+
+Fixes issue [#241](https://github.com/naturalcrit/homebrewery/issues/241)
+
+##### Gazook89
+
+* [x] Added a :fas_glasses: HIDE button to the page navigation bar
+
+##### G-Ambatte
+
+* [x] Automatic local backups of your files, in case of accidental data loss. Stores up to 5 snapshots of each brew edited in your browser, incrementing from a few minutes old to a maximum of several days. Restore a backup by clicking an entry in the new {{openSans **:fas_clock_rotate_left: HISTORY**}} button in the snippet bar.
+
+Fixes issue [#3070](https://github.com/naturalcrit/homebrewery/issues/3070)
+
+* [x] Fix issue with legacy brews breaking on Share page
+
+Fixes issue [#3764](https://github.com/naturalcrit/homebrewery/issues/3764)
+
+* [x] Fix print size when printing a zoomed document
+
+Fixes issue [#3744](https://github.com/naturalcrit/homebrewery/issues/3744)
+
+##### All
+
+* [x] Background code cleanup, security fixes, dev tool improvements, dependency updates, prep for upcoming features, etc.
+}}
+
+### Wednesday 9/25/2024 - v3.15.1
+
+{{taskList
+##### calculuschild
+
+* [x] Background fixes to handle Google Drive issues
+
+* [x] Remove duplicate error logging
+
+##### calculuschild, 5e-Cleric
+
+* [x] Fix links in {{openSans **RECENT BREWS :fas_clock_rotate_left:**}} and user {{openSans **BREWS :fas_beer_mug_empty:**}} pointing to trashed Google Drive files after transferring from Google to Homebrewery storage
+
+Fixes issue [#3776](https://github.com/naturalcrit/homebrewery/issues/3776)
+}}
+
+\page
+
### Wednesday 9/04/2024 - v3.15.0
{{taskList
diff --git a/client/homebrew/brewRenderer/brewRenderer.jsx b/client/homebrew/brewRenderer/brewRenderer.jsx
index 17a7d9345..dba99638b 100644
--- a/client/homebrew/brewRenderer/brewRenderer.jsx
+++ b/client/homebrew/brewRenderer/brewRenderer.jsx
@@ -1,7 +1,7 @@
/*eslint max-lines: ["warn", {"max": 300, "skipBlankLines": true, "skipComments": true}]*/
require('./brewRenderer.less');
const React = require('react');
-const { useState, useRef, useCallback } = React;
+const { useState, useRef, useCallback, useMemo } = React;
const _ = require('lodash');
const MarkdownLegacy = require('naturalcrit/markdownLegacy.js');
@@ -44,7 +44,7 @@ const BrewPage = (props)=>{
//v=====--------------------< Brew Renderer Component >-------------------=====v//
-const renderedPages = [];
+let renderedPages = [];
let rawPages = [];
const BrewRenderer = (props)=>{
@@ -179,6 +179,9 @@ const BrewRenderer = (props)=>{
styleObject.backgroundImage = `url("data:image/svg+xml;utf8,${global.config.deployment} ")`;
}
+ const renderedStyle = useMemo(()=> renderStyle(), [props.style?.length, props.themeBundle]);
+ renderedPages = useMemo(() => renderPages(), [props.text?.length]);
+
return (
<>
{/*render dummy page while iFrame is mounting.*/}
@@ -214,9 +217,9 @@ const BrewRenderer = (props)=>{
{state.isMounted
&&
<>
- {renderStyle()}
+ {renderedStyle}
- {renderPages()}
+ {renderedPages}
>
}
diff --git a/client/homebrew/brewRenderer/notificationPopup/notificationPopup.jsx b/client/homebrew/brewRenderer/notificationPopup/notificationPopup.jsx
index cba6a629c..f4e0b1c95 100644
--- a/client/homebrew/brewRenderer/notificationPopup/notificationPopup.jsx
+++ b/client/homebrew/brewRenderer/notificationPopup/notificationPopup.jsx
@@ -4,7 +4,7 @@ const _ = require('lodash');
import Dialog from '../../../components/dialog.jsx';
-const DISMISS_KEY = 'dismiss_notification04-09-24';
+const DISMISS_KEY = 'dismiss_notification01-10-24';
const DISMISS_BUTTON = ;
const NotificationPopup = ()=>{
diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx
index 13c22fb81..9fef72cbb 100644
--- a/client/homebrew/editor/editor.jsx
+++ b/client/homebrew/editor/editor.jsx
@@ -314,7 +314,7 @@ const Editor = createClass({
},
brewJump : function(targetPage=this.props.currentEditorCursorPageNum, smooth=true){
- if(!window || isJumping)
+ if(!window || !this.isText() || isJumping)
return;
// Get current brewRenderer scroll position and calculate target position
@@ -355,7 +355,7 @@ const Editor = createClass({
},
sourceJump : function(targetPage=this.props.currentBrewRendererPageNum, smooth=true){
- if(!this.isText || isJumping)
+ if(!this.isText() || isJumping)
return;
const textSplit = this.props.renderer == 'V3' ? /^\\page$/gm : /\\page/;
diff --git a/package-lock.json b/package-lock.json
index 02db16114..e3fb0ad29 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "homebrewery",
- "version": "3.15.0",
+ "version": "3.16.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "homebrewery",
- "version": "3.15.0",
+ "version": "3.16.0",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -45,7 +45,7 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-frame-component": "^4.1.3",
- "react-router-dom": "6.26.2",
+ "react-router-dom": "6.27.0",
"sanitize-filename": "1.6.3",
"superagent": "^10.1.0",
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
@@ -59,7 +59,7 @@
"jest": "^29.7.0",
"jest-expect-message": "^1.1.3",
"postcss-less": "^6.0.0",
- "stylelint": "^16.9.0",
+ "stylelint": "^16.10.0",
"stylelint-config-recess-order": "^5.1.1",
"stylelint-config-recommended": "^14.0.1",
"supertest": "^7.0.0"
@@ -2900,9 +2900,9 @@
}
},
"node_modules/@remix-run/router": {
- "version": "1.19.2",
- "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.2.tgz",
- "integrity": "sha512-baiMx18+IMuD1yyvOGaHM9QrVUPGGG0jC+z+IPHnRJWUAUvaKuWKyE8gjDj2rzv3sz9zOGoRSPgeBVHRhZnBlA==",
+ "version": "1.20.0",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.20.0.tgz",
+ "integrity": "sha512-mUnk8rPJBI9loFDZ+YzPGdeniYK+FTmRD1TMCz7ev2SNIozyKKpnGgsxO34u6Z4z/t0ITuu7voi/AshfsGsgFg==",
"engines": {
"node": ">=14.0.0"
}
@@ -5037,23 +5037,21 @@
}
},
"node_modules/css-functions-list": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.2.tgz",
- "integrity": "sha512-c+N0v6wbKVxTu5gOBBFkr9BEdBWaqqjQeiJ8QvSRIJOf+UxlJh930m8e6/WNeODIK0mYLFkoONrnj16i2EcvfQ==",
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.3.tgz",
+ "integrity": "sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">=12 || >=16"
}
},
"node_modules/css-tree": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
- "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.0.0.tgz",
+ "integrity": "sha512-o88DVQ6GzsABn1+6+zo2ct801dBO5OASVyxbbvA2W20ue2puSh/VOuqUj90eUeMSX/xqGqBmOKiRQN7tJOuBXw==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "mdn-data": "2.0.30",
+ "mdn-data": "2.10.0",
"source-map-js": "^1.0.1"
},
"engines": {
@@ -5134,12 +5132,11 @@
}
},
"node_modules/debug": {
- "version": "4.3.6",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
- "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
- "license": "MIT",
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"dependencies": {
- "ms": "2.1.2"
+ "ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
@@ -10358,11 +10355,10 @@
}
},
"node_modules/mdn-data": {
- "version": "2.0.30",
- "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
- "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
- "dev": true,
- "license": "CC0-1.0"
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.10.0.tgz",
+ "integrity": "sha512-qq7C3EtK3yJXMwz1zAab65pjl+UhohqMOctTgcqjLOWABqmwj+me02LSsCuEUxnst9X1lCBpoE0WArGKgdGDzw==",
+ "dev": true
},
"node_modules/media-typer": {
"version": "0.3.0",
@@ -10787,12 +10783,6 @@
}
}
},
- "node_modules/mongoose/node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "license": "MIT"
- },
"node_modules/mpath": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
@@ -10815,10 +10805,9 @@
}
},
"node_modules/ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "license": "MIT"
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/nan": {
"version": "2.20.0",
@@ -11539,10 +11528,9 @@
}
},
"node_modules/picocolors": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
- "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
- "license": "ISC"
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
+ "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw=="
},
"node_modules/picomatch": {
"version": "2.3.1",
@@ -11665,9 +11653,9 @@
}
},
"node_modules/postcss": {
- "version": "8.4.41",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz",
- "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==",
+ "version": "8.4.47",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
+ "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
"dev": true,
"funding": [
{
@@ -11685,8 +11673,8 @@
],
"dependencies": {
"nanoid": "^3.3.7",
- "picocolors": "^1.0.1",
- "source-map-js": "^1.2.0"
+ "picocolors": "^1.1.0",
+ "source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
@@ -11712,9 +11700,9 @@
"dev": true
},
"node_modules/postcss-safe-parser": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.0.tgz",
- "integrity": "sha512-ovehqRNVCpuFzbXoTb4qLtyzK3xn3t/CUBxOs8LsnQjQrShaB4lKiHoVqY8ANaC0hBMHq5QVWk77rwGklFUDrg==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz",
+ "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==",
"dev": true,
"funding": [
{
@@ -11730,7 +11718,6 @@
"url": "https://github.com/sponsors/ai"
}
],
- "license": "MIT",
"engines": {
"node": ">=18.0"
},
@@ -12073,11 +12060,11 @@
"license": "MIT"
},
"node_modules/react-router": {
- "version": "6.26.2",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.2.tgz",
- "integrity": "sha512-tvN1iuT03kHgOFnLPfLJ8V95eijteveqdOSk+srqfePtQvqCExB8eHOYnlilbOcyJyKnYkr1vJvf7YqotAJu1A==",
+ "version": "6.27.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.27.0.tgz",
+ "integrity": "sha512-YA+HGZXz4jaAkVoYBE98VQl+nVzI+cVI2Oj/06F5ZM+0u3TgedN9Y9kmMRo2mnkSK2nCpNQn0DVob4HCsY/WLw==",
"dependencies": {
- "@remix-run/router": "1.19.2"
+ "@remix-run/router": "1.20.0"
},
"engines": {
"node": ">=14.0.0"
@@ -12087,12 +12074,12 @@
}
},
"node_modules/react-router-dom": {
- "version": "6.26.2",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.2.tgz",
- "integrity": "sha512-z7YkaEW0Dy35T3/QKPYB1LjMK2R1fxnHO8kWpUMTBdfVzZrWOiY9a7CtN8HqdWtDUWd5FY6Dl8HFsqVwH4uOtQ==",
+ "version": "6.27.0",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.27.0.tgz",
+ "integrity": "sha512-+bvtFWMC0DgAFrfKXKG9Fc+BcXWRUO1aJIihbB79xaeq0v5UzfvnM5houGUm1Y461WVRcgAQ+Clh5rdb1eCx4g==",
"dependencies": {
- "@remix-run/router": "1.19.2",
- "react-router": "6.26.2"
+ "@remix-run/router": "1.20.0",
+ "react-router": "6.27.0"
},
"engines": {
"node": ">=14.0.0"
@@ -12624,11 +12611,6 @@
"node": ">= 0.8"
}
},
- "node_modules/send/node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
- },
"node_modules/serve-static": {
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
@@ -13032,11 +13014,10 @@
}
},
"node_modules/source-map-js": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
- "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
- "license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
@@ -13418,9 +13399,9 @@
"license": "ISC"
},
"node_modules/stylelint": {
- "version": "16.9.0",
- "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.9.0.tgz",
- "integrity": "sha512-31Nm3WjxGOBGpQqF43o3wO9L5AC36TPIe6030Lnm13H3vDMTcS21DrLh69bMX+DBilKqMMVLian4iG6ybBoNRQ==",
+ "version": "16.10.0",
+ "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.10.0.tgz",
+ "integrity": "sha512-z/8X2rZ52dt2c0stVwI9QL2AFJhLhbPkyfpDFcizs200V/g7v+UYY6SNcB9hKOLcDDX/yGLDsY/pX08sLkz9xQ==",
"dev": true,
"funding": [
{
@@ -13441,17 +13422,17 @@
"balanced-match": "^2.0.0",
"colord": "^2.9.3",
"cosmiconfig": "^9.0.0",
- "css-functions-list": "^3.2.2",
- "css-tree": "^2.3.1",
- "debug": "^4.3.6",
+ "css-functions-list": "^3.2.3",
+ "css-tree": "^3.0.0",
+ "debug": "^4.3.7",
"fast-glob": "^3.3.2",
"fastest-levenshtein": "^1.0.16",
- "file-entry-cache": "^9.0.0",
+ "file-entry-cache": "^9.1.0",
"global-modules": "^2.0.0",
"globby": "^11.1.0",
"globjoin": "^0.1.4",
"html-tags": "^3.3.1",
- "ignore": "^5.3.2",
+ "ignore": "^6.0.2",
"imurmurhash": "^0.1.4",
"is-plain-object": "^5.0.0",
"known-css-properties": "^0.34.0",
@@ -13460,14 +13441,13 @@
"micromatch": "^4.0.8",
"normalize-path": "^3.0.0",
"picocolors": "^1.0.1",
- "postcss": "^8.4.41",
+ "postcss": "^8.4.47",
"postcss-resolve-nested-selector": "^0.1.6",
- "postcss-safe-parser": "^7.0.0",
+ "postcss-safe-parser": "^7.0.1",
"postcss-selector-parser": "^6.1.2",
"postcss-value-parser": "^4.2.0",
"resolve-from": "^5.0.0",
"string-width": "^4.2.3",
- "strip-ansi": "^7.1.0",
"supports-hyperlinks": "^3.1.0",
"svg-tags": "^1.0.0",
"table": "^6.8.2",
@@ -13529,19 +13509,6 @@
"stylelint": "^14.0.0 || ^15.0.0 || ^16.0.1"
}
},
- "node_modules/stylelint/node_modules/ansi-regex": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
- "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-regex?sponsor=1"
- }
- },
"node_modules/stylelint/node_modules/balanced-match": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz",
@@ -13550,11 +13517,10 @@
"license": "MIT"
},
"node_modules/stylelint/node_modules/file-entry-cache": {
- "version": "9.0.0",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-9.0.0.tgz",
- "integrity": "sha512-6MgEugi8p2tiUhqO7GnPsmbCCzj0YRCwwaTbpGRyKZesjRSzkqkAE9fPp7V2yMs5hwfgbQLgdvSSkGNg1s5Uvw==",
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-9.1.0.tgz",
+ "integrity": "sha512-/pqPFG+FdxWQj+/WSuzXSDaNzxgTLr/OrR1QuqfEZzDakpdYE70PwUxL7BPUa8hpjbvY1+qvCl8k+8Tq34xJgg==",
"dev": true,
- "license": "MIT",
"dependencies": {
"flat-cache": "^5.0.0"
},
@@ -13567,7 +13533,6 @@
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-5.0.0.tgz",
"integrity": "sha512-JrqFmyUl2PnPi1OvLyTVHnQvwQ0S+e6lGSwu8OkAZlSaNIZciTY2H/cOOROxsBA1m/LZNHDsqAgDZt6akWcjsQ==",
"dev": true,
- "license": "MIT",
"dependencies": {
"flatted": "^3.3.1",
"keyv": "^4.5.4"
@@ -13576,6 +13541,15 @@
"node": ">=18"
}
},
+ "node_modules/stylelint/node_modules/ignore": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-6.0.2.tgz",
+ "integrity": "sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==",
+ "dev": true,
+ "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",
@@ -13599,22 +13573,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/stylelint/node_modules/strip-ansi": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
- "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^6.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/strip-ansi?sponsor=1"
- }
- },
"node_modules/stylelint/node_modules/write-file-atomic": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz",
diff --git a/package.json b/package.json
index 5d0be26ef..a4c6ee38a 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.15.0",
+ "version": "3.16.0",
"engines": {
"npm": "^10.2.x",
"node": "^20.17.x"
@@ -121,7 +121,7 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-frame-component": "^4.1.3",
- "react-router-dom": "6.26.2",
+ "react-router-dom": "6.27.0",
"sanitize-filename": "1.6.3",
"superagent": "^10.1.0",
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
@@ -135,7 +135,7 @@
"jest": "^29.7.0",
"jest-expect-message": "^1.1.3",
"postcss-less": "^6.0.0",
- "stylelint": "^16.9.0",
+ "stylelint": "^16.10.0",
"stylelint-config-recess-order": "^5.1.1",
"stylelint-config-recommended": "^14.0.1",
"supertest": "^7.0.0"
diff --git a/server/app.js b/server/app.js
index bf6ec37fb..5f3a35150 100644
--- a/server/app.js
+++ b/server/app.js
@@ -10,7 +10,6 @@ const app = express();
const config = require('./config.js');
const fs = require('fs-extra');
-
const { homebrewApi, getBrew, getUsersBrewThemes, getCSS } = require('./homebrew.api.js');
const GoogleActions = require('./googleActions.js');
const serveCompressedStaticAssets = require('./static-assets.mv.js');
@@ -32,6 +31,8 @@ const sanitizeBrew = (brew, accessType)=>{
return brew;
};
+app.set('trust proxy', 1 /* number of proxies between user and server */)
+
app.use('/', serveCompressedStaticAssets(`build`));
app.use(require('./middleware/content-negotiation.js'));
app.use(require('body-parser').json({ limit: '25mb' }));
@@ -257,6 +258,8 @@ app.get('/user/:username', async (req, res, next)=>{
console.log(err);
});
+ brews.forEach(brew => brew.stubbed = true); //All brews from MongoDB are "stubbed"
+
if(ownAccount && req?.account?.googleId){
const auth = await GoogleActions.authCheck(req.account, res);
let googleBrews = await GoogleActions.listGoogleBrews(auth)
@@ -264,12 +267,12 @@ app.get('/user/:username', async (req, res, next)=>{
console.error(err);
});
+ // If stub matches file from Google, use Google metadata over stub metadata
if(googleBrews && googleBrews.length > 0) {
for (const brew of brews.filter((brew)=>brew.googleId)) {
const match = googleBrews.findIndex((b)=>b.editId === brew.editId);
if(match !== -1) {
brew.googleId = googleBrews[match].googleId;
- brew.stubbed = true;
brew.pageCount = googleBrews[match].pageCount;
brew.renderer = googleBrews[match].renderer;
brew.version = googleBrews[match].version;
@@ -278,6 +281,7 @@ app.get('/user/:username', async (req, res, next)=>{
}
}
+ //Remaining unstubbed google brews display current user as author
googleBrews = googleBrews.map((brew)=>({ ...brew, authors: [req.account.username] }));
brews = _.concat(brews, googleBrews);
}
@@ -380,7 +384,7 @@ app.get('/share/:id', asyncHandler(getBrew('share')), asyncHandler(async (req, r
app.get('/account', asyncHandler(async (req, res, next)=>{
const data = {};
data.title = 'Account Information Page';
-
+
if(!req.account) {
res.set('WWW-Authenticate', 'Bearer realm="Authorization Required"');
const error = new Error('No valid account');
@@ -394,22 +398,12 @@ app.get('/account', asyncHandler(async (req, res, next)=>{
let googleCount = [];
if(req.account) {
if(req.account.googleId) {
- try {
- auth = await GoogleActions.authCheck(req.account, res, false);
- } catch (e) {
- auth = undefined;
- console.log('Google auth check failed!');
- console.log(e);
- }
- if(auth.credentials.access_token) {
- try {
- googleCount = await GoogleActions.listGoogleBrews(auth);
- } catch (e) {
- googleCount = undefined;
- console.log('List Google files failed!');
- console.log(e);
- }
- }
+ auth = await GoogleActions.authCheck(req.account, res, false)
+
+ googleCount = await GoogleActions.listGoogleBrews(auth)
+ .catch((err)=>{
+ console.error(err);
+ });
}
const query = { authors: req.account.username, googleId: { $exists: false } };
@@ -423,7 +417,7 @@ app.get('/account', asyncHandler(async (req, res, next)=>{
username : req.account.username,
issued : req.account.issued,
googleId : Boolean(req.account.googleId),
- authCheck : Boolean(req.account.googleId && auth.credentials.access_token),
+ authCheck : Boolean(req.account.googleId && auth?.credentials.access_token),
mongoCount : mongoCount,
googleCount : googleCount?.length
};
@@ -468,8 +462,8 @@ app.get('/vault', asyncHandler(async(req, res, next)=>{
//Send rendered page
app.use(asyncHandler(async (req, res, next)=>{
- if(!req.route) return res.redirect('/'); // Catch-all for invalid routes
-
+ if (!req.route) return res.redirect('/'); // Catch-all for invalid routes
+
const page = await renderPage(req, res);
if(!page) return;
res.send(page);
diff --git a/server/googleActions.js b/server/googleActions.js
index b8e96e652..bc97551ee 100644
--- a/server/googleActions.js
+++ b/server/googleActions.js
@@ -25,6 +25,15 @@ if(!config.get('service_account')){
const defaultAuth = serviceAuth || config.get('google_api_key');
+const retryConfig = {
+ retry: 3, // Number of retry attempts
+ retryDelay: 100, // Initial delay in milliseconds
+ retryDelayMultiplier: 2, // Multiplier for exponential backoff
+ maxRetryDelay: 32000, // Maximum delay in milliseconds
+ httpMethodsToRetry: ['PATCH'], // Only retry PATCH requests
+ statusCodesToRetry: [[429, 429]], // Only retry on 429 status code
+};
+
const GoogleActions = {
authCheck : (account, res, updateTokens=true)=>{
@@ -112,9 +121,7 @@ const GoogleActions = {
})
.catch((err)=>{
console.log(`Error Listing Google Brews`);
- console.error(err);
throw (err);
- //TODO: Should break out here, but continues on for some reason.
});
fileList.push(...obj.data.files);
NextPageToken = obj.data.nextPageToken;
@@ -147,7 +154,7 @@ const GoogleActions = {
return brews;
},
- updateGoogleBrew : async (brew)=>{
+ updateGoogleBrew : async (brew, userIp)=>{
const drive = googleDrive.drive({ version: 'v3', auth: defaultAuth });
await drive.files.update({
@@ -168,7 +175,11 @@ const GoogleActions = {
media : {
mimeType : 'text/plain',
body : brew.text
- }
+ },
+ headers: {
+ 'X-Forwarded-For': userIp, // Set the X-Forwarded-For header
+ },
+ retryConfig
})
.catch((err)=>{
console.log('Error saving to google');
diff --git a/server/homebrew.api.js b/server/homebrew.api.js
index 22e2cee7b..213b341ca 100644
--- a/server/homebrew.api.js
+++ b/server/homebrew.api.js
@@ -353,7 +353,7 @@ const api = {
if(!brew.googleId) return;
} else if(brew.googleId) {
// If the google id exists and no other actions are being performed, update the google brew
- const updated = await GoogleActions.updateGoogleBrew(api.excludeGoogleProps(brew));
+ const updated = await GoogleActions.updateGoogleBrew(api.excludeGoogleProps(brew), req.ip);
if(!updated) return;
}