0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-23 14:23:21 +00:00

Compare commits

..

515 Commits

Author SHA1 Message Date
Trevor Buckner
63bebe1efd Lint everything
Catching up on a bunch of linting so random changes stop showing up on PRs when the linter is run.
2025-10-06 00:02:24 -04:00
Trevor Buckner
22e26d635a Merge pull request #4460 from naturalcrit/cleanupLocalStorageKeysTest
Clean up localStorageMap code
2025-10-05 23:28:34 -04:00
Trevor Buckner
643e0ac650 small cleanups of localstorage keys code 2025-10-05 23:24:50 -04:00
Trevor Buckner
5395412ac5 Remove tests for getLocalStorageMap()
The function is a simple getter with trivial logic; test is effectively just asserting the size of the map, which coverage adds no meaningful value and adds cruft to the codebase.
2025-10-05 23:24:35 -04:00
Trevor Buckner
dc4610ea1b Merge pull request #4447 from dbolack-ab/issue_3426
Applies G-Ambatte's fix for Firefox browser lock.
2025-10-05 22:31:15 -04:00
Trevor Buckner
1e71e9e18a Use blockquote and table elements, not .classes 2025-10-05 22:19:43 -04:00
Trevor Buckner
4203e90d09 Merge branch 'master' into issue_3426 2025-10-05 22:09:06 -04:00
Trevor Buckner
dc94555c94 Merge pull request #4458 from naturalcrit/new/edit/home_commonSaveButton
Make the renderSaveButton() function common between edit/new/home
2025-10-05 22:03:32 -04:00
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
David Bolack
74e17e154f Merge branch 'issue_3426' of github.com:dbolack-ab/homebrewery into issue_3426 2025-10-05 20:12:32 -05: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
David Bolack
8f715a6615 Isolate change to Firefox 2025-10-05 18:36:14 -05: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
8f7ae35f08 Merge branch 'master' into issue_3426 2025-10-04 18:32:15 -04: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
David Bolack
f0bb06e706 Merge branch 'master' into issue_3426 2025-10-03 18:52:56 -05: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
David Bolack
c50c279ef3 Merge branch 'issue_3426' of github.com:dbolack-ab/homebrewery into issue_3426 2025-09-22 20:36:45 -05:00
David Bolack
cc246fb31a Merge branch 'master' into issue_3426 2025-09-22 20:36:09 -05: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
4c5eef46a0 Merge branch 'master' into issue_3426 2025-08-21 16:33:19 +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
David Bolack
a1ab27b57f Applies G-Ambatte's fix 2025-07-30 19:47:59 -05: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
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
David Bolack
b7a7446f75 Merge branch 'master' into marked-definition-lists 2025-04-08 21:43:14 -05:00
Víctor Losada Hernández
551763fecb initial commit 2025-03-19 14:04:29 +01:00
David Bolack
4b9b1ec9ac Merge branch 'master' into marked-definition-lists 2025-03-05 10:36:50 -06: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
David Bolack
caca578709 Merge branch 'master' into marked-definition-lists 2025-02-28 00:00:47 -06:00
David Bolack
09ac8b8a32 Move definition list tokens to an extension 2025-02-12 22:57:53 -06:00
G.Ambatte
a917937f12 Merge branch 'master' into newTheme-UnearthedArcana 2024-07-22 14:35:14 +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
83 changed files with 12902 additions and 4212 deletions

View File

@@ -64,9 +64,6 @@ 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

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

@@ -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
@@ -145,3 +146,4 @@ your contribution to the project, please join our [gitter chat][gitter-url].
[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

@@ -88,24 +88,94 @@ pre {
## changelog
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
### Tuesday 03/18/2025 - v3.18.1
### 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] Revert colon rendering from br elements to blank divs
* [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] Allow for local connections within a same network when running a local version
Fixes issue [#4094](https://github.com/naturalcrit/homebrewery/issues/4094)
* [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)
* [x] Add US Letter size page snippet
Fixes issue [#3893](https://github.com/naturalcrit/homebrewery/issues/3893)
}}
\page
### Monday 03/10/2025 - v3.18.0
{{taskList
##### dbolack
##### 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
@@ -167,7 +237,7 @@ Fixes issue [#4073](https://github.com/naturalcrit/homebrewery/issues/4073)
* [x] Fix Reddit link crash when title has non-latin chars
##### dbolack
##### abquintic
* [x] Fix page shadows toolbar option

View File

@@ -7,15 +7,17 @@ import LockTools from './lockTools/lockTools.jsx';
const tabGroups = ['brew', 'notifications', 'authors', 'locks'];
const ADMIN_TAB = 'HB_adminPage_currentTab';
const Admin = ()=>{
const [currentTab, setCurrentTab] = useState('');
useEffect(()=>{
setCurrentTab(localStorage.getItem('hbAdminTab') || 'brew');
setCurrentTab(localStorage.getItem(ADMIN_TAB) || 'brew');
}, []);
useEffect(()=>{
localStorage.setItem('hbAdminTab', currentTab);
localStorage.setItem(ADMIN_TAB, currentTab);
}, [currentTab]);
return (

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';

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

@@ -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,8 +41,8 @@ const BrewPage = (props)=>{
index : 0,
...props
};
const pageRef = useRef(null);
const cleanText = safeHTML(`${props.contents}\n<div class="columnSplit"></div>\n`);
const pageRef = useRef(null);
const cleanText = safeHTML(props.contents);
useEffect(()=>{
if(!pageRef.current) return;
@@ -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,6 +191,7 @@ 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} />;
@@ -196,6 +208,9 @@ const BrewRenderer = (props)=>{
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} />;
@@ -271,6 +286,7 @@ const BrewRenderer = (props)=>{
const handleDisplayOptionsChange = (newDisplayOptions)=>{
setDisplayOptions(newDisplayOptions);
localStorage.setItem(TOOLBAR_STATE_KEY, JSON.stringify(newDisplayOptions));
};
const pagesStyle = {
@@ -279,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]);
@@ -313,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

@@ -6,7 +6,6 @@
overflow-y : scroll;
will-change : transform;
&:has(.facing, .flow) { padding : 60px 30px; }
&.deployment { background-color : darkred; }
:where(.pages) {
&.facing {
display : grid;
@@ -68,12 +67,16 @@
@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; }
}
}

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,7 +153,7 @@
align-items : center;
justify-content : center;
width : auto;
min-width : 46px;
min-width : 40px;
height : 100%;
&:hover { background-color : #444444; }
&:focus {outline : none; border : 1px solid #D3D3D3;}
@@ -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);
}
}
}
@@ -183,7 +187,5 @@
left : 0;
z-index : 5;
display : flex;
width : 32px;
min-width : unset;
height : 100%;
}

View File

@@ -10,11 +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 PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m;
const SNIPPETBREAK_REGEX_V3 = /^\\snippet\ .*$/;
const SNIPPETBAR_HEIGHT = 25;
const DEFAULT_STYLE_TEXT = dedent`
/*=======--- Example CSS styling ---=======*/
/* Any CSS here will apply to your document! */
@@ -41,11 +40,8 @@ const Editor = createClass({
style : ''
},
onTextChange : ()=>{},
onStyleChange : ()=>{},
onMetaChange : ()=>{},
onSnipChange : ()=>{},
reportError : ()=>{},
onBrewChange : ()=>{},
reportError : ()=>{},
onCursorPageChange : ()=>{},
onViewPageChange : ()=>{},
@@ -60,8 +56,9 @@ const Editor = createClass({
},
getInitialState : function() {
return {
editorTheme : this.props.editorTheme,
view : 'text' //'text', 'style', 'meta', 'snippet'
editorTheme : this.props.editorTheme,
view : 'text', //'text', 'style', 'meta', 'snippet'
snippetbarHeight : 25
};
},
@@ -88,6 +85,7 @@ const Editor = createClass({
editorTheme : editorTheme
});
}
this.setState({ snippetbarHeight: document.querySelector('.editor > .snippetBar').offsetHeight });
},
componentDidUpdate : function(prevProps, prevState, snapshot) {
@@ -142,7 +140,7 @@ const Editor = createClass({
handleViewChange : function(newView){
this.props.setMoveArrows(newView === 'text');
this.setState({
view : newView
}, ()=>{
@@ -211,7 +209,7 @@ const Editor = createClass({
// New Codemirror styling for V3 renderer
if(this.props.renderer === 'V3') {
if(line.match(/^\\column$/)){
if(line.match(/^\\column(?:break)?$/)){
codeMirror.addLineClass(lineNumber, 'text', 'columnSplit');
}
@@ -412,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){
@@ -434,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()){
@@ -446,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()){
@@ -461,7 +464,7 @@ const Editor = createClass({
<MetadataEditor
metadata={this.props.brew}
themeBundle={this.props.themeBundle}
onChange={this.props.onMetaChange}
onChange={this.props.onBrewChange('metadata')}
reportError={this.props.reportError}
userThemes={this.props.userThemes}/>
</>;
@@ -475,10 +478,11 @@ const Editor = createClass({
language='gfm'
view={this.state.view}
value={this.props.brew.snippets}
onChange={this.props.onSnipChange}
onChange={this.props.onBrewChange('snippets')}
enableFolding={true}
editorTheme={this.state.editorTheme}
rerenderParent={this.rerenderParent} />
rerenderParent={this.rerenderParent}
style={{ height: `calc(100% - ${this.state.snippetbarHeight}px)` }} />
</>;
}
},

View File

@@ -5,7 +5,7 @@
height : 100%;
container : editor / inline-size;
.codeEditor {
height : 100%;
height : calc(100% - 25px);
.CodeMirror { height : 100%; }
.pageLine, .snippetLine {
background : #33333328;
@@ -108,8 +108,4 @@
span { padding : 2px 5px; }
}
}
@container editor (width < 553px) {
.editor .codeEditor .CodeMirror { height : calc(100% - 51px);}
}

View File

@@ -18,7 +18,7 @@ module.exports = {
try {
Boolean(new URL(value));
return null;
} catch (e) {
} catch {
return 'Must be a valid URL';
}
}

View File

@@ -51,7 +51,7 @@
&.meta {
.tooltipLeft('Properties');
}
&.snip {
&.snippet {
.tooltipLeft('Snippets');
}
&.undo {
@@ -93,7 +93,7 @@
&.editorTheme {
.tooltipLeft('Editor Themes');
font-size : 0.75em;
color : black;
color : inherit;
&.active {
position : relative;
background-color : #999999;

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,12 +1,14 @@
@import 'naturalcrit/styles/core.less';
.homebrew {
height : 100%;
background-color:@steel;
&.deployment { background-color : darkred; }
.sitePage {
display : flex;
flex-direction : column;
height : 100%;
overflow-y : hidden;
background-color : @steel;
.content {
position : relative;
flex : auto;

View File

@@ -1,144 +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>;
}
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>;
}
let errMsg = '';
try {
errMsg += `${error.toString()}\n\n`;
errMsg += `\`\`\`\n${error.stack}\n`;
errMsg += `${JSON.stringify(response?.error, null, ' ')}\n\`\`\``;
console.log(errMsg);
} catch {}
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

@@ -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 = ({

View File

@@ -64,7 +64,7 @@
border-radius : 4px;
&::before {
margin-right : 3px;
font-family : 'Font Awesome 5 Free';
font-family : 'Font Awesome 6 Free';
font-size : 12px;
}
&.type {
@@ -115,15 +115,15 @@
}
}
.googleDriveIcon {
height : 18px;
padding : 0px;
margin : -5px;
height : 18px;
}
.homebreweryIcon {
position : relative;
top : 5px;
left : -5px;
height : 24px;
mix-blend-mode : darken;
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

@@ -30,7 +30,7 @@
h1:hover { cursor : pointer; }
.active::before, .inactive::before {
padding-right : 0.5em;
font-family : 'Font Awesome 5 Free';
font-family : 'Font Awesome 6 Free';
font-size : 0.6cm;
font-weight : 900;
}
@@ -130,12 +130,12 @@
border-radius : 3px;
&::before {
margin-right : 3px;
font-family : 'Font Awesome 5 Free';
font-family : 'Font Awesome 6 Free';
font-size : 12px;
}
&::after {
margin-left : 3px;
font-family : 'Font Awesome 5 Free';
font-family : 'Font Awesome 6 Free';
font-size : 12px;
content : '\f00d';
}

View File

@@ -28,7 +28,8 @@
background-color : #00000077;
&::before {
margin-right : 5px;
font-family : 'FONT AWESOME 5 FREE';
font-family : 'Font Awesome 6 Free';
font-weight : 900;
content : '\f00c';
}
}

View File

@@ -1,499 +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);
},
componentDidUpdate : function(){
const hasChange = this.hasChanges();
if(this.state.isPending != hasChange){
this.setState({
isPending : hasChange
});
if(autoSaveEnabled) trySave(false, hasChange);
}, [currentBrew]);
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
}));
}
},
};
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 updateBrew = (newData)=>setCurrentBrew((prevBrew)=>({
...prevBrew,
style : newData.style,
text : newData.text,
snippets : newData.snippets
}));
handleSplitMove : function(){
this.editor.current.update();
},
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
};
handleEditorViewPageChange : function(pageNumber){
this.setState({ currentEditorViewPageNum: pageNumber });
},
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 },
htmlErrors : htmlErrors,
}), ()=>{if(this.state.autoSave) this.trySave();});
},
handleSnipChange : function(snippet){
//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(snippet);
this.setState((prevState)=>({
brew : { ...prevState.brew, snippets: snippet },
isPending : true,
htmlErrors : htmlErrors,
}), ()=>{if(this.state.autoSave) this.trySave();});
},
handleStyleChange : function(style){
this.setState((prevState)=>({
brew : { ...prevState.brew, style: style }
}), ()=>{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
}
}), ()=>{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 = {
...this.state.brew,
googleId : res.body.googleId ? res.body.googleId : null,
editId : res.body.editId,
const updatedFields = {
googleId : res.body.googleId ?? null,
editId : res.body.editId,
shareId : res.body.shareId,
version : res.body.version
};
history.replaceState(null, null, `/edit/${this.savedBrew.editId}`);
this.setState(()=>({
brew : this.savedBrew,
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>;
},
renderSaveButton : function(){
)}
</Nav.item>
);
const renderSaveButton = ()=>{
// #1 - Currently saving, show SAVING
if(this.state.isSaving){
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(this.state.isPending && this.state.autoSaveWarning){
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.`;
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>;
}
// #3 - Unsaved changes exist, click to save, show SAVE NOW
// Use trySave(true) instead of save() to use debounced save function
if(this.state.isPending){
return <Nav.item className='save' onClick={()=>this.trySave(true)} color='blue' icon='fas fa-save'>Save Now</Nav.item>;
}
if(unsavedChanges)
return <Nav.item className='save' onClick={()=>trySave(true)} color='blue' icon='fas fa-save'>save now</Nav.item>;
// #4 - No unsaved changes, autosave is ON, show AUTO-SAVED
if(this.state.autoSave){
return <Nav.item className='save saved'>auto-saved.</Nav.item>;
}
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>;
},
return <Nav.item className='save saved'>saved</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));
});
},
const toggleAutoSave = ()=>{
clearTimeout(warnUnsavedTimeout.current);
clearTimeout(saveTimeout.current);
localStorage.setItem(AUTOSAVE_KEY, JSON.stringify(!autoSaveEnabled));
setAutoSaveEnabled(!autoSaveEnabled);
setWarnUnsavedChanges(autoSaveEnabled);
};
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;
},
const renderAutoSaveButton = ()=>(
<Nav.item onClick={toggleAutoSave}>
Autosave <i className={autoSaveEnabled ? 'fas fa-power-off active' : 'fas fa-power-off'}></i>
</Nav.item>
);
errorReported : function(error) {
this.setState({
error
});
},
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>;
},
processShareId : function() {
return this.state.brew.googleId && !this.state.brew.stubbed ?
this.state.brew.googleId + this.state.brew.shareId :
this.state.brew.shareId;
},
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.baseUrl}/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.baseUrl}/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} reviewRequested={this.props.brew.lock.reviewRequested} />}
{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}
onSnipChange={this.handleSnipChange}
onMetaChange={this.handleMetaChange}
reportError={this.errorReported}
renderer={this.state.brew.renderer}
userThemes={this.props.userThemes}
themeBundle={this.state.themeBundle}
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

@@ -176,6 +176,26 @@ const errorIndex = (props)=>{
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

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()}
{renderNavbar()}
<div className='content'>
<SplitPane onDragFinish={this.handleSplitMove}>
<SplitPane onDragFinish={handleSplitMove}>
<Editor
ref={this.editor}
brew={this.state.brew}
onTextChange={this.handleTextChange}
renderer={this.state.brew.renderer}
ref={editorRef}
brew={currentBrew}
onBrewChange={handleBrewChange}
renderer={currentBrew.renderer}
showEditButtons={false}
themeBundle={this.state.themeBundle}
onCursorPageChange={this.handleEditorCursorPageChange}
onViewPageChange={this.handleEditorViewPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
themeBundle={themeBundle}
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}
onPageChange={this.handleBrewRendererPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
themeBundle={this.state.themeBundle}
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

@@ -34,7 +34,13 @@
}
.navItem.save {
.fadeInRight();
.transition(opacity);
background-color : @orange;
&:hover { background-color : @green; }
&.neverSaved {
.fadeOutRight();
opacity: 0;
}
}
}

View File

@@ -1,276 +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);
},
handleSnipChange : function(snippet){
//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(snippet);
this.setState((prevState)=>({
brew : { ...prevState.brew, snippets: snippet },
isPending : true,
htmlErrors : htmlErrors,
}), ()=>{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 },
}), ()=>{
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);
const 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)
.post(`/api${saveGoogle ? '?saveToGoogle=true' : ''}`)
.send(updatedBrew)
.catch((err)=>{
this.setState({ isSaving: false, error: err });
setIsSaving(false);
setError(err);
});
setIsSaving(false);
if(!res) return;
brew = res.body;
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()}
return (
<div className='newPage sitePage'>
{renderNavbar()}
<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}
onSnipChange={this.handleSnipChange}
renderer={this.state.brew.renderer}
userThemes={this.props.userThemes}
themeBundle={this.state.themeBundle}
onCursorPageChange={this.handleEditorCursorPageChange}
onViewPageChange={this.handleEditorViewPageChange}
currentEditorViewPageNum={this.state.currentEditorViewPageNum}
currentEditorCursorPageNum={this.state.currentEditorCursorPageNum}
currentBrewRendererPageNum={this.state.currentBrewRendererPageNum}
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={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 = NewPage;

View File

@@ -1,6 +1,12 @@
.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

@@ -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';

View File

@@ -1,14 +1,16 @@
.vaultPage {
height : 100%;
overflow-y : hidden;
background-color : #2C3E50;
*:not(input) { user-select : none; }
.form {
background:white;
}
:where(.content .dataGroup) {
width : 100%;
height : 100%;
background : white;
&.form .brewLookup {
position : relative;
@@ -171,7 +173,6 @@
max-height : 100%;
padding : 70px 50px;
overflow-y : scroll;
background-color : #2C3E50;
container-type : inline-size;
h3 { font-size : 25px; }

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,22 @@
import getLocalStorageMap from './localStorageKeyMap.js';
const updateLocalStorage = function(){
// Return if no window and thus no local storage
if(typeof window === 'undefined') return;
const localStorageKeyMap = getLocalStorageMap();
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

@@ -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

5613
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "homebrewery",
"description": "Create authentic looking D&D homebrews using only markdown",
"version": "3.18.1",
"version": "3.19.3",
"type": "module",
"engines": {
"npm": "^10.8.x",
@@ -36,7 +36,6 @@
"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:emojis": "jest tests/markdown/emojis.test.js --verbose --noStackTrace",
@@ -73,7 +72,7 @@
"lines": 50
},
"server/homebrew.api.js": {
"statements": 70,
"statements": 60,
"branches": 50,
"functions": 65,
"lines": 70
@@ -84,66 +83,72 @@
]
},
"dependencies": {
"@babel/core": "^7.26.10",
"@babel/plugin-transform-runtime": "^7.26.10",
"@babel/preset-env": "^7.26.9",
"@babel/preset-react": "^7.26.3",
"@googleapis/drive": "^11.0.0",
"@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.41.0",
"core-js": "^3.44.0",
"cors": "^2.8.5",
"create-react-class": "^15.7.0",
"dedent-tabs": "^0.10.3",
"expr-eval": "^2.0.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": "15.0.8",
"marked-emoji": "^2.0.0",
"marked-extended-tables": "^2.0.1",
"marked-gfm-heading-id": "^4.0.1",
"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.13.2",
"mongoose": "^8.16.3",
"nanoid": "5.1.5",
"nconf": "^0.12.1",
"nconf": "^0.13.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-frame-component": "^4.1.3",
"react-router": "^7.5.0",
"romans": "^3.0.0",
"react-router": "^7.6.3",
"romans": "^3.1.0",
"sanitize-filename": "1.6.3",
"superagent": "^10.2.0",
"superagent": "^10.2.1",
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git",
"written-number": "^0.11.1"
},
"devDependencies": {
"@stylistic/stylelint-plugin": "^3.1.2",
"babel-plugin-transform-import-meta": "^2.3.2",
"eslint": "^9.24.0",
"eslint-plugin-jest": "^28.11.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.0.0",
"jest": "^29.7.0",
"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.18.0",
"stylelint-config-recess-order": "^6.0.0",
"stylelint-config-recommended": "^16.0.0",
"supertest": "^7.1.0"
"stylelint": "^16.24.0",
"stylelint-config-recess-order": "^7.3.0",
"stylelint-config-recommended": "^17.0.0",
"supertest": "^7.1.4"
}
}

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

@@ -383,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'
};
@@ -404,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);
@@ -433,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'
@@ -485,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 = {

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

@@ -8,8 +8,10 @@ import Markdown from '../shared/naturalcrit/markdown.js';
import yaml from 'js-yaml';
import asyncHandler from 'express-async-handler';
import { nanoid } from 'nanoid';
import { splitTextStyleAndMetadata,
brewSnippetsToJSON } 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';
@@ -46,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`
@@ -337,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 ()=>{
@@ -412,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)=>{
@@ -482,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', ()=>{
@@ -1052,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,29 +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 }
lock : { type: Object, index: true }
}, { versionKey: false });
HomebrewSchema.statics.increaseView = async function(query) {
@@ -43,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';});
@@ -63,6 +65,15 @@ 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 {

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');
});
});

View File

@@ -8,7 +8,7 @@ const brewSnippetsToJSON = (menuTitle, userBrewSnippets, themeBundleSnippets=nul
const mpAsSnippets = [];
// Snippets from Themes first.
if(themeBundleSnippets) {
for (let themes of themeBundleSnippets) {
for (const themes of themeBundleSnippets) {
if(typeof themes !== 'string') {
const userSnippets = [];
const snipSplit = themes.snippets.trim().split(textSplit).slice(1);
@@ -76,9 +76,9 @@ const yamlSnippetsToText = (yamlObj)=>{
if(typeof yamlObj == 'string') return yamlObj;
let snippetsText = '';
for (let snippet of yamlObj) {
for (let subSnippet of snippet.subsnippets) {
for (const snippet of yamlObj) {
for (const subSnippet of snippet.subsnippets) {
snippetsText = `${snippetsText}\\snippet ${subSnippet.name}\n${subSnippet.gen || ''}\n`;
}
}
@@ -116,32 +116,62 @@ const printCurrentBrew = ()=>{
}
};
const fetchThemeBundle = async (obj, renderer, theme)=>{
const fetchThemeBundle = async (setError, setThemeBundle, renderer, theme)=>{
if(!renderer || !theme) return;
const res = await request
.get(`/api/theme/${renderer}/${theme}`)
.catch((err)=>{
obj.setState({ error: err });
setError(err);
});
if(!res) {
obj.setState((prevState)=>({
...prevState,
themeBundle : {}
}));
setThemeBundle({});
return;
}
const themeBundle = res.body;
themeBundle.joinedStyles = themeBundle.styles.map((style)=>`<style>${style}</style>`).join('\n\n');
obj.setState((prevState)=>({
...prevState,
themeBundle : themeBundle,
error : null
}));
setThemeBundle(themeBundle);
setError(null);
};
const debugTextMismatch = (clientTextRaw, serverTextRaw, label)=>{
const clientText = clientTextRaw?.normalize('NFC') || '';
const serverText = serverTextRaw?.normalize('NFC') || '';
const clientBuffer = Buffer.from(clientText, 'utf8');
const serverBuffer = Buffer.from(serverText, 'utf8');
if(clientBuffer.equals(serverBuffer)) {
console.log(`${label} text matches byte-for-byte.`);
return;
}
console.warn(`${label} text mismatch detected.`);
console.log(`Client length: ${clientBuffer.length}`);
console.log(`Server length: ${serverBuffer.length}`);
// Byte-level diff
for (let i = 0; i < Math.min(clientBuffer.length, serverBuffer.length); i++) {
if(clientBuffer[i] !== serverBuffer[i]) {
console.log(`Byte mismatch at offset ${i}: client=0x${clientBuffer[i].toString(16)} server=0x${serverBuffer[i].toString(16)}`);
break;
}
}
// Char-level diff
for (let i = 0; i < Math.min(clientText.length, serverText.length); i++) {
if(clientText[i] !== serverText[i]) {
console.log(`Char mismatch at index ${i}:`);
console.log(` Client: '${clientText[i]}' (U+${clientText.charCodeAt(i).toString(16).toUpperCase()})`);
console.log(` Server: '${serverText[i]}' (U+${serverText.charCodeAt(i).toString(16).toUpperCase()})`);
break;
}
}
};
export {
splitTextStyleAndMetadata,
printCurrentBrew,
fetchThemeBundle,
brewSnippetsToJSON
brewSnippetsToJSON,
debugTextMismatch
};

View File

@@ -38,15 +38,6 @@
animation-duration : 0.4s;
}
.CodeMirror-vscrollbar {
&::-webkit-scrollbar { width : 20px; }
&::-webkit-scrollbar-thumb {
width : 20px;
background : linear-gradient(90deg, #858585 15px, #808080 15px);
}
}
//.cm-tab {
// background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAQAAACOs/baAAAARUlEQVR4nGJgIAG8JkXxUAcCtDWemcGR1lY4MvgzCEKY7jSBjgxBDAG09UEQzAe0AMwMHrSOAwEGRtpaMIwAAAAA//8DAG4ID9EKs6YqAAAAAElFTkSuQmCC) no-repeat right;
//}

View File

@@ -4,12 +4,13 @@ import _ from 'lodash';
import { Parser as MathParser } from 'expr-eval';
import { marked as Marked } from 'marked';
import MarkedExtendedTables from 'marked-extended-tables';
import MarkedDefinitionLists from 'marked-definition-lists';
import MarkedAlignedParagraphs from 'marked-alignment-paragraphs';
import MarkedNonbreakingSpaces from 'marked-nonbreaking-spaces';
import MarkedSubSuperText from 'marked-subsuper-text';
import { markedSmartypantsLite as MarkedSmartypantsLite } from 'marked-smartypants-lite';
import { gfmHeadingId as MarkedGFMHeadingId, resetHeadings as MarkedGFMResetHeadingIDs } from 'marked-gfm-heading-id';
import { markedEmoji as MarkedEmojis } from 'marked-emoji';
import MarkedAlignedParagraphs from 'marked-alignment-paragraphs';
import MarkedNonbreakingSpaces from 'marked-nonbreaking-spaces';
import MarkedSubSuperText from 'marked-subsuper-text';
import { romanize } from 'romans';
import writtenNumber from 'written-number';
@@ -184,7 +185,7 @@ const mustacheSpans = {
start(src) { return src.match(/{{[^{]/)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const completeSpan = /^{{[^\n]*}}/; // Regex for the complete token
const inlineRegex = /{{(?=((?:[:=](?:"['\w,\-()#%=?. ]*"|[\w\-()#%.]*)|[^"=':{}\s]*)*))\1 *|}}/g;
const inlineRegex = /{{(?=((?:[:=](?:"['\w,\-+*/()#%=?. ]*"|[\w\-+*/()#%.]*)|[^"=':{}\s]*)*))\1 *|}}/g;
const match = completeSpan.exec(src);
if(match) {
//Find closing delimiter
@@ -241,7 +242,7 @@ const mustacheDivs = {
start(src) { return src.match(/\n *{{[^{]/m)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const completeBlock = /^ *{{[^\n}]* *\n.*\n *}}/s; // Regex for the complete token
const blockRegex = /^ *{{(?=((?:[:=](?:"['\w,\-()#%=?. ]*"|[\w\-()#%.]*)|[^"=':{}\s]*)*))\1 *$|^ *}}$/gm;
const blockRegex = /^ *{{(?=((?:[:=](?:"['\w,\-+*/()#%=?. ]*"|[\w\-+*/()#%.]*)|[^"=':{}\s]*)*))\1 *$|^ *}}$/gm;
const match = completeBlock.exec(src);
if(match) {
//Find closing delimiter
@@ -296,7 +297,7 @@ const mustacheInjectInline = {
level : 'inline',
start(src) { return src.match(/ *{[^{\n]/)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const inlineRegex = /^ *{(?=((?:[:=](?:"['\w,\-()#%=?. ]*"|[\w\-()#%.]*)|[^"=':{}\s]*)*))\1}/g;
const inlineRegex = /^ *{(?=((?:[:=](?:"['\w,\-+*/()#%=?. ]*"|[\w\-+*/()#%.]*)|[^"=':{}\s]*)*))\1}/g;
const match = inlineRegex.exec(src);
if(match) {
const lastToken = tokens[tokens.length - 1];
@@ -342,7 +343,7 @@ const mustacheInjectBlock = {
level : 'block',
start(src) { return src.match(/\n *{[^{\n]/m)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const inlineRegex = /^ *{(?=((?:[:=](?:"['\w,\-()#%=?. ]*"|[\w\-()#%.]*)|[^"=':{}\s]*)*))\1}/ym;
const inlineRegex = /^ *{(?=((?:[:=](?:"['\w,\-+*/()#%=?. ]*"|[\w\-+*/()#%.]*)|[^"=':{}\s]*)*))\1}/ym;
const match = inlineRegex.exec(src);
if(match) {
const lastToken = tokens[tokens.length - 1];
@@ -410,93 +411,6 @@ const forcedParagraphBreaks = {
}
};
const definitionListsSingleLine = {
name : 'definitionListsSingleLine',
level : 'block',
start(src) { return src.match(/\n[^\n]*?::[^\n]*/m)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const regex = /^([^\n]*?)::([^\n]*)(?:\n|$)/ym;
let match;
let endIndex = 0;
const definitions = [];
while (match = regex.exec(src)) {
const originalLine = match[0]; // This line and below to handle conflict with emojis
let firstLine = originalLine; // Remove in V4 when definitionListsInline updated to
this.lexer.inlineTokens(firstLine.trim()) // require spaces around `::`
.filter((t)=>t.type == 'emoji')
.map((emoji)=>firstLine = firstLine.replace(emoji.raw, 'x'.repeat(emoji.raw.length)));
const newMatch = /^([^\n]*?)::([^\n]*)(?:\n|$)/ym.exec(firstLine);
if(newMatch) {
definitions.push({
dt : this.lexer.inlineTokens(originalLine.slice(0, newMatch[1].length).trim()),
dd : this.lexer.inlineTokens(originalLine.slice(newMatch[1].length + 2).trim())
});
} // End of emoji hack.
endIndex = regex.lastIndex;
}
if(definitions.length) {
return {
type : 'definitionListsSingleLine',
raw : src.slice(0, endIndex),
definitions
};
}
},
renderer(token) {
return `<dl>${token.definitions.reduce((html, def)=>{
return `${html}<dt>${this.parser.parseInline(def.dt)}</dt>`
+ `<dd>${this.parser.parseInline(def.dd)}</dd>\n`;
}, '')}</dl>`;
}
};
const definitionListsMultiLine = {
name : 'definitionListsMultiLine',
level : 'block',
start(src) { return src.match(/\n[^\n]*\n::[^:\n]/m)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const regex = /(\n?\n?(?!::)[^\n]+?(?=\n::[^:\n]))|\n::([^:\n](?:.|\n)*?(?=(?:\n::)|(?:\n\n)|$))/y;
let match;
let endIndex = 0;
const definitions = [];
while (match = regex.exec(src)) {
if(match[1]) {
if(this.lexer.blockTokens(match[1].trim())[0]?.type !== 'paragraph') // DT must not be another block-level token besides <p>
break;
definitions.push({
dt : this.lexer.inlineTokens(match[1].trim()),
dds : []
});
}
if(match[2] && definitions.length) {
definitions[definitions.length - 1].dds.push(
this.lexer.inlineTokens(match[2].trim().replace(/\s/g, ' '))
);
}
endIndex = regex.lastIndex;
}
if(definitions.length) {
return {
type : 'definitionListsMultiLine',
raw : src.slice(0, endIndex),
definitions
};
}
},
renderer(token) {
let returnVal = `<dl>`;
token.definitions.forEach((def)=>{
const dds = def.dds.map((s)=>{
return `\n<dd>${this.parser.parseInline(s).trim()}</dd>`;
}).join('');
returnVal += `<dt>${this.parser.parseInline(def.dt)}</dt>${dds}\n`;
});
returnVal = returnVal.trim();
return `${returnVal}</dl>`;
}
};
//v=====--------------------< Variable Handling >-------------------=====v// 242 lines
const replaceVar = function(input, hoist=false, allowUnresolved=false) {
const regex = /([!$]?)\[((?!\s*\])(?:\\.|[^\[\]\\])+)\]/g;
@@ -521,7 +435,7 @@ const replaceVar = function(input, hoist=false, allowUnresolved=false) {
try {
return mathParser.evaluate(replacedLabel);
} catch (error) {
} catch {
return undefined; // Return undefined if invalid math result
}
}
@@ -765,8 +679,8 @@ const tableTerminators = [
];
Marked.use(MarkedVariables());
Marked.use({ extensions : [definitionListsMultiLine, definitionListsSingleLine, forcedParagraphBreaks,
mustacheSpans, mustacheDivs, mustacheInjectInline] });
Marked.use(MarkedDefinitionLists());
Marked.use({ extensions: [forcedParagraphBreaks, mustacheSpans, mustacheDivs, mustacheInjectInline] });
Marked.use(mustacheInjectBlock);
Marked.use(MarkedAlignedParagraphs());
Marked.use(MarkedSubSuperText());
@@ -911,7 +825,7 @@ const Markdown = {
MarkedGFMResetHeadingIDs();
}
rawBrewText = rawBrewText.replace(/^\\column$/gm, `\n<div class='columnSplit'></div>\n`);
rawBrewText = rawBrewText.replace(/^\\column(?:break)?$/gm, `\n<div class='columnSplit'></div>\n`);
const opts = Marked.defaults;

View File

@@ -49,7 +49,7 @@ const cleanUrl = function (sanitize, base, href) {
prot = decodeURIComponent(unescape(href))
.replace(nonWordAndColonTest, '')
.toLowerCase();
} catch (e) {
} catch {
return null;
}
if(prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) {
@@ -58,7 +58,7 @@ const cleanUrl = function (sanitize, base, href) {
}
try {
href = encodeURI(href).replace(/%25/g, '%');
} catch (e) {
} catch {
return null;
}
return href;

View File

@@ -4,6 +4,17 @@ require('jsdom-global')();
import { safeHTML } from '../../client/homebrew/brewRenderer/safeHTML';
test('Exit if no document', function() {
const doc = document;
document = undefined;
const result = safeHTML('');
document = doc;
expect(result).toBe(null);
});
test('Javascript via href', function() {
const source = `<a href="javascript:alert('This is a JavaScript injection via href attribute')">Click me</a>`;
const rendered = safeHTML(source);

View File

@@ -52,7 +52,7 @@ body { counter-reset : page-numbers 0; }
width : 215.9mm;
height : 279.4mm;
padding : 1.4cm 1.9cm 1.7cm;
overflow : hidden;
overflow : clip;
background-color : var(--HB_Color_Background);
text-rendering : optimizeLegibility;
contain : strict;
@@ -611,3 +611,17 @@ h6,
}
.toc.wide li { break-inside : auto; }
}
/**********************************
Firefox endruns
**********************************/
@supports (-moz-user-select: none) { // This section will only apply to Firefox; it's the only browser that supports `-mos-xyz...`
.page {
blockquote, table {
page-break-inside: auto;
break-inside: auto;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

View File

@@ -0,0 +1,6 @@
{
"name" : "UnearthedArcana",
"renderer" : "V3",
"baseTheme" : false,
"baseSnippets" : false
}

View File

@@ -0,0 +1,38 @@
@import (less) './themes/fonts/5e/fonts.less';
@import (less) './themes/assets/assets.less';
:root {
//Colors
--HB_Color_Background : #FFFFFF; // White
--HB_Color_WatercolorStain : #000000; // Black
}
.page {
font-family: Cambria,Georgia,serif;
font-size: 14px;
h1, h2, h3, h4 {
font-variant: small-caps;
font-weight: normal;
}
h1 {
column-span: all;
-webkit-column-span: all;
font-size: 40px;
}
h2 {
font-size: 26px;
}
h3 {
font-size: 20px;
border-bottom: 2px solid black;
}
h4 {
font-size: 18px;
}
h5 {
font-size: 16px;
}
h6 {
font-size: 14px;
}
}

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 94.65 94.6"
version="1.1"
id="svg11"
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/">
<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">
<g
id="base"
style="fill:#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" />
</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: 1.8 KiB

View File

@@ -1,129 +0,0 @@
/* Main BG color and normal text color */
.CodeMirror {
background: #293134;
color: #91A6AA;
}
/* Brew BG */
.brewRenderer {
background-color: #293134;
}
/* Blinking cursor */
.CodeMirror-cursor {
border-left: 1px solid #e0e2e4;
}
/* HB DARK NAV START*/
/* Bars at the top */
.snippetBar {
background-color: #2F393C;
color: white;
}
nav {
background-color: #293134;
}
nav .navItem {
background-color: #293134;
}
/* Fix for Homebrewery custom Snippet icons */
.snippetBar .fac {
filter: invert(1);
}
.snippetBar .snippetGroup .dropdown {
background-color: #2F393C;
}
/* HB DARK NAV END */
/* Line number stuff */
.CodeMirror-gutter-elt {
color: #81969A;
}
.CodeMirror-linenumber {
background-color: #293134;
}
.CodeMirror-gutter {
background-color: #293134;
}
/* column splits */
.editor .codeEditor .columnSplit {
font-style: italic;
color: inherit;
background-color:#1f5763;
border-bottom: #299 solid 1px;
}
/* Colors for headings and such */
/* ###Headings */
.cm-s-default .cm-header {
color: #c51b1b;
-webkit-text-stroke-width: 0.1px;
-webkit-text-stroke-color: #000;
}
/* bold points */
.cm-header, .cm-strong {
font-weight: bold;
color: #309dd2;
}
/* Link headings */
.cm-s-default .cm-link {
color: #dd6300;
}
/* links */
.cm-s-default .cm-string {
color: #aa8261;
}
/*@import*/
.cm-s-default .cm-def {
color:#2986cc;
}
/* Bullets and such */
.cm-s-default .cm-variable-2 {
color: #3cbf30;
}
/* blocks */
.editor .codeEditor .block:not(.cm-comment) {
color: #e3e3e3;
}
/* inline blocks */
.editor .codeEditor .inline-block {
color: #e3e3e3;
}
/* Tags (divs) */
.cm-s-default .cm-tag {
color: #e3ff00;
}
.cm-s-default .cm-attribute {
color: #e3ff00;
}
.cm-s-default .cm-atom {
color:#000;
}
.cm-s-default .cm-qualifier{
color:#ee1919;
}
.cm-s-default .cm-comment{
color:#bbc700;
}
.cm-s-default .cm-keyword {
color:#c302df;
background-color:#b1b1b1;
}
.cm-s-default .cm-property.cm-error {
color:#c50202;
}
.CodeMirror-foldmarker {
color:#f0ff00;
}
/* New page */
.editor .codeEditor .pageLine {
background: #000;
color:#000;
border-bottom: 1px solid #fff;
}
.cm-s-default .cm-builtin {
color:#fff;
}

View File

@@ -0,0 +1,134 @@
/*stylelint-disable*/
.editor .snippetBar {
color: white;
background-color: #2F393C;
.dropdown {
background-color: #2F393C;
}
.editors {
border-color: #ccc;
}
}
/* Main BG color and normal text color */
.CodeMirror {
--bg: #293134;
--highlight: #bcbcbc;
color: #91A6AA;
background: var(--bg);
.CodeMirror-scroll {
.CodeMirror-gutters {
border-right: 1px solid #555;
background: var(--bg);
.CodeMirror-gutter {
background-color: var(--bg);
&.CodeMirror-foldgutter {
cursor: pointer;
border-left: 1px solid #555;
transition: background 0.1s;
&:hover {
background: #555;
}
}
}
}
.CodeMirror-lines {
/* Line numbers*/
.CodeMirror-linenumber.CodeMirror-gutter-elt {
background-color: var(--bg);
color: #81969A;
}
/* Blinking cursor */
.CodeMirror-cursor {
border-left: 1px solid #E0E2E4;
}
.pageLine {
color: #000000;
background: #000000;
border-bottom: 1px solid #FFFFFF;
}
.CodeMirror-code .CodeMirror-line {
&.columnSplit {
font-style: italic;
color: inherit;
background-color: #1F5763;
border-bottom: #229999 solid 1px;
}
/*syntax*/
.cm-header {
font-weight: bold;
color: #C51B1B;
-webkit-text-stroke-width: 0.1px;
-webkit-text-stroke-color: #000000;
}
.cm-strong {
color: #309DD2;
}
.cm-em {
/*italics*/
}
.cm-link {
color: #DD6300;
}
.cm-string {
color: #AA8261;
}
/* @import */
.cm-def {
color: #2986CC;
}
/* Bullets and such */
.cm-variable-2 {
color: #3CBF30;
}
.block:not(.cm-comment) {
color: #E3E3E3;
}
.inline-block {
color: #E3E3E3;
}
.cm-tag {
color: #E3FF00;
}
.cm-attribute {
color: #E3FF00;
}
.cm-atom {
color: #c1939a;
}
.cm-number {
color: #2986CC;
}
.cm-property:not(.cm-error) ~ .cm-variable {
color:#9e1f9e;
}
.cm-qualifier {
color: #EE1919;
}
.cm-comment {
color: #BBC700;
}
.cm-keyword {
color: white;
}
.cm-error {
color: #C50202;
}
.CodeMirror-foldmarker {
color: #F0FF00;
}
.cm-builtin {
color: #FFFFFF;
}
.dt-highlight {
background: #ffffff14;
}
.dl-colon-highlight {
background: #ccc;
}
.dl-highlight.dd-highlight {
color: #b5858d;
}
}
}
}
}

View File

@@ -15,7 +15,7 @@
"cobalt",
"colorforth",
"darcula",
"darkbrewery-v301",
"darkbrewery",
"darkvision",
"dracula",
"duotone-dark",

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,2 +1,10 @@
@import (less) "./themes/fonts/iconFonts/fontawesome-free.less";
@import (less) "./themes/fonts/iconFonts/fontawesome-solid.less";
@import (less) "./themes/fonts/iconFonts/fontawesome-brands.less";
@import (less) "./themes/fonts/iconFonts/fontawesome-regular.less";
/* Icon Font: Font Awesome */
.far,.fas,.fab { display : inline; }
.far,.fas,.fab { display : inline; }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,19 @@
/*!
* Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2024 Fonticons, Inc.
*/
:root, :host {
--fa-style-family-classic: 'Font Awesome 6 Free';
--fa-font-regular: normal 400 1em/1 'Font Awesome 6 Free'; }
@font-face {
font-family: 'Font Awesome 6 Free';
font-style: normal;
font-weight: 400;
font-display: block;
src: url("/fonts/iconFonts/fa-regular-400.woff2") format("woff2"), url("/fonts/iconFonts/fa-regular-400.ttf") format("truetype"); }
.far,
.fa-regular {
font-weight: 400; }

View File

@@ -0,0 +1,19 @@
/*!
* Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2024 Fonticons, Inc.
*/
:root, :host {
--fa-style-family-classic: 'Font Awesome 6 Free';
--fa-font-solid: normal 900 1em/1 'Font Awesome 6 Free'; }
@font-face {
font-family: 'Font Awesome 6 Free';
font-style: normal;
font-weight: 900;
font-display: block;
src: url("/fonts/iconFonts/fa-solid-900.woff2") format("woff2"), url("/fonts/iconFonts/fa-solid-900.ttf") format("truetype"); }
.fas,
.fa-solid {
font-weight: 900; }

View File

@@ -35,6 +35,13 @@
"baseTheme": "Blank",
"baseSnippets": "5ePHB",
"path": "Journal"
},
"UnearthedArcana": {
"name": "UnearthedArcana",
"renderer": "V3",
"baseTheme": false,
"baseSnippets": false,
"path": "UnearthedArcana"
}
}
}