diff --git a/.circleci/config.yml b/.circleci/config.yml index 2025e8fe7..d02402927 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,7 +10,7 @@ orbs: jobs: build: docker: - - image: cimg/node:20.17.0 + - image: cimg/node:20.18.0 - image: mongo:4.4 working_directory: ~/homebrewery diff --git a/.stylelintrc.json b/.stylelintrc.json index 2c7a9afdf..b5f2e7712 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -1,48 +1,48 @@ { - "extends": [ - "stylelint-config-recess-order", - "stylelint-config-recommended"], - "plugins": [ - "@stylistic/stylelint-plugin", - "./stylelint_plugins/declaration-colon-align.js", - "./stylelint_plugins/declaration-colon-min-space-before", - "./stylelint_plugins/declaration-block-multi-line-min-declarations" - ], - "customSyntax": "postcss-less", - "rules": { - "no-descending-specificity" : null, - "at-rule-no-unknown" : null, - "function-no-unknown" : null, - "font-family-no-missing-generic-family-keyword" : null, - "font-weight-notation" : "named-where-possible", - "font-family-name-quotes" : "always-unless-keyword", - "@stylistic/indentation" : "tab", - "no-duplicate-selectors" : true, - "@stylistic/color-hex-case" : "upper", - "color-hex-length" : "long", - "@stylistic/selector-combinator-space-after" : "always", - "@stylistic/selector-combinator-space-before" : "always", - "@stylistic/selector-attribute-operator-space-before" : "never", - "@stylistic/selector-attribute-operator-space-after" : "never", - "@stylistic/selector-attribute-brackets-space-inside" : "never", - "selector-attribute-quotes" : "always", - "selector-pseudo-element-colon-notation" : "double", - "@stylistic/selector-pseudo-class-parentheses-space-inside" : "never", - "@stylistic/block-opening-brace-space-before" : "always", - "naturalcrit/declaration-colon-min-space-before" : 1, - "@stylistic/declaration-block-trailing-semicolon" : "always", - "@stylistic/declaration-colon-space-after" : "always", - "@stylistic/number-leading-zero" : "always", - "function-url-quotes" : ["always", { "except": ["empty"] }], - "function-url-scheme-disallowed-list" : ["data","http"], - "comment-whitespace-inside" : "always", - "@stylistic/string-quotes" : "single", - "@stylistic/media-feature-range-operator-space-before" : "always", - "@stylistic/media-feature-range-operator-space-after" : "always", - "@stylistic/media-feature-parentheses-space-inside" : "never", - "@stylistic/media-feature-colon-space-before" : "always", - "@stylistic/media-feature-colon-space-after" : "always", - "naturalcrit/declaration-colon-align" : true, - "naturalcrit/declaration-block-multi-line-min-declarations": 1 - } + "extends": [ + "stylelint-config-recess-order", + "stylelint-config-recommended"], + "plugins": [ + "@stylistic/stylelint-plugin", + "./stylelint_plugins/declaration-colon-align.js", + "./stylelint_plugins/declaration-colon-min-space-before", + "./stylelint_plugins/declaration-block-multi-line-min-declarations" + ], + "customSyntax": "postcss-less", + "rules": { + "no-descending-specificity" : null, + "at-rule-no-unknown" : null, + "function-no-unknown" : null, + "font-family-no-missing-generic-family-keyword" : null, + "font-weight-notation" : "named-where-possible", + "font-family-name-quotes" : "always-unless-keyword", + "@stylistic/indentation" : "tab", + "no-duplicate-selectors" : true, + "@stylistic/color-hex-case" : "upper", + "color-hex-length" : "long", + "@stylistic/selector-combinator-space-after" : "always", + "@stylistic/selector-combinator-space-before" : "always", + "@stylistic/selector-attribute-operator-space-before" : "never", + "@stylistic/selector-attribute-operator-space-after" : "never", + "@stylistic/selector-attribute-brackets-space-inside" : "never", + "selector-attribute-quotes" : "always", + "selector-pseudo-element-colon-notation" : "double", + "@stylistic/selector-pseudo-class-parentheses-space-inside" : "never", + "@stylistic/block-opening-brace-space-before" : "always", + "naturalcrit/declaration-colon-min-space-before" : 1, + "@stylistic/declaration-block-trailing-semicolon" : "always", + "@stylistic/declaration-colon-space-after" : "always", + "@stylistic/number-leading-zero" : "always", + "function-url-quotes" : ["always", { "except": ["empty"] }], + "function-url-scheme-disallowed-list" : ["data","http"], + "comment-whitespace-inside" : "always", + "@stylistic/string-quotes" : "single", + "@stylistic/media-feature-range-operator-space-before" : "always", + "@stylistic/media-feature-range-operator-space-after" : "always", + "@stylistic/media-feature-parentheses-space-inside" : "never", + "@stylistic/media-feature-colon-space-before" : "always", + "@stylistic/media-feature-colon-space-after" : "always", + "naturalcrit/declaration-colon-align" : true, + "naturalcrit/declaration-block-multi-line-min-declarations" : 1 + } } diff --git a/README.md b/README.md index df7f41503..5206f4cbf 100644 --- a/README.md +++ b/README.md @@ -144,3 +144,4 @@ your contribution to the project, please join our [gitter chat][gitter-url]. [github-mark-duplicate-url]: https://docs.github.com/en/free-pro-team@latest/github/managing-your-work-on-github/about-duplicate-issues-and-pull-requests [github-pr-docs-url]: https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request [gitter-url]: https://gitter.im/naturalcrit/Lobby + diff --git a/changelog.md b/changelog.md index 3736ba9b0..f560ce1e5 100644 --- a/changelog.md +++ b/changelog.md @@ -77,14 +77,164 @@ pre { } .varSyntaxTable th:first-of-type { - width:6cm; + width:6cm; +} + +.page .exampleTable td,th { + border:1px dashed #00000030; } ``` - ## changelog For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery). +### Tuesday 03/18/2025 - v3.18.1 + +{{taskList +##### G-Ambatte +* [x] Revert colon rendering from br elements to blank divs + +##### 5e-Cleric +* [x] Allow for local connections within a same network when running a local version +Fixes issue [#4094](https://github.com/naturalcrit/homebrewery/issues/4094) + +* [x] Add US Letter size page snippet +Fixes issue [#3893](https://github.com/naturalcrit/homebrewery/issues/3893) +}} + +### Monday 03/10/2025 - v3.18.0 + +{{taskList +##### dbolack +* [x] Add ability to paste in any Share ID/URL into a brew's {{openSans :fas_circle_info: **Properties** :fas_arrow_right: **THEMES**}} selection, as long as that brew has been tagged as `meta:theme`. You can now share your custom brew themes without needing to make a personal copy. +* [x] Begin migration of custom Markdown extensions into their own NPM packages, for easier adoption by other users or projects +* [x] Fix external HTML appearing in open codeblocks + +Fixes issue [#3206](https://github.com/naturalcrit/homebrewery/issues/3206) + +* [x] Fix tables not rendering when directly after text + + +##### G-Ambatte +* [x] Cleanup of "cover pages" in the {{openSans :fas_rectangle_list: **NAVIGATION**}} list +* [x] Fix autosave triggering when no changes are present + +Fixes issue [#4051](https://github.com/naturalcrit/homebrewery/issues/4051) + +* [x] Remove empty table rows resulting from rowspan + +Fixes issue [#1729](https://github.com/naturalcrit/homebrewery/issues/1729) + +##### 5e-Cleric +* [x] Style fixes for covers art and logos on A4 size pages +* [x] Fix crash when trying to open brews that don't exist +* [x] Tweaks and style update styling on {{openSans **VAULT** :fas_dungeon:}} page. + +Fixes issue [#4079](https://github.com/naturalcrit/homebrewery/issues/4079) + +##### Calculuschild +* [x] `꞉꞉꞉꞉` now produces `
` instead of a `
` +* [x] Fix typos in tables freezing the editor + +Fixes issue [#4059](https://github.com/naturalcrit/homebrewery/issues/4059) + + +##### MollyMaclachlan (New Contributor!) +* [x] Fixed typos in the Monster Stat Block snippet + +Fixes issue [#4073](https://github.com/naturalcrit/homebrewery/issues/4073) + + +##### All +* [x] Update dependencies and scripts +* [x] Refactor components and backend tools +}} + +\column + +### Thursday 01/30/2025 - v3.17.0 + +{{taskList +##### 5e-Cleric + +* [x] Update FAQ + +* [x] Fix styling for Vault buttons and checkboxes + +* [x] Improve navigation bar styling + +* [x] Add feature to change username at https://www.naturalcrit.com/account + +* [x] Fix Reddit link crash when title has non-latin chars + +##### dbolack + +* [x] Fix page shadows toolbar option + +Fixes issue [#3919](https://github.com/naturalcrit/homebrewery/issues/3919) + +* [x] Add `:>>>` syntax for horizontal :>>>>> spaces + +* [x] Update Docker install instructions + +Fixes issue [#1930](https://github.com/naturalcrit/homebrewery/issues/1930) + +* [x] Allow styling pages via `\page{myStyles}` (with calculuschild) + +Fixes issue [#3901](https://github.com/naturalcrit/homebrewery/issues/3901) + +* [x] Update Ubuntu install instructions + +Fixes issue [#1952](https://github.com/naturalcrit/homebrewery/issues/1952) + +* [x] Add `:-:` `:-` `-:` syntax for paragraph alignment, similar to table column alignment; for example: + +-: -: Right-aligned + +:-: :-: Centered + +* [x] Add `:-- 50% --:` syntax to allow setting table column widths by percentage; for example: +``` +| Narrow | Wide | +|:- 10% -:|:-90%--:| +| Cell | Cell | +``` + + +| Narrow | Wide | +|:- 10% -:|:-90%--:| +|Cell | Cell | +{exampleTable} + +##### G-Ambatte + +* [x] Fix crash when opening brew Properties tab + +Fixes issue [#3927](https://github.com/naturalcrit/homebrewery/issues/3927) + +* [x] Update error pages with steps to refresh credentials + +Fixes issue [#3955](https://github.com/naturalcrit/homebrewery/issues/3955) + +* [x] Add {{openSans :fas_rectangle_list: **NAVIGATION**}} menu to the viewer toolbar + +##### calculuschild + +* [x] Reduce display lag on large brews + +##### Gazook89 + +* [x] Smarter detection of current page number + +Fixes issue [#3824](https://github.com/naturalcrit/homebrewery/issues/3824) + +##### All +* [x] Update dependencies and scripts +* [x] Refactor components and fix various errors +}} + +\page + ### Wednesday 11/27/2024 - v3.16.1 {{taskList @@ -2053,4 +2203,4 @@ Massive changelog incoming: * Added `phb.standalone.css` plus a build system for creating it * Added page numbers and footer text -* Page accent now flips each page +* Page accent now flips each page \ No newline at end of file diff --git a/client/admin/admin.jsx b/client/admin/admin.jsx index f2f2667a4..29973d221 100644 --- a/client/admin/admin.jsx +++ b/client/admin/admin.jsx @@ -1,47 +1,50 @@ -require('./admin.less'); -const React = require('react'); -const createClass = require('create-react-class'); - +import './admin.less'; +import React, { useEffect, useState } from 'react'; const BrewUtils = require('./brewUtils/brewUtils.jsx'); const NotificationUtils = require('./notificationUtils/notificationUtils.jsx'); +import AuthorUtils from './authorUtils/authorUtils.jsx'; +import LockTools from './lockTools/lockTools.jsx'; -const tabGroups = ['brew', 'notifications']; +const tabGroups = ['brew', 'notifications', 'authors', 'locks']; -const Admin = createClass({ - getDefaultProps : function() { - return {}; - }, +const Admin = ()=>{ + const [currentTab, setCurrentTab] = useState(''); - getInitialState : function(){ - return ({ - currentTab : 'brew' - }); - }, + useEffect(()=>{ + setCurrentTab(localStorage.getItem('hbAdminTab') || 'brew'); + }, []); - handleClick : function(newTab){ - if(this.state.currentTab === newTab) return; - this.setState({ - currentTab : newTab - }); - }, + useEffect(()=>{ + localStorage.setItem('hbAdminTab', currentTab); + }, [currentTab]); - render : function(){ - return
+ return ( +
- homebrewery admin + The Homebrewery Admin Page + back to homepage
- {this.state.currentTab==='brew' && } - {this.state.currentTab==='notifications' && } + {currentTab === 'brew' && } + {currentTab === 'notifications' && } + {currentTab === 'authors' && } + {currentTab === 'locks' && }
-
; - } -}); +
+ ); +}; module.exports = Admin; diff --git a/client/admin/admin.less b/client/admin/admin.less index c6c9b4662..1955f8a93 100644 --- a/client/admin/admin.less +++ b/client/admin/admin.less @@ -22,7 +22,7 @@ body { } :where(.admin) { - + padding-bottom : 50px; header { padding : 20px 0px; margin-bottom : 30px; @@ -30,6 +30,7 @@ body { color : white; background-color : @red; i { margin-right : 30px; } + a { float : right; } } hr { margin : 30px 0px; } @@ -48,21 +49,23 @@ body { } dl { - @maxItemWidth : 132px; + display : grid; + grid-template-columns : 120px 1fr; + row-gap : 10px; + align-items : center; + justify-items : start; + padding-top : 0.5em; dt { - float : left; - width : @maxItemWidth; - clear : left; - text-align : right; + float : left; + clear : left; + height : fit-content; + font-weight : 900; + text-align : right; &::after { content : ' : '; } } - dd { - height : 1em; - padding : 0 0 0.5em 0; - margin-left : @maxItemWidth + 6px; - } + dd { height : fit-content; } } - + .tabs button { margin-right : 3px; margin-left : 3px; @@ -90,11 +93,45 @@ body { } } + table { + padding : 10px; + + tr { + border-bottom : 1px solid; + &:last-of-type { border : none; } + &:nth-child(even) { background : #DDDDDD; } + } + + thead { + background : rgb(193,236,230); + border-bottom : 2px solid; + } + + th, td { + padding : 5px 10px; + vertical-align : middle; + text-align : center; + border-right : 1px solid; + + &:last-child { border-right : none; } + } + + th { font-weight : 900; } + + td { + &:first-child { + font-weight : 900; + text-align : left; + } + } + } + .error { - background: rgb(178, 54, 54); - color:white; - font-weight: 900; - margin-block:10px; - padding:10px; + float : right; + padding : 10px; + margin-block : 10px; + font-weight : 900; + color : white; + background : rgb(178, 54, 54); } } diff --git a/client/admin/authorUtils/authorLookup/authorLookup.jsx b/client/admin/authorUtils/authorLookup/authorLookup.jsx new file mode 100644 index 000000000..abdece6f7 --- /dev/null +++ b/client/admin/authorUtils/authorLookup/authorLookup.jsx @@ -0,0 +1,87 @@ +import './authorLookup.less'; + +import React from 'react'; +import request from 'superagent'; + +const authorLookup = ()=>{ + const [author, setAuthor] = React.useState(''); + const [searching, setSearching] = React.useState(false); + const [results, setResults] = React.useState([]); + + const lookup = async ()=>{ + if(!author) return; + + setSearching(true); + setResults([]); + + const brews = await request.get(`/admin/user/list/${author}`); + setResults(brews.body); + setSearching(false); + }; + + const renderResults = ()=>{ + if(results.length == 0) return <> +

Results

+

None found.

+ ; + + return <> +

{`Results - ${results.length} brews` }

+ + + + + + + + + + + + {results + .sort((a, b)=>{ // Sort brews from most recently updated + if(a.updatedAt > b.updatedAt) return -1; + return 1; + }) + .map((brew, idx)=>{ + return + + + + + + ; + })} + +
TitleShareEditLast UpdateStorage
{brew.title}{brew.shareId}{brew.editId}{brew.updatedAt}{brew.googleId ? 'Google' : 'Homebrewery'}
+ ; + }; + + const handleKeyPress = (evt)=>{ + if(evt.key === 'Enter') return lookup(); + }; + + const handleChange = (evt)=>{ + setAuthor(evt.target.value); + }; + + return ( +
+
+

Author Lookup

+ +
+
+ {renderResults()} +
+
+ ); +}; + +module.exports = authorLookup; diff --git a/client/admin/authorUtils/authorLookup/authorLookup.less b/client/admin/authorUtils/authorLookup/authorLookup.less new file mode 100644 index 000000000..8c37e80d1 --- /dev/null +++ b/client/admin/authorUtils/authorLookup/authorLookup.less @@ -0,0 +1,29 @@ +.authorLookup { + position : relative; + display : flex; + flex-direction : column; + + .field { + display : flex; + gap : 5px; + align-items : center; + justify-items : stretch; + width : 100%; + margin-bottom : 20px; + + + input { + height : 33px; + padding : 0px 10px; + margin-bottom : unset; + font-family : monospace; + } + + button { + width : 50px; + + i { margin-right : 10px; } + } + } + +} \ No newline at end of file diff --git a/client/admin/authorUtils/authorUtils.jsx b/client/admin/authorUtils/authorUtils.jsx new file mode 100644 index 000000000..a96eea528 --- /dev/null +++ b/client/admin/authorUtils/authorUtils.jsx @@ -0,0 +1,13 @@ +import React from 'react'; + +import AuthorLookup from './authorLookup/authorLookup.jsx'; + +const authorUtils = ()=>{ + return ( +
+ +
+ ); +}; + +module.exports = authorUtils; \ No newline at end of file diff --git a/client/admin/brewUtils/brewCleanup/brewCleanup.jsx b/client/admin/brewUtils/brewCleanup/brewCleanup.jsx index a166ae112..d4b17c570 100644 --- a/client/admin/brewUtils/brewCleanup/brewCleanup.jsx +++ b/client/admin/brewUtils/brewCleanup/brewCleanup.jsx @@ -1,10 +1,8 @@ -require('./brewCleanup.less'); const React = require('react'); const createClass = require('create-react-class'); const request = require('superagent'); - const BrewCleanup = createClass({ displayName : 'BrewCleanup', getDefaultProps(){ @@ -39,9 +37,9 @@ const BrewCleanup = createClass({ if(!this.state.primed) return; if(!this.state.count){ - return
No Matching Brews found.
; + return
No Matching Brews found.
; } - return
+ return
; }, render(){ - return
+ return

Brew Cleanup

Removes very short brews to tidy up the database

@@ -65,7 +63,7 @@ const BrewCleanup = createClass({ {this.renderPrimed()} {this.state.error - &&
{this.state.error.toString()}
+ &&
{this.state.error.toString()}
}
; } diff --git a/client/admin/brewUtils/brewCleanup/brewCleanup.less b/client/admin/brewUtils/brewCleanup/brewCleanup.less deleted file mode 100644 index 16fc98957..000000000 --- a/client/admin/brewUtils/brewCleanup/brewCleanup.less +++ /dev/null @@ -1,9 +0,0 @@ -.BrewCleanup { - .removeBox { - margin-top : 20px; - button { - margin-right : 10px; - background-color : @red; - } - } -} \ No newline at end of file diff --git a/client/admin/brewUtils/brewCompress/brewCompress.jsx b/client/admin/brewUtils/brewCompress/brewCompress.jsx index 2c8e5b023..ccb59e027 100644 --- a/client/admin/brewUtils/brewCompress/brewCompress.jsx +++ b/client/admin/brewUtils/brewCompress/brewCompress.jsx @@ -1,10 +1,7 @@ -require('./brewCompress.less'); const React = require('react'); const createClass = require('create-react-class'); - const request = require('superagent'); - const BrewCompress = createClass({ displayName : 'BrewCompress', getDefaultProps(){ @@ -53,9 +50,9 @@ const BrewCompress = createClass({ if(!this.state.primed) return; if(!this.state.count){ - return
No Matching Brews found.
; + return
No Matching Brews found.
; } - return
+ return
; }, render(){ - return
+ return

Brew Compression

Compresses the text in brews to binary

diff --git a/client/admin/brewUtils/brewCompress/brewCompress.less b/client/admin/brewUtils/brewCompress/brewCompress.less deleted file mode 100644 index 8668e9280..000000000 --- a/client/admin/brewUtils/brewCompress/brewCompress.less +++ /dev/null @@ -1,9 +0,0 @@ -.BrewCompress { - .removeBox { - margin-top : 20px; - button { - margin-right : 10px; - background-color : @red; - } - } -} \ No newline at end of file diff --git a/client/admin/brewUtils/brewLookup/brewLookup.jsx b/client/admin/brewUtils/brewLookup/brewLookup.jsx index e5b585ced..fb780f29e 100644 --- a/client/admin/brewUtils/brewLookup/brewLookup.jsx +++ b/client/admin/brewUtils/brewLookup/brewLookup.jsx @@ -1,5 +1,3 @@ -require('./brewLookup.less'); - const React = require('react'); const createClass = require('create-react-class'); const cx = require('classnames'); @@ -55,7 +53,7 @@ const BrewLookup = createClass({ renderFoundBrew(){ const brew = this.state.foundBrew; - return
+ return
Title
{brew.title}
@@ -90,7 +88,7 @@ const BrewLookup = createClass({ }, render(){ - return
+ return

Brew Lookup

; } diff --git a/client/admin/brewUtils/brewLookup/brewLookup.less b/client/admin/brewUtils/brewLookup/brewLookup.less deleted file mode 100644 index da15e3a64..000000000 --- a/client/admin/brewUtils/brewLookup/brewLookup.less +++ /dev/null @@ -1,6 +0,0 @@ -.brewLookup { - .cleanButton { - display : inline-block; - width : 100%; - } -} \ No newline at end of file diff --git a/client/admin/brewUtils/brewUtils.jsx b/client/admin/brewUtils/brewUtils.jsx index de8c29895..bab2cb82f 100644 --- a/client/admin/brewUtils/brewUtils.jsx +++ b/client/admin/brewUtils/brewUtils.jsx @@ -1,6 +1,6 @@ const React = require('react'); const createClass = require('create-react-class'); - +require('./brewUtils.less'); const BrewCleanup = require('./brewCleanup/brewCleanup.jsx'); const BrewLookup = require('./brewLookup/brewLookup.jsx'); diff --git a/client/admin/brewUtils/brewUtils.less b/client/admin/brewUtils/brewUtils.less new file mode 100644 index 000000000..5bbbc3f69 --- /dev/null +++ b/client/admin/brewUtils/brewUtils.less @@ -0,0 +1,29 @@ +.brewUtil { + .result { + margin-top : 20px; + button { + margin-right : 10px; + background-color : @red; + } + } + .cleanButton { + display : inline-block; + width : 100%; + } +} + +.stats { + position : relative; + + .pending { + position : absolute; + top : 0.5em; + left : 100px; + width : 100%; + height : 100%; + } + + &:has(.pending) { opacity : 0.5; } + + dl { grid-template-columns : 200px 250px; } +} \ No newline at end of file diff --git a/client/admin/brewUtils/stats/stats.jsx b/client/admin/brewUtils/stats/stats.jsx index 85ce10610..7f96618f9 100644 --- a/client/admin/brewUtils/stats/stats.jsx +++ b/client/admin/brewUtils/stats/stats.jsx @@ -1,11 +1,8 @@ -require('./stats.less'); const React = require('react'); const createClass = require('create-react-class'); -const cx = require('classnames'); const request = require('superagent'); - const Stats = createClass({ displayName : 'Stats', getDefaultProps(){ @@ -14,7 +11,8 @@ const Stats = createClass({ getInitialState(){ return { stats : { - totalBrews : 0 + totalBrews : 0, + totalPublishedBrews : 0 }, fetching : false }; @@ -29,11 +27,13 @@ const Stats = createClass({ .finally(()=>this.setState({ fetching: false })); }, render(){ - return
+ return

Stats

Total Brew Count
{this.state.stats.totalBrews}
+
Total Brews Published
+
{this.state.stats.totalPublishedBrews}
{this.state.fetching diff --git a/client/admin/brewUtils/stats/stats.less b/client/admin/brewUtils/stats/stats.less deleted file mode 100644 index b5a4612e1..000000000 --- a/client/admin/brewUtils/stats/stats.less +++ /dev/null @@ -1,13 +0,0 @@ - -.Stats { - position : relative; - - .pending { - position : absolute; - top : 0px; - left : 0px; - width : 100%; - height : 100%; - background-color : rgba(238,238,238, 0.5); - } -} \ No newline at end of file diff --git a/client/admin/lockTools/lockTools.jsx b/client/admin/lockTools/lockTools.jsx new file mode 100644 index 000000000..9a28d330f --- /dev/null +++ b/client/admin/lockTools/lockTools.jsx @@ -0,0 +1,342 @@ +/*eslint max-lines: ["warn", {"max": 500, "skipBlankLines": true, "skipComments": true}]*/ +require('./lockTools.less'); +const React = require('react'); +const createClass = require('create-react-class'); + +import request from '../../homebrew/utils/request-middleware.js'; + +const LockTools = createClass({ + displayName : 'LockTools', + getInitialState : function() { + return { + fetching : false, + reviewCount : 0 + }; + }, + + componentDidMount : function() { + this.updateReviewCount(); + }, + + updateReviewCount : async function() { + const newCount = await request.get('/api/lock/count') + .then((res)=>{return res.body?.count || 'Unknown';}); + if(newCount != this.state.reviewCount){ + this.setState({ + reviewCount : newCount + }); + } + }, + + updateLockData : function(lock){ + this.setState({ + lock : lock + }); + }, + + render : function() { + return
+

Lock Count

+

Number of brews currently locked: {this.state.reviewCount}

+ +
+ +
+ +
+ +
+
+ + +
+
+
; + } +}); + +const LockBrew = createClass({ + displayName : 'LockBrew', + getInitialState : function() { + // Default values + return { + brewId : this.props.lock?.shareId || '', + code : this.props.lock?.code || 455, + editMessage : this.props.lock?.editMessage || '', + shareMessage : this.props.lock?.shareMessage || 'This Brew has been locked.', + result : {}, + overwrite : false, + }; + }, + + handleChange : function(e, varName) { + const output = {}; + output[varName] = e.target.value; + this.setState(output); + }, + + submit : function(e){ + e.preventDefault(); + if(!this.state.editMessage) return; + const newLock = { + overwrite : this.state.overwrite, + code : parseInt(this.state.code) || 100, + editMessage : this.state.editMessage, + shareMessage : this.state.shareMessage, + applied : new Date + }; + + request.post(`/api/lock/${this.state.brewId}`) + .send(newLock) + .set('Content-Type', 'application/json') + .then((response)=>{ + this.setState({ result: response.body }); + }) + .catch((err)=>{ + this.setState({ result: err.response.body }); + }); + }, + + renderInput : function (name) { + return this.handleChange(e, name)} autoComplete='off' required/>; + }, + + renderResult : function(){ + return <> +

Result:

+ + + {Object.keys(this.state.result).map((key, idx)=>{ + return + + + ; + })} + +
{key}{this.state.result[key].toString()} +
+ ; + }, + + render : function() { + return
+
+

Lock Brew

+
+ +
+ +
+ +
+ +
+ + +
+ {this.state.result && this.renderResult()} +
+
+

Suggestions

+
+

Codes

+
    +
  • 455 - Generic Lock
  • +
  • 456 - Copyright issues
  • +
  • 457 - Confidential Information Leakage
  • +
  • 458 - Sensitive Personal Information
  • +
  • 459 - Defamation or Libel
  • +
  • 460 - Hate Speech or Discrimination
  • +
  • 461 - Illegal Activities
  • +
  • 462 - Malware or Phishing
  • +
  • 463 - Plagiarism
  • +
  • 465 - Misrepresentation
  • +
  • 466 - Inappropriate Content
  • +
+
+
+

Messages

+
    +
  • Private Message: This is the private message that is ONLY displayed to the authors of the locked brew. This message MUST specify exactly what actions must be taken in order to have the brew unlocked.
  • +
  • Public Message: This is the public message that is displayed to the EVERYONE that attempts to view the locked brew.
  • +
+
+
+
; + } +}); + +const LockTable = createClass({ + displayName : 'LockTable', + getDefaultProps : function() { + return { + title : '', + text : '', + fetchURL : '/api/locks', + resultName : '', + propertyNames : ['shareId'], + loadBrew : ()=>{} + }; + }, + + getInitialState : function() { + return { + result : '', + error : '', + searching : false + }; + }, + + lockKey : React.createRef(0), + + clickFn : function (){ + this.setState({ searching: true, error: null }); + + request.get(this.props.fetchURL) + .then((res)=>this.setState({ result: res.body })) + .catch((err)=>this.setState({ result: err.response.body })) + .finally(()=>{ + this.setState({ searching: false }); + }); + }, + + updateBrewLockData : function (lockData){ + this.lockKey.current++; + const brewData = { + key : this.lockKey.current, + shareId : lockData.shareId, + code : lockData.lock.code, + editMessage : lockData.lock.editMessage, + shareMessage : lockData.lock.shareMessage + }; + this.props.loadBrew(brewData); + }, + + render : function () { + return <> +
+
+

{this.props.title}

+ +
+ {this.state.result[this.props.resultName] && + <> +

{this.props.text}: {this.state.result[this.props.resultName].length}

+ + + + {this.props.propertyNames.map((name, idx)=>{ + return ; + })} + + + + + + {this.state.result[this.props.resultName].map((result, resultIdx)=>{ + return + {this.props.propertyNames.map((name, nameIdx)=>{ + return ; + })} + + + ; + })} + +
{name}clipload
+ {result[name].toString()} + {navigator.clipboard.writeText(result.shareId.toString());}}>{this.updateBrewLockData(result);}}>
+ + } +
+ ; + } +}); + +const LockLookup = createClass({ + displayName : 'LockLookup', + getDefaultProps : function() { + return { + fetchURL : '/api/lookup' + }; + }, + + getInitialState : function() { + return { + query : '', + result : '', + error : '', + searching : false + }; + }, + + handleChange(e){ + this.setState({ query: e.target.value }); + }, + + clickFn(){ + this.setState({ searching: true, error: null }); + + request.put(`${this.props.fetchURL}/${this.state.query}`) + .then((res)=>this.setState({ result: res.body })) + .catch((err)=>this.setState({ result: err.response.body })) + .finally(()=>{ + this.setState({ searching: false }); + }); + }, + + renderResult : function(){ + return
+

Result:

+ + + {Object.keys(this.state.result).map((key, idx)=>{ + return + + + ; + })} + +
{key}{this.state.result[key].toString()} +
+
; + }, + + render : function() { + return
+

{this.props.title}

+ + + + {this.state.error + &&
{this.state.error.toString()}
+ } + + {this.state.result && this.renderResult()} +
; + } +}); + +module.exports = LockTools; \ No newline at end of file diff --git a/client/admin/lockTools/lockTools.less b/client/admin/lockTools/lockTools.less new file mode 100644 index 000000000..1ec9c524a --- /dev/null +++ b/client/admin/lockTools/lockTools.less @@ -0,0 +1,66 @@ +.lockTools { + .lockBrew { + columns : 2; + + .lockForm { + break-inside : avoid; + + label { + display : inline-block; + width : 100%; + line-height : 2.25em; + text-align : right; + input { + float : right; + width : 65%; + margin-left : 10px; + } + &.checkbox { + line-height: 1.5em; + input { + width : 1.5em; + height : 1.5em; + } + } + } + } + + .lockSuggestions { + line-height : 1.2em; + break-inside : avoid; + columns : 2; + h2 { column-span : all; } + h3 { margin-top : 0px; } + b { font-weight : 600; } + + .lockCodes { break-inside : avoid; } + } + } + + .lockTable { + cursor : default; + break-inside : avoid; + .row:hover { + color : #000000; + background-color : #CCCCCC; + } + .icon { + cursor : pointer; + &:hover { text-shadow : 0px 0px 6px black; } + } + } + + th, td { + padding : 4px 10px; + text-align : center; + } + table, td { border : 1px solid #333333; } + + .brewLookup { + min-height : 175px; + break-inside : avoid; + h2 { margin-top : 0px; } + } + + button i { padding-left : 5px; } +} \ No newline at end of file diff --git a/client/admin/notificationUtils/notificationAdd/notificationAdd.less b/client/admin/notificationUtils/notificationAdd/notificationAdd.less index 878da24c2..14bdabd03 100644 --- a/client/admin/notificationUtils/notificationAdd/notificationAdd.less +++ b/client/admin/notificationUtils/notificationAdd/notificationAdd.less @@ -6,31 +6,32 @@ .field { display : grid; - grid-template-columns : 120px 150px; + grid-template-columns : 120px 200px; align-items : center; justify-items : stretch; width : 100%; margin-bottom : 20px; - - + input { height : 33px; padding : 0px 10px; margin-bottom : unset; font-family : monospace; + + &[type='date'] { width : 14ch; } } textarea { width : 50ch; min-height : 7em; max-height : 20em; - resize : vertical; padding : 10px; + resize : vertical; } } button { - width: 200px; + width : 200px; i { margin-right : 10px; } } diff --git a/client/admin/notificationUtils/notificationLookup/notificationLookup.less b/client/admin/notificationUtils/notificationLookup/notificationLookup.less index 3f9b78310..65903213c 100644 --- a/client/admin/notificationUtils/notificationLookup/notificationLookup.less +++ b/client/admin/notificationUtils/notificationLookup/notificationLookup.less @@ -1,8 +1,8 @@ - .notificationLookup { width : 450px; - height : fit-content; + height : fit-content; + .noNotification { margin-block : 20px; } .notificationList { display : flex; flex-direction : column; @@ -30,11 +30,6 @@ font-size : 20px; font-weight : 900; } - - dl dt{ - font-weight: 900; - } } } - .noNotification { margin-block : 20px; } } \ No newline at end of file diff --git a/client/components/Anchored.less b/client/components/Anchored.less index 4f0e2fa8f..aeb9f1d5f 100644 --- a/client/components/Anchored.less +++ b/client/components/Anchored.less @@ -1,13 +1,11 @@ .anchored-box { - position:absolute; - @supports (inset-block-start: anchor(bottom)){ - inset-block-start: anchor(bottom); - } - justify-self: anchor-center; - visibility: hidden; - &.active { - visibility: visible; + position : absolute; + visibility : hidden; + justify-self : anchor-center; + @supports (inset-block-start: anchor(bottom)) { + inset-block-start : anchor(bottom); } + &.active { visibility : visible; } } \ No newline at end of file diff --git a/client/components/combobox.jsx b/client/components/combobox.jsx index 5fcc154bc..ae9f1d7f8 100644 --- a/client/components/combobox.jsx +++ b/client/components/combobox.jsx @@ -45,6 +45,7 @@ const Combobox = createClass({ }, handleDropdown : function(show){ this.setState({ + value : show ? '' : this.props.default, showDropdown : show, inputFocused : this.props.autoSuggest.clearAutoSuggestOnClick ? show : false }); @@ -58,10 +59,10 @@ const Combobox = createClass({ this.props.onEntry(e); }); }, - handleSelect : function(e){ + handleSelect : function(value, data=value){ this.setState({ - value : e.currentTarget.getAttribute('data-value') - }, ()=>{this.props.onSelect(this.state.value);}); + value : value + }, ()=>{this.props.onSelect(data);}); ; }, renderTextInput : function(){ @@ -78,10 +79,11 @@ const Combobox = createClass({ if(!e.target.checkValidity()){ this.setState({ value : this.props.default - }, ()=>this.props.onEntry(e)); + }); } }} /> +
); }, @@ -92,11 +94,10 @@ const Combobox = createClass({ const filterOn = _.isString(this.props.autoSuggest.filterOn) ? [this.props.autoSuggest.filterOn] : this.props.autoSuggest.filterOn; const filteredArrays = filterOn.map((attr)=>{ const children = dropdownChildren.filter((item)=>{ - if(suggestMethod === 'includes'){ + if(suggestMethod === 'includes') return item.props[attr]?.toLowerCase().includes(this.state.value.toLowerCase()); - } else if(suggestMethod === 'startsWith'){ + if(suggestMethod === 'startsWith') return item.props[attr]?.toLowerCase().startsWith(this.state.value.toLowerCase()); - } }); return children; }); @@ -111,7 +112,7 @@ const Combobox = createClass({ }, render : function () { const dropdownChildren = this.state.options.map((child, i)=>{ - const clone = React.cloneElement(child, { onClick: (e)=>this.handleSelect(e) }); + const clone = React.cloneElement(child, { onClick: ()=>this.handleSelect(child.props.value, child.props.data) }); return clone; }); return ( diff --git a/client/components/combobox.less b/client/components/combobox.less index 3810a874e..27f78356b 100644 --- a/client/components/combobox.less +++ b/client/components/combobox.less @@ -1,50 +1,46 @@ .dropdown-container { - position:relative; - input { - width: 100%; - } - .dropdown-options { - position:absolute; - background-color: white; - z-index: 100; - width: 100%; - border: 1px solid gray; - overflow-y: auto; - max-height: 200px; + position : relative; + input { width : 100%; } + .item i { + position : absolute; + right : 10px; + color : black; + } + .dropdown-options { + position : absolute; + z-index : 100; + width : 100%; + max-height : 200px; + overflow-y : auto; + background-color : white; + border : 1px solid gray; - &::-webkit-scrollbar { - width: 14px; - } - &::-webkit-scrollbar-track { - background: #ffffff; - } - &::-webkit-scrollbar-thumb { - background-color: #949494; - border-radius: 10px; - border: 3px solid #ffffff; - } - - .item { - position:relative; - font-size: 11px; - font-family: Open Sans; - padding: 5px; - cursor: default; - margin: 0 3px; - //border-bottom: 1px solid darkgray; - &:hover { - filter: brightness(120%); - background-color: rgb(163, 163, 163); - } - .detail { - width:100%; - text-align: left; - color: rgb(124, 124, 124); - font-style:italic; - font-size: 9px; - } - } - - } + &::-webkit-scrollbar { width : 14px; } + &::-webkit-scrollbar-track { background : #FFFFFF; } + &::-webkit-scrollbar-thumb { + background-color : #949494; + border : 3px solid #FFFFFF; + border-radius : 10px; + } + .item { + position : relative; + padding : 5px; + margin : 0 3px; + font-family : 'Open Sans'; + font-size : 11px; + cursor : default; + &:hover { + background-color : rgb(163, 163, 163); + filter : brightness(120%); + } + .detail { + width : 100%; + font-size : 9px; + font-style : italic; + color : rgb(124, 124, 124); + text-align : left; + } + } + } } diff --git a/client/homebrew/brewRenderer/brewRenderer.jsx b/client/homebrew/brewRenderer/brewRenderer.jsx index 17f261c2d..c83a2029b 100644 --- a/client/homebrew/brewRenderer/brewRenderer.jsx +++ b/client/homebrew/brewRenderer/brewRenderer.jsx @@ -17,10 +17,9 @@ const dedent = require('dedent-tabs').default; const { printCurrentBrew } = require('../../../shared/helpers.js'); import HeaderNav from './headerNav/headerNav.jsx'; - import { safeHTML } from './safeHTML.js'; - +const PAGEBREAK_REGEX_V3 = /^(?=\\page(?: *{[^\n{}]*})?$)/m; const PAGE_HEIGHT = 1056; const INITIAL_CONTENT = dedent` @@ -40,7 +39,7 @@ const BrewPage = (props)=>{ ...props }; const pageRef = useRef(null); - const cleanText = safeHTML(props.contents); + const cleanText = safeHTML(`${props.contents}\n
\n`); useEffect(()=>{ if(!pageRef.current) return; @@ -78,7 +77,7 @@ const BrewPage = (props)=>{ }; }, []); - return
+ return
; }; @@ -126,7 +125,7 @@ const BrewRenderer = (props)=>{ if(props.renderer == 'legacy') { rawPages = props.text.split('\\page'); } else { - rawPages = props.text.split(/^\\page$/gm); + rawPages = props.text.split(PAGEBREAK_REGEX_V3); } const handlePageVisibilityChange = (pageNum, isVisible, isCenter)=>{ @@ -173,20 +172,33 @@ const BrewRenderer = (props)=>{ const renderPage = (pageText, index)=>{ - const styles = { + let styles = { ...(!displayOptions.pageShadows ? { boxShadow: 'none' } : {}) // Add more conditions as needed }; + let classes = 'page'; + let attributes = {}; if(props.renderer == 'legacy') { const html = MarkdownLegacy.render(pageText); return ; } else { - pageText += `\n\n \n\\column\n `; //Artificial column break at page end to emulate column-fill:auto (until `wide` is used, when column-fill:balance will reappear) + if(pageText.startsWith('\\page')) { + const firstLineTokens = Markdown.marked.lexer(pageText.split('\n', 1)[0])[0].tokens; + const injectedTags = firstLineTokens?.find((obj)=>obj.injectedTags !== undefined)?.injectedTags; + if(injectedTags) { + styles = { ...styles, ...injectedTags.styles }; + styles = _.mapKeys(styles, (v, k)=>k.startsWith('--') ? k : _.camelCase(k)); // Convert CSS to camelCase for React + classes = [classes, injectedTags.classes].join(' ').trim(); + attributes = injectedTags.attributes; + } + pageText = pageText.includes('\n') ? pageText.substring(pageText.indexOf('\n') + 1) : ''; // Remove the \page line + } + const html = Markdown.render(pageText, index); - return ; + return ; } }; diff --git a/client/homebrew/brewRenderer/brewRenderer.less b/client/homebrew/brewRenderer/brewRenderer.less index 68c688fb6..128c419be 100644 --- a/client/homebrew/brewRenderer/brewRenderer.less +++ b/client/homebrew/brewRenderer/brewRenderer.less @@ -1,43 +1,39 @@ @import (multiple, less) 'shared/naturalcrit/styles/reset.less'; .brewRenderer { + height : 100vh; + padding-top : 60px; overflow-y : scroll; will-change : transform; - padding-top : 60px; - height : 100vh; - &:has(.facing, .flow) { - padding : 60px 30px; - } - &.deployment { - background-color: darkred; - } + &:has(.facing, .flow) { padding : 60px 30px; } + &.deployment { background-color : darkred; } :where(.pages) { &.facing { - display: grid; - grid-template-columns: repeat(2, auto); - grid-template-rows: repeat(3, auto); - gap: 10px 10px; - justify-content: safe center; + display : grid; + grid-template-rows : repeat(3, auto); + grid-template-columns : repeat(2, auto); + gap : 10px 10px; + justify-content : safe center; &.recto .page:first-child { // sets first page on 'right' ('recto') of the preview, as if for a Cover page. // todo: add a checkbox to toggle this setting - grid-column-start: 2; + grid-column-start : 2; } & :where(.page) { - margin-left: unset !important; - margin-right: unset !important; + margin-right : unset !important; + margin-left : unset !important; } } &.flow { - display: flex; - flex-wrap: wrap; - gap: 10px; - justify-content: safe center; + display : flex; + flex-wrap : wrap; + gap : 10px; + justify-content : safe center; & :where(.page) { - flex: 0 0 auto; - margin-left: unset !important; - margin-right: unset !important; + flex : 0 0 auto; + margin-right : unset !important; + margin-left : unset !important; } } @@ -50,9 +46,7 @@ margin-left : auto; box-shadow : 1px 4px 14px #000000; } - *[id] { - scroll-margin-top:100px; - } + *[id] { scroll-margin-top : 100px; } } &::-webkit-scrollbar { width : 20px; @@ -79,11 +73,9 @@ overflow-y : unset; .pages { margin : 0px; - zoom: 100% !important; + zoom : 100% !important; & > .page { box-shadow : unset; } } } - .headerNav { - visibility: hidden; - } + .headerNav { visibility : hidden; } } \ No newline at end of file diff --git a/client/homebrew/brewRenderer/headerNav/headerNav.jsx b/client/homebrew/brewRenderer/headerNav/headerNav.jsx index 68963129f..04ced2585 100644 --- a/client/homebrew/brewRenderer/headerNav/headerNav.jsx +++ b/client/homebrew/brewRenderer/headerNav/headerNav.jsx @@ -3,7 +3,6 @@ require('./headerNav.less'); import * as React from 'react'; import * as _ from 'lodash'; - const MAX_TEXT_LENGTH = 40; const HeaderNav = React.forwardRef(({}, pagesRef)=>{ @@ -11,11 +10,30 @@ const HeaderNav = React.forwardRef(({}, pagesRef)=>{ const renderHeaderLinks = ()=>{ if(!pagesRef.current) return; + // Top Level Pages + // Pages that contain an element with a specified class (e.g. cover pages, table of contents) + // will NOT have its content scanned for navigation headers, instead displaying a custom label + // --- + // The property name is class that will be used for detecting the page is a top level page + // The property value is a function that returns the text to be used + + const topLevelPages = { + '.frontCover' : (el, pageType)=>{ const text = getHeaderContent(el); return text ? `Cover: ${text}` : 'Cover Page'; }, + '.insideCover' : (el, pageType)=>{ const text = getHeaderContent(el); return text ? `Interior: ${text}` : 'Interior Cover Page'; }, + '.partCover' : (el, pageType)=>{ const text = getHeaderContent(el); return text ? `Section: ${text}` : 'Section Cover Page'; }, + '.backCover' : (el, pageType)=>{ const text = getHeaderContent(el); return text ? `Back: ${text}` : 'Rear Cover Page'; }, + '.toc' : ()=>{ return 'Table of Contents'; }, + }; + + const getHeaderContent = (el)=>el.querySelector('h1')?.textContent; + + const topLevelPageSelector = Object.keys(topLevelPages).join(','); + const selector = [ - '.pages > .page', // All page elements, which by definition have IDs - '.page:not(:has(.toc)) > [id]', // All direct children of non-ToC .page with an ID (Legacy) - '.page:not(:has(.toc)) > .columnWrapper > [id]', // All direct children of non-ToC .page > .columnWrapper with an ID (V3) - '.page:not(:has(.toc)) h2', // All non-ToC H2 titles, like Monster frame titles + '.pages > .page', // All page elements, which by definition have IDs + `.page:not(:has(${topLevelPageSelector})) > [id]`, // All direct children of non-excluded .pages with an ID (Legacy) + `.page:not(:has(${topLevelPageSelector})) > .columnWrapper > [id]`, // All direct children of non-excluded .page > .columnWrapper with an ID (V3) + `.page:not(:has(${topLevelPageSelector})) h2`, // All non-excluded H2 titles, like Monster frame titles ]; const elements = pagesRef.current.querySelectorAll(selector.join(',')); if(!elements) return; @@ -23,45 +41,35 @@ const HeaderNav = React.forwardRef(({}, pagesRef)=>{ // navList is a list of objects which have the following structure: // { - // depth : how deeply indented the item should be - // text : the text to display in the nav link - // link : the hyperlink to navigate to when clicked - // className : [optional] the class to apply to the nav link for styling + // depth : how deeply indented the item should be + // text : the text to display in the nav link + // link : the hyperlink to navigate to when clicked + // className : [optional] the class to apply to the nav link for styling // } elements.forEach((el)=>{ - if(el.className.match(/\bpage\b/)) { - let text = `Page ${el.id.slice(1)}`; // The ID of a page *should* always be equal to `p` followed by the page number - if(el.querySelector('.toc')){ // If the page contains a table of contents, add "- Contents" to the display text - text += ' - Contents'; - }; - navList.push({ - depth : 0, // Pages are always at the least indented level - text : text, - link : el.id, - className : 'pageLink' - }); - return; - } - if(el.localName.match(/^h[1-6]/)){ // Header elements H1 through H6 - navList.push({ - depth : el.localName[1], // Depth is set by the header level - text : el.textContent, // Use `textContent` because `innerText` is affected by rendering, e.g. 'content-visibility: auto' - link : el.id - }); - return; - } - navList.push({ - depth : 7, // All unmatched elements with IDs are set to the maximum depth (7) - text : el.textContent, // Use `textContent` because `innerText` is affected by rendering, e.g. 'content-visibility: auto' + const navEntry = { // Default structure of a navList entry + depth : 7, // All unmatched elements with IDs are set to the maximum depth (7) + text : el.textContent, // Use `textContent` because `innerText` is affected by rendering, e.g. 'content-visibility: auto' link : el.id - }); - }); - - return _.map(navList, (navItem, index)=>{ - return ; + }; + if(el.classList.contains('page')) { + let text = `Page ${el.id.slice(1)}`; // Get the page # by trimming off the 'p' from the ID + const pageType = Object.keys(topLevelPages).find((pageType)=>el.querySelector(pageType)); + if(pageType) + text += ` - ${topLevelPages[pageType](el, pageType)}`; // If a Top Level Page, add extra label + + navEntry.depth = 0; // Pages are always at the least indented level + navEntry.text = text; + navEntry.className = 'pageLink'; + } else if(el.localName.match(/^h[1-6]/)){ // Header elements H1 through H6 + navEntry.depth = el.localName[1]; // Depth is set by the header level + } + navList.push(navEntry); }); + return _.map(navList, (navItem, index)=> + ); }; return ; -} -); +}); const HeaderNavItem = ({ link, text, depth, className })=>{ diff --git a/client/homebrew/brewRenderer/headerNav/headerNav.less b/client/homebrew/brewRenderer/headerNav/headerNav.less index 8b35041d9..a5fd11f5e 100644 --- a/client/homebrew/brewRenderer/headerNav/headerNav.less +++ b/client/homebrew/brewRenderer/headerNav/headerNav.less @@ -1,45 +1,37 @@ .headerNav { - position: fixed; - top: 32px; - left: 0px; - padding: 5px 10px; - background-color: #ccc; - border-radius: 5px; - max-height: calc(100vh - 32px); - max-width: 40vw; - overflow-y: auto; - &.active { - padding-bottom: 10px; - .navIcon { - padding-bottom: 10px; - } - } - .navIcon { - cursor: pointer; + position : fixed; + top : 32px; + left : 0px; + max-width : 40vw; + max-height : calc(100vh - 32px); + padding : 5px 10px; + overflow-y : auto; + background-color : #CCCCCC; + border-radius : 5px; + &.active { + padding-bottom : 10px; + .navIcon { padding-bottom : 10px; } } + .navIcon { cursor : pointer; } li { - list-style-type: none; + list-style-type : none; a { - display: inline-block; - width: 100%; - font-family: 'Open Sans'; - font-size: 12px; - padding: 2px; - color: inherit; - text-decoration: none; - cursor: pointer; - &:hover { - text-decoration: underline; - } - &.pageLink { - font-weight: 900; - } + display : inline-block; + width : 100%; + padding : 2px; + font-family : 'Open Sans'; + font-size : 12px; + color : inherit; + text-decoration : none; + cursor : pointer; + &:hover { text-decoration : underline; } + &.pageLink { font-weight : 900; } - @depths: 1,2,3,4,5,6,7; + @depths: 0,1,2,3,4,5,6,7; each(@depths, { &.depth-@{value} { - padding-left: ((@value - 1) * 0.5em); + padding-left: ((@value) * 0.5em); } }); } diff --git a/client/homebrew/brewRenderer/notificationPopup/notificationPopup.jsx b/client/homebrew/brewRenderer/notificationPopup/notificationPopup.jsx index b2045f13d..38a85e0c7 100644 --- a/client/homebrew/brewRenderer/notificationPopup/notificationPopup.jsx +++ b/client/homebrew/brewRenderer/notificationPopup/notificationPopup.jsx @@ -1,6 +1,7 @@ require('./notificationPopup.less'); import React, { useEffect, useState } from 'react'; import request from '../../utils/request-middleware.js'; +import Markdown from 'naturalcrit/markdown.js'; import Dialog from '../../../components/dialog.jsx'; @@ -40,11 +41,10 @@ const NotificationPopup = ()=>{ const renderNotificationsList = ()=>{ if(error) return
{error}
; - return notifications.map((notification)=>(
  • {notification.title}
    -

    +

  • )); }; diff --git a/client/homebrew/brewRenderer/notificationPopup/notificationPopup.less b/client/homebrew/brewRenderer/notificationPopup/notificationPopup.less index 79edf37b2..85d4c8365 100644 --- a/client/homebrew/brewRenderer/notificationPopup/notificationPopup.less +++ b/client/homebrew/brewRenderer/notificationPopup/notificationPopup.less @@ -48,17 +48,46 @@ } ul { margin-top : 15px; - font-size : 0.8em; + font-size : 0.9em; list-style-position : outside; list-style-type : disc; li { - margin-top : 1.4em; - font-size : 0.8em; - line-height : 1.4em; - em { - text-transform:capitalize; - font-weight : 800; + padding-left : 1em; + margin-top : 1.5em; + font-size : 0.9em; + line-height : 1.5em; + em { + font-weight : 800; + text-transform : capitalize; + } + li { + margin-top : 0; + line-height : 1.2em; + list-style-type : square; } } + ul ul,ol ol,ul ol,ol ul { + margin-bottom : 0px; + margin-left : 1.5em; + } } -} + + /* Markdown styling */ + code { + padding : 0.1em 0.5em; + font-family : 'Courier New', 'Courier', monospace; + overflow-wrap : break-word; + white-space : pre-wrap; + background : #08115A; + border-radius : 2px; + } + pre code { + display : inline-block; + width : 100%; + } + .blank { + height : 1em; + margin-top : 0; + & + * { margin-top : 0; } + } +} \ No newline at end of file diff --git a/client/homebrew/brewRenderer/toolBar/toolBar.less b/client/homebrew/brewRenderer/toolBar/toolBar.less index a24731489..8f1464c8f 100644 --- a/client/homebrew/brewRenderer/toolBar/toolBar.less +++ b/client/homebrew/brewRenderer/toolBar/toolBar.less @@ -156,7 +156,7 @@ min-width : 46px; height : 100%; &:hover { background-color : #444444; } - &:focus { border : 1px solid #D3D3D3;outline : none;} + &:focus {outline : none; border : 1px solid #D3D3D3;} &:disabled { color : #777777; background-color : unset !important; @@ -182,8 +182,8 @@ position : absolute; left : 0; z-index : 5; + display : flex; width : 32px; min-width : unset; height : 100%; - display : flex; } \ No newline at end of file diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index bba5f3ad9..6859c5aa2 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -12,7 +12,9 @@ const MetadataEditor = require('./metadataEditor/metadataEditor.jsx'); const EDITOR_THEME_KEY = 'HOMEBREWERY-EDITOR-THEME'; -const SNIPPETBAR_HEIGHT = 25; +const PAGEBREAK_REGEX_V3 = /^(?=\\page(?: *{[^\n{}]*})?$)/m; +const SNIPPETBREAK_REGEX_V3 = /^\\snippet\ .*$/; +const SNIPPETBAR_HEIGHT = 25; const DEFAULT_STYLE_TEXT = dedent` /*=======--- Example CSS styling ---=======*/ /* Any CSS here will apply to your document! */ @@ -21,6 +23,13 @@ const DEFAULT_STYLE_TEXT = dedent` color: black; }`; +const DEFAULT_SNIPPET_TEXT = dedent` + \snippet example snippet + + The text between \`\snippet title\` lines will become a snippet of name \`title\` as this example provides. + + This snippet is accessible in the brew tab, and will be inherited if the brew is used as a theme. +`; let isJumping = false; const Editor = createClass({ @@ -35,6 +44,7 @@ const Editor = createClass({ onTextChange : ()=>{}, onStyleChange : ()=>{}, onMetaChange : ()=>{}, + onSnipChange : ()=>{}, reportError : ()=>{}, onCursorPageChange : ()=>{}, @@ -51,7 +61,7 @@ const Editor = createClass({ getInitialState : function() { return { editorTheme : this.props.editorTheme, - view : 'text' //'text', 'style', 'meta' + view : 'text' //'text', 'style', 'meta', 'snippet' }; }, @@ -61,12 +71,11 @@ const Editor = createClass({ isText : function() {return this.state.view == 'text';}, isStyle : function() {return this.state.view == 'style';}, isMeta : function() {return this.state.view == 'meta';}, + isSnip : function() {return this.state.view == 'snippet';}, componentDidMount : function() { - this.updateEditorSize(); this.highlightCustomMarkdown(); - window.addEventListener('resize', this.updateEditorSize); document.getElementById('BrewRenderer').addEventListener('keydown', this.handleControlKeys); document.addEventListener('keydown', this.handleControlKeys); @@ -81,10 +90,6 @@ const Editor = createClass({ } }, - componentWillUnmount : function() { - window.removeEventListener('resize', this.updateEditorSize); - }, - componentDidUpdate : function(prevProps, prevState, snapshot) { this.highlightCustomMarkdown(); @@ -117,24 +122,16 @@ const Editor = createClass({ } }, - updateEditorSize : function() { - if(this.codeEditor.current) { - let paneHeight = this.editor.current.parentNode.clientHeight; - paneHeight -= SNIPPETBAR_HEIGHT; - this.codeEditor.current.codeMirror.setSize(null, paneHeight); - } - }, - updateCurrentCursorPage : function(cursor) { - const lines = this.props.brew.text.split('\n').slice(0, cursor.line + 1); - const pageRegex = this.props.brew.renderer == 'V3' ? /^\\page$/ : /\\page/; + const lines = this.props.brew.text.split('\n').slice(1, cursor.line + 1); + const pageRegex = this.props.brew.renderer == 'V3' ? PAGEBREAK_REGEX_V3 : /\\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 lines = this.props.brew.text.split('\n').slice(1, topScrollLine + 1); + const pageRegex = this.props.brew.renderer == 'V3' ? PAGEBREAK_REGEX_V3 : /\\page/; const currentPage = lines.reduce((count, line)=>count + (pageRegex.test(line) ? 1 : 0), 1); this.props.onViewPageChange(currentPage); }, @@ -145,17 +142,17 @@ const Editor = createClass({ handleViewChange : function(newView){ this.props.setMoveArrows(newView === 'text'); + this.setState({ view : newView }, ()=>{ this.codeEditor.current?.codeMirror.focus(); - this.updateEditorSize(); - }); //TODO: not sure if updateeditorsize needed + }); }, highlightCustomMarkdown : function(){ if(!this.codeEditor.current) return; - if(this.state.view === 'text') { + if((this.state.view === 'text') ||(this.state.view === 'snippet')) { const codeMirror = this.codeEditor.current.codeMirror; codeMirror.operation(()=>{ // Batch CodeMirror styling @@ -174,12 +171,18 @@ const Editor = createClass({ for (let i=customHighlights.length - 1;i>=0;i--) customHighlights[i].clear(); - let editorPageCount = 2; // start page count from page 2 + let userSnippetCount = 1; // start snippet count from snippet 1 + let editorPageCount = 1; // start page count from page 1 - _.forEach(this.props.brew.text.split('\n'), (line, lineNumber)=>{ + const whichSource = this.state.view === 'text' ? this.props.brew.text : this.props.brew.snippets; + _.forEach(whichSource?.split('\n'), (line, lineNumber)=>{ + + const tabHighlight = this.state.view === 'text' ? 'pageLine' : 'snippetLine'; + const textOrSnip = this.state.view === 'text'; //reset custom line styles codeMirror.removeLineClass(lineNumber, 'background', 'pageLine'); + codeMirror.removeLineClass(lineNumber, 'background', 'snippetLine'); codeMirror.removeLineClass(lineNumber, 'text'); codeMirror.removeLineClass(lineNumber, 'wrap', 'sourceMoveFlash'); @@ -190,21 +193,24 @@ const Editor = createClass({ // Styling for \page breaks if((this.props.renderer == 'legacy' && line.includes('\\page')) || - (this.props.renderer == 'V3' && line.match(/^\\page$/))) { + (this.props.renderer == 'V3' && line.match(textOrSnip ? PAGEBREAK_REGEX_V3 : SNIPPETBREAK_REGEX_V3))) { + + if((lineNumber > 0) && (textOrSnip)) // Since \page is optional on first line of document, + editorPageCount += 1; // don't use it to increment page count; stay at 1 + else if(this.state.view !== 'text') userSnippetCount += 1; // add back the original class 'background' but also add the new class '.pageline' - codeMirror.addLineClass(lineNumber, 'background', 'pageLine'); + codeMirror.addLineClass(lineNumber, 'background', tabHighlight); const pageCountElement = Object.assign(document.createElement('span'), { className : 'editor-page-count', - textContent : editorPageCount + textContent : textOrSnip ? editorPageCount : userSnippetCount }); codeMirror.setBookmark({ line: lineNumber, ch: line.length }, pageCountElement); - - editorPageCount += 1; }; + // New Codemirror styling for V3 renderer - if(this.props.renderer == 'V3') { + if(this.props.renderer === 'V3') { if(line.match(/^\\column$/)){ codeMirror.addLineClass(lineNumber, 'text', 'columnSplit'); } @@ -358,7 +364,7 @@ const Editor = createClass({ if(!this.isText() || isJumping) return; - const textSplit = this.props.renderer == 'V3' ? /^\\page$/gm : /\\page/; + const textSplit = this.props.renderer == 'V3' ? PAGEBREAK_REGEX_V3 : /\\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; @@ -454,11 +460,27 @@ const Editor = createClass({ rerenderParent={this.rerenderParent} /> ; } + + if(this.isSnip()){ + if(!this.props.brew.snippets) { this.props.brew.snippets = DEFAULT_SNIPPET_TEXT; } + return <> + + ; + } }, redo : function(){ @@ -499,7 +521,7 @@ const Editor = createClass({ historySize={this.historySize()} currentEditorTheme={this.state.editorTheme} updateEditorTheme={this.updateEditorTheme} - snippetBundle={this.props.snippetBundle} + themeBundle={this.props.themeBundle} cursorPos={this.codeEditor.current?.getCursorPosition() || {}} updateBrew={this.props.updateBrew} /> diff --git a/client/homebrew/editor/editor.less b/client/homebrew/editor/editor.less index b2e96683e..7fbed0ff7 100644 --- a/client/homebrew/editor/editor.less +++ b/client/homebrew/editor/editor.less @@ -1,12 +1,13 @@ @import 'themes/codeMirror/customEditorStyles.less'; .editor { - position : relative; - width : 100%; - container: editor / inline-size; - + position : relative; + width : 100%; + height : 100%; + container : editor / inline-size; .codeEditor { height : 100%; - .pageLine { + .CodeMirror { height : 100%; } + .pageLine, .snippetLine { background : #33333328; border-top : #333399 solid 1px; } @@ -14,6 +15,10 @@ float : right; color : grey; } + .editor-snippet-count { + float : right; + color : grey; + } .columnSplit { font-style : italic; color : grey; @@ -45,26 +50,26 @@ color : green; } .emoji:not(.cm-comment) { - margin-left : 2px; - color : #360034; - background : #ffc8ff; - border-radius : 6px; - font-weight : bold; padding-bottom : 1px; + margin-left : 2px; + font-weight : bold; + color : #360034; + outline : solid 2px #FF96FC; outline-offset : -2px; - outline : solid 2px #ff96fc; + background : #FFC8FF; + border-radius : 6px; } .superscript:not(.cm-comment) { - font-weight : bold; - color : goldenrod; - vertical-align : super; font-size : 0.9em; + font-weight : bold; + vertical-align : super; + color : goldenrod; } .subscript:not(.cm-comment) { - font-weight : bold; - color : rgb(123, 123, 15); - vertical-align : sub; font-size : 0.9em; + font-weight : bold; + vertical-align : sub; + color : rgb(123, 123, 15); } .dl-highlight { &.dl-colon-highlight { @@ -104,3 +109,7 @@ } } + +@container editor (width < 553px) { + .editor .codeEditor .CodeMirror { height : calc(100% - 51px);} +} \ No newline at end of file diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.jsx b/client/homebrew/editor/metadataEditor/metadataEditor.jsx index bfc3b8b61..8f256922f 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.jsx +++ b/client/homebrew/editor/metadataEditor/metadataEditor.jsx @@ -4,7 +4,6 @@ const React = require('react'); const createClass = require('create-react-class'); const _ = require('lodash'); import request from '../../utils/request-middleware.js'; -const Nav = require('naturalcrit/nav/nav.jsx'); const Combobox = require('client/components/combobox.jsx'); const TagInput = require('../tagInput/tagInput.jsx'); @@ -40,6 +39,7 @@ const MetadataEditor = createClass({ theme : '5ePHB', lang : 'en' }, + onChange : ()=>{}, reportError : ()=>{} }; @@ -67,6 +67,11 @@ const MetadataEditor = createClass({ const inputRules = validations[name] ?? []; const validationErr = inputRules.map((rule)=>rule(e.target.value)).filter(Boolean); + const debouncedReportValidity = _.debounce((target, errMessage)=>{ + callIfExists(target, 'setCustomValidity', errMessage); + callIfExists(target, 'reportValidity'); + }, 300); // 300ms debounce delay, adjust as needed + // if no validation rules, save to props if(validationErr.length === 0){ callIfExists(e.target, 'setCustomValidity', ''); @@ -74,14 +79,16 @@ const MetadataEditor = createClass({ ...this.props.metadata, [name] : e.target.value }); + return true; } else { // if validation issues, display built-in browser error popup with each error. const errMessage = validationErr.map((err)=>{ return `- ${err}`; }).join('\n'); - callIfExists(e.target, 'setCustomValidity', errMessage); - callIfExists(e.target, 'reportValidity'); + + debouncedReportValidity(e.target, errMessage); + return false; } }, @@ -102,6 +109,7 @@ const MetadataEditor = createClass({ } this.props.onChange(this.props.metadata, 'renderer'); }, + handlePublish : function(val){ this.props.onChange({ ...this.props.metadata, @@ -112,6 +120,14 @@ const MetadataEditor = createClass({ handleTheme : function(theme){ this.props.metadata.renderer = theme.renderer; this.props.metadata.theme = theme.path; + + this.props.onChange(this.props.metadata, 'theme'); + }, + + handleThemeWritein : function(e) { + const shareId = e.target.value.split('/').pop(); //Extract just the ID if a URL was pasted in + this.props.metadata.theme = shareId; + this.props.onChange(this.props.metadata, 'theme'); }, @@ -200,7 +216,7 @@ const MetadataEditor = createClass({ if(theme.path == this.props.metadata.shareId) return; const preview = theme.thumbnail || `/themes/${theme.renderer}/${theme.path}/dropdownPreview.png`; const texture = theme.thumbnail || `/themes/${theme.renderer}/${theme.path}/dropdownTexture.png`; - return
    this.handleTheme(theme)} title={''}> + return
    {theme.author ?? renderer} : {theme.name}
    @@ -210,26 +226,40 @@ const MetadataEditor = createClass({
    ; - }); + }).filter(Boolean); }; const currentRenderer = this.props.metadata.renderer; - const currentTheme = mergedThemes[`${_.upperFirst(this.props.metadata.renderer)}`][this.props.metadata.theme] - ?? { name: `!!! THEME MISSING !!! ID=${this.props.metadata.theme}` }; + const currentThemeDisplay = this.props.themeBundle?.name ? `${this.props.themeBundle.author ?? currentRenderer} : ${this.props.themeBundle.name}` : 'No Theme Selected'; let dropdown; if(currentRenderer == 'legacy') { dropdown = - -
    {`Themes are not supported in the Legacy Renderer`}
    -
    ; +
    +
    Themes are not supported in the Legacy Renderer
    +
    ; } else { dropdown = - -
    {currentTheme.author ?? _.upperFirst(currentRenderer)} : {currentTheme.name}
    - - {listThemes(currentRenderer)} -
    ; +
    + this.handleTheme(value)} + onEntry={(e)=>{ + e.target.setCustomValidity(''); //Clear the validation popup while typing + if(this.handleFieldChange('theme', e)) + this.handleThemeWritein(e); + }} + options={listThemes(currentRenderer)} + autoSuggest={{ + suggestMethod : 'includes', + clearAutoSuggestOnClick : true, + filterOn : ['value', 'title'] + }} + /> + Select from the list below (built-in themes and brews you have tagged "meta:theme"), or paste in the Share URL or Share ID of any brew. +
    ; } return
    @@ -244,15 +274,13 @@ const MetadataEditor = createClass({ return _.map(langCodes.sort(), (code, index)=>{ const localName = new Intl.DisplayNames([code], { type: 'language' }); const englishName = new Intl.DisplayNames('en', { type: 'language' }); - return
    - {`${code}`} -
    {`${localName.of(code)}`}
    + return
    + {code} +
    {localName.of(code)}
    ; }); }; - const debouncedHandleFieldChange = _.debounce(this.handleFieldChange, 500); - return
    @@ -263,16 +291,15 @@ const MetadataEditor = createClass({ onSelect={(value)=>this.handleLanguage(value)} onEntry={(e)=>{ e.target.setCustomValidity(''); //Clear the validation popup while typing - debouncedHandleFieldChange('lang', e); + this.handleFieldChange('lang', e); }} options={listLanguages()} autoSuggest={{ suggestMethod : 'startsWith', clearAutoSuggestOnClick : true, - filterOn : ['data-value', 'data-detail', 'title'] + filterOn : ['value', 'detail', 'title'] }} - > - + /> Sets the HTML Lang property for your brew. May affect hyphenation or spellcheck.
    @@ -345,7 +372,7 @@ const MetadataEditor = createClass({ placeholder='add tag' unique={true} values={this.props.metadata.tags} onChange={(e)=>this.handleFieldChange('tags', e)} - /> + />
    @@ -370,7 +397,7 @@ const MetadataEditor = createClass({ values={this.props.metadata.invitedAuthors} notes={['Invited author usernames are case sensitive.', 'After adding an invited author, send them the edit link. There, they can choose to accept or decline the invitation.']} onChange={(e)=>this.handleFieldChange('invitedAuthors', e)} - /> + />

    Privacy

    diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.less b/client/homebrew/editor/metadataEditor/metadataEditor.less index 2cff01cfe..fd04f07d9 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.less +++ b/client/homebrew/editor/metadataEditor/metadataEditor.less @@ -1,28 +1,31 @@ @import 'naturalcrit/styles/colors.less'; +.userThemeName { + padding-right : 10px; + padding-left : 10px; +} .metadataEditor { position : absolute; - z-index : 5; box-sizing : border-box; width : 100%; height : calc(100vh - 54px); // 54px is the height of the navbar + snippet bar. probably a better way to dynamic get this. padding : 25px; overflow-y : auto; + font-size : 13px; background-color : #999999; - font-size : 13px; h1 { - margin: 0 0 40px; - font-weight: bold; - text-transform: uppercase; + margin : 0 0 40px; + font-weight : bold; + text-transform : uppercase; } h2 { - margin : 20px 0; - font-weight : bold; - border-bottom: 2px solid gray; - color: #555; + margin : 20px 0; + font-weight : bold; + color : #555555; + border-bottom : 2px solid gray; } & > div { margin-bottom : 10px; } @@ -51,10 +54,10 @@ min-width : 200px; & > label { width : 80px; + font-size : 0.9em; font-weight : 800; line-height : 1.8em; text-transform : uppercase; - font-size: .9em; } & > .value { flex : 1 1 auto; @@ -71,8 +74,7 @@ border : 1px solid gray; &:focus { outline : 1px solid #444444; } } - &.thumbnail { - height : 1.4em; + &.thumbnail, &.themes { label { line-height : 2.0em; } .value { overflow : hidden; @@ -88,6 +90,17 @@ } } + &.themes { + .value { + overflow : visible; + text-overflow : auto; + } + button { + padding-right : 5px; + padding-left : 5px; + } + } + &.description { flex : 1; textarea.value { @@ -123,8 +136,8 @@ margin-right : 15px; font-size : 0.9em; font-weight : 800; - white-space : nowrap; vertical-align : middle; + white-space : nowrap; cursor : pointer; user-select : none; } @@ -151,94 +164,74 @@ .colorButton(@red); } } - .authors.field .value { - line-height : 1.5em; - } + .authors.field .value { line-height : 1.5em; } .themes.field { - .navDropdownContainer { + & .dropdown-container { position : relative; z-index : 100; background-color : white; - &.disabled { - font-style : italic; - color : dimgray; - background-color : darkgray; - } - & > div:first-child { - padding : 3px 3px; - background-color : inherit; - border : 1px solid gray; - i { float : right; } - &:hover { - color : white; - background-color : @blue; + } + & .dropdown-options { overflow-y : visible; } + .disabled { + font-style : italic; + color : dimgray; + background-color : darkgray; + } + .item { + position : relative; + padding : 3px 3px; + overflow : visible; + background-color : white; + border-top : 1px solid rgb(118, 118, 118); + .preview { + position : absolute; + top : 0; + right : 0; + z-index : 1; + display : flex; + flex-direction : column; + width : 200px; + overflow : hidden; + color : black; + background : #CCCCCC; + border-radius : 5px; + box-shadow : 0 0 5px black; + opacity : 0; + transition : opacity 250ms ease; + h6 { + padding-block : 0.5em; + padding-inline : 1em; + font-weight : 900; + border-bottom : 2px solid hsl(0,0%,40%); } } - .navDropdown .item > p { - width : 45%; - height : 1.1em; - overflow : hidden; - text-overflow : ellipsis; - white-space : nowrap; - } - .navDropdown { - position : absolute; - width : 100%; - box-shadow : 0px 5px 10px rgba(0, 0, 0, 0.3); - .item { - position : relative; - padding : 3px 3px; - overflow : visible; - background-color : white; - border-top : 1px solid rgb(118, 118, 118); - .preview { - position : absolute; - top : 0; - right : 0; - z-index : 1; - display : flex; - flex-direction : column; - width : 200px; - overflow : hidden; - color : black; - background : #CCCCCC; - border-radius : 5px; - box-shadow : 0 0 5px black; - opacity : 0; - transition : opacity 250ms ease; - h6 { - padding-block : 0.5em; - padding-inline : 1em; - font-weight : 900; - border-bottom : 2px solid hsl(0,0%,40%); - } - } - &:hover { - color : white; - background-color : @blue; - } - &:hover > .preview { opacity : 1; } - .texture-container { - position : absolute; - top : 0; - left : 0; - width : 100%; - height : 100%; - min-height : 100%; - overflow : hidden; - > img { - position : absolute; - top : 0px; - right : 0; - width : 50%; - min-height : 100%; - -webkit-mask-image : linear-gradient(90deg, transparent, black 20%); - mask-image : linear-gradient(90deg, transparent, black 20%); - } - } + + .texture-container { + position : absolute; + top : 0; + left : 0; + width : 100%; + height : 100%; + min-height : 100%; + overflow : hidden; + > img { + position : absolute; + top : 0; + right : 0; + width : 50%; + min-height : 100%; + -webkit-mask-image : linear-gradient(90deg, transparent, black 20%); + mask-image : linear-gradient(90deg, transparent, black 20%); } } + + &:hover { + color : white; + background-color : @blue; + filter : unset; + } + &:hover > .preview { opacity : 1; } } } diff --git a/client/homebrew/editor/metadataEditor/validations.js b/client/homebrew/editor/metadataEditor/validations.js index 32c8131f6..858fca6c4 100644 --- a/client/homebrew/editor/metadataEditor/validations.js +++ b/client/homebrew/editor/metadataEditor/validations.js @@ -27,6 +27,19 @@ module.exports = { (value)=>{ return new RegExp(/^([a-zA-Z]{2,3})(-[a-zA-Z]{4})?(-(?:[0-9]{3}|[a-zA-Z]{2}))?$/).test(value) === false && (value.length > 0) ? 'Invalid language code.' : null; } + ], + theme : [ + (value)=>{ + const URL = global.config.baseUrl.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); //Escape any regex characters + const shareIDPattern = '[a-zA-Z0-9-_]{12}'; + const shareURLRegex = new RegExp(`^${URL}\\/share\\/${shareIDPattern}$`); + const shareIDRegex = new RegExp(`^${shareIDPattern}$`); + if(value?.length === 0) return null; + if(shareURLRegex.test(value)) return null; + if(shareIDRegex.test(value)) return null; + + return 'Must be a valid Share URL or a 12-character ID.'; + } ] }; diff --git a/client/homebrew/editor/snippetbar/snippetbar.jsx b/client/homebrew/editor/snippetbar/snippetbar.jsx index f7d9508f8..5e2051a86 100644 --- a/client/homebrew/editor/snippetbar/snippetbar.jsx +++ b/client/homebrew/editor/snippetbar/snippetbar.jsx @@ -6,6 +6,7 @@ const _ = require('lodash'); const cx = require('classnames'); import { loadHistory } from '../../utils/versionHistory.js'; +import { brewSnippetsToJSON } from '../../../../shared/helpers.js'; //Import all themes const ThemeSnippets = {}; @@ -40,7 +41,7 @@ const Snippetbar = createClass({ unfoldCode : ()=>{}, updateEditorTheme : ()=>{}, cursorPos : {}, - snippetBundle : [], + themeBundle : [], updateBrew : ()=>{} }; }, @@ -64,7 +65,10 @@ const Snippetbar = createClass({ }, componentDidUpdate : async function(prevProps, prevState) { - if(prevProps.renderer != this.props.renderer || prevProps.theme != this.props.theme || prevProps.snippetBundle != this.props.snippetBundle) { + if(prevProps.renderer != this.props.renderer || + prevProps.theme != this.props.theme || + prevProps.themeBundle != this.props.themeBundle || + prevProps.brew.snippets != this.props.brew.snippets) { this.setState({ snippets : this.compileSnippets() }); @@ -97,7 +101,7 @@ const Snippetbar = createClass({ if(key == 'snippets') { const result = _.reverse(_.unionBy(_.reverse(newValue), _.reverse(oldValue), 'name')); // Join snippets together, with preference for the child theme over the parent theme return result.filter((snip)=>snip.gen || snip.subsnippets); - } + }; }, compileSnippets : function() { @@ -105,15 +109,21 @@ const Snippetbar = createClass({ let oldSnippets = _.keyBy(compiledSnippets, 'groupName'); - for (let snippets of this.props.snippetBundle) { - if(typeof(snippets) == 'string') // load staticThemes as needed; they were sent as just a file name - snippets = ThemeSnippets[snippets]; + if(this.props.themeBundle.snippets) { + for (let snippets of this.props.themeBundle.snippets) { + if(typeof(snippets) == 'string') // load staticThemes as needed; they were sent as just a file name + snippets = ThemeSnippets[snippets]; - const newSnippets = _.keyBy(_.cloneDeep(snippets), 'groupName'); - compiledSnippets = _.values(_.mergeWith(oldSnippets, newSnippets, this.mergeCustomizer)); + const newSnippets = _.keyBy(_.cloneDeep(snippets), 'groupName'); + compiledSnippets = _.values(_.mergeWith(oldSnippets, newSnippets, this.mergeCustomizer)); - oldSnippets = _.keyBy(compiledSnippets, 'groupName'); + oldSnippets = _.keyBy(compiledSnippets, 'groupName'); + } } + + const userSnippetsasJSON = brewSnippetsToJSON(this.props.brew.title || 'New Document', this.props.brew.snippets, this.props.themeBundle.snippets); + compiledSnippets.push(userSnippetsasJSON); + return compiledSnippets; }, @@ -207,59 +217,60 @@ const Snippetbar = createClass({ renderEditorButtons : function(){ if(!this.props.showEditButtons) return; - - return ( -
    - {this.props.view !== 'meta' && <>
    -
    - - { this.state.showHistory && this.renderHistoryItems() } +
    + {this.props.view !== 'meta' && <>
    +
    + + { this.state.showHistory && this.renderHistoryItems() } +
    +
    + +
    +
    + +
    -
    - -
    -
    - -
    -
    -
    -
    - -
    -
    - -
    -
    - - {this.state.themeSelector && this.renderThemeSelector()} -
    -
    } - +
    +
    + +
    +
    + +
    +
    + + {this.state.themeSelector && this.renderThemeSelector()} +
    +
    } -
    -
    this.props.onViewChange('text')}> - +
    +
    this.props.onViewChange('text')}> + +
    +
    this.props.onViewChange('style')}> + +
    +
    this.props.onViewChange('snippet')}> + +
    +
    this.props.onViewChange('meta')}> + +
    -
    this.props.onViewChange('style')}> - -
    -
    this.props.onViewChange('meta')}> - -
    -
    -
    - ) +
    + ); }, render : function(){ @@ -272,11 +283,6 @@ const Snippetbar = createClass({ module.exports = Snippetbar; - - - - - const SnippetGroup = createClass({ displayName : 'SnippetGroup', getDefaultProps : function() { @@ -310,7 +316,8 @@ const SnippetGroup = createClass({ }, render : function(){ - return
    + const snippetGroup = `snippetGroup snippetBarButton ${this.props.snippets.length === 0 ? 'disabledSnippets' : ''}`; + return
    {this.props.groupName} diff --git a/client/homebrew/editor/snippetbar/snippetbar.less b/client/homebrew/editor/snippetbar/snippetbar.less index 7d56dc718..a0691f8b6 100644 --- a/client/homebrew/editor/snippetbar/snippetbar.less +++ b/client/homebrew/editor/snippetbar/snippetbar.less @@ -14,15 +14,15 @@ .snippets { display : flex; justify-content : flex-start; - min-width : 327.58px; + min-width : 432.18px; //must be controlled every time an item is added, must be hardcoded for the wrapping as it is applied } .editors { display : flex; justify-content : flex-end; - min-width : 225px; + min-width : 250px; //must be controlled every time an item is added, must be hardcoded for the wrapping as it is applied - &:only-child { margin-left : auto;min-width:unset;} + &:only-child {min-width : unset; margin-left : auto;} >div { display : flex; @@ -39,9 +39,7 @@ text-align : center; cursor : pointer; - &.editorTool:not(.active) { - cursor:not-allowed; - } + &.editorTool:not(.active) { cursor : not-allowed; } &:hover,&.selected { background-color : #999999; } &.text { @@ -53,6 +51,9 @@ &.meta { .tooltipLeft('Properties'); } + &.snip { + .tooltipLeft('Snippets'); + } &.undo { .tooltipLeft('Undo'); font-size : 0.75em; @@ -151,9 +152,9 @@ position : absolute; top : 100%; z-index : 1000; + visibility : hidden; padding : 0px; margin-left : -5px; - visibility : hidden; background-color : #DDDDDD; .snippet { position : relative; @@ -228,8 +229,15 @@ } } } + .disabledSnippets { + color: grey; + cursor: not-allowed; + + &:hover { background-color: #DDDDDD;} + } + } -@container editor (width < 553px) { +@container editor (width < 683px) { .snippetBar { .editors { flex : 1; diff --git a/client/homebrew/editor/tagInput/tagInput.jsx b/client/homebrew/editor/tagInput/tagInput.jsx index 816541167..d60e23b1b 100644 --- a/client/homebrew/editor/tagInput/tagInput.jsx +++ b/client/homebrew/editor/tagInput/tagInput.jsx @@ -3,43 +3,43 @@ const React = require('react'); const { useState, useEffect } = React; const _ = require('lodash'); -const TagInput = ({ unique = true, values = [], ...props }) => { +const TagInput = ({ unique = true, values = [], ...props })=>{ const [tempInputText, setTempInputText] = useState(''); - const [tagList, setTagList] = useState(values.map((value) => ({ value, editing: false }))); + const [tagList, setTagList] = useState(values.map((value)=>({ value, editing: false }))); useEffect(()=>{ - handleChange(tagList.map((context)=>context.value)) - }, [tagList]) + handleChange(tagList.map((context)=>context.value)); + }, [tagList]); const handleChange = (value)=>{ props.onChange({ target : { value } - }) + }); }; - const handleInputKeyDown = ({ evt, value, index, options = {} }) => { - if (_.includes(['Enter', ','], evt.key)) { + const handleInputKeyDown = ({ evt, value, index, options = {} })=>{ + if(_.includes(['Enter', ','], evt.key)) { evt.preventDefault(); submitTag(evt.target.value, value, index); - if (options.clear) { + if(options.clear) { setTempInputText(''); } } }; - const submitTag = (newValue, originalValue, index) => { - setTagList((prevContext) => { + const submitTag = (newValue, originalValue, index)=>{ + setTagList((prevContext)=>{ // remove existing tag if(newValue === null){ return [...prevContext].filter((context, i)=>i !== index); } // add new tag if(originalValue === null){ - return [...prevContext, { value: newValue, editing: false }] + return [...prevContext, { value: newValue, editing: false }]; } // update existing tag - return prevContext.map((context, i) => { - if (i === index) { + return prevContext.map((context, i)=>{ + if(i === index) { return { ...context, value: newValue, editing: false }; } return context; @@ -47,10 +47,10 @@ const TagInput = ({ unique = true, values = [], ...props }) => { }); }; - const editTag = (index) => { - setTagList((prevContext) => { - return prevContext.map((context, i) => { - if (i === index) { + const editTag = (index)=>{ + setTagList((prevContext)=>{ + return prevContext.map((context, i)=>{ + if(i === index) { return { ...context, editing: true }; } return { ...context, editing: false }; @@ -58,25 +58,25 @@ const TagInput = ({ unique = true, values = [], ...props }) => { }); }; - const renderReadTag = (context, index) => { + const renderReadTag = (context, index)=>{ return (
  • editTag(index)}> + onClick={()=>editTag(index)}> {context.value} - +
  • ); }; - const renderWriteTag = (context, index) => { + const renderWriteTag = (context, index)=>{ return ( handleInputKeyDown({evt, value: context.value, index: index})} - autoFocus + defaultValue={context.value} + onKeyDown={(evt)=>handleInputKeyDown({ evt, value: context.value, index: index })} + autoFocus /> ); }; @@ -86,7 +86,7 @@ const TagInput = ({ unique = true, values = [], ...props }) => {
      - {tagList.map((context, index) => { return context.editing ? renderWriteTag(context, index) : renderReadTag(context, index); })} + {tagList.map((context, index)=>{ return context.editing ? renderWriteTag(context, index) : renderReadTag(context, index); })}
    { className='value' placeholder={props.placeholder} value={tempInputText} - onChange={(e) => setTempInputText(e.target.value)} - onKeyDown={(evt) => handleInputKeyDown({ evt, value: null, options: { clear: true } })} + onChange={(e)=>setTempInputText(e.target.value)} + onKeyDown={(evt)=>handleInputKeyDown({ evt, value: null, options: { clear: true } })} />
    diff --git a/client/homebrew/homebrew.less b/client/homebrew/homebrew.less index 828de796f..e265c2941 100644 --- a/client/homebrew/homebrew.less +++ b/client/homebrew/homebrew.less @@ -1,36 +1,32 @@ @import 'naturalcrit/styles/core.less'; -.homebrew{ +.homebrew { height : 100%; - .sitePage{ + .sitePage { display : flex; - height : 100%; - background-color : @steel; flex-direction : column; + height : 100%; overflow-y : hidden; - .content{ + background-color : @steel; + .content { position : relative; - height : calc(~"100% - 29px"); //Navbar height flex : auto; + height : calc(~'100% - 29px'); //Navbar height overflow-y : hidden; } &.listPage .content { overflow-y : scroll; &::-webkit-scrollbar { - width: 20px; - &:horizontal{ - height: 20px; - width:auto; + width : 20px; + &:horizontal { + width : auto; + height : 20px; } &-thumb { - background: linear-gradient(90deg, #d3c1af 15px, #00000000 15px); - &:horizontal{ - background: linear-gradient(0deg, #d3c1af 15px, #00000000 15px); - } - } - &-corner { - visibility: hidden; + background : linear-gradient(90deg, #D3C1AF 15px, #00000000 15px); + &:horizontal { background : linear-gradient(0deg, #D3C1AF 15px, #00000000 15px); } } + &-corner { visibility : hidden; } } } } diff --git a/client/homebrew/navbar/error-navitem.jsx b/client/homebrew/navbar/error-navitem.jsx index f6788e6d5..3de26ca56 100644 --- a/client/homebrew/navbar/error-navitem.jsx +++ b/client/homebrew/navbar/error-navitem.jsx @@ -116,6 +116,19 @@ const ErrorNavItem = createClass({ ; } + if(HBErrorCode === '10') { + return + Oops! +
    + Looks like the brew you have selected + as a theme is not tagged for use as a + theme. Verify that + brew + {response.body.brewId} has the meta:theme tag! +
    +
    ; + } + return Oops!
    diff --git a/client/homebrew/navbar/error-navitem.less b/client/homebrew/navbar/error-navitem.less index be138dca4..637ddac95 100644 --- a/client/homebrew/navbar/error-navitem.less +++ b/client/homebrew/navbar/error-navitem.less @@ -1,78 +1,70 @@ .navItem.error { - position : relative; - background-color : @red; + position : relative; + background-color : @red; } -.errorContainer{ - animation-name: glideDown; - animation-duration: 0.4s; - position : absolute; - top : 100%; - left : 50%; - z-index : 1000; - width : 140px; - padding : 3px; - color : white; - background-color : #333; - border : 3px solid #444; - border-radius : 5px; - transform : translate(-50% + 3px, 10px); - text-align : center; - font-size : 10px; - font-weight : 800; - text-transform : uppercase; - .lowercase { - text-transform : none; +.errorContainer { + position : absolute; + top : 100%; + left : 50%; + z-index : 1000; + width : 140px; + padding : 3px; + font-size : 10px; + font-weight : 800; + color : white; + text-align : center; + text-transform : uppercase; + background-color : #333333; + border : 3px solid #444444; + border-radius : 5px; + transform : translate(-50% + 3px, 10px); + animation-name : glideDown; + animation-duration : 0.4s; + .lowercase { text-transform : none; } + a { color : @teal; } + &::before { + position : absolute; + top : -23px; + left : 53px; + width : 0px; + height : 0px; + content : ''; + border-top : 10px solid transparent; + border-right : 10px solid transparent; + border-bottom : 10px solid #444444; + border-left : 10px solid transparent; + } + &::after { + position : absolute; + top : -19px; + left : 53px; + width : 0px; + height : 0px; + content : ''; + border-top : 10px solid transparent; + border-right : 10px solid transparent; + border-bottom : 10px solid #333333; + border-left : 10px solid transparent; + } + .deny { + display : inline-block; + width : 48%; + padding : 5px; + margin : 1px; + background-color : #333333; + border-left : 1px solid #666666; + .animate(background-color); + &:hover { background-color : red; } + } + .confirm { + display : inline-block; + width : 48%; + padding : 5px; + margin : 1px; + color : white; + background-color : #333333; + .animate(background-color); + &:hover { background-color : teal; } } - a{ - color : @teal; - } - &:before { - content: ""; - width: 0px; - height: 0px; - position: absolute; - border-left: 10px solid transparent; - border-right: 10px solid transparent; - border-top: 10px solid transparent; - border-bottom: 10px solid #444; - left: 53px; - top: -23px; - } - &:after { - content: ""; - width: 0px; - height: 0px; - position: absolute; - border-left: 10px solid transparent; - border-right: 10px solid transparent; - border-top: 10px solid transparent; - border-bottom: 10px solid #333; - left: 53px; - top: -19px; - } - .deny { - width : 48%; - margin : 1px; - padding : 5px; - background-color : #333; - display : inline-block; - border-left : 1px solid #666; - .animate(background-color); - &:hover{ - background-color : red; - } - } - .confirm { - width : 48%; - margin : 1px; - padding : 5px; - background-color : #333; - display : inline-block; - color : white; - .animate(background-color); - &:hover{ - background-color : teal; - } - } } diff --git a/client/homebrew/navbar/navbar.less b/client/homebrew/navbar/navbar.less index ae11c1e7e..aa233d631 100644 --- a/client/homebrew/navbar/navbar.less +++ b/client/homebrew/navbar/navbar.less @@ -24,11 +24,11 @@ } .homebrew nav { + position : relative; + z-index : 2; + display : flex; + justify-content : space-between; background-color : #333333; - position : relative; - z-index : 2; - display : flex; - justify-content : space-between; .navSection { display : flex; @@ -82,8 +82,8 @@ font-weight : 800; line-height : 13px; color : white; - text-decoration : none; text-transform : uppercase; + text-decoration : none; cursor : pointer; background-color : #333333; i { @@ -106,11 +106,11 @@ display : block; width : 100%; overflow : hidden; + text-overflow : ellipsis; font-size : 12px; font-weight : 800; color : white; text-align : center; - text-overflow : ellipsis; text-transform : initial; white-space : nowrap; background-color : transparent; @@ -170,16 +170,16 @@ h4 { box-sizing : border-box; display : block; - flex-basis : 20%; flex-grow : 1; + flex-basis : 20%; min-width : 76px; padding : 5px 0; color : #BBBBBB; text-align : center; } p { - flex-basis : 80%; flex-grow : 1; + flex-basis : 80%; padding : 5px 0; font-family : 'Open Sans', sans-serif; font-size : 10px; @@ -215,10 +215,10 @@ z-index : 10000; box-sizing : border-box; display : block; + visibility : hidden; width : 100%; padding : 13px 5px; text-align : center; - visibility : hidden; background-color : #333333; } } diff --git a/client/homebrew/pages/basePages/listPage/brewItem/brewItem.jsx b/client/homebrew/pages/basePages/listPage/brewItem/brewItem.jsx index ef98e8425..ef309a613 100644 --- a/client/homebrew/pages/basePages/listPage/brewItem/brewItem.jsx +++ b/client/homebrew/pages/basePages/listPage/brewItem/brewItem.jsx @@ -30,11 +30,11 @@ const BrewItem = ({ } request.delete(`/api/${brew.googleId ?? ''}${brew.editId}`).send().end((err, res)=>{ - if (err) reportError(err); else window.location.reload(); - }); + if(err) reportError(err); else window.location.reload(); + }); }, [brew, reportError]); - const updateFilter = useCallback((type, term)=> updateListFilter(type, term), [updateListFilter]); + const updateFilter = useCallback((type, term)=>updateListFilter(type, term), [updateListFilter]); const renderDeleteBrewLink = ()=>{ if(!brew.editId) return null; diff --git a/client/homebrew/pages/basePages/listPage/brewItem/brewItem.less b/client/homebrew/pages/basePages/listPage/brewItem/brewItem.less index a3c17215e..0d45e8537 100644 --- a/client/homebrew/pages/basePages/listPage/brewItem/brewItem.less +++ b/client/homebrew/pages/basePages/listPage/brewItem/brewItem.less @@ -1,148 +1,129 @@ -.brewItem{ +.brewItem { position : relative; + box-sizing : border-box; display : inline-block; - vertical-align : top; - box-sizing : border-box; - box-sizing : border-box; - overflow : hidden; width : 48%; min-height : 105px; - margin-right : 15px; - margin-bottom : 15px; padding : 5px 15px 2px 6px; padding-right : 15px; - border : 1px solid #c9ad6a; + margin-right : 15px; + margin-bottom : 15px; + overflow : hidden; + vertical-align : top; + background-color : #CAB2802E; + border : 1px solid #C9AD6A; border-radius : 5px; + box-shadow : 0px 4px 5px 0px #333333; + break-inside : avoid; -webkit-column-break-inside : avoid; page-break-inside : avoid; - break-inside : avoid; - box-shadow : 0px 4px 5px 0px #333; - background-color : #cab2802e; - .thumbnail { - position: absolute; - width: 150px; - height: 100%; - top: 0; - right: 0; - z-index: -1; - background-size: contain; - background-repeat: no-repeat; - background-position: right top; - mask-image: linear-gradient(80deg, #0000 20%, #050 40%); - -webkit-mask-image: linear-gradient(80deg, #0000 20%, #050 40%); - opacity: 50%; + .thumbnail { + position : absolute; + top : 0; + right : 0; + z-index : -1; + width : 150px; + height : 100%; + background-repeat : no-repeat; + background-position : right top; + background-size : contain; + opacity : 50%; + -webkit-mask-image : linear-gradient(80deg, #00000000 20%, #005500 40%); + mask-image : linear-gradient(80deg, #00000000 20%, #005500 40%); } .text { min-height : 54px; - h4{ + h4 { margin-bottom : 5px; font-size : 2.2em; } } - .info{ - position: initial; - bottom: 2px; - font-family : ScalySansRemake; + .info { + position : initial; + bottom : 2px; + font-family : "ScalySansRemake"; font-size : 1.2em; - &>span{ + & > span { margin-right : 12px; line-height : 1.5em; - a { - color:inherit; - } + a { color : inherit; } } } .brewTags span { - background-color: #c8ac6e3b; - margin: 2px; - padding: 2px; - border: 1px solid #c8ac6e; - border-radius: 4px; - white-space: nowrap; - display: inline-block; - font-weight: bold; - border-color: currentColor; - cursor : pointer; - &:before { - font-family: 'Font Awesome 5 Free'; - font-size: 12px; - margin-right: 3px; + display : inline-block; + padding : 2px; + margin : 2px; + font-weight : bold; + white-space : nowrap; + cursor : pointer; + background-color : #C8AC6E3B; + border : 1px solid #C8AC6E; + border-color : currentColor; + border-radius : 4px; + &::before { + margin-right : 3px; + font-family : 'Font Awesome 5 Free'; + font-size : 12px; } &.type { - background-color: #0080003b; - color: #008000; - &:before{ - content: '\f0ad'; - } + color : #008000; + background-color : #0080003B; + &::before { content : '\f0ad'; } } &.group { - background-color: #5050503b; - color: #000000; - &:before{ - content: '\f500'; - } + color : #000000; + background-color : #5050503B; + &::before { content : '\f500'; } } &.meta { - background-color: #0000803b; - color: #000080; - &:before{ - content: '\f05a'; - } + color : #000080; + background-color : #0000803B; + &::before { content : '\f05a'; } } &.system { - background-color: #8000003b; - color: #800000; - &:before{ - content: '\f518'; - } + color : #800000; + background-color : #8000003B; + &::before { content : '\f518'; } } } - &:hover{ - .links{ - opacity : 1; - } + &:hover { + .links { opacity : 1; } } - &:nth-child(2n + 1){ - margin-right : 0px; - } - .links{ + &:nth-child(2n + 1) { margin-right : 0px; } + .links { .animate(opacity); position : absolute; top : 0px; right : 0px; - height : 100%; width : 2em; - opacity : 0; - background-color : fade(black, 60%); + height : 100%; text-align : center; - a{ + background-color : fade(black, 60%); + opacity : 0; + a { .animate(opacity); display : block; margin : 8px 0px; - opacity : 0.6; font-size : 1.3em; color : white; text-decoration : unset; - &:hover{ - opacity : 1; - } - i{ - cursor : pointer; - } + opacity : 0.6; + &:hover { opacity : 1; } + i { cursor : pointer; } } } .googleDriveIcon { - height : 18px; + height : 18px; padding : 0px; margin : -5px; } .homebreweryIcon { - mix-blend-mode : darken; - height : 24px; position : relative; top : 5px; left : -5px; + height : 24px; + mix-blend-mode : darken; } } diff --git a/client/homebrew/pages/basePages/listPage/listPage.less b/client/homebrew/pages/basePages/listPage/listPage.less index 0aa4a278d..bf899bc71 100644 --- a/client/homebrew/pages/basePages/listPage/listPage.less +++ b/client/homebrew/pages/basePages/listPage/listPage.less @@ -1,5 +1,5 @@ -.noColumns(){ +.noColumns() { column-count : auto; column-fill : auto; column-gap : normal; @@ -13,177 +13,151 @@ height : auto; min-height : 279.4mm; margin : 20px auto; - contain : unset; + contain : unset; } -.listPage{ - .content{ +.listPage { + .content { z-index : 1; - .page{ + .page { .noColumns() !important; //Needed to override PHB Theme since this is on a lower @layer - &::after{ - display : none; - } - .noBrews{ + &::after { display : none; } + .noBrews { margin : 10px 0px; font-size : 1.3em; font-style : italic; } .brewCollection { - h1:hover{ - cursor: pointer; - } + h1:hover { cursor : pointer; } .active::before, .inactive::before { - font-family: 'Font Awesome 5 Free'; - font-weight: 900; - font-size: 0.6cm; - padding-right: 0.5em; - } - .active { - color: var(--HB_Color_HeaderText); - } - .active::before { - content: '\f107'; - } - .inactive { - color: #707070; - } - .inactive::before { - content: '\f105'; + padding-right : 0.5em; + font-family : 'Font Awesome 5 Free'; + font-size : 0.6cm; + font-weight : 900; } + .active { color : var(--HB_Color_HeaderText); } + .active::before { content : '\f107'; } + .inactive { color : #707070; } + .inactive::before { content : '\f105'; } } } } .sort-container { - font-family : 'Open Sans', sans-serif; - position : sticky; - top : 0; - left : 0; - width : 100%; - height : 30px; - background-color : #555; - border-top : 1px solid #666; - border-bottom : 1px solid #666; - color : white; - text-align : center; - z-index : 1; - display : flex; - justify-content : center; - align-items : baseline; - column-gap : 15px; - row-gap : 5px; - flex-wrap : wrap; - h6{ - text-transform : uppercase; + position : sticky; + top : 0; + left : 0; + z-index : 1; + display : flex; + flex-wrap : wrap; + row-gap : 5px; + column-gap : 15px; + align-items : baseline; + justify-content : center; + width : 100%; + height : 30px; + font-family : 'Open Sans', sans-serif; + color : white; + text-align : center; + background-color : #555555; + border-top : 1px solid #666666; + border-bottom : 1px solid #666666; + h6 { font-family : 'Open Sans', sans-serif; font-size : 11px; font-weight : bold; + text-transform : uppercase; } .sort-option { - display: flex; - align-items: center; - padding: 0 8px; - color: #ccc; - height: 100%; + display : flex; + align-items : center; + height : 100%; + padding : 0 8px; + color : #CCCCCC; - &:hover{ - background-color : #444; - } + &:hover { background-color : #444444; } &.active { - font-weight: bold; - color: #ddd; - background-color: #333; + font-weight : bold; + color : #DDDDDD; + background-color : #333333; - button { - color: white; - font-weight: 800; - height: 100%; - & + .sortDir { - padding-left: 5px; + button { + height : 100%; + font-weight : 800; + color : white; + & + .sortDir { padding-left : 5px; } } } - } } .filter-option { - margin-left: 20px; - background-color : transparent !important; + margin-left : 20px; font-size : 11px; - i{ - padding-right : 5px; - } + background-color : transparent !important; + i { padding-right : 5px; } + } + button { + padding : 0; + font-family : 'Open Sans', sans-serif; + font-size : 11px; + font-weight : normal; + color : #CCCCCC; + text-transform : uppercase; + background-color : transparent; } - button{ - background-color : transparent; - font-family : 'Open Sans', sans-serif; - text-transform : uppercase; - font-weight : normal; - font-size : 11px; - color : #ccc; - padding : 0; - } } .tags-container { - height : 30px; - background-color : #555; - border-top : 1px solid #666; - border-bottom : 1px solid #666; - color : white; display : flex; - justify-content : center; - align-items : center; - column-gap : 15px; - row-gap : 5px; flex-wrap : wrap; + row-gap : 5px; + column-gap : 15px; + align-items : center; + justify-content : center; + height : 30px; + color : white; + background-color : #555555; + border-top : 1px solid #666666; + border-bottom : 1px solid #666666; span { + padding : 3px; font-family : 'Open Sans', sans-serif; font-size : 11px; font-weight : bold; + color : #DFDFDF; + cursor : pointer; border : 1px solid; border-radius : 3px; - padding : 3px; - cursor : pointer; - color: #dfdfdf; - &:before { - font-family: 'Font Awesome 5 Free'; - font-size: 12px; - margin-right: 3px; + &::before { + margin-right : 3px; + font-family : 'Font Awesome 5 Free'; + font-size : 12px; } - &:after { - content: '\f00d'; - font-family: 'Font Awesome 5 Free'; - font-size: 12px; - margin-left: 3px; + &::after { + margin-left : 3px; + font-family : 'Font Awesome 5 Free'; + font-size : 12px; + content : '\f00d'; } &.type { - background-color: #008000; - border-color: #00a000; - &:before{ - content: '\f0ad'; - } + background-color : #008000; + border-color : #00A000; + &::before { content : '\f0ad'; } } &.group { - background-color: #505050; - border-color: #000000; - &:before{ - content: '\f500'; - } + background-color : #505050; + border-color : #000000; + &::before { content : '\f500'; } } &.meta { - background-color: #000080; - border-color: #0000a0; - &:before{ - content: '\f05a'; - } + background-color : #000080; + border-color : #0000A0; + &::before { content : '\f05a'; } } &.system { - background-color: #800000; - border-color: #a00000; - &:before{ - content: '\f518'; - } + background-color : #800000; + border-color : #A00000; + &::before { content : '\f518'; } } } } diff --git a/client/homebrew/pages/basePages/uiPage/uiPage.less b/client/homebrew/pages/basePages/uiPage/uiPage.less index 913c74a2e..27f079e20 100644 --- a/client/homebrew/pages/basePages/uiPage/uiPage.less +++ b/client/homebrew/pages/basePages/uiPage/uiPage.less @@ -1,7 +1,7 @@ .homebrew { .uiPage.sitePage { .content { - width : ~"min(90vw, 1000px)"; + width : ~'min(90vw, 1000px)'; padding : 2% 4%; margin-top : 25px; margin-right : auto; @@ -17,19 +17,19 @@ border : 2px solid black; border-radius : 5px; button { + width : 125px; + margin-right : 5px; + color : black; background-color : transparent; border : 1px solid black; border-radius : 5px; - width : 125px; - color : black; - margin-right : 5px; &.active { - background-color: #0007; - color: white; - &:before { - content: '\f00c'; - font-family: 'FONT AWESOME 5 FREE'; - margin-right: 5px; + color : white; + background-color : #00000077; + &::before { + margin-right : 5px; + font-family : 'FONT AWESOME 5 FREE'; + content : '\f00c'; } } } diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index ffb6a6b40..f2b1e809f 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -102,6 +102,14 @@ const EditPage = createClass({ window.onbeforeunload = function(){}; document.removeEventListener('keydown', this.handleControlKeys); }, + componentDidUpdate : function(){ + const hasChange = this.hasChanges(); + if(this.state.isPending != hasChange){ + this.setState({ + isPending : hasChange + }); + } + }, handleControlKeys : function(e){ if(!(e.ctrlKey || e.metaKey)) return; @@ -138,6 +146,17 @@ const EditPage = createClass({ this.setState((prevState)=>({ brew : { ...prevState.brew, text: text }, + htmlErrors : htmlErrors, + }), ()=>{if(this.state.autoSave) this.trySave();}); + }, + + handleSnipChange : function(snippet){ + //If there are errors, run the validator on every change to give quick feedback + let htmlErrors = this.state.htmlErrors; + if(htmlErrors.length) htmlErrors = Markdown.validate(snippet); + + this.setState((prevState)=>({ + brew : { ...prevState.brew, snippets: snippet }, isPending : true, htmlErrors : htmlErrors, }), ()=>{if(this.state.autoSave) this.trySave();}); @@ -145,8 +164,7 @@ const EditPage = createClass({ handleStyleChange : function(style){ this.setState((prevState)=>({ - brew : { ...prevState.brew, style: style }, - isPending : true + brew : { ...prevState.brew, style: style } }), ()=>{if(this.state.autoSave) this.trySave();}); }, @@ -158,8 +176,7 @@ const EditPage = createClass({ brew : { ...prevState.brew, ...metadata - }, - isPending : true, + } }), ()=>{if(this.state.autoSave) this.trySave();}); }, @@ -247,16 +264,17 @@ const EditPage = createClass({ }); if(!res) return; - this.savedBrew = res.body; + this.savedBrew = { + ...this.state.brew, + googleId : res.body.googleId ? res.body.googleId : null, + editId : res.body.editId, + shareId : res.body.shareId, + version : res.body.version + }; history.replaceState(null, null, `/edit/${this.savedBrew.editId}`); - this.setState((prevState)=>({ - brew : { ...prevState.brew, - googleId : this.savedBrew.googleId ? this.savedBrew.googleId : null, - editId : this.savedBrew.editId, - shareId : this.savedBrew.shareId, - version : this.savedBrew.version - }, + this.setState(()=>({ + brew : this.savedBrew, isPending : false, isSaving : false, unsavedTime : new Date() @@ -311,7 +329,14 @@ const EditPage = createClass({ }, renderSaveButton : function(){ - if(this.state.autoSaveWarning && this.hasChanges()){ + + // #1 - Currently saving, show SAVING + if(this.state.isSaving){ + return saving...; + } + + // #2 - Unsaved changes exist, autosave is OFF and warning timer has expired, show AUTOSAVE WARNING + if(this.state.isPending && this.state.autoSaveWarning){ this.setAutosaveWarning(); const elapsedTime = Math.round((new Date() - this.state.unsavedTime) / 1000 / 60); const text = elapsedTime == 0 ? 'Autosave is OFF.' : `Autosave is OFF, and you haven't saved for ${elapsedTime} minutes.`; @@ -324,18 +349,17 @@ const EditPage = createClass({ ; } - if(this.state.isSaving){ - return saving...; + // #3 - Unsaved changes exist, click to save, show SAVE NOW + // Use trySave(true) instead of save() to use debounced save function + if(this.state.isPending){ + return this.trySave(true)} color='blue' icon='fas fa-save'>Save Now; } - if(this.state.isPending && this.hasChanges()){ - return Save Now; - } - if(!this.state.isPending && !this.state.isSaving && this.state.autoSave){ + // #4 - No unsaved changes, autosave is ON, show AUTO-SAVED + if(this.state.autoSave){ return auto-saved.; } - if(!this.state.isPending && !this.state.isSaving){ - return saved.; - } + // DEFAULT - No unsaved changes, show SAVED + return saved.; }, handleAutoSave : function(){ @@ -379,7 +403,7 @@ const EditPage = createClass({ const title = `${this.props.brew.title} ${systems}`; const text = `Hey guys! I've been working on this homebrew. I'd love your feedback. Check it out. -**[Homebrewery Link](${global.config.publicUrl}/share/${shareLink})**`; +**[Homebrewery Link](${global.config.baseUrl}/share/${shareLink})**`; return `https://www.reddit.com/r/UnearthedArcana/submit?title=${encodeURIComponent(title.toWellFormed())}&text=${encodeURIComponent(text)}`; }, @@ -410,7 +434,7 @@ const EditPage = createClass({ view - {navigator.clipboard.writeText(`${global.config.publicUrl}/share/${shareLink}`);}}> + {navigator.clipboard.writeText(`${global.config.baseUrl}/share/${shareLink}`);}}> copy url @@ -431,7 +455,7 @@ const EditPage = createClass({ {this.renderNavbar()} - {this.props.brew.lock && } + {this.props.brew.lock && }
    {}, - message : '', + shareId : 0, + disableLock : ()=>{}, + lock : {}, + message : 'Unable to retrieve Lock Message', + reviewRequested : false, ...props }; - const removeLock = ()=>{ - alert(`Not yet implemented - ID ${props.shareId}`); + const [reviewState, setReviewState] = React.useState(props.reviewRequested); + + const removeLock = async ()=>{ + await request.put(`/api/lock/review/request/${props.shareId}`) + .then(()=>{ + setReviewState(true); + }); + }; + + const renderReviewButton = function(){ + if(reviewState){ return ; }; + return ; }; return @@ -19,11 +32,11 @@ function LockNotification(props) {

    This brew been locked by the Administrators. It will not be accessible by any method other than the Editor until the lock is removed.


    LOCK REASON

    -

    {props.message || 'Unable to retrieve Lock Message'}

    +

    {props.message}


    Once you have resolved this issue, click REQUEST LOCK REMOVAL to notify the Administrators for review.

    Click CONTINUE TO EDITOR to temporarily hide this notification; it will reappear the next time the page is reloaded.

    - + {renderReviewButton()}
    ; }; diff --git a/client/homebrew/pages/editPage/lockNotification/lockNotification.less b/client/homebrew/pages/editPage/lockNotification/lockNotification.less index 54f1a9569..930b070c4 100644 --- a/client/homebrew/pages/editPage/lockNotification/lockNotification.less +++ b/client/homebrew/pages/editPage/lockNotification/lockNotification.less @@ -11,10 +11,12 @@ &::backdrop { background-color : #000000AA; } button { + padding : 2px 15px; margin : 10px; color : white; background-color : #333333; + &.inactive, &:hover { background-color : #777777; } } diff --git a/client/homebrew/pages/errorPage/errors/errorIndex.js b/client/homebrew/pages/errorPage/errors/errorIndex.js index ccdd86768..b13b19eb1 100644 --- a/client/homebrew/pages/errorPage/errors/errorIndex.js +++ b/client/homebrew/pages/errorPage/errors/errorIndex.js @@ -2,6 +2,11 @@ const dedent = require('dedent-tabs').default; const loginUrl = 'https://www.naturalcrit.com/login'; +// Prevent parsing text (e.g. document titles) as markdown +const escape = (text = '')=>{ + return text.split('').map((char)=>`&#${char.charCodeAt(0)};`).join(''); +}; + //001-050 : Brew errors //050-100 : Other pages errors @@ -89,7 +94,7 @@ const errorIndex = (props)=>{ : - **Brew Title:** ${props.brew.brewTitle || 'Unable to show title'} + **Brew Title:** ${escape(props.brew.brewTitle) || 'Unable to show title'} **Current Authors:** ${props.brew.authors?.map((author)=>{return `[${author}](/user/${author})`;}).join(', ') || 'Unable to list authors'} @@ -104,7 +109,7 @@ const errorIndex = (props)=>{ : - **Brew Title:** ${props.brew.brewTitle || 'Unable to show title'} + **Brew Title:** ${escape(props.brew.brewTitle) || 'Unable to show title'} **Current Authors:** ${props.brew.authors?.map((author)=>{return `[${author}](/user/${author})`;}).join(', ') || 'Unable to list authors'} @@ -163,6 +168,14 @@ const errorIndex = (props)=>{ **Brew ID:** ${props.brew.brewId}`, + // Theme Not Valid + '10' : dedent` + ## The selected theme is not tagged as a theme. + + The brew selected as a theme exists, but has not been marked for use as a theme with the \`theme:meta\` tag. + + If the selected brew is your document, you may designate it as a theme by adding the \`theme:meta\` tag.`, + //account page when account is not defined '50' : dedent` ## You are not signed in @@ -181,13 +194,47 @@ const errorIndex = (props)=>{ **Brew ID:** ${props.brew.brewId} - **Brew Title:** ${props.brew.brewTitle}`, + **Brew Title:** ${escape(props.brew.brewTitle)} + + **Brew Authors:** ${props.brew.authors?.map((author)=>{return `[${author}](/user/${author})`;}).join(', ') || 'Unable to list authors'}`, // ####### Admin page error ####### '52' : dedent` ## Access Denied You need to provide correct administrator credentials to access this page.`, + // ####### Lock Errors + + '60' : dedent`Lock Error: General`, + + '61' : dedent`Lock Get Error: Unable to get lock count`, + + '62' : dedent`Lock Set Error: Cannot lock`, + + '63' : dedent`Lock Set Error: Brew not found`, + + '64' : dedent`Lock Set Error: Already locked`, + + '65' : dedent`Lock Remove Error: Cannot unlock`, + + '66' : dedent`Lock Remove Error: Brew not found`, + + '67' : dedent`Lock Remove Error: Not locked`, + + '68' : dedent`Lock Get Review Error: Cannot get review requests`, + + '69' : dedent`Lock Set Review Error: Cannot set review request`, + + '70' : dedent`Lock Set Review Error: Brew not found`, + + '71' : dedent`Lock Set Review Error: Review already requested`, + + '72' : dedent`Lock Remove Review Error: Cannot clear review request`, + + '73' : dedent`Lock Remove Review Error: Brew not found`, + + // ####### Other Errors + '90' : dedent` An unexpected error occurred while looking for these brews. Try again in a few minutes.`, diff --git a/client/homebrew/pages/homePage/homePage.jsx b/client/homebrew/pages/homePage/homePage.jsx index 00d0c801d..d03e30c91 100644 --- a/client/homebrew/pages/homePage/homePage.jsx +++ b/client/homebrew/pages/homePage/homePage.jsx @@ -100,32 +100,32 @@ const HomePage = createClass({ return
    {this.renderNavbar()} -
    - - - - +
    + + + +
    Save current diff --git a/client/homebrew/pages/homePage/homePage.less b/client/homebrew/pages/homePage/homePage.less index a7523bd3c..4cf9ff4fe 100644 --- a/client/homebrew/pages/homePage/homePage.less +++ b/client/homebrew/pages/homePage/homePage.less @@ -1,50 +1,40 @@ -.homePage{ +.homePage { position : relative; - a.floatingNewButton{ + a.floatingNewButton { .animate(background-color); position : absolute; - display : block; right : 70px; bottom : 50px; - z-index : 100; z-index : 5001; + display : block; padding : 1em; - background-color : @orange; font-size : 1.5em; color : white; text-decoration : none; + background-color : @orange; box-shadow : 3px 3px 15px black; - &:hover{ - background-color : darken(@orange, 20%); - } + &:hover { background-color : darken(@orange, 20%); } } - .floatingSaveButton{ + .floatingSaveButton { .animateAll(); position : absolute; - display : block; right : 200px; bottom : 70px; - z-index : 100; z-index : 5000; + display : block; padding : 0.8em; - cursor : pointer; - background-color : @blue; font-size : 0.8em; color : white; text-decoration : none; + cursor : pointer; + background-color : @blue; box-shadow : 3px 3px 15px black; - &:hover{ - background-color : darken(@blue, 20%); - } - &.show{ - right : 350px; - } + &:hover { background-color : darken(@blue, 20%); } + &.show { right : 350px; } } - .navItem.save{ - background-color: @orange; - &:hover{ - background-color: @green; - } + .navItem.save { + background-color : @orange; + &:hover { background-color : @green; } } } diff --git a/client/homebrew/pages/newPage/newPage.jsx b/client/homebrew/pages/newPage/newPage.jsx index ee2c67d5f..3e4e5ce41 100644 --- a/client/homebrew/pages/newPage/newPage.jsx +++ b/client/homebrew/pages/newPage/newPage.jsx @@ -141,6 +141,18 @@ const NewPage = createClass({ localStorage.setItem(STYLEKEY, style); }, + handleSnipChange : function(snippet){ + //If there are errors, run the validator on every change to give quick feedback + let htmlErrors = this.state.htmlErrors; + if(htmlErrors.length) htmlErrors = Markdown.validate(snippet); + + this.setState((prevState)=>({ + brew : { ...prevState.brew, snippets: snippet }, + isPending : true, + htmlErrors : htmlErrors, + }), ()=>{if(this.state.autoSave) this.trySave();}); + }, + handleMetaChange : function(metadata, field=undefined){ if(field == 'theme' || field == 'renderer') // Fetch theme bundle only if theme or renderer was changed fetchThemeBundle(this, metadata.renderer, metadata.theme); @@ -223,38 +235,39 @@ const NewPage = createClass({ render : function(){ return
    {this.renderNavbar()} -
    - - - - +
    + + + +
    ; } diff --git a/client/homebrew/pages/newPage/newPage.less b/client/homebrew/pages/newPage/newPage.less index f83827ffb..ebc44d543 100644 --- a/client/homebrew/pages/newPage/newPage.less +++ b/client/homebrew/pages/newPage/newPage.less @@ -1,8 +1,6 @@ -.newPage{ - .navItem.save{ - background-color: @orange; - &:hover{ - background-color: @green; - } +.newPage { + .navItem.save { + background-color : @orange; + &:hover { background-color : @green; } } } diff --git a/client/homebrew/pages/sharePage/sharePage.jsx b/client/homebrew/pages/sharePage/sharePage.jsx index 15eae54f7..e9c5540a2 100644 --- a/client/homebrew/pages/sharePage/sharePage.jsx +++ b/client/homebrew/pages/sharePage/sharePage.jsx @@ -23,7 +23,9 @@ const SharePage = (props)=>{ }); const handleBrewRendererPageChange = useCallback((pageNumber)=>{ - updateState({ currentBrewRendererPageNum: pageNumber }); + setState((prevState)=>({ + currentBrewRendererPageNum : pageNumber, + ...prevState })); }, []); const handleControlKeys = (e)=>{ diff --git a/client/homebrew/pages/sharePage/sharePage.less b/client/homebrew/pages/sharePage/sharePage.less index 754108506..b76dc50f9 100644 --- a/client/homebrew/pages/sharePage/sharePage.less +++ b/client/homebrew/pages/sharePage/sharePage.less @@ -1,9 +1,7 @@ -.sharePage{ +.sharePage { nav .navSection.titleSection { - flex-grow: 1; - justify-content: center; - } - .content{ - overflow-y : hidden; + flex-grow : 1; + justify-content : center; } + .content { overflow-y : hidden; } } diff --git a/client/homebrew/pages/vaultPage/vaultPage.jsx b/client/homebrew/pages/vaultPage/vaultPage.jsx index 21a8e8363..f979aa4f7 100644 --- a/client/homebrew/pages/vaultPage/vaultPage.jsx +++ b/client/homebrew/pages/vaultPage/vaultPage.jsx @@ -99,14 +99,14 @@ const VaultPage = (props)=>{ setSearching(true); setError(null); - const title = titleRef.current.value || ''; - const author = authorRef.current.value || ''; - const count = countRef.current.value || 10; - const v3 = v3Ref.current.checked != false; - const legacy = legacyRef.current.checked != false; + const title = titleRef.current.value || ''; + const author = authorRef.current.value || ''; + const count = countRef.current.value || 10; + const v3 = v3Ref.current.checked != false; + const legacy = legacyRef.current.checked != false; const sortOption = sort || 'title'; - const dirOption = dir || 'asc'; - const pageProp = page || 1; + const dirOption = dir || 'asc'; + const pageProp = page || 1; setSort(sortOption); setdir(dirOption); @@ -247,7 +247,7 @@ const VaultPage = (props)=>{
  • Some common words like "a", "after", "through", "itself", "here", etc., - are ignored in searches. The full list can be found   + are ignored in searches. The full list can be found  here @@ -286,9 +286,9 @@ const VaultPage = (props)=>{ }; const renderPaginationControls = ()=>{ - if(!totalBrews) return null; + if(!totalBrews || totalBrews < 10) return null; - const countInt = parseInt(props.query.count || 20); + const countInt = parseInt(brewCollection.length || 20); const totalPages = Math.ceil(totalBrews / countInt); let startPage, endPage; @@ -355,7 +355,7 @@ const VaultPage = (props)=>{ }; const renderFoundBrews = ()=>{ - if(searching) { + if(searching && !brewCollection) { return (

    Searching

    @@ -395,6 +395,7 @@ const VaultPage = (props)=>{ {`Brews found: `} {totalBrews} + {brewCollection.length > 10 && renderPaginationControls()} {brewCollection.map((brew, index)=>{ return ( { {renderNavItems()} -
    - -
    {renderForm()}
    -
    - {renderSortBar()} - {renderFoundBrews()} -
    -
    +
    + +
    {renderForm()}
    +
    + {renderSortBar()} + {renderFoundBrews()} +
    +
    ); diff --git a/client/homebrew/pages/vaultPage/vaultPage.less b/client/homebrew/pages/vaultPage/vaultPage.less index a69bcb33b..8a5f3a714 100644 --- a/client/homebrew/pages/vaultPage/vaultPage.less +++ b/client/homebrew/pages/vaultPage/vaultPage.less @@ -5,7 +5,7 @@ *:not(input) { user-select : none; } - .content .dataGroup { + :where(.content .dataGroup) { width : 100%; height : 100%; background : white; @@ -169,9 +169,10 @@ width : 100%; height : 100%; max-height : 100%; - padding : 50px 50px 70px 50px; + padding : 70px 50px; overflow-y : scroll; background-color : #2C3E50; + container-type : inline-size; h3 { font-size : 25px; } @@ -236,6 +237,7 @@ margin-right : 40px; color : black; isolation : isolate; + transition : width 0.5s; &::after { position : absolute; @@ -269,8 +271,8 @@ .links { z-index : 2; } hr { - margin : 0px; visibility : hidden; + margin : 0px; } .thumbnail { z-index : -1; } @@ -278,30 +280,37 @@ .paginationControls { position : absolute; + top : 35px; left : 50%; display : grid; grid-template-areas : 'previousPage currentPage nextPage'; grid-template-columns : 50px 1fr 50px; + gap : 20px; place-items : center; width : auto; + font-size : 15px; translate : -50%; + &:last-child { top : unset; } + .pages { display : flex; grid-area : currentPage; + gap : 1em; justify-content : space-evenly; width : 100%; height : 100%; - padding : 5px 8px; text-align : center; .pageNumber { - margin-inline : 1vw; + place-content : center; + width : fit-content; + min-width : 2em; font-family : 'Open Sans'; font-weight : 900; color : white; - text-underline-position : under; text-wrap : nowrap; + text-underline-position : under; cursor : pointer; &.currentPage { @@ -329,7 +338,6 @@ } } } - } @keyframes trailingDots { @@ -344,8 +352,7 @@ 100% { content : ' ...'; } } -// media query for when the page is smaller than 1079 px in width -@media screen and (max-width : 1079px) { +@container (width < 670px) { .vaultPage { .dataGroup.form .brewLookup { padding : 1px 20px 20px 10px; } diff --git a/client/icons/customIcons.less b/client/icons/customIcons.less index 1c8d1bd47..a2caffc57 100644 --- a/client/icons/customIcons.less +++ b/client/icons/customIcons.less @@ -1,84 +1,34 @@ .fac { display : inline-block; - background-color : currentColor; - mask-size : contain; - mask-repeat : no-repeat; - mask-position : center; width : 1em; aspect-ratio : 1; + background-color : currentColor; + mask-repeat : no-repeat; + mask-position : center; + mask-size : contain; } -.position-top-left { - mask-image: url('../icons/position-top-left.svg'); -} -.position-top-right { - mask-image: url('../icons/position-top-right.svg'); -} -.position-bottom-left { - mask-image: url('../icons/position-bottom-left.svg'); -} -.position-bottom-right { - mask-image: url('../icons/position-bottom-right.svg'); -} -.position-top { - mask-image: url('../icons/position-top.svg'); -} -.position-right { - mask-image: url('../icons/position-right.svg'); -} -.position-bottom { - mask-image: url('../icons/position-bottom.svg'); -} -.position-left { - mask-image: url('../icons/position-left.svg'); -} -.mask-edge { - mask-image: url('../icons/mask-edge.svg'); -} -.mask-corner { - mask-image: url('../icons/mask-corner.svg'); -} -.mask-center { - mask-image: url('../icons/mask-center.svg'); -} -.book-front-cover { - mask-image: url('../icons/book-front-cover.svg'); -} -.book-back-cover { - mask-image: url('../icons/book-back-cover.svg'); -} -.book-inside-cover { - mask-image: url('../icons/book-inside-cover.svg'); -} -.book-part-cover { - mask-image: url('../icons/book-part-cover.svg'); -} -.image-wrap-left { - mask-image: url('../icons/image-wrap-left.svg'); -} -.image-wrap-right { - mask-image: url('../icons/image-wrap-right.svg'); -} -.davek { - mask-image: url('../icons/Davek.svg'); -} -.rellanic { - mask-image: url('../icons/Rellanic.svg'); -} -.iokharic { - mask-image: url('../icons/Iokharic.svg'); -} -.zoom-to-fit { - mask-image: url('../icons/zoom-to-fit.svg'); -} -.fit-width { - mask-image: url('../icons/fit-width.svg'); -} -.single-spread { - mask-image: url('../icons/single-spread.svg'); -} -.facing-spread { - mask-image: url('../icons/facing-spread.svg'); -} -.flow-spread { - mask-image: url('../icons/flow-spread.svg'); -} +.position-top-left { mask-image : url('../icons/position-top-left.svg'); } +.position-top-right { mask-image : url('../icons/position-top-right.svg'); } +.position-bottom-left { mask-image : url('../icons/position-bottom-left.svg'); } +.position-bottom-right { mask-image : url('../icons/position-bottom-right.svg'); } +.position-top { mask-image : url('../icons/position-top.svg'); } +.position-right { mask-image : url('../icons/position-right.svg'); } +.position-bottom { mask-image : url('../icons/position-bottom.svg'); } +.position-left { mask-image : url('../icons/position-left.svg'); } +.mask-edge { mask-image : url('../icons/mask-edge.svg'); } +.mask-corner { mask-image : url('../icons/mask-corner.svg'); } +.mask-center { mask-image : url('../icons/mask-center.svg'); } +.book-front-cover { mask-image : url('../icons/book-front-cover.svg'); } +.book-back-cover { mask-image : url('../icons/book-back-cover.svg'); } +.book-inside-cover { mask-image : url('../icons/book-inside-cover.svg'); } +.book-part-cover { mask-image : url('../icons/book-part-cover.svg'); } +.image-wrap-left { mask-image : url('../icons/image-wrap-left.svg'); } +.image-wrap-right { mask-image : url('../icons/image-wrap-right.svg'); } +.davek { mask-image : url('../icons/Davek.svg'); } +.rellanic { mask-image : url('../icons/Rellanic.svg'); } +.iokharic { mask-image : url('../icons/Iokharic.svg'); } +.zoom-to-fit { mask-image : url('../icons/zoom-to-fit.svg'); } +.fit-width { mask-image : url('../icons/fit-width.svg'); } +.single-spread { mask-image : url('../icons/single-spread.svg'); } +.facing-spread { mask-image : url('../icons/facing-spread.svg'); } +.flow-spread { mask-image : url('../icons/flow-spread.svg'); } diff --git a/install/README.UBUNTU.md b/install/README.UBUNTU.md index d14cfef46..13fa7631d 100644 --- a/install/README.UBUNTU.md +++ b/install/README.UBUNTU.md @@ -24,12 +24,16 @@ These instructions assume that you are installing to a completely new, fresh Ubu These installation instructions have been tested on the following Ubuntu releases: -- *ubuntu-20.04.3-desktop-amd64* + - *ubuntu-24.04.1-desktop-amd64* + - *ubuntu-22.04.5-desktop-amd64* + - *ubuntu-20.04.6-desktop-amd64* ## Final Notes While this installation process works successfully at the time of writing (December 19, 2021), it relies on all of the Node.JS packages used in the HomeBrewery project retaining their cross-platform capabilities to continue to function. This is one of the inherent advantages of Node.JS, but it is by no means guaranteed and as such, functionality or even installation may fail without warning at some point in the future. +Earlier versions of Ubuntu may requier an alternate Mongo setup, see https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-ubuntu/ for assistance. + Regards, G December 19, 2021 diff --git a/install/ubuntu/etc/systemd/system/homebrewery.service b/install/ubuntu/etc/systemd/system/homebrewery.service index 939d11fb8..defa85a31 100644 --- a/install/ubuntu/etc/systemd/system/homebrewery.service +++ b/install/ubuntu/etc/systemd/system/homebrewery.service @@ -3,7 +3,8 @@ Description=Homebrewery Web Server [Service] User=root -After=mongodb +BindsTo=mongod.service +After=mongod.service Environment=NODE_ENV=local WorkingDirectory=/usr/local/homebrewery ExecStart=node server.js diff --git a/install/ubuntu/install.sh b/install/ubuntu/install.sh index ebad7f3f2..a0fcc8c17 100644 --- a/install/ubuntu/install.sh +++ b/install/ubuntu/install.sh @@ -1,14 +1,60 @@ #!/bin/sh +# Detect Ubuntu Version +export DISTRO=$(grep "^NAME=" /etc/os-release | awk -F '=' '{print $2}' | sed 's/"//g') +export DISTRO_VER=$(grep "VERSION_ID=" /etc/os-release | awk -F '=' '{print $2}' | sed 's/"//g') +export MATCHED="Yes" + +if [ "${DISTRO}" != "Ubuntu" ]; +then + echo :: Ubuntu not detected. Are you using an alternate spin or derivative? + echo :: Detected - ${DISTRO} + read -p [y/N] YESNO + if [ "${YESNO}" != "Y" ] && [ ]"${YESNO}" != "y" ]; then + exit + fi + + MATCHED="No" +fi + # Install CURL and add required NodeJS source to package repo echo ::Install CURL apt install -y curl echo ::Add NodeJS source to package repo -curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash - +curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - +# Add Mongo CE Source +if [ ${DISTRO} = "Ubuntu" ]; + then + echo ::Add Mongo CE source to package repo + curl -fsSL https://www.mongodb.org/static/pgp/server-8.0.asc | \ + sudo gpg -o /usr/share/keyrings/mongodb-server-8.0.gpg \ + --dearmor + if [ "${DISTRO_VER}" == "24.04" ]; then + echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg ] https://repo.mongodb.org/apt/ubuntu noble/mongodb-org/8.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-8.0.list + elif [ "${DISTRO_VER}" == "22.04" ]; then + echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/8.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-8.0.list + elif [ "${DISTRO_VER}" == "20.04" ]; then + echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/8.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-8.0.list + else + MATCHED="No" + fi + sudo apt-get update +fi + +if [ ${MATCHED} == "No" ]; then + echo :: WARNING + echo :: Unable to determine Ubuntu version for Mongo installation purposes. + echo :: Please check your spin/distro documentation to install Mongo CE and enable it on startup. +fi + # Install required packages echo ::Install Homebrewery requirements -apt satisfy -y git nodejs npm mongodb +apt satisfy -y git nodejs npm mongodb-org + +# Enable and start Mongo +systemctl enable mongod +systemctl start mongod # Clone Homebrewery repo echo ::Get Homebrewery files diff --git a/package-lock.json b/package-lock.json index da1759b43..71d2693a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,76 +1,79 @@ { "name": "homebrewery", - "version": "3.16.1", + "version": "3.18.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "homebrewery", - "version": "3.16.1", + "version": "3.18.1", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.26.0", - "@babel/plugin-transform-runtime": "^7.25.9", - "@babel/preset-env": "^7.26.0", + "@babel/core": "^7.26.10", + "@babel/plugin-transform-runtime": "^7.26.10", + "@babel/preset-env": "^7.26.9", "@babel/preset-react": "^7.26.3", - "@googleapis/drive": "^8.14.0", - "body-parser": "^1.20.2", + "@googleapis/drive": "^11.0.0", + "body-parser": "^2.2.0", "classnames": "^2.5.1", "codemirror": "^5.65.6", "cookie-parser": "^1.4.7", - "core-js": "^3.39.0", + "core-js": "^3.41.0", "cors": "^2.8.5", "create-react-class": "^15.7.0", "dedent-tabs": "^0.10.3", - "dompurify": "^3.2.3", "expr-eval": "^2.0.2", - "express": "^4.21.2", + "express": "^5.1.0", "express-async-handler": "^1.2.0", "express-static-gzip": "2.2.0", - "fs-extra": "11.2.0", + "fs-extra": "11.3.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.3", - "marked-extended-tables": "^1.1.0", - "marked-gfm-heading-id": "^3.2.0", - "marked-smartypants-lite": "^1.0.2", + "marked": "15.0.8", + "marked-emoji": "^2.0.0", + "marked-extended-tables": "^2.0.1", + "marked-gfm-heading-id": "^4.0.1", + "marked-nonbreaking-spaces": "^1.0.1", + "marked-smartypants-lite": "^1.0.3", + "marked-subsuper-text": "^1.0.3", "markedLegacy": "npm:marked@^0.3.19", "moment": "^2.30.1", - "mongoose": "^8.9.3", - "nanoid": "5.0.9", + "mongoose": "^8.13.2", + "nanoid": "5.1.5", "nconf": "^0.12.1", "react": "^18.3.1", "react-dom": "^18.3.1", "react-frame-component": "^4.1.3", - "react-router": "^7.1.1", + "react-router": "^7.5.0", + "romans": "^3.0.0", "sanitize-filename": "1.6.3", - "superagent": "^10.1.1", - "vitreum": "git+https://git@github.com/calculuschild/vitreum.git" + "superagent": "^10.2.0", + "vitreum": "git+https://git@github.com/calculuschild/vitreum.git", + "written-number": "^0.11.1" }, "devDependencies": { - "@stylistic/stylelint-plugin": "^3.1.1", + "@stylistic/stylelint-plugin": "^3.1.2", "babel-plugin-transform-import-meta": "^2.3.2", - "eslint": "^9.17.0", - "eslint-plugin-jest": "^28.10.0", - "eslint-plugin-react": "^7.37.3", - "globals": "^15.14.0", + "eslint": "^9.24.0", + "eslint-plugin-jest": "^28.11.0", + "eslint-plugin-react": "^7.37.5", + "globals": "^16.0.0", "jest": "^29.7.0", "jest-expect-message": "^1.1.3", "jsdom-global": "^3.0.2", "postcss-less": "^6.0.0", - "stylelint": "^16.12.0", - "stylelint-config-recess-order": "^5.1.1", - "stylelint-config-recommended": "^14.0.1", - "supertest": "^7.0.0" + "stylelint": "^16.18.0", + "stylelint-config-recess-order": "^6.0.0", + "stylelint-config-recommended": "^16.0.0", + "supertest": "^7.1.0" }, "engines": { "node": "^20.18.x", - "npm": "^10.2.x" + "npm": "^10.8.x" } }, "node_modules/@ampproject/remapping": { @@ -87,9 +90,10 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.0.tgz", - "integrity": "sha512-INCKxTtbXtcNbUZ3YXutwMpEleqttcswhAdee7dhuoVrD2cnuc3PqtERBtxkX5nziX9vnBL8WXmSGwv8CuPV6g==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", @@ -100,28 +104,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.0.tgz", - "integrity": "sha512-qETICbZSLe7uXv9VE8T/RWOdIE5qqyTucOt4zLYMafj2MRO271VGgLd4RACJMeBO37UPWhXiKMBk7YlJ0fOzQA==", + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", + "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", - "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.26.0", - "@babel/generator": "^7.26.0", - "@babel/helper-compilation-targets": "^7.25.9", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.0", - "@babel/parser": "^7.26.0", - "@babel/template": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -137,12 +143,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.0.tgz", - "integrity": "sha512-/AIkAmInnWwgEAJGQr9vY0c66Mj6kjkE2ZPB1PurTRaRAh3U+J45sAQMjQDJdh4WbR3l0x5xkimXBKyBXXAu2w==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.10.tgz", + "integrity": "sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==", + "license": "MIT", "dependencies": { - "@babel/parser": "^7.26.0", - "@babel/types": "^7.26.0", + "@babel/parser": "^7.26.10", + "@babel/types": "^7.26.10", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" @@ -162,24 +169,13 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.25.9.tgz", - "integrity": "sha512-C47lC7LIDCnz0h4vai/tpNOI95tCd5ZT3iBt/DBH5lXKHZsyNQv18yf1wIIg2ntiQNgmAvA+DgZ82iW8Qdym8g==", - "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", - "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", + "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.25.9", + "@babel/compat-data": "^7.26.5", "@babel/helper-validator-option": "^7.25.9", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", @@ -226,9 +222,9 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", - "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz", + "integrity": "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==", "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", @@ -293,9 +289,10 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", - "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", + "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -332,18 +329,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-simple-access": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.9.tgz", - "integrity": "sha512-c6WHXuiaRsJTyHYLJV75t9IqsmTbItYfdj99PnzYGQZkYKvan5/2jKJ7gu31J3/BJ/A18grImSPModuyG/Eo0Q==", - "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", @@ -394,23 +379,25 @@ } }, "node_modules/@babel/helpers": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", - "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", + "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", + "license": "MIT", "dependencies": { - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.0" + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.1.tgz", - "integrity": "sha512-reoQYNiAJreZNsJzyrDNzFQ+IQ5JFiIzAHJg9bn94S3l+4++J7RsIhNMoB+lgP/9tpmiAQqspv+xfdxTSzREOw==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.10.tgz", + "integrity": "sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.26.0" + "@babel/types": "^7.26.10" }, "bin": { "parser": "bin/babel-parser.js" @@ -562,7 +549,6 @@ "version": "7.26.0", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", - "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -754,13 +740,14 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.9.tgz", - "integrity": "sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==", + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.26.8.tgz", + "integrity": "sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-plugin-utils": "^7.26.5", "@babel/helper-remap-async-to-generator": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/traverse": "^7.26.8" }, "engines": { "node": ">=6.9.0" @@ -786,11 +773,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.25.9.tgz", - "integrity": "sha512-toHc9fzab0ZfenFpsyYinOX0J/5dgJVA2fm64xPewu7CoYHWEivIWKxkK2rMi4r3yQqLnVmheMXRdG+k239CgA==", + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz", + "integrity": "sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.26.5" }, "engines": { "node": ">=6.9.0" @@ -958,11 +946,11 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.25.9.tgz", - "integrity": "sha512-KRhdhlVk2nObA5AYa7QMgTMTVJdfHprfpAk4DjZVtllqRg9qarilstTKEhpVjyt+Npi8ThRyiV8176Am3CodPA==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", + "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", + "license": "MIT", "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { @@ -987,11 +975,12 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz", - "integrity": "sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.26.9.tgz", + "integrity": "sha512-Hry8AusVm8LW5BVFgiyUReuoGzPUpdHQQqJY5bZnbbf+ngOHWuCuYFKw/BqaaWlvEUrF91HMhDtEaI1hZzNbLg==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-plugin-utils": "^7.26.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "engines": { @@ -1089,13 +1078,13 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.25.9.tgz", - "integrity": "sha512-dwh2Ol1jWwL2MgkCzUSOvfmKElqQcuswAZypBSUsScMXvgdT8Ekq5YA6TtqpTVWH+4903NmboMuH1o9i8Rxlyg==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", + "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-simple-access": "^7.25.9" + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1166,11 +1155,12 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.25.9.tgz", - "integrity": "sha512-ENfftpLZw5EItALAD4WsY/KUWvhUlZndm5GC7G3evUsVeSJB6p0pBeLQUnRnBCBx7zV0RKQjR9kCuwrsIrjWog==", + "version": "7.26.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz", + "integrity": "sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.26.5" }, "engines": { "node": ">=6.9.0" @@ -1418,14 +1408,15 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.25.9.tgz", - "integrity": "sha512-nZp7GlEl+yULJrClz0SwHPqir3lc0zsPrDHQUcxGspSL7AKrexNSEfTbfqnDNJUO13bgKyfuOLMF8Xqtu8j3YQ==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.26.10.tgz", + "integrity": "sha512-NWaL2qG6HRpONTnj4JvDU6th4jYeZOJgu3QhmFTCihib0ermtOJqktA5BduGm3suhhVe9EMP9c9+mfJ/I9slqw==", + "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-plugin-utils": "^7.26.5", "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.6", + "babel-plugin-polyfill-corejs3": "^0.11.0", "babel-plugin-polyfill-regenerator": "^0.6.1", "semver": "^6.3.1" }, @@ -1480,11 +1471,12 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz", - "integrity": "sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==", + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.26.8.tgz", + "integrity": "sha512-OmGDL5/J0CJPJZTHZbi2XpO0tyT2Ia7fzpW5GURwdtp2X3fMmN8au/ej6peC/T33/+CRiIpA8Krse8hFGVmT5Q==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.26.5" }, "engines": { "node": ">=6.9.0" @@ -1494,11 +1486,12 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.9.tgz", - "integrity": "sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==", + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.26.7.tgz", + "integrity": "sha512-jfoTXXZTgGg36BmhqT3cAYK5qkmqvJpvNrPhaK/52Vgjhw4Rq29s9UqpWWV0D6yuRmgiFH/BUVlkl96zJWqnaw==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.26.5" }, "engines": { "node": ">=6.9.0" @@ -1567,13 +1560,14 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.0.tgz", - "integrity": "sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.9.tgz", + "integrity": "sha512-vX3qPGE8sEKEAZCWk05k3cpTAE3/nOYca++JA+Rd0z2NCNzabmYvEiSShKzm10zdquOIAVXsy2Ei/DTW34KlKQ==", + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.26.0", - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", + "@babel/compat-data": "^7.26.8", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-plugin-utils": "^7.26.5", "@babel/helper-validator-option": "^7.25.9", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", @@ -1585,9 +1579,9 @@ "@babel/plugin-syntax-import-attributes": "^7.26.0", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.25.9", - "@babel/plugin-transform-async-generator-functions": "^7.25.9", + "@babel/plugin-transform-async-generator-functions": "^7.26.8", "@babel/plugin-transform-async-to-generator": "^7.25.9", - "@babel/plugin-transform-block-scoped-functions": "^7.25.9", + "@babel/plugin-transform-block-scoped-functions": "^7.26.5", "@babel/plugin-transform-block-scoping": "^7.25.9", "@babel/plugin-transform-class-properties": "^7.25.9", "@babel/plugin-transform-class-static-block": "^7.26.0", @@ -1598,21 +1592,21 @@ "@babel/plugin-transform-duplicate-keys": "^7.25.9", "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", "@babel/plugin-transform-dynamic-import": "^7.25.9", - "@babel/plugin-transform-exponentiation-operator": "^7.25.9", + "@babel/plugin-transform-exponentiation-operator": "^7.26.3", "@babel/plugin-transform-export-namespace-from": "^7.25.9", - "@babel/plugin-transform-for-of": "^7.25.9", + "@babel/plugin-transform-for-of": "^7.26.9", "@babel/plugin-transform-function-name": "^7.25.9", "@babel/plugin-transform-json-strings": "^7.25.9", "@babel/plugin-transform-literals": "^7.25.9", "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", "@babel/plugin-transform-member-expression-literals": "^7.25.9", "@babel/plugin-transform-modules-amd": "^7.25.9", - "@babel/plugin-transform-modules-commonjs": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.26.3", "@babel/plugin-transform-modules-systemjs": "^7.25.9", "@babel/plugin-transform-modules-umd": "^7.25.9", "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", "@babel/plugin-transform-new-target": "^7.25.9", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.25.9", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.26.6", "@babel/plugin-transform-numeric-separator": "^7.25.9", "@babel/plugin-transform-object-rest-spread": "^7.25.9", "@babel/plugin-transform-object-super": "^7.25.9", @@ -1628,17 +1622,17 @@ "@babel/plugin-transform-shorthand-properties": "^7.25.9", "@babel/plugin-transform-spread": "^7.25.9", "@babel/plugin-transform-sticky-regex": "^7.25.9", - "@babel/plugin-transform-template-literals": "^7.25.9", - "@babel/plugin-transform-typeof-symbol": "^7.25.9", + "@babel/plugin-transform-template-literals": "^7.26.8", + "@babel/plugin-transform-typeof-symbol": "^7.26.7", "@babel/plugin-transform-unicode-escapes": "^7.25.9", "@babel/plugin-transform-unicode-property-regex": "^7.25.9", "@babel/plugin-transform-unicode-regex": "^7.25.9", "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", "@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-corejs3": "^0.11.0", "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.38.1", + "core-js-compat": "^3.40.0", "semver": "^6.3.1" }, "engines": { @@ -1683,9 +1677,10 @@ } }, "node_modules/@babel/runtime": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.9.tgz", - "integrity": "sha512-4zpTHZ9Cm6L9L+uIqghQX8ZXg8HKFcjYO3qHoO8zTmRm6HQUJ8SSJ+KRvbMBZn0EGVlT4DRYeQ/6hjlyXBh+Kg==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -1694,28 +1689,30 @@ } }, "node_modules/@babel/template": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", - "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", + "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.26.9", + "@babel/types": "^7.26.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", - "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.10.tgz", + "integrity": "sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A==", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/generator": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/template": "^7.25.9", - "@babel/types": "^7.25.9", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/types": "^7.26.10", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -1733,9 +1730,10 @@ } }, "node_modules/@babel/types": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", - "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz", + "integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==", + "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" @@ -1854,12 +1852,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.0.tgz", - "integrity": "sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==", + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", + "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.4", + "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -1867,20 +1866,35 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/core": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.0.tgz", - "integrity": "sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==", + "node_modules/@eslint/config-helpers": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz", + "integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==", "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", + "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/eslintrc": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", - "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -1904,6 +1918,7 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -1912,39 +1927,57 @@ } }, "node_modules/@eslint/js": { - "version": "9.17.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz", - "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==", + "version": "9.24.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.24.0.tgz", + "integrity": "sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA==", "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/object-schema": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", - "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", "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.3", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz", - "integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==", + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", + "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", "dev": true, + "license": "Apache-2.0", "dependencies": { + "@eslint/core": "^0.13.0", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", + "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@googleapis/drive": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@googleapis/drive/-/drive-8.14.0.tgz", - "integrity": "sha512-AOokfpP6pCdcJXWA8khaCEgbGpWYavWTdAAhL4idbbf2VCQcJ2f7vPalAYNu6a4Sfj0Ly4Ehnd1xw9J9TixB1A==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@googleapis/drive/-/drive-11.0.0.tgz", + "integrity": "sha512-vhkl6/MZ8k5h5XOyenWOD4ys+SNdb8wKYvzU6OBGFx/TzJyHRm4JwgvE8uVDFU6efzNRS0mOiNRfY6nrmHOTtg==", + "license": "Apache-2.0", "dependencies": { "googleapis-common": "^7.0.0" }, @@ -2002,10 +2035,11 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", - "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", + "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=18.18" }, @@ -2671,10 +2705,46 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@keyv/serialize": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.0.3.tgz", + "integrity": "sha512-qnEovoOp5Np2JDGonIDL6Ayihw0RhnRh6vxPuHo4RDn1UOzwEo4AeIfpL6UGIrsceWrCMiVPgwRjbHu4vYFc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3" + } + }, + "node_modules/@keyv/serialize/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/@mongodb-js/saslprep": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz", - "integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.2.2.tgz", + "integrity": "sha512-EB0O3SCSNRUFk66iRCpI+cXzIjdswfCs7F6nOC3RAGJ7xr5YhaicvsRwJ9eyzYvYRlCSDUO/c7g4yNulxKC1WA==", + "license": "MIT", "dependencies": { "sparse-bitfield": "^3.0.3" } @@ -2745,10 +2815,11 @@ } }, "node_modules/@stylistic/stylelint-plugin": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@stylistic/stylelint-plugin/-/stylelint-plugin-3.1.1.tgz", - "integrity": "sha512-XagAHHIa528EvyGybv8EEYGK5zrVW74cHpsjhtovVATbhDRuJYfE+X4HCaAieW9lCkwbX6L+X0I4CiUG3w/hFw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@stylistic/stylelint-plugin/-/stylelint-plugin-3.1.2.tgz", + "integrity": "sha512-tylFJGMQo62alGazK74MNxFjMagYOHmBZiePZFOJK2n13JZta0uVkB3Bh5qodUmOLtRH+uxH297EibK14UKm8g==", "dev": true, + "license": "MIT", "dependencies": { "@csstools/css-parser-algorithms": "^3.0.1", "@csstools/css-tokenizer": "^3.0.1", @@ -2864,7 +2935,8 @@ "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 + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { "version": "22.1.0", @@ -2883,21 +2955,17 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "optional": true - }, "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==" + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" }, "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": "*" } @@ -3061,23 +3129,45 @@ } }, "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" }, "engines": { "node": ">= 0.6" } }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -3090,6 +3180,7 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -3143,6 +3234,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -3183,6 +3275,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -3244,6 +3337,7 @@ "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" @@ -3255,12 +3349,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, "node_modules/array-includes": { "version": "3.1.8", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", @@ -3346,6 +3434,7 @@ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", @@ -3381,6 +3470,7 @@ "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", "dev": true, + "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", @@ -3444,6 +3534,7 @@ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -3498,6 +3589,7 @@ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, + "license": "MIT", "dependencies": { "possible-typed-array-names": "^1.0.0" }, @@ -3635,12 +3727,13 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.10.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", - "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", + "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", + "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.2", - "core-js-compat": "^3.38.0" + "@babel/helper-define-polyfill-provider": "^0.6.3", + "core-js-compat": "^3.40.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -3663,6 +3756,7 @@ "resolved": "https://registry.npmjs.org/babel-plugin-transform-import-meta/-/babel-plugin-transform-import-meta-2.3.2.tgz", "integrity": "sha512-902o4GiQqI1GqAXfD5rEoz0PJamUfJ3VllpdWaNsFTwdaNjFSFHawvBO+cp5K2j+g2h3bZ4lnM1Xb6yFYGihtA==", "dev": true, + "license": "BSD", "dependencies": { "@babel/template": "^7.25.9", "tslib": "^2.8.1" @@ -3809,16 +3903,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, "node_modules/bn.js": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", @@ -3826,43 +3910,25 @@ "license": "MIT" }, "node_modules/body-parser": { - "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", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", "license": "MIT", "dependencies": { - "ms": "2.0.0" + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" } }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -4083,9 +4149,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", - "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", "funding": [ { "type": "opencollective", @@ -4100,11 +4166,12 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001663", - "electron-to-chromium": "^1.5.28", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.0" + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -4124,9 +4191,10 @@ } }, "node_modules/bson": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.1.tgz", - "integrity": "sha512-P92xmHDQjSKPLHqFxefqMxASNq/aWJMEZugpCjf+AF/pgcUpMMQCg7t7+ewko0/u8AapvF3luf/FoehddEK+sA==", + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.3.tgz", + "integrity": "sha512-MTxGsqgYTwfshYWTRdmZRC+M7FnG1b4y7RO7p2k3X24Wq0yv1m77Wsj0BzlPzd/IowgESfsruQCUToa7vbOpPQ==", + "license": "Apache-2.0", "engines": { "node": ">=16.20.1" } @@ -4194,6 +4262,27 @@ "node": ">=0.10.0" } }, + "node_modules/cacheable": { + "version": "1.8.10", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.8.10.tgz", + "integrity": "sha512-0ZnbicB/N2R6uziva8l6O6BieBklArWyiGx4GkwAhLKhSHyQtRfM9T1nx7HHuHDKkYB/efJQhz3QJ6x/YqoZzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^1.8.1", + "keyv": "^5.3.2" + } + }, + "node_modules/cacheable/node_modules/keyv": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.3.2.tgz", + "integrity": "sha512-Lji2XRxqqa5Wg+CHLVfFKBImfJZ4pCSccu9eVWK6w4c2SDFLd8JAn1zqTuSFnsxb7ope6rMsnIHfp+eBbRBRZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.0.3" + } + }, "node_modules/cached-path-relative": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.1.0.tgz", @@ -4204,6 +4293,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", @@ -4218,9 +4308,10 @@ } }, "node_modules/call-bind-apply-helpers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", - "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -4230,12 +4321,13 @@ } }, "node_modules/call-bound": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", - "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "get-intrinsic": "^1.2.6" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" @@ -4265,9 +4357,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001667", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001667.tgz", - "integrity": "sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==", + "version": "1.0.30001704", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001704.tgz", + "integrity": "sha512-+L2IgBbV6gXB4ETf0keSvLr7JUrRVbIaB/lrQ1+z8mRcQiisG5k+lG6O4n6Y5q6f5EuNfaYXKgymucphlEXQew==", "funding": [ { "type": "opencollective", @@ -4281,7 +4373,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/char-regex": { "version": "1.0.2", @@ -4439,6 +4532,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -4449,7 +4543,8 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" }, "node_modules/colord": { "version": "2.9.3", @@ -4539,9 +4634,9 @@ "license": "MIT" }, "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" @@ -4619,9 +4714,9 @@ } }, "node_modules/core-js": { - "version": "3.39.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.39.0.tgz", - "integrity": "sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==", + "version": "3.41.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.41.0.tgz", + "integrity": "sha512-SJ4/EHwS36QMJd6h/Rg+GyR4A5xE0FSI3eZ+iBVpfqf1x0eTSg1smWLHrA+2jQThZSh97fmSgFSU8B61nxosxA==", "hasInstallScript": true, "license": "MIT", "funding": { @@ -4630,11 +4725,12 @@ } }, "node_modules/core-js-compat": { - "version": "3.38.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz", - "integrity": "sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==", + "version": "3.41.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.41.0.tgz", + "integrity": "sha512-RFsU9LySVue9RTwdDVX/T0e2Y6jRYWXERKElIjpuEOEnxaXffI0X7RUwVzfYLfzuLXSNJDYoRYUAmRUcyln20A==", + "license": "MIT", "dependencies": { - "browserslist": "^4.23.3" + "browserslist": "^4.24.4" }, "funding": { "type": "opencollective", @@ -4807,6 +4903,7 @@ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -4848,13 +4945,13 @@ } }, "node_modules/css-tree": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.0.1.tgz", - "integrity": "sha512-8Fxxv+tGhORlshCdCwnNJytvlvq46sOLSYEx2ZIGurahWvMucSRnyjPA3AmrMq4VPRYbHVpWj5VkiVasrM2H4Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", "dev": true, "license": "MIT", "dependencies": { - "mdn-data": "2.12.1", + "mdn-data": "2.12.2", "source-map-js": "^1.0.1" }, "engines": { @@ -4954,6 +5051,7 @@ "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", @@ -4971,6 +5069,7 @@ "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", @@ -4988,6 +5087,7 @@ "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -5001,9 +5101,10 @@ } }, "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -5277,18 +5378,11 @@ "npm": ">=1.2" } }, - "node_modules/dompurify": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.3.tgz", - "integrity": "sha512-U1U5Hzc2MO0oW3DF+G9qYN0aT7atAou4AgI0XjWz061nyBPbdxkfdhfy5uMgGn6+oLFCfn44ZGbdDqCzVmlOWA==", - "optionalDependencies": { - "@types/trusted-types": "^2.0.7" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -5323,9 +5417,10 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "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==" + "version": "1.5.118", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.118.tgz", + "integrity": "sha512-yNDUus0iultYyVoEFLnQeei7LOQkL8wg8GQpkPCRrOlJXlcCwa6eGKZkxQ9ciHsqZyYbj8Jd94X1CTPzGm+uIA==", + "license": "ISC" }, "node_modules/elliptic": { "version": "6.6.0", @@ -5426,6 +5521,7 @@ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.7.tgz", "integrity": "sha512-OygGC8kIcDhXX+6yAZRGLqwi2CmEXCbLQixeGUgYeR+Qwlppqmo7DIDr8XibtEBZp+fJcoYpoatp5qwLMEdcqQ==", "dev": true, + "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", @@ -5486,6 +5582,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -5504,6 +5601,7 @@ "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", @@ -5527,9 +5625,9 @@ } }, "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -5568,6 +5666,7 @@ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", "dev": true, + "license": "MIT", "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", @@ -5581,9 +5680,9 @@ } }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "license": "MIT", "engines": { "node": ">=6" @@ -5596,21 +5695,23 @@ "license": "MIT" }, "node_modules/eslint": { - "version": "9.17.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", - "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", + "version": "9.24.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.24.0.tgz", + "integrity": "sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.0", - "@eslint/core": "^0.9.0", - "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.17.0", - "@eslint/plugin-kit": "^0.2.3", + "@eslint/config-array": "^0.20.0", + "@eslint/config-helpers": "^0.2.0", + "@eslint/core": "^0.12.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.24.0", + "@eslint/plugin-kit": "^0.2.7", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.1", + "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", @@ -5618,7 +5719,7 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.2.0", + "eslint-scope": "^8.3.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", @@ -5655,10 +5756,11 @@ } }, "node_modules/eslint-plugin-jest": { - "version": "28.10.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.10.0.tgz", - "integrity": "sha512-hyMWUxkBH99HpXT3p8hc7REbEZK3D+nk8vHXGgpB+XXsi0gO4PxMSP+pjfUzb67GnV9yawV9a53eUmcde1CCZA==", + "version": "28.11.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.11.0.tgz", + "integrity": "sha512-QAfipLcNCWLVocVbZW8GimKn5p5iiMcgGbRzz8z/P5q7xw+cNEpYqyzFMtIF/ZgF2HLOyy+dYBut+DoYolvqig==", "dev": true, + "license": "MIT", "dependencies": { "@typescript-eslint/utils": "^6.0.0 || ^7.0.0 || ^8.0.0" }, @@ -5680,10 +5782,11 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.37.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.3.tgz", - "integrity": "sha512-DomWuTQPFYZwF/7c9W2fkKkStqZmBd3uugfqBYLdkZ3Hii23WzZuOLUskGxB8qkSKqftxEeGL1TB2kMhrce0jA==", + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", "dev": true, + "license": "MIT", "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", @@ -5695,7 +5798,7 @@ "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", - "object.entries": "^1.1.8", + "object.entries": "^1.1.9", "object.fromentries": "^2.0.8", "object.values": "^1.2.1", "prop-types": "^15.8.1", @@ -5743,10 +5846,11 @@ } }, "node_modules/eslint-scope": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", - "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -5841,6 +5945,7 @@ "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", @@ -5858,6 +5963,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -5897,6 +6003,7 @@ "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" }, @@ -6041,45 +6148,41 @@ "license": "MIT" }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "license": "MIT", "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" }, "engines": { - "node": ">= 0.10.0" + "node": ">= 18" }, "funding": { "type": "opencollective", @@ -6096,33 +6199,87 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/express-static-gzip/-/express-static-gzip-2.2.0.tgz", "integrity": "sha512-4ZQ0pHX0CAauxmzry2/8XFLM6aZA4NBvg9QezSlsEO1zLnl7vMFa48/WIcjzdfOiEUS4S1npPPKP2NHHYAp6qg==", + "license": "MIT", "dependencies": { "parseurl": "^1.3.3", "serve-static": "^1.16.2" } }, - "node_modules/express/node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "node_modules/express/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/express/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/express/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", "license": "MIT", "dependencies": { - "ms": "2.0.0" + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" } }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" + "node_modules/express/node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/express/node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } }, "node_modules/extend": { "version": "3.0.2", @@ -6194,9 +6351,9 @@ "license": "MIT" }, "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, "license": "MIT", "dependencies": { @@ -6204,7 +6361,7 @@ "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -6247,7 +6404,8 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/fastest-levenshtein": { "version": "1.0.16", @@ -6292,13 +6450,6 @@ "node": ">=16.0.0" } }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "license": "MIT", - "optional": true - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -6312,35 +6463,22 @@ } }, "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" }, "engines": { "node": ">= 0.8" } }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "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==" - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -6373,9 +6511,9 @@ } }, "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true, "license": "ISC" }, @@ -6384,6 +6522,7 @@ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", "dev": true, + "license": "MIT", "dependencies": { "is-callable": "^1.1.3" } @@ -6455,9 +6594,9 @@ } }, "node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", + "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", @@ -6502,6 +6641,7 @@ "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", @@ -6594,20 +6734,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", - "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "dunder-proto": "^1.0.0", + "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", + "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", - "math-intrinsics": "^1.0.0" + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -6626,6 +6767,19 @@ "node": ">=8.0.0" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -6644,6 +6798,7 @@ "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", @@ -6754,10 +6909,11 @@ } }, "node_modules/globals": { - "version": "15.14.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz", - "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==", + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.0.0.tgz", + "integrity": "sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -6848,6 +7004,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -6888,6 +7045,7 @@ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -6921,6 +7079,7 @@ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", "dev": true, + "license": "MIT", "dependencies": { "dunder-proto": "^1.0.0" }, @@ -6935,6 +7094,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -7075,6 +7235,13 @@ "minimalistic-crypto-utils": "^1.0.1" } }, + "node_modules/hookified": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.8.1.tgz", + "integrity": "sha512-GrO2l93P8xCWBSTBX9l2BxI78VU/MAAYag+pG8curS3aBGy0++ZlxrQ7PdUOUVMbn5BwkGb6+eRrnf43ipnFEA==", + "dev": true, + "license": "MIT" + }, "node_modules/html-encoding-sniffer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", @@ -7179,12 +7346,12 @@ } }, "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" @@ -7370,6 +7537,7 @@ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", "dev": true, + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", @@ -7405,6 +7573,7 @@ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", @@ -7429,6 +7598,7 @@ "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", "dev": true, + "license": "MIT", "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -7444,6 +7614,7 @@ "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "dev": true, + "license": "MIT", "dependencies": { "has-bigints": "^1.0.2" }, @@ -7471,6 +7642,7 @@ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.1.tgz", "integrity": "sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" @@ -7493,6 +7665,7 @@ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -7532,6 +7705,7 @@ "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", @@ -7549,6 +7723,7 @@ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" @@ -7596,6 +7771,7 @@ "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.3" }, @@ -7630,6 +7806,7 @@ "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", "dev": true, + "license": "MIT", "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -7657,6 +7834,7 @@ "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -7678,6 +7856,7 @@ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" @@ -7707,11 +7886,18 @@ "license": "MIT", "peer": true }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", @@ -7730,6 +7916,7 @@ "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -7742,6 +7929,7 @@ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.3" }, @@ -7769,6 +7957,7 @@ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" @@ -7785,6 +7974,7 @@ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "has-symbols": "^1.1.0", @@ -7802,6 +7992,7 @@ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "dev": true, + "license": "MIT", "dependencies": { "which-typed-array": "^1.1.16" }, @@ -7817,6 +8008,7 @@ "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -7829,6 +8021,7 @@ "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.0.tgz", "integrity": "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.2" }, @@ -7844,6 +8037,7 @@ "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" @@ -7874,7 +8068,8 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", @@ -8004,6 +8199,7 @@ "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.4.tgz", "integrity": "sha512-x4WH0BWmrMmg4oHHl+duwubhrvczGlyuGAZu3nvrf0UXOfPu8IhZObFEr7DE/iv01YgVZrsOiRcqw2srkKEDIA==", "dev": true, + "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", @@ -9360,7 +9556,8 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify": { "version": "0.0.1", @@ -9686,7 +9883,8 @@ "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/loose-envify": { "version": "1.4.0", @@ -9770,9 +9968,9 @@ } }, "node_modules/marked": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-11.2.0.tgz", - "integrity": "sha512-HR0m3bvu0jAPYiIvLUUQtdg1g6D247//lvcekpHO1WMvbwDlwSkZAX9Lw4F4YHE1T0HaaNve0tuAWuV1UJ6vtw==", + "version": "15.0.8", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.8.tgz", + "integrity": "sha512-rli4l2LyZqpQuRve5C0rkn6pj3hT8EWPC+zkAxFTAJLxRbENfTAhEQq9itrmf1Y81QtAX5D/MYlGlIomNgj9lA==", "license": "MIT", "bin": { "marked": "bin/marked.js" @@ -9782,40 +9980,60 @@ } }, "node_modules/marked-emoji": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/marked-emoji/-/marked-emoji-1.4.3.tgz", - "integrity": "sha512-HDZx1VOmzu7XT2QNKWfrHGbNRMTWKj9XD78yrcH1madD30HpGLMODPOmKr/e7CA7NKKXkpXXNdndQn++ysXmHg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/marked-emoji/-/marked-emoji-2.0.0.tgz", + "integrity": "sha512-oTZ8fqbdVDHFQnqCE1tg4ND7zEd7cUVNHliR9Ldu4eys0J86uz/5Uksjd2mt5xcX16OOScDEr3MmPjajI/ZDHA==", + "license": "MIT", "peerDependencies": { "marked": ">=4 <16" } }, "node_modules/marked-extended-tables": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/marked-extended-tables/-/marked-extended-tables-1.1.0.tgz", - "integrity": "sha512-xPQlnmr/mpIJEjwg9/guSKzxoKu7hyCD/sM59mcZc+nMIh2JuVM2se+kCa1Jo1UH+BEHcNlZ221ziJ/cOxAgCA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/marked-extended-tables/-/marked-extended-tables-2.0.1.tgz", + "integrity": "sha512-DV4Si978ZdaFbycIxzG4TdaNMtC0J8QfIKj1UOCejgJHwVjVJse8DNdJriWDeo/n74DWVYpRC6S56AdgWDPrPA==", + "license": "MIT", "peerDependencies": { "marked": ">=3 <16" } }, "node_modules/marked-gfm-heading-id": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/marked-gfm-heading-id/-/marked-gfm-heading-id-3.2.0.tgz", - "integrity": "sha512-Xfxpr5lXLDLY10XqzSCA9l2dDaiabQUgtYM9hw8yunyVsB/xYBRpiic6BOiY/EAJw1ik1eWr1ET1HKOAPZBhXg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/marked-gfm-heading-id/-/marked-gfm-heading-id-4.1.1.tgz", + "integrity": "sha512-EeQZieAQmsI6c2tWLx0ETd0VjPwLV8qi+HT0dIsfVMERm0rCIuXfRvZXJbo1SgUi++lmuR1LVY+QzgNiLNvVpw==", "license": "MIT", "dependencies": { "github-slugger": "^2.0.0" }, "peerDependencies": { - "marked": ">=4 <13" + "marked": ">=13 <16" + } + }, + "node_modules/marked-nonbreaking-spaces": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/marked-nonbreaking-spaces/-/marked-nonbreaking-spaces-1.0.1.tgz", + "integrity": "sha512-CUeFRc6OdMMSJThgOM7WAkUjL59VfSf79urKyKYtH9fs3hnrhC3+syFBimYh4vpvUZmjnXoZX0K6V3vZKmyRWQ==", + "license": "MIT", + "peerDependencies": { + "marked": ">=3 <16" } }, "node_modules/marked-smartypants-lite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/marked-smartypants-lite/-/marked-smartypants-lite-1.0.2.tgz", - "integrity": "sha512-cEANts+s3+gnTzXPvPT2z4V8NfbMEL9QooKUviug0DkaKkXQWrUwDAmFnQAkLSJCw2BQcD8YPDyxu0HJ3mg36w==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/marked-smartypants-lite/-/marked-smartypants-lite-1.0.3.tgz", + "integrity": "sha512-OOL8cjFog8KtgUFpkihRu6G42Dz2QPOU4k2xfKmMJ0CdoX+BHBDmdesHUdoQKM0mlwTsRSU3shpMeM/LWuUokg==", "license": "MIT", "peerDependencies": { - "marked": ">=4 <12" + "marked": ">=4 <16" + } + }, + "node_modules/marked-subsuper-text": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/marked-subsuper-text/-/marked-subsuper-text-1.0.3.tgz", + "integrity": "sha512-v5hVVJo6L7HQtplIT8OYNbRWMCGupXYuZ7U9qTsC4yLDtfw24oM5xmWVYfzqzX6hD7KneMfDssMPt6U7fslbxQ==", + "license": "MIT", + "peerDependencies": { + "marked": ">=3 <16" } }, "node_modules/markedLegacy": { @@ -9835,6 +10053,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -9862,25 +10081,26 @@ } }, "node_modules/mdn-data": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.1.tgz", - "integrity": "sha512-rsfnCbOHjqrhWxwt5/wtSLzpoKTzW7OXdT5lLOIH1OTYhWu9rRJveGq0sKvDZODABH7RX+uoR+DYcpFnq4Tf6Q==", + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", "dev": true, "license": "CC0-1.0" }, "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "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==" + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT" }, "node_modules/meow": { "version": "13.2.0", @@ -9896,9 +10116,13 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -10129,53 +10353,58 @@ } }, "node_modules/mongodb-connection-string-url": { - "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==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "license": "Apache-2.0", "dependencies": { "@types/whatwg-url": "^11.0.2", - "whatwg-url": "^13.0.0" + "whatwg-url": "^14.1.0 || ^13.0.0" } }, "node_modules/mongodb-connection-string-url/node_modules/tr46": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", - "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.0.tgz", + "integrity": "sha512-IUWnUK7ADYR5Sl1fZlO1INDUhVhatWl7BtJWsIhwJ0UAK7ilzzIa8uIqOO/aYVWHZPJkKbEL+362wrzoeRF7bw==", + "license": "MIT", "dependencies": { - "punycode": "^2.3.0" + "punycode": "^2.3.1" }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/mongodb-connection-string-url/node_modules/webidl-conversions": { "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" } }, "node_modules/mongodb-connection-string-url/node_modules/whatwg-url": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", - "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "license": "MIT", "dependencies": { - "tr46": "^4.1.1", + "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/mongoose": { - "version": "8.9.3", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.9.3.tgz", - "integrity": "sha512-G50GNPdMqhoiRAJ/24GYAzg13yxXDD3FOOFeYiFwtHmHpAJem3hxbYIxAhLJGWbYEiUZL0qFMu2LXYkgGAmo+Q==", + "version": "8.13.2", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.13.2.tgz", + "integrity": "sha512-riCBqZmNkYBWjXpM3qWLDQw7QmTKsVZDPhLXFJqC87+OjocEVpvS3dA2BPPUiLAu+m0/QmEj5pSXKhH+/DgerQ==", + "license": "MIT", "dependencies": { - "bson": "^6.10.1", + "bson": "^6.10.3", "kareem": "2.6.3", - "mongodb": "~6.12.0", + "mongodb": "~6.15.0", "mpath": "0.9.0", "mquery": "5.0.0", "ms": "2.1.3", @@ -10193,6 +10422,7 @@ "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": { @@ -10206,6 +10436,7 @@ "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": { @@ -10222,6 +10453,7 @@ "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": { @@ -10236,6 +10468,7 @@ "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": { @@ -10247,12 +10480,13 @@ } }, "node_modules/mongoose/node_modules/mongodb": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.12.0.tgz", - "integrity": "sha512-RM7AHlvYfS7jv7+BXund/kR64DryVI+cHbVAy9P61fnb1RcWZqOW1/Wj2YhqMCx+MuYhqTRGv7AwHBzmsCKBfA==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.15.0.tgz", + "integrity": "sha512-ifBhQ0rRzHDzqp9jAQP6OwHSH7dbYIQjD3SbJs9YYk9AikKEettW/9s/tbSFDTpXcRbF+u1aLrhHxDFaYtZpFQ==", + "license": "Apache-2.0", "dependencies": { "@mongodb-js/saslprep": "^1.1.9", - "bson": "^6.10.1", + "bson": "^6.10.3", "mongodb-connection-string-url": "^3.0.0" }, "engines": { @@ -10325,9 +10559,9 @@ "optional": true }, "node_modules/nanoid": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.9.tgz", - "integrity": "sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", + "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", "funding": [ { "type": "github", @@ -10495,9 +10729,9 @@ } }, "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -10531,9 +10765,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "license": "MIT" }, "node_modules/nodemon": { @@ -10651,6 +10885,7 @@ "version": "1.13.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -10683,6 +10918,7 @@ "version": "4.1.7", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", @@ -10699,15 +10935,16 @@ } }, "node_modules/object.entries": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", - "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "es-object-atoms": "^1.1.1" }, "engines": { "node": ">= 0.4" @@ -10749,6 +10986,7 @@ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", @@ -11033,10 +11271,13 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } }, "node_modules/path-type": { "version": "4.0.0", @@ -11185,14 +11426,15 @@ "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" } }, "node_modules/postcss": { - "version": "8.4.49", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", - "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", "dev": true, "funding": [ { @@ -11210,7 +11452,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", + "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -11294,9 +11536,9 @@ "license": "MIT" }, "node_modules/postcss/node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "dev": true, "funding": [ { @@ -11469,11 +11711,12 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -11540,14 +11783,14 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", - "iconv-lite": "0.4.24", + "iconv-lite": "0.6.3", "unpipe": "1.0.0" }, "engines": { @@ -11598,9 +11841,10 @@ "license": "MIT" }, "node_modules/react-router": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.1.1.tgz", - "integrity": "sha512-39sXJkftkKWRZ2oJtHhCxmoCrBCULr/HAH4IT5DHlgu/Q0FCPV0S4Lx+abjDTx/74xoZzNYDYbOZWlJjruyuDQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.0.tgz", + "integrity": "sha512-estOHrRlDMKdlQa6Mj32gIks4J+AxNsYoE0DbTTxiMy2mPzZuWSDU+N85/r1IlNR7kGfznF3VCUlvc5IUO+B9g==", + "license": "MIT", "dependencies": { "@types/cookie": "^0.6.0", "cookie": "^1.0.1", @@ -11691,6 +11935,7 @@ "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.9.tgz", "integrity": "sha512-r0Ay04Snci87djAsI4U+WNRcSw5S4pOH7qFjd/veA5gC7TbqESR3tcj28ia95L/fYUDw11JKP7uqUKUAfVvV5Q==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", @@ -11792,6 +12037,7 @@ "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", "integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -11875,6 +12121,7 @@ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -11976,6 +12223,28 @@ "inherits": "^2.0.1" } }, + "node_modules/romans": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/romans/-/romans-3.0.0.tgz", + "integrity": "sha512-7DDsAfhtpRr/ZFQXiHDrC3Pe00agcAsFiNt5nNx4ZAQlsc6yJG0mvXA5WAvO8YZyOg349twm2GYhHLw7rCXAzw==", + "license": "MIT" + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/rrweb-cssom": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", @@ -12013,6 +12282,7 @@ "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", @@ -12061,6 +12331,7 @@ "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -12324,6 +12595,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", @@ -12342,6 +12614,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" @@ -12357,6 +12630,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -12374,6 +12648,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -12464,6 +12739,7 @@ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", @@ -12636,6 +12912,7 @@ "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" } @@ -12839,6 +13116,7 @@ "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", @@ -12877,6 +13155,7 @@ "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", @@ -12898,6 +13177,7 @@ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", @@ -12982,9 +13262,9 @@ "license": "ISC" }, "node_modules/stylelint": { - "version": "16.12.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.12.0.tgz", - "integrity": "sha512-F8zZ3L/rBpuoBZRvI4JVT20ZanPLXfQLzMOZg1tzPflRVh9mKpOZ8qcSIhh1my3FjAjZWG4T2POwGnmn6a6hbg==", + "version": "16.18.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.18.0.tgz", + "integrity": "sha512-OXb68qzesv7J70BSbFwfK3yTVLEVXiQ/ro6wUE4UrSbKCMjLLA02S8Qq3LC01DxKyVjk7z8xh35aB4JzO3/sNA==", "dev": true, "funding": [ { @@ -12996,6 +13276,7 @@ "url": "https://github.com/sponsors/stylelint" } ], + "license": "MIT", "dependencies": { "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3", @@ -13006,16 +13287,16 @@ "colord": "^2.9.3", "cosmiconfig": "^9.0.0", "css-functions-list": "^3.2.3", - "css-tree": "^3.0.1", + "css-tree": "^3.1.0", "debug": "^4.3.7", - "fast-glob": "^3.3.2", + "fast-glob": "^3.3.3", "fastest-levenshtein": "^1.0.16", - "file-entry-cache": "^9.1.0", + "file-entry-cache": "^10.0.7", "global-modules": "^2.0.0", "globby": "^11.1.0", "globjoin": "^0.1.4", "html-tags": "^3.3.1", - "ignore": "^6.0.2", + "ignore": "^7.0.3", "imurmurhash": "^0.1.4", "is-plain-object": "^5.0.0", "known-css-properties": "^0.35.0", @@ -13024,14 +13305,14 @@ "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "picocolors": "^1.1.1", - "postcss": "^8.4.49", + "postcss": "^8.5.3", "postcss-resolve-nested-selector": "^0.1.6", "postcss-safe-parser": "^7.0.1", - "postcss-selector-parser": "^7.0.0", + "postcss-selector-parser": "^7.1.0", "postcss-value-parser": "^4.2.0", "resolve-from": "^5.0.0", "string-width": "^4.2.3", - "supports-hyperlinks": "^3.1.0", + "supports-hyperlinks": "^3.2.0", "svg-tags": "^1.0.0", "table": "^6.9.0", "write-file-atomic": "^5.0.1" @@ -13044,10 +13325,11 @@ } }, "node_modules/stylelint-config-recess-order": { - "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==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recess-order/-/stylelint-config-recess-order-6.0.0.tgz", + "integrity": "sha512-1KqrttqpIrCYFAVQ1/bbgXo7EvvcjmkxxmnzVr+U66Xr2OlrNZqQ5+44Tmct6grCWY6wGTIBh2tSANqcmwIM2g==", "dev": true, + "license": "ISC", "dependencies": { "stylelint-order": "^6.0.4" }, @@ -13056,9 +13338,9 @@ } }, "node_modules/stylelint-config-recommended": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-14.0.1.tgz", - "integrity": "sha512-bLvc1WOz/14aPImu/cufKAZYfXs/A/owZfSMZ4N+16WGXLoX5lOir53M6odBxvhgmgdxCVnNySJmZKx73T93cg==", + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-16.0.0.tgz", + "integrity": "sha512-4RSmPjQegF34wNcK1e1O3Uz91HN8P1aFdFzio90wNK9mjgAI19u5vsU868cVZboKzCaa5XbpvtTzAAGQAxpcXA==", "dev": true, "funding": [ { @@ -13075,7 +13357,7 @@ "node": ">=18.12.0" }, "peerDependencies": { - "stylelint": "^16.1.0" + "stylelint": "^16.16.0" } }, "node_modules/stylelint-order": { @@ -13147,43 +13429,41 @@ "license": "MIT" }, "node_modules/stylelint/node_modules/file-entry-cache": { - "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==", + "version": "10.0.8", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.0.8.tgz", + "integrity": "sha512-FGXHpfmI4XyzbLd3HQ8cbUcsFGohJpZtmQRHr8z8FxxtCe2PcpgIlVLwIgunqjvRmXypBETvwhV4ptJizA+Y1Q==", "dev": true, + "license": "MIT", "dependencies": { - "flat-cache": "^5.0.0" - }, - "engines": { - "node": ">=18" + "flat-cache": "^6.1.8" } }, "node_modules/stylelint/node_modules/flat-cache": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-5.0.0.tgz", - "integrity": "sha512-JrqFmyUl2PnPi1OvLyTVHnQvwQ0S+e6lGSwu8OkAZlSaNIZciTY2H/cOOROxsBA1m/LZNHDsqAgDZt6akWcjsQ==", + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.8.tgz", + "integrity": "sha512-R6MaD3nrJAtO7C3QOuS79ficm2pEAy++TgEUD8ii1LVlbcgZ9DtASLkt9B+RZSFCzm7QHDMlXPsqqB6W2Pfr1Q==", "dev": true, + "license": "MIT", "dependencies": { - "flatted": "^3.3.1", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=18" + "cacheable": "^1.8.9", + "flatted": "^3.3.3", + "hookified": "^1.8.1" } }, "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==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.3.tgz", + "integrity": "sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/stylelint/node_modules/postcss-selector-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", - "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, "license": "MIT", "dependencies": { @@ -13241,9 +13521,10 @@ } }, "node_modules/superagent": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.1.1.tgz", - "integrity": "sha512-9pIwrHrOj3uAnqg9gDlW7EA2xv+N5au/dSM0kM22HTqmUu8jBxNT+8uA7tA3UoCnmiqzpSbu8rasIUZvbyamMQ==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.0.tgz", + "integrity": "sha512-IKeoGox6oG9zyDeizaezkJ2/aK0wc5la9st7WsAKyrAkfJ56W3whVbVtF68k6wuc87/y9T85NyON5FLz7Mrzzw==", + "license": "MIT", "dependencies": { "component-emitter": "^1.3.0", "cookiejar": "^2.1.4", @@ -13272,9 +13553,9 @@ } }, "node_modules/supertest": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.0.0.tgz", - "integrity": "sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.0.tgz", + "integrity": "sha512-5QeSO8hSrKghtcWEoPiO036fxH0Ii2wVQfFZSP0oqQhmjk8bOLhDFXr4JrvaFmPuEWUoq4znY3uSi8UzLKxGqw==", "dev": true, "license": "MIT", "dependencies": { @@ -13330,10 +13611,11 @@ } }, "node_modules/supports-hyperlinks": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.1.0.tgz", - "integrity": "sha512-2rn0BZ+/f7puLOHZm1HOJfwBggfaHXUpPUSSG/SWM4TWp5KCfmNYwnC3hruy2rZlMnmWZ+QAGpZfchu3f3695A==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" @@ -13342,7 +13624,7 @@ "node": ">=14.18" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" } }, "node_modules/supports-hyperlinks/node_modules/has-flag": { @@ -13350,6 +13632,7 @@ "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" } @@ -13359,6 +13642,7 @@ "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" }, @@ -13406,6 +13690,7 @@ "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", @@ -13422,6 +13707,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -13437,7 +13723,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/test-exclude": { "version": "6.0.0", @@ -13739,13 +14026,35 @@ } }, "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "license": "MIT", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" }, "engines": { "node": ">= 0.6" @@ -13756,6 +14065,7 @@ "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", @@ -13770,6 +14080,7 @@ "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "for-each": "^0.3.3", @@ -13789,6 +14100,7 @@ "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", "dev": true, + "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", @@ -13810,6 +14122,7 @@ "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", @@ -13860,6 +14173,7 @@ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", @@ -14036,9 +14350,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", - "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "funding": [ { "type": "opencollective", @@ -14055,8 +14369,8 @@ ], "license": "MIT", "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -14070,6 +14384,7 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } @@ -14142,15 +14457,6 @@ "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", "license": "ISC" }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", @@ -14594,20 +14900,6 @@ "node": ">=18" } }, - "node_modules/whatwg-encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/whatwg-mimetype": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", @@ -14650,6 +14942,7 @@ "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", "dev": true, + "license": "MIT", "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", @@ -14669,6 +14962,7 @@ "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "function.prototype.name": "^1.1.6", @@ -14696,6 +14990,7 @@ "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "dev": true, + "license": "MIT", "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", @@ -14714,6 +15009,7 @@ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz", "integrity": "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==", "dev": true, + "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", @@ -14776,6 +15072,12 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/written-number": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/written-number/-/written-number-0.11.1.tgz", + "integrity": "sha512-LhQ68uUnzHH0bwm/QiGA9JwqgadSDOwqB2AIs/LBsrOY6ScqVXKRN2slTCeKAhstDBJ/Of/Yxcjn0pnQmVlmtg==", + "license": "MIT" + }, "node_modules/ws": { "version": "7.5.10", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", diff --git a/package.json b/package.json index 7acffcda8..a98cf59ff 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "homebrewery", "description": "Create authentic looking D&D homebrews using only markdown", - "version": "3.16.1", + "version": "3.18.1", "type": "module", "engines": { - "npm": "^10.2.x", + "npm": "^10.8.x", "node": "^20.18.x" }, "repository": { @@ -84,62 +84,65 @@ ] }, "dependencies": { - "@babel/core": "^7.26.0", - "@babel/plugin-transform-runtime": "^7.25.9", - "@babel/preset-env": "^7.26.0", + "@babel/core": "^7.26.10", + "@babel/plugin-transform-runtime": "^7.26.10", + "@babel/preset-env": "^7.26.9", "@babel/preset-react": "^7.26.3", - "@googleapis/drive": "^8.14.0", - "body-parser": "^1.20.2", + "@googleapis/drive": "^11.0.0", + "body-parser": "^2.2.0", "classnames": "^2.5.1", "codemirror": "^5.65.6", "cookie-parser": "^1.4.7", - "core-js": "^3.39.0", + "core-js": "^3.41.0", "cors": "^2.8.5", "create-react-class": "^15.7.0", "dedent-tabs": "^0.10.3", - "dompurify": "^3.2.3", "expr-eval": "^2.0.2", - "express": "^4.21.2", + "express": "^5.1.0", "express-async-handler": "^1.2.0", "express-static-gzip": "2.2.0", - "fs-extra": "11.2.0", + "fs-extra": "11.3.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.3", - "marked-extended-tables": "^1.1.0", - "marked-gfm-heading-id": "^3.2.0", - "marked-smartypants-lite": "^1.0.2", + "marked": "15.0.8", + "marked-emoji": "^2.0.0", + "marked-extended-tables": "^2.0.1", + "marked-gfm-heading-id": "^4.0.1", + "marked-nonbreaking-spaces": "^1.0.1", + "marked-smartypants-lite": "^1.0.3", + "marked-subsuper-text": "^1.0.3", "markedLegacy": "npm:marked@^0.3.19", "moment": "^2.30.1", - "mongoose": "^8.9.3", - "nanoid": "5.0.9", + "mongoose": "^8.13.2", + "nanoid": "5.1.5", "nconf": "^0.12.1", "react": "^18.3.1", "react-dom": "^18.3.1", "react-frame-component": "^4.1.3", - "react-router": "^7.1.1", + "react-router": "^7.5.0", + "romans": "^3.0.0", "sanitize-filename": "1.6.3", - "superagent": "^10.1.1", - "vitreum": "git+https://git@github.com/calculuschild/vitreum.git" + "superagent": "^10.2.0", + "vitreum": "git+https://git@github.com/calculuschild/vitreum.git", + "written-number": "^0.11.1" }, "devDependencies": { - "@stylistic/stylelint-plugin": "^3.1.1", + "@stylistic/stylelint-plugin": "^3.1.2", "babel-plugin-transform-import-meta": "^2.3.2", - "eslint": "^9.17.0", - "eslint-plugin-jest": "^28.10.0", - "eslint-plugin-react": "^7.37.3", - "globals": "^15.14.0", + "eslint": "^9.24.0", + "eslint-plugin-jest": "^28.11.0", + "eslint-plugin-react": "^7.37.5", + "globals": "^16.0.0", "jest": "^29.7.0", "jest-expect-message": "^1.1.3", "jsdom-global": "^3.0.2", "postcss-less": "^6.0.0", - "stylelint": "^16.12.0", - "stylelint-config-recess-order": "^5.1.1", - "stylelint-config-recommended": "^14.0.1", - "supertest": "^7.0.0" + "stylelint": "^16.18.0", + "stylelint-config-recess-order": "^6.0.0", + "stylelint-config-recommended": "^16.0.0", + "supertest": "^7.1.0" } } diff --git a/scripts/buildHomebrew.js b/scripts/buildHomebrew.js index 656714d87..4d55a4176 100644 --- a/scripts/buildHomebrew.js +++ b/scripts/buildHomebrew.js @@ -10,7 +10,7 @@ import babel from '@babel/core'; import babelConfig from '../babel.config.json' with { type : 'json' }; import less from 'less'; -const isDev = !!process.argv.find((arg) => arg === '--dev'); +const isDev = !!process.argv.find((arg)=>arg === '--dev'); const babelify = async (code)=>(await babel.transformAsync(code, babelConfig)).code; @@ -53,7 +53,7 @@ fs.emptyDirSync('./build'); const themes = { Legacy: {}, V3: {} }; let themeFiles = fs.readdirSync('./themes/Legacy'); - for (let dir of themeFiles) { + for (const dir of themeFiles) { const themeData = JSON.parse(fs.readFileSync(`./themes/Legacy/${dir}/settings.json`).toString()); themeData.path = dir; themes.Legacy[dir] = (themeData); @@ -70,7 +70,7 @@ fs.emptyDirSync('./build'); } themeFiles = fs.readdirSync('./themes/V3'); - for (let dir of themeFiles) { + for (const dir of themeFiles) { const themeData = JSON.parse(fs.readFileSync(`./themes/V3/${dir}/settings.json`).toString()); themeData.path = dir; themes.V3[dir] = (themeData); @@ -113,7 +113,7 @@ fs.emptyDirSync('./build'); const stream = fs.createWriteStream(editorThemeFile, { flags: 'a' }); stream.write('[\n"default"'); - for (let themeFile of editorThemeFiles) { + for (const themeFile of editorThemeFiles) { stream.write(`,\n"${themeFile.slice(0, -4)}"`); } stream.write('\n]\n'); diff --git a/server/admin.api.js b/server/admin.api.js index 1a39f020b..a3d7622f1 100644 --- a/server/admin.api.js +++ b/server/admin.api.js @@ -1,3 +1,4 @@ +/*eslint max-lines: ["warn", {"max": 500, "skipBlankLines": true, "skipComments": true}]*/ import { model as HomebrewModel } from './homebrew.model.js'; import { model as NotificationModel } from './notifications.model.js'; import express from 'express'; @@ -11,6 +12,7 @@ import { splitTextStyleAndMetadata } from '../shared/helpers.js'; const router = express.Router(); + process.env.ADMIN_USER = process.env.ADMIN_USER || 'admin'; process.env.ADMIN_PASS = process.env.ADMIN_PASS || 'password3'; @@ -93,7 +95,7 @@ router.get('/admin/finduncompressed', mw.adminOnly, (req, res)=>{ /* Cleans `` from the "text" field of a brew */ router.put('/admin/clean/script/:id', asyncHandler(HomebrewAPI.getBrew('admin', false)), async (req, res)=>{ - console.log(`[ADMIN] Cleaning script tags from ShareID ${req.params.id}`); + console.log(`[ADMIN: ${req.account?.username || 'Not Logged In'}] Cleaning script tags from ShareID ${req.params.id}`); function cleanText(text){return text.replaceAll(/(<\/?s)cript/gi, '');}; @@ -114,6 +116,18 @@ router.put('/admin/clean/script/:id', asyncHandler(HomebrewAPI.getBrew('admin', return await HomebrewAPI.updateBrew(req, res); }); +/* Get list of a user's documents */ +router.get('/admin/user/list/:user', mw.adminOnly, async (req, res)=>{ + const username = req.params.user; + const fields = { _id: 0, text: 0, textBin: 0 }; // Remove unnecessary fields from document lists + + console.log(`[ADMIN: ${req.account?.username || 'Not Logged In'}] Get brew list for ${username}`); + + const brews = await HomebrewModel.getByUser(username, true, fields); + + return res.json(brews); +}); + /* Compresses the "text" field of a brew to binary */ router.put('/admin/compress/:id', (req, res)=>{ HomebrewModel.findOne({ _id: req.params.id }) @@ -135,7 +149,6 @@ router.put('/admin/compress/:id', (req, res)=>{ }); }); - router.get('/admin/stats', mw.adminOnly, async (req, res)=>{ try { const totalBrewsCount = await HomebrewModel.countDocuments({}); @@ -151,6 +164,180 @@ router.get('/admin/stats', mw.adminOnly, async (req, res)=>{ } }); +// ####################### LOCKS + +router.get('/api/lock/count', mw.adminOnly, asyncHandler(async (req, res)=>{ + + const countLocksQuery = { + lock : { $exists: true } + }; + const count = await HomebrewModel.countDocuments(countLocksQuery) + .catch((error)=>{ + throw { name: 'Lock Count Error', message: 'Unable to get lock count', status: 500, HBErrorCode: '61', error }; + }); + + return res.json({ count }); + +})); + +router.get('/api/locks', mw.adminOnly, asyncHandler(async (req, res)=>{ + const countLocksPipeline = [ + { + $match : + { + 'lock' : { '$exists': 1 } + }, + }, + { + $project : { + shareId : 1, + editId : 1, + title : 1, + lock : 1 + } + } + ]; + const lockedDocuments = await HomebrewModel.aggregate(countLocksPipeline) + .catch((error)=>{ + throw { name: 'Can Not Get Locked Brews', message: 'Unable to get locked brew collection', status: 500, HBErrorCode: '68', error }; + }); + return res.json({ + lockedDocuments + }); + +})); + +router.post('/api/lock/:id', mw.adminOnly, asyncHandler(async (req, res)=>{ + + const lock = req.body; + + lock.applied = new Date; + + const filter = { + shareId : req.params.id + }; + + const brew = await HomebrewModel.findOne(filter); + + if(!brew) throw { name: 'Brew Not Found', message: 'Cannot find brew to lock', shareId: req.params.id, status: 500, HBErrorCode: '63' }; + + if(brew.lock && !lock.overwrite) { + throw { name: 'Already Locked', message: 'Lock already exists on brew', shareId: req.params.id, title: brew.title, status: 500, HBErrorCode: '64' }; + } + + lock.overwrite = undefined; + + brew.lock = lock; + brew.markModified('lock'); + + await brew.save() + .catch((error)=>{ + throw { name: 'Lock Error', message: 'Unable to set lock', shareId: req.params.id, status: 500, HBErrorCode: '62', error }; + }); + + return res.json({ name: 'LOCKED', message: `Lock applied to brew ID ${brew.shareId} - ${brew.title}`, ...lock }); + +})); + +router.put('/api/unlock/:id', mw.adminOnly, asyncHandler(async (req, res)=>{ + + const filter = { + shareId : req.params.id + }; + + const brew = await HomebrewModel.findOne(filter); + + if(!brew) throw { name: 'Brew Not Found', message: 'Cannot find brew to unlock', shareId: req.params.id, status: 500, HBErrorCode: '66' }; + + if(!brew.lock) throw { name: 'Not Locked', message: 'Cannot unlock as brew is not locked', shareId: req.params.id, status: 500, HBErrorCode: '67' }; + + brew.lock = undefined; + brew.markModified('lock'); + + await brew.save() + .catch((error)=>{ + throw { name: 'Cannot Unlock', message: 'Unable to clear lock', shareId: req.params.id, status: 500, HBErrorCode: '65', error }; + }); + + return res.json({ name: 'Unlocked', message: `Lock removed from brew ID ${req.params.id}` }); +})); + +router.get('/api/lock/reviews', mw.adminOnly, asyncHandler(async (req, res)=>{ + const countReviewsPipeline = [ + { + $match : + { + 'lock.reviewRequested' : { '$exists': 1 } + }, + }, + { + $project : { + shareId : 1, + editId : 1, + title : 1, + lock : 1 + } + } + ]; + const reviewDocuments = await HomebrewModel.aggregate(countReviewsPipeline) + .catch((error)=>{ + throw { name: 'Can Not Get Reviews', message: 'Unable to get review collection', status: 500, HBErrorCode: '68', error }; + }); + return res.json({ + reviewDocuments + }); + +})); + +router.put('/api/lock/review/request/:id', asyncHandler(async (req, res)=>{ + // === This route is NOT Admin only === + // Any user can request a review of their document + const filter = { + shareId : req.params.id, + lock : { $exists: 1 } + }; + + const brew = await HomebrewModel.findOne(filter); + if(!brew) { throw { name: 'Brew Not Found', message: `Cannot find a locked brew with ID ${req.params.id}`, code: 500, HBErrorCode: '70' }; }; + + if(brew.lock.reviewRequested){ + throw { name: 'Review Already Requested', message: `Review already requested for brew ${brew.shareId} - ${brew.title}`, code: 500, HBErrorCode: '71' }; + }; + + brew.lock.reviewRequested = new Date(); + brew.markModified('lock'); + + await brew.save() + .catch((error)=>{ + throw { name: 'Can Not Set Review Request', message: `Unable to set request for review on brew ID ${req.params.id}`, code: 500, HBErrorCode: '69', error }; + }); + + return res.json({ name: 'Review Requested', message: `Review requested on brew ID ${brew.shareId} - ${brew.title}` }); + +})); + +router.put('/api/lock/review/remove/:id', mw.adminOnly, asyncHandler(async (req, res)=>{ + + const filter = { + shareId : req.params.id, + 'lock.reviewRequested' : { $exists: 1 } + }; + + const brew = await HomebrewModel.findOne(filter); + if(!brew) { throw { name: 'Can Not Clear Review Request', message: `Brew ID ${req.params.id} does not have a review pending!`, HBErrorCode: '73' }; }; + + brew.lock.reviewRequested = undefined; + brew.markModified('lock'); + + await brew.save() + .catch((error)=>{ + throw { name: 'Can Not Clear Review Request', message: `Unable to remove request for review on brew ID ${req.params.id}`, HBErrorCode: '72', error }; + }); + + return res.json({ name: 'Review Request Cleared', message: `Review request removed for brew ID ${brew.shareId} - ${brew.title}` }); + +})); + // ####################### NOTIFICATIONS router.get('/admin/notification/all', async (req, res, next)=>{ diff --git a/server/admin.api.spec.js b/server/admin.api.spec.js index 6a23393b1..e156c6c8f 100644 --- a/server/admin.api.spec.js +++ b/server/admin.api.spec.js @@ -1,6 +1,8 @@ +/*eslint max-lines: ["warn", {"max": 1000, "skipBlankLines": true, "skipComments": true}]*/ import supertest from 'supertest'; import HBApp from './app.js'; -import {model as NotificationModel } from './notifications.model.js'; +import { model as NotificationModel } from './notifications.model.js'; +import { model as HomebrewModel } from './homebrew.model.js'; // Mimic https responses to avoid being redirected all the time @@ -16,7 +18,7 @@ describe('Tests for admin api', ()=>{ const testNotifications = ['a', 'b']; jest.spyOn(NotificationModel, 'find') - .mockImplementationOnce(() => { + .mockImplementationOnce(()=>{ return { exec: jest.fn().mockResolvedValue(testNotifications) }; }); @@ -59,7 +61,7 @@ describe('Tests for admin api', ()=>{ expect(response.body).toEqual(savedNotification); }); - it('should handle error adding a notification without dismissKey', async () => { + it('should handle error adding a notification without dismissKey', async ()=>{ const inputNotification = { title : 'Test Notification', text : 'This is a test notification', @@ -75,7 +77,7 @@ describe('Tests for admin api', ()=>{ const response = await app .post('/admin/notification/add') - .set('Authorization', 'Basic ' + Buffer.from('admin:password3').toString('base64')) + .set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`) .send(inputNotification); expect(response.status).toBe(500); @@ -86,14 +88,14 @@ describe('Tests for admin api', ()=>{ const dismissKey = 'testKey'; jest.spyOn(NotificationModel, 'findOneAndDelete') - .mockImplementationOnce((key) => { + .mockImplementationOnce((key)=>{ return { exec: jest.fn().mockResolvedValue(key) }; }); const response = await app .delete(`/admin/notification/delete/${dismissKey}`) .set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`); - expect(NotificationModel.findOneAndDelete).toHaveBeenCalledWith({'dismissKey': 'testKey'}); + expect(NotificationModel.findOneAndDelete).toHaveBeenCalledWith({ 'dismissKey': 'testKey' }); expect(response.status).toBe(200); expect(response.body).toEqual({ dismissKey: 'testKey' }); }); @@ -102,16 +104,602 @@ describe('Tests for admin api', ()=>{ const dismissKey = 'testKey'; jest.spyOn(NotificationModel, 'findOneAndDelete') - .mockImplementationOnce(() => { + .mockImplementationOnce(()=>{ return { exec: jest.fn().mockResolvedValue() }; }); const response = await app .delete(`/admin/notification/delete/${dismissKey}`) .set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`); - expect(NotificationModel.findOneAndDelete).toHaveBeenCalledWith({'dismissKey': 'testKey'}); + expect(NotificationModel.findOneAndDelete).toHaveBeenCalledWith({ 'dismissKey': 'testKey' }); expect(response.status).toBe(500); expect(response.body).toEqual({ message: 'Notification not found' }); }); }); + + describe('Locks', ()=>{ + describe('Count', ()=>{ + it('Count of all locked documents', async ()=>{ + const testNumber = 16777216; // 8^8, because why not + + jest.spyOn(HomebrewModel, 'countDocuments') + .mockImplementationOnce(()=>{ + return Promise.resolve(testNumber); + }); + + const response = await app + .set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`) + .get('/api/lock/count'); + + expect(response.status).toBe(200); + expect(response.body).toEqual({ count: testNumber }); + }); + + it('Handle error while fetching count of locked documents', async ()=>{ + jest.spyOn(HomebrewModel, 'countDocuments') + .mockImplementationOnce(()=>{ + return Promise.reject(); + }); + + const response = await app + .set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`) + .get('/api/lock/count'); + + expect(response.status).toBe(500); + expect(response.body).toEqual({ + HBErrorCode : '61', + message : 'Unable to get lock count', + name : 'Lock Count Error', + originalUrl : '/api/lock/count', + status : 500, + }); + }); + }); + + describe('Lists', ()=>{ + it('Get list of all locked documents', async ()=>{ + const testLocks = ['a', 'b']; + + jest.spyOn(HomebrewModel, 'aggregate') + .mockImplementationOnce(()=>{ + return Promise.resolve(testLocks); + }); + + const response = await app + .set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`) + .get('/api/locks'); + + expect(response.status).toBe(200); + expect(response.body).toEqual({ lockedDocuments: testLocks }); + }); + + it('Handle error while fetching list of all locked documents', async ()=>{ + jest.spyOn(HomebrewModel, 'aggregate') + .mockImplementationOnce(()=>{ + return Promise.reject(); + }); + + const response = await app + .set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`) + .get('/api/locks'); + + expect(response.status).toBe(500); + expect(response.body).toEqual({ + HBErrorCode : '68', + message : 'Unable to get locked brew collection', + name : 'Can Not Get Locked Brews', + originalUrl : '/api/locks', + status : 500 + }); + }); + + it('Get list of all locked documents with pending review requests', async ()=>{ + const testLocks = ['a', 'b']; + + jest.spyOn(HomebrewModel, 'aggregate') + .mockImplementationOnce(()=>{ + return Promise.resolve(testLocks); + }); + + const response = await app + .set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`) + .get('/api/lock/reviews'); + + expect(response.status).toBe(200); + expect(response.body).toEqual({ reviewDocuments: testLocks }); + }); + + it('Handle error while fetching list of all locked documents with pending review requests', async ()=>{ + jest.spyOn(HomebrewModel, 'aggregate') + .mockImplementationOnce(()=>{ + return Promise.reject(); + }); + + const response = await app + .set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`) + .get('/api/lock/reviews'); + + expect(response.status).toBe(500); + expect(response.body).toEqual({ + HBErrorCode : '68', + message : 'Unable to get review collection', + name : 'Can Not Get Reviews', + originalUrl : '/api/lock/reviews', + status : 500 + }); + }); + }); + + describe('Lock', ()=>{ + it('Lock a brew', async ()=>{ + const testBrew = { + shareId : 'shareId', + title : 'title', + markModified : ()=>{ return true; }, + save : ()=>{ return Promise.resolve(); } + }; + + const testLock = { + code : 999, + editMessage : 'edit', + shareMessage : 'share' + }; + + jest.spyOn(HomebrewModel, 'findOne') + .mockImplementationOnce(()=>{ + return Promise.resolve(testBrew); + }); + + const response = await app + .set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`) + .post(`/api/lock/${testBrew.shareId}`) + .send(testLock); + + expect(response.status).toBe(200); + expect(response.body).toMatchObject({ + applied : expect.any(String), + code : testLock.code, + editMessage : testLock.editMessage, + shareMessage : testLock.shareMessage, + name : 'LOCKED', + message : `Lock applied to brew ID ${testBrew.shareId} - ${testBrew.title}` + }); + }); + + it('Overwrite lock on a locked brew', async ()=>{ + const testLock = { + code : 999, + editMessage : 'newEdit', + shareMessage : 'newShare', + overwrite : true + }; + + const testBrew = { + shareId : 'shareId', + title : 'title', + markModified : ()=>{ return true; }, + save : ()=>{ return Promise.resolve(); }, + lock : { + code : 1, + editMessage : 'oldEdit', + shareMessage : 'oldShare', + } + }; + + jest.spyOn(HomebrewModel, 'findOne') + .mockImplementationOnce(()=>{ + return Promise.resolve(testBrew); + }); + + const response = await app + .set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`) + .post(`/api/lock/${testBrew.shareId}`) + .send(testLock); + + expect(response.status).toBe(200); + expect(response.body).toMatchObject({ + applied : expect.any(String), + code : testLock.code, + editMessage : testLock.editMessage, + shareMessage : testLock.shareMessage, + name : 'LOCKED', + message : `Lock applied to brew ID ${testBrew.shareId} - ${testBrew.title}` + }); + }); + + it('Error when locking a locked brew', async ()=>{ + const testLock = { + code : 999, + editMessage : 'newEdit', + shareMessage : 'newShare' + }; + + const testBrew = { + shareId : 'shareId', + title : 'title', + markModified : ()=>{ return true; }, + save : ()=>{ return Promise.resolve(); }, + lock : { + code : 1, + editMessage : 'oldEdit', + shareMessage : 'oldShare', + } + }; + + jest.spyOn(HomebrewModel, 'findOne') + .mockImplementationOnce(()=>{ + return Promise.resolve(testBrew); + }); + + const response = await app + .set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`) + .post(`/api/lock/${testBrew.shareId}`) + .send(testLock); + + expect(response.status).toBe(500); + expect(response.body).toEqual({ + HBErrorCode : '64', + message : 'Lock already exists on brew', + name : 'Already Locked', + originalUrl : `/api/lock/${testBrew.shareId}`, + shareId : testBrew.shareId, + status : 500, + title : 'title' + }); + }); + + it('Handle save error while locking a brew', async ()=>{ + const testBrew = { + shareId : 'shareId', + title : 'title', + markModified : ()=>{ return true; }, + save : ()=>{ return Promise.reject(); } + }; + + const testLock = { + code : 999, + editMessage : 'edit', + shareMessage : 'share' + }; + + jest.spyOn(HomebrewModel, 'findOne') + .mockImplementationOnce(()=>{ + return Promise.resolve(testBrew); + }); + + const response = await app + .set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`) + .post(`/api/lock/${testBrew.shareId}`) + .send(testLock); + + expect(response.status).toBe(500); + expect(response.body).toEqual({ + HBErrorCode : '62', + message : 'Unable to set lock', + name : 'Lock Error', + originalUrl : `/api/lock/${testBrew.shareId}`, + shareId : testBrew.shareId, + status : 500 + }); + }); + }); + + describe('Unlock', ()=>{ + it('Unlock a brew', async ()=>{ + const testLock = { + applied : 'YES', + code : 999, + editMessage : 'edit', + shareMessage : 'share' + }; + + const testBrew = { + shareId : 'shareId', + title : 'title', + markModified : ()=>{ return true; }, + save : ()=>{ return Promise.resolve(); }, + lock : testLock + }; + + jest.spyOn(HomebrewModel, 'findOne') + .mockImplementationOnce(()=>{ + return Promise.resolve(testBrew); + }); + + const response = await app + .set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`) + .put(`/api/unlock/${testBrew.shareId}`); + + expect(response.status).toBe(200); + expect(response.body).toEqual({ + name : 'Unlocked', + message : `Lock removed from brew ID ${testBrew.shareId}` + }); + }); + + it('Error when unlocking a brew with no lock', async ()=>{ + const testBrew = { + shareId : 'shareId', + title : 'title', + markModified : ()=>{ return true; }, + save : ()=>{ return Promise.resolve(); }, + }; + + jest.spyOn(HomebrewModel, 'findOne') + .mockImplementationOnce(()=>{ + return Promise.resolve(testBrew); + }); + + const response = await app + .set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`) + .put(`/api/unlock/${testBrew.shareId}`); + + expect(response.status).toBe(500); + expect(response.body).toEqual({ + HBErrorCode : '67', + message : 'Cannot unlock as brew is not locked', + name : 'Not Locked', + originalUrl : `/api/unlock/${testBrew.shareId}`, + shareId : testBrew.shareId, + status : 500, + }); + }); + + it('Handle error while unlocking a brew', async ()=>{ + const testLock = { + applied : 'YES', + code : 999, + editMessage : 'edit', + shareMessage : 'share' + }; + + const testBrew = { + shareId : 'shareId', + title : 'title', + markModified : ()=>{ return true; }, + save : ()=>{ return Promise.reject(); }, + lock : testLock + }; + + jest.spyOn(HomebrewModel, 'findOne') + .mockImplementationOnce(()=>{ + return Promise.resolve(testBrew); + }); + + const response = await app + .set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`) + .put(`/api/unlock/${testBrew.shareId}`); + + expect(response.status).toBe(500); + expect(response.body).toEqual({ + HBErrorCode : '65', + message : 'Unable to clear lock', + name : 'Cannot Unlock', + originalUrl : `/api/unlock/${testBrew.shareId}`, + shareId : testBrew.shareId, + status : 500 + }); + }); + }); + + describe('Reviews', ()=>{ + it('Add review request to a locked brew', async ()=>{ + const testLock = { + applied : 'YES', + code : 999, + editMessage : 'edit', + shareMessage : 'share' + }; + + const testBrew = { + shareId : 'shareId', + title : 'title', + markModified : ()=>{ return true; }, + save : ()=>{ return Promise.resolve(); }, + lock : testLock + }; + + jest.spyOn(HomebrewModel, 'findOne') + .mockImplementationOnce(()=>{ + return Promise.resolve(testBrew); + }); + + const response = await app + .put(`/api/lock/review/request/${testBrew.shareId}`); + + expect(response.status).toBe(200); + expect(response.body).toEqual({ + message : `Review requested on brew ID ${testBrew.shareId} - ${testBrew.title}`, + name : 'Review Requested', + }); + }); + + it('Error when cannot find a locked brew', async ()=>{ + const testBrew = { + shareId : 'shareId' + }; + + jest.spyOn(HomebrewModel, 'findOne') + .mockImplementationOnce(()=>{ + return Promise.resolve(false); + }); + + + const response = await app + .put(`/api/lock/review/request/${testBrew.shareId}`) + .catch((err)=>{return err;}); + + expect(response.status).toBe(500); + expect(response.body).toEqual({ + message : `Cannot find a locked brew with ID ${testBrew.shareId}`, + name : 'Brew Not Found', + HBErrorCode : '70', + code : 500, + originalUrl : `/api/lock/review/request/${testBrew.shareId}` + }); + }); + + it('Error when review is already requested', async ()=>{ + const testLock = { + applied : 'YES', + code : 999, + editMessage : 'edit', + shareMessage : 'share', + reviewRequested : 'YES' + }; + + const testBrew = { + shareId : 'shareId', + title : 'title', + markModified : ()=>{ return true; }, + save : ()=>{ return Promise.resolve(); }, + lock : testLock + }; + + jest.spyOn(HomebrewModel, 'findOne') + .mockImplementationOnce(()=>{ + return Promise.resolve(false); + }); + + + const response = await app + .put(`/api/lock/review/request/${testBrew.shareId}`) + .catch((err)=>{return err;}); + + expect(response.status).toBe(500); + expect(response.body).toEqual({ + HBErrorCode : '70', + code : 500, + message : `Cannot find a locked brew with ID ${testBrew.shareId}`, + name : 'Brew Not Found', + originalUrl : `/api/lock/review/request/${testBrew.shareId}` + }); + }); + + it('Handle error while adding review request to a locked brew', async ()=>{ + const testLock = { + applied : 'YES', + code : 999, + editMessage : 'edit', + shareMessage : 'share' + }; + + const testBrew = { + shareId : 'shareId', + title : 'title', + markModified : ()=>{ return true; }, + save : ()=>{ return Promise.reject(); }, + lock : testLock + }; + + jest.spyOn(HomebrewModel, 'findOne') + .mockImplementationOnce(()=>{ + return Promise.resolve(testBrew); + }); + + const response = await app + .put(`/api/lock/review/request/${testBrew.shareId}`); + + expect(response.status).toBe(500); + expect(response.body).toEqual({ + HBErrorCode : '69', + code : 500, + message : `Unable to set request for review on brew ID ${testBrew.shareId}`, + name : 'Can Not Set Review Request', + originalUrl : `/api/lock/review/request/${testBrew.shareId}` + }); + }); + + it('Clear review request from a locked brew', async ()=>{ + const testLock = { + applied : 'YES', + code : 999, + editMessage : 'edit', + shareMessage : 'share', + reviewRequested : 'YES' + }; + + const testBrew = { + shareId : 'shareId', + title : 'title', + markModified : ()=>{ return true; }, + save : ()=>{ return Promise.resolve(); }, + lock : testLock + }; + + jest.spyOn(HomebrewModel, 'findOne') + .mockImplementationOnce(()=>{ + return Promise.resolve(testBrew); + }); + + const response = await app + .set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`) + .put(`/api/lock/review/remove/${testBrew.shareId}`); + + expect(response.status).toBe(200); + expect(response.body).toEqual({ + message : `Review request removed for brew ID ${testBrew.shareId} - ${testBrew.title}`, + name : 'Review Request Cleared' + }); + }); + + it('Error when clearing review request from a brew with no review request', async ()=>{ + const testBrew = { + shareId : 'shareId', + }; + + jest.spyOn(HomebrewModel, 'findOne') + .mockImplementationOnce(()=>{ + return Promise.resolve(false); + }); + + const response = await app + .set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`) + .put(`/api/lock/review/remove/${testBrew.shareId}`); + + expect(response.status).toBe(500); + expect(response.body).toEqual({ + HBErrorCode : '73', + message : `Brew ID ${testBrew.shareId} does not have a review pending!`, + name : 'Can Not Clear Review Request', + originalUrl : `/api/lock/review/remove/${testBrew.shareId}` + }); + }); + + it('Handle error while clearing review request from a locked brew', async ()=>{ + const testLock = { + applied : 'YES', + code : 999, + editMessage : 'edit', + shareMessage : 'share', + reviewRequested : 'YES' + }; + + const testBrew = { + shareId : 'shareId', + title : 'title', + markModified : ()=>{ return true; }, + save : ()=>{ return Promise.reject(); }, + lock : testLock + }; + + jest.spyOn(HomebrewModel, 'findOne') + .mockImplementationOnce(()=>{ + return Promise.resolve(testBrew); + }); + + const response = await app + .set('Authorization', `Basic ${Buffer.from('admin:password3').toString('base64')}`) + .put(`/api/lock/review/remove/${testBrew.shareId}`); + + expect(response.status).toBe(500); + expect(response.body).toEqual({ + HBErrorCode : '72', + message : `Unable to remove request for review on brew ID ${testBrew.shareId}`, + name : 'Can Not Clear Review Request', + originalUrl : `/api/lock/review/remove/${testBrew.shareId}` + }); + }); + }); + }); }); diff --git a/server/app.js b/server/app.js index 3b0247518..d2e31127a 100644 --- a/server/app.js +++ b/server/app.js @@ -2,7 +2,7 @@ // Set working directory to project root import { dirname } from 'path'; import { fileURLToPath } from 'url'; -import packageJSON from './../package.json' with { type: 'json' }; +import packageJSON from './../package.json' with { type: 'json' }; const __dirname = dirname(fileURLToPath(import.meta.url)); process.chdir(`${__dirname}/..`); @@ -11,7 +11,6 @@ const version = packageJSON.version; import _ from 'lodash'; import jwt from 'jwt-simple'; import express from 'express'; -import yaml from 'js-yaml'; import config from './config.js'; import fs from 'fs-extra'; @@ -70,13 +69,11 @@ const corsOptions = { 'https://homebrewery-stage.herokuapp.com', ]; - if(isLocalEnvironment) { - allowedOrigins.push('http://localhost:8000', 'http://localhost:8010'); - } + const localNetworkRegex = /^http:\/\/(localhost|127\.0\.0\.1|10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+|172\.(1[6-9]|2\d|3[0-1])\.\d+\.\d+):\d+$/; const herokuRegex = /^https:\/\/(?:homebrewery-pr-\d+\.herokuapp\.com|naturalcrit-pr-\d+\.herokuapp\.com)$/; // Matches any Heroku app - if(!origin || allowedOrigins.includes(origin) || herokuRegex.test(origin)) { + if(!origin || allowedOrigins.includes(origin) || herokuRegex.test(origin) || (isLocalEnvironment && localNetworkRegex.test(origin))) { callback(null, true); } else { console.log(origin, 'not allowed'); @@ -352,7 +349,7 @@ app.get('/user/:username', async (req, res, next)=>{ app.put('/api/user/rename', async (req, res)=>{ const { username, newUsername } = req.body; const ownAccount = req.account && (req.account.username == newUsername); - + if(!username || !newUsername) return res.status(400).json({ error: 'Username and newUsername are required.' }); if(!ownAccount) @@ -591,6 +588,7 @@ const renderPage = async (req, res)=>{ const configuration = { local : isLocalEnvironment, publicUrl : config.get('publicUrl') ?? '', + baseUrl : `${req.protocol}://${req.get('host')}`, environment : nodeEnv, deployment : config.get('heroku_app_name') ?? '' }; diff --git a/server/googleActions.js b/server/googleActions.js index 2c2cbac73..0ca7556ba 100644 --- a/server/googleActions.js +++ b/server/googleActions.js @@ -27,12 +27,12 @@ if(!config.get('service_account')){ const defaultAuth = serviceAuth || config.get('google_api_key'); const retryConfig = { - retry: 3, // Number of retry attempts - retryDelay: 100, // Initial delay in milliseconds - retryDelayMultiplier: 2, // Multiplier for exponential backoff - maxRetryDelay: 32000, // Maximum delay in milliseconds - httpMethodsToRetry: ['PATCH'], // Only retry PATCH requests - statusCodesToRetry: [[429, 429]], // Only retry on 429 status code + retry : 3, // Number of retry attempts + retryDelay : 100, // Initial delay in milliseconds + retryDelayMultiplier : 2, // Multiplier for exponential backoff + maxRetryDelay : 32000, // Maximum delay in milliseconds + httpMethodsToRetry : ['PATCH'], // Only retry PATCH requests + statusCodesToRetry : [[429, 429]], // Only retry on 429 status code }; const GoogleActions = { @@ -177,8 +177,8 @@ const GoogleActions = { mimeType : 'text/plain', body : brew.text }, - headers: { - 'X-Forwarded-For': userIp, // Set the X-Forwarded-For header + headers : { + 'X-Forwarded-For' : userIp, // Set the X-Forwarded-For header }, retryConfig }) diff --git a/server/homebrew.api.js b/server/homebrew.api.js index 9a479732e..2ab29f31b 100644 --- a/server/homebrew.api.js +++ b/server/homebrew.api.js @@ -1,6 +1,6 @@ /* eslint-disable max-lines */ import _ from 'lodash'; -import {model as HomebrewModel} from './homebrew.model.js'; +import { model as HomebrewModel } from './homebrew.model.js'; import express from 'express'; import zlib from 'zlib'; import GoogleActions from './googleActions.js'; @@ -8,9 +8,11 @@ import Markdown from '../shared/naturalcrit/markdown.js'; import yaml from 'js-yaml'; import asyncHandler from 'express-async-handler'; import { nanoid } from 'nanoid'; -import { splitTextStyleAndMetadata } from '../shared/helpers.js'; +import { splitTextStyleAndMetadata, + brewSnippetsToJSON } from '../shared/helpers.js'; import checkClientVersion from './middleware/check-client-version.js'; + const router = express.Router(); import { DEFAULT_BREW, DEFAULT_BREW_LOAD } from './brewDefaults.js'; @@ -92,7 +94,7 @@ const api = { const accessMap = { edit : { editId: id }, share : { shareId: id }, - admin : { $or : [{ editId: id }, { shareId: id }] } + admin : { $or: [{ editId: id }, { shareId: id }] } }; // Try to find the document in the Homebrewery database -- if it doesn't exist, that's fine. @@ -118,8 +120,8 @@ const api = { throw { ...accessError, message: 'User is not logged in', HBErrorCode: '04' }; } - if(stub?.lock?.locked && accessType != 'edit') { - throw { HBErrorCode: '51', code: stub?.lock.code, message: stub?.lock.shareMessage, brewId: stub?.shareId, brewTitle: stub?.title }; + if(stub?.lock && accessType === 'share') { + throw { HBErrorCode: '51', code: stub.lock.code, message: stub.lock.shareMessage, brewId: stub.shareId, brewTitle: stub.title, brewAuthors: stub.authors }; } // If there's a google id, get it if requesting the full brew or if no stub found yet @@ -175,12 +177,15 @@ const api = { `${text}`; } const metadata = _.pick(brew, ['title', 'description', 'tags', 'systems', 'renderer', 'theme']); + const snippetsArray = brewSnippetsToJSON('brew_snippets', brew.snippets, null, false).snippets; + metadata.snippets = snippetsArray.length > 0 ? snippetsArray : undefined; text = `\`\`\`metadata\n` + `${yaml.dump(metadata)}\n` + `\`\`\`\n\n` + `${text}`; return text; }, + getGoodBrewTitle : (text)=>{ const tokens = Markdown.marked.lexer(text); return (tokens.find((token)=>token.type === 'heading' || token.type === 'paragraph')?.text || 'No Title') @@ -279,6 +284,8 @@ const api = { let currentTheme; const completeStyles = []; const completeSnippets = []; + let themeName; + let themeAuthor; while (req.params.id) { //=== User Themes ===// @@ -292,15 +299,20 @@ const api = { currentTheme = req.brew; splitTextStyleAndMetadata(currentTheme); + if(!currentTheme.tags.some((tag)=>tag === 'meta:theme' || tag === 'meta:Theme')) + throw { brewId: req.params.id, name: 'Invalid Theme Selected', message: 'Selected theme does not have the meta:theme tag', status: 422, HBErrorCode: '10' }; + themeName ??= currentTheme.title; + themeAuthor ??= currentTheme.authors?.[0]; // If there is anything in the snippets or style members, append them to the appropriate array - if(currentTheme?.snippets) completeSnippets.push(JSON.parse(currentTheme.snippets)); + if(currentTheme?.snippets) completeSnippets.push({ name: currentTheme.title, snippets: currentTheme.snippets }); if(currentTheme?.style) completeStyles.push(`/* From Brew: ${req.protocol}://${req.get('host')}/share/${req.params.id} */\n\n${currentTheme.style}`); req.params.id = currentTheme.theme; req.params.renderer = currentTheme.renderer; } else { //=== Static Themes ===// + themeName ??= req.params.id; const localSnippets = `${req.params.renderer}_${req.params.id}`; // Just log the name for loading on client const localStyle = `@import url(\"/themes/${req.params.renderer}/${req.params.id}/style.css\");`; completeSnippets.push(localSnippets); @@ -313,7 +325,9 @@ const api = { const returnObj = { // Reverse the order of the arrays so they are listed oldest parent to youngest child. styles : completeStyles.reverse(), - snippets : completeSnippets.reverse() + snippets : completeSnippets.reverse(), + name : themeName, + author : themeAuthor }; res.setHeader('Content-Type', 'application/json'); diff --git a/server/homebrew.api.spec.js b/server/homebrew.api.spec.js index 8270b1568..8bb3a0c0b 100644 --- a/server/homebrew.api.spec.js +++ b/server/homebrew.api.spec.js @@ -302,7 +302,7 @@ describe('Tests for api', ()=>{ }); it('access is denied to a locked brew', async()=>{ - const lockBrew = { title: 'test brew', shareId: '1', lock: { locked: true, code: 404, shareMessage: 'brew locked' } }; + const lockBrew = { title: 'test brew', shareId: '1', lock: { code: 404, shareMessage: 'brew locked' } }; model.get = jest.fn(()=>toBrewPromise(lockBrew)); api.getId = jest.fn(()=>({ id: '1', googleId: undefined })); @@ -576,7 +576,7 @@ brew`); describe('Theme bundle', ()=>{ it('should return Theme Bundle for a User Theme', async ()=>{ const brews = { - userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: null, shareId: 'userThemeAID', style: 'User Theme A Style' } + userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: null, shareId: 'userThemeAID', style: 'User Theme A Style', tags: ['meta:theme'], authors: ['authorName'] } }; const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew })); @@ -587,6 +587,8 @@ brew`); expect(res.status).toHaveBeenCalledWith(200); expect(res.send).toHaveBeenCalledWith({ + name : 'User Theme A', + author : 'authorName', styles : ['/* From Brew: https://localhost/share/userThemeAID */\n\nUser Theme A Style'], snippets : [] }); @@ -594,9 +596,9 @@ brew`); it('should return Theme Bundle for nested User Themes', async ()=>{ const brews = { - userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: 'userThemeBID', shareId: 'userThemeAID', style: 'User Theme A Style' }, - userThemeBID : { title: 'User Theme B', renderer: 'V3', theme: 'userThemeCID', shareId: 'userThemeBID', style: 'User Theme B Style' }, - userThemeCID : { title: 'User Theme C', renderer: 'V3', theme: null, shareId: 'userThemeCID', style: 'User Theme C Style' } + userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: 'userThemeBID', shareId: 'userThemeAID', style: 'User Theme A Style', tags: ['meta:theme'], authors: ['authorName'] }, + userThemeBID : { title: 'User Theme B', renderer: 'V3', theme: 'userThemeCID', shareId: 'userThemeBID', style: 'User Theme B Style', tags: ['meta:theme'], authors: ['authorName'] }, + userThemeCID : { title: 'User Theme C', renderer: 'V3', theme: null, shareId: 'userThemeCID', style: 'User Theme C Style', tags: ['meta:theme'], authors: ['authorName'] } }; const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew })); @@ -607,6 +609,8 @@ brew`); expect(res.status).toHaveBeenCalledWith(200); expect(res.send).toHaveBeenCalledWith({ + name : 'User Theme A', + author : 'authorName', styles : [ '/* From Brew: https://localhost/share/userThemeCID */\n\nUser Theme C Style', '/* From Brew: https://localhost/share/userThemeBID */\n\nUser Theme B Style', @@ -623,6 +627,8 @@ brew`); expect(res.status).toHaveBeenCalledWith(200); expect(res.send).toHaveBeenCalledWith({ + name : '5ePHB', + author : undefined, styles : [ `/* From Theme Blank */\n\n@import url("/themes/V3/Blank/style.css");`, `/* From Theme 5ePHB */\n\n@import url("/themes/V3/5ePHB/style.css");` @@ -636,9 +642,9 @@ brew`); it('should return Theme Bundle for nested User and Static Themes together', async ()=>{ const brews = { - userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: 'userThemeBID', shareId: 'userThemeAID', style: 'User Theme A Style' }, - userThemeBID : { title: 'User Theme B', renderer: 'V3', theme: 'userThemeCID', shareId: 'userThemeBID', style: 'User Theme B Style' }, - userThemeCID : { title: 'User Theme C', renderer: 'V3', theme: '5eDMG', shareId: 'userThemeCID', style: 'User Theme C Style' } + userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: 'userThemeBID', shareId: 'userThemeAID', style: 'User Theme A Style', tags: ['meta:theme'], authors: ['authorName'] }, + userThemeBID : { title: 'User Theme B', renderer: 'V3', theme: 'userThemeCID', shareId: 'userThemeBID', style: 'User Theme B Style', tags: ['meta:theme'], authors: ['authorName'] }, + userThemeCID : { title: 'User Theme C', renderer: 'V3', theme: '5eDMG', shareId: 'userThemeCID', style: 'User Theme C Style', tags: ['meta:theme'], authors: ['authorName'] } }; const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew })); @@ -649,6 +655,8 @@ brew`); expect(res.status).toHaveBeenCalledWith(200); expect(res.send).toHaveBeenCalledWith({ + name : 'User Theme A', + author : 'authorName', styles : [ `/* From Theme Blank */\n\n@import url("/themes/V3/Blank/style.css");`, `/* From Theme 5ePHB */\n\n@import url("/themes/V3/5ePHB/style.css");`, @@ -665,9 +673,9 @@ brew`); }); }); - it('should fail for an invalid Theme in the chain', async()=>{ + it('should fail for a missing Theme in the chain', async()=>{ const brews = { - userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: 'missingTheme', shareId: 'userThemeAID', style: 'User Theme A Style' }, + userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: 'missingTheme', shareId: 'userThemeAID', style: 'User Theme A Style', tags: ['meta:theme'], authors: ['authorName'] }, }; const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew })); @@ -686,6 +694,27 @@ brew`); name : 'ThemeLoad Error', status : 404 }); }); + + it('should fail for a User Theme not tagged with meta:theme', async ()=>{ + const brews = { + userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: null, shareId: 'userThemeAID', style: 'User Theme A Style' } + }; + + const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew })); + model.get = jest.fn((getParams)=>toBrewPromise(brews[getParams.shareId])); + const req = { params: { renderer: 'V3', id: 'userThemeAID' }, get: ()=>{ return 'localhost'; }, protocol: 'https' }; + + let err; + await api.getThemeBundle(req, res) + .catch((e)=>err = e); + + expect(err).toEqual({ + HBErrorCode : '10', + brewId : 'userThemeAID', + message : 'Selected theme does not have the meta:theme tag', + name : 'Invalid Theme Selected', + status : 422 }); + }); }); describe('deleteBrew', ()=>{ @@ -910,7 +939,7 @@ brew`); }); describe('Get CSS', ()=>{ it('should return brew style content as CSS text', async ()=>{ - const testBrew = { title: 'test brew', text: '```css\n\nI Have a style!\n````\n\n' }; + const testBrew = { title: 'test brew', text: '```css\n\nI Have a style!\n```\n\n' }; const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew })); api.getId = jest.fn(()=>({ id: '1', googleId: undefined })); @@ -1005,7 +1034,7 @@ brew`); expect(testBrew.theme).toEqual('5ePHB'); expect(testBrew.lang).toEqual('en'); // Style - expect(testBrew.style).toEqual('style\nstyle\nstyle'); + expect(testBrew.style).toEqual('style\nstyle\nstyle\n'); // Text expect(testBrew.text).toEqual('text\n'); }); diff --git a/server/homebrew.model.js b/server/homebrew.model.js index adeac0d5d..2e74b1de2 100644 --- a/server/homebrew.model.js +++ b/server/homebrew.model.js @@ -27,7 +27,9 @@ const HomebrewSchema = mongoose.Schema({ updatedAt : { type: Date, default: Date.now }, lastViewed : { type: Date, default: Date.now }, views : { type: Number, default: 0 }, - version : { type: Number, default: 1 } + version : { type: Number, default: 1 }, + + lock : { type: Object } }, { versionKey: false }); HomebrewSchema.statics.increaseView = async function(query) { @@ -63,7 +65,7 @@ HomebrewSchema.statics.getByUser = async function(username, allowAccess=false, f const Homebrew = mongoose.model('Homebrew', HomebrewSchema); -export { +export { HomebrewSchema as schema, Homebrew as model }; diff --git a/server/vault.api.js b/server/vault.api.js index 6a7b9fb91..3a6c6e989 100644 --- a/server/vault.api.js +++ b/server/vault.api.js @@ -1,6 +1,6 @@ import express from 'express'; import asyncHandler from 'express-async-handler'; -import {model as HomebrewModel } from './homebrew.model.js'; +import { model as HomebrewModel } from './homebrew.model.js'; const router = express.Router(); @@ -29,7 +29,7 @@ const rendererConditions = (legacy, v3)=>{ return {}; // If all renderers selected, renderer field not needed in query for speed }; -const sortConditions = (sort, dir) => { +const sortConditions = (sort, dir)=>{ return { [sort]: dir === 'asc' ? 1 : -1 }; }; diff --git a/shared/helpers.js b/shared/helpers.js index b2190cdcd..0ca681dfb 100644 --- a/shared/helpers.js +++ b/shared/helpers.js @@ -2,24 +2,103 @@ import _ from 'lodash'; import yaml from 'js-yaml'; import request from '../client/homebrew/utils/request-middleware.js'; +// Convert the templates from a brew to a Snippets Structure. +const brewSnippetsToJSON = (menuTitle, userBrewSnippets, themeBundleSnippets=null, full=true)=>{ + const textSplit = /^(\\snippet +.+\n)/gm; + const mpAsSnippets = []; + // Snippets from Themes first. + if(themeBundleSnippets) { + for (let themes of themeBundleSnippets) { + if(typeof themes !== 'string') { + const userSnippets = []; + const snipSplit = themes.snippets.trim().split(textSplit).slice(1); + for (let snips = 0; snips < snipSplit.length; snips+=2) { + if(!snipSplit[snips].startsWith('\\snippet ')) break; + const snippetName = snipSplit[snips].split(/\\snippet +/)[1].split('\n')[0].trim(); + if(snippetName.length != 0) { + userSnippets.push({ + name : snippetName, + icon : '', + gen : snipSplit[snips + 1], + }); + } + } + if(userSnippets.length > 0) { + mpAsSnippets.push({ + name : themes.name, + icon : '', + gen : '', + subsnippets : userSnippets + }); + } + } + } + } + // Local Snippets + if(userBrewSnippets) { + const userSnippets = []; + const snipSplit = userBrewSnippets.trim().split(textSplit).slice(1); + for (let snips = 0; snips < snipSplit.length; snips+=2) { + if(!snipSplit[snips].startsWith('\\snippet ')) break; + const snippetName = snipSplit[snips].split(/\\snippet +/)[1].split('\n')[0].trim(); + if(snippetName.length != 0) { + const subSnip = { + name : snippetName, + gen : snipSplit[snips + 1], + }; + // if(full) subSnip.icon = ''; + userSnippets.push(subSnip); + } + } + if(userSnippets.length) { + mpAsSnippets.push({ + name : menuTitle, + // icon : '', + subsnippets : userSnippets + }); + } + } + + const returnObj = { + snippets : mpAsSnippets + }; + + if(full) { + returnObj.groupName = 'Brew Snippets'; + returnObj.icon = 'fas fa-th-list'; + returnObj.view = 'text'; + } + + return returnObj; +}; + +const yamlSnippetsToText = (yamlObj)=>{ + if(typeof yamlObj == 'string') return yamlObj; + + let snippetsText = ''; + + for (let snippet of yamlObj) { + for (let subSnippet of snippet.subsnippets) { + snippetsText = `${snippetsText}\\snippet ${subSnippet.name}\n${subSnippet.gen || ''}\n`; + } + } + return snippetsText; +}; + const splitTextStyleAndMetadata = (brew)=>{ brew.text = brew.text.replaceAll('\r\n', '\n'); if(brew.text.startsWith('```metadata')) { - const index = brew.text.indexOf('```\n\n'); - const metadataSection = brew.text.slice(12, index - 1); + const index = brew.text.indexOf('\n```\n\n'); + const metadataSection = brew.text.slice(11, index + 1); const metadata = yaml.load(metadataSection); Object.assign(brew, _.pick(metadata, ['title', 'description', 'tags', 'systems', 'renderer', 'theme', 'lang'])); - brew.text = brew.text.slice(index + 5); + brew.snippets = yamlSnippetsToText(_.pick(metadata, ['snippets']).snippets || ''); + brew.text = brew.text.slice(index + 6); } if(brew.text.startsWith('```css')) { - const index = brew.text.indexOf('```\n\n'); - brew.style = brew.text.slice(7, index - 1); - brew.text = brew.text.slice(index + 5); - } - if(brew.text.startsWith('```snippets')) { - const index = brew.text.indexOf('```\n\n'); - brew.snippets = brew.text.slice(11, index - 1); - brew.text = brew.text.slice(index + 5); + const index = brew.text.indexOf('\n```\n\n'); + brew.style = brew.text.slice(7, index + 1); + brew.text = brew.text.slice(index + 6); } // Handle old brews that still have empty strings in the tags metadata @@ -44,13 +123,19 @@ const fetchThemeBundle = async (obj, renderer, theme)=>{ .catch((err)=>{ obj.setState({ error: err }); }); - if(!res) return; - + if(!res) { + obj.setState((prevState)=>({ + ...prevState, + themeBundle : {} + })); + return; + } const themeBundle = res.body; themeBundle.joinedStyles = themeBundle.styles.map((style)=>``).join('\n\n'); obj.setState((prevState)=>({ ...prevState, - themeBundle : themeBundle + themeBundle : themeBundle, + error : null })); }; @@ -58,4 +143,5 @@ export { splitTextStyleAndMetadata, printCurrentBrew, fetchThemeBundle, + brewSnippetsToJSON }; diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index 852243d81..6f5fa6fc6 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -1,11 +1,16 @@ +/* eslint-disable max-depth */ /* eslint-disable max-lines */ import _ from 'lodash'; import { Parser as MathParser } from 'expr-eval'; -import { marked as Marked } from 'marked'; +import { marked as Marked } from 'marked'; import MarkedExtendedTables from 'marked-extended-tables'; import { markedSmartypantsLite as MarkedSmartypantsLite } from 'marked-smartypants-lite'; import { gfmHeadingId as MarkedGFMHeadingId, resetHeadings as MarkedGFMResetHeadingIDs } from 'marked-gfm-heading-id'; import { markedEmoji as MarkedEmojis } from 'marked-emoji'; +import MarkedNonbreakingSpaces from 'marked-nonbreaking-spaces'; +import MarkedSubSuperText from 'marked-subsuper-text'; +import { romanize } from 'romans'; +import writtenNumber from 'written-number'; //Icon fonts included so they can appear in emoji autosuggest dropdown import diceFont from '../../themes/fonts/iconFonts/diceFont.js'; @@ -57,9 +62,57 @@ mathParser.functions.signed = function (a) { if(a >= 0) return `+${a}`; return `${a}`; }; +// Add Roman numeral functions +mathParser.functions.toRomans = function (a) { + return romanize(a); +}; +mathParser.functions.toRomansUpper = function (a) { + return romanize(a).toUpperCase(); +}; +mathParser.functions.toRomansLower = function (a) { + return romanize(a).toLowerCase(); +}; +// Add character functions +mathParser.functions.toChar = function (a) { + if(a <= 0) return a; + const genChars = function (i) { + return (i > 26 ? genChars(Math.floor((i - 1) / 26)) : '') + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[(i - 1) % 26]; + }; + return genChars(a); +}; +mathParser.functions.toCharUpper = function (a) { + return mathParser.functions.toChar(a).toUpperCase(); +}; +mathParser.functions.toCharLower = function (a) { + return mathParser.functions.toChar(a).toLowerCase(); +}; +// Add word functions +mathParser.functions.toWords = function (a) { + return writtenNumber(a); +}; +mathParser.functions.toWordsUpper = function (a) { + return mathParser.functions.toWords(a).toUpperCase(); +}; +mathParser.functions.toWordsLower = function (a) { + return mathParser.functions.toWords(a).toLowerCase(); +}; +mathParser.functions.toWordsCaps = function (a) { + const words = mathParser.functions.toWords(a).split(' '); + return words.map((word)=>{ + return word.replace(/(?:^|\b|\s)(\w)/g, function(w, index) { + return index === 0 ? w.toLowerCase() : w.toUpperCase(); + }); + }).join(' '); +}; + +// Normalize variable names; trim edge spaces and shorten blocks of whitespace to 1 space +const normalizeVarNames = (label)=>{ + return label.trim().replace(/\s+/g, ' '); +}; //Processes the markdown within an HTML block if it's just a class-wrapper -renderer.html = function (html) { +renderer.html = function (token) { + let html = token.text; if(_.startsWith(_.trim(html), '')){ const openTag = html.substring(0, html.indexOf('>')+1); html = html.substring(html.indexOf('>')+1); @@ -70,18 +123,21 @@ renderer.html = function (html) { }; // Don't wrap {{ Spans alone on a line, or {{ Divs in

    tags -renderer.paragraph = function(text){ +renderer.paragraph = function(token){ let match; + const text = this.parser.parseInline(token.tokens); if(text.startsWith(')$/)) return `${match[1].trim() ? `

    ${match[1]}

    ` : ''}`${key}="${value}"`).join(' ')}` : ''}` + + `${tags.styles ? ` style="${Object.entries(tags.styles).map(([key, value])=>`${key}:${value};`).join(' ')}"` : ''}` + + `${tags.attributes ? ` ${Object.entries(tags.attributes).map(([key, value])=>`${key}="${value}"`).join(' ')}` : ''}` + `>${this.parser.parseInline(token.tokens)}`; // parseInline to turn child tokens into HTML } }; @@ -228,7 +284,7 @@ const mustacheDivs = { return ``${key}="${value}"`).join(' ')}` : ''}` + `>${this.parser.parse(token.tokens)}
    `; // parse to turn child tokens into HTML } @@ -265,18 +321,13 @@ const mustacheInjectInline = { const text = this.parser.parseInline([token]); const originalTags = extractHTMLStyleTags(text); const injectedTags = token.injectedTags; - const tags = { - id : injectedTags.id || originalTags.id || null, - classes : [originalTags.classes, injectedTags.classes].join(' ').trim() || null, - styles : [originalTags.styles, injectedTags.styles].join(' ').trim() || null, - attributes : Object.assign(originalTags.attributes ?? {}, injectedTags.attributes ?? {}) - }; + const tags = mergeHTMLTags(originalTags, injectedTags); const openingTag = /(<[^\s<>]+)[^\n<>]*(>.*)/s.exec(text); if(openingTag) { return `${openingTag[1]}` + `${tags.classes ? ` class="${tags.classes}"` : ''}` + `${tags.id ? ` id="${tags.id}"` : ''}` + - `${tags.styles ? ` style="${tags.styles}"` : ''}` + + `${!_.isEmpty(tags.styles) ? ` style="${Object.entries(tags.styles).map(([key, value])=>`${key}:${value};`).join(' ')}"` : ''}` + `${!_.isEmpty(tags.attributes) ? ` ${Object.entries(tags.attributes).map(([key, value])=>`${key}="${value}"`).join(' ')}` : ''}` + `${openingTag[2]}`; // parse to turn child tokens into HTML } @@ -314,18 +365,13 @@ const mustacheInjectBlock = { const text = this.parser.parse([token]); const originalTags = extractHTMLStyleTags(text); const injectedTags = token.injectedTags; - const tags = { - id : injectedTags.id || originalTags.id || null, - classes : [originalTags.classes, injectedTags.classes].join(' ').trim() || null, - styles : [originalTags.styles, injectedTags.styles].join(' ').trim() || null, - attributes : Object.assign(originalTags.attributes ?? {}, injectedTags.attributes ?? {}) - }; + const tags = mergeHTMLTags(originalTags, injectedTags); const openingTag = /(<[^\s<>]+)[^\n<>]*(>.*)/s.exec(text); if(openingTag) { return `${openingTag[1]}` + `${tags.classes ? ` class="${tags.classes}"` : ''}` + `${tags.id ? ` id="${tags.id}"` : ''}` + - `${tags.styles ? ` style="${tags.styles}"` : ''}` + + `${!_.isEmpty(tags.styles) ? ` style="${Object.entries(tags.styles).map(([key, value])=>`${key}:${value};`).join(' ')}"` : ''}` + `${!_.isEmpty(tags.attributes) ? ` ${Object.entries(tags.attributes).map(([key, value])=>`${key}="${value}"`).join(' ')}` : ''}` + `${openingTag[2]}`; // parse to turn child tokens into HTML } @@ -342,34 +388,42 @@ const mustacheInjectBlock = { } }; -const superSubScripts = { - name : 'superSubScript', - level : 'inline', - start(src) { return src.match(/\^/m)?.index; }, // Hint to Marked.js to stop and check for a match +const justifiedParagraphClasses = []; +justifiedParagraphClasses[2] = 'Left'; +justifiedParagraphClasses[4] = 'Right'; +justifiedParagraphClasses[6] = 'Center'; + +const justifiedParagraphs = { + name : 'justifiedParagraphs', + level : 'block', + start(src) { + return src.match(/\n(?:-:|:-|-:) {1}/m)?.index; + }, // Hint to Marked.js to stop and check for a match tokenizer(src, tokens) { - const superRegex = /^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^/m; - const subRegex = /^\^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^\^/m; - let isSuper = false; - let match = subRegex.exec(src); - if(!match){ - match = superRegex.exec(src); - if(match) - isSuper = true; - } + const regex = /^(((:-))|((-:))|((:-:))) .+(\n(([^\n].*\n)*(\n|$))|$)/ygm; + const match = regex.exec(src); if(match?.length) { + let whichJustify; + if(match[2]?.length) whichJustify = 2; + if(match[4]?.length) whichJustify = 4; + if(match[6]?.length) whichJustify = 6; return { - type : 'superSubScript', // Should match "name" above - raw : match[0], // Text to consume from the source - tag : isSuper ? 'sup' : 'sub', - tokens : this.lexer.inlineTokens(match[1]) + type : 'justifiedParagraphs', // Should match "name" above + raw : match[0], // Text to consume from the source + length : match[whichJustify].length, + text : match[0].slice(match[whichJustify].length), + class : justifiedParagraphClasses[whichJustify], + tokens : this.lexer.inlineTokens(match[0].slice(match[whichJustify].length + 1)) }; } }, renderer(token) { - return `<${token.tag}>${this.parser.parseInline(token.tokens)}`; + return `

    ${this.parser.parseInline(token.tokens)}

    `; } + }; + const forcedParagraphBreaks = { name : 'hardBreaks', level : 'block', @@ -387,28 +441,7 @@ const forcedParagraphBreaks = { } }, renderer(token) { - return `
    `.repeat(token.length).concat('\n'); - } -}; - -const nonbreakingSpaces = { - name : 'nonbreakingSpaces', - level : 'inline', - start(src) { return src.match(/:>+/m)?.index; }, // Hint to Marked.js to stop and check for a match - tokenizer(src, tokens) { - const regex = /:(>+)/ym; - const match = regex.exec(src); - if(match?.length) { - return { - type : 'nonbreakingSpaces', // Should match "name" above - raw : match[0], // Text to consume from the source - length : match[1].length, - text : '' - }; - } - }, - renderer(token) { - return ` `.repeat(token.length).concat(''); + return `
    \n`.repeat(token.length); } }; @@ -505,7 +538,7 @@ const replaceVar = function(input, hoist=false, allowUnresolved=false) { const match = regex.exec(input); const prefix = match[1]; - const label = match[2]; + const label = normalizeVarNames(match[2]); // Ensure the label name is normalized as it should be in the var stack. //v=====--------------------< HANDLE MATH >-------------------=====v// const mathRegex = /[a-z]+\(|[+\-*/^(),]/g; @@ -660,8 +693,8 @@ function MarkedVariables() { }); } if(match[3]) { // Block Definition - const label = match[4] ? match[4].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space - const content = match[5] ? match[5].trim().replace(/[ \t]+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space + const label = match[4] ? normalizeVarNames(match[4]) : null; + const content = match[5] ? match[5].trim().replace(/[ \t]+/g, ' ') : null; // Normalize text content (except newlines for block-level content) varsQueue.push( { type : 'varDefBlock', @@ -670,7 +703,7 @@ function MarkedVariables() { }); } if(match[6]) { // Block Call - const label = match[7] ? match[7].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space + const label = match[7] ? normalizeVarNames(match[7]) : null; varsQueue.push( { type : 'varCallBlock', @@ -679,8 +712,8 @@ function MarkedVariables() { }); } if(match[8]) { // Inline Definition - const label = match[10] ? match[10].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space - let content = match[11] ? match[11].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space + const label = match[10] ? normalizeVarNames(match[10]) : null; + let content = match[11] || null; // In case of nested (), find the correct matching end ) let level = 0; @@ -696,10 +729,8 @@ function MarkedVariables() { break; } } - if(i > -1) { - combinedRegex.lastIndex = combinedRegex.lastIndex - (content.length - i); - content = content.slice(0, i).trim().replace(/\s+/g, ' '); - } + combinedRegex.lastIndex = combinedRegex.lastIndex - (content.length - i); + content = content.slice(0, i).trim().replace(/\s+/g, ' '); varsQueue.push( { type : 'varDefBlock', @@ -713,7 +744,7 @@ function MarkedVariables() { }); } if(match[12]) { // Inline Call - const label = match[13] ? match[13].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space + const label = match[13] ? normalizeVarNames(match[13]) : null; varsQueue.push( { type : 'varCallInline', @@ -769,11 +800,13 @@ const tableTerminators = [ ]; Marked.use(MarkedVariables()); -Marked.use({ extensions : [definitionListsMultiLine, definitionListsSingleLine, forcedParagraphBreaks, - nonbreakingSpaces, superSubScripts, mustacheSpans, mustacheDivs, mustacheInjectInline] }); +Marked.use({ extensions : [justifiedParagraphs, definitionListsMultiLine, definitionListsSingleLine, forcedParagraphBreaks, + mustacheSpans, mustacheDivs, mustacheInjectInline] }); Marked.use(mustacheInjectBlock); +Marked.use(MarkedSubSuperText()); +Marked.use(MarkedNonbreakingSpaces()); Marked.use({ renderer: renderer, tokenizer: tokenizer, mangle: false }); -Marked.use(MarkedExtendedTables(tableTerminators), MarkedGFMHeadingId({ globalSlugs: true }), +Marked.use(MarkedExtendedTables({ interruptPatterns: tableTerminators }), MarkedGFMHeadingId({ globalSlugs: true }), MarkedSmartypantsLite(), MarkedEmojis(MarkedEmojiOptions)); function cleanUrl(href) { @@ -835,15 +868,20 @@ const processStyleTags = (string)=>{ const index = attr.indexOf('='); let [key, value] = [attr.substring(0, index), attr.substring(index + 1)]; value = value.replace(/"/g, ''); - obj[key] = value; + obj[key.trim()] = value.trim(); return obj; }, {}) || null; - const styles = tags?.length ? tags.map((tag)=>tag.replace(/:"?([^"]*)"?/g, ':$1;').trim()).join(' ') : null; + const styles = tags?.length ? tags.reduce((styleObj, style)=>{ + const index = style.indexOf(':'); + const [key, value] = [style.substring(0, index), style.substring(index + 1)]; + styleObj[key.trim()] = value.replace(/"?([^"]*)"?/g, '$1').trim(); + return styleObj; + }, {}) : null; return { id : id, classes : classes, - styles : styles, + styles : _.isEmpty(styles) ? null : styles, attributes : _.isEmpty(attributes) ? null : attributes }; }; @@ -853,25 +891,40 @@ const extractHTMLStyleTags = (htmlString)=>{ const firstElementOnly = htmlString.split('>')[0]; const id = firstElementOnly.match(/id="([^"]*)"/)?.[1] || null; const classes = firstElementOnly.match(/class="([^"]*)"/)?.[1] || null; - const styles = firstElementOnly.match(/style="([^"]*)"/)?.[1] || null; + const styles = firstElementOnly.match(/style="([^"]*)"/)?.[1] + ?.split(';').reduce((styleObj, style)=>{ + if(style.trim() === '') return styleObj; + const index = style.indexOf(':'); + const [key, value] = [style.substring(0, index), style.substring(index + 1)]; + styleObj[key.trim()] = value.trim(); + return styleObj; + }, {}) || null; const attributes = firstElementOnly.match(/[a-zA-Z]+="[^"]*"/g) ?.filter((attr)=>!attr.startsWith('class="') && !attr.startsWith('style="') && !attr.startsWith('id="')) .reduce((obj, attr)=>{ const index = attr.indexOf('='); - let [key, value] = [attr.substring(0, index), attr.substring(index + 1)]; - value = value.replace(/"/g, ''); - obj[key] = value; + const [key, value] = [attr.substring(0, index), attr.substring(index + 1)]; + obj[key.trim()] = value.replace(/"/g, ''); return obj; }, {}) || null; return { id : id, classes : classes, - styles : styles, + styles : _.isEmpty(styles) ? null : styles, attributes : _.isEmpty(attributes) ? null : attributes }; }; +const mergeHTMLTags = (originalTags, newTags)=>{ + return { + id : newTags.id || originalTags.id || null, + classes : [originalTags.classes, newTags.classes].join(' ').trim() || null, + styles : Object.assign(originalTags.styles ?? {}, newTags.styles ?? {}), + attributes : Object.assign(originalTags.attributes ?? {}, newTags.attributes ?? {}) + }; +}; + const globalVarsList = {}; let varsQueue = []; let globalPageNumber = 0; @@ -879,7 +932,13 @@ let globalPageNumber = 0; const Markdown = { marked : Marked, render : (rawBrewText, pageNumber=0)=>{ - globalVarsList[pageNumber] = {}; //Reset global links for current page, to ensure values are parsed in order + const lastPageNumber = pageNumber > 0 ? globalVarsList[pageNumber - 1].HB_pageNumber.content : 0; + globalVarsList[pageNumber] = { //Reset global links for current page, to ensure values are parsed in order + 'HB_pageNumber' : { //Add document variables for this page + content : !isNaN(Number(lastPageNumber)) ? Number(lastPageNumber) + 1 : lastPageNumber, + resolved : true + } + }; varsQueue = []; //Could move into MarkedVariables() globalPageNumber = pageNumber; if(pageNumber==0) { diff --git a/shared/naturalcrit/nav/nav.jsx b/shared/naturalcrit/nav/nav.jsx index d9b403239..50dff4c33 100644 --- a/shared/naturalcrit/nav/nav.jsx +++ b/shared/naturalcrit/nav/nav.jsx @@ -12,8 +12,8 @@ const Nav = { displayName : 'Nav.base', render : function(){ return ; + {this.props.children} + ; } }), logo : function(){ diff --git a/shared/naturalcrit/splitPane/splitPane.jsx b/shared/naturalcrit/splitPane/splitPane.jsx index 1500c759f..4c77d81a5 100644 --- a/shared/naturalcrit/splitPane/splitPane.jsx +++ b/shared/naturalcrit/splitPane/splitPane.jsx @@ -29,8 +29,8 @@ const SplitPane = (props)=>{ const limitPosition = (x, min = 1, max = window.innerWidth - 13)=>Math.round(Math.min(max, Math.max(min, x))); //when resizing, the divider should grow smaller if less space is given, then grow back if the space is restored, to the original position - const handleResize = () =>setDividerPos(limitPosition(window.localStorage.getItem(storageKey), 0.1 * (window.innerWidth - 13), 0.9 * (window.innerWidth - 13))); - + const handleResize = ()=>setDividerPos(limitPosition(window.localStorage.getItem(storageKey), 0.1 * (window.innerWidth - 13), 0.9 * (window.innerWidth - 13))); + const handleUp =(e)=>{ e.preventDefault(); if(isDragging) { diff --git a/shared/naturalcrit/splitPane/splitPane.less b/shared/naturalcrit/splitPane/splitPane.less index 8b61097be..80a8695af 100644 --- a/shared/naturalcrit/splitPane/splitPane.less +++ b/shared/naturalcrit/splitPane/splitPane.less @@ -21,8 +21,8 @@ background-color : #BBBBBB; .dots { display : table-cell; - text-align : center; vertical-align : middle; + text-align : center; i { display : block !important; margin : 10px 0px; diff --git a/shared/naturalcrit/styles/animations.less b/shared/naturalcrit/styles/animations.less index 69aac3e09..5757df151 100644 --- a/shared/naturalcrit/styles/animations.less +++ b/shared/naturalcrit/styles/animations.less @@ -3,127 +3,127 @@ @defaultEasing : ease; //Animates all properties on an element -.animateAll(@duration : @defaultDuration, @easing : @defaultEasing){ - -webkit-transition: all @duration @easing; - -moz-transition: all @duration @easing; - -o-transition: all @duration @easing; - transition: all @duration @easing; +.animateAll(@duration : @defaultDuration, @easing : @defaultEasing) { + -webkit-transition : all @duration @easing; + -moz-transition : all @duration @easing; + -o-transition : all @duration @easing; + transition : all @duration @easing; } //Animates Specific property -.animate(@prop, @duration : @defaultDuration, @easing : @defaultEasing){ - -webkit-transition: @prop @duration @easing; - -moz-transition: @prop @duration @easing; - -o-transition: @prop @duration @easing; - transition: @prop @duration @easing; +.animate(@prop, @duration : @defaultDuration, @easing : @defaultEasing) { + -webkit-transition : @prop @duration @easing; + -moz-transition : @prop @duration @easing; + -o-transition : @prop @duration @easing; + transition : @prop @duration @easing; } -.animateMany(...){ +.animateMany(...) { @value: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`; - -webkit-transition-property: @value; - -moz-transition-property: @value; - -o-transition-property: @value; - transition-property: @value; + -webkit-transition-property : @value; + -moz-transition-property : @value; + -o-transition-property : @value; + transition-property : @value; .animateDuration(); .animateEasing(); } -.animateDuration(@duration : @defaultDuration){ - -webkit-transition-duration: @duration; - -moz-transition-duration: @duration; - -o-transition-duration: @duration; - transition-duration: @duration; +.animateDuration(@duration : @defaultDuration) { + -webkit-transition-duration : @duration; + -moz-transition-duration : @duration; + -o-transition-duration : @duration; + transition-duration : @duration; } -.animateEasing(@easing : @defaultEasing){ - -webkit-transition-timing-function: @easing; - -moz-transition-timing-function: @easing; - -o-transition-timing-function: @easing; - transition-timing-function: @easing; +.animateEasing(@easing : @defaultEasing) { + -webkit-transition-timing-function : @easing; + -moz-transition-timing-function : @easing; + -o-transition-timing-function : @easing; + transition-timing-function : @easing; } .transition (@prop, @duration: @defaultDuration) { - -webkit-transition: @prop @duration, -webkit-transform @duration; - -moz-transition: @prop @duration, -moz-transform @duration; - -o-transition: @prop @duration, -o-transform @duration; - -ms-transition: @prop @duration, -ms-transform @duration; - transition: @prop @duration, transform @duration; + -webkit-transition : @prop @duration, -webkit-transform @duration; + -moz-transition : @prop @duration, -moz-transform @duration; + -o-transition : @prop @duration, -o-transform @duration; + -ms-transition : @prop @duration, -ms-transform @duration; + transition : @prop @duration, transform @duration; } .transform (@transform) { - -webkit-transform: @transform; - -moz-transform: @transform; - -o-transform: @transform; - -ms-transform: @transform; - transform: @transform; + -webkit-transform : @transform; + -moz-transform : @transform; + -o-transform : @transform; + -ms-transform : @transform; + transform : @transform; } -.delay(@delay){ - animation-delay:@delay; - -webkit-animation-delay:@delay; - transition-delay:@delay; - -webkit-transition-delay:@delay; +.delay(@delay) { + -webkit-transition-delay : @delay; + transition-delay : @delay; + -webkit-animation-delay : @delay; + animation-delay : @delay; } -.keep(){ - -webkit-animation-fill-mode:forwards; - -moz-animation-fill-mode:forwards; - -ms-animation-fill-mode:forwards; - -o-animation-fill-mode:forwards; - animation-fill-mode:forwards; +.keep() { + -webkit-animation-fill-mode : forwards; + -moz-animation-fill-mode : forwards; + -ms-animation-fill-mode : forwards; + -o-animation-fill-mode : forwards; + animation-fill-mode : forwards; } -.sequentialDelay(@delayInc : 0.2s, @initialDelay : 0s){ - &:nth-child(1){.delay(0*@delayInc + @initialDelay)} - &:nth-child(2){.delay(1*@delayInc + @initialDelay)} - &:nth-child(3){.delay(2*@delayInc + @initialDelay)} - &:nth-child(4){.delay(3*@delayInc + @initialDelay)} - &:nth-child(5){.delay(4*@delayInc + @initialDelay)} - &:nth-child(6){.delay(5*@delayInc + @initialDelay)} - &:nth-child(7){.delay(6*@delayInc + @initialDelay)} - &:nth-child(8){.delay(7*@delayInc + @initialDelay)} - &:nth-child(9){.delay(8*@delayInc + @initialDelay)} - &:nth-child(10){.delay(9*@delayInc + @initialDelay)} - &:nth-child(11){.delay(10*@delayInc + @initialDelay)} - &:nth-child(12){.delay(11*@delayInc + @initialDelay)} - &:nth-child(13){.delay(12*@delayInc + @initialDelay)} - &:nth-child(14){.delay(13*@delayInc + @initialDelay)} - &:nth-child(15){.delay(14*@delayInc + @initialDelay)} - &:nth-child(16){.delay(15*@delayInc + @initialDelay)} - &:nth-child(17){.delay(16*@delayInc + @initialDelay)} - &:nth-child(18){.delay(17*@delayInc + @initialDelay)} - &:nth-child(19){.delay(18*@delayInc + @initialDelay)} - &:nth-child(20){.delay(19*@delayInc + @initialDelay)} +.sequentialDelay(@delayInc : 0.2s, @initialDelay : 0s) { + &:nth-child(1) {.delay(0*@delayInc + @initialDelay); } + &:nth-child(2) {.delay(1*@delayInc + @initialDelay); } + &:nth-child(3) {.delay(2*@delayInc + @initialDelay); } + &:nth-child(4) {.delay(3*@delayInc + @initialDelay); } + &:nth-child(5) {.delay(4*@delayInc + @initialDelay); } + &:nth-child(6) {.delay(5*@delayInc + @initialDelay); } + &:nth-child(7) {.delay(6*@delayInc + @initialDelay); } + &:nth-child(8) {.delay(7*@delayInc + @initialDelay); } + &:nth-child(9) {.delay(8*@delayInc + @initialDelay); } + &:nth-child(10) {.delay(9*@delayInc + @initialDelay); } + &:nth-child(11) {.delay(10*@delayInc + @initialDelay); } + &:nth-child(12) {.delay(11*@delayInc + @initialDelay); } + &:nth-child(13) {.delay(12*@delayInc + @initialDelay); } + &:nth-child(14) {.delay(13*@delayInc + @initialDelay); } + &:nth-child(15) {.delay(14*@delayInc + @initialDelay); } + &:nth-child(16) {.delay(15*@delayInc + @initialDelay); } + &:nth-child(17) {.delay(16*@delayInc + @initialDelay); } + &:nth-child(18) {.delay(17*@delayInc + @initialDelay); } + &:nth-child(19) {.delay(18*@delayInc + @initialDelay); } + &:nth-child(20) {.delay(19*@delayInc + @initialDelay); } } -.createFrames(@name, @from, @to){ +.createFrames(@name, @from, @to) { @frames: { from { @from(); } to { @to(); } }; - @-webkit-keyframes @name {@frames();} - @-moz-keyframes @name {@frames();} - @-ms-keyframes @name {@frames();} - @-o-keyframes @name {@frames();} - @keyframes @name {@frames();} + @-webkit-keyframes @name {@frames();} + @-moz-keyframes @name {@frames();} + @-ms-keyframes @name {@frames();} + @-o-keyframes @name {@frames();} + @keyframes @name {@frames();} } -.createAnimation(@name, @duration : @defaultDuration, @easing : @defaultEasing){ - -webkit-animation-name: @name; - -moz-animation-name: @name; - -ms-animation-name: @name; - animation-name: @name; - -webkit-animation-duration: @duration; - -moz-animation-duration: @duration; - -ms-animation-duration: @duration; - animation-duration: @duration; - -webkit-animation-timing-function: @easing; - -moz-animation-timing-function: @easing; - -ms-animation-timing-function: @easing; - animation-timing-function: @easing; +.createAnimation(@name, @duration : @defaultDuration, @easing : @defaultEasing) { + -webkit-animation-name : @name; + -moz-animation-name : @name; + -ms-animation-name : @name; + animation-name : @name; + -webkit-animation-duration : @duration; + -moz-animation-duration : @duration; + -ms-animation-duration : @duration; + animation-duration : @duration; + -webkit-animation-timing-function : @easing; + -moz-animation-timing-function : @easing; + -ms-animation-timing-function : @easing; + animation-timing-function : @easing; } @@ -132,82 +132,82 @@ Standard Animations ****************************/ -.fadeIn(@duration : @defaultDuration, @easing : @defaultEasing){ +.fadeIn(@duration : @defaultDuration, @easing : @defaultEasing) { .createAnimation(fadeIn; @duration; @easing); .createFrames(fadeIn, - { opacity : 0; }, + { opacity : 0; }, { opacity : 1; } ); } -.fadeInDown(@duration : @defaultDuration, @easing : @defaultEasing){ +.fadeInDown(@duration : @defaultDuration, @easing : @defaultEasing) { .createAnimation(fadeInDown; @duration; @easing); .createFrames(fadeInDown, - { opacity : 0; .transform(translateY(20px));}, + { opacity : 0; .transform(translateY(20px));}, { opacity : 1; .transform(translateY(0px));} ); } -.fadeInTop(@duration : @defaultDuration, @easing : @defaultEasing){ +.fadeInTop(@duration : @defaultDuration, @easing : @defaultEasing) { .createAnimation(fadeInTop; @duration; @easing); .createFrames(fadeInTop, - { opacity : 0; .transform(translateY(-20px)); }, + { opacity : 0; .transform(translateY(-20px)); }, { opacity : 1; .transform(translateY(0px));} ); } -.fadeInLeft(@duration : @defaultDuration, @easing : @defaultEasing){ +.fadeInLeft(@duration : @defaultDuration, @easing : @defaultEasing) { .createAnimation(fadeInLeft; @duration; @easing); .createFrames(fadeInLeft, - { opacity: 0; .transform(translateX(-20px));}, + { opacity: 0; .transform(translateX(-20px));}, { opacity: 1; .transform(translateX(0));} ); } -.fadeInRight(@duration : @defaultDuration, @easing : @defaultEasing){ +.fadeInRight(@duration : @defaultDuration, @easing : @defaultEasing) { .createAnimation(fadeInRight; @duration; @easing); .createFrames(fadeInRight, - { opacity: 0; .transform(translateX(20px));}, + { opacity: 0; .transform(translateX(20px));}, { opacity: 1; .transform(translateX(0));} ); } -.fadeOut(@duration : @defaultDuration, @easing : @defaultEasing){ +.fadeOut(@duration : @defaultDuration, @easing : @defaultEasing) { .createAnimation(fadeOut; @duration; @easing); .createFrames(fadeOut, - { opacity : 1; }, + { opacity : 1; }, { opacity : 0; } ); } -.fadeOutDown(@duration : @defaultDuration, @easing : @defaultEasing){ +.fadeOutDown(@duration : @defaultDuration, @easing : @defaultEasing) { .createAnimation(fadeOutDown; @duration; @easing); .createFrames(fadeOutDown, - { opacity : 1; .transform(translateY(0)); visibility: visible;}, + { opacity : 1; .transform(translateY(0)); visibility: visible;}, { opacity : 0; .transform(translateY(20px)); visibility: hidden;} ); } -.fadeOutTop(@duration : @defaultDuration, @easing : @defaultEasing){ +.fadeOutTop(@duration : @defaultDuration, @easing : @defaultEasing) { .createAnimation(fadeOutTop; @duration; @easing); .createFrames(fadeOutTop, - { opacity : 1; .transform(translateY(0)); }, + { opacity : 1; .transform(translateY(0)); }, { opacity : 0; .transform(translateY(-20px)); } ); } -.fadeOutLeft(@duration : @defaultDuration, @easing : @defaultEasing){ +.fadeOutLeft(@duration : @defaultDuration, @easing : @defaultEasing) { .createAnimation(fadeOutLeft; @duration; @easing); .createFrames(fadeOutLeft, - { opacity : 1; .transform(translateX(0));}, + { opacity : 1; .transform(translateX(0));}, { opacity : 0; .transform(translateX(-20px));} ); } -.fadeOutRight(@duration : @defaultDuration, @easing : @defaultEasing){ +.fadeOutRight(@duration : @defaultDuration, @easing : @defaultEasing) { .createAnimation(fadeOutRight; @duration; @easing); .createFrames(fadeOutRight, - { opacity : 1; .transform(translateX(0));}, + { opacity : 1; .transform(translateX(0));}, { opacity : 0; .transform(translateX(20px));} ); } @@ -219,50 +219,50 @@ Fun Animations ****************************/ -.spin(@duration : @defaultDuration, @easing : @defaultEasing){ +.spin(@duration : @defaultDuration, @easing : @defaultEasing) { .createAnimation(spin, @duration, @easing); - .spinKeyFrames(){ + .spinKeyFrames() { from { .transform(rotate(0deg)); } - to { .transform(rotate(360deg)); } + to { .transform(rotate(360deg)); } } @-webkit-keyframes spin {.spinKeyFrames();} - @-moz-keyframes spin {.spinKeyFrames();} - @-ms-keyframes spin {.spinKeyFrames();} - @-o-keyframes spin {.spinKeyFrames();} - @keyframes spin {.spinKeyFrames();} + @-moz-keyframes spin {.spinKeyFrames();} + @-ms-keyframes spin {.spinKeyFrames();} + @-o-keyframes spin {.spinKeyFrames();} + @keyframes spin {.spinKeyFrames();} } -.bounce(@duration : @defaultDuration, @easing : @defaultEasing){ +.bounce(@duration : @defaultDuration, @easing : @defaultEasing) { .createAnimation(bounce, @duration, @easing); - .bounceKeyFrames(){ + .bounceKeyFrames() { 0%, 20%, 50%, 80%, 100% { .transform(translateY(0));} 40% { .transform(translateY(-30px));} 60% { .transform(translateY(-15px));} } @-webkit-keyframes bounce {.bounceKeyFrames();} - @-moz-keyframes bounce {.bounceKeyFrames();} - @-ms-keyframes bounce {.bounceKeyFrames();} - @-o-keyframes bounce {.bounceKeyFrames();} - @keyframes bounce {.bounceKeyFrames();} + @-moz-keyframes bounce {.bounceKeyFrames();} + @-ms-keyframes bounce {.bounceKeyFrames();} + @-o-keyframes bounce {.bounceKeyFrames();} + @keyframes bounce {.bounceKeyFrames();} } -.pulse(@duration : @defaultDuration, @easing : @defaultEasing){ +.pulse(@duration : @defaultDuration, @easing : @defaultEasing) { .createAnimation(pulse, @duration, @easing); - .pulseKeyFrames(){ - 0% { .transform(scale(1));} - 50% { .transform(scale(1.4));} + .pulseKeyFrames() { + 0% { .transform(scale(1));} + 50% { .transform(scale(1.4));} 100% { .transform(scale(1));} } @-webkit-keyframes pulse {.pulseKeyFrames();} - @-moz-keyframes pulse {.pulseKeyFrames();} - @-ms-keyframes pulse {.pulseKeyFrames();} - @-o-keyframes pulse {.pulseKeyFrames();} - @keyframes pulse {.pulseKeyFrames();} + @-moz-keyframes pulse {.pulseKeyFrames();} + @-ms-keyframes pulse {.pulseKeyFrames();} + @-o-keyframes pulse {.pulseKeyFrames();} + @keyframes pulse {.pulseKeyFrames();} } -.rubberBand(@duration : @defaultDuration, @easing : @defaultEasing){ +.rubberBand(@duration : @defaultDuration, @easing : @defaultEasing) { .createAnimation(rubberBand, @duration, @easing); - .rubberBandKeyFrames(){ + .rubberBandKeyFrames() { 0% {.transform(scale(1));} 30% {.transform(scaleX(1.25) scaleY(0.75));} 40% {.transform(scaleX(0.75) scaleY(1.25));} @@ -270,32 +270,32 @@ 100% {.transform(scale(1));} } @-webkit-keyframes rubberBand {.rubberBandKeyFrames();} - @-moz-keyframes rubberBand {.rubberBandKeyFrames();} - @-ms-keyframes rubberBand {.rubberBandKeyFrames();} - @-o-keyframes rubberBand {.rubberBandKeyFrames();} - @keyframes rubberBand {.rubberBandKeyFrames();} + @-moz-keyframes rubberBand {.rubberBandKeyFrames();} + @-ms-keyframes rubberBand {.rubberBandKeyFrames();} + @-o-keyframes rubberBand {.rubberBandKeyFrames();} + @keyframes rubberBand {.rubberBandKeyFrames();} } -.shake(@duration : @defaultDuration, @easing : @defaultEasing){ +.shake(@duration : @defaultDuration, @easing : @defaultEasing) { .createAnimation(shake, @duration, @easing); - .shakeKeyFrames(){ + .shakeKeyFrames() { 0%, 100% {.transform( translateX(0));} 10%, 30%, 50%, 70%, 90% {.transform( translateX(-10px));} 20%, 40%, 60%, 80% {.transform( translateX(10px));} } @-webkit-keyframes shake {.shakeKeyFrames();} - @-moz-keyframes shake {.shakeKeyFrames();} - @-ms-keyframes shake {.shakeKeyFrames();} - @-o-keyframes shake {.shakeKeyFrames();} - @keyframes shake {.shakeKeyFrames();} + @-moz-keyframes shake {.shakeKeyFrames();} + @-ms-keyframes shake {.shakeKeyFrames();} + @-o-keyframes shake {.shakeKeyFrames();} + @keyframes shake {.shakeKeyFrames();} } -.swing(@duration : @defaultDuration, @easing : @defaultEasing){ - -webkit-transform-origin: top center; - -ms-transform-origin: top center; - transform-origin: top center; +.swing(@duration : @defaultDuration, @easing : @defaultEasing) { + -webkit-transform-origin : top center; + -ms-transform-origin : top center; + transform-origin : top center; .createAnimation(swing, @duration, @easing); - .swingKeyFrames(){ + .swingKeyFrames() { 20% {.transform(rotate(15deg));} 40% {.transform(rotate(-10deg));} 60% {.transform(rotate(5deg));} @@ -303,18 +303,18 @@ 100% {.transform(rotate(0deg));} } @-webkit-keyframes swing {.swingKeyFrames();} - @-moz-keyframes swing {.swingKeyFrames();} - @-ms-keyframes swing {.swingKeyFrames();} - @-o-keyframes swing {.swingKeyFrames();} - @keyframes swing {.swingKeyFrames();} + @-moz-keyframes swing {.swingKeyFrames();} + @-ms-keyframes swing {.swingKeyFrames();} + @-o-keyframes swing {.swingKeyFrames();} + @keyframes swing {.swingKeyFrames();} } -.twist(@duration : @defaultDuration, @easing : @defaultEasing){ - -webkit-transform-origin: center center; - -ms-transform-origin: center center; - transform-origin: center center; +.twist(@duration : @defaultDuration, @easing : @defaultEasing) { + -webkit-transform-origin : center center; + -ms-transform-origin : center center; + transform-origin : center center; .createAnimation(swing, @duration, @easing); - .swingKeyFrames(){ + .swingKeyFrames() { 20% {.transform(rotate(15deg));} 40% {.transform(rotate(-10deg));} 60% {.transform(rotate(5deg));} @@ -322,15 +322,15 @@ 100% {.transform(rotate(0deg));} } @-webkit-keyframes swing {.swingKeyFrames();} - @-moz-keyframes swing {.swingKeyFrames();} - @-ms-keyframes swing {.swingKeyFrames();} - @-o-keyframes swing {.swingKeyFrames();} - @keyframes swing {.swingKeyFrames();} + @-moz-keyframes swing {.swingKeyFrames();} + @-ms-keyframes swing {.swingKeyFrames();} + @-o-keyframes swing {.swingKeyFrames();} + @keyframes swing {.swingKeyFrames();} } -.wobble(@duration : @defaultDuration, @easing : @defaultEasing){ +.wobble(@duration : @defaultDuration, @easing : @defaultEasing) { .createAnimation(wobble, @duration, @easing); - .wobbleKeyFrames(){ + .wobbleKeyFrames() { 0% {.transform(translateX(0%));} 15% {.transform(translateX(-25%) rotate(-5deg));} 30% {.transform(translateX(20%) rotate(3deg));} @@ -340,22 +340,22 @@ 100% {.transform(translateX(0%));} } @-webkit-keyframes wobble {.wobbleKeyFrames();} - @-moz-keyframes wobble {.wobbleKeyFrames();} - @-ms-keyframes wobble {.wobbleKeyFrames();} - @-o-keyframes wobble {.wobbleKeyFrames();} - @keyframes wobble {.wobbleKeyFrames();} + @-moz-keyframes wobble {.wobbleKeyFrames();} + @-ms-keyframes wobble {.wobbleKeyFrames();} + @-o-keyframes wobble {.wobbleKeyFrames();} + @keyframes wobble {.wobbleKeyFrames();} } -.popIn(@duration : @defaultDuration, @easing : @defaultEasing){ +.popIn(@duration : @defaultDuration, @easing : @defaultEasing) { .createAnimation(popIn, @duration, @easing); - .popInKeyFrames(){ - 0% { .transform(scale(0));} - 70% { .transform(scale(1.4));} + .popInKeyFrames() { + 0% { .transform(scale(0));} + 70% { .transform(scale(1.4));} 100% { .transform(scale(1));} } @-webkit-keyframes popIn {.popInKeyFrames();} - @-moz-keyframes popIn {.popInKeyFrames();} - @-ms-keyframes popIn {.popInKeyFrames();} - @-o-keyframes popIn {.popInKeyFrames();} - @keyframes popIn {.popInKeyFrames();} + @-moz-keyframes popIn {.popInKeyFrames();} + @-ms-keyframes popIn {.popInKeyFrames();} + @-o-keyframes popIn {.popInKeyFrames();} + @keyframes popIn {.popInKeyFrames();} } diff --git a/shared/naturalcrit/styles/colors.less b/shared/naturalcrit/styles/colors.less index 30a7610a2..c096b9b4f 100644 --- a/shared/naturalcrit/styles/colors.less +++ b/shared/naturalcrit/styles/colors.less @@ -23,47 +23,47 @@ @grey : #7F8C8D; #backgroundColors { - &.tealLight{ background-color : @tealLight }; - &.teal{ background-color : @teal }; - &.greenLight{ background-color : @greenLight }; - &.green{ background-color : @green }; - &.blueLight{ background-color : @blueLight }; - &.blue{ background-color : @blue }; - &.purpleLight{ background-color : @purpleLight }; - &.purple{ background-color : @purple }; - &.steelLight{ background-color : @steelLight }; - &.steel{ background-color : @steel }; - &.yellowLight{ background-color : @yellowLight }; - &.yellow{ background-color : @yellow }; - &.orangeLight{ background-color : @orangeLight }; - &.orange{ background-color : @orange }; - &.redLight{ background-color : @redLight }; - &.red{ background-color : @red }; - &.silverLight{ background-color : @silverLight }; - &.silver{ background-color : @silver }; - &.greyLight{ background-color : @greyLight }; - &.grey{ background-color : @grey }; + &.tealLight { background-color : @tealLight; }; + &.teal { background-color : @teal; }; + &.greenLight { background-color : @greenLight; }; + &.green { background-color : @green; }; + &.blueLight { background-color : @blueLight; }; + &.blue { background-color : @blue; }; + &.purpleLight { background-color : @purpleLight; }; + &.purple { background-color : @purple; }; + &.steelLight { background-color : @steelLight; }; + &.steel { background-color : @steel; }; + &.yellowLight { background-color : @yellowLight; }; + &.yellow { background-color : @yellow; }; + &.orangeLight { background-color : @orangeLight; }; + &.orange { background-color : @orange; }; + &.redLight { background-color : @redLight; }; + &.red { background-color : @red; }; + &.silverLight { background-color : @silverLight; }; + &.silver { background-color : @silver; }; + &.greyLight { background-color : @greyLight; }; + &.grey { background-color : @grey; }; } #backgroundColorsHover { - &.tealLight:hover{ background-color : @tealLight }; - &.teal:hover{ background-color : @teal }; - &.greenLight:hover{ background-color : @greenLight }; - &.green:hover{ background-color : @green }; - &.blueLight:hover{ background-color : @blueLight }; - &.blue:hover{ background-color : @blue }; - &.purpleLight:hover{ background-color : @purpleLight }; - &.purple:hover{ background-color : @purple }; - &.steelLight:hover{ background-color : @steelLight }; - &.steel:hover{ background-color : @steel }; - &.yellowLight:hover{ background-color : @yellowLight }; - &.yellow:hover{ background-color : @yellow }; - &.orangeLight:hover{ background-color : @orangeLight }; - &.orange:hover{ background-color : @orange }; - &.redLight:hover{ background-color : @redLight }; - &.red:hover{ background-color : @red }; - &.silverLight:hover{ background-color : @silverLight }; - &.silver:hover{ background-color : @silver }; - &.greyLight:hover{ background-color : @greyLight }; - &.grey:hover{ background-color : @grey }; + &.tealLight:hover { background-color : @tealLight; }; + &.teal:hover { background-color : @teal; }; + &.greenLight:hover { background-color : @greenLight; }; + &.green:hover { background-color : @green; }; + &.blueLight:hover { background-color : @blueLight; }; + &.blue:hover { background-color : @blue; }; + &.purpleLight:hover { background-color : @purpleLight; }; + &.purple:hover { background-color : @purple; }; + &.steelLight:hover { background-color : @steelLight; }; + &.steel:hover { background-color : @steel; }; + &.yellowLight:hover { background-color : @yellowLight; }; + &.yellow:hover { background-color : @yellow; }; + &.orangeLight:hover { background-color : @orangeLight; }; + &.orange:hover { background-color : @orange; }; + &.redLight:hover { background-color : @redLight; }; + &.red:hover { background-color : @red; }; + &.silverLight:hover { background-color : @silverLight; }; + &.silver:hover { background-color : @silver; }; + &.greyLight:hover { background-color : @greyLight; }; + &.grey:hover { background-color : @grey; }; } \ No newline at end of file diff --git a/shared/naturalcrit/styles/core.less b/shared/naturalcrit/styles/core.less index 3248269c5..02db5db18 100644 --- a/shared/naturalcrit/styles/core.less +++ b/shared/naturalcrit/styles/core.less @@ -12,37 +12,31 @@ font-family : 'CodeBold'; src : data-uri('naturalcrit/styles/CODE Bold.otf') format('opentype'); } -html,body, #reactRoot{ +html,body, #reactRoot { height : 100vh; min-height : 100vh; margin : 0; font-family : 'Open Sans', sans-serif; } -*{ - box-sizing : border-box; -} -.colorButton(@backgroundColor : @green){ +* { box-sizing : border-box; } +.colorButton(@backgroundColor : @green) { .animate(background-color); display : inline-block; padding : 0.6em 1.2em; - cursor : pointer; - background-color : @backgroundColor; font-family : 'Open Sans', sans-serif; font-size : 0.8em; font-weight : 800; color : white; - text-decoration : none; text-transform : uppercase; - border : none; + text-decoration : none; + cursor : pointer; outline : none; - &:hover{ - background-color : darken(@backgroundColor, 5%); - } - &:active{ - background-color : darken(@backgroundColor, 10%); - } - &:disabled{ + background-color : @backgroundColor; + border : none; + &:hover { background-color : darken(@backgroundColor, 5%); } + &:active { background-color : darken(@backgroundColor, 10%); } + &:disabled { + cursor : not-allowed; background-color : @silver !important; - cursor:not-allowed; } } diff --git a/shared/naturalcrit/styles/elements.less b/shared/naturalcrit/styles/elements.less index e7f8b05f5..ca4574f26 100644 --- a/shared/naturalcrit/styles/elements.less +++ b/shared/naturalcrit/styles/elements.less @@ -1,86 +1,76 @@ @containerWidth : 1000px; -html, body{ +html, body { position : relative; height : 100%; min-height : 100%; - background-color : #eee; font-family : 'Lato', sans-serif; color : @copyGrey; + background-color : #EEEEEE; } -.container{ +.container { position : relative; max-width : @containerWidth; - margin : 0 auto; padding-right : 20px; padding-left : 20px; + margin : 0 auto; } -h1{ +h1 { margin-top : 10px; margin-bottom : 15px; font-size : 2em; } -h2{ +h2 { margin-top : 10px; margin-bottom : 15px; font-size : 1.5em; font-weight : 900; } -h3{ +h3 { margin-top : 5px; margin-bottom : 7px; font-size : 1em; font-weight : 900; } -p{ +p { margin-bottom : 1em; font-size : 16px; - color : @copyGrey; line-height : 1.5em; + color : @copyGrey; } -code{ - background-color : #F8F8F8; - font-family : 'Courier', mono; +code { + font-family : 'Courier', "mono"; color : black; white-space : pre; + background-color : #F8F8F8; } -a{ - color : inherit; -} -strong{ - font-weight : bold; -} -button{ +a { color : inherit; } +strong { font-weight : bold; } +button { .button(); } -.button(@backgroundColor : @green){ +.button(@backgroundColor : @green) { .animate(background-color); display : inline-block; padding : 0.6em 1.2em; - cursor : pointer; - background-color : @backgroundColor; - font-family : "Lato", Helvetica, Arial, sans-serif; + font-family : 'Lato', "Helvetica", "Arial", sans-serif; font-size : 15px; color : white; text-decoration : none; - border : none; - outline : none; - &:hover{ - background-color : darken(@backgroundColor, 5%); - } - &:active{ - background-color : darken(@backgroundColor, 10%); - } - &:disabled{ - background-color : @silver !important; - } -} -.iconButton(@backgroundColor : @green){ - padding : 0.6em; cursor : pointer; + outline : none; background-color : @backgroundColor; + border : none; + &:hover { background-color : darken(@backgroundColor, 5%); } + &:active { background-color : darken(@backgroundColor, 10%); } + &:disabled { background-color : @silver !important; } +} +.iconButton(@backgroundColor : @green) { + padding : 0.6em; font-size : 14px; color : white; text-align : center; + cursor : pointer; + background-color : @backgroundColor; } \ No newline at end of file diff --git a/shared/naturalcrit/styles/reset.less b/shared/naturalcrit/styles/reset.less index df5564a21..21e07a1c0 100644 --- a/shared/naturalcrit/styles/reset.less +++ b/shared/naturalcrit/styles/reset.less @@ -1,33 +1,23 @@ -:where(html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,button,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video){ - border:0;font-size:100%;font:inherit;vertical-align:baseline;margin:0;padding:0 +:where(html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,button,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video) {padding : 0;margin : 0;font : inherit;font-size : 100%;vertical-align : baseline; + border : 0; } -:where(article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section){ - display:block -} +:where(article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section) { display : block; } -:where(body){ - line-height:1 -} +:where(body) { line-height : 1; } -:where(ol,ul){ - list-style:none -} +:where(ol,ul) { list-style : none; } -:where(blockquote,q){ - quotes:none -} +:where(blockquote,q) { quotes : none; } -:where(blockquote:before,blockquote:after,q:before,q:after){ - content:none -} +:where(blockquote::before,blockquote::after,q::before,q::after) { content : none; } -:where(table){ - border-collapse:collapse;border-spacing:0 +:where(table) {border-spacing : 0; + border-collapse : collapse; } :where(button) { - background-color: unset; - text-transform: unset; - color: unset; + color : unset; + text-transform : unset; + background-color : unset; } diff --git a/shared/naturalcrit/styles/tooltip.less b/shared/naturalcrit/styles/tooltip.less index 078cfd0c1..b21439486 100644 --- a/shared/naturalcrit/styles/tooltip.less +++ b/shared/naturalcrit/styles/tooltip.less @@ -2,116 +2,115 @@ @tooltipColor : #383838; @arrowSize : 6px; @arrowPosition : 18px; -[data-tooltip]{ +[data-tooltip] { .tooltip(attr(data-tooltip)); } -[data-tooltip-top]{ +[data-tooltip-top] { .tooltipTop(attr(data-tooltip-top)); } -[data-tooltip-bottom]{ +[data-tooltip-bottom] { .tooltipBottom(attr(data-tooltip-bottom)); } -[data-tooltip-left]{ +[data-tooltip-left] { .tooltipLeft(attr(data-tooltip-left)); } -[data-tooltip-right]{ +[data-tooltip-right] { .tooltipRight(attr(data-tooltip-right)); } -.tooltip(@content){ +.tooltip(@content) { .tooltipBottom(@content); } -.tooltipTop(@content){ +.tooltipTop(@content) { .tooltipBase(@content); - &:before { + &::before { margin-bottom : -@arrowSize * 2; border-top-color : @tooltipColor; } - &:after{ margin-left: -18px; } - &:before, &:after{ + &::after { margin-left : -18px; } + &::before, &::after { bottom : 100%; left : 50%; } - &:hover:after, &:hover:before, &:focus:after, &:focus:before { + &:hover::after, &:hover::before, &:focus::after, &:focus::before { .transform(translateY(-(@arrowSize + 2))); } } -.tooltipBottom(@content){ +.tooltipBottom(@content) { .tooltipBase(@content); - &:before { + &::before { margin-top : -@arrowSize * 2; border-bottom-color : @tooltipColor; } - &:after{ margin-left: -18px; } - &:before, &:after{ + &::after { margin-left : -18px; } + &::before, &::after { top : 100%; left : 50%; } - &:hover:after, &:hover:before, &:focus:after, &:focus:before { + &:hover::after, &:hover::before, &:focus::after, &:focus::before { .transform(translateY(@arrowSize + 2)); } } -.tooltipLeft(@content){ +.tooltipLeft(@content) { .tooltipBase(@content); - &:before { + &::before { margin-right : -@arrowSize * 2; margin-bottom : -@arrowSize; border-left-color : @tooltipColor; } - &:after{ margin-bottom: -14px;} - &:before, &:after { + &::after { margin-bottom : -14px;} + &::before, &::after { right : 100%; bottom : 50%; } - &:hover:after, &:hover:before, &:focus:after, &:focus:before { + &:hover::after, &:hover::before, &:focus::after, &:focus::before { .transform(translateX(-(@arrowSize + 2))); } } -.tooltipRight(@content){ +.tooltipRight(@content) { .tooltipBase(@content); - &:before { + &::before { margin-bottom : -@arrowSize; margin-left : -@arrowSize * 2; border-right-color : @tooltipColor; } - &:after{ margin-bottom: -14px;} - &:before, &:after { + &::after { margin-bottom : -14px;} + &::before, &::after { bottom : 50%; left : 100%; } - &:hover:after, &:hover:before, &:focus:after, &:focus:before { + &:hover::after, &:hover::before, &:focus::after, &:focus::before { .transform(translateX(@arrowSize + 2)); } } -.tooltipShow(){ -} -.tooltipBase(@content){ +.tooltipShow(){ } +.tooltipBase(@content) { //position: relative; - &:before, &:after{ + &::before, &::after { .animateAll(); position : absolute; z-index : 1000000; - opacity : 0; pointer-events : none; + opacity : 0; } //Arrow - &:before{ - content : ''; + &::before { z-index : 1000001; + content : ''; background : transparent; border : @arrowSize solid transparent; } //Box - &:after{ - content : @content; + &::after { visibility : hidden; padding : 8px 10px; - background : @tooltipColor; font-size : 12px; - color : white; line-height : 12px; + color : white; white-space : nowrap; + content : @content; + background : @tooltipColor; } - &:hover:before, &:hover:after { + &:hover::before, &:hover::after { visibility : visible; opacity : 1; } diff --git a/stylelint_plugins/declaration-block-multi-line-min-declarations.js b/stylelint_plugins/declaration-block-multi-line-min-declarations.js index ba40ab5f6..517fd27ec 100644 --- a/stylelint_plugins/declaration-block-multi-line-min-declarations.js +++ b/stylelint_plugins/declaration-block-multi-line-min-declarations.js @@ -1,5 +1,5 @@ -const stylelint = require('stylelint'); -const { isNumber } = require('stylelint/lib/utils/validateTypes.cjs'); +import stylelint from 'stylelint'; +import { isNumber } from 'stylelint/lib/utils/validateTypes.mjs'; const { report, ruleMessages, validateOptions } = stylelint.utils; const ruleName = 'naturalcrit/declaration-block-multi-line-min-declarations'; @@ -7,9 +7,8 @@ const messages = ruleMessages(ruleName, { expected : (decls)=>`Rule with ${decls} declaration${decls == 1 ? '' : 's'} should be single line`, }); - -module.exports = stylelint.createPlugin(ruleName, function getPlugin(primaryOption, secondaryOptionObject, context) { - return function lint(postcssRoot, postcssResult) { +const ruleFunction = (primaryOption, secondaryOptionObject, context)=>{ + return (postcssRoot, postcssResult)=>{ const validOptions = validateOptions( postcssResult, @@ -20,26 +19,23 @@ module.exports = stylelint.createPlugin(ruleName, function getPlugin(primaryOpti } ); - if(!validOptions) { //If the options are invalid, don't lint + if(!validOptions) //If the options are invalid, don't lint return; - } + const isAutoFixing = Boolean(context.fix); postcssRoot.walkRules((rule)=>{ //Iterate CSS rules //Apply rule only if all children are decls (no further nested rules) - if(rule.nodes.length > primaryOption || !rule.nodes.every((node)=>node.type === 'decl')) { + if(rule.nodes.length > primaryOption || !rule.nodes.every((node)=>node.type === 'decl')) return; - } //Ignore if already one line if(!rule.nodes.some((node)=>node.raws.before.includes('\n')) && !rule.raws.after.includes('\n')) return; if(isAutoFixing) { //We are in “fix” mode - rule.each((decl)=>{ - decl.raws.before = ' '; - }); + rule.each((decl)=>decl.raws.before = ' '); rule.raws.after = ' '; } else { report({ @@ -52,7 +48,9 @@ module.exports = stylelint.createPlugin(ruleName, function getPlugin(primaryOpti } }); }; -}); +}; -module.exports.ruleName = ruleName; -module.exports.messages = messages; +ruleFunction.ruleName = ruleName; +ruleFunction.messages = messages; + +export default stylelint.createPlugin(ruleName, ruleFunction); diff --git a/stylelint_plugins/declaration-colon-align.js b/stylelint_plugins/declaration-colon-align.js index f1f5269d3..aeb92fcd8 100644 --- a/stylelint_plugins/declaration-colon-align.js +++ b/stylelint_plugins/declaration-colon-align.js @@ -1,32 +1,29 @@ -const stylelint = require('stylelint'); - +import stylelint from 'stylelint'; const { report, ruleMessages, validateOptions } = stylelint.utils; + const ruleName = 'naturalcrit/declaration-colon-align'; const messages = ruleMessages(ruleName, { expected : (rule)=>`Expected colons aligned within rule "${rule}"`, }); - -module.exports = stylelint.createPlugin(ruleName, function getPlugin(primaryOption, secondaryOptionObject, context) { - return function lint(postcssRoot, postcssResult) { +const ruleFunction = (primaryOption, secondaryOptionObject, context)=>{ + return (postcssRoot, postcssResult)=>{ const validOptions = validateOptions( postcssResult, ruleName, { actual : primaryOption, - possible : [ - true, - false - ] + possible : [true, false] } ); - if(!validOptions) { //If the options are invalid, don't lint + if(!validOptions) // If the options are invalid, don't lint return; - } + const isAutoFixing = Boolean(context.fix); - postcssRoot.walkRules((rule)=>{ //Iterate CSS rules + + postcssRoot.walkRules((rule)=>{ // Iterate CSS rules let maxColonPos = 0; let misaligned = false; @@ -36,33 +33,37 @@ module.exports = stylelint.createPlugin(ruleName, function getPlugin(primaryOpti return; const colonPos = declaration.prop.length + declaration.raws.between.indexOf(':'); - if(maxColonPos > 0 && colonPos != maxColonPos) { + + if(maxColonPos > 0 && colonPos != maxColonPos) misaligned = true; - } + maxColonPos = Math.max(maxColonPos, colonPos); }); - if(misaligned) { - if(isAutoFixing) { //We are in “fix” mode - rule.each((declaration)=>{ - if(declaration.type != 'decl') - return; + if(!misaligned) + return; - declaration.raws.between = `${' '.repeat(maxColonPos - declaration.prop.length)}:${declaration.raws.between.split(':')[1]}`; - }); - } else { //We are in “report only” mode - report({ - ruleName, - result : postcssResult, - message : messages.expected(rule.selector), // Build the reported message - node : rule, // Specify the reported node - word : rule.selector, // Which exact word caused the error? This positions the error properly - }); - } + if(isAutoFixing) { // We are in “fix” mode + rule.each((declaration)=>{ + if(declaration.type != 'decl') + return; + + declaration.raws.between = `${' '.repeat(maxColonPos - declaration.prop.length)}:${declaration.raws.between.split(':')[1]}`; + }); + } else { // We are in “report only” mode + report({ + ruleName, + result : postcssResult, + message : messages.expected(rule.selector), // Build the reported message + node : rule, // Specify the reported node + word : rule.selector, // Which exact word caused the error? This positions the error properly + }); } }); }; -}); +}; -module.exports.ruleName = ruleName; -module.exports.messages = messages; +ruleFunction.ruleName = ruleName; +ruleFunction.messages = messages; + +export default stylelint.createPlugin(ruleName, ruleFunction); \ No newline at end of file diff --git a/stylelint_plugins/declaration-colon-min-space-before.js b/stylelint_plugins/declaration-colon-min-space-before.js index e75c035a6..7776c6fef 100644 --- a/stylelint_plugins/declaration-colon-min-space-before.js +++ b/stylelint_plugins/declaration-colon-min-space-before.js @@ -1,5 +1,5 @@ -const stylelint = require('stylelint'); -const { isNumber } = require('stylelint/lib/utils/validateTypes.cjs'); +import stylelint from 'stylelint'; +import { isNumber } from 'stylelint/lib/utils/validateTypes.mjs'; const { report, ruleMessages, validateOptions } = stylelint.utils; const ruleName = 'naturalcrit/declaration-colon-min-space-before'; @@ -7,9 +7,8 @@ const messages = ruleMessages(ruleName, { expected : (num)=>`Expected at least ${num} space${num == 1 ? '' : 's'} before ":"` }); - -module.exports = stylelint.createPlugin(ruleName, function getPlugin(primaryOption, secondaryOptionObject, context) { - return function lint(postcssRoot, postcssResult) { +const ruleFunction = (primaryOption, secondaryOptionObject, context)=>{ + return (postcssRoot, postcssResult)=>{ const validOptions = validateOptions( postcssResult, @@ -30,9 +29,9 @@ module.exports = stylelint.createPlugin(ruleName, function getPlugin(primaryOpti const between = decl.raws.between; const colonIndex = between.indexOf(':'); - if(between.slice(0, colonIndex).length >= primaryOption) { + if(between.slice(0, colonIndex).length >= primaryOption) return; - } + if(isAutoFixing) { //We are in “fix” mode decl.raws.between = between.slice(0, colonIndex).replace(/\s*$/, ' '.repeat(primaryOption)) + between.slice(colonIndex); } else { @@ -46,7 +45,9 @@ module.exports = stylelint.createPlugin(ruleName, function getPlugin(primaryOpti } }); }; -}); +}; -module.exports.ruleName = ruleName; -module.exports.messages = messages; +ruleFunction.ruleName = ruleName; +ruleFunction.messages = messages; + +export default stylelint.createPlugin(ruleName, ruleFunction); \ No newline at end of file diff --git a/tests/markdown/basic.test.js b/tests/markdown/basic.test.js index e5feec0b3..aaa2adf58 100644 --- a/tests/markdown/basic.test.js +++ b/tests/markdown/basic.test.js @@ -1,4 +1,4 @@ -/* eslint-disable max-lines */ + import Markdown from 'naturalcrit/markdown.js'; diff --git a/tests/markdown/definition-lists.test.js b/tests/markdown/definition-lists.test.js index 039754ca1..144dad880 100644 --- a/tests/markdown/definition-lists.test.js +++ b/tests/markdown/definition-lists.test.js @@ -1,4 +1,4 @@ -/* eslint-disable max-lines */ + import Markdown from 'naturalcrit/markdown.js'; @@ -92,12 +92,12 @@ describe('Multiline Definition Lists', ()=>{ test('Multiline Definition Term must have at least one non-empty Definition', function() { const source = 'Term 1\n::'; const rendered = Markdown.render(source).trim(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

    Term 1

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

    Term 1

    \n
    \n
    `); }); test('Multiline Definition List must have at least one non-newline character after ::', function() { const source = 'Term 1\n::\nDefinition 1\n\n'; const rendered = Markdown.render(source).trim(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

    Term 1

    \n
    \n

    Definition 1

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

    Term 1

    \n
    \n
    \n

    Definition 1

    `); }); }); diff --git a/tests/markdown/hard-breaks.test.js b/tests/markdown/hard-breaks.test.js index 8af102716..63bfaef9c 100644 --- a/tests/markdown/hard-breaks.test.js +++ b/tests/markdown/hard-breaks.test.js @@ -1,4 +1,4 @@ -/* eslint-disable max-lines */ + import Markdown from 'naturalcrit/markdown.js'; @@ -12,31 +12,31 @@ describe('Hard Breaks', ()=>{ test('Double Break', function() { const source = '::\n\n'; const rendered = Markdown.render(source).trim(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
    `); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
    \n
    `); }); test('Triple Break', function() { const source = ':::\n\n'; const rendered = Markdown.render(source).trim(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
    `); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
    \n
    \n
    `); }); test('Many Break', function() { const source = '::::::::::\n\n'; const rendered = Markdown.render(source).trim(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
    `); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    `); }); test('Multiple sets of Breaks', function() { const source = ':::\n:::\n:::'; const rendered = Markdown.render(source).trim(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
    \n
    \n
    `); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    `); }); test('Break directly between two paragraphs', function() { const source = 'Line 1\n::\nLine 2'; const rendered = Markdown.render(source).trim(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

    Line 1

    \n
    \n

    Line 2

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

    Line 1

    \n
    \n
    \n

    Line 2

    `); }); test('Ignored inside a code block', function() { diff --git a/tests/markdown/mustache-syntax.test.js b/tests/markdown/mustache-syntax.test.js index 261a5fd32..d17518411 100644 --- a/tests/markdown/mustache-syntax.test.js +++ b/tests/markdown/mustache-syntax.test.js @@ -300,7 +300,7 @@ describe('Injection: When an injection tag follows an element', ()=>{ it('Renders a span "text" with its own styles, appended with injected styles', function() { const source = '{{color:blue,height:10px text}}{width:10px,color:red}'; const rendered = Markdown.render(source); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('text'); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('text'); }); it('Renders a span "text" with its own classes, appended with injected classes', function() { @@ -429,7 +429,7 @@ describe('Injection: When an injection tag follows an element', ()=>{ }} {width:10px,color:red}`; const rendered = Markdown.render(source).trimReturns(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('

    text

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

    text

    '); }); it('Renders a span "text" with its own classes, appended with injected classes', function() { diff --git a/tests/markdown/non-breaking-spaces.test.js b/tests/markdown/non-breaking-spaces.test.js index 9dad4eb0f..e498645a6 100644 --- a/tests/markdown/non-breaking-spaces.test.js +++ b/tests/markdown/non-breaking-spaces.test.js @@ -1,72 +1,24 @@ -/* eslint-disable max-lines */ + import Markdown from 'naturalcrit/markdown.js'; -describe('Non-Breaking Spaces', ()=>{ - test('Single Space', function() { - const source = ':>\n\n'; - const rendered = Markdown.render(source).trim(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

     

    `); - }); - - test('Double Space', function() { - const source = ':>>\n\n'; - const rendered = Markdown.render(source).trim(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

      

    `); - }); - - test('Triple Space', function() { - const source = ':>>>\n\n'; - const rendered = Markdown.render(source).trim(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

       

    `); - }); - - test('Many Space', function() { - const source = ':>>>>>>>>>>\n\n'; - const rendered = Markdown.render(source).trim(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

              

    `); - }); - - test('Multiple sets of Spaces', function() { - const source = ':>>>\n:>>>\n:>>>'; - const rendered = Markdown.render(source).trim(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

       \n   \n   

    `); - }); - - test('Pair of inline Spaces', function() { - const source = ':>>:>>'; - const rendered = Markdown.render(source).trim(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

        

    `); - }); - - test('Space directly between two paragraphs', function() { - const source = 'Line 1\n:>>\nLine 2'; - const rendered = Markdown.render(source).trim(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

    Line 1\n  \nLine 2

    `); - }); - - test('Ignored inside a code block', function() { - const source = '```\n\n:>\n\n```\n'; - const rendered = Markdown.render(source).trim(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
    \n:>\n
    `); - }); - +describe('Non-Breaking Spaces Interactions', ()=>{ test('I am actually a single-line definition list!', function() { const source = 'Term ::> Definition 1\n'; const rendered = Markdown.render(source).trim(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
    Term
    > Definition 1
    \n
    `); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
    Term
    > Definition 1
    \n
    `); }); test('I am actually a definition list!', function() { const source = 'Term\n::> Definition 1\n'; const rendered = Markdown.render(source).trim(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
    Term
    \n
    > Definition 1
    `); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
    Term
    \n
    > Definition 1
    `); }); test('I am actually a two-term definition list!', function() { const source = 'Term\n::> Definition 1\n::>> Definition 2'; const rendered = Markdown.render(source).trim(); - expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
    Term
    \n
    > Definition 1
    \n
    >> Definition 2
    `); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
    Term
    \n
    > Definition 1
    \n
    >> Definition 2
    `); }); }); diff --git a/tests/markdown/paragraph-justification.test.js b/tests/markdown/paragraph-justification.test.js new file mode 100644 index 000000000..7876f5a26 --- /dev/null +++ b/tests/markdown/paragraph-justification.test.js @@ -0,0 +1,27 @@ + + +import Markdown from 'naturalcrit/markdown.js'; + +describe('Justification', ()=>{ + test('Left Justify', function() { + const source = ':- Hello'; + const rendered = Markdown.render(source); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

    Hello

    `); + }); + test('Right Justify', function() { + const source = '-: Hello'; + const rendered = Markdown.render(source); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

    Hello

    `); + }); + test('Center Justify', function() { + const source = ':-: Hello'; + const rendered = Markdown.render(source); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`

    Hello

    `); + }); + + test('Ignored inside a code block', function() { + const source = '```\n\n:- Hello\n\n```\n'; + const rendered = Markdown.render(source); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`
    \n:- Hello\n
    \n`); + }); +}); diff --git a/tests/markdown/variables.test.js b/tests/markdown/variables.test.js index be16e8a22..ea98fe56c 100644 --- a/tests/markdown/variables.test.js +++ b/tests/markdown/variables.test.js @@ -370,6 +370,30 @@ describe('Cross-page variables', ()=>{ const rendered = renderAllPages([source0, source1]).join('\n\\page\n').trimReturns(); expect(rendered, `Input:\n${[source0, source1].join('\n\\page\n')}`, { showPrefix: false }).toBe('

    two

    one

    \\page

    two

    '); }); + + it('Page numbering across pages : default', function() { + const source0 = `$[HB_pageNumber]\n\n`; + const source1 = `$[HB_pageNumber]\n\n`; + renderAllPages([source0, source1]).join('\n\\page\n').trimReturns(); //Requires one full render of document before hoisting is picked up + const rendered = renderAllPages([source0, source1]).join('\n\\page\n').trimReturns(); + expect(rendered, `Input:\n${[source0, source1].join('\n\\page\n')}`, { showPrefix: false }).toBe('

    1

    \\page

    2

    '); + }); + + it('Page numbering across pages : custom page number (Number)', function() { + const source0 = `[HB_pageNumber]:100\n\n$[HB_pageNumber]\n\n`; + const source1 = `$[HB_pageNumber]\n\n`; + renderAllPages([source0, source1]).join('\n\\page\n').trimReturns(); //Requires one full render of document before hoisting is picked up + const rendered = renderAllPages([source0, source1]).join('\n\\page\n').trimReturns(); + expect(rendered, `Input:\n${[source0, source1].join('\n\\page\n')}`, { showPrefix: false }).toBe('

    100

    \\page

    101

    '); + }); + + it('Page numbering across pages : custom page number (NaN)', function() { + const source0 = `[HB_pageNumber]:a\n\n$[HB_pageNumber]\n\n`; + const source1 = `$[HB_pageNumber]\n\n`; + renderAllPages([source0, source1]).join('\n\\page\n').trimReturns(); //Requires one full render of document before hoisting is picked up + const rendered = renderAllPages([source0, source1]).join('\n\\page\n').trimReturns(); + expect(rendered, `Input:\n${[source0, source1].join('\n\\page\n')}`, { showPrefix: false }).toBe('

    a

    \\page

    a

    '); + }); }); describe('Math function parameter handling', ()=>{ @@ -402,4 +426,110 @@ describe('Variable names that are subsets of other names', ()=>{ const rendered = Markdown.render(source).trimReturns(); expect(rendered).toBe('

    14

    '); }); +}); + +describe('Regression Tests', ()=>{ + it('Don\'t Eat all the parentheticals!', function() { + const source='\n| title 1 | title 2 | title 3 | title 4|\n|-----------|---------|---------|--------|\n|[foo](bar) | Ipsum | ) | ) |\n'; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered).toBe('
    title 1title 2title 3title 4
    fooIpsum))
    '); + }); + + it('Handle Extra spaces in image alt-text 1', function(){ + const source='![ where is my image??](http://i.imgur.com/hMna6G0.png)'; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered).toBe('

    \"where

    '); + }); + + it('Handle Extra spaces in image alt-text 2', function(){ + const source='![where is my image??](http://i.imgur.com/hMna6G0.png)'; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered).toBe('

    \"where

    '); + }); + + it('Handle Extra spaces in image alt-text 3', function(){ + const source='![where is my image?? ](http://i.imgur.com/hMna6G0.png)'; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered).toBe('

    \"where

    '); + }); + + it('Handle Extra spaces in image alt-text 4', function(){ + const source='![where is my image??](http://i.imgur.com/hMna6G0.png){height=20%,width=20%}'; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered).toBe('

    \"where

    '); + }); +}); + +describe('Custom Math Function Tests', ()=>{ + it('Sign Test', function() { + const source = `[a]: 13\n\n[b]: -11\n\nPositive: $[sign(a)]\n\nNegative: $[sign(b)]`; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered).toBe('

    Positive: +

    Negative: -

    '); + }); + + it('Signed Test', function() { + const source = `[a]: 13\n\n[b]: -11\n\nPositive: $[signed(a)]\n\nNegative: $[signed(b)]`; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered).toBe('

    Positive: +13

    Negative: -11

    '); + }); + + it('Roman Numerals Test', function() { + const source = `[a]: 18\n\nRoman Numeral: $[toRomans(a)]`; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered).toBe('

    Roman Numeral: XVIII

    '); + }); + + it('Roman Numerals Test - Uppercase', function() { + const source = `[a]: 18\n\nRoman Numeral: $[toRomansUpper(a)]`; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered).toBe('

    Roman Numeral: XVIII

    '); + }); + + it('Roman Numerals Test - Lowercase', function() { + const source = `[a]: 18\n\nRoman Numeral: $[toRomansLower(a)]`; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered).toBe('

    Roman Numeral: xviii

    '); + }); + + it('Number to Characters Test', function() { + const source = `[a]: 18\n\n[b]: 39\n\nCharacters: $[toChar(a)] $[toChar(b)]`; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered).toBe('

    Characters: R AM

    '); + }); + + it('Number to Characters Test - Uppercase', function() { + const source = `[a]: 18\n\n[b]: 39\n\nCharacters: $[toCharUpper(a)] $[toCharUpper(b)]`; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered).toBe('

    Characters: R AM

    '); + }); + + it('Number to Characters Test - Lowercase', function() { + const source = `[a]: 18\n\n[b]: 39\n\nCharacters: $[toCharLower(a)] $[toCharLower(b)]`; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered).toBe('

    Characters: r am

    '); + }); + + it('Number to Words Test', function() { + const source = `[a]: 80085\n\nWords: $[toWords(a)]`; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered).toBe('

    Words: eighty thousand and eighty-five

    '); + }); + + it('Number to Words Test - Uppercase', function() { + const source = `[a]: 80085\n\nWords: $[toWordsUpper(a)]`; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered).toBe('

    Words: EIGHTY THOUSAND AND EIGHTY-FIVE

    '); + }); + + it('Number to Words Test - Lowercase', function() { + const source = `[a]: 80085\n\nWords: $[toWordsLower(a)]`; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered).toBe('

    Words: eighty thousand and eighty-five

    '); + }); + + it('Number to Words Test - Capitalized', function() { + const source = `[a]: 80085\n\nWords: $[toWordsCaps(a)]`; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered).toBe('

    Words: Eighty Thousand And Eighty-Five

    '); + }); }); \ No newline at end of file diff --git a/themes/Legacy/5ePHB/style.less b/themes/Legacy/5ePHB/style.less index 4ebfbf840..41524c74c 100644 --- a/themes/Legacy/5ePHB/style.less +++ b/themes/Legacy/5ePHB/style.less @@ -9,26 +9,22 @@ @headerText : #58180D; // Dark maroon @monsterStatBackground : #FDF1DC; // Lighter parchment @captionText : #766649; // Brown -@page { margin: 0; } -body { - counter-reset : phb-page-numbers; -} -*{ - -webkit-print-color-adjust : exact; -} -.useSansSerif(){ - font-family : ScalySans; - em{ - font-family : ScalySans; +@page { margin : 0; } +body { counter-reset : phb-page-numbers; } +* { -webkit-print-color-adjust : exact; } +.useSansSerif() { + font-family : 'ScalySans'; + em { + font-family : 'ScalySans'; font-style : italic; } - strong{ - font-family : ScalySans; + strong { + font-family : 'ScalySans'; font-weight : 800; letter-spacing : -0.02em; } } -.useColumns(@multiplier : 1){ +.useColumns(@multiplier : 1) { column-count : 2; column-fill : auto; column-gap : 1cm; @@ -40,21 +36,21 @@ body { -webkit-column-gap : 1cm; -moz-column-gap : 1cm; } -.phb, .page{ +.phb, .page { .useColumns(); - counter-increment : phb-page-numbers; position : relative; z-index : 15; box-sizing : border-box; - overflow : hidden; - height : 279.4mm; width : 215.9mm; + height : 279.4mm; padding : 1.0cm 1.7cm; padding-bottom : 1.5cm; + overflow : hidden; + font-family : 'BookSanity'; + font-size : 0.317cm; + counter-increment : phb-page-numbers; background-color : @background; background-image : @backgroundImage; - font-family : BookSanity; - font-size : 0.317cm; text-rendering : optimizeLegibility; page-break-before : always; page-break-after : always; @@ -63,199 +59,175 @@ body { contain-intrinsic-size : auto none; } -.phb{ +.phb { //***************************** // * BASE // *****************************/ - p{ + p { padding-bottom : 0.8em; line-height : 1.269em; - &+p{ - margin-top : -0.8em; - } + & + p { margin-top : -0.8em; } } - ul{ - margin-bottom : 0.8em; + ul { padding-left : 1.4em; + margin-bottom : 0.8em; line-height : 1.269em; list-style-position : outside; list-style-type : disc; } - ol{ - margin-bottom : 0.8em; + ol { padding-left : 1.4em; + margin-bottom : 0.8em; line-height : 1.269em; list-style-position : outside; list-style-type : decimal; } //Indents after p or lists - p+p, ul+p, ol+p{ - text-indent : 1em; - } - img{ - z-index : -1; - } - strong{ + p + p, ul + p, ol + p { text-indent : 1em; } + img { z-index : -1; } + strong { font-weight : bold; letter-spacing : 0.03em; } - em{ - font-style : italic; - } - sup{ + em { font-style : italic; } + sup { + font-size : smaller; + line-height : 0; vertical-align : super; - font-size : smaller; - line-height : 0; } - sub{ - vertical-align : sub; + sub { font-size : smaller; line-height : 0; + vertical-align : sub; } //***************************** // * HEADERS // *****************************/ - h1,h2,h3,h4{ + h1,h2,h3,h4 { margin-top : 0.2em; margin-bottom : 0.2em; - font-family : MrJeeves; + font-family : 'MrJeeves'; font-weight : 800; color : @headerText; } - h1{ + h1 { column-span : all; font-size : 0.987cm; -webkit-column-span : all; -moz-column-span : all; - &+p::first-letter{ + & + p::first-letter { float : left; - font-family : Solberry; + font-family : 'Solberry'; font-size : 10em; - color : #222; line-height : 0.795em; + color : #222222; } } - h2{ - font-size : 0.705cm; - } - h3{ + h2 { font-size : 0.705cm; } + h3 { font-size : 0.529cm; border-bottom : 2px solid @headerUnderline; } - h4{ + h4 { margin-bottom : 0.00em; font-size : 0.458cm; } - h5{ + h5 { margin-bottom : 0.2em; - font-family : ScalySansSmallCaps; + font-family : 'ScalySansSmallCaps'; font-size : 0.423cm; font-weight : 900; } //***************************** // * TABLE // *****************************/ - table{ + table { .useSansSerif(); width : 100%; margin-bottom : 1em; font-size : 10pt; - thead{ - display: table-row-group; + thead { + display : table-row-group; font-weight : 800; - th{ - vertical-align : bottom; - padding-bottom : 0.3em; + th { padding-right : 0.1em; + padding-bottom : 0.3em; padding-left : 0.1em; + vertical-align : bottom; } } - tbody{ - tr{ - td{ - padding : 0.3em 0.1em; - } - &:nth-child(odd){ - background-color : @noteGreen; - } + tbody { + tr { + td { padding : 0.3em 0.1em; } + &:nth-child(odd) { background-color : @noteGreen; } } } } //***************************** // * NOTE // *****************************/ - blockquote{ + blockquote { .useSansSerif(); box-sizing : border-box; - margin-bottom : 1em; padding : 5px 10px; + margin-bottom : 1em; background-color : @noteGreen; border-style : solid; border-width : 11px; border-image : @noteBorderImage 11; border-image-outset : 9px 0px; - box-shadow : 1px 4px 14px #888; - p, ul{ + box-shadow : 1px 4px 14px #888888; + p, ul { font-size : 0.352cm; line-height : 1.083em; } } //If a note starts a column, give it space at the top to render border - pre+blockquote, h2+blockquote, h3+blockquote, h4+blockquote, h5+blockquote { - margin-top : 13px; - } + pre + blockquote, h2 + blockquote, h3 + blockquote, h4 + blockquote, h5 + blockquote { margin-top : 13px; } //***************************** // * MONSTER STAT BLOCK // *****************************/ - hr+blockquote{ + hr+blockquote { position : relative; padding-top : 15px; background-color : @monsterStatBackground; border-style : solid; border-width : 10px; border-image : @monsterBorderImageLegacy 10; - h2{ + h2 { margin-top : -8px; margin-bottom : 0px; - &+p{ - padding-bottom : 0px; - } + & + p { padding-bottom : 0px; } } - h3{ - font-family : ScalySans; - font-weight : 400; + h3 { + font-family : 'ScalySans'; + font-weight : normal; border-bottom : 1px solid @headerText; } - hr+ul{ - color : @headerText; - } - ul{ + hr + ul { color : @headerText; } + ul { .useSansSerif(); padding-left : 1em; font-size : 0.352cm; } // Monster Ability table - hr+table{ + hr + table { margin : 0; background-color : transparent; border-style : none; border-image : none; - tbody{ - tr:nth-child(odd), tr:nth-child(even){ - background-color : transparent; - } + tbody { + tr:nth-child(odd), tr:nth-child(even) { background-color : transparent; } } } - table{ - color : @headerText; - } - p+p{ - margin-top : 0em; + table { color : @headerText; } + p + p { padding-bottom : 0.5em; + margin-top : 0em; text-indent : 0em; } //Triangle dividers - hr{ + hr { visibility : visible; height : 6px; margin : 4px 0px; @@ -265,100 +237,90 @@ body { } } //Full Width - hr+hr+blockquote{ + hr + hr + blockquote { .useColumns(0.96); column-fill : balance; } //***************************** // * FOOTER // *****************************/ - &:after{ - content : ""; + &:after { position : absolute; bottom : 0px; left : 0px; z-index : 100; - height : 50px; width : 100%; + height : 50px; + content : ''; background-image : @footerAccentImage; background-size : cover; } - &:nth-child(even){ - &:after{ - transform : scaleX(-1); - } - .pageNumber{ - left : 2px; - } - .footnote{ + &:nth-child(even) { + &::after { transform : scaleX(-1); } + .pageNumber { left : 2px; } + .footnote { left : 80px; text-align : left; } } - .pageNumber{ + .pageNumber { position : absolute; right : 2px; bottom : 22px; width : 50px; font-size : 0.9em; - color : #c9ad6a; + color : #C9AD6A; text-align : center; - &.auto::after { - content : counter(phb-page-numbers); - } + &.auto::after { content : counter(phb-page-numbers); } } - .footnote{ + .footnote { position : absolute; right : 80px; bottom : 32px; z-index : 150; width : 200px; font-size : 0.8em; - color : #c9ad6a; + color : #C9AD6A; text-align : right; } //***************************** // * EXTRAS // *****************************/ - hr{ + hr { visibility : hidden; margin : 0px; } //Modified unorder list, used in spells - hr+ul{ - margin-bottom : 0.5em; + hr + ul { padding-left : 1em; + margin-bottom : 0.5em; text-indent : -1em; list-style-type : none; } //Column Break - pre, code{ + pre, code { visibility : hidden; -webkit-column-break-after : always; break-after : always; -moz-column-break-after : always; } //Avoid breaking up - p,blockquote,table{ + p,blockquote,table { z-index : 15; -webkit-column-break-inside : avoid; page-break-inside : avoid; break-inside : avoid; } //Better spacing for spell blocks - h4+p+hr+ul{ - margin-top : -0.5em - } + h4 + p + hr + ul { margin-top : -0.5em; } //Text indent right after table - table+p{ - text-indent : 1em; - } + table + p { text-indent : 1em; } // Nested lists - ul ul,ol ol,ul ol,ol ul{ + ul ul,ol ol,ul ol,ol ul { margin-bottom : 0px; margin-left : 1.5em; } - li{ + li { -webkit-column-break-inside : avoid; page-break-inside : avoid; break-inside : avoid; @@ -367,89 +329,81 @@ body { //***************************** // * SPELL LIST // *****************************/ -.phb .spellList{ +.phb .spellList { .useSansSerif(); column-count : 4; - column-span : all; -webkit-column-span : all; -moz-column-span : all; - ul+h5{ - margin-top : 15px; - } - p, ul{ + column-span : all; + ul + h5 { margin-top : 15px; } + p, ul { font-size : 0.352cm; line-height : 1.263em; } - ul{ - margin-bottom : 0.5em; + ul { padding-left : 1em; + margin-bottom : 0.5em; text-indent : -1em; list-style-type : none; + break-inside : auto; -webkit-column-break-inside : auto; page-break-inside : auto; - break-inside : auto; } } //***************************** // * WIDE // *****************************/ -.phb .wide{ - column-span : all; +.phb .wide { -webkit-column-span : all; -moz-column-span : all; + column-span : all; } //***************************** // * CLASS TABLE // *****************************/ -.phb .classTable{ +.phb .classTable { margin-top : 25px; margin-bottom : 40px; border-collapse : separate; background-color : white; border : initial; border-style : solid; + border-image-source : @frameBorderImage; + border-image-slice : 150 200 150 200; + border-image-width : 47px; border-image-outset : 25px 17px; border-image-repeat : stretch; - border-image-slice : 150 200 150 200; - border-image-source : @frameBorderImage; - border-image-width : 47px; - h5{ - margin-bottom : 10px; - } + h5 { margin-bottom : 10px; } } //************************************ // * DESCRIPTIVE TEXT BOX // ************************************/ -.phb .descriptive{ +.phb .descriptive { margin-bottom : 1em; - background-color : #faf7ea; - font-family : ScalySans; + font-family : 'ScalySans'; + background-color : #FAF7EA; border-style : solid; border-width : 7px; border-image : @descriptiveBoxImage 12 stretch; border-image-outset : 4px; - box-shadow : 0px 0px 6px #faf7ea; - p{ + box-shadow : 0px 0px 6px #FAF7EA; + p { display : block; padding-bottom : 0px; line-height : 1.47em; } - p + p { - padding-top : .8em; - } + p + p { padding-top : 0.8em; } em { - font-family : ScalySans; + font-family : 'ScalySans'; font-style : italic; } strong { - font-family : ScalySans; + font-family : 'ScalySans'; font-weight : 800; letter-spacing : -0.02em; } } -.phb pre+.descriptive{ - margin-top : 8px; -} +.phb pre + .descriptive { margin-top : 8px; } //***************************** // * ARTIST CREDIT BLOCK @@ -457,47 +411,41 @@ body { .phb { .artist { position : absolute; - text-align : center; - font-family : WalterTurncoat; + font-family : 'WalterTurncoat'; font-size : 0.27cm; color : @captionText; + text-align : center; p, p + p { margin : unset; - text-indent : unset; line-height : 0.941em; + text-indent : unset; } - h5 { + h5 { + font-family : 'WalterTurncoat'; font-size : 1.3em; - font-family : WalterTurncoat; } - a{ + a { color : inherit; text-decoration : unset; - &:hover { - text-decoration : underline; - } + &:hover { text-decoration : underline; } } } } //***************************** // * TABLE OF CONTENTS // *****************************/ -.phb .toc{ +.phb .toc { -webkit-column-break-inside : avoid; page-break-inside : avoid; break-inside : avoid; - a{ + a { color : black; text-decoration : none; - &:hover{ - text-decoration : underline; - } + &:hover { text-decoration : underline; } } - ul{ + ul { padding-left : 0; list-style-type : none; } - &>ul>li{ - margin-bottom : 10px; - } + & > ul > li { margin-bottom : 10px; } } diff --git a/themes/V3/5eDMG/snippets.js b/themes/V3/5eDMG/snippets.js index 636befb60..bad5e8e6a 100644 --- a/themes/V3/5eDMG/snippets.js +++ b/themes/V3/5eDMG/snippets.js @@ -1,4 +1,4 @@ -/* eslint-disable max-lines */ + module.exports = [ ]; diff --git a/themes/V3/5eDMG/style.less b/themes/V3/5eDMG/style.less index 2ced98312..d79533c2c 100644 --- a/themes/V3/5eDMG/style.less +++ b/themes/V3/5eDMG/style.less @@ -7,37 +7,29 @@ } .page { - background-image : url(/assets/DMG_background.png); + background-image : url('/assets/DMG_background.png'); background-size : cover; - /*TABLES WITHIN NOTES*/ - .note table tbody tr:nth-child(odd) { - background:#fff; - } + /* TABLES WITHIN NOTES */ + .note table tbody tr:nth-child(odd) { background : #FFFFFF; } - /*DROP CAP*/ + /* DROP CAP */ h1 + p::first-letter { - background-image: unset; - color:black; + color : black; + background-image : unset; } - .quote p:first-child::first-line { - all: unset; + .quote p:first-child::first-line { all : unset; } + + &::after { + height : 58px; + background-image : url('/assets/DMG_footerAccent.png'); } - &:after { - background-image : url(/assets/DMG_footerAccent.png); - height: 58px; - } - - .footnote { - bottom : 40px; - } + .footnote { bottom : 40px; } } .page:has(.partCover) { - .partCover { - background-image: @partCoverHeaderDMG; - } + .partCover { background-image : @partCoverHeaderDMG; } } diff --git a/themes/V3/5ePHB/snippets.js b/themes/V3/5ePHB/snippets.js index dbcdc6f2a..27ea62bea 100644 --- a/themes/V3/5ePHB/snippets.js +++ b/themes/V3/5ePHB/snippets.js @@ -6,164 +6,12 @@ const MonsterBlockGen = require('./snippets/monsterblock.gen.js'); const scriptGen = require('./snippets/script.gen.js'); const ClassFeatureGen = require('./snippets/classfeature.gen.js'); const CoverPageGen = require('./snippets/coverpage.gen.js'); -const TableOfContentsGen = require('./snippets/tableOfContents.gen.js'); -const indexGen = require('./snippets/index.gen.js'); const QuoteGen = require('./snippets/quote.gen.js'); const dedent = require('dedent-tabs').default; module.exports = [ - - { - groupName : 'Text Editor', - icon : 'fas fa-pencil-alt', - view : 'text', - snippets : [ - { - name : 'Table of Contents', - icon : 'fas fa-book', - gen : TableOfContentsGen, - experimental : true, - subsnippets : [ - { - name : 'Generate Table of Contents', - icon : 'fas fa-book', - gen : TableOfContentsGen, - experimental : true - }, - { - 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 : '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 : 'Index', - icon : 'fas fa-bars', - gen : indexGen, - experimental : true - } - ] - }, { groupName : 'Style Editor', icon : 'fas fa-pencil-alt', @@ -192,70 +40,9 @@ module.exports = [ line-height: 1em; }\n\n` }, - { - name : 'Table of Contents Toggles', - icon : 'fas fa-book', - subsnippets : [ - { - name : 'Enable H1-H4 all pages', - icon : 'fas fa-dice-four', - gen : `.page {\n\th4 {--TOC: include; }\n}\n\n`, - }, - { - name : 'Enable H1-H5 all pages', - icon : 'fas fa-dice-five', - gen : `.page {\n\th4, h5 {--TOC: include; }\n}\n\n`, - }, - { - name : 'Enable H1-H6 all pages', - icon : 'fas fa-dice-six', - gen : `.page {\n\th4, h5, h6 {--TOC: include; }\n}\n\n`, - }, - ] - } ] }, - - /*********************** IMAGES *******************/ - { - groupName : 'Images', - icon : 'fas fa-images', - view : 'text', - snippets : [ - { - name : 'Image', - icon : 'fas fa-image', - gen : dedent` - ![cat warrior](https://s-media-cache-ak0.pinimg.com/736x/4a/81/79/4a8179462cfdf39054a418efd4cb743e.jpg) {width:325px,mix-blend-mode:multiply} - - {{artist,position:relative,top:-230px,left:10px,margin-bottom:-30px - ##### Cat Warrior - [Kyoung Hwan Kim](https://www.artstation.com/tahra) - }}` - }, - { - name : 'Background Image', - icon : 'fas fa-tree', - gen : dedent` - ![homebrew mug](http://i.imgur.com/hMna6G0.png) {position:absolute,top:50px,right:30px,width:280px} - - {{artist,top:80px,right:30px - ##### Homebrew Mug - [naturalcrit](https://homebrew.naturalcrit.com) - }}` - }, - { - name : 'Watermark', - icon : 'fas fa-id-card', - gen : dedent` - {{watermark Homebrewery}}\n` - }, - ] - }, - - /************************* PHB ********************/ - { groupName : 'PHB', icon : 'fas fa-book', @@ -450,9 +237,6 @@ module.exports = [ ] }, - - - /**************** PAGE *************/ { diff --git a/themes/V3/5ePHB/snippets/monsterblock.gen.js b/themes/V3/5ePHB/snippets/monsterblock.gen.js index e61c206f0..dffb59d04 100644 --- a/themes/V3/5ePHB/snippets/monsterblock.gen.js +++ b/themes/V3/5ePHB/snippets/monsterblock.gen.js @@ -148,7 +148,7 @@ const genAction = function(){ 'Turnbuckle Roll' ]); - return `***${name}.*** *Melee Weapon Attack:* +4 to hit, reach 5ft., one target. *Hit* 5 (1d6 + 2) `; + return `***${name}.*** *Melee Weapon Attack:* +4 to hit, reach 5 ft., one target. *Hit:* 5 (1d6 + 2) `; }; @@ -161,8 +161,8 @@ module.exports = { *${getType()}, ${getAlignment()}* ___ **Armor Class** :: ${_.random(10, 20)} (chain mail, shield) - **Hit Points** :: ${_.random(1, 150)}(1d4 + 5) - **Speed** :: ${_.random(0, 50)}ft. + **Hit Points** :: ${_.random(1, 150)} (1d4 + 5) + **Speed** :: ${_.random(0, 50)} ft. ___ | STR | DEX | CON | INT | WIS | CHA | |:-----:|:-----:|:-----:|:-----:|:-----:|:-----:| diff --git a/themes/V3/5ePHB/style.less b/themes/V3/5ePHB/style.less index ba975e58a..555866ba4 100644 --- a/themes/V3/5ePHB/style.less +++ b/themes/V3/5ePHB/style.less @@ -17,7 +17,6 @@ .useSansSerif() { font-family : 'ScalySansRemake'; font-size : 0.318cm; - line-height : 1.2em; p,dl,ul,ol { line-height : 1.2em; } ul, ol { padding-left : 1em; } em { font-style : italic; } @@ -306,12 +305,12 @@ margin-left : -0.16cm; background-color : var(--HB_Color_MonsterStatBackground); background-image : @monsterBlockBackground; - background-blend-mode : overlay; border-style : solid; border-width : 7px 6px; border-image : @monsterBorderImage 14 round; border-image-outset : 0px 2px; box-shadow : 1px 4px 14px #888888; + background-blend-mode : overlay; } position : relative; @@ -336,9 +335,9 @@ //Triangle dividers hr { + visibility : visible; height : 6px; margin : 0.12cm 0cm; - visibility : visible; background-image : @redTriangleImage; background-size : 100% 100%; border : none; @@ -356,8 +355,8 @@ } .bonus { - float: right; - padding-right: 0.5em; + float : right; + padding-right : 0.5em; } // Monster Ability table @@ -457,8 +456,8 @@ // * EXTRAS // *****************************/ hr { - margin : 0px; visibility : hidden; + margin : 0px; } //Text indent right after table table + p { text-indent : 1em; } @@ -526,10 +525,10 @@ content : ''; background-image : @classTableDecoration, @classTableDecoration; - filter : drop-shadow(0px 0px 1px #C8C5C080); background-repeat : no-repeat, no-repeat; background-position : top, bottom; background-size : contain, contain; + filter : drop-shadow(0px 0px 1px #C8C5C080); transform : translateY(-50%) translateX(-50%); } &.decoration.wide::before { @@ -546,39 +545,40 @@ columns : 1; text-align : center; &::after { display : none; } + .frontCover { position : absolute; } h1 { - margin-top : 1.2cm; - margin-bottom : 0; - font-family : 'NodestoCapsCondensed'; - font-size : 2.245cm; - font-weight : normal; - line-height : 1.9cm; - color : white; - text-shadow : unset; - text-transform : uppercase; - -webkit-text-stroke: 0.2cm black; - paint-order:stroke; + margin-top : 1.55cm; + margin-bottom : 0; + font-family : 'NodestoCapsCondensed'; + font-size : 2.245cm; + font-weight : normal; + line-height : 1.9cm; + color : white; + text-transform : uppercase; + text-shadow : unset; + -webkit-text-stroke : 0.2cm black; + paint-order : stroke; } h2 { - font-family : 'NodestoCapsCondensed'; - font-size : 0.85cm; - font-weight : normal; - color : white; - letter-spacing : 0.1cm; - -webkit-text-stroke: 0.14cm black; - paint-order:stroke; + font-family : 'NodestoCapsCondensed'; + font-size : 0.85cm; + font-weight : normal; + color : white; + letter-spacing : 0.1cm; + -webkit-text-stroke : 0.14cm black; + paint-order : stroke; } hr { position : relative; display : block; + visibility : visible; width : 12cm; height : 0.5cm; margin : auto; - visibility : visible; background-image : @horizontalRule; - filter : drop-shadow(0 0 3px black); background-size : 100% 100%; border : none; + filter : drop-shadow(0 0 3px black); } .banner { position : absolute; @@ -601,19 +601,19 @@ filter : drop-shadow(2px 2px 2px black); } .footnote { - position : absolute; - right : 0; - bottom : 1.3cm; - left : 0; - width : 70%; - margin-right : auto; - margin-left : auto; - font-family : 'Overpass'; - font-size : 0.496cm; - color : white; - text-align : center; - -webkit-text-stroke: 0.1cm black; - paint-order:stroke; + position : absolute; + right : 0; + bottom : 1.3cm; + left : 0; + width : 70%; + margin-right : auto; + margin-left : auto; + font-family : 'Overpass'; + font-size : 0.496cm; + color : white; + text-align : center; + -webkit-text-stroke : 0.1cm black; + paint-order : stroke; } .logo { position : absolute; @@ -621,10 +621,7 @@ right : 0; left : 0; filter : drop-shadow(0 0 0.075cm black); - img { - width : 100%; - height : 2cm; - } + img { height : 2cm; } } } // ***************************** @@ -634,8 +631,9 @@ columns : 1; text-align : center; &::after { display : none; } + .insideCover { position : absolute; } h1 { - margin-top : 1.2cm; + margin-top : 1.55cm; margin-bottom : 0; font-family : 'NodestoCapsCondensed'; font-size : 2.1cm; @@ -652,10 +650,10 @@ hr { position : relative; display : block; + visibility : visible; width : 12cm; height : 0.5cm; margin : auto; - visibility : visible; background-image : @horizontalRule; background-size : 100% 100%; border : none; @@ -666,26 +664,23 @@ bottom : 1cm; left : 0; height : 2cm; - img { - width : 100%; - height : 2cm; - } + img { height : 2cm; } } } // ***************************** // * BACK COVER // *****************************/ .page:has(.backCover) { - padding : 2.25cm 1.3cm 2cm 1.3cm; - color : #FFFFFF; - columns : 1; + padding : 2.25cm 1.3cm 2cm 1.3cm; + line-height : 1.4em; + color : #FFFFFF; + columns : 1; &::after { display : none; } .columnWrapper { width : 7.6cm; } .backCover { position : absolute; inset : 0; z-index : -1; - width : 11cm; background-image : @backCover; background-repeat : no-repeat; background-size : contain; @@ -708,12 +703,12 @@ height : 100%; } hr { + visibility : visible; width : 4.5cm; height : 0.53cm; margin-top : 1.1cm; margin-right : auto; margin-left : auto; - visibility : visible; background-image : @horizontalRule; background-size : 100% 100%; border : none; @@ -737,7 +732,6 @@ img { position : relative; z-index : 0; - width : 100%; height : 1.5cm; } p { @@ -797,54 +791,13 @@ // * TABLE OF CONTENTS // *****************************/ -// Default Exclusions -// Anything not excluded is included, default Headers are H1, H2, and H3. -h4, -h5, -h6, -.page:has(.frontCover), -.page:has(.backCover), -.page:has(.insideCover), -.monster, -.noToC, -.toc { --TOC: exclude; } - - -// 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! - -// 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; } -.tocIncludeH4 h4 {--TOC: include; } -.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; } +// Additional Default Exclusions +.monster { --TOC : exclude; } .page:has(.partCover) { - --TOC: exclude; - & h1 { - --TOC: include; - } - } + --TOC : exclude; + & h1 { --TOC : include; } +} .page { &:has(.toc)::after { display : none; } @@ -910,9 +863,7 @@ h6, .useColumns(0.96, @fillMode: balance); } } - .toc.wide li { - break-inside: auto; - } + .toc.wide li { break-inside : auto; } } // ***************************** @@ -937,9 +888,7 @@ h6, .page h1 + * { margin-top : 0; } -.page .descriptive.wide + * { - margin-top: 0; -} +.page .descriptive.wide + * { margin-top : 0; } //***************************** // * RUNE TABLE @@ -954,8 +903,8 @@ h6, width : 1.3cm; height : 1.3cm; font-weight : normal; - text-transform : uppercase; vertical-align : middle; + text-transform : uppercase; outline : 1px solid #000000; } th { @@ -976,6 +925,7 @@ h6, } } } + // ***************************** // * INDEX // *****************************/ diff --git a/themes/V3/Blank/snippets.js b/themes/V3/Blank/snippets.js index e92e757cf..c5198fd87 100644 --- a/themes/V3/Blank/snippets.js +++ b/themes/V3/Blank/snippets.js @@ -4,6 +4,8 @@ const WatercolorGen = require('./snippets/watercolor.gen.js'); const ImageMaskGen = require('./snippets/imageMask.gen.js'); const FooterGen = require('./snippets/footer.gen.js'); const dedent = require('dedent-tabs').default; +const TableOfContentsGen = require('./snippets/tableOfContents.gen.js'); +const indexGen = require('./snippets/index.gen.js'); module.exports = [ @@ -36,6 +38,11 @@ module.exports = [ icon : 'fas fa-sort-numeric-down', gen : '{{pageNumber,auto}}\n' }, + { + name : 'Variable Auto Page Number', + icon : 'fas fa-sort-numeric-down', + gen : '{{pageNumber $[HB_pageNumber]}}\n' + }, { name : 'Skip Page Number Increment this Page', icon : 'fas fa-xmark', @@ -141,7 +148,53 @@ module.exports = [ [Homebrewery.Naturalcrit.com](https://homebrewery.naturalcrit.com) }}\n\n`; }, - } + }, + { + name : 'Table of Contents', + icon : 'fas fa-book', + gen : TableOfContentsGen, + experimental : true, + subsnippets : [ + { + name : 'Table of Contents', + icon : 'fas fa-book', + gen : TableOfContentsGen, + experimental : true + }, + { + 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 : 'Index', + icon : 'fas fa-bars', + gen : indexGen, + experimental : true + }, + ] }, { @@ -153,7 +206,7 @@ module.exports = [ name : 'Add Comment', icon : 'fas fa-code', gen : '/* This is a comment that will not be rendered into your brew. */' - }, + } ] }, @@ -438,6 +491,15 @@ module.exports = [ icon : 'fas fa-print', view : 'style', snippets : [ + { + name : 'US Letter Page Size', + icon : 'far fa-file', + gen : dedent`/* US Letter Page Size */ + .page { + width : 215.9mm; /* 8.5in */ + height : 279.4mm; /* 11in */ + }\n\n`, + }, { name : 'A3 Page Size', icon : 'far fa-file', diff --git a/themes/V3/5ePHB/snippets/index.gen.js b/themes/V3/Blank/snippets/index.gen.js similarity index 100% rename from themes/V3/5ePHB/snippets/index.gen.js rename to themes/V3/Blank/snippets/index.gen.js diff --git a/themes/V3/5ePHB/snippets/tableOfContents.gen.js b/themes/V3/Blank/snippets/tableOfContents.gen.js similarity index 100% rename from themes/V3/5ePHB/snippets/tableOfContents.gen.js rename to themes/V3/Blank/snippets/tableOfContents.gen.js diff --git a/themes/V3/Blank/style.less b/themes/V3/Blank/style.less index 4f838a5d1..01b85326f 100644 --- a/themes/V3/Blank/style.less +++ b/themes/V3/Blank/style.less @@ -5,6 +5,7 @@ @import (less) './themes/fonts/iconFonts/diceFont.less'; @import (less) './themes/fonts/iconFonts/gameIcons.less'; @import (less) './themes/fonts/iconFonts/fontAwesome.less'; +@import (less) './themes/fonts/Journal/fonts.less'; :root { //Colors @@ -21,9 +22,9 @@ body { counter-reset : page-numbers 0; } // *****************************/ .page { .block { - break-inside : avoid; display : inline-block; width : 100%; + break-inside : avoid; img { z-index : 0; } } .inline-block { @@ -58,8 +59,8 @@ body { counter-reset : page-numbers 0; } content-visibility : auto; contain-intrinsic-size : auto none; } - //***************************** - // * BASE +//***************************** +// * BASE // *****************************/ .page { p { @@ -120,7 +121,7 @@ body { counter-reset : page-numbers 0; } // * CODE BLOCKS // ************************************/ code { - font-family : 'Courier New', "Courier", monospace; + font-family : 'Courier New', 'Courier', monospace; overflow-wrap : break-word; white-space : pre-wrap; } @@ -133,10 +134,10 @@ body { counter-reset : page-numbers 0; } // * EXTRAS // *****************************/ .columnSplit { - margin-top : 0; visibility : hidden; - -webkit-column-break-after : always; + margin-top : 0; break-after : always; + -webkit-column-break-after : always; -moz-column-break-after : always; & + * { margin-top : 0; } } @@ -199,11 +200,11 @@ body { counter-reset : page-numbers 0; } background-color : var(--HB_Color_WatercolorStain); /* default color */ background-size : cover; -webkit-mask-image : var(--wc); - -webkit-mask-size : contain; - -webkit-mask-repeat : no-repeat; mask-image : var(--wc); - mask-size : contain; + -webkit-mask-repeat : no-repeat; mask-repeat : no-repeat; + -webkit-mask-size : contain; + mask-size : contain; --wc : @watercolor1; /* default image */ } @@ -231,15 +232,15 @@ body { counter-reset : page-numbers 0; } height : 200%; background-image : var(--checkerboard); background-size : 20px; - transform : translateY(50%) translateX(-50%) rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY)); -webkit-mask-image : var(--wc), var(--revealer); - -webkit-mask-repeat : repeat-x; - -webkit-mask-size : 50%; //Scale only X to fit page width, leave height at aspect ratio, designed to hang off the edge - -webkit-mask-position : 50% calc(50% - var(--offset)); mask-image : var(--wc); + -webkit-mask-repeat : repeat-x; mask-repeat : repeat-x; - mask-size : 50%; + -webkit-mask-position : 50% calc(50% - var(--offset)); mask-position : 50% calc(50% - var(--offset)); + -webkit-mask-size : 50%; //Scale only X to fit page width, leave height at aspect ratio, designed to hang off the edge + mask-size : 50%; + transform : translateY(50%) translateX(-50%) rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY)); --rotation : 0; --revealer : none; --checkerboard : none; @@ -276,19 +277,19 @@ body { counter-reset : page-numbers 0; } } &.revealImage { --revealer : linear-gradient(0deg, rgba(0,0,0,0.2) 0%, rgba(0,0,0,0.2)); - --checkerboard : url("/assets/waterColorMasks/missingImage.png"); //shows any masked regions not filled by image + --checkerboard : url('/assets/waterColorMasks/missingImage.png'); //shows any masked regions not filled by image } } .imageMaskEdge { - &1 { --wc : url("/assets/waterColorMasks/edge/0001.webp"); } - &2 { --wc : url("/assets/waterColorMasks/edge/0002.webp"); } - &3 { --wc : url("/assets/waterColorMasks/edge/0003.webp"); } - &4 { --wc : url("/assets/waterColorMasks/edge/0004.webp"); } - &5 { --wc : url("/assets/waterColorMasks/edge/0005.webp"); } - &6 { --wc : url("/assets/waterColorMasks/edge/0006.webp"); } - &7 { --wc : url("/assets/waterColorMasks/edge/0007.webp"); } - &8 { --wc : url("/assets/waterColorMasks/edge/0008.webp"); } + &1 { --wc : url('/assets/waterColorMasks/edge/0001.webp'); } + &2 { --wc : url('/assets/waterColorMasks/edge/0002.webp'); } + &3 { --wc : url('/assets/waterColorMasks/edge/0003.webp'); } + &4 { --wc : url('/assets/waterColorMasks/edge/0004.webp'); } + &5 { --wc : url('/assets/waterColorMasks/edge/0005.webp'); } + &6 { --wc : url('/assets/waterColorMasks/edge/0006.webp'); } + &7 { --wc : url('/assets/waterColorMasks/edge/0007.webp'); } + &8 { --wc : url('/assets/waterColorMasks/edge/0008.webp'); } } [class*='imageMaskCenter'] { @@ -296,15 +297,15 @@ body { counter-reset : page-numbers 0; } left : calc(var(--offsetX)); width : 100%; height : 100%; - transform : rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY)); -webkit-mask-image : var(--wc), var(--revealer); - -webkit-mask-repeat : no-repeat; - -webkit-mask-size : 100% 100%; //Scale both dimensions to fit page size - -webkit-mask-position : 0% 0%; mask-image : var(--wc), var(--revealer); + -webkit-mask-repeat : no-repeat; mask-repeat : no-repeat; - mask-size : 100% 100%; //Scale both dimensions to fit page size + -webkit-mask-position : 0% 0%; mask-position : 50% 50%; + -webkit-mask-size : 100% 100%; //Scale both dimensions to fit page size + mask-size : 100% 100%; //Scale both dimensions to fit page size + transform : rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY)); & > p:has(img) { position : absolute; @@ -321,23 +322,23 @@ body { counter-reset : page-numbers 0; } } .imageMaskCenter { - &1 { --wc : url("/assets/waterColorMasks/center/0001.webp"); } - &2 { --wc : url("/assets/waterColorMasks/center/0002.webp"); } - &3 { --wc : url("/assets/waterColorMasks/center/0003.webp"); } - &4 { --wc : url("/assets/waterColorMasks/center/0004.webp"); } - &5 { --wc : url("/assets/waterColorMasks/center/0005.webp"); } - &6 { --wc : url("/assets/waterColorMasks/center/0006.webp"); } - &7 { --wc : url("/assets/waterColorMasks/center/0007.webp"); } - &8 { --wc : url("/assets/waterColorMasks/center/0008.webp"); } - &9 { --wc : url("/assets/waterColorMasks/center/0009.webp"); } - &10 { --wc : url("/assets/waterColorMasks/center/0010.webp"); } - &11 { --wc : url("/assets/waterColorMasks/center/0011.webp"); } - &12 { --wc : url("/assets/waterColorMasks/center/0012.webp"); } - &13 { --wc : url("/assets/waterColorMasks/center/0013.webp"); } - &14 { --wc : url("/assets/waterColorMasks/center/0014.webp"); } - &15 { --wc : url("/assets/waterColorMasks/center/0015.webp"); } - &16 { --wc : url("/assets/waterColorMasks/center/0016.webp"); } - &special { --wc : url("/assets/waterColorMasks/center/special.webp"); } + &1 { --wc : url('/assets/waterColorMasks/center/0001.webp'); } + &2 { --wc : url('/assets/waterColorMasks/center/0002.webp'); } + &3 { --wc : url('/assets/waterColorMasks/center/0003.webp'); } + &4 { --wc : url('/assets/waterColorMasks/center/0004.webp'); } + &5 { --wc : url('/assets/waterColorMasks/center/0005.webp'); } + &6 { --wc : url('/assets/waterColorMasks/center/0006.webp'); } + &7 { --wc : url('/assets/waterColorMasks/center/0007.webp'); } + &8 { --wc : url('/assets/waterColorMasks/center/0008.webp'); } + &9 { --wc : url('/assets/waterColorMasks/center/0009.webp'); } + &10 { --wc : url('/assets/waterColorMasks/center/0010.webp'); } + &11 { --wc : url('/assets/waterColorMasks/center/0011.webp'); } + &12 { --wc : url('/assets/waterColorMasks/center/0012.webp'); } + &13 { --wc : url('/assets/waterColorMasks/center/0013.webp'); } + &14 { --wc : url('/assets/waterColorMasks/center/0014.webp'); } + &15 { --wc : url('/assets/waterColorMasks/center/0015.webp'); } + &16 { --wc : url('/assets/waterColorMasks/center/0016.webp'); } + &special { --wc : url('/assets/waterColorMasks/center/special.webp'); } } @@ -346,15 +347,15 @@ body { counter-reset : page-numbers 0; } left : calc(-50% + var(--offsetX)); width : 200%; height : 200%; - transform : rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY)); -webkit-mask-image : var(--wc), var(--revealer); - -webkit-mask-repeat : no-repeat; - -webkit-mask-size : 100% 100%; //Scale both dimensions to fit page size - -webkit-mask-position : 50% 50%; mask-image : var(--wc), var(--revealer); + -webkit-mask-repeat : no-repeat; mask-repeat : no-repeat; - mask-size : 100% 100%; //Scale both dimensions to fit page size + -webkit-mask-position : 50% 50%; mask-position : 50% 50%; + -webkit-mask-size : 100% 100%; //Scale both dimensions to fit page size + mask-size : 100% 100%; //Scale both dimensions to fit page size + transform : rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY)); & > p:has(img) { bottom : 25%; left : 25%; @@ -367,43 +368,43 @@ body { counter-reset : page-numbers 0; } } } .imageMaskCorner { - &1 { --wc : url("/assets/waterColorMasks/corner/0001.webp"); } - &2 { --wc : url("/assets/waterColorMasks/corner/0002.webp"); } - &3 { --wc : url("/assets/waterColorMasks/corner/0003.webp"); } - &4 { --wc : url("/assets/waterColorMasks/corner/0004.webp"); } - &5 { --wc : url("/assets/waterColorMasks/corner/0005.webp"); } - &6 { --wc : url("/assets/waterColorMasks/corner/0006.webp"); } - &7 { --wc : url("/assets/waterColorMasks/corner/0007.webp"); } - &8 { --wc : url("/assets/waterColorMasks/corner/0008.webp"); } - &9 { --wc : url("/assets/waterColorMasks/corner/0009.webp"); } - &10 { --wc : url("/assets/waterColorMasks/corner/0010.webp"); } - &11 { --wc : url("/assets/waterColorMasks/corner/0011.webp"); } - &12 { --wc : url("/assets/waterColorMasks/corner/0012.webp"); } - &13 { --wc : url("/assets/waterColorMasks/corner/0013.webp"); } - &14 { --wc : url("/assets/waterColorMasks/corner/0014.webp"); } - &15 { --wc : url("/assets/waterColorMasks/corner/0015.webp"); } - &16 { --wc : url("/assets/waterColorMasks/corner/0016.webp"); } - &17 { --wc : url("/assets/waterColorMasks/corner/0017.webp"); } - &18 { --wc : url("/assets/waterColorMasks/corner/0018.webp"); } - &19 { --wc : url("/assets/waterColorMasks/corner/0019.webp"); } - &20 { --wc : url("/assets/waterColorMasks/corner/0020.webp"); } - &21 { --wc : url("/assets/waterColorMasks/corner/0021.webp"); } - &22 { --wc : url("/assets/waterColorMasks/corner/0022.webp"); } - &23 { --wc : url("/assets/waterColorMasks/corner/0023.webp"); } - &24 { --wc : url("/assets/waterColorMasks/corner/0024.webp"); } - &25 { --wc : url("/assets/waterColorMasks/corner/0025.webp"); } - &26 { --wc : url("/assets/waterColorMasks/corner/0026.webp"); } - &27 { --wc : url("/assets/waterColorMasks/corner/0027.webp"); } - &28 { --wc : url("/assets/waterColorMasks/corner/0028.webp"); } - &29 { --wc : url("/assets/waterColorMasks/corner/0029.webp"); } - &30 { --wc : url("/assets/waterColorMasks/corner/0030.webp"); } - &31 { --wc : url("/assets/waterColorMasks/corner/0031.webp"); } - &32 { --wc : url("/assets/waterColorMasks/corner/0032.webp"); } - &33 { --wc : url("/assets/waterColorMasks/corner/0033.webp"); } - &34 { --wc : url("/assets/waterColorMasks/corner/0034.webp"); } - &35 { --wc : url("/assets/waterColorMasks/corner/0035.webp"); } - &36 { --wc : url("/assets/waterColorMasks/corner/0036.webp"); } - &37 { --wc : url("/assets/waterColorMasks/corner/0037.webp"); } + &1 { --wc : url('/assets/waterColorMasks/corner/0001.webp'); } + &2 { --wc : url('/assets/waterColorMasks/corner/0002.webp'); } + &3 { --wc : url('/assets/waterColorMasks/corner/0003.webp'); } + &4 { --wc : url('/assets/waterColorMasks/corner/0004.webp'); } + &5 { --wc : url('/assets/waterColorMasks/corner/0005.webp'); } + &6 { --wc : url('/assets/waterColorMasks/corner/0006.webp'); } + &7 { --wc : url('/assets/waterColorMasks/corner/0007.webp'); } + &8 { --wc : url('/assets/waterColorMasks/corner/0008.webp'); } + &9 { --wc : url('/assets/waterColorMasks/corner/0009.webp'); } + &10 { --wc : url('/assets/waterColorMasks/corner/0010.webp'); } + &11 { --wc : url('/assets/waterColorMasks/corner/0011.webp'); } + &12 { --wc : url('/assets/waterColorMasks/corner/0012.webp'); } + &13 { --wc : url('/assets/waterColorMasks/corner/0013.webp'); } + &14 { --wc : url('/assets/waterColorMasks/corner/0014.webp'); } + &15 { --wc : url('/assets/waterColorMasks/corner/0015.webp'); } + &16 { --wc : url('/assets/waterColorMasks/corner/0016.webp'); } + &17 { --wc : url('/assets/waterColorMasks/corner/0017.webp'); } + &18 { --wc : url('/assets/waterColorMasks/corner/0018.webp'); } + &19 { --wc : url('/assets/waterColorMasks/corner/0019.webp'); } + &20 { --wc : url('/assets/waterColorMasks/corner/0020.webp'); } + &21 { --wc : url('/assets/waterColorMasks/corner/0021.webp'); } + &22 { --wc : url('/assets/waterColorMasks/corner/0022.webp'); } + &23 { --wc : url('/assets/waterColorMasks/corner/0023.webp'); } + &24 { --wc : url('/assets/waterColorMasks/corner/0024.webp'); } + &25 { --wc : url('/assets/waterColorMasks/corner/0025.webp'); } + &26 { --wc : url('/assets/waterColorMasks/corner/0026.webp'); } + &27 { --wc : url('/assets/waterColorMasks/corner/0027.webp'); } + &28 { --wc : url('/assets/waterColorMasks/corner/0028.webp'); } + &29 { --wc : url('/assets/waterColorMasks/corner/0029.webp'); } + &30 { --wc : url('/assets/waterColorMasks/corner/0030.webp'); } + &31 { --wc : url('/assets/waterColorMasks/corner/0031.webp'); } + &32 { --wc : url('/assets/waterColorMasks/corner/0032.webp'); } + &33 { --wc : url('/assets/waterColorMasks/corner/0033.webp'); } + &34 { --wc : url('/assets/waterColorMasks/corner/0034.webp'); } + &35 { --wc : url('/assets/waterColorMasks/corner/0035.webp'); } + &36 { --wc : url('/assets/waterColorMasks/corner/0036.webp'); } + &37 { --wc : url('/assets/waterColorMasks/corner/0037.webp'); } } } @@ -427,17 +428,6 @@ body { counter-reset : page-numbers 0; } } } -//***************************** -// * BLANK LINE -// *****************************/ -.page { - .blank { - height : 1em; - margin-top : 0; - & + * { margin-top : 0; } - } -} - //***************************** // * WIDE // *****************************/ @@ -448,6 +438,11 @@ body { counter-reset : page-numbers 0; } margin-bottom : 1em; & + * { margin-top : 0; } } + .blank { + height : 1em; + margin-top : 0; + & + * { margin-top : 0; } + } } //***************************** @@ -472,8 +467,8 @@ body { counter-reset : page-numbers 0; } height : 1.5cm; margin : 0 auto; background-color : black; - -webkit-mask : url("/assets/naturalCritLogoWhite.svg") center / contain no-repeat; - mask : url("/assets/naturalCritLogoWhite.svg") center / contain no-repeat; + -webkit-mask : url('/assets/naturalCritLogoWhite.svg') center / contain no-repeat; + mask : url('/assets/naturalCritLogoWhite.svg') center / contain no-repeat; } .homebreweryIcon.red { background-color : red; } .homebreweryIcon.gold { background-image : linear-gradient(to top left, brown 22.5%, gold 40%, white 60%, gold 67.5%, brown 82.5%); } @@ -497,12 +492,122 @@ body { counter-reset : page-numbers 0; } .pageNumber { left : 30px; } } - .resetCounting { - counter-set : page-numbers 1; - } + .resetCounting { counter-set : page-numbers 1; } - &:not(:has(.skipCounting)) { - counter-increment : page-numbers; - } + &:not(:has(.skipCounting)) { counter-increment : page-numbers; } -} \ No newline at end of file +} + +// ***************************** +// * INDEX +// *****************************/ +.page { + .index { + + ul ul { margin : 0; } + + ul { + padding-left : 0; + text-indent : 0; + list-style-type : none; + } + + & > ul > li { + padding-left : 1.5em; + text-indent : -1.5em; + } + } +} + +// ***************************** +// * TABLE OF CONTENTS +// *****************************/ + +// Default Exclusions +// Anything not exlcuded is included, default Headers are H1, H2, and H3. +h4, +h5, +h6, +.page:has(.frontCover), +.page:has(.backCover), +.page:has(.insideCover), +.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; } + +.tocIncludeH1 h1 {--TOC : include; } +.tocIncludeH2 h2 {--TOC : include; } +.tocIncludeH3 h3 {--TOC : include; } +.tocIncludeH4 h4 {--TOC : include; } +.tocIncludeH5 h5 {--TOC : include; } +.tocIncludeH6 h6 {--TOC : include; } + +.page { + &:has(.toc)::after { display : none; } + .toc { + -webkit-column-break-inside : avoid; + page-break-inside : avoid; + break-inside : avoid; + h1 { + margin-bottom : 0.3cm; + text-align : center; + } + a { + display : inline; + color : inherit; + text-decoration : none; + &:hover { text-decoration : underline; } + } + h4 { + margin-top : 0.2cm; + line-height : 0.4cm; + & + ul li { line-height : 1.2em; } + } + ul { + padding-left : 0; + margin-top : 0; + list-style-type : none; + a { + display : flex; + flex-flow : row nowrap; + justify-content : space-between; + width : 100%; + } + li + li h3 { + margin-top : 0.26cm; + line-height : 1em; + } + h3 span:first-child::after { border : none; } + span { + display : contents; + &:first-child::after { + bottom : 0.08cm; + flex : 1; + margin-right : 0.16cm; + margin-bottom : 0.08cm; + margin-left : 0.08cm; /* Spacing before dot leaders */ + content : ''; + border-bottom : 0.05cm dotted #000000; + } + &:last-child { + display : inline-block; + align-self : flex-end; + font-size : 0.34cm; + font-weight : normal; + } + } + ul { /* List indent */ + margin-left : 1em; + } + } + &.wide { + .useColumns(0.96, @fillMode: balance); + } + } + .toc.wide li { break-inside : auto; } +} diff --git a/themes/V3/Journal/snippets.js b/themes/V3/Journal/snippets.js index 636befb60..bad5e8e6a 100644 --- a/themes/V3/Journal/snippets.js +++ b/themes/V3/Journal/snippets.js @@ -1,4 +1,4 @@ -/* eslint-disable max-lines */ + module.exports = [ ]; diff --git a/themes/V3/Journal/style.less b/themes/V3/Journal/style.less index b8ed3ce8f..74c976f47 100644 --- a/themes/V3/Journal/style.less +++ b/themes/V3/Journal/style.less @@ -11,47 +11,35 @@ --HB_Color_WatercolorStain : #BBAD82; // Light brown } -.useSansSerif(){ - font-family : PermanentMarker; +.useSansSerif() { + font-family : 'PermanentMarker'; font-size : 0.3cm; line-height : 1.2em; color : var(--HB_Color_Text2); - p,dl,ul,ol { - line-height : 1.2em; - } - ul, ol { - padding-left : 1em; - } - em{ - font-style : italic; - } - strong{ - font-weight : 800; + p,dl,ul,ol { line-height : 1.2em; } + ul, ol { padding-left : 1em; } + em { font-style : italic; } + strong { font-size : 1.1em; + font-weight : 800; } - h5 + * { - margin-top : 0.1cm; - } -} -.useColumns(@multiplier : 1, @fillMode: balance){ - column-gap : 0.5cm; + h5 + * { margin-top : 0.1cm; } } +.useColumns(@multiplier : 1, @fillMode: balance) { column-gap : 0.5cm; } -.page{ - background-size : 200% 100%; - background-repeat : no-repeat; - filter : drop-shadow(1px 4px 14px black); - background-image : url(/assets/Journal/Background1.webp); +.page { padding : 2.1cm 1.9cm 1.7cm 3.8cm; - &:nth-of-type(2n + 1) { - background-position : left; - } + background-image : url('/assets/Journal/Background1.webp'); + background-repeat : no-repeat; + background-size : 200% 100%; + filter : drop-shadow(1px 4px 14px black); + &:nth-of-type(2n + 1) { background-position : left; } &:nth-of-type(2n) { - background-position : right; padding : 2.1cm 3.9cm 1.7cm 1.8cm; + background-position : right; } &:nth-of-type(2) { - background-image : url(/assets/Journal/Background2.webp); //Only first page should show ribbon + background-image : url('/assets/Journal/Background2.webp'); //Only first page should show ribbon } & .columnWrapper { @@ -59,167 +47,137 @@ } } - //***************************** - // * BASE +//***************************** +// * BASE // *****************************/ -.page{ - color : var(--HB_Color_Text); - font-family : ReenieBeanie; +.page { + font-family : 'ReenieBeanie'; font-size : 0.53cm; line-height : 0.8em; - p + * { - margin-top : 0.325cm; - } - p + p{ - margin-top : 0; - } - ul{ - margin-bottom : 0.8em; - } - ol{ - margin-bottom : 0.8em; - } - em{ + color : var(--HB_Color_Text); + p + * { margin-top : 0.325cm; } + p + p { margin-top : 0; } + ul { margin-bottom : 0.8em; } + ol { margin-bottom : 0.8em; } + em { + font-style : unset; text-decoration : underline; - font-style : unset; - } - del{ - text-decoration-style: double; } + del { text-decoration-style : double; } //Indents after p or lists - p+p, ul+p, ol+p{ - text-indent : 1em; - } + p + p, ul + p, ol + p { text-indent : 1em; } //***************************** // * HEADERS // *****************************/ - h1,h2,h3,h4,h5{ - font-family : FrederickaTheGreat; + h1,h2,h3,h4,h5 { + font-family : 'FrederickaTheGreat'; font-weight : unset; color : var(--HB_Color_HeaderText); } - h1{ + h1 { margin-bottom : 0.18cm; //Margin-bottom only because this is WIDE font-size : 0.89cm; - line-height : 1em; font-variant : small-caps; - &+p::first-letter{ + line-height : 1em; + & + p::first-letter { float : left; - font-family : FrederickaTheGreat; - line-height : 1em; - font-size : 1.9em; - padding-left : 40px; //Allow background color to extend into margins - margin-top : -0.3cm; - margin-bottom : -20px; - margin-left : -40px; - margin-right : 0.1em; padding-top : 0.3em; padding-bottom : 2px; + padding-left : 40px; //Allow background color to extend into margins + margin-top : -0.3cm; + margin-right : 0.1em; + margin-bottom : -20px; + margin-left : -40px; + font-family : 'FrederickaTheGreat'; + font-size : 1.9em; + line-height : 1em; } - &+p::first-line{ - font-variant : small-caps; - } + & + p::first-line { font-variant : small-caps; } } - h2{ + h2 { font-size : 0.62cm; line-height : 0.988em; //Font is misaligned. Shift up slightly } - h3{ + h3 { + margin-left : -0.9em; font-size : 0.575cm; line-height : 0.995em; //Font is misaligned. Shift up slightly - margin-left : -0.9em; } - h4{ + h4 { + padding-bottom : 5px; font-size : 0.55cm; line-height : 0.971em; //Font is misaligned. Shift up slightly color : var(--HB_Color_Text); - padding-bottom : 5px; - transform:rotate(0deg); - &:nth-of-type(2n) { - transform:rotate(1deg); - } - &:nth-of-type(3n) { - transform:rotate(-1.5deg); - } + transform : rotate(0deg); + &:nth-of-type(2n) { transform : rotate(1deg); } + &:nth-of-type(3n) { transform : rotate(-1.5deg); } } - h5{ - font-family : PermanentMarker; + h5 { + font-family : 'PermanentMarker'; font-size : 0.4cm; - color : var(--HB_Color_Text2); font-weight : bold; line-height : 0.951em; //Font is misaligned. Shift up slightly - & + * { - margin-top : 0.2cm; - } + color : var(--HB_Color_Text2); + & + * { margin-top : 0.2cm; } } //***************************** // * TABLE // *****************************/ - table{ + table { .useSansSerif(); - & + * { - margin-top : 0.325cm; - } - thead{ - th{ - vertical-align : bottom; + & + * { margin-top : 0.325cm; } + thead { + th { padding : 0.14em 0; + vertical-align : bottom; } } - tbody{ - tr{ - td{ - padding : 0.14em 0; - } - &:nth-child(odd){ - background-image : linear-gradient(to left, #41212100, #41212122, #41212100); - } + tbody { + tr { + td { padding : 0.14em 0; } + &:nth-child(odd) { background-image : linear-gradient(to left, #41212100, #41212122, #41212100); } } } } //***************************** // * NOTE // *****************************/ - .note{ + .note { .useSansSerif(); + padding : 0.2cm; + background-image : url('/assets/Journal/HashMarks.png'), + linear-gradient(to bottom right, #FF000000, #A36A4E14, #41212100); + background-repeat : no-repeat; + background-position : center; + background-size : 120% 120%; border-style : solid; border-width : 1px; - border-image-source : url(/assets/Journal/Border1.png); + border-image-source : url('/assets/Journal/Border1.png'); border-image-slice : 18 18 18 18; border-image-width : 6px 6px 6px 6px; border-image-outset : 5px 5px 5px 5px; border-image-repeat : stretch stretch; - background-image : url(/assets/Journal/HashMarks.png), - linear-gradient(to bottom right, #ff000000, #a36a4e14, #41212100); - background-size : 120% 120%; - background-repeat : no-repeat; - background-position : center; - padding : 0.2cm; :where(&) { margin-top : 9px; //Prevent top border getting cut off on colbreak } - & + * { - margin-top : 0.45cm; - } - h5 { - font-size : 0.375cm; - } - p{ - padding-bottom : 0px; - } - :last-child { - margin-bottom : 0; - } + & + * { margin-top : 0.45cm; } + h5 { font-size : 0.375cm; } + p { padding-bottom : 0px; } + :last-child { margin-bottom : 0; } } //************************************ // * DESCRIPTIVE TEXT BOX // ************************************/ - * + .descriptive { - margin-top : 0.6cm; - } - .descriptive{ + * + .descriptive { margin-top : 0.6cm; } + .descriptive { .useSansSerif(); + padding : 0.2cm; + background-image : url('/assets/Journal/HashMarks.png'), + linear-gradient(to bottom right, #FF000000, #41212114, #41212100); + background-repeat : no-repeat; + background-position : center; + background-size : 120% 120%; border-style : solid; border-width : 1px; border-image-source : url('/assets/Journal/Border2.png'); @@ -227,27 +185,13 @@ border-image-width : 20px; border-image-outset : 16px 20px 16px 20px; border-image-repeat : stretch stretch; - background-image : url(/assets/Journal/HashMarks.png), - linear-gradient(to bottom right, #ff000000, #41212114, #41212100); - background-size : 120% 120%; - background-repeat : no-repeat; - background-position : center; - padding : 0.2cm; :where(&) { margin-top : 4px; //Prevent top border getting cut off on colbreak } - & + * { - margin-top : 0.45cm; - } - h5 { - font-size : 0.375cm; - } - p{ - padding-bottom : 0px; - } - :last-child { - margin-bottom : 0; - } + & + * { margin-top : 0.45cm; } + h5 { font-size : 0.375cm; } + p { padding-bottom : 0px; } + :last-child { margin-bottom : 0; } } //***************************** // * Images Snippets @@ -257,25 +201,23 @@ .artist { position : absolute; width : auto; - text-align : center; - font-family : WalterTurncoat; + font-family : 'WalterTurncoat'; font-size : 0.27cm; color : var(--HB_Color_CaptionText); + text-align : center; p, p + p { margin : unset; - text-indent : unset; line-height : 1em; + text-indent : unset; } - h5 { + h5 { + font-family : 'WalterTurncoat'; font-size : 1.3em; - font-family : WalterTurncoat; } - a{ + a { color : inherit; text-decoration : unset; - &:hover { - text-decoration : underline; - } + &:hover { text-decoration : underline; } } } @@ -285,6 +227,10 @@ .monster { .useSansSerif(); &.frame { + padding : 0.2cm; + background-image : url('/assets/Journal/HashMarks.png'), + linear-gradient(to bottom right, #FF000000, #A36A4E14, #41212100); + background-size : 100%; border-style : solid; border-width : 7px 6px; border-image-source : url('/assets/Journal/Border3.png'); @@ -292,33 +238,29 @@ border-image-width : 15px 20px 15px 20px; border-image-outset : 12px 12px 12px 12px; border-image-repeat : stretch round; - background-image : url('/assets/Journal/HashMarks.png'), - linear-gradient(to bottom right, #ff000000, #a36a4e14, #41212100); background-blend-mode : screen multiply; - background-size : 100%; - padding : 0.2cm; } - - color: var(--HB_Color_Text); position : relative; padding : 0px; margin-bottom : 0.325cm; + color : var(--HB_Color_Text); + //Headers - h2{ + h2 { + margin : 0; font-size : 0.62cm; line-height : 1em; - margin : 0; - &+p { + & + p { margin-bottom : 0; //Monster size and type subtext } } - h3{ + h3 { + padding-bottom : 0.05cm; margin-left : 0; font-variant : small-caps; - padding-bottom : 0.05cm; } - hr{ + hr { visibility : visible; height : 6px; margin : 0.12cm 0cm; @@ -330,24 +272,18 @@ } // Monster Ability table - hr + table:first-of-type{ + hr + table:first-of-type { margin : 0; - column-span : none; - background-image : none; + color : inherit; + background-image : none; border-style : none; border-image : none; - color : inherit; - tr { - background-image : none; - } - td,th { - padding: 0px; - } + column-span : none; + tr { background-image : none; } + td,th { padding : 0px; } } - :last-child { - margin-bottom : 0; - } + :last-child { margin-bottom : 0; } strong, em { font-style : normal; @@ -356,29 +292,27 @@ } //Full Width - .monster.wide{ + .monster.wide { .useColumns(0.96, @fillMode: balance); } //***************************** // * FOOTER // *****************************/ - &:nth-child(odd){ - .pageNumber{ - left : 3cm; - } - .footnote{ + &:nth-child(odd) { + .pageNumber { left : 3cm; } + .footnote { left : 4.5cm; text-align : left; } } - .pageNumber{ - font-family : FrederickaTheGreat; + .pageNumber { right : 3cm; bottom : 1.25cm; + font-family : 'FrederickaTheGreat'; color : var(--HB_Color_HeaderText); } - .footnote{ + .footnote { position : absolute; right : 4.5cm; bottom : 1.25cm; @@ -391,154 +325,134 @@ //************************************ // * CODE BLOCKS // ************************************/ - code{ - font-size : 0.3cm; + code { padding : 0px 4px; - color : var(--HB_Color_Text); + font-size : 0.3cm; vertical-align : middle; - background-color : #faf7ea; + color : var(--HB_Color_Text); + background-color : #FAF7EA; border-radius : 4px; } - pre code{ + pre code { + padding : 0.15cm; + margin-bottom : 2px; border-style : solid; border-width : 1px; + border-radius : 12px; border-image : @codeBorderImage 26 stretch; border-image-width : 10px; border-image-outset : 2px; - border-radius : 12px; - margin-bottom : 2px; - padding : 0.15cm; .page :where(&) { margin-top : 2px; //Prevent top border getting cut off on colbreak } - & + * { - margin-top : 0.325cm; - } + & + * { margin-top : 0.325cm; } } //***************************** // * EXTRAS // *****************************/ - hr{ + hr { visibility : hidden; - border : none; margin : 0px; + border : none; } //Text indent right after table - table+p{ - text-indent : 1em; - } + table + p { text-indent : 1em; } a, a:visited, a:hover { - color: var(--HB_Color_Text); - transition:all 1s ease; - } - a:hover { - color:red; + color : var(--HB_Color_Text); + transition : all 1s ease; } + a:hover { color : red; } } //***************************** // * SPELL LIST // *****************************/ -.page .spellList{ +.page .spellList { .useSansSerif(); - font-family : PermanentMarker; + font-family : 'PermanentMarker'; column-count : 2; - ul+h5{ - margin-top : 15px; - } - ul{ - margin-bottom : 0.5em; + ul + h5 { margin-top : 15px; } + ul { padding-left : 1em; + margin-bottom : 0.5em; text-indent : -1em; list-style-type : none; + break-inside : auto; -webkit-column-break-inside : auto; page-break-inside : auto; - break-inside : auto; - } - &.wide{ - column-count : 4; } + &.wide { column-count : 4; } } //***************************** // * CLASS TABLE // *****************************/ -.page .classTable{ - th[colspan]:not([rowspan]) { - white-space : nowrap; - } - h5 + table{ - margin-top : 0.2cm; - } +.page .classTable { + th[colspan]:not([rowspan]) { white-space : nowrap; } + h5 + table { margin-top : 0.2cm; } } //***************************** // * TABLE OF CONTENTS // *****************************/ -.page .toc{ +.page .toc { -webkit-column-break-inside : avoid; page-break-inside : avoid; break-inside : avoid; h1 { - text-align : center; margin-bottom : 0.3cm; + text-align : center; } - a{ + a { display : inline; color : inherit; text-decoration : none; - &:hover{ - text-decoration : underline; - } + &:hover { text-decoration : underline; } } h4 { margin-top : 0.2cm; line-height : 0.4cm; - & + ul li { - line-height: 1.2em; - } + & + ul li { line-height : 1.2em; } } - ul{ + ul { padding-left : 0; list-style-type : none; li + li h3 { margin-top : 0.26cm; - line-height : 1em - } - h3 span:first-child::after { - border : none; + line-height : 1em; } + h3 span:first-child::after { border : none; } span { display : table-cell; &:first-child { - position : relative; - overflow : hidden; + position : relative; + overflow : hidden; &::after { - content : ""; position : absolute; bottom : 0.08cm; - margin-left : 0.06cm; /* Spacing before dot leaders */ width : 100%; - border-bottom : 0.05cm dotted #000; + margin-left : 0.06cm; /* Spacing before dot leaders */ + content : ''; + border-bottom : 0.05cm dotted #000000; } } &:last-child { - font-family : ReenieBeanie; - font-size : 0.34cm; - font-weight : normal; - color : black; - text-align : right; - vertical-align : bottom; /* Keep page number bottom-aligned */ width : 1%; padding-left : 0.06cm; /* Spacing after dot leaders */ - /*white-space : nowrap; /* Uncomment if needed */ + font-family : 'ReenieBeanie'; + font-size : 0.34cm; + font-weight : normal; + vertical-align : bottom; /* Keep page number bottom-aligned */ + color : black; + text-align : right; + /* white-space : nowrap; /* Uncomment if needed */ } } - ul { /*List indent*/ + ul { /* List indent */ margin-left : 1em; } } - &.wide{ + &.wide { .useColumns(0.96, @fillMode: balance); } } @@ -546,6 +460,4 @@ //***************************** // * WIDE // *****************************/ -.page .wide { - margin-bottom : 0.45cm; -} +.page .wide { margin-bottom : 0.45cm; } diff --git a/themes/codeMirror/customEditorStyles.less b/themes/codeMirror/customEditorStyles.less index 367eaec33..8c48c1b43 100644 --- a/themes/codeMirror/customEditorStyles.less +++ b/themes/codeMirror/customEditorStyles.less @@ -1,88 +1,83 @@ .editor .codeEditor .CodeMirror { - // Themes with dark backgrounds - &.cm-s-3024-night, - &.cm-s-abbott, - &.cm-s-abcdef, - &.cm-s-ambiance, - &.cm-s-ayu-dark, - &.cm-s-ayu-mirage, - &.cm-s-base16-dark, - &.cm-s-bespin, - &.cm-s-blackboard, - &.cm-s-cobalt, - &.cm-s-colorforth, - &.cm-s-darcula, - &.cm-s-dracula, - &.cm-s-duotone-dark, - &.cm-s-erlang-dark, - &.cm-s-gruvbox-dark, - &.cm-s-hopscotch, - &.cm-s-icecoder, - &.cm-s-isotope, - &.cm-s-lesser-dark, - &.cm-s-liquibyte, - &.cm-s-lucario, - &.cm-s-material, - &.cm-s-material-darker, - &.cm-s-material-ocean, - &.cm-s-material-palenight, - &.cm-s-mbo, - &.cm-s-midnight, - &.cm-s-monokai, - &.cm-s-moxer, - &.cm-s-night, - &.cm-s-nord, - &.cm-s-oceanic-next, - &.cm-s-panda-syntax, - &.cm-s-paraiso-dark, - &.cm-s-pastel-on-dark, - &.cm-s-railscasts, - &.cm-s-rubyblue, - &.cm-s-seti, - &.cm-s-shadowfox, - &.cm-s-the-matrix, - &.cm-s-tomorrow-night-bright, - &.cm-s-tomorrow-night-eighties, - &.cm-s-twilight, - &.cm-s-vibrant-ink, - &.cm-s-xq-dark, - &.cm-s-yonce, - &.cm-s-zenburn - { - .CodeMirror-code { - .block:not(.cm-comment) { - color: magenta; - } - .columnSplit { - color: black; - background-color: rgba(35,153,153,0.5); - } - .pageLine { - background-color: rgba(255,255,255,0.5); - & ~ pre.CodeMirror-line { - color: black; - } - } - } - } - // Themes with light backgrounds - &.cm-s-default, - &.cm-s-3024-day, - &.cm-s-ambiance-mobile, - &.cm-s-base16-light, - &.cm-s-duotone-light, - &.cm-s-eclipse, - &.cm-s-elegant, - &.cm-s-juejin, - &.cm-s-neat, - &.cm-s-neo, - &.cm-s-paraiso-lightm - &.cm-s-solarized, - &.cm-s-ssms, - &.cm-s-ttcn, - &.cm-s-xq-light, - &.cm-s-yeti { - // Future styling for themes with light backgrounds - --dummyVar: 'currently unused'; - } + // Themes with dark backgrounds + &.cm-s-3024-night, + &.cm-s-abbott, + &.cm-s-abcdef, + &.cm-s-ambiance, + &.cm-s-ayu-dark, + &.cm-s-ayu-mirage, + &.cm-s-base16-dark, + &.cm-s-bespin, + &.cm-s-blackboard, + &.cm-s-cobalt, + &.cm-s-colorforth, + &.cm-s-darcula, + &.cm-s-dracula, + &.cm-s-duotone-dark, + &.cm-s-erlang-dark, + &.cm-s-gruvbox-dark, + &.cm-s-hopscotch, + &.cm-s-icecoder, + &.cm-s-isotope, + &.cm-s-lesser-dark, + &.cm-s-liquibyte, + &.cm-s-lucario, + &.cm-s-material, + &.cm-s-material-darker, + &.cm-s-material-ocean, + &.cm-s-material-palenight, + &.cm-s-mbo, + &.cm-s-midnight, + &.cm-s-monokai, + &.cm-s-moxer, + &.cm-s-night, + &.cm-s-nord, + &.cm-s-oceanic-next, + &.cm-s-panda-syntax, + &.cm-s-paraiso-dark, + &.cm-s-pastel-on-dark, + &.cm-s-railscasts, + &.cm-s-rubyblue, + &.cm-s-seti, + &.cm-s-shadowfox, + &.cm-s-the-matrix, + &.cm-s-tomorrow-night-bright, + &.cm-s-tomorrow-night-eighties, + &.cm-s-twilight, + &.cm-s-vibrant-ink, + &.cm-s-xq-dark, + &.cm-s-yonce, + &.cm-s-zenburn { + .CodeMirror-code { + .block:not(.cm-comment) { color : magenta; } + .columnSplit { + color : black; + background-color : rgba(35,153,153,0.5); + } + .pageLine { + background-color : rgba(255,255,255,0.5); + & ~ pre.CodeMirror-line { color : black; } + } + } + } + // Themes with light backgrounds + &.cm-s-default, + &.cm-s-3024-day, + &.cm-s-ambiance-mobile, + &.cm-s-base16-light, + &.cm-s-duotone-light, + &.cm-s-eclipse, + &.cm-s-elegant, + &.cm-s-juejin, + &.cm-s-neat, + &.cm-s-neo, + &.cm-s-paraiso-lightm + &.cm-s-solarized, + &.cm-s-ssms, + &.cm-s-ttcn, + &.cm-s-xq-light, + &.cm-s-yeti { + // Future styling for themes with light backgrounds + --dummyVar : 'currently unused'; + } } diff --git a/themes/fonts/5e legacy/fonts.less b/themes/fonts/5e legacy/fonts.less index d4c10c456..680e395ec 100644 --- a/themes/fonts/5e legacy/fonts.less +++ b/themes/fonts/5e legacy/fonts.less @@ -1,61 +1,61 @@ /* Main Font, serif */ @font-face { - font-family: BookSanity; - src: url('../../../fonts/5e legacy/Bookinsanity.woff2'); - font-weight: normal; - font-style: normal; + font-family : "BookSanity"; + font-style : normal; + font-weight : normal; + src : url('../../../fonts/5e legacy/Bookinsanity.woff2'); } @font-face { - font-family: BookSanity; - src: url('../../../fonts/5e legacy/Bookinsanity Bold.woff2'); - font-weight: bold; - font-style: normal; + font-family : "BookSanity"; + font-style : normal; + font-weight : bold; + src : url('../../../fonts/5e legacy/Bookinsanity Bold.woff2'); } @font-face { - font-family: BookSanity; - src: url('../../../fonts/5e legacy/Bookinsanity Italic.woff2'); - font-weight: normal; - font-style: italic; + font-family : "BookSanity"; + font-style : italic; + font-weight : normal; + src : url('../../../fonts/5e legacy/Bookinsanity Italic.woff2'); } @font-face { - font-family: BookSanity; - src: url('../../../fonts/5e legacy/Bookinsanity Bold Italic.woff2'); - font-weight: bold; - font-style: italic; + font-family : "BookSanity"; + font-style : italic; + font-weight : bold; + src : url('../../../fonts/5e legacy/Bookinsanity Bold Italic.woff2'); } /* Notes and Tables, sans-serif */ @font-face { - font-family: ScalySans; - src: url('../../../fonts/5e legacy/Scaly Sans.woff2'); - font-weight: normal; - font-style: normal; + font-family : "ScalySans"; + font-style : normal; + font-weight : normal; + src : url('../../../fonts/5e legacy/Scaly Sans.woff2'); } @font-face { - font-family: ScalySansSmallCaps; - src: url('../../../fonts/5e legacy/Scaly Sans Caps.woff2'); - font-weight: normal; - font-style: normal; + font-family : "ScalySansSmallCaps"; + font-style : normal; + font-weight : normal; + src : url('../../../fonts/5e legacy/Scaly Sans Caps.woff2'); } @font-face { - font-family: WalterTurncoat; - src: url('../../../fonts/5e legacy/WalterTurncoat-Regular.woff2'); - font-weight: normal; - font-style: normal; + font-family : "WalterTurncoat"; + font-style : normal; + font-weight : normal; + src : url('../../../fonts/5e legacy/WalterTurncoat-Regular.woff2'); } /* Headers */ @font-face { - font-family: MrJeeves; - src: url('../../../fonts/5e legacy/Mr Eaves Small Caps.woff2'); - font-weight: normal; - font-style: normal; + font-family : "MrJeeves"; + font-style : normal; + font-weight : normal; + src : url('../../../fonts/5e legacy/Mr Eaves Small Caps.woff2'); } /* Fancy Drop Cap */ @font-face { - font-family: Solberry; - src: url('../../../fonts/5e legacy/Solbera Imitation.woff2'); - font-weight: normal; - font-style: normal; + font-family : "Solberry"; + font-style : normal; + font-weight : normal; + src : url('../../../fonts/5e legacy/Solbera Imitation.woff2'); } diff --git a/themes/fonts/5e/fonts.less b/themes/fonts/5e/fonts.less index c028b06f9..eec5c418f 100644 --- a/themes/fonts/5e/fonts.less +++ b/themes/fonts/5e/fonts.less @@ -1,143 +1,143 @@ /* Main Font, serif */ @font-face { - font-family: BookInsanityRemake; - src: url('../../../fonts/5e/Bookinsanity.woff2'); - font-weight: normal; - font-style: normal; + font-family : "BookInsanityRemake"; + font-style : normal; + font-weight : normal; + src : url('../../../fonts/5e/Bookinsanity.woff2'); } @font-face { - font-family: BookInsanityRemake; - src: url('../../../fonts/5e/Bookinsanity Bold.woff2'); - font-weight: bold; - font-style: normal; + font-family : "BookInsanityRemake"; + font-style : normal; + font-weight : bold; + src : url('../../../fonts/5e/Bookinsanity Bold.woff2'); } @font-face { - font-family: BookInsanityRemake; - src: url('../../../fonts/5e/Bookinsanity Italic.woff2'); - font-weight: normal; - font-style: italic; + font-family : "BookInsanityRemake"; + font-style : italic; + font-weight : normal; + src : url('../../../fonts/5e/Bookinsanity Italic.woff2'); } @font-face { - font-family: BookInsanityRemake; - src: url('../../../fonts/5e/Bookinsanity Bold Italic.woff2'); - font-weight: bold; - font-style: italic; + font-family : "BookInsanityRemake"; + font-style : italic; + font-weight : bold; + src : url('../../../fonts/5e/Bookinsanity Bold Italic.woff2'); } /* Notes and Tables, sans-serif */ @font-face { - font-family: ScalySansRemake; - src: url('../../../fonts/5e/Scaly Sans.woff2'); - font-weight: normal; - font-style: normal; + font-family : "ScalySansRemake"; + font-style : normal; + font-weight : normal; + src : url('../../../fonts/5e/Scaly Sans.woff2'); } @font-face { - font-family: ScalySansRemake; - src: url('../../../fonts/5e/Scaly Sans Bold.woff2'); - font-weight: bold; - font-style: normal; + font-family : "ScalySansRemake"; + font-style : normal; + font-weight : bold; + src : url('../../../fonts/5e/Scaly Sans Bold.woff2'); } @font-face { - font-family: ScalySansRemake; - src: url('../../../fonts/5e/Scaly Sans Italic.woff2'); - font-weight: normal; - font-style: italic; + font-family : "ScalySansRemake"; + font-style : italic; + font-weight : normal; + src : url('../../../fonts/5e/Scaly Sans Italic.woff2'); } @font-face { - font-family: ScalySansRemake; - src: url('../../../fonts/5e/Scaly Sans Bold Italic.woff2'); - font-weight: bold; - font-style: italic; + font-family : "ScalySansRemake"; + font-style : italic; + font-weight : bold; + src : url('../../../fonts/5e/Scaly Sans Bold Italic.woff2'); } @font-face { - font-family: ScalySansSmallCapsRemake; - src: url('../../../fonts/5e/Scaly Sans Caps.woff2'); - font-weight: normal; - font-style: normal; + font-family : "ScalySansSmallCapsRemake"; + font-style : normal; + font-weight : normal; + src : url('../../../fonts/5e/Scaly Sans Caps.woff2'); } @font-face { - font-family: WalterTurncoat; - src: url('../../../fonts/5e/WalterTurncoat-Regular.woff2'); - font-weight: normal; - font-style: normal; + font-family : "WalterTurncoat"; + font-style : normal; + font-weight : normal; + src : url('../../../fonts/5e/WalterTurncoat-Regular.woff2'); } /* Headers */ @font-face { - font-family: MrEavesRemake; - src: url('../../../fonts/5e/Mr Eaves Small Caps.woff2'); - font-weight: normal; - font-style: normal; + font-family : "MrEavesRemake"; + font-style : normal; + font-weight : normal; + src : url('../../../fonts/5e/Mr Eaves Small Caps.woff2'); } /* Fancy Drop Cap */ @font-face { - font-family: SolberaImitationRemake; //Tweaked 5e version - src: url('../../../fonts/5e/Solbera Imitation Tweak.woff2'); - font-weight: 100 1000; - font-style: normal; - font-style: italic; + font-family : "SolberaImitationRemake"; //Tweaked 5e version + font-style : normal; + font-style : italic; + font-weight : 100 1000; + src : url('../../../fonts/5e/Solbera Imitation Tweak.woff2'); } /* Cover Page */ @font-face { - font-family: NodestoCapsCondensed; - src: url('../../../fonts/5e/Nodesto Caps Condensed.woff2'); - font-weight: normal; - font-style: normal; + font-family : "NodestoCapsCondensed"; + font-style : normal; + font-weight : normal; + src : url('../../../fonts/5e/Nodesto Caps Condensed.woff2'); } @font-face { - font-family: NodestoCapsCondensed; - src: url('../../../fonts/5e/Nodesto Caps Condensed Bold.woff2'); - font-weight: bold; - font-style: normal; + font-family : "NodestoCapsCondensed"; + font-style : normal; + font-weight : bold; + src : url('../../../fonts/5e/Nodesto Caps Condensed Bold.woff2'); } @font-face { - font-family: NodestoCapsCondensed; - src: url('../../../fonts/5e/Nodesto Caps Condensed Italic.woff2'); - font-weight: normal; - font-style: italic; + font-family : "NodestoCapsCondensed"; + font-style : italic; + font-weight : normal; + src : url('../../../fonts/5e/Nodesto Caps Condensed Italic.woff2'); } @font-face { - font-family: NodestoCapsCondensed; - src: url('../../../fonts/5e/Nodesto Caps Condensed Bold Italic.woff2'); - font-weight: bold; - font-style: italic; + font-family : "NodestoCapsCondensed"; + font-style : italic; + font-weight : bold; + src : url('../../../fonts/5e/Nodesto Caps Condensed Bold Italic.woff2'); } @font-face { - font-family: NodestoCapsWide; - src: url('../../../fonts/5e/Nodesto Caps Wide.woff2'); - font-weight: normal; - font-style: normal + font-family : "NodestoCapsWide"; + font-style : normal; + font-weight : normal; + src : url('../../../fonts/5e/Nodesto Caps Wide.woff2'); } @font-face { - font-family: Overpass; - src: url('../../../fonts/5e/Overpass Medium.woff2'); - font-weight: 500; - font-style: normal; + font-family : "Overpass"; + font-style : normal; + font-weight : 500; + src : url('../../../fonts/5e/Overpass Medium.woff2'); } @font-face { - font-family: Davek; - src: url('../../../fonts/5e/Davek.woff2'); - font-weight: 500; - font-style: normal; + font-family : "Davek"; + font-style : normal; + font-weight : 500; + src : url('../../../fonts/5e/Davek.woff2'); } @font-face { - font-family: Iokharic; - src: url('../../../fonts/5e/Iokharic.woff2'); - font-weight: 500; - font-style: normal; + font-family : "Iokharic"; + font-style : normal; + font-weight : 500; + src : url('../../../fonts/5e/Iokharic.woff2'); } @font-face { - font-family: Rellanic; - src: url('../../../fonts/5e/Rellanic.woff2'); - font-weight: 500; - font-style: normal; + font-family : "Rellanic"; + font-style : normal; + font-weight : 500; + src : url('../../../fonts/5e/Rellanic.woff2'); } diff --git a/themes/fonts/Blank/fonts.less b/themes/fonts/Blank/fonts.less index 4a3d2d1e8..6558c84b4 100644 --- a/themes/fonts/Blank/fonts.less +++ b/themes/fonts/Blank/fonts.less @@ -18,29 +18,29 @@ License: */ @font-face { - font-family: Pagella; - src: url('../../../fonts/Blank/texgyrepagella-regular.woff2'); - font-weight: normal; - font-style: normal; + font-family : "Pagella"; + font-style : normal; + font-weight : normal; + src : url('../../../fonts/Blank/texgyrepagella-regular.woff2'); } @font-face { - font-family: Pagella; - src: url('../../../fonts/Blank/texgyrepagella-bold.woff2'); - font-weight: bold; - font-style: normal; + font-family : "Pagella"; + font-style : normal; + font-weight : bold; + src : url('../../../fonts/Blank/texgyrepagella-bold.woff2'); } @font-face { - font-family: Pagella; - src: url('../../../fonts/Blank/texgyrepagella-italic.woff2'); - font-weight: normal; - font-style: italic; + font-family : "Pagella"; + font-style : italic; + font-weight : normal; + src : url('../../../fonts/Blank/texgyrepagella-italic.woff2'); } @font-face { - font-family: Pagella; - src: url('../../../fonts/Blank/texgyrepagella-bolditalic.woff2'); - font-weight: bold; - font-style: italic; + font-family : "Pagella"; + font-style : italic; + font-weight : bold; + src : url('../../../fonts/Blank/texgyrepagella-bolditalic.woff2'); } diff --git a/themes/fonts/Journal/fonts.less b/themes/fonts/Journal/fonts.less index 703b594ba..20190c651 100644 --- a/themes/fonts/Journal/fonts.less +++ b/themes/fonts/Journal/fonts.less @@ -1,58 +1,58 @@ /* Main Font, serif */ @font-face { - font-family: ReenieBeanie; - src: url('../../../fonts/Journal/ReenieBeanie-Regular.woff2'); - font-weight: normal; - font-style: normal; + font-family : "ReenieBeanie"; + font-style : normal; + font-weight : normal; + src : url('../../../fonts/Journal/ReenieBeanie-Regular.woff2'); } /* Notes and Tables, sans-serif */ @font-face { - font-family: PermanentMarker; - src: url('../../../fonts/Journal/PermanentMarker-Regular.woff2'); - font-weight: normal; - font-style: normal; + font-family : "PermanentMarker"; + font-style : normal; + font-weight : normal; + src : url('../../../fonts/Journal/PermanentMarker-Regular.woff2'); } @font-face { - font-family: WalterTurncoat; - src: url('../../../fonts/5e/WalterTurncoat-Regular.woff2'); - font-weight: normal; - font-style: normal; + font-family : "WalterTurncoat"; + font-style : normal; + font-weight : normal; + src : url('../../../fonts/5e/WalterTurncoat-Regular.woff2'); } /* Headers */ @font-face { - font-family: FrederickaTheGreat; - src: url('../../../fonts/Journal/FrederickaTheGreat-Regular.woff2'); - font-weight: normal; - font-style: normal; + font-family : "FrederickaTheGreat"; + font-style : normal; + font-weight : normal; + src : url('../../../fonts/Journal/FrederickaTheGreat-Regular.woff2'); } /* Cover Page */ @font-face { - font-family: NodestoCapsCondensed; - src: url('../fonts/5e/Nodesto Caps Condensed.woff2'); - font-weight: normal; - font-style: normal; + font-family : "NodestoCapsCondensed"; + font-style : normal; + font-weight : normal; + src : url('../fonts/5e/Nodesto Caps Condensed.woff2'); } @font-face { - font-family: NodestoCapsCondensed; - src: url('../fonts/5e/Nodesto Caps Condensed Bold.woff2'); - font-weight: bold; - font-style: normal; + font-family : "NodestoCapsCondensed"; + font-style : normal; + font-weight : bold; + src : url('../fonts/5e/Nodesto Caps Condensed Bold.woff2'); } @font-face { - font-family: NodestoCapsCondensed; - src: url('../fonts/5e/Nodesto Caps Condensed Italic.woff2'); - font-weight: normal; - font-style: italic; + font-family : "NodestoCapsCondensed"; + font-style : italic; + font-weight : normal; + src : url('../fonts/5e/Nodesto Caps Condensed Italic.woff2'); } @font-face { - font-family: NodestoCapsCondensed; - src: url('../fonts/5e/Nodesto Caps Condensed Bold Italic.woff2'); - font-weight: bold; - font-style: italic; + font-family : "NodestoCapsCondensed"; + font-style : italic; + font-weight : bold; + src : url('../fonts/5e/Nodesto Caps Condensed Bold Italic.woff2'); } diff --git a/themes/fonts/iconFonts/diceFont.less b/themes/fonts/iconFonts/diceFont.less index ec80f132b..3b60093d0 100644 --- a/themes/fonts/iconFonts/diceFont.less +++ b/themes/fonts/iconFonts/diceFont.less @@ -13,8 +13,8 @@ font-weight : normal; font-variant : normal; line-height : 1; - text-decoration : inherit; text-transform : none; + text-decoration : inherit; text-rendering : optimizeLegibility; /* Better Font Rendering =========== */ diff --git a/themes/fonts/iconFonts/fontAwesome.js b/themes/fonts/iconFonts/fontAwesome.js index f5f89e3aa..bf11a7c92 100644 --- a/themes/fonts/iconFonts/fontAwesome.js +++ b/themes/fonts/iconFonts/fontAwesome.js @@ -1,3 +1,5 @@ +/* eslint-disable max-lines */ + const fontAwesome = { // FONT-AWESOME SOLID 'fas_0' : 'fas fa-0', diff --git a/themes/phb.depricated.less b/themes/phb.depricated.less index 992dab35b..7cc574183 100644 --- a/themes/phb.depricated.less +++ b/themes/phb.depricated.less @@ -1,31 +1,31 @@ -.phb{ +.phb { //Double hr for full width elements - hr+hr+blockquote{ - column-span : all; + hr + hr + blockquote { -webkit-column-span : all; -moz-column-span : all; + column-span : all; } //***************************** // * CLASS TABLE // *****************************/ - hr+table{ + hr+table { + padding-top : 10px; margin-top : -5px; margin-bottom : 50px; - padding-top : 10px; border-collapse : separate; background-color : white; border : initial; border-style : solid; + border-image-source : @frameBorderImage; + border-image-slice : 150 200 150 200; + border-image-width : 47px; border-image-outset : 37px 17px; border-image-repeat : round; - border-image-slice : 150 200 150 200; - border-image-source : @frameBorderImage; - border-image-width : 47px; } - h5+hr+table{ - column-span : all; + h5 + hr + table { -webkit-column-span : all; -moz-column-span : all; + column-span : all; } } \ No newline at end of file