diff --git a/.circleci/config.yml b/.circleci/config.yml index 274ec25ac..f18f84943 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,7 +10,7 @@ orbs: jobs: build: docker: - - image: cimg/node:20.8.0 + - image: cimg/node:20.17.0 - image: mongo:4.4 working_directory: ~/homebrewery @@ -27,7 +27,7 @@ jobs: # fallback to using the latest cache if no exact match is found - v1-dependencies- - - run: sudo npm install -g npm@10.2.0 + - run: sudo npm install -g npm@10.8.2 - node/install-packages: app-dir: ~/homebrewery cache-path: node_modules @@ -45,7 +45,7 @@ jobs: test: docker: - - image: cimg/node:20.8.0 + - image: cimg/node:20.17.0 working_directory: ~/homebrewery parallelism: 1 diff --git a/.gitattributes b/.gitattributes index 20eac6017..2f90f4172 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,3 @@ -package-lock.json binary \ No newline at end of file +package-lock.json binary + +*.json text eol=lf \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..b02835726 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,36 @@ + + +## Description + + +## Related Issues or Discussions + +- Closes # + +## QA Instructions, Screenshots, Recordings + +_Please replace this line with instructions on how to test or view your changes, as well as any before/after +images for UI changes._ + +### Reviewer Checklist + +_Please replace the list below with specific features you want reviewers to look at._ + +*Reviewers, refer to this list when testing features, or suggest new items * +- [ ] Verify new features are functional + - [ ] Feature A does X + - [ ] Feature B does Y +- [ ] Verify old features have not broken + - [ ] Feature Z can still be used +- [ ] Test for edge cases / try to break things + - [ ] Feature A handles negative numbers +- [ ] Identify opportunities for simplification and refactoring +- [ ] Check for code legibility and appropriate comments + +
Copy this list diff --git a/changelog.md b/changelog.md index 033eee86d..1f7815d8d 100644 --- a/changelog.md +++ b/changelog.md @@ -81,12 +81,184 @@ pre { } ``` + ## changelog For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery). -### Tuesday 8/13/2024 - v3.14.1 -{{taskList +### Saturday 10/12/2024 - v3.16.0 +{{taskList +##### 5e-Cleric + +* [x] Added a new API endpoint `/metadata/:shareId` to fetch metadata about individual brews + +Fixes issue [#2638](https://github.com/naturalcrit/homebrewery/issues/2638) + +* [x] Added A3, A5, and Card page size snippets under {{openSans **:fas_paintbrush: STYLE TAB :fas_arrow_right: :fas_print: PRINT**}} + +* [x] Adjust navbar styling for very long titles + +Fixes issue [#2071](https://github.com/naturalcrit/homebrewery/issues/2071) + +* [x] Added some sorting options to the {{openSans **VAULT** {{fas,fa-dungeon}}}} page + +* [x] Fix `language` property not working in share page + +Fixes issue [#3776](https://github.com/naturalcrit/homebrewery/issues/3776) + +##### abquintic + +* [x] New {{openSans **:fas_pencil: TEXT EDITOR :fas_arrow_right: :fas_bookmark: PAGE NUMBER :fas_arrow_right:**}} +{{openSans **:fas_xmark: SKIP PAGE NUMBER**}} and {{openSans **:fas_arrow_rotate_left: RESTART PAGE NUMBER**}} snippets for more control over automatic page numbering. + +Fixes issue [#513](https://github.com/naturalcrit/homebrewery/issues/513) + +* [x] New Table of Contents control options via {{openSans **:fas_pencil: TEXT EDITOR :fas_arrow_right: :fas_book: TABLE OF CONTENTS**}} submenus. By default, H1-H3 is included in the ToC generation, but the new options allow marking `{{blocks}}` to include or exclude specific or ranges of contained headers. Also, a global option to increase the default range of H1-H3 to H1-H4/5/6. After applying these markers, you must regenerate the Table of Contents to see the changes. + +* [x] Added a ":fas_lock: SYNC VIEWS" button onto the divider bar. When locked, scrolling on either panel will sync the other panel to the same page. + +Fixes issue [#241](https://github.com/naturalcrit/homebrewery/issues/241) + +##### Gazook89 + +* [x] Added a :fas_glasses: HIDE button to the page navigation bar + +##### G-Ambatte + +* [x] Automatic local backups of your files, in case of accidental data loss. Stores up to 5 snapshots of each brew edited in your browser, incrementing from a few minutes old to a maximum of several days. Restore a backup by clicking an entry in the new {{openSans **:fas_clock_rotate_left: HISTORY**}} button in the snippet bar. + +Fixes issue [#3070](https://github.com/naturalcrit/homebrewery/issues/3070) + +* [x] Fix issue with legacy brews breaking on Share page + +Fixes issue [#3764](https://github.com/naturalcrit/homebrewery/issues/3764) + +* [x] Fix print size when printing a zoomed document + +Fixes issue [#3744](https://github.com/naturalcrit/homebrewery/issues/3744) + +##### All + +* [x] Background code cleanup, security fixes, dev tool improvements, dependency updates, prep for upcoming features, etc. +}} + +### Wednesday 9/25/2024 - v3.15.1 + +{{taskList +##### calculuschild + +* [x] Background fixes to handle Google Drive issues + +* [x] Remove duplicate error logging + +##### calculuschild, 5e-Cleric + +* [x] Fix links in {{openSans **RECENT BREWS :fas_clock_rotate_left:**}} and user {{openSans **BREWS :fas_beer_mug_empty:**}} pointing to trashed Google Drive files after transferring from Google to Homebrewery storage + +Fixes issue [#3776](https://github.com/naturalcrit/homebrewery/issues/3776) +}} + +\page + +### Wednesday 9/04/2024 - v3.15.0 + +{{taskList +##### 5e-Cleric, abquintic, calculuschild, Gazook89, G-Ambatte, Ericsheid, Kaiburr + +* [x] New {{openSans **VAULT** {{fas,fa-dungeon}}}} page 🎉🎉🎉 +: +All **PUBLISHED** brews ({{openSans :fas_circle_info: **Properties**}} menu) will be searchable, by title or author, and filtered by renderer. More features and adjustments will be coming. +: +Note: If any of your own brews are not showing up in search (particularly if stored on Google Drive), please edit and re-save to ensure our database has the data needed from document to be searchable. + +Fixes issue [#697](https://github.com/naturalcrit/homebrewery/issues/697) + +##### Gazook89 + +* [x] Auto-focus on text editor when switching editor tabs +}} + +### Wednesday 8/28/2024 - v3.14.3 + +{{taskList +##### calculuschild, G-Ambatte + +* [x] New {{openSans **IMAGES → {{fac,image-wrap-left}} IMAGE WRAP LEFT/RIGHT**}} snippets + +Fixes issue [#380](https://github.com/naturalcrit/homebrewery/issues/380) + +* [x] Fix v3.14.2 bug with `꞉꞉꞉꞉` failing after tables + +##### 5e-Cleric + +* [x] Fix Account page crash when not logged in + +Fixes issue [#3605](https://github.com/naturalcrit/homebrewery/issues/3605) + +##### abquintic + +* [x] Fix jump hotkeys conflicting with `CTRL + SHIFT`. Preview and Source movement shortcuts now use `CTRL + SHIFT + META + LEFT\RIGHTARROW` + +##### G-Ambatte + +* [x] Fix display issue with image wrap icons +}} + + +### Tuesday 8/27/2024 - v3.14.2 + +{{taskList +##### calculuschild + +* [x] Reroute invalid urls to homepage + +Fixes issues [#3269](https://github.com/naturalcrit/homebrewery/issues/3629) + +* [x] Background dependency updates + +##### G-Ambatte + +* [x] Add route to get brew styling via `/css/shareId` + +Fixes issues [#1097](https://github.com/naturalcrit/homebrewery/issues/1097) + +* [x] Fix `:emojis:` preventing code folding + +Fixes issues [#3604](https://github.com/naturalcrit/homebrewery/issues/3604) + +* [x] Fix mask image warping when rotated and stretched + +Fixes issues [#3636](https://github.com/naturalcrit/homebrewery/issues/3636) + +* [x] Fix Table of Contents uppercasing + +Fixes issues [#3572](https://github.com/naturalcrit/homebrewery/issues/3572) + +##### abquintic + +* [x] Create globally unique Header IDs across pages + +Fixes issues [#1430](https://github.com/naturalcrit/homebrewery/issues/1430) + +* [x] Fix colon `꞉꞉꞉꞉` being parsed in codeblocks + +* [x] Prevent crashes when loading undefined renderer or theme bundle + +* [x] Add Jump-To hotkeys + + * Use `CTRL/META + SHIFT + LEFTARROW` to brewJump + * Use `CTRL/META + SHIFT + RIGHTARROW` to sourceJump + +* [x] Prevent reload from clobbering modified fresh clones + +##### 5e-Cleric, Gazook89 + +* [x] Viewer tools for zoom/page navigation +}} + +### Tuesday 8/13/2024 - v3.14.1 + +{{taskList ##### abquintic * [x] Allow Table of Contents to flow across columns @@ -129,16 +301,13 @@ Fixes issues [#3613](https://github.com/naturalcrit/homebrewery/issues/3613) Fixes issues [#3622](https://github.com/naturalcrit/homebrewery/issues/3622) - ##### calculuschild * [x] Fix `/migrate` page using an editor context instead of share context - ##### 5e-Cleric * [x] Fix Monster Stat Blocks losing color in Safari - }} \page @@ -509,7 +678,7 @@ Fixes issue [#2729](https://github.com/naturalcrit/homebrewery/issues/2729), ### Thursday 17/08/2023 - v3.9.2 {{taskList -##### Calculuschild +##### calculuschild * [x] Fix links to certain old Google Drive files @@ -567,7 +736,7 @@ Fixes issue [#1924](https://github.com/naturalcrit/homebrewery/issues/1924) ### Friday 02/06/2023 - v3.9.0 {{taskList -##### Calculuschild +##### calculuschild * [x] Fix some files not showing up on userpage when user has a large number of brews in Google Drive @@ -664,7 +833,7 @@ Fixes issues [#2731](https://github.com/naturalcrit/homebrewery/issues/2731) ### Monday 13/03/2023 - v3.7.2 {{taskList -##### Calculuschild +##### calculuschild * [x] Fix wide Monster Stat Blocks not spanning columns on Legacy }} @@ -687,7 +856,7 @@ Fixes issues [#1569](https://github.com/naturalcrit/homebrewery/issues/1569) * [x] Updated the Google Drive icon * [x] Backend fix to unit tests failing intermittently -##### Calculuschild +##### calculuschild * [x] Fix PDF pixelation on CoverPage text outlines }} @@ -699,7 +868,7 @@ Fixes issues [#1569](https://github.com/naturalcrit/homebrewery/issues/1569) **NOTE:** Some new snippets will now show a {{beta BETA}} tag. Feel free to use them, but be aware we may change how they work depending on your feedback. }} -##### Calculuschild +##### calculuschild * [x] New {{openSans **IMAGES → WATERCOLOR EDGE** {{fac,mask-edge}} }} and {{openSans **WATERCOLOR CORNER** {{fac,mask-corner}} }} snippets for V3, which adds a stylish watercolor texture to the edge of your images! (Thanks to /u/flamableconcrete on Reddit for providing these image masks!) @@ -843,7 +1012,7 @@ Fixes issues [#1670](https://github.com/naturalcrit/homebrewery/issues/1670) ### Thursday 28/10/2022 - v3.3.1 {{taskList -##### Calculuschild +##### calculuschild * [x] Fixes to several broken CSS styles from v3.3.0 @@ -858,7 +1027,7 @@ Fixes issues [#2468](https://github.com/naturalcrit/homebrewery/issues/2468) ### Friday 19/10/2022 - v3.3.0 {{taskList -##### Calculuschild +##### calculuschild * [x] Fix for tables broken by Chrome v106 @@ -941,7 +1110,7 @@ Fixes issues [#2317](https://github.com/naturalcrit/homebrewery/issues/2317), [ ### Wednesday 31/08/2022 - v3.2.1 {{taskList -##### Calculuschild +##### calculuschild * [x] Reference Links should now work inside tables @@ -967,7 +1136,7 @@ Fixes issues [#2317](https://github.com/naturalcrit/homebrewery/issues/2317), [ ### Saturday 27/08/2022 - v3.2.0 {{taskList -##### Calculuschild +##### calculuschild * [x] The V3 renderer is now the default for new brews. @@ -994,7 +1163,7 @@ Fixes issues [#2317](https://github.com/naturalcrit/homebrewery/issues/2317), [ ### Thursday 09/06/2022 - v3.1.1 {{taskList -##### Calculuschild: +##### calculuschild: * [x] Fixed class table decorations appearing on top of the table in PDF output. @@ -1838,4 +2007,4 @@ Massive changelog incoming: * Added `phb.standalone.css` plus a build system for creating it * Added page numbers and footer text -* Page accent now flips each page \ No newline at end of file +* Page accent now flips each page diff --git a/client/admin/admin.jsx b/client/admin/admin.jsx index 92e0b2aee..f2f2667a4 100644 --- a/client/admin/admin.jsx +++ b/client/admin/admin.jsx @@ -2,35 +2,44 @@ require('./admin.less'); const React = require('react'); const createClass = require('create-react-class'); +const BrewUtils = require('./brewUtils/brewUtils.jsx'); +const NotificationUtils = require('./notificationUtils/notificationUtils.jsx'); -const BrewCleanup = require('./brewCleanup/brewCleanup.jsx'); -const BrewLookup = require('./brewLookup/brewLookup.jsx'); -const BrewCompress = require ('./brewCompress/brewCompress.jsx'); -const Stats = require('./stats/stats.jsx'); +const tabGroups = ['brew', 'notifications']; const Admin = createClass({ getDefaultProps : function() { return {}; }, + getInitialState : function(){ + return ({ + currentTab : 'brew' + }); + }, + + handleClick : function(newTab){ + if(this.state.currentTab === newTab) return; + this.setState({ + currentTab : newTab + }); + }, + render : function(){ return
-
homebrewery admin
-
- -
- -
- -
- -
+
+ + {this.state.currentTab==='brew' && } + {this.state.currentTab==='notifications' && } +
; } }); diff --git a/client/admin/admin.less b/client/admin/admin.less index a61335835..c6c9b4662 100644 --- a/client/admin/admin.less +++ b/client/admin/admin.less @@ -6,39 +6,95 @@ @import 'font-awesome/css/font-awesome.css'; -html,body, #reactContainer, .naturalCrit{ - min-height : 100%; -} +html,body, #reactContainer, .naturalCrit { min-height : 100%; } @sidebarWidth : 250px; -body{ - background-color : #eee; - font-family : 'Open Sans', sans-serif; - color : #4b5055; - font-weight : 100; - text-rendering : optimizeLegibility; - margin : 0; +body { + height : 100%; padding : 0; - height : 100%; + margin : 0; + font-family : 'Open Sans', sans-serif; + font-weight : 100; + color : #4B5055; + background-color : #EEEEEE; + text-rendering : optimizeLegibility; } -.admin{ +:where(.admin) { - header{ + header { + padding : 20px 0px; + margin-bottom : 30px; + font-size : 2em; + color : white; background-color : @red; - font-size: 2em; - padding : 20px 0px; - color : white; - margin-bottom: 30px; - i{ - margin-right: 30px; + i { margin-right : 30px; } + } + + hr { margin : 30px 0px; } + + :where(.container) { + input { + height : 33px; + padding : 0px 10px; + margin-bottom : 20px; + font-family : monospace; + } + + button { + height : 37px; + vertical-align : middle; + } + + dl { + @maxItemWidth : 132px; + dt { + float : left; + width : @maxItemWidth; + clear : left; + text-align : right; + &::after { content : ' : '; } + } + dd { + height : 1em; + padding : 0 0 0.5em 0; + margin-left : @maxItemWidth + 6px; + } + } + + .tabs button { + margin-right : 3px; + margin-left : 3px; + color : black; + background-color : #EEEEEE; + border : 1px solid #444444; + border-radius : 5px; + &:hover { + color : #EEEEEE; + background-color : #444444; + } + &.active { + margin-right : 2px; + margin-left : 2px; + text-decoration : underline; + background-color : #CCCCCC; + border : 2px solid #444444; + } + } + + .notificationUtils { + display : flex; + gap : 50px; + justify-content : space-between; } } - hr{ - margin : 30px 0px; + .error { + background: rgb(178, 54, 54); + color:white; + font-weight: 900; + margin-block:10px; + padding:10px; } - - } diff --git a/client/admin/brewCleanup/brewCleanup.less b/client/admin/brewCleanup/brewCleanup.less deleted file mode 100644 index ec7582855..000000000 --- a/client/admin/brewCleanup/brewCleanup.less +++ /dev/null @@ -1,10 +0,0 @@ -.BrewCleanup{ - .removeBox{ - margin-top: 20px; - button{ - background-color: @red; - margin-right: 10px; - } - } - -} \ No newline at end of file diff --git a/client/admin/brewCompress/brewCompress.less b/client/admin/brewCompress/brewCompress.less deleted file mode 100644 index 2a2bf42ea..000000000 --- a/client/admin/brewCompress/brewCompress.less +++ /dev/null @@ -1,10 +0,0 @@ -.BrewCompress{ - .removeBox{ - margin-top: 20px; - button{ - background-color: @red; - margin-right: 10px; - } - } - -} \ No newline at end of file diff --git a/client/admin/brewLookup/brewLookup.less b/client/admin/brewLookup/brewLookup.less deleted file mode 100644 index 61eeec424..000000000 --- a/client/admin/brewLookup/brewLookup.less +++ /dev/null @@ -1,30 +0,0 @@ - -.brewLookup{ - input{ - height : 33px; - margin-bottom : 20px; - padding : 0px 10px; - font-family : monospace; - } - button{ - vertical-align : middle; - height : 37px; - } - dl{ - @maxItemWidth : 132px; - dt{ - float : left; - clear : left; - width : @maxItemWidth; - text-align : right; - &::after { - content: " : "; - } - } - dd{ - height : 1em; - margin-left : @maxItemWidth + 6px; - padding : 0 0 0.5em 0; - } - } -} \ No newline at end of file diff --git a/client/admin/brewCleanup/brewCleanup.jsx b/client/admin/brewUtils/brewCleanup/brewCleanup.jsx similarity index 100% rename from client/admin/brewCleanup/brewCleanup.jsx rename to client/admin/brewUtils/brewCleanup/brewCleanup.jsx diff --git a/client/admin/brewUtils/brewCleanup/brewCleanup.less b/client/admin/brewUtils/brewCleanup/brewCleanup.less new file mode 100644 index 000000000..16fc98957 --- /dev/null +++ b/client/admin/brewUtils/brewCleanup/brewCleanup.less @@ -0,0 +1,9 @@ +.BrewCleanup { + .removeBox { + margin-top : 20px; + button { + margin-right : 10px; + background-color : @red; + } + } +} \ No newline at end of file diff --git a/client/admin/brewCompress/brewCompress.jsx b/client/admin/brewUtils/brewCompress/brewCompress.jsx similarity index 100% rename from client/admin/brewCompress/brewCompress.jsx rename to client/admin/brewUtils/brewCompress/brewCompress.jsx diff --git a/client/admin/brewUtils/brewCompress/brewCompress.less b/client/admin/brewUtils/brewCompress/brewCompress.less new file mode 100644 index 000000000..8668e9280 --- /dev/null +++ b/client/admin/brewUtils/brewCompress/brewCompress.less @@ -0,0 +1,9 @@ +.BrewCompress { + .removeBox { + margin-top : 20px; + button { + margin-right : 10px; + background-color : @red; + } + } +} \ No newline at end of file diff --git a/client/admin/brewLookup/brewLookup.jsx b/client/admin/brewUtils/brewLookup/brewLookup.jsx similarity index 98% rename from client/admin/brewLookup/brewLookup.jsx rename to client/admin/brewUtils/brewLookup/brewLookup.jsx index c9212d990..50a2f2015 100644 --- a/client/admin/brewLookup/brewLookup.jsx +++ b/client/admin/brewUtils/brewLookup/brewLookup.jsx @@ -1,4 +1,3 @@ -require('./brewLookup.less'); const React = require('react'); const createClass = require('create-react-class'); const cx = require('classnames'); diff --git a/client/admin/brewUtils/brewUtils.jsx b/client/admin/brewUtils/brewUtils.jsx new file mode 100644 index 000000000..de8c29895 --- /dev/null +++ b/client/admin/brewUtils/brewUtils.jsx @@ -0,0 +1,24 @@ +const React = require('react'); +const createClass = require('create-react-class'); + + +const BrewCleanup = require('./brewCleanup/brewCleanup.jsx'); +const BrewLookup = require('./brewLookup/brewLookup.jsx'); +const BrewCompress = require ('./brewCompress/brewCompress.jsx'); +const Stats = require('./stats/stats.jsx'); + +const BrewUtils = createClass({ + render : function(){ + return <> + +
+ +
+ +
+ + ; + } +}); + +module.exports = BrewUtils; diff --git a/client/admin/stats/stats.jsx b/client/admin/brewUtils/stats/stats.jsx similarity index 100% rename from client/admin/stats/stats.jsx rename to client/admin/brewUtils/stats/stats.jsx diff --git a/client/admin/brewUtils/stats/stats.less b/client/admin/brewUtils/stats/stats.less new file mode 100644 index 000000000..b5a4612e1 --- /dev/null +++ b/client/admin/brewUtils/stats/stats.less @@ -0,0 +1,13 @@ + +.Stats { + position : relative; + + .pending { + position : absolute; + top : 0px; + left : 0px; + width : 100%; + height : 100%; + background-color : rgba(238,238,238, 0.5); + } +} \ No newline at end of file diff --git a/client/admin/notificationUtils/notificationAdd/notificationAdd.jsx b/client/admin/notificationUtils/notificationAdd/notificationAdd.jsx new file mode 100644 index 000000000..5a8ebf5d0 --- /dev/null +++ b/client/admin/notificationUtils/notificationAdd/notificationAdd.jsx @@ -0,0 +1,109 @@ +require('./notificationAdd.less'); +const React = require('react'); +const { useState, useRef } = require('react'); +const request = require('superagent'); + +const NotificationAdd = ()=>{ + const [notificationResult, setNotificationResult] = useState(null); + const [searching, setSearching] = useState(false); + const [error, setError] = useState(null); + + const dismissKeyRef = useRef(null); + const titleRef = useRef(null); + const textRef = useRef(null); + const startAtRef = useRef(null); + const stopAtRef = useRef(null); + + const saveNotification = async ()=>{ + const dismissKey = dismissKeyRef.current.value; + const title = titleRef.current.value; + const text = textRef.current.value; + const startAt = new Date(startAtRef.current.value); + const stopAt = new Date(stopAtRef.current.value); + + // Basic validation + if(!dismissKey || !title || !text || isNaN(startAt.getTime()) || isNaN(stopAt.getTime())) { + setError('All fields are required'); + return; + } + if(startAt >= stopAt) { + setError('End date must be after the start date!'); + return; + } + + const data = { + dismissKey, + title, + text, + startAt : startAt?.toISOString() ?? '', + stopAt : stopAt?.toISOString() ?? '', + }; + + try { + setSearching(true); + setError(null); + const response = await request.post('/admin/notification/add').send(data); + console.log(response.body); + + // Reset form fields + dismissKeyRef.current.value = ''; + titleRef.current.value = ''; + textRef.current.value = ''; + + setNotificationResult('Notification successfully created.'); + setSearching(false); + } catch (err) { + console.log(err.response.body.message); + setError(`Error saving notification: ${err.response.body.message}`); + setSearching(false); + } + }; + + return ( +
+

Add Notification

+ + + + + + + + + + + +
{notificationResult}
+ + + {error &&
{error}
} +
+ ); +}; + +module.exports = NotificationAdd; diff --git a/client/admin/notificationUtils/notificationAdd/notificationAdd.less b/client/admin/notificationUtils/notificationAdd/notificationAdd.less new file mode 100644 index 000000000..878da24c2 --- /dev/null +++ b/client/admin/notificationUtils/notificationAdd/notificationAdd.less @@ -0,0 +1,37 @@ +.notificationAdd { + position : relative; + display : flex; + flex-direction : column; + width : 500px; + + .field { + display : grid; + grid-template-columns : 120px 150px; + align-items : center; + justify-items : stretch; + width : 100%; + margin-bottom : 20px; + + + input { + height : 33px; + padding : 0px 10px; + margin-bottom : unset; + font-family : monospace; + } + + textarea { + width : 50ch; + min-height : 7em; + max-height : 20em; + resize : vertical; + padding : 10px; + } + } + + button { + width: 200px; + + i { margin-right : 10px; } + } +} \ No newline at end of file diff --git a/client/admin/notificationUtils/notificationLookup/notificationLookup.jsx b/client/admin/notificationUtils/notificationLookup/notificationLookup.jsx new file mode 100644 index 000000000..71f8da59c --- /dev/null +++ b/client/admin/notificationUtils/notificationLookup/notificationLookup.jsx @@ -0,0 +1,105 @@ +require('./notificationLookup.less'); + +const React = require('react'); +const { useState } = require('react'); +const request = require('superagent'); +const Moment = require('moment'); + +const NotificationDetail = ({ notification, onDelete })=>( + <> +
+
Key
+
{notification.dismissKey}
+ +
Title
+
{notification.title || 'No Title'}
+ +
Text
+
{notification.text || 'No Text'}
+ +
Created
+
{Moment(notification.createdAt).format('LLLL')}
+ +
Start
+
{Moment(notification.startAt).format('LLLL') || 'No Start Time'}
+ +
Stop
+
{Moment(notification.stopAt).format('LLLL') || 'No End Time'}
+
+ + +); + +const NotificationLookup = ()=>{ + const [searching, setSearching] = useState(false); + const [error, setError] = useState(null); + const [notifications, setNotifications] = useState([]); + + const lookupAll = async ()=>{ + setSearching(true); + setError(null); + + try { + const res = await request.get('/admin/notification/all'); + setNotifications(res.body || []); + } catch (err) { + console.log(err); + setError(`Error looking up notifications: ${err.response.body.message}`); + } finally { + setSearching(false); + } + }; + + const deleteNotification = async (dismissKey)=>{ + if(!dismissKey) return; + + const confirmed = window.confirm( + `Really delete notification ${dismissKey}?` + ); + if(!confirmed) { + console.log('Delete notification cancelled'); + return; + } + console.log('Delete notification confirm'); + try { + await request.delete(`/admin/notification/delete/${dismissKey}`); + lookupAll(); + } catch (err) { + console.log(err); + setError(`Error deleting notification: ${err.response.body.message}`); + }; + }; + + const renderNotificationsList = ()=>{ + if(error) + return
{error}
; + + if(notifications.length === 0) + return
No notifications available.
; + + return ( +
    + {notifications.map((notification)=>( +
  • +
    + {notification.title || 'No Title'} + +
    +
  • + ))} +
+ ); + }; + + return ( +
+

Check all Notifications

+ + {renderNotificationsList()} +
+ ); +}; + +module.exports = NotificationLookup; diff --git a/client/admin/notificationUtils/notificationLookup/notificationLookup.less b/client/admin/notificationUtils/notificationLookup/notificationLookup.less new file mode 100644 index 000000000..3f9b78310 --- /dev/null +++ b/client/admin/notificationUtils/notificationLookup/notificationLookup.less @@ -0,0 +1,40 @@ + +.notificationLookup { + width : 450px; + height : fit-content; + + .notificationList { + display : flex; + flex-direction : column; + max-height : 500px; + margin-block : 20px; + overflow : auto; + border : 1px solid; + border-radius : 5px; + + li { + padding : 10px; + background : #CCCCCC; + + &:nth-child(even) { background : #DDDDDD; } + &:first-child { + border-top-left-radius : 5px; + border-top-right-radius : 5px; + } + &:last-child { + border-bottom-right-radius : 5px; + border-bottom-left-radius : 5px; + } + + summary { + font-size : 20px; + font-weight : 900; + } + + dl dt{ + font-weight: 900; + } + } + } + .noNotification { margin-block : 20px; } +} \ No newline at end of file diff --git a/client/admin/notificationUtils/notificationUtils.jsx b/client/admin/notificationUtils/notificationUtils.jsx new file mode 100644 index 000000000..22ea21328 --- /dev/null +++ b/client/admin/notificationUtils/notificationUtils.jsx @@ -0,0 +1,15 @@ +const React = require('react'); + +const NotificationLookup = require('./notificationLookup/notificationLookup.jsx'); +const NotificationAdd = require('./notificationAdd/notificationAdd.jsx'); + +const NotificationUtils = ()=>{ + return ( +
+ + +
+ ); +}; + +module.exports = NotificationUtils; diff --git a/client/admin/stats/stats.less b/client/admin/stats/stats.less deleted file mode 100644 index 5337bf671..000000000 --- a/client/admin/stats/stats.less +++ /dev/null @@ -1,28 +0,0 @@ - -.Stats{ - position : relative; - .pending{ - position : absolute; - top : 0px; - left : 0px; - height : 100%; - width : 100%; - background-color : rgba(238,238,238, 0.5); - } - dl{ - @maxItemWidth : 132px; - dt{ - float : left; - clear : left; - width : @maxItemWidth; - text-align : right; - &::after { - content: " : "; - } - } - dd{ - margin : 0 0 0 @maxItemWidth + 10px; - padding : 0 0 0.5em 0; - } - } -} \ No newline at end of file diff --git a/client/homebrew/brewRenderer/brewRenderer.jsx b/client/homebrew/brewRenderer/brewRenderer.jsx index 4b82c6bc0..dba99638b 100644 --- a/client/homebrew/brewRenderer/brewRenderer.jsx +++ b/client/homebrew/brewRenderer/brewRenderer.jsx @@ -1,12 +1,13 @@ /*eslint max-lines: ["warn", {"max": 300, "skipBlankLines": true, "skipComments": true}]*/ require('./brewRenderer.less'); const React = require('react'); -const { useState, useRef, useEffect } = React; +const { useState, useRef, useCallback, useMemo } = React; const _ = require('lodash'); const MarkdownLegacy = require('naturalcrit/markdownLegacy.js'); const Markdown = require('naturalcrit/markdown.js'); const ErrorBar = require('./errorBar/errorBar.jsx'); +const ToolBar = require('./toolBar/toolBar.jsx'); //TODO: move to the brew renderer const RenderWarnings = require('homebrewery/renderWarnings/renderWarnings.jsx'); @@ -43,27 +44,29 @@ const BrewPage = (props)=>{ //v=====--------------------< Brew Renderer Component >-------------------=====v// -const renderedPages = []; +let renderedPages = []; let rawPages = []; const BrewRenderer = (props)=>{ props = { - text : '', - style : '', - renderer : 'legacy', - theme : '5ePHB', - lang : '', - errors : [], - currentEditorPage : 0, - themeBundle : {}, + text : '', + style : '', + renderer : 'legacy', + theme : '5ePHB', + lang : '', + errors : [], + currentEditorCursorPageNum : 1, + currentEditorViewPageNum : 1, + currentBrewRendererPageNum : 1, + themeBundle : {}, + onPageChange : ()=>{}, ...props }; const [state, setState] = useState({ - viewablePageNumber : 0, - height : PAGE_HEIGHT, - isMounted : false, - visibility : 'hidden', + isMounted : false, + visibility : 'hidden', + zoom : 100 }); const mainRef = useRef(null); @@ -74,49 +77,27 @@ const BrewRenderer = (props)=>{ rawPages = props.text.split(/^\\page$/gm); } - useEffect(()=>{ // Unmounting steps - return ()=>{window.removeEventListener('resize', updateSize);}; - }, []); + const updateCurrentPage = useCallback(_.throttle((e)=>{ + const { scrollTop, clientHeight, scrollHeight } = e.target; + const totalScrollableHeight = scrollHeight - clientHeight; + const currentPageNumber = Math.max(Math.ceil((scrollTop / totalScrollableHeight) * rawPages.length), 1); - const updateSize = ()=>{ - setState((prevState)=>({ - ...prevState, - height : mainRef.current.parentNode.clientHeight, - })); - }; - - const handleScroll = (e)=>{ - const target = e.target; - setState((prevState)=>({ - ...prevState, - viewablePageNumber : Math.floor(target.scrollTop / target.scrollHeight * rawPages.length) - })); - }; + props.onPageChange(currentPageNumber); + }, 200), []); const isInView = (index)=>{ if(!state.isMounted) return false; - if(index == props.currentEditorPage) //Already rendered before this step + if(index == props.currentEditorCursorPageNum - 1) //Already rendered before this step return false; - if(Math.abs(index - state.viewablePageNumber) <= 3) + if(Math.abs(index - props.currentBrewRendererPageNum - 1) <= 3) return true; return false; }; - const renderPageInfo = ()=>{ - return
-
- {props.renderer} -
-
- {state.viewablePageNumber + 1} / {rawPages.length} -
-
; - }; - const renderDummyPage = (index)=>{ return
@@ -148,7 +129,7 @@ const BrewRenderer = (props)=>{ renderedPages.length = 0; // Render currently-edited page first so cross-page effects (variables, links) can propagate out first - renderedPages[props.currentEditorPage] = renderPage(rawPages[props.currentEditorPage], props.currentEditorPage); + renderedPages[props.currentEditorCursorPageNum - 1] = renderPage(rawPages[props.currentEditorCursorPageNum - 1], props.currentEditorCursorPageNum - 1); _.forEach(rawPages, (page, index)=>{ if((isInView(index) || !renderedPages[index]) && typeof window !== 'undefined'){ @@ -170,8 +151,6 @@ const BrewRenderer = (props)=>{ const frameDidMount = ()=>{ //This triggers when iFrame finishes internal "componentDidMount" setTimeout(()=>{ //We still see a flicker where the style isn't applied yet, so wait 100ms before showing iFrame - updateSize(); - window.addEventListener('resize', updateSize); renderPages(); //Make sure page is renderable before showing setState((prevState)=>({ ...prevState, @@ -186,11 +165,28 @@ const BrewRenderer = (props)=>{ document.dispatchEvent(new MouseEvent('click')); }; + //Toolbar settings: + const handleZoom = (newZoom)=>{ + setState((prevState)=>({ + ...prevState, + zoom : newZoom + })); + }; + + const styleObject = {}; + + if(global.config.deployment) { + styleObject.backgroundImage = `url("data:image/svg+xml;utf8,${global.config.deployment}")`; + } + + const renderedStyle = useMemo(()=> renderStyle(), [props.style?.length, props.themeBundle]); + renderedPages = useMemo(() => renderPages(), [props.text?.length]); + return ( <> {/*render dummy page while iFrame is mounting.*/} {!state.isMounted - ?
+ ?
{renderDummyPage(1)}
@@ -198,35 +194,37 @@ const BrewRenderer = (props)=>{ : null} -
+
+ + {/*render in iFrame so broken code doesn't crash the site.*/} {emitClick();}} > -
+ style={ styleObject }> + {/* Apply CSS from Style tab and render pages from Markdown tab */} {state.isMounted && <> - {renderStyle()} -
- {renderPages()} + {renderedStyle} +
+ {renderedPages}
}
- {renderPageInfo()} ); }; diff --git a/client/homebrew/brewRenderer/brewRenderer.less b/client/homebrew/brewRenderer/brewRenderer.less index 28ea8005e..81e7e4f22 100644 --- a/client/homebrew/brewRenderer/brewRenderer.less +++ b/client/homebrew/brewRenderer/brewRenderer.less @@ -1,8 +1,13 @@ @import (multiple, less) 'shared/naturalcrit/styles/reset.less'; .brewRenderer { - will-change : transform; - overflow-y : scroll; + overflow-y : scroll; + will-change : transform; + padding-top : 30px; + height : 100vh; + &.deployment { + background-color: darkred; + } :where(.pages) { margin : 30px 0px; & > :where(.page) { @@ -14,66 +19,32 @@ box-shadow : 1px 4px 14px #000000; } } - &::-webkit-scrollbar { - width: 20px; - &:horizontal{ - height: 20px; - width:auto; + width : 20px; + &:horizontal { + width : auto; + height : 20px; } &-thumb { - background: linear-gradient(90deg, #d3c1af 15px, #00000000 15px); - &:horizontal{ - background: linear-gradient(0deg, #d3c1af 15px, #00000000 15px); - } - } - &-corner { - visibility: hidden; + background : linear-gradient(90deg, #D3C1AF 15px, #00000000 15px); + &:horizontal { background : linear-gradient(0deg, #D3C1AF 15px, #00000000 15px); } } + &-corner { visibility : hidden; } } - - - - - } + .pane { position : relative; } -.pageInfo { - position : absolute; - right : 17px; - bottom : 0; - z-index : 1000; - font-size : 10px; - font-weight : 800; - color : white; - background-color : #333333; - div { - display : inline-block; - padding : 8px 10px; - &:not(:last-child) { border-right : 1px solid #666666; } - } -} -.ppr_msg { - position : absolute; - bottom : 0; - left : 0px; - z-index : 1000; - padding : 8px 10px; - font-size : 10px; - font-weight : 800; - color : white; - background-color : #333333; -} @media print { + .toolBar { display : none; } .brewRenderer { - height: 100%; - overflow-y: unset; + height : 100%; + padding-top : unset; + overflow-y : unset; .pages { - margin: 0px; - &>.page { - box-shadow: unset; - } + margin : 0px; + zoom: 100% !important; + & > .page { box-shadow : unset; } } } } \ No newline at end of file diff --git a/client/homebrew/brewRenderer/notificationPopup/notificationPopup.jsx b/client/homebrew/brewRenderer/notificationPopup/notificationPopup.jsx index cca60bbec..f4e0b1c95 100644 --- a/client/homebrew/brewRenderer/notificationPopup/notificationPopup.jsx +++ b/client/homebrew/brewRenderer/notificationPopup/notificationPopup.jsx @@ -4,7 +4,7 @@ const _ = require('lodash'); import Dialog from '../../../components/dialog.jsx'; -const DISMISS_KEY = 'dismiss_notification12-04-23'; +const DISMISS_KEY = 'dismiss_notification01-10-24'; const DISMISS_BUTTON = ; const NotificationPopup = ()=>{ @@ -15,11 +15,12 @@ const NotificationPopup = ()=>{ This website is always improving and we are still adding new features and squashing bugs. Keep the following in mind:
    -
  • - Don't store IMAGES in Google Drive
    - Google Drive is not an image service, and will block images from being used - in brews if they get more views than expected. Google has confirmed they won't fix - this, so we recommend you look for another image hosting service such as imgur, ImgBB or Google Photos. +
  • + Search brews with our new page!
    + We have been working very hard in making this possible, now you can share your work and look at it in the new Vault page! + All PUBLISHED brews will be available to anyone searching there, by title or author, and filtering by renderer. + + More features will be coming.
  • diff --git a/client/homebrew/brewRenderer/notificationPopup/notificationPopup.less b/client/homebrew/brewRenderer/notificationPopup/notificationPopup.less index 26d764aff..2982055c8 100644 --- a/client/homebrew/brewRenderer/notificationPopup/notificationPopup.less +++ b/client/homebrew/brewRenderer/notificationPopup/notificationPopup.less @@ -1,9 +1,10 @@ .popups { position : fixed; - top : @navbarHeight; + top : calc(@navbarHeight + @viewerToolsHeight); right : 24px; z-index : 10001; width : 450px; + margin-top : 5px; } .notificationPopup { diff --git a/client/homebrew/brewRenderer/toolBar/toolBar.jsx b/client/homebrew/brewRenderer/toolBar/toolBar.jsx new file mode 100644 index 000000000..73b48d778 --- /dev/null +++ b/client/homebrew/brewRenderer/toolBar/toolBar.jsx @@ -0,0 +1,164 @@ +require('./toolBar.less'); +const React = require('react'); +const { useState, useEffect } = React; +const _ = require('lodash'); + + +const MAX_ZOOM = 300; +const MIN_ZOOM = 10; + +const ToolBar = ({ onZoomChange, currentPage, onPageChange, totalPages })=>{ + + const [zoomLevel, setZoomLevel] = useState(100); + const [pageNum, setPageNum] = useState(currentPage); + const [toolsVisible, setToolsVisible] = useState(true); + + useEffect(()=>{ + onZoomChange(zoomLevel); + }, [zoomLevel]); + + useEffect(()=>{ + setPageNum(currentPage); + }, [currentPage]); + + const handleZoomButton = (zoom)=>{ + setZoomLevel(_.round(_.clamp(zoom, MIN_ZOOM, MAX_ZOOM))); + }; + + const handlePageInput = (pageInput)=>{ + if(/[0-9]/.test(pageInput)) + setPageNum(parseInt(pageInput)); // input type is 'text', so `page` comes in as a string, not number. + }; + + const scrollToPage = (pageNumber)=>{ + pageNumber = _.clamp(pageNumber, 1, totalPages); + const iframe = document.getElementById('BrewRenderer'); + const brewRenderer = iframe?.contentWindow?.document.querySelector('.brewRenderer'); + const page = brewRenderer?.querySelector(`#p${pageNumber}`); + page?.scrollIntoView({ block: 'start' }); + setPageNum(pageNumber); + }; + + + const calculateChange = (mode)=>{ + const iframe = document.getElementById('BrewRenderer'); + const iframeWidth = iframe.getBoundingClientRect().width; + const iframeHeight = iframe.getBoundingClientRect().height; + const pages = iframe.contentWindow.document.getElementsByClassName('page'); + + let desiredZoom = 0; + + if(mode == 'fill'){ + // 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; + + } else if(mode == 'fit'){ + // find the page with the largest single dim (height or width) so that zoom can be adapted to fit it. + const minDimRatio = [...pages].reduce((minRatio, page)=>Math.min(minRatio, iframeWidth / page.offsetWidth, iframeHeight / page.offsetHeight), Infinity); + + desiredZoom = minDimRatio * 100; + } + + const margin = 5; // extra space so page isn't edge to edge (not truly "to fill") + + const deltaZoom = (desiredZoom - zoomLevel) - margin; + return deltaZoom; + }; + + return ( +
    + + {/*v=====----------------------< Zoom Controls >---------------------=====v*/} +
    + + + + handleZoomButton(parseInt(e.target.value))} + /> + + + + +
    + + {/*v=====----------------------< Page Controls >---------------------=====v*/} +
    + + +
    + e.target.select()} + onChange={(e)=>handlePageInput(e.target.value)} + onBlur={()=>scrollToPage(pageNum)} + onKeyDown={(e)=>e.key == 'Enter' && scrollToPage(pageNum)} + /> + / {totalPages} +
    + + +
    +
    + ); +}; + +module.exports = ToolBar; diff --git a/client/homebrew/brewRenderer/toolBar/toolBar.less b/client/homebrew/brewRenderer/toolBar/toolBar.less new file mode 100644 index 000000000..3bed0eeda --- /dev/null +++ b/client/homebrew/brewRenderer/toolBar/toolBar.less @@ -0,0 +1,128 @@ +@import (less) './client/icons/customIcons.less'; + +.toolBar { + position : absolute; + z-index : 1; + box-sizing : border-box; + display : flex; + flex-wrap : wrap; + gap : 8px 30px; + align-items : center; + justify-content : center; + width : 100%; + height : auto; + padding : 2px 0; + font-family : 'Open Sans', sans-serif; + color : #CCCCCC; + background-color : #555555; + & > *:not(.toggleButton) { + opacity: 1; + transition: all .2s ease; + } + + .group { + box-sizing : border-box; + display : flex; + gap : 0 3px; + align-items : center; + justify-content : center; + height : 28px; + } + + .tool { + display : flex; + align-items : center; + } + + input { + position : relative; + height : 1.5em; + padding : 2px 5px; + font-family : 'Open Sans', sans-serif; + color : #000000; + background : #EEEEEE; + border : 1px solid gray; + &:focus { outline : 1px solid #D3D3D3; } + + // `.range-input` if generic to all range inputs, or `#zoom-slider` if only for zoom slider + &.range-input { + padding : 2px 0; + color : #D3D3D3; + accent-color : #D3D3D3; + + &::-webkit-slider-thumb, &::-moz-slider-thumb { + width : 5px; + height : 5px; + cursor : pointer; + outline : none; + } + + &:hover::after { + position : absolute; + bottom : -30px; + left : 50%; + z-index : 1; + display : grid; + place-items : center; + width : 4ch; + height : 1.2lh; + pointer-events : none; + content : attr(value); + background-color : #555555; + border : 1px solid #A1A1A1; + transform : translate(-50%, 50%); + } + } + + // `.text-input` if generic to all range inputs, or `#page-input` if only for current page input + &#page-input { + width : 4ch; + margin-right : 1ch; + text-align : center; + } + } + + button { + box-sizing : content-box; + display : flex; + align-items : center; + justify-content : center; + width : auto; + min-width : 46px; + height : 100%; + padding : 0 0px; + font-weight : unset; + color : inherit; + background-color : unset; + &:hover { background-color : #444444; } + &:focus { outline : 1px solid #D3D3D3; } + &:disabled { + color : #777777; + background-color : unset !important; + } + i { + font-size:1.2em; + } + } + + &.hidden { + width: 32px; + transition: all .3s ease; + flex-wrap:nowrap; + overflow: hidden; + background-color: unset; + opacity: .5; + & > *:not(.toggleButton) { + opacity: 0; + transition: all .2s ease; + } + } +} + +button.toggleButton { + z-index : 5; + position:absolute; + left: 0; + width: 32px; + min-width: unset; +} \ No newline at end of file diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index 41d3f91e4..9fef72cbb 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -1,9 +1,8 @@ -/*eslint max-lines: ["warn", {"max": 300, "skipBlankLines": true, "skipComments": true}]*/ +/*eslint max-lines: ["warn", {"max": 500, "skipBlankLines": true, "skipComments": true}]*/ require('./editor.less'); const React = require('react'); const createClass = require('create-react-class'); const _ = require('lodash'); -const cx = require('classnames'); const dedent = require('dedent-tabs').default; const Markdown = require('../../../shared/naturalcrit/markdown.js'); @@ -22,6 +21,7 @@ const DEFAULT_STYLE_TEXT = dedent` color: black; }`; +let isJumping = false; const Editor = createClass({ displayName : 'Editor', @@ -37,8 +37,15 @@ const Editor = createClass({ onMetaChange : ()=>{}, reportError : ()=>{}, + onCursorPageChange : ()=>{}, + onViewPageChange : ()=>{}, + editorTheme : 'default', - renderer : 'legacy' + renderer : 'legacy', + + currentEditorCursorPageNum : 1, + currentEditorViewPageNum : 1, + currentBrewRendererPageNum : 1, }; }, getInitialState : function() { @@ -56,12 +63,16 @@ const Editor = createClass({ isMeta : function() {return this.state.view == 'meta';}, componentDidMount : function() { + this.updateEditorSize(); this.highlightCustomMarkdown(); window.addEventListener('resize', this.updateEditorSize); document.getElementById('BrewRenderer').addEventListener('keydown', this.handleControlKeys); document.addEventListener('keydown', this.handleControlKeys); + this.codeEditor.current.codeMirror.on('cursorActivity', (cm)=>{this.updateCurrentCursorPage(cm.getCursor());}); + this.codeEditor.current.codeMirror.on('scroll', _.throttle(()=>{this.updateCurrentViewPage(this.codeEditor.current.getTopVisibleLine());}, 200)); + const editorTheme = window.localStorage.getItem(EDITOR_THEME_KEY); if(editorTheme) { this.setState({ @@ -75,29 +86,37 @@ const Editor = createClass({ }, componentDidUpdate : function(prevProps, prevState, snapshot) { + this.highlightCustomMarkdown(); - if(prevProps.moveBrew !== this.props.moveBrew) { + if(prevProps.moveBrew !== this.props.moveBrew) this.brewJump(); - }; - if(prevProps.moveSource !== this.props.moveSource) { + + if(prevProps.moveSource !== this.props.moveSource) this.sourceJump(); - }; + + if(this.props.liveScroll) { + if(prevProps.currentBrewRendererPageNum !== this.props.currentBrewRendererPageNum) { + this.sourceJump(this.props.currentBrewRendererPageNum, false); + } else if(prevProps.currentEditorViewPageNum !== this.props.currentEditorViewPageNum) { + this.brewJump(this.props.currentEditorViewPageNum, false); + } else if(prevProps.currentEditorCursorPageNum !== this.props.currentEditorCursorPageNum) { + this.brewJump(this.props.currentEditorCursorPageNum, false); + } + } }, handleControlKeys : function(e){ - if(!(e.ctrlKey || e.metaKey)) return; - console.log(e); + if(!(e.ctrlKey && e.metaKey && e.shiftKey)) return; const LEFTARROW_KEY = 37; const RIGHTARROW_KEY = 39; - if (e.shiftKey && (e.keyCode == RIGHTARROW_KEY)) this.brewJump(); - if (e.shiftKey && (e.keyCode == LEFTARROW_KEY)) this.sourceJump(); - if ((e.keyCode == LEFTARROW_KEY) || (e.keyCode == RIGHTARROW_KEY)) { + if(e.keyCode == RIGHTARROW_KEY) this.brewJump(); + if(e.keyCode == LEFTARROW_KEY) this.sourceJump(); + if(e.keyCode == LEFTARROW_KEY || e.keyCode == RIGHTARROW_KEY) { e.stopPropagation(); e.preventDefault(); } }, - updateEditorSize : function() { if(this.codeEditor.current) { let paneHeight = this.editor.current.parentNode.clientHeight; @@ -106,6 +125,20 @@ const Editor = createClass({ } }, + updateCurrentCursorPage : function(cursor) { + const lines = this.props.brew.text.split('\n').slice(0, cursor.line + 1); + const pageRegex = this.props.brew.renderer == 'V3' ? /^\\page$/ : /\\page/; + const currentPage = lines.reduce((count, line)=>count + (pageRegex.test(line) ? 1 : 0), 1); + this.props.onCursorPageChange(currentPage); + }, + + updateCurrentViewPage : function(topScrollLine) { + const lines = this.props.brew.text.split('\n').slice(0, topScrollLine + 1); + const pageRegex = this.props.brew.renderer == 'V3' ? /^\\page$/ : /\\page/; + const currentPage = lines.reduce((count, line)=>count + (pageRegex.test(line) ? 1 : 0), 1); + this.props.onViewPageChange(currentPage); + }, + handleInject : function(injectText){ this.codeEditor.current?.injectText(injectText, false); }, @@ -114,19 +147,10 @@ const Editor = createClass({ this.props.setMoveArrows(newView === 'text'); this.setState({ view : newView - }, this.updateEditorSize); //TODO: not sure if updateeditorsize needed - }, - - getCurrentPage : function(){ - const lines = this.props.brew.text.split('\n').slice(0, this.codeEditor.current.getCursorPosition().line + 1); - return _.reduce(lines, (r, line)=>{ - if( - (this.props.renderer == 'legacy' && line.indexOf('\\page') !== -1) - || - (this.props.renderer == 'V3' && line.match(/^\\page$/)) - ) r++; - return r; - }, 1); + }, ()=>{ + this.codeEditor.current?.codeMirror.focus(); + this.updateEditorSize(); + }); //TODO: not sure if updateeditorsize needed }, highlightCustomMarkdown : function(){ @@ -136,8 +160,18 @@ const Editor = createClass({ codeMirror.operation(()=>{ // Batch CodeMirror styling + const foldLines = []; + //reset custom text styles - const customHighlights = codeMirror.getAllMarks().filter((mark)=>!mark.__isFold); //Don't undo code folding + const customHighlights = codeMirror.getAllMarks().filter((mark)=>{ + // Record details of folded sections + if(mark.__isFold) { + const fold = mark.find(); + foldLines.push({ from: fold.from?.line, to: fold.to?.line }); + } + return !mark.__isFold; + }); //Don't undo code folding + for (let i=customHighlights.length - 1;i>=0;i--) customHighlights[i].clear(); let editorPageCount = 2; // start page count from page 2 @@ -149,6 +183,11 @@ const Editor = createClass({ codeMirror.removeLineClass(lineNumber, 'text'); codeMirror.removeLineClass(lineNumber, 'wrap', 'sourceMoveFlash'); + // Don't process lines inside folded text + // If the current lineNumber is inside any folded marks, skip line styling + if(foldLines.some((fold)=>lineNumber >= fold.from && lineNumber <= fold.to)) + return; + // Styling for \page breaks if((this.props.renderer == 'legacy' && line.includes('\\page')) || (this.props.renderer == 'V3' && line.match(/^\\page$/))) { @@ -172,7 +211,7 @@ const Editor = createClass({ // definition lists if(line.includes('::')){ - if(/^:*$/.test(line) == true){ return }; + if(/^:*$/.test(line) == true){ return; }; const regex = /^([^\n]*?:?\s?)(::[^\n]*)(?:\n|$)/ymd; // the `d` flag, for match indices, throws an ESLint error. let match; while ((match = regex.exec(line)) != null){ @@ -180,10 +219,10 @@ const Editor = createClass({ codeMirror.markText({ line: lineNumber, ch: match.indices[1][0] }, { line: lineNumber, ch: match.indices[1][1] }, { className: 'dt-highlight' }); codeMirror.markText({ line: lineNumber, ch: match.indices[2][0] }, { line: lineNumber, ch: match.indices[2][1] }, { className: 'dd-highlight' }); const ddIndex = match.indices[2][0]; - let colons = /::/g; - let colonMatches = colons.exec(match[2]); + const colons = /::/g; + const colonMatches = colons.exec(match[2]); if(colonMatches !== null){ - codeMirror.markText({ line: lineNumber, ch: colonMatches.index + ddIndex }, { line: lineNumber, ch: colonMatches.index + colonMatches[0].length + ddIndex }, { className: 'dl-colon-highlight'} ) + codeMirror.markText({ line: lineNumber, ch: colonMatches.index + ddIndex }, { line: lineNumber, ch: colonMatches.index + colonMatches[0].length + ddIndex }, { className: 'dl-colon-highlight' }); } } } @@ -193,12 +232,12 @@ const Editor = createClass({ let startIndex = line.indexOf('^'); const superRegex = /\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^/gy; const subRegex = /\^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^\^/gy; - + while (startIndex >= 0) { superRegex.lastIndex = subRegex.lastIndex = startIndex; let isSuper = false; - let match = subRegex.exec(line) || superRegex.exec(line); - if (match) { + const match = subRegex.exec(line) || superRegex.exec(line); + if(match) { isSuper = !subRegex.lastIndex; codeMirror.markText({ line: lineNumber, ch: match.index }, { line: lineNumber, ch: match.index + match[0].length }, { className: isSuper ? 'superscript' : 'subscript' }); } @@ -248,20 +287,20 @@ const Editor = createClass({ while (startIndex >= 0) { emojiRegex.lastIndex = startIndex; - let match = emojiRegex.exec(line); - if (match) { + const match = emojiRegex.exec(line); + if(match) { let tokens = Markdown.marked.lexer(match[0]); - tokens = tokens[0].tokens.filter(t => t.type == 'emoji') - if (!tokens.length) + tokens = tokens[0].tokens.filter((t)=>t.type == 'emoji'); + if(!tokens.length) return; - let startPos = { line: lineNumber, ch: match.index }; - let endPos = { line: lineNumber, ch: match.index + match[0].length }; + const startPos = { line: lineNumber, ch: match.index }; + const endPos = { line: lineNumber, ch: match.index + match[0].length }; // Iterate over conflicting marks and clear them - var marks = codeMirror.findMarks(startPos, endPos); + const marks = codeMirror.findMarks(startPos, endPos); marks.forEach(function(marker) { - marker.clear(); + if(!marker.__isFold) marker.clear(); }); codeMirror.markText(startPos, endPos, { className: 'emoji' }); } @@ -274,75 +313,93 @@ const Editor = createClass({ } }, - brewJump : function(targetPage=this.getCurrentPage()){ - if(!window) return; - // console.log(`Scroll to: p${targetPage}`); + brewJump : function(targetPage=this.props.currentEditorCursorPageNum, smooth=true){ + if(!window || !this.isText() || isJumping) + return; + + // Get current brewRenderer scroll position and calculate target position const brewRenderer = window.frames['BrewRenderer'].contentDocument.getElementsByClassName('brewRenderer')[0]; const currentPos = brewRenderer.scrollTop; const targetPos = window.frames['BrewRenderer'].contentDocument.getElementById(`p${targetPage}`).getBoundingClientRect().top; - const interimPos = targetPos >= 0 ? -30 : 30; - const bounceDelay = 100; - const scrollDelay = 500; - - if(!this.throttleBrewMove) { - this.throttleBrewMove = _.throttle((currentPos, interimPos, targetPos)=>{ - brewRenderer.scrollTo({ top: currentPos + interimPos, behavior: 'smooth' }); - setTimeout(()=>{ - brewRenderer.scrollTo({ top: currentPos + targetPos, behavior: 'smooth', block: 'start' }); - }, bounceDelay); - }, scrollDelay, { leading: true, trailing: false }); + const checkIfScrollComplete = ()=>{ + let scrollingTimeout; + clearTimeout(scrollingTimeout); // Reset the timer every time a scroll event occurs + scrollingTimeout = setTimeout(()=>{ + isJumping = false; + brewRenderer.removeEventListener('scroll', checkIfScrollComplete); + }, 150); // If 150 ms pass without a brewRenderer scroll event, assume scrolling is done }; - this.throttleBrewMove(currentPos, interimPos, targetPos); - // const hashPage = (page != 1) ? `p${page}` : ''; - // window.location.hash = hashPage; + isJumping = true; + checkIfScrollComplete(); + brewRenderer.addEventListener('scroll', checkIfScrollComplete); + + if(smooth) { + const bouncePos = targetPos >= 0 ? -30 : 30; //Do a little bounce before scrolling + const bounceDelay = 100; + const scrollDelay = 500; + + if(!this.throttleBrewMove) { + this.throttleBrewMove = _.throttle((currentPos, bouncePos, targetPos)=>{ + brewRenderer.scrollTo({ top: currentPos + bouncePos, behavior: 'smooth' }); + setTimeout(()=>{ + brewRenderer.scrollTo({ top: currentPos + targetPos, behavior: 'smooth', block: 'start' }); + }, bounceDelay); + }, scrollDelay, { leading: true, trailing: false }); + }; + this.throttleBrewMove(currentPos, bouncePos, targetPos); + } else { + brewRenderer.scrollTo({ top: currentPos + targetPos, behavior: 'instant', block: 'start' }); + } }, - sourceJump : function(targetLine=null){ - if(this.isText()) { - if(targetLine == null) { - targetLine = 0; + sourceJump : function(targetPage=this.props.currentBrewRendererPageNum, smooth=true){ + if(!this.isText() || isJumping) + return; - const pageCollection = window.frames['BrewRenderer'].contentDocument.getElementsByClassName('page'); - const brewRendererHeight = window.frames['BrewRenderer'].contentDocument.getElementsByClassName('brewRenderer').item(0).getBoundingClientRect().height; + const textSplit = this.props.renderer == 'V3' ? /^\\page$/gm : /\\page/; + const textString = this.props.brew.text.split(textSplit).slice(0, targetPage-1).join(textSplit); + const targetLine = textString.match('\n') ? textString.split('\n').length - 1 : -1; - let currentPage = 1; - for (const page of pageCollection) { - if(page.getBoundingClientRect().bottom > (brewRendererHeight / 2)) { - currentPage = parseInt(page.id.slice(1)) || 1; - break; - } + let currentY = this.codeEditor.current.codeMirror.getScrollInfo().top; + let targetY = this.codeEditor.current.codeMirror.heightAtLine(targetLine, 'local', true); + + const checkIfScrollComplete = ()=>{ + let scrollingTimeout; + clearTimeout(scrollingTimeout); // Reset the timer every time a scroll event occurs + scrollingTimeout = setTimeout(()=>{ + isJumping = false; + this.codeEditor.current.codeMirror.off('scroll', checkIfScrollComplete); + }, 150); // If 150 ms pass without a scroll event, assume scrolling is done + }; + + isJumping = true; + checkIfScrollComplete(); + this.codeEditor.current.codeMirror.on('scroll', checkIfScrollComplete); + + if(smooth) { + //Scroll 1/10 of the way every 10ms until 1px off. + const incrementalScroll = setInterval(()=>{ + currentY += (targetY - currentY) / 10; + this.codeEditor.current.codeMirror.scrollTo(null, currentY); + + // Update target: target height is not accurate until within +-10 lines of the visible window + if(Math.abs(targetY - currentY > 100)) + targetY = this.codeEditor.current.codeMirror.heightAtLine(targetLine, 'local', true); + + // End when close enough + if(Math.abs(targetY - currentY) < 1) { + this.codeEditor.current.codeMirror.scrollTo(null, targetY); // Scroll any remaining difference + this.codeEditor.current.setCursorPosition({ line: targetLine + 1, ch: 0 }); + this.codeEditor.current.codeMirror.addLineClass(targetLine + 1, 'wrap', 'sourceMoveFlash'); + clearInterval(incrementalScroll); } - - const textSplit = this.props.renderer == 'V3' ? /^\\page$/gm : /\\page/; - const textString = this.props.brew.text.split(textSplit).slice(0, currentPage-1).join(textSplit); - const textPosition = textString.length; - const lineCount = textString.match('\n') ? textString.slice(0, textPosition).split('\n').length : 0; - - targetLine = lineCount - 1; //Scroll to `\page`, which is one line back. - - let currentY = this.codeEditor.current.codeMirror.getScrollInfo().top; - let targetY = this.codeEditor.current.codeMirror.heightAtLine(targetLine, 'local', true); - - //Scroll 1/10 of the way every 10ms until 1px off. - const incrementalScroll = setInterval(()=>{ - currentY += (targetY - currentY) / 10; - this.codeEditor.current.codeMirror.scrollTo(null, currentY); - - // Update target: target height is not accurate until within +-10 lines of the visible window - if(Math.abs(targetY - currentY > 100)) - targetY = this.codeEditor.current.codeMirror.heightAtLine(targetLine, 'local', true); - - // End when close enough - if(Math.abs(targetY - currentY) < 1) { - this.codeEditor.current.codeMirror.scrollTo(null, targetY); // Scroll any remaining difference - this.codeEditor.current.setCursorPosition({ line: targetLine + 1, ch: 0 }); - this.codeEditor.current.codeMirror.addLineClass(targetLine + 1, 'wrap', 'sourceMoveFlash'); - clearInterval(incrementalScroll); - } - }, 10); - } + }, 10); + } else { + this.codeEditor.current.codeMirror.scrollTo(null, targetY); // Scroll any remaining difference + this.codeEditor.current.setCursorPosition({ line: targetLine + 1, ch: 0 }); + this.codeEditor.current.codeMirror.addLineClass(targetLine + 1, 'wrap', 'sourceMoveFlash'); } }, @@ -443,7 +500,9 @@ const Editor = createClass({ currentEditorTheme={this.state.editorTheme} updateEditorTheme={this.updateEditorTheme} snippetBundle={this.props.snippetBundle} - cursorPos={this.codeEditor.current?.getCursorPosition() || {}} /> + cursorPos={this.codeEditor.current?.getCursorPosition() || {}} + updateBrew={this.props.updateBrew} + /> {this.renderEditor()}
diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.jsx b/client/homebrew/editor/metadataEditor/metadataEditor.jsx index 0f1f6ad54..e66fa64e2 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.jsx +++ b/client/homebrew/editor/metadataEditor/metadataEditor.jsx @@ -304,17 +304,14 @@ const MetadataEditor = createClass({ onChange={(e)=>this.handleRenderer('V3', e)} /> V3 - - - Click here to see the demo page for the old Legacy renderer! - + Click here to see the demo page for the old Legacy renderer!
; }, render : function(){ return
-

Brew

+

Properties Editor

@@ -362,9 +359,7 @@ const MetadataEditor = createClass({ {this.renderRenderOptions()} -
- -

Authors

+

Authors

{this.renderAuthors()} @@ -375,15 +370,13 @@ const MetadataEditor = createClass({ notes={['Invited author usernames are case sensitive.', 'After adding an invited author, send them the edit link. There, they can choose to accept or decline the invitation.']} onChange={(e)=>this.handleFieldChange('invitedAuthors', e)}/> -
- -

Privacy

+

Privacy

{this.renderPublish()} - Published homebrews will be publicly viewable and searchable (eventually...) + Published brews are searchable in the Vault and visible on your user page. Unpublished brews are not indexed in the Vault or visible on your user page, but can still be shared and indexed by search engines. You can unpublish a brew any time.
diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.less b/client/homebrew/editor/metadataEditor/metadataEditor.less index 27ebd88c2..62ec6b37b 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.less +++ b/client/homebrew/editor/metadataEditor/metadataEditor.less @@ -1,5 +1,6 @@ @import 'naturalcrit/styles/colors.less'; + .metadataEditor { position : absolute; z-index : 5; @@ -9,12 +10,19 @@ padding : 25px; overflow-y : auto; background-color : #999999; + font-size : 13px; - .sectionHead { + h1 { + margin: 0 0 40px; + font-weight: bold; + text-transform: uppercase; + } + + h2 { margin : 20px 0; - font-weight : 1000; - - &:first-of-type { margin-top : 0; } + font-weight : bold; + border-bottom: 2px solid gray; + color: #555; } & > div { margin-bottom : 10px; } @@ -43,15 +51,21 @@ min-width : 200px; & > label { width : 80px; - font-size : 11px; font-weight : 800; line-height : 1.8em; text-transform : uppercase; + font-size: .9em; } & > .value { flex : 1 1 auto; width : 50px; &:invalid { background : #FFB9B9; } + small { + display : block; + font-size : 0.9em; + font-style : italic; + line-height : 1.4em; + } } input[type='text'], textarea { border : 1px solid gray; @@ -78,7 +92,6 @@ textarea.value { height : auto; font-family : 'Open Sans', sans-serif; - font-size : 0.8em; resize : none; } } @@ -87,12 +100,6 @@ z-index : 200; max-width : 150px; } - small { - display : inline-block; - font-size : 0.6em; - font-style : italic; - line-height : 1.4em; - } } @@ -113,18 +120,13 @@ display : inline-flex; align-items : center; margin-right : 15px; - font-size : 0.7em; + font-size : 0.9em; font-weight : 800; white-space : nowrap; vertical-align : middle; cursor : pointer; user-select : none; } - a { - display : inline-flex; - font-size : 0.7em; - font-weight : 800; - } input { margin : 3px; vertical-align : middle; @@ -149,12 +151,10 @@ } } .authors.field .value { - font-size : 0.8em; line-height : 1.5em; } .themes.field { - font-size : 13.33px; .navDropdownContainer { position : relative; z-index : 100; @@ -165,9 +165,9 @@ background-color : darkgray; } & > div:first-child { - padding : 6px 3px; + padding : 3px 3px; background-color : inherit; - border : 2px solid rgb(118,118,118); + border : 1px solid gray; i { float : right; } &:hover { color : white; @@ -240,6 +240,7 @@ } } } + .field .list { display : flex; flex : 1 0; @@ -258,15 +259,15 @@ color : white; text-align : center; cursor : pointer; - + i { position : relative; top : 50%; transform : translateY(-50%); } - + &:not(:last-child) { border-right : 1px solid black; } - + &:last-child { border-radius : 0 0.5em 0.5em 0; } } @@ -277,8 +278,7 @@ background-color : #DDDDDD; border-radius : 0.5em; - .icon { - #groupedIcon; } + .icon { #groupedIcon; } } .input-group { @@ -294,17 +294,30 @@ height : 100%; } - .invalid:focus { background-color : pink; } + .input-group { + height : ~'calc(.9em + 4px + .6em)'; - .icon { - #groupedIcon; - top : -0.54em; - right : 1px; - height : 97%; - font-size : 0.8em; + input { border-radius : 0.5em 0 0 0.5em; } - i { font-size : 1.125em; } + input:last-child { border-radius : 0.5em; } + + .value { + width : 7.5vw; + min-width : 75px; + height : 100%; + } + + .invalid:focus { background-color : pink; } + + .icon { + #groupedIcon; + top : -0.54em; + right : 1px; + height : 97%; + + i { font-size : 1.125em; } + } } } } -} +} \ No newline at end of file diff --git a/client/homebrew/editor/snippetbar/snippetbar.jsx b/client/homebrew/editor/snippetbar/snippetbar.jsx index dd45f0773..fc6bc6db6 100644 --- a/client/homebrew/editor/snippetbar/snippetbar.jsx +++ b/client/homebrew/editor/snippetbar/snippetbar.jsx @@ -1,10 +1,12 @@ -/*eslint max-lines: ["warn", {"max": 250, "skipBlankLines": true, "skipComments": true}]*/ +/*eslint max-lines: ["warn", {"max": 350, "skipBlankLines": true, "skipComments": true}]*/ require('./snippetbar.less'); const React = require('react'); const createClass = require('create-react-class'); const _ = require('lodash'); const cx = require('classnames'); +import { loadHistory } from '../../utils/versionHistory.js'; + //Import all themes const ThemeSnippets = {}; ThemeSnippets['Legacy_5ePHB'] = require('themes/Legacy/5ePHB/snippets.js'); @@ -38,7 +40,8 @@ const Snippetbar = createClass({ unfoldCode : ()=>{}, updateEditorTheme : ()=>{}, cursorPos : {}, - snippetBundle : [] + snippetBundle : [], + updateBrew : ()=>{} }; }, @@ -46,31 +49,54 @@ const Snippetbar = createClass({ return { renderer : this.props.renderer, themeSelector : false, - snippets : [] + snippets : [], + showHistory : false, + historyExists : false, + historyItems : [] }; }, - componentDidMount : async function() { + componentDidMount : async function(prevState) { const snippets = this.compileSnippets(); this.setState({ snippets : snippets }); }, - componentDidUpdate : async function(prevProps) { + componentDidUpdate : async function(prevProps, prevState) { if(prevProps.renderer != this.props.renderer || prevProps.theme != this.props.theme || prevProps.snippetBundle != this.props.snippetBundle) { - const snippets = this.compileSnippets(); this.setState({ - snippets : snippets + snippets : this.compileSnippets() + }); + }; + + // Update history list if it has changed + const checkHistoryItems = await loadHistory(this.props.brew); + + // If all items have the noData property, there is no saved data + const checkHistoryExists = !checkHistoryItems.every((historyItem)=>{ + return historyItem?.noData; + }); + if(prevState.historyExists != checkHistoryExists){ + this.setState({ + historyExists : checkHistoryExists + }); + } + + // If any history items have changed, update the list + if(checkHistoryExists && checkHistoryItems.some((historyItem, index)=>{ + return index >= prevState.historyItems.length || !_.isEqual(historyItem, prevState.historyItems[index]); + })){ + this.setState({ + historyItems : checkHistoryItems }); } }, - mergeCustomizer : function(oldValue, newValue, key) { if(key == 'snippets') { const result = _.reverse(_.unionBy(_.reverse(newValue), _.reverse(oldValue), 'name')); // Join snippets together, with preference for the child theme over the parent theme - return _.filter(result, 'gen'); //Only keep snippets with a 'gen' property. + return result.filter((snip)=>snip.gen || snip.subsnippets); } }, @@ -141,6 +167,41 @@ const Snippetbar = createClass({
}, + replaceContent : function(item){ + return this.props.updateBrew(item); + }, + + toggleHistoryMenu : function(){ + this.setState({ + showHistory : !this.state.showHistory + }); + }, + + renderHistoryItems : function() { + if(!this.state.historyExists) return; + + return
+ {_.map(this.state.historyItems, (item, index)=>{ + if(item.noData || !item.savedAt) return; + + const saveTime = new Date(item.savedAt); + const diffMs = new Date() - saveTime; + const diffSecs = Math.floor(diffMs / 1000); + + let diffString = `about ${diffSecs} seconds ago`; + + if(diffSecs > 60) diffString = `about ${Math.floor(diffSecs / 60)} minutes ago`; + if(diffSecs > (60 * 60)) diffString = `about ${Math.floor(diffSecs / (60 * 60))} hours ago`; + if(diffSecs > (24 * 60 * 60)) diffString = `about ${Math.floor(diffSecs / (24 * 60 * 60))} days ago`; + if(diffSecs > (7 * 24 * 60 * 60)) diffString = `about ${Math.floor(diffSecs / (7 * 24 * 60 * 60))} weeks ago`; + + return
{this.replaceContent(item);}} > + + v{item.version} : {diffString} +
; + })} +
; + }, renderEditorButtons : function(){ if(!this.props.showEditButtons) return; @@ -162,6 +223,28 @@ const Snippetbar = createClass({ } return
+
+ + { this.state.showHistory && this.renderHistoryItems() } +
+
+ +
+
+ +
+
+ {foldButtons} +
+ + {this.state.themeSelector && this.renderThemeSelector()} +
+ +
this.props.onViewChange('text')}> @@ -230,8 +313,9 @@ const SnippetGroup = createClass({ return _.map(snippets, (snippet)=>{ return
this.handleSnippetClick(e, snippet)}> - {snippet.name} + {snippet.name} {snippet.experimental && beta} + {snippet.disabled && disabled} {snippet.subsnippets && <>
diff --git a/client/homebrew/editor/snippetbar/snippetbar.less b/client/homebrew/editor/snippetbar/snippetbar.less index 7b0754a07..852a95c98 100644 --- a/client/homebrew/editor/snippetbar/snippetbar.less +++ b/client/homebrew/editor/snippetbar/snippetbar.less @@ -59,6 +59,21 @@ font-size : 0.75em; color : inherit; } + &.history { + .tooltipLeft('History'); + font-size : 0.75em; + color : grey; + position : relative; + &.active { + color : inherit; + } + &>.dropdown{ + right : -1px; + &>.snippet{ + padding-right : 10px; + } + } + } &.editorTheme { .tooltipLeft('Editor Themes'); font-size : 0.75em; @@ -170,6 +185,7 @@ } } .name { margin-right : auto; } + .disabled { text-decoration: line-through; } .beta { align-self : center; padding : 4px 6px; diff --git a/client/homebrew/editor/stringArrayEditor/stringArrayEditor.jsx b/client/homebrew/editor/stringArrayEditor/stringArrayEditor.jsx index 8f06ae561..47ab038cc 100644 --- a/client/homebrew/editor/stringArrayEditor/stringArrayEditor.jsx +++ b/client/homebrew/editor/stringArrayEditor/stringArrayEditor.jsx @@ -128,7 +128,7 @@ const StringArrayEditor = createClass({ return
-
+
{valueElements}
diff --git a/client/homebrew/homebrew.jsx b/client/homebrew/homebrew.jsx index 2226c4f3f..63cf295fe 100644 --- a/client/homebrew/homebrew.jsx +++ b/client/homebrew/homebrew.jsx @@ -10,6 +10,7 @@ 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'); const WithRoute = (props)=>{ @@ -71,6 +72,7 @@ const Homebrew = createClass({ } /> } /> } /> + }/> } /> } /> } /> diff --git a/client/homebrew/navbar/editTitle.navitem.jsx b/client/homebrew/navbar/editTitle.navitem.jsx deleted file mode 100644 index 94ae5d0b0..000000000 --- a/client/homebrew/navbar/editTitle.navitem.jsx +++ /dev/null @@ -1,34 +0,0 @@ -const React = require('react'); -const createClass = require('create-react-class'); -const cx = require('classnames'); -const Nav = require('naturalcrit/nav/nav.jsx'); - -const MAX_TITLE_LENGTH = 50; - - -const EditTitle = createClass({ - displayName : 'EditTitleNavItem', - getDefaultProps : function() { - return { - title : '', - onChange : function(){} - }; - }, - - handleChange : function(e){ - if(e.target.value.length > MAX_TITLE_LENGTH) return; - this.props.onChange(e.target.value); - }, - render : function(){ - return - - -
= MAX_TITLE_LENGTH })}> - {this.props.title.length}/{MAX_TITLE_LENGTH} -
-
; - }, - -}); - -module.exports = EditTitle; diff --git a/client/homebrew/navbar/error-navitem.jsx b/client/homebrew/navbar/error-navitem.jsx index 5dd5c1eb9..f6788e6d5 100644 --- a/client/homebrew/navbar/error-navitem.jsx +++ b/client/homebrew/navbar/error-navitem.jsx @@ -111,7 +111,7 @@ const ErrorNavItem = createClass({ Looks like there was a problem retreiving the theme, or a theme that it inherits, for this brew. Verify that brew - {response.body.brewId} still exists! + {response.body.brewId} still exists!
; } diff --git a/client/homebrew/navbar/navbar.less b/client/homebrew/navbar/navbar.less index d0f2f77e8..c1cda38c3 100644 --- a/client/homebrew/navbar/navbar.less +++ b/client/homebrew/navbar/navbar.less @@ -1,6 +1,7 @@ @import 'naturalcrit/styles/colors.less'; @navbarHeight : 28px; +@viewerToolsHeight : 32px; @keyframes pinkColoring { 0% { color : pink; } @@ -34,6 +35,11 @@ display : flex; align-items : center; &:last-child .navItem { border-left : 1px solid #666666; } + + &:has(.brewTitle) { + flex-grow : 1; + min-width : 300px; + } } // "NaturalCrit" logo .navLogo { @@ -68,6 +74,10 @@ .navItem { #backgroundColorsHover; .animate(background-color); + display : flex; + align-items : center; + justify-content : center; + height : 100%; padding : 8px 12px; font-size : 10px; font-weight : 800; @@ -93,39 +103,20 @@ animation-duration : 2s; } } - &.editTitle { // this is not needed at all currently - you used to be able to edit the title via the navbar. - padding : 2px 12px; - input { - width : 250px; - padding : 2px; - margin : 0; - font-family : 'Open Sans', sans-serif; - font-size : 12px; - font-weight : 800; - color : white; - text-align : center; - background-color : transparent; - border : 1px solid @blue; - outline : none; - } - .charCount { - display : inline-block; - margin-left : 8px; - color : #666666; - text-align : right; - vertical-align : bottom; - &.max { color : @red; } - } - } &.brewTitle { - flex-grow : 1; + display : block; + width : 100%; + overflow : hidden; font-size : 12px; font-weight : 800; color : white; text-align : center; - text-transform : initial; - background-color : transparent; + text-overflow : ellipsis; + text-transform : initial; + white-space : nowrap; + background-color : transparent; } + // "The Homebrewery" logo &.homebrewLogo { .animate(color); @@ -239,23 +230,25 @@ } .navDropdownContainer { position : relative; + height : 100%; + .navDropdown { - position: absolute; - top: 28px; - right: 0px; - z-index: 10000; - width: max-content; - min-width:100%; - max-height: calc(100vh - 28px); - overflow: hidden auto; - display: flex; - flex-direction: column; - align-items: flex-end; + position : absolute; + //top: 28px; + right : 0px; + z-index : 10000; + display : flex; + flex-direction : column; + align-items : flex-end; + width : max-content; + min-width : 100%; + max-height : calc(100vh - 28px); + overflow : hidden auto; .navItem { position : relative; display : flex; - justify-content : space-between; align-items : center; + justify-content : space-between; width : 100%; border : 1px solid #888888; border-bottom : 0; @@ -277,10 +270,10 @@ overflow : hidden auto; color : white; text-decoration : none; - background-color : #333333; - border-top : 1px solid #888888; scrollbar-color : #666666 #333333; scrollbar-width : thin; + background-color : #333333; + border-top : 1px solid #888888; .clear { position : absolute; top : 50%; diff --git a/client/homebrew/navbar/recent.navitem.jsx b/client/homebrew/navbar/recent.navitem.jsx index 431bdd8df..a6cbbf406 100644 --- a/client/homebrew/navbar/recent.navitem.jsx +++ b/client/homebrew/navbar/recent.navitem.jsx @@ -36,7 +36,7 @@ const RecentItems = createClass({ //== Add current brew to appropriate recent items list (depending on storageKey) ==// if(this.props.storageKey == 'edit'){ let editId = this.props.brew.editId; - if(this.props.brew.googleId){ + if(this.props.brew.googleId && !this.props.brew.stubbed){ editId = `${this.props.brew.googleId}${this.props.brew.editId}`; } edited = _.filter(edited, (brew)=>{ @@ -51,7 +51,7 @@ const RecentItems = createClass({ } if(this.props.storageKey == 'view'){ let shareId = this.props.brew.shareId; - if(this.props.brew.googleId){ + if(this.props.brew.googleId && !this.props.brew.stubbed){ shareId = `${this.props.brew.googleId}${this.props.brew.shareId}`; } viewed = _.filter(viewed, (brew)=>{ @@ -83,7 +83,7 @@ const RecentItems = createClass({ let edited = JSON.parse(localStorage.getItem(EDIT_KEY) || '[]'); if(this.props.storageKey == 'edit') { let prevEditId = prevProps.brew.editId; - if(prevProps.brew.googleId){ + if(prevProps.brew.googleId && !this.props.brew.stubbed){ prevEditId = `${prevProps.brew.googleId}${prevProps.brew.editId}`; } @@ -91,7 +91,7 @@ const RecentItems = createClass({ return brew.id !== prevEditId; }); let editId = this.props.brew.editId; - if(this.props.brew.googleId){ + if(this.props.brew.googleId && !this.props.brew.stubbed){ editId = `${this.props.brew.googleId}${this.props.brew.editId}`; } edited.unshift({ diff --git a/client/homebrew/navbar/reddit.navitem.jsx b/client/homebrew/navbar/reddit.navitem.jsx deleted file mode 100644 index 1d9f95604..000000000 --- a/client/homebrew/navbar/reddit.navitem.jsx +++ /dev/null @@ -1,44 +0,0 @@ -const React = require('react'); -const createClass = require('create-react-class'); -const Nav = require('naturalcrit/nav/nav.jsx'); - -const MAIN_URL = 'https://www.reddit.com/r/UnearthedArcana/submit?selftext=true'; - - -const RedditShare = createClass({ - displayName : 'RedditShareNavItem', - getDefaultProps : function() { - return { - brew : { - title : '', - sharedId : '', - text : '' - } - }; - }, - - getText : function(){ - - }, - - - handleClick : function(){ - const url = [ - MAIN_URL, - `title=${encodeURIComponent(this.props.brew.title ? this.props.brew.title : 'Check out my brew!')}`, - `text=${encodeURIComponent(this.props.brew.text)}` - ].join('&'); - - window.open(url, '_blank'); - }, - - - render : function(){ - return - share on reddit - ; - }, - -}); - -module.exports = RedditShare; diff --git a/client/homebrew/navbar/vault.navitem.jsx b/client/homebrew/navbar/vault.navitem.jsx new file mode 100644 index 000000000..087297011 --- /dev/null +++ b/client/homebrew/navbar/vault.navitem.jsx @@ -0,0 +1,17 @@ +const React = require('react'); + +const Nav = require('naturalcrit/nav/nav.jsx'); + +module.exports = function (props) { + return ( + + Vault + + ); +}; diff --git a/client/homebrew/pages/basePages/listPage/brewItem/brewItem.jsx b/client/homebrew/pages/basePages/listPage/brewItem/brewItem.jsx index bf0624f1c..039bc98f5 100644 --- a/client/homebrew/pages/basePages/listPage/brewItem/brewItem.jsx +++ b/client/homebrew/pages/basePages/listPage/brewItem/brewItem.jsx @@ -19,7 +19,8 @@ const BrewItem = createClass({ stubbed : true }, updateListFilter : ()=>{}, - reportError : ()=>{} + reportError : ()=>{}, + renderStorage : true }; }, @@ -95,6 +96,7 @@ const BrewItem = createClass({ }, renderStorageIcon : function(){ + if(!this.props.renderStorage) return; if(this.props.brew.googleId) { return @@ -142,10 +144,14 @@ const BrewItem = createClass({ } {brew.authors?.map((author, index)=>( - <> - {author} - {index < brew.authors.length - 1 && ', '} - ))} + + {author === 'hidden' + ? {author} + : {author} + } + {index < brew.authors.length - 1 && ', '} + + ))}
diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index 39a6d1931..fcc43e81a 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -1,8 +1,9 @@ /* eslint-disable max-lines */ require('./editPage.less'); const React = require('react'); -const createClass = require('create-react-class'); const _ = require('lodash'); +const createClass = require('create-react-class'); + const request = require('../../utils/request-middleware.js'); const { Meta } = require('vitreum/headtags'); @@ -27,9 +28,11 @@ const Markdown = require('naturalcrit/markdown.js'); const { DEFAULT_BREW_LOAD } = require('../../../../server/brewDefaults.js'); const { printCurrentBrew, fetchThemeBundle } = require('../../../../shared/helpers.js'); +import { updateHistory, versionHistoryGarbageCollection } from '../../utils/versionHistory.js'; + const googleDriveIcon = require('../../googleDrive.svg'); -const SAVE_TIMEOUT = 3000; +const SAVE_TIMEOUT = 10000; const EditPage = createClass({ displayName : 'EditPage', @@ -41,22 +44,24 @@ const EditPage = createClass({ 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(), - currentEditorPage : 0, - displayLockMessage : this.props.brew.lock || false, - themeBundle : {} + 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 : {} }; }, @@ -113,16 +118,27 @@ const EditPage = createClass({ this.editor.current.update(); }, + 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 }, - isPending : true, - htmlErrors : htmlErrors, - currentEditorPage : this.editor.current.getCurrentPage() - 1 //Offset index since Marked starts pages at 0 + brew : { ...prevState.brew, text: text }, + isPending : true, + htmlErrors : htmlErrors, }), ()=>{if(this.state.autoSave) this.trySave();}); }, @@ -150,6 +166,16 @@ const EditPage = createClass({ 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()){ @@ -202,6 +228,9 @@ const EditPage = createClass({ htmlErrors : Markdown.validate(prevState.brew.text) })); + await updateHistory(this.state.brew); + await versionHistoryGarbageCollection(); + const transfer = this.state.saveGoogle == _.isNil(this.state.brew.googleId); const brew = this.state.brew; @@ -413,6 +442,12 @@ const EditPage = createClass({ renderer={this.state.brew.renderer} userThemes={this.props.userThemes} snippetBundle={this.state.themeBundle.snippets} + updateBrew={this.updateBrew} + onCursorPageChange={this.handleEditorCursorPageChange} + onViewPageChange={this.handleEditorViewPageChange} + currentEditorViewPageNum={this.state.currentEditorViewPageNum} + currentEditorCursorPageNum={this.state.currentEditorCursorPageNum} + currentBrewRendererPageNum={this.state.currentBrewRendererPageNum} /> diff --git a/client/homebrew/pages/errorPage/errors/errorIndex.js b/client/homebrew/pages/errorPage/errors/errorIndex.js index 7bf2caae1..298ec8c7e 100644 --- a/client/homebrew/pages/errorPage/errors/errorIndex.js +++ b/client/homebrew/pages/errorPage/errors/errorIndex.js @@ -2,6 +2,9 @@ const dedent = require('dedent-tabs').default; const loginUrl = 'https://www.naturalcrit.com/login'; +//001-050 : Brew errors +//050-100 : Other pages errors + const errorIndex = (props)=>{ return { // Default catch all @@ -149,8 +152,16 @@ const errorIndex = (props)=>{ **Brew ID:** ${props.brew.brewId}`, + //account page when account is not defined + '50' : dedent` + ## You are not signed in + + You are trying to access the account page, but are not signed in to an account. + + Please login or signup at our [login page](https://www.naturalcrit.com/login?redirect=https://homebrewery.naturalcrit.com/account).`, + // Brew locked by Administrators error - '100' : dedent` + '51' : dedent` ## This brew has been locked. Only an author may request that this lock is removed. @@ -160,7 +171,17 @@ const errorIndex = (props)=>{ **Brew ID:** ${props.brew.brewId} **Brew Title:** ${props.brew.brewTitle}`, + + // ####### Admin page error ####### + '52': dedent` + ## Access Denied + You need to provide correct administrator credentials to access this page.`, + + '90' : dedent` An unexpected error occurred while looking for these brews. + Try again in a few minutes.`, + + '91' : dedent` An unexpected error occurred while trying to get the total of brews.`, }; }; -module.exports = errorIndex; \ No newline at end of file +module.exports = errorIndex; diff --git a/client/homebrew/pages/homePage/homePage.jsx b/client/homebrew/pages/homePage/homePage.jsx index 490b22596..ac3be81df 100644 --- a/client/homebrew/pages/homePage/homePage.jsx +++ b/client/homebrew/pages/homePage/homePage.jsx @@ -1,7 +1,6 @@ require('./homePage.less'); const React = require('react'); const createClass = require('create-react-class'); -const _ = require('lodash'); const cx = require('classnames'); const request = require('../../utils/request-middleware.js'); const { Meta } = require('vitreum/headtags'); @@ -10,12 +9,12 @@ 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'); - const SplitPane = require('naturalcrit/splitPane/splitPane.jsx'); const Editor = require('../../editor/editor.jsx'); const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx'); @@ -32,11 +31,13 @@ const HomePage = createClass({ }, getInitialState : function() { return { - brew : this.props.brew, - welcomeText : this.props.brew.text, - error : undefined, - currentEditorPage : 0, - themeBundle : {} + brew : this.props.brew, + welcomeText : this.props.brew.text, + error : undefined, + currentEditorViewPageNum : 1, + currentEditorCursorPageNum : 1, + currentBrewRendererPageNum : 1, + themeBundle : {} }; }, @@ -61,10 +62,22 @@ const HomePage = createClass({ handleSplitMove : function(){ this.editor.current.update(); }, + + handleEditorViewPageChange : function(pageNumber){ + this.setState({ currentEditorViewPageNum: pageNumber }); + }, + + handleEditorCursorPageChange : function(pageNumber){ + this.setState({ currentEditorCursorPageNum: pageNumber }); + }, + + handleBrewRendererPageChange : function(pageNumber){ + this.setState({ currentBrewRendererPageNum: pageNumber }); + }, + handleTextChange : function(text){ this.setState((prevState)=>({ - brew : { ...prevState.brew, text: text }, - currentEditorPage : this.editor.current.getCurrentPage() - 1 //Offset index since Marked starts pages at 0 + brew : { ...prevState.brew, text: text }, })); }, renderNavbar : function(){ @@ -76,6 +89,7 @@ const HomePage = createClass({ } + @@ -96,12 +110,20 @@ const HomePage = createClass({ renderer={this.state.brew.renderer} showEditButtons={false} snippetBundle={this.state.themeBundle.snippets} + onCursorPageChange={this.handleEditorCursorPageChange} + onViewPageChange={this.handleEditorViewPageChange} + currentEditorViewPageNum={this.state.currentEditorViewPageNum} + currentEditorCursorPageNum={this.state.currentEditorCursorPageNum} + currentBrewRendererPageNum={this.state.currentBrewRendererPageNum} /> diff --git a/client/homebrew/pages/newPage/newPage.jsx b/client/homebrew/pages/newPage/newPage.jsx index 5b0f59c00..c147cd474 100644 --- a/client/homebrew/pages/newPage/newPage.jsx +++ b/client/homebrew/pages/newPage/newPage.jsx @@ -39,13 +39,15 @@ const NewPage = createClass({ const brew = this.props.brew; return { - brew : brew, - isSaving : false, - saveGoogle : (global.account && global.account.googleId ? true : false), - error : null, - htmlErrors : Markdown.validate(brew.text), - currentEditorPage : 0, - themeBundle : {} + 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 : {} }; }, @@ -108,15 +110,26 @@ const NewPage = createClass({ this.editor.current.update(); }, + 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, - currentEditorPage : this.editor.current.getCurrentPage() - 1 //Offset index since Marked starts pages at 0 + brew : { ...prevState.brew, text: text }, + htmlErrors : htmlErrors, })); localStorage.setItem(BREWKEY, text); }, @@ -221,6 +234,11 @@ const NewPage = createClass({ renderer={this.state.brew.renderer} userThemes={this.props.userThemes} snippetBundle={this.state.themeBundle.snippets} + onCursorPageChange={this.handleEditorCursorPageChange} + onViewPageChange={this.handleEditorViewPageChange} + currentEditorViewPageNum={this.state.currentEditorViewPageNum} + currentEditorCursorPageNum={this.state.currentEditorCursorPageNum} + currentBrewRendererPageNum={this.state.currentBrewRendererPageNum} /> diff --git a/client/homebrew/pages/sharePage/sharePage.jsx b/client/homebrew/pages/sharePage/sharePage.jsx index 9b4f9b73d..2d96e1ce6 100644 --- a/client/homebrew/pages/sharePage/sharePage.jsx +++ b/client/homebrew/pages/sharePage/sharePage.jsx @@ -25,7 +25,8 @@ const SharePage = createClass({ getInitialState : function() { return { - themeBundle : {} + themeBundle : {}, + currentBrewRendererPageNum : 1 }; }, @@ -39,6 +40,10 @@ const SharePage = createClass({ document.removeEventListener('keydown', this.handleControlKeys); }, + handleBrewRendererPageChange : function(pageNumber){ + this.setState({ currentBrewRendererPageNum: pageNumber }); + }, + handleControlKeys : function(e){ if(!(e.ctrlKey || e.metaKey)) return; const P_KEY = 80; @@ -114,9 +119,12 @@ const SharePage = createClass({
diff --git a/client/homebrew/pages/userPage/userPage.jsx b/client/homebrew/pages/userPage/userPage.jsx index 01778be44..d6fe25b30 100644 --- a/client/homebrew/pages/userPage/userPage.jsx +++ b/client/homebrew/pages/userPage/userPage.jsx @@ -12,6 +12,7 @@ const Account = require('../../navbar/account.navitem.jsx'); const NewBrew = require('../../navbar/newbrew.navitem.jsx'); const HelpNavItem = require('../../navbar/help.navitem.jsx'); const ErrorNavItem = require('../../navbar/error-navitem.jsx'); +const VaultNavitem = require('../../navbar/vault.navitem.jsx'); const UserPage = createClass({ displayName : 'UserPage', @@ -66,6 +67,7 @@ const UserPage = createClass({ } + diff --git a/client/homebrew/pages/vaultPage/vaultPage.jsx b/client/homebrew/pages/vaultPage/vaultPage.jsx new file mode 100644 index 000000000..a51039345 --- /dev/null +++ b/client/homebrew/pages/vaultPage/vaultPage.jsx @@ -0,0 +1,432 @@ +/*eslint max-lines: ["warn", {"max": 400, "skipBlankLines": true, "skipComments": true}]*/ +/*eslint max-params:["warn", { max: 10 }], */ +require('./vaultPage.less'); + +const React = require('react'); +const { useState, useEffect, useRef } = React; + +const Nav = require('naturalcrit/nav/nav.jsx'); +const Navbar = require('../../navbar/navbar.jsx'); +const RecentNavItem = require('../../navbar/recent.navitem.jsx').both; +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 ErrorIndex = require('../errorPage/errors/errorIndex.js'); + +const request = require('../../utils/request-middleware.js'); + +const VaultPage = (props)=>{ + const [pageState, setPageState] = useState(parseInt(props.query.page) || 1); + + const [sortState, setSort] = useState(props.query.sort || 'title'); + const [dirState, setdir] = useState(props.query.dir || 'asc'); + + //Response state + const [brewCollection, setBrewCollection] = useState(null); + const [totalBrews, setTotalBrews] = useState(null); + const [searching, setSearching] = useState(false); + const [error, setError] = useState(null); + + const titleRef = useRef(null); + const authorRef = useRef(null); + const countRef = useRef(null); + const v3Ref = useRef(null); + const legacyRef = useRef(null); + const submitButtonRef = useRef(null); + + useEffect(()=>{ + disableSubmitIfFormInvalid(); + loadPage(pageState, true, props.query.sort, props.query.dir); + }, []); + + const updateStateWithBrews = (brews, page)=>{ + setBrewCollection(brews || null); + setPageState(parseInt(page) || 1); + setSearching(false); + }; + + const updateUrl = (titleValue, authorValue, countValue, v3Value, legacyValue, page, sort, dir)=>{ + const url = new URL(window.location.href); + const urlParams = new URLSearchParams(url.search); + + urlParams.set('title', titleValue); + urlParams.set('author', authorValue); + urlParams.set('count', countValue); + urlParams.set('v3', v3Value); + urlParams.set('legacy', legacyValue); + urlParams.set('page', page); + urlParams.set('sort', sort); + urlParams.set('dir', dir); + + url.search = urlParams.toString(); + window.history.replaceState(null, '', url.toString()); + }; + + const performSearch = async (title, author, count, v3, legacy, page, sort, dir)=>{ + updateUrl(title, author, count, v3, legacy, page, sort, dir); + + const response = await request + .get(`/api/vault?title=${title}&author=${author}&v3=${v3}&legacy=${legacy}&count=${count}&page=${page}&sort=${sort}&dir=${dir}`) + .catch((error)=>{ + console.log('error at loadPage: ', error); + setError(error); + updateStateWithBrews([], 1); + }); + + if(response.ok) + updateStateWithBrews(response.body.brews, page); + }; + + const loadTotal = async (title, author, v3, legacy)=>{ + setTotalBrews(null); + + const response = await request.get(`/api/vault/total?title=${title}&author=${author}&v3=${v3}&legacy=${legacy}`) + .catch((error)=>{ + console.log('error at loadTotal: ', error); + setError(error); + updateStateWithBrews([], 1); + }); + + if(response.ok) + setTotalBrews(response.body.totalBrews); + }; + + const loadPage = async (page, updateTotal, sort, dir)=>{ + if(!validateForm()) return; + + setSearching(true); + setError(null); + + const title = titleRef.current.value || ''; + const author = authorRef.current.value || ''; + const count = countRef.current.value || 10; + const v3 = v3Ref.current.checked != false; + const legacy = legacyRef.current.checked != false; + const sortOption = sort || 'title'; + const dirOption = dir || 'asc'; + const pageProp = page || 1; + + setSort(sortOption); + setdir(dirOption); + + performSearch(title, author, count, v3, legacy, pageProp, sortOption, dirOption); + + if(updateTotal) + loadTotal(title, author, v3, legacy); + }; + + const renderNavItems = ()=>( + + + + Vault: Search for brews + + + + + + + + + + ); + + const validateForm = ()=>{ + //form validity: title or author must be written, and at least one renderer set + const isTitleValid = titleRef.current.validity.valid && titleRef.current.value; + const isAuthorValid = authorRef.current.validity.valid && authorRef.current.value; + const isCheckboxChecked = legacyRef.current.checked || v3Ref.current.checked; + + const isFormValid = (isTitleValid || isAuthorValid) && isCheckboxChecked; + + return isFormValid; + }; + + const disableSubmitIfFormInvalid = ()=>{ + submitButtonRef.current.disabled = !validateForm(); + }; + + const renderForm = ()=>( +
+

Brew Lookup

+
+ + + + + + + + + + + +
+ +

Tips and tricks

+
    +
  • + Only published brews are searchable via this tool +
  • +
  • + Usernames are case-sensitive +
  • +
  • + Use "word" to match an exact string, + and - to exclude words (at least one word must not be negated) +
  • +
  • + Some common words like "a", "after", "through", "itself", "here", etc., + are ignored in searches. The full list can be found   + + here + +
  • +
+ New features will be coming, such as filters and search by tags. +
+
+ ); + + const renderSortOption = (optionTitle, optionValue)=>{ + const oppositeDir = dirState === 'asc' ? 'desc' : 'asc'; + + return ( +
+ + {sortState === optionValue && ( + + )} +
+ ); + }; + + const renderSortBar = ()=>{ + + return ( +
+ {renderSortOption('Title', 'title', props.query.dir)} + {renderSortOption('Created Date', 'createdAt', props.query.dir)} + {renderSortOption('Updated Date', 'updatedAt', props.query.dir)} + {renderSortOption('Views', 'views', props.query.dir)} +
+ ); + }; + + const renderPaginationControls = ()=>{ + if(!totalBrews) return null; + + const countInt = parseInt(props.query.count || 20); + const totalPages = Math.ceil(totalBrews / countInt); + + let startPage, endPage; + if(pageState <= 6) { + startPage = 1; + endPage = Math.min(totalPages, 10); + } else if(pageState + 4 >= totalPages) { + startPage = Math.max(1, totalPages - 9); + endPage = totalPages; + } else { + startPage = pageState - 5; + endPage = pageState + 4; + } + + const pagesAroundCurrent = new Array(endPage - startPage + 1) + .fill() + .map((_, index)=>( + loadPage(startPage + index, false, sortState, dirState)} + > + {startPage + index} + + )); + + return ( +
+ +
    + {startPage > 1 && ( + loadPage(1, false, sortState, dirState)} + > + 1 ... + + )} + {pagesAroundCurrent} + {endPage < totalPages && ( + loadPage(totalPages, false, sortState, dirState)} + > + ... {totalPages} + + )} +
+ +
+ ); + }; + + const renderFoundBrews = ()=>{ + if(searching) { + return ( +
+

Searching

+
+ ); + } + + if(error) { + const errorText = ErrorIndex()[error.HBErrorCode.toString()] || ''; + + return ( +
+

Error: {errorText}

+
+ ); + } + + if(!brewCollection) { + return ( +
+

No search yet

+
+ ); + } + + if(brewCollection.length === 0) { + return ( +
+

No brews found

+
+ ); + } + + return ( +
+ + {`Brews found: `} + {totalBrews} + + {brewCollection.map((brew, index)=>{ + return ( + + ); + })} + {renderPaginationControls()} +
+ ); + }; + + return ( +
+ + + {renderNavItems()} +
+ +
{renderForm()}
+ +
+ {renderSortBar()} + {renderFoundBrews()} +
+
+
+
+ ); +}; + +module.exports = VaultPage; diff --git a/client/homebrew/pages/vaultPage/vaultPage.less b/client/homebrew/pages/vaultPage/vaultPage.less new file mode 100644 index 000000000..b925a7d3e --- /dev/null +++ b/client/homebrew/pages/vaultPage/vaultPage.less @@ -0,0 +1,400 @@ +.vaultPage { + height : 100%; + overflow-y : hidden; + background-color : #2C3E50; + + *:not(input) { user-select : none; } + + .content { + height : 100%; + background : #2C3E50; + + .dataGroup { + width : 100%; + height : 100%; + background : white; + + &.form .brewLookup { + position : relative; + padding : 50px clamp(20px, 4vw, 50px); + + small { + font-size : 10pt; + color : #555555; + + a { color : #333333; } + } + + code { + padding-inline : 5px; + font-family : monospace; + background : lightgrey; + border-radius : 5px; + } + + h1, h2, h3, h4 { + font-family : 'CodeBold'; + letter-spacing : 2px; + } + + legend { + h3 { + margin-block : 30px 20px; + font-size : 20px; + text-align : center; + border-bottom : 2px solid; + } + ul { + padding-inline : 30px 10px; + li { + margin-block : 5px; + line-height : calc(1em + 5px); + list-style : disc; + } + } + } + + &::after { + position : absolute; + top : 0; + right : 0; + left : 0; + display : block; + padding : 10px; + font-weight : 900; + color : white; + white-space : pre-wrap; + content : 'Error:\A At least one renderer should be enabled to make a search'; + background : rgb(255, 60, 60); + opacity : 0; + transition : opacity 0.5s; + } + &:not(:has(input[type='checkbox']:checked))::after { opacity : 1; } + + .formTitle { + margin : 20px 0; + font-size : 30px; + color : black; + text-align : center; + border-bottom : 2px solid; + } + + .formContents { + position : relative; + display : flex; + flex-direction : column; + + label { + display : flex; + align-items : center; + margin : 10px 0; + } + select { margin : 0 10px; } + + input { + margin : 0 10px; + + &:invalid { background : rgb(255, 188, 181); } + + &[type='checkbox'] { + position : relative; + display : inline-block; + width : 50px; + height : 30px; + font-family : 'WalterTurncoat'; + font-size : 20px; + font-weight : 800; + color : white; + letter-spacing : 2px; + appearance : none; + background : red; + isolation : isolate; + border-radius : 5px; + + &::before,&::after { + position : absolute; + inset : 0; + z-index : 5; + padding-top : 2px; + text-align : center; + } + + &::before { + display : block; + content : 'No'; + } + + &::after { + display : none; + content : 'Yes'; + } + + &:checked { + background : green; + + &::before { display : none; } + &::after { display : block; } + } + } + } + + #searchButton { + position : absolute; + right : 20px; + bottom : 0; + + i { + margin-left : 10px; + animation-duration : 1000s; + } + } + } + } + + &.resultsContainer { + display : flex; + flex-direction : column; + height : 100%; + overflow-y : auto; + font-family : 'BookInsanityRemake'; + font-size : 0.34cm; + + h3 { + font-family : 'Open Sans'; + font-weight : 900; + color : white; + } + + .sort-container { + display : flex; + flex-wrap : wrap; + column-gap : 15px; + justify-content : center; + height : 30px; + color : white; + background-color : #555555; + border-top : 1px solid #666666; + border-bottom : 1px solid #666666; + + .sort-option { + display : flex; + align-items : center; + padding : 0 8px; + + &:hover { background-color : #444444; } + + &.active { + background-color : #333333; + + button { + font-weight : 800; + color : white; + + & + .sortDir { padding-left : 5px; } + } + } + + button { + padding : 0; + font-size : 11px; + font-weight : normal; + color : #CCCCCC; + text-transform : uppercase; + background-color : transparent; + + &:hover { background : none; } + } + } + } + + .foundBrews { + position : relative; + width : 100%; + height : 100%; + max-height : 100%; + padding : 50px 50px 70px 50px; + overflow-y : scroll; + background-color : #2C3E50; + + h3 { font-size : 25px; } + + &.noBrews { + display : grid; + place-items : center; + color : white; + } + + &.searching { + display : grid; + place-items : center; + color : white; + + h3 { position : relative; } + + h3.searchAnim::after { + position : absolute; + top : 50%; + right : 0; + width : max-content; + height : 1em; + content : ''; + translate : calc(100% + 5px) -50%; + animation : trailingDots 2s ease infinite; + } + } + + .totalBrews { + position : fixed; + right : 0; + bottom : 0; + z-index : 1000; + padding : 8px 10px; + font-family : 'Open Sans'; + font-size : 11px; + font-weight : 800; + color : white; + background-color : #333333; + + .searchAnim { + position : relative; + display : inline-block; + width : 3ch; + height : 1em; + } + + .searchAnim::after { + position : absolute; + top : 50%; + right : 0; + width : max-content; + height : 1em; + content : ''; + translate : -50% -50%; + animation : trailingDots 2s ease infinite; + } + } + + .brewItem { + width : 47%; + margin-right : 40px; + color : black; + isolation : isolate; + + &::after { + position : absolute; + inset : 0; + z-index : -2; + display : block; + content : ''; + background-image : url('/assets/parchmentBackground.jpg'); + } + + &:nth-child(even of .brewItem) { margin-right : 0; } + + h2 { + font-family : 'MrEavesRemake'; + font-size : 0.75cm; + font-weight : 800; + line-height : 0.988em; + color : var(--HB_Color_HeaderText); + } + .info { + position : relative; + z-index : 2; + font-family : 'ScalySansRemake'; + font-size : 1.2em; + + >span { + margin-right : 12px; + line-height : 1.5em; + } + } + .links { z-index : 2; } + + hr { + margin : 0px; + visibility : hidden; + } + + .thumbnail { z-index : -1; } + } + + .paginationControls { + position : absolute; + left : 50%; + display : grid; + grid-template-areas : 'previousPage currentPage nextPage'; + grid-template-columns : 50px 1fr 50px; + place-items : center; + width : auto; + translate : -50%; + + .pages { + display : flex; + grid-area : currentPage; + justify-content : space-evenly; + width : 100%; + height : 100%; + padding : 5px 8px; + text-align : center; + + .pageNumber { + margin-inline : 1vw; + font-family : 'Open Sans'; + font-weight : 900; + color : white; + text-underline-position : under; + text-wrap : nowrap; + cursor : pointer; + + &.currentPage { + color : gold; + text-decoration : underline; + pointer-events : none; + } + + &.firstPage { margin-right : -5px; } + + &.lastPage { margin-left : -5px; } + } + } + + button { + width : max-content; + + &.previousPage { grid-area : previousPage; } + + &.nextPage { grid-area : nextPage; } + } + + } + } + } + } + } +} + +@keyframes trailingDots { + + 0%, + 32% { content : ' .'; } + + 33%, + 65% { content : ' ..'; } + + 66%, + 100% { content : ' ...'; } +} + +// media query for when the page is smaller than 1079 px in width +@media screen and (max-width : 1079px) { + .vaultPage .content { + + .dataGroup.form .brewLookup { padding : 1px 20px 20px 10px; } + + .dataGroup.resultsContainer .foundBrews .brewItem { + width : 100%; + margin-inline : auto; + } + } +} diff --git a/client/homebrew/utils/versionHistory.js b/client/homebrew/utils/versionHistory.js new file mode 100644 index 000000000..a23af844a --- /dev/null +++ b/client/homebrew/utils/versionHistory.js @@ -0,0 +1,119 @@ +import * as IDB from 'idb-keyval/dist/index.js'; + +export const HISTORY_PREFIX = 'HOMEBREWERY-HISTORY'; +export const HISTORY_SLOTS = 5; + +// History values in minutes +const HISTORY_SAVE_DELAYS = { + '0' : 0, + '1' : 2, + '2' : 10, + '3' : 60, + '4' : 12 * 60, + '5' : 2 * 24 * 60 +}; +// const HISTORY_SAVE_DELAYS = { +// '0' : 0, +// '1' : 1, +// '2' : 2, +// '3' : 3, +// '4' : 4, +// '5' : 5 +// }; + +const HB_DB = 'HOMEBREWERY-DB'; +const HB_STORE = 'HISTORY'; + +const GARBAGE_COLLECT_DELAY = 28 * 24 * 60; +// const GARBAGE_COLLECT_DELAY = 10; + + +function getKeyBySlot(brew, slot){ + // Return a string representing the key for this brew and history slot + return `${HISTORY_PREFIX}-${brew.shareId}-${slot}`; +}; + +function parseBrewForStorage(brew, slot = 0) { + // Strip out unneeded object properties + // Returns an array of [ key, brew ] + const archiveBrew = { + title : brew.title, + text : brew.text, + style : brew.style, + version : brew.version, + shareId : brew.shareId, + savedAt : brew?.savedAt || new Date(), + expireAt : new Date() + }; + + archiveBrew.expireAt.setMinutes(archiveBrew.expireAt.getMinutes() + HISTORY_SAVE_DELAYS[slot]); + + const key = getKeyBySlot(brew, slot); + + return [key, archiveBrew]; +} + +// Create a custom IDB store +async function createHBStore(){ + return await IDB.createStore(HB_DB, HB_STORE); +} + +export async function loadHistory(brew){ + const DEFAULT_HISTORY_ITEM = { expireAt: '2000-01-01T00:00:00.000Z', shareId: brew.shareId, noData: true }; + + const historyKeys = []; + + // Create array of all history keys + for (let i = 1; i <= HISTORY_SLOTS; i++){ + historyKeys.push(getKeyBySlot(brew, i)); + }; + + // Load all keys from IDB at once + const dataArray = await IDB.getMany(historyKeys, await createHBStore()); + return dataArray.map((data)=>{ return data ?? DEFAULT_HISTORY_ITEM; }); +} + +export async function updateHistory(brew) { + const history = await loadHistory(brew); + + // Walk each version position + for (let slot = HISTORY_SLOTS - 1; slot >= 0; slot--){ + const storedVersion = history[slot]; + + // If slot has expired, update all lower slots and break + if(new Date() >= new Date(storedVersion.expireAt)){ + + // Create array of arrays : [ [key1, value1], [key2, value2], ..., [keyN, valueN] ] + // to pass to IDB.setMany + const historyUpdate = []; + + for (let updateSlot = slot; updateSlot > 0; updateSlot--){ + // Move data from updateSlot to updateSlot + 1 + if(!history[updateSlot - 1]?.noData) { + historyUpdate.push(parseBrewForStorage(history[updateSlot - 1], updateSlot + 1)); + } + }; + + // Update the most recent brew + historyUpdate.push(parseBrewForStorage(brew, 1)); + + await IDB.setMany(historyUpdate, await createHBStore()); + + // Break out of data checks because we found an expired value + break; + } + }; +}; + +export async function versionHistoryGarbageCollection(){ + + const entries = await IDB.entries(await createHBStore()); + + for (const [key, value] of entries){ + const expireAt = new Date(value.savedAt); + expireAt.setMinutes(expireAt.getMinutes() + GARBAGE_COLLECT_DELAY); + if(new Date() > expireAt){ + await IDB.del(key, await createHBStore()); + }; + }; +}; \ No newline at end of file diff --git a/client/icons/customIcons.less b/client/icons/customIcons.less index dd6605326..0d462833d 100644 --- a/client/icons/customIcons.less +++ b/client/icons/customIcons.less @@ -1,57 +1,75 @@ .fac { - display : inline-block; + display : inline-block; + background-color : currentColor; + mask-size : contain; + mask-repeat : no-repeat; + mask-position : center; + width : 1em; + aspect-ratio : 1; } .position-top-left { - content: url('../icons/position-top-left.svg'); + mask-image: url('../icons/position-top-left.svg'); } .position-top-right { - content: url('../icons/position-top-right.svg'); + mask-image: url('../icons/position-top-right.svg'); } .position-bottom-left { - content: url('../icons/position-bottom-left.svg'); + mask-image: url('../icons/position-bottom-left.svg'); } .position-bottom-right { - content: url('../icons/position-bottom-right.svg'); + mask-image: url('../icons/position-bottom-right.svg'); } .position-top { - content: url('../icons/position-top.svg'); + mask-image: url('../icons/position-top.svg'); } .position-right { - content: url('../icons/position-right.svg'); + mask-image: url('../icons/position-right.svg'); } .position-bottom { - content: url('../icons/position-bottom.svg'); + mask-image: url('../icons/position-bottom.svg'); } .position-left { - content: url('../icons/position-left.svg'); + mask-image: url('../icons/position-left.svg'); } .mask-edge { - content: url('../icons/mask-edge.svg'); + mask-image: url('../icons/mask-edge.svg'); } .mask-corner { - content: url('../icons/mask-corner.svg'); + mask-image: url('../icons/mask-corner.svg'); } .mask-center { - content: url('../icons/mask-center.svg'); + mask-image: url('../icons/mask-center.svg'); } .book-front-cover { - content: url('../icons/book-front-cover.svg'); + mask-image: url('../icons/book-front-cover.svg'); } .book-back-cover { - content: url('../icons/book-back-cover.svg'); + mask-image: url('../icons/book-back-cover.svg'); } .book-inside-cover { - content: url('../icons/book-inside-cover.svg'); + mask-image: url('../icons/book-inside-cover.svg'); } .book-part-cover { - content: url('../icons/book-part-cover.svg'); + mask-image: url('../icons/book-part-cover.svg'); +} +.image-wrap-left { + mask-image: url('../icons/image-wrap-left.svg'); +} +.image-wrap-right { + mask-image: url('../icons/image-wrap-right.svg'); } .davek { - content: url('../icons/Davek.svg'); + mask-image: url('../icons/Davek.svg'); } .rellanic { - content: url('../icons/Rellanic.svg'); + mask-image: url('../icons/Rellanic.svg'); } .iokharic { - content: url('../icons/Iokharic.svg'); + mask-image: url('../icons/Iokharic.svg'); +} +.zoom-to-fit { + mask-image: url('../icons/zoom-to-fit.svg'); +} +.fit-width { + mask-image: url('../icons/fit-width.svg'); } diff --git a/client/icons/fit-width.svg b/client/icons/fit-width.svg new file mode 100644 index 000000000..dd3e52f75 --- /dev/null +++ b/client/icons/fit-width.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/client/icons/image-wrap-left.svg b/client/icons/image-wrap-left.svg new file mode 100644 index 000000000..fe1024e43 --- /dev/null +++ b/client/icons/image-wrap-left.svg @@ -0,0 +1,58 @@ + + diff --git a/client/icons/image-wrap-right.svg b/client/icons/image-wrap-right.svg new file mode 100644 index 000000000..336a20b64 --- /dev/null +++ b/client/icons/image-wrap-right.svg @@ -0,0 +1,58 @@ + + diff --git a/client/icons/zoom-to-fit.svg b/client/icons/zoom-to-fit.svg new file mode 100644 index 000000000..5179ec45e --- /dev/null +++ b/client/icons/zoom-to-fit.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/config/default.json b/config/default.json index 12b35e6cf..bea3b2663 100644 --- a/config/default.json +++ b/config/default.json @@ -6,5 +6,7 @@ "enable_v3" : true, "enable_themes" : true, "local_environments" : ["docker", "local"], - "publicUrl" : "https://homebrewery.naturalcrit.com" + "publicUrl" : "https://homebrewery.naturalcrit.com", + "hb_images" : null, + "hb_fonts" : null } diff --git a/package-lock.json b/package-lock.json index 49ac3f775..e3fb0ad29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,70 +1,71 @@ { "name": "homebrewery", - "version": "3.14.1", + "version": "3.16.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "homebrewery", - "version": "3.14.1", + "version": "3.16.0", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.25.2", - "@babel/plugin-transform-runtime": "^7.25.4", - "@babel/preset-env": "^7.25.4", - "@babel/preset-react": "^7.24.7", - "@googleapis/drive": "^8.13.0", + "@babel/core": "^7.25.8", + "@babel/plugin-transform-runtime": "^7.25.7", + "@babel/preset-env": "^7.25.8", + "@babel/preset-react": "^7.25.7", + "@googleapis/drive": "^8.14.0", "body-parser": "^1.20.2", "classnames": "^2.5.1", "codemirror": "^5.65.6", - "cookie-parser": "^1.4.6", + "cookie-parser": "^1.4.7", "create-react-class": "^15.7.0", "dedent-tabs": "^0.10.3", - "dompurify": "^3.1.6", + "dompurify": "^3.1.7", "expr-eval": "^2.0.2", - "express": "^4.19.2", + "express": "^4.21.1", "express-async-handler": "^1.2.0", - "express-static-gzip": "2.1.7", + "express-static-gzip": "2.1.8", "fs-extra": "11.2.0", + "idb-keyval": "^6.2.1", "js-yaml": "^4.1.0", "jwt-simple": "^0.5.6", "less": "^3.13.1", "lodash": "^4.17.21", "marked": "11.2.0", "marked-emoji": "^1.4.2", - "marked-extended-tables": "^1.0.8", + "marked-extended-tables": "^1.0.10", "marked-gfm-heading-id": "^3.2.0", "marked-smartypants-lite": "^1.0.2", "markedLegacy": "npm:marked@^0.3.19", "moment": "^2.30.1", - "mongoose": "^8.5.3", + "mongoose": "^8.7.1", "nanoid": "3.3.4", "nconf": "^0.12.1", "react": "^18.3.1", "react-dom": "^18.3.1", "react-frame-component": "^4.1.3", - "react-router-dom": "6.26.1", + "react-router-dom": "6.27.0", "sanitize-filename": "1.6.3", - "superagent": "^9.0.2", + "superagent": "^10.1.0", "vitreum": "git+https://git@github.com/calculuschild/vitreum.git" }, "devDependencies": { - "@stylistic/stylelint-plugin": "^3.0.1", - "eslint": "^9.9.0", - "eslint-plugin-jest": "^28.8.0", - "eslint-plugin-react": "^7.35.0", - "globals": "^15.9.0", + "@stylistic/stylelint-plugin": "^3.1.1", + "eslint": "^9.12.0", + "eslint-plugin-jest": "^28.8.3", + "eslint-plugin-react": "^7.37.1", + "globals": "^15.11.0", "jest": "^29.7.0", "jest-expect-message": "^1.1.3", "postcss-less": "^6.0.0", - "stylelint": "^16.8.2", - "stylelint-config-recess-order": "^5.1.0", + "stylelint": "^16.10.0", + "stylelint-config-recess-order": "^5.1.1", "stylelint-config-recommended": "^14.0.1", "supertest": "^7.0.0" }, "engines": { - "node": "^20.8.x", + "node": "^20.17.x", "npm": "^10.2.x" } }, @@ -82,12 +83,11 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", - "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz", + "integrity": "sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==", "dependencies": { - "@babel/highlight": "^7.24.7", + "@babel/highlight": "^7.25.7", "picocolors": "^1.0.0" }, "engines": { @@ -95,29 +95,28 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", - "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.8.tgz", + "integrity": "sha512-ZsysZyXY4Tlx+Q53XdnOFmqwfB9QDTHYxaZYajWRoBLuLEAwI2UIbtxOjWh/cFaa9IKUlcB+DDuoskLuKu56JA==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", - "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", - "license": "MIT", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.8.tgz", + "integrity": "sha512-Oixnb+DzmRT30qu9d3tJSQkxuygWm32DFykT4bRoORPa9hZ/L4KhVB/XiRm6KG+roIEM7DBQlmg27kw2HZkdZg==", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.0", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-module-transforms": "^7.25.2", - "@babel/helpers": "^7.25.0", - "@babel/parser": "^7.25.0", - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.2", - "@babel/types": "^7.25.2", + "@babel/code-frame": "^7.25.7", + "@babel/generator": "^7.25.7", + "@babel/helper-compilation-targets": "^7.25.7", + "@babel/helper-module-transforms": "^7.25.7", + "@babel/helpers": "^7.25.7", + "@babel/parser": "^7.25.8", + "@babel/template": "^7.25.7", + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.8", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -133,53 +132,50 @@ } }, "node_modules/@babel/generator": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.4.tgz", - "integrity": "sha512-NFtZmZsyzDPJnk9Zg3BbTfKKc9UlHYzD0E//p2Z3B9nCwwtJW9T0gVbCz8+fBngnn4zf1Dr3IK8PHQQHq0lDQw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.7.tgz", + "integrity": "sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==", "dependencies": { - "@babel/types": "^7.25.4", + "@babel/types": "^7.25.7", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", - "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.7.tgz", + "integrity": "sha512-4xwU8StnqnlIhhioZf1tqnVWeQ9pvH/ujS8hRfw/WOza+/a+1qv69BWNy+oY231maTCWgKWhfBU7kDpsds6zAA==", "dependencies": { - "@babel/types": "^7.24.7" + "@babel/types": "^7.25.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz", - "integrity": "sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.25.7.tgz", + "integrity": "sha512-12xfNeKNH7jubQNm7PAkzlLwEmCs1tfuX3UjIw6vP6QXi+leKh6+LyC/+Ed4EIQermwd58wsyh070yjDHFlNGg==", "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", - "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.7.tgz", + "integrity": "sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A==", "dependencies": { - "@babel/compat-data": "^7.25.2", - "@babel/helper-validator-option": "^7.24.8", - "browserslist": "^4.23.1", + "@babel/compat-data": "^7.25.7", + "@babel/helper-validator-option": "^7.25.7", + "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -188,16 +184,16 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.4.tgz", - "integrity": "sha512-ro/bFs3/84MDgDmMwbcHgDa8/E6J3QKNTk4xJJnVeFtGE+tL0K26E3pNxhYz2b67fJpt7Aphw5XcploKXuCvCQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.7.tgz", + "integrity": "sha512-bD4WQhbkx80mAyj/WCm4ZHcF4rDxkoLFO6ph8/5/mQ3z4vAzltQXAmbc7GvVJx5H+lk5Mi5EmbTeox5nMGCsbw==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-member-expression-to-functions": "^7.24.8", - "@babel/helper-optimise-call-expression": "^7.24.7", - "@babel/helper-replace-supers": "^7.25.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/traverse": "^7.25.4", + "@babel/helper-annotate-as-pure": "^7.25.7", + "@babel/helper-member-expression-to-functions": "^7.25.7", + "@babel/helper-optimise-call-expression": "^7.25.7", + "@babel/helper-replace-supers": "^7.25.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.7", + "@babel/traverse": "^7.25.7", "semver": "^6.3.1" }, "engines": { @@ -208,13 +204,12 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.2.tgz", - "integrity": "sha512-+wqVGP+DFmqwFD3EH6TMTfUNeqDehV3E/dl+Sd54eaXqm17tEUNbEIn4sVivVowbvUpOtIGxdo3GoXyDH9N/9g==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.7.tgz", + "integrity": "sha512-byHhumTj/X47wJ6C6eLpK7wW/WBEcnUeb7D0FNc/jFQnQVw7DOso3Zz5u9x/zLrFVkHa89ZGDbkAa1D54NdrCQ==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "regexpu-core": "^5.3.1", + "@babel/helper-annotate-as-pure": "^7.25.7", + "regexpu-core": "^6.1.1", "semver": "^6.3.1" }, "engines": { @@ -241,41 +236,38 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", - "integrity": "sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.7.tgz", + "integrity": "sha512-O31Ssjd5K6lPbTX9AAYpSKrZmLeagt9uwschJd+Ixo6QiRyfpvgtVQp8qrDR9UNFjZ8+DO34ZkdrN+BnPXemeA==", "dependencies": { - "@babel/traverse": "^7.24.8", - "@babel/types": "^7.24.8" + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", - "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.7.tgz", + "integrity": "sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==", "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", - "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.7.tgz", + "integrity": "sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==", "dependencies": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-simple-access": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7", - "@babel/traverse": "^7.25.2" + "@babel/helper-module-imports": "^7.25.7", + "@babel/helper-simple-access": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", + "@babel/traverse": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -285,35 +277,32 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", - "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.7.tgz", + "integrity": "sha512-VAwcwuYhv/AT+Vfr28c9y6SHzTan1ryqrydSTFGjU0uDJHw3uZ+PduI8plCLkRsDnqK2DMEDmwrOQRsK/Ykjng==", "dependencies": { - "@babel/types": "^7.24.7" + "@babel/types": "^7.25.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", - "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.7.tgz", + "integrity": "sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.0.tgz", - "integrity": "sha512-NhavI2eWEIz/H9dbrG0TuOicDhNexze43i5z7lEqwYm0WEZVTwnPpA0EafUTP7+6/W79HWIP2cTe3Z5NiSTVpw==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.7.tgz", + "integrity": "sha512-kRGE89hLnPfcz6fTrlNU+uhgcwv0mBE4Gv3P9Ke9kLVJYpi4AMVVEElXvB5CabrPZW4nCM8P8UyyjrzCM0O2sw==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-wrap-function": "^7.25.0", - "@babel/traverse": "^7.25.0" + "@babel/helper-annotate-as-pure": "^7.25.7", + "@babel/helper-wrap-function": "^7.25.7", + "@babel/traverse": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -323,14 +312,13 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz", - "integrity": "sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.7.tgz", + "integrity": "sha512-iy8JhqlUW9PtZkd4pHM96v6BdJ66Ba9yWSE4z0W4TvSZwLBPkyDsiIU3ENe4SmrzRBs76F7rQXTy1lYC49n6Lw==", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.24.8", - "@babel/helper-optimise-call-expression": "^7.24.7", - "@babel/traverse": "^7.25.0" + "@babel/helper-member-expression-to-functions": "^7.25.7", + "@babel/helper-optimise-call-expression": "^7.25.7", + "@babel/traverse": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -340,92 +328,84 @@ } }, "node_modules/@babel/helper-simple-access": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", - "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.7.tgz", + "integrity": "sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==", "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", - "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.7.tgz", + "integrity": "sha512-pPbNbchZBkPMD50K0p3JGcFMNLVUCuU/ABybm/PGNj4JiHrpmNyqqCphBk4i19xXtNV0JhldQJJtbSW5aUvbyA==", "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz", + "integrity": "sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz", + "integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", - "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.7.tgz", + "integrity": "sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.0.tgz", - "integrity": "sha512-s6Q1ebqutSiZnEjaofc/UKDyC4SbzV5n5SrA2Gq8UawLycr3i04f1dX4OzoQVnexm6aOCh37SQNYlJ/8Ku+PMQ==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.7.tgz", + "integrity": "sha512-MA0roW3JF2bD1ptAaJnvcabsVlNQShUaThyJbCDD4bCp8NEgiFvpoqRI2YS22hHlc2thjO/fTg2ShLMC3jygAg==", "dependencies": { - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.0", - "@babel/types": "^7.25.0" + "@babel/template": "^7.25.7", + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz", - "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.7.tgz", + "integrity": "sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA==", "dependencies": { - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.0" + "@babel/template": "^7.25.7", + "@babel/types": "^7.25.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", - "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.7.tgz", + "integrity": "sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==", "dependencies": { - "@babel/helper-validator-identifier": "^7.24.7", + "@babel/helper-validator-identifier": "^7.25.7", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" @@ -435,11 +415,11 @@ } }, "node_modules/@babel/parser": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.4.tgz", - "integrity": "sha512-nq+eWrOgdtu3jG5Os4TQP3x3cLA8hR8TvJNjD8vnPa20WGycimcparWnLK4jJhElTK6SDyuJo1weMKO/5LpmLA==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.8.tgz", + "integrity": "sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==", "dependencies": { - "@babel/types": "^7.25.4" + "@babel/types": "^7.25.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -449,13 +429,12 @@ } }, "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.3.tgz", - "integrity": "sha512-wUrcsxZg6rqBXG05HG1FPYgsP6EvwF4WpBbxIpWIIYnH8wG0gzx3yZY3dtEHas4sTAOGkbTsc9EGPxwff8lRoA==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.7.tgz", + "integrity": "sha512-UV9Lg53zyebzD1DwQoT9mzkEKa922LNUp5YkTJ6Uta0RbyXaQNUgcvSt7qIu1PpPzVb6rd10OVNTzkyBGeVmxQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/traverse": "^7.25.3" + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/traverse": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -465,12 +444,11 @@ } }, "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.0.tgz", - "integrity": "sha512-Bm4bH2qsX880b/3ziJ8KD711LT7z4u8CFudmjqle65AZj/HNUFhEf90dqYv6O86buWvSBmeQDjv0Tn2aF/bIBA==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.7.tgz", + "integrity": "sha512-GDDWeVLNxRIkQTnJn2pDOM1pkCgYdSqPeT1a9vh9yIqu2uzzgw1zcqEb+IJOhy+dTBMlNdThrDIksr2o09qrrQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -480,12 +458,11 @@ } }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.0.tgz", - "integrity": "sha512-lXwdNZtTmeVOOFtwM/WDe7yg1PL8sYhRk/XH0FzbR2HDQ0xC+EnQ/JHeoMYSavtU115tnUk0q9CDyq8si+LMAA==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.7.tgz", + "integrity": "sha512-wxyWg2RYaSUYgmd9MR0FyRGyeOMQE/Uzr1wzd/g5cf5bwi9A4v6HFdDm7y1MgDtod/fLOSTZY6jDgV0xU9d5bA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -495,14 +472,13 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz", - "integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.7.tgz", + "integrity": "sha512-Xwg6tZpLxc4iQjorYsyGMyfJE7nP5MV8t/Ka58BgiA7Jw0fRqQNcANlLfdJ/yvBt9z9LD2We+BEkT7vLqZRWng==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.7", + "@babel/plugin-transform-optional-chaining": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -512,13 +488,12 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.0.tgz", - "integrity": "sha512-tggFrk1AIShG/RUQbEwt2Tr/E+ObkfwrPjR6BjbRvsx24+PSjK8zrq0GWPNCjo8qpRx4DuJzlcvWJqlm+0h3kw==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.7.tgz", + "integrity": "sha512-UVATLMidXrnH+GMUIuxq55nejlj02HP7F5ETyBONzP6G87fPBogG4CH6kxrSrdIuAjdwNO9VzyaYsrZPscWUrw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/traverse": "^7.25.0" + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/traverse": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -543,6 +518,7 @@ "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -568,6 +544,7 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" @@ -576,52 +553,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.7.tgz", - "integrity": "sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.25.7.tgz", + "integrity": "sha512-ZvZQRmME0zfJnDQnVBKYzHxXT7lYBB3Revz1GuS7oLXWMgqUPX4G+DDbT30ICClht9WKV34QVrZhSw6WdklwZQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -631,12 +568,11 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", - "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.7.tgz", + "integrity": "sha512-AqVo+dguCgmpi/3mYBdu9lkngOBlQ2w2vnNpa6gfiCxQZLzV4ZbhsXitJ2Yblkoe1VQwtHSaNmIaGll/26YWRw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -649,6 +585,7 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" @@ -661,6 +598,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -670,12 +608,11 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", - "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.7.tgz", + "integrity": "sha512-ruZOnKO+ajVL/MVx+PwNBPOkrnXTXoWMtte1MBpegfCArhqOe3Bj52avVj1huLLxNKYKXYaSxZ2F+woK1ekXfw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -688,6 +625,7 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" @@ -700,6 +638,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -712,6 +651,7 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" @@ -724,6 +664,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -736,6 +677,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -748,6 +690,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -756,25 +699,11 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-top-level-await": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" @@ -819,12 +748,11 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz", - "integrity": "sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.7.tgz", + "integrity": "sha512-EJN2mKxDwfOUCPxMO6MUI58RN3ganiRAG/MS/S3HfB6QFNjroAMelQo/gybyYq97WerCBAZoyrAoW8Tzdq2jWg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -834,14 +762,13 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.4.tgz", - "integrity": "sha512-jz8cV2XDDTqjKPwVPJBIjORVEmSGYhdRa8e5k5+vN+uwcjSrSxUaebBRa4ko1jqNF2uxyg8G6XYk30Jv285xzg==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.8.tgz", + "integrity": "sha512-9ypqkozyzpG+HxlH4o4gdctalFGIjjdufzo7I2XPda0iBnZ6a+FO0rIEQcdSPXp02CkvGsII1exJhmROPQd5oA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-remap-async-to-generator": "^7.25.0", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/traverse": "^7.25.4" + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-remap-async-to-generator": "^7.25.7", + "@babel/traverse": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -851,14 +778,13 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", - "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.7.tgz", + "integrity": "sha512-ZUCjAavsh5CESCmi/xCpX1qcCaAglzs/7tmuvoFnJgA1dM7gQplsguljoTg+Ru8WENpX89cQyAtWoaE0I3X3Pg==", "dependencies": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-remap-async-to-generator": "^7.24.7" + "@babel/helper-module-imports": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-remap-async-to-generator": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -868,12 +794,11 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz", - "integrity": "sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.25.7.tgz", + "integrity": "sha512-xHttvIM9fvqW+0a3tZlYcZYSBpSWzGBFIt/sYG3tcdSzBB8ZeVgz2gBP7Df+sM0N1850jrviYSSeUuc+135dmQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -883,12 +808,11 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.0.tgz", - "integrity": "sha512-yBQjYoOjXlFv9nlXb3f1casSHOZkWr29NX+zChVanLg5Nc157CrbEX9D7hxxtTpuFy7Q0YzmmWfJxzvps4kXrQ==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.7.tgz", + "integrity": "sha512-ZEPJSkVZaeTFG/m2PARwLZQ+OG0vFIhPlKHK/JdIMy8DbRJ/htz6LRrTFtdzxi9EHmcwbNPAKDnadpNSIW+Aow==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -898,12 +822,12 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.4.tgz", - "integrity": "sha512-nZeZHyCWPfjkdU5pA/uHiTaDAFUEqkpzf1YoQT2NeSynCGYq9rxfyI3XpQbfx/a0hSnFH6TGlEXvae5Vi7GD8g==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.7.tgz", + "integrity": "sha512-mhyfEW4gufjIqYFo9krXHJ3ElbFLIze5IDp+wQTxoPd+mwFb1NxatNAwmv8Q8Iuxv7Zc+q8EkiMQwc9IhyGf4g==", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.4", - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-create-class-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -913,14 +837,12 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz", - "integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==", - "license": "MIT", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.25.8.tgz", + "integrity": "sha512-e82gl3TCorath6YLf9xUwFehVvjvfqFhdOo4+0iVIVju+6XOi5XHkqB3P2AXnSwoeTX0HBoXq5gJFtvotJzFnQ==", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-class-static-block": "^7.14.5" + "@babel/helper-create-class-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -930,15 +852,15 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.4.tgz", - "integrity": "sha512-oexUfaQle2pF/b6E0dwsxQtAol9TLSO88kQvym6HHBWFliV2lGdrPieX+WgMRLSJDVzdYywk7jXbLPuO2KLTLg==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.7.tgz", + "integrity": "sha512-9j9rnl+YCQY0IGoeipXvnk3niWicIB6kCsWRGLwX241qSXpbA4MKxtp/EdvFxsc4zI5vqfLxzOd0twIJ7I99zg==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-replace-supers": "^7.25.0", - "@babel/traverse": "^7.25.4", + "@babel/helper-annotate-as-pure": "^7.25.7", + "@babel/helper-compilation-targets": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-replace-supers": "^7.25.7", + "@babel/traverse": "^7.25.7", "globals": "^11.1.0" }, "engines": { @@ -957,13 +879,12 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz", - "integrity": "sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.7.tgz", + "integrity": "sha512-QIv+imtM+EtNxg/XBKL3hiWjgdLjMOmZ+XzQwSgmBfKbfxUjBzGgVPklUuE55eq5/uVoh8gg3dqlrwR/jw3ZeA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/template": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/template": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -973,12 +894,11 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.8.tgz", - "integrity": "sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.7.tgz", + "integrity": "sha512-xKcfLTlJYUczdaM1+epcdh1UGewJqr9zATgrNHcLBcV2QmfvPPEixo/sK/syql9cEmbr7ulu5HMFG5vbbt/sEA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -988,13 +908,12 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz", - "integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.7.tgz", + "integrity": "sha512-kXzXMMRzAtJdDEgQBLF4oaiT6ZCU3oWHgpARnTKDAqPkDJ+bs3NrZb310YYevR5QlRo3Kn7dzzIdHbZm1VzJdQ==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-regexp-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1004,12 +923,11 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz", - "integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.7.tgz", + "integrity": "sha512-by+v2CjoL3aMnWDOyCIg+yxU9KXSRa9tN6MbqggH5xvymmr9p4AMjYkNlQy4brMceBnUyHZ9G8RnpvT8wP7Cfg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1019,13 +937,12 @@ } }, "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.0.tgz", - "integrity": "sha512-YLpb4LlYSc3sCUa35un84poXoraOiQucUTTu8X1j18JV+gNa8E0nyUf/CjZ171IRGr4jEguF+vzJU66QZhn29g==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.7.tgz", + "integrity": "sha512-HvS6JF66xSS5rNKXLqkk7L9c/jZ/cdIVIcoPVrnl8IsVpLggTjXs8OWekbLHs/VtYDDh5WXnQyeE3PPUGm22MA==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.0", - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-create-regexp-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1035,13 +952,11 @@ } }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz", - "integrity": "sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==", - "license": "MIT", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.8.tgz", + "integrity": "sha512-gznWY+mr4ZQL/EWPcbBQUP3BXS5FwZp8RUOw06BaRn8tQLzN4XLIxXejpHN9Qo8x8jjBmAAKp6FoS51AgkSA/A==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1051,13 +966,12 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz", - "integrity": "sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.25.7.tgz", + "integrity": "sha512-yjqtpstPfZ0h/y40fAXRv2snciYr0OAoMXY/0ClC7tm4C/nG5NJKmIItlaYlLbIVAWNfrYuy9dq1bE0SbX0PEg==", "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1067,13 +981,11 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz", - "integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==", - "license": "MIT", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.8.tgz", + "integrity": "sha512-sPtYrduWINTQTW7FtOy99VCTWp4H23UX7vYcut7S4CIMEXU+54zKX9uCoGkLsWXteyaMXzVHgzWbLfQ1w4GZgw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1083,13 +995,12 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz", - "integrity": "sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.7.tgz", + "integrity": "sha512-n/TaiBGJxYFWvpJDfsxSj9lEEE44BFM1EPGz4KEiTipTgkoFVVcCmzAL3qA7fdQU96dpo4gGf5HBx/KnDvqiHw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1099,14 +1010,13 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.25.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.1.tgz", - "integrity": "sha512-TVVJVdW9RKMNgJJlLtHsKDTydjZAbwIsn6ySBPQaEAUU5+gVvlJt/9nRmqVbsV/IBanRjzWoaAQKLoamWVOUuA==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.7.tgz", + "integrity": "sha512-5MCTNcjCMxQ63Tdu9rxyN6cAWurqfrDZ76qvVPrGYdBxIj+EawuuxTu/+dgJlhK5eRz3v1gLwp6XwS8XaX2NiQ==", "dependencies": { - "@babel/helper-compilation-targets": "^7.24.8", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/traverse": "^7.25.1" + "@babel/helper-compilation-targets": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/traverse": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1116,13 +1026,11 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz", - "integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==", - "license": "MIT", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.8.tgz", + "integrity": "sha512-4OMNv7eHTmJ2YXs3tvxAfa/I43di+VcF+M4Wt66c88EAED1RoGaf1D64cL5FkRpNL+Vx9Hds84lksWvd/wMIdA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-json-strings": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1132,12 +1040,11 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.2.tgz", - "integrity": "sha512-HQI+HcTbm9ur3Z2DkO+jgESMAMcYLuN/A7NRw9juzxAezN9AvqvUTnpKP/9kkYANz6u7dFlAyOu44ejuGySlfw==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.7.tgz", + "integrity": "sha512-fwzkLrSu2fESR/cm4t6vqd7ebNIopz2QHGtjoU+dswQo/P6lwAG04Q98lliE3jkz/XqnbGFLnUcE0q0CVUf92w==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1147,13 +1054,11 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz", - "integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==", - "license": "MIT", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.8.tgz", + "integrity": "sha512-f5W0AhSbbI+yY6VakT04jmxdxz+WsID0neG7+kQZbCOjuyJNdL5Nn4WIBm4hRpKnUcO9lP0eipUhFN12JpoH8g==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1163,12 +1068,11 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz", - "integrity": "sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.7.tgz", + "integrity": "sha512-Std3kXwpXfRV0QtQy5JJcRpkqP8/wG4XL7hSKZmGlxPlDqmpXtEPRmhF7ztnlTCtUN3eXRUJp+sBEZjaIBVYaw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1178,13 +1082,12 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz", - "integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.7.tgz", + "integrity": "sha512-CgselSGCGzjQvKzghCvDTxKHP3iooenLpJDO842ehn5D2G5fJB222ptnDwQho0WjEvg7zyoxb9P+wiYxiJX5yA==", "dependencies": { - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-module-transforms": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1194,14 +1097,13 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz", - "integrity": "sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.25.7.tgz", + "integrity": "sha512-L9Gcahi0kKFYXvweO6n0wc3ZG1ChpSFdgG+eV1WYZ3/dGbJK7vvk91FgGgak8YwRgrCuihF8tE/Xg07EkL5COg==", "dependencies": { - "@babel/helper-module-transforms": "^7.24.8", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-simple-access": "^7.24.7" + "@babel/helper-module-transforms": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-simple-access": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1211,15 +1113,14 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz", - "integrity": "sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.7.tgz", + "integrity": "sha512-t9jZIvBmOXJsiuyOwhrIGs8dVcD6jDyg2icw1VL4A/g+FnWyJKwUfSSU2nwJuMV2Zqui856El9u+ElB+j9fV1g==", "dependencies": { - "@babel/helper-module-transforms": "^7.25.0", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "@babel/traverse": "^7.25.0" + "@babel/helper-module-transforms": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", + "@babel/traverse": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1229,13 +1130,12 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz", - "integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.7.tgz", + "integrity": "sha512-p88Jg6QqsaPh+EB7I9GJrIqi1Zt4ZBHUQtjw3z1bzEXcLh6GfPqzZJ6G+G1HBGKUNukT58MnKG7EN7zXQBCODw==", "dependencies": { - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-module-transforms": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1245,13 +1145,12 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz", - "integrity": "sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.7.tgz", + "integrity": "sha512-BtAT9LzCISKG3Dsdw5uso4oV1+v2NlVXIIomKJgQybotJY3OwCwJmkongjHgwGKoZXd0qG5UZ12JUlDQ07W6Ow==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-regexp-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1261,12 +1160,11 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz", - "integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.7.tgz", + "integrity": "sha512-CfCS2jDsbcZaVYxRFo2qtavW8SpdzmBXC2LOI4oO0rP+JSRDxxF3inF4GcPsLgfb5FjkhXG5/yR/lxuRs2pySA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1276,13 +1174,11 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz", - "integrity": "sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==", - "license": "MIT", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.25.8.tgz", + "integrity": "sha512-Z7WJJWdQc8yCWgAmjI3hyC+5PXIubH9yRKzkl9ZEG647O9szl9zvmKLzpbItlijBnVhTUf1cpyWBsZ3+2wjWPQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1292,13 +1188,11 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz", - "integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==", - "license": "MIT", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.8.tgz", + "integrity": "sha512-rm9a5iEFPS4iMIy+/A/PiS0QN0UyjPIeVvbU5EMZFKJZHt8vQnasbpo3T3EFcxzCeYO0BHfc4RqooCZc51J86Q==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1308,15 +1202,13 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz", - "integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==", - "license": "MIT", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.8.tgz", + "integrity": "sha512-LkUu0O2hnUKHKE7/zYOIjByMa4VRaV2CD/cdGz0AxU9we+VA3kDDggKEzI0Oz1IroG+6gUP6UmWEHBMWZU316g==", "dependencies": { - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.24.7" + "@babel/helper-compilation-targets": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/plugin-transform-parameters": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1326,13 +1218,12 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz", - "integrity": "sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.7.tgz", + "integrity": "sha512-pWT6UXCEW3u1t2tcAGtE15ornCBvopHj9Bps9D2DsH15APgNVOTwwczGckX+WkAvBmuoYKRCFa4DK+jM8vh5AA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-replace-supers": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-replace-supers": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1342,13 +1233,11 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz", - "integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==", - "license": "MIT", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.8.tgz", + "integrity": "sha512-EbQYweoMAHOn7iJ9GgZo14ghhb9tTjgOc88xFgYngifx7Z9u580cENCV159M4xDh3q/irbhSjZVpuhpC2gKBbg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1358,14 +1247,12 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.8.tgz", - "integrity": "sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw==", - "license": "MIT", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.8.tgz", + "integrity": "sha512-q05Bk7gXOxpTHoQ8RSzGSh/LHVB9JEIkKnk3myAWwZHnYiTGYtbdrYkIsS8Xyh4ltKf7GNUSgzs/6P2bJtBAQg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1375,12 +1262,11 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz", - "integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.7.tgz", + "integrity": "sha512-FYiTvku63me9+1Nz7TOx4YMtW3tWXzfANZtrzHhUZrz4d47EEtMQhzFoZWESfXuAMMT5mwzD4+y1N8ONAX6lMQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1390,12 +1276,12 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.4.tgz", - "integrity": "sha512-ao8BG7E2b/URaUQGqN3Tlsg+M3KlHY6rJ1O1gXAEUnZoyNQnvKyH87Kfg+FoxSeyWUB8ISZZsC91C44ZuBFytw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.7.tgz", + "integrity": "sha512-KY0hh2FluNxMLwOCHbxVOKfdB5sjWG4M183885FmaqWWiGMhRZq4DQRKH6mHdEucbJnyDyYiZNwNG424RymJjA==", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.4", - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-create-class-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1405,15 +1291,13 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz", - "integrity": "sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==", - "license": "MIT", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.8.tgz", + "integrity": "sha512-8Uh966svuB4V8RHHg0QJOB32QK287NBksJOByoKmHMp1TAobNniNalIkI2i5IPj5+S9NYCG4VIjbEuiSN8r+ow==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-create-class-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + "@babel/helper-annotate-as-pure": "^7.25.7", + "@babel/helper-create-class-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1423,12 +1307,11 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz", - "integrity": "sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.7.tgz", + "integrity": "sha512-lQEeetGKfFi0wHbt8ClQrUSUMfEeI3MMm74Z73T9/kuz990yYVtfofjf3NuA42Jy3auFOpbjDyCSiIkTs1VIYw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1438,12 +1321,11 @@ } }, "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.7.tgz", - "integrity": "sha512-H/Snz9PFxKsS1JLI4dJLtnJgCJRoo0AUm3chP6NYr+9En1JMKloheEiLIhlp5MDVznWo+H3AAC1Mc8lmUEpsgg==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.7.tgz", + "integrity": "sha512-r0QY7NVU8OnrwE+w2IWiRom0wwsTbjx4+xH2RTd7AVdof3uurXOF+/mXHQDRk+2jIvWgSaCHKMgggfvM4dyUGA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1453,16 +1335,15 @@ } }, "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.2.tgz", - "integrity": "sha512-KQsqEAVBpU82NM/B/N9j9WOdphom1SZH3R+2V7INrQUH+V9EBFwZsEJl8eBIVeQE62FxJCc70jzEZwqU7RcVqA==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.7.tgz", + "integrity": "sha512-vILAg5nwGlR9EXE8JIOX4NHXd49lrYbN8hnjffDtoULwpL9hUx/N55nqh2qd0q6FyNDfjl9V79ecKGvFbcSA0Q==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/plugin-syntax-jsx": "^7.24.7", - "@babel/types": "^7.25.2" + "@babel/helper-annotate-as-pure": "^7.25.7", + "@babel/helper-module-imports": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/plugin-syntax-jsx": "^7.25.7", + "@babel/types": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1472,12 +1353,11 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.24.7.tgz", - "integrity": "sha512-QG9EnzoGn+Qar7rxuW+ZOsbWOt56FvvI93xInqsZDC5fsekx1AlIO4KIJ5M+D0p0SqSH156EpmZyXq630B8OlQ==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.25.7.tgz", + "integrity": "sha512-5yd3lH1PWxzW6IZj+p+Y4OLQzz0/LzlOG8vGqonHfVR3euf1vyzyMUJk9Ac+m97BH46mFc/98t9PmYLyvgL3qg==", "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.24.7" + "@babel/plugin-transform-react-jsx": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1487,13 +1367,12 @@ } }, "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.24.7.tgz", - "integrity": "sha512-PLgBVk3fzbmEjBJ/u8kFzOqS9tUeDjiaWud/rRym/yjCo/M9cASPlnrd2ZmmZpQT40fOOrvR8jh+n8jikrOhNA==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.7.tgz", + "integrity": "sha512-6YTHJ7yjjgYqGc8S+CbEXhLICODk0Tn92j+vNJo07HFk9t3bjFgAKxPLFhHwF2NjmQVSI1zBRfBWUeVBa2osfA==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-annotate-as-pure": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1503,12 +1382,11 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz", - "integrity": "sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.7.tgz", + "integrity": "sha512-mgDoQCRjrY3XK95UuV60tZlFCQGXEtMg8H+IsW72ldw1ih1jZhzYXbJvghmAEpg5UVhhnCeia1CkGttUvCkiMQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-plugin-utils": "^7.25.7", "regenerator-transform": "^0.15.2" }, "engines": { @@ -1519,12 +1397,11 @@ } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz", - "integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.7.tgz", + "integrity": "sha512-3OfyfRRqiGeOvIWSagcwUTVk2hXBsr/ww7bLn6TRTuXnexA+Udov2icFOxFX9abaj4l96ooYkcNN1qi2Zvqwng==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1534,12 +1411,12 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.25.4.tgz", - "integrity": "sha512-8hsyG+KUYGY0coX6KUCDancA0Vw225KJ2HJO0yCNr1vq5r+lJTleDaJf0K7iOhjw4SWhu03TMBzYTJ9krmzULQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.25.7.tgz", + "integrity": "sha512-Y9p487tyTzB0yDYQOtWnC+9HGOuogtP3/wNpun1xJXEEvI6vip59BSBTsHnekZLqxmPcgsrAKt46HAAb//xGhg==", "dependencies": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-module-imports": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", "babel-plugin-polyfill-corejs2": "^0.4.10", "babel-plugin-polyfill-corejs3": "^0.10.6", "babel-plugin-polyfill-regenerator": "^0.6.1", @@ -1553,12 +1430,11 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz", - "integrity": "sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.7.tgz", + "integrity": "sha512-uBbxNwimHi5Bv3hUccmOFlUy3ATO6WagTApenHz9KzoIdn0XeACdB12ZJ4cjhuB2WSi80Ez2FWzJnarccriJeA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1568,13 +1444,12 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz", - "integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.7.tgz", + "integrity": "sha512-Mm6aeymI0PBh44xNIv/qvo8nmbkpZze1KvR8MkEqbIREDxoiWTi18Zr2jryfRMwDfVZF9foKh060fWgni44luw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1584,12 +1459,11 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz", - "integrity": "sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.7.tgz", + "integrity": "sha512-ZFAeNkpGuLnAQ/NCsXJ6xik7Id+tHuS+NT+ue/2+rn/31zcdnupCdmunOizEaP0JsUmTFSTOPoQY7PkK2pttXw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1599,12 +1473,11 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz", - "integrity": "sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.7.tgz", + "integrity": "sha512-SI274k0nUsFFmyQupiO7+wKATAmMFf8iFgq2O+vVFXZ0SV9lNfT1NGzBEhjquFmD8I9sqHLguH+gZVN3vww2AA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1614,12 +1487,11 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz", - "integrity": "sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.7.tgz", + "integrity": "sha512-OmWmQtTHnO8RSUbL0NTdtpbZHeNTnm68Gj5pA4Y2blFNh+V4iZR68V1qL9cI37J21ZN7AaCnkfdHtLExQPf2uA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1629,12 +1501,11 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", - "integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.7.tgz", + "integrity": "sha512-BN87D7KpbdiABA+t3HbVqHzKWUDN3dymLaTnPFAMyc8lV+KN3+YzNhVRNdinaCPA4AUqx7ubXbQ9shRjYBl3SQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1644,13 +1515,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz", - "integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.7.tgz", + "integrity": "sha512-IWfR89zcEPQGB/iB408uGtSPlQd3Jpq11Im86vUgcmSTcoWAiQMCTOa2K2yNNqFJEBVICKhayctee65Ka8OB0w==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-regexp-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1660,13 +1530,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz", - "integrity": "sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.7.tgz", + "integrity": "sha512-8JKfg/hiuA3qXnlLx8qtv5HWRbgyFx2hMMtpDDuU2rTckpKkGu4ycK5yYHwuEa16/quXfoxHBIApEsNyMWnt0g==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-regexp-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1676,12 +1545,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.4.tgz", - "integrity": "sha512-qesBxiWkgN1Q+31xUE9RcMk79eOXXDCv6tfyGMRSs4RGlioSg2WVyQAm07k726cSE56pa+Kb0y9epX2qaXzTvA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.7.tgz", + "integrity": "sha512-YRW8o9vzImwmh4Q3Rffd09bH5/hvY0pxg+1H1i0f7APoUeg12G7+HhLj9ZFNIrYkgBXhIijPJ+IXypN0hLTIbw==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.2", - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-create-regexp-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1691,92 +1560,77 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.4.tgz", - "integrity": "sha512-W9Gyo+KmcxjGahtt3t9fb14vFRWvPpu5pT6GBlovAK6BTBcxgjfVMSQCfJl4oi35ODrxP6xx2Wr8LNST57Mraw==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.8.tgz", + "integrity": "sha512-58T2yulDHMN8YMUxiLq5YmWUnlDCyY1FsHM+v12VMx+1/FlrUj5tY50iDCpofFQEM8fMYOaY9YRvym2jcjn1Dg==", "dependencies": { - "@babel/compat-data": "^7.25.4", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-validator-option": "^7.24.8", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.3", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.0", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.0", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.0", + "@babel/compat-data": "^7.25.8", + "@babel/helper-compilation-targets": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-validator-option": "^7.25.7", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.7", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.7", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.7", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.24.7", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-import-assertions": "^7.25.7", + "@babel/plugin-syntax-import-attributes": "^7.25.7", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.24.7", - "@babel/plugin-transform-async-generator-functions": "^7.25.4", - "@babel/plugin-transform-async-to-generator": "^7.24.7", - "@babel/plugin-transform-block-scoped-functions": "^7.24.7", - "@babel/plugin-transform-block-scoping": "^7.25.0", - "@babel/plugin-transform-class-properties": "^7.25.4", - "@babel/plugin-transform-class-static-block": "^7.24.7", - "@babel/plugin-transform-classes": "^7.25.4", - "@babel/plugin-transform-computed-properties": "^7.24.7", - "@babel/plugin-transform-destructuring": "^7.24.8", - "@babel/plugin-transform-dotall-regex": "^7.24.7", - "@babel/plugin-transform-duplicate-keys": "^7.24.7", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.0", - "@babel/plugin-transform-dynamic-import": "^7.24.7", - "@babel/plugin-transform-exponentiation-operator": "^7.24.7", - "@babel/plugin-transform-export-namespace-from": "^7.24.7", - "@babel/plugin-transform-for-of": "^7.24.7", - "@babel/plugin-transform-function-name": "^7.25.1", - "@babel/plugin-transform-json-strings": "^7.24.7", - "@babel/plugin-transform-literals": "^7.25.2", - "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", - "@babel/plugin-transform-member-expression-literals": "^7.24.7", - "@babel/plugin-transform-modules-amd": "^7.24.7", - "@babel/plugin-transform-modules-commonjs": "^7.24.8", - "@babel/plugin-transform-modules-systemjs": "^7.25.0", - "@babel/plugin-transform-modules-umd": "^7.24.7", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", - "@babel/plugin-transform-new-target": "^7.24.7", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", - "@babel/plugin-transform-numeric-separator": "^7.24.7", - "@babel/plugin-transform-object-rest-spread": "^7.24.7", - "@babel/plugin-transform-object-super": "^7.24.7", - "@babel/plugin-transform-optional-catch-binding": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.8", - "@babel/plugin-transform-parameters": "^7.24.7", - "@babel/plugin-transform-private-methods": "^7.25.4", - "@babel/plugin-transform-private-property-in-object": "^7.24.7", - "@babel/plugin-transform-property-literals": "^7.24.7", - "@babel/plugin-transform-regenerator": "^7.24.7", - "@babel/plugin-transform-reserved-words": "^7.24.7", - "@babel/plugin-transform-shorthand-properties": "^7.24.7", - "@babel/plugin-transform-spread": "^7.24.7", - "@babel/plugin-transform-sticky-regex": "^7.24.7", - "@babel/plugin-transform-template-literals": "^7.24.7", - "@babel/plugin-transform-typeof-symbol": "^7.24.8", - "@babel/plugin-transform-unicode-escapes": "^7.24.7", - "@babel/plugin-transform-unicode-property-regex": "^7.24.7", - "@babel/plugin-transform-unicode-regex": "^7.24.7", - "@babel/plugin-transform-unicode-sets-regex": "^7.25.4", + "@babel/plugin-transform-arrow-functions": "^7.25.7", + "@babel/plugin-transform-async-generator-functions": "^7.25.8", + "@babel/plugin-transform-async-to-generator": "^7.25.7", + "@babel/plugin-transform-block-scoped-functions": "^7.25.7", + "@babel/plugin-transform-block-scoping": "^7.25.7", + "@babel/plugin-transform-class-properties": "^7.25.7", + "@babel/plugin-transform-class-static-block": "^7.25.8", + "@babel/plugin-transform-classes": "^7.25.7", + "@babel/plugin-transform-computed-properties": "^7.25.7", + "@babel/plugin-transform-destructuring": "^7.25.7", + "@babel/plugin-transform-dotall-regex": "^7.25.7", + "@babel/plugin-transform-duplicate-keys": "^7.25.7", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.7", + "@babel/plugin-transform-dynamic-import": "^7.25.8", + "@babel/plugin-transform-exponentiation-operator": "^7.25.7", + "@babel/plugin-transform-export-namespace-from": "^7.25.8", + "@babel/plugin-transform-for-of": "^7.25.7", + "@babel/plugin-transform-function-name": "^7.25.7", + "@babel/plugin-transform-json-strings": "^7.25.8", + "@babel/plugin-transform-literals": "^7.25.7", + "@babel/plugin-transform-logical-assignment-operators": "^7.25.8", + "@babel/plugin-transform-member-expression-literals": "^7.25.7", + "@babel/plugin-transform-modules-amd": "^7.25.7", + "@babel/plugin-transform-modules-commonjs": "^7.25.7", + "@babel/plugin-transform-modules-systemjs": "^7.25.7", + "@babel/plugin-transform-modules-umd": "^7.25.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.7", + "@babel/plugin-transform-new-target": "^7.25.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.25.8", + "@babel/plugin-transform-numeric-separator": "^7.25.8", + "@babel/plugin-transform-object-rest-spread": "^7.25.8", + "@babel/plugin-transform-object-super": "^7.25.7", + "@babel/plugin-transform-optional-catch-binding": "^7.25.8", + "@babel/plugin-transform-optional-chaining": "^7.25.8", + "@babel/plugin-transform-parameters": "^7.25.7", + "@babel/plugin-transform-private-methods": "^7.25.7", + "@babel/plugin-transform-private-property-in-object": "^7.25.8", + "@babel/plugin-transform-property-literals": "^7.25.7", + "@babel/plugin-transform-regenerator": "^7.25.7", + "@babel/plugin-transform-reserved-words": "^7.25.7", + "@babel/plugin-transform-shorthand-properties": "^7.25.7", + "@babel/plugin-transform-spread": "^7.25.7", + "@babel/plugin-transform-sticky-regex": "^7.25.7", + "@babel/plugin-transform-template-literals": "^7.25.7", + "@babel/plugin-transform-typeof-symbol": "^7.25.7", + "@babel/plugin-transform-unicode-escapes": "^7.25.7", + "@babel/plugin-transform-unicode-property-regex": "^7.25.7", + "@babel/plugin-transform-unicode-regex": "^7.25.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.25.7", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.10", "babel-plugin-polyfill-corejs3": "^0.10.6", "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.37.1", + "core-js-compat": "^3.38.1", "semver": "^6.3.1" }, "engines": { @@ -1801,17 +1655,16 @@ } }, "node_modules/@babel/preset-react": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.24.7.tgz", - "integrity": "sha512-AAH4lEkpmzFWrGVlHaxJB7RLH21uPQ9+He+eFLWHmF9IuFQVugz8eAsamaW0DXRrTfco5zj1wWtpdcXJUOfsag==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.25.7.tgz", + "integrity": "sha512-GjV0/mUEEXpi1U5ZgDprMRRgajGMRW3G5FjMr5KLKD8nT2fTG8+h/klV3+6Dm5739QE+K5+2e91qFKAYI3pmRg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-validator-option": "^7.24.7", - "@babel/plugin-transform-react-display-name": "^7.24.7", - "@babel/plugin-transform-react-jsx": "^7.24.7", - "@babel/plugin-transform-react-jsx-development": "^7.24.7", - "@babel/plugin-transform-react-pure-annotations": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-validator-option": "^7.25.7", + "@babel/plugin-transform-react-display-name": "^7.25.7", + "@babel/plugin-transform-react-jsx": "^7.25.7", + "@babel/plugin-transform-react-jsx-development": "^7.25.7", + "@babel/plugin-transform-react-pure-annotations": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1820,17 +1673,10 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", - "license": "MIT" - }, "node_modules/@babel/runtime": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", - "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.7.tgz", + "integrity": "sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -1839,29 +1685,28 @@ } }, "node_modules/@babel/template": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", - "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", - "license": "MIT", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.7.tgz", + "integrity": "sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==", "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.25.0", - "@babel/types": "^7.25.0" + "@babel/code-frame": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/types": "^7.25.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.4.tgz", - "integrity": "sha512-VJ4XsrD+nOvlXyLzmLzUs/0qjFS4sK30te5yEFlvbbUNEgKaVb2BHZUpAL+ttLPQAHNrsI3zZisbfha5Cvr8vg==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.7.tgz", + "integrity": "sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==", "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.4", - "@babel/parser": "^7.25.4", - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.4", + "@babel/code-frame": "^7.25.7", + "@babel/generator": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/template": "^7.25.7", + "@babel/types": "^7.25.7", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -1879,12 +1724,12 @@ } }, "node_modules/@babel/types": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.4.tgz", - "integrity": "sha512-zQ1ijeeCXVEh+aNL0RlmkPkG8HUiDcU2pzQQFjtbntgAczRASFzj4H+6+bV+dy1ntKR14I/DypeuRG1uma98iQ==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.8.tgz", + "integrity": "sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==", "dependencies": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", + "@babel/helper-string-parser": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -2022,11 +1867,10 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.17.1.tgz", - "integrity": "sha512-BlYOpej8AQ8Ev9xVqroV7a02JK3SkBAaN9GfMMH9W6Ch8FlQlkjGw4Ir7+FgYwfirivAf4t+GtzuAxqfukmISA==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", + "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.4", "debug": "^4.3.1", @@ -2036,6 +1880,15 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/core": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.6.0.tgz", + "integrity": "sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/eslintrc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", @@ -2074,11 +1927,10 @@ } }, "node_modules/@eslint/js": { - "version": "9.9.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.9.0.tgz", - "integrity": "sha512-hhetes6ZHP3BlXLxmd8K2SNgkhNSi+UcecbnwWKwpP7kyi/uC75DJ1lOOBO3xrC4jyojtGE3YxKZPHfk4yrgug==", + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.12.0.tgz", + "integrity": "sha512-eohesHH8WFRUprDNyEREgqP6beG6htMeUYeCpkEgBCieCMme5r9zFWjzAJp//9S+Kub4rqE+jXe9Cp1a7IYIIA==", "dev": true, - "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -2088,15 +1940,26 @@ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", "dev": true, - "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz", + "integrity": "sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==", + "dev": true, + "dependencies": { + "levn": "^0.4.1" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@googleapis/drive": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/@googleapis/drive/-/drive-8.13.0.tgz", - "integrity": "sha512-xpXzZeYtNNFLy1m2D5A8/QR2bngpjLPEvO5KZUW4Dlwi/SBHYNTjVm37IQagtQg6QUJlFb4lVLewenUdZZB1rA==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/@googleapis/drive/-/drive-8.14.0.tgz", + "integrity": "sha512-AOokfpP6pCdcJXWA8khaCEgbGpWYavWTdAAhL4idbbf2VCQcJ2f7vPalAYNu6a4Sfj0Ly4Ehnd1xw9J9TixB1A==", "dependencies": { "googleapis-common": "^7.0.0" }, @@ -2104,6 +1967,28 @@ "node": ">=12.0.0" } }, + "node_modules/@humanfs/core": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.0.tgz", + "integrity": "sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==", + "dev": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.5", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.5.tgz", + "integrity": "sha512-KSPA4umqSG4LHYRodq31VDwKAvaTF4xmVlzM8Aeh4PlU1JQ3IG0wiA8C25d3RQ9nJyM3mBHyI53K06VVL/oFFg==", + "dev": true, + "dependencies": { + "@humanfs/core": "^0.19.0", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -2119,11 +2004,10 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", - "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", "dev": true, - "license": "Apache-2.0", "engines": { "node": ">=18.18" }, @@ -2970,10 +2854,9 @@ } }, "node_modules/@mongodb-js/saslprep": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.8.tgz", - "integrity": "sha512-qKwC/M/nNNaKUBMQ0nuzm47b7ZYWQHN3pcXq4IIcoSBc2hOIrflAxJduIvvqmhoz3gR2TacTAs8vlsCVPkiEdQ==", - "license": "MIT", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz", + "integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==", "dependencies": { "sparse-bitfield": "^3.0.3" } @@ -3017,9 +2900,9 @@ } }, "node_modules/@remix-run/router": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.1.tgz", - "integrity": "sha512-S45oynt/WH19bHbIXjtli6QmwNYvaz+vtnubvNpNDvUOoA/OWh6j1OikIP3G+v5GHdxyC6EXoChG3HgYGEUfcg==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.20.0.tgz", + "integrity": "sha512-mUnk8rPJBI9loFDZ+YzPGdeniYK+FTmRD1TMCz7ev2SNIozyKKpnGgsxO34u6Z4z/t0ITuu7voi/AshfsGsgFg==", "engines": { "node": ">=14.0.0" } @@ -3052,14 +2935,14 @@ } }, "node_modules/@stylistic/stylelint-plugin": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@stylistic/stylelint-plugin/-/stylelint-plugin-3.0.1.tgz", - "integrity": "sha512-j3mH8HSw2Rob/KJFWZ627w3CQ8gQqVHtzCdPeEffUg5vOgpz4rgrR+Xw2kU0OQCDcdW8Y1nKfdXKKjM5Rn8X0g==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@stylistic/stylelint-plugin/-/stylelint-plugin-3.1.1.tgz", + "integrity": "sha512-XagAHHIa528EvyGybv8EEYGK5zrVW74cHpsjhtovVATbhDRuJYfE+X4HCaAieW9lCkwbX6L+X0I4CiUG3w/hFw==", "dev": true, "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.0", - "@csstools/css-tokenizer": "^3.0.0", - "@csstools/media-query-list-parser": "^3.0.0", + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1", + "@csstools/media-query-list-parser": "^3.0.1", "is-plain-object": "^5.0.0", "postcss-selector-parser": "^6.1.2", "postcss-value-parser": "^4.2.0", @@ -3118,6 +3001,12 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -3155,6 +3044,12 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, "node_modules/@types/node": { "version": "22.1.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.1.0.tgz", @@ -3175,14 +3070,12 @@ "node_modules/@types/webidl-conversions": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", - "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", - "license": "MIT" + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" }, "node_modules/@types/whatwg-url": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", - "license": "MIT", "dependencies": { "@types/webidl-conversions": "*" } @@ -3471,7 +3364,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "license": "MIT", "dependencies": { "color-convert": "^1.9.0" }, @@ -4134,10 +4026,9 @@ "license": "MIT" }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", - "license": "MIT", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -4147,7 +4038,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -4392,9 +4283,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", - "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", + "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", "funding": [ { "type": "opencollective", @@ -4409,10 +4300,9 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001646", - "electron-to-chromium": "^1.5.4", + "caniuse-lite": "^1.0.30001663", + "electron-to-chromium": "^1.5.28", "node-releases": "^2.0.18", "update-browserslist-db": "^1.1.0" }, @@ -4437,7 +4327,6 @@ "version": "6.8.0", "resolved": "https://registry.npmjs.org/bson/-/bson-6.8.0.tgz", "integrity": "sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ==", - "license": "Apache-2.0", "engines": { "node": ">=16.20.1" } @@ -4551,9 +4440,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001647", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001647.tgz", - "integrity": "sha512-n83xdNiyeNcHpzWY+1aFbqCK7LuLfBricc4+alSQL2Xb6OR3XpnQAmlDG+pQcdTfiHRuLcQ96VOfrPSGiNJYSg==", + "version": "1.0.30001667", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001667.tgz", + "integrity": "sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==", "funding": [ { "type": "opencollective", @@ -4567,14 +4456,12 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ], - "license": "CC-BY-4.0" + ] }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -4740,7 +4627,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "license": "MIT", "dependencies": { "color-name": "1.1.3" } @@ -4748,8 +4634,7 @@ "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "license": "MIT" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, "node_modules/colord": { "version": "2.9.3", @@ -4866,21 +4751,19 @@ "license": "MIT" }, "node_modules/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", - "license": "MIT", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "engines": { "node": ">= 0.6" } }, "node_modules/cookie-parser": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", - "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", - "license": "MIT", + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", "dependencies": { - "cookie": "0.4.1", + "cookie": "0.7.2", "cookie-signature": "1.0.6" }, "engines": { @@ -5154,23 +5037,21 @@ } }, "node_modules/css-functions-list": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.2.tgz", - "integrity": "sha512-c+N0v6wbKVxTu5gOBBFkr9BEdBWaqqjQeiJ8QvSRIJOf+UxlJh930m8e6/WNeODIK0mYLFkoONrnj16i2EcvfQ==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.3.tgz", + "integrity": "sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==", "dev": true, - "license": "MIT", "engines": { "node": ">=12 || >=16" } }, "node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.0.0.tgz", + "integrity": "sha512-o88DVQ6GzsABn1+6+zo2ct801dBO5OASVyxbbvA2W20ue2puSh/VOuqUj90eUeMSX/xqGqBmOKiRQN7tJOuBXw==", "dev": true, - "license": "MIT", "dependencies": { - "mdn-data": "2.0.30", + "mdn-data": "2.10.0", "source-map-js": "^1.0.1" }, "engines": { @@ -5251,12 +5132,11 @@ } }, "node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", - "license": "MIT", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -5521,10 +5401,9 @@ } }, "node_modules/dompurify": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.6.tgz", - "integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==", - "license": "(MPL-2.0 OR Apache-2.0)" + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.7.tgz", + "integrity": "sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ==" }, "node_modules/duplexer2": { "version": "0.1.4", @@ -5551,10 +5430,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.4.tgz", - "integrity": "sha512-orzA81VqLyIGUEA77YkVA1D+N+nNfl2isJVjjmOyrlxuooZ19ynb+dOlaDTqd/idKRS9lDCSBmtzM+kyCsMnkA==", - "license": "ISC" + "version": "1.5.32", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.32.tgz", + "integrity": "sha512-M+7ph0VGBQqqpTT2YrabjNKSQ2fEl9PVx6AK3N558gDH9NO8O6XN9SXXFWRo9u9PbEg/bWq+tjXQr+eXmxubCw==" }, "node_modules/elliptic": { "version": "6.5.7", @@ -5596,10 +5474,9 @@ "license": "MIT" }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "engines": { "node": ">= 0.8" } @@ -5820,34 +5697,36 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "license": "MIT", "engines": { "node": ">=0.8.0" } }, "node_modules/eslint": { - "version": "9.9.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.9.0.tgz", - "integrity": "sha512-JfiKJrbx0506OEerjK2Y1QlldtBxkAlLxT5OEcRF8uaQ86noDe2k31Vw9rnSWv+MXZHj7OOUV/dA0AhdLFcyvA==", + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.12.0.tgz", + "integrity": "sha512-UVIOlTEWxwIopRL1wgSQYdnVDcEvs2wyaO6DGo5mXqe3r16IoCNWkR29iHhyaP4cICWjbgbmFUGAhh0GJRuGZw==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.11.0", - "@eslint/config-array": "^0.17.1", + "@eslint/config-array": "^0.18.0", + "@eslint/core": "^0.6.0", "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.9.0", + "@eslint/js": "9.12.0", + "@eslint/plugin-kit": "^0.2.0", + "@humanfs/node": "^0.16.5", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.3.0", - "@nodelib/fs.walk": "^1.2.8", + "@humanwhocodes/retry": "^0.3.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.0.2", - "eslint-visitor-keys": "^4.0.0", - "espree": "^10.1.0", + "eslint-scope": "^8.1.0", + "eslint-visitor-keys": "^4.1.0", + "espree": "^10.2.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -5857,14 +5736,11 @@ "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { @@ -5886,9 +5762,9 @@ } }, "node_modules/eslint-plugin-jest": { - "version": "28.8.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.8.0.tgz", - "integrity": "sha512-Tubj1hooFxCl52G4qQu0edzV/+EZzPUeN8p2NnW5uu4fbDs+Yo7+qDVDc4/oG3FbCqEBmu/OC3LSsyiU22oghw==", + "version": "28.8.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.8.3.tgz", + "integrity": "sha512-HIQ3t9hASLKm2IhIOqnu+ifw7uLZkIlR7RYNv7fMcEi/p0CIiJmfriStQS2LDkgtY4nyLbIZAD+JL347Yc2ETQ==", "dev": true, "dependencies": { "@typescript-eslint/utils": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -5911,11 +5787,10 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.35.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.35.0.tgz", - "integrity": "sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA==", + "version": "7.37.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.1.tgz", + "integrity": "sha512-xwTnwDqzbDRA8uJ7BMxPs/EXRB3i8ZfnOIp8BsxEQkT0nHPp+WWceqGgo6rKb9ctNi8GJLDT4Go5HAWELa/WMg==", "dev": true, - "license": "MIT", "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", @@ -5975,11 +5850,10 @@ } }, "node_modules/eslint-scope": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz", - "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.1.0.tgz", + "integrity": "sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -6071,11 +5945,10 @@ } }, "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", - "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", + "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -6107,15 +5980,14 @@ } }, "node_modules/espree": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", - "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.2.0.tgz", + "integrity": "sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.12.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.0.0" + "eslint-visitor-keys": "^4.1.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6125,11 +5997,10 @@ } }, "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", - "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", + "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -6169,7 +6040,6 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -6314,37 +6184,36 @@ "license": "MIT" }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", - "license": "MIT", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -6362,19 +6231,17 @@ "license": "MIT" }, "node_modules/express-static-gzip": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/express-static-gzip/-/express-static-gzip-2.1.7.tgz", - "integrity": "sha512-QOCZUC+lhPPCjIJKpQGu1Oa61Axg9Mq09Qvit8Of7kzpMuwDeMSqjjQteQS3OVw/GkENBoSBheuQDWPlngImvw==", - "license": "MIT", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/express-static-gzip/-/express-static-gzip-2.1.8.tgz", + "integrity": "sha512-g8tiJuI9Y9Ffy59ehVXvqb0hhP83JwZiLxzanobPaMbkB5qBWA8nuVgd+rcd5qzH3GkgogTALlc0BaADYwnMbQ==", "dependencies": { - "serve-static": "^1.14.1" + "serve-static": "^1.16.2" } }, "node_modules/express/node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "license": "MIT", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "engines": { "node": ">= 0.6" } @@ -6583,13 +6450,12 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "license": "MIT", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -6604,7 +6470,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -6612,8 +6477,7 @@ "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/find-up": { "version": "5.0.0", @@ -7026,11 +6890,10 @@ } }, "node_modules/globals": { - "version": "15.9.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.9.0.tgz", - "integrity": "sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA==", + "version": "15.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.11.0.tgz", + "integrity": "sha512-yeyNSjdbyVaWurlwCpcA6XNBrHTMIeDdj0/hnvX/OLJ9ekOXYbLsLinH/MucQyGvNnXhidTdNhTtJaffL2sMfw==", "dev": true, - "license": "MIT", "engines": { "node": ">=18" }, @@ -7432,6 +7295,11 @@ "node": ">=0.10.0" } }, + "node_modules/idb-keyval": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz", + "integrity": "sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==" + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -7940,16 +7808,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-plain-object": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", @@ -9964,15 +9822,14 @@ } }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "license": "MIT", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/json-bigint": { @@ -10434,12 +10291,11 @@ } }, "node_modules/marked-extended-tables": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/marked-extended-tables/-/marked-extended-tables-1.0.8.tgz", - "integrity": "sha512-GcVQP7EnfQ98o09ooqM4t4M0qfpKdKuk7/z4qZfgkLyXTXsIyFS1eeBmfC36o1NbR6aSq8ynL/LeTz3w4RS27Q==", - "license": "MIT", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/marked-extended-tables/-/marked-extended-tables-1.0.10.tgz", + "integrity": "sha512-zvRS0GPTkxq8UWawSDecd1Rxd2KD8crrmq2QALGDdrgkcgRNQzHlbnlujBGuXxdgDJg7f6UTv+JpcfejBpKdSg==", "peerDependencies": { - "marked": ">=3 <12" + "marked": ">=3 <15" } }, "node_modules/marked-gfm-heading-id": { @@ -10499,11 +10355,10 @@ } }, "node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", - "dev": true, - "license": "CC0-1.0" + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.10.0.tgz", + "integrity": "sha512-qq7C3EtK3yJXMwz1zAab65pjl+UhohqMOctTgcqjLOWABqmwj+me02LSsCuEUxnst9X1lCBpoE0WArGKgdGDzw==", + "dev": true }, "node_modules/media-typer": { "version": "0.3.0", @@ -10517,8 +10372,7 @@ "node_modules/memory-pager": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "license": "MIT" + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" }, "node_modules/meow": { "version": "13.2.0", @@ -10534,10 +10388,12 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "license": "MIT" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -10566,11 +10422,10 @@ } }, "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, - "license": "MIT", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -10769,7 +10624,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz", "integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==", - "license": "Apache-2.0", "dependencies": { "@types/whatwg-url": "^11.0.2", "whatwg-url": "^13.0.0" @@ -10779,7 +10633,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", - "license": "MIT", "dependencies": { "punycode": "^2.3.0" }, @@ -10791,7 +10644,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "license": "BSD-2-Clause", "engines": { "node": ">=12" } @@ -10800,7 +10652,6 @@ "version": "13.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", - "license": "MIT", "dependencies": { "tr46": "^4.1.1", "webidl-conversions": "^7.0.0" @@ -10810,13 +10661,13 @@ } }, "node_modules/mongoose": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.5.3.tgz", - "integrity": "sha512-OubSDbsAclDFGHjV82MsKyIGQWFc42Ot1l+0dhRS6U9xODM7rm/ES/WpOQd8Ds9j0Mx8QzxZtrSCnBh6o9wUqw==", + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.7.1.tgz", + "integrity": "sha512-RpNMyhyzLVCVbf8xTVbrf/18G3MqQzNw5pJdvOJ60fzbCa3cOZzz9L+8XpqzBXtRlgZGWv0T7MmOtvrT8ocp1Q==", "dependencies": { "bson": "^6.7.0", "kareem": "2.6.3", - "mongodb": "6.7.0", + "mongodb": "6.9.0", "mpath": "0.9.0", "mquery": "5.0.0", "ms": "2.1.3", @@ -10834,7 +10685,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "license": "MIT", "optional": true, "peer": true, "dependencies": { @@ -10848,7 +10698,6 @@ "version": "5.1.3", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", - "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { @@ -10865,7 +10714,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", - "license": "Apache-2.0", "optional": true, "peer": true, "dependencies": { @@ -10880,7 +10728,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "license": "MIT", "optional": true, "peer": true, "dependencies": { @@ -10892,10 +10739,9 @@ } }, "node_modules/mongoose/node_modules/mongodb": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.7.0.tgz", - "integrity": "sha512-TMKyHdtMcO0fYBNORiYdmM25ijsHs+Njs963r4Tro4OQZzqYigAzYQouwWRg4OIaiLRUEGUh/1UAcH5lxdSLIA==", - "license": "Apache-2.0", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.9.0.tgz", + "integrity": "sha512-UMopBVx1LmEUbW/QE0Hw18u583PEDVQmUmVzzBRH0o/xtE9DBRA5ZYLOjpLIa03i8FXjzvQECJcqoMvCXftTUA==", "dependencies": { "@mongodb-js/saslprep": "^1.1.5", "bson": "^6.7.0", @@ -10937,12 +10783,6 @@ } } }, - "node_modules/mongoose/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/mpath": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", @@ -10965,10 +10805,9 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "license": "MIT" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/nan": { "version": "2.20.0", @@ -11658,10 +11497,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", - "license": "MIT" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "node_modules/path-type": { "version": "4.0.0", @@ -11690,10 +11528,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", - "license": "ISC" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -11816,9 +11653,9 @@ } }, "node_modules/postcss": { - "version": "8.4.41", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", - "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "dev": true, "funding": [ { @@ -11836,8 +11673,8 @@ ], "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -11863,9 +11700,9 @@ "dev": true }, "node_modules/postcss-safe-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.0.tgz", - "integrity": "sha512-ovehqRNVCpuFzbXoTb4qLtyzK3xn3t/CUBxOs8LsnQjQrShaB4lKiHoVqY8ANaC0hBMHq5QVWk77rwGklFUDrg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", + "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", "dev": true, "funding": [ { @@ -11881,7 +11718,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "engines": { "node": ">=18.0" }, @@ -12095,12 +11931,11 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "license": "BSD-3-Clause", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -12225,11 +12060,11 @@ "license": "MIT" }, "node_modules/react-router": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.1.tgz", - "integrity": "sha512-kIwJveZNwp7teQRI5QmwWo39A5bXRyqpH0COKKmPnyD2vBvDwgFXSqDUYtt1h+FEyfnE8eXr7oe0MxRzVwCcvQ==", + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.27.0.tgz", + "integrity": "sha512-YA+HGZXz4jaAkVoYBE98VQl+nVzI+cVI2Oj/06F5ZM+0u3TgedN9Y9kmMRo2mnkSK2nCpNQn0DVob4HCsY/WLw==", "dependencies": { - "@remix-run/router": "1.19.1" + "@remix-run/router": "1.20.0" }, "engines": { "node": ">=14.0.0" @@ -12239,12 +12074,12 @@ } }, "node_modules/react-router-dom": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.1.tgz", - "integrity": "sha512-veut7m41S1fLql4pLhxeSW3jlqs+4MtjRLj0xvuCEXsxusJCbs6I8yn9BxzzDX2XDgafrccY6hwjmd/bL54tFw==", + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.27.0.tgz", + "integrity": "sha512-+bvtFWMC0DgAFrfKXKG9Fc+BcXWRUO1aJIihbB79xaeq0v5UzfvnM5houGUm1Y461WVRcgAQ+Clh5rdb1eCx4g==", "dependencies": { - "@remix-run/router": "1.19.1", - "react-router": "6.26.1" + "@remix-run/router": "1.20.0", + "react-router": "6.27.0" }, "engines": { "node": ">=14.0.0" @@ -12336,14 +12171,12 @@ "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "license": "MIT" + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" }, "node_modules/regenerate-unicode-properties": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", - "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", - "license": "MIT", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", "dependencies": { "regenerate": "^1.4.2" }, @@ -12354,14 +12187,12 @@ "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "license": "MIT" + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/regenerator-transform": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", - "license": "MIT", "dependencies": { "@babel/runtime": "^7.8.4" } @@ -12436,15 +12267,14 @@ } }, "node_modules/regexpu-core": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", - "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", - "license": "MIT", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.1.1.tgz", + "integrity": "sha512-k67Nb9jvwJcJmVpw0jPttR1/zVfnKf8Km0IPatrU/zJ5XeG3+Slx0xLXs9HByJSzXzrlz5EDvN6yLNMDc2qdnw==", "dependencies": { - "@babel/regjsgen": "^0.8.0", "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsparser": "^0.9.1", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.11.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.1.0" }, @@ -12452,26 +12282,22 @@ "node": ">=4" } }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==" + }, "node_modules/regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "license": "BSD-2-Clause", + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.11.0.tgz", + "integrity": "sha512-vTbzVAjQDzwQdKuvj7qEq6OlAprCjE656khuGQ4QaBLg7abQ9I9ISpmLuc6inWe7zP75AECjqUa4g4sdQvOXhg==", "dependencies": { - "jsesc": "~0.5.0" + "jsesc": "~3.0.2" }, "bin": { "regjsparser": "bin/parser" } }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "bin": { - "jsesc": "bin/jsesc" - } - }, "node_modules/remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -12742,10 +12568,9 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "license": "MIT", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -12769,7 +12594,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -12777,25 +12601,25 @@ "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "license": "MIT", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" @@ -13190,11 +13014,10 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, - "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -13235,7 +13058,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", - "license": "MIT", "dependencies": { "memory-pager": "^1.0.2" } @@ -13577,9 +13399,9 @@ "license": "ISC" }, "node_modules/stylelint": { - "version": "16.8.2", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.8.2.tgz", - "integrity": "sha512-fInKATippQhcSm7AB+T32GpI+626yohrg33GkFT/5jzliUw5qhlwZq2UQQwgl3HsHrf09oeARi0ZwgY/UWEv9A==", + "version": "16.10.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.10.0.tgz", + "integrity": "sha512-z/8X2rZ52dt2c0stVwI9QL2AFJhLhbPkyfpDFcizs200V/g7v+UYY6SNcB9hKOLcDDX/yGLDsY/pX08sLkz9xQ==", "dev": true, "funding": [ { @@ -13592,42 +13414,41 @@ } ], "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.0", - "@csstools/css-tokenizer": "^3.0.0", - "@csstools/media-query-list-parser": "^3.0.0", + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1", + "@csstools/media-query-list-parser": "^3.0.1", "@csstools/selector-specificity": "^4.0.0", "@dual-bundle/import-meta-resolve": "^4.1.0", "balanced-match": "^2.0.0", "colord": "^2.9.3", "cosmiconfig": "^9.0.0", - "css-functions-list": "^3.2.2", - "css-tree": "^2.3.1", - "debug": "^4.3.6", + "css-functions-list": "^3.2.3", + "css-tree": "^3.0.0", + "debug": "^4.3.7", "fast-glob": "^3.3.2", "fastest-levenshtein": "^1.0.16", - "file-entry-cache": "^9.0.0", + "file-entry-cache": "^9.1.0", "global-modules": "^2.0.0", "globby": "^11.1.0", "globjoin": "^0.1.4", "html-tags": "^3.3.1", - "ignore": "^5.3.2", + "ignore": "^6.0.2", "imurmurhash": "^0.1.4", "is-plain-object": "^5.0.0", "known-css-properties": "^0.34.0", "mathml-tag-names": "^2.1.3", "meow": "^13.2.0", - "micromatch": "^4.0.7", + "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "picocolors": "^1.0.1", - "postcss": "^8.4.41", + "postcss": "^8.4.47", "postcss-resolve-nested-selector": "^0.1.6", - "postcss-safe-parser": "^7.0.0", + "postcss-safe-parser": "^7.0.1", "postcss-selector-parser": "^6.1.2", "postcss-value-parser": "^4.2.0", "resolve-from": "^5.0.0", "string-width": "^4.2.3", - "strip-ansi": "^7.1.0", - "supports-hyperlinks": "^3.0.0", + "supports-hyperlinks": "^3.1.0", "svg-tags": "^1.0.0", "table": "^6.8.2", "write-file-atomic": "^5.0.1" @@ -13640,9 +13461,9 @@ } }, "node_modules/stylelint-config-recess-order": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recess-order/-/stylelint-config-recess-order-5.1.0.tgz", - "integrity": "sha512-ddapCF6B/kEtQYIFhQFReQ0dvK1ZdgJDM/SGFtIyeooYDbqaJqcOlGkRRGaVErCQYJY/bPSPsLRS2LdQtLJUVQ==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/stylelint-config-recess-order/-/stylelint-config-recess-order-5.1.1.tgz", + "integrity": "sha512-eDAHWVBelzDbMbdMj15pSw0Ycykv5eLeriJdbGCp0zd44yvhgZLI+wyVHegzXp5NrstxTPSxl0fuOVKdMm0XLA==", "dev": true, "dependencies": { "stylelint-order": "^6.0.4" @@ -13688,83 +13509,6 @@ "stylelint": "^14.0.0 || ^15.0.0 || ^16.0.1" } }, - "node_modules/stylelint/node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.1.tgz", - "integrity": "sha512-lSquqZCHxDfuTg/Sk2hiS0mcSFCEBuj49JfzPHJogDBT0mGCyY5A1AQzBWngitrp7i1/HAZpIgzF/VjhOEIJIg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.1" - } - }, - "node_modules/stylelint/node_modules/@csstools/css-tokenizer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.1.tgz", - "integrity": "sha512-UBqaiu7kU0lfvaP982/o3khfXccVlHPWp0/vwwiIgDF0GmqqqxoiXC/6FCjlS9u92f7CoEz6nXKQnrn1kIAkOw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/stylelint/node_modules/@csstools/media-query-list-parser": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-3.0.1.tgz", - "integrity": "sha512-HNo8gGD02kHmcbX6PvCoUuOQvn4szyB9ca63vZHKX5A81QytgDG4oxG4IaEfHTlEZSZ6MjPEMWIVU+zF2PZcgw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.1", - "@csstools/css-tokenizer": "^3.0.1" - } - }, - "node_modules/stylelint/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, "node_modules/stylelint/node_modules/balanced-match": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", @@ -13773,11 +13517,10 @@ "license": "MIT" }, "node_modules/stylelint/node_modules/file-entry-cache": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-9.0.0.tgz", - "integrity": "sha512-6MgEugi8p2tiUhqO7GnPsmbCCzj0YRCwwaTbpGRyKZesjRSzkqkAE9fPp7V2yMs5hwfgbQLgdvSSkGNg1s5Uvw==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-9.1.0.tgz", + "integrity": "sha512-/pqPFG+FdxWQj+/WSuzXSDaNzxgTLr/OrR1QuqfEZzDakpdYE70PwUxL7BPUa8hpjbvY1+qvCl8k+8Tq34xJgg==", "dev": true, - "license": "MIT", "dependencies": { "flat-cache": "^5.0.0" }, @@ -13790,7 +13533,6 @@ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-5.0.0.tgz", "integrity": "sha512-JrqFmyUl2PnPi1OvLyTVHnQvwQ0S+e6lGSwu8OkAZlSaNIZciTY2H/cOOROxsBA1m/LZNHDsqAgDZt6akWcjsQ==", "dev": true, - "license": "MIT", "dependencies": { "flatted": "^3.3.1", "keyv": "^4.5.4" @@ -13799,6 +13541,15 @@ "node": ">=18" } }, + "node_modules/stylelint/node_modules/ignore": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-6.0.2.tgz", + "integrity": "sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/stylelint/node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -13822,22 +13573,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/stylelint/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/stylelint/node_modules/write-file-atomic": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", @@ -13862,10 +13597,9 @@ } }, "node_modules/superagent": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-9.0.2.tgz", - "integrity": "sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w==", - "license": "MIT", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.1.0.tgz", + "integrity": "sha512-JMmik7PbnXGlq7g528Gi6apHbVbTz2vrE3du6fuG4kIPSb2PnLoSOPvfjKn8aQYuJcBWAKW6ZG90qPPsE5jZxQ==", "dependencies": { "component-emitter": "^1.3.0", "cookiejar": "^2.1.4", @@ -13907,6 +13641,38 @@ "node": ">=14.18.0" } }, + "node_modules/supertest/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/supertest/node_modules/superagent": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-9.0.2.tgz", + "integrity": "sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w==", + "dev": true, + "dependencies": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.4", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^3.5.1", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.0" + }, + "engines": { + "node": ">=14.18.0" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -13920,17 +13686,19 @@ } }, "node_modules/supports-hyperlinks": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.0.0.tgz", - "integrity": "sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.1.0.tgz", + "integrity": "sha512-2rn0BZ+/f7puLOHZm1HOJfwBggfaHXUpPUSSG/SWM4TWp5KCfmNYwnC3hruy2rZlMnmWZ+QAGpZfchu3f3695A==", "dev": true, - "license": "MIT", "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" }, "engines": { "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/supports-hyperlinks/node_modules/has-flag": { @@ -13938,7 +13706,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -13948,7 +13715,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -14463,10 +14229,9 @@ "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "license": "MIT", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", "engines": { "node": ">=4" } @@ -14475,7 +14240,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "license": "MIT", "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" @@ -14485,10 +14249,9 @@ } }, "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", - "license": "MIT", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", "engines": { "node": ">=4" } @@ -14497,7 +14260,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "license": "MIT", "engines": { "node": ">=4" } @@ -14671,21 +14433,6 @@ "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", "license": "MIT" }, - "node_modules/url/node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", diff --git a/package.json b/package.json index 8226325cd..a4c6ee38a 100644 --- a/package.json +++ b/package.json @@ -1,20 +1,20 @@ { "name": "homebrewery", "description": "Create authentic looking D&D homebrews using only markdown", - "version": "3.14.1", + "version": "3.16.0", "engines": { "npm": "^10.2.x", - "node": "^20.8.x" + "node": "^20.17.x" }, "repository": { "type": "git", "url": "git://github.com/naturalcrit/homebrewery.git" }, "scripts": { - "dev": "node scripts/dev.js", - "quick": "node scripts/quick.js", - "build": "node scripts/buildHomebrew.js && node scripts/buildAdmin.js", - "builddev": "node scripts/buildHomebrew.js --dev", + "dev": "node --experimental-require-module scripts/dev.js", + "quick": "node --experimental-require-module scripts/quick.js", + "build": "node --experimental-require-module scripts/buildHomebrew.js && node --experimental-require-module scripts/buildAdmin.js", + "builddev": "node --experimental-require-module scripts/buildHomebrew.js --dev", "lint": "eslint --fix", "lint:dry": "eslint", "stylelint": "stylelint --fix **/*.{less}", @@ -24,6 +24,8 @@ "test": "jest --runInBand", "test:api-unit": "jest \"server/.*.spec.js\" --verbose", "test:api-unit:themes": "jest \"server/.*.spec.js\" -t \"theme bundle\" --verbose", + "test:api-unit:css": "jest \"server/.*.spec.js\" -t \"Get CSS\" --verbose", + "test:api-unit:notifications": "jest \"server/.*.spec.js\" -t \"Notifications\" --verbose", "test:coverage": "jest --coverage --silent --runInBand", "test:dev": "jest --verbose --watch", "test:basic": "jest tests/markdown/basic.test.js --verbose", @@ -36,10 +38,10 @@ "test:hard-breaks": "jest tests/markdown/hard-breaks.test.js --verbose --noStackTrace", "test:emojis": "jest tests/markdown/emojis.test.js --verbose --noStackTrace", "test:route": "jest tests/routes/static-pages.test.js --verbose", - "phb": "node scripts/phb.js", + "phb": "node --experimental-require-module scripts/phb.js", "prod": "set NODE_ENV=production && npm run build", "postinstall": "npm run build", - "start": "node server.js" + "start": "node --experimental-require-module server.js" }, "author": "stolksdorf", "license": "MIT", @@ -84,56 +86,57 @@ ] }, "dependencies": { - "@babel/core": "^7.25.2", - "@babel/plugin-transform-runtime": "^7.25.4", - "@babel/preset-env": "^7.25.4", - "@babel/preset-react": "^7.24.7", - "@googleapis/drive": "^8.13.0", + "@babel/core": "^7.25.8", + "@babel/plugin-transform-runtime": "^7.25.7", + "@babel/preset-env": "^7.25.8", + "@babel/preset-react": "^7.25.7", + "@googleapis/drive": "^8.14.0", "body-parser": "^1.20.2", "classnames": "^2.5.1", "codemirror": "^5.65.6", - "cookie-parser": "^1.4.6", + "cookie-parser": "^1.4.7", "create-react-class": "^15.7.0", "dedent-tabs": "^0.10.3", - "dompurify": "^3.1.6", + "dompurify": "^3.1.7", "expr-eval": "^2.0.2", - "express": "^4.19.2", + "express": "^4.21.1", "express-async-handler": "^1.2.0", - "express-static-gzip": "2.1.7", + "express-static-gzip": "2.1.8", "fs-extra": "11.2.0", + "idb-keyval": "^6.2.1", "js-yaml": "^4.1.0", "jwt-simple": "^0.5.6", "less": "^3.13.1", "lodash": "^4.17.21", "marked": "11.2.0", "marked-emoji": "^1.4.2", - "marked-extended-tables": "^1.0.8", + "marked-extended-tables": "^1.0.10", "marked-gfm-heading-id": "^3.2.0", "marked-smartypants-lite": "^1.0.2", "markedLegacy": "npm:marked@^0.3.19", "moment": "^2.30.1", - "mongoose": "^8.5.3", + "mongoose": "^8.7.1", "nanoid": "3.3.4", "nconf": "^0.12.1", "react": "^18.3.1", "react-dom": "^18.3.1", "react-frame-component": "^4.1.3", - "react-router-dom": "6.26.1", + "react-router-dom": "6.27.0", "sanitize-filename": "1.6.3", - "superagent": "^9.0.2", + "superagent": "^10.1.0", "vitreum": "git+https://git@github.com/calculuschild/vitreum.git" }, "devDependencies": { - "@stylistic/stylelint-plugin": "^3.0.1", - "eslint": "^9.9.0", - "eslint-plugin-jest": "^28.8.0", - "eslint-plugin-react": "^7.35.0", - "globals": "^15.9.0", + "@stylistic/stylelint-plugin": "^3.1.1", + "eslint": "^9.12.0", + "eslint-plugin-jest": "^28.8.3", + "eslint-plugin-react": "^7.37.1", + "globals": "^15.11.0", "jest": "^29.7.0", "jest-expect-message": "^1.1.3", "postcss-less": "^6.0.0", - "stylelint": "^16.8.2", - "stylelint-config-recess-order": "^5.1.0", + "stylelint": "^16.10.0", + "stylelint-config-recess-order": "^5.1.1", "stylelint-config-recommended": "^14.0.1", "supertest": "^7.0.0" } diff --git a/server/admin.api.js b/server/admin.api.js index fe2def3ce..0ec6a9c88 100644 --- a/server/admin.api.js +++ b/server/admin.api.js @@ -1,7 +1,7 @@ const HomebrewModel = require('./homebrew.model.js').model; +const NotificationModel = require('./notifications.model.js').model; const router = require('express').Router(); const Moment = require('moment'); -//const render = require('vitreum/steps/render'); const templateFn = require('../client/template.js'); const zlib = require('zlib'); @@ -22,7 +22,7 @@ const mw = { if(process.env.ADMIN_USER === username && process.env.ADMIN_PASS === password){ return next(); } - return res.status(401).send('Access denied'); + throw { HBErrorCode: '52', code: 401, message: 'Access denied' }; } }; @@ -138,12 +138,48 @@ router.get('/admin/stats', mw.adminOnly, async (req, res)=>{ } }); +// ####################### NOTIFICATIONS + +router.get('/admin/notification/all', async (req, res, next)=>{ + try { + const notifications = await NotificationModel.getAll(); + return res.json(notifications); + } catch (error) { + console.log('Error getting all notifications: ', error.message); + return res.status(500).json({ message: error.message }); + } +}); + +router.post('/admin/notification/add', mw.adminOnly, async (req, res, next)=>{ + console.table(req.body); + try { + const notification = await NotificationModel.addNotification(req.body); + return res.status(201).json(notification); + } catch (error) { + console.log('Error adding notification: ', error.message); + return res.status(500).json({ message: error.message }); + } +}); + +router.delete('/admin/notification/delete/:id', mw.adminOnly, async (req, res, next)=>{ + try { + const notification = await NotificationModel.deleteNotification(req.params.id); + return res.json(notification); + } catch (error) { + console.error('Error deleting notification: { key: ', req.params.id, ' error: ', error.message, ' }'); + return res.status(500).json({ message: error.message }); + } +}); + router.get('/admin', mw.adminOnly, (req, res)=>{ templateFn('admin', { url : req.originalUrl }) .then((page)=>res.send(page)) - .catch((err)=>res.sendStatus(500)); + .catch((err)=>{ + console.log(err); + res.sendStatus(500); + }); }); module.exports = router; diff --git a/server/admin.api.spec.js b/server/admin.api.spec.js new file mode 100644 index 000000000..b0dbd5d84 --- /dev/null +++ b/server/admin.api.spec.js @@ -0,0 +1,116 @@ +const supertest = require('supertest'); + +const app = supertest.agent(require('app.js').app) + .set('X-Forwarded-Proto', 'https'); + +const NotificationModel = require('./notifications.model.js').model; + +describe('Tests for admin api', ()=>{ + afterEach(()=>{ + jest.resetAllMocks(); + }); + + describe('Notifications', ()=>{ + it('should return list of all notifications', async ()=>{ + const testNotifications = ['a', 'b']; + + jest.spyOn(NotificationModel, 'find') + .mockImplementationOnce(() => { + return { exec: jest.fn().mockResolvedValue(testNotifications) }; + }); + + const response = await app + .get('/admin/notification/all') + .set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`); + + expect(response.status).toBe(200); + expect(response.body).toEqual(testNotifications); + }); + + it('should add a new notification', async ()=>{ + const inputNotification = { + title : 'Test Notification', + text : 'This is a test notification', + startAt : new Date().toISOString(), + stopAt : new Date().toISOString(), + dismissKey : 'testKey' + }; + + const savedNotification = { + ...inputNotification, + _id : expect.any(String), + createdAt : expect.any(String), + startAt : inputNotification.startAt, + stopAt : inputNotification.stopAt, + }; + + jest.spyOn(NotificationModel.prototype, 'save') + .mockImplementationOnce(function() { + return Promise.resolve(this); + }); + + const response = await app + .post('/admin/notification/add') + .set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`) + .send(inputNotification); + + expect(response.status).toBe(201); + expect(response.body).toEqual(savedNotification); + }); + + it('should handle error adding a notification without dismissKey', async () => { + const inputNotification = { + title : 'Test Notification', + text : 'This is a test notification', + startAt : new Date().toISOString(), + stopAt : new Date().toISOString() + }; + + //Change 'save' function to just return itself instead of actually interacting with the database + jest.spyOn(NotificationModel.prototype, 'save') + .mockImplementationOnce(function() { + return Promise.resolve(this); + }); + + const response = await app + .post('/admin/notification/add') + .set('Authorization', 'Basic ' + Buffer.from('admin:password3').toString('base64')) + .send(inputNotification); + + expect(response.status).toBe(500); + expect(response.body).toEqual({ message: 'Dismiss key is required!' }); + }); + + it('should delete a notification based on its dismiss key', async ()=>{ + const dismissKey = 'testKey'; + + jest.spyOn(NotificationModel, 'findOneAndDelete') + .mockImplementationOnce((key) => { + return { exec: jest.fn().mockResolvedValue(key) }; + }); + const response = await app + .delete(`/admin/notification/delete/${dismissKey}`) + .set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`); + + expect(NotificationModel.findOneAndDelete).toHaveBeenCalledWith({'dismissKey': 'testKey'}); + expect(response.status).toBe(200); + expect(response.body).toEqual({ dismissKey: 'testKey' }); + }); + + it('should handle error deleting a notification that doesnt exist', async ()=>{ + const dismissKey = 'testKey'; + + jest.spyOn(NotificationModel, 'findOneAndDelete') + .mockImplementationOnce(() => { + return { exec: jest.fn().mockResolvedValue() }; + }); + const response = await app + .delete(`/admin/notification/delete/${dismissKey}`) + .set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`); + + expect(NotificationModel.findOneAndDelete).toHaveBeenCalledWith({'dismissKey': 'testKey'}); + expect(response.status).toBe(500); + expect(response.body).toEqual({ message: 'Notification not found' }); + }); + }); +}); diff --git a/server/app.js b/server/app.js index 8b3e4652b..5f3a35150 100644 --- a/server/app.js +++ b/server/app.js @@ -1,521 +1,561 @@ -/*eslint max-lines: ["warn", {"max": 500, "skipBlankLines": true, "skipComments": true}]*/ -// Set working directory to project root -process.chdir(`${__dirname}/..`); - -const _ = require('lodash'); -const jwt = require('jwt-simple'); -const express = require('express'); -const yaml = require('js-yaml'); -const app = express(); -const config = require('./config.js'); - -const { homebrewApi, getBrew, getUsersBrewThemes } = require('./homebrew.api.js'); -const GoogleActions = require('./googleActions.js'); -const serveCompressedStaticAssets = require('./static-assets.mv.js'); -const sanitizeFilename = require('sanitize-filename'); -const asyncHandler = require('express-async-handler'); -const templateFn = require('./../client/template.js'); - -const { DEFAULT_BREW } = require('./brewDefaults.js'); - -const { splitTextStyleAndMetadata } = require('../shared/helpers.js'); - - -const sanitizeBrew = (brew, accessType)=>{ - brew._id = undefined; - brew.__v = undefined; - if(accessType !== 'edit' && accessType !== 'shareAuthor') { - brew.editId = undefined; - } - return brew; -}; - -app.use('/', serveCompressedStaticAssets(`build`)); -app.use(require('./middleware/content-negotiation.js')); -app.use(require('body-parser').json({ limit: '25mb' })); -app.use(require('cookie-parser')()); -app.use(require('./forcessl.mw.js')); - -//Account Middleware -app.use((req, res, next)=>{ - if(req.cookies && req.cookies.nc_session){ - try { - req.account = jwt.decode(req.cookies.nc_session, config.get('secret')); - //console.log("Just loaded up JWT from cookie:"); - //console.log(req.account); - } catch (e){} - } - - req.config = { - google_client_id : config.get('google_client_id'), - google_client_secret : config.get('google_client_secret') - }; - return next(); -}); - -app.use(homebrewApi); -app.use(require('./admin.api.js')); - -const HomebrewModel = require('./homebrew.model.js').model; -const welcomeText = require('fs').readFileSync('client/homebrew/pages/homePage/welcome_msg.md', 'utf8'); -const welcomeTextLegacy = require('fs').readFileSync('client/homebrew/pages/homePage/welcome_msg_legacy.md', 'utf8'); -const migrateText = require('fs').readFileSync('client/homebrew/pages/homePage/migrate.md', 'utf8'); -const changelogText = require('fs').readFileSync('changelog.md', 'utf8'); -const faqText = require('fs').readFileSync('faq.md', 'utf8'); - -String.prototype.replaceAll = function(s, r){return this.split(s).join(r);}; - -const defaultMetaTags = { - site_name : 'The Homebrewery - Make your Homebrew content look legit!', - title : 'The Homebrewery', - description : 'A NaturalCrit Tool for creating authentic Homebrews using Markdown.', - image : `${config.get('publicUrl')}/thumbnail.png`, - type : 'website' -}; - -//Robots.txt -app.get('/robots.txt', (req, res)=>{ - return res.sendFile(`robots.txt`, { root: process.cwd() }); -}); - -//Home page -app.get('/', (req, res, next)=>{ - req.brew = { - text : welcomeText, - renderer : 'V3', - theme : '5ePHB' - }, - - req.ogMeta = { ...defaultMetaTags, - title : 'Homepage', - description : 'Homepage' - }; - - splitTextStyleAndMetadata(req.brew); - return next(); -}); - -//Home page Legacy -app.get('/legacy', (req, res, next)=>{ - req.brew = { - text : welcomeTextLegacy, - renderer : 'legacy', - theme : '5ePHB' - }, - - req.ogMeta = { ...defaultMetaTags, - title : 'Homepage (Legacy)', - description : 'Homepage' - }; - - splitTextStyleAndMetadata(req.brew); - return next(); -}); - -//Legacy/Other Document -> v3 Migration Guide -app.get('/migrate', (req, res, next)=>{ - req.brew = { - text : migrateText, - renderer : 'V3', - theme : '5ePHB' - }, - - req.ogMeta = { ...defaultMetaTags, - title : 'v3 Migration Guide', - description : 'A brief guide to converting Legacy documents to the v3 renderer.' - }; - - splitTextStyleAndMetadata(req.brew); - return next(); -}); - -//Changelog page -app.get('/changelog', async (req, res, next)=>{ - req.brew = { - title : 'Changelog', - text : changelogText, - renderer : 'V3', - theme : '5ePHB' - }, - - req.ogMeta = { ...defaultMetaTags, - title : 'Changelog', - description : 'Development changelog.' - }; - - splitTextStyleAndMetadata(req.brew); - return next(); -}); - -//FAQ page -app.get('/faq', async (req, res, next)=>{ - req.brew = { - title : 'FAQ', - text : faqText, - renderer : 'V3', - theme : '5ePHB' - }, - - req.ogMeta = { ...defaultMetaTags, - title : 'FAQ', - description : 'Frequently Asked Questions' - }; - - splitTextStyleAndMetadata(req.brew); - return next(); -}); - -//Source page -app.get('/source/:id', asyncHandler(getBrew('share')), (req, res)=>{ - const { brew } = req; - - const replaceStrings = { '&': '&', '<': '<', '>': '>' }; - let text = brew.text; - for (const replaceStr in replaceStrings) { - text = text.replaceAll(replaceStr, replaceStrings[replaceStr]); - } - text = `
${text}
`; - res.status(200).send(text); -}); - -//Download brew source page -app.get('/download/:id', asyncHandler(getBrew('share')), (req, res)=>{ - const { brew } = req; - sanitizeBrew(brew, 'share'); - const prefix = 'HB - '; - - const encodeRFC3986ValueChars = (str)=>{ - return ( - encodeURIComponent(str) - .replace(/[!'()*]/g, (char)=>{`%${char.charCodeAt(0).toString(16).toUpperCase()}`;}) - ); - }; - - let fileName = sanitizeFilename(`${prefix}${brew.title}`).replaceAll(' ', ''); - if(!fileName || !fileName.length) { fileName = `${prefix}-Untitled-Brew`; }; - res.set({ - 'Cache-Control' : 'no-cache', - 'Content-Type' : 'text/plain', - 'Content-Disposition' : `attachment; filename*=UTF-8''${encodeRFC3986ValueChars(fileName)}.txt` - }); - res.status(200).send(brew.text); -}); - -//User Page -app.get('/user/:username', async (req, res, next)=>{ - const ownAccount = req.account && (req.account.username == req.params.username); - - req.ogMeta = { ...defaultMetaTags, - title : `${req.params.username}'s Collection`, - description : 'View my collection of homebrew on the Homebrewery.' - // type : could be 'profile'? - }; - - const fields = [ - 'googleId', - 'title', - 'pageCount', - 'description', - 'authors', - 'lang', - 'published', - 'views', - 'shareId', - 'editId', - 'createdAt', - 'updatedAt', - 'lastViewed', - 'thumbnail', - 'tags' - ]; - - let brews = await HomebrewModel.getByUser(req.params.username, ownAccount, fields) - .catch((err)=>{ - console.log(err); - }); - - if(ownAccount && req?.account?.googleId){ - const auth = await GoogleActions.authCheck(req.account, res); - let googleBrews = await GoogleActions.listGoogleBrews(auth) - .catch((err)=>{ - console.error(err); - }); - - if(googleBrews && googleBrews.length > 0) { - for (const brew of brews.filter((brew)=>brew.googleId)) { - const match = googleBrews.findIndex((b)=>b.editId === brew.editId); - if(match !== -1) { - brew.googleId = googleBrews[match].googleId; - brew.stubbed = true; - brew.pageCount = googleBrews[match].pageCount; - brew.renderer = googleBrews[match].renderer; - brew.version = googleBrews[match].version; - brew.webViewLink = googleBrews[match].webViewLink; - googleBrews.splice(match, 1); - } - } - - googleBrews = googleBrews.map((brew)=>({ ...brew, authors: [req.account.username] })); - brews = _.concat(brews, googleBrews); - } - } - - req.brews = _.map(brews, (brew)=>{ - // Clean up brew data - brew.title = brew.title?.trim(); - brew.description = brew.description?.trim(); - return sanitizeBrew(brew, ownAccount ? 'edit' : 'share'); - }); - - return next(); -}); - -//Edit Page -app.get('/edit/:id', asyncHandler(getBrew('edit')), asyncHandler(async(req, res, next)=>{ - req.brew = req.brew.toObject ? req.brew.toObject() : req.brew; - - req.userThemes = await(getUsersBrewThemes(req.account?.username)); - - req.ogMeta = { ...defaultMetaTags, - title : req.brew.title || 'Untitled Brew', - description : req.brew.description || 'No description.', - image : req.brew.thumbnail || defaultMetaTags.image, - type : 'article' - }; - - sanitizeBrew(req.brew, 'edit'); - splitTextStyleAndMetadata(req.brew); - res.header('Cache-Control', 'no-cache, no-store'); //reload the latest saved brew when pressing back button, not the cached version before save. - return next(); -})); - -//New Page from ID -app.get('/new/:id', asyncHandler(getBrew('share')), asyncHandler(async(req, res, next)=>{ - sanitizeBrew(req.brew, 'share'); - splitTextStyleAndMetadata(req.brew); - const brew = { - shareId : req.brew.shareId, - title : `CLONE - ${req.brew.title}`, - text : req.brew.text, - style : req.brew.style, - renderer : req.brew.renderer, - theme : req.brew.theme, - tags : req.brew.tags, - }; - req.brew = _.defaults(brew, DEFAULT_BREW); - - req.userThemes = await(getUsersBrewThemes(req.account?.username)); - - req.ogMeta = { ...defaultMetaTags, - title : 'New', - description : 'Start crafting your homebrew on the Homebrewery!' - }; - - return next(); -})); - -//New Page -app.get('/new', asyncHandler(async(req, res, next)=>{ - req.userThemes = await(getUsersBrewThemes(req.account?.username)); - - req.ogMeta = { ...defaultMetaTags, - title : 'New', - description : 'Start crafting your homebrew on the Homebrewery!' - }; - - return next(); -})); - -//Share Page -app.get('/share/:id', asyncHandler(getBrew('share')), asyncHandler(async (req, res, next)=>{ - const { brew } = req; - req.ogMeta = { ...defaultMetaTags, - title : req.brew.title || 'Untitled Brew', - description : req.brew.description || 'No description.', - image : req.brew.thumbnail || defaultMetaTags.image, - type : 'article' - }; - - // increase visitor view count, do not include visits by author(s) - if(!brew.authors.includes(req.account?.username)){ - if(req.params.id.length > 12 && !brew._id) { - const googleId = brew.googleId; - const shareId = brew.shareId; - await GoogleActions.increaseView(googleId, shareId, 'share', brew) - .catch((err)=>{next(err);}); - } else { - await HomebrewModel.increaseView({ shareId: brew.shareId }); - } - }; - - brew.authors.includes(req.account?.username) ? sanitizeBrew(req.brew, 'shareAuthor') : sanitizeBrew(req.brew, 'share'); - splitTextStyleAndMetadata(req.brew); - return next(); -})); - -//Account Page -app.get('/account', asyncHandler(async (req, res, next)=>{ - const data = {}; - data.title = 'Account Information Page'; - - let auth; - let googleCount = []; - if(req.account) { - if(req.account.googleId) { - try { - auth = await GoogleActions.authCheck(req.account, res, false); - } catch (e) { - auth = undefined; - console.log('Google auth check failed!'); - console.log(e); - } - if(auth.credentials.access_token) { - try { - googleCount = await GoogleActions.listGoogleBrews(auth); - } catch (e) { - googleCount = undefined; - console.log('List Google files failed!'); - console.log(e); - } - } - } - - const query = { authors: req.account.username, googleId: { $exists: false } }; - const mongoCount = await HomebrewModel.countDocuments(query) - .catch((err)=>{ - mongoCount = 0; - console.log(err); - }); - - data.accountDetails = { - username : req.account.username, - issued : req.account.issued, - googleId : Boolean(req.account.googleId), - authCheck : Boolean(req.account.googleId && auth.credentials.access_token), - mongoCount : mongoCount, - googleCount : googleCount?.length - }; - } - - req.brew = data; - - req.ogMeta = { ...defaultMetaTags, - title : `Account Page`, - description : null - }; - - return next(); -})); - -const nodeEnv = config.get('node_env'); -const isLocalEnvironment = config.get('local_environments').includes(nodeEnv); -// Local only -if(isLocalEnvironment){ - // Login - app.post('/local/login', (req, res)=>{ - const username = req.body.username; - if(!username) return; - - const payload = jwt.encode({ username: username, issued: new Date }, config.get('secret')); - return res.json(payload); - }); -} - -//Send rendered page -app.use(asyncHandler(async (req, res, next)=>{ - if (!req.route) return res.redirect('/'); // Catch-all for invalid routes - - const page = await renderPage(req, res); - if(!page) return; - res.send(page); -})); - -//Render the page -const renderPage = async (req, res)=>{ - // Create configuration object - const configuration = { - local : isLocalEnvironment, - publicUrl : config.get('publicUrl') ?? '', - environment : nodeEnv - }; - const props = { - version : require('./../package.json').version, - url : req.customUrl || req.originalUrl, - brew : req.brew, - brews : req.brews, - googleBrews : req.googleBrews, - account : req.account, - enable_v3 : config.get('enable_v3'), - enable_themes : config.get('enable_themes'), - config : configuration, - ogMeta : req.ogMeta, - userThemes : req.userThemes - }; - const title = req.brew ? req.brew.title : ''; - const page = await templateFn('homebrew', title, props) - .catch((err)=>{ - console.log(err); - }); - return page; -}; - -//v=====----- Error-Handling Middleware -----=====v// -//Format Errors as plain objects so all fields will appear in the string sent -const formatErrors = (key, value)=>{ - if(value instanceof Error) { - const error = {}; - Object.getOwnPropertyNames(value).forEach(function (key) { - error[key] = value[key]; - }); - return error; - } - return value; -}; - -const getPureError = (error)=>{ - return JSON.parse(JSON.stringify(error, formatErrors)); -}; - -app.use(async (err, req, res, next)=>{ - err.originalUrl = req.originalUrl; - console.error(err); - - if(err.originalUrl?.startsWith('/api/')) { - // console.log('API error'); - res.status(err.status || err.response?.status || 500).send(err); - return; - } - - // console.log('non-API error'); - const status = err.status || err.code || 500; - - req.ogMeta = { ...defaultMetaTags, - title : 'Error Page', - description : 'Something went wrong!' - }; - req.brew = { - ...err, - title : 'Error - Something went wrong!', - text : err.errors?.map((error)=>{return error.message;}).join('\n\n') || err.message || 'Unknown error!', - status : status, - HBErrorCode : err.HBErrorCode ?? '00', - pureError : getPureError(err) - }; - req.customUrl= '/error'; - - const page = await renderPage(req, res); - if(!page) return; - res.send(page); -}); - -app.use((req, res)=>{ - if(!res.headersSent) { - console.error('Headers have not been sent, responding with a server error.', req.url); - res.status(500).send('An error occurred and the server did not send a response. The error has been logged, please note the time this occurred and report this issue.'); - } -}); -//^=====--------------------------------------=====^// - -module.exports = { - app : app -}; +/*eslint max-lines: ["warn", {"max": 500, "skipBlankLines": true, "skipComments": true}]*/ +// Set working directory to project root +process.chdir(`${__dirname}/..`); + +const _ = require('lodash'); +const jwt = require('jwt-simple'); +const express = require('express'); +const yaml = require('js-yaml'); +const app = express(); +const config = require('./config.js'); +const fs = require('fs-extra'); + +const { homebrewApi, getBrew, getUsersBrewThemes, getCSS } = require('./homebrew.api.js'); +const GoogleActions = require('./googleActions.js'); +const serveCompressedStaticAssets = require('./static-assets.mv.js'); +const sanitizeFilename = require('sanitize-filename'); +const asyncHandler = require('express-async-handler'); +const templateFn = require('./../client/template.js'); + +const { DEFAULT_BREW } = require('./brewDefaults.js'); + +const { splitTextStyleAndMetadata } = require('../shared/helpers.js'); + + +const sanitizeBrew = (brew, accessType)=>{ + brew._id = undefined; + brew.__v = undefined; + if(accessType !== 'edit' && accessType !== 'shareAuthor') { + brew.editId = undefined; + } + return brew; +}; + +app.set('trust proxy', 1 /* number of proxies between user and server */) + +app.use('/', serveCompressedStaticAssets(`build`)); +app.use(require('./middleware/content-negotiation.js')); +app.use(require('body-parser').json({ limit: '25mb' })); +app.use(require('cookie-parser')()); +app.use(require('./forcessl.mw.js')); + +//Account Middleware +app.use((req, res, next)=>{ + if(req.cookies && req.cookies.nc_session){ + try { + req.account = jwt.decode(req.cookies.nc_session, config.get('secret')); + //console.log("Just loaded up JWT from cookie:"); + //console.log(req.account); + } catch (e){} + } + + req.config = { + google_client_id : config.get('google_client_id'), + google_client_secret : config.get('google_client_secret') + }; + return next(); +}); + +app.use(homebrewApi); +app.use(require('./admin.api.js')); +app.use(require('./vault.api.js')); + +const HomebrewModel = require('./homebrew.model.js').model; +const welcomeText = require('fs').readFileSync('client/homebrew/pages/homePage/welcome_msg.md', 'utf8'); +const welcomeTextLegacy = require('fs').readFileSync('client/homebrew/pages/homePage/welcome_msg_legacy.md', 'utf8'); +const migrateText = require('fs').readFileSync('client/homebrew/pages/homePage/migrate.md', 'utf8'); +const changelogText = require('fs').readFileSync('changelog.md', 'utf8'); +const faqText = require('fs').readFileSync('faq.md', 'utf8'); + +String.prototype.replaceAll = function(s, r){return this.split(s).join(r);}; + +const defaultMetaTags = { + site_name : 'The Homebrewery - Make your Homebrew content look legit!', + title : 'The Homebrewery', + description : 'A NaturalCrit Tool for creating authentic Homebrews using Markdown.', + image : `${config.get('publicUrl')}/thumbnail.png`, + type : 'website' +}; + +//Robots.txt +app.get('/robots.txt', (req, res)=>{ + return res.sendFile(`robots.txt`, { root: process.cwd() }); +}); + +//Home page +app.get('/', (req, res, next)=>{ + req.brew = { + text : welcomeText, + renderer : 'V3', + theme : '5ePHB' + }, + + req.ogMeta = { ...defaultMetaTags, + title : 'Homepage', + description : 'Homepage' + }; + + splitTextStyleAndMetadata(req.brew); + return next(); +}); + +//Home page Legacy +app.get('/legacy', (req, res, next)=>{ + req.brew = { + text : welcomeTextLegacy, + renderer : 'legacy', + theme : '5ePHB' + }, + + req.ogMeta = { ...defaultMetaTags, + title : 'Homepage (Legacy)', + description : 'Homepage' + }; + + splitTextStyleAndMetadata(req.brew); + return next(); +}); + +//Legacy/Other Document -> v3 Migration Guide +app.get('/migrate', (req, res, next)=>{ + req.brew = { + text : migrateText, + renderer : 'V3', + theme : '5ePHB' + }, + + req.ogMeta = { ...defaultMetaTags, + title : 'v3 Migration Guide', + description : 'A brief guide to converting Legacy documents to the v3 renderer.' + }; + + splitTextStyleAndMetadata(req.brew); + return next(); +}); + +//Changelog page +app.get('/changelog', async (req, res, next)=>{ + req.brew = { + title : 'Changelog', + text : changelogText, + renderer : 'V3', + theme : '5ePHB' + }, + + req.ogMeta = { ...defaultMetaTags, + title : 'Changelog', + description : 'Development changelog.' + }; + + splitTextStyleAndMetadata(req.brew); + return next(); +}); + +//FAQ page +app.get('/faq', async (req, res, next)=>{ + req.brew = { + title : 'FAQ', + text : faqText, + renderer : 'V3', + theme : '5ePHB' + }, + + req.ogMeta = { ...defaultMetaTags, + title : 'FAQ', + description : 'Frequently Asked Questions' + }; + + splitTextStyleAndMetadata(req.brew); + return next(); +}); + +//Source page +app.get('/source/:id', asyncHandler(getBrew('share')), (req, res)=>{ + const { brew } = req; + + const replaceStrings = { '&': '&', '<': '<', '>': '>' }; + let text = brew.text; + for (const replaceStr in replaceStrings) { + text = text.replaceAll(replaceStr, replaceStrings[replaceStr]); + } + text = `
${text}
`; + res.status(200).send(text); +}); + +//Download brew source page +app.get('/download/:id', asyncHandler(getBrew('share')), (req, res)=>{ + const { brew } = req; + sanitizeBrew(brew, 'share'); + const prefix = 'HB - '; + + const encodeRFC3986ValueChars = (str)=>{ + return ( + encodeURIComponent(str) + .replace(/[!'()*]/g, (char)=>{`%${char.charCodeAt(0).toString(16).toUpperCase()}`;}) + ); + }; + + let fileName = sanitizeFilename(`${prefix}${brew.title}`).replaceAll(' ', ''); + if(!fileName || !fileName.length) { fileName = `${prefix}-Untitled-Brew`; }; + res.set({ + 'Cache-Control' : 'no-cache', + 'Content-Type' : 'text/plain', + 'Content-Disposition' : `attachment; filename*=UTF-8''${encodeRFC3986ValueChars(fileName)}.txt` + }); + res.status(200).send(brew.text); +}); + +//Serve brew metadata +app.get('/metadata/:id', asyncHandler(getBrew('share')), (req, res)=>{ + const { brew } = req; + sanitizeBrew(brew, 'share'); + + const fields = ['title', 'pageCount', 'description', 'authors', 'lang', + 'published', 'views', 'shareId', 'createdAt', 'updatedAt', + 'lastViewed', 'thumbnail', 'tags' + ]; + + const metadata = fields.reduce((acc, field)=>{ + if(brew[field] !== undefined) acc[field] = brew[field]; + return acc; + }, {}); + res.status(200).json(metadata); +}); + +//Serve brew styling +app.get('/css/:id', asyncHandler(getBrew('share')), (req, res)=>{getCSS(req, res);}); + +//User Page +app.get('/user/:username', async (req, res, next)=>{ + const ownAccount = req.account && (req.account.username == req.params.username); + + req.ogMeta = { ...defaultMetaTags, + title : `${req.params.username}'s Collection`, + description : 'View my collection of homebrew on the Homebrewery.' + // type : could be 'profile'? + }; + + const fields = [ + 'googleId', + 'title', + 'pageCount', + 'description', + 'authors', + 'lang', + 'published', + 'views', + 'shareId', + 'editId', + 'createdAt', + 'updatedAt', + 'lastViewed', + 'thumbnail', + 'tags' + ]; + + let brews = await HomebrewModel.getByUser(req.params.username, ownAccount, fields) + .catch((err)=>{ + console.log(err); + }); + + brews.forEach(brew => brew.stubbed = true); //All brews from MongoDB are "stubbed" + + if(ownAccount && req?.account?.googleId){ + const auth = await GoogleActions.authCheck(req.account, res); + let googleBrews = await GoogleActions.listGoogleBrews(auth) + .catch((err)=>{ + console.error(err); + }); + + // If stub matches file from Google, use Google metadata over stub metadata + if(googleBrews && googleBrews.length > 0) { + for (const brew of brews.filter((brew)=>brew.googleId)) { + const match = googleBrews.findIndex((b)=>b.editId === brew.editId); + if(match !== -1) { + brew.googleId = googleBrews[match].googleId; + brew.pageCount = googleBrews[match].pageCount; + brew.renderer = googleBrews[match].renderer; + brew.version = googleBrews[match].version; + brew.webViewLink = googleBrews[match].webViewLink; + googleBrews.splice(match, 1); + } + } + + //Remaining unstubbed google brews display current user as author + googleBrews = googleBrews.map((brew)=>({ ...brew, authors: [req.account.username] })); + brews = _.concat(brews, googleBrews); + } + } + + req.brews = _.map(brews, (brew)=>{ + // Clean up brew data + brew.title = brew.title?.trim(); + brew.description = brew.description?.trim(); + return sanitizeBrew(brew, ownAccount ? 'edit' : 'share'); + }); + + return next(); +}); + +//Edit Page +app.get('/edit/:id', asyncHandler(getBrew('edit')), asyncHandler(async(req, res, next)=>{ + req.brew = req.brew.toObject ? req.brew.toObject() : req.brew; + + req.userThemes = await(getUsersBrewThemes(req.account?.username)); + + req.ogMeta = { ...defaultMetaTags, + title : req.brew.title || 'Untitled Brew', + description : req.brew.description || 'No description.', + image : req.brew.thumbnail || defaultMetaTags.image, + type : 'article' + }; + + sanitizeBrew(req.brew, 'edit'); + splitTextStyleAndMetadata(req.brew); + res.header('Cache-Control', 'no-cache, no-store'); //reload the latest saved brew when pressing back button, not the cached version before save. + return next(); +})); + +//New Page from ID +app.get('/new/:id', asyncHandler(getBrew('share')), asyncHandler(async(req, res, next)=>{ + sanitizeBrew(req.brew, 'share'); + splitTextStyleAndMetadata(req.brew); + const brew = { + shareId : req.brew.shareId, + title : `CLONE - ${req.brew.title}`, + text : req.brew.text, + style : req.brew.style, + renderer : req.brew.renderer, + theme : req.brew.theme, + tags : req.brew.tags, + }; + req.brew = _.defaults(brew, DEFAULT_BREW); + + req.userThemes = await(getUsersBrewThemes(req.account?.username)); + + req.ogMeta = { ...defaultMetaTags, + title : 'New', + description : 'Start crafting your homebrew on the Homebrewery!' + }; + + return next(); +})); + +//New Page +app.get('/new', asyncHandler(async(req, res, next)=>{ + req.userThemes = await(getUsersBrewThemes(req.account?.username)); + + req.ogMeta = { ...defaultMetaTags, + title : 'New', + description : 'Start crafting your homebrew on the Homebrewery!' + }; + + return next(); +})); + +//Share Page +app.get('/share/:id', asyncHandler(getBrew('share')), asyncHandler(async (req, res, next)=>{ + const { brew } = req; + req.ogMeta = { ...defaultMetaTags, + title : req.brew.title || 'Untitled Brew', + description : req.brew.description || 'No description.', + image : req.brew.thumbnail || defaultMetaTags.image, + type : 'article' + }; + + // increase visitor view count, do not include visits by author(s) + if(!brew.authors.includes(req.account?.username)){ + if(req.params.id.length > 12 && !brew._id) { + const googleId = brew.googleId; + const shareId = brew.shareId; + await GoogleActions.increaseView(googleId, shareId, 'share', brew) + .catch((err)=>{next(err);}); + } else { + await HomebrewModel.increaseView({ shareId: brew.shareId }); + } + }; + + brew.authors.includes(req.account?.username) ? sanitizeBrew(req.brew, 'shareAuthor') : sanitizeBrew(req.brew, 'share'); + splitTextStyleAndMetadata(req.brew); + return next(); +})); + +//Account Page +app.get('/account', asyncHandler(async (req, res, next)=>{ + const data = {}; + data.title = 'Account Information Page'; + + if(!req.account) { + res.set('WWW-Authenticate', 'Bearer realm="Authorization Required"'); + const error = new Error('No valid account'); + error.status = 401; + error.HBErrorCode = '50'; + error.page = data.title; + return next(error); + }; + + let auth; + let googleCount = []; + if(req.account) { + if(req.account.googleId) { + auth = await GoogleActions.authCheck(req.account, res, false) + + googleCount = await GoogleActions.listGoogleBrews(auth) + .catch((err)=>{ + console.error(err); + }); + } + + const query = { authors: req.account.username, googleId: { $exists: false } }; + const mongoCount = await HomebrewModel.countDocuments(query) + .catch((err)=>{ + mongoCount = 0; + console.log(err); + }); + + data.accountDetails = { + username : req.account.username, + issued : req.account.issued, + googleId : Boolean(req.account.googleId), + authCheck : Boolean(req.account.googleId && auth?.credentials.access_token), + mongoCount : mongoCount, + googleCount : googleCount?.length + }; + } + + req.brew = data; + + req.ogMeta = { ...defaultMetaTags, + title : `Account Page`, + description : null + }; + + return next(); +})); + +const nodeEnv = config.get('node_env'); +const isLocalEnvironment = config.get('local_environments').includes(nodeEnv); +// Local only +if(isLocalEnvironment){ + // Login + app.post('/local/login', (req, res)=>{ + const username = req.body.username; + if(!username) return; + + const payload = jwt.encode({ username: username, issued: new Date }, config.get('secret')); + return res.json(payload); + }); +} + +// Add Static Local Paths +app.use('/staticImages', express.static(config.get('hb_images') && fs.existsSync(config.get('hb_images')) ? config.get('hb_images') :'staticImages')); +app.use('/staticFonts', express.static(config.get('hb_fonts') && fs.existsSync(config.get('hb_fonts')) ? config.get('hb_fonts'):'staticFonts')); + +//Vault Page +app.get('/vault', asyncHandler(async(req, res, next)=>{ + req.ogMeta = { ...defaultMetaTags, + title : 'The Vault', + description : 'Search for Brews' + }; + return next(); +})); + +//Send rendered page +app.use(asyncHandler(async (req, res, next)=>{ + if (!req.route) return res.redirect('/'); // Catch-all for invalid routes + + const page = await renderPage(req, res); + if(!page) return; + res.send(page); +})); + +//Render the page +const renderPage = async (req, res)=>{ + // Create configuration object + const configuration = { + local : isLocalEnvironment, + publicUrl : config.get('publicUrl') ?? '', + environment : nodeEnv, + deployment : config.get('heroku_app_name') ?? '' + }; + const props = { + version : require('./../package.json').version, + url : req.customUrl || req.originalUrl, + brew : req.brew, + brews : req.brews, + googleBrews : req.googleBrews, + account : req.account, + enable_v3 : config.get('enable_v3'), + enable_themes : config.get('enable_themes'), + config : configuration, + ogMeta : req.ogMeta, + userThemes : req.userThemes + }; + const title = req.brew ? req.brew.title : ''; + const page = await templateFn('homebrew', title, props) + .catch((err)=>{ + console.log(err); + }); + return page; +}; + +//v=====----- Error-Handling Middleware -----=====v// +//Format Errors as plain objects so all fields will appear in the string sent +const formatErrors = (key, value)=>{ + if(value instanceof Error) { + const error = {}; + Object.getOwnPropertyNames(value).forEach(function (key) { + error[key] = value[key]; + }); + return error; + } + return value; +}; + +const getPureError = (error)=>{ + return JSON.parse(JSON.stringify(error, formatErrors)); +}; + +app.use(async (err, req, res, next)=>{ + err.originalUrl = req.originalUrl; + console.error(err); + + if(err.originalUrl?.startsWith('/api')) { + // console.log('API error'); + res.status(err.status || err.response?.status || 500).send(err); + return; + } + + // console.log('non-API error'); + const status = err.status || err.code || 500; + + req.ogMeta = { ...defaultMetaTags, + title : 'Error Page', + description : 'Something went wrong!' + }; + req.brew = { + ...err, + title : 'Error - Something went wrong!', + text : err.errors?.map((error)=>{return error.message;}).join('\n\n') || err.message || 'Unknown error!', + status : status, + HBErrorCode : err.HBErrorCode ?? '00', + pureError : getPureError(err) + }; + req.customUrl= '/error'; + + const page = await renderPage(req, res); + if(!page) return; + res.send(page); +}); + +app.use((req, res)=>{ + if(!res.headersSent) { + console.error('Headers have not been sent, responding with a server error.', req.url); + res.status(500).send('An error occurred and the server did not send a response. The error has been logged, please note the time this occurred and report this issue.'); + } +}); +//^=====--------------------------------------=====^// + +module.exports = { + app : app +}; diff --git a/server/googleActions.js b/server/googleActions.js index 93367248e..bc97551ee 100644 --- a/server/googleActions.js +++ b/server/googleActions.js @@ -25,6 +25,15 @@ if(!config.get('service_account')){ const defaultAuth = serviceAuth || config.get('google_api_key'); +const retryConfig = { + retry: 3, // Number of retry attempts + retryDelay: 100, // Initial delay in milliseconds + retryDelayMultiplier: 2, // Multiplier for exponential backoff + maxRetryDelay: 32000, // Maximum delay in milliseconds + httpMethodsToRetry: ['PATCH'], // Only retry PATCH requests + statusCodesToRetry: [[429, 429]], // Only retry on 429 status code +}; + const GoogleActions = { authCheck : (account, res, updateTokens=true)=>{ @@ -112,9 +121,7 @@ const GoogleActions = { }) .catch((err)=>{ console.log(`Error Listing Google Brews`); - console.error(err); throw (err); - //TODO: Should break out here, but continues on for some reason. }); fileList.push(...obj.data.files); NextPageToken = obj.data.nextPageToken; @@ -147,7 +154,7 @@ const GoogleActions = { return brews; }, - updateGoogleBrew : async (brew)=>{ + updateGoogleBrew : async (brew, userIp)=>{ const drive = googleDrive.drive({ version: 'v3', auth: defaultAuth }); await drive.files.update({ @@ -168,11 +175,14 @@ const GoogleActions = { media : { mimeType : 'text/plain', body : brew.text - } + }, + headers: { + 'X-Forwarded-For': userIp, // Set the X-Forwarded-For header + }, + retryConfig }) .catch((err)=>{ console.log('Error saving to google'); - console.error(err); throw (err); }); @@ -211,7 +221,6 @@ const GoogleActions = { }) .catch((err)=>{ console.log('Error while creating new Google brew'); - console.error(err); throw (err); }); diff --git a/server/homebrew.api.js b/server/homebrew.api.js index c314454e2..213b341ca 100644 --- a/server/homebrew.api.js +++ b/server/homebrew.api.js @@ -99,7 +99,7 @@ const api = { stub = stub?.toObject(); if(stub?.lock?.locked && accessType != 'edit') { - throw { HBErrorCode: '100', code: stub.lock.code, message: stub.lock.shareMessage, brewId: stub.shareId, brewTitle: stub.title }; + throw { HBErrorCode: '51', code: stub.lock.code, message: stub.lock.shareMessage, brewId: stub.shareId, brewTitle: stub.title }; } // If there is a google id, try to find the google brew @@ -148,6 +148,20 @@ const api = { next(); }; }, + + getCSS : async (req, res)=>{ + const { brew } = req; + if(!brew) return res.status(404).send(''); + splitTextStyleAndMetadata(brew); + if(!brew.style) return res.status(404).send(''); + + res.set({ + 'Cache-Control' : 'no-cache', + 'Content-Type' : 'text/css' + }); + return res.status(200).send(brew.style); + }, + mergeBrewText : (brew)=>{ let text = brew.text; if(brew.style !== undefined) { @@ -228,11 +242,8 @@ const api = { let googleId, saved; if(saveToGoogle) { - googleId = await api.newGoogleBrew(req.account, newHomebrew, res) - .catch((err)=>{ - console.error(err); - res.status(err?.status || err?.response?.status || 500).send(err?.message || err); - }); + googleId = await api.newGoogleBrew(req.account, newHomebrew, res); + if(!googleId) return; api.excludeStubProps(newHomebrew); newHomebrew.googleId = googleId; @@ -337,19 +348,13 @@ const api = { brew.googleId = undefined; } else if(!brew.googleId && saveToGoogle) { // If we don't have a google id and the user wants to save to google, create the google brew and set the google id on the brew - brew.googleId = await api.newGoogleBrew(req.account, api.excludeGoogleProps(brew), res) - .catch((err)=>{ - console.error(err); - res.status(err.status || err.response.status).send(err.message || err); - }); + brew.googleId = await api.newGoogleBrew(req.account, api.excludeGoogleProps(brew), res); + if(!brew.googleId) return; } else if(brew.googleId) { // If the google id exists and no other actions are being performed, update the google brew - const updated = await GoogleActions.updateGoogleBrew(api.excludeGoogleProps(brew)) - .catch((err)=>{ - console.error(err); - res.status(err?.response?.status || 500).send(err); - }); + const updated = await GoogleActions.updateGoogleBrew(api.excludeGoogleProps(brew), req.ip); + if(!updated) return; } diff --git a/server/homebrew.api.spec.js b/server/homebrew.api.spec.js index 5f1739b97..a1222cb57 100644 --- a/server/homebrew.api.spec.js +++ b/server/homebrew.api.spec.js @@ -50,6 +50,7 @@ describe('Tests for api', ()=>{ res = { status : jest.fn(()=>res), send : jest.fn(()=>{}), + set : jest.fn(()=>{}), setHeader : jest.fn(()=>{}) }; @@ -308,7 +309,7 @@ describe('Tests for api', ()=>{ const req = { brew: {} }; const next = jest.fn(); - await expect(fn(req, null, next)).rejects.toEqual({ 'HBErrorCode': '100', 'brewId': '1', 'brewTitle': 'test brew', 'code': 404, 'message': 'brew locked' }); + await expect(fn(req, null, next)).rejects.toEqual({ 'HBErrorCode': '51', 'brewId': '1', 'brewTitle': 'test brew', 'code': 404, 'message': 'brew locked' }); }); }); @@ -559,16 +560,6 @@ brew`); views : 0 }); }); - - it('should handle google error', async()=>{ - google.newGoogleBrew = jest.fn(()=>{ - throw 'err'; - }); - await api.newBrew({ body: { text: 'asdf', title: '' }, query: { saveToGoogle: true }, account: { username: 'test user' } }, res); - - expect(res.status).toHaveBeenCalledWith(500); - expect(res.send).toHaveBeenCalledWith('err'); - }); }); describe('deleteGoogleBrew', ()=>{ @@ -916,4 +907,66 @@ brew`); expect(saved.googleId).toEqual(brew.googleId); }); }); + describe('Get CSS', ()=>{ + it('should return brew style content as CSS text', async ()=>{ + const testBrew = { title: 'test brew', text: '```css\n\nI Have a style!\n````\n\n' }; + + const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew })); + api.getId = jest.fn(()=>({ id: '1', googleId: undefined })); + model.get = jest.fn(()=>toBrewPromise(testBrew)); + + const fn = api.getBrew('share', true); + const req = { brew: {} }; + const next = jest.fn(); + await fn(req, null, next); + await api.getCSS(req, res); + + expect(req.brew).toEqual(testBrew); + expect(req.brew).toHaveProperty('style', '\nI Have a style!\n'); + expect(res.status).toHaveBeenCalledWith(200); + expect(res.send).toHaveBeenCalledWith('\nI Have a style!\n'); + expect(res.set).toHaveBeenCalledWith({ + 'Cache-Control' : 'no-cache', + 'Content-Type' : 'text/css' + }); + }); + + it('should return 404 when brew has no style content', async ()=>{ + const testBrew = { title: 'test brew', text: 'I don\'t have a style!' }; + + const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew })); + api.getId = jest.fn(()=>({ id: '1', googleId: undefined })); + model.get = jest.fn(()=>toBrewPromise(testBrew)); + + const fn = api.getBrew('share', true); + const req = { brew: {} }; + const next = jest.fn(); + await fn(req, null, next); + await api.getCSS(req, res); + + expect(req.brew).toEqual(testBrew); + expect(req.brew).toHaveProperty('style'); + expect(res.status).toHaveBeenCalledWith(404); + expect(res.send).toHaveBeenCalledWith(''); + }); + + it('should return 404 when brew does not exist', async ()=>{ + const testBrew = { }; + + const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew })); + api.getId = jest.fn(()=>({ id: '1', googleId: undefined })); + model.get = jest.fn(()=>toBrewPromise(testBrew)); + + const fn = api.getBrew('share', true); + const req = { brew: {} }; + const next = jest.fn(); + await fn(req, null, next); + await api.getCSS(req, res); + + expect(req.brew).toEqual(testBrew); + expect(req.brew).toHaveProperty('style'); + expect(res.status).toHaveBeenCalledWith(404); + expect(res.send).toHaveBeenCalledWith(''); + }); + }); }); diff --git a/server/middleware/content-negotiation.js b/server/middleware/content-negotiation.js index 201e64a25..a5bc7dc83 100644 --- a/server/middleware/content-negotiation.js +++ b/server/middleware/content-negotiation.js @@ -1,12 +1,16 @@ +const config = require('../config.js'); +const nodeEnv = config.get('node_env'); +const isLocalEnvironment = config.get('local_environments').includes(nodeEnv); + module.exports = (req, res, next)=>{ const isImageRequest = req.get('Accept')?.split(',') ?.filter((h)=>!h.includes('q=')) ?.every((h)=>/image\/.*/.test(h)); - if(isImageRequest) { + if(isImageRequest && !isLocalEnvironment && !req.url?.startsWith('/staticImages')) { return res.status(406).send({ message : 'Request for image at this URL is not supported' }); } next(); -}; \ No newline at end of file +}; diff --git a/server/notifications.model.js b/server/notifications.model.js new file mode 100644 index 000000000..0a32bde8a --- /dev/null +++ b/server/notifications.model.js @@ -0,0 +1,62 @@ +const mongoose = require('mongoose'); +const _ = require('lodash'); + +const NotificationSchema = new mongoose.Schema({ + dismissKey : { type: String, unique: true, required: true }, + title : { type: String, default: '' }, + text : { type: String, default: '' }, + createdAt : { type: Date, default: Date.now }, + startAt : { type: Date, default: Date.now }, + stopAt : { type: Date, default: Date.now }, +}, { versionKey: false }); + +NotificationSchema.statics.addNotification = async function(data) { + if(!data.dismissKey) throw { message: 'Dismiss key is required!' }; + + const defaults = { + title : '', + text : '', + startAt : new Date(), + stopAt : new Date(), + }; + + const notificationData = _.defaults(data, defaults); + + try { + const newNotification = new this(notificationData); + const savedNotification = await newNotification.save(); + return savedNotification; + } catch (err) { + throw { message: err.message || 'Error saving notification' }; + } +}; + +NotificationSchema.statics.deleteNotification = async function(dismissKey) { + if(!dismissKey) throw { message: 'Dismiss key is required!' }; + + try { + const deletedNotification = await this.findOneAndDelete({ dismissKey }).exec(); + if(!deletedNotification) { + throw { message: 'Notification not found' }; + } + return deletedNotification; + } catch (err) { + throw { message: err.message || 'Error deleting notification' }; + } +}; + +NotificationSchema.statics.getAll = async function() { + try { + const notifications = await this.find().exec(); + return notifications; + } catch (err) { + throw { message: err.message || 'Error retrieving notifications' }; + } +}; + +const Notification = mongoose.model('Notification', NotificationSchema); + +module.exports = { + schema : NotificationSchema, + model : Notification, +}; diff --git a/server/vault.api.js b/server/vault.api.js new file mode 100644 index 000000000..8aa382f26 --- /dev/null +++ b/server/vault.api.js @@ -0,0 +1,109 @@ +const express = require('express'); +const asyncHandler = require('express-async-handler'); +const HomebrewModel = require('./homebrew.model.js').model; + +const router = express.Router(); + +const titleConditions = (title)=>{ + if(!title) return {}; + return { + $text : { + $search : title, + $caseSensitive : false, + }, + }; +}; + +const authorConditions = (author)=>{ + if(!author) return {}; + return { authors: author }; +}; + +const rendererConditions = (legacy, v3)=>{ + if(legacy === 'true' && v3 !== 'true') + return { renderer: 'legacy' }; + + if(v3 === 'true' && legacy !== 'true') + return { renderer: 'V3' }; + + return {}; // If all renderers selected, renderer field not needed in query for speed +}; + +const sortConditions = (sort, dir) => { + return { [sort]: dir === 'asc' ? 1 : -1 }; +}; + +const findBrews = async (req, res)=>{ + const title = req.query.title || ''; + const author = req.query.author || ''; + const page = Math.max(parseInt(req.query.page) || 1, 1); + const count = Math.max(parseInt(req.query.count) || 20, 10); + const skip = (page - 1) * count; + const sort = req.query.sort || 'title'; + const dir = req.query.dir || 'asc'; + + const combinedQuery = { + $and : [ + { published: true }, + rendererConditions(req.query.legacy, req.query.v3), + titleConditions(title), + authorConditions(author) + ], + }; + + const projection = { + editId : 0, + googleId : 0, + text : 0, + textBin : 0, + version : 0 + }; + + await HomebrewModel.find(combinedQuery, projection) + .sort(sortConditions(sort, dir)) + .skip(skip) + .limit(count) + .maxTimeMS(5000) + .exec() + .then((brews)=>{ + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + + const processedBrews = brews.map((brew)=>{ + brew.authors = brew.authors.map((author)=>emailRegex.test(author) ? 'hidden' : author + ); + return brew; + }); + res.json({ brews: processedBrews, page }); + }) + .catch((error)=>{ + throw { ...error, message: 'Error finding brews in Vault search', HBErrorCode: 90 }; + }); +}; + +const findTotal = async (req, res)=>{ + const title = req.query.title || ''; + const author = req.query.author || ''; + + const combinedQuery = { + $and : [ + { published: true }, + rendererConditions(req.query.legacy, req.query.v3), + titleConditions(title), + authorConditions(author) + ], + }; + + await HomebrewModel.countDocuments(combinedQuery) + .then((totalBrews)=>{ + console.log(`when returning, the total of brews is ${totalBrews} for the query ${JSON.stringify(combinedQuery)}`); + res.json({ totalBrews }); + }) + .catch((error)=>{ + throw { ...error, message: 'Error finding brews in Vault search findTotal function', HBErrorCode: 91 }; + }); +}; + +router.get('/api/vault/total', asyncHandler(findTotal)); +router.get('/api/vault', asyncHandler(findBrews)); + +module.exports = router; diff --git a/shared/naturalcrit/codeEditor/codeEditor.jsx b/shared/naturalcrit/codeEditor/codeEditor.jsx index 3186e39f1..fb69b6dcf 100644 --- a/shared/naturalcrit/codeEditor/codeEditor.jsx +++ b/shared/naturalcrit/codeEditor/codeEditor.jsx @@ -397,6 +397,11 @@ const CodeEditor = createClass({ getCursorPosition : function(){ return this.codeMirror.getCursor(); }, + getTopVisibleLine : function(){ + const rect = this.codeMirror.getWrapperElement().getBoundingClientRect(); + const topVisibleLine = this.codeMirror.lineAtHeight(rect.top, 'window'); + return topVisibleLine; + }, updateSize : function(){ this.codeMirror.refresh(); }, diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index 2ee99f72b..ef789bdd6 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -3,7 +3,7 @@ const _ = require('lodash'); const Marked = require('marked'); const MarkedExtendedTables = require('marked-extended-tables'); const { markedSmartypantsLite: MarkedSmartypantsLite } = require('marked-smartypants-lite'); -const { gfmHeadingId: MarkedGFMHeadingId } = require('marked-gfm-heading-id'); +const { gfmHeadingId: MarkedGFMHeadingId, resetHeadings: MarkedGFMResetHeadingIDs } = require('marked-gfm-heading-id'); const { markedEmoji: MarkedEmojis } = require('marked-emoji'); //Icon fonts included so they can appear in emoji autosuggest dropdown @@ -102,6 +102,20 @@ renderer.link = function (href, title, text) { return out; }; +// Expose `src` attribute as `--HB_src` to make the URL accessible via CSS +renderer.image = function (href, title, text) { + href = cleanUrl(href); + if(href === null) + return text; + + let out = `${text}`` }; +const tableTerminators = [ + `:+\\n`, // hardBreak + ` *{[^\n]+}`, // blockInjector + ` *{{[^{\n]*\n.*?\n}}` // mustacheDiv +]; + Marked.use(MarkedVariables()); Marked.use({ extensions : [definitionListsMultiLine, definitionListsSingleLine, forcedParagraphBreaks, superSubScripts, mustacheSpans, mustacheDivs, mustacheInjectInline] }); Marked.use(mustacheInjectBlock); Marked.use({ renderer: renderer, tokenizer: tokenizer, mangle: false }); -Marked.use(MarkedExtendedTables(), MarkedGFMHeadingId(), MarkedSmartypantsLite(), MarkedEmojis(MarkedEmojiOptions)); +Marked.use(MarkedExtendedTables(tableTerminators), MarkedGFMHeadingId({ globalSlugs: true }), MarkedSmartypantsLite(), MarkedEmojis(MarkedEmojiOptions)); function cleanUrl(href) { - try { - href = encodeURI(href).replace(/%25/g, '%'); - } catch { - return null; - } - return href; + try { + href = encodeURI(href).replace(/%25/g, '%'); + } catch { + return null; + } + return href; } const escapeTest = /[&<>"']/; @@ -836,10 +856,13 @@ let globalPageNumber = 0; module.exports = { marked : Marked, - render : (rawBrewText, pageNumber=1)=>{ - globalVarsList[pageNumber] = {}; //Reset global links for current page, to ensure values are parsed in order - varsQueue = []; //Could move into MarkedVariables() - globalPageNumber = pageNumber; + render : (rawBrewText, pageNumber=0)=>{ + globalVarsList[pageNumber] = {}; //Reset global links for current page, to ensure values are parsed in order + varsQueue = []; //Could move into MarkedVariables() + globalPageNumber = pageNumber; + if(pageNumber==0) { + MarkedGFMResetHeadingIDs(); + } rawBrewText = rawBrewText.replace(/^\\column$/gm, `\n
\n`); const opts = Marked.defaults; diff --git a/shared/naturalcrit/splitPane/splitPane.jsx b/shared/naturalcrit/splitPane/splitPane.jsx index 55af5e386..23ae5d321 100644 --- a/shared/naturalcrit/splitPane/splitPane.jsx +++ b/shared/naturalcrit/splitPane/splitPane.jsx @@ -7,8 +7,9 @@ const SplitPane = createClass({ displayName : 'SplitPane', getDefaultProps : function() { return { - storageKey : 'naturalcrit-pane-split', - onDragFinish : function(){} //fires when dragging + storageKey : 'naturalcrit-pane-split', + onDragFinish : function(){}, //fires when dragging + showDividerButtons : true }; }, @@ -41,6 +42,10 @@ const SplitPane = createClass({ }); } window.addEventListener('resize', this.handleWindowResize); + + // This lives here instead of in the initial render because you cannot touch localStorage until the componant mounts. + const loadLiveScroll = window.localStorage.getItem('liveScroll') === 'true'; + this.setState({ liveScroll: loadLiveScroll }); }, componentWillUnmount : function() { @@ -88,6 +93,11 @@ const SplitPane = createClass({ userSetDividerPos : newSize }); }, + + liveScrollToggle : function() { + window.localStorage.setItem('liveScroll', String(!this.state.liveScroll)); + this.setState({ liveScroll: !this.state.liveScroll }); + }, /* unFocus : function() { if(document.selection){ @@ -119,13 +129,18 @@ const SplitPane = createClass({ onClick={()=>this.setState({ moveBrew: !this.state.moveBrew })} >
+
+ +
; } }, renderDivider : function(){ return <> - {this.renderMoveArrows()} + {this.props.showDividerButtons && this.renderMoveArrows()}
@@ -142,9 +157,12 @@ const SplitPane = createClass({ width={this.state.currentDividerPos} > {React.cloneElement(this.props.children[0], { - moveBrew : this.state.moveBrew, - moveSource : this.state.moveSource, - setMoveArrows : this.setMoveArrows + ...(this.props.showDividerButtons && { + moveBrew : this.state.moveBrew, + moveSource : this.state.moveSource, + liveScroll : this.state.liveScroll, + setMoveArrows : this.setMoveArrows, + }), })} {this.renderDivider()} diff --git a/shared/naturalcrit/splitPane/splitPane.less b/shared/naturalcrit/splitPane/splitPane.less index 831b5ce47..e5b3dd7f8 100644 --- a/shared/naturalcrit/splitPane/splitPane.less +++ b/shared/naturalcrit/splitPane/splitPane.less @@ -53,6 +53,15 @@ .tooltipRight('Jump to location in Preview'); top : 60px; } + &.lock{ + .tooltipRight('De-sync Editor and Preview locations.'); + top : 90px; + background: #666; + } + &.unlock{ + .tooltipRight('Sync Editor and Preview locations'); + top : 90px; + } &:hover{ background-color: #666; } diff --git a/tests/markdown/mustache-syntax.test.js b/tests/markdown/mustache-syntax.test.js index 3f7f2529b..51284ef2b 100644 --- a/tests/markdown/mustache-syntax.test.js +++ b/tests/markdown/mustache-syntax.test.js @@ -322,9 +322,9 @@ describe('Injection: When an injection tag follows an element', ()=>{ }); it('Renders an image element with injected style', function() { - const source = '![alt text](http://i.imgur.com/hMna6G0.png){position:absolute}'; + const source = '![alt text](https://i.imgur.com/hMna6G0.png){position:absolute}'; const rendered = Markdown.render(source).trimReturns(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('

alt text

'); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('

alt text

'); }); it('Renders an element modified by only the first of two consecutive injections', function() { @@ -343,19 +343,19 @@ describe('Injection: When an injection tag follows an element', ()=>{ it('Renders an image with added attributes', function() { const source = `![homebrew mug](https://i.imgur.com/hMna6G0.png) {position:absolute,bottom:20px,left:130px,width:220px,a="b and c",d=e}`; const rendered = Markdown.render(source).trimReturns(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

homebrew mug

`); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

homebrew mug

`); }); it('Renders an image with "=" in the url, and added attributes', function() { const source = `![homebrew mug](https://i.imgur.com/hMna6G0.png?auth=12345&height=1024) {position:absolute,bottom:20px,left:130px,width:220px,a="b and c",d=e}`; const rendered = Markdown.render(source).trimReturns(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

homebrew mug

`); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

homebrew mug

`); }); it('Renders an image and added attributes with "=" in the value, ', function() { const source = `![homebrew mug](https://i.imgur.com/hMna6G0.png) {position:absolute,bottom:20px,left:130px,width:220px,a="b and c",d=e,otherUrl="url?auth=12345"}`; const rendered = Markdown.render(source).trimReturns(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

homebrew mug

`); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

homebrew mug

`); }); }); diff --git a/tests/markdown/variables.test.js b/tests/markdown/variables.test.js index 2c8db375e..bf778b14d 100644 --- a/tests/markdown/variables.test.js +++ b/tests/markdown/variables.test.js @@ -315,21 +315,21 @@ describe('Normal Links and Images', ()=>{ const source = `![alt text](url)`; const rendered = Markdown.render(source).trimReturns(); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent` -

alt text

`.trimReturns()); +

alt text

`.trimReturns()); }); it('Renders normal images with a title', function() { const source = 'An image ![alt text](url "and title")!'; const rendered = Markdown.render(source).trimReturns(); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent` -

An image alt text!

`.trimReturns()); +

An image alt text!

`.trimReturns()); }); it('Applies curly injectors to images', function() { const source = `![alt text](url){width:100px}`; const rendered = Markdown.render(source).trimReturns(); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent` -

alt text

`.trimReturns()); +

alt text

`.trimReturns()); }); it('Renders normal links', function() { diff --git a/themes/V3/5ePHB/snippets.js b/themes/V3/5ePHB/snippets.js index 4daa05c51..543b1cf5c 100644 --- a/themes/V3/5ePHB/snippets.js +++ b/themes/V3/5ePHB/snippets.js @@ -27,35 +27,157 @@ module.exports = [ experimental : true, subsnippets : [ { - name : 'Table of Contents', + name : 'Generate Table of Contents', icon : 'fas fa-book', gen : TableOfContentsGen, experimental : true }, { - name : 'Include in ToC up to H3', - icon : 'fas fa-dice-three', + name : 'Table of Contents Individual Inclusion', + icon : 'fas fa-book', + gen : dedent `\n{{tocInclude# CHANGE # to your header level + }}\n`, + subsnippets : [ + { + name : 'Individual Inclusion H1', + icon : 'fas fa-book', + gen : dedent `\n{{tocIncludeH1 \n + }}\n`, + }, + { + name : 'Individual Inclusion H2', + icon : 'fas fa-book', + gen : dedent `\n{{tocIncludeH2 \n + }}\n`, + }, + { + name : 'Individual Inclusion H3', + icon : 'fas fa-book', + gen : dedent `\n{{tocIncludeH3 \n + }}\n`, + }, + { + name : 'Individual Inclusion H4', + icon : 'fas fa-book', + gen : dedent `\n{{tocIncludeH4 \n + }}\n`, + }, + { + name : 'Individual Inclusion H5', + icon : 'fas fa-book', + gen : dedent `\n{{tocIncludeH5 \n + }}\n`, + }, + { + name : 'Individual Inclusion H6', + icon : 'fas fa-book', + gen : dedent `\n{{tocIncludeH6 \n + }}\n`, + } + ] + }, + { + name : 'Table of Contents Range Inclusion', + icon : 'fas fa-book', gen : dedent `\n{{tocDepthH3 }}\n`, + subsnippets : [ + { + name : 'Include in ToC up to H3', + icon : 'fas fa-dice-three', + gen : dedent `\n{{tocDepthH3 + }}\n`, + }, + { + name : 'Include in ToC up to H4', + icon : 'fas fa-dice-four', + gen : dedent `\n{{tocDepthH4 + }}\n`, + }, + { + name : 'Include in ToC up to H5', + icon : 'fas fa-dice-five', + gen : dedent `\n{{tocDepthH5 + }}\n`, + }, + { + name : 'Include in ToC up to H6', + icon : 'fas fa-dice-six', + gen : dedent `\n{{tocDepthH6 + }}\n`, + }, + ] }, { - name : 'Include in ToC up to H4', - icon : 'fas fa-dice-four', - gen : dedent `\n{{tocDepthH4 + name : 'Table of Contents Individual Exclusion', + icon : 'fas fa-book', + gen : dedent `\n{{tocExcludeH1 \n }}\n`, + subsnippets : [ + { + name : 'Individual Exclusion H1', + icon : 'fas fa-book', + gen : dedent `\n{{tocExcludeH1 \n + }}\n`, + }, + { + name : 'Individual Exclusion H2', + icon : 'fas fa-book', + gen : dedent `\n{{tocExcludeH2 \n + }}\n`, + }, + { + name : 'Individual Exclusion H3', + icon : 'fas fa-book', + gen : dedent `\n{{tocExcludeH3 \n + }}\n`, + }, + { + name : 'Individual Exclusion H4', + icon : 'fas fa-book', + gen : dedent `\n{{tocExcludeH4 \n + }}\n`, + }, + { + name : 'Individual Exclusion H5', + icon : 'fas fa-book', + gen : dedent `\n{{tocExcludeH5 \n + }}\n`, + }, + { + name : 'Individual Exclusion H6', + icon : 'fas fa-book', + gen : dedent `\n{{tocExcludeH6 \n + }}\n`, + }, + ] }, + { - name : 'Include in ToC up to H5', - icon : 'fas fa-dice-five', - gen : dedent `\n{{tocDepthH5 - }}\n`, - }, - { - name : 'Include in ToC up to H6', - icon : 'fas fa-dice-six', - gen : dedent `\n{{tocDepthH6 - }}\n`, + name : 'Table of Contents Toggles', + icon : 'fas fa-book', + //gen : `{{tocGlobalH4}}\n\n`, + disabled : true + // RELIES ON .PAGES:HAS() WHICH IS VERY SLOW + // WILL BE MOVED TO STYLE TAB SNIPPETS + // subsnippets : [ + // { + // name : 'Enable H1-H4 all pages', + // icon : 'fas fa-dice-four', + // gen : `{{tocGlobalH4}}\n\n`, + // }, + // { + // name : 'Enable H1-H5 all pages', + // icon : 'fas fa-dice-five', + // gen : `{{tocGlobalH5}}\n\n`, + // }, + // { + // name : 'Enable H1-H6 all pages', + // icon : 'fas fa-dice-six', + // gen : `{{tocGlobalH6}}\n\n`, + // }, + // ] } ] }, @@ -94,7 +216,7 @@ module.exports = [ background-image: linear-gradient(-45deg, #322814, #998250, #322814); line-height: 1em; }\n\n` - } + }, ] }, diff --git a/themes/V3/5ePHB/snippets/tableOfContents.gen.js b/themes/V3/5ePHB/snippets/tableOfContents.gen.js index 3aea01735..44c400762 100644 --- a/themes/V3/5ePHB/snippets/tableOfContents.gen.js +++ b/themes/V3/5ePHB/snippets/tableOfContents.gen.js @@ -1,77 +1,78 @@ -const _ = require('lodash'); const dedent = require('dedent-tabs').default; -const getTOC = (pages)=>{ +// Map each actual page to its footer label, accounting for skips or numbering resets +const mapPages = (pages)=>{ + let actualPage = 0; + let mappedPage = 0; // Number displayed in footer + const pageMap = []; - const recursiveAdd = (title, page, targetDepth, child, curDepth=0)=>{ - if(curDepth > 5) return; // Something went wrong. - if(curDepth == targetDepth) { - child.push({ - title : title, - page : page, - children : [] - }); - } else { - if(child.length == 0) { - child.push({ - title : null, - page : page, - children : [] - }); + pages.forEach((page)=>{ + actualPage++; + const doSkip = page.querySelector('.skipCounting'); + const doReset = page.querySelector('.resetCounting'); + + if(doReset) + mappedPage = 1; + if(!doSkip && !doReset) + mappedPage++; + + pageMap[actualPage] = { + mappedPage : mappedPage, + showPage : !doSkip + }; + }); + return pageMap; +}; + +const getMarkdown = (headings, pageMap)=>{ + const levelPad = ['- ###', ' - ####', ' -', ' -', ' -', ' -']; + + const allMarkdown = []; + const depthChain = [0]; + + headings.forEach((heading)=>{ + const page = parseInt(heading.closest('.page').id?.replace(/^p/, '')); + const mappedPage = pageMap[page].mappedPage; + const showPage = pageMap[page].showPage; + const title = heading.textContent.trim(); + const ToCExclude = getComputedStyle(heading).getPropertyValue('--TOC'); + const depth = parseInt(heading.tagName.substring(1)); + + if(!title || !showPage || ToCExclude == 'exclude') + return; + + //If different header depth than last, remove indents until nearest higher-level header, then indent once + if(depth !== depthChain[depthChain.length -1]) { + while (depth <= depthChain[depthChain.length - 1]) { + depthChain.pop(); } - recursiveAdd(title, page, targetDepth, _.last(child).children, curDepth+1,); + depthChain.push(depth); } - }; - const res = []; + const markdown = `${levelPad[depthChain.length - 2]} [{{ ${title}}}{{ ${mappedPage}}}](#p${page})`; + allMarkdown.push(markdown); + }); + return allMarkdown.join('\n'); +}; +const getTOC = ()=>{ const iframe = document.getElementById('BrewRenderer'); const iframeDocument = iframe.contentDocument || iframe.contentWindow.document; const headings = iframeDocument.querySelectorAll('h1, h2, h3, h4, h5, h6'); - const headerDepth = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6']; + const pages = iframeDocument.querySelectorAll('.page'); - _.each(headings, (heading)=>{ - const onPage = parseInt(heading.closest('.page').id?.replace(/^p/, '')); - const ToCExclude = getComputedStyle(heading).getPropertyValue('--TOC'); - - if(ToCExclude != 'exclude') { - recursiveAdd(heading.textContent.trim(), onPage, headerDepth.indexOf(heading.tagName), res); - } - }); - return res; -}; - - -const ToCIterate = (entries, curDepth=0)=>{ - const levelPad = ['- ###', ' - ####', ' - ', ' - ', ' - ', ' - ']; - const toc = []; - if(entries.title !== null){ - toc.push(`${levelPad[curDepth]} [{{ ${entries.title}}}{{ ${entries.page}}}](#p${entries.page})`); - } - if(entries.children.length) { - _.each(entries.children, (entry, idx)=>{ - const children = ToCIterate(entry, entry.title == null ? curDepth : curDepth+1); - if(children.length) { - toc.push(...children); - } - }); - } - return toc; + const pageMap = mapPages(pages); + return getMarkdown(headings, pageMap); }; module.exports = function(props){ - const pages = props.brew.text.split('\\page'); - const TOC = getTOC(pages); - const markdown = _.reduce(TOC, (r, g1, idx1)=>{ - r.push(ToCIterate(g1).join('\n')); - return r; - }, []).join('\n'); + const TOC = getTOC(); return dedent` {{toc,wide # Contents - ${markdown} + ${TOC} }} \n`; -}; +}; \ No newline at end of file diff --git a/themes/V3/5ePHB/style.less b/themes/V3/5ePHB/style.less index ddffbec2f..1216d0370 100644 --- a/themes/V3/5ePHB/style.less +++ b/themes/V3/5ePHB/style.less @@ -11,6 +11,7 @@ --HB_Color_CaptionText : #766649; // Brown --HB_Color_WatercolorStain : #BBAD82; // Light brown --HB_Color_Footnotes : #C9AD6A; // Gold + --TOC : 'include'; } .useSansSerif() { @@ -797,7 +798,7 @@ // *****************************/ // Default Exclusions -// Anything not exlcuded is included, default Headers are H1, H2, and H3. +// Anything not excluded is included, default Headers are H1, H2, and H3. h4, h5, h6, @@ -808,12 +809,26 @@ h6, .noToC, .toc { --TOC: exclude; } -.tocDepthH2 :is(h1, h2) {--TOC: include; } -.tocDepthH3 :is(h1, h2, h3) {--TOC: include; } -.tocDepthH4 :is(h1, h2, h3, h4) {--TOC: include; } -.tocDepthH5 :is(h1, h2, h3, h4, h5) {--TOC: include; } -.tocDepthH6 :is(h1, h2, h3, h4, h5, h6) {--TOC: include; } +// Brew level default inclusion changes. +// These add Headers 'back' to inclusion. + +//NOTE: DO NOT USE :HAS WITH .PAGES!!! EXTREMELY SLOW TO RENDER ON LARGE DOCS! +//WILL BE MOVED TO A STYLE TAB SNIPPET INSTEAD +// .pages:has(.tocGlobalH4) { +// h4 {--TOC: include; } +// } + +// .pages:has(.tocGlobalH5) { +// h4, h5 {--TOC: include; } +// } + +// .pages:has(.tocGlobalH6) { +// h4, h5, h6 {--TOC: include; } +// } + +// Block level inclusion changes +// These include either a single (include) or a range (depth) .tocIncludeH1 h1 {--TOC: include; } .tocIncludeH2 h2 {--TOC: include; } .tocIncludeH3 h3 {--TOC: include; } @@ -821,6 +836,21 @@ h6, .tocIncludeH5 h5 {--TOC: include; } .tocIncludeH6 h6 {--TOC: include; } +.tocDepthH2 :is(h1, h2) {--TOC: include; } +.tocDepthH3 :is(h1, h2, h3) {--TOC: include; } +.tocDepthH4 :is(h1, h2, h3, h4) {--TOC: include; } +.tocDepthH5 :is(h1, h2, h3, h4, h5) {--TOC: include; } +.tocDepthH6 :is(h1, h2, h3, h4, h5, h6) {--TOC: include; } + +// Block level exclusion changes +// These exclude a single block level +.tocExcludeH1 h1 {--TOC: exclude; } +.tocExcludeH2 h2 {--TOC: exclude; } +.tocExcludeH3 h3 {--TOC: exclude; } +.tocExcludeH4 h4 {--TOC: exclude; } +.tocExcludeH5 h5 {--TOC: exclude; } +.tocExcludeH6 h6 {--TOC: exclude; } + .page:has(.partCover) { --TOC: exclude; & h1 { diff --git a/themes/V3/Blank/snippets.js b/themes/V3/Blank/snippets.js index 8d45560c5..e92e757cf 100644 --- a/themes/V3/Blank/snippets.js +++ b/themes/V3/Blank/snippets.js @@ -23,14 +23,30 @@ module.exports = [ gen : '\n\\page\n' }, { - name : 'Page Number', - icon : 'fas fa-bookmark', - gen : '{{pageNumber 1}}\n' - }, - { - name : 'Auto-incrementing Page Number', - icon : 'fas fa-sort-numeric-down', - gen : '{{pageNumber,auto}}\n' + name : 'Page Numbering', + icon : 'fas fa-bookmark', + subsnippets : [ + { + name : 'Page Number', + icon : 'fas fa-bookmark', + gen : '{{pageNumber 1}}\n' + }, + { + name : 'Auto-incrementing Page Number', + icon : 'fas fa-sort-numeric-down', + gen : '{{pageNumber,auto}}\n' + }, + { + name : 'Skip Page Number Increment this Page', + icon : 'fas fa-xmark', + gen : '{{skipCounting}}\n' + }, + { + name : 'Restart Numbering', + icon : 'fas fa-arrow-rotate-left', + gen : '{{resetCounting}}\n' + }, + ] }, { name : 'Footer', @@ -153,6 +169,18 @@ module.exports = [ gen : dedent` ![cat warrior](https://s-media-cache-ak0.pinimg.com/736x/4a/81/79/4a8179462cfdf39054a418efd4cb743e.jpg) {width:325px,mix-blend-mode:multiply}` }, + { + name : 'Image Wrap Left', + icon : 'fac image-wrap-left', + gen : dedent` + ![homebrewery_mug](http://i.imgur.com/hMna6G0.png) {width:280px,margin-right:-3cm,wrapLeft}` + }, + { + name : 'Image Wrap Right', + icon : 'fac image-wrap-right', + gen : dedent` + ![homebrewery_mug](http://i.imgur.com/hMna6G0.png) {width:280px,margin-left:-3cm,wrapRight}` + }, { name : 'Background Image', icon : 'fas fa-tree', @@ -340,6 +368,11 @@ module.exports = [ icon : 'font MrEavesRemake', gen : dedent`{{font-family:MrEavesRemake Dummy Text}}` }, + { + name : 'Pagella', + icon : 'font Pagella', + gen : dedent`{{font-family:Pagella Dummy Text}}` + }, { name : 'Solbera Imitation', icon : 'font SolberaImitationRemake', @@ -398,22 +431,40 @@ module.exports = [ ] }, - /**************** PAGE *************/ + /**************** LAYOUT *************/ { groupName : 'Print', icon : 'fas fa-print', view : 'style', snippets : [ + { + name : 'A3 Page Size', + icon : 'far fa-file', + gen : dedent`/* A3 Page Size */ + .page { + width : 297mm; + height : 420mm; + }\n\n`, + }, { name : 'A4 Page Size', icon : 'far fa-file', gen : dedent`/* A4 Page Size */ - .page{ + .page { width : 210mm; height : 296.8mm; }\n\n` }, + { + name : 'A5 Page Size', + icon : 'far fa-file', + gen : dedent`/* A5 Page Size */ + .page { + width : 148mm; + height : 210mm; + }\n\n`, + }, { name : 'Square Page Size', icon : 'far fa-file', @@ -425,6 +476,17 @@ module.exports = [ columns : unset; }\n\n` }, + { + name : 'Card Page Size', + icon : 'far fa-file', + gen : dedent`/* Card Size */ + .page { + width : 63.5mm; + height : 88.9mm; + padding : 5mm; + columns : unset; + }\n\n` + }, { name : 'Ink Friendly', icon : 'fas fa-tint', @@ -440,5 +502,5 @@ module.exports = [ }\n\n` }, ] - } + }, ]; diff --git a/themes/V3/Blank/style.less b/themes/V3/Blank/style.less index 18a478cf9..8229baa28 100644 --- a/themes/V3/Blank/style.less +++ b/themes/V3/Blank/style.less @@ -1,3 +1,4 @@ +@import (less) './themes/fonts/Blank/fonts.less'; @import (less) './themes/fonts/5e/fonts.less'; @import (less) './themes/assets/assets.less'; @import (less) './themes/fonts/iconFonts/elderberryInn.less'; @@ -12,7 +13,7 @@ } @page { margin : 0; } -body { counter-reset : page-numbers; } +body { counter-reset : page-numbers 0; } * { -webkit-print-color-adjust : exact; } //***************************** @@ -51,7 +52,6 @@ body { counter-reset : page-numbers; } height : 279.4mm; padding : 1.4cm 1.9cm 1.7cm; overflow : hidden; - counter-increment : page-numbers; background-color : var(--HB_Color_Background); text-rendering : optimizeLegibility; contain : size; @@ -156,6 +156,19 @@ body { counter-reset : page-numbers; } break-inside : avoid; } + /* Wrap Text */ + .wrapLeft { + shape-outside : var(--HB_src); + float : right; + shape-margin : 0.2cm; + } + + .wrapRight { + shape-outside : var(--HB_src); + float : left; + shape-margin : 0.2cm; + } + /* Watermark */ .watermark { position : absolute; @@ -481,4 +494,13 @@ body { counter-reset : page-numbers; } &:nth-child(even) { .pageNumber { left : 30px; } } -} + + .resetCounting { + counter-set : page-numbers 1; + } + + &:not(:has(.skipCounting)) { + counter-increment : page-numbers; + } + +} \ No newline at end of file diff --git a/themes/fonts/Blank/fonts.less b/themes/fonts/Blank/fonts.less new file mode 100644 index 000000000..4a3d2d1e8 --- /dev/null +++ b/themes/fonts/Blank/fonts.less @@ -0,0 +1,46 @@ +/* +TeX Gyre Pagella +License: + % Copyright 2007--2018 for TeX Gyre extensions by B. Jackowski, + % J.M. Nowacki et al. (on behalf of TeX Users Groups). Vietnamese + % characters were added by Han The Thanh. + % + % This work can be freely used and distributed under + % the GUST Font License (GFL -- see GUST-FONT-LICENSE.txt) + % which is actually an instance of the LaTeX Project Public License + % (LPPL -- see http://www.latex-project.org/lppl.txt ). + % + % This work has the maintenance status "maintained". The Current Maintainer + % of this work is Bogus\l{}aw Jackowski and Janusz M. Nowacki. + % + % This work consists of the files listed + % in the MANIFEST-TeX-Gyre-Pagella.txt file. +*/ + +@font-face { + font-family: Pagella; + src: url('../../../fonts/Blank/texgyrepagella-regular.woff2'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: Pagella; + src: url('../../../fonts/Blank/texgyrepagella-bold.woff2'); + font-weight: bold; + font-style: normal; +} + +@font-face { + font-family: Pagella; + src: url('../../../fonts/Blank/texgyrepagella-italic.woff2'); + font-weight: normal; + font-style: italic; +} + +@font-face { + font-family: Pagella; + src: url('../../../fonts/Blank/texgyrepagella-bolditalic.woff2'); + font-weight: bold; + font-style: italic; +} diff --git a/themes/fonts/Blank/texgyrepagella-bold.woff2 b/themes/fonts/Blank/texgyrepagella-bold.woff2 new file mode 100644 index 000000000..5256e2d9e Binary files /dev/null and b/themes/fonts/Blank/texgyrepagella-bold.woff2 differ diff --git a/themes/fonts/Blank/texgyrepagella-bolditalic.woff2 b/themes/fonts/Blank/texgyrepagella-bolditalic.woff2 new file mode 100644 index 000000000..7384fcb1a Binary files /dev/null and b/themes/fonts/Blank/texgyrepagella-bolditalic.woff2 differ diff --git a/themes/fonts/Blank/texgyrepagella-italic.woff2 b/themes/fonts/Blank/texgyrepagella-italic.woff2 new file mode 100644 index 000000000..a6d15f26e Binary files /dev/null and b/themes/fonts/Blank/texgyrepagella-italic.woff2 differ diff --git a/themes/fonts/Blank/texgyrepagella-regular.woff2 b/themes/fonts/Blank/texgyrepagella-regular.woff2 new file mode 100644 index 000000000..c37301bca Binary files /dev/null and b/themes/fonts/Blank/texgyrepagella-regular.woff2 differ