0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-23 20:53:05 +00:00

Compare commits

..

1064 Commits

Author SHA1 Message Date
Trevor Buckner
41aebf084b Make the renderSaveButton() function common between edit/new/home
Each of the edit/home/new pages renders its save button differently. This makes it a common function with all the same possible render states (does the document have unsaved changes? Is it already saved? Was it auto-saved?).

- Common save button
- Adds the "save" button to /home page which wasn't there before
- Animates the "save" button in /home and /new when the user makes their first change to signal that yes, you do have to actually click the save button if you want to keep this.
- "reminder... you haven't saved for X minutes" still not functional on /new and /home since that involves more moving pieces.
2025-10-05 21:57:19 -04:00
Trevor Buckner
a944b23ca0 Merge pull request #4457 from naturalcrit/new/home/edit/_unsavedChanges_common
Make `unsavedChanges` state common
2025-10-05 20:09:14 -04:00
Trevor Buckner
12052853db Merge branch 'master' into new/home/edit/_unsavedChanges_common 2025-10-05 20:07:56 -04:00
Trevor Buckner
c0f67bef5a Merge pull request #4434 from naturalcrit/fix-red-background
Fix dev background
2025-10-05 19:51:01 -04:00
Víctor Losada Hernández
1f51abaf10 this makes more sense 2025-10-05 19:57:49 +02:00
Víctor Losada Hernández
c90a8c53a5 lets test this 2025-10-05 19:56:50 +02:00
Víctor Losada Hernández
ac18f4bd1d Merge branch 'master' of https://github.com/naturalcrit/homebrewery into fix-red-background 2025-10-05 19:43:29 +02:00
Víctor Losada Hernández
7393aef806 set up development config variavle 2025-10-05 19:42:01 +02:00
Trevor Buckner
2c4c4b8f92 Make unsavedChanges state common
/editPage.jsx uses `unsavedChanges` state to detect when autosave should fire, or unsaved changes warning should display.

/homePage.jsx uses a similar check (different variables) to detect when to show the popup "save now"! button

/newPage.jsx doesn't do any of this, but probably should pop up a warning when saving hasn't happened for a long time

This commit just gives all of the pages the same common `unsavedChanges` state, calculated in the same way, and updates any sections that depend on that updated state.

This is precursor work to adding "unsaved changes" warnings to all three pages.
2025-10-04 22:17:24 -04:00
Trevor Buckner
c751d647d9 Merge pull request #4440 from naturalcrit/UnifyNewHomeEdit-Structure&Naming
Clean Up Common features of new/home/edit
2025-10-04 21:52:28 -04:00
Trevor Buckner
6057b35d19 Merge branch 'master' into UnifyNewHomeEdit-Structure&Naming 2025-10-04 21:48:10 -04:00
Trevor Buckner
521d42f32f Merge pull request #4455 from G-Ambatte/enableOldKeyDeletion
Permanently enable old local storage key deletion
2025-10-04 21:47:21 -04:00
Trevor Buckner
e9f8302597 Merge branch 'master' into enableOldKeyDeletion 2025-10-04 21:47:06 -04:00
Trevor Buckner
f429b1755d Merge pull request #4456 from naturalcrit/RemoveHandlePageChangeWrappers
Remove handler function for cursor/renderer page. Use setState directly
2025-10-04 21:46:32 -04:00
Trevor Buckner
20e12ebcb5 Remove handler function for cursor/renderer page. Use setState directly 2025-10-04 21:39:24 -04:00
G.Ambatte
ae51213c8c Permanently enable old local storage key deletion 2025-10-05 12:25:51 +13:00
Trevor Buckner
44023f390c Merge pull request #4453 from G-Ambatte/fixDockerInstructionsForWindows-#4443
Add Windows-specific instructions to Docker README
2025-10-04 18:02:47 -04:00
Trevor Buckner
48b95712e2 Merge branch 'master' into fixDockerInstructionsForWindows-#4443 2025-10-04 18:01:58 -04:00
Trevor Buckner
16c28e16ce Merge pull request #4452 from G-Ambatte/standardizeLocalStorageKeyNames-#4119
Standardize local storage key names #4119
2025-10-04 17:54:10 -04:00
G.Ambatte
379b518c6b Merge branch 'master' into fixDockerInstructionsForWindows-#4443 2025-10-04 15:40:03 +13:00
G.Ambatte
962dcbdbf6 Update Docker instructions 2025-10-04 15:36:14 +13:00
G.Ambatte
400fa250ee Move key deletion out of key update check 2025-10-04 15:19:31 +13:00
G.Ambatte
e82921f81a Add key to activate deletion for testing 2025-10-04 14:54:57 +13:00
G.Ambatte
18367526bd Merge branch 'master' into standardizeLocalStorageKeyNames-#4119 2025-10-04 13:07:51 +13:00
Víctor Losada Hernández
aff9a85769 end of file character shit 2025-10-03 21:38:43 +02:00
Víctor Losada Hernández
e0379a0baa last cleanup 2025-10-03 21:38:10 +02:00
Víctor Losada Hernández
e8a0681015 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into fix-red-background 2025-10-03 21:37:06 +02:00
Víctor Losada Hernández
3ed61ebe2c Merge branch 'fix-red-background' of https://github.com/naturalcrit/homebrewery into fix-red-background 2025-10-03 21:32:55 +02:00
Víctor Losada Hernández
c2e51b0baa removing isclient check to see what's what 2025-10-03 21:32:52 +02:00
Trevor Buckner
bc258f5239 Merge branch 'master' into UnifyNewHomeEdit-Structure&Naming 2025-10-02 22:02:53 -04:00
Trevor Buckner
e64fc83ea6 Merge pull request #4436 from G-Ambatte/addMongoIndexes
Add indexes to Mongo schema
2025-10-02 20:48:32 -04:00
G.Ambatte
ee6d2ac35d Merge branch 'master' into addMongoIndexes 2025-10-03 13:42:25 +13:00
G.Ambatte
f22f7196ca Update README.md 2025-10-03 13:41:53 +13:00
Trevor Buckner
ba23763294 Merge branch 'master' into UnifyNewHomeEdit-Structure&Naming 2025-10-02 19:45:42 -04:00
Trevor Buckner
7f832a55db Merge pull request #4439 from naturalcrit/CommonHandleBrewChange
Combine handle<Text/Style/Snippet/Meta>Change() into one common function
2025-10-02 19:39:35 -04:00
Trevor Buckner
1c6a39363c Combine handleText/Style/Snippet/Meta functions into common function
Also adds any related imports and key names
2025-10-02 19:33:15 -04:00
Trevor Buckner
bcca5fa97d In /homepage, rename brew state to currentBrew to match /new and /edit 2025-10-02 19:27:45 -04:00
Trevor Buckner
51b91567f6 Merge branch 'master' into fix-red-background 2025-10-02 18:39:31 -04:00
Trevor Buckner
bfe6142b04 Merge pull request #4438 from G-Ambatte/fixDefaultSaveLocation-#4437
Fix default save location functionality
2025-10-02 18:37:59 -04:00
Víctor Losada Hernández
aef835dfe7 Merge branch 'master' into fixDefaultSaveLocation-#4437 2025-10-02 12:42:09 +02:00
Víctor Losada Hernández
274fbcb29e Merge pull request #4435 from naturalcrit/remove-scrollbar-styles
remove custom scrollbar styles
2025-10-02 12:40:59 +02:00
Víctor Losada Hernández
eefda9fe45 simplifying per suggestion 2025-10-02 12:40:12 +02:00
G.Ambatte
900cf6aebb Change SAVEKEY definition to after username is populated 2025-10-02 22:59:24 +13:00
G.Ambatte
24db8f85ac Merge branch 'standardizeLocalStorageKeyNames-#4119' of https://github.com/G-Ambatte/homebrewery into standardizeLocalStorageKeyNames-#4119 2025-10-02 18:30:07 +13:00
G.Ambatte
82a8db129e Merge branch 'naturalcrit:master' into addMongoIndexes 2025-10-02 10:54:21 +13:00
Víctor Losada Hernández
6d4ad6af7d Merge branch 'master' of https://github.com/naturalcrit/homebrewery into remove-scrollbar-styles 2025-10-01 22:57:53 +02:00
Víctor Losada Hernández
e793db7b37 separating the words to make it less ugly 2025-10-01 22:55:32 +02:00
Víctor Losada Hernández
ff5450ad8c Merge branch 'master' of https://github.com/naturalcrit/homebrewery into fix-red-background 2025-09-29 22:28:12 +02:00
Víctor Losada Hernández
4b753970c9 remove scrollbar 2025-09-29 22:19:19 +02:00
Trevor Buckner
07495b0dea Make Print/Vault/New common nav buttons to all pages 2025-09-29 12:48:25 -04:00
G.Ambatte
718dba3e4a Merge branch 'master' into standardizeLocalStorageKeyNames-#4119 2025-09-29 23:14:14 +13:00
Trevor Buckner
c6ed67db08 Merge branch 'master' into UnifyNewHomeEdit-Structure&Naming 2025-09-26 22:56:15 -04:00
Trevor Buckner
fb75bd46d0 Merge pull request #4425 from naturalcrit/ChangeAutoSaveToTimer
Fix Autosave and unsaved changes warning
2025-09-22 19:57:02 -04:00
Trevor Buckner
c5071aa27e Restore unsaved warning timeout duration to 15 mins 2025-09-22 19:55:39 -04:00
Trevor Buckner
f0baa763ec lint 2025-09-22 19:52:42 -04:00
Trevor Buckner
3ec650557e Fix Autosave and unsaved changes warning
Use normal setTimeout for autosave instead of _.debounce. Fixes a lot of issues with functional component.

Also fix existing bug where multiple "unsaved data" warnings could be queued up if the user keeps typing while the warning is being displayed.
2025-09-22 19:49:57 -04:00
Trevor Buckner
242ff8712f Merge pull request #4420 from naturalcrit/MoveShareDropdownMenuToSeparateComponent
Move "share" dropdown to own component
2025-09-18 22:48:08 -04:00
Trevor Buckner
31a8101df7 Move "share" dropdown to own component 2025-09-13 19:37:59 -04:00
Trevor Buckner
788324fe31 Merge handText/Style/Snip/MetaChange into handleBrewChange 2025-09-11 22:03:25 -04:00
Trevor Buckner
da8772daa7 Use setXXXPageNum instead of handleXXXPageNum
No need for separate wrapper functions when we can just pass the setState functions directly.
2025-09-11 16:14:45 -04:00
G.Ambatte
87a36bb02d Add tests for localStorageKeyMap.js 2025-09-11 21:57:00 +12:00
G.Ambatte
1459f6a320 Tweak local storage update logic 2025-09-11 21:25:13 +12:00
G.Ambatte
a11fa72261 Change JSON file to JS getter function 2025-09-11 21:02:56 +12:00
G.Ambatte
2663d86627 Don't update storage values if key already exists 2025-09-10 20:31:50 +12:00
G.Ambatte
8d4ea7cfd8 Update listPage storage keys 2025-09-10 20:20:42 +12:00
G.Ambatte
b6818e963b Remove unused dismiss keys 2025-09-10 20:12:50 +12:00
G.Ambatte
dc1bc471aa Update SplitPane keys 2025-09-10 20:10:18 +12:00
G.Ambatte
5504c1b96b Update Autosave key 2025-09-10 20:00:53 +12:00
G.Ambatte
fd370c777d Update Editor theme key 2025-09-10 19:57:16 +12:00
G.Ambatte
58277585e1 Update Recent Items keys 2025-09-10 19:55:42 +12:00
G.Ambatte
885c0105f3 Update adminPage key 2025-09-10 19:36:39 +12:00
G.Ambatte
52486495c8 Update adminPage storage keys 2025-09-10 19:35:14 +12:00
G.Ambatte
328e071268 Update BrewRenderer toolbar keys 2025-09-10 19:22:54 +12:00
G.Ambatte
088ca9971c Update accountPage keys 2025-09-10 19:16:29 +12:00
G.Ambatte
c99f59d42b Update newPage.jsx keys 2025-09-10 19:08:47 +12:00
G.Ambatte
cb3eb77c61 Merge branch 'standardizeLocalStorageKeyNames-#4119' of https://github.com/G-Ambatte/homebrewery into standardizeLocalStorageKeyNames-#4119 2025-09-10 18:54:00 +12:00
G.Ambatte
7163b1a287 Add function to add dynamic keys to local storage map 2025-09-10 18:53:53 +12:00
G.Ambatte
08d228831d Add missing keys to JSON, tweak layout 2025-09-10 18:53:23 +12:00
G.Ambatte
ad8bb34c93 Merge branch 'master' into standardizeLocalStorageKeyNames-#4119 2025-09-10 16:58:16 +12:00
Trevor Buckner
02a7920b2c Merge pull request #4415 from naturalcrit/MakeEditPageFunctional
Refactor editPage.jsx into a functional component
2025-09-09 22:47:26 -04:00
Trevor Buckner
43c639246b Merge branch 'master' into MakeEditPageFunctional 2025-09-09 22:41:08 -04:00
Trevor Buckner
c2e6150edf Fix mistaken delete 2025-09-09 22:39:11 -04:00
Trevor Buckner
95a1d74644 Linting 2025-09-09 22:35:55 -04:00
Trevor Buckner
1044aa74b0 Cleanup 2025-09-09 22:27:58 -04:00
Trevor Buckner
8a0f350c47 Fix mutating HTMLErrors directly instead of setState 2025-09-09 22:19:43 -04:00
Trevor Buckner
6f2c397574 Restore autosave warning to 15 minutes 2025-09-09 20:47:09 -04:00
Trevor Buckner
8706f91b58 Fis autosaveWarning 2025-09-09 08:37:17 -04:00
G.Ambatte
1eb5b6d3a4 Copy exisitng key data to new keys 2025-09-09 22:04:40 +12:00
Trevor Buckner
90f6e7ec37 Make autosaving work
debouncing does not play nice with functional component. Any debounced function gets locked in as the original state, meaning we keep saving the original document and overwriting the current document when a save fires.

Must pass in the parameters instead of pulling directly from state to work properly.
2025-09-09 01:57:13 -04:00
Trevor Buckner
90a81237ec rename handleAutoSave to toggleAutoSave 2025-09-08 23:18:25 -04:00
Trevor Buckner
883f59ff0d rename autosave state to autoSaveEnabled 2025-09-08 23:13:21 -04:00
Trevor Buckner
a75364c7f6 remove unused displayLockMessage state 2025-09-08 23:06:16 -04:00
Trevor Buckner
597ce7cb48 Convert renderNavBar and render 2025-09-08 23:05:47 -04:00
Trevor Buckner
d94afa9c50 convert functions and states 2025-09-08 19:33:02 -04:00
Víctor Losada Hernández
13de195a66 Merge pull request #4413 from naturalcrit/dependabot/npm_and_yarn/dev-dependencies-9d9674f2b4
Bump the dev-dependencies group with 3 updates
2025-09-08 16:28:08 +02:00
dependabot[bot]
32f9a44acf Bump the dev-dependencies group with 3 updates
Bumps the dev-dependencies group with 3 updates: [eslint](https://github.com/eslint/eslint), [stylelint](https://github.com/stylelint/stylelint) and [stylelint-config-recess-order](https://github.com/stormwarning/stylelint-config-recess-order).


Updates `eslint` from 9.34.0 to 9.35.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.34.0...v9.35.0)

Updates `stylelint` from 16.23.1 to 16.24.0
- [Release notes](https://github.com/stylelint/stylelint/releases)
- [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint/compare/16.23.1...16.24.0)

Updates `stylelint-config-recess-order` from 7.2.0 to 7.3.0
- [Release notes](https://github.com/stormwarning/stylelint-config-recess-order/releases)
- [Changelog](https://github.com/stormwarning/stylelint-config-recess-order/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stormwarning/stylelint-config-recess-order/compare/v7.2.0...v7.3.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 9.35.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: stylelint
  dependency-version: 16.24.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: stylelint-config-recess-order
  dependency-version: 7.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-08 03:01:23 +00:00
Víctor Losada Hernández
bb32f9fe95 Merge pull request #3022 from G-Ambatte/newTheme-UnearthedArcana
[NEW THEME]:  Unearthed Arcana
2025-09-07 22:32:29 +02:00
G.Ambatte
63f4f5712e Merge branch 'master' into newTheme-UnearthedArcana 2025-09-08 08:25:23 +12:00
Víctor Losada Hernández
ede7ad683a Merge pull request #4400 from naturalcrit/dependabot/npm_and_yarn/dev-dependencies-117382e062
Bump jest from 30.0.5 to 30.1.1 in the dev-dependencies group
2025-09-03 19:31:11 +02:00
dependabot[bot]
172c11646a Bump jest from 30.0.5 to 30.1.1 in the dev-dependencies group
Bumps the dev-dependencies group with 1 update: [jest](https://github.com/jestjs/jest/tree/HEAD/packages/jest).


Updates `jest` from 30.0.5 to 30.1.1
- [Release notes](https://github.com/jestjs/jest/releases)
- [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jestjs/jest/commits/v30.1.1/packages/jest)

---
updated-dependencies:
- dependency-name: jest
  dependency-version: 30.1.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-03 17:29:06 +00:00
Trevor Buckner
bbeac49552 Merge pull request #4411 from naturalcrit/MakeNewPageFunctionalComponent
make newPage functional
2025-09-02 22:40:26 -04:00
Trevor Buckner
1aeded648e make newPage functional 2025-09-02 22:21:49 -04:00
Trevor Buckner
c1ebc68cd4 Merge pull request #4407 from naturalcrit/MakeHomePageFunctionalComponent
Refactor homePage.jsx into a functional component
2025-08-30 20:20:16 -04:00
Trevor Buckner
93b86632fc Change from require to import 2025-08-30 20:14:29 -04:00
Trevor Buckner
d01860d4de Merge branch 'master' into MakeHomePageFunctionalComponent 2025-08-30 19:47:10 -04:00
Trevor Buckner
86ac11e512 Merge pull request #4406 from naturalcrit/Convert-ErrorNavItem-to-functional-component
Refactor ErrorNavItem to not need "this" parameter
2025-08-30 19:43:36 -04:00
Trevor Buckner
9c336062c6 Fix typo 2025-08-30 19:39:15 -04:00
Trevor Buckner
2cd47c46f6 Merge branch 'master' into Convert-ErrorNavItem-to-functional-component 2025-08-30 19:35:50 -04:00
Trevor Buckner
8671404bdc Refactor ErrorNavItem to not need "this" parameter
Toward making edit/new/home pages functional, which do not have "this"
2025-08-30 19:35:22 -04:00
Trevor Buckner
601fc732b0 Merge pull request #4404 from naturalcrit/MakeFetchThemeHelperWorkWithFunctional
Changes fetchThemeBundle helper to not need "this" parameter
2025-08-30 19:07:46 -04:00
Trevor Buckner
fb3ab47ab0 Merge branch 'master' into MakeFetchThemeHelperWorkWithFunctional 2025-08-30 19:03:57 -04:00
Trevor Buckner
518a3434be Changes fetchThemeBundle helper to not need "this" parameter
Looks a bit ugly but this is temporary toward converting edit/home/new into functional components
2025-08-30 19:02:39 -04:00
Trevor Buckner
d01f4fb77e Merge pull request #4403 from naturalcrit/revert-4212-issue_4201
Revert "Add missing punctuation and sentence structure characters to mustache style assignment regex"
2025-08-30 18:58:37 -04:00
Trevor Buckner
6600d9344c Revert "Add missing punctuation and sentence structure characters to mustache style assignment regex" 2025-08-30 18:53:55 -04:00
Trevor Buckner
0371635e11 Merge pull request #4402 from dbolack-ab/issue_4401
Prevent extra columns
2025-08-30 18:52:26 -04:00
Trevor Buckner
53f6e48f8f cleanup extra \n being added 2025-08-30 18:51:59 -04:00
Trevor Buckner
da578c53a8 Remove extraneous changes
Overcorrecting in the other direction
2025-08-30 18:50:49 -04:00
David Bolack
986bfdd00a Prevent extra columns 2025-08-30 17:37:23 -05:00
Trevor Buckner
15c04ef37e Update homePage.jsx 2025-08-30 17:14:37 -04:00
Trevor Buckner
8cf55932a9 Fix useEffect and Refs; Update fetchThemeBundle to work with functional 2025-08-30 17:10:20 -04:00
Trevor Buckner
759dcb5833 Change functions to const vars 2025-08-30 16:49:54 -04:00
Trevor Buckner
83c3eacf83 Change props and state to functional style 2025-08-30 16:45:47 -04:00
G.Ambatte
8a788a6ebf Merge branch 'master' into newTheme-UnearthedArcana 2025-08-27 13:24:25 +12:00
Víctor Losada Hernández
7198c21229 Merge pull request #4398 from naturalcrit/dependabot/npm_and_yarn/dev-dependencies-97ef339e7a
Bump eslint from 9.33.0 to 9.34.0 in the dev-dependencies group
2025-08-25 09:34:38 +02:00
G.Ambatte
6c3a5f193d Merge branch 'master' into addMongoIndexes 2025-08-25 17:24:27 +12:00
dependabot[bot]
f1ad1b9124 Bump eslint from 9.33.0 to 9.34.0 in the dev-dependencies group
Bumps the dev-dependencies group with 1 update: [eslint](https://github.com/eslint/eslint).


Updates `eslint` from 9.33.0 to 9.34.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.33.0...v9.34.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 9.34.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-25 04:44:12 +00:00
Víctor Losada Hernández
593a98db9a Merge pull request #4395 from naturalcrit/dependabot/npm_and_yarn/sha.js-2.4.12
Bump sha.js from 2.4.11 to 2.4.12
2025-08-24 22:19:42 +02:00
dependabot[bot]
e25c3daad6 Bump sha.js from 2.4.11 to 2.4.12
Bumps [sha.js](https://github.com/crypto-browserify/sha.js) from 2.4.11 to 2.4.12.
- [Changelog](https://github.com/browserify/sha.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crypto-browserify/sha.js/compare/v2.4.11...v2.4.12)

---
updated-dependencies:
- dependency-name: sha.js
  dependency-version: 2.4.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-24 20:15:31 +00:00
Víctor Losada Hernández
96b175e74d Merge pull request #4394 from naturalcrit/dependabot/npm_and_yarn/cipher-base-1.0.6
Bump cipher-base from 1.0.4 to 1.0.6
2025-08-24 22:14:05 +02:00
dependabot[bot]
8924685c26 Bump cipher-base from 1.0.4 to 1.0.6
Bumps [cipher-base](https://github.com/crypto-browserify/cipher-base) from 1.0.4 to 1.0.6.
- [Changelog](https://github.com/browserify/cipher-base/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crypto-browserify/cipher-base/compare/v1.0.4...v1.0.6)

---
updated-dependencies:
- dependency-name: cipher-base
  dependency-version: 1.0.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-24 16:30:44 +00:00
Víctor Losada Hernández
74c9d7b3f1 Merge pull request #4396 from naturalcrit/dependabot/npm_and_yarn/stylelint-config-recommended-17.0.0
Bump stylelint-config-recommended from 16.0.0 to 17.0.0
2025-08-24 18:29:18 +02:00
dependabot[bot]
cd378cad0c Bump stylelint-config-recommended from 16.0.0 to 17.0.0
Bumps [stylelint-config-recommended](https://github.com/stylelint/stylelint-config-recommended) from 16.0.0 to 17.0.0.
- [Release notes](https://github.com/stylelint/stylelint-config-recommended/releases)
- [Changelog](https://github.com/stylelint/stylelint-config-recommended/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint-config-recommended/compare/16.0.0...17.0.0)

---
updated-dependencies:
- dependency-name: stylelint-config-recommended
  dependency-version: 17.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-24 16:25:55 +00:00
Víctor Losada Hernández
ce304996f0 Merge pull request #4212 from dbolack-ab/issue_4201
Add missing punctuation and sentence structure characters to mustache style assignment regex
2025-08-24 14:14:07 +02:00
David Bolack
029c105ff1 Fix \} in divblocks 2025-08-23 14:04:37 -05:00
David Bolack
1f81cc9af0 Merge branch 'issue_4201' of github.com:dbolack-ab/homebrewery into issue_4201 2025-08-23 12:02:32 -05:00
David Bolack
6ac6eae863 Merge branch 'master' into issue_4201 2025-08-23 12:02:11 -05:00
Víctor Losada Hernández
a47a1a25a4 Merge pull request #4393 from naturalcrit/refine-import-file-error-feedback
fix file import error 2
2025-08-21 16:58:23 +02:00
Víctor Losada Hernández
0500ac305a Merge branch 'refine-import-file-error-feedback' of https://github.com/naturalcrit/homebrewery into refine-import-file-error-feedback 2025-08-21 16:55:13 +02:00
Víctor Losada Hernández
e1a441b04a Merge branch 'master' of https://github.com/naturalcrit/homebrewery into refine-import-file-error-feedback 2025-08-21 16:54:55 +02:00
Víctor Losada Hernández
b98c297079 Merge branch 'master' into refine-import-file-error-feedback 2025-08-21 16:54:28 +02:00
Víctor Losada Hernández
90dfc75ce9 Merge branch 'refine-import-file-error-feedback' of https://github.com/naturalcrit/homebrewery into refine-import-file-error-feedback 2025-08-21 16:53:39 +02:00
Víctor Losada Hernández
dd46a059c5 aah, i forgot to add the latest commit 2025-08-21 16:53:36 +02:00
Víctor Losada Hernández
2d881b8dc9 Merge pull request #4391 from naturalcrit/refine-import-file-error-feedback
Refine import file error feedback
2025-08-21 16:42:45 +02:00
Víctor Losada Hernández
e023bfeef6 Merge branch 'master' into refine-import-file-error-feedback 2025-08-21 16:38:43 +02:00
Víctor Losada Hernández
8b351925c1 Merge pull request #4361 from naturalcrit/dependabot/npm_and_yarn/stylistic/stylelint-plugin-4.0.0
Bump @stylistic/stylelint-plugin from 3.1.3 to 4.0.0
2025-08-21 16:37:06 +02:00
Víctor Losada Hernández
5ddd631dfd Merge branch 'master' into dependabot/npm_and_yarn/stylistic/stylelint-plugin-4.0.0 2025-08-21 16:34:00 +02:00
Víctor Losada Hernández
5ff6327c72 Merge pull request #4364 from G-Ambatte/addRequestMiddlewareTests
Add tests for request-middleware.js
2025-08-21 16:30:20 +02:00
Víctor Losada Hernández
c993a1a8c9 Merge branch 'master' into addRequestMiddlewareTests 2025-08-21 16:26:59 +02:00
Víctor Losada Hernández
b9372f17d9 Merge branch 'master' into issue_4201 2025-08-21 16:10:04 +02:00
Víctor Losada Hernández
6b7c57f0e4 Merge pull request #4368 from naturalcrit/fixAccountPageFAIcons
Fix Account page FA icon font weights
2025-08-21 16:05:52 +02:00
Víctor Losada Hernández
6c5063a30d Merge branch 'master' into fixAccountPageFAIcons 2025-08-21 16:02:21 +02:00
Víctor Losada Hernández
e20da7c67f Merge pull request #4382 from emmanuel-ferdman/master
Handle mongo count qurey error by returning default value
2025-08-21 16:02:09 +02:00
Víctor Losada Hernández
3596eabbf5 Merge branch 'master' into fixAccountPageFAIcons 2025-08-21 16:00:18 +02:00
Víctor Losada Hernández
fb4ca21cb4 Merge branch 'master' into master 2025-08-21 15:59:04 +02:00
dependabot[bot]
99769c90f8 Bump @stylistic/stylelint-plugin from 3.1.3 to 4.0.0
Bumps [@stylistic/stylelint-plugin](https://github.com/stylelint-stylistic/stylelint-stylistic) from 3.1.3 to 4.0.0.
- [Release notes](https://github.com/stylelint-stylistic/stylelint-stylistic/releases)
- [Changelog](https://github.com/stylelint-stylistic/stylelint-stylistic/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint-stylistic/stylelint-stylistic/compare/v3.1.3...v4.0.0)

---
updated-dependencies:
- dependency-name: "@stylistic/stylelint-plugin"
  dependency-version: 4.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-21 13:58:23 +00:00
Víctor Losada Hernández
301c50cca9 Merge pull request #4383 from naturalcrit/dependabot/npm_and_yarn/dev-dependencies-b389a345e5
Bump the dev-dependencies group across 1 directory with 3 updates
2025-08-21 15:56:56 +02:00
Víctor Losada Hernández
320f1e120f reordering 2025-08-21 12:23:41 +02:00
Víctor Losada Hernández
cca9ebefdb better error handling for file import 2025-08-20 23:23:13 +02:00
dependabot[bot]
aebc49c2d4 Bump the dev-dependencies group across 1 directory with 3 updates
Bumps the dev-dependencies group with 3 updates in the / directory: [eslint](https://github.com/eslint/eslint), [stylelint](https://github.com/stylelint/stylelint) and [stylelint-config-recess-order](https://github.com/stormwarning/stylelint-config-recess-order).


Updates `eslint` from 9.31.0 to 9.33.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.31.0...v9.33.0)

Updates `stylelint` from 16.22.0 to 16.23.1
- [Release notes](https://github.com/stylelint/stylelint/releases)
- [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint/compare/16.22.0...16.23.1)

Updates `stylelint-config-recess-order` from 7.1.0 to 7.2.0
- [Release notes](https://github.com/stormwarning/stylelint-config-recess-order/releases)
- [Changelog](https://github.com/stormwarning/stylelint-config-recess-order/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stormwarning/stylelint-config-recess-order/compare/v7.1.0...v7.2.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 9.33.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: stylelint
  dependency-version: 16.23.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: stylelint-config-recess-order
  dependency-version: 7.2.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 13:52:36 +00:00
Emmanuel Ferdman
1eb226ea13 Handle mongo count qurey error by returning default value
Signed-off-by: Emmanuel Ferdman <emmanuelferdman@gmail.com>
2025-08-10 23:26:41 -07:00
G.Ambatte
8049b5be9d Merge branch 'master' into addRequestMiddlewareTests 2025-08-04 11:17:14 +12:00
G.Ambatte
a8dab28fcf Fix Account page FA icon font weights 2025-07-30 12:00:50 +12:00
Trevor Buckner
253dbb358b Merge pull request #4366 from naturalcrit/relocateSplitPane
Moving splitPane over to the components folder
2025-07-29 16:36:39 -04:00
Trevor Buckner
719edd82c5 Moving splitPane over to the components folder
Just to reduce the number of changes needed to review on the UI overhaul #4122 PR
2025-07-29 16:35:25 -04:00
G.Ambatte
16d7b11b8d Add request-middleware test file 2025-07-26 18:44:59 +12:00
David Bolack
e2ed7b8600 Merge branch 'master' into issue_4201 2025-07-23 20:21:49 -05:00
Trevor Buckner
63d957fdc6 Merge pull request #4357 from naturalcrit/dependabot/npm_and_yarn/dev-dependencies-e74ffdea55
Bump the dev-dependencies group across 1 directory with 3 updates
2025-07-23 16:39:37 -04:00
dependabot[bot]
7751c0e37b Bump the dev-dependencies group across 1 directory with 3 updates
Bumps the dev-dependencies group with 3 updates in the / directory: [jest](https://github.com/jestjs/jest/tree/HEAD/packages/jest), [stylelint](https://github.com/stylelint/stylelint) and [supertest](https://github.com/ladjs/supertest).


Updates `jest` from 30.0.4 to 30.0.5
- [Release notes](https://github.com/jestjs/jest/releases)
- [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jestjs/jest/commits/v30.0.5/packages/jest)

Updates `stylelint` from 16.21.1 to 16.22.0
- [Release notes](https://github.com/stylelint/stylelint/releases)
- [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint/compare/16.21.1...16.22.0)

Updates `supertest` from 7.1.3 to 7.1.4
- [Release notes](https://github.com/ladjs/supertest/releases)
- [Commits](https://github.com/ladjs/supertest/compare/v7.1.3...v7.1.4)

---
updated-dependencies:
- dependency-name: jest
  dependency-version: 30.0.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: stylelint
  dependency-version: 16.22.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: supertest
  dependency-version: 7.1.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-23 03:52:56 +00:00
Trevor Buckner
990bf80b59 Comment out patch contents from logs
patch contents on failed patches clogging logs with pages and pages of text
2025-07-22 14:45:57 -04:00
Trevor Buckner
f16598f238 Fix Google ID Validation Regex
Google IDs with underscores were failing.

Regex found in Google drive documentation: https://developers.google.com/workspace/docs/api/concepts/document
2025-07-22 14:39:09 -04:00
G.Ambatte
4d014bf379 Merge branch 'master' into addMongoIndexes 2025-07-20 14:06:53 +12:00
G.Ambatte
4856c803ed Use local_environments from config 2025-07-20 11:48:22 +12:00
G.Ambatte
d9cd270f3b Add autobuild to Mongoose connection (local/docker ONLY) 2025-07-20 11:43:14 +12:00
G.Ambatte
878ea1449d Add indexes to HomebrewSchema 2025-07-20 11:42:24 +12:00
Trevor Buckner
579e9e0ec5 Merge pull request #4347 from G-Ambatte/experimentalDiffSaveFix
Diff patching fix using encodeURI
2025-07-19 15:30:10 -04:00
Trevor Buckner
f6629f2f9e Merge pull request #4287 from dbolack-ab/opengraph_locale
Add brew locale to opengraph localization
2025-07-19 15:27:44 -04:00
G.Ambatte
b87c78474d Fix for diff patching using encodeURI 2025-07-19 14:49:02 +12:00
Víctor Losada Hernández
d6a5a1f03c no idea what changed but now it works 2025-07-18 00:39:36 +02:00
Víctor Losada Hernández
f04d6cdd1f fix to current 2025-07-17 23:32:18 +02:00
Víctor Losada Hernández
4fd61ce92c Merge branch 'master' of https://github.com/naturalcrit/homebrewery into fix-red-background 2025-07-17 23:30:01 +02:00
Trevor Buckner
958d282a58 Merge branch 'master' into opengraph_locale 2025-07-17 14:30:16 -04:00
David Bolack
7e56ae2019 locale typo. 2025-07-16 10:34:16 -05:00
David Bolack
ebca50ed4b Merge branch 'master' into opengraph_locale 2025-07-16 10:33:27 -05:00
Trevor Buckner
bfd14757c2 Merge pull request #4210 from dbolack-ab/legacy_gmb
Add column, columnbreak, and pagebreak compatibulity to Legacy
2025-07-15 15:58:17 -04:00
Trevor Buckner
3626ed5a31 Rename regex, move column replacement
Renaming COLUMNBREAK_REGEX_LEGACY for consistency in naming scheme with the other regexes.

Moving the legacy `\column` replacement down to `renderPages()` where we do similar text modification steps for V3.
2025-07-15 14:47:04 -04:00
Trevor Buckner
d385bacdd6 Merge branch 'master' into legacy_gmb 2025-07-15 14:22:31 -04:00
Trevor Buckner
cbbb2c0a7d Merge pull request #4225 from naturalcrit/fix-calc-in-curly-elements
Fix calc operator regex
2025-07-15 14:21:35 -04:00
Trevor Buckner
fbe637ff82 Add to non-quoted case as well
`{{greenBox,height:calc(10px*2) }}` should also be valid without using quotes.
2025-07-15 14:16:17 -04:00
Trevor Buckner
82bd16c623 Merge branch 'master' into fix-calc-in-curly-elements 2025-07-15 14:09:02 -04:00
Trevor Buckner
d1f13af67b Merge pull request #4340 from naturalcrit/MoreHomebrew.jsxCleanup
More homebrew.jsx cleanup
2025-07-15 13:56:39 -04:00
Trevor Buckner
b6c03e88b8 Looks like react is needed by some other components later on 2025-07-15 17:53:01 +00:00
Trevor Buckner
b587d17397 Remove unused React import 2025-07-15 17:41:56 +00:00
Trevor Buckner
0a02f910f8 Clean up WithRoute 2025-07-15 17:32:10 +00:00
Trevor Buckner
ddfa06e76b Change requires to imports 2025-07-15 17:17:09 +00:00
Trevor Buckner
0c2b1fec04 Merge pull request #4226 from naturalcrit/refactor-homebrew.jsx-to-functional
refactor homebrew.jsx to be functional
2025-07-15 12:59:19 -04:00
Trevor Buckner
6de7a64acd Add comment for to-well-formed 2025-07-15 12:58:06 -04:00
Trevor Buckner
b9fe4c3901 Merge branch 'master' into refactor-homebrew.jsx-to-functional 2025-07-15 11:32:28 -04:00
Trevor Buckner
5ae01862e5 Merge pull request #4339 from naturalcrit/dependabot/npm_and_yarn/prod-dependencies-b017ff4ab1
Bump the prod-dependencies group across 1 directory with 2 updates
2025-07-15 11:17:59 -04:00
Trevor Buckner
398df7a061 Merge branch 'master' into dependabot/npm_and_yarn/prod-dependencies-b017ff4ab1 2025-07-15 11:15:54 -04:00
Trevor Buckner
443b0f6a37 Merge pull request #4320 from G-Ambatte/experimentalIDValidations
Brew ID validations
2025-07-15 11:14:45 -04:00
Trevor Buckner
544175b994 Merge branch 'master' into experimentalIDValidations 2025-07-15 11:14:11 -04:00
Trevor Buckner
955602e7ee Merge pull request #4333 from G-Ambatte/addForceSSLTests
Add forceSSL middleware tests
2025-07-15 11:11:33 -04:00
G.Ambatte
90e577dd3f Rework tests 2025-07-15 09:02:57 +12:00
G.Ambatte
828208aadb Add more ID validation test cases 2025-07-15 08:19:05 +12:00
G.Ambatte
973e071e93 Slightly loosen Google ID match criteria, add comments 2025-07-15 08:13:35 +12:00
dependabot[bot]
f9e7aa355d Bump the prod-dependencies group across 1 directory with 2 updates
Bumps the prod-dependencies group with 2 updates in the / directory: [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) and [mongoose](https://github.com/Automattic/mongoose).


Updates `core-js` from 3.43.0 to 3.44.0
- [Release notes](https://github.com/zloirock/core-js/releases)
- [Changelog](https://github.com/zloirock/core-js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zloirock/core-js/commits/v3.44.0/packages/core-js)

Updates `mongoose` from 8.16.1 to 8.16.3
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/8.16.1...8.16.3)

---
updated-dependencies:
- dependency-name: core-js
  dependency-version: 3.44.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-dependencies
- dependency-name: mongoose
  dependency-version: 8.16.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-14 20:09:17 +00:00
Trevor Buckner
24dfd41714 Merge branch 'master' into experimentalIDValidations 2025-07-14 13:37:19 -04:00
Trevor Buckner
638e54535d Merge branch 'master' into addForceSSLTests 2025-07-14 13:16:14 -04:00
Trevor Buckner
cbc6956221 Merge pull request #4334 from G-Ambatte/addTokenTests
Add token.js tests
2025-07-14 13:15:43 -04:00
Trevor Buckner
248d2038ec Cleanup token.js 2025-07-14 13:10:19 -04:00
Trevor Buckner
5b66175b8c Merge pull request #4337 from naturalcrit/dependabot/npm_and_yarn/dev-dependencies-3db4bbab60
Bump the dev-dependencies group across 1 directory with 2 updates
2025-07-14 12:59:01 -04:00
Trevor Buckner
552aa7d41a Merge branch 'master' into dependabot/npm_and_yarn/dev-dependencies-3db4bbab60 2025-07-14 12:53:33 -04:00
Trevor Buckner
b0a108b543 Merge pull request #4338 from G-Ambatte/addSafeHTMLTest
Increase safeHTML testing to 100% coverage
2025-07-14 12:53:12 -04:00
G.Ambatte
505d2840c0 Merge branch 'master' into addSafeHTMLTest 2025-07-14 21:26:22 +12:00
G.Ambatte
41ff50fefe Add missing test 2025-07-14 21:23:38 +12:00
G.Ambatte
2fbcc84a50 Merge branch 'master' into experimentalIDValidations 2025-07-14 14:50:05 +12:00
G.Ambatte
45e4d27c0a Merge branch 'master' into addForceSSLTests 2025-07-14 14:46:10 +12:00
G.Ambatte
77bf3ffc6f Merge branch 'master' into addTokenTests 2025-07-14 14:46:07 +12:00
dependabot[bot]
bc045ec6c9 Bump the dev-dependencies group across 1 directory with 2 updates
Bumps the dev-dependencies group with 2 updates in the / directory: [eslint](https://github.com/eslint/eslint) and [supertest](https://github.com/ladjs/supertest).


Updates `eslint` from 9.30.1 to 9.31.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.30.1...v9.31.0)

Updates `supertest` from 7.1.1 to 7.1.3
- [Release notes](https://github.com/ladjs/supertest/releases)
- [Commits](https://github.com/ladjs/supertest/compare/v7.1.1...v7.1.3)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 9.31.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: supertest
  dependency-version: 7.1.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-14 00:02:03 +00:00
Trevor Buckner
6390ea076a Merge pull request #4330 from naturalcrit/CompressSaveDataUpload
Gzip brew object when sending for save update
2025-07-13 20:00:21 -04:00
Trevor Buckner
6affcb587d Merge branch 'master' into CompressSaveDataUpload 2025-07-13 19:57:10 -04:00
Trevor Buckner
7787afabff Merge pull request #4336 from naturalcrit/fixVersionTest
Fix test option 2 - Add text property to version check test object
2025-07-13 19:55:16 -04:00
Trevor Buckner
fb4a8e5cf1 Merge branch 'CompressSaveDataUpload' of https://github.com/naturalcrit/homebrewery into CompressSaveDataUpload 2025-07-13 19:38:10 -04:00
Trevor Buckner
8432a6e367 cleanup 2025-07-13 19:38:08 -04:00
G.Ambatte
90ee08de42 Add text property to test object 2025-07-14 11:06:35 +12:00
G.Ambatte
40839b18e4 Add tests for token.js 2025-07-14 00:14:58 +12:00
G.Ambatte
677c02cfa5 Add forceSSL tests 2025-07-13 22:36:53 +12:00
G.Ambatte
a7a8803e9d Merge branch 'master' into experimentalIDValidations 2025-07-13 20:52:52 +12:00
Trevor Buckner
5fbc111db7 Merge branch 'master' into CompressSaveDataUpload 2025-07-13 00:55:59 -04:00
Trevor Buckner
5edea7d0f4 Turns out body-parser automatically inflates gzip. Can remove. 2025-07-13 00:55:16 -04:00
Trevor Buckner
d3a9d813c9 Log brew compression size just for testing purposes 2025-07-13 00:54:51 -04:00
Trevor Buckner
fc475b2a7e Allow babel to transpile fflate 2025-07-13 00:52:06 -04:00
Trevor Buckner
76b76b3bb6 Merge pull request #4286 from dbolack-ab/snippets-save-history-too
Add brew snippets to local save history
2025-07-11 13:32:13 -04:00
Trevor Buckner
22ef3cbebc Gzip brew object when sending for save update 2025-07-11 16:55:30 +00:00
Trevor Buckner
9da8a17053 Remove text mismatch logs 2025-07-10 17:17:25 -04:00
Trevor Buckner
7cadbfbd7b allowExceedingIndices for our patch applier
Test if it allows patches to go through, and log error if it doesn't match the expected output.
2025-07-10 17:11:31 -04:00
Trevor Buckner
98b9e86787 Merge pull request #4329 from naturalcrit/AdditionalPatchLogging
On patch failure, compare client and server text bytewise
2025-07-10 12:05:24 -04:00
Trevor Buckner
489b4b2694 Also log differences on MD5 mismatch 2025-07-10 12:04:09 -04:00
Trevor Buckner
8d279260c2 Merge branch 'master' into AdditionalPatchLogging 2025-07-10 11:19:07 -04:00
Trevor Buckner
7c08c430d0 Merge pull request #4324 from G-Ambatte/experimentalSplitHashAndVersionChecks
Split hash and version checks in updateBrew function
2025-07-10 11:18:53 -04:00
Trevor Buckner
45689d119e Comment out one failing test
Patch failure test is no longer being thrown while we monitor in the background
2025-07-10 11:18:39 -04:00
Trevor Buckner
c5805af935 On patch failure, compare client and server text bytewise 2025-07-10 11:12:42 -04:00
Trevor Buckner
b2c4bb7082 Merge branch 'master' into experimentalSplitHashAndVersionChecks 2025-07-10 11:06:29 -04:00
Trevor Buckner
68460447dc Merge pull request #4327 from dbolack-ab/fallbackTest
Run patch processing in parallel to prior system
2025-07-10 09:50:43 -04:00
Trevor Buckner
440c7beff6 Up to 3.19.3 so users can get the update 2025-07-10 09:47:21 -04:00
David Bolack
c7610cf0f8 Run patch processing in parallel to prior system to attempt to narrow down not-quite-so-edge cases that did not come up prior to user testing. 2025-07-10 07:10:13 -05:00
G.Ambatte
7f3a818558 Add Homebrew API coverage tests 2025-07-10 23:25:57 +12:00
G.Ambatte
bc82afa5b2 Split version from hash checks 2025-07-10 21:42:51 +12:00
G.Ambatte
abef250631 Update ID validation check 2025-07-10 20:58:46 +12:00
G.Ambatte
1794e96d50 Update tests 2025-07-10 20:46:01 +12:00
G.Ambatte
25f25da499 Adjust validation regex for IDs 2025-07-10 20:39:12 +12:00
G.Ambatte
aa15bdaacb Initial pass at ID validations 2025-07-10 19:59:09 +12:00
Trevor Buckner
7ba7991631 Additional diff server error logging 2025-07-10 00:37:03 -04:00
Trevor Buckner
0e1ac26999 v3.19.2 2025-07-09 22:42:21 -04:00
Trevor Buckner
f49fed8c35 Merge pull request #4311 from dbolack-ab/md5Fix
Normalize strings before running MD5s
2025-07-09 22:40:15 -04:00
Trevor Buckner
a8236fbab4 Ok. I'm lowering the coverage threshold 2025-07-09 22:14:24 -04:00
Trevor Buckner
daf4eceedd Small rearrangement 2025-07-09 22:09:16 -04:00
David Bolack
a02361ee65 Move normalization to before diffing 2025-07-09 19:37:57 -05:00
David Bolack
81e20f032e NOrmalize strings before rnuning MD5s 2025-07-09 18:52:45 -05:00
Trevor Buckner
1d92b98568 Merge pull request #4310 from dbolack-ab/2025-06-09_Debug
Add Patch wrapper/unwrapper for saves
2025-07-09 18:42:23 -04:00
David Bolack
0f4157d084 Add Patch wrapper/unwrapper for saves
Object encapsulation for the win?
2025-07-09 17:16:47 -05:00
Trevor Buckner
4dcc3749d8 Merge branch 'master' into snippets-save-history-too 2025-07-09 13:04:08 -04:00
Trevor Buckner
8f058d56f2 Merge pull request #4304 from naturalcrit/3.19.1
up To V3.19.1
2025-07-09 11:11:59 -04:00
Trevor Buckner
d192a064d6 up To V3.19.1 2025-07-09 15:10:53 +00:00
Trevor Buckner
cccb531e17 Merge pull request #4280 from naturalcrit/SaveDiffs
Diff Saving
2025-07-09 09:55:33 -04:00
Trevor Buckner
6414e73e7d Cleanup and better handling of pre-save snapshot 2025-07-08 15:50:27 -04:00
Trevor Buckner
41daf8d172 comment out hash check 2025-07-07 21:05:50 +00:00
Trevor Buckner
4c897fdeb5 Add MD5 hash check 2025-07-07 21:00:03 +00:00
Trevor Buckner
89ce4de354 Add hash-wasm package for md5 2025-07-07 20:47:34 +00:00
Trevor Buckner
43095507ee Fix 2025-07-07 19:26:03 +00:00
Trevor Buckner
eb7fbbe018 Merge branch 'master' into SaveDiffs 2025-07-07 15:07:44 -04:00
Trevor Buckner
869958ec38 Don't save unless previous save is complete 2025-07-07 19:00:01 +00:00
David Bolack
99b90e0998 Include snippets in the restoration. 2025-07-07 13:54:29 -05:00
Trevor Buckner
57a48100d3 Merge pull request #4300 from naturalcrit/rename_isPending_to_unsavedChanges
Rename isPending state to more explicit unsavedChanges
2025-07-07 14:54:25 -04:00
Trevor Buckner
8538e4fadb Rename isPending state to more explicit unsavedChanges 2025-07-07 18:48:36 +00:00
Trevor Buckner
9a002511a3 Merge pull request #4254 from naturalcrit/dependabot/npm_and_yarn/stylelint-config-recess-order-7.1.0
Bump stylelint-config-recess-order from 6.0.0 to 7.1.0
2025-07-03 16:23:48 -04:00
Trevor Buckner
3fa3a52e05 Merge branch 'master' into dependabot/npm_and_yarn/stylelint-config-recess-order-7.1.0 2025-07-03 16:14:17 -04:00
Trevor Buckner
4fe920dac3 Merge pull request #4244 from naturalcrit/dependabot/npm_and_yarn/googleapis/drive-13.0.1
Bump @googleapis/drive from 12.1.0 to 13.0.1
2025-07-03 16:13:44 -04:00
dependabot[bot]
71dff5fbf9 Bump @googleapis/drive from 12.1.0 to 13.0.1
Bumps [@googleapis/drive](https://github.com/googleapis/google-api-nodejs-client) from 12.1.0 to 13.0.1.
- [Release notes](https://github.com/googleapis/google-api-nodejs-client/releases)
- [Changelog](https://github.com/googleapis/google-api-nodejs-client/blob/main/.release-please-manifest.json)
- [Commits](https://github.com/googleapis/google-api-nodejs-client/compare/iam-v12.1.0...iam-v13.0.1)

---
updated-dependencies:
- dependency-name: "@googleapis/drive"
  dependency-version: 13.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-03 20:03:12 +00:00
dependabot[bot]
26419d2ccb Bump stylelint-config-recess-order from 6.0.0 to 7.1.0
Bumps [stylelint-config-recess-order](https://github.com/stormwarning/stylelint-config-recess-order) from 6.0.0 to 7.1.0.
- [Release notes](https://github.com/stormwarning/stylelint-config-recess-order/releases)
- [Changelog](https://github.com/stormwarning/stylelint-config-recess-order/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stormwarning/stylelint-config-recess-order/compare/v6.0.0...v7.1.0)

---
updated-dependencies:
- dependency-name: stylelint-config-recess-order
  dependency-version: 7.1.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-03 20:03:10 +00:00
Trevor Buckner
f02fe2d8f3 Merge pull request #4261 from naturalcrit/dependabot/npm_and_yarn/eslint-plugin-jest-29.0.1
Bump eslint-plugin-jest from 28.11.0 to 29.0.1
2025-07-03 16:01:53 -04:00
dependabot[bot]
318fb53eb2 Bump eslint-plugin-jest from 28.11.0 to 29.0.1
Bumps [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) from 28.11.0 to 29.0.1.
- [Release notes](https://github.com/jest-community/eslint-plugin-jest/releases)
- [Changelog](https://github.com/jest-community/eslint-plugin-jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jest-community/eslint-plugin-jest/compare/v28.11.0...v29.0.1)

---
updated-dependencies:
- dependency-name: eslint-plugin-jest
  dependency-version: 29.0.1
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-03 19:32:43 +00:00
Trevor Buckner
6a32b7427b Merge pull request #4270 from naturalcrit/dependabot/npm_and_yarn/pbkdf2-3.1.3
Bump pbkdf2 from 3.1.2 to 3.1.3
2025-07-03 15:31:22 -04:00
Trevor Buckner
5886bd65e5 Merge branch 'master' into dependabot/npm_and_yarn/pbkdf2-3.1.3 2025-07-03 15:27:43 -04:00
Trevor Buckner
9c5f80cbdb Merge pull request #4295 from naturalcrit/dependabot/npm_and_yarn/dev-dependencies-6ab8ca7825
Bump the dev-dependencies group with 3 updates
2025-07-03 15:26:55 -04:00
dependabot[bot]
79d8956c4f Bump pbkdf2 from 3.1.2 to 3.1.3
Bumps [pbkdf2](https://github.com/crypto-browserify/pbkdf2) from 3.1.2 to 3.1.3.
- [Changelog](https://github.com/browserify/pbkdf2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crypto-browserify/pbkdf2/compare/v3.1.2...v3.1.3)

---
updated-dependencies:
- dependency-name: pbkdf2
  dependency-version: 3.1.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-03 19:24:44 +00:00
Trevor Buckner
2e491b3556 Merge branch 'master' into dependabot/npm_and_yarn/dev-dependencies-6ab8ca7825 2025-07-03 15:23:48 -04:00
Trevor Buckner
d9a8afa272 Merge pull request #4296 from naturalcrit/dependabot/npm_and_yarn/prod-dependencies-2b50ddedc8
Bump the prod-dependencies group with 6 updates
2025-07-03 15:23:18 -04:00
dependabot[bot]
209195202c Bump the prod-dependencies group with 6 updates
Bumps the prod-dependencies group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) | `7.27.1` | `7.27.6` |
| [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) | `3.42.0` | `3.43.0` |
| [marked-emoji](https://github.com/UziTech/marked-emoji) | `2.0.0` | `2.0.1` |
| [mongoose](https://github.com/Automattic/mongoose) | `8.15.0` | `8.16.1` |
| [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) | `7.6.0` | `7.6.3` |
| [romans](https://github.com/qbunt/romans) | `3.0.0` | `3.1.0` |


Updates `@babel/runtime` from 7.27.1 to 7.27.6
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.27.6/packages/babel-runtime)

Updates `core-js` from 3.42.0 to 3.43.0
- [Release notes](https://github.com/zloirock/core-js/releases)
- [Changelog](https://github.com/zloirock/core-js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zloirock/core-js/commits/v3.43.0/packages/core-js)

Updates `marked-emoji` from 2.0.0 to 2.0.1
- [Release notes](https://github.com/UziTech/marked-emoji/releases)
- [Changelog](https://github.com/UziTech/marked-emoji/blob/main/release.config.cjs)
- [Commits](https://github.com/UziTech/marked-emoji/compare/v2.0.0...v2.0.1)

Updates `mongoose` from 8.15.0 to 8.16.1
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/8.15.0...8.16.1)

Updates `react-router` from 7.6.0 to 7.6.3
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router@7.6.3/packages/react-router)

Updates `romans` from 3.0.0 to 3.1.0
- [Release notes](https://github.com/qbunt/romans/releases)
- [Commits](https://github.com/qbunt/romans/compare/v3.0.0...v3.1.0)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-version: 7.27.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-dependencies
- dependency-name: core-js
  dependency-version: 3.43.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-dependencies
- dependency-name: marked-emoji
  dependency-version: 2.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-dependencies
- dependency-name: mongoose
  dependency-version: 8.16.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-dependencies
- dependency-name: react-router
  dependency-version: 7.6.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-dependencies
- dependency-name: romans
  dependency-version: 3.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-03 18:54:23 +00:00
dependabot[bot]
64235c844a Bump the dev-dependencies group with 3 updates
Bumps the dev-dependencies group with 3 updates: [@stylistic/stylelint-plugin](https://github.com/stylelint-stylistic/stylelint-stylistic), [babel-plugin-transform-import-meta](https://github.com/javiertury/babel-plugin-transform-import-meta) and [stylelint](https://github.com/stylelint/stylelint).


Updates `@stylistic/stylelint-plugin` from 3.1.2 to 3.1.3
- [Release notes](https://github.com/stylelint-stylistic/stylelint-stylistic/releases)
- [Changelog](https://github.com/stylelint-stylistic/stylelint-stylistic/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint-stylistic/stylelint-stylistic/compare/v3.1.2...v3.1.3)

Updates `babel-plugin-transform-import-meta` from 2.3.2 to 2.3.3
- [Changelog](https://github.com/javiertury/babel-plugin-transform-import-meta/blob/master/CHANGELOG.md)
- [Commits](https://github.com/javiertury/babel-plugin-transform-import-meta/compare/v2.3.2...v2.3.3)

Updates `stylelint` from 16.19.1 to 16.21.1
- [Release notes](https://github.com/stylelint/stylelint/releases)
- [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint/compare/16.19.1...16.21.1)

---
updated-dependencies:
- dependency-name: "@stylistic/stylelint-plugin"
  dependency-version: 3.1.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: babel-plugin-transform-import-meta
  dependency-version: 2.3.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: stylelint
  dependency-version: 16.21.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-03 18:52:53 +00:00
Trevor Buckner
5d000a4599 Update dependabot.yml
Fixed bad spacing typo
2025-07-03 14:50:48 -04:00
Trevor Buckner
380e593b42 Group minor & patch dependency PRs
Instead of individual dependabot PRs that need to be merged, then we trigger a rebase, wait 5 minutes, then merge the next...

We can group dependency updates together in a single PR. This change makes all dev dependencies bundle minor and patch versions into one PR, and similarly with production dependencies. Major versions will still have separate PRs as they tend to break things.
2025-07-03 14:45:29 -04:00
Trevor Buckner
169f089d08 Merge pull request #4284 from naturalcrit/dependabot/npm_and_yarn/marked-gfm-heading-id-4.1.2
Bump marked-gfm-heading-id from 4.1.1 to 4.1.2
2025-07-03 14:03:01 -04:00
dependabot[bot]
b3977ed141 Bump marked-gfm-heading-id from 4.1.1 to 4.1.2
Bumps [marked-gfm-heading-id](https://github.com/markedjs/marked-gfm-heading-id) from 4.1.1 to 4.1.2.
- [Release notes](https://github.com/markedjs/marked-gfm-heading-id/releases)
- [Changelog](https://github.com/markedjs/marked-gfm-heading-id/blob/main/release.config.cjs)
- [Commits](https://github.com/markedjs/marked-gfm-heading-id/compare/v4.1.1...v4.1.2)

---
updated-dependencies:
- dependency-name: marked-gfm-heading-id
  dependency-version: 4.1.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-03 17:52:30 +00:00
Trevor Buckner
9800561de7 Merge pull request #4292 from naturalcrit/dependabot/npm_and_yarn/babel/plugin-transform-runtime-7.28.0
Bump @babel/plugin-transform-runtime from 7.27.1 to 7.28.0
2025-07-03 13:50:55 -04:00
dependabot[bot]
166af08e6a Bump @babel/plugin-transform-runtime from 7.27.1 to 7.28.0
Bumps [@babel/plugin-transform-runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-runtime) from 7.27.1 to 7.28.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.28.0/packages/babel-plugin-transform-runtime)

---
updated-dependencies:
- dependency-name: "@babel/plugin-transform-runtime"
  dependency-version: 7.28.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-03 17:47:13 +00:00
Trevor Buckner
48f17f7c5e Merge pull request #4289 from naturalcrit/dependabot/npm_and_yarn/eslint-9.30.1
Bump eslint from 9.27.0 to 9.30.1
2025-07-03 13:45:34 -04:00
dependabot[bot]
87c9f52222 Bump eslint from 9.27.0 to 9.30.1
Bumps [eslint](https://github.com/eslint/eslint) from 9.27.0 to 9.30.1.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.27.0...v9.30.1)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 9.30.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-03 17:41:39 +00:00
Trevor Buckner
c80b7ffd66 Merge pull request #4290 from naturalcrit/dependabot/npm_and_yarn/globals-16.3.0
Bump globals from 16.1.0 to 16.3.0
2025-07-03 13:40:08 -04:00
dependabot[bot]
5f16ce3dbd Bump globals from 16.1.0 to 16.3.0
Bumps [globals](https://github.com/sindresorhus/globals) from 16.1.0 to 16.3.0.
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v16.1.0...v16.3.0)

---
updated-dependencies:
- dependency-name: globals
  dependency-version: 16.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-03 17:31:36 +00:00
Trevor Buckner
b5ff26f857 Merge pull request #4293 from naturalcrit/dependabot/npm_and_yarn/babel/preset-env-7.28.0
Bump @babel/preset-env from 7.27.2 to 7.28.0
2025-07-03 13:30:02 -04:00
Trevor Buckner
578b01bbb1 Merge branch 'master' into dependabot/npm_and_yarn/babel/preset-env-7.28.0 2025-07-03 13:27:27 -04:00
Trevor Buckner
67467e0099 Merge pull request #4294 from naturalcrit/dependabot/npm_and_yarn/jest-30.0.4
Bump jest from 29.7.0 to 30.0.4
2025-07-03 13:27:15 -04:00
dependabot[bot]
da21bf20f9 Bump jest from 29.7.0 to 30.0.4
Bumps [jest](https://github.com/jestjs/jest/tree/HEAD/packages/jest) from 29.7.0 to 30.0.4.
- [Release notes](https://github.com/jestjs/jest/releases)
- [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jestjs/jest/commits/v30.0.4/packages/jest)

---
updated-dependencies:
- dependency-name: jest
  dependency-version: 30.0.4
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-03 03:54:56 +00:00
dependabot[bot]
df7fcf1e5f Bump @babel/preset-env from 7.27.2 to 7.28.0
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.27.2 to 7.28.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.28.0/packages/babel-preset-env)

---
updated-dependencies:
- dependency-name: "@babel/preset-env"
  dependency-version: 7.28.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-03 03:54:32 +00:00
David Bolack
702ece6671 Add brew locale to opengraph localization 2025-06-30 12:39:30 -05:00
David Bolack
1008321957 Add brew snippets to local save history
solves #3113
2025-06-30 12:17:46 -05:00
David Bolack
b547486c48 Merge branch 'master' into legacy_gmb 2025-06-30 10:54:35 -05:00
David Bolack
6bb0b8001b Merge branch 'master' into issue_4201 2025-06-30 10:54:16 -05:00
Trevor Buckner
e1e661976d Initial test 2025-06-27 08:07:02 -04:00
Víctor Losada Hernández
7bdeeee9ef Merge pull request #4237 from naturalcrit/fix-toolbar-issues
Add defaults to the toolbar for rowGap and columnGap
2025-06-25 17:01:10 +02:00
Víctor Losada Hernández
becf35d336 Merge branch 'fix-toolbar-issues' of https://github.com/naturalcrit/homebrewery into fix-toolbar-issues 2025-06-25 16:58:26 +02:00
Víctor Losada Hernández
d7585767c9 remove too strong shadow 2025-06-25 16:58:24 +02:00
Víctor Losada Hernández
f9bb6209b7 Merge branch 'master' into fix-toolbar-issues 2025-06-25 16:55:20 +02:00
Víctor Losada Hernández
13702a2f62 last thing 2025-06-25 16:54:56 +02:00
Trevor Buckner
a6a684c89e Merge pull request #4272 from naturalcrit/RemoveTextBinFromSaveResponse
Don't send full text back to client on save
2025-06-24 17:45:40 -04:00
Trevor Buckner
862fa7de89 Don't send full text back to client on save
We return the stub after saving. When saving to HB MongoDB, the stub also includes the full text. This does not need to be sent back to the client.
2025-06-24 17:44:22 -04:00
Trevor Buckner
b671cf7b02 Merge pull request #4265 from naturalcrit/HandleRequestAbortError
Handle Request Abort Error
2025-06-20 17:58:17 -04:00
Trevor Buckner
d5dbe0b4ba Update error-navitem.jsx 2025-06-20 17:53:08 -04:00
Víctor Losada Hernández
c2cf695c17 add defaults 2025-06-02 13:50:51 +02:00
Víctor Losada Hernández
6d0d6f08b5 initial commit 2025-05-28 09:09:14 +02:00
Víctor Losada Hernández
77dcc9b433 initial commit 2025-05-28 08:34:52 +02:00
Víctor Losada Hernández
88b70d340e final bit 2025-05-27 11:27:04 +02:00
Víctor Losada Hernández
ed05d8c754 move all to homebrew.jsx 2025-05-27 11:25:01 +02:00
Víctor Losada Hernández
077aaeb815 log 2025-05-27 10:54:07 +02:00
David Bolack
50d2a0d3a2 FIx regression 2025-05-26 23:13:21 -05:00
David Bolack
17f60ee159 Merge branch 'master' into issue_4201 2025-05-26 23:03:16 -05:00
Víctor Losada Hernández
5f2f3a6f3d Merge pull request #4214 from dbolack-ab/issue_4211
Clone snippets
2025-05-25 15:29:59 +02:00
David Bolack
bbb812cb06 Clone snippets 2025-05-25 08:07:50 -05:00
David Bolack
e842599b22 Add missing punction and sentence structure characters to mustache style assignment regex 2025-05-24 22:35:03 -05:00
David Bolack
5648e55774 Add column, columnbreak, and pagebreak compatibuility to Legacy 2025-05-23 14:45:37 -05:00
Trevor Buckner
c051580545 Merge pull request #4207 from naturalcrit/fix-codemirror-overflow
Fix Codemirror Overflow
2025-05-23 10:03:26 -04:00
Trevor Buckner
6e72fe2600 Merge branch 'master' into fix-codemirror-overflow 2025-05-23 10:00:33 -04:00
Trevor Buckner
03602ae1e0 Merge pull request #4208 from 5e-Cleric/add-icons-back-to-admin
bring back icons to admin
2025-05-22 23:47:39 -04:00
Víctor Losada Hernández
8de738a146 initial commit 2025-05-23 00:20:32 +02:00
Víctor Losada Hernández
6960beb739 updating changelog to reflect reality 2025-05-22 23:35:26 +02:00
Víctor Losada Hernández
6748639ec5 remove dumb console log from another pr 2025-05-22 23:35:00 +02:00
Víctor Losada Hernández
e5651807fd Merge branch 'master' into fix-codemirror-overflow 2025-05-22 23:30:02 +02:00
Víctor Losada Hernández
9adf6dee61 use js for it 2025-05-22 23:29:08 +02:00
Víctor Losada Hernández
03527a1f95 fix it damn it 2025-05-22 22:35:26 +02:00
Trevor Buckner
651863b0f7 Merge pull request #4185 from naturalcrit/dependabot/npm_and_yarn/express-static-gzip-3.0.0
Bump express-static-gzip from 2.2.0 to 3.0.0
2025-05-22 15:41:56 -04:00
Trevor Buckner
450ecd24b7 Merge branch 'master' into dependabot/npm_and_yarn/express-static-gzip-3.0.0 2025-05-22 15:08:56 -04:00
Trevor Buckner
995cfa2aa4 Merge pull request #4170 from dbolack-ab/issue_3318
Tweak icon height
2025-05-22 15:04:46 -04:00
Trevor Buckner
5eecb5ea20 Remove unnecessary properties 2025-05-22 15:01:38 -04:00
Trevor Buckner
0885473b66 Merge branch 'master' into pr/4170 2025-05-22 14:59:31 -04:00
dependabot[bot]
eabff4f6b2 Bump express-static-gzip from 2.2.0 to 3.0.0
Bumps [express-static-gzip](https://github.com/tkoenig89/express-static-gzip) from 2.2.0 to 3.0.0.
- [Release notes](https://github.com/tkoenig89/express-static-gzip/releases)
- [Commits](https://github.com/tkoenig89/express-static-gzip/compare/v2.2.0...v3.0.0)

---
updated-dependencies:
- dependency-name: express-static-gzip
  dependency-version: 3.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-22 18:40:45 +00:00
Trevor Buckner
a773df25d0 Merge pull request #4200 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.15.0
Bump mongoose from 8.14.3 to 8.15.0
2025-05-22 14:39:30 -04:00
dependabot[bot]
b07f75ac36 Bump mongoose from 8.14.3 to 8.15.0
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.14.3 to 8.15.0.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/8.14.3...8.15.0)

---
updated-dependencies:
- dependency-name: mongoose
  dependency-version: 8.15.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-22 18:33:03 +00:00
Trevor Buckner
ed5fbadd73 Merge pull request #4206 from naturalcrit/v3.19.0
v3.19.0
2025-05-22 14:31:47 -04:00
Trevor Buckner
c74c2c8efe Merge branch 'master' into v3.19.0 2025-05-22 14:31:02 -04:00
Trevor Buckner
1efe570dae Up version to 3.19.0 2025-05-22 14:30:15 -04:00
Trevor Buckner
2571460f42 Merge pull request #4199 from naturalcrit/dependabot/npm_and_yarn/eslint-9.27.0
Bump eslint from 9.26.0 to 9.27.0
2025-05-22 11:49:48 -04:00
dependabot[bot]
dbb67113b9 Bump eslint from 9.26.0 to 9.27.0
Bumps [eslint](https://github.com/eslint/eslint) from 9.26.0 to 9.27.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.26.0...v9.27.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 9.27.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-22 00:18:55 +00:00
Trevor Buckner
33e3e018f3 Merge pull request #4202 from naturalcrit/dependabot/npm_and_yarn/googleapis/drive-12.1.0
Bump @googleapis/drive from 12.0.0 to 12.1.0
2025-05-21 20:17:36 -04:00
dependabot[bot]
07adf0342d Bump @googleapis/drive from 12.0.0 to 12.1.0
Bumps [@googleapis/drive](https://github.com/googleapis/google-api-nodejs-client) from 12.0.0 to 12.1.0.
- [Release notes](https://github.com/googleapis/google-api-nodejs-client/releases)
- [Changelog](https://github.com/googleapis/google-api-nodejs-client/blob/main/release-please-config.json)
- [Commits](https://github.com/googleapis/google-api-nodejs-client/compare/12.0.0...iam-v12.1.0)

---
updated-dependencies:
- dependency-name: "@googleapis/drive"
  dependency-version: 12.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-21 19:56:20 +00:00
Trevor Buckner
b2b1cb4985 Merge pull request #4203 from naturalcrit/dependabot/npm_and_yarn/marked-15.0.12
Bump marked from 15.0.11 to 15.0.12
2025-05-21 15:55:01 -04:00
dependabot[bot]
c4d6cc4579 Bump marked from 15.0.11 to 15.0.12
Bumps [marked](https://github.com/markedjs/marked) from 15.0.11 to 15.0.12.
- [Release notes](https://github.com/markedjs/marked/releases)
- [Changelog](https://github.com/markedjs/marked/blob/master/.releaserc.json)
- [Commits](https://github.com/markedjs/marked/compare/v15.0.11...v15.0.12)

---
updated-dependencies:
- dependency-name: marked
  dependency-version: 15.0.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-21 03:25:36 +00:00
Trevor Buckner
01fbb4439e Merge pull request #4197 from naturalcrit/dependabot/npm_and_yarn/supertest-7.1.1
Bump supertest from 7.1.0 to 7.1.1
2025-05-20 13:52:29 -04:00
dependabot[bot]
eb48d981d6 Bump supertest from 7.1.0 to 7.1.1
Bumps [supertest](https://github.com/ladjs/supertest) from 7.1.0 to 7.1.1.
- [Release notes](https://github.com/ladjs/supertest/releases)
- [Commits](https://github.com/ladjs/supertest/compare/v7.1.0...v7.1.1)

---
updated-dependencies:
- dependency-name: supertest
  dependency-version: 7.1.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-14 14:13:10 +00:00
Trevor Buckner
3624fcef0f Merge pull request #4198 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.14.3
Bump mongoose from 8.14.2 to 8.14.3
2025-05-14 10:11:50 -04:00
dependabot[bot]
ab62f0fcf9 Bump mongoose from 8.14.2 to 8.14.3
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.14.2 to 8.14.3.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/8.14.2...8.14.3)

---
updated-dependencies:
- dependency-name: mongoose
  dependency-version: 8.14.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-14 03:13:55 +00:00
Víctor Losada Hernández
9e78671e4f Merge pull request #4195 from dbolack-ab/F5_to_F6
Correct missed Font Awesome version number references
2025-05-11 22:18:56 +02:00
Víctor Losada Hernández
f64a7b38ae Merge branch 'master' into F5_to_F6 2025-05-11 22:14:02 +02:00
David Bolack
3fdedd8861 Correct missed Font Awesome Reference versions 2025-05-11 15:08:20 -05:00
Víctor Losada Hernández
1d4ebbb689 Merge pull request #4191 from naturalcrit/fix-darkbrewery
Fix-darkbrewery
2025-05-11 16:00:53 +02:00
Víctor Losada Hernández
c4f148a3a1 Merge branch 'master' into fix-darkbrewery 2025-05-11 15:56:46 +02:00
David Bolack
7abf45e8ba Merge branch 'master' into issue_3318 2025-05-10 18:53:31 -05:00
David Bolack
bbae62e0b7 Merge branch 'issue_3318' of github.com:dbolack-ab/homebrewery into issue_3318 2025-05-10 18:53:19 -05:00
David Bolack
a9d71078d3 A better look, I think? 2025-05-10 18:52:32 -05:00
David Bolack
5bde870586 Merge branch 'master' of github.com:naturalcrit/homebrewery 2025-05-10 18:37:45 -05:00
Trevor Buckner
7ea78870bf Merge pull request #4186 from naturalcrit/dependabot/npm_and_yarn/babel/preset-env-7.27.2
Bump @babel/preset-env from 7.26.9 to 7.27.2
2025-05-10 17:50:08 -04:00
Trevor Buckner
393caa86eb add babel/runtime 2025-05-10 17:41:39 -04:00
dependabot[bot]
9b7a3c5c70 Bump @babel/preset-env from 7.26.9 to 7.27.2
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.26.9 to 7.27.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.27.2/packages/babel-preset-env)

---
updated-dependencies:
- dependency-name: "@babel/preset-env"
  dependency-version: 7.27.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-10 17:32:41 +00:00
Trevor Buckner
fe69bd50b5 Merge pull request #4190 from naturalcrit/dependabot/npm_and_yarn/idb-keyval-6.2.2
Bump idb-keyval from 6.2.1 to 6.2.2
2025-05-10 13:31:24 -04:00
Trevor Buckner
a2c4f604b3 Merge pull request #4193 from dbolack-ab/issue_4192
Resolve Issue 4192
2025-05-10 13:31:08 -04:00
Trevor Buckner
083e8c9b52 remove duplicate comment 2025-05-10 13:30:38 -04:00
David Bolack
d2a025ca41 Merge branch 'master' of github.com:naturalcrit/homebrewery 2025-05-10 12:27:33 -05:00
Trevor Buckner
181d6b7e0a Merge branch 'master' into pr/4193 2025-05-10 13:24:48 -04:00
Trevor Buckner
dd20fc8475 Revert "Revert debris"
This reverts commit ab400b82d6.
2025-05-10 13:21:35 -04:00
David Bolack
33ea397915 Restore removed line and warn.
Yeah, it's a bit extra.
2025-05-10 09:50:19 -05:00
Víctor Losada Hernández
320fb02543 remove empty lines in css 2025-05-09 20:44:59 +02:00
Víctor Losada Hernández
e127a6a557 refactor the theme 2025-05-09 20:42:23 +02:00
Víctor Losada Hernández
e774dfd97d change darkbrewery's name 2025-05-09 20:42:16 +02:00
Víctor Losada Hernández
1dcea0fe6a fix snippet editor button tooltip 2025-05-09 20:42:01 +02:00
Víctor Losada Hernández
0ca53f8db6 Merge branch 'master' into issue_3318 2025-05-09 19:08:04 +02:00
dependabot[bot]
5395a759ed Bump idb-keyval from 6.2.1 to 6.2.2
Bumps [idb-keyval](https://github.com/jakearchibald/idb-keyval) from 6.2.1 to 6.2.2.
- [Changelog](https://github.com/jakearchibald/idb-keyval/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jakearchibald/idb-keyval/compare/v6.2.1...v6.2.2)

---
updated-dependencies:
- dependency-name: idb-keyval
  dependency-version: 6.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-09 13:32:48 +00:00
Trevor Buckner
8f470fb000 Merge pull request #4188 from naturalcrit/dependabot/npm_and_yarn/react-router-7.6.0
Bump react-router from 7.5.3 to 7.6.0
2025-05-09 09:31:27 -04:00
Trevor Buckner
90c375a5c8 Merge pull request #4184 from dbolack-ab/GMB_Compats_1
Allow \pagebreak and \columnbreak compatibility for GMB users
2025-05-09 09:31:17 -04:00
Trevor Buckner
e8cc4a0c58 Merge branch 'master' into GMB_Compats_1 2025-05-09 09:29:12 -04:00
dependabot[bot]
cf68cc46ad Bump react-router from 7.5.3 to 7.6.0
Bumps [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) from 7.5.3 to 7.6.0.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router@7.6.0/packages/react-router)

---
updated-dependencies:
- dependency-name: react-router
  dependency-version: 7.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-09 13:24:37 +00:00
Trevor Buckner
653e20b4e4 Merge pull request #4189 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.14.2
Bump mongoose from 8.14.1 to 8.14.2
2025-05-09 09:23:15 -04:00
dependabot[bot]
e97d45e5b5 Bump mongoose from 8.14.1 to 8.14.2
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.14.1 to 8.14.2.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/8.14.1...8.14.2)

---
updated-dependencies:
- dependency-name: mongoose
  dependency-version: 8.14.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-09 03:28:37 +00:00
Trevor Buckner
691cd048e2 Merge pull request #4161 from naturalcrit/dependabot/npm_and_yarn/googleapis/drive-12.0.0
Bump @googleapis/drive from 11.0.0 to 12.0.0
2025-05-08 11:25:10 -04:00
Trevor Buckner
5071105f8c Merge pull request #4187 from naturalcrit/dependabot/npm_and_yarn/globals-16.1.0
Bump globals from 16.0.0 to 16.1.0
2025-05-08 00:12:04 -04:00
dependabot[bot]
9cd009e89b Bump globals from 16.0.0 to 16.1.0
Bumps [globals](https://github.com/sindresorhus/globals) from 16.0.0 to 16.1.0.
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v16.0.0...v16.1.0)

---
updated-dependencies:
- dependency-name: globals
  dependency-version: 16.1.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-08 04:00:08 +00:00
Trevor Buckner
acaf293c7c Merge branch 'master' into dependabot/npm_and_yarn/googleapis/drive-12.0.0 2025-05-07 16:57:30 -04:00
Trevor Buckner
79503dd17f Merge pull request #4156 from 5e-Cleric/toolbar-few-fixes
toolbar, small fixes
2025-05-07 16:34:08 -04:00
dependabot[bot]
485b6a0041 Bump @googleapis/drive from 11.0.0 to 12.0.0
Bumps [@googleapis/drive](https://github.com/googleapis/google-api-nodejs-client) from 11.0.0 to 12.0.0.
- [Release notes](https://github.com/googleapis/google-api-nodejs-client/releases)
- [Changelog](https://github.com/googleapis/google-api-nodejs-client/blob/12.0.0/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-api-nodejs-client/compare/11.0.0...12.0.0)

---
updated-dependencies:
- dependency-name: "@googleapis/drive"
  dependency-version: 12.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-07 20:27:44 +00:00
Trevor Buckner
983781303b Merge branch 'master' into toolbar-few-fixes 2025-05-07 16:27:29 -04:00
Trevor Buckner
9c8e03f961 Merge pull request #4181 from naturalcrit/dependabot/npm_and_yarn/react-router-7.5.3
Bump react-router from 7.5.1 to 7.5.3
2025-05-07 16:26:28 -04:00
Víctor Losada Hernández
a298288888 Merge branch 'master' into toolbar-few-fixes 2025-05-07 21:38:46 +02:00
dependabot[bot]
c48703aed5 Bump react-router from 7.5.1 to 7.5.3
Bumps [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) from 7.5.1 to 7.5.3.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router@7.5.3/packages/react-router)

---
updated-dependencies:
- dependency-name: react-router
  dependency-version: 7.5.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-07 19:05:23 +00:00
Trevor Buckner
09000bd20f Merge pull request #4173 from naturalcrit/dependabot/npm_and_yarn/babel/plugin-transform-runtime-7.27.1
Bump @babel/plugin-transform-runtime from 7.26.10 to 7.27.1
2025-05-07 15:03:59 -04:00
Trevor Buckner
237caa84f7 Merge branch 'master' into dependabot/npm_and_yarn/babel/plugin-transform-runtime-7.27.1 2025-05-07 15:00:43 -04:00
Trevor Buckner
d292d60ee9 Merge pull request #4172 from naturalcrit/dependabot/npm_and_yarn/core-js-3.42.0
Bump core-js from 3.41.0 to 3.42.0
2025-05-07 15:00:27 -04:00
dependabot[bot]
395e406d65 Bump @babel/plugin-transform-runtime from 7.26.10 to 7.27.1
Bumps [@babel/plugin-transform-runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-runtime) from 7.26.10 to 7.27.1.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.27.1/packages/babel-plugin-transform-runtime)

---
updated-dependencies:
- dependency-name: "@babel/plugin-transform-runtime"
  dependency-version: 7.27.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-07 18:52:34 +00:00
Trevor Buckner
806c3f63bb Merge branch 'master' into dependabot/npm_and_yarn/core-js-3.42.0 2025-05-07 14:51:56 -04:00
Trevor Buckner
4a296809a0 Merge pull request #4175 from naturalcrit/dependabot/npm_and_yarn/babel/preset-react-7.27.1
Bump @babel/preset-react from 7.26.3 to 7.27.1
2025-05-07 14:51:18 -04:00
dependabot[bot]
f8361fa141 Bump core-js from 3.41.0 to 3.42.0
Bumps [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) from 3.41.0 to 3.42.0.
- [Release notes](https://github.com/zloirock/core-js/releases)
- [Changelog](https://github.com/zloirock/core-js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zloirock/core-js/commits/v3.42.0/packages/core-js)

---
updated-dependencies:
- dependency-name: core-js
  dependency-version: 3.42.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-07 18:43:22 +00:00
Trevor Buckner
8542056d6e Merge branch 'master' into dependabot/npm_and_yarn/babel/preset-react-7.27.1 2025-05-07 14:42:30 -04:00
Trevor Buckner
f23be91b6d Merge pull request #4174 from naturalcrit/dependabot/npm_and_yarn/babel/core-7.27.1
Bump @babel/core from 7.26.10 to 7.27.1
2025-05-07 14:41:59 -04:00
dependabot[bot]
f810bea4c8 Bump @babel/preset-react from 7.26.3 to 7.27.1
Bumps [@babel/preset-react](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-react) from 7.26.3 to 7.27.1.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.27.1/packages/babel-preset-react)

---
updated-dependencies:
- dependency-name: "@babel/preset-react"
  dependency-version: 7.27.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-07 18:37:10 +00:00
dependabot[bot]
42136b89fd Bump @babel/core from 7.26.10 to 7.27.1
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.26.10 to 7.27.1.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.27.1/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  dependency-version: 7.27.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-07 18:37:04 +00:00
Trevor Buckner
eb604d9201 Merge pull request #4182 from naturalcrit/dependabot/npm_and_yarn/eslint-9.26.0
Bump eslint from 9.25.1 to 9.26.0
2025-05-07 14:35:43 -04:00
dependabot[bot]
e341069196 Bump eslint from 9.25.1 to 9.26.0
Bumps [eslint](https://github.com/eslint/eslint) from 9.25.1 to 9.26.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.25.1...v9.26.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 9.26.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-07 18:32:37 +00:00
Trevor Buckner
3a54ac9d7d Merge pull request #4164 from naturalcrit/dependabot/npm_and_yarn/marked-15.0.11
Bump marked from 15.0.9 to 15.0.11
2025-05-07 14:31:10 -04:00
Trevor Buckner
42d8c1b33f Merge branch 'master' into dependabot/npm_and_yarn/marked-15.0.11 2025-05-07 14:27:58 -04:00
Trevor Buckner
f700620373 Merge pull request #4167 from naturalcrit/dependabot/npm_and_yarn/stylelint-16.19.1
Bump stylelint from 16.18.0 to 16.19.1
2025-05-07 14:27:46 -04:00
dependabot[bot]
0f059bce66 Bump stylelint from 16.18.0 to 16.19.1
Bumps [stylelint](https://github.com/stylelint/stylelint) from 16.18.0 to 16.19.1.
- [Release notes](https://github.com/stylelint/stylelint/releases)
- [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint/compare/16.18.0...16.19.1)

---
updated-dependencies:
- dependency-name: stylelint
  dependency-version: 16.19.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-07 17:21:48 +00:00
Trevor Buckner
0eb68aaf72 Merge pull request #4183 from naturalcrit/dependabot/npm_and_yarn/superagent-10.2.1
Bump superagent from 10.2.0 to 10.2.1
2025-05-07 13:20:24 -04:00
Trevor Buckner
b9f825c168 Merge branch 'master' into dependabot/npm_and_yarn/marked-15.0.11 2025-05-07 13:20:10 -04:00
Víctor Losada Hernández
58c2504394 Merge branch 'toolbar-few-fixes' of https://github.com/5e-Cleric/homebrewery into toolbar-few-fixes 2025-05-07 12:01:16 +02:00
Víctor Losada Hernández
a9aadbfef9 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into toolbar-few-fixes 2025-05-07 12:01:09 +02:00
Víctor Losada Hernández
dae5922fd0 Merge branch 'master' into toolbar-few-fixes 2025-05-07 12:00:46 +02:00
Víctor Losada Hernández
5fb20991bb fix ttolbar visibility storage 2025-05-07 11:57:51 +02:00
Víctor Losada Hernández
75fe7b2c67 increase visibility of toolbar toggle 2025-05-07 11:57:41 +02:00
David Bolack
ab400b82d6 Revert debris 2025-05-06 16:36:19 -05:00
David Bolack
6867cb5a4a Clear up debris that was on the wrong branch 2025-05-06 16:35:53 -05:00
David Bolack
742de8582c Allow pagebreak and columnbreak compatibility 2025-05-06 16:27:55 -05:00
David Bolack
600ff5f367 Merge branch 'master' of github.com:naturalcrit/homebrewery 2025-05-06 16:14:02 -05:00
dependabot[bot]
e751facf32 Bump superagent from 10.2.0 to 10.2.1
Bumps [superagent](https://github.com/ladjs/superagent) from 10.2.0 to 10.2.1.
- [Release notes](https://github.com/ladjs/superagent/releases)
- [Changelog](https://github.com/ladjs/superagent/blob/master/HISTORY.md)
- [Commits](https://github.com/ladjs/superagent/compare/v10.2.0...v10.2.1)

---
updated-dependencies:
- dependency-name: superagent
  dependency-version: 10.2.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-06 03:36:49 +00:00
Víctor Losada Hernández
959d5fb6c9 Merge pull request #4169 from dbolack-ab/self-host-fa
Localise out Fontawesome use.
2025-05-06 00:28:26 +02:00
Víctor Losada Hernández
3456d503b2 Merge branch 'master' into self-host-fa 2025-05-06 00:25:16 +02:00
David Bolack
9ef291a8ae Verbage change 2025-05-05 08:53:47 -05:00
David Bolack
ff174870e2 Merge branch 'master' of github.com:naturalcrit/homebrewery 2025-05-05 08:52:23 -05:00
Víctor Losada Hernández
a015714d5e Merge pull request #4178 from naturalcrit/template-metadata-brew
Add author as metadata for link unfurling
2025-05-03 00:32:01 +02:00
Víctor Losada Hernández
9bcab7b82b Merge branch 'master' into template-metadata-brew 2025-05-02 21:28:59 +02:00
dependabot[bot]
bc0cb0d0be Bump marked from 15.0.9 to 15.0.11
Bumps [marked](https://github.com/markedjs/marked) from 15.0.9 to 15.0.11.
- [Release notes](https://github.com/markedjs/marked/releases)
- [Changelog](https://github.com/markedjs/marked/blob/master/.releaserc.json)
- [Commits](https://github.com/markedjs/marked/compare/v15.0.9...v15.0.11)

---
updated-dependencies:
- dependency-name: marked
  dependency-version: 15.0.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-02 18:51:01 +00:00
Víctor Losada Hernández
ce4299a1f0 Merge pull request #4158 from G-Ambatte/fixNavigationScrollError-#4157
Change page overflow from hidden to clip
2025-05-02 20:50:27 +02:00
Víctor Losada Hernández
398e985e65 Merge branch 'master' into fixNavigationScrollError-#4157 2025-05-02 20:50:13 +02:00
Trevor Buckner
a5f597f598 Merge pull request #4149 from naturalcrit/dependabot/npm_and_yarn/react-router-7.5.1
Bump react-router from 7.5.0 to 7.5.1
2025-05-02 14:49:43 -04:00
Víctor Losada Hernández
beb7ecd0a9 Merge branch 'master' into fixNavigationScrollError-#4157 2025-05-02 20:46:34 +02:00
Trevor Buckner
ea625a0fbc Merge branch 'master' into dependabot/npm_and_yarn/react-router-7.5.1 2025-05-02 14:41:50 -04:00
Trevor Buckner
932120883b Merge pull request #4171 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.14.1
Bump mongoose from 8.13.2 to 8.14.1
2025-05-02 14:41:07 -04:00
Víctor Losada Hernández
b29406da8b Merge branch 'master' into fixNavigationScrollError-#4157 2025-05-02 20:34:09 +02:00
Víctor Losada Hernández
4cc2d429c5 try to pass it as title 2025-05-02 17:15:24 +02:00
Víctor Losada Hernández
77563d12a6 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into template-metadata-brew 2025-05-02 17:11:02 +02:00
Víctor Losada Hernández
b914bf3bf5 simple as cake 2025-05-02 17:09:09 +02:00
David Bolack
6f52b8473f Swap in an svg 2025-04-30 10:38:15 -05:00
David Bolack
44713eda4e Might work? 2025-04-29 23:20:06 -05:00
dependabot[bot]
e552282299 Bump mongoose from 8.13.2 to 8.14.1
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.13.2 to 8.14.1.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/8.13.2...8.14.1)

---
updated-dependencies:
- dependency-name: mongoose
  dependency-version: 8.14.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-30 03:28:34 +00:00
David Bolack
9ecd53267f Tweak icon height 2025-04-29 19:43:11 -05:00
David Bolack
5ee1cf6aa5 Localise out Fontawesome use. 2025-04-29 19:17:40 -05:00
Trevor Buckner
1295f635dc Merge branch 'master' into dependabot/npm_and_yarn/react-router-7.5.1 2025-04-22 14:00:53 -04:00
Trevor Buckner
60142d9467 Merge pull request #4159 from naturalcrit/dependabot/npm_and_yarn/marked-15.0.9
Bump marked from 15.0.8 to 15.0.9
2025-04-22 13:57:56 -04:00
dependabot[bot]
6dc4355972 Bump marked from 15.0.8 to 15.0.9
Bumps [marked](https://github.com/markedjs/marked) from 15.0.8 to 15.0.9.
- [Release notes](https://github.com/markedjs/marked/releases)
- [Changelog](https://github.com/markedjs/marked/blob/master/.releaserc.json)
- [Commits](https://github.com/markedjs/marked/compare/v15.0.8...v15.0.9)

---
updated-dependencies:
- dependency-name: marked
  dependency-version: 15.0.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-22 13:46:13 +00:00
Trevor Buckner
555a26f0d6 Merge pull request #4160 from naturalcrit/dependabot/npm_and_yarn/eslint-9.25.1
Bump eslint from 9.25.0 to 9.25.1
2025-04-22 09:44:51 -04:00
dependabot[bot]
abce7d8531 Bump eslint from 9.25.0 to 9.25.1
Bumps [eslint](https://github.com/eslint/eslint) from 9.25.0 to 9.25.1.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.25.0...v9.25.1)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 9.25.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-22 03:31:44 +00:00
Víctor Losada Hernández
678d981121 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into toolbar-few-fixes 2025-04-22 00:45:52 +02:00
Víctor Losada Hernández
32f8c18adc Merge branch 'store-toolbar-state-in-storage' of https://github.com/naturalcrit/homebrewery into toolbar-few-fixes 2025-04-22 00:43:09 +02:00
Víctor Losada Hernández
0aead96dcf fix columnGap 2025-04-22 00:42:48 +02:00
G.Ambatte
c238094e4c Change page overflow from hidden to clip 2025-04-22 10:28:35 +12:00
Trevor Buckner
657eeea4d5 Merge pull request #4155 from naturalcrit/dependabot/npm_and_yarn/eslint-9.25.0
Bump eslint from 9.24.0 to 9.25.0
2025-04-21 16:51:46 -04:00
Víctor Losada Hernández
1e34e85aab actually fix it 2025-04-21 22:23:55 +02:00
Víctor Losada Hernández
b747968e74 fixing toFit to actually fit in any mode 2025-04-21 22:17:52 +02:00
Víctor Losada Hernández
25629173c9 Merge branch 'store-toolbar-state-in-storage' of https://github.com/naturalcrit/homebrewery into toolbar-few-fixes 2025-04-21 20:45:54 +02:00
Víctor Losada Hernández
96642c07d3 initial attempt at facing width button 2025-04-21 20:45:48 +02:00
Víctor Losada Hernández
2bd0f909f3 fix snippetbar being eaten 2025-04-21 20:45:36 +02:00
Víctor Losada Hernández
9b4047f3f9 small css changes 2025-04-21 20:12:42 +02:00
dependabot[bot]
91e2916199 Bump eslint from 9.24.0 to 9.25.0
Bumps [eslint](https://github.com/eslint/eslint) from 9.24.0 to 9.25.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.24.0...v9.25.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 9.25.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-21 03:46:54 +00:00
Trevor Buckner
3fcc677f96 Merge branch 'master' into dependabot/npm_and_yarn/react-router-7.5.1 2025-04-19 21:32:21 -04:00
Trevor Buckner
3f77e32550 Merge pull request #4147 from G-Ambatte/fixFacingFlowPrintIssues
Add print-specific styling for facing and flow page layouts
2025-04-18 07:50:27 -04:00
dependabot[bot]
c4903c4993 Bump react-router from 7.5.0 to 7.5.1
Bumps [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) from 7.5.0 to 7.5.1.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router@7.5.1/packages/react-router)

---
updated-dependencies:
- dependency-name: react-router
  dependency-version: 7.5.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-18 03:47:56 +00:00
Trevor Buckner
630f9002aa Merge branch 'master' into fixFacingFlowPrintIssues 2025-04-17 15:40:13 -04:00
Trevor Buckner
aea7809fbd Merge pull request #4049 from dbolack-ab/marked-definition-lists
Move Definition Lists extensions to NPM
2025-04-17 15:38:46 -04:00
Trevor Buckner
30e644d5e0 Merge branch 'marked-definition-lists' of https://github.com/dbolack-ab/homebrewery into pr/4049 2025-04-17 15:35:01 -04:00
Trevor Buckner
fe2f5a405c Update package-lock.json 2025-04-17 15:31:25 -04:00
Trevor Buckner
07a1890ed9 Merge branch 'master' into pr/4049 2025-04-17 15:18:40 -04:00
G.Ambatte
fc400c226c Merge branch 'master' into fixFacingFlowPrintIssues 2025-04-16 10:31:45 +12:00
Trevor Buckner
8e3ccec855 Remove old references to extensions 2025-04-15 15:29:53 -04:00
Trevor Buckner
25c09bc241 Merge branch 'master' into marked-definition-lists 2025-04-15 13:57:57 -04:00
Trevor Buckner
0eaba3de01 Merge pull request #4145 from naturalcrit/dependabot/npm_and_yarn/nconf-0.13.0
Bump nconf from 0.12.1 to 0.13.0
2025-04-15 13:01:23 -04:00
Trevor Buckner
ece1a7e9a7 Merge branch 'master' into dependabot/npm_and_yarn/nconf-0.13.0 2025-04-15 11:47:33 -04:00
Trevor Buckner
2ef7a1521b Merge pull request #4148 from naturalcrit/rebuildPackageLock
Rebuild package-lock
2025-04-15 11:47:20 -04:00
Trevor Buckner
b72357096a Rebuild package-lock 2025-04-15 14:58:16 +00:00
G.Ambatte
8f4c74d0ce Add print-specific styling for facing and flow page layouts 2025-04-15 19:36:35 +12:00
dependabot[bot]
2589e6d919 Bump nconf from 0.12.1 to 0.13.0
Bumps [nconf](https://github.com/flatiron/nconf) from 0.12.1 to 0.13.0.
- [Release notes](https://github.com/flatiron/nconf/releases)
- [Changelog](https://github.com/indexzero/nconf/blob/v0.13.0/CHANGELOG.md)
- [Commits](https://github.com/flatiron/nconf/compare/v0.12.1...v0.13.0)

---
updated-dependencies:
- dependency-name: nconf
  dependency-version: 0.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-14 21:36:06 +00:00
Trevor Buckner
99fb8faf96 Merge pull request #4044 from dbolack-ab/marked-justifiedParagraphs
Migrate the justified paragraphs extension to an NPM
2025-04-14 17:34:51 -04:00
Trevor Buckner
519da0a5c0 Remove incorrect extension calls 2025-04-14 16:44:25 -04:00
Trevor Buckner
814a70b704 Merge branch 'master' into marked-justifiedParagraphs 2025-04-14 16:37:11 -04:00
Trevor Buckner
ff72f6cbd1 Merge pull request #4046 from dbolack-ab/marked-nonbreaking-spaces
Migrate nonbreaking spaces to npm module.
2025-04-14 14:11:54 -04:00
Trevor Buckner
511f33c44d Merge branch 'master' into marked-nonbreaking-spaces 2025-04-14 13:58:14 -04:00
Trevor Buckner
b455165fd3 Merge pull request #4144 from naturalcrit/dependabot/npm_and_yarn/babel/runtime-7.27.0
Bump @babel/runtime from 7.25.9 to 7.27.0
2025-04-11 22:47:50 -04:00
Trevor Buckner
7be03ab738 Merge branch 'master' into dependabot/npm_and_yarn/babel/runtime-7.27.0 2025-04-11 22:22:13 -04:00
Víctor Losada Hernández
b287163ef7 Merge pull request #3421 from G-Ambatte/addLockRoutes-#3326
Add lock routes
2025-04-11 12:12:32 +02:00
G.Ambatte
1429674013 Merge branch 'addLockRoutes-#3326' of https://github.com/G-Ambatte/homebrewery into addLockRoutes-#3326 2025-04-11 00:26:24 +12:00
G.Ambatte
5576a76731 Close malformed string 2025-04-11 00:26:19 +12:00
Víctor Losada Hernández
9c1a0fd798 Merge branch 'master' into addLockRoutes-#3326 2025-04-10 13:46:15 +02:00
dependabot[bot]
70afa96bb0 Bump @babel/runtime from 7.25.9 to 7.27.0
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.25.9 to 7.27.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.27.0/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-version: 7.27.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-10 07:56:27 +00:00
Víctor Losada Hernández
808f4dd9a0 Merge pull request #3870 from dbolack-ab/localSnippetEditor
Snippet editor tab
2025-04-10 09:55:18 +02:00
G.Ambatte
8e74ba07fe Merge branch 'addLockRoutes-#3326' of https://github.com/G-Ambatte/homebrewery into addLockRoutes-#3326 2025-04-10 19:15:38 +12:00
G.Ambatte
0c33df1cd6 Add tests for lock API functions 2025-04-10 19:15:34 +12:00
G.Ambatte
73bb6acc14 Update locked brew test for new structure 2025-04-10 10:21:35 +12:00
G.Ambatte
654c44ebc9 Change Locked Brew Error Page to use standardized author list
Co-authored-by: Víctor Losada Hernández <5ecleric.naturalcrit@gmail.com>
2025-04-10 08:39:35 +12:00
David Bolack
a6703ef731 Clear up confusing commment and consolidate style tag. 2025-04-09 13:30:03 -05:00
G.Ambatte
04defb97b0 Tweak styling for Overwrite checkbox 2025-04-09 20:10:48 +12:00
G.Ambatte
da4f6c9307 Load lock details to the Lock Brew form 2025-04-09 17:54:51 +12:00
David Bolack
c3b0311a4b Merge branch 'marked-nonbreaking-spaces' of github.com:dbolack-ab/homebrewery into marked-nonbreaking-spaces 2025-04-08 21:50:04 -05:00
David Bolack
196f290320 Merge branch 'master' into marked-nonbreaking-spaces 2025-04-08 21:49:07 -05:00
David Bolack
b1fec69d8f Merge branch 'master' into marked-justifiedParagraphs 2025-04-08 21:47:32 -05:00
David Bolack
b7a7446f75 Merge branch 'master' into marked-definition-lists 2025-04-08 21:43:14 -05:00
David Bolack
bd9d9d4ab6 Merge branch 'master' into localSnippetEditor 2025-04-08 20:35:11 -05:00
David Bolack
c6cd6e9864 A little bit of cleanup and structure flattening
Fixes failed tests.
2025-04-08 20:29:32 -05:00
G.Ambatte
5e7e314baa Update fields returned for Lock and Review Tables 2025-04-09 11:38:57 +12:00
G.Ambatte
f2b995660a Merge branch 'master' into addLockRoutes-#3326 2025-04-09 11:36:40 +12:00
G.Ambatte
95f44f4460 Revert unnecessary change in app.js 2025-04-09 11:04:57 +12:00
G.Ambatte
bd68b9c0cb Remove unnecessary variable 2025-04-09 10:48:02 +12:00
G.Ambatte
b19d05fbf7 Remove commented out console.logs 2025-04-09 10:41:58 +12:00
G.Ambatte
dc724492ef Prevent BrewUtils from loading when it is not the current Admin tab 2025-04-09 10:26:10 +12:00
G.Ambatte
e3de7b9f01 Tweak lock styling 2025-04-09 10:17:35 +12:00
G.Ambatte
be2f1786b5 Add authors to locked brew error message 2025-04-09 09:59:34 +12:00
Trevor Buckner
99a3131724 Merge pull request #4132 from naturalcrit/dependabot/npm_and_yarn/express-5.1.0
Bump express from 4.21.2 to 5.1.0
2025-04-08 17:39:00 -04:00
dependabot[bot]
0dbbc469e1 Bump express from 4.21.2 to 5.1.0
Bumps [express](https://github.com/expressjs/express) from 4.21.2 to 5.1.0.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.21.2...v5.1.0)

---
updated-dependencies:
- dependency-name: express
  dependency-version: 5.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 21:12:15 +00:00
Trevor Buckner
d5a80cc89a Merge pull request #4123 from naturalcrit/dependabot/npm_and_yarn/body-parser-2.2.0
Bump body-parser from 1.20.3 to 2.2.0
2025-04-08 17:11:01 -04:00
dependabot[bot]
0be5c6c576 Bump body-parser from 1.20.3 to 2.2.0
Bumps [body-parser](https://github.com/expressjs/body-parser) from 1.20.3 to 2.2.0.
- [Release notes](https://github.com/expressjs/body-parser/releases)
- [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/body-parser/compare/1.20.3...v2.2.0)

---
updated-dependencies:
- dependency-name: body-parser
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 20:57:29 +00:00
Trevor Buckner
d7b478e830 Merge pull request #4141 from naturalcrit/dependabot/npm_and_yarn/stylelint-config-recommended-16.0.0
Bump stylelint-config-recommended from 15.0.0 to 16.0.0
2025-04-08 16:56:14 -04:00
Trevor Buckner
3ce76f450c Merge branch 'master' into dependabot/npm_and_yarn/stylelint-config-recommended-16.0.0 2025-04-08 14:48:22 -04:00
Trevor Buckner
ad04c68596 Merge pull request #4136 from naturalcrit/dependabot/npm_and_yarn/eslint-plugin-react-7.37.5
Bump eslint-plugin-react from 7.37.4 to 7.37.5
2025-04-08 14:48:03 -04:00
Trevor Buckner
0d8bf5f0aa Merge branch 'master' into dependabot/npm_and_yarn/eslint-plugin-react-7.37.5 2025-04-08 14:43:11 -04:00
dependabot[bot]
075fdb194e Bump stylelint-config-recommended from 15.0.0 to 16.0.0
Bumps [stylelint-config-recommended](https://github.com/stylelint/stylelint-config-recommended) from 15.0.0 to 16.0.0.
- [Release notes](https://github.com/stylelint/stylelint-config-recommended/releases)
- [Changelog](https://github.com/stylelint/stylelint-config-recommended/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint-config-recommended/compare/15.0.0...16.0.0)

---
updated-dependencies:
- dependency-name: stylelint-config-recommended
  dependency-version: 16.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 18:41:53 +00:00
Trevor Buckner
6ab1b7705a Merge pull request #4138 from naturalcrit/dependabot/npm_and_yarn/react-router-7.5.0
Bump react-router from 7.4.0 to 7.5.0
2025-04-08 14:40:31 -04:00
Trevor Buckner
9151b8c575 Merge branch 'master' into dependabot/npm_and_yarn/react-router-7.5.0 2025-04-08 14:04:16 -04:00
Trevor Buckner
d5186a03e9 Merge pull request #4140 from naturalcrit/dependabot/npm_and_yarn/stylelint-16.18.0
Bump stylelint from 16.17.0 to 16.18.0
2025-04-08 14:04:00 -04:00
dependabot[bot]
b461ac0a68 Bump react-router from 7.4.0 to 7.5.0
Bumps [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) from 7.4.0 to 7.5.0.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router@7.5.0/packages/react-router)

---
updated-dependencies:
- dependency-name: react-router
  dependency-version: 7.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 17:55:07 +00:00
Trevor Buckner
477c9d1555 Merge branch 'master' into dependabot/npm_and_yarn/stylelint-16.18.0 2025-04-08 13:54:07 -04:00
Trevor Buckner
ea365e18f4 Merge pull request #4139 from naturalcrit/dependabot/npm_and_yarn/eslint-9.24.0
Bump eslint from 9.23.0 to 9.24.0
2025-04-08 13:53:45 -04:00
dependabot[bot]
512eedfc39 Bump stylelint from 16.17.0 to 16.18.0
Bumps [stylelint](https://github.com/stylelint/stylelint) from 16.17.0 to 16.18.0.
- [Release notes](https://github.com/stylelint/stylelint/releases)
- [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint/compare/16.17.0...16.18.0)

---
updated-dependencies:
- dependency-name: stylelint
  dependency-version: 16.18.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 17:51:04 +00:00
dependabot[bot]
518bc7030d Bump eslint from 9.23.0 to 9.24.0
Bumps [eslint](https://github.com/eslint/eslint) from 9.23.0 to 9.24.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.23.0...v9.24.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 9.24.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 17:51:04 +00:00
Trevor Buckner
ae8dc61423 Merge pull request #4135 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.13.2
Bump mongoose from 8.13.0 to 8.13.2
2025-04-08 13:49:41 -04:00
Trevor Buckner
b89532caa1 Merge branch 'master' into dependabot/npm_and_yarn/eslint-plugin-react-7.37.5 2025-04-08 13:48:31 -04:00
dependabot[bot]
9a57b407a5 Bump mongoose from 8.13.0 to 8.13.2
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.13.0 to 8.13.2.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/8.13.0...8.13.2)

---
updated-dependencies:
- dependency-name: mongoose
  dependency-version: 8.13.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 17:46:52 +00:00
Trevor Buckner
776de3618a Merge pull request #4143 from naturalcrit/dependabot/npm_and_yarn/marked-15.0.8
Bump marked from 15.0.0 to 15.0.8
2025-04-08 13:45:35 -04:00
Trevor Buckner
b4d575c383 Merge branch 'master' into dependabot/npm_and_yarn/eslint-plugin-react-7.37.5 2025-04-08 13:39:15 -04:00
Trevor Buckner
dc4382d067 Merge branch 'master' into dependabot/npm_and_yarn/marked-15.0.8 2025-04-08 13:38:56 -04:00
Trevor Buckner
08e00aa38b Merge pull request #4104 from naturalcrit/fix-editor-overflow
CodeEditor overflow issue fix
2025-04-08 13:30:08 -04:00
dependabot[bot]
08946ce5d4 Bump marked from 15.0.0 to 15.0.8
Bumps [marked](https://github.com/markedjs/marked) from 15.0.0 to 15.0.8.
- [Release notes](https://github.com/markedjs/marked/releases)
- [Changelog](https://github.com/markedjs/marked/blob/master/.releaserc.json)
- [Commits](https://github.com/markedjs/marked/compare/v15.0.0...v15.0.8)

---
updated-dependencies:
- dependency-name: marked
  dependency-version: 15.0.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 17:22:50 +00:00
dependabot[bot]
75212511d2 Bump eslint-plugin-react from 7.37.4 to 7.37.5
Bumps [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) from 7.37.4 to 7.37.5.
- [Release notes](https://github.com/jsx-eslint/eslint-plugin-react/releases)
- [Changelog](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.37.4...v7.37.5)

---
updated-dependencies:
- dependency-name: eslint-plugin-react
  dependency-version: 7.37.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-08 17:22:37 +00:00
Trevor Buckner
79845f2d63 Merge branch 'master' into fix-editor-overflow 2025-04-08 13:22:26 -04:00
Trevor Buckner
c982ff546c Merge pull request #4088 from G-Ambatte/autoPageNumberBrewVariable
Add pageNumber brew variable
2025-04-08 13:21:14 -04:00
David Bolack
9f56d100aa change tab 2025-04-08 00:32:11 -05:00
David Bolack
d0c3765f8f Move Snippets store to metadata block.
Note this still stores the snippets as a string for the passed about brew object.
2025-04-07 23:22:47 -05:00
G.Ambatte
1ded1cad5a Change accessType check 2025-04-08 09:28:54 +12:00
G.Ambatte
ec6258a2a5 Add overwrite option to API function 2025-04-07 22:02:53 +12:00
G.Ambatte
f8566392f6 Add overwrite option for updating locks 2025-04-07 22:02:28 +12:00
G.Ambatte
7a1042fedd Stylelint fixes 2025-04-07 22:02:08 +12:00
G.Ambatte
61efc2d152 Tweak styling 2025-04-07 21:12:37 +12:00
G.Ambatte
f74c2049a7 Rename Edit and Share page message fields 2025-04-07 20:54:44 +12:00
G.Ambatte
7451dda632 Add locked brews table 2025-04-07 20:54:21 +12:00
G.Ambatte
ab9b151b8a Add API route to return all locked brews 2025-04-07 20:52:35 +12:00
G.Ambatte
26aa302714 Remove debugging test route 2025-04-07 20:04:45 +12:00
G.Ambatte
e2f2b2962f Revert request middleware change as it is no longer necessary 2025-04-07 17:34:31 +12:00
G.Ambatte
a218b87215 Shift remaining lock API functions to use throw 2025-04-07 16:32:33 +12:00
G.Ambatte
ef6f022ea3 Tweak lock notification styling 2025-04-07 15:59:54 +12:00
G.Ambatte
a594d45611 Remove unnecessary default option 2025-04-07 15:59:34 +12:00
G.Ambatte
4c4a023f34 Fix lock notification message 2025-04-07 15:59:06 +12:00
G.Ambatte
1e35e1096f Reduce data retrieved for brews with requested reviews 2025-04-07 14:39:33 +12:00
G.Ambatte
bd145f17da Tweak styling 2025-04-07 14:38:37 +12:00
G.Ambatte
99c342f19b Use throw in Lock API calls 2025-04-07 14:38:25 +12:00
G.Ambatte
0bca3393d4 Add LOCK header in comment 2025-04-07 08:39:33 +12:00
G.Ambatte
41bd27b573 Refactor /api/lock/review/remove 2025-04-07 08:37:35 +12:00
G.Ambatte
30430cb8cb Refactor /admin/lock/review/request 2025-04-07 08:36:33 +12:00
G.Ambatte
3cf98617f5 Refactor /api/lock/reviews 2025-04-07 08:33:39 +12:00
G.Ambatte
fa4b2ae0e3 Refactor /api/lock 2025-04-07 08:27:32 +12:00
G.Ambatte
e2b38829f2 Refactor /api/lock/count 2025-04-07 08:25:42 +12:00
G.Ambatte
0a4ac7a35a Adjust location of package.json 2025-04-06 19:48:32 +12:00
G.Ambatte
cb060ae8b1 Fix state of "Request Review" button on page load 2025-04-06 19:26:39 +12:00
G.Ambatte
98edd2740f Merge branch 'addLockRoutes-#3326' of https://github.com/G-Ambatte/homebrewery into addLockRoutes-#3326 2025-04-06 19:09:14 +12:00
G.Ambatte
82e711a344 Shift request calls to import 2025-04-06 19:09:11 +12:00
G.Ambatte
2166d55878 Merge branch 'master' into autoPageNumberBrewVariable 2025-04-06 17:27:17 +12:00
David Bolack
8e8f520eaa Fix Highlighting issue with new brew sample snippets 2025-03-31 20:50:54 -05:00
Víctor Losada Hernández
4eeaa7c650 default text for snippet tab 2025-03-31 17:11:25 +02:00
Víctor Losada Hernández
f5fc106d01 pixel frame fix 2025-03-31 00:52:41 +02:00
Víctor Losada Hernández
de1773361a final css fix 2025-03-31 00:40:00 +02:00
Víctor Losada Hernández
b9b45632b0 fix package-lock 2025-03-31 00:19:43 +02:00
Víctor Losada Hernández
2ce7c6c2be css breakpoint changed 2025-03-31 00:14:03 +02:00
Víctor Losada Hernández
4137d0dd82 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into localSnippetEditor 2025-03-31 00:12:58 +02:00
G.Ambatte
29bd8b45c3 Merge branch 'master' into addLockRoutes-#3326 2025-03-30 15:28:36 +13:00
David Bolack
9d1601f424 Seems to be working - no idea why... 2025-03-29 19:14:10 -05:00
David Bolack
7525e087ff Regression Fix WIP 2025-03-29 18:47:03 -05:00
Víctor Losada Hernández
be4991a419 Merge pull request #4124 from naturalcrit/dependabot/npm_and_yarn/stylelint-16.17.0
Bump stylelint from 16.16.0 to 16.17.0
2025-03-27 10:25:37 +01:00
G.Ambatte
ac89f428b2 Post merge fixes 2025-03-27 18:25:56 +13:00
G.Ambatte
7765cb31bf Merge branch 'master' into addLockRoutes-#3326 2025-03-27 17:43:21 +13:00
dependabot[bot]
8729407da6 Bump stylelint from 16.16.0 to 16.17.0
Bumps [stylelint](https://github.com/stylelint/stylelint) from 16.16.0 to 16.17.0.
- [Release notes](https://github.com/stylelint/stylelint/releases)
- [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint/compare/16.16.0...16.17.0)

---
updated-dependencies:
- dependency-name: stylelint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-27 03:39:34 +00:00
Trevor Buckner
eac87b65d8 Update tests and custom link renderer
v15 changes where html gets escaped and escapes plain text more consistently. Needed to update tests to match.
2025-03-26 19:26:20 -04:00
G.Ambatte
848c68689d Add NaN custom pageNumber test 2025-03-27 12:09:58 +13:00
G.Ambatte
65001c44e6 Add tests for variable page numbering 2025-03-27 12:03:22 +13:00
G.Ambatte
6ec37d3fa4 Rename HB_PageNumber to HB_pageNumber 2025-03-27 12:03:00 +13:00
G.Ambatte
9f68d60703 Merge branch 'master' into autoPageNumberBrewVariable 2025-03-27 11:48:20 +13:00
Trevor Buckner
fc8654bff5 Merge pull request #4107 from dbolack-ab/issue_4105
"Workaround" for bad \page mustache blocks
2025-03-26 18:05:14 -04:00
Trevor Buckner
ebbf7cf3a2 Merge branch 'master' into issue_4105 2025-03-26 18:03:59 -04:00
Trevor Buckner
225fcef291 up Marked to v14.1.4 2025-03-26 18:03:22 -04:00
Trevor Buckner
b460acad0d Merge pull request #4091 from naturalcrit/dependabot/npm_and_yarn/babel/core-7.26.10
Bump @babel/core from 7.26.9 to 7.26.10
2025-03-26 11:49:24 -04:00
dependabot[bot]
1c1808378b Bump @babel/core from 7.26.9 to 7.26.10
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.26.9 to 7.26.10.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.10/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-26 15:47:08 +00:00
Trevor Buckner
2d25e08040 Merge pull request #4090 from naturalcrit/dependabot/npm_and_yarn/babel/plugin-transform-runtime-7.26.10
Bump @babel/plugin-transform-runtime from 7.26.9 to 7.26.10
2025-03-26 11:45:52 -04:00
dependabot[bot]
8fc1919d7c Bump @babel/plugin-transform-runtime from 7.26.9 to 7.26.10
Bumps [@babel/plugin-transform-runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-runtime) from 7.26.9 to 7.26.10.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.10/packages/babel-plugin-transform-runtime)

---
updated-dependencies:
- dependency-name: "@babel/plugin-transform-runtime"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-26 15:40:50 +00:00
Trevor Buckner
790c17ad53 Merge pull request #4118 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.13.0
Bump mongoose from 8.12.1 to 8.13.0
2025-03-26 11:39:35 -04:00
dependabot[bot]
d315e4f008 Bump mongoose from 8.12.1 to 8.13.0
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.12.1 to 8.13.0.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/8.12.1...8.13.0)

---
updated-dependencies:
- dependency-name: mongoose
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-26 15:28:21 +00:00
Trevor Buckner
2bfc41ce30 Merge pull request #4114 from naturalcrit/dependabot/npm_and_yarn/eslint-9.23.0
Bump eslint from 9.22.0 to 9.23.0
2025-03-26 11:27:01 -04:00
dependabot[bot]
cdacaac049 Bump eslint from 9.22.0 to 9.23.0
Bumps [eslint](https://github.com/eslint/eslint) from 9.22.0 to 9.23.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.22.0...v9.23.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-26 15:26:53 +00:00
Trevor Buckner
da88fd0b3f Merge pull request #4092 from naturalcrit/dependabot/npm_and_yarn/superagent-10.2.0
Bump superagent from 10.1.1 to 10.2.0
2025-03-26 11:25:28 -04:00
dependabot[bot]
0095e4582b Bump superagent from 10.1.1 to 10.2.0
Bumps [superagent](https://github.com/ladjs/superagent) from 10.1.1 to 10.2.0.
- [Release notes](https://github.com/ladjs/superagent/releases)
- [Changelog](https://github.com/ladjs/superagent/blob/master/HISTORY.md)
- [Commits](https://github.com/ladjs/superagent/compare/v10.1.1...v10.2.0)

---
updated-dependencies:
- dependency-name: superagent
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-26 15:18:49 +00:00
Trevor Buckner
5cf0945fa7 Merge pull request #4108 from naturalcrit/dependabot/npm_and_yarn/react-router-7.4.0
Bump react-router from 7.3.0 to 7.4.0
2025-03-26 11:17:24 -04:00
dependabot[bot]
8e10e9dea9 Bump react-router from 7.3.0 to 7.4.0
Bumps [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) from 7.3.0 to 7.4.0.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router@7.4.0/packages/react-router)

---
updated-dependencies:
- dependency-name: react-router
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-26 15:13:56 +00:00
Trevor Buckner
07f6439093 Merge pull request #4103 from naturalcrit/dependabot/npm_and_yarn/nanoid-5.1.5
Bump nanoid from 5.1.3 to 5.1.5
2025-03-26 11:12:32 -04:00
Trevor Buckner
8c979b8545 Merge branch 'master' into dependabot/npm_and_yarn/nanoid-5.1.5 2025-03-26 11:10:11 -04:00
Trevor Buckner
4f3929c658 Merge pull request #4111 from naturalcrit/dependabot/npm_and_yarn/googleapis/drive-11.0.0
Bump @googleapis/drive from 8.16.0 to 11.0.0
2025-03-26 11:09:52 -04:00
dependabot[bot]
c8cf9e3002 Bump @googleapis/drive from 8.16.0 to 11.0.0
Bumps [@googleapis/drive](https://github.com/googleapis/google-api-nodejs-client) from 8.16.0 to 11.0.0.
- [Release notes](https://github.com/googleapis/google-api-nodejs-client/releases)
- [Changelog](https://github.com/googleapis/google-api-nodejs-client/blob/11.0.0/CHANGELOG.md)
- [Commits](https://github.com/googleapis/google-api-nodejs-client/compare/drive-v8.16.0...11.0.0)

---
updated-dependencies:
- dependency-name: "@googleapis/drive"
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-26 15:06:30 +00:00
dependabot[bot]
7bc323c92c Bump nanoid from 5.1.3 to 5.1.5
Bumps [nanoid](https://github.com/ai/nanoid) from 5.1.3 to 5.1.5.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/5.1.3...5.1.5)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-26 15:06:16 +00:00
Trevor Buckner
4d141fa6a3 Merge pull request #4110 from naturalcrit/dependabot/npm_and_yarn/supertest-7.1.0
Bump supertest from 7.0.0 to 7.1.0
2025-03-26 11:04:54 -04:00
Trevor Buckner
25d1db5584 Merge branch 'master' into dependabot/npm_and_yarn/supertest-7.1.0 2025-03-26 11:00:12 -04:00
David Bolack
565d58bb31 Add clearing for snippets 2025-03-24 21:06:55 -05:00
David Bolack
2f95cc5f45 Merge branch 'master' into localSnippetEditor 2025-03-24 15:10:25 -05:00
David Bolack
a62588a4c9 Fix fouled up regex that only worked by accident 2025-03-24 14:58:14 -05:00
Víctor Losada Hernández
4afef9d3b3 Merge branch 'master' into issue_4105 2025-03-24 13:14:31 +01:00
Víctor Losada Hernández
a887b87350 Merge pull request #3664 from dbolack-ab/SnippetsReorg
Relocate general purpose snippets still in 5ePHB theme to Blank
2025-03-24 13:14:01 +01:00
Víctor Losada Hernández
3672285e92 additional linting pass through themes 2025-03-24 13:09:56 +01:00
Víctor Losada Hernández
ea4dd5defd Merge branch 'master' of https://github.com/naturalcrit/homebrewery into SnippetsReorg 2025-03-24 13:07:16 +01:00
David Bolack
712ee111d4 Move Dropcap settings back to PHB 2025-03-23 20:48:30 -05:00
David Bolack
0fc7571c35 Somehow this change did not get commited or pushed.
This swaps the if block for an optional chaining
2025-03-23 20:41:14 -05:00
David Bolack
84cdf6a14e Merge branch 'master' into issue_4105 2025-03-23 20:34:34 -05:00
Víctor Losada Hernández
1f77656a1c Merge branch 'master' into marked-nonbreaking-spaces 2025-03-22 23:44:58 +01:00
Víctor Losada Hernández
bbd95ffe2a Merge pull request #4014 from dbolack-ab/issue_3659
Fix space issue in image tags
2025-03-22 23:40:23 +01:00
Víctor Losada Hernández
e3a7e1f403 Merge branch 'master' into issue_3659 2025-03-22 16:39:49 +01:00
Víctor Losada Hernández
746c71f44b Merge pull request #4113 from G-Ambatte/fixLocalInstallCORS
Fix CORS regex test for local installs
2025-03-22 16:31:00 +01:00
G.Ambatte
cb61891450 Change local regex testing 2025-03-22 12:31:38 +13:00
dependabot[bot]
481219402c Bump supertest from 7.0.0 to 7.1.0
Bumps [supertest](https://github.com/ladjs/supertest) from 7.0.0 to 7.1.0.
- [Release notes](https://github.com/ladjs/supertest/releases)
- [Commits](https://github.com/ladjs/supertest/compare/v7.0.0...v7.1.0)

---
updated-dependencies:
- dependency-name: supertest
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-21 03:29:48 +00:00
David Bolack
48285e6738 Add validation to ensure \page mustaches have return valid contents from the lexar 2025-03-19 17:45:47 -05:00
Víctor Losada Hernández
551763fecb initial commit 2025-03-19 14:04:29 +01:00
Víctor Losada Hernández
d2507fe99f remove empty lines 2025-03-19 13:29:16 +01:00
Víctor Losada Hernández
07ff9a114e lint 2025-03-19 13:26:58 +01:00
Víctor Losada Hernández
962d98543e initial fix 2025-03-19 10:56:16 +01:00
Víctor Losada Hernández
7f17887e0e Merge pull request #4102 from naturalcrit/linting
Linting the entire project
2025-03-18 20:21:51 +01:00
Víctor Losada Hernández
8e37806791 jsx files 2025-03-18 19:47:49 +01:00
Víctor Losada Hernández
f076e05f49 js files 2025-03-18 19:46:11 +01:00
Víctor Losada Hernández
163e3927b5 style lint 2025-03-18 19:38:58 +01:00
Víctor Losada Hernández
0234de12bb Merge pull request #4097 from naturalcrit/dependabot/npm_and_yarn/stylelint-16.16.0
Bump stylelint from 16.15.0 to 16.16.0
2025-03-18 13:22:49 +01:00
dependabot[bot]
21be329e77 Bump stylelint from 16.15.0 to 16.16.0
Bumps [stylelint](https://github.com/stylelint/stylelint) from 16.15.0 to 16.16.0.
- [Release notes](https://github.com/stylelint/stylelint/releases)
- [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint/compare/16.15.0...16.16.0)

---
updated-dependencies:
- dependency-name: stylelint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-18 11:45:04 +00:00
Víctor Losada Hernández
314c5cef7e Merge pull request #4100 from naturalcrit/update-to-3.18.1
Up to v3.18.1
2025-03-18 12:43:42 +01:00
Víctor Losada Hernández
ec36365697 add letter size snippet 2025-03-18 12:39:31 +01:00
Víctor Losada Hernández
f47a32067e add changelog 2025-03-18 12:39:24 +01:00
Víctor Losada Hernández
fef571b1d6 version bump + npm audit fix 2025-03-18 12:13:17 +01:00
Víctor Losada Hernández
80b33e3fed Merge pull request #4099 from G-Ambatte/revertColonChange
Fix minor v3.18.0 issues
2025-03-18 12:07:03 +01:00
Víctor Losada Hernández
8b67118303 Merge branch 'master' into revertColonChange 2025-03-18 09:40:23 +01:00
Víctor Losada Hernández
d5969a6573 Merge pull request #4095 from naturalcrit/allow-all-origins-when-local
[LOCAL]: Fix cors issue in local network
2025-03-18 09:38:15 +01:00
Víctor Losada Hernández
547682a59a Merge branch 'master' into allow-all-origins-when-local 2025-03-18 09:31:10 +01:00
G.Ambatte
b2903137eb Fix hard-breaks tests 2025-03-18 11:08:15 +13:00
G.Ambatte
7329c69cfd Update tests 2025-03-18 11:02:09 +13:00
G.Ambatte
83a095923e Add columnSplit to end of each page for older Chrome versions 2025-03-18 10:51:09 +13:00
G.Ambatte
a44056a64b Restore blank styling 2025-03-18 10:02:29 +13:00
G.Ambatte
a705e3b9d8 Change : from br to div class='blank' 2025-03-18 09:54:27 +13:00
David Bolack
d55a6cfd88 Merge branch 'issue_3659' of github.com:dbolack-ab/homebrewery into issue_3659 2025-03-14 19:52:10 -05:00
David Bolack
86ff2ab96b Add tests for regression 2025-03-14 19:51:35 -05:00
David Bolack
a960299612 Merge branch 'master' into issue_3659 2025-03-14 19:50:32 -05:00
David Bolack
99efe7f06b Add default value for document name in snippets menu 2025-03-14 19:26:37 -05:00
David Bolack
b605346c7d Fix regeression in snippets 2025-03-14 18:36:18 -05:00
David Bolack
ab6c1ae402 Merge branch 'master' into localSnippetEditor 2025-03-14 17:44:32 -05:00
Víctor Losada Hernández
94bcc8e997 update to include all possible local adresses 2025-03-13 22:59:23 +01:00
Víctor Losada Hernández
72c2857237 initial fix 2025-03-13 22:32:12 +01:00
G.Ambatte
20f0d16a58 Fix missed var name update 2025-03-14 08:18:30 +13:00
G.Ambatte
9a26626412 Change automatic var name to HB_PageNumber 2025-03-14 08:12:30 +13:00
G.Ambatte
f4afc91df7 Rename toWordsCamel to toWordsCaps 2025-03-14 08:08:30 +13:00
G.Ambatte
baafb6d2f9 Tweak camelcase function 2025-03-13 14:54:45 +13:00
G.Ambatte
8b9e084b17 Add variable page numbering snippet 2025-03-13 14:16:41 +13:00
G.Ambatte
44a01f27fe Add tests for custom math functions 2025-03-13 13:58:06 +13:00
G.Ambatte
543d18f9d9 Add written number functions 2025-03-13 12:22:08 +13:00
G.Ambatte
7371f57ded Add written number package 2025-03-13 12:21:53 +13:00
G.Ambatte
ee543b7090 Add int to char functions 2025-03-13 11:59:12 +13:00
G.Ambatte
b67eb59461 Add Roman numerals function 2025-03-13 10:50:18 +13:00
G.Ambatte
e787a68859 Add roman numeral package 2025-03-13 10:49:59 +13:00
G.Ambatte
edc4f8ec63 Change to increment previous 2025-03-12 13:28:43 +13:00
G.Ambatte
aec958249a Add automatic pageNumber variable to globalVarsList 2025-03-12 10:21:22 +13:00
Trevor Buckner
f083391efd Merge pull request #4080 from naturalcrit/fix-vault-pagination
Vault fixes
2025-03-10 19:38:06 -04:00
Trevor Buckner
2c63c01723 Update Changelog 2025-03-10 19:37:32 -04:00
Trevor Buckner
85af5bbd27 Merge branch 'master' into fix-vault-pagination 2025-03-10 19:34:37 -04:00
Trevor Buckner
17f26b803c Merge pull request #4086 from naturalcrit/non-content_spans_dont_impact_flow
Style .frontCover and .insideCover to position : absolute
2025-03-10 19:32:27 -04:00
Trevor Buckner
8093380e0c .frontCover and .insideCover to position : absolute
.partCover and .backCover are already position : absolute.
2025-03-10 19:15:16 -04:00
Trevor Buckner
07f0cef67c Up marked-extended-tables to v2.0.1 2025-03-10 17:05:45 -04:00
Trevor Buckner
4241aa535b Merge pull request #4083 from G-Ambatte/killDomPurify
Remove unused DOMPurify package
2025-03-10 15:03:13 -04:00
Trevor Buckner
4c85f3ec4b Merge branch 'master' into killDomPurify 2025-03-10 15:02:21 -04:00
Trevor Buckner
57f273a276 Merge pull request #4084 from naturalcrit/v3.18.0
v3.18.0
2025-03-10 14:55:58 -04:00
Trevor Buckner
e159e57222 Changelog up to v3.18.0 2025-03-10 14:54:30 -04:00
G.Ambatte
d4991164e9 Merge branch 'master' into killDomPurify 2025-03-11 07:40:34 +13:00
Trevor Buckner
baa1ed2b53 Merge pull request #4082 from naturalcrit/dependabot/npm_and_yarn/nanoid-5.1.3
Bump nanoid from 5.1.2 to 5.1.3
2025-03-10 14:38:17 -04:00
G.Ambatte
f1e291e313 Remove unused DOMPurify package 2025-03-11 07:32:58 +13:00
dependabot[bot]
814f3a6c20 Bump nanoid from 5.1.2 to 5.1.3
Bumps [nanoid](https://github.com/ai/nanoid) from 5.1.2 to 5.1.3.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/5.1.2...5.1.3)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-10 18:03:14 +00:00
Trevor Buckner
43dc1bed7d Merge pull request #4081 from naturalcrit/dependabot/npm_and_yarn/eslint-9.22.0
Bump eslint from 9.21.0 to 9.22.0
2025-03-10 14:01:55 -04:00
dependabot[bot]
313492a344 Bump eslint from 9.21.0 to 9.22.0
Bumps [eslint](https://github.com/eslint/eslint) from 9.21.0 to 9.22.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.21.0...v9.22.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-10 03:19:07 +00:00
Víctor Losada Hernández
4cd5c13841 lint styles 2025-03-08 19:38:42 +01:00
Víctor Losada Hernández
c7a19857dd remove visual glitch when performing aa search 2025-03-08 19:35:12 +01:00
Víctor Losada Hernández
b07317b0f7 display pagination on top as well 2025-03-08 19:32:26 +01:00
Víctor Losada Hernández
c0eef7530e added transition to smooth change 2025-03-08 17:27:34 +01:00
Víctor Losada Hernández
55618a10b9 fix container query 2025-03-08 17:16:23 +01:00
Víctor Losada Hernández
5f48b30449 lint styles 2025-03-08 13:46:11 +01:00
Víctor Losada Hernández
e523886345 lint server api 2025-03-08 13:42:49 +01:00
Víctor Losada Hernández
4918dc5239 manual lint 2025-03-08 13:42:29 +01:00
Víctor Losada Hernández
a0de6295c7 fix space in help text 2025-03-08 13:39:06 +01:00
Víctor Losada Hernández
3db778a665 fix 2 column issue 2025-03-08 13:30:06 +01:00
Víctor Losada Hernández
a7eef65694 fix pagination not correctly updating 2025-03-08 13:16:41 +01:00
Trevor Buckner
8d1464a2c4 Merge pull request #4078 from naturalcrit/dependabot/npm_and_yarn/react-router-7.3.0
Bump react-router from 7.2.0 to 7.3.0
2025-03-06 23:39:16 -05:00
dependabot[bot]
552cf30863 Bump react-router from 7.2.0 to 7.3.0
Bumps [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) from 7.2.0 to 7.3.0.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router@7.3.0/packages/react-router)

---
updated-dependencies:
- dependency-name: react-router
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-07 03:48:38 +00:00
Trevor Buckner
20baa9984f Cleanup 2025-03-06 11:13:39 -05:00
Trevor Buckner
4daa8042a2 Merge pull request #4005 from dbolack-ab/issue_3206
Workaround for unclosed <pre> blocks before rendering.
2025-03-05 20:13:45 -05:00
Trevor Buckner
51e79c2c5f Remove extra column break entirely
Not needed with change to column-fill auto

See https://github.com/naturalcrit/homebrewery/pull/3013
2025-03-05 20:13:35 -05:00
Trevor Buckner
88e8140b60 Merge branch 'master' into pr/4005 2025-03-05 19:59:36 -05:00
Trevor Buckner
252698b135 Merge pull request #4077 from naturalcrit/Update-Marked.js-to-v14.0.0
Update to Marked v14
2025-03-05 15:50:08 -05:00
Trevor Buckner
21f1704626 Update to Marked v14 2025-03-05 15:40:14 -05:00
David Bolack
7f128b0dae Merge branch 'master' into issue_3659 2025-03-05 12:16:47 -06:00
David Bolack
869d69b986 Merge branch 'marked-justifiedParagraphs' of github.com:dbolack-ab/homebrewery into marked-justifiedParagraphs 2025-03-05 12:12:31 -06:00
David Bolack
c04cc94570 Merge branch 'master' into marked-justifiedParagraphs 2025-03-05 12:09:56 -06:00
David Bolack
46093ba6ba Update package versions 2025-03-05 11:23:29 -06:00
David Bolack
19556d9f36 Merge branch 'master' into issue_3206 2025-03-05 11:09:03 -06:00
Trevor Buckner
0d4d97c5c5 Merge pull request #4070 from naturalcrit/dependabot/npm_and_yarn/stylelint-16.15.0
Bump stylelint from 16.14.1 to 16.15.0
2025-03-05 12:00:37 -05:00
David Bolack
55f333a9e5 Changed tactic per suggestion
Unsure if the test is absolutely necessary.
2025-03-05 10:52:43 -06:00
dependabot[bot]
2361cdeadc Bump stylelint from 16.14.1 to 16.15.0
Bumps [stylelint](https://github.com/stylelint/stylelint) from 16.14.1 to 16.15.0.
- [Release notes](https://github.com/stylelint/stylelint/releases)
- [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint/compare/16.14.1...16.15.0)

---
updated-dependencies:
- dependency-name: stylelint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-05 16:47:30 +00:00
Trevor Buckner
aeae704173 Merge pull request #4071 from naturalcrit/dependabot/npm_and_yarn/core-js-3.41.0
Bump core-js from 3.40.0 to 3.41.0
2025-03-05 11:46:08 -05:00
David Bolack
c420410904 Merge branch 'master' into issue_3206 2025-03-05 10:37:58 -06:00
dependabot[bot]
0daf8c5c83 Bump core-js from 3.40.0 to 3.41.0
Bumps [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) from 3.40.0 to 3.41.0.
- [Release notes](https://github.com/zloirock/core-js/releases)
- [Changelog](https://github.com/zloirock/core-js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zloirock/core-js/commits/v3.41.0/packages/core-js)

---
updated-dependencies:
- dependency-name: core-js
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-05 16:37:35 +00:00
David Bolack
4b9b1ec9ac Merge branch 'master' into marked-definition-lists 2025-03-05 10:36:50 -06:00
Trevor Buckner
924d014c69 Merge pull request #4074 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.12.1
Bump mongoose from 8.11.0 to 8.12.1
2025-03-05 11:36:18 -05:00
dependabot[bot]
8992cf8251 Bump mongoose from 8.11.0 to 8.12.1
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.11.0 to 8.12.1.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/8.11.0...8.12.1)

---
updated-dependencies:
- dependency-name: mongoose
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-05 16:26:08 +00:00
Trevor Buckner
7c6aa0ffec Merge pull request #4075 from naturalcrit/dependabot/npm_and_yarn/marked-emoji-2.0.0
Bump marked-emoji from 1.4.3 to 2.0.0
2025-03-05 11:24:42 -05:00
Trevor Buckner
ebe64c508f Merge branch 'master' into dependabot/npm_and_yarn/marked-emoji-2.0.0 2025-03-05 11:20:08 -05:00
Trevor Buckner
f3514cfea6 Merge pull request #4076 from MollyMaclachlan/master
Fix formatting inconsistencies in 5ePHB monster stat block
2025-03-05 10:52:29 -05:00
Trevor Buckner
8ed25fb7cf Merge branch 'master' into master 2025-03-05 10:52:21 -05:00
Trevor Buckner
762cd58d52 Update usage of Marked-extended-tables options 2025-03-05 10:49:46 -05:00
Murdo B. Maclachlan
477f706eb9 Fix formatting inconsistencies in 5ePHB monster stat block
Closes #4073. Fixes minor formatting inconsistencies in the monster stat block between the 5ePHB theme and actual 5e manuals.
2025-03-05 13:01:31 +00:00
dependabot[bot]
edcf9979a7 Bump marked-emoji from 1.4.3 to 2.0.0
Bumps [marked-emoji](https://github.com/UziTech/marked-emoji) from 1.4.3 to 2.0.0.
- [Release notes](https://github.com/UziTech/marked-emoji/releases)
- [Changelog](https://github.com/UziTech/marked-emoji/blob/main/release.config.cjs)
- [Commits](https://github.com/UziTech/marked-emoji/compare/v1.4.3...v2.0.0)

---
updated-dependencies:
- dependency-name: marked-emoji
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-05 03:48:15 +00:00
David Bolack
01f075d3f5 Merge branch 'master' into marked-definition-lists 2025-02-28 17:06:44 -06:00
David Bolack
de18a53efe Update markd-definition-lists 2025-02-28 17:03:51 -06:00
Trevor Buckner
ef2beec590 Merge pull request #4068 from naturalcrit/dependabot/npm_and_yarn/googleapis/drive-8.16.0
Bump @googleapis/drive from 8.14.0 to 8.16.0
2025-02-28 16:01:32 -05:00
dependabot[bot]
c10559ba5f Bump @googleapis/drive from 8.14.0 to 8.16.0
Bumps [@googleapis/drive](https://github.com/googleapis/google-api-nodejs-client) from 8.14.0 to 8.16.0.
- [Release notes](https://github.com/googleapis/google-api-nodejs-client/releases)
- [Changelog](https://github.com/googleapis/google-api-nodejs-client/blob/main/release-please-config.json)
- [Commits](https://github.com/googleapis/google-api-nodejs-client/compare/drive-v8.14.0...drive-v8.16.0)

---
updated-dependencies:
- dependency-name: "@googleapis/drive"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-28 19:59:46 +00:00
Trevor Buckner
69c633dabe Merge pull request #4067 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.11.0
Bump mongoose from 8.10.2 to 8.11.0
2025-02-28 14:58:32 -05:00
dependabot[bot]
8bdcdcd510 Bump mongoose from 8.10.2 to 8.11.0
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.10.2 to 8.11.0.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/8.10.2...8.11.0)

---
updated-dependencies:
- dependency-name: mongoose
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-28 13:01:06 +00:00
Trevor Buckner
ce03f598b2 Merge pull request #4066 from naturalcrit/dependabot/npm_and_yarn/marked-extended-tables-2.0.0
Bump marked-extended-tables from 1.1.0 to 2.0.0
2025-02-28 07:59:47 -05:00
David Bolack
caca578709 Merge branch 'master' into marked-definition-lists 2025-02-28 00:00:47 -06:00
dependabot[bot]
addbf19682 Bump marked-extended-tables from 1.1.0 to 2.0.0
Bumps [marked-extended-tables](https://github.com/calculuschild/marked-extended-tables) from 1.1.0 to 2.0.0.
- [Release notes](https://github.com/calculuschild/marked-extended-tables/releases)
- [Changelog](https://github.com/calculuschild/marked-extended-tables/blob/main/release.config.cjs)
- [Commits](https://github.com/calculuschild/marked-extended-tables/compare/v1.1.0...v2.0.0)

---
updated-dependencies:
- dependency-name: marked-extended-tables
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-27 03:57:55 +00:00
Trevor Buckner
479aae4b2f Merge pull request #4065 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.10.2
Bump mongoose from 8.10.1 to 8.10.2
2025-02-26 10:46:36 -05:00
Trevor Buckner
4b6652c470 Merge pull request #4052 from G-Ambatte/fixSaveBug
Fix bug in save logic
2025-02-26 10:45:53 -05:00
dependabot[bot]
e9d1209ce8 Bump mongoose from 8.10.1 to 8.10.2
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.10.1 to 8.10.2.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/8.10.1...8.10.2)

---
updated-dependencies:
- dependency-name: mongoose
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-26 03:33:15 +00:00
G.Ambatte
7c62e49767 Merge branch 'master' into fixSaveBug 2025-02-25 22:39:18 +13:00
G.Ambatte
9b0da36365 Rework isPending & hasChanges 2025-02-25 19:56:23 +13:00
Trevor Buckner
1391a9053d Merge pull request #4062 from naturalcrit/dependabot/npm_and_yarn/globals-16.0.0
Bump globals from 15.15.0 to 16.0.0
2025-02-24 14:21:59 -05:00
dependabot[bot]
fee88d1d47 Bump globals from 15.15.0 to 16.0.0
Bumps [globals](https://github.com/sindresorhus/globals) from 15.15.0 to 16.0.0.
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v15.15.0...v16.0.0)

---
updated-dependencies:
- dependency-name: globals
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-24 18:39:19 +00:00
Trevor Buckner
a47dc51bd1 Merge pull request #4064 from naturalcrit/dependabot/npm_and_yarn/eslint-9.21.0
Bump eslint from 9.20.1 to 9.21.0
2025-02-24 13:37:52 -05:00
dependabot[bot]
cfb9e1afa2 Bump eslint from 9.20.1 to 9.21.0
Bumps [eslint](https://github.com/eslint/eslint) from 9.20.1 to 9.21.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.20.1...v9.21.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-24 18:34:06 +00:00
Trevor Buckner
540a0a7a36 Merge pull request #4063 from naturalcrit/dependabot/npm_and_yarn/nanoid-5.1.2
Bump nanoid from 5.1.0 to 5.1.2
2025-02-24 13:32:41 -05:00
dependabot[bot]
b7e422ac06 Bump nanoid from 5.1.0 to 5.1.2
Bumps [nanoid](https://github.com/ai/nanoid) from 5.1.0 to 5.1.2.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/5.1.0...5.1.2)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-24 03:57:12 +00:00
David Bolack
06e3fd6248 Merge branch 'master' into marked-nonbreaking-spaces 2025-02-21 14:06:53 -06:00
Trevor Buckner
f3315d654e tabs to spaces 2025-02-21 14:37:33 -05:00
Víctor Losada Hernández
df5eeb5c97 Merge pull request #4023 from 5e-Cleric/fix-snippet-styling
Small adjustments to the PHB theme snippets
2025-02-20 21:18:24 +01:00
Víctor Losada Hernández
e2de225625 Merge branch 'fix-snippet-styling' of https://github.com/5e-Cleric/homebrewery into fix-snippet-styling 2025-02-20 20:44:32 +01:00
Víctor Losada Hernández
5b7d5bee24 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into fix-snippet-styling 2025-02-20 20:44:18 +01:00
Trevor Buckner
18eb3ec643 Merge branch 'master' into fix-snippet-styling 2025-02-20 13:35:05 -05:00
Trevor Buckner
5f9cc48fe1 Merge pull request #4060 from naturalcrit/dependabot/npm_and_yarn/react-router-7.2.0
Bump react-router from 7.1.5 to 7.2.0
2025-02-20 13:02:43 -05:00
dependabot[bot]
56d1855518 Bump react-router from 7.1.5 to 7.2.0
Bumps [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) from 7.1.5 to 7.2.0.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router@7.2.0/packages/react-router)

---
updated-dependencies:
- dependency-name: react-router
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-20 03:59:59 +00:00
Trevor Buckner
758b508955 Merge pull request #4058 from naturalcrit/Change_Hardbreak_-_to_-br-
Change hardbreak  to `<br>`
2025-02-19 16:25:54 -05:00
Trevor Buckner
3221b40903 Merge pull request #4043 from dbolack-ab/marked-subsuper
Move Superscript/Subscript functions into their own module
2025-02-19 16:25:31 -05:00
Trevor Buckner
39a49a6d62 Merge branch 'master' into marked-subsuper 2025-02-19 16:21:04 -05:00
Trevor Buckner
02f63e0b02 Merge branch 'master' into Change_Hardbreak_-_to_-br- 2025-02-19 16:15:49 -05:00
Trevor Buckner
0ba943ceb0 Update Test Cases 2025-02-19 16:15:31 -05:00
Trevor Buckner
578a8d7eba Add \n after each <br> 2025-02-19 16:00:37 -05:00
Trevor Buckner
9a9d7a6b5e Merge pull request #3984 from G-Ambatte/headerNaxExtension
Extend header nav to skip headers in "top level" pages
2025-02-19 15:54:32 -05:00
Trevor Buckner
917b6b3145 Shrinking down some logic 2025-02-19 15:52:52 -05:00
Trevor Buckner
b36376f9e8 Linting 2025-02-19 13:44:28 -05:00
David Bolack
58a22750c5 Update package-lock 2025-02-19 11:38:38 -06:00
David Bolack
df1b601de7 Merge branch 'master' into marked-subsuper 2025-02-19 11:37:38 -06:00
David Bolack
1ed44282e3 Merge branch 'master' of github.com:naturalcrit/homebrewery 2025-02-19 11:36:51 -06:00
G.Ambatte
f421ce1d93 Fix missing depth styling 2025-02-19 16:10:27 +13:00
G.Ambatte
ca0f18acd6 Update getTextContent function to getHeaderContent 2025-02-19 15:22:08 +13:00
G.Ambatte
87d76ea8f6 Change topLevelPages functions for cover pages 2025-02-19 13:46:35 +13:00
G.Ambatte
9f5a29099c Address requested changes 2025-02-19 13:24:07 +13:00
G.Ambatte
0360d6b6c5 Merge branch 'master' into headerNaxExtension 2025-02-19 07:59:38 +13:00
Trevor Buckner
8d2057431b Merge pull request #3923 from dbolack-ab/writeinBrewTheme
WIP: User Brew Theme Write-in
2025-02-18 13:54:34 -05:00
Trevor Buckner
ee0d737b9c Merge branch 'master' into writeinBrewTheme 2025-02-18 13:44:56 -05:00
Trevor Buckner
cb27b26103 Merge pull request #4055 from naturalcrit/dependabot/npm_and_yarn/babel/preset-env-7.26.9
Bump @babel/preset-env from 7.26.8 to 7.26.9
2025-02-18 09:39:19 -05:00
dependabot[bot]
0564fb82f6 Bump @babel/preset-env from 7.26.8 to 7.26.9
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.26.8 to 7.26.9.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.9/packages/babel-preset-env)

---
updated-dependencies:
- dependency-name: "@babel/preset-env"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-18 02:06:21 +00:00
Trevor Buckner
5596f2d9da Merge pull request #4056 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.10.1
Bump mongoose from 8.10.0 to 8.10.1
2025-02-17 21:05:06 -05:00
dependabot[bot]
a11b67f139 Bump mongoose from 8.10.0 to 8.10.1
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.10.0 to 8.10.1.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/8.10.0...8.10.1)

---
updated-dependencies:
- dependency-name: mongoose
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-17 22:02:38 +00:00
Trevor Buckner
17717ea2a9 Merge pull request #4053 from naturalcrit/dependabot/npm_and_yarn/nanoid-5.1.0
Bump nanoid from 5.0.9 to 5.1.0
2025-02-17 17:01:18 -05:00
dependabot[bot]
c15e7b2da3 Bump nanoid from 5.0.9 to 5.1.0
Bumps [nanoid](https://github.com/ai/nanoid) from 5.0.9 to 5.1.0.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/5.0.9...5.1.0)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-17 21:53:00 +00:00
Trevor Buckner
fcca56f502 Merge pull request #4054 from naturalcrit/dependabot/npm_and_yarn/babel/core-7.26.9
Bump @babel/core from 7.26.8 to 7.26.9
2025-02-17 16:51:47 -05:00
dependabot[bot]
68f66b2bac Bump @babel/core from 7.26.8 to 7.26.9
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.26.8 to 7.26.9.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.9/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-17 21:47:15 +00:00
Trevor Buckner
0d71f291e7 Merge pull request #4057 from naturalcrit/dependabot/npm_and_yarn/babel/plugin-transform-runtime-7.26.9
Bump @babel/plugin-transform-runtime from 7.26.8 to 7.26.9
2025-02-17 16:45:56 -05:00
Trevor Buckner
fc065d250b Fix useSansSerif() case (monster stat blocks, descriptiven note, etc.)
Removed line-height value that affects only <br> height. Doesn't impact anywhere else because they all have their own explicit line-heights already.
2025-02-17 16:27:22 -05:00
Trevor Buckner
01d93b98d5 Remove LESS styling for .blank 2025-02-17 15:09:04 -05:00
Trevor Buckner
f5aa37bd5e Change Marked extension to emit <br> instead of <div class="blank"> 2025-02-17 15:08:47 -05:00
dependabot[bot]
d6d445dad5 Bump @babel/plugin-transform-runtime from 7.26.8 to 7.26.9
Bumps [@babel/plugin-transform-runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-runtime) from 7.26.8 to 7.26.9.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.9/packages/babel-plugin-transform-runtime)

---
updated-dependencies:
- dependency-name: "@babel/plugin-transform-runtime"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-17 03:58:22 +00:00
G.Ambatte
1af66cf571 Add default case for SAVE button text 2025-02-15 00:13:23 +13:00
G.Ambatte
2cb8b5d014 Set brew state to exactly match savedBrew 2025-02-15 00:12:55 +13:00
G.Ambatte
34a0b4eb05 Base savedBrew on current brew state, apply only updated properties from API call 2025-02-15 00:12:20 +13:00
Trevor Buckner
854a2ab35e Fix /new 2025-02-13 17:56:39 -05:00
Trevor Buckner
42accdb54f linting 2025-02-13 17:54:37 -05:00
Trevor Buckner
7e5bade4fa Merge branch 'master' into writeinBrewTheme 2025-02-13 16:21:17 -05:00
Trevor Buckner
ed30a1cd7d Update other tests to pass 2025-02-13 16:20:59 -05:00
Trevor Buckner
94f478477d Add test for missing meta:theme tag 2025-02-13 16:20:50 -05:00
Trevor Buckner
50bda9455f Immediately clear errors if a theme successfully loads 2025-02-13 15:51:22 -05:00
Trevor Buckner
d8d672fada Error message if chosen theme does not have "meta:theme" tag. 2025-02-13 15:51:06 -05:00
Trevor Buckner
bf297939dc Debounce validation popup 2025-02-13 15:01:35 -05:00
Trevor Buckner
df563b9294 Change combobox default text 2025-02-13 14:53:02 -05:00
Trevor Buckner
e584eec8c2 When clicked, combobox textbox clears 2025-02-13 14:51:45 -05:00
Trevor Buckner
557178172b Merge pull request #4050 from naturalcrit/dependabot/npm_and_yarn/elliptic-6.6.1
Bump elliptic from 6.6.0 to 6.6.1
2025-02-13 14:14:17 -05:00
Trevor Buckner
45e98debbd Remove z-index for metadata editor to not cover nav bar (errors/dropdowns/etc)
The other editor panels don't have a z-index. For some reason the metadata editor does, and it covers items from the navbar
2025-02-13 00:43:14 -05:00
Trevor Buckner
0bd5ac42b6 Remove too-small height for themes + thumbnail 2025-02-13 00:40:59 -05:00
dependabot[bot]
af729de096 Bump elliptic from 6.6.0 to 6.6.1
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.6.0 to 6.6.1.
- [Commits](https://github.com/indutny/elliptic/compare/v6.6.0...v6.6.1)

---
updated-dependencies:
- dependency-name: elliptic
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-13 05:13:33 +00:00
Trevor Buckner
40cd53fcb8 Merge pull request #4048 from naturalcrit/dependabot/npm_and_yarn/globals-15.15.0
Bump globals from 15.14.0 to 15.15.0
2025-02-13 00:12:26 -05:00
Trevor Buckner
f326d11232 Added input validation (allows Share ID or Share URL) 2025-02-13 00:05:30 -05:00
David Bolack
09ac8b8a32 Move definition list tokens to an extension 2025-02-12 22:57:53 -06:00
dependabot[bot]
85ea91fed8 Bump globals from 15.14.0 to 15.15.0
Bumps [globals](https://github.com/sindresorhus/globals) from 15.14.0 to 15.15.0.
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v15.14.0...v15.15.0)

---
updated-dependencies:
- dependency-name: globals
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-13 03:21:18 +00:00
David Bolack
a0c9b8849c Merge branch 'master' of github.com:naturalcrit/homebrewery 2025-02-12 16:23:54 -06:00
Víctor Losada Hernández
ff91ebb06a Merge pull request #4022 from 5e-Cleric/update-admin
Update admin
2025-02-12 23:14:39 +01:00
Víctor Losada Hernández
21baab784e Merge branch 'update-admin' of https://github.com/5e-Cleric/homebrewery into update-admin 2025-02-12 23:11:17 +01:00
Víctor Losada Hernández
1f3a0f1f99 adapt width of table to date iso and remove colors 2025-02-12 23:10:51 +01:00
Trevor Buckner
6b4f5bd0af Linting on .less files 2025-02-12 15:30:20 -05:00
David Bolack
0c2f0ac31e Remove tests performed in nonbreaking space module 2025-02-12 12:52:01 -06:00
David Bolack
777f51c661 First pass testing
If completes, remove most tests.
2025-02-12 12:32:28 -06:00
David Bolack
3cfdb7eeb0 Temporarily restore old tests. 2025-02-12 12:26:02 -06:00
David Bolack
1f9495099f WIP 2025-02-12 12:22:02 -06:00
David Bolack
52cf1ddea0 Merge branch 'master' of github.com:naturalcrit/homebrewery 2025-02-12 10:03:45 -06:00
Víctor Losada Hernández
b79c5954ff minor style changes 2025-02-12 13:02:26 +01:00
Víctor Losada Hernández
9944398e4c add decent table styles 2025-02-12 12:54:34 +01:00
Víctor Losada Hernández
489f00b785 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into update-admin 2025-02-12 12:08:34 +01:00
Trevor Buckner
1a515f8d9c Merge pull request #4045 from naturalcrit/dependabot/npm_and_yarn/eslint-9.20.1
Bump eslint from 9.20.0 to 9.20.1
2025-02-11 23:16:57 -05:00
dependabot[bot]
f386ba3f45 Bump eslint from 9.20.0 to 9.20.1
Bumps [eslint](https://github.com/eslint/eslint) from 9.20.0 to 9.20.1.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.20.0...v9.20.1)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-12 03:52:31 +00:00
David Bolack
80564dd8db Remove relocated tests from executiomn 2025-02-11 15:01:20 -06:00
David Bolack
db16248afb Remove Paragraph Justifcation test execution. 2025-02-11 14:57:24 -06:00
David Bolack
cf4c1f7009 Merge branch 'master' into SnippetsReorg 2025-02-11 14:46:03 -06:00
David Bolack
3ffdb34312 Tweaks in response to CC comments 2025-02-11 14:45:39 -06:00
David Bolack
dc8d0e9483 Restore .monster 2025-02-11 14:37:25 -06:00
David Bolack
38bd3b0fc5 Migrate the justified paragraphs extension to an NPM 2025-02-11 14:34:01 -06:00
Trevor Buckner
634450d4a9 Merge pull request #4031 from G-Ambatte/addAdminGetBrewsByUser
Add Admin function to get list of brews by username
2025-02-11 15:21:22 -05:00
G.Ambatte
559f55f781 Merge branch 'master' into addAdminGetBrewsByUser 2025-02-12 08:23:43 +13:00
G.Ambatte
64b7527ad0 Convert space indentation to tabs 2025-02-12 08:01:07 +13:00
G.Ambatte
d48d5260a4 Fix missing } 2025-02-12 07:54:15 +13:00
David Bolack
41dc78375c Merge branch 'master' into marked-subsuper 2025-02-11 12:50:46 -06:00
G.Ambatte
bbc601cf47 Simplify sort algorithm
Co-authored-by: Trevor Buckner <calculuschild@gmail.com>
2025-02-12 07:42:35 +13:00
G.Ambatte
e89920bd1e Remove unneeded .then()
Co-authored-by: Trevor Buckner <calculuschild@gmail.com>
2025-02-12 07:41:53 +13:00
Trevor Buckner
2e12980180 Merge branch 'master' into fix-snippet-styling 2025-02-11 11:23:05 -05:00
Trevor Buckner
b77af1bcc8 Merge pull request #4040 from naturalcrit/dependabot/npm_and_yarn/babel/preset-env-7.26.8
Bump @babel/preset-env from 7.26.7 to 7.26.8
2025-02-11 11:22:14 -05:00
dependabot[bot]
45d188fea1 Bump @babel/preset-env from 7.26.7 to 7.26.8
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.26.7 to 7.26.8.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.8/packages/babel-preset-env)

---
updated-dependencies:
- dependency-name: "@babel/preset-env"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-11 16:19:50 +00:00
Trevor Buckner
1ce26ca953 Merge pull request #4041 from naturalcrit/dependabot/npm_and_yarn/babel/core-7.26.8
Bump @babel/core from 7.26.7 to 7.26.8
2025-02-11 11:18:26 -05:00
dependabot[bot]
d1c0557341 Bump @babel/core from 7.26.7 to 7.26.8
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.26.7 to 7.26.8.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.8/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-11 16:04:52 +00:00
Trevor Buckner
4e857a1a99 Merge pull request #4033 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.10.0
Bump mongoose from 8.9.6 to 8.10.0
2025-02-11 11:03:19 -05:00
Trevor Buckner
547ac11756 Merge branch 'master' into dependabot/npm_and_yarn/mongoose-8.10.0 2025-02-11 11:01:27 -05:00
Trevor Buckner
0e2443f772 Merge pull request #4039 from naturalcrit/dependabot/npm_and_yarn/eslint-9.20.0
Bump eslint from 9.19.0 to 9.20.0
2025-02-11 11:01:03 -05:00
dependabot[bot]
9d16f4556e Bump mongoose from 8.9.6 to 8.10.0
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.9.6 to 8.10.0.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/8.9.6...8.10.0)

---
updated-dependencies:
- dependency-name: mongoose
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-11 15:23:08 +00:00
dependabot[bot]
6d0d0057f6 Bump eslint from 9.19.0 to 9.20.0
Bumps [eslint](https://github.com/eslint/eslint) from 9.19.0 to 9.20.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.19.0...v9.20.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-11 15:22:28 +00:00
Trevor Buckner
b8d9023c98 Merge pull request #4038 from naturalcrit/dependabot/npm_and_yarn/babel/plugin-transform-runtime-7.26.8
Bump @babel/plugin-transform-runtime from 7.25.9 to 7.26.8
2025-02-11 10:21:48 -05:00
Trevor Buckner
4578cf6584 Merge branch 'master' into dependabot/npm_and_yarn/babel/plugin-transform-runtime-7.26.8 2025-02-11 10:19:58 -05:00
Trevor Buckner
111869d33b Merge pull request #4032 from naturalcrit/dependabot/npm_and_yarn/stylistic/stylelint-plugin-3.1.2
Bump @stylistic/stylelint-plugin from 3.1.1 to 3.1.2
2025-02-11 10:19:31 -05:00
Trevor Buckner
d0b4486e15 Merge branch 'master' into dependabot/npm_and_yarn/stylistic/stylelint-plugin-3.1.2 2025-02-11 10:15:39 -05:00
Trevor Buckner
1aed753911 Use ComboBox component for Theme Selector 2025-02-10 22:20:54 -05:00
Trevor Buckner
c080e5b191 Add author to snippetBundle 2025-02-10 22:20:12 -05:00
David Bolack
11396389ab Move Superscript/Subscript functions into their own module 2025-02-10 20:47:17 -06:00
Trevor Buckner
0bcf228881 Merge branch 'master' into writeinBrewTheme 2025-02-10 00:49:22 -05:00
Trevor Buckner
de30722554 Merge pull request #4042 from naturalcrit/cleanUpCombobox
Clean up combobox component
2025-02-10 00:48:48 -05:00
Trevor Buckner
6cfdfad7d3 Clean up combobox component 2025-02-10 00:33:33 -05:00
dependabot[bot]
a9fa0bd32d Bump @babel/plugin-transform-runtime from 7.25.9 to 7.26.8
Bumps [@babel/plugin-transform-runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-runtime) from 7.25.9 to 7.26.8.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.8/packages/babel-plugin-transform-runtime)

---
updated-dependencies:
- dependency-name: "@babel/plugin-transform-runtime"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-10 03:10:29 +00:00
Trevor Buckner
cfbf4021dc Remove unneeded filter 2025-02-09 12:23:42 -05:00
Trevor Buckner
e3780e844d Merge branch 'writeinBrewTheme' of https://github.com/dbolack-ab/homebrewery into pr/3923 2025-02-09 12:07:31 -05:00
Trevor Buckner
659510e364 Remove unused function 2025-02-09 12:07:09 -05:00
Trevor Buckner
29da0396fd Merge branch 'master' into writeinBrewTheme 2025-02-09 12:03:06 -05:00
Trevor Buckner
c3e08181e9 Merge branch 'master' into dependabot/npm_and_yarn/stylistic/stylelint-plugin-3.1.2 2025-02-09 12:02:12 -05:00
Víctor Losada Hernández
213a719337 Merge pull request #4036 from G-Ambatte/correctChangeLogTypo
Correct typo in date of v3.17.0
2025-02-09 11:33:37 +01:00
G.Ambatte
a7a7e46e89 Correct typo in date of v3.17.0 2025-02-09 22:40:54 +13:00
David Bolack
d061b902d5 Make all typefaces available via blank - for now. 2025-02-07 19:26:56 -06:00
David Bolack
0a86990bdf Merge branch 'master' into SnippetsReorg 2025-02-07 19:02:28 -06:00
dependabot[bot]
ada06c9618 Bump @stylistic/stylelint-plugin from 3.1.1 to 3.1.2
Bumps [@stylistic/stylelint-plugin](https://github.com/stylelint-stylistic/stylelint-stylistic) from 3.1.1 to 3.1.2.
- [Release notes](https://github.com/stylelint-stylistic/stylelint-stylistic/releases)
- [Changelog](https://github.com/stylelint-stylistic/stylelint-stylistic/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint-stylistic/stylelint-stylistic/compare/v3.1.1...v3.1.2)

---
updated-dependencies:
- dependency-name: "@stylistic/stylelint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-06 03:24:23 +00:00
G.Ambatte
03798e945d Add UI 2025-02-05 21:35:33 +13:00
G.Ambatte
6d2cbaacc0 Add Admin API for getByUser 2025-02-05 21:35:21 +13:00
David Bolack
f2f894381e Merge branch 'master' into issue_3206 2025-02-04 21:10:25 -06:00
David Bolack
7c293f51cb Merge branch 'master' into issue_3659 2025-02-04 21:09:55 -06:00
David Bolack
67b31c476c Merge branch 'master' into localSnippetEditor 2025-02-04 21:07:02 -06:00
Trevor Buckner
10fae6dbac Merge pull request #4026 from 5e-Cleric/fix-errorpage-error-if-brew-title-doesn't-exist
fix undefined value in errorIndex.js if brew doesn't exist
2025-02-03 14:24:30 -05:00
Víctor Losada Hernández
ebc7f055fa Merge branch 'master' into fix-errorpage-error-if-brew-title-doesn't-exist 2025-02-03 15:26:36 +01:00
Víctor Losada Hernández
ce01b6c1ff initial commit 2025-02-03 15:10:34 +01:00
Trevor Buckner
553562611f Merge pull request #4024 from naturalcrit/dependabot/npm_and_yarn/react-router-7.1.5
Bump react-router from 7.1.4 to 7.1.5
2025-02-03 00:07:11 -05:00
dependabot[bot]
423caefe1a Bump react-router from 7.1.4 to 7.1.5
Bumps [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) from 7.1.4 to 7.1.5.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router@7.1.5/packages/react-router)

---
updated-dependencies:
- dependency-name: react-router
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-03 04:45:39 +00:00
Trevor Buckner
ae1de819ea Merge pull request #4025 from naturalcrit/dependabot/npm_and_yarn/mongoose-8.9.6
Bump mongoose from 8.9.5 to 8.9.6
2025-02-02 23:44:17 -05:00
dependabot[bot]
27c4cfd25c Bump mongoose from 8.9.5 to 8.9.6
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.9.5 to 8.9.6.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/8.9.5...8.9.6)

---
updated-dependencies:
- dependency-name: mongoose
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-03 03:16:41 +00:00
Víctor Losada Hernández
bf22104474 move padding to a more deserving place 2025-02-02 22:19:38 +01:00
Víctor Losada Hernández
c3e0a687c0 Merge branch 'master' into writeinBrewTheme 2025-02-02 22:18:20 +01:00
Víctor Losada Hernández
00a2b130eb Merge branch 'master' into headerNaxExtension 2025-02-02 16:44:30 +01:00
Víctor Losada Hernández
8eef810f3f backcover logo width 2025-02-01 20:30:54 +01:00
Víctor Losada Hernández
a04df0fdfc small adjustements 2025-02-01 19:27:55 +01:00
Víctor Losada Hernández
a504703d41 removed unnecessary files and refactored layout of dl 2025-02-01 15:47:41 +01:00
David Bolack
497f8bde83 Merge branch 'master' into issue_3659 2025-01-31 16:27:28 -06:00
David Bolack
8711265506 Merge branch 'issue_3659' of github.com:dbolack-ab/homebrewery into issue_3659 2025-01-31 16:25:33 -06:00
David Bolack
b1ff68c3b1 Update code to use helper function to ensure unifrom normalization of Variable labels. 2025-01-31 16:23:32 -06:00
Víctor Losada Hernández
3ce9bb1310 initial commit 2025-01-31 23:20:35 +01:00
David Bolack
564f5d71b2 WIP 2025-01-31 16:12:00 -06:00
David Bolack
158122ed55 Merge branch 'master' into issue_3659 2025-01-31 16:05:00 -06:00
Víctor Losada Hernández
66bfc8f27b style change 2025-01-31 22:33:47 +01:00
Trevor Buckner
6c8b94453e Merge pull request #4019 from naturalcrit/update-notif-to-handle-markdown
upadte notification popup to handle markdown
2025-01-31 14:31:31 -05:00
Víctor Losada Hernández
460fb655d8 bring margin back 2025-01-31 20:11:57 +01:00
Víctor Losada Hernández
be1742d01d remove unnecessary spaces 2025-01-31 20:08:10 +01:00
Víctor Losada Hernández
5d3742aea6 Merge branch 'update-notif-to-handle-markdown' of https://github.com/naturalcrit/homebrewery into update-notif-to-handle-markdown 2025-01-31 20:02:33 +01:00
Víctor Losada Hernández
1966027289 linting & suggested changes 2025-01-31 20:02:31 +01:00
Víctor Losada Hernández
35d50cc9d1 Merge branch 'master' of https://github.com/naturalcrit/homebrewery into update-notif-to-handle-markdown 2025-01-31 20:01:29 +01:00
Trevor Buckner
3f41306306 Merge branch 'master' into update-notif-to-handle-markdown 2025-01-31 13:43:50 -05:00
Trevor Buckner
988bf1b0a9 Merge pull request #4020 from naturalcrit/FixStyleLintImport
Fix StyleLint require to import
2025-01-31 13:42:19 -05:00
Trevor Buckner
2f1ade8463 lint 2025-01-31 13:38:25 -05:00
Trevor Buckner
518924d725 Use import, run eslint 2025-01-31 13:36:42 -05:00
Trevor Buckner
6269651c8d Update Marked to v13.0.3 2025-01-31 12:42:54 -05:00
Víctor Losada Hernández
057abcda0d reduce style between li elements 2025-01-31 12:04:40 +01:00
Víctor Losada Hernández
b6b23a787c upadte notification popup to handle markdown 2025-01-31 11:52:46 +01:00
Trevor Buckner
899004cfaf Merge pull request #4017 from naturalcrit/dependabot/npm_and_yarn/react-router-7.1.4
Bump react-router from 7.1.3 to 7.1.4
2025-01-31 00:35:03 -05:00
dependabot[bot]
7e826cd4f5 Bump react-router from 7.1.3 to 7.1.4
Bumps [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) from 7.1.3 to 7.1.4.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router@7.1.4/packages/react-router)

---
updated-dependencies:
- dependency-name: react-router
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-31 05:22:30 +00:00
Trevor Buckner
3b150891bc Merge pull request #4016 from naturalcrit/dependabot/npm_and_yarn/dompurify-3.2.4
Bump dompurify from 3.2.3 to 3.2.4
2025-01-31 00:21:09 -05:00
dependabot[bot]
e87acc3f0f Bump dompurify from 3.2.3 to 3.2.4
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.2.3 to 3.2.4.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.2.3...3.2.4)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-31 05:17:57 +00:00
Trevor Buckner
b1e99f1385 Marked 12.0.2 2025-01-31 00:16:37 -05:00
Trevor Buckner
4e0b6d634d Marked 12.0.1 2025-01-31 00:12:13 -05:00
Trevor Buckner
a72f0f2f34 Merge pull request #4018 from naturalcrit/up-Marked-to-v12
Update Marked.js to v12.0.0
2025-01-31 00:08:01 -05:00
Trevor Buckner
23944f4fe0 smartypants package updated to support higher Marked versions
Next Marked versions break things; need to update incrementally
2025-01-31 00:03:44 -05:00
Trevor Buckner
c244199190 Merge pull request #4015 from naturalcrit/v3.17
Update to v3.17.0
2025-01-30 22:38:26 -05:00
Trevor Buckner
8848c06b15 Rewording. Add more detailed examples. Add Table width syntax 2025-01-30 22:37:55 -05:00
Víctor Losada Hernández
37d56f7365 compile removed items 2025-01-30 23:35:42 +01:00
Víctor Losada Hernández
e2d6b5afc4 Merge branch 'v3.17' of https://github.com/naturalcrit/homebrewery into v3.17 2025-01-30 23:33:54 +01:00
Víctor Losada Hernández
e4df577a32 remove internal changes 2025-01-30 23:33:49 +01:00
Trevor Buckner
f005cb784f Update changelog.md 2025-01-30 14:18:26 -05:00
Víctor Losada Hernández
d733b1f8f8 reformat changelog 2025-01-30 20:02:34 +01:00
Víctor Losada Hernández
d8d403ffb8 Update to v3.17.0 2025-01-30 18:54:27 +01:00
Trevor Buckner
574d68f678 Merge pull request #4004 from naturalcrit/altpageattributes
Alternate \page{curlies}
2025-01-30 09:33:13 -05:00
David Bolack
f1eb6e1ce4 Alter varCallInline content to alig with preceeding varDefBlock in inline definitions.
This was failing due to both labels having the extraneous spaces cleaned up but the variable label portion of the varCallInline did not, preventing them from being matched during variable resolution.
2025-01-28 21:31:43 -06:00
David Bolack
004729b2a4 Fix editor regression. 2025-01-28 19:37:02 -06:00
David Bolack
c27d9978fe Merge branch 'master' into localSnippetEditor 2025-01-28 19:30:06 -06:00
David Bolack
f608cb2d65 Merge branch 'issue_3206' of github.com:dbolack-ab/homebrewery into issue_3206 2025-01-28 19:26:37 -06:00
David Bolack
28a1610573 Merge branch 'master' into issue_3206 2025-01-28 19:26:10 -06:00
Trevor Buckner
df00160bc4 Merge branch 'master' into pr/4005 2025-01-27 23:29:28 -05:00
David Bolack
98c353b9fe Merge branch 'master' into writeinBrewTheme 2025-01-24 14:31:28 -06:00
David Bolack
342ac76982 Merge branch 'master' into localSnippetEditor 2025-01-24 14:30:12 -06:00
David Bolack
60b6dbb388 Workaround for unclosed <pre> blocks before rendering.
Unsure if this is a fix you really need but it resolves the issue posted.
2025-01-24 13:55:48 -06:00
G.Ambatte
b7241f79cb Merge branch 'master' into headerNaxExtension 2025-01-22 08:46:42 +13:00
G.Ambatte
f6c95fb8b7 Extend header nav to exclude frontCover pages 2025-01-14 08:25:46 +13:00
David Bolack
662f039daa Merge conflict clearing 2025-01-12 14:13:27 -06:00
David Bolack
20c46bd27f Merge branch 'master' into localSnippetEditor 2025-01-12 14:04:23 -06:00
David Bolack
2d47cd2a76 Formatting cleanup 2025-01-08 09:28:37 -06:00
David Bolack
6eb938bb37 Change theme button toggle to be a bit more obvious. 2025-01-08 09:25:16 -06:00
David Bolack
94a431eec8 Update tests. 2025-01-07 22:28:12 -06:00
David Bolack
4eb71b1220 Merge branch 'master' into writeinBrewTheme 2025-01-07 22:16:39 -06:00
David Bolack
74122d9057 Display name of write in theme next to write-in
Clear user's active ThemeBundle when an incomplete/broken/invalid writein.

Needs theming help.
2025-01-07 22:11:01 -06:00
David Bolack
e7f8cda6ae Merge branch 'master' into writeinBrewTheme 2025-01-07 20:36:53 -06:00
David Bolack
b9f7e820c7 Functionally? working. 2025-01-07 20:36:38 -06:00
David Bolack
fe2d02a24c Work in progress.
Still issues with saving the state  of the theme pulldowns and collecting the written in theme.
2024-12-30 12:28:47 -06:00
David Bolack
7c357a2aa1 Attempt to save state but seems to break brew. 2024-12-29 23:23:48 -06:00
David Bolack
0e6380a8bd Merge branch 'master' into localSnippetEditor 2024-12-28 16:09:48 -06:00
David Bolack
5eb8432544 Merge branch 'master' into writeinBrewTheme 2024-12-28 16:02:36 -06:00
David Bolack
ed099aa061 Disable the BrewSnippets menu if empty. 2024-12-20 21:47:05 -06:00
David Bolack
9745daf6e2 Merge branch 'master' into writeinBrewTheme 2024-12-20 15:19:06 -06:00
David Bolack
5f54777663 Merge branch 'master' into localSnippetEditor 2024-12-20 15:18:25 -06:00
David Bolack
47d7c69d1b Merge branch 'master' into writeinBrewTheme 2024-12-10 23:20:48 -06:00
David Bolack
909affcf99 Merge branch 'localSnippetEditor' of github.com:dbolack-ab/homebrewery into localSnippetEditor 2024-12-10 23:10:31 -06:00
David Bolack
86856605b9 Add editor highlighting 2024-12-10 23:08:51 -06:00
David Bolack
dae297e0f5 Partially implement disabled BrewSnippets 2024-12-10 21:52:38 -06:00
David Bolack
6e5f071f22 Move Properties icon to the end of the snippets bar. 2024-12-10 21:21:41 -06:00
David Bolack
12c155b46f Merge branch 'master' into localSnippetEditor 2024-12-10 21:17:51 -06:00
Trevor Buckner
f51c51f041 Merge branch 'master' into localSnippetEditor 2024-12-10 11:11:09 -05:00
David Bolack
43222b7651 Fix test 2024-12-04 21:41:04 -06:00
David Bolack
70f86c6ebd Merge branch 'master' into localSnippetEditor 2024-12-04 21:31:10 -06:00
David Bolack
8492c63f62 Merge branch 'master' into writeinBrewTheme 2024-12-04 20:40:14 -06:00
David Bolack
73c68fd11c Functional first pass.
Needs:

 - [ ] opinions on UI placement
 - [ ] opinions on best choice for displaying a write-in based User Brew ( flip to writin box? Add to drop-down list? )
2024-11-27 21:35:29 -06:00
David Bolack
74b4cb2afd Revert local error in package.json 2024-11-25 13:59:24 -06:00
David Bolack
fa96836b63 Merge branch 'master' into localSnippetEditor 2024-11-25 13:55:54 -06:00
David Bolack
e763ae1631 t shouldn't have been saved... 2024-11-24 21:57:59 -06:00
David Bolack
008b31e530 Correct failing test. 2024-11-24 18:58:50 -06:00
David Bolack
b6e445c445 Convert storage of snippets in Brew to yaml by request. 2024-11-24 18:13:32 -06:00
David Bolack
c5935ec262 Add Snippets to /new 2024-11-23 01:57:07 -06:00
David Bolack
5f67494f77 Merge branch 'master' into localSnippetEditor 2024-11-23 01:39:38 -06:00
David Bolack
e98b614f05 Merge branch 'localSnippetEditor' of github.com:dbolack-ab/homebrewery into localSnippetEditor 2024-11-14 06:56:14 -06:00
David Bolack
d541a70da5 Remove unneeded dedent 2024-11-14 06:54:36 -06:00
G.Ambatte
cc9586aa64 Merge branch 'master' into localSnippetEditor 2024-11-14 16:41:26 +13:00
David Bolack
f7561b7824 Insert a CR 2024-11-13 20:53:04 -06:00
David Bolack
83abdc2ee6 Merge branch 'master' into localSnippetEditor 2024-11-12 18:42:50 -06:00
David Bolack
e0400c0425 Snippets should go after existing tab sections 2024-11-12 18:41:31 -06:00
David Bolack
687b7e04d9 Merge branch 'master' into localSnippetEditor 2024-11-11 21:21:57 -06:00
David Bolack
f43a155e6e Fix Test 2024-11-03 12:52:54 -06:00
David Bolack
f4e9516233 Remove testing helper 2024-11-03 12:41:04 -06:00
David Bolack
7f7f3557b3 Mostly working. Needs tweakages. Presentable 2024-11-03 12:30:14 -06:00
David Bolack
b9b3d284cf WOrking snippet editor - menu population regression 2024-11-03 11:14:31 -06:00
David Bolack
4f240bf110 WIP 2024-11-02 22:30:18 -05:00
David Bolack
7cd82ffc4e WOrking snippets insertion from local. 2024-11-02 17:37:43 -05:00
David Bolack
4448410c3e Partial implementation 2024-11-01 14:02:27 -05:00
Trevor Buckner
220f4fad24 Merge branch 'master' into addLockRoutes-#3326 2024-09-24 13:37:53 -04:00
G.Ambatte
05c1d31550 Merge branch 'master' into addLockRoutes-#3326 2024-09-18 15:21:20 +12:00
Trevor Buckner
c8bacabf24 Merge branch 'master' into addLockRoutes-#3326 2024-09-16 16:19:34 -04:00
G.Ambatte
bfab34f8c6 Merge branch 'master' into addLockRoutes-#3326 2024-09-06 08:07:33 +12:00
David Bolack
a3c01305df Revert "Move Page styles ( cover Page, etc ) to Blank from 5ePHB"
This reverts commit ad1dfc8e2b.
2024-08-25 19:15:57 -05:00
David Bolack
ad1dfc8e2b Move Page styles ( cover Page, etc ) to Blank from 5ePHB 2024-08-23 16:52:35 -05:00
David Bolack
2c573bfef5 Failed to save one file's changes. 2024-08-23 14:18:58 -05:00
David Bolack
fd5ff2c61a Relocate more general purposes snippets from 5ePHB to Blank
Should include all supporting style content.
2024-08-23 14:04:04 -05:00
G.Ambatte
1d03b200a5 Merge branch 'master' into addLockRoutes-#3326 2024-08-14 08:39:55 +12:00
G.Ambatte
b068749380 Move copy ID to clipboard to separate button 2024-07-25 22:11:14 +12:00
G.Ambatte
f82f893014 Merge branch 'master' into addLockRoutes-#3326 2024-07-23 10:28:59 +12:00
G.Ambatte
a917937f12 Merge branch 'master' into newTheme-UnearthedArcana 2024-07-22 14:35:14 +12:00
G.Ambatte
7094d43ee5 Shift API calls to /api from /admin 2024-06-29 15:29:02 +12:00
G.Ambatte
51296a9100 Merge branch 'master' into addLockRoutes-#3326 2024-06-29 10:34:55 +12:00
G.Ambatte
0803362a50 More styling tweaks 2024-06-12 22:29:47 +12:00
G.Ambatte
7e80787679 Tweak styling 2024-06-12 22:18:12 +12:00
G.Ambatte
fc99328459 Tweak styling, remove obsolete locked property 2024-06-12 21:48:55 +12:00
G.Ambatte
c594fc58b3 Spread lock object in /lock response 2024-06-12 21:47:52 +12:00
G.Ambatte
24cf78bc03 Add lock result 2024-06-12 20:44:55 +12:00
G.Ambatte
17aa564c57 Add styling for result tables 2024-06-08 21:05:44 +12:00
G.Ambatte
7d37602d43 Remove debugging line 2024-06-08 20:01:53 +12:00
G.Ambatte
44f2e38387 Shift unlock and removeReview functions to use PUT 2024-06-08 20:00:10 +12:00
G.Ambatte
7830c7e2eb Add divs for styling of brewsAwaitingReview 2024-06-08 19:59:01 +12:00
G.Ambatte
deef5998c5 Update LockBrew text and styling 2024-06-08 18:55:14 +12:00
G.Ambatte
01ae858a14 Update LockLookup placeholder value 2024-06-08 17:08:05 +12:00
G.Ambatte
634b099ade Add styling to LockTable 2024-06-08 16:48:16 +12:00
G.Ambatte
240342007b Fix lock count function 2024-06-08 16:36:20 +12:00
G.Ambatte
bc7731b819 Fix review requested aggregation pipeline 2024-06-08 16:08:18 +12:00
G.Ambatte
00fd1e415c Change styling for inactive REQUEST REVIEW button 2024-06-08 15:53:28 +12:00
G.Ambatte
f81d16309c Add functionality to REQUEST REVIEW button 2024-06-08 15:53:01 +12:00
G.Ambatte
fe09b89fcb Shift API call for REQUEST REVIEW to PUT 2024-06-08 15:31:46 +12:00
G.Ambatte
6314672109 Pass the entire lock object to LockNotification 2024-06-08 14:59:08 +12:00
G.Ambatte
d995169b3c Merge branch 'master' into addLockRoutes-#3326 2024-06-08 12:06:29 +12:00
G.Ambatte
2c6e00702c Merge branch 'master' into addLockRoutes-#3326 2024-06-06 23:06:59 +12:00
G.Ambatte
6d9e564d1c Merge branch 'master' into addLockRoutes-#3326 2024-05-30 00:02:37 +12:00
G.Ambatte
d751addf9d Change lock static error message 2024-05-16 17:18:50 +12:00
G.Ambatte
1af7adc09b Merge branch 'addLockRoutes-#3326' of https://github.com/G-Ambatte/homebrewery into addLockRoutes-#3326 2024-05-16 17:06:19 +12:00
G.Ambatte
7b287fb0f4 Fix test 2024-05-16 17:04:37 +12:00
G.Ambatte
489c65af3e Merge branch 'master' into addLockRoutes-#3326 2024-05-16 17:02:27 +12:00
G.Ambatte
4e11a0c737 Fix error message 2024-05-16 17:02:00 +12:00
G.Ambatte
96b955f2fe Change lock check to stub.lock from stub.lock.locked 2024-05-16 17:00:26 +12:00
G.Ambatte
f5014f29c3 Linter fix 2024-05-15 23:13:53 +12:00
G.Ambatte
b237f29636 Clean up API functions 2024-05-15 19:58:29 +12:00
G.Ambatte
c0c674d862 Comment out debug logging 2024-05-15 19:52:08 +12:00
G.Ambatte
7c4721932d Remove unnecessary function from HB.model 2024-05-15 19:50:27 +12:00
G.Ambatte
b093be52a2 Update esLint rules 2024-05-15 16:57:28 +12:00
G.Ambatte
65f1c19721 Add functional Lock Brew component 2024-05-15 16:14:09 +12:00
G.Ambatte
2502c0e87c Add missing return status messages 2024-05-15 16:08:26 +12:00
G.Ambatte
2a91d3ddbd Additional styling 2024-05-15 16:07:28 +12:00
G.Ambatte
e7dc757293 Initial UI functionality for most Lock functions 2024-05-11 23:58:37 +12:00
G.Ambatte
1fe2a26e83 Tabify Admin page 2024-05-10 12:38:31 +12:00
G.Ambatte
9b7471d6d2 Merge branch 'master' into addLockRoutes-#3326 2024-05-09 22:57:42 +12:00
G.Ambatte
b4ce621630 Request review no longer Admin only 2024-05-09 07:37:37 +12:00
G.Ambatte
bf88f63482 Merge branch 'master' into addLockRoutes-#3326 2024-05-08 23:30:31 +12:00
G.Ambatte
20d48d7dc2 Add review removal route 2024-04-25 15:59:11 +12:00
G.Ambatte
4b1d6ebd7c Rename review request route 2024-04-25 15:58:43 +12:00
G.Ambatte
e9db7d1bb9 Add return on success to lock route 2024-04-25 15:37:50 +12:00
G.Ambatte
1c2ae8392c Increase max lines in Linter 2024-04-25 15:32:26 +12:00
G.Ambatte
e8b9b3d583 Add lock route 2024-04-25 15:27:40 +12:00
G.Ambatte
71b84e1aba Log message when review already requested 2024-04-25 14:33:29 +12:00
G.Ambatte
99f5aad942 Stop review request if it has already been logged 2024-04-25 14:32:04 +12:00
G.Ambatte
8feae7efb6 Function request review route 2024-04-22 00:32:32 +12:00
G.Ambatte
874cbe1da4 Don't forget to update the schema! 2024-04-22 00:26:34 +12:00
G.Ambatte
14c7a11528 Fix error message 2024-04-21 22:51:53 +12:00
G.Ambatte
b4a7dc0cbd Update lock review route 2024-04-21 22:48:11 +12:00
G.Ambatte
8c5f2ff61c Add route to get locked documents with review requested 2024-04-21 22:17:10 +12:00
G.Ambatte
770025da04 Add lock count route, update pipelines 2024-04-21 14:42:20 +12:00
G.Ambatte
eb719e34a8 Add aggregate query to HB model 2024-04-21 14:35:51 +12:00
G.Ambatte
b54448f830 Merge branch 'master' into newTheme-UnearthedArcana 2024-02-25 11:55:40 +13:00
G.Ambatte
b88480c9ba Merge branch 'master' into newTheme-UnearthedArcana 2023-10-14 11:42:31 +13:00
G.Ambatte
a8897b2813 Merge branch 'master' into newTheme-UnearthedArcana 2023-09-09 09:49:15 +12:00
G.Ambatte
cb139ae775 Merge branch 'master' into newTheme-UnearthedArcana 2023-09-06 08:32:31 +12:00
G.Ambatte
89a788ff9f Add new theme - Unearthed Arcana 2023-09-03 16:56:21 +12:00
159 changed files with 17687 additions and 7221 deletions

View File

@@ -10,7 +10,7 @@ orbs:
jobs:
build:
docker:
- image: cimg/node:20.17.0
- image: cimg/node:20.18.0
- image: mongo:4.4
working_directory: ~/homebrewery
@@ -64,18 +64,12 @@ jobs:
- run:
name: Test - Mustache Spans
command: npm run test:mustache-syntax
- run:
name: Test - Definition Lists
command: npm run test:definition-lists
- run:
name: Test - Hard Breaks
command: npm run test:hard-breaks
- run:
name: Test - Non-Breaking Spaces
command: npm run test:non-breaking-spaces
- run:
name: Test - Paragraph Justification
command: npm run test:paragraph-justification
- run:
name: Test - Variables
command: npm run test:variables

View File

@@ -5,6 +5,15 @@ updates:
schedule:
interval: daily
open-pull-requests-limit: 99
groups:
dev-dependencies:
dependency-type: "development"
patterns: ["*"]
update-types: ["patch", "minor"]
prod-dependencies:
dependency-type: "production"
patterns: ["*"]
update-types: ["patch", "minor"]
ignore:
- dependency-name: eslint
versions:

View File

@@ -1,48 +1,48 @@
{
"extends": [
"stylelint-config-recess-order",
"stylelint-config-recommended"],
"plugins": [
"@stylistic/stylelint-plugin",
"./stylelint_plugins/declaration-colon-align.js",
"./stylelint_plugins/declaration-colon-min-space-before",
"./stylelint_plugins/declaration-block-multi-line-min-declarations"
],
"customSyntax": "postcss-less",
"rules": {
"no-descending-specificity" : null,
"at-rule-no-unknown" : null,
"function-no-unknown" : null,
"font-family-no-missing-generic-family-keyword" : null,
"font-weight-notation" : "named-where-possible",
"font-family-name-quotes" : "always-unless-keyword",
"@stylistic/indentation" : "tab",
"no-duplicate-selectors" : true,
"@stylistic/color-hex-case" : "upper",
"color-hex-length" : "long",
"@stylistic/selector-combinator-space-after" : "always",
"@stylistic/selector-combinator-space-before" : "always",
"@stylistic/selector-attribute-operator-space-before" : "never",
"@stylistic/selector-attribute-operator-space-after" : "never",
"@stylistic/selector-attribute-brackets-space-inside" : "never",
"selector-attribute-quotes" : "always",
"selector-pseudo-element-colon-notation" : "double",
"@stylistic/selector-pseudo-class-parentheses-space-inside" : "never",
"@stylistic/block-opening-brace-space-before" : "always",
"naturalcrit/declaration-colon-min-space-before" : 1,
"@stylistic/declaration-block-trailing-semicolon" : "always",
"@stylistic/declaration-colon-space-after" : "always",
"@stylistic/number-leading-zero" : "always",
"function-url-quotes" : ["always", { "except": ["empty"] }],
"function-url-scheme-disallowed-list" : ["data","http"],
"comment-whitespace-inside" : "always",
"@stylistic/string-quotes" : "single",
"@stylistic/media-feature-range-operator-space-before" : "always",
"@stylistic/media-feature-range-operator-space-after" : "always",
"@stylistic/media-feature-parentheses-space-inside" : "never",
"@stylistic/media-feature-colon-space-before" : "always",
"@stylistic/media-feature-colon-space-after" : "always",
"naturalcrit/declaration-colon-align" : true,
"naturalcrit/declaration-block-multi-line-min-declarations": 1
}
"extends": [
"stylelint-config-recess-order",
"stylelint-config-recommended"],
"plugins": [
"@stylistic/stylelint-plugin",
"./stylelint_plugins/declaration-colon-align.js",
"./stylelint_plugins/declaration-colon-min-space-before",
"./stylelint_plugins/declaration-block-multi-line-min-declarations"
],
"customSyntax": "postcss-less",
"rules": {
"no-descending-specificity" : null,
"at-rule-no-unknown" : null,
"function-no-unknown" : null,
"font-family-no-missing-generic-family-keyword" : null,
"font-weight-notation" : "named-where-possible",
"font-family-name-quotes" : "always-unless-keyword",
"@stylistic/indentation" : "tab",
"no-duplicate-selectors" : true,
"@stylistic/color-hex-case" : "upper",
"color-hex-length" : "long",
"@stylistic/selector-combinator-space-after" : "always",
"@stylistic/selector-combinator-space-before" : "always",
"@stylistic/selector-attribute-operator-space-before" : "never",
"@stylistic/selector-attribute-operator-space-after" : "never",
"@stylistic/selector-attribute-brackets-space-inside" : "never",
"selector-attribute-quotes" : "always",
"selector-pseudo-element-colon-notation" : "double",
"@stylistic/selector-pseudo-class-parentheses-space-inside" : "never",
"@stylistic/block-opening-brace-space-before" : "always",
"naturalcrit/declaration-colon-min-space-before" : 1,
"@stylistic/declaration-block-trailing-semicolon" : "always",
"@stylistic/declaration-colon-space-after" : "always",
"@stylistic/number-leading-zero" : "always",
"function-url-quotes" : ["always", { "except": ["empty"] }],
"function-url-scheme-disallowed-list" : ["data","http"],
"comment-whitespace-inside" : "always",
"@stylistic/string-quotes" : "single",
"@stylistic/media-feature-range-operator-space-before" : "always",
"@stylistic/media-feature-range-operator-space-after" : "always",
"@stylistic/media-feature-parentheses-space-inside" : "never",
"@stylistic/media-feature-colon-space-before" : "always",
"@stylistic/media-feature-colon-space-after" : "always",
"naturalcrit/declaration-colon-align" : true,
"naturalcrit/declaration-block-multi-line-min-declarations" : 1
}
}

View File

@@ -49,7 +49,7 @@ Make an changes you need to `config/docker.json` then build the image. If it doe
"web_port" : 8000,
"enable_v3" : true,
"mongodb_uri": "mongodb://172.17.0.2/homebrewery",
"enable_themes" : true,
"enable_themes" : true
}
```
@@ -90,6 +90,13 @@ docker run --name homebrewery-mongodb -d --restart unless-stopped -v mongodata:/
docker run --name homebrewery-app -d --restart unless-stopped -e NODE_ENV=docker -v $(pwd)/config/docker.json:/usr/src/app/config/docker.json -p 8000:8000 docker.io/library/homebrewery:latest
```
**NOTE:** If you are running from the Windows command line, this will not work as `$(pwd)` is not valid syntax. Use this command instead:
```shell
# Make sure you run this in the homebrewery directory
docker run --name homebrewery-app -d --restart unless-stopped -e NODE_ENV=docker -v %cd%/config/docker.json:/usr/src/app/config/docker.json -p 8000:8000 docker.io/library/homebrewery:latest
```
## Updating the Image
When Homebrewery code updates, your docker container will not automatically follow the changes. To do so you will need to rebuild your homebrewery image.
@@ -117,3 +124,9 @@ docker-compose build homebrewery
docker run --name homebrewery-app -d --restart unless-stopped -e NODE_ENV=docker -v $(pwd)/config/docker.json:/usr/src/app/config/docker.json -p 8000:8000 docker.io/library/homebrewery:latest
```
**NOTE:** If you are running from the Windows command line, this will not work as `$(pwd)` is not valid syntax. Use this command instead:
```shell
# Make sure you run this in the homebrewery directory
docker run --name homebrewery-app -d --restart unless-stopped -e NODE_ENV=docker -v %cd%/config/docker.json:/usr/src/app/config/docker.json -p 8000:8000 docker.io/library/homebrewery:latest
```

View File

@@ -75,8 +75,9 @@ it using the two commands:
1. `npm install`
1. `npm start`
You should now be able to go to [http://localhost:8000](http://localhost:8000)
in your browser and use The Homebrewery offline.
When the Homebrewery server is started for the first time, it will modify the database to create the indexes required for better Homebrewery performance. This may take a few moments to complete for each index, dependent on how much content is in your local database - a brand new, empty database should be done in seconds.
On completion, you should be able to go to [http://localhost:8000](http://localhost:8000) in your browser and use The Homebrewery offline.
If you had any issue at all, here are some links that may be useful:
- [Course](https://learn.mongodb.com/courses/m103-basic-cluster-administration) on cluster administration, useful for beginners
@@ -144,3 +145,5 @@ your contribution to the project, please join our [gitter chat][gitter-url].
[github-mark-duplicate-url]: https://docs.github.com/en/free-pro-team@latest/github/managing-your-work-on-github/about-duplicate-issues-and-pull-requests
[github-pr-docs-url]: https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request
[gitter-url]: https://gitter.im/naturalcrit/Lobby

View File

@@ -77,14 +77,234 @@ pre {
}
.varSyntaxTable th:first-of-type {
width:6cm;
width:6cm;
}
.page .exampleTable td,th {
border:1px dashed #00000030;
}
```
## changelog
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
### Wednesday 7/09/2025 - v3.19.3
{{taskList
##### calculuschild
* [x] Restoring original saving behavior; will continue investigating why save was failing for some users in background
}}
### Wednesday 7/09/2025 - v3.19.2
{{taskList
##### calculuschild
* [x] Hotfix for saving issues - Please refresh your browser and report if problems continue
}}
### Wednesday 7/09/2025 - v3.19.1
{{taskList
##### calculuschild
* [x] Send diffs instead of full file on save - should help with timeout/disconnect errors
}}
\column
### Thursday 05/22/2025 - v3.19.0
{{taskList
##### abquintic
* [x] Fix crash due to colons after `\page`
Fixes issue [#4105](https://github.com/naturalcrit/homebrewery/issues/4105)
* [x] Fix images with spaces in alt text not rendering
Fixes issue [#3659](https://github.com/naturalcrit/homebrewery/issues/3659)
* [x] Custom snippets! Open the new {{openSans **:fas_table_list: SNIPPETS**}} tab (next to the {{openSans **:fas_paintbrush: STYLE**}} tab). Custom snippets will appear in a new snippet dropdown, and will be included when imported as a custom theme.
* [x] Move several generic styles/snippets from PHB to the Blank theme; generic snippets like image masks no longer require the PHB theme.
* [x] Extract several Markdown+ syntax extensions into their own NPM packages, for use by the wider community.
* [x] Allow `\pagebreak` and `\columnbreak` as alternatives to `\page` and `\column`
Partially fixes issue [#4035](https://github.com/naturalcrit/homebrewery/issues/4035)
* [x] Fix misbehaving column breaks on old Chrome
Fixes issue [#4192](https://github.com/naturalcrit/homebrewery/issues/4192)
* [x] Self-host font-awesome icons; fix missing icons on local installs
Fixes issue [#1965](https://github.com/naturalcrit/homebrewery/issues/1965)
Fixes issue [#1548](https://github.com/naturalcrit/homebrewery/issues/1548)
##### G-Ambatte
* [x] Fix CORS issue on local installs
* [x] Fix print size issues when using the Facing and Flow view options.
Fixes issue [#4146](https://github.com/naturalcrit/homebrewery/issues/4146)
* [x] New built-in `$[HB_pageNumber]` variable. Works with math operations or can be reassigned like any other variable for more customization over the old `{{pageNumber,auto}}` snippet.\
New snippet found at {{openSans **:fas_pencil: TEXT EDITOR :fas_arrow_right: :fas_bookmark: PAGE NUMBERING :fas_arrow_right: :fas_arrow_down_1_9: VARIABLE AUTO PAGE NUMBER**}}
##### 5e-Cleric
* [x] Fix search bar covering up snippet bar (3 times)
Fixes issue [#4098](https://github.com/naturalcrit/homebrewery/issues/4098)
* [x] Save view toolbar settings across sessions
Fixes issue [#3835](https://github.com/naturalcrit/homebrewery/issues/3835)
* [x] Fix styling issues on the view toolbar
* [x] Update the Darkbrewery editor theme
Fixes issue [#3312](https://github.com/naturalcrit/homebrewery/issues/3312)
}}
\page
### Monday 03/10/2025 - v3.18.0
{{taskList
##### abquintic
* [x] Add ability to paste in any Share ID/URL into a brew's {{openSans :fas_circle_info: **Properties** :fas_arrow_right: **THEMES**}} selection, as long as that brew has been tagged as `meta:theme`. You can now share your custom brew themes without needing to make a personal copy.
* [x] Begin migration of custom Markdown extensions into their own NPM packages, for easier adoption by other users or projects
* [x] Fix external HTML appearing in open codeblocks
Fixes issue [#3206](https://github.com/naturalcrit/homebrewery/issues/3206)
* [x] Fix tables not rendering when directly after text
##### G-Ambatte
* [x] Cleanup of "cover pages" in the {{openSans :fas_rectangle_list: **NAVIGATION**}} list
* [x] Fix autosave triggering when no changes are present
Fixes issue [#4051](https://github.com/naturalcrit/homebrewery/issues/4051)
* [x] Remove empty table rows resulting from rowspan
Fixes issue [#1729](https://github.com/naturalcrit/homebrewery/issues/1729)
##### 5e-Cleric
* [x] Style fixes for covers art and logos on A4 size pages
* [x] Fix crash when trying to open brews that don't exist
* [x] Tweaks and style update styling on {{openSans **VAULT** :fas_dungeon:}} page.
Fixes issue [#4079](https://github.com/naturalcrit/homebrewery/issues/4079)
##### Calculuschild
* [x] `` now produces `<br>` instead of a `<div>`
* [x] Fix typos in tables freezing the editor
Fixes issue [#4059](https://github.com/naturalcrit/homebrewery/issues/4059)
##### MollyMaclachlan (New Contributor!)
* [x] Fixed typos in the Monster Stat Block snippet
Fixes issue [#4073](https://github.com/naturalcrit/homebrewery/issues/4073)
##### All
* [x] Update dependencies and scripts
* [x] Refactor components and backend tools
}}
\column
### Thursday 01/30/2025 - v3.17.0
{{taskList
##### 5e-Cleric
* [x] Update FAQ
* [x] Fix styling for Vault buttons and checkboxes
* [x] Improve navigation bar styling
* [x] Add feature to change username at https://www.naturalcrit.com/account
* [x] Fix Reddit link crash when title has non-latin chars
##### abquintic
* [x] Fix page shadows toolbar option
Fixes issue [#3919](https://github.com/naturalcrit/homebrewery/issues/3919)
* [x] Add `:>>>` syntax for horizontal :>>>>> spaces
* [x] Update Docker install instructions
Fixes issue [#1930](https://github.com/naturalcrit/homebrewery/issues/1930)
* [x] Allow styling pages via `\page{myStyles}` (with calculuschild)
Fixes issue [#3901](https://github.com/naturalcrit/homebrewery/issues/3901)
* [x] Update Ubuntu install instructions
Fixes issue [#1952](https://github.com/naturalcrit/homebrewery/issues/1952)
* [x] Add `:-:` `:-` `-:` syntax for paragraph alignment, similar to table column alignment; for example:
-: -: Right-aligned
:-: :-: Centered
* [x] Add `:-- 50% --:` syntax to allow setting table column widths by percentage; for example:
```
| Narrow | Wide |
|:- 10% -:|:-90%--:|
| Cell | Cell |
```
| Narrow | Wide |
|:- 10% -:|:-90%--:|
|Cell | Cell |
{exampleTable}
##### G-Ambatte
* [x] Fix crash when opening brew Properties tab
Fixes issue [#3927](https://github.com/naturalcrit/homebrewery/issues/3927)
* [x] Update error pages with steps to refresh credentials
Fixes issue [#3955](https://github.com/naturalcrit/homebrewery/issues/3955)
* [x] Add {{openSans :fas_rectangle_list: **NAVIGATION**}} menu to the viewer toolbar
##### calculuschild
* [x] Reduce display lag on large brews
##### Gazook89
* [x] Smarter detection of current page number
Fixes issue [#3824](https://github.com/naturalcrit/homebrewery/issues/3824)
##### All
* [x] Update dependencies and scripts
* [x] Refactor components and fix various errors
}}
\page
### Wednesday 11/27/2024 - v3.16.1
{{taskList
@@ -2053,4 +2273,4 @@ Massive changelog incoming:
* Added `phb.standalone.css` plus a build system for creating it
* Added page numbers and footer text
* Page accent now flips each page
* Page accent now flips each page

View File

@@ -1,47 +1,52 @@
require('./admin.less');
const React = require('react');
const createClass = require('create-react-class');
import './admin.less';
import React, { useEffect, useState } from 'react';
const BrewUtils = require('./brewUtils/brewUtils.jsx');
const NotificationUtils = require('./notificationUtils/notificationUtils.jsx');
import AuthorUtils from './authorUtils/authorUtils.jsx';
import LockTools from './lockTools/lockTools.jsx';
const tabGroups = ['brew', 'notifications'];
const tabGroups = ['brew', 'notifications', 'authors', 'locks'];
const Admin = createClass({
getDefaultProps : function() {
return {};
},
const ADMIN_TAB = 'HB_adminPage_currentTab';
getInitialState : function(){
return ({
currentTab : 'brew'
});
},
const Admin = ()=>{
const [currentTab, setCurrentTab] = useState('');
handleClick : function(newTab){
if(this.state.currentTab === newTab) return;
this.setState({
currentTab : newTab
});
},
useEffect(()=>{
setCurrentTab(localStorage.getItem(ADMIN_TAB) || 'brew');
}, []);
render : function(){
return <div className='admin'>
useEffect(()=>{
localStorage.setItem(ADMIN_TAB, currentTab);
}, [currentTab]);
return (
<div className='admin'>
<header>
<div className='container'>
<i className='fas fa-rocket' />
homebrewery admin
The Homebrewery Admin Page
<a href='/'>back to homepage</a>
</div>
</header>
<main className='container'>
<nav className='tabs'>
{tabGroups.map((tab, idx)=>{ return <button className={tab===this.state.currentTab ? 'active' : ''} key={idx} onClick={()=>{ return this.handleClick(tab); }}>{tab.toUpperCase()}</button>; })}
{tabGroups.map((tab, idx)=>(
<button
className={tab === currentTab ? 'active' : ''}
key={idx}
onClick={()=>setCurrentTab(tab)}>
{tab.toUpperCase()}
</button>
))}
</nav>
{this.state.currentTab==='brew' && <BrewUtils />}
{this.state.currentTab==='notifications' && <NotificationUtils />}
{currentTab === 'brew' && <BrewUtils />}
{currentTab === 'notifications' && <NotificationUtils />}
{currentTab === 'authors' && <AuthorUtils />}
{currentTab === 'locks' && <LockTools />}
</main>
</div>;
}
});
</div>
);
};
module.exports = Admin;

View File

@@ -3,6 +3,7 @@
@import 'naturalcrit/styles/animations.less';
@import 'naturalcrit/styles/colors.less';
@import 'naturalcrit/styles/tooltip.less';
@import './themes/fonts/iconFonts/fontAwesome.less';
@import 'font-awesome/css/font-awesome.css';
@@ -22,7 +23,7 @@ body {
}
:where(.admin) {
padding-bottom : 50px;
header {
padding : 20px 0px;
margin-bottom : 30px;
@@ -30,6 +31,7 @@ body {
color : white;
background-color : @red;
i { margin-right : 30px; }
a { float : right; }
}
hr { margin : 30px 0px; }
@@ -48,21 +50,23 @@ body {
}
dl {
@maxItemWidth : 132px;
display : grid;
grid-template-columns : 120px 1fr;
row-gap : 10px;
align-items : center;
justify-items : start;
padding-top : 0.5em;
dt {
float : left;
width : @maxItemWidth;
clear : left;
text-align : right;
float : left;
clear : left;
height : fit-content;
font-weight : 900;
text-align : right;
&::after { content : ' : '; }
}
dd {
height : 1em;
padding : 0 0 0.5em 0;
margin-left : @maxItemWidth + 6px;
}
dd { height : fit-content; }
}
.tabs button {
margin-right : 3px;
margin-left : 3px;
@@ -90,11 +94,45 @@ body {
}
}
table {
padding : 10px;
tr {
border-bottom : 1px solid;
&:last-of-type { border : none; }
&:nth-child(even) { background : #DDDDDD; }
}
thead {
background : rgb(193,236,230);
border-bottom : 2px solid;
}
th, td {
padding : 5px 10px;
vertical-align : middle;
text-align : center;
border-right : 1px solid;
&:last-child { border-right : none; }
}
th { font-weight : 900; }
td {
&:first-child {
font-weight : 900;
text-align : left;
}
}
}
.error {
background: rgb(178, 54, 54);
color:white;
font-weight: 900;
margin-block:10px;
padding:10px;
float : right;
padding : 10px;
margin-block : 10px;
font-weight : 900;
color : white;
background : rgb(178, 54, 54);
}
}

View File

@@ -0,0 +1,87 @@
import './authorLookup.less';
import React from 'react';
import request from 'superagent';
const authorLookup = ()=>{
const [author, setAuthor] = React.useState('');
const [searching, setSearching] = React.useState(false);
const [results, setResults] = React.useState([]);
const lookup = async ()=>{
if(!author) return;
setSearching(true);
setResults([]);
const brews = await request.get(`/admin/user/list/${author}`);
setResults(brews.body);
setSearching(false);
};
const renderResults = ()=>{
if(results.length == 0) return <>
<h2>Results</h2>
<p>None found.</p>
</>;
return <>
<h2>{`Results - ${results.length} brews` }</h2>
<table className='resultsTable'>
<thead>
<tr>
<th>Title</th>
<th>Share</th>
<th>Edit</th>
<th>Last Update</th>
<th>Storage</th>
</tr>
</thead>
<tbody>
{results
.sort((a, b)=>{ // Sort brews from most recently updated
if(a.updatedAt > b.updatedAt) return -1;
return 1;
})
.map((brew, idx)=>{
return <tr key={idx}>
<td><strong>{brew.title}</strong></td>
<td><a href={`/share/${brew.shareId}`}>{brew.shareId}</a></td>
<td>{brew.editId}</td>
<td style={{ width: '200px' }}>{brew.updatedAt}</td>
<td>{brew.googleId ? 'Google' : 'Homebrewery'}</td>
</tr>;
})}
</tbody>
</table>
</>;
};
const handleKeyPress = (evt)=>{
if(evt.key === 'Enter') return lookup();
};
const handleChange = (evt)=>{
setAuthor(evt.target.value);
};
return (
<div className='authorLookup'>
<div className='authorLookupInputs'>
<h2>Author Lookup</h2>
<label className='field'>
Author Name:
<input className='fieldInput' value={author} onKeyDown={handleKeyPress} onChange={handleChange} />
<button onClick={lookup}>
<i className={`fas ${searching ? 'fa-spin fa-spinner' : 'fa-search'}`} />
</button>
</label>
</div>
<div className='authorLookupResults'>
{renderResults()}
</div>
</div>
);
};
module.exports = authorLookup;

View File

@@ -0,0 +1,29 @@
.authorLookup {
position : relative;
display : flex;
flex-direction : column;
.field {
display : flex;
gap : 5px;
align-items : center;
justify-items : stretch;
width : 100%;
margin-bottom : 20px;
input {
height : 33px;
padding : 0px 10px;
margin-bottom : unset;
font-family : monospace;
}
button {
width : 50px;
i { margin-right : 10px; }
}
}
}

View File

@@ -0,0 +1,13 @@
import React from 'react';
import AuthorLookup from './authorLookup/authorLookup.jsx';
const authorUtils = ()=>{
return (
<section className='authorUtils'>
<AuthorLookup />
</section>
);
};
module.exports = authorUtils;

View File

@@ -1,10 +1,8 @@
require('./brewCleanup.less');
const React = require('react');
const createClass = require('create-react-class');
const request = require('superagent');
const BrewCleanup = createClass({
displayName : 'BrewCleanup',
getDefaultProps(){
@@ -39,9 +37,9 @@ const BrewCleanup = createClass({
if(!this.state.primed) return;
if(!this.state.count){
return <div className='removeBox'>No Matching Brews found.</div>;
return <div className='result noBrews'>No Matching Brews found.</div>;
}
return <div className='removeBox'>
return <div className='result'>
<button onClick={this.cleanup} className='remove'>
{this.state.pending
? <i className='fas fa-spin fa-spinner' />
@@ -52,7 +50,7 @@ const BrewCleanup = createClass({
</div>;
},
render(){
return <div className='BrewCleanup'>
return <div className='brewUtil brewCleanup'>
<h2> Brew Cleanup </h2>
<p>Removes very short brews to tidy up the database</p>
@@ -65,7 +63,7 @@ const BrewCleanup = createClass({
{this.renderPrimed()}
{this.state.error
&& <div className='error'>{this.state.error.toString()}</div>
&& <div className='error noBrews'>{this.state.error.toString()}</div>
}
</div>;
}

View File

@@ -1,9 +0,0 @@
.BrewCleanup {
.removeBox {
margin-top : 20px;
button {
margin-right : 10px;
background-color : @red;
}
}
}

View File

@@ -1,10 +1,7 @@
require('./brewCompress.less');
const React = require('react');
const createClass = require('create-react-class');
const request = require('superagent');
const BrewCompress = createClass({
displayName : 'BrewCompress',
getDefaultProps(){
@@ -53,9 +50,9 @@ const BrewCompress = createClass({
if(!this.state.primed) return;
if(!this.state.count){
return <div className='removeBox'>No Matching Brews found.</div>;
return <div className='result noBrews'>No Matching Brews found.</div>;
}
return <div className='removeBox'>
return <div className='result'>
<button onClick={this.cleanup} className='remove'>
{this.state.pending
? <i className='fas fa-spin fa-spinner' />
@@ -69,7 +66,7 @@ const BrewCompress = createClass({
</div>;
},
render(){
return <div className='BrewCompress'>
return <div className='brewUtil brewCompress'>
<h2> Brew Compression </h2>
<p>Compresses the text in brews to binary</p>

View File

@@ -1,9 +0,0 @@
.BrewCompress {
.removeBox {
margin-top : 20px;
button {
margin-right : 10px;
background-color : @red;
}
}
}

View File

@@ -1,5 +1,3 @@
require('./brewLookup.less');
const React = require('react');
const createClass = require('create-react-class');
const cx = require('classnames');
@@ -55,7 +53,7 @@ const BrewLookup = createClass({
renderFoundBrew(){
const brew = this.state.foundBrew;
return <div className='foundBrew'>
return <div className='result'>
<dl>
<dt>Title</dt>
<dd>{brew.title}</dd>
@@ -90,7 +88,7 @@ const BrewLookup = createClass({
},
render(){
return <div className='brewLookup'>
return <div className='brewUtil brewLookup'>
<h2>Brew Lookup</h2>
<input type='text' value={this.state.query} onChange={this.handleChange} placeholder='edit or share id' />
<button onClick={this.lookup}>
@@ -106,7 +104,7 @@ const BrewLookup = createClass({
{this.state.foundBrew
? this.renderFoundBrew()
: <div className='noBrew'>No brew found.</div>
: <div className='result noBrew'>No brew found.</div>
}
</div>;
}

View File

@@ -1,6 +0,0 @@
.brewLookup {
.cleanButton {
display : inline-block;
width : 100%;
}
}

View File

@@ -1,6 +1,6 @@
const React = require('react');
const createClass = require('create-react-class');
require('./brewUtils.less');
const BrewCleanup = require('./brewCleanup/brewCleanup.jsx');
const BrewLookup = require('./brewLookup/brewLookup.jsx');

View File

@@ -0,0 +1,29 @@
.brewUtil {
.result {
margin-top : 20px;
button {
margin-right : 10px;
background-color : @red;
}
}
.cleanButton {
display : inline-block;
width : 100%;
}
}
.stats {
position : relative;
.pending {
position : absolute;
top : 0.5em;
left : 100px;
width : 100%;
height : 100%;
}
&:has(.pending) { opacity : 0.5; }
dl { grid-template-columns : 200px 250px; }
}

View File

@@ -1,11 +1,8 @@
require('./stats.less');
const React = require('react');
const createClass = require('create-react-class');
const cx = require('classnames');
const request = require('superagent');
const Stats = createClass({
displayName : 'Stats',
getDefaultProps(){
@@ -14,7 +11,8 @@ const Stats = createClass({
getInitialState(){
return {
stats : {
totalBrews : 0
totalBrews : 0,
totalPublishedBrews : 0
},
fetching : false
};
@@ -29,11 +27,13 @@ const Stats = createClass({
.finally(()=>this.setState({ fetching: false }));
},
render(){
return <div className='Stats'>
return <div className='brewUtil stats'>
<h2> Stats </h2>
<dl>
<dt>Total Brew Count</dt>
<dd>{this.state.stats.totalBrews}</dd>
<dt>Total Brews Published</dt>
<dd>{this.state.stats.totalPublishedBrews}</dd>
</dl>
{this.state.fetching

View File

@@ -1,13 +0,0 @@
.Stats {
position : relative;
.pending {
position : absolute;
top : 0px;
left : 0px;
width : 100%;
height : 100%;
background-color : rgba(238,238,238, 0.5);
}
}

View File

@@ -0,0 +1,342 @@
/*eslint max-lines: ["warn", {"max": 500, "skipBlankLines": true, "skipComments": true}]*/
require('./lockTools.less');
const React = require('react');
const createClass = require('create-react-class');
import request from '../../homebrew/utils/request-middleware.js';
const LockTools = createClass({
displayName : 'LockTools',
getInitialState : function() {
return {
fetching : false,
reviewCount : 0
};
},
componentDidMount : function() {
this.updateReviewCount();
},
updateReviewCount : async function() {
const newCount = await request.get('/api/lock/count')
.then((res)=>{return res.body?.count || 'Unknown';});
if(newCount != this.state.reviewCount){
this.setState({
reviewCount : newCount
});
}
},
updateLockData : function(lock){
this.setState({
lock : lock
});
},
render : function() {
return <div className='lockTools'>
<h2>Lock Count</h2>
<p>Number of brews currently locked: {this.state.reviewCount}</p>
<button onClick={this.updateReviewCount}>REFRESH</button>
<hr />
<LockTable title='Locked Brews' text='Total Locked Brews' resultName='lockedDocuments' fetchURL='/api/locks' propertyNames={['shareId', 'title']} loadBrew={this.updateLockData} ></LockTable>
<hr />
<LockTable title='Brews Awaiting Review' text='Total Reviews Waiting' resultName='reviewDocuments' fetchURL='/api/lock/reviews' propertyNames={['shareId', 'title']} loadBrew={this.updateLockData} ></LockTable>
<hr />
<LockBrew key={this.state.lock?.key || 0} lock={this.state.lock}></LockBrew>
<hr />
<div style={{ columns: 2 }}>
<LockLookup title='Unlock Brew' fetchURL='/api/unlock' updateFn={this.updateReviewCount}></LockLookup>
<LockLookup title='Clear Review Request' fetchURL='/api/lock/review/remove'></LockLookup>
</div>
<hr />
</div>;
}
});
const LockBrew = createClass({
displayName : 'LockBrew',
getInitialState : function() {
// Default values
return {
brewId : this.props.lock?.shareId || '',
code : this.props.lock?.code || 455,
editMessage : this.props.lock?.editMessage || '',
shareMessage : this.props.lock?.shareMessage || 'This Brew has been locked.',
result : {},
overwrite : false,
};
},
handleChange : function(e, varName) {
const output = {};
output[varName] = e.target.value;
this.setState(output);
},
submit : function(e){
e.preventDefault();
if(!this.state.editMessage) return;
const newLock = {
overwrite : this.state.overwrite,
code : parseInt(this.state.code) || 100,
editMessage : this.state.editMessage,
shareMessage : this.state.shareMessage,
applied : new Date
};
request.post(`/api/lock/${this.state.brewId}`)
.send(newLock)
.set('Content-Type', 'application/json')
.then((response)=>{
this.setState({ result: response.body });
})
.catch((err)=>{
this.setState({ result: err.response.body });
});
},
renderInput : function (name) {
return <input type='text' name={name} value={this.state[name]} onChange={(e)=>this.handleChange(e, name)} autoComplete='off' required/>;
},
renderResult : function(){
return <>
<h3>Result:</h3>
<table>
<tbody>
{Object.keys(this.state.result).map((key, idx)=>{
return <tr key={`${idx}-row`}>
<td key={`${idx}-key`}>{key}</td>
<td key={`${idx}-value`}>{this.state.result[key].toString()}
</td>
</tr>;
})}
</tbody>
</table>
</>;
},
render : function() {
return <div className='lockBrew'>
<div className='lockForm'>
<h2>Lock Brew</h2>
<form onSubmit={this.submit}>
<label>
ID:
{this.renderInput('brewId')}
</label>
<br />
<label>
Error Code:
{this.renderInput('code')}
</label>
<br />
<label>
Private Message:
{this.renderInput('editMessage')}
</label>
<br />
<label>
Public Message:
{this.renderInput('shareMessage')}
</label>
<br />
<label className='checkbox'>
Overwrite
<input name='overwrite' className='checkbox' type='checkbox' value={this.state.overwrite} onClick={()=>{return this.setState((prevState)=>{return { overwrite: !prevState.overwrite };});}} />
</label>
<label>
<input type='submit' />
</label>
</form>
{this.state.result && this.renderResult()}
</div>
<div className='lockSuggestions'>
<h2>Suggestions</h2>
<div className='lockCodes'>
<h3>Codes</h3>
<ul>
<li>455 - Generic Lock</li>
<li>456 - Copyright issues</li>
<li>457 - Confidential Information Leakage</li>
<li>458 - Sensitive Personal Information</li>
<li>459 - Defamation or Libel</li>
<li>460 - Hate Speech or Discrimination</li>
<li>461 - Illegal Activities</li>
<li>462 - Malware or Phishing</li>
<li>463 - Plagiarism</li>
<li>465 - Misrepresentation</li>
<li>466 - Inappropriate Content</li>
</ul>
</div>
<div className='lockMessages'>
<h3>Messages</h3>
<ul>
<li><b>Private Message:</b> This is the private message that is ONLY displayed to the authors of the locked brew. This message MUST specify exactly what actions must be taken in order to have the brew unlocked.</li>
<li><b>Public Message:</b> This is the public message that is displayed to the EVERYONE that attempts to view the locked brew.</li>
</ul>
</div>
</div>
</div>;
}
});
const LockTable = createClass({
displayName : 'LockTable',
getDefaultProps : function() {
return {
title : '',
text : '',
fetchURL : '/api/locks',
resultName : '',
propertyNames : ['shareId'],
loadBrew : ()=>{}
};
},
getInitialState : function() {
return {
result : '',
error : '',
searching : false
};
},
lockKey : React.createRef(0),
clickFn : function (){
this.setState({ searching: true, error: null });
request.get(this.props.fetchURL)
.then((res)=>this.setState({ result: res.body }))
.catch((err)=>this.setState({ result: err.response.body }))
.finally(()=>{
this.setState({ searching: false });
});
},
updateBrewLockData : function (lockData){
this.lockKey.current++;
const brewData = {
key : this.lockKey.current,
shareId : lockData.shareId,
code : lockData.lock.code,
editMessage : lockData.lock.editMessage,
shareMessage : lockData.lock.shareMessage
};
this.props.loadBrew(brewData);
},
render : function () {
return <>
<div className='brewsAwaitingReview'>
<div className='brewBlock'>
<h2>{this.props.title}</h2>
<button onClick={this.clickFn}>
REFRESH
<i className={`fas ${!this.state.searching ? 'fa-search' : 'fa-spin fa-spinner'}`} />
</button>
</div>
{this.state.result[this.props.resultName] &&
<>
<p>{this.props.text}: {this.state.result[this.props.resultName].length}</p>
<table className='lockTable'>
<thead>
<tr>
{this.props.propertyNames.map((name, idx)=>{
return <th key={idx}>{name}</th>;
})}
<th>clip</th>
<th>load</th>
</tr>
</thead>
<tbody>
{this.state.result[this.props.resultName].map((result, resultIdx)=>{
return <tr className='row' key={`${resultIdx}-row`}>
{this.props.propertyNames.map((name, nameIdx)=>{
return <td key={`${resultIdx}-${nameIdx}`}>
{result[name].toString()}
</td>;
})}
<td className='icon' title='Copy ID to Clipboard' onClick={()=>{navigator.clipboard.writeText(result.shareId.toString());}}><i className='fa-regular fa-clipboard'></i></td>
<td className='icon' title='View Lock details' onClick={()=>{this.updateBrewLockData(result);}}><i className='fa-regular fa-circle-down'></i></td>
</tr>;
})}
</tbody>
</table>
</>
}
</div>
</>;
}
});
const LockLookup = createClass({
displayName : 'LockLookup',
getDefaultProps : function() {
return {
fetchURL : '/api/lookup'
};
},
getInitialState : function() {
return {
query : '',
result : '',
error : '',
searching : false
};
},
handleChange(e){
this.setState({ query: e.target.value });
},
clickFn(){
this.setState({ searching: true, error: null });
request.put(`${this.props.fetchURL}/${this.state.query}`)
.then((res)=>this.setState({ result: res.body }))
.catch((err)=>this.setState({ result: err.response.body }))
.finally(()=>{
this.setState({ searching: false });
});
},
renderResult : function(){
return <div className='lockLookup'>
<h3>Result:</h3>
<table>
<tbody>
{Object.keys(this.state.result).map((key, idx)=>{
return <tr key={`${idx}-row`}>
<td key={`${idx}-key`}>{key}</td>
<td key={`${idx}-value`}>{this.state.result[key].toString()}
</td>
</tr>;
})}
</tbody>
</table>
</div>;
},
render : function() {
return <div className='brewLookup'>
<h2>{this.props.title}</h2>
<input type='text' value={this.state.query} onChange={this.handleChange} placeholder='share id' />
<button onClick={this.clickFn}>
<i className={`fas ${!this.state.searching ? 'fa-search' : 'fa-spin fa-spinner'}`} />
</button>
{this.state.error
&& <div className='error'>{this.state.error.toString()}</div>
}
{this.state.result && this.renderResult()}
</div>;
}
});
module.exports = LockTools;

View File

@@ -0,0 +1,66 @@
.lockTools {
.lockBrew {
columns : 2;
.lockForm {
break-inside : avoid;
label {
display : inline-block;
width : 100%;
line-height : 2.25em;
text-align : right;
input {
float : right;
width : 65%;
margin-left : 10px;
}
&.checkbox {
line-height: 1.5em;
input {
width : 1.5em;
height : 1.5em;
}
}
}
}
.lockSuggestions {
line-height : 1.2em;
break-inside : avoid;
columns : 2;
h2 { column-span : all; }
h3 { margin-top : 0px; }
b { font-weight : 600; }
.lockCodes { break-inside : avoid; }
}
}
.lockTable {
cursor : default;
break-inside : avoid;
.row:hover {
color : #000000;
background-color : #CCCCCC;
}
.icon {
cursor : pointer;
&:hover { text-shadow : 0px 0px 6px black; }
}
}
th, td {
padding : 4px 10px;
text-align : center;
}
table, td { border : 1px solid #333333; }
.brewLookup {
min-height : 175px;
break-inside : avoid;
h2 { margin-top : 0px; }
}
button i { padding-left : 5px; }
}

View File

@@ -6,31 +6,32 @@
.field {
display : grid;
grid-template-columns : 120px 150px;
grid-template-columns : 120px 200px;
align-items : center;
justify-items : stretch;
width : 100%;
margin-bottom : 20px;
input {
height : 33px;
padding : 0px 10px;
margin-bottom : unset;
font-family : monospace;
&[type='date'] { width : 14ch; }
}
textarea {
width : 50ch;
min-height : 7em;
max-height : 20em;
resize : vertical;
padding : 10px;
resize : vertical;
}
}
button {
width: 200px;
width : 200px;
i { margin-right : 10px; }
}

View File

@@ -1,8 +1,8 @@
.notificationLookup {
width : 450px;
height : fit-content;
height : fit-content;
.noNotification { margin-block : 20px; }
.notificationList {
display : flex;
flex-direction : column;
@@ -30,11 +30,6 @@
font-size : 20px;
font-weight : 900;
}
dl dt{
font-weight: 900;
}
}
}
.noNotification { margin-block : 20px; }
}

View File

@@ -1,13 +1,11 @@
.anchored-box {
position:absolute;
@supports (inset-block-start: anchor(bottom)){
inset-block-start: anchor(bottom);
}
justify-self: anchor-center;
visibility: hidden;
&.active {
visibility: visible;
position : absolute;
visibility : hidden;
justify-self : anchor-center;
@supports (inset-block-start: anchor(bottom)) {
inset-block-start : anchor(bottom);
}
&.active { visibility : visible; }
}

View File

@@ -45,6 +45,7 @@ const Combobox = createClass({
},
handleDropdown : function(show){
this.setState({
value : show ? '' : this.props.default,
showDropdown : show,
inputFocused : this.props.autoSuggest.clearAutoSuggestOnClick ? show : false
});
@@ -58,10 +59,10 @@ const Combobox = createClass({
this.props.onEntry(e);
});
},
handleSelect : function(e){
handleSelect : function(value, data=value){
this.setState({
value : e.currentTarget.getAttribute('data-value')
}, ()=>{this.props.onSelect(this.state.value);});
value : value
}, ()=>{this.props.onSelect(data);});
;
},
renderTextInput : function(){
@@ -78,10 +79,11 @@ const Combobox = createClass({
if(!e.target.checkValidity()){
this.setState({
value : this.props.default
}, ()=>this.props.onEntry(e));
});
}
}}
/>
<i className='fas fa-caret-down'/>
</div>
);
},
@@ -92,11 +94,10 @@ const Combobox = createClass({
const filterOn = _.isString(this.props.autoSuggest.filterOn) ? [this.props.autoSuggest.filterOn] : this.props.autoSuggest.filterOn;
const filteredArrays = filterOn.map((attr)=>{
const children = dropdownChildren.filter((item)=>{
if(suggestMethod === 'includes'){
if(suggestMethod === 'includes')
return item.props[attr]?.toLowerCase().includes(this.state.value.toLowerCase());
} else if(suggestMethod === 'startsWith'){
if(suggestMethod === 'startsWith')
return item.props[attr]?.toLowerCase().startsWith(this.state.value.toLowerCase());
}
});
return children;
});
@@ -111,7 +112,7 @@ const Combobox = createClass({
},
render : function () {
const dropdownChildren = this.state.options.map((child, i)=>{
const clone = React.cloneElement(child, { onClick: (e)=>this.handleSelect(e) });
const clone = React.cloneElement(child, { onClick: ()=>this.handleSelect(child.props.value, child.props.data) });
return clone;
});
return (

View File

@@ -1,50 +1,46 @@
.dropdown-container {
position:relative;
input {
width: 100%;
}
.dropdown-options {
position:absolute;
background-color: white;
z-index: 100;
width: 100%;
border: 1px solid gray;
overflow-y: auto;
max-height: 200px;
position : relative;
input { width : 100%; }
.item i {
position : absolute;
right : 10px;
color : black;
}
.dropdown-options {
position : absolute;
z-index : 100;
width : 100%;
max-height : 200px;
overflow-y : auto;
background-color : white;
border : 1px solid gray;
&::-webkit-scrollbar {
width: 14px;
}
&::-webkit-scrollbar-track {
background: #ffffff;
}
&::-webkit-scrollbar-thumb {
background-color: #949494;
border-radius: 10px;
border: 3px solid #ffffff;
}
.item {
position:relative;
font-size: 11px;
font-family: Open Sans;
padding: 5px;
cursor: default;
margin: 0 3px;
//border-bottom: 1px solid darkgray;
&:hover {
filter: brightness(120%);
background-color: rgb(163, 163, 163);
}
.detail {
width:100%;
text-align: left;
color: rgb(124, 124, 124);
font-style:italic;
font-size: 9px;
}
}
}
&::-webkit-scrollbar { width : 14px; }
&::-webkit-scrollbar-track { background : #FFFFFF; }
&::-webkit-scrollbar-thumb {
background-color : #949494;
border : 3px solid #FFFFFF;
border-radius : 10px;
}
.item {
position : relative;
padding : 5px;
margin : 0 3px;
font-family : 'Open Sans';
font-size : 11px;
cursor : default;
&:hover {
background-color : rgb(163, 163, 163);
filter : brightness(120%);
}
.detail {
width : 100%;
font-size : 9px;
font-style : italic;
color : rgb(124, 124, 124);
text-align : left;
}
}
}
}

View File

@@ -2,7 +2,8 @@ require('./splitPane.less');
const React = require('react');
const { useState, useEffect } = React;
const storageKey = 'naturalcrit-pane-split';
const PANE_WIDTH_KEY = 'HB_editor_splitWidth';
const LIVE_SCROLL_KEY = 'HB_editor_liveScroll';
const SplitPane = (props)=>{
const {
@@ -18,9 +19,9 @@ const SplitPane = (props)=>{
const [liveScroll, setLiveScroll] = useState(false);
useEffect(()=>{
const savedPos = window.localStorage.getItem(storageKey);
const savedPos = window.localStorage.getItem(PANE_WIDTH_KEY);
setDividerPos(savedPos ? limitPosition(savedPos, 0.1 * (window.innerWidth - 13), 0.9 * (window.innerWidth - 13)) : window.innerWidth / 2);
setLiveScroll(window.localStorage.getItem('liveScroll') === 'true');
setLiveScroll(window.localStorage.getItem(LIVE_SCROLL_KEY) === 'true');
window.addEventListener('resize', handleResize);
return ()=>window.removeEventListener('resize', handleResize);
@@ -29,13 +30,13 @@ const SplitPane = (props)=>{
const limitPosition = (x, min = 1, max = window.innerWidth - 13)=>Math.round(Math.min(max, Math.max(min, x)));
//when resizing, the divider should grow smaller if less space is given, then grow back if the space is restored, to the original position
const handleResize = () =>setDividerPos(limitPosition(window.localStorage.getItem(storageKey), 0.1 * (window.innerWidth - 13), 0.9 * (window.innerWidth - 13)));
const handleResize = ()=>setDividerPos(limitPosition(window.localStorage.getItem(PANE_WIDTH_KEY), 0.1 * (window.innerWidth - 13), 0.9 * (window.innerWidth - 13)));
const handleUp =(e)=>{
e.preventDefault();
if(isDragging) {
onDragFinish(dividerPos);
window.localStorage.setItem(storageKey, dividerPos);
window.localStorage.setItem(PANE_WIDTH_KEY, dividerPos);
}
setIsDragging(false);
};
@@ -52,7 +53,7 @@ const SplitPane = (props)=>{
};
const liveScrollToggle = ()=>{
window.localStorage.setItem('liveScroll', String(!liveScroll));
window.localStorage.setItem(LIVE_SCROLL_KEY, String(!liveScroll));
setLiveScroll(!liveScroll);
};

View File

@@ -21,8 +21,8 @@
background-color : #BBBBBB;
.dots {
display : table-cell;
text-align : center;
vertical-align : middle;
text-align : center;
i {
display : block !important;
margin : 10px 0px;

View File

@@ -19,12 +19,15 @@ const { printCurrentBrew } = require('../../../shared/helpers.js');
import HeaderNav from './headerNav/headerNav.jsx';
import { safeHTML } from './safeHTML.js';
const PAGEBREAK_REGEX_V3 = /^(?=\\page(?: *{[^\n{}]*})?$)/m;
const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m;
const PAGEBREAK_REGEX_LEGACY = /\\page(?:break)?/m;
const COLUMNBREAK_REGEX_LEGACY = /\\column(:?break)?/m;
const PAGE_HEIGHT = 1056;
const TOOLBAR_STATE_KEY = 'HB_renderer_toolbarState';
const INITIAL_CONTENT = dedent`
<!DOCTYPE html><html><head>
<link href="//use.fontawesome.com/releases/v6.5.1/css/all.css" rel="stylesheet" type="text/css" />
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
<link href='/homebrew/bundle.css' type="text/css" rel='stylesheet' />
<base target=_blank>
@@ -38,7 +41,7 @@ const BrewPage = (props)=>{
index : 0,
...props
};
const pageRef = useRef(null);
const pageRef = useRef(null);
const cleanText = safeHTML(props.contents);
useEffect(()=>{
@@ -114,16 +117,24 @@ const BrewRenderer = (props)=>{
zoomLevel : 100,
spread : 'single',
startOnRight : true,
pageShadows : true
pageShadows : true,
rowGap : 5,
columnGap : 10,
});
//useEffect to store or gather toolbar state from storage
useEffect(()=>{
const toolbarState = JSON.parse(window.localStorage.getItem(TOOLBAR_STATE_KEY));
toolbarState && setDisplayOptions(toolbarState);
}, []);
const [headerState, setHeaderState] = useState(false);
const mainRef = useRef(null);
const pagesRef = useRef(null);
if(props.renderer == 'legacy') {
rawPages = props.text.split('\\page');
rawPages = props.text.split(PAGEBREAK_REGEX_LEGACY);
} else {
rawPages = props.text.split(PAGEBREAK_REGEX_V3);
}
@@ -180,23 +191,26 @@ const BrewRenderer = (props)=>{
let attributes = {};
if(props.renderer == 'legacy') {
pageText.replace(COLUMNBREAK_REGEX_LEGACY, '```\n````\n'); // Allow Legacy brews to use `\column(break)`
const html = MarkdownLegacy.render(pageText);
return <BrewPage className='page phb' index={index} key={index} contents={html} style={styles} onVisibilityChange={handlePageVisibilityChange} />;
} 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
styles = _.mapKeys(styles, (v, k)=>k.startsWith('--') ? k : _.camelCase(k)); // Convert CSS to camelCase for React
classes = [classes, injectedTags.classes].join(' ').trim();
attributes = injectedTags.attributes;
}
pageText = pageText.includes('\n') ? pageText.substring(pageText.indexOf('\n') + 1) : ''; // Remove the \page line
}
// DO NOT REMOVE!!! REQUIRED FOR BACKWARDS COMPATIBILITY WITH NON-UPGRADABLE VERSIONS OF CHROME.
pageText += `\n\n&nbsp;\n\\column\n&nbsp;`; //Artificial column break at page end to emulate column-fill:auto (until `wide` is used, when column-fill:balance will reappear)
const html = Markdown.render(pageText, index);
return <BrewPage className={classes} index={index} key={index} contents={html} style={styles} attributes={attributes} onVisibilityChange={handlePageVisibilityChange} />;
@@ -272,6 +286,7 @@ const BrewRenderer = (props)=>{
const handleDisplayOptionsChange = (newDisplayOptions)=>{
setDisplayOptions(newDisplayOptions);
localStorage.setItem(TOOLBAR_STATE_KEY, JSON.stringify(newDisplayOptions));
};
const pagesStyle = {
@@ -280,12 +295,6 @@ const BrewRenderer = (props)=>{
rowGap : `${displayOptions.rowGap}px`
};
const styleObject = {};
if(global.config.deployment) {
styleObject.backgroundImage = `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='40px' width='200px'><text x='0' y='15' fill='%23fff7' font-size='20'>${global.config.deployment}</text></svg>")`;
}
const renderedStyle = useMemo(()=>renderStyle(), [props.style, props.themeBundle]);
renderedPages = useMemo(()=>renderPages(), [props.text, displayOptions]);
@@ -314,10 +323,9 @@ const BrewRenderer = (props)=>{
contentDidMount={frameDidMount}
onClick={()=>{emitClick();}}
>
<div className={`brewRenderer ${global.config.deployment && 'deployment'}`}
<div className='brewRenderer'
onKeyDown={handleControlKeys}
tabIndex={-1}
style={ styleObject }
>
{/* Apply CSS from Style tab and render pages from Markdown tab */}

View File

@@ -1,43 +1,38 @@
@import (multiple, less) 'shared/naturalcrit/styles/reset.less';
.brewRenderer {
height : 100vh;
padding-top : 60px;
overflow-y : scroll;
will-change : transform;
padding-top : 60px;
height : 100vh;
&:has(.facing, .flow) {
padding : 60px 30px;
}
&.deployment {
background-color: darkred;
}
&:has(.facing, .flow) { padding : 60px 30px; }
:where(.pages) {
&.facing {
display: grid;
grid-template-columns: repeat(2, auto);
grid-template-rows: repeat(3, auto);
gap: 10px 10px;
justify-content: safe center;
display : grid;
grid-template-rows : repeat(3, auto);
grid-template-columns : repeat(2, auto);
gap : 10px 10px;
justify-content : safe center;
&.recto .page:first-child {
// sets first page on 'right' ('recto') of the preview, as if for a Cover page.
// todo: add a checkbox to toggle this setting
grid-column-start: 2;
grid-column-start : 2;
}
& :where(.page) {
margin-left: unset !important;
margin-right: unset !important;
margin-right : unset !important;
margin-left : unset !important;
}
}
&.flow {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: safe center;
display : flex;
flex-wrap : wrap;
gap : 10px;
justify-content : safe center;
& :where(.page) {
flex: 0 0 auto;
margin-left: unset !important;
margin-right: unset !important;
flex : 0 0 auto;
margin-right : unset !important;
margin-left : unset !important;
}
}
@@ -50,9 +45,7 @@
margin-left : auto;
box-shadow : 1px 4px 14px #000000;
}
*[id] {
scroll-margin-top:100px;
}
*[id] { scroll-margin-top : 100px; }
}
&::-webkit-scrollbar {
width : 20px;
@@ -74,16 +67,18 @@
@media print {
.toolBar { display : none; }
.brewRenderer {
height : 100%;
padding-top : unset;
overflow-y : unset;
height : 100%;
padding : unset;
overflow-y : unset;
&:has(.facing, .flow) {
padding : unset;
}
.pages {
margin : 0px;
zoom: 100% !important;
margin : 0px;
zoom : 100% !important;
display : block;
& > .page { box-shadow : unset; }
}
}
.headerNav {
visibility: hidden;
}
.headerNav { visibility : hidden; }
}

View File

@@ -3,7 +3,6 @@ require('./headerNav.less');
import * as React from 'react';
import * as _ from 'lodash';
const MAX_TEXT_LENGTH = 40;
const HeaderNav = React.forwardRef(({}, pagesRef)=>{
@@ -11,11 +10,30 @@ const HeaderNav = React.forwardRef(({}, pagesRef)=>{
const renderHeaderLinks = ()=>{
if(!pagesRef.current) return;
// Top Level Pages
// Pages that contain an element with a specified class (e.g. cover pages, table of contents)
// will NOT have its content scanned for navigation headers, instead displaying a custom label
// ---
// The property name is class that will be used for detecting the page is a top level page
// The property value is a function that returns the text to be used
const topLevelPages = {
'.frontCover' : (el, pageType)=>{ const text = getHeaderContent(el); return text ? `Cover: ${text}` : 'Cover Page'; },
'.insideCover' : (el, pageType)=>{ const text = getHeaderContent(el); return text ? `Interior: ${text}` : 'Interior Cover Page'; },
'.partCover' : (el, pageType)=>{ const text = getHeaderContent(el); return text ? `Section: ${text}` : 'Section Cover Page'; },
'.backCover' : (el, pageType)=>{ const text = getHeaderContent(el); return text ? `Back: ${text}` : 'Rear Cover Page'; },
'.toc' : ()=>{ return 'Table of Contents'; },
};
const getHeaderContent = (el)=>el.querySelector('h1')?.textContent;
const topLevelPageSelector = Object.keys(topLevelPages).join(',');
const selector = [
'.pages > .page', // All page elements, which by definition have IDs
'.page:not(:has(.toc)) > [id]', // All direct children of non-ToC .page with an ID (Legacy)
'.page:not(:has(.toc)) > .columnWrapper > [id]', // All direct children of non-ToC .page > .columnWrapper with an ID (V3)
'.page:not(:has(.toc)) h2', // All non-ToC H2 titles, like Monster frame titles
'.pages > .page', // All page elements, which by definition have IDs
`.page:not(:has(${topLevelPageSelector})) > [id]`, // All direct children of non-excluded .pages with an ID (Legacy)
`.page:not(:has(${topLevelPageSelector})) > .columnWrapper > [id]`, // All direct children of non-excluded .page > .columnWrapper with an ID (V3)
`.page:not(:has(${topLevelPageSelector})) h2`, // All non-excluded H2 titles, like Monster frame titles
];
const elements = pagesRef.current.querySelectorAll(selector.join(','));
if(!elements) return;
@@ -23,45 +41,35 @@ const HeaderNav = React.forwardRef(({}, pagesRef)=>{
// navList is a list of objects which have the following structure:
// {
// depth : how deeply indented the item should be
// text : the text to display in the nav link
// link : the hyperlink to navigate to when clicked
// className : [optional] the class to apply to the nav link for styling
// depth : how deeply indented the item should be
// text : the text to display in the nav link
// link : the hyperlink to navigate to when clicked
// className : [optional] the class to apply to the nav link for styling
// }
elements.forEach((el)=>{
if(el.className.match(/\bpage\b/)) {
let text = `Page ${el.id.slice(1)}`; // The ID of a page *should* always be equal to `p` followed by the page number
if(el.querySelector('.toc')){ // If the page contains a table of contents, add "- Contents" to the display text
text += ' - Contents';
};
navList.push({
depth : 0, // Pages are always at the least indented level
text : text,
link : el.id,
className : 'pageLink'
});
return;
}
if(el.localName.match(/^h[1-6]/)){ // Header elements H1 through H6
navList.push({
depth : el.localName[1], // Depth is set by the header level
text : el.textContent, // Use `textContent` because `innerText` is affected by rendering, e.g. 'content-visibility: auto'
link : el.id
});
return;
}
navList.push({
depth : 7, // All unmatched elements with IDs are set to the maximum depth (7)
text : el.textContent, // Use `textContent` because `innerText` is affected by rendering, e.g. 'content-visibility: auto'
const navEntry = { // Default structure of a navList entry
depth : 7, // All unmatched elements with IDs are set to the maximum depth (7)
text : el.textContent, // Use `textContent` because `innerText` is affected by rendering, e.g. 'content-visibility: auto'
link : el.id
});
});
return _.map(navList, (navItem, index)=>{
return <HeaderNavItem {...navItem} key={index} />;
};
if(el.classList.contains('page')) {
let text = `Page ${el.id.slice(1)}`; // Get the page # by trimming off the 'p' from the ID
const pageType = Object.keys(topLevelPages).find((pageType)=>el.querySelector(pageType));
if(pageType)
text += ` - ${topLevelPages[pageType](el, pageType)}`; // If a Top Level Page, add extra label
navEntry.depth = 0; // Pages are always at the least indented level
navEntry.text = text;
navEntry.className = 'pageLink';
} else if(el.localName.match(/^h[1-6]/)){ // Header elements H1 through H6
navEntry.depth = el.localName[1]; // Depth is set by the header level
}
navList.push(navEntry);
});
return _.map(navList, (navItem, index)=><HeaderNavItem {...navItem} key={index} />
);
};
return <nav className='headerNav'>
@@ -69,8 +77,7 @@ const HeaderNav = React.forwardRef(({}, pagesRef)=>{
{renderHeaderLinks()}
</ul>
</nav>;
}
);
});
const HeaderNavItem = ({ link, text, depth, className })=>{

View File

@@ -1,45 +1,37 @@
.headerNav {
position: fixed;
top: 32px;
left: 0px;
padding: 5px 10px;
background-color: #ccc;
border-radius: 5px;
max-height: calc(100vh - 32px);
max-width: 40vw;
overflow-y: auto;
&.active {
padding-bottom: 10px;
.navIcon {
padding-bottom: 10px;
}
}
.navIcon {
cursor: pointer;
position : fixed;
top : 32px;
left : 0px;
max-width : 40vw;
max-height : calc(100vh - 32px);
padding : 5px 10px;
overflow-y : auto;
background-color : #CCCCCC;
border-radius : 5px;
&.active {
padding-bottom : 10px;
.navIcon { padding-bottom : 10px; }
}
.navIcon { cursor : pointer; }
li {
list-style-type: none;
list-style-type : none;
a {
display: inline-block;
width: 100%;
font-family: 'Open Sans';
font-size: 12px;
padding: 2px;
color: inherit;
text-decoration: none;
cursor: pointer;
&:hover {
text-decoration: underline;
}
&.pageLink {
font-weight: 900;
}
display : inline-block;
width : 100%;
padding : 2px;
font-family : 'Open Sans';
font-size : 12px;
color : inherit;
text-decoration : none;
cursor : pointer;
&:hover { text-decoration : underline; }
&.pageLink { font-weight : 900; }
@depths: 1,2,3,4,5,6,7;
@depths: 0,1,2,3,4,5,6,7;
each(@depths, {
&.depth-@{value} {
padding-left: ((@value - 1) * 0.5em);
padding-left: ((@value) * 0.5em);
}
});
}

View File

@@ -1,6 +1,7 @@
require('./notificationPopup.less');
import React, { useEffect, useState } from 'react';
import request from '../../utils/request-middleware.js';
import Markdown from 'naturalcrit/markdown.js';
import Dialog from '../../../components/dialog.jsx';
@@ -40,11 +41,10 @@ const NotificationPopup = ()=>{
const renderNotificationsList = ()=>{
if(error) return <div className='error'>{error}</div>;
return notifications.map((notification)=>(
<li key={notification.dismissKey} >
<em>{notification.title}</em><br />
<p dangerouslySetInnerHTML={{ __html: notification.text }}></p>
<p dangerouslySetInnerHTML={{ __html: Markdown.render(notification.text) }}></p>
</li>
));
};

View File

@@ -48,17 +48,46 @@
}
ul {
margin-top : 15px;
font-size : 0.8em;
font-size : 0.9em;
list-style-position : outside;
list-style-type : disc;
li {
margin-top : 1.4em;
font-size : 0.8em;
line-height : 1.4em;
em {
text-transform:capitalize;
font-weight : 800;
padding-left : 1em;
margin-top : 1.5em;
font-size : 0.9em;
line-height : 1.5em;
em {
font-weight : 800;
text-transform : capitalize;
}
li {
margin-top : 0;
line-height : 1.2em;
list-style-type : square;
}
}
ul ul,ol ol,ul ol,ol ul {
margin-bottom : 0px;
margin-left : 1.5em;
}
}
}
/* Markdown styling */
code {
padding : 0.1em 0.5em;
font-family : 'Courier New', 'Courier', monospace;
overflow-wrap : break-word;
white-space : pre-wrap;
background : #08115A;
border-radius : 2px;
}
pre code {
display : inline-block;
width : 100%;
}
.blank {
height : 1em;
margin-top : 0;
& + * { margin-top : 0; }
}
}

View File

@@ -9,6 +9,8 @@ import { Anchored, AnchoredBox, AnchoredTrigger } from '../../../components/Anch
const MAX_ZOOM = 300;
const MIN_ZOOM = 10;
const TOOLBAR_VISIBILITY = 'HB_renderer_toolbarVisibility';
const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPages, headerState, setHeaderState })=>{
const [pageNum, setPageNum] = useState(1);
@@ -20,6 +22,12 @@ const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPa
setPageNum(pageRange);
}, [visiblePages]);
useEffect(()=>{
const Visibility = localStorage.getItem(TOOLBAR_VISIBILITY);
if(Visibility) setToolsVisible(Visibility === 'true');
}, []);
const handleZoomButton = (zoom)=>{
handleOptionChange('zoomLevel', _.round(_.clamp(zoom, MIN_ZOOM, MAX_ZOOM)));
};
@@ -55,15 +63,30 @@ const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPa
// find widest page, in case pages are different widths, so that the zoom is adapted to not cut the widest page off screen.
const widestPage = _.maxBy([...pages], 'offsetWidth').offsetWidth;
desiredZoom = (iframeWidth / widestPage) * 100;
if(displayOptions.spread === 'facing')
desiredZoom = (iframeWidth / ((widestPage * 2) + parseInt(displayOptions.columnGap))) * 100;
else
desiredZoom = (iframeWidth / (widestPage + 20)) * 100;
} else if(mode == 'fit'){
let minDimRatio;
// find the page with the largest single dim (height or width) so that zoom can be adapted to fit it.
if(displayOptions.spread === 'facing')
minDimRatio = [...pages].reduce((minRatio, page)=>Math.min(minRatio, iframeWidth / page.offsetWidth / 2), Infinity); // if 'facing' spread, fit two pages in view
let minDimRatio;
if(displayOptions.spread === 'single')
minDimRatio = [...pages].reduce(
(minRatio, page)=>Math.min(minRatio,
iframeWidth / page.offsetWidth,
iframeHeight / page.offsetHeight
),
Infinity
);
else
minDimRatio = [...pages].reduce((minRatio, page)=>Math.min(minRatio, iframeWidth / page.offsetWidth, iframeHeight / page.offsetHeight), Infinity);
minDimRatio = [...pages].reduce(
(minRatio, page)=>Math.min(minRatio,
iframeWidth / ((page.offsetWidth * 2) + parseInt(displayOptions.columnGap)),
iframeHeight / page.offsetHeight
),
Infinity
);
desiredZoom = minDimRatio * 100;
}
@@ -77,7 +100,10 @@ const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPa
return (
<div id='preview-toolbar' className={`toolBar ${toolsVisible ? 'visible' : 'hidden'}`} role='toolbar'>
<div className='toggleButton'>
<button title={`${toolsVisible ? 'Hide' : 'Show'} Preview Toolbar`} onClick={()=>{setToolsVisible(!toolsVisible);}}><i className='fas fa-glasses' /></button>
<button title={`${toolsVisible ? 'Hide' : 'Show'} Preview Toolbar`} onClick={()=>{
setToolsVisible(!toolsVisible);
localStorage.setItem(TOOLBAR_VISIBILITY, !toolsVisible);
}}><i className='fas fa-glasses' /></button>
<button title={`${headerState ? 'Hide' : 'Show'} Header Navigation`} onClick={()=>{setHeaderState(!headerState);}}><i className='fas fa-rectangle-list' /></button>
</div>
{/*v=====----------------------< Zoom Controls >---------------------=====v*/}
@@ -142,7 +168,7 @@ const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPa
id='single-spread'
className='tool'
title='Single Page'
onClick={()=>{handleOptionChange('spread', 'active');}}
onClick={()=>{handleOptionChange('spread', 'single');}}
aria-checked={displayOptions.spread === 'single'}
><i className='fac single-spread' /></button>
<button role='radio'
@@ -167,11 +193,11 @@ const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPa
<h1>Options</h1>
<label title='Modify the horizontal space between pages.'>
Column gap
<input type='range' min={0} max={200} defaultValue={10} className='range-input' onChange={(evt)=>handleOptionChange('columnGap', evt.target.value)} />
<input type='range' min={0} max={200} defaultValue={displayOptions.columnGap || 10} className='range-input' onChange={(evt)=>handleOptionChange('columnGap', evt.target.value)} />
</label>
<label title='Modify the vertical space between rows of pages.'>
Row gap
<input type='range' min={0} max={200} defaultValue={10} className='range-input' onChange={(evt)=>handleOptionChange('rowGap', evt.target.value)} />
<input type='range' min={0} max={200} defaultValue={displayOptions.rowGap || 10} className='range-input' onChange={(evt)=>handleOptionChange('rowGap', evt.target.value)} />
</label>
<label title='Start 1st page on the right side, such as if you have cover page.'>
Start on right

View File

@@ -6,12 +6,12 @@
box-sizing : border-box;
display : flex;
flex-wrap : wrap;
gap : 8px 30px;
gap : 8px 20px;
align-items : center;
justify-content : center;
width : 100%;
height : auto;
padding : 2px 0;
padding : 2px 10px 2px 90px;
font-family : 'Open Sans', sans-serif;
font-size : 13px;
color : #CCCCCC;
@@ -153,10 +153,10 @@
align-items : center;
justify-content : center;
width : auto;
min-width : 46px;
min-width : 40px;
height : 100%;
&:hover { background-color : #444444; }
&:focus { border : 1px solid #D3D3D3;outline : none;}
&:focus {outline : none; border : 1px solid #D3D3D3;}
&:disabled {
color : #777777;
background-color : unset !important;
@@ -169,12 +169,16 @@
width : 92px;
overflow : hidden;
background-color : unset;
opacity : 0.5;
opacity : 0.7;
transition : all 0.3s ease;
& > *:not(.toggleButton) {
opacity : 0;
transition : all 0.2s ease;
}
.toggleButton button i {
filter: drop-shadow(0 0 2px black) drop-shadow(0 0 1px black);
}
}
}
@@ -182,8 +186,6 @@
position : absolute;
left : 0;
z-index : 5;
width : 32px;
min-width : unset;
height : 100%;
display : flex;
height : 100%;
}

View File

@@ -10,10 +10,10 @@ const CodeEditor = require('naturalcrit/codeEditor/codeEditor.jsx');
const SnippetBar = require('./snippetbar/snippetbar.jsx');
const MetadataEditor = require('./metadataEditor/metadataEditor.jsx');
const EDITOR_THEME_KEY = 'HOMEBREWERY-EDITOR-THEME';
const EDITOR_THEME_KEY = 'HB_editor_theme';
const PAGEBREAK_REGEX_V3 = /^(?=\\page(?: *{[^\n{}]*})?$)/m;
const SNIPPETBAR_HEIGHT = 25;
const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m;
const SNIPPETBREAK_REGEX_V3 = /^\\snippet\ .*$/;
const DEFAULT_STYLE_TEXT = dedent`
/*=======--- Example CSS styling ---=======*/
/* Any CSS here will apply to your document! */
@@ -22,6 +22,13 @@ const DEFAULT_STYLE_TEXT = dedent`
color: black;
}`;
const DEFAULT_SNIPPET_TEXT = dedent`
\snippet example snippet
The text between \`\snippet title\` lines will become a snippet of name \`title\` as this example provides.
This snippet is accessible in the brew tab, and will be inherited if the brew is used as a theme.
`;
let isJumping = false;
const Editor = createClass({
@@ -33,10 +40,8 @@ const Editor = createClass({
style : ''
},
onTextChange : ()=>{},
onStyleChange : ()=>{},
onMetaChange : ()=>{},
reportError : ()=>{},
onBrewChange : ()=>{},
reportError : ()=>{},
onCursorPageChange : ()=>{},
onViewPageChange : ()=>{},
@@ -51,8 +56,9 @@ const Editor = createClass({
},
getInitialState : function() {
return {
editorTheme : this.props.editorTheme,
view : 'text' //'text', 'style', 'meta'
editorTheme : this.props.editorTheme,
view : 'text', //'text', 'style', 'meta', 'snippet'
snippetbarHeight : 25
};
},
@@ -62,12 +68,11 @@ const Editor = createClass({
isText : function() {return this.state.view == 'text';},
isStyle : function() {return this.state.view == 'style';},
isMeta : function() {return this.state.view == 'meta';},
isSnip : function() {return this.state.view == 'snippet';},
componentDidMount : function() {
this.updateEditorSize();
this.highlightCustomMarkdown();
window.addEventListener('resize', this.updateEditorSize);
document.getElementById('BrewRenderer').addEventListener('keydown', this.handleControlKeys);
document.addEventListener('keydown', this.handleControlKeys);
@@ -80,10 +85,7 @@ const Editor = createClass({
editorTheme : editorTheme
});
}
},
componentWillUnmount : function() {
window.removeEventListener('resize', this.updateEditorSize);
this.setState({ snippetbarHeight: document.querySelector('.editor > .snippetBar').offsetHeight });
},
componentDidUpdate : function(prevProps, prevState, snapshot) {
@@ -118,14 +120,6 @@ const Editor = createClass({
}
},
updateEditorSize : function() {
if(this.codeEditor.current) {
let paneHeight = this.editor.current.parentNode.clientHeight;
paneHeight -= SNIPPETBAR_HEIGHT;
this.codeEditor.current.codeMirror.setSize(null, paneHeight);
}
},
updateCurrentCursorPage : function(cursor) {
const lines = this.props.brew.text.split('\n').slice(1, cursor.line + 1);
const pageRegex = this.props.brew.renderer == 'V3' ? PAGEBREAK_REGEX_V3 : /\\page/;
@@ -146,17 +140,17 @@ const Editor = createClass({
handleViewChange : function(newView){
this.props.setMoveArrows(newView === 'text');
this.setState({
view : newView
}, ()=>{
this.codeEditor.current?.codeMirror.focus();
this.updateEditorSize();
}); //TODO: not sure if updateeditorsize needed
});
},
highlightCustomMarkdown : function(){
if(!this.codeEditor.current) return;
if(this.state.view === 'text') {
if((this.state.view === 'text') ||(this.state.view === 'snippet')) {
const codeMirror = this.codeEditor.current.codeMirror;
codeMirror.operation(()=>{ // Batch CodeMirror styling
@@ -175,12 +169,18 @@ const Editor = createClass({
for (let i=customHighlights.length - 1;i>=0;i--) customHighlights[i].clear();
let userSnippetCount = 1; // start snippet count from snippet 1
let editorPageCount = 1; // start page count from page 1
_.forEach(this.props.brew.text.split('\n'), (line, lineNumber)=>{
const whichSource = this.state.view === 'text' ? this.props.brew.text : this.props.brew.snippets;
_.forEach(whichSource?.split('\n'), (line, lineNumber)=>{
const tabHighlight = this.state.view === 'text' ? 'pageLine' : 'snippetLine';
const textOrSnip = this.state.view === 'text';
//reset custom line styles
codeMirror.removeLineClass(lineNumber, 'background', 'pageLine');
codeMirror.removeLineClass(lineNumber, 'background', 'snippetLine');
codeMirror.removeLineClass(lineNumber, 'text');
codeMirror.removeLineClass(lineNumber, 'wrap', 'sourceMoveFlash');
@@ -191,23 +191,25 @@ const Editor = createClass({
// Styling for \page breaks
if((this.props.renderer == 'legacy' && line.includes('\\page')) ||
(this.props.renderer == 'V3' && line.match(PAGEBREAK_REGEX_V3))) {
(this.props.renderer == 'V3' && line.match(textOrSnip ? PAGEBREAK_REGEX_V3 : SNIPPETBREAK_REGEX_V3))) {
if(lineNumber > 0) // Since \page is optional on first line of document,
if((lineNumber > 0) && (textOrSnip)) // Since \page is optional on first line of document,
editorPageCount += 1; // don't use it to increment page count; stay at 1
else if(this.state.view !== 'text') userSnippetCount += 1;
// add back the original class 'background' but also add the new class '.pageline'
codeMirror.addLineClass(lineNumber, 'background', 'pageLine');
codeMirror.addLineClass(lineNumber, 'background', tabHighlight);
const pageCountElement = Object.assign(document.createElement('span'), {
className : 'editor-page-count',
textContent : editorPageCount
textContent : textOrSnip ? editorPageCount : userSnippetCount
});
codeMirror.setBookmark({ line: lineNumber, ch: line.length }, pageCountElement);
};
// New Codemirror styling for V3 renderer
if(this.props.renderer == 'V3') {
if(line.match(/^\\column$/)){
if(this.props.renderer === 'V3') {
if(line.match(/^\\column(?:break)?$/)){
codeMirror.addLineClass(lineNumber, 'text', 'columnSplit');
}
@@ -408,6 +410,9 @@ const Editor = createClass({
//Called when there are changes to the editor's dimensions
update : function(){
this.codeEditor.current?.updateSize();
const snipHeight = document.querySelector('.editor > .snippetBar').offsetHeight;
if(snipHeight !== this.state.snippetbarHeight)
this.setState({ snippetbarHeight: snipHeight });
},
updateEditorTheme : function(newTheme){
@@ -430,9 +435,10 @@ const Editor = createClass({
language='gfm'
view={this.state.view}
value={this.props.brew.text}
onChange={this.props.onTextChange}
onChange={this.props.onBrewChange('text')}
editorTheme={this.state.editorTheme}
rerenderParent={this.rerenderParent} />
rerenderParent={this.rerenderParent}
style={{ height: `calc(100% - ${this.state.snippetbarHeight}px)` }} />
</>;
}
if(this.isStyle()){
@@ -442,10 +448,11 @@ const Editor = createClass({
language='css'
view={this.state.view}
value={this.props.brew.style ?? DEFAULT_STYLE_TEXT}
onChange={this.props.onStyleChange}
onChange={this.props.onBrewChange('style')}
enableFolding={true}
editorTheme={this.state.editorTheme}
rerenderParent={this.rerenderParent} />
rerenderParent={this.rerenderParent}
style={{ height: `calc(100% - ${this.state.snippetbarHeight}px)` }} />
</>;
}
if(this.isMeta()){
@@ -456,11 +463,28 @@ const Editor = createClass({
rerenderParent={this.rerenderParent} />
<MetadataEditor
metadata={this.props.brew}
onChange={this.props.onMetaChange}
themeBundle={this.props.themeBundle}
onChange={this.props.onBrewChange('metadata')}
reportError={this.props.reportError}
userThemes={this.props.userThemes}/>
</>;
}
if(this.isSnip()){
if(!this.props.brew.snippets) { this.props.brew.snippets = DEFAULT_SNIPPET_TEXT; }
return <>
<CodeEditor key='codeEditor'
ref={this.codeEditor}
language='gfm'
view={this.state.view}
value={this.props.brew.snippets}
onChange={this.props.onBrewChange('snippets')}
enableFolding={true}
editorTheme={this.state.editorTheme}
rerenderParent={this.rerenderParent}
style={{ height: `calc(100% - ${this.state.snippetbarHeight}px)` }} />
</>;
}
},
redo : function(){
@@ -501,7 +525,7 @@ const Editor = createClass({
historySize={this.historySize()}
currentEditorTheme={this.state.editorTheme}
updateEditorTheme={this.updateEditorTheme}
snippetBundle={this.props.snippetBundle}
themeBundle={this.props.themeBundle}
cursorPos={this.codeEditor.current?.getCursorPosition() || {}}
updateBrew={this.props.updateBrew}
/>

View File

@@ -1,12 +1,13 @@
@import 'themes/codeMirror/customEditorStyles.less';
.editor {
position : relative;
width : 100%;
container: editor / inline-size;
position : relative;
width : 100%;
height : 100%;
container : editor / inline-size;
.codeEditor {
height : 100%;
.pageLine {
height : calc(100% - 25px);
.CodeMirror { height : 100%; }
.pageLine, .snippetLine {
background : #33333328;
border-top : #333399 solid 1px;
}
@@ -14,6 +15,10 @@
float : right;
color : grey;
}
.editor-snippet-count {
float : right;
color : grey;
}
.columnSplit {
font-style : italic;
color : grey;
@@ -45,26 +50,26 @@
color : green;
}
.emoji:not(.cm-comment) {
margin-left : 2px;
color : #360034;
background : #ffc8ff;
border-radius : 6px;
font-weight : bold;
padding-bottom : 1px;
margin-left : 2px;
font-weight : bold;
color : #360034;
outline : solid 2px #FF96FC;
outline-offset : -2px;
outline : solid 2px #ff96fc;
background : #FFC8FF;
border-radius : 6px;
}
.superscript:not(.cm-comment) {
font-weight : bold;
color : goldenrod;
vertical-align : super;
font-size : 0.9em;
font-weight : bold;
vertical-align : super;
color : goldenrod;
}
.subscript:not(.cm-comment) {
font-weight : bold;
color : rgb(123, 123, 15);
vertical-align : sub;
font-size : 0.9em;
font-weight : bold;
vertical-align : sub;
color : rgb(123, 123, 15);
}
.dl-highlight {
&.dl-colon-highlight {
@@ -103,4 +108,4 @@
span { padding : 2px 5px; }
}
}
}

View File

@@ -4,7 +4,6 @@ const React = require('react');
const createClass = require('create-react-class');
const _ = require('lodash');
import request from '../../utils/request-middleware.js';
const Nav = require('naturalcrit/nav/nav.jsx');
const Combobox = require('client/components/combobox.jsx');
const TagInput = require('../tagInput/tagInput.jsx');
@@ -40,6 +39,7 @@ const MetadataEditor = createClass({
theme : '5ePHB',
lang : 'en'
},
onChange : ()=>{},
reportError : ()=>{}
};
@@ -67,6 +67,11 @@ const MetadataEditor = createClass({
const inputRules = validations[name] ?? [];
const validationErr = inputRules.map((rule)=>rule(e.target.value)).filter(Boolean);
const debouncedReportValidity = _.debounce((target, errMessage)=>{
callIfExists(target, 'setCustomValidity', errMessage);
callIfExists(target, 'reportValidity');
}, 300); // 300ms debounce delay, adjust as needed
// if no validation rules, save to props
if(validationErr.length === 0){
callIfExists(e.target, 'setCustomValidity', '');
@@ -74,14 +79,16 @@ const MetadataEditor = createClass({
...this.props.metadata,
[name] : e.target.value
});
return true;
} else {
// if validation issues, display built-in browser error popup with each error.
const errMessage = validationErr.map((err)=>{
return `- ${err}`;
}).join('\n');
callIfExists(e.target, 'setCustomValidity', errMessage);
callIfExists(e.target, 'reportValidity');
debouncedReportValidity(e.target, errMessage);
return false;
}
},
@@ -102,6 +109,7 @@ const MetadataEditor = createClass({
}
this.props.onChange(this.props.metadata, 'renderer');
},
handlePublish : function(val){
this.props.onChange({
...this.props.metadata,
@@ -112,6 +120,14 @@ const MetadataEditor = createClass({
handleTheme : function(theme){
this.props.metadata.renderer = theme.renderer;
this.props.metadata.theme = theme.path;
this.props.onChange(this.props.metadata, 'theme');
},
handleThemeWritein : function(e) {
const shareId = e.target.value.split('/').pop(); //Extract just the ID if a URL was pasted in
this.props.metadata.theme = shareId;
this.props.onChange(this.props.metadata, 'theme');
},
@@ -200,7 +216,7 @@ const MetadataEditor = createClass({
if(theme.path == this.props.metadata.shareId) return;
const preview = theme.thumbnail || `/themes/${theme.renderer}/${theme.path}/dropdownPreview.png`;
const texture = theme.thumbnail || `/themes/${theme.renderer}/${theme.path}/dropdownTexture.png`;
return <div className='item' key={`${renderer}_${theme.name}`} onClick={()=>this.handleTheme(theme)} title={''}>
return <div className='item' key={`${renderer}_${theme.name}`} value={`${theme.author ?? renderer} : ${theme.name}`} data={theme} title={''}>
{theme.author ?? renderer} : {theme.name}
<div className='texture-container'>
<img src={texture}/>
@@ -210,26 +226,40 @@ const MetadataEditor = createClass({
<img src={preview}/>
</div>
</div>;
});
}).filter(Boolean);
};
const currentRenderer = this.props.metadata.renderer;
const currentTheme = mergedThemes[`${_.upperFirst(this.props.metadata.renderer)}`][this.props.metadata.theme]
?? { name: `!!! THEME MISSING !!! ID=${this.props.metadata.theme}` };
const currentThemeDisplay = this.props.themeBundle?.name ? `${this.props.themeBundle.author ?? currentRenderer} : ${this.props.themeBundle.name}` : 'No Theme Selected';
let dropdown;
if(currentRenderer == 'legacy') {
dropdown =
<Nav.dropdown className='disabled value' trigger='disabled'>
<div> {`Themes are not supported in the Legacy Renderer`} <i className='fas fa-caret-down'></i> </div>
</Nav.dropdown>;
<div className='disabled value' trigger='disabled'>
<div> Themes are not supported in the Legacy Renderer </div>
</div>;
} else {
dropdown =
<Nav.dropdown className='value' trigger='click'>
<div> {currentTheme.author ?? _.upperFirst(currentRenderer)} : {currentTheme.name} <i className='fas fa-caret-down'></i> </div>
{listThemes(currentRenderer)}
</Nav.dropdown>;
<div className='value'>
<Combobox trigger='click'
className='themes-dropdown'
default={currentThemeDisplay}
placeholder='Select from below, or enter the Share URL or ID of a brew with the meta:theme tag'
onSelect={(value)=>this.handleTheme(value)}
onEntry={(e)=>{
e.target.setCustomValidity(''); //Clear the validation popup while typing
if(this.handleFieldChange('theme', e))
this.handleThemeWritein(e);
}}
options={listThemes(currentRenderer)}
autoSuggest={{
suggestMethod : 'includes',
clearAutoSuggestOnClick : true,
filterOn : ['value', 'title']
}}
/>
<small>Select from the list below (built-in themes and brews you have tagged "meta:theme"), or paste in the Share URL or Share ID of any brew.</small>
</div>;
}
return <div className='field themes'>
@@ -244,15 +274,13 @@ const MetadataEditor = createClass({
return _.map(langCodes.sort(), (code, index)=>{
const localName = new Intl.DisplayNames([code], { type: 'language' });
const englishName = new Intl.DisplayNames('en', { type: 'language' });
return <div className='item' title={`${englishName.of(code)}`} key={`${index}`} data-value={`${code}`} data-detail={`${localName.of(code)}`}>
{`${code}`}
<div className='detail'>{`${localName.of(code)}`}</div>
return <div className='item' title={englishName.of(code)} key={`${index}`} value={code} detail={localName.of(code)}>
{code}
<div className='detail'>{localName.of(code)}</div>
</div>;
});
};
const debouncedHandleFieldChange = _.debounce(this.handleFieldChange, 500);
return <div className='field language'>
<label>language</label>
<div className='value'>
@@ -263,16 +291,15 @@ const MetadataEditor = createClass({
onSelect={(value)=>this.handleLanguage(value)}
onEntry={(e)=>{
e.target.setCustomValidity(''); //Clear the validation popup while typing
debouncedHandleFieldChange('lang', e);
this.handleFieldChange('lang', e);
}}
options={listLanguages()}
autoSuggest={{
suggestMethod : 'startsWith',
clearAutoSuggestOnClick : true,
filterOn : ['data-value', 'data-detail', 'title']
filterOn : ['value', 'detail', 'title']
}}
>
</Combobox>
/>
<small>Sets the HTML Lang property for your brew. May affect hyphenation or spellcheck.</small>
</div>
@@ -345,7 +372,7 @@ const MetadataEditor = createClass({
placeholder='add tag' unique={true}
values={this.props.metadata.tags}
onChange={(e)=>this.handleFieldChange('tags', e)}
/>
/>
<div className='field systems'>
<label>systems</label>
@@ -370,7 +397,7 @@ const MetadataEditor = createClass({
values={this.props.metadata.invitedAuthors}
notes={['Invited author usernames are case sensitive.', 'After adding an invited author, send them the edit link. There, they can choose to accept or decline the invitation.']}
onChange={(e)=>this.handleFieldChange('invitedAuthors', e)}
/>
/>
<h2>Privacy</h2>

View File

@@ -1,28 +1,31 @@
@import 'naturalcrit/styles/colors.less';
.userThemeName {
padding-right : 10px;
padding-left : 10px;
}
.metadataEditor {
position : absolute;
z-index : 5;
box-sizing : border-box;
width : 100%;
height : calc(100vh - 54px); // 54px is the height of the navbar + snippet bar. probably a better way to dynamic get this.
padding : 25px;
overflow-y : auto;
font-size : 13px;
background-color : #999999;
font-size : 13px;
h1 {
margin: 0 0 40px;
font-weight: bold;
text-transform: uppercase;
margin : 0 0 40px;
font-weight : bold;
text-transform : uppercase;
}
h2 {
margin : 20px 0;
font-weight : bold;
border-bottom: 2px solid gray;
color: #555;
margin : 20px 0;
font-weight : bold;
color : #555555;
border-bottom : 2px solid gray;
}
& > div { margin-bottom : 10px; }
@@ -51,10 +54,10 @@
min-width : 200px;
& > label {
width : 80px;
font-size : 0.9em;
font-weight : 800;
line-height : 1.8em;
text-transform : uppercase;
font-size: .9em;
}
& > .value {
flex : 1 1 auto;
@@ -71,8 +74,7 @@
border : 1px solid gray;
&:focus { outline : 1px solid #444444; }
}
&.thumbnail {
height : 1.4em;
&.thumbnail, &.themes {
label { line-height : 2.0em; }
.value {
overflow : hidden;
@@ -88,6 +90,17 @@
}
}
&.themes {
.value {
overflow : visible;
text-overflow : auto;
}
button {
padding-right : 5px;
padding-left : 5px;
}
}
&.description {
flex : 1;
textarea.value {
@@ -123,8 +136,8 @@
margin-right : 15px;
font-size : 0.9em;
font-weight : 800;
white-space : nowrap;
vertical-align : middle;
white-space : nowrap;
cursor : pointer;
user-select : none;
}
@@ -151,94 +164,74 @@
.colorButton(@red);
}
}
.authors.field .value {
line-height : 1.5em;
}
.authors.field .value { line-height : 1.5em; }
.themes.field {
.navDropdownContainer {
& .dropdown-container {
position : relative;
z-index : 100;
background-color : white;
&.disabled {
font-style : italic;
color : dimgray;
background-color : darkgray;
}
& > div:first-child {
padding : 3px 3px;
background-color : inherit;
border : 1px solid gray;
i { float : right; }
&:hover {
color : white;
background-color : @blue;
}
& .dropdown-options { overflow-y : visible; }
.disabled {
font-style : italic;
color : dimgray;
background-color : darkgray;
}
.item {
position : relative;
padding : 3px 3px;
overflow : visible;
background-color : white;
border-top : 1px solid rgb(118, 118, 118);
.preview {
position : absolute;
top : 0;
right : 0;
z-index : 1;
display : flex;
flex-direction : column;
width : 200px;
overflow : hidden;
color : black;
background : #CCCCCC;
border-radius : 5px;
box-shadow : 0 0 5px black;
opacity : 0;
transition : opacity 250ms ease;
h6 {
padding-block : 0.5em;
padding-inline : 1em;
font-weight : 900;
border-bottom : 2px solid hsl(0,0%,40%);
}
}
.navDropdown .item > p {
width : 45%;
height : 1.1em;
overflow : hidden;
text-overflow : ellipsis;
white-space : nowrap;
}
.navDropdown {
position : absolute;
width : 100%;
box-shadow : 0px 5px 10px rgba(0, 0, 0, 0.3);
.item {
position : relative;
padding : 3px 3px;
overflow : visible;
background-color : white;
border-top : 1px solid rgb(118, 118, 118);
.preview {
position : absolute;
top : 0;
right : 0;
z-index : 1;
display : flex;
flex-direction : column;
width : 200px;
overflow : hidden;
color : black;
background : #CCCCCC;
border-radius : 5px;
box-shadow : 0 0 5px black;
opacity : 0;
transition : opacity 250ms ease;
h6 {
padding-block : 0.5em;
padding-inline : 1em;
font-weight : 900;
border-bottom : 2px solid hsl(0,0%,40%);
}
}
&:hover {
color : white;
background-color : @blue;
}
&:hover > .preview { opacity : 1; }
.texture-container {
position : absolute;
top : 0;
left : 0;
width : 100%;
height : 100%;
min-height : 100%;
overflow : hidden;
> img {
position : absolute;
top : 0px;
right : 0;
width : 50%;
min-height : 100%;
-webkit-mask-image : linear-gradient(90deg, transparent, black 20%);
mask-image : linear-gradient(90deg, transparent, black 20%);
}
}
.texture-container {
position : absolute;
top : 0;
left : 0;
width : 100%;
height : 100%;
min-height : 100%;
overflow : hidden;
> img {
position : absolute;
top : 0;
right : 0;
width : 50%;
min-height : 100%;
-webkit-mask-image : linear-gradient(90deg, transparent, black 20%);
mask-image : linear-gradient(90deg, transparent, black 20%);
}
}
&:hover {
color : white;
background-color : @blue;
filter : unset;
}
&:hover > .preview { opacity : 1; }
}
}

View File

@@ -27,6 +27,19 @@ module.exports = {
(value)=>{
return new RegExp(/^([a-zA-Z]{2,3})(-[a-zA-Z]{4})?(-(?:[0-9]{3}|[a-zA-Z]{2}))?$/).test(value) === false && (value.length > 0) ? 'Invalid language code.' : null;
}
],
theme : [
(value)=>{
const URL = global.config.baseUrl.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); //Escape any regex characters
const shareIDPattern = '[a-zA-Z0-9-_]{12}';
const shareURLRegex = new RegExp(`^${URL}\\/share\\/${shareIDPattern}$`);
const shareIDRegex = new RegExp(`^${shareIDPattern}$`);
if(value?.length === 0) return null;
if(shareURLRegex.test(value)) return null;
if(shareIDRegex.test(value)) return null;
return 'Must be a valid Share URL or a 12-character ID.';
}
]
};

View File

@@ -6,6 +6,7 @@ const _ = require('lodash');
const cx = require('classnames');
import { loadHistory } from '../../utils/versionHistory.js';
import { brewSnippetsToJSON } from '../../../../shared/helpers.js';
//Import all themes
const ThemeSnippets = {};
@@ -40,7 +41,7 @@ const Snippetbar = createClass({
unfoldCode : ()=>{},
updateEditorTheme : ()=>{},
cursorPos : {},
snippetBundle : [],
themeBundle : [],
updateBrew : ()=>{}
};
},
@@ -64,7 +65,10 @@ const Snippetbar = createClass({
},
componentDidUpdate : async function(prevProps, prevState) {
if(prevProps.renderer != this.props.renderer || prevProps.theme != this.props.theme || prevProps.snippetBundle != this.props.snippetBundle) {
if(prevProps.renderer != this.props.renderer ||
prevProps.theme != this.props.theme ||
prevProps.themeBundle != this.props.themeBundle ||
prevProps.brew.snippets != this.props.brew.snippets) {
this.setState({
snippets : this.compileSnippets()
});
@@ -97,7 +101,7 @@ const Snippetbar = createClass({
if(key == 'snippets') {
const result = _.reverse(_.unionBy(_.reverse(newValue), _.reverse(oldValue), 'name')); // Join snippets together, with preference for the child theme over the parent theme
return result.filter((snip)=>snip.gen || snip.subsnippets);
}
};
},
compileSnippets : function() {
@@ -105,15 +109,21 @@ const Snippetbar = createClass({
let oldSnippets = _.keyBy(compiledSnippets, 'groupName');
for (let snippets of this.props.snippetBundle) {
if(typeof(snippets) == 'string') // load staticThemes as needed; they were sent as just a file name
snippets = ThemeSnippets[snippets];
if(this.props.themeBundle.snippets) {
for (let snippets of this.props.themeBundle.snippets) {
if(typeof(snippets) == 'string') // load staticThemes as needed; they were sent as just a file name
snippets = ThemeSnippets[snippets];
const newSnippets = _.keyBy(_.cloneDeep(snippets), 'groupName');
compiledSnippets = _.values(_.mergeWith(oldSnippets, newSnippets, this.mergeCustomizer));
const newSnippets = _.keyBy(_.cloneDeep(snippets), 'groupName');
compiledSnippets = _.values(_.mergeWith(oldSnippets, newSnippets, this.mergeCustomizer));
oldSnippets = _.keyBy(compiledSnippets, 'groupName');
oldSnippets = _.keyBy(compiledSnippets, 'groupName');
}
}
const userSnippetsasJSON = brewSnippetsToJSON(this.props.brew.title || 'New Document', this.props.brew.snippets, this.props.themeBundle.snippets);
compiledSnippets.push(userSnippetsasJSON);
return compiledSnippets;
},
@@ -207,59 +217,60 @@ const Snippetbar = createClass({
renderEditorButtons : function(){
if(!this.props.showEditButtons) return;
return (
<div className='editors'>
{this.props.view !== 'meta' && <><div className='historyTools'>
<div className={`editorTool snippetGroup history ${this.state.historyExists ? 'active' : ''}`}
onClick={this.toggleHistoryMenu} >
<i className='fas fa-clock-rotate-left' />
{ this.state.showHistory && this.renderHistoryItems() }
<div className='editors'>
{this.props.view !== 'meta' && <><div className='historyTools'>
<div className={`editorTool snippetGroup history ${this.state.historyExists ? 'active' : ''}`}
onClick={this.toggleHistoryMenu} >
<i className='fas fa-clock-rotate-left' />
{ this.state.showHistory && this.renderHistoryItems() }
</div>
<div className={`editorTool undo ${this.props.historySize.undo ? 'active' : ''}`}
onClick={this.props.undo} >
<i className='fas fa-undo' />
</div>
<div className={`editorTool redo ${this.props.historySize.redo ? 'active' : ''}`}
onClick={this.props.redo} >
<i className='fas fa-redo' />
</div>
</div>
<div className={`editorTool undo ${this.props.historySize.undo ? 'active' : ''}`}
onClick={this.props.undo} >
<i className='fas fa-undo' />
</div>
<div className={`editorTool redo ${this.props.historySize.redo ? 'active' : ''}`}
onClick={this.props.redo} >
<i className='fas fa-redo' />
</div>
</div>
<div className='codeTools'>
<div className={`editorTool foldAll ${this.props.foldCode ? 'active' : ''}`}
onClick={this.props.foldCode} >
<i className='fas fa-compress-alt' />
</div>
<div className={`editorTool unfoldAll ${this.props.unfoldCode ? 'active' : ''}`}
onClick={this.props.unfoldCode} >
<i className='fas fa-expand-alt' />
</div>
<div className={`editorTheme ${this.state.themeSelector ? 'active' : ''}`}
onClick={this.toggleThemeSelector} >
<i className='fas fa-palette' />
{this.state.themeSelector && this.renderThemeSelector()}
</div>
</div></>}
<div className='codeTools'>
<div className={`editorTool foldAll ${this.props.foldCode ? 'active' : ''}`}
onClick={this.props.foldCode} >
<i className='fas fa-compress-alt' />
</div>
<div className={`editorTool unfoldAll ${this.props.unfoldCode ? 'active' : ''}`}
onClick={this.props.unfoldCode} >
<i className='fas fa-expand-alt' />
</div>
<div className={`editorTheme ${this.state.themeSelector ? 'active' : ''}`}
onClick={this.toggleThemeSelector} >
<i className='fas fa-palette' />
{this.state.themeSelector && this.renderThemeSelector()}
</div>
</div></>}
<div className='tabs'>
<div className={cx('text', { selected: this.props.view === 'text' })}
onClick={()=>this.props.onViewChange('text')}>
<i className='fa fa-beer' />
<div className='tabs'>
<div className={cx('text', { selected: this.props.view === 'text' })}
onClick={()=>this.props.onViewChange('text')}>
<i className='fa fa-beer' />
</div>
<div className={cx('style', { selected: this.props.view === 'style' })}
onClick={()=>this.props.onViewChange('style')}>
<i className='fa fa-paint-brush' />
</div>
<div className={cx('snippet', { selected: this.props.view === 'snippet' })}
onClick={()=>this.props.onViewChange('snippet')}>
<i className='fas fa-th-list' />
</div>
<div className={cx('meta', { selected: this.props.view === 'meta' })}
onClick={()=>this.props.onViewChange('meta')}>
<i className='fas fa-info-circle' />
</div>
</div>
<div className={cx('style', { selected: this.props.view === 'style' })}
onClick={()=>this.props.onViewChange('style')}>
<i className='fa fa-paint-brush' />
</div>
<div className={cx('meta', { selected: this.props.view === 'meta' })}
onClick={()=>this.props.onViewChange('meta')}>
<i className='fas fa-info-circle' />
</div>
</div>
</div>
)
</div>
);
},
render : function(){
@@ -272,11 +283,6 @@ const Snippetbar = createClass({
module.exports = Snippetbar;
const SnippetGroup = createClass({
displayName : 'SnippetGroup',
getDefaultProps : function() {
@@ -310,7 +316,8 @@ const SnippetGroup = createClass({
},
render : function(){
return <div className='snippetGroup snippetBarButton'>
const snippetGroup = `snippetGroup snippetBarButton ${this.props.snippets.length === 0 ? 'disabledSnippets' : ''}`;
return <div className={snippetGroup}>
<div className='text'>
<i className={this.props.icon} />
<span className='groupName'>{this.props.groupName}</span>

View File

@@ -14,15 +14,15 @@
.snippets {
display : flex;
justify-content : flex-start;
min-width : 327.58px;
min-width : 432.18px; //must be controlled every time an item is added, must be hardcoded for the wrapping as it is applied
}
.editors {
display : flex;
justify-content : flex-end;
min-width : 225px;
min-width : 250px; //must be controlled every time an item is added, must be hardcoded for the wrapping as it is applied
&:only-child { margin-left : auto;min-width:unset;}
&:only-child {min-width : unset; margin-left : auto;}
>div {
display : flex;
@@ -39,9 +39,7 @@
text-align : center;
cursor : pointer;
&.editorTool:not(.active) {
cursor:not-allowed;
}
&.editorTool:not(.active) { cursor : not-allowed; }
&:hover,&.selected { background-color : #999999; }
&.text {
@@ -53,6 +51,9 @@
&.meta {
.tooltipLeft('Properties');
}
&.snippet {
.tooltipLeft('Snippets');
}
&.undo {
.tooltipLeft('Undo');
font-size : 0.75em;
@@ -92,7 +93,7 @@
&.editorTheme {
.tooltipLeft('Editor Themes');
font-size : 0.75em;
color : black;
color : inherit;
&.active {
position : relative;
background-color : #999999;
@@ -151,9 +152,9 @@
position : absolute;
top : 100%;
z-index : 1000;
visibility : hidden;
padding : 0px;
margin-left : -5px;
visibility : hidden;
background-color : #DDDDDD;
.snippet {
position : relative;
@@ -228,8 +229,15 @@
}
}
}
.disabledSnippets {
color: grey;
cursor: not-allowed;
&:hover { background-color: #DDDDDD;}
}
}
@container editor (width < 553px) {
@container editor (width < 683px) {
.snippetBar {
.editors {
flex : 1;

View File

@@ -3,43 +3,43 @@ const React = require('react');
const { useState, useEffect } = React;
const _ = require('lodash');
const TagInput = ({ unique = true, values = [], ...props }) => {
const TagInput = ({ unique = true, values = [], ...props })=>{
const [tempInputText, setTempInputText] = useState('');
const [tagList, setTagList] = useState(values.map((value) => ({ value, editing: false })));
const [tagList, setTagList] = useState(values.map((value)=>({ value, editing: false })));
useEffect(()=>{
handleChange(tagList.map((context)=>context.value))
}, [tagList])
handleChange(tagList.map((context)=>context.value));
}, [tagList]);
const handleChange = (value)=>{
props.onChange({
target : { value }
})
});
};
const handleInputKeyDown = ({ evt, value, index, options = {} }) => {
if (_.includes(['Enter', ','], evt.key)) {
const handleInputKeyDown = ({ evt, value, index, options = {} })=>{
if(_.includes(['Enter', ','], evt.key)) {
evt.preventDefault();
submitTag(evt.target.value, value, index);
if (options.clear) {
if(options.clear) {
setTempInputText('');
}
}
};
const submitTag = (newValue, originalValue, index) => {
setTagList((prevContext) => {
const submitTag = (newValue, originalValue, index)=>{
setTagList((prevContext)=>{
// remove existing tag
if(newValue === null){
return [...prevContext].filter((context, i)=>i !== index);
}
// add new tag
if(originalValue === null){
return [...prevContext, { value: newValue, editing: false }]
return [...prevContext, { value: newValue, editing: false }];
}
// update existing tag
return prevContext.map((context, i) => {
if (i === index) {
return prevContext.map((context, i)=>{
if(i === index) {
return { ...context, value: newValue, editing: false };
}
return context;
@@ -47,10 +47,10 @@ const TagInput = ({ unique = true, values = [], ...props }) => {
});
};
const editTag = (index) => {
setTagList((prevContext) => {
return prevContext.map((context, i) => {
if (i === index) {
const editTag = (index)=>{
setTagList((prevContext)=>{
return prevContext.map((context, i)=>{
if(i === index) {
return { ...context, editing: true };
}
return { ...context, editing: false };
@@ -58,25 +58,25 @@ const TagInput = ({ unique = true, values = [], ...props }) => {
});
};
const renderReadTag = (context, index) => {
const renderReadTag = (context, index)=>{
return (
<li key={index}
data-value={context.value}
className='tag'
onClick={() => editTag(index)}>
onClick={()=>editTag(index)}>
{context.value}
<button onClick={(evt)=>{evt.stopPropagation(); submitTag(null, context.value, index)}}><i className='fa fa-times fa-fw'/></button>
<button onClick={(evt)=>{evt.stopPropagation(); submitTag(null, context.value, index);}}><i className='fa fa-times fa-fw'/></button>
</li>
);
};
const renderWriteTag = (context, index) => {
const renderWriteTag = (context, index)=>{
return (
<input type='text'
key={index}
defaultValue={context.value}
onKeyDown={(evt) => handleInputKeyDown({evt, value: context.value, index: index})}
autoFocus
defaultValue={context.value}
onKeyDown={(evt)=>handleInputKeyDown({ evt, value: context.value, index: index })}
autoFocus
/>
);
};
@@ -86,7 +86,7 @@ const TagInput = ({ unique = true, values = [], ...props }) => {
<label>{props.label}</label>
<div className='value'>
<ul className='list'>
{tagList.map((context, index) => { return context.editing ? renderWriteTag(context, index) : renderReadTag(context, index); })}
{tagList.map((context, index)=>{ return context.editing ? renderWriteTag(context, index) : renderReadTag(context, index); })}
</ul>
<input
@@ -94,8 +94,8 @@ const TagInput = ({ unique = true, values = [], ...props }) => {
className='value'
placeholder={props.placeholder}
value={tempInputText}
onChange={(e) => setTempInputText(e.target.value)}
onKeyDown={(evt) => handleInputKeyDown({ evt, value: null, options: { clear: true } })}
onChange={(e)=>setTempInputText(e.target.value)}
onKeyDown={(evt)=>handleInputKeyDown({ evt, value: null, options: { clear: true } })}
/>
</div>
</div>

View File

@@ -1,95 +1,87 @@
//╔===--------------- Polyfills --------------===╗//
import 'core-js/es/string/to-well-formed.js';
//╚===--------------- ---------------===╝//
/* eslint-disable camelcase */
import 'core-js/es/string/to-well-formed.js'; //Polyfill for older browsers
import './homebrew.less';
import React from 'react';
import { StaticRouter as Router, Route, Routes, useParams, useSearchParams } from 'react-router';
require('./homebrew.less');
const React = require('react');
const createClass = require('create-react-class');
const { StaticRouter:Router } = require('react-router');
const { Route, Routes, useParams, useSearchParams } = require('react-router');
import { updateLocalStorage } from './utils/updateLocalStorage/updateLocalStorageKeys.js';
const HomePage = require('./pages/homePage/homePage.jsx');
const EditPage = require('./pages/editPage/editPage.jsx');
const UserPage = require('./pages/userPage/userPage.jsx');
const SharePage = require('./pages/sharePage/sharePage.jsx');
const NewPage = require('./pages/newPage/newPage.jsx');
const ErrorPage = require('./pages/errorPage/errorPage.jsx');
const VaultPage = require('./pages/vaultPage/vaultPage.jsx');
const AccountPage = require('./pages/accountPage/accountPage.jsx');
import HomePage from './pages/homePage/homePage.jsx';
import EditPage from './pages/editPage/editPage.jsx';
import UserPage from './pages/userPage/userPage.jsx';
import SharePage from './pages/sharePage/sharePage.jsx';
import NewPage from './pages/newPage/newPage.jsx';
import ErrorPage from './pages/errorPage/errorPage.jsx';
import VaultPage from './pages/vaultPage/vaultPage.jsx';
import AccountPage from './pages/accountPage/accountPage.jsx';
const WithRoute = (props)=>{
const WithRoute = ({ el: Element, ...rest })=>{
const params = useParams();
const [searchParams] = useSearchParams();
const queryParams = {};
for (const [key, value] of searchParams?.entries() || []) {
queryParams[key] = value;
}
const Element = props.el;
const allProps = {
...props,
...params,
query : queryParams,
el : undefined
};
return <Element {...allProps} />;
const queryParams = Object.fromEntries(searchParams?.entries() || []);
return <Element {...rest} {...params} query={queryParams} />;
};
const Homebrew = createClass({
displayName : 'Homebrewery',
getDefaultProps : function() {
return {
url : '',
welcomeText : '',
changelog : '',
version : '0.0.0',
account : null,
enable_v3 : false,
brew : {
title : '',
text : '',
shareId : null,
editId : null,
createdAt : null,
updatedAt : null,
lang : ''
}
};
},
const Homebrew = (props)=>{
const {
url = '',
version = '0.0.0',
account = null,
enable_v3 = false,
enable_themes,
config,
brew = {
title : '',
text : '',
shareId : null,
editId : null,
createdAt : null,
updatedAt : null,
lang : ''
},
userThemes,
brews
} = props;
getInitialState : function() {
global.account = this.props.account;
global.version = this.props.version;
global.enable_v3 = this.props.enable_v3;
global.enable_themes = this.props.enable_themes;
global.config = this.props.config;
global.account = account;
global.version = version;
global.enable_v3 = enable_v3;
global.enable_themes = enable_themes;
global.config = config;
return {};
},
const backgroundObject = ()=>{
if(global.config.deployment || (config.local && config.development)){
const bgText = global.config.deployment || 'Local';
return {
backgroundImage : `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='100px' width='200px'><text x='0' y='15' fill='%23fff7' font-size='20'>${bgText}</text></svg>")`
};
}
return null;
};
updateLocalStorage();
render : function (){
return (
<Router location={this.props.url}>
<div className='homebrew'>
<Routes>
<Route path='/edit/:id' element={<WithRoute el={EditPage} brew={this.props.brew} userThemes={this.props.userThemes}/>} />
<Route path='/share/:id' element={<WithRoute el={SharePage} brew={this.props.brew} />} />
<Route path='/new/:id' element={<WithRoute el={NewPage} brew={this.props.brew} userThemes={this.props.userThemes}/>} />
<Route path='/new' element={<WithRoute el={NewPage} userThemes={this.props.userThemes}/> } />
<Route path='/user/:username' element={<WithRoute el={UserPage} brews={this.props.brews} />} />
<Route path='/vault' element={<WithRoute el={VaultPage}/>}/>
<Route path='/changelog' element={<WithRoute el={SharePage} brew={this.props.brew} disableMeta={true} />} />
<Route path='/faq' element={<WithRoute el={SharePage} brew={this.props.brew} disableMeta={true} />} />
<Route path='/migrate' element={<WithRoute el={SharePage} brew={this.props.brew} disableMeta={true} />} />
<Route path='/account' element={<WithRoute el={AccountPage} brew={this.props.brew} accountDetails={this.props.brew.accountDetails} />} />
<Route path='/legacy' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
<Route path='/error' element={<WithRoute el={ErrorPage} brew={this.props.brew} />} />
<Route path='/' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
<Route path='/*' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
</Routes>
</div>
</Router>
);
}
});
return (
<Router location={url}>
<div className={`homebrew${(config.deployment || config.local) ? ' deployment' : ''}`} style={backgroundObject()}>
<Routes>
<Route path='/edit/:id' element={<WithRoute el={EditPage} brew={brew} userThemes={userThemes}/>} />
<Route path='/share/:id' element={<WithRoute el={SharePage} brew={brew} />} />
<Route path='/new/:id' element={<WithRoute el={NewPage} brew={brew} userThemes={userThemes}/>} />
<Route path='/new' element={<WithRoute el={NewPage} userThemes={userThemes}/> } />
<Route path='/user/:username' element={<WithRoute el={UserPage} brews={brews} />} />
<Route path='/vault' element={<WithRoute el={VaultPage}/>}/>
<Route path='/changelog' element={<WithRoute el={SharePage} brew={brew} disableMeta={true} />} />
<Route path='/faq' element={<WithRoute el={SharePage} brew={brew} disableMeta={true} />} />
<Route path='/migrate' element={<WithRoute el={SharePage} brew={brew} disableMeta={true} />} />
<Route path='/account' element={<WithRoute el={AccountPage} brew={brew} accountDetails={brew.accountDetails} />} />
<Route path='/legacy' element={<WithRoute el={HomePage} brew={brew} />} />
<Route path='/error' element={<WithRoute el={ErrorPage} brew={brew} />} />
<Route path='/' element={<WithRoute el={HomePage} brew={brew} />} />
<Route path='/*' element={<WithRoute el={HomePage} brew={brew} />} />
</Routes>
</div>
</Router>
);
};
module.exports = Homebrew;

View File

@@ -1,36 +1,34 @@
@import 'naturalcrit/styles/core.less';
.homebrew{
.homebrew {
height : 100%;
.sitePage{
background-color:@steel;
&.deployment { background-color : darkred; }
.sitePage {
display : flex;
height : 100%;
background-color : @steel;
flex-direction : column;
height : 100%;
overflow-y : hidden;
.content{
.content {
position : relative;
height : calc(~"100% - 29px"); //Navbar height
flex : auto;
height : calc(~'100% - 29px'); //Navbar height
overflow-y : hidden;
}
&.listPage .content {
overflow-y : scroll;
&::-webkit-scrollbar {
width: 20px;
&:horizontal{
height: 20px;
width:auto;
width : 20px;
&:horizontal {
width : auto;
height : 20px;
}
&-thumb {
background: linear-gradient(90deg, #d3c1af 15px, #00000000 15px);
&:horizontal{
background: linear-gradient(0deg, #d3c1af 15px, #00000000 15px);
}
}
&-corner {
visibility: hidden;
background : linear-gradient(90deg, #D3C1AF 15px, #00000000 15px);
&:horizontal { background : linear-gradient(0deg, #D3C1AF 15px, #00000000 15px); }
}
&-corner { visibility : hidden; }
}
}
}

View File

@@ -1,131 +1,138 @@
require('./error-navitem.less');
const React = require('react');
const Nav = require('naturalcrit/nav/nav.jsx');
const createClass = require('create-react-class');
const ErrorNavItem = createClass({
getDefaultProps : function() {
return {
error : '',
parent : null
};
},
render : function() {
const clearError = ()=>{
const state = {
error : null
};
if(this.props.parent.state.isSaving) {
state.isSaving = false;
}
this.props.parent.setState(state);
};
const ErrorNavItem = ({error = '', clearError})=>{
const response = error.response;
const errorCode = error.code
const status = response?.status;
const HBErrorCode = response?.body?.HBErrorCode;
const message = response?.body?.message;
const error = this.props.error;
const response = error.response;
const status = response.status;
const HBErrorCode = response.body?.HBErrorCode;
const message = response.body?.message;
let errMsg = '';
try {
errMsg += `${error.toString()}\n\n`;
errMsg += `\`\`\`\n${error.stack}\n`;
errMsg += `${JSON.stringify(response.error, null, ' ')}\n\`\`\``;
console.log(errMsg);
} catch (e){}
if(status === 409) {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
{message ?? 'Conflict: please refresh to get latest changes'}
</div>
</Nav.item>;
}
if(status === 412) {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
{message ?? 'Your client is out of date. Please save your changes elsewhere and refresh.'}
</div>
</Nav.item>;
}
if(HBErrorCode === '04') {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
You are no longer signed in as an author of
this brew! Were you signed out from a different
window? Visit our log in page, then try again!
<br></br>
<a target='_blank' rel='noopener noreferrer'
href={`https://www.naturalcrit.com/login?redirect=${window.location.href}`}>
<div className='confirm'>
Sign In
</div>
</a>
<div className='deny'>
Not Now
</div>
</div>
</Nav.item>;
}
if(response.body?.errors?.[0].reason == 'storageQuotaExceeded') {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
Can't save because your Google Drive seems to be full!
</div>
</Nav.item>;
}
if(response.req.url.match(/^\/api.*Google.*$/m)){
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
Looks like your Google credentials have
expired! Visit our log in page to sign out
and sign back in with Google,
then try saving again!
<br></br>
<a target='_blank' rel='noopener noreferrer'
href={`https://www.naturalcrit.com/login?redirect=${window.location.href}`}>
<div className='confirm'>
Sign In
</div>
</a>
<div className='deny'>
Not Now
</div>
</div>
</Nav.item>;
}
if(HBErrorCode === '09') {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
Looks like there was a problem retreiving
the theme, or a theme that it inherits,
for this brew. Verify that brew <a className='lowercase' target='_blank' rel='noopener noreferrer' href={`/share/${response.body.brewId}`}>
{response.body.brewId}</a> still exists!
</div>
</Nav.item>;
}
let errMsg = '';
try {
errMsg += `${error.toString()}\n\n`;
errMsg += `\`\`\`\n${error.stack}\n`;
errMsg += `${JSON.stringify(response?.error, null, ' ')}\n\`\`\``;
console.log(errMsg);
} catch (e){}
if(status === 409) {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer'>
Looks like there was a problem saving. <br />
Report the issue <a target='_blank' rel='noopener noreferrer' href={`https://github.com/naturalcrit/homebrewery/issues/new?template=save_issue.yml&error-code=${encodeURIComponent(errMsg)}`}>
here
</a>.
<div className='errorContainer' onClick={clearError}>
{message ?? 'Conflict: please refresh to get latest changes'}
</div>
</Nav.item>;
}
});
if(status === 412) {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
{message ?? 'Your client is out of date. Please save your changes elsewhere and refresh.'}
</div>
</Nav.item>;
}
if(HBErrorCode === '04') {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
You are no longer signed in as an author of
this brew! Were you signed out from a different
window? Visit our log in page, then try again!
<br></br>
<a target='_blank' rel='noopener noreferrer'
href={`https://www.naturalcrit.com/login?redirect=${window.location.href}`}>
<div className='confirm'>
Sign In
</div>
</a>
<div className='deny'>
Not Now
</div>
</div>
</Nav.item>;
}
if(response?.body?.errors?.[0].reason == 'storageQuotaExceeded') {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
Can't save because your Google Drive seems to be full!
</div>
</Nav.item>;
}
if(response?.req.url.match(/^\/api.*Google.*$/m)){
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
Looks like your Google credentials have
expired! Visit our log in page to sign out
and sign back in with Google,
then try saving again!
<br></br>
<a target='_blank' rel='noopener noreferrer'
href={`https://www.naturalcrit.com/login?redirect=${window.location.href}`}>
<div className='confirm'>
Sign In
</div>
</a>
<div className='deny'>
Not Now
</div>
</div>
</Nav.item>;
}
if(HBErrorCode === '09') {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
Looks like there was a problem retreiving
the theme, or a theme that it inherits,
for this brew. Verify that brew <a className='lowercase' target='_blank' rel='noopener noreferrer' href={`/share/${response.body.brewId}`}>
{response.body.brewId}</a> still exists!
</div>
</Nav.item>;
}
if(HBErrorCode === '10') {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
Looks like the brew you have selected
as a theme is not tagged for use as a
theme. Verify that
brew <a className='lowercase' target='_blank' rel='noopener noreferrer' href={`/share/${response.body.brewId}`}>
{response.body.brewId}</a> has the <span className='lowercase'>meta:theme</span> tag!
</div>
</Nav.item>;
}
if(errorCode === 'ECONNABORTED') {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
The request to the server was interrupted or timed out.
This can happen due to a network issue, or if
trying to save a particularly large brew.
Please check your internet connection and try again.
</div>
</Nav.item>;
}
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer'>
Looks like there was a problem saving. <br />
Report the issue <a target='_blank' rel='noopener noreferrer' href={`https://github.com/naturalcrit/homebrewery/issues/new?template=save_issue.yml&error-code=${encodeURIComponent(errMsg)}`}>
here
</a>.
</div>
</Nav.item>;
};
module.exports = ErrorNavItem;

View File

@@ -1,78 +1,70 @@
.navItem.error {
position : relative;
background-color : @red;
position : relative;
background-color : @red;
}
.errorContainer{
animation-name: glideDown;
animation-duration: 0.4s;
position : absolute;
top : 100%;
left : 50%;
z-index : 1000;
width : 140px;
padding : 3px;
color : white;
background-color : #333;
border : 3px solid #444;
border-radius : 5px;
transform : translate(-50% + 3px, 10px);
text-align : center;
font-size : 10px;
font-weight : 800;
text-transform : uppercase;
.lowercase {
text-transform : none;
.errorContainer {
position : absolute;
top : 100%;
left : 50%;
z-index : 1000;
width : 140px;
padding : 3px;
font-size : 10px;
font-weight : 800;
color : white;
text-align : center;
text-transform : uppercase;
background-color : #333333;
border : 3px solid #444444;
border-radius : 5px;
transform : translate(-50% + 3px, 10px);
animation-name : glideDown;
animation-duration : 0.4s;
.lowercase { text-transform : none; }
a { color : @teal; }
&::before {
position : absolute;
top : -23px;
left : 53px;
width : 0px;
height : 0px;
content : '';
border-top : 10px solid transparent;
border-right : 10px solid transparent;
border-bottom : 10px solid #444444;
border-left : 10px solid transparent;
}
&::after {
position : absolute;
top : -19px;
left : 53px;
width : 0px;
height : 0px;
content : '';
border-top : 10px solid transparent;
border-right : 10px solid transparent;
border-bottom : 10px solid #333333;
border-left : 10px solid transparent;
}
.deny {
display : inline-block;
width : 48%;
padding : 5px;
margin : 1px;
background-color : #333333;
border-left : 1px solid #666666;
.animate(background-color);
&:hover { background-color : red; }
}
.confirm {
display : inline-block;
width : 48%;
padding : 5px;
margin : 1px;
color : white;
background-color : #333333;
.animate(background-color);
&:hover { background-color : teal; }
}
a{
color : @teal;
}
&:before {
content: "";
width: 0px;
height: 0px;
position: absolute;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid transparent;
border-bottom: 10px solid #444;
left: 53px;
top: -23px;
}
&:after {
content: "";
width: 0px;
height: 0px;
position: absolute;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid transparent;
border-bottom: 10px solid #333;
left: 53px;
top: -19px;
}
.deny {
width : 48%;
margin : 1px;
padding : 5px;
background-color : #333;
display : inline-block;
border-left : 1px solid #666;
.animate(background-color);
&:hover{
background-color : red;
}
}
.confirm {
width : 48%;
margin : 1px;
padding : 5px;
background-color : #333;
display : inline-block;
color : white;
.animate(background-color);
&:hover{
background-color : teal;
}
}
}

View File

@@ -24,11 +24,11 @@
}
.homebrew nav {
position : relative;
z-index : 2;
display : flex;
justify-content : space-between;
background-color : #333333;
position : relative;
z-index : 2;
display : flex;
justify-content : space-between;
.navSection {
display : flex;
@@ -82,8 +82,8 @@
font-weight : 800;
line-height : 13px;
color : white;
text-decoration : none;
text-transform : uppercase;
text-decoration : none;
cursor : pointer;
background-color : #333333;
i {
@@ -106,11 +106,11 @@
display : block;
width : 100%;
overflow : hidden;
text-overflow : ellipsis;
font-size : 12px;
font-weight : 800;
color : white;
text-align : center;
text-overflow : ellipsis;
text-transform : initial;
white-space : nowrap;
background-color : transparent;
@@ -170,16 +170,16 @@
h4 {
box-sizing : border-box;
display : block;
flex-basis : 20%;
flex-grow : 1;
flex-basis : 20%;
min-width : 76px;
padding : 5px 0;
color : #BBBBBB;
text-align : center;
}
p {
flex-basis : 80%;
flex-grow : 1;
flex-basis : 80%;
padding : 5px 0;
font-family : 'Open Sans', sans-serif;
font-size : 10px;
@@ -215,10 +215,10 @@
z-index : 10000;
box-sizing : border-box;
display : block;
visibility : hidden;
width : 100%;
padding : 13px 5px;
text-align : center;
visibility : hidden;
background-color : #333333;
}
}

View File

@@ -5,33 +5,45 @@ const { splitTextStyleAndMetadata } = require('../../../shared/helpers.js'); //
const BREWKEY = 'homebrewery-new';
const STYLEKEY = 'homebrewery-new-style';
const METAKEY = 'homebrewery-new-meta';
const METAKEY = 'homebrewery-new-meta';
const NewBrew = ()=>{
const handleFileChange = (e)=>{
const file = e.target.files[0];
if(file) {
const reader = new FileReader();
reader.onload = (e)=>{
const fileContent = e.target.result;
const newBrew = {
text : fileContent,
style : ''
};
if(fileContent.startsWith('```metadata')) {
splitTextStyleAndMetadata(newBrew); // Modify newBrew directly
localStorage.setItem(BREWKEY, newBrew.text);
localStorage.setItem(STYLEKEY, newBrew.style);
localStorage.setItem(METAKEY, JSON.stringify(_.pick(newBrew, ['title', 'description', 'tags', 'systems', 'renderer', 'theme', 'lang'])));
window.location.href = '/new';
} else {
alert('This file is invalid, please, enter a valid file');
}
};
reader.readAsText(file);
}
if(!file) return;
const currentNew = localStorage.getItem(BREWKEY);
if(currentNew && !confirm(
`You have some text in the new brew space, if you load a file that text will be lost, are you sure you want to load the file?`
)) return;
const reader = new FileReader();
reader.onload = (e)=>{
const fileContent = e.target.result;
const newBrew = { text: fileContent, style: '' };
if(fileContent.startsWith('```metadata')) {
splitTextStyleAndMetadata(newBrew);
localStorage.setItem(BREWKEY, newBrew.text);
localStorage.setItem(STYLEKEY, newBrew.style);
localStorage.setItem(METAKEY, JSON.stringify(
_.pick(newBrew, ['title', 'description', 'tags', 'systems', 'renderer', 'theme', 'lang'])
));
window.location.href = '/new';
return;
}
const type = file.name.split('.').pop().toLowerCase();
alert(`This file is invalid: ${!type ? "Missing file extension" :`.${type} files are not supported`}. Only .txt files exported from the Homebrewery are allowed.`);
console.log(file);
};
reader.readAsText(file);
};
return (
<Nav.dropdown>
<Nav.item

View File

@@ -5,8 +5,8 @@ const Moment = require('moment');
const Nav = require('naturalcrit/nav/nav.jsx');
const EDIT_KEY = 'homebrewery-recently-edited';
const VIEW_KEY = 'homebrewery-recently-viewed';
const EDIT_KEY = 'HB_nav_recentlyEdited';
const VIEW_KEY = 'HB_nav_recentlyViewed';
const RecentItems = createClass({

View File

@@ -0,0 +1,35 @@
import React from 'react';
import dedent from 'dedent-tabs';
import Nav from 'naturalcrit/nav/nav.jsx';
const getShareId = (brew)=>(
brew.googleId && !brew.stubbed
? brew.googleId + brew.shareId
: brew.shareId
);
const getRedditLink = (brew)=>{
const text = dedent`
Hey guys! I've been working on this homebrew. I'd love your feedback. Check it out.
**[Homebrewery Link](${global.config.baseUrl}/share/${getShareId(brew)})**`;
return `https://www.reddit.com/r/UnearthedArcana/submit?title=${encodeURIComponent(brew.title.toWellFormed())}&text=${encodeURIComponent(text)}`;
};
export default ({brew}) => (
<Nav.dropdown>
<Nav.item color='teal' icon='fas fa-share-alt'>
share
</Nav.item>
<Nav.item color='blue' href={`/share/${getShareId(brew)}`}>
view
</Nav.item>
<Nav.item color='blue' onClick={()=>{navigator.clipboard.writeText(`${global.config.baseUrl}/share/${getShareId(brew)}`);}}>
copy url
</Nav.item>
<Nav.item color='blue' href={getRedditLink(brew)} newTab rel='noopener noreferrer'>
post to reddit
</Nav.item>
</Nav.dropdown>
);

View File

@@ -13,7 +13,7 @@ const AccountPage = (props)=>{
// initialize save location from local storage based on user id
React.useEffect(()=>{
if(!saveLocation && accountDetails.username) {
SAVEKEY = `HOMEBREWERY-DEFAULT-SAVE-LOCATION-${accountDetails.username}`;
SAVEKEY = `HB_editor_defaultSave_${accountDetails.username}`;
// if no SAVEKEY in local storage, default save location to Google Drive if user has Google account.
let saveLocation = window.localStorage.getItem(SAVEKEY);
saveLocation = saveLocation ?? (accountDetails.googleId ? 'GOOGLE-DRIVE' : 'HOMEBREWERY');

View File

@@ -5,7 +5,7 @@ const moment = require('moment');
import request from '../../../../utils/request-middleware.js';
const googleDriveIcon = require('../../../../googleDrive.svg');
const homebreweryIcon = require('../../../../thumbnail.png');
const homebreweryIcon = require('../../../../thumbnail.svg');
const dedent = require('dedent-tabs').default;
const BrewItem = ({
@@ -30,11 +30,11 @@ const BrewItem = ({
}
request.delete(`/api/${brew.googleId ?? ''}${brew.editId}`).send().end((err, res)=>{
if (err) reportError(err); else window.location.reload();
});
if(err) reportError(err); else window.location.reload();
});
}, [brew, reportError]);
const updateFilter = useCallback((type, term)=> updateListFilter(type, term), [updateListFilter]);
const updateFilter = useCallback((type, term)=>updateListFilter(type, term), [updateListFilter]);
const renderDeleteBrewLink = ()=>{
if(!brew.editId) return null;

View File

@@ -1,148 +1,129 @@
.brewItem{
.brewItem {
position : relative;
box-sizing : border-box;
display : inline-block;
vertical-align : top;
box-sizing : border-box;
box-sizing : border-box;
overflow : hidden;
width : 48%;
min-height : 105px;
margin-right : 15px;
margin-bottom : 15px;
padding : 5px 15px 2px 6px;
padding-right : 15px;
border : 1px solid #c9ad6a;
margin-right : 15px;
margin-bottom : 15px;
overflow : hidden;
vertical-align : top;
background-color : #CAB2802E;
border : 1px solid #C9AD6A;
border-radius : 5px;
box-shadow : 0px 4px 5px 0px #333333;
break-inside : avoid;
-webkit-column-break-inside : avoid;
page-break-inside : avoid;
break-inside : avoid;
box-shadow : 0px 4px 5px 0px #333;
background-color : #cab2802e;
.thumbnail {
position: absolute;
width: 150px;
height: 100%;
top: 0;
right: 0;
z-index: -1;
background-size: contain;
background-repeat: no-repeat;
background-position: right top;
mask-image: linear-gradient(80deg, #0000 20%, #050 40%);
-webkit-mask-image: linear-gradient(80deg, #0000 20%, #050 40%);
opacity: 50%;
.thumbnail {
position : absolute;
top : 0;
right : 0;
z-index : -1;
width : 150px;
height : 100%;
background-repeat : no-repeat;
background-position : right top;
background-size : contain;
opacity : 50%;
-webkit-mask-image : linear-gradient(80deg, #00000000 20%, #005500 40%);
mask-image : linear-gradient(80deg, #00000000 20%, #005500 40%);
}
.text {
min-height : 54px;
h4{
h4 {
margin-bottom : 5px;
font-size : 2.2em;
}
}
.info{
position: initial;
bottom: 2px;
font-family : ScalySansRemake;
.info {
position : initial;
bottom : 2px;
font-family : "ScalySansRemake";
font-size : 1.2em;
&>span{
& > span {
margin-right : 12px;
line-height : 1.5em;
a {
color:inherit;
}
a { color : inherit; }
}
}
.brewTags span {
background-color: #c8ac6e3b;
margin: 2px;
padding: 2px;
border: 1px solid #c8ac6e;
border-radius: 4px;
white-space: nowrap;
display: inline-block;
font-weight: bold;
border-color: currentColor;
cursor : pointer;
&:before {
font-family: 'Font Awesome 5 Free';
font-size: 12px;
margin-right: 3px;
display : inline-block;
padding : 2px;
margin : 2px;
font-weight : bold;
white-space : nowrap;
cursor : pointer;
background-color : #C8AC6E3B;
border : 1px solid #C8AC6E;
border-color : currentColor;
border-radius : 4px;
&::before {
margin-right : 3px;
font-family : 'Font Awesome 6 Free';
font-size : 12px;
}
&.type {
background-color: #0080003b;
color: #008000;
&:before{
content: '\f0ad';
}
color : #008000;
background-color : #0080003B;
&::before { content : '\f0ad'; }
}
&.group {
background-color: #5050503b;
color: #000000;
&:before{
content: '\f500';
}
color : #000000;
background-color : #5050503B;
&::before { content : '\f500'; }
}
&.meta {
background-color: #0000803b;
color: #000080;
&:before{
content: '\f05a';
}
color : #000080;
background-color : #0000803B;
&::before { content : '\f05a'; }
}
&.system {
background-color: #8000003b;
color: #800000;
&:before{
content: '\f518';
}
color : #800000;
background-color : #8000003B;
&::before { content : '\f518'; }
}
}
&:hover{
.links{
opacity : 1;
}
&:hover {
.links { opacity : 1; }
}
&:nth-child(2n + 1){
margin-right : 0px;
}
.links{
&:nth-child(2n + 1) { margin-right : 0px; }
.links {
.animate(opacity);
position : absolute;
top : 0px;
right : 0px;
height : 100%;
width : 2em;
opacity : 0;
background-color : fade(black, 60%);
height : 100%;
text-align : center;
a{
background-color : fade(black, 60%);
opacity : 0;
a {
.animate(opacity);
display : block;
margin : 8px 0px;
opacity : 0.6;
font-size : 1.3em;
color : white;
text-decoration : unset;
&:hover{
opacity : 1;
}
i{
cursor : pointer;
}
opacity : 0.6;
&:hover { opacity : 1; }
i { cursor : pointer; }
}
}
.googleDriveIcon {
height : 18px;
padding : 0px;
margin : -5px;
height : 18px;
}
.homebreweryIcon {
mix-blend-mode : darken;
height : 24px;
position : relative;
top : 5px;
left : -5px;
position : relative;
padding : 0px;
top : 5px;
left : -7.5px;
height : 18px;
}
}

View File

@@ -7,7 +7,9 @@ const moment = require('moment');
const BrewItem = require('./brewItem/brewItem.jsx');
const USERPAGE_KEY_PREFIX = 'HOMEBREWERY-LISTPAGE';
const USERPAGE_SORT_DIR = 'HB_listPage_sortDir';
const USERPAGE_SORT_TYPE = 'HB_listPage_sortType';
const USERPAGE_GROUP_VISIBILITY_PREFIX = 'HB_listPage_visibility_group';
const DEFAULT_SORT_TYPE = 'alpha';
const DEFAULT_SORT_DIR = 'asc';
@@ -50,12 +52,12 @@ const ListPage = createClass({
// LOAD FROM LOCAL STORAGE
if(typeof window !== 'undefined') {
const newSortType = (this.state.sortType ?? (localStorage.getItem(`${USERPAGE_KEY_PREFIX}-SORTTYPE`) || DEFAULT_SORT_TYPE));
const newSortDir = (this.state.sortDir ?? (localStorage.getItem(`${USERPAGE_KEY_PREFIX}-SORTDIR`) || DEFAULT_SORT_DIR));
const newSortType = (this.state.sortType ?? (localStorage.getItem(USERPAGE_SORT_TYPE) || DEFAULT_SORT_TYPE));
const newSortDir = (this.state.sortDir ?? (localStorage.getItem(USERPAGE_SORT_DIR) || DEFAULT_SORT_DIR));
this.updateUrl(this.state.filterString, newSortType, newSortDir);
const brewCollection = this.props.brewCollection.map((brewGroup)=>{
brewGroup.visible = (localStorage.getItem(`${USERPAGE_KEY_PREFIX}-VISIBILITY-${brewGroup.class}`) ?? 'true')=='true';
brewGroup.visible = (localStorage.getItem(`${USERPAGE_GROUP_VISIBILITY_PREFIX}_${brewGroup.class}`) ?? 'true')=='true';
return brewGroup;
});
@@ -73,10 +75,10 @@ const ListPage = createClass({
saveToLocalStorage : function() {
this.state.brewCollection.map((brewGroup)=>{
localStorage.setItem(`${USERPAGE_KEY_PREFIX}-VISIBILITY-${brewGroup.class}`, `${brewGroup.visible}`);
localStorage.setItem(`${USERPAGE_GROUP_VISIBILITY_PREFIX}_${brewGroup.class}`, `${brewGroup.visible}`);
});
localStorage.setItem(`${USERPAGE_KEY_PREFIX}-SORTTYPE`, this.state.sortType);
localStorage.setItem(`${USERPAGE_KEY_PREFIX}-SORTDIR`, this.state.sortDir);
localStorage.setItem(USERPAGE_SORT_TYPE, this.state.sortType);
localStorage.setItem(USERPAGE_SORT_DIR, this.state.sortDir);
},
renderBrews : function(brews){

View File

@@ -1,5 +1,5 @@
.noColumns(){
.noColumns() {
column-count : auto;
column-fill : auto;
column-gap : normal;
@@ -13,177 +13,151 @@
height : auto;
min-height : 279.4mm;
margin : 20px auto;
contain : unset;
contain : unset;
}
.listPage{
.content{
.listPage {
.content {
z-index : 1;
.page{
.page {
.noColumns() !important; //Needed to override PHB Theme since this is on a lower @layer
&::after{
display : none;
}
.noBrews{
&::after { display : none; }
.noBrews {
margin : 10px 0px;
font-size : 1.3em;
font-style : italic;
}
.brewCollection {
h1:hover{
cursor: pointer;
}
h1:hover { cursor : pointer; }
.active::before, .inactive::before {
font-family: 'Font Awesome 5 Free';
font-weight: 900;
font-size: 0.6cm;
padding-right: 0.5em;
}
.active {
color: var(--HB_Color_HeaderText);
}
.active::before {
content: '\f107';
}
.inactive {
color: #707070;
}
.inactive::before {
content: '\f105';
padding-right : 0.5em;
font-family : 'Font Awesome 6 Free';
font-size : 0.6cm;
font-weight : 900;
}
.active { color : var(--HB_Color_HeaderText); }
.active::before { content : '\f107'; }
.inactive { color : #707070; }
.inactive::before { content : '\f105'; }
}
}
}
.sort-container {
font-family : 'Open Sans', sans-serif;
position : sticky;
top : 0;
left : 0;
width : 100%;
height : 30px;
background-color : #555;
border-top : 1px solid #666;
border-bottom : 1px solid #666;
color : white;
text-align : center;
z-index : 1;
display : flex;
justify-content : center;
align-items : baseline;
column-gap : 15px;
row-gap : 5px;
flex-wrap : wrap;
h6{
text-transform : uppercase;
position : sticky;
top : 0;
left : 0;
z-index : 1;
display : flex;
flex-wrap : wrap;
row-gap : 5px;
column-gap : 15px;
align-items : baseline;
justify-content : center;
width : 100%;
height : 30px;
font-family : 'Open Sans', sans-serif;
color : white;
text-align : center;
background-color : #555555;
border-top : 1px solid #666666;
border-bottom : 1px solid #666666;
h6 {
font-family : 'Open Sans', sans-serif;
font-size : 11px;
font-weight : bold;
text-transform : uppercase;
}
.sort-option {
display: flex;
align-items: center;
padding: 0 8px;
color: #ccc;
height: 100%;
display : flex;
align-items : center;
height : 100%;
padding : 0 8px;
color : #CCCCCC;
&:hover{
background-color : #444;
}
&:hover { background-color : #444444; }
&.active {
font-weight: bold;
color: #ddd;
background-color: #333;
font-weight : bold;
color : #DDDDDD;
background-color : #333333;
button {
color: white;
font-weight: 800;
height: 100%;
& + .sortDir {
padding-left: 5px;
button {
height : 100%;
font-weight : 800;
color : white;
& + .sortDir { padding-left : 5px; }
}
}
}
}
.filter-option {
margin-left: 20px;
background-color : transparent !important;
margin-left : 20px;
font-size : 11px;
i{
padding-right : 5px;
}
background-color : transparent !important;
i { padding-right : 5px; }
}
button {
padding : 0;
font-family : 'Open Sans', sans-serif;
font-size : 11px;
font-weight : normal;
color : #CCCCCC;
text-transform : uppercase;
background-color : transparent;
}
button{
background-color : transparent;
font-family : 'Open Sans', sans-serif;
text-transform : uppercase;
font-weight : normal;
font-size : 11px;
color : #ccc;
padding : 0;
}
}
.tags-container {
height : 30px;
background-color : #555;
border-top : 1px solid #666;
border-bottom : 1px solid #666;
color : white;
display : flex;
justify-content : center;
align-items : center;
column-gap : 15px;
row-gap : 5px;
flex-wrap : wrap;
row-gap : 5px;
column-gap : 15px;
align-items : center;
justify-content : center;
height : 30px;
color : white;
background-color : #555555;
border-top : 1px solid #666666;
border-bottom : 1px solid #666666;
span {
padding : 3px;
font-family : 'Open Sans', sans-serif;
font-size : 11px;
font-weight : bold;
color : #DFDFDF;
cursor : pointer;
border : 1px solid;
border-radius : 3px;
padding : 3px;
cursor : pointer;
color: #dfdfdf;
&:before {
font-family: 'Font Awesome 5 Free';
font-size: 12px;
margin-right: 3px;
&::before {
margin-right : 3px;
font-family : 'Font Awesome 6 Free';
font-size : 12px;
}
&:after {
content: '\f00d';
font-family: 'Font Awesome 5 Free';
font-size: 12px;
margin-left: 3px;
&::after {
margin-left : 3px;
font-family : 'Font Awesome 6 Free';
font-size : 12px;
content : '\f00d';
}
&.type {
background-color: #008000;
border-color: #00a000;
&:before{
content: '\f0ad';
}
background-color : #008000;
border-color : #00A000;
&::before { content : '\f0ad'; }
}
&.group {
background-color: #505050;
border-color: #000000;
&:before{
content: '\f500';
}
background-color : #505050;
border-color : #000000;
&::before { content : '\f500'; }
}
&.meta {
background-color: #000080;
border-color: #0000a0;
&:before{
content: '\f05a';
}
background-color : #000080;
border-color : #0000A0;
&::before { content : '\f05a'; }
}
&.system {
background-color: #800000;
border-color: #a00000;
&:before{
content: '\f518';
}
background-color : #800000;
border-color : #A00000;
&::before { content : '\f518'; }
}
}
}

View File

@@ -1,7 +1,7 @@
.homebrew {
.uiPage.sitePage {
.content {
width : ~"min(90vw, 1000px)";
width : ~'min(90vw, 1000px)';
padding : 2% 4%;
margin-top : 25px;
margin-right : auto;
@@ -17,19 +17,20 @@
border : 2px solid black;
border-radius : 5px;
button {
width : 125px;
margin-right : 5px;
color : black;
background-color : transparent;
border : 1px solid black;
border-radius : 5px;
width : 125px;
color : black;
margin-right : 5px;
&.active {
background-color: #0007;
color: white;
&:before {
content: '\f00c';
font-family: 'FONT AWESOME 5 FREE';
margin-right: 5px;
color : white;
background-color : #00000077;
&::before {
margin-right : 5px;
font-family : 'Font Awesome 6 Free';
font-weight : 900;
content : '\f00c';
}
}
}

View File

@@ -1,474 +1,415 @@
/* eslint-disable max-lines */
require('./editPage.less');
const React = require('react');
const _ = require('lodash');
const createClass = require('create-react-class');
import './editPage.less';
import request from '../../utils/request-middleware.js';
const { Meta } = require('vitreum/headtags');
// Common imports
import React, { useState, useEffect, useRef } from 'react';
import request from '../../utils/request-middleware.js';
import Markdown from 'naturalcrit/markdown.js';
import _ from 'lodash';
const Nav = require('naturalcrit/nav/nav.jsx');
const Navbar = require('../../navbar/navbar.jsx');
import { DEFAULT_BREW_LOAD } from '../../../../server/brewDefaults.js';
import { printCurrentBrew, fetchThemeBundle, splitTextStyleAndMetadata } from '../../../../shared/helpers.js';
const NewBrew = require('../../navbar/newbrew.navitem.jsx');
const HelpNavItem = require('../../navbar/help.navitem.jsx');
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');
import SplitPane from 'client/components/splitPane/splitPane.jsx';
import Editor from '../../editor/editor.jsx';
import BrewRenderer from '../../brewRenderer/brewRenderer.jsx';
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
const Editor = require('../../editor/editor.jsx');
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
import Nav from 'naturalcrit/nav/nav.jsx';
import Navbar from '../../navbar/navbar.jsx';
import NewBrewItem from '../../navbar/newbrew.navitem.jsx';
import AccountNavItem from '../../navbar/account.navitem.jsx';
import ErrorNavItem from '../../navbar/error-navitem.jsx';
import HelpNavItem from '../../navbar/help.navitem.jsx';
import VaultNavItem from '../../navbar/vault.navitem.jsx';
import PrintNavItem from '../../navbar/print.navitem.jsx';
import { both as RecentNavItem } from '../../navbar/recent.navitem.jsx';
const LockNotification = require('./lockNotification/lockNotification.jsx');
import Markdown from 'naturalcrit/markdown.js';
const { DEFAULT_BREW_LOAD } = require('../../../../server/brewDefaults.js');
const { printCurrentBrew, fetchThemeBundle } = require('../../../../shared/helpers.js');
// Page specific imports
import { Meta } from 'vitreum/headtags';
import { md5 } from 'hash-wasm';
import { gzipSync, strToU8 } from 'fflate';
import { makePatches, stringifyPatches } from '@sanity/diff-match-patch';
import ShareNavItem from '../../navbar/share.navitem.jsx';
import LockNotification from './lockNotification/lockNotification.jsx';
import { updateHistory, versionHistoryGarbageCollection } from '../../utils/versionHistory.js';
const googleDriveIcon = require('../../googleDrive.svg');
import googleDriveIcon from '../../googleDrive.svg';
const SAVE_TIMEOUT = 10000;
const UNSAVED_WARNING_TIMEOUT = 900000; //Warn user afer 15 minutes of unsaved changes
const UNSAVED_WARNING_POPUP_TIMEOUT = 4000; //Show the warning for 4 seconds
const EditPage = createClass({
displayName : 'EditPage',
getDefaultProps : function() {
return {
brew : DEFAULT_BREW_LOAD
};
},
getInitialState : function() {
return {
brew : this.props.brew,
isSaving : false,
isPending : false,
alertTrashedGoogleBrew : this.props.brew.trashed,
alertLoginToTransfer : false,
saveGoogle : this.props.brew.googleId ? true : false,
confirmGoogleTransfer : false,
error : null,
htmlErrors : Markdown.validate(this.props.brew.text),
url : '',
autoSave : true,
autoSaveWarning : false,
unsavedTime : new Date(),
currentEditorViewPageNum : 1,
currentEditorCursorPageNum : 1,
currentBrewRendererPageNum : 1,
displayLockMessage : this.props.brew.lock || false,
themeBundle : {}
};
},
const AUTOSAVE_KEY = 'HB_editor_autoSaveOn';
const BREWKEY = 'HB_newPage_content';
const STYLEKEY = 'HB_newPage_style';
const SNIPKEY = 'HB_newPage_snippets';
const METAKEY = 'HB_newPage_meta';
editor : React.createRef(null),
savedBrew : null,
const useLocalStorage = false;
const neverSaved = false;
componentDidMount : function(){
this.setState({
url : window.location.href
});
const EditPage = (props)=>{
props = {
brew : DEFAULT_BREW_LOAD,
...props
};
this.savedBrew = JSON.parse(JSON.stringify(this.props.brew)); //Deep copy
const [currentBrew , setCurrentBrew ] = useState(props.brew);
const [isSaving , setIsSaving ] = useState(false);
const [lastSavedTime , setLastSavedTime ] = useState(new Date());
const [saveGoogle , setSaveGoogle ] = useState(!!props.brew.googleId);
const [error , setError ] = useState(null);
const [HTMLErrors , setHTMLErrors ] = useState(Markdown.validate(props.brew.text));
const [currentEditorViewPageNum , setCurrentEditorViewPageNum ] = useState(1);
const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1);
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
const [themeBundle , setThemeBundle ] = useState({});
const [unsavedChanges , setUnsavedChanges ] = useState(false);
const [alertTrashedGoogleBrew , setAlertTrashedGoogleBrew ] = useState(props.brew.trashed);
const [alertLoginToTransfer , setAlertLoginToTransfer ] = useState(false);
const [confirmGoogleTransfer , setConfirmGoogleTransfer ] = useState(false);
const [autoSaveEnabled , setAutoSaveEnabled ] = useState(true);
const [warnUnsavedChanges , setWarnUnsavedChanges ] = useState(true);
this.setState({ autoSave: JSON.parse(localStorage.getItem('AUTOSAVE_ON')) ?? true }, ()=>{
if(this.state.autoSave){
this.trySave();
} else {
this.setState({ autoSaveWarning: true });
const editorRef = useRef(null);
const lastSavedBrew = useRef(_.cloneDeep(props.brew));
const saveTimeout = useRef(null);
const warnUnsavedTimeout = useRef(null);
const trySaveRef = useRef(trySave); // CTRL+S listener lives outside React and needs ref to use trySave with latest copy of brew
const unsavedChangesRef = useRef(unsavedChanges); // Similarly, onBeforeUnload lives outside React and needs ref to unsavedChanges
useEffect(()=>{
const autoSavePref = JSON.parse(localStorage.getItem(AUTOSAVE_KEY) ?? true);
setAutoSaveEnabled(autoSavePref);
setWarnUnsavedChanges(!autoSavePref);
setHTMLErrors(Markdown.validate(currentBrew.text));
fetchThemeBundle(setError, setThemeBundle, currentBrew.renderer, currentBrew.theme);
const handleControlKeys = (e)=>{
if(!(e.ctrlKey || e.metaKey)) return;
if(e.keyCode === 83) trySaveRef.current(true);
if(e.keyCode === 80) printCurrentBrew();
if([83, 80].includes(e.keyCode)) {
e.stopPropagation();
e.preventDefault();
}
});
};
document.addEventListener('keydown', handleControlKeys);
window.onbeforeunload = ()=>{
if(this.state.isSaving || this.state.isPending){
if(unsavedChangesRef.current)
return 'You have unsaved changes!';
}
};
return ()=>{
document.removeEventListener('keydown', handleControlKeys);
window.onBeforeUnload = null;
};
}, []);
this.setState((prevState)=>({
htmlErrors : Markdown.validate(prevState.brew.text)
}));
useEffect(()=>{
trySaveRef.current = trySave;
unsavedChangesRef.current = unsavedChanges;
});
fetchThemeBundle(this, this.props.brew.renderer, this.props.brew.theme);
useEffect(()=>{
const hasChange = !_.isEqual(currentBrew, lastSavedBrew.current);
setUnsavedChanges(hasChange);
document.addEventListener('keydown', this.handleControlKeys);
},
componentWillUnmount : function() {
window.onbeforeunload = function(){};
document.removeEventListener('keydown', this.handleControlKeys);
},
if(autoSaveEnabled) trySave(false, hasChange);
}, [currentBrew]);
handleControlKeys : function(e){
if(!(e.ctrlKey || e.metaKey)) return;
const S_KEY = 83;
const P_KEY = 80;
if(e.keyCode == S_KEY) this.trySave(true);
if(e.keyCode == P_KEY) printCurrentBrew();
if(e.keyCode == P_KEY || e.keyCode == S_KEY){
e.stopPropagation();
e.preventDefault();
const handleSplitMove = ()=>{
editorRef.current?.update();
};
const handleBrewChange = (field) => (value, subfield) => { //'text', 'style', 'snippets', 'metadata'
if (subfield == 'renderer' || subfield == 'theme')
fetchThemeBundle(setError, setThemeBundle, value.renderer, value.theme);
//If there are HTML errors, run the validator on every change to give quick feedback
if(HTMLErrors.length && (field == 'text' || field == 'snippets'))
setHTMLErrors(Markdown.validate(value));
if(field == 'metadata') setCurrentBrew(prev => ({ ...prev, ...value }));
else setCurrentBrew(prev => ({ ...prev, [field]: value }));
if(useLocalStorage) {
if(field == 'text') localStorage.setItem(BREWKEY, value);
if(field == 'style') localStorage.setItem(STYLEKEY, value);
if(field == 'snippets') localStorage.setItem(SNIPKEY, value);
if(field == 'metadata') localStorage.setItem(METAKEY, JSON.stringify({
renderer : value.renderer,
theme : value.theme,
lang : value.lang
}));
}
},
};
handleSplitMove : function(){
this.editor.current.update();
},
const updateBrew = (newData)=>setCurrentBrew((prevBrew)=>({
...prevBrew,
style : newData.style,
text : newData.text,
snippets : newData.snippets
}));
handleEditorViewPageChange : function(pageNumber){
this.setState({ currentEditorViewPageNum: pageNumber });
},
const resetWarnUnsavedTimer = ()=>{
setTimeout(()=>setWarnUnsavedChanges(false), UNSAVED_WARNING_POPUP_TIMEOUT); // Hide the warning after 4 seconds
clearTimeout(warnUnsavedTimeout.current);
warnUnsavedTimeout.current = setTimeout(()=>setWarnUnsavedChanges(true), UNSAVED_WARNING_TIMEOUT); // 15 minutes between unsaved work warnings
};
handleEditorCursorPageChange : function(pageNumber){
this.setState({ currentEditorCursorPageNum: pageNumber });
},
handleBrewRendererPageChange : function(pageNumber){
this.setState({ currentBrewRendererPageNum: pageNumber });
},
handleTextChange : function(text){
//If there are errors, run the validator on every change to give quick feedback
let htmlErrors = this.state.htmlErrors;
if(htmlErrors.length) htmlErrors = Markdown.validate(text);
this.setState((prevState)=>({
brew : { ...prevState.brew, text: text },
isPending : true,
htmlErrors : htmlErrors,
}), ()=>{if(this.state.autoSave) this.trySave();});
},
handleStyleChange : function(style){
this.setState((prevState)=>({
brew : { ...prevState.brew, style: style },
isPending : true
}), ()=>{if(this.state.autoSave) this.trySave();});
},
handleMetaChange : function(metadata, field=undefined){
if(field == 'theme' || field == 'renderer') // Fetch theme bundle only if theme or renderer was changed
fetchThemeBundle(this, metadata.renderer, metadata.theme);
this.setState((prevState)=>({
brew : {
...prevState.brew,
...metadata
},
isPending : true,
}), ()=>{if(this.state.autoSave) this.trySave();});
},
hasChanges : function(){
return !_.isEqual(this.state.brew, this.savedBrew);
},
updateBrew : function(newData){
this.setState((prevState)=>({
brew : {
...prevState.brew,
style : newData.style,
text : newData.text
}
}));
},
trySave : function(immediate=false){
if(!this.debounceSave) this.debounceSave = _.debounce(this.save, SAVE_TIMEOUT);
if(this.hasChanges()){
this.debounceSave();
} else {
this.debounceSave.cancel();
}
if(immediate) this.debounceSave.flush();
},
handleGoogleClick : function(){
const handleGoogleClick = ()=>{
if(!global.account?.googleId) {
this.setState({
alertLoginToTransfer : true
});
setAlertLoginToTransfer(true);
return;
}
this.setState((prevState)=>({
confirmGoogleTransfer : !prevState.confirmGoogleTransfer
}));
this.setState({
error : null,
isSaving : false
});
},
closeAlerts : function(event){
event.stopPropagation(); //Only handle click once so alert doesn't reopen
this.setState({
alertTrashedGoogleBrew : false,
alertLoginToTransfer : false,
confirmGoogleTransfer : false
});
},
setConfirmGoogleTransfer((prev)=>!prev);
setError(null);
};
toggleGoogleStorage : function(){
this.setState((prevState)=>({
saveGoogle : !prevState.saveGoogle,
isSaving : false,
error : null
}), ()=>this.save());
},
const closeAlerts = (e)=>{
e.stopPropagation(); //Only handle click once so alert doesn't reopen
setAlertTrashedGoogleBrew(false);
setAlertLoginToTransfer(false);
setConfirmGoogleTransfer(false);
};
save : async function(){
if(this.debounceSave && this.debounceSave.cancel) this.debounceSave.cancel();
const toggleGoogleStorage = ()=>{
setSaveGoogle((prev)=>!prev);
setError(null);
trySave(true);
};
this.setState((prevState)=>({
isSaving : true,
error : null,
htmlErrors : Markdown.validate(prevState.brew.text)
}));
const trySave = (immediate = false, hasChanges = true)=>{
clearTimeout(saveTimeout.current);
if(isSaving) return;
if(!hasChanges && !immediate) return;
const newTimeout = immediate ? 0 : SAVE_TIMEOUT;
await updateHistory(this.state.brew).catch(console.error);
saveTimeout.current = setTimeout(async ()=>{
setIsSaving(true);
setError(null);
await save(currentBrew, saveGoogle)
.catch((err)=>{
setError(err);
});
setIsSaving(false);
setLastSavedTime(new Date());
if(!autoSaveEnabled) resetWarnUnsavedTimer();
}, newTimeout);
};
const save = async (brew, saveToGoogle)=>{
setHTMLErrors(Markdown.validate(brew.text));
await updateHistory(brew).catch(console.error);
await versionHistoryGarbageCollection().catch(console.error);
const transfer = this.state.saveGoogle == _.isNil(this.state.brew.googleId);
//Prepare content to send to server
const brewToSave = {
...brew,
text : brew.text.normalize('NFC'),
pageCount : ((brew.renderer === 'legacy' ? brew.text.match(/\\page/g) : brew.text.match(/^\\page$/gm)) || []).length + 1,
patches : stringifyPatches(makePatches(encodeURI(lastSavedBrew.current.text.normalize('NFC')), encodeURI(brew.text.normalize('NFC')))),
hash : await md5(lastSavedBrew.current.text),
textBin : undefined,
version : lastSavedBrew.current.version
};
const brew = this.state.brew;
brew.pageCount = ((brew.renderer=='legacy' ? brew.text.match(/\\page/g) : brew.text.match(/^\\page$/gm)) || []).length + 1;
const compressedBrew = gzipSync(strToU8(JSON.stringify(brewToSave)));
const transfer = saveToGoogle === _.isNil(brew.googleId);
const params = transfer ? `?${saveToGoogle ? 'saveToGoogle' : 'removeFromGoogle'}=true` : '';
const params = `${transfer ? `?${this.state.saveGoogle ? 'saveToGoogle' : 'removeFromGoogle'}=true` : ''}`;
const res = await request
.put(`/api/update/${brew.editId}${params}`)
.send(brew)
.put(`/api/update/${brewToSave.editId}${params}`)
.set('Content-Encoding', 'gzip')
.set('Content-Type', 'application/json')
.send(compressedBrew)
.catch((err)=>{
console.log('Error Updating Local Brew');
this.setState({ error: err });
console.error('Error Updating Local Brew');
setError(err);
});
if(!res) return;
this.savedBrew = res.body;
history.replaceState(null, null, `/edit/${this.savedBrew.editId}`);
const updatedFields = {
googleId : res.body.googleId ?? null,
editId : res.body.editId,
shareId : res.body.shareId,
version : res.body.version
};
this.setState((prevState)=>({
brew : { ...prevState.brew,
googleId : this.savedBrew.googleId ? this.savedBrew.googleId : null,
editId : this.savedBrew.editId,
shareId : this.savedBrew.shareId,
version : this.savedBrew.version
},
isPending : false,
isSaving : false,
unsavedTime : new Date()
lastSavedBrew.current = {
...brew,
...updatedFields
};
setCurrentBrew((prevBrew)=>({
...prevBrew,
...updatedFields
}));
},
renderGoogleDriveIcon : function(){
return <Nav.item className='googleDriveStorage' onClick={this.handleGoogleClick}>
<img src={googleDriveIcon} className={this.state.saveGoogle ? '' : 'inactive'} alt='Google Drive icon'/>
history.replaceState(null, null, `/edit/${res.body.editId}`);
};
{this.state.confirmGoogleTransfer &&
<div className='errorContainer' onClick={this.closeAlerts}>
{ this.state.saveGoogle
? `Would you like to transfer this brew from your Google Drive storage back to the Homebrewery?`
: `Would you like to transfer this brew from the Homebrewery to your personal Google Drive storage?`
}
const renderGoogleDriveIcon = ()=>(
<Nav.item className='googleDriveStorage' onClick={handleGoogleClick}>
<img src={googleDriveIcon} className={saveGoogle ? '' : 'inactive'} alt='Google Drive icon' />
{confirmGoogleTransfer && (
<div className='errorContainer' onClick={closeAlerts}>
{saveGoogle
? 'Would you like to transfer this brew from your Google Drive storage back to the Homebrewery?'
: 'Would you like to transfer this brew from the Homebrewery to your personal Google Drive storage?'}
<br />
<div className='confirm' onClick={this.toggleGoogleStorage}>
Yes
</div>
<div className='deny'>
No
</div>
<div className='confirm' onClick={toggleGoogleStorage}> Yes </div>
<div className='deny'> No </div>
</div>
}
)}
{this.state.alertLoginToTransfer &&
<div className='errorContainer' onClick={this.closeAlerts}>
You must be signed in to a Google account to transfer
between the homebrewery and Google Drive!
<a target='_blank' rel='noopener noreferrer'
href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}>
<div className='confirm'>
Sign In
</div>
{alertLoginToTransfer && (
<div className='errorContainer' onClick={closeAlerts}>
You must be signed in to a Google account to transfer between the homebrewery and Google Drive!
<a target='_blank' rel='noopener noreferrer' href={`https://www.naturalcrit.com/login?redirect=${window.location.href}`}>
<div className='confirm'> Sign In </div>
</a>
<div className='deny'>
Not Now
</div>
<div className='deny'> Not Now </div>
</div>
}
)}
{this.state.alertTrashedGoogleBrew &&
<div className='errorContainer' onClick={this.closeAlerts}>
This brew is currently in your Trash folder on Google Drive!<br />If you want to keep it, make sure to move it before it is deleted permanently!<br />
<div className='confirm'>
OK
</div>
{alertTrashedGoogleBrew && (
<div className='errorContainer' onClick={closeAlerts}>
This brew is currently in your Trash folder on Google Drive!<br />
If you want to keep it, make sure to move it before it is deleted permanently!<br />
<div className='confirm'> OK </div>
</div>
}
</Nav.item>;
},
)}
</Nav.item>
);
renderSaveButton : function(){
if(this.state.autoSaveWarning && this.hasChanges()){
this.setAutosaveWarning();
const elapsedTime = Math.round((new Date() - this.state.unsavedTime) / 1000 / 60);
const text = elapsedTime == 0 ? 'Autosave is OFF.' : `Autosave is OFF, and you haven't saved for ${elapsedTime} minutes.`;
const renderSaveButton = ()=>{
// #1 - Currently saving, show SAVING
if(isSaving)
return <Nav.item className='save' icon='fas fa-spinner fa-spin'>saving...</Nav.item>;
// #2 - Unsaved changes exist, autosave is OFF and warning timer has expired, show AUTOSAVE WARNING
if(unsavedChanges && warnUnsavedChanges) {
resetWarnUnsavedTimer();
const elapsedTime = Math.round((new Date() - lastSavedTime) / 1000 / 60);
const text = elapsedTime === 0
? 'Autosave is OFF.'
: `Autosave is OFF, and you haven't saved for ${elapsedTime} minutes.`;
return <Nav.item className='save error' icon='fas fa-exclamation-circle'>
Reminder...
<div className='errorContainer'>
{text}
</div>
Reminder...
<div className='errorContainer'>{text}</div>
</Nav.item>;
}
if(this.state.isSaving){
return <Nav.item className='save' icon='fas fa-spinner fa-spin'>saving...</Nav.item>;
}
if(this.state.isPending && this.hasChanges()){
return <Nav.item className='save' onClick={this.save} color='blue' icon='fas fa-save'>Save Now</Nav.item>;
}
if(!this.state.isPending && !this.state.isSaving && this.state.autoSave){
return <Nav.item className='save saved'>auto-saved.</Nav.item>;
}
if(!this.state.isPending && !this.state.isSaving){
return <Nav.item className='save saved'>saved.</Nav.item>;
}
},
// #3 - Unsaved changes exist, click to save, show SAVE NOW
if(unsavedChanges)
return <Nav.item className='save' onClick={()=>trySave(true)} color='blue' icon='fas fa-save'>save now</Nav.item>;
handleAutoSave : function(){
if(this.warningTimer) clearTimeout(this.warningTimer);
this.setState((prevState)=>({
autoSave : !prevState.autoSave,
autoSaveWarning : prevState.autoSave
}), ()=>{
localStorage.setItem('AUTOSAVE_ON', JSON.stringify(this.state.autoSave));
});
},
// #4 - No unsaved changes, autosave is ON, show AUTO-SAVED
if(autoSaveEnabled)
return <Nav.item className='save saved'>auto-saved</Nav.item>;
setAutosaveWarning : function(){
setTimeout(()=>this.setState({ autoSaveWarning: false }), 4000); // 4 seconds to display
this.warningTimer = setTimeout(()=>{this.setState({ autoSaveWarning: true });}, 900000); // 15 minutes between warnings
this.warningTimer;
},
// #5 - No unsaved changes, and has never been saved, hide the button
if(neverSaved)
return <Nav.item className='save neverSaved'>save now</Nav.item>;
errorReported : function(error) {
this.setState({
error
});
},
// DEFAULT - No unsaved changes, show SAVED
return <Nav.item className='save saved'>saved</Nav.item>;
};
renderAutoSaveButton : function(){
return <Nav.item onClick={this.handleAutoSave}>
Autosave <i className={this.state.autoSave ? 'fas fa-power-off active' : 'fas fa-power-off'}></i>
</Nav.item>;
},
const toggleAutoSave = ()=>{
clearTimeout(warnUnsavedTimeout.current);
clearTimeout(saveTimeout.current);
localStorage.setItem(AUTOSAVE_KEY, JSON.stringify(!autoSaveEnabled));
setAutoSaveEnabled(!autoSaveEnabled);
setWarnUnsavedChanges(autoSaveEnabled);
};
processShareId : function() {
return this.state.brew.googleId && !this.state.brew.stubbed ?
this.state.brew.googleId + this.state.brew.shareId :
this.state.brew.shareId;
},
const renderAutoSaveButton = ()=>(
<Nav.item onClick={toggleAutoSave}>
Autosave <i className={autoSaveEnabled ? 'fas fa-power-off active' : 'fas fa-power-off'}></i>
</Nav.item>
);
getRedditLink : function(){
const shareLink = this.processShareId();
const systems = this.props.brew.systems.length > 0 ? ` [${this.props.brew.systems.join(' - ')}]` : '';
const title = `${this.props.brew.title} ${systems}`;
const text = `Hey guys! I've been working on this homebrew. I'd love your feedback. Check it out.
**[Homebrewery Link](${global.config.publicUrl}/share/${shareLink})**`;
return `https://www.reddit.com/r/UnearthedArcana/submit?title=${encodeURIComponent(title.toWellFormed())}&text=${encodeURIComponent(text)}`;
},
renderNavbar : function(){
const shareLink = this.processShareId();
const clearError = ()=>{
setError(null);
setIsSaving(false);
};
const renderNavbar = ()=>{
return <Navbar>
<Nav.section>
<Nav.item className='brewTitle'>{this.state.brew.title}</Nav.item>
<Nav.item className='brewTitle'>{currentBrew.title}</Nav.item>
</Nav.section>
<Nav.section>
{this.renderGoogleDriveIcon()}
{this.state.error ?
<ErrorNavItem error={this.state.error} parent={this}></ErrorNavItem> :
<Nav.dropdown className='save-menu'>
{this.renderSaveButton()}
{this.renderAutoSaveButton()}
</Nav.dropdown>
}
<NewBrew />
<HelpNavItem/>
<Nav.dropdown>
<Nav.item color='teal' icon='fas fa-share-alt'>
share
</Nav.item>
<Nav.item color='blue' href={`/share/${shareLink}`}>
view
</Nav.item>
<Nav.item color='blue' onClick={()=>{navigator.clipboard.writeText(`${global.config.publicUrl}/share/${shareLink}`);}}>
copy url
</Nav.item>
<Nav.item color='blue' href={this.getRedditLink()} newTab={true} rel='noopener noreferrer'>
post to reddit
</Nav.item>
</Nav.dropdown>
{renderGoogleDriveIcon()}
{error
? <ErrorNavItem error={error} clearError={clearError} />
: <Nav.dropdown className='save-menu'>
{renderSaveButton()}
{renderAutoSaveButton()}
</Nav.dropdown>}
<NewBrewItem />
<PrintNavItem />
<HelpNavItem />
<VaultNavItem />
<RecentNavItem brew={this.state.brew} storageKey='edit' />
<Account />
<ShareNavItem brew={currentBrew} />
<RecentNavItem brew={currentBrew} storageKey='edit' />
<AccountNavItem/>
</Nav.section>
</Navbar>;
},
};
render : function(){
return <div className='editPage sitePage'>
return (
<div className='editPage sitePage'>
<Meta name='robots' content='noindex, nofollow' />
{this.renderNavbar()}
{this.props.brew.lock && <LockNotification shareId={this.props.brew.shareId} message={this.props.brew.lock.editMessage} />}
{renderNavbar()}
{currentBrew.lock && <LockNotification shareId={currentBrew.shareId} message={currentBrew.lock.editMessage} reviewRequested={currentBrew.lock.reviewRequested}/>}
<div className='content'>
<SplitPane onDragFinish={this.handleSplitMove}>
<SplitPane onDragFinish={handleSplitMove}>
<Editor
ref={this.editor}
brew={this.state.brew}
onTextChange={this.handleTextChange}
onStyleChange={this.handleStyleChange}
onMetaChange={this.handleMetaChange}
reportError={this.errorReported}
renderer={this.state.brew.renderer}
userThemes={this.props.userThemes}
snippetBundle={this.state.themeBundle.snippets}
updateBrew={this.updateBrew}
onCursorPageChange={this.handleEditorCursorPageChange}
onViewPageChange={this.handleEditorViewPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
ref={editorRef}
brew={currentBrew}
onBrewChange={handleBrewChange}
reportError={setError}
renderer={currentBrew.renderer}
userThemes={props.userThemes}
themeBundle={themeBundle}
updateBrew={updateBrew}
onCursorPageChange={setCurrentEditorCursorPageNum}
onViewPageChange={setCurrentEditorViewPageNum}
currentEditorViewPageNum={currentEditorViewPageNum}
currentEditorCursorPageNum={currentEditorCursorPageNum}
currentBrewRendererPageNum={currentBrewRendererPageNum}
/>
<BrewRenderer
text={this.state.brew.text}
style={this.state.brew.style}
renderer={this.state.brew.renderer}
theme={this.state.brew.theme}
themeBundle={this.state.themeBundle}
errors={this.state.htmlErrors}
lang={this.state.brew.lang}
onPageChange={this.handleBrewRendererPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
text={currentBrew.text}
style={currentBrew.style}
renderer={currentBrew.renderer}
theme={currentBrew.theme}
themeBundle={themeBundle}
errors={HTMLErrors}
lang={currentBrew.lang}
onPageChange={setCurrentBrewRendererPageNum}
currentEditorViewPageNum={currentEditorViewPageNum}
currentEditorCursorPageNum={currentEditorCursorPageNum}
currentBrewRendererPageNum={currentBrewRendererPageNum}
allowPrint={true}
/>
</SplitPane>
</div>
</div>;
}
});
</div>
);
};
module.exports = EditPage;

View File

@@ -1,29 +1,25 @@
@keyframes glideDown {
0% {transform : translate(-50% + 3px, 0px);
opacity : 0;}
100% {transform : translate(-50% + 3px, 10px);
opacity : 1;}
0% {
opacity : 0;transform : translate(-50% + 3px, 0px);}
100% {
opacity : 1;transform : translate(-50% + 3px, 10px);}
}
.editPage{
.navItem.save{
.editPage {
.navItem.save {
position : relative;
width : 106px;
text-align : center;
position : relative;
&.saved{
&.saved {
color : #666666;
cursor : initial;
color : #666;
}
}
.googleDriveStorage {
position : relative;
}
.googleDriveStorage img{
height : 18px;
.googleDriveStorage { position : relative; }
.googleDriveStorage img {
height : 18px;
padding : 0px;
margin : -5px;
&.inactive {
filter: grayscale(1);
}
&.inactive { filter : grayscale(1); }
}
}

View File

@@ -1,17 +1,30 @@
require('./lockNotification.less');
const React = require('react');
import './lockNotification.less';
import * as React from 'react';
import request from '../../../utils/request-middleware.js';
import Dialog from '../../../../components/dialog.jsx';
function LockNotification(props) {
props = {
shareId : 0,
disableLock : ()=>{},
message : '',
shareId : 0,
disableLock : ()=>{},
lock : {},
message : 'Unable to retrieve Lock Message',
reviewRequested : false,
...props
};
const removeLock = ()=>{
alert(`Not yet implemented - ID ${props.shareId}`);
const [reviewState, setReviewState] = React.useState(props.reviewRequested);
const removeLock = async ()=>{
await request.put(`/api/lock/review/request/${props.shareId}`)
.then(()=>{
setReviewState(true);
});
};
const renderReviewButton = function(){
if(reviewState){ return <button className='inactive'>REVIEW REQUESTED</button>; };
return <button onClick={removeLock}>REQUEST LOCK REMOVAL</button>;
};
return <Dialog className='lockNotification' blocking closeText='CONTINUE TO EDITOR' >
@@ -19,11 +32,11 @@ function LockNotification(props) {
<p>This brew been locked by the Administrators. It will not be accessible by any method other than the Editor until the lock is removed.</p>
<hr />
<h3>LOCK REASON</h3>
<p>{props.message || 'Unable to retrieve Lock Message'}</p>
<p>{props.message}</p>
<hr />
<p>Once you have resolved this issue, click REQUEST LOCK REMOVAL to notify the Administrators for review.</p>
<p>Click CONTINUE TO EDITOR to temporarily hide this notification; it will reappear the next time the page is reloaded.</p>
<button onClick={removeLock}>REQUEST LOCK REMOVAL</button>
{renderReviewButton()}
</Dialog>;
};

View File

@@ -11,10 +11,12 @@
&::backdrop { background-color : #000000AA; }
button {
padding : 2px 15px;
margin : 10px;
color : white;
background-color : #333333;
&.inactive,
&:hover { background-color : #777777; }
}

View File

@@ -3,8 +3,8 @@ const dedent = require('dedent-tabs').default;
const loginUrl = 'https://www.naturalcrit.com/login';
// Prevent parsing text (e.g. document titles) as markdown
const escape = (text) => {
return text.split('').map(char => `&#${char.charCodeAt(0)};`).join('');
const escape = (text = '')=>{
return text.split('').map((char)=>`&#${char.charCodeAt(0)};`).join('');
};
//001-050 : Brew errors
@@ -168,6 +168,34 @@ const errorIndex = (props)=>{
**Brew ID:** ${props.brew.brewId}`,
// Theme Not Valid
'10' : dedent`
## The selected theme is not tagged as a theme.
The brew selected as a theme exists, but has not been marked for use as a theme with the \`theme:meta\` tag.
If the selected brew is your document, you may designate it as a theme by adding the \`theme:meta\` tag.`,
// ID validation error
'11' : dedent`
## No Homebrewery document could be found.
The server could not locate the Homebrewery document. The Brew ID failed the validation check.
:
**Brew ID:** ${props.brew.brewId}`,
// Google ID validation error
'12' : dedent`
## No Google document could be found.
The server could not locate the Google document. The Google ID failed the validation check.
:
**Brew ID:** ${props.brew.brewId}`,
//account page when account is not defined
'50' : dedent`
## You are not signed in
@@ -186,13 +214,47 @@ const errorIndex = (props)=>{
**Brew ID:** ${props.brew.brewId}
**Brew Title:** ${escape(props.brew.brewTitle)}`,
**Brew Title:** ${escape(props.brew.brewTitle)}
**Brew Authors:** ${props.brew.authors?.map((author)=>{return `[${author}](/user/${author})`;}).join(', ') || 'Unable to list authors'}`,
// ####### Admin page error #######
'52' : dedent`
## Access Denied
You need to provide correct administrator credentials to access this page.`,
// ####### Lock Errors
'60' : dedent`Lock Error: General`,
'61' : dedent`Lock Get Error: Unable to get lock count`,
'62' : dedent`Lock Set Error: Cannot lock`,
'63' : dedent`Lock Set Error: Brew not found`,
'64' : dedent`Lock Set Error: Already locked`,
'65' : dedent`Lock Remove Error: Cannot unlock`,
'66' : dedent`Lock Remove Error: Brew not found`,
'67' : dedent`Lock Remove Error: Not locked`,
'68' : dedent`Lock Get Review Error: Cannot get review requests`,
'69' : dedent`Lock Set Review Error: Cannot set review request`,
'70' : dedent`Lock Set Review Error: Brew not found`,
'71' : dedent`Lock Set Review Error: Review already requested`,
'72' : dedent`Lock Remove Review Error: Cannot clear review request`,
'73' : dedent`Lock Remove Review Error: Brew not found`,
// ####### Other Errors
'90' : dedent` An unexpected error occurred while looking for these brews.
Try again in a few minutes.`,

View File

@@ -1,141 +1,224 @@
require('./homePage.less');
const React = require('react');
const createClass = require('create-react-class');
const cx = require('classnames');
import request from '../../utils/request-middleware.js';
const { Meta } = require('vitreum/headtags');
/* eslint-disable max-lines */
import './homePage.less';
const Nav = require('naturalcrit/nav/nav.jsx');
const Navbar = require('../../navbar/navbar.jsx');
const NewBrewItem = require('../../navbar/newbrew.navitem.jsx');
const HelpNavItem = require('../../navbar/help.navitem.jsx');
const VaultNavItem = require('../../navbar/vault.navitem.jsx');
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
const AccountNavItem = require('../../navbar/account.navitem.jsx');
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
const { fetchThemeBundle } = require('../../../../shared/helpers.js');
// Common imports
import React, { useState, useEffect, useRef } from 'react';
import request from '../../utils/request-middleware.js';
import Markdown from 'naturalcrit/markdown.js';
import _ from 'lodash';
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
const Editor = require('../../editor/editor.jsx');
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
import { DEFAULT_BREW } from '../../../../server/brewDefaults.js';
import { printCurrentBrew, fetchThemeBundle, splitTextStyleAndMetadata } from '../../../../shared/helpers.js';
const { DEFAULT_BREW } = require('../../../../server/brewDefaults.js');
import SplitPane from 'client/components/splitPane/splitPane.jsx';
import Editor from '../../editor/editor.jsx';
import BrewRenderer from '../../brewRenderer/brewRenderer.jsx';
const HomePage = createClass({
displayName : 'HomePage',
getDefaultProps : function() {
return {
brew : DEFAULT_BREW,
ver : '0.0.0'
import Nav from 'naturalcrit/nav/nav.jsx';
import Navbar from '../../navbar/navbar.jsx';
import NewBrewItem from '../../navbar/newbrew.navitem.jsx';
import AccountNavItem from '../../navbar/account.navitem.jsx';
import ErrorNavItem from '../../navbar/error-navitem.jsx';
import HelpNavItem from '../../navbar/help.navitem.jsx';
import VaultNavItem from '../../navbar/vault.navitem.jsx';
import PrintNavItem from '../../navbar/print.navitem.jsx';
import { both as RecentNavItem } from '../../navbar/recent.navitem.jsx';
// Page specific imports
import { Meta } from 'vitreum/headtags';
const BREWKEY = 'homebrewery-new';
const STYLEKEY = 'homebrewery-new-style';
const SNIPKEY = 'homebrewery-new-snippets';
const METAKEY = 'homebrewery-new-meta';
const useLocalStorage = false;
const neverSaved = true;
const HomePage =(props)=>{
props = {
brew : DEFAULT_BREW,
ver : '0.0.0',
...props
};
const [currentBrew , setCurrentBrew] = useState(props.brew);
const [error , setError] = useState(undefined);
const [HTMLErrors , setHTMLErrors] = useState(Markdown.validate(props.brew.text));
const [currentEditorViewPageNum , setCurrentEditorViewPageNum] = useState(1);
const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1);
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
const [themeBundle , setThemeBundle] = useState({});
const [unsavedChanges , setUnsavedChanges] = useState(false);
const [isSaving , setIsSaving] = useState(false);
const [autoSaveEnabled , setAutoSaveEnable] = useState(false);
const editorRef = useRef(null);
const lastSavedBrew = useRef(_.cloneDeep(props.brew));
useEffect(()=>{
fetchThemeBundle(setError, setThemeBundle, currentBrew.renderer, currentBrew.theme);
const handleControlKeys = (e)=>{
if(!(e.ctrlKey || e.metaKey)) return;
if(e.keyCode === 83) trySaveRef.current(true);
if(e.keyCode === 80) printCurrentBrew();
if([83, 80].includes(e.keyCode)) {
e.stopPropagation();
e.preventDefault();
}
};
},
getInitialState : function() {
return {
brew : this.props.brew,
welcomeText : this.props.brew.text,
error : undefined,
currentEditorViewPageNum : 1,
currentEditorCursorPageNum : 1,
currentBrewRendererPageNum : 1,
themeBundle : {}
document.addEventListener('keydown', handleControlKeys);
return () => {
document.removeEventListener('keydown', handleControlKeys);
};
},
}, []);
editor : React.createRef(null),
componentDidMount : function() {
fetchThemeBundle(this, this.props.brew.renderer, this.props.brew.theme);
},
handleSave : function(){
const save = ()=>{
request.post('/api')
.send(this.state.brew)
.send(currentBrew)
.end((err, res)=>{
if(err) {
this.setState({ error: err });
setError(err);
return;
}
const brew = res.body;
window.location = `/edit/${brew.editId}`;
const saved = res.body;
window.location = `/edit/${saved.editId}`;
});
},
handleSplitMove : function(){
this.editor.current.update();
},
};
handleEditorViewPageChange : function(pageNumber){
this.setState({ currentEditorViewPageNum: pageNumber });
},
useEffect(()=>{
const hasChange = !_.isEqual(currentBrew, lastSavedBrew.current);
setUnsavedChanges(hasChange);
handleEditorCursorPageChange : function(pageNumber){
this.setState({ currentEditorCursorPageNum: pageNumber });
},
if(autoSaveEnabled) trySave(false, hasChange);
}, [currentBrew]);
handleBrewRendererPageChange : function(pageNumber){
this.setState({ currentBrewRendererPageNum: pageNumber });
},
const handleSplitMove = ()=>{
editorRef.current.update();
};
handleTextChange : function(text){
this.setState((prevState)=>({
brew : { ...prevState.brew, text: text },
}));
},
renderNavbar : function(){
return <Navbar ver={this.props.ver}>
const handleBrewChange = (field) => (value, subfield) => { //'text', 'style', 'snippets', 'metadata'
if (subfield == 'renderer' || subfield == 'theme')
fetchThemeBundle(setError, setThemeBundle, value.renderer, value.theme);
//If there are HTML errors, run the validator on every change to give quick feedback
if(HTMLErrors.length && (field == 'text' || field == 'snippets'))
setHTMLErrors(Markdown.validate(value));
if(field == 'metadata') setCurrentBrew(prev => ({ ...prev, ...value }));
else setCurrentBrew(prev => ({ ...prev, [field]: value }));
if(useLocalStorage) {
if(field == 'text') localStorage.setItem(BREWKEY, value);
if(field == 'style') localStorage.setItem(STYLEKEY, value);
if(field == 'snippets') localStorage.setItem(SNIPKEY, value);
if(field == 'metadata') localStorage.setItem(METAKEY, JSON.stringify({
renderer : value.renderer,
theme : value.theme,
lang : value.lang
}));
}
};
const renderSaveButton = ()=>{
// #1 - Currently saving, show SAVING
if(isSaving)
return <Nav.item className='save' icon='fas fa-spinner fa-spin'>saving...</Nav.item>;
// #2 - Unsaved changes exist, autosave is OFF and warning timer has expired, show AUTOSAVE WARNING
// if(unsavedChanges && warnUnsavedChanges) {
// resetWarnUnsavedTimer();
// const elapsedTime = Math.round((new Date() - lastSavedTime) / 1000 / 60);
// const text = elapsedTime === 0
// ? 'Autosave is OFF.'
// : `Autosave is OFF, and you haven't saved for ${elapsedTime} minutes.`;
// return <Nav.item className='save error' icon='fas fa-exclamation-circle'>
// Reminder...
// <div className='errorContainer'>{text}</div>
// </Nav.item>;
// }
// #3 - Unsaved changes exist, click to save, show SAVE NOW
if(unsavedChanges)
return <Nav.item className='save' onClick={save} color='blue' icon='fas fa-save'>save now</Nav.item>;
// #4 - No unsaved changes, autosave is ON, show AUTO-SAVED
if(autoSaveEnabled)
return <Nav.item className='save saved'>auto-saved</Nav.item>;
// #5 - No unsaved changes, and has never been saved, hide the button
if(neverSaved)
return <Nav.item className='save neverSaved'>save now</Nav.item>;
// DEFAULT - No unsaved changes, show SAVED
return <Nav.item className='save saved'>saved</Nav.item>;
};
const clearError = ()=>{
setError(null);
setIsSaving(false);
};
const renderNavbar = ()=>{
return <Navbar ver={props.ver}>
<Nav.section>
{this.state.error ?
<ErrorNavItem error={this.state.error} parent={this}></ErrorNavItem> :
null
}
{error
? <ErrorNavItem error={error} clearError={clearError} />
: renderSaveButton()}
<NewBrewItem />
<PrintNavItem />
<HelpNavItem />
<VaultNavItem />
<RecentNavItem />
<AccountNavItem />
</Nav.section>
</Navbar>;
},
};
render : function(){
return <div className='homePage sitePage'>
return (
<div className='homePage sitePage'>
<Meta name='google-site-verification' content='NwnAQSSJZzAT7N-p5MY6ydQ7Njm67dtbu73ZSyE5Fy4' />
{this.renderNavbar()}
<div className="content">
<SplitPane onDragFinish={this.handleSplitMove}>
<Editor
ref={this.editor}
brew={this.state.brew}
onTextChange={this.handleTextChange}
renderer={this.state.brew.renderer}
showEditButtons={false}
snippetBundle={this.state.themeBundle.snippets}
onCursorPageChange={this.handleEditorCursorPageChange}
onViewPageChange={this.handleEditorViewPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
/>
<BrewRenderer
text={this.state.brew.text}
style={this.state.brew.style}
renderer={this.state.brew.renderer}
onPageChange={this.handleBrewRendererPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
themeBundle={this.state.themeBundle}
/>
</SplitPane>
{renderNavbar()}
<div className='content'>
<SplitPane onDragFinish={handleSplitMove}>
<Editor
ref={editorRef}
brew={currentBrew}
onBrewChange={handleBrewChange}
renderer={currentBrew.renderer}
showEditButtons={false}
themeBundle={themeBundle}
onCursorPageChange={setCurrentEditorCursorPageNum}
onViewPageChange={setCurrentEditorViewPageNum}
currentEditorViewPageNum={currentEditorViewPageNum}
currentEditorCursorPageNum={currentEditorCursorPageNum}
currentBrewRendererPageNum={currentBrewRendererPageNum}
/>
<BrewRenderer
text={currentBrew.text}
style={currentBrew.style}
renderer={currentBrew.renderer}
onPageChange={setCurrentBrewRendererPageNum}
currentEditorViewPageNum={currentEditorViewPageNum}
currentEditorCursorPageNum={currentEditorCursorPageNum}
currentBrewRendererPageNum={currentBrewRendererPageNum}
themeBundle={themeBundle}
/>
</SplitPane>
</div>
<div className={cx('floatingSaveButton', { show: this.state.welcomeText != this.state.brew.text })} onClick={this.handleSave}>
<div className={`floatingSaveButton${unsavedChanges ? ' show' : ''}`} onClick={save}>
Save current <i className='fas fa-save' />
</div>
<a href='/new' className='floatingNewButton'>
Create your own <i className='fas fa-magic' />
</a>
</div>;
}
});
</div>
)
};
module.exports = HomePage;

View File

@@ -1,50 +1,46 @@
.homePage{
.homePage {
position : relative;
a.floatingNewButton{
a.floatingNewButton {
.animate(background-color);
position : absolute;
display : block;
right : 70px;
bottom : 50px;
z-index : 100;
z-index : 5001;
display : block;
padding : 1em;
background-color : @orange;
font-size : 1.5em;
color : white;
text-decoration : none;
background-color : @orange;
box-shadow : 3px 3px 15px black;
&:hover{
background-color : darken(@orange, 20%);
}
&:hover { background-color : darken(@orange, 20%); }
}
.floatingSaveButton{
.floatingSaveButton {
.animateAll();
position : absolute;
display : block;
right : 200px;
bottom : 70px;
z-index : 100;
z-index : 5000;
display : block;
padding : 0.8em;
cursor : pointer;
background-color : @blue;
font-size : 0.8em;
color : white;
text-decoration : none;
cursor : pointer;
background-color : @blue;
box-shadow : 3px 3px 15px black;
&:hover{
background-color : darken(@blue, 20%);
}
&.show{
right : 350px;
}
&:hover { background-color : darken(@blue, 20%); }
&.show { right : 350px; }
}
.navItem.save{
background-color: @orange;
&:hover{
background-color: @green;
.navItem.save {
.fadeInRight();
.transition(opacity);
background-color : @orange;
&:hover { background-color : @green; }
&.neverSaved {
.fadeOutRight();
opacity: 0;
}
}
}

View File

@@ -1,263 +1,270 @@
/*eslint max-lines: ["warn", {"max": 300, "skipBlankLines": true, "skipComments": true}]*/
require('./newPage.less');
const React = require('react');
const createClass = require('create-react-class');
import request from '../../utils/request-middleware.js';
/* eslint-disable max-lines */
import './newPage.less';
import Markdown from 'naturalcrit/markdown.js';
// Common imports
import React, { useState, useEffect, useRef } from 'react';
import request from '../../utils/request-middleware.js';
import Markdown from 'naturalcrit/markdown.js';
import _ from 'lodash';
const Nav = require('naturalcrit/nav/nav.jsx');
const PrintNavItem = require('../../navbar/print.navitem.jsx');
const Navbar = require('../../navbar/navbar.jsx');
const AccountNavItem = require('../../navbar/account.navitem.jsx');
const ErrorNavItem = require('../../navbar/error-navitem.jsx');
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
const HelpNavItem = require('../../navbar/help.navitem.jsx');
import { DEFAULT_BREW } from '../../../../server/brewDefaults.js';
import { printCurrentBrew, fetchThemeBundle, splitTextStyleAndMetadata } from '../../../../shared/helpers.js';
const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
const Editor = require('../../editor/editor.jsx');
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
import SplitPane from 'client/components/splitPane/splitPane.jsx';
import Editor from '../../editor/editor.jsx';
import BrewRenderer from '../../brewRenderer/brewRenderer.jsx';
const { DEFAULT_BREW } = require('../../../../server/brewDefaults.js');
const { printCurrentBrew, fetchThemeBundle } = require('../../../../shared/helpers.js');
import Nav from 'naturalcrit/nav/nav.jsx';
import Navbar from '../../navbar/navbar.jsx';
import NewBrewItem from '../../navbar/newbrew.navitem.jsx';
import AccountNavItem from '../../navbar/account.navitem.jsx';
import ErrorNavItem from '../../navbar/error-navitem.jsx';
import HelpNavItem from '../../navbar/help.navitem.jsx';
import VaultNavItem from '../../navbar/vault.navitem.jsx';
import PrintNavItem from '../../navbar/print.navitem.jsx';
import { both as RecentNavItem } from '../../navbar/recent.navitem.jsx';
const BREWKEY = 'homebrewery-new';
const STYLEKEY = 'homebrewery-new-style';
const METAKEY = 'homebrewery-new-meta';
let SAVEKEY;
// Page specific imports
import { Meta } from 'vitreum/headtags';
const BREWKEY = 'HB_newPage_content';
const STYLEKEY = 'HB_newPage_style';
const METAKEY = 'HB_newPage_metadata';
const SNIPKEY = 'HB_newPage_snippets';
const SAVEKEYPREFIX = 'HB_editor_defaultSave_';
const NewPage = createClass({
displayName : 'NewPage',
getDefaultProps : function() {
return {
brew : DEFAULT_BREW
const useLocalStorage = true;
const neverSaved = true;
const NewPage = (props) => {
props = {
brew: DEFAULT_BREW,
...props
};
const [currentBrew , setCurrentBrew ] = useState(props.brew);
const [isSaving , setIsSaving ] = useState(false);
const [saveGoogle , setSaveGoogle ] = useState(global.account?.googleId ? true : false);
const [error , setError ] = useState(null);
const [HTMLErrors , setHTMLErrors ] = useState(Markdown.validate(props.brew.text));
const [currentEditorViewPageNum , setCurrentEditorViewPageNum ] = useState(1);
const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1);
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
const [themeBundle , setThemeBundle ] = useState({});
const [unsavedChanges , setUnsavedChanges ] = useState(false);
const [autoSaveEnabled , setAutoSaveEnabled ] = useState(false);
const editorRef = useRef(null);
const lastSavedBrew = useRef(_.cloneDeep(props.brew));
useEffect(() => {
loadBrew();
fetchThemeBundle(setError, setThemeBundle, currentBrew.renderer, currentBrew.theme);
const handleControlKeys = (e)=>{
if(!(e.ctrlKey || e.metaKey)) return;
if(e.keyCode === 83) trySaveRef.current(true);
if(e.keyCode === 80) printCurrentBrew();
if([83, 80].includes(e.keyCode)) {
e.stopPropagation();
e.preventDefault();
}
};
},
getInitialState : function() {
const brew = this.props.brew;
document.addEventListener('keydown', handleControlKeys);
return {
brew : brew,
isSaving : false,
saveGoogle : (global.account && global.account.googleId ? true : false),
error : null,
htmlErrors : Markdown.validate(brew.text),
currentEditorViewPageNum : 1,
currentEditorCursorPageNum : 1,
currentBrewRendererPageNum : 1,
themeBundle : {}
return () => {
document.removeEventListener('keydown', handleControlKeys);
};
},
}, []);
editor : React.createRef(null),
componentDidMount : function() {
document.addEventListener('keydown', this.handleControlKeys);
const brew = this.state.brew;
if(!this.props.brew.shareId && typeof window !== 'undefined') { //Load from localStorage if in client browser
const loadBrew = ()=>{
const brew = { ...currentBrew };
if(!brew.shareId && typeof window !== 'undefined') { //Load from localStorage if in client browser
const brewStorage = localStorage.getItem(BREWKEY);
const styleStorage = localStorage.getItem(STYLEKEY);
const metaStorage = JSON.parse(localStorage.getItem(METAKEY));
const metaStorage = JSON.parse(localStorage.getItem(METAKEY));
brew.text = brewStorage ?? brew.text;
brew.style = styleStorage ?? brew.style;
// brew.title = metaStorage?.title || this.state.brew.title;
// brew.description = metaStorage?.description || this.state.brew.description;
brew.text = brewStorage ?? brew.text;
brew.style = styleStorage ?? brew.style;
brew.renderer = metaStorage?.renderer ?? brew.renderer;
brew.theme = metaStorage?.theme ?? brew.theme;
brew.lang = metaStorage?.lang ?? brew.lang;
}
SAVEKEY = `HOMEBREWERY-DEFAULT-SAVE-LOCATION-${global.account?.username || ''}`;
const SAVEKEY = `${SAVEKEYPREFIX}${global.account?.username}`;
const saveStorage = localStorage.getItem(SAVEKEY) || 'HOMEBREWERY';
this.setState({
brew : brew,
saveGoogle : (saveStorage == 'GOOGLE-DRIVE' && this.state.saveGoogle)
});
fetchThemeBundle(this, this.props.brew.renderer, this.props.brew.theme);
setCurrentBrew(brew);
lastSavedBrew.current = brew;
setSaveGoogle(saveStorage == 'GOOGLE-DRIVE' && saveGoogle);
localStorage.setItem(BREWKEY, brew.text);
if(brew.style)
localStorage.setItem(STYLEKEY, brew.style);
localStorage.setItem(METAKEY, JSON.stringify({ 'renderer': brew.renderer, 'theme': brew.theme, 'lang': brew.lang }));
if(window.location.pathname != '/new') {
localStorage.setItem(METAKEY, JSON.stringify({ renderer: brew.renderer, theme: brew.theme, lang: brew.lang }));
if(window.location.pathname !== '/new')
window.history.replaceState({}, window.location.title, '/new/');
}
},
componentWillUnmount : function() {
document.removeEventListener('keydown', this.handleControlKeys);
},
};
handleControlKeys : function(e){
if(!(e.ctrlKey || e.metaKey)) return;
const S_KEY = 83;
const P_KEY = 80;
if(e.keyCode == S_KEY) this.save();
if(e.keyCode == P_KEY) printCurrentBrew();
if(e.keyCode == P_KEY || e.keyCode == S_KEY){
e.stopPropagation();
e.preventDefault();
}
},
useEffect(()=>{
const hasChange = !_.isEqual(currentBrew, lastSavedBrew.current);
setUnsavedChanges(hasChange);
handleSplitMove : function(){
this.editor.current.update();
},
if(autoSaveEnabled) trySave(false, hasChange);
}, [currentBrew]);
handleEditorViewPageChange : function(pageNumber){
this.setState({ currentEditorViewPageNum: pageNumber });
},
const handleSplitMove = ()=>{
editorRef.current.update();
};
handleEditorCursorPageChange : function(pageNumber){
this.setState({ currentEditorCursorPageNum: pageNumber });
},
const handleBrewChange = (field) => (value, subfield) => { //'text', 'style', 'snippets', 'metadata'
if (subfield == 'renderer' || subfield == 'theme')
fetchThemeBundle(setError, setThemeBundle, value.renderer, value.theme);
handleBrewRendererPageChange : function(pageNumber){
this.setState({ currentBrewRendererPageNum: pageNumber });
},
//If there are HTML errors, run the validator on every change to give quick feedback
if(HTMLErrors.length && (field == 'text' || field == 'snippets'))
setHTMLErrors(Markdown.validate(value));
handleTextChange : function(text){
//If there are errors, run the validator on every change to give quick feedback
let htmlErrors = this.state.htmlErrors;
if(htmlErrors.length) htmlErrors = Markdown.validate(text);
if(field == 'metadata') setCurrentBrew(prev => ({ ...prev, ...value }));
else setCurrentBrew(prev => ({ ...prev, [field]: value }));
this.setState((prevState)=>({
brew : { ...prevState.brew, text: text },
htmlErrors : htmlErrors,
}));
localStorage.setItem(BREWKEY, text);
},
handleStyleChange : function(style){
this.setState((prevState)=>({
brew : { ...prevState.brew, style: style },
}));
localStorage.setItem(STYLEKEY, style);
},
handleMetaChange : function(metadata, field=undefined){
if(field == 'theme' || field == 'renderer') // Fetch theme bundle only if theme or renderer was changed
fetchThemeBundle(this, metadata.renderer, metadata.theme);
this.setState((prevState)=>({
brew : { ...prevState.brew, ...metadata },
}), ()=>{
localStorage.setItem(METAKEY, JSON.stringify({
// 'title' : this.state.brew.title,
// 'description' : this.state.brew.description,
'renderer' : this.state.brew.renderer,
'theme' : this.state.brew.theme,
'lang' : this.state.brew.lang
if(useLocalStorage) {
if(field == 'text') localStorage.setItem(BREWKEY, value);
if(field == 'style') localStorage.setItem(STYLEKEY, value);
if(field == 'snippets') localStorage.setItem(SNIPKEY, value);
if(field == 'metadata') localStorage.setItem(METAKEY, JSON.stringify({
renderer : value.renderer,
theme : value.theme,
lang : value.lang
}));
});
;
},
save : async function(){
this.setState({
isSaving : true
});
let brew = this.state.brew;
// Split out CSS to Style if CSS codefence exists
if(brew.text.startsWith('```css') && brew.text.indexOf('```\n\n') > 0) {
const index = brew.text.indexOf('```\n\n');
brew.style = `${brew.style ? `${brew.style}\n` : ''}${brew.text.slice(7, index - 1)}`;
brew.text = brew.text.slice(index + 5);
}
};
const save = async () => {
setIsSaving(true);
let updatedBrew = { ...currentBrew };
splitTextStyleAndMetadata(updatedBrew);
const pageRegex = updatedBrew.renderer === 'legacy' ? /\\page/g : /^\\page$/gm;
updatedBrew.pageCount = (updatedBrew.text.match(pageRegex) || []).length + 1;
brew.pageCount=((brew.renderer=='legacy' ? brew.text.match(/\\page/g) : brew.text.match(/^\\page$/gm)) || []).length + 1;
const res = await request
.post(`/api${this.state.saveGoogle ? '?saveToGoogle=true' : ''}`)
.send(brew)
.catch((err)=>{
this.setState({ isSaving: false, error: err });
.post(`/api${saveGoogle ? '?saveToGoogle=true' : ''}`)
.send(updatedBrew)
.catch((err) => {
setIsSaving(false);
setError(err);
});
if(!res) return;
brew = res.body;
setIsSaving(false)
if (!res) return;
const savedBrew = res.body;
localStorage.removeItem(BREWKEY);
localStorage.removeItem(STYLEKEY);
localStorage.removeItem(METAKEY);
window.location = `/edit/${brew.editId}`;
},
window.location = `/edit/${savedBrew.editId}`;
};
renderSaveButton : function(){
if(this.state.isSaving){
return <Nav.item icon='fas fa-spinner fa-spin' className='save'>
save...
</Nav.item>;
} else {
return <Nav.item icon='fas fa-save' className='save' onClick={this.save}>
save
</Nav.item>;
}
},
const renderSaveButton = ()=>{
// #1 - Currently saving, show SAVING
if(isSaving)
return <Nav.item className='save' icon='fas fa-spinner fa-spin'>saving...</Nav.item>;
renderNavbar : function(){
return <Navbar>
// #2 - Unsaved changes exist, autosave is OFF and warning timer has expired, show AUTOSAVE WARNING
// if(unsavedChanges && warnUnsavedChanges) {
// resetWarnUnsavedTimer();
// const elapsedTime = Math.round((new Date() - lastSavedTime) / 1000 / 60);
// const text = elapsedTime === 0
// ? 'Autosave is OFF.'
// : `Autosave is OFF, and you haven't saved for ${elapsedTime} minutes.`;
// return <Nav.item className='save error' icon='fas fa-exclamation-circle'>
// Reminder...
// <div className='errorContainer'>{text}</div>
// </Nav.item>;
// }
// #3 - Unsaved changes exist, click to save, show SAVE NOW
if(unsavedChanges)
return <Nav.item className='save' onClick={save} color='blue' icon='fas fa-save'>save now</Nav.item>;
// #4 - No unsaved changes, autosave is ON, show AUTO-SAVED
if(autoSaveEnabled)
return <Nav.item className='save saved'>auto-saved</Nav.item>;
// #5 - No unsaved changes, and has never been saved, hide the button
if(neverSaved)
return <Nav.item className='save neverSaved'>save now</Nav.item>;
// DEFAULT - No unsaved changes, show SAVED
return <Nav.item className='save saved'>saved</Nav.item>;
};
const clearError = ()=>{
setError(null);
setIsSaving(false);
};
const renderNavbar = () => (
<Navbar>
<Nav.section>
<Nav.item className='brewTitle'>{this.state.brew.title}</Nav.item>
<Nav.item className='brewTitle'>{currentBrew.title}</Nav.item>
</Nav.section>
<Nav.section>
{this.state.error ?
<ErrorNavItem error={this.state.error} parent={this}></ErrorNavItem> :
this.renderSaveButton()
}
{error
? <ErrorNavItem error={error} clearError={clearError} />
: renderSaveButton()}
<NewBrewItem />
<PrintNavItem />
<HelpNavItem />
<VaultNavItem />
<RecentNavItem />
<AccountNavItem />
</Nav.section>
</Navbar>;
},
</Navbar>
);
render : function(){
return <div className='newPage sitePage'>
{this.renderNavbar()}
<div className="content">
<SplitPane onDragFinish={this.handleSplitMove}>
<Editor
ref={this.editor}
brew={this.state.brew}
onTextChange={this.handleTextChange}
onStyleChange={this.handleStyleChange}
onMetaChange={this.handleMetaChange}
renderer={this.state.brew.renderer}
userThemes={this.props.userThemes}
snippetBundle={this.state.themeBundle.snippets}
onCursorPageChange={this.handleEditorCursorPageChange}
onViewPageChange={this.handleEditorViewPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
/>
<BrewRenderer
text={this.state.brew.text}
style={this.state.brew.style}
renderer={this.state.brew.renderer}
theme={this.state.brew.theme}
themeBundle={this.state.themeBundle}
errors={this.state.htmlErrors}
lang={this.state.brew.lang}
onPageChange={this.handleBrewRendererPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
allowPrint={true}
/>
</SplitPane>
return (
<div className='newPage sitePage'>
{renderNavbar()}
<div className='content'>
<SplitPane onDragFinish={handleSplitMove}>
<Editor
ref={editorRef}
brew={currentBrew}
onBrewChange={handleBrewChange}
renderer={currentBrew.renderer}
userThemes={props.userThemes}
themeBundle={themeBundle}
onCursorPageChange={setCurrentEditorCursorPageNum}
onViewPageChange={setCurrentEditorViewPageNum}
currentEditorViewPageNum={currentEditorViewPageNum}
currentEditorCursorPageNum={currentEditorCursorPageNum}
currentBrewRendererPageNum={currentBrewRendererPageNum}
/>
<BrewRenderer
text={currentBrew.text}
style={currentBrew.style}
renderer={currentBrew.renderer}
theme={currentBrew.theme}
themeBundle={themeBundle}
errors={HTMLErrors}
lang={currentBrew.lang}
onPageChange={setCurrentBrewRendererPageNum}
currentEditorViewPageNum={currentEditorViewPageNum}
currentEditorCursorPageNum={currentEditorCursorPageNum}
currentBrewRendererPageNum={currentBrewRendererPageNum}
allowPrint={true}
/>
</SplitPane>
</div>
</div>;
}
});
</div>
);
};
module.exports = NewPage;

View File

@@ -1,8 +1,12 @@
.newPage{
.navItem.save{
background-color: @orange;
&:hover{
background-color: @green;
.newPage {
.navItem.save {
.fadeInRight();
.transition(opacity);
background-color : @orange;
&:hover { background-color : @green; }
&.neverSaved {
.fadeOutRight();
opacity: 0;
}
}
}

View File

@@ -17,15 +17,11 @@ const { printCurrentBrew, fetchThemeBundle } = require('../../../../shared/helpe
const SharePage = (props)=>{
const { brew = DEFAULT_BREW_LOAD, disableMeta = false } = props;
const [state, setState] = useState({
themeBundle : {},
currentBrewRendererPageNum : 1,
});
const [themeBundle, setThemeBundle] = useState({});
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
const handleBrewRendererPageChange = useCallback((pageNumber)=>{
setState((prevState)=>({
currentBrewRendererPageNum : pageNumber,
...prevState }));
setCurrentBrewRendererPageNum(pageNumber);
}, []);
const handleControlKeys = (e)=>{
@@ -40,11 +36,7 @@ const SharePage = (props)=>{
useEffect(()=>{
document.addEventListener('keydown', handleControlKeys);
fetchThemeBundle(
{ setState },
brew.renderer,
brew.theme
);
fetchThemeBundle(undefined, setThemeBundle, brew.renderer, brew.theme);
return ()=>{
document.removeEventListener('keydown', handleControlKeys);
@@ -114,9 +106,9 @@ const SharePage = (props)=>{
lang={brew.lang}
renderer={brew.renderer}
theme={brew.theme}
themeBundle={state.themeBundle}
themeBundle={themeBundle}
onPageChange={handleBrewRendererPageChange}
currentBrewRendererPageNum={state.currentBrewRendererPageNum}
currentBrewRendererPageNum={currentBrewRendererPageNum}
allowPrint={true}
/>
</div>

View File

@@ -1,9 +1,7 @@
.sharePage{
.sharePage {
nav .navSection.titleSection {
flex-grow: 1;
justify-content: center;
}
.content{
overflow-y : hidden;
flex-grow : 1;
justify-content : center;
}
.content { overflow-y : hidden; }
}

View File

@@ -39,10 +39,14 @@ const UserPage = (props)=>{
}] : [])
];
const clearError = ()=>{
setError(null);
};
const navItems = (
<Navbar>
<Nav.section>
{error && (<ErrorNavItem error={error} parent={null}></ErrorNavItem>)}
{error && (<ErrorNavItem error={error} clearError={clearError}></ErrorNavItem>)}
<NewBrew />
<HelpNavItem />
<VaultNavitem />

View File

@@ -12,7 +12,7 @@ const Account = require('../../navbar/account.navitem.jsx');
const NewBrew = require('../../navbar/newbrew.navitem.jsx');
const HelpNavItem = require('../../navbar/help.navitem.jsx');
const BrewItem = require('../basePages/listPage/brewItem/brewItem.jsx');
const SplitPane = require('../../../../shared/naturalcrit/splitPane/splitPane.jsx');
const SplitPane = require('client/components/splitPane/splitPane.jsx');
const ErrorIndex = require('../errorPage/errors/errorIndex.js');
import request from '../../utils/request-middleware.js';
@@ -99,14 +99,14 @@ const VaultPage = (props)=>{
setSearching(true);
setError(null);
const title = titleRef.current.value || '';
const author = authorRef.current.value || '';
const count = countRef.current.value || 10;
const v3 = v3Ref.current.checked != false;
const legacy = legacyRef.current.checked != false;
const title = titleRef.current.value || '';
const author = authorRef.current.value || '';
const count = countRef.current.value || 10;
const v3 = v3Ref.current.checked != false;
const legacy = legacyRef.current.checked != false;
const sortOption = sort || 'title';
const dirOption = dir || 'asc';
const pageProp = page || 1;
const dirOption = dir || 'asc';
const pageProp = page || 1;
setSort(sortOption);
setdir(dirOption);
@@ -247,7 +247,7 @@ const VaultPage = (props)=>{
</li>
<li>
Some common words like "a", "after", "through", "itself", "here", etc.,
are ignored in searches. The full list can be found &nbsp;
are ignored in searches. The full list can be found&nbsp;
<a href='https://github.com/mongodb/mongo/blob/0e3b3ca8480ddddf5d0105d11a94bd4698335312/src/mongo/db/fts/stop_words_english.txt'>
here
</a>
@@ -286,9 +286,9 @@ const VaultPage = (props)=>{
};
const renderPaginationControls = ()=>{
if(!totalBrews) return null;
if(!totalBrews || totalBrews < 10) return null;
const countInt = parseInt(props.query.count || 20);
const countInt = parseInt(brewCollection.length || 20);
const totalPages = Math.ceil(totalBrews / countInt);
let startPage, endPage;
@@ -355,7 +355,7 @@ const VaultPage = (props)=>{
};
const renderFoundBrews = ()=>{
if(searching) {
if(searching && !brewCollection) {
return (
<div className='foundBrews searching'>
<h3 className='searchAnim'>Searching</h3>
@@ -395,6 +395,7 @@ const VaultPage = (props)=>{
{`Brews found: `}
<span>{totalBrews}</span>
</span>
{brewCollection.length > 10 && renderPaginationControls()}
{brewCollection.map((brew, index)=>{
return (
<BrewItem
@@ -415,14 +416,14 @@ const VaultPage = (props)=>{
<link href='/themes/V3/Blank/style.css' rel='stylesheet' />
<link href='/themes/V3/5ePHB/style.css' rel='stylesheet' />
{renderNavItems()}
<div className="content">
<SplitPane showDividerButtons={false}>
<div className='form dataGroup'>{renderForm()}</div>
<div className='resultsContainer dataGroup'>
{renderSortBar()}
{renderFoundBrews()}
</div>
</SplitPane>
<div className='content'>
<SplitPane showDividerButtons={false}>
<div className='form dataGroup'>{renderForm()}</div>
<div className='resultsContainer dataGroup'>
{renderSortBar()}
{renderFoundBrews()}
</div>
</SplitPane>
</div>
</div>
);

View File

@@ -1,14 +1,16 @@
.vaultPage {
height : 100%;
overflow-y : hidden;
background-color : #2C3E50;
*:not(input) { user-select : none; }
.content .dataGroup {
.form {
background:white;
}
:where(.content .dataGroup) {
width : 100%;
height : 100%;
background : white;
&.form .brewLookup {
position : relative;
@@ -169,9 +171,9 @@
width : 100%;
height : 100%;
max-height : 100%;
padding : 50px 50px 70px 50px;
padding : 70px 50px;
overflow-y : scroll;
background-color : #2C3E50;
container-type : inline-size;
h3 { font-size : 25px; }
@@ -236,6 +238,7 @@
margin-right : 40px;
color : black;
isolation : isolate;
transition : width 0.5s;
&::after {
position : absolute;
@@ -269,8 +272,8 @@
.links { z-index : 2; }
hr {
margin : 0px;
visibility : hidden;
margin : 0px;
}
.thumbnail { z-index : -1; }
@@ -278,30 +281,37 @@
.paginationControls {
position : absolute;
top : 35px;
left : 50%;
display : grid;
grid-template-areas : 'previousPage currentPage nextPage';
grid-template-columns : 50px 1fr 50px;
gap : 20px;
place-items : center;
width : auto;
font-size : 15px;
translate : -50%;
&:last-child { top : unset; }
.pages {
display : flex;
grid-area : currentPage;
gap : 1em;
justify-content : space-evenly;
width : 100%;
height : 100%;
padding : 5px 8px;
text-align : center;
.pageNumber {
margin-inline : 1vw;
place-content : center;
width : fit-content;
min-width : 2em;
font-family : 'Open Sans';
font-weight : 900;
color : white;
text-underline-position : under;
text-wrap : nowrap;
text-underline-position : under;
cursor : pointer;
&.currentPage {
@@ -329,7 +339,6 @@
}
}
}
}
@keyframes trailingDots {
@@ -344,8 +353,7 @@
100% { content : ' ...'; }
}
// media query for when the page is smaller than 1079 px in width
@media screen and (max-width : 1079px) {
@container (width < 670px) {
.vaultPage {
.dataGroup.form .brewLookup { padding : 1px 20px 20px 10px; }

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 94.65 94.6"
version="1.1"
id="svg11"
sodipodi:docname="thumbnail.svg"
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<sodipodi:namedview
id="namedview13"
pagecolor="#ffffff"
bordercolor="#111111"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="8.4989431"
inkscape:cx="38.887188"
inkscape:cy="47.417661"
inkscape:window-width="1920"
inkscape:window-height="1043"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg11" />
<defs
id="defs4">
<style
id="style2">.cls-1{fill:#ed1f24;}</style>
</defs>
<title
id="title6">NaturalCritLogo</title>
<g
id="Layer_2"
data-name="Layer 2"
style="fill:#000000;stroke:#000000">
<g
id="base"
style="fill:#000000;stroke:#000000">
<path
id="D20"
class="cls-1"
d="M63.45.09s-45.91,12.4-46,12.45a.71.71,0,0,0-.15.08l-.15.1-.12.11a1.07,1.07,0,0,0-.14.16l-.09.11-.12.23,0,.06L.2,54.9a1.59,1.59,0,0,0,.11,1.69L29.36,94h0l0,0,.08.08.08.08.09.09.08.06.13.07a0,0,0,0,0,0,0,1.59,1.59,0,0,0,.27.12l.13.05.06,0a1.55,1.55,0,0,0,.37,0,1.63,1.63,0,0,0,.31,0l45.67-8.3.16,0,.11,0,.12,0,.06,0s0,0,0,0l.06,0a1.65,1.65,0,0,0,.36-.28l0-.06a1.6,1.6,0,0,0,.26-.38s0,0,0,0v0h0a.14.14,0,0,1,0-.06L94.52,43.74a1.4,1.4,0,0,0,.11-.4.41.41,0,0,0,0-.11,1.13,1.13,0,0,0,0-.26.66.66,0,0,0,0-.14,2,2,0,0,0-.06-.26l0-.11a2.68,2.68,0,0,0-.18-.33v0L65.29.6C64.77-.31,63.45.09,63.45.09ZM74.9,81.7l-28.81-18L78.5,38.49ZM44.1,61l-11-40.17L77,35.39ZM82,37.78l8.92,5.95L79,73.48Zm4.46-1.1-4.6-3.06L75.69,21.36Zm-9.26-4.8-42.07-14,28.05-14ZM30.56,16.34l-6.49-2.16L47.85,7.7Zm-11.35-.21L27.88,19,7.64,45Zm10.73,5.76L40.78,61.64,4.64,54.42Zm10.82,43.2L30.26,89.6,5.75,58.09Zm3.16,1.24L71.74,83.72l-38.26,7Z"
style="fill:#000000;fill-opacity:1;stroke:#000000" />
</g>
</g>
<metadata
id="metadata1">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:title>NaturalCritLogo</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,74 @@
import requestMiddleware from './request-middleware';
jest.mock('superagent');
import request from 'superagent';
describe('request-middleware', ()=>{
let version;
let setFn;
let testFn;
beforeEach(()=>{
jest.resetAllMocks();
version = global.version;
global.version = '999';
setFn = jest.fn();
testFn = jest.fn(()=>{ return { set: setFn }; });
});
afterEach(()=>{
global.version = version;
});
it('should add header to get', ()=>{
// Ensure tests functions have been reset
expect(testFn).not.toHaveBeenCalled();
expect(setFn).not.toHaveBeenCalled();
request.get = testFn;
requestMiddleware.get('path');
expect(testFn).toHaveBeenCalledWith('path');
expect(setFn).toHaveBeenCalledWith('Homebrewery-Version', '999');
});
it('should add header to put', ()=>{
expect(testFn).not.toHaveBeenCalled();
expect(setFn).not.toHaveBeenCalled();
request.put = testFn;
requestMiddleware.put('path');
expect(testFn).toHaveBeenCalledWith('path');
expect(setFn).toHaveBeenCalledWith('Homebrewery-Version', '999');
});
it('should add header to post', ()=>{
expect(testFn).not.toHaveBeenCalled();
expect(setFn).not.toHaveBeenCalled();
request.post = testFn;
requestMiddleware.post('path');
expect(testFn).toHaveBeenCalledWith('path');
expect(setFn).toHaveBeenCalledWith('Homebrewery-Version', '999');
});
it('should add header to delete', ()=>{
expect(testFn).not.toHaveBeenCalled();
expect(setFn).not.toHaveBeenCalled();
request.delete = testFn;
requestMiddleware.delete('path');
expect(testFn).toHaveBeenCalledWith('path');
expect(setFn).toHaveBeenCalledWith('Homebrewery-Version', '999');
});
});

View File

@@ -0,0 +1,35 @@
const getLocalStorageMap = function(){
const localStorageMap = {
'AUTOSAVE_ON' : 'HB_editor_autoSaveOn',
'HOMEBREWERY-EDITOR-THEME' : 'HB_editor_theme',
'liveScroll' : 'HB_editor_liveScroll',
'naturalcrit-pane-split' : 'HB_editor_splitWidth',
'HOMEBREWERY-LISTPAGE-SORTDIR' : 'HB_listPage_sortDir',
'HOMEBREWERY-LISTPAGE-SORTTYPE' : 'HB_listPage_sortType',
'HOMEBREWERY-LISTPAGE-VISIBILITY-published' : 'HB_listPage_visibility_group_published',
'HOMEBREWERY-LISTPAGE-VISIBILITY-unpublished' : 'HB_listPage_visibility_group_unpublished',
'hbAdminTab' : 'HB_adminPage_currentTab',
'homebrewery-new' : 'HB_newPage_content',
'homebrewery-new-meta' : 'HB_newPage_metadata',
'homebrewery-new-style' : 'HB_newPage_style',
'homebrewery-recently-edited' : 'HB_nav_recentlyEdited',
'homebrewery-recently-viewed' : 'HB_nav_recentlyViewed',
'hb_toolbarState' : 'HB_renderer_toolbarState',
'hb_toolbarVisibility' : 'HB_renderer_toolbarVisibility'
};
if(global?.account?.username){
const username = global.account.username;
localStorageMap[`HOMEBREWERY-DEFAULT-SAVE-LOCATION-${username}`] = `HB_editor_defaultSave_${username}`;
}
return localStorageMap;
};
export default getLocalStorageMap;

View File

@@ -0,0 +1,30 @@
import getLocalStorageMap from './localStorageKeyMap.js';
describe('getLocalStorageMap', ()=>{
it('no username', ()=>{
const account = global.account;
delete global.account;
const map = getLocalStorageMap();
global.account = account;
expect(map).toBeInstanceOf(Object);
expect(Object.entries(map)).toHaveLength(16);
});
it('no username', ()=>{
const account = global.account;
global.account = { username: 'test' };
const map = getLocalStorageMap();
global.account = account;
expect(map).toBeInstanceOf(Object);
expect(Object.entries(map)).toHaveLength(17);
expect(map).toHaveProperty('HOMEBREWERY-DEFAULT-SAVE-LOCATION-test', 'HB_editor_defaultSave_test');
});
});

View File

@@ -0,0 +1,25 @@
import getLocalStorageMap from './localStorageKeyMap.js';
const updateLocalStorage = function(){
// Return if no window and thus no local storage
if(typeof window === 'undefined') return;
// Return if the local storage key map has no content
const localStorageKeyMap = getLocalStorageMap();
if(Object.keys(localStorageKeyMap).length == 0) return;
const storage = window.localStorage;
Object.keys(localStorageKeyMap).forEach((key)=>{
if(storage[key]){
if(!storage[localStorageKeyMap[key]]){
const data = storage.getItem(key);
storage.setItem(localStorageKeyMap[key], data);
};
storage.removeItem(key);
}
});
};
export { updateLocalStorage };

View File

@@ -42,6 +42,7 @@ function parseBrewForStorage(brew, slot = 0) {
title : brew.title,
text : brew.text,
style : brew.style,
snippets : brew.snippets,
version : brew.version,
shareId : brew.shareId,
savedAt : brew?.savedAt || new Date(),

View File

@@ -1,84 +1,34 @@
.fac {
display : inline-block;
background-color : currentColor;
mask-size : contain;
mask-repeat : no-repeat;
mask-position : center;
width : 1em;
aspect-ratio : 1;
background-color : currentColor;
mask-repeat : no-repeat;
mask-position : center;
mask-size : contain;
}
.position-top-left {
mask-image: url('../icons/position-top-left.svg');
}
.position-top-right {
mask-image: url('../icons/position-top-right.svg');
}
.position-bottom-left {
mask-image: url('../icons/position-bottom-left.svg');
}
.position-bottom-right {
mask-image: url('../icons/position-bottom-right.svg');
}
.position-top {
mask-image: url('../icons/position-top.svg');
}
.position-right {
mask-image: url('../icons/position-right.svg');
}
.position-bottom {
mask-image: url('../icons/position-bottom.svg');
}
.position-left {
mask-image: url('../icons/position-left.svg');
}
.mask-edge {
mask-image: url('../icons/mask-edge.svg');
}
.mask-corner {
mask-image: url('../icons/mask-corner.svg');
}
.mask-center {
mask-image: url('../icons/mask-center.svg');
}
.book-front-cover {
mask-image: url('../icons/book-front-cover.svg');
}
.book-back-cover {
mask-image: url('../icons/book-back-cover.svg');
}
.book-inside-cover {
mask-image: url('../icons/book-inside-cover.svg');
}
.book-part-cover {
mask-image: url('../icons/book-part-cover.svg');
}
.image-wrap-left {
mask-image: url('../icons/image-wrap-left.svg');
}
.image-wrap-right {
mask-image: url('../icons/image-wrap-right.svg');
}
.davek {
mask-image: url('../icons/Davek.svg');
}
.rellanic {
mask-image: url('../icons/Rellanic.svg');
}
.iokharic {
mask-image: url('../icons/Iokharic.svg');
}
.zoom-to-fit {
mask-image: url('../icons/zoom-to-fit.svg');
}
.fit-width {
mask-image: url('../icons/fit-width.svg');
}
.single-spread {
mask-image: url('../icons/single-spread.svg');
}
.facing-spread {
mask-image: url('../icons/facing-spread.svg');
}
.flow-spread {
mask-image: url('../icons/flow-spread.svg');
}
.position-top-left { mask-image : url('../icons/position-top-left.svg'); }
.position-top-right { mask-image : url('../icons/position-top-right.svg'); }
.position-bottom-left { mask-image : url('../icons/position-bottom-left.svg'); }
.position-bottom-right { mask-image : url('../icons/position-bottom-right.svg'); }
.position-top { mask-image : url('../icons/position-top.svg'); }
.position-right { mask-image : url('../icons/position-right.svg'); }
.position-bottom { mask-image : url('../icons/position-bottom.svg'); }
.position-left { mask-image : url('../icons/position-left.svg'); }
.mask-edge { mask-image : url('../icons/mask-edge.svg'); }
.mask-corner { mask-image : url('../icons/mask-corner.svg'); }
.mask-center { mask-image : url('../icons/mask-center.svg'); }
.book-front-cover { mask-image : url('../icons/book-front-cover.svg'); }
.book-back-cover { mask-image : url('../icons/book-back-cover.svg'); }
.book-inside-cover { mask-image : url('../icons/book-inside-cover.svg'); }
.book-part-cover { mask-image : url('../icons/book-part-cover.svg'); }
.image-wrap-left { mask-image : url('../icons/image-wrap-left.svg'); }
.image-wrap-right { mask-image : url('../icons/image-wrap-right.svg'); }
.davek { mask-image : url('../icons/Davek.svg'); }
.rellanic { mask-image : url('../icons/Rellanic.svg'); }
.iokharic { mask-image : url('../icons/Iokharic.svg'); }
.zoom-to-fit { mask-image : url('../icons/zoom-to-fit.svg'); }
.fit-width { mask-image : url('../icons/fit-width.svg'); }
.single-spread { mask-image : url('../icons/single-spread.svg'); }
.facing-spread { mask-image : url('../icons/facing-spread.svg'); }
.flow-spread { mask-image : url('../icons/flow-spread.svg'); }

View File

@@ -14,7 +14,6 @@ const template = async function(name, title='', props = {}){
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, height=device-height, interactive-widget=resizes-visual" />
<link href="//use.fontawesome.com/releases/v6.5.1/css/all.css" rel="stylesheet" type="text/css" />
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
<link href=${`/${name}/bundle.css`} type="text/css" rel='stylesheet' />
<link rel="icon" href="/assets/favicon.ico" type="image/x-icon" />

View File

@@ -1,4 +1,5 @@
{
"development": true,
"host" : "homebrewery.local.naturalcrit.com:8000",
"naturalcrit_url" : "local.naturalcrit.com:8010",
"secret" : "secret",

View File

@@ -0,0 +1,3 @@
# About
Run `deploy.bash` to download, extract, and deploy the font awesome files into place for building. Should only be needed when Font Awesome version changes and we want the new version.

View File

@@ -0,0 +1,42 @@
#!/bin/bash
# Deploys the Font Awesome files for HB self-hosting to settle various issues.
THEURL=https://use.fontawesome.com/releases/v6.7.2/fontawesome-free-6.7.2-web.zip
THEFILE=fontawesome-free-6.7.2-web.zip
if [ ! "$(which wget)" ]; then
echo "Please manually download ${THEURL}"
exit -1
fi
wget ${THEURL}
if [ $? -ne 0 ]; then
echo "Error downloading ${THEURL}"
exit -2
fi
if [ ! "$(which unzip)" ]; then
echo "Please unzip the file with your tool of choice."
exit -3
fi
unzip fontawesome-free-6.7.2-web.zip
if [ $? -ne 0 ]; then
echo "Error extracting ${THEFILE}"
fi
echo "Copying fonts"
cp -rv fontawesome-free-*-web/webfonts/*.woff2 ../themes/fonts/iconFonts
echo "Copying and updating css"
echo "fontawesome-free.less"
sed 's/..\/webfonts/\/fonts\/iconFonts/g' fontawesome-free-*-web/css/fontawesome.css > ../themes/fonts/iconFonts/fontawesome-free.less
echo "fontawesome-solid.less"
sed 's/..\/webfonts/\/fonts\/iconFonts/g' fontawesome-free-*-web/css/solid.css > ../themes/fonts/iconFonts/fontawesome-solid.less
echo "fontawesome-brands.less"
sed 's/..\/webfonts/\/fonts\/iconFonts/g' fontawesome-free-*-web/css/brands.css > ../themes/fonts/iconFonts/fontawesome-brands.less
echo "fontawesome-regular.less"
sed 's/..\/webfonts/\/fonts\/iconFonts/g' fontawesome-free-*-web/css/regular.css > ../themes/fonts/iconFonts/fontawesome-regular.less

6273
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,10 @@
{
"name": "homebrewery",
"description": "Create authentic looking D&D homebrews using only markdown",
"version": "3.16.1",
"version": "3.19.3",
"type": "module",
"engines": {
"npm": "^10.2.x",
"npm": "^10.8.x",
"node": "^20.18.x"
},
"repository": {
@@ -36,10 +36,8 @@
"test:mustache-syntax:inline": "jest \".*(mustache-syntax).*\" -t '^Inline:.*' --verbose --noStackTrace",
"test:mustache-syntax:block": "jest \".*(mustache-syntax).*\" -t '^Block:.*' --verbose --noStackTrace",
"test:mustache-syntax:injection": "jest \".*(mustache-syntax).*\" -t '^Injection:.*' --verbose --noStackTrace",
"test:definition-lists": "jest tests/markdown/definition-lists.test.js --verbose --noStackTrace",
"test:hard-breaks": "jest tests/markdown/hard-breaks.test.js --verbose --noStackTrace",
"test:non-breaking-spaces": "jest tests/markdown/non-breaking-spaces.test.js --verbose --noStackTrace",
"test:paragraph-justification": "jest tests/markdown/paragraph-justification.test.js --verbose --noStackTrace",
"test:emojis": "jest tests/markdown/emojis.test.js --verbose --noStackTrace",
"test:route": "jest tests/routes/static-pages.test.js --verbose",
"test:safehtml": "jest tests/html/safeHTML.test.js --verbose",
@@ -74,7 +72,7 @@
"lines": 50
},
"server/homebrew.api.js": {
"statements": 70,
"statements": 60,
"branches": 50,
"functions": 65,
"lines": 70
@@ -85,62 +83,72 @@
]
},
"dependencies": {
"@babel/core": "^7.26.7",
"@babel/plugin-transform-runtime": "^7.25.9",
"@babel/preset-env": "^7.26.7",
"@babel/preset-react": "^7.26.3",
"@googleapis/drive": "^8.14.0",
"body-parser": "^1.20.2",
"@babel/core": "^7.27.1",
"@babel/plugin-transform-runtime": "^7.28.0",
"@babel/preset-env": "^7.28.0",
"@babel/preset-react": "^7.27.1",
"@babel/runtime": "^7.27.6",
"@dmsnell/diff-match-patch": "^1.1.0",
"@googleapis/drive": "^13.0.1",
"@sanity/diff-match-patch": "^3.2.0",
"body-parser": "^2.2.0",
"classnames": "^2.5.1",
"codemirror": "^5.65.6",
"cookie-parser": "^1.4.7",
"core-js": "^3.40.0",
"core-js": "^3.44.0",
"cors": "^2.8.5",
"create-react-class": "^15.7.0",
"dedent-tabs": "^0.10.3",
"dompurify": "^3.2.3",
"expr-eval": "^2.0.2",
"express": "^4.21.2",
"express": "^5.1.0",
"express-async-handler": "^1.2.0",
"express-static-gzip": "2.2.0",
"express-static-gzip": "3.0.0",
"fflate": "^0.8.2",
"fs-extra": "11.3.0",
"idb-keyval": "^6.2.1",
"hash-wasm": "^4.12.0",
"idb-keyval": "^6.2.2",
"js-yaml": "^4.1.0",
"jwt-simple": "^0.5.6",
"less": "^3.13.1",
"lodash": "^4.17.21",
"marked": "11.2.0",
"marked-emoji": "^1.4.3",
"marked-extended-tables": "^1.1.0",
"marked-gfm-heading-id": "^3.2.0",
"marked-smartypants-lite": "^1.0.2",
"marked": "15.0.12",
"marked-alignment-paragraphs": "^1.0.0",
"marked-definition-lists": "^1.0.1",
"marked-emoji": "^2.0.1",
"marked-extended-tables": "^2.0.1",
"marked-gfm-heading-id": "^4.1.2",
"marked-nonbreaking-spaces": "^1.0.1",
"marked-smartypants-lite": "^1.0.3",
"marked-subsuper-text": "^1.0.3",
"markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.30.1",
"mongoose": "^8.9.5",
"nanoid": "5.0.9",
"nconf": "^0.12.1",
"mongoose": "^8.16.3",
"nanoid": "5.1.5",
"nconf": "^0.13.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-frame-component": "^4.1.3",
"react-router": "^7.1.3",
"react-router": "^7.6.3",
"romans": "^3.1.0",
"sanitize-filename": "1.6.3",
"superagent": "^10.1.1",
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
"superagent": "^10.2.1",
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git",
"written-number": "^0.11.1"
},
"devDependencies": {
"@stylistic/stylelint-plugin": "^3.1.1",
"babel-plugin-transform-import-meta": "^2.3.2",
"eslint": "^9.19.0",
"eslint-plugin-jest": "^28.11.0",
"eslint-plugin-react": "^7.37.4",
"globals": "^15.14.0",
"jest": "^29.7.0",
"@stylistic/stylelint-plugin": "^4.0.0",
"babel-plugin-transform-import-meta": "^2.3.3",
"eslint": "^9.35.0",
"eslint-plugin-jest": "^29.0.1",
"eslint-plugin-react": "^7.37.5",
"globals": "^16.3.0",
"jest": "^30.1.3",
"jest-expect-message": "^1.1.3",
"jsdom-global": "^3.0.2",
"postcss-less": "^6.0.0",
"stylelint": "^16.14.1",
"stylelint-config-recess-order": "^6.0.0",
"stylelint-config-recommended": "^15.0.0",
"supertest": "^7.0.0"
"stylelint": "^16.24.0",
"stylelint-config-recess-order": "^7.3.0",
"stylelint-config-recommended": "^17.0.0",
"supertest": "^7.1.4"
}
}

View File

@@ -10,7 +10,7 @@ import babel from '@babel/core';
import babelConfig from '../babel.config.json' with { type : 'json' };
import less from 'less';
const isDev = !!process.argv.find((arg) => arg === '--dev');
const isDev = !!process.argv.find((arg)=>arg === '--dev');
const babelify = async (code)=>(await babel.transformAsync(code, babelConfig)).code;
@@ -53,7 +53,7 @@ fs.emptyDirSync('./build');
const themes = { Legacy: {}, V3: {} };
let themeFiles = fs.readdirSync('./themes/Legacy');
for (let dir of themeFiles) {
for (const dir of themeFiles) {
const themeData = JSON.parse(fs.readFileSync(`./themes/Legacy/${dir}/settings.json`).toString());
themeData.path = dir;
themes.Legacy[dir] = (themeData);
@@ -70,7 +70,7 @@ fs.emptyDirSync('./build');
}
themeFiles = fs.readdirSync('./themes/V3');
for (let dir of themeFiles) {
for (const dir of themeFiles) {
const themeData = JSON.parse(fs.readFileSync(`./themes/V3/${dir}/settings.json`).toString());
themeData.path = dir;
themes.V3[dir] = (themeData);
@@ -113,7 +113,7 @@ fs.emptyDirSync('./build');
const stream = fs.createWriteStream(editorThemeFile, { flags: 'a' });
stream.write('[\n"default"');
for (let themeFile of editorThemeFiles) {
for (const themeFile of editorThemeFiles) {
stream.write(`,\n"${themeFile.slice(0, -4)}"`);
}
stream.write('\n]\n');

View File

@@ -27,6 +27,8 @@
"codemirror/addon/selection/active-line.js",
"codemirror/addon/hint/show-hint.js",
"moment",
"superagent"
"superagent",
"@sanity/diff-match-patch",
"fflate"
]
}

View File

@@ -1,3 +1,4 @@
/*eslint max-lines: ["warn", {"max": 500, "skipBlankLines": true, "skipComments": true}]*/
import { model as HomebrewModel } from './homebrew.model.js';
import { model as NotificationModel } from './notifications.model.js';
import express from 'express';
@@ -11,6 +12,7 @@ import { splitTextStyleAndMetadata } from '../shared/helpers.js';
const router = express.Router();
process.env.ADMIN_USER = process.env.ADMIN_USER || 'admin';
process.env.ADMIN_PASS = process.env.ADMIN_PASS || 'password3';
@@ -93,7 +95,7 @@ router.get('/admin/finduncompressed', mw.adminOnly, (req, res)=>{
/* Cleans `<script` and `</script>` from the "text" field of a brew */
router.put('/admin/clean/script/:id', asyncHandler(HomebrewAPI.getBrew('admin', false)), async (req, res)=>{
console.log(`[ADMIN] Cleaning script tags from ShareID ${req.params.id}`);
console.log(`[ADMIN: ${req.account?.username || 'Not Logged In'}] Cleaning script tags from ShareID ${req.params.id}`);
function cleanText(text){return text.replaceAll(/(<\/?s)cript/gi, '');};
@@ -114,6 +116,18 @@ router.put('/admin/clean/script/:id', asyncHandler(HomebrewAPI.getBrew('admin',
return await HomebrewAPI.updateBrew(req, res);
});
/* Get list of a user's documents */
router.get('/admin/user/list/:user', mw.adminOnly, async (req, res)=>{
const username = req.params.user;
const fields = { _id: 0, text: 0, textBin: 0 }; // Remove unnecessary fields from document lists
console.log(`[ADMIN: ${req.account?.username || 'Not Logged In'}] Get brew list for ${username}`);
const brews = await HomebrewModel.getByUser(username, true, fields);
return res.json(brews);
});
/* Compresses the "text" field of a brew to binary */
router.put('/admin/compress/:id', (req, res)=>{
HomebrewModel.findOne({ _id: req.params.id })
@@ -135,7 +149,6 @@ router.put('/admin/compress/:id', (req, res)=>{
});
});
router.get('/admin/stats', mw.adminOnly, async (req, res)=>{
try {
const totalBrewsCount = await HomebrewModel.countDocuments({});
@@ -151,6 +164,180 @@ router.get('/admin/stats', mw.adminOnly, async (req, res)=>{
}
});
// ####################### LOCKS
router.get('/api/lock/count', mw.adminOnly, asyncHandler(async (req, res)=>{
const countLocksQuery = {
lock : { $exists: true }
};
const count = await HomebrewModel.countDocuments(countLocksQuery)
.catch((error)=>{
throw { name: 'Lock Count Error', message: 'Unable to get lock count', status: 500, HBErrorCode: '61', error };
});
return res.json({ count });
}));
router.get('/api/locks', mw.adminOnly, asyncHandler(async (req, res)=>{
const countLocksPipeline = [
{
$match :
{
'lock' : { '$exists': 1 }
},
},
{
$project : {
shareId : 1,
editId : 1,
title : 1,
lock : 1
}
}
];
const lockedDocuments = await HomebrewModel.aggregate(countLocksPipeline)
.catch((error)=>{
throw { name: 'Can Not Get Locked Brews', message: 'Unable to get locked brew collection', status: 500, HBErrorCode: '68', error };
});
return res.json({
lockedDocuments
});
}));
router.post('/api/lock/:id', mw.adminOnly, asyncHandler(async (req, res)=>{
const lock = req.body;
lock.applied = new Date;
const filter = {
shareId : req.params.id
};
const brew = await HomebrewModel.findOne(filter);
if(!brew) throw { name: 'Brew Not Found', message: 'Cannot find brew to lock', shareId: req.params.id, status: 500, HBErrorCode: '63' };
if(brew.lock && !lock.overwrite) {
throw { name: 'Already Locked', message: 'Lock already exists on brew', shareId: req.params.id, title: brew.title, status: 500, HBErrorCode: '64' };
}
lock.overwrite = undefined;
brew.lock = lock;
brew.markModified('lock');
await brew.save()
.catch((error)=>{
throw { name: 'Lock Error', message: 'Unable to set lock', shareId: req.params.id, status: 500, HBErrorCode: '62', error };
});
return res.json({ name: 'LOCKED', message: `Lock applied to brew ID ${brew.shareId} - ${brew.title}`, ...lock });
}));
router.put('/api/unlock/:id', mw.adminOnly, asyncHandler(async (req, res)=>{
const filter = {
shareId : req.params.id
};
const brew = await HomebrewModel.findOne(filter);
if(!brew) throw { name: 'Brew Not Found', message: 'Cannot find brew to unlock', shareId: req.params.id, status: 500, HBErrorCode: '66' };
if(!brew.lock) throw { name: 'Not Locked', message: 'Cannot unlock as brew is not locked', shareId: req.params.id, status: 500, HBErrorCode: '67' };
brew.lock = undefined;
brew.markModified('lock');
await brew.save()
.catch((error)=>{
throw { name: 'Cannot Unlock', message: 'Unable to clear lock', shareId: req.params.id, status: 500, HBErrorCode: '65', error };
});
return res.json({ name: 'Unlocked', message: `Lock removed from brew ID ${req.params.id}` });
}));
router.get('/api/lock/reviews', mw.adminOnly, asyncHandler(async (req, res)=>{
const countReviewsPipeline = [
{
$match :
{
'lock.reviewRequested' : { '$exists': 1 }
},
},
{
$project : {
shareId : 1,
editId : 1,
title : 1,
lock : 1
}
}
];
const reviewDocuments = await HomebrewModel.aggregate(countReviewsPipeline)
.catch((error)=>{
throw { name: 'Can Not Get Reviews', message: 'Unable to get review collection', status: 500, HBErrorCode: '68', error };
});
return res.json({
reviewDocuments
});
}));
router.put('/api/lock/review/request/:id', asyncHandler(async (req, res)=>{
// === This route is NOT Admin only ===
// Any user can request a review of their document
const filter = {
shareId : req.params.id,
lock : { $exists: 1 }
};
const brew = await HomebrewModel.findOne(filter);
if(!brew) { throw { name: 'Brew Not Found', message: `Cannot find a locked brew with ID ${req.params.id}`, code: 500, HBErrorCode: '70' }; };
if(brew.lock.reviewRequested){
throw { name: 'Review Already Requested', message: `Review already requested for brew ${brew.shareId} - ${brew.title}`, code: 500, HBErrorCode: '71' };
};
brew.lock.reviewRequested = new Date();
brew.markModified('lock');
await brew.save()
.catch((error)=>{
throw { name: 'Can Not Set Review Request', message: `Unable to set request for review on brew ID ${req.params.id}`, code: 500, HBErrorCode: '69', error };
});
return res.json({ name: 'Review Requested', message: `Review requested on brew ID ${brew.shareId} - ${brew.title}` });
}));
router.put('/api/lock/review/remove/:id', mw.adminOnly, asyncHandler(async (req, res)=>{
const filter = {
shareId : req.params.id,
'lock.reviewRequested' : { $exists: 1 }
};
const brew = await HomebrewModel.findOne(filter);
if(!brew) { throw { name: 'Can Not Clear Review Request', message: `Brew ID ${req.params.id} does not have a review pending!`, HBErrorCode: '73' }; };
brew.lock.reviewRequested = undefined;
brew.markModified('lock');
await brew.save()
.catch((error)=>{
throw { name: 'Can Not Clear Review Request', message: `Unable to remove request for review on brew ID ${req.params.id}`, HBErrorCode: '72', error };
});
return res.json({ name: 'Review Request Cleared', message: `Review request removed for brew ID ${brew.shareId} - ${brew.title}` });
}));
// ####################### NOTIFICATIONS
router.get('/admin/notification/all', async (req, res, next)=>{

View File

@@ -1,6 +1,8 @@
/*eslint max-lines: ["warn", {"max": 1000, "skipBlankLines": true, "skipComments": true}]*/
import supertest from 'supertest';
import HBApp from './app.js';
import {model as NotificationModel } from './notifications.model.js';
import { model as NotificationModel } from './notifications.model.js';
import { model as HomebrewModel } from './homebrew.model.js';
// Mimic https responses to avoid being redirected all the time
@@ -16,7 +18,7 @@ describe('Tests for admin api', ()=>{
const testNotifications = ['a', 'b'];
jest.spyOn(NotificationModel, 'find')
.mockImplementationOnce(() => {
.mockImplementationOnce(()=>{
return { exec: jest.fn().mockResolvedValue(testNotifications) };
});
@@ -59,7 +61,7 @@ describe('Tests for admin api', ()=>{
expect(response.body).toEqual(savedNotification);
});
it('should handle error adding a notification without dismissKey', async () => {
it('should handle error adding a notification without dismissKey', async ()=>{
const inputNotification = {
title : 'Test Notification',
text : 'This is a test notification',
@@ -75,7 +77,7 @@ describe('Tests for admin api', ()=>{
const response = await app
.post('/admin/notification/add')
.set('Authorization', 'Basic ' + Buffer.from('admin:password3').toString('base64'))
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.send(inputNotification);
expect(response.status).toBe(500);
@@ -86,14 +88,14 @@ describe('Tests for admin api', ()=>{
const dismissKey = 'testKey';
jest.spyOn(NotificationModel, 'findOneAndDelete')
.mockImplementationOnce((key) => {
.mockImplementationOnce((key)=>{
return { exec: jest.fn().mockResolvedValue(key) };
});
const response = await app
.delete(`/admin/notification/delete/${dismissKey}`)
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`);
expect(NotificationModel.findOneAndDelete).toHaveBeenCalledWith({'dismissKey': 'testKey'});
expect(NotificationModel.findOneAndDelete).toHaveBeenCalledWith({ 'dismissKey': 'testKey' });
expect(response.status).toBe(200);
expect(response.body).toEqual({ dismissKey: 'testKey' });
});
@@ -102,16 +104,602 @@ describe('Tests for admin api', ()=>{
const dismissKey = 'testKey';
jest.spyOn(NotificationModel, 'findOneAndDelete')
.mockImplementationOnce(() => {
.mockImplementationOnce(()=>{
return { exec: jest.fn().mockResolvedValue() };
});
const response = await app
.delete(`/admin/notification/delete/${dismissKey}`)
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`);
expect(NotificationModel.findOneAndDelete).toHaveBeenCalledWith({'dismissKey': 'testKey'});
expect(NotificationModel.findOneAndDelete).toHaveBeenCalledWith({ 'dismissKey': 'testKey' });
expect(response.status).toBe(500);
expect(response.body).toEqual({ message: 'Notification not found' });
});
});
describe('Locks', ()=>{
describe('Count', ()=>{
it('Count of all locked documents', async ()=>{
const testNumber = 16777216; // 8^8, because why not
jest.spyOn(HomebrewModel, 'countDocuments')
.mockImplementationOnce(()=>{
return Promise.resolve(testNumber);
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.get('/api/lock/count');
expect(response.status).toBe(200);
expect(response.body).toEqual({ count: testNumber });
});
it('Handle error while fetching count of locked documents', async ()=>{
jest.spyOn(HomebrewModel, 'countDocuments')
.mockImplementationOnce(()=>{
return Promise.reject();
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.get('/api/lock/count');
expect(response.status).toBe(500);
expect(response.body).toEqual({
HBErrorCode : '61',
message : 'Unable to get lock count',
name : 'Lock Count Error',
originalUrl : '/api/lock/count',
status : 500,
});
});
});
describe('Lists', ()=>{
it('Get list of all locked documents', async ()=>{
const testLocks = ['a', 'b'];
jest.spyOn(HomebrewModel, 'aggregate')
.mockImplementationOnce(()=>{
return Promise.resolve(testLocks);
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.get('/api/locks');
expect(response.status).toBe(200);
expect(response.body).toEqual({ lockedDocuments: testLocks });
});
it('Handle error while fetching list of all locked documents', async ()=>{
jest.spyOn(HomebrewModel, 'aggregate')
.mockImplementationOnce(()=>{
return Promise.reject();
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.get('/api/locks');
expect(response.status).toBe(500);
expect(response.body).toEqual({
HBErrorCode : '68',
message : 'Unable to get locked brew collection',
name : 'Can Not Get Locked Brews',
originalUrl : '/api/locks',
status : 500
});
});
it('Get list of all locked documents with pending review requests', async ()=>{
const testLocks = ['a', 'b'];
jest.spyOn(HomebrewModel, 'aggregate')
.mockImplementationOnce(()=>{
return Promise.resolve(testLocks);
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.get('/api/lock/reviews');
expect(response.status).toBe(200);
expect(response.body).toEqual({ reviewDocuments: testLocks });
});
it('Handle error while fetching list of all locked documents with pending review requests', async ()=>{
jest.spyOn(HomebrewModel, 'aggregate')
.mockImplementationOnce(()=>{
return Promise.reject();
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.get('/api/lock/reviews');
expect(response.status).toBe(500);
expect(response.body).toEqual({
HBErrorCode : '68',
message : 'Unable to get review collection',
name : 'Can Not Get Reviews',
originalUrl : '/api/lock/reviews',
status : 500
});
});
});
describe('Lock', ()=>{
it('Lock a brew', async ()=>{
const testBrew = {
shareId : 'shareId',
title : 'title',
markModified : ()=>{ return true; },
save : ()=>{ return Promise.resolve(); }
};
const testLock = {
code : 999,
editMessage : 'edit',
shareMessage : 'share'
};
jest.spyOn(HomebrewModel, 'findOne')
.mockImplementationOnce(()=>{
return Promise.resolve(testBrew);
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.post(`/api/lock/${testBrew.shareId}`)
.send(testLock);
expect(response.status).toBe(200);
expect(response.body).toMatchObject({
applied : expect.any(String),
code : testLock.code,
editMessage : testLock.editMessage,
shareMessage : testLock.shareMessage,
name : 'LOCKED',
message : `Lock applied to brew ID ${testBrew.shareId} - ${testBrew.title}`
});
});
it('Overwrite lock on a locked brew', async ()=>{
const testLock = {
code : 999,
editMessage : 'newEdit',
shareMessage : 'newShare',
overwrite : true
};
const testBrew = {
shareId : 'shareId',
title : 'title',
markModified : ()=>{ return true; },
save : ()=>{ return Promise.resolve(); },
lock : {
code : 1,
editMessage : 'oldEdit',
shareMessage : 'oldShare',
}
};
jest.spyOn(HomebrewModel, 'findOne')
.mockImplementationOnce(()=>{
return Promise.resolve(testBrew);
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.post(`/api/lock/${testBrew.shareId}`)
.send(testLock);
expect(response.status).toBe(200);
expect(response.body).toMatchObject({
applied : expect.any(String),
code : testLock.code,
editMessage : testLock.editMessage,
shareMessage : testLock.shareMessage,
name : 'LOCKED',
message : `Lock applied to brew ID ${testBrew.shareId} - ${testBrew.title}`
});
});
it('Error when locking a locked brew', async ()=>{
const testLock = {
code : 999,
editMessage : 'newEdit',
shareMessage : 'newShare'
};
const testBrew = {
shareId : 'shareId',
title : 'title',
markModified : ()=>{ return true; },
save : ()=>{ return Promise.resolve(); },
lock : {
code : 1,
editMessage : 'oldEdit',
shareMessage : 'oldShare',
}
};
jest.spyOn(HomebrewModel, 'findOne')
.mockImplementationOnce(()=>{
return Promise.resolve(testBrew);
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.post(`/api/lock/${testBrew.shareId}`)
.send(testLock);
expect(response.status).toBe(500);
expect(response.body).toEqual({
HBErrorCode : '64',
message : 'Lock already exists on brew',
name : 'Already Locked',
originalUrl : `/api/lock/${testBrew.shareId}`,
shareId : testBrew.shareId,
status : 500,
title : 'title'
});
});
it('Handle save error while locking a brew', async ()=>{
const testBrew = {
shareId : 'shareId',
title : 'title',
markModified : ()=>{ return true; },
save : ()=>{ return Promise.reject(); }
};
const testLock = {
code : 999,
editMessage : 'edit',
shareMessage : 'share'
};
jest.spyOn(HomebrewModel, 'findOne')
.mockImplementationOnce(()=>{
return Promise.resolve(testBrew);
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.post(`/api/lock/${testBrew.shareId}`)
.send(testLock);
expect(response.status).toBe(500);
expect(response.body).toEqual({
HBErrorCode : '62',
message : 'Unable to set lock',
name : 'Lock Error',
originalUrl : `/api/lock/${testBrew.shareId}`,
shareId : testBrew.shareId,
status : 500
});
});
});
describe('Unlock', ()=>{
it('Unlock a brew', async ()=>{
const testLock = {
applied : 'YES',
code : 999,
editMessage : 'edit',
shareMessage : 'share'
};
const testBrew = {
shareId : 'shareId',
title : 'title',
markModified : ()=>{ return true; },
save : ()=>{ return Promise.resolve(); },
lock : testLock
};
jest.spyOn(HomebrewModel, 'findOne')
.mockImplementationOnce(()=>{
return Promise.resolve(testBrew);
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.put(`/api/unlock/${testBrew.shareId}`);
expect(response.status).toBe(200);
expect(response.body).toEqual({
name : 'Unlocked',
message : `Lock removed from brew ID ${testBrew.shareId}`
});
});
it('Error when unlocking a brew with no lock', async ()=>{
const testBrew = {
shareId : 'shareId',
title : 'title',
markModified : ()=>{ return true; },
save : ()=>{ return Promise.resolve(); },
};
jest.spyOn(HomebrewModel, 'findOne')
.mockImplementationOnce(()=>{
return Promise.resolve(testBrew);
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.put(`/api/unlock/${testBrew.shareId}`);
expect(response.status).toBe(500);
expect(response.body).toEqual({
HBErrorCode : '67',
message : 'Cannot unlock as brew is not locked',
name : 'Not Locked',
originalUrl : `/api/unlock/${testBrew.shareId}`,
shareId : testBrew.shareId,
status : 500,
});
});
it('Handle error while unlocking a brew', async ()=>{
const testLock = {
applied : 'YES',
code : 999,
editMessage : 'edit',
shareMessage : 'share'
};
const testBrew = {
shareId : 'shareId',
title : 'title',
markModified : ()=>{ return true; },
save : ()=>{ return Promise.reject(); },
lock : testLock
};
jest.spyOn(HomebrewModel, 'findOne')
.mockImplementationOnce(()=>{
return Promise.resolve(testBrew);
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.put(`/api/unlock/${testBrew.shareId}`);
expect(response.status).toBe(500);
expect(response.body).toEqual({
HBErrorCode : '65',
message : 'Unable to clear lock',
name : 'Cannot Unlock',
originalUrl : `/api/unlock/${testBrew.shareId}`,
shareId : testBrew.shareId,
status : 500
});
});
});
describe('Reviews', ()=>{
it('Add review request to a locked brew', async ()=>{
const testLock = {
applied : 'YES',
code : 999,
editMessage : 'edit',
shareMessage : 'share'
};
const testBrew = {
shareId : 'shareId',
title : 'title',
markModified : ()=>{ return true; },
save : ()=>{ return Promise.resolve(); },
lock : testLock
};
jest.spyOn(HomebrewModel, 'findOne')
.mockImplementationOnce(()=>{
return Promise.resolve(testBrew);
});
const response = await app
.put(`/api/lock/review/request/${testBrew.shareId}`);
expect(response.status).toBe(200);
expect(response.body).toEqual({
message : `Review requested on brew ID ${testBrew.shareId} - ${testBrew.title}`,
name : 'Review Requested',
});
});
it('Error when cannot find a locked brew', async ()=>{
const testBrew = {
shareId : 'shareId'
};
jest.spyOn(HomebrewModel, 'findOne')
.mockImplementationOnce(()=>{
return Promise.resolve(false);
});
const response = await app
.put(`/api/lock/review/request/${testBrew.shareId}`)
.catch((err)=>{return err;});
expect(response.status).toBe(500);
expect(response.body).toEqual({
message : `Cannot find a locked brew with ID ${testBrew.shareId}`,
name : 'Brew Not Found',
HBErrorCode : '70',
code : 500,
originalUrl : `/api/lock/review/request/${testBrew.shareId}`
});
});
it('Error when review is already requested', async ()=>{
const testLock = {
applied : 'YES',
code : 999,
editMessage : 'edit',
shareMessage : 'share',
reviewRequested : 'YES'
};
const testBrew = {
shareId : 'shareId',
title : 'title',
markModified : ()=>{ return true; },
save : ()=>{ return Promise.resolve(); },
lock : testLock
};
jest.spyOn(HomebrewModel, 'findOne')
.mockImplementationOnce(()=>{
return Promise.resolve(false);
});
const response = await app
.put(`/api/lock/review/request/${testBrew.shareId}`)
.catch((err)=>{return err;});
expect(response.status).toBe(500);
expect(response.body).toEqual({
HBErrorCode : '70',
code : 500,
message : `Cannot find a locked brew with ID ${testBrew.shareId}`,
name : 'Brew Not Found',
originalUrl : `/api/lock/review/request/${testBrew.shareId}`
});
});
it('Handle error while adding review request to a locked brew', async ()=>{
const testLock = {
applied : 'YES',
code : 999,
editMessage : 'edit',
shareMessage : 'share'
};
const testBrew = {
shareId : 'shareId',
title : 'title',
markModified : ()=>{ return true; },
save : ()=>{ return Promise.reject(); },
lock : testLock
};
jest.spyOn(HomebrewModel, 'findOne')
.mockImplementationOnce(()=>{
return Promise.resolve(testBrew);
});
const response = await app
.put(`/api/lock/review/request/${testBrew.shareId}`);
expect(response.status).toBe(500);
expect(response.body).toEqual({
HBErrorCode : '69',
code : 500,
message : `Unable to set request for review on brew ID ${testBrew.shareId}`,
name : 'Can Not Set Review Request',
originalUrl : `/api/lock/review/request/${testBrew.shareId}`
});
});
it('Clear review request from a locked brew', async ()=>{
const testLock = {
applied : 'YES',
code : 999,
editMessage : 'edit',
shareMessage : 'share',
reviewRequested : 'YES'
};
const testBrew = {
shareId : 'shareId',
title : 'title',
markModified : ()=>{ return true; },
save : ()=>{ return Promise.resolve(); },
lock : testLock
};
jest.spyOn(HomebrewModel, 'findOne')
.mockImplementationOnce(()=>{
return Promise.resolve(testBrew);
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.put(`/api/lock/review/remove/${testBrew.shareId}`);
expect(response.status).toBe(200);
expect(response.body).toEqual({
message : `Review request removed for brew ID ${testBrew.shareId} - ${testBrew.title}`,
name : 'Review Request Cleared'
});
});
it('Error when clearing review request from a brew with no review request', async ()=>{
const testBrew = {
shareId : 'shareId',
};
jest.spyOn(HomebrewModel, 'findOne')
.mockImplementationOnce(()=>{
return Promise.resolve(false);
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.put(`/api/lock/review/remove/${testBrew.shareId}`);
expect(response.status).toBe(500);
expect(response.body).toEqual({
HBErrorCode : '73',
message : `Brew ID ${testBrew.shareId} does not have a review pending!`,
name : 'Can Not Clear Review Request',
originalUrl : `/api/lock/review/remove/${testBrew.shareId}`
});
});
it('Handle error while clearing review request from a locked brew', async ()=>{
const testLock = {
applied : 'YES',
code : 999,
editMessage : 'edit',
shareMessage : 'share',
reviewRequested : 'YES'
};
const testBrew = {
shareId : 'shareId',
title : 'title',
markModified : ()=>{ return true; },
save : ()=>{ return Promise.reject(); },
lock : testLock
};
jest.spyOn(HomebrewModel, 'findOne')
.mockImplementationOnce(()=>{
return Promise.resolve(testBrew);
});
const response = await app
.set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`)
.put(`/api/lock/review/remove/${testBrew.shareId}`);
expect(response.status).toBe(500);
expect(response.body).toEqual({
HBErrorCode : '72',
message : `Unable to remove request for review on brew ID ${testBrew.shareId}`,
name : 'Can Not Clear Review Request',
originalUrl : `/api/lock/review/remove/${testBrew.shareId}`
});
});
});
});
});

View File

@@ -2,7 +2,7 @@
// Set working directory to project root
import { dirname } from 'path';
import { fileURLToPath } from 'url';
import packageJSON from './../package.json' with { type: 'json' };
import packageJSON from './../package.json' with { type: 'json' };
const __dirname = dirname(fileURLToPath(import.meta.url));
process.chdir(`${__dirname}/..`);
@@ -11,7 +11,6 @@ const version = packageJSON.version;
import _ from 'lodash';
import jwt from 'jwt-simple';
import express from 'express';
import yaml from 'js-yaml';
import config from './config.js';
import fs from 'fs-extra';
@@ -70,13 +69,11 @@ const corsOptions = {
'https://homebrewery-stage.herokuapp.com',
];
if(isLocalEnvironment) {
allowedOrigins.push('http://localhost:8000', 'http://localhost:8010');
}
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');
@@ -352,7 +349,7 @@ app.get('/user/:username', async (req, res, next)=>{
app.put('/api/user/rename', async (req, res)=>{
const { username, newUsername } = req.body;
const ownAccount = req.account && (req.account.username == newUsername);
if(!username || !newUsername)
return res.status(400).json({ error: 'Username and newUsername are required.' });
if(!ownAccount)
@@ -386,6 +383,7 @@ app.get('/edit/:id', asyncHandler(getBrew('edit')), asyncHandler(async(req, res,
title : req.brew.title || 'Untitled Brew',
description : req.brew.description || 'No description.',
image : req.brew.thumbnail || defaultMetaTags.image,
locale : req.brew.lang,
type : 'article'
};
@@ -407,6 +405,7 @@ app.get('/new/:id', asyncHandler(getBrew('share')), asyncHandler(async(req, res,
renderer : req.brew.renderer,
theme : req.brew.theme,
tags : req.brew.tags,
snippets : req.brew.snippets
};
req.brew = _.defaults(brew, DEFAULT_BREW);
@@ -436,7 +435,7 @@ app.get('/new', asyncHandler(async(req, res, next)=>{
app.get('/share/:id', asyncHandler(getBrew('share')), asyncHandler(async (req, res, next)=>{
const { brew } = req;
req.ogMeta = { ...defaultMetaTags,
title : req.brew.title || 'Untitled Brew',
title : `${req.brew.title || 'Untitled Brew'} - ${req.brew.authors[0] || 'No author.'}`,
description : req.brew.description || 'No description.',
image : req.brew.thumbnail || defaultMetaTags.image,
type : 'article'
@@ -488,8 +487,8 @@ app.get('/account', asyncHandler(async (req, res, next)=>{
const query = { authors: req.account.username, googleId: { $exists: false } };
const mongoCount = await HomebrewModel.countDocuments(query)
.catch((err)=>{
mongoCount = 0;
console.log(err);
return 0;
});
data.accountDetails = {
@@ -552,6 +551,7 @@ const renderPage = async (req, res)=>{
const configuration = {
local : isLocalEnvironment,
publicUrl : config.get('publicUrl') ?? '',
baseUrl : `${req.protocol}://${req.get('host')}`,
environment : nodeEnv,
deployment : config.get('heroku_app_name') ?? ''
};

View File

@@ -27,7 +27,10 @@ const disconnect = async ()=>{
};
const connect = async (config)=>{
return await Mongoose.connect(getMongoDBURL(config), { retryWrites: false })
return await Mongoose.connect(getMongoDBURL(config), {
retryWrites : false,
autoIndex : (config.get('local_environments').includes(config.get('node_env')))
})
.catch((error)=>handleConnectionError(error));
};

View File

@@ -0,0 +1,66 @@
import forceSSL from './forcessl.mw';
describe('Tests for ForceSSL middleware', ()=>{
let originalEnv;
let nextFn;
let req = {};
let res = {};
beforeEach(()=>{
originalEnv = process.env.NODE_ENV;
nextFn = jest.fn();
req = {
header : ()=>{ return 'http'; },
get : ()=>{ return 'test'; },
url : 'URL'
};
res = {
redirect : jest.fn()
};
});
afterEach(()=>{
process.env.NODE_ENV = originalEnv;
jest.clearAllMocks();
});
it('should not redirect when NODE_ENV is set to local', ()=>{
process.env.NODE_ENV = 'local';
forceSSL(null, null, nextFn);
expect(res.redirect).not.toHaveBeenCalled();
expect(nextFn).toHaveBeenCalled();
});
it('should not redirect when NODE_ENV is set to docker', ()=>{
process.env.NODE_ENV = 'docker';
forceSSL(null, null, nextFn);
expect(res.redirect).not.toHaveBeenCalled();
expect(nextFn).toHaveBeenCalled();
});
it('should redirect with 302 when header is not HTTPS and NODE_ENV is not local or docker', ()=>{
process.env.NODE_ENV = 'test';
forceSSL(req, res, nextFn);
expect(res.redirect).toHaveBeenCalledWith(302, 'https://testURL');
expect(nextFn).not.toHaveBeenCalled();
});
it('should not redirect when header is HTTPS and NODE_ENV is not local or docker', ()=>{
process.env.NODE_ENV = 'test';
req.header = ()=>{ return 'https'; };
forceSSL(req, res, nextFn);
expect(res.redirect).not.toHaveBeenCalled();
expect(nextFn).toHaveBeenCalled();
});
});

View File

@@ -27,12 +27,12 @@ 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
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 = {
@@ -177,8 +177,8 @@ const GoogleActions = {
mimeType : 'text/plain',
body : brew.text
},
headers: {
'X-Forwarded-For': userIp, // Set the X-Forwarded-For header
headers : {
'X-Forwarded-For' : userIp, // Set the X-Forwarded-For header
},
retryConfig
})

View File

@@ -1,6 +1,6 @@
/* eslint-disable max-lines */
import _ from 'lodash';
import {model as HomebrewModel} from './homebrew.model.js';
import { model as HomebrewModel } from './homebrew.model.js';
import express from 'express';
import zlib from 'zlib';
import GoogleActions from './googleActions.js';
@@ -8,9 +8,13 @@ import Markdown from '../shared/naturalcrit/markdown.js';
import yaml from 'js-yaml';
import asyncHandler from 'express-async-handler';
import { nanoid } from 'nanoid';
import { splitTextStyleAndMetadata } from '../shared/helpers.js';
import {makePatches, applyPatches, stringifyPatches, parsePatch} from '@sanity/diff-match-patch';
import { md5 } from 'hash-wasm';
import { splitTextStyleAndMetadata,
brewSnippetsToJSON, debugTextMismatch } from '../shared/helpers.js';
import checkClientVersion from './middleware/check-client-version.js';
const router = express.Router();
import { DEFAULT_BREW, DEFAULT_BREW_LOAD } from './brewDefaults.js';
@@ -44,6 +48,20 @@ const api = {
}
id = id.slice(googleId.length);
}
// ID Validation Checks
// Homebrewery ID
// Typically 12 characters, but the DB shows a range of 7 to 14 characters
if(!id.match(/^[a-zA-Z0-9-_]{7,14}$/)){
throw { name: 'ID Error', message: 'Invalid ID', status: 404, HBErrorCode: '11', brewId: id };
}
// Google ID
// Typically 33 characters, old format is 44 - always starts with a 1
// Managed by Google, may change outside of our control, so any length between 33 and 44 is acceptable
if(googleId && !googleId.match(/^1(?:[a-zA-Z0-9-_]{32,43})$/)){
throw { name: 'Google ID Error', message: 'Invalid ID', status: 404, HBErrorCode: '12', brewId: id };
}
return { id, googleId };
},
//Get array of any of this user's brews tagged with `meta:theme`
@@ -92,7 +110,7 @@ const api = {
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.
@@ -118,8 +136,8 @@ const api = {
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(stub?.lock && accessType === 'share') {
throw { HBErrorCode: '51', code: stub.lock.code, message: stub.lock.shareMessage, brewId: stub.shareId, brewTitle: stub.title, brewAuthors: stub.authors };
}
// If there's a google id, get it if requesting the full brew or if no stub found yet
@@ -175,12 +193,15 @@ const api = {
`${text}`;
}
const metadata = _.pick(brew, ['title', 'description', 'tags', 'systems', 'renderer', 'theme']);
const snippetsArray = brewSnippetsToJSON('brew_snippets', brew.snippets, null, false).snippets;
metadata.snippets = snippetsArray.length > 0 ? snippetsArray : undefined;
text = `\`\`\`metadata\n` +
`${yaml.dump(metadata)}\n` +
`\`\`\`\n\n` +
`${text}`;
return text;
},
getGoodBrewTitle : (text)=>{
const tokens = Markdown.marked.lexer(text);
return (tokens.find((token)=>token.type === 'heading' || token.type === 'paragraph')?.text || 'No Title')
@@ -279,6 +300,8 @@ const api = {
let currentTheme;
const completeStyles = [];
const completeSnippets = [];
let themeName;
let themeAuthor;
while (req.params.id) {
//=== User Themes ===//
@@ -292,15 +315,20 @@ const api = {
currentTheme = req.brew;
splitTextStyleAndMetadata(currentTheme);
if(!currentTheme.tags.some((tag)=>tag === 'meta:theme' || tag === 'meta:Theme'))
throw { brewId: req.params.id, name: 'Invalid Theme Selected', message: 'Selected theme does not have the meta:theme tag', status: 422, HBErrorCode: '10' };
themeName ??= currentTheme.title;
themeAuthor ??= currentTheme.authors?.[0];
// If there is anything in the snippets or style members, append them to the appropriate array
if(currentTheme?.snippets) completeSnippets.push(JSON.parse(currentTheme.snippets));
if(currentTheme?.snippets) completeSnippets.push({ name: currentTheme.title, snippets: currentTheme.snippets });
if(currentTheme?.style) completeStyles.push(`/* From Brew: ${req.protocol}://${req.get('host')}/share/${req.params.id} */\n\n${currentTheme.style}`);
req.params.id = currentTheme.theme;
req.params.renderer = currentTheme.renderer;
} else {
//=== Static Themes ===//
themeName ??= req.params.id;
const localSnippets = `${req.params.renderer}_${req.params.id}`; // Just log the name for loading on client
const localStyle = `@import url(\"/themes/${req.params.renderer}/${req.params.id}/style.css\");`;
completeSnippets.push(localSnippets);
@@ -313,7 +341,9 @@ const api = {
const returnObj = {
// Reverse the order of the arrays so they are listed oldest parent to youngest child.
styles : completeStyles.reverse(),
snippets : completeSnippets.reverse()
snippets : completeSnippets.reverse(),
name : themeName,
author : themeAuthor
};
res.setHeader('Content-Type', 'application/json');
@@ -323,21 +353,52 @@ const api = {
// Initialize brew from request and body, destructure query params, and set the initial value for the after-save method
const brewFromClient = api.excludePropsFromUpdate(req.body);
const brewFromServer = req.brew;
if(brewFromServer.version && brewFromClient.version && brewFromServer.version > brewFromClient.version) {
splitTextStyleAndMetadata(brewFromServer);
if(brewFromServer?.version !== brewFromClient?.version){
console.log(`Version mismatch on brew ${brewFromClient.editId}`);
res.setHeader('Content-Type', 'application/json');
return res.status(409).send(JSON.stringify({ message: `The brew has been changed on a different device. Please save your changes elsewhere, refresh, and try again.` }));
return res.status(409).send(JSON.stringify({ message: `The server version is out of sync with the saved brew. Please save your changes elsewhere, refresh, and try again.` }));
}
let brew = _.assign(brewFromServer, brewFromClient);
brewFromServer.text = brewFromServer.text.normalize('NFC');
brewFromServer.hash = await md5(brewFromServer.text);
if(brewFromServer?.hash !== brewFromClient?.hash) {
console.log(`Hash mismatch on brew ${brewFromClient.editId}`);
//debugTextMismatch(brewFromClient.text, brewFromServer.text, `edit/${brewFromClient.editId}`);
res.setHeader('Content-Type', 'application/json');
return res.status(409).send(JSON.stringify({ message: `The server copy is out of sync with the saved brew. Please save your changes elsewhere, refresh, and try again.` }));
}
try {
const patches = parsePatch(brewFromClient.patches);
// Patch to a throwaway variable while parallelizing - we're more concerned with error/no error.
const patchedResult = decodeURI(applyPatches(patches, encodeURI(brewFromServer.text))[0]);
if(patchedResult != brewFromClient.text)
throw("Patches did not apply cleanly, text mismatch detected");
// brew.text = applyPatches(patches, brewFromServer.text)[0];
} catch (err) {
//debugTextMismatch(brewFromClient.text, brewFromServer.text, `edit/${brewFromClient.editId}`);
console.error('Failed to apply patches:', {
//patches : brewFromClient.patches,
brewId : brewFromClient.editId || 'unknown',
error : err
});
// While running in parallel, don't throw the error upstream.
// throw err; // rethrow to preserve the 500 behavior
}
let brew = _.assign(brewFromServer, brewFromClient);
brew.title = brew.title.trim();
brew.description = brew.description.trim() || '';
brew.text = api.mergeBrewText(brew);
const googleId = brew.googleId;
const { saveToGoogle, removeFromGoogle } = req.query;
let afterSave = async ()=>true;
brew.title = brew.title.trim();
brew.description = brew.description.trim() || '';
brew.text = api.mergeBrewText(brew);
if(brew.googleId && removeFromGoogle) {
// If the google id exists and we're removing it from google, set afterSave to delete the google brew and mark the brew's google id as undefined
afterSave = async ()=>{
@@ -398,6 +459,8 @@ const api = {
const after = await afterSave();
if(!after) return;
saved.textBin = undefined; // Remove textBin from the saved object to save bandwidth
res.status(200).send(saved);
},
deleteGoogleBrew : async (account, id, editId, res)=>{
@@ -468,10 +531,10 @@ const api = {
};
router.post('/api', checkClientVersion, asyncHandler(api.newBrew));
router.put('/api/:id', checkClientVersion, asyncHandler(api.getBrew('edit', true)), asyncHandler(api.updateBrew));
router.put('/api/update/:id', checkClientVersion, asyncHandler(api.getBrew('edit', true)), asyncHandler(api.updateBrew));
router.put('/api/:id', checkClientVersion, asyncHandler(api.getBrew('edit', false)), asyncHandler(api.updateBrew));
router.put('/api/update/:id', checkClientVersion, asyncHandler(api.getBrew('edit', false)), asyncHandler(api.updateBrew));
router.delete('/api/:id', checkClientVersion, asyncHandler(api.deleteBrew));
router.get('/api/remove/:id', checkClientVersion, asyncHandler(api.deleteBrew));
router.get('/api/theme/:renderer/:id', asyncHandler(api.getThemeBundle));
export default api;
export default api;

View File

@@ -99,18 +99,87 @@ describe('Tests for api', ()=>{
expect(googleId).toBeUndefined();
});
it('should throw if id is too short', ()=>{
let err;
try {
api.getId({
params : {
id : 'abcd'
}
});
} catch (e) {
err = e;
};
expect(err).toEqual({ HBErrorCode: '11', brewId: 'abcd', message: 'Invalid ID', name: 'ID Error', status: 404 });
});
it('should return id and google id from request body', ()=>{
const { id, googleId } = api.getId({
params : {
id : 'abcdefgh'
id : 'abcdefghijkl'
},
body : {
googleId : '12345'
googleId : '123456789012345678901234567890123'
}
});
expect(id).toEqual('abcdefgh');
expect(googleId).toEqual('12345');
expect(id).toEqual('abcdefghijkl');
expect(googleId).toEqual('123456789012345678901234567890123');
});
it('should throw invalid - google id right length but does not match pattern', ()=>{
let err;
try {
api.getId({
params : {
id : 'abcdefghijkl'
},
body : {
googleId : '012345678901234567890123456789012'
}
});
} catch (e) {
err = e;
}
expect(err).toEqual({ HBErrorCode: '12', brewId: 'abcdefghijkl', message: 'Invalid ID', name: 'Google ID Error', status: 404 });
});
it('should throw invalid - google id too short (32 char)', ()=>{
let err;
try {
api.getId({
params : {
id : 'abcdefghijkl'
},
body : {
googleId : '12345678901234567890123456789012'
}
});
} catch (e) {
err = e;
}
expect(err).toEqual({ HBErrorCode: '12', brewId: 'abcdefghijkl', message: 'Invalid ID', name: 'Google ID Error', status: 404 });
});
it('should throw invalid - google id too long (45 char)', ()=>{
let err;
try {
api.getId({
params : {
id : 'abcdefghijkl'
},
body : {
googleId : '123456789012345678901234567890123456789012345'
}
});
} catch (e) {
err = e;
}
expect(err).toEqual({ HBErrorCode: '12', brewId: 'abcdefghijkl', message: 'Invalid ID', name: 'Google ID Error', status: 404 });
});
it('should return 12-char id and google id from params', ()=>{
@@ -302,7 +371,7 @@ describe('Tests for api', ()=>{
});
it('access is denied to a locked brew', async()=>{
const lockBrew = { title: 'test brew', shareId: '1', lock: { locked: true, code: 404, shareMessage: 'brew locked' } };
const lockBrew = { title: 'test brew', shareId: '1', lock: { code: 404, shareMessage: 'brew locked' } };
model.get = jest.fn(()=>toBrewPromise(lockBrew));
api.getId = jest.fn(()=>({ id: '1', googleId: undefined }));
@@ -576,7 +645,7 @@ brew`);
describe('Theme bundle', ()=>{
it('should return Theme Bundle for a User Theme', async ()=>{
const brews = {
userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: null, shareId: 'userThemeAID', style: 'User Theme A Style' }
userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: null, shareId: 'userThemeAID', style: 'User Theme A Style', tags: ['meta:theme'], authors: ['authorName'] }
};
const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew }));
@@ -587,6 +656,8 @@ brew`);
expect(res.status).toHaveBeenCalledWith(200);
expect(res.send).toHaveBeenCalledWith({
name : 'User Theme A',
author : 'authorName',
styles : ['/* From Brew: https://localhost/share/userThemeAID */\n\nUser Theme A Style'],
snippets : []
});
@@ -594,9 +665,9 @@ brew`);
it('should return Theme Bundle for nested User Themes', async ()=>{
const brews = {
userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: 'userThemeBID', shareId: 'userThemeAID', style: 'User Theme A Style' },
userThemeBID : { title: 'User Theme B', renderer: 'V3', theme: 'userThemeCID', shareId: 'userThemeBID', style: 'User Theme B Style' },
userThemeCID : { title: 'User Theme C', renderer: 'V3', theme: null, shareId: 'userThemeCID', style: 'User Theme C Style' }
userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: 'userThemeBID', shareId: 'userThemeAID', style: 'User Theme A Style', tags: ['meta:theme'], authors: ['authorName'] },
userThemeBID : { title: 'User Theme B', renderer: 'V3', theme: 'userThemeCID', shareId: 'userThemeBID', style: 'User Theme B Style', tags: ['meta:theme'], authors: ['authorName'] },
userThemeCID : { title: 'User Theme C', renderer: 'V3', theme: null, shareId: 'userThemeCID', style: 'User Theme C Style', tags: ['meta:theme'], authors: ['authorName'] }
};
const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew }));
@@ -607,6 +678,8 @@ brew`);
expect(res.status).toHaveBeenCalledWith(200);
expect(res.send).toHaveBeenCalledWith({
name : 'User Theme A',
author : 'authorName',
styles : [
'/* From Brew: https://localhost/share/userThemeCID */\n\nUser Theme C Style',
'/* From Brew: https://localhost/share/userThemeBID */\n\nUser Theme B Style',
@@ -623,6 +696,8 @@ brew`);
expect(res.status).toHaveBeenCalledWith(200);
expect(res.send).toHaveBeenCalledWith({
name : '5ePHB',
author : undefined,
styles : [
`/* From Theme Blank */\n\n@import url("/themes/V3/Blank/style.css");`,
`/* From Theme 5ePHB */\n\n@import url("/themes/V3/5ePHB/style.css");`
@@ -636,9 +711,9 @@ brew`);
it('should return Theme Bundle for nested User and Static Themes together', async ()=>{
const brews = {
userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: 'userThemeBID', shareId: 'userThemeAID', style: 'User Theme A Style' },
userThemeBID : { title: 'User Theme B', renderer: 'V3', theme: 'userThemeCID', shareId: 'userThemeBID', style: 'User Theme B Style' },
userThemeCID : { title: 'User Theme C', renderer: 'V3', theme: '5eDMG', shareId: 'userThemeCID', style: 'User Theme C Style' }
userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: 'userThemeBID', shareId: 'userThemeAID', style: 'User Theme A Style', tags: ['meta:theme'], authors: ['authorName'] },
userThemeBID : { title: 'User Theme B', renderer: 'V3', theme: 'userThemeCID', shareId: 'userThemeBID', style: 'User Theme B Style', tags: ['meta:theme'], authors: ['authorName'] },
userThemeCID : { title: 'User Theme C', renderer: 'V3', theme: '5eDMG', shareId: 'userThemeCID', style: 'User Theme C Style', tags: ['meta:theme'], authors: ['authorName'] }
};
const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew }));
@@ -649,6 +724,8 @@ brew`);
expect(res.status).toHaveBeenCalledWith(200);
expect(res.send).toHaveBeenCalledWith({
name : 'User Theme A',
author : 'authorName',
styles : [
`/* From Theme Blank */\n\n@import url("/themes/V3/Blank/style.css");`,
`/* From Theme 5ePHB */\n\n@import url("/themes/V3/5ePHB/style.css");`,
@@ -665,9 +742,9 @@ brew`);
});
});
it('should fail for an invalid Theme in the chain', async()=>{
it('should fail for a missing Theme in the chain', async()=>{
const brews = {
userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: 'missingTheme', shareId: 'userThemeAID', style: 'User Theme A Style' },
userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: 'missingTheme', shareId: 'userThemeAID', style: 'User Theme A Style', tags: ['meta:theme'], authors: ['authorName'] },
};
const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew }));
@@ -686,6 +763,27 @@ brew`);
name : 'ThemeLoad Error',
status : 404 });
});
it('should fail for a User Theme not tagged with meta:theme', async ()=>{
const brews = {
userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: null, shareId: 'userThemeAID', style: 'User Theme A Style' }
};
const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew }));
model.get = jest.fn((getParams)=>toBrewPromise(brews[getParams.shareId]));
const req = { params: { renderer: 'V3', id: 'userThemeAID' }, get: ()=>{ return 'localhost'; }, protocol: 'https' };
let err;
await api.getThemeBundle(req, res)
.catch((e)=>err = e);
expect(err).toEqual({
HBErrorCode : '10',
brewId : 'userThemeAID',
message : 'Selected theme does not have the meta:theme tag',
name : 'Invalid Theme Selected',
status : 422 });
});
});
describe('deleteBrew', ()=>{
@@ -910,7 +1008,7 @@ brew`);
});
describe('Get CSS', ()=>{
it('should return brew style content as CSS text', async ()=>{
const testBrew = { title: 'test brew', text: '```css\n\nI Have a style!\n````\n\n' };
const testBrew = { title: 'test brew', text: '```css\n\nI Have a style!\n```\n\n' };
const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew }));
api.getId = jest.fn(()=>({ id: '1', googleId: undefined }));
@@ -1005,7 +1103,7 @@ brew`);
expect(testBrew.theme).toEqual('5ePHB');
expect(testBrew.lang).toEqual('en');
// Style
expect(testBrew.style).toEqual('style\nstyle\nstyle');
expect(testBrew.style).toEqual('style\nstyle\nstyle\n');
// Text
expect(testBrew.text).toEqual('text\n');
});
@@ -1023,4 +1121,83 @@ brew`);
expect(testBrew.tags).toEqual(['tag a']);
});
});
describe('updateBrew', ()=>{
it('should return error on version mismatch', async ()=>{
const brewFromClient = { version: 1 };
const brewFromServer = { version: 1000, text: '' };
const req = {
brew : brewFromServer,
body : brewFromClient
};
await api.updateBrew(req, res);
expect(res.status).toHaveBeenCalledWith(409);
expect(res.send).toHaveBeenCalledWith('{\"message\":\"The server version is out of sync with the saved brew. Please save your changes elsewhere, refresh, and try again.\"}');
});
it('should return error on hash mismatch', async ()=>{
const brewFromClient = { version: 1, hash: '1234' };
const brewFromServer = { version: 1, text: 'test' };
const req = {
brew : brewFromServer,
body : brewFromClient
};
await api.updateBrew(req, res);
expect(req.brew.hash).toBe('098f6bcd4621d373cade4e832627b4f6');
expect(res.status).toHaveBeenCalledWith(409);
expect(res.send).toHaveBeenCalledWith('{\"message\":\"The server copy is out of sync with the saved brew. Please save your changes elsewhere, refresh, and try again.\"}');
});
// Commenting this one out for now, since we are no longer throwing this error while we monitor
// it('should return error on applying patches', async ()=>{
// const brewFromClient = { version: 1, hash: '098f6bcd4621d373cade4e832627b4f6', patches: 'not a valid patch string' };
// const brewFromServer = { version: 1, text: 'test', title: 'Test Title', description: 'Test Description' };
// const req = {
// brew : brewFromServer,
// body : brewFromClient,
// };
// let err;
// try {
// await api.updateBrew(req, res);
// } catch (e) {
// err = e;
// }
// expect(err).toEqual(Error('Invalid patch string: not a valid patch string'));
// });
it('should save brew, no ID', async ()=>{
const brewFromClient = { version: 1, hash: '098f6bcd4621d373cade4e832627b4f6', patches: '' };
const brewFromServer = { version: 1, text: 'test', title: 'Test Title', description: 'Test Description' };
model.save = jest.fn((brew)=>{return brew;});
const req = {
brew : brewFromServer,
body : brewFromClient,
query : { saveToGoogle: false, removeFromGoogle: false }
};
await api.updateBrew(req, res);
expect(res.status).toHaveBeenCalledWith(200);
expect(res.send).toHaveBeenCalledWith(
expect.objectContaining({
_id : '1',
description : 'Test Description',
hash : '098f6bcd4621d373cade4e832627b4f6',
title : 'Test Title',
version : 2
})
);
});
});
});

View File

@@ -7,27 +7,29 @@ import zlib from 'zlib';
const HomebrewSchema = mongoose.Schema({
shareId : { type: String, default: ()=>{return nanoid(12);}, index: { unique: true } },
editId : { type: String, default: ()=>{return nanoid(12);}, index: { unique: true } },
googleId : { type: String },
title : { type: String, default: '' },
googleId : { type: String, index: true },
title : { type: String, default: '', index: true },
text : { type: String, default: '' },
textBin : { type: Buffer },
pageCount : { type: Number, default: 1 },
pageCount : { type: Number, default: 1, index: true },
description : { type: String, default: '' },
tags : [String],
tags : { type: [String], index: true },
systems : [String],
lang : { type: String, default: 'en' },
renderer : { type: String, default: '' },
authors : [String],
lang : { type: String, default: 'en', index: true },
renderer : { type: String, default: '', index: true },
authors : { type: [String], index: true },
invitedAuthors : [String],
published : { type: Boolean, default: false },
thumbnail : { type: String, default: '' },
published : { type: Boolean, default: false, index: true },
thumbnail : { type: String, default: '', index: true },
createdAt : { type: Date, default: Date.now },
updatedAt : { type: Date, default: Date.now },
lastViewed : { type: Date, default: Date.now },
createdAt : { type: Date, default: Date.now, index: true },
updatedAt : { type: Date, default: Date.now, index: true },
lastViewed : { type: Date, default: Date.now, index: true },
views : { type: Number, default: 0 },
version : { type: Number, default: 1 }
version : { type: Number, default: 1, index: true },
lock : { type: Object, index: true }
}, { versionKey: false });
HomebrewSchema.statics.increaseView = async function(query) {
@@ -41,6 +43,8 @@ HomebrewSchema.statics.increaseView = async function(query) {
return brew;
};
// STATIC FUNCTIONS
HomebrewSchema.statics.get = async function(query, fields=null){
const brew = await Homebrew.findOne(query, fields).orFail()
.catch((error)=>{throw 'Can not find brew';});
@@ -61,9 +65,18 @@ HomebrewSchema.statics.getByUser = async function(username, allowAccess=false, f
return brews;
};
// INDEXES
HomebrewSchema.index({ updatedAt: -1, lastViewed: -1 });
HomebrewSchema.index({ published: 1, title: 'text' });
HomebrewSchema.index({ lock: 1, sparse: true });
HomebrewSchema.path('lock.reviewRequested').index({ sparse: true });
const Homebrew = mongoose.model('Homebrew', HomebrewSchema);
export {
export {
HomebrewSchema as schema,
Homebrew as model
};

View File

@@ -5,21 +5,16 @@ import config from './config.js';
const generateAccessToken = (account)=>{
const payload = account;
// When the token was issued
payload.issued = (new Date());
// Which service issued the Token
payload.issuer = config.get('authentication_token_issuer');
// Which service is the token intended for
payload.audience = config.get('authentication_token_audience');
// The signing key for signing the token
payload.issued = (new Date()); // When the token was issued
payload.issuer = config.get('authentication_token_issuer'); // Which service issued the Token
payload.audience = config.get('authentication_token_audience'); // Which service is the token intended for
const secret = config.get('authentication_token_secret'); // The signing key for signing the token
delete payload.password;
delete payload._id;
const secret = config.get('authentication_token_secret');
const token = jwt.encode(payload, secret);
return token;
};
export default generateAccessToken;
export default generateAccessToken;

27
server/token.spec.js Normal file
View File

@@ -0,0 +1,27 @@
import { expect, jest } from '@jest/globals';
import config from './config.js';
import generateAccessToken from './token';
describe('Tests for Token', ()=>{
it('Get token', ()=>{
// Mock the Config module, so we aren't grabbing actual secrets for testing
jest.mock('./config.js');
config.get = jest.fn((param)=>{
// The requested key name will be reflected to the output
return param;
});
const account = {};
const token = generateAccessToken(account);
// If these tests fail, the config mock has failed
expect(account).toHaveProperty('issuer', 'authentication_token_issuer');
expect(account).toHaveProperty('audience', 'authentication_token_audience');
// Because the inputs are fixed, this JWT key should be static
expect(typeof token).toBe('string');
});
});

Some files were not shown because too many files have changed in this diff Show More