diff --git a/README.DOCKER.md b/README.DOCKER.md index 4dfbef045..dba2e2257 100644 --- a/README.DOCKER.md +++ b/README.DOCKER.md @@ -47,9 +47,7 @@ Make an changes you need to `config/docker.json` then build the image. If it doe "naturalcrit_url" : "local.naturalcrit.com:8010", "secret" : "secret", "web_port" : 8000, -"enable_v3" : true, "mongodb_uri": "mongodb://172.17.0.2/homebrewery", -"enable_themes" : true, } ``` @@ -90,6 +88,13 @@ docker run --name homebrewery-mongodb -d --restart unless-stopped -v mongodata:/ docker run --name homebrewery-app -d --restart unless-stopped -e NODE_ENV=docker -v $(pwd)/config/docker.json:/usr/src/app/config/docker.json -p 8000:8000 docker.io/library/homebrewery:latest ``` +**NOTE:** If you are running from the Windows command line, this will not work as `$(pwd)` is not valid syntax. Use this command instead: +```shell +# Make sure you run this in the homebrewery directory +docker run --name homebrewery-app -d --restart unless-stopped -e NODE_ENV=docker -v %cd%/config/docker.json:/usr/src/app/config/docker.json -p 8000:8000 docker.io/library/homebrewery:latest +``` + + ## Updating the Image When Homebrewery code updates, your docker container will not automatically follow the changes. To do so you will need to rebuild your homebrewery image. @@ -117,3 +122,9 @@ docker-compose build homebrewery docker run --name homebrewery-app -d --restart unless-stopped -e NODE_ENV=docker -v $(pwd)/config/docker.json:/usr/src/app/config/docker.json -p 8000:8000 docker.io/library/homebrewery:latest ``` +**NOTE:** If you are running from the Windows command line, this will not work as `$(pwd)` is not valid syntax. Use this command instead: +```shell +# Make sure you run this in the homebrewery directory +docker run --name homebrewery-app -d --restart unless-stopped -e NODE_ENV=docker -v %cd%/config/docker.json:/usr/src/app/config/docker.json -p 8000:8000 docker.io/library/homebrewery:latest +``` + diff --git a/README.md b/README.md index 5206f4cbf..a33f2f073 100644 --- a/README.md +++ b/README.md @@ -75,8 +75,9 @@ it using the two commands: 1. `npm install` 1. `npm start` -You should now be able to go to [http://localhost:8000](http://localhost:8000) -in your browser and use The Homebrewery offline. +When the Homebrewery server is started for the first time, it will modify the database to create the indexes required for better Homebrewery performance. This may take a few moments to complete for each index, dependent on how much content is in your local database - a brand new, empty database should be done in seconds. + +On completion, you should be able to go to [http://localhost:8000](http://localhost:8000) in your browser and use The Homebrewery offline. If you had any issue at all, here are some links that may be useful: - [Course](https://learn.mongodb.com/courses/m103-basic-cluster-administration) on cluster administration, useful for beginners @@ -145,3 +146,4 @@ your contribution to the project, please join our [gitter chat][gitter-url]. [github-pr-docs-url]: https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request [gitter-url]: https://gitter.im/naturalcrit/Lobby + diff --git a/client/admin/admin.jsx b/client/admin/admin.jsx index 29973d221..787c2a3eb 100644 --- a/client/admin/admin.jsx +++ b/client/admin/admin.jsx @@ -7,15 +7,17 @@ import LockTools from './lockTools/lockTools.jsx'; const tabGroups = ['brew', 'notifications', 'authors', 'locks']; +const ADMIN_TAB = 'HB_adminPage_currentTab'; + const Admin = ()=>{ const [currentTab, setCurrentTab] = useState(''); useEffect(()=>{ - setCurrentTab(localStorage.getItem('hbAdminTab') || 'brew'); + setCurrentTab(localStorage.getItem(ADMIN_TAB) || 'brew'); }, []); useEffect(()=>{ - localStorage.setItem('hbAdminTab', currentTab); + localStorage.setItem(ADMIN_TAB, currentTab); }, [currentTab]); return ( diff --git a/client/components/splitPane/splitPane.jsx b/client/components/splitPane/splitPane.jsx index 4c77d81a5..78ba59ed3 100644 --- a/client/components/splitPane/splitPane.jsx +++ b/client/components/splitPane/splitPane.jsx @@ -2,7 +2,8 @@ require('./splitPane.less'); const React = require('react'); const { useState, useEffect } = React; -const storageKey = 'naturalcrit-pane-split'; +const PANE_WIDTH_KEY = 'HB_editor_splitWidth'; +const LIVE_SCROLL_KEY = 'HB_editor_liveScroll'; const SplitPane = (props)=>{ const { @@ -18,9 +19,9 @@ const SplitPane = (props)=>{ const [liveScroll, setLiveScroll] = useState(false); useEffect(()=>{ - const savedPos = window.localStorage.getItem(storageKey); + const savedPos = window.localStorage.getItem(PANE_WIDTH_KEY); setDividerPos(savedPos ? limitPosition(savedPos, 0.1 * (window.innerWidth - 13), 0.9 * (window.innerWidth - 13)) : window.innerWidth / 2); - setLiveScroll(window.localStorage.getItem('liveScroll') === 'true'); + setLiveScroll(window.localStorage.getItem(LIVE_SCROLL_KEY) === 'true'); window.addEventListener('resize', handleResize); return ()=>window.removeEventListener('resize', handleResize); @@ -29,13 +30,13 @@ const SplitPane = (props)=>{ const limitPosition = (x, min = 1, max = window.innerWidth - 13)=>Math.round(Math.min(max, Math.max(min, x))); //when resizing, the divider should grow smaller if less space is given, then grow back if the space is restored, to the original position - const handleResize = ()=>setDividerPos(limitPosition(window.localStorage.getItem(storageKey), 0.1 * (window.innerWidth - 13), 0.9 * (window.innerWidth - 13))); + const handleResize = ()=>setDividerPos(limitPosition(window.localStorage.getItem(PANE_WIDTH_KEY), 0.1 * (window.innerWidth - 13), 0.9 * (window.innerWidth - 13))); const handleUp =(e)=>{ e.preventDefault(); if(isDragging) { onDragFinish(dividerPos); - window.localStorage.setItem(storageKey, dividerPos); + window.localStorage.setItem(PANE_WIDTH_KEY, dividerPos); } setIsDragging(false); }; @@ -52,7 +53,7 @@ const SplitPane = (props)=>{ }; const liveScrollToggle = ()=>{ - window.localStorage.setItem('liveScroll', String(!liveScroll)); + window.localStorage.setItem(LIVE_SCROLL_KEY, String(!liveScroll)); setLiveScroll(!liveScroll); }; diff --git a/client/homebrew/brewRenderer/brewRenderer.jsx b/client/homebrew/brewRenderer/brewRenderer.jsx index 7a101e9f9..a19d33375 100644 --- a/client/homebrew/brewRenderer/brewRenderer.jsx +++ b/client/homebrew/brewRenderer/brewRenderer.jsx @@ -24,6 +24,8 @@ const PAGEBREAK_REGEX_LEGACY = /\\page(?:break)?/m; const COLUMNBREAK_REGEX_LEGACY = /\\column(:?break)?/m; const PAGE_HEIGHT = 1056; +const TOOLBAR_STATE_KEY = 'HB_renderer_toolbarState'; + const INITIAL_CONTENT = dedent` @@ -122,7 +124,7 @@ const BrewRenderer = (props)=>{ //useEffect to store or gather toolbar state from storage useEffect(()=>{ - const toolbarState = JSON.parse(window.localStorage.getItem('hb_toolbarState')); + const toolbarState = JSON.parse(window.localStorage.getItem(TOOLBAR_STATE_KEY)); toolbarState && setDisplayOptions(toolbarState); }, []); @@ -284,7 +286,7 @@ const BrewRenderer = (props)=>{ const handleDisplayOptionsChange = (newDisplayOptions)=>{ setDisplayOptions(newDisplayOptions); - localStorage.setItem('hb_toolbarState', JSON.stringify(newDisplayOptions)); + localStorage.setItem(TOOLBAR_STATE_KEY, JSON.stringify(newDisplayOptions)); }; const pagesStyle = { @@ -293,12 +295,6 @@ const BrewRenderer = (props)=>{ rowGap : `${displayOptions.rowGap}px` }; - const styleObject = {}; - - if(global.config.deployment) { - styleObject.backgroundImage = `url("data:image/svg+xml;utf8,${global.config.deployment}")`; - } - const renderedStyle = useMemo(()=>renderStyle(), [props.style, props.themeBundle]); renderedPages = useMemo(()=>renderPages(), [props.text, displayOptions]); @@ -327,10 +323,9 @@ const BrewRenderer = (props)=>{ contentDidMount={frameDidMount} onClick={()=>{emitClick();}} > -
{/* Apply CSS from Style tab and render pages from Markdown tab */} diff --git a/client/homebrew/brewRenderer/brewRenderer.less b/client/homebrew/brewRenderer/brewRenderer.less index b0a3e9779..bb4fe69c5 100644 --- a/client/homebrew/brewRenderer/brewRenderer.less +++ b/client/homebrew/brewRenderer/brewRenderer.less @@ -6,7 +6,6 @@ overflow-y : scroll; will-change : transform; &:has(.facing, .flow) { padding : 60px 30px; } - &.deployment { background-color : darkred; } :where(.pages) { &.facing { display : grid; diff --git a/client/homebrew/brewRenderer/toolBar/toolBar.jsx b/client/homebrew/brewRenderer/toolBar/toolBar.jsx index 6938eacb7..4aee3b6bd 100644 --- a/client/homebrew/brewRenderer/toolBar/toolBar.jsx +++ b/client/homebrew/brewRenderer/toolBar/toolBar.jsx @@ -9,6 +9,8 @@ import { Anchored, AnchoredBox, AnchoredTrigger } from '../../../components/Anch const MAX_ZOOM = 300; const MIN_ZOOM = 10; +const TOOLBAR_VISIBILITY = 'HB_renderer_toolbarVisibility'; + const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPages, headerState, setHeaderState })=>{ const [pageNum, setPageNum] = useState(1); @@ -21,8 +23,8 @@ const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPa }, [visiblePages]); useEffect(()=>{ - const Visibility = localStorage.getItem('hb_toolbarVisibility'); - if (Visibility) setToolsVisible(Visibility === 'true'); + const Visibility = localStorage.getItem(TOOLBAR_VISIBILITY); + if(Visibility) setToolsVisible(Visibility === 'true'); }, []); @@ -100,7 +102,7 @@ const ToolBar = ({ displayOptions, onDisplayOptionsChange, visiblePages, totalPa
diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index 8d331e46e..d6fdf3f08 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -10,7 +10,7 @@ const CodeEditor = require('naturalcrit/codeEditor/codeEditor.jsx'); const SnippetBar = require('./snippetbar/snippetbar.jsx'); const MetadataEditor = require('./metadataEditor/metadataEditor.jsx'); -const EDITOR_THEME_KEY = 'HOMEBREWERY-EDITOR-THEME'; +const EDITOR_THEME_KEY = 'HB_editor_theme'; const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m; const SNIPPETBREAK_REGEX_V3 = /^\\snippet\ .*$/; @@ -40,11 +40,8 @@ const Editor = createClass({ style : '' }, - onTextChange : ()=>{}, - onStyleChange : ()=>{}, - onMetaChange : ()=>{}, - onSnipChange : ()=>{}, - reportError : ()=>{}, + onBrewChange : ()=>{}, + reportError : ()=>{}, onCursorPageChange : ()=>{}, onViewPageChange : ()=>{}, @@ -143,7 +140,7 @@ const Editor = createClass({ handleViewChange : function(newView){ this.props.setMoveArrows(newView === 'text'); - + this.setState({ view : newView }, ()=>{ @@ -328,10 +325,10 @@ const Editor = createClass({ const brewRenderer = window.frames['BrewRenderer'].contentDocument.getElementsByClassName('brewRenderer')[0]; const currentPos = brewRenderer.scrollTop; const targetPos = window.frames['BrewRenderer'].contentDocument.getElementById(`p${targetPage}`).getBoundingClientRect().top; - - const checkIfScrollComplete = ()=>{ - let scrollingTimeout; - clearTimeout(scrollingTimeout); // Reset the timer every time a scroll event occurs + + let scrollingTimeout; + const checkIfScrollComplete = ()=>{ // Prevent interrupting a scroll in progress if user clicks multiple times + clearTimeout(scrollingTimeout); // Reset the timer every time a scroll event occurs scrollingTimeout = setTimeout(()=>{ isJumping = false; brewRenderer.removeEventListener('scroll', checkIfScrollComplete); @@ -372,8 +369,8 @@ const Editor = createClass({ let currentY = this.codeEditor.current.codeMirror.getScrollInfo().top; let targetY = this.codeEditor.current.codeMirror.heightAtLine(targetLine, 'local', true); - const checkIfScrollComplete = ()=>{ - let scrollingTimeout; + let scrollingTimeout; + const checkIfScrollComplete = ()=>{ // Prevent interrupting a scroll in progress if user clicks multiple times clearTimeout(scrollingTimeout); // Reset the timer every time a scroll event occurs scrollingTimeout = setTimeout(()=>{ isJumping = false; @@ -412,7 +409,6 @@ const Editor = createClass({ //Called when there are changes to the editor's dimensions update : function(){ - this.codeEditor.current?.updateSize(); const snipHeight = document.querySelector('.editor > .snippetBar').offsetHeight; if(snipHeight !== this.state.snippetbarHeight) this.setState({ snippetbarHeight: snipHeight }); @@ -438,7 +434,7 @@ const Editor = createClass({ language='gfm' view={this.state.view} value={this.props.brew.text} - onChange={this.props.onTextChange} + onChange={this.props.onBrewChange('text')} editorTheme={this.state.editorTheme} rerenderParent={this.rerenderParent} style={{ height: `calc(100% - ${this.state.snippetbarHeight}px)` }} /> @@ -451,7 +447,7 @@ const Editor = createClass({ language='css' view={this.state.view} value={this.props.brew.style ?? DEFAULT_STYLE_TEXT} - onChange={this.props.onStyleChange} + onChange={this.props.onBrewChange('style')} enableFolding={true} editorTheme={this.state.editorTheme} rerenderParent={this.rerenderParent} @@ -467,7 +463,7 @@ const Editor = createClass({ ; @@ -481,7 +477,7 @@ const Editor = createClass({ language='gfm' view={this.state.view} value={this.props.brew.snippets} - onChange={this.props.onSnipChange} + onChange={this.props.onBrewChange('snippets')} enableFolding={true} editorTheme={this.state.editorTheme} rerenderParent={this.rerenderParent} diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.jsx b/client/homebrew/editor/metadataEditor/metadataEditor.jsx index 8f256922f..338ad9360 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.jsx +++ b/client/homebrew/editor/metadataEditor/metadataEditor.jsx @@ -207,8 +207,6 @@ const MetadataEditor = createClass({ }, renderThemeDropdown : function(){ - if(!global.enable_themes) return; - const mergedThemes = _.merge(Themes, this.props.userThemes); const listThemes = (renderer)=>{ @@ -307,8 +305,6 @@ const MetadataEditor = createClass({ }, renderRenderOptions : function(){ - if(!global.enable_v3) return; - return
diff --git a/client/homebrew/editor/metadataEditor/validations.js b/client/homebrew/editor/metadataEditor/validations.js index 858fca6c4..d0e052b07 100644 --- a/client/homebrew/editor/metadataEditor/validations.js +++ b/client/homebrew/editor/metadataEditor/validations.js @@ -18,7 +18,7 @@ module.exports = { try { Boolean(new URL(value)); return null; - } catch (e) { + } catch { return 'Must be a valid URL'; } } diff --git a/client/homebrew/homebrew.jsx b/client/homebrew/homebrew.jsx index a6b4b9175..e38293bde 100644 --- a/client/homebrew/homebrew.jsx +++ b/client/homebrew/homebrew.jsx @@ -4,6 +4,8 @@ import './homebrew.less'; import React from 'react'; import { StaticRouter as Router, Route, Routes, useParams, useSearchParams } from 'react-router'; +import { updateLocalStorage } from './utils/updateLocalStorage/updateLocalStorageKeys.js'; + import HomePage from './pages/homePage/homePage.jsx'; import EditPage from './pages/editPage/editPage.jsx'; import UserPage from './pages/userPage/userPage.jsx'; @@ -17,7 +19,6 @@ const WithRoute = ({ el: Element, ...rest })=>{ const params = useParams(); const [searchParams] = useSearchParams(); const queryParams = Object.fromEntries(searchParams?.entries() || []); - return ; }; @@ -26,8 +27,6 @@ const Homebrew = (props)=>{ url = '', version = '0.0.0', account = null, - enable_v3 = false, - enable_themes, config, brew = { title : '', @@ -44,13 +43,22 @@ const Homebrew = (props)=>{ global.account = account; global.version = version; - global.enable_v3 = enable_v3; - global.enable_themes = enable_themes; global.config = config; + const backgroundObject = ()=>{ + if(global.config.deployment || (config.local && config.development)){ + const bgText = global.config.deployment || 'Local'; + return { + backgroundImage : `url("data:image/svg+xml;utf8,${bgText}")` + }; + } + return null; + }; + updateLocalStorage(); + return ( -
+
} /> } /> diff --git a/client/homebrew/homebrew.less b/client/homebrew/homebrew.less index e265c2941..2cbc35857 100644 --- a/client/homebrew/homebrew.less +++ b/client/homebrew/homebrew.less @@ -1,12 +1,14 @@ @import 'naturalcrit/styles/core.less'; .homebrew { height : 100%; + background-color:@steel; + &.deployment { background-color : darkred; } + .sitePage { display : flex; flex-direction : column; height : 100%; overflow-y : hidden; - background-color : @steel; .content { position : relative; flex : auto; diff --git a/client/homebrew/navbar/error-navitem.jsx b/client/homebrew/navbar/error-navitem.jsx index 6d9bec444..a2650dbc8 100644 --- a/client/homebrew/navbar/error-navitem.jsx +++ b/client/homebrew/navbar/error-navitem.jsx @@ -2,9 +2,9 @@ require('./error-navitem.less'); const React = require('react'); const Nav = require('naturalcrit/nav/nav.jsx'); -const ErrorNavItem = ({error = '', clearError})=>{ +const ErrorNavItem = ({ error = '', clearError })=>{ const response = error.response; - const errorCode = error.code + const errorCode = error.code; const status = response?.status; const HBErrorCode = response?.body?.HBErrorCode; const message = response?.body?.message; @@ -15,7 +15,7 @@ const ErrorNavItem = ({error = '', clearError})=>{ errMsg += `\`\`\`\n${error.stack}\n`; errMsg += `${JSON.stringify(response?.error, null, ' ')}\n\`\`\``; console.log(errMsg); - } catch (e){} + } catch {} if(status === 409) { return @@ -112,6 +112,15 @@ const ErrorNavItem = ({error = '', clearError})=>{ ; } + if(HBErrorCode === '13') { + return + Oops! +
+ Server has lost connection to the database. +
+
; + } + if(errorCode === 'ECONNABORTED') { return Oops! diff --git a/client/homebrew/navbar/navbar.less b/client/homebrew/navbar/navbar.less index aa233d631..7b0217bf8 100644 --- a/client/homebrew/navbar/navbar.less +++ b/client/homebrew/navbar/navbar.less @@ -37,7 +37,10 @@ &:has(.brewTitle) { flex-grow : 1; - min-width : 300px; + min-width : 300px; + } + >.brewTitle { + cursor:auto; } } // "NaturalCrit" logo diff --git a/client/homebrew/navbar/newbrew.navitem.jsx b/client/homebrew/navbar/newbrew.navitem.jsx index ccade4e8b..d19b7595f 100644 --- a/client/homebrew/navbar/newbrew.navitem.jsx +++ b/client/homebrew/navbar/newbrew.navitem.jsx @@ -34,8 +34,8 @@ const NewBrew = ()=>{ } const type = file.name.split('.').pop().toLowerCase(); - - alert(`This file is invalid: ${!type ? "Missing file extension" :`.${type} files are not supported`}. Only .txt files exported from the Homebrewery are allowed.`); + + alert(`This file is invalid: ${!type ? 'Missing file extension' :`.${type} files are not supported`}. Only .txt files exported from the Homebrewery are allowed.`); console.log(file); diff --git a/client/homebrew/navbar/recent.navitem.jsx b/client/homebrew/navbar/recent.navitem.jsx index a6cbbf406..4c4722515 100644 --- a/client/homebrew/navbar/recent.navitem.jsx +++ b/client/homebrew/navbar/recent.navitem.jsx @@ -5,8 +5,8 @@ const Moment = require('moment'); const Nav = require('naturalcrit/nav/nav.jsx'); -const EDIT_KEY = 'homebrewery-recently-edited'; -const VIEW_KEY = 'homebrewery-recently-viewed'; +const EDIT_KEY = 'HB_nav_recentlyEdited'; +const VIEW_KEY = 'HB_nav_recentlyViewed'; const RecentItems = createClass({ diff --git a/client/homebrew/navbar/share.navitem.jsx b/client/homebrew/navbar/share.navitem.jsx index a08ac6878..0cd1a52b7 100644 --- a/client/homebrew/navbar/share.navitem.jsx +++ b/client/homebrew/navbar/share.navitem.jsx @@ -2,22 +2,22 @@ import React from 'react'; import dedent from 'dedent-tabs'; import Nav from 'naturalcrit/nav/nav.jsx'; - const getShareId = (brew)=>( - brew.googleId && !brew.stubbed - ? brew.googleId + brew.shareId - : brew.shareId - ); +const getShareId = (brew)=>( + brew.googleId && !brew.stubbed + ? brew.googleId + brew.shareId + : brew.shareId +); - const getRedditLink = (brew)=>{ - const text = dedent` +const getRedditLink = (brew)=>{ + const text = dedent` Hey guys! I've been working on this homebrew. I'd love your feedback. Check it out. **[Homebrewery Link](${global.config.baseUrl}/share/${getShareId(brew)})**`; - return `https://www.reddit.com/r/UnearthedArcana/submit?title=${encodeURIComponent(brew.title.toWellFormed())}&text=${encodeURIComponent(text)}`; - }; + return `https://www.reddit.com/r/UnearthedArcana/submit?title=${encodeURIComponent(brew.title.toWellFormed())}&text=${encodeURIComponent(text)}`; +}; -export default ({brew}) => ( +export default ({ brew })=>( share diff --git a/client/homebrew/pages/accountPage/accountPage.jsx b/client/homebrew/pages/accountPage/accountPage.jsx index 598683504..b69eb6e3e 100644 --- a/client/homebrew/pages/accountPage/accountPage.jsx +++ b/client/homebrew/pages/accountPage/accountPage.jsx @@ -13,7 +13,7 @@ const AccountPage = (props)=>{ // initialize save location from local storage based on user id React.useEffect(()=>{ if(!saveLocation && accountDetails.username) { - SAVEKEY = `HOMEBREWERY-DEFAULT-SAVE-LOCATION-${accountDetails.username}`; + SAVEKEY = `HB_editor_defaultSave_${accountDetails.username}`; // if no SAVEKEY in local storage, default save location to Google Drive if user has Google account. let saveLocation = window.localStorage.getItem(SAVEKEY); saveLocation = saveLocation ?? (accountDetails.googleId ? 'GOOGLE-DRIVE' : 'HOMEBREWERY'); diff --git a/client/homebrew/pages/basePages/listPage/listPage.jsx b/client/homebrew/pages/basePages/listPage/listPage.jsx index ec557ffb1..4afc14364 100644 --- a/client/homebrew/pages/basePages/listPage/listPage.jsx +++ b/client/homebrew/pages/basePages/listPage/listPage.jsx @@ -7,7 +7,9 @@ const moment = require('moment'); const BrewItem = require('./brewItem/brewItem.jsx'); -const USERPAGE_KEY_PREFIX = 'HOMEBREWERY-LISTPAGE'; +const USERPAGE_SORT_DIR = 'HB_listPage_sortDir'; +const USERPAGE_SORT_TYPE = 'HB_listPage_sortType'; +const USERPAGE_GROUP_VISIBILITY_PREFIX = 'HB_listPage_visibility_group'; const DEFAULT_SORT_TYPE = 'alpha'; const DEFAULT_SORT_DIR = 'asc'; @@ -50,12 +52,12 @@ const ListPage = createClass({ // LOAD FROM LOCAL STORAGE if(typeof window !== 'undefined') { - const newSortType = (this.state.sortType ?? (localStorage.getItem(`${USERPAGE_KEY_PREFIX}-SORTTYPE`) || DEFAULT_SORT_TYPE)); - const newSortDir = (this.state.sortDir ?? (localStorage.getItem(`${USERPAGE_KEY_PREFIX}-SORTDIR`) || DEFAULT_SORT_DIR)); + const newSortType = (this.state.sortType ?? (localStorage.getItem(USERPAGE_SORT_TYPE) || DEFAULT_SORT_TYPE)); + const newSortDir = (this.state.sortDir ?? (localStorage.getItem(USERPAGE_SORT_DIR) || DEFAULT_SORT_DIR)); this.updateUrl(this.state.filterString, newSortType, newSortDir); const brewCollection = this.props.brewCollection.map((brewGroup)=>{ - brewGroup.visible = (localStorage.getItem(`${USERPAGE_KEY_PREFIX}-VISIBILITY-${brewGroup.class}`) ?? 'true')=='true'; + brewGroup.visible = (localStorage.getItem(`${USERPAGE_GROUP_VISIBILITY_PREFIX}_${brewGroup.class}`) ?? 'true')=='true'; return brewGroup; }); @@ -73,10 +75,10 @@ const ListPage = createClass({ saveToLocalStorage : function() { this.state.brewCollection.map((brewGroup)=>{ - localStorage.setItem(`${USERPAGE_KEY_PREFIX}-VISIBILITY-${brewGroup.class}`, `${brewGroup.visible}`); + localStorage.setItem(`${USERPAGE_GROUP_VISIBILITY_PREFIX}_${brewGroup.class}`, `${brewGroup.visible}`); }); - localStorage.setItem(`${USERPAGE_KEY_PREFIX}-SORTTYPE`, this.state.sortType); - localStorage.setItem(`${USERPAGE_KEY_PREFIX}-SORTDIR`, this.state.sortDir); + localStorage.setItem(USERPAGE_SORT_TYPE, this.state.sortType); + localStorage.setItem(USERPAGE_SORT_DIR, this.state.sortDir); }, renderBrews : function(brews){ diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index 6c2220ec1..524f31ac9 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -1,44 +1,54 @@ /* eslint-disable max-lines */ import './editPage.less'; -import React, { useState, useEffect, useRef, useCallback } from 'react'; +// Common imports +import React, { useState, useEffect, useRef } from 'react'; import request from '../../utils/request-middleware.js'; import Markdown from 'naturalcrit/markdown.js'; +import _ from 'lodash'; -import _ from 'lodash';; -import { makePatches, stringifyPatches } from '@sanity/diff-match-patch'; -import { md5 } from 'hash-wasm'; -import { gzipSync, strToU8 } from 'fflate'; -import { Meta } from 'vitreum/headtags'; +import { DEFAULT_BREW_LOAD } from '../../../../server/brewDefaults.js'; +import { printCurrentBrew, fetchThemeBundle, splitTextStyleAndMetadata } from '../../../../shared/helpers.js'; + +import SplitPane from 'client/components/splitPane/splitPane.jsx'; +import Editor from '../../editor/editor.jsx'; +import BrewRenderer from '../../brewRenderer/brewRenderer.jsx'; import Nav from 'naturalcrit/nav/nav.jsx'; import Navbar from '../../navbar/navbar.jsx'; import NewBrewItem from '../../navbar/newbrew.navitem.jsx'; import AccountNavItem from '../../navbar/account.navitem.jsx'; -import ShareNavItem from '../../navbar/share.navitem.jsx'; import ErrorNavItem from '../../navbar/error-navitem.jsx'; import HelpNavItem from '../../navbar/help.navitem.jsx'; import VaultNavItem from '../../navbar/vault.navitem.jsx'; import PrintNavItem from '../../navbar/print.navitem.jsx'; import { both as RecentNavItem } from '../../navbar/recent.navitem.jsx'; -import SplitPane from 'client/components/splitPane/splitPane.jsx'; -import Editor from '../../editor/editor.jsx'; -import BrewRenderer from '../../brewRenderer/brewRenderer.jsx'; +// Page specific imports +import { Meta } from 'vitreum/headtags'; +import { md5 } from 'hash-wasm'; +import { gzipSync, strToU8 } from 'fflate'; +import { makePatches, stringifyPatches } from '@sanity/diff-match-patch'; +import ShareNavItem from '../../navbar/share.navitem.jsx'; import LockNotification from './lockNotification/lockNotification.jsx'; - -import { DEFAULT_BREW_LOAD } from '../../../../server/brewDefaults.js'; -import { printCurrentBrew, fetchThemeBundle } from '../../../../shared/helpers.js'; - import { updateHistory, versionHistoryGarbageCollection } from '../../utils/versionHistory.js'; - import googleDriveIcon from '../../googleDrive.svg'; const SAVE_TIMEOUT = 10000; const UNSAVED_WARNING_TIMEOUT = 900000; //Warn user afer 15 minutes of unsaved changes const UNSAVED_WARNING_POPUP_TIMEOUT = 4000; //Show the warning for 4 seconds + +const AUTOSAVE_KEY = 'HB_editor_autoSaveOn'; +const BREWKEY = 'HB_newPage_content'; +const STYLEKEY = 'HB_newPage_style'; +const SNIPKEY = 'HB_newPage_snippets'; +const METAKEY = 'HB_newPage_meta'; + +const useLocalStorage = false; +const neverSaved = false; + const EditPage = (props)=>{ props = { brew : DEFAULT_BREW_LOAD, @@ -70,7 +80,7 @@ const EditPage = (props)=>{ const unsavedChangesRef = useRef(unsavedChanges); // Similarly, onBeforeUnload lives outside React and needs ref to unsavedChanges useEffect(()=>{ - const autoSavePref = JSON.parse(localStorage.getItem('AUTOSAVE_ON') ?? true); + const autoSavePref = JSON.parse(localStorage.getItem(AUTOSAVE_KEY) ?? true); setAutoSaveEnabled(autoSavePref); setWarnUnsavedChanges(!autoSavePref); setHTMLErrors(Markdown.validate(currentBrew.text)); @@ -113,41 +123,27 @@ const EditPage = (props)=>{ editorRef.current?.update(); }; - const handleEditorViewPageChange = (pageNumber)=>{ - setCurrentEditorViewPageNum(pageNumber); - }; + const handleBrewChange = (field)=>(value, subfield)=>{ //'text', 'style', 'snippets', 'metadata' + if(subfield == 'renderer' || subfield == 'theme') + fetchThemeBundle(setError, setThemeBundle, value.renderer, value.theme); - const handleEditorCursorPageChange = (pageNumber)=>{ - setCurrentEditorCursorPageNum(pageNumber); - }; - - const handleBrewRendererPageChange = (pageNumber)=>{ - setCurrentBrewRendererPageNum(pageNumber); - }; - - const handleTextChange = (text)=>{ //If there are HTML errors, run the validator on every change to give quick feedback - if(HTMLErrors.length) - setHTMLErrors(Markdown.validate(text)); - setCurrentBrew((prevBrew)=>({ ...prevBrew, text })); - }; + if(HTMLErrors.length && (field == 'text' || field == 'snippets')) + setHTMLErrors(Markdown.validate(value)); - const handleStyleChange = (style)=>{ - setCurrentBrew((prevBrew)=>({ ...prevBrew, style })); - }; + if(field == 'metadata') setCurrentBrew((prev)=>({ ...prev, ...value })); + else setCurrentBrew((prev)=>({ ...prev, [field]: value })); - const handleSnipChange = (snippet)=>{ - //If there are HTML errors, run the validator on every change to give quick feedback - if(HTMLErrors.length) - setHTMLErrors(Markdown.validate(snippet)); - setCurrentBrew((prevBrew)=>({ ...prevBrew, snippets: snippet })); - }; - - const handleMetaChange = (metadata, field = undefined)=>{ - if(field === 'theme' || field === 'renderer') - fetchThemeBundle(setError, setThemeBundle, metadata.renderer, metadata.theme); - - setCurrentBrew((prev)=>({ ...prev, ...metadata })); + if(useLocalStorage) { + if(field == 'text') localStorage.setItem(BREWKEY, value); + if(field == 'style') localStorage.setItem(STYLEKEY, value); + if(field == 'snippets') localStorage.setItem(SNIPKEY, value); + if(field == 'metadata') localStorage.setItem(METAKEY, JSON.stringify({ + renderer : value.renderer, + theme : value.theme, + lang : value.lang + })); + } }; const updateBrew = (newData)=>setCurrentBrew((prevBrew)=>({ @@ -313,20 +309,24 @@ const EditPage = (props)=>{ // #3 - Unsaved changes exist, click to save, show SAVE NOW if(unsavedChanges) - return trySave(true)} color='blue' icon='fas fa-save'>Save Now; + return trySave(true)} color='blue' icon='fas fa-save'>save now; // #4 - No unsaved changes, autosave is ON, show AUTO-SAVED if(autoSaveEnabled) - return auto-saved.; + return auto-saved; + + // #5 - No unsaved changes, and has never been saved, hide the button + if(neverSaved) + return save now; // DEFAULT - No unsaved changes, show SAVED - return saved.; + return saved; }; const toggleAutoSave = ()=>{ clearTimeout(warnUnsavedTimeout.current); clearTimeout(saveTimeout.current); - localStorage.setItem('AUTOSAVE_ON', JSON.stringify(!autoSaveEnabled)); + localStorage.setItem(AUTOSAVE_KEY, JSON.stringify(!autoSaveEnabled)); setAutoSaveEnabled(!autoSaveEnabled); setWarnUnsavedChanges(autoSaveEnabled); }; @@ -356,11 +356,11 @@ const EditPage = (props)=>{ {renderSaveButton()} {renderAutoSaveButton()} } - - - + + + @@ -380,17 +380,14 @@ const EditPage = (props)=>{ { themeBundle={themeBundle} errors={HTMLErrors} lang={currentBrew.lang} - onPageChange={handleBrewRendererPageChange} + onPageChange={setCurrentBrewRendererPageNum} currentEditorViewPageNum={currentEditorViewPageNum} currentEditorCursorPageNum={currentEditorCursorPageNum} currentBrewRendererPageNum={currentBrewRendererPageNum} diff --git a/client/homebrew/pages/errorPage/errors/errorIndex.js b/client/homebrew/pages/errorPage/errors/errorIndex.js index c0220b648..89abd570f 100644 --- a/client/homebrew/pages/errorPage/errors/errorIndex.js +++ b/client/homebrew/pages/errorPage/errors/errorIndex.js @@ -196,6 +196,12 @@ const errorIndex = (props)=>{ **Brew ID:** ${props.brew.brewId}`, + // Database Connection Lost + '13' : dedent` + ## Database connection has been lost. + + The server could not communicate with the database.`, + //account page when account is not defined '50' : dedent` ## You are not signed in diff --git a/client/homebrew/pages/homePage/homePage.jsx b/client/homebrew/pages/homePage/homePage.jsx index 84967b1ff..82c5b7084 100644 --- a/client/homebrew/pages/homePage/homePage.jsx +++ b/client/homebrew/pages/homePage/homePage.jsx @@ -1,51 +1,84 @@ +/* eslint-disable max-lines */ import './homePage.less'; -import React from 'react'; -import { useEffect, useState, useRef } from 'react'; -import request from '../../utils/request-middleware.js'; -import { Meta } from 'vitreum/headtags'; +// Common imports +import React, { useState, useEffect, useRef } from 'react'; +import request from '../../utils/request-middleware.js'; +import Markdown from 'naturalcrit/markdown.js'; +import _ from 'lodash'; -import Nav from 'naturalcrit/nav/nav.jsx'; -import Navbar from '../../navbar/navbar.jsx'; -import NewBrewItem from '../../navbar/newbrew.navitem.jsx'; -import HelpNavItem from '../../navbar/help.navitem.jsx'; -import VaultNavItem from '../../navbar/vault.navitem.jsx'; -import { both as RecentNavItem } from '../../navbar/recent.navitem.jsx'; -import AccountNavItem from '../../navbar/account.navitem.jsx'; -import ErrorNavItem from '../../navbar/error-navitem.jsx'; -import { fetchThemeBundle } from '../../../../shared/helpers.js'; +import { DEFAULT_BREW } from '../../../../server/brewDefaults.js'; +import { printCurrentBrew, fetchThemeBundle, splitTextStyleAndMetadata } from '../../../../shared/helpers.js'; -import SplitPane from 'client/components/splitPane/splitPane.jsx'; -import Editor from '../../editor/editor.jsx'; -import BrewRenderer from '../../brewRenderer/brewRenderer.jsx'; +import SplitPane from 'client/components/splitPane/splitPane.jsx'; +import Editor from '../../editor/editor.jsx'; +import BrewRenderer from '../../brewRenderer/brewRenderer.jsx'; -import { DEFAULT_BREW } from '../../../../server/brewDefaults.js'; +import Nav from 'naturalcrit/nav/nav.jsx'; +import Navbar from '../../navbar/navbar.jsx'; +import NewBrewItem from '../../navbar/newbrew.navitem.jsx'; +import AccountNavItem from '../../navbar/account.navitem.jsx'; +import ErrorNavItem from '../../navbar/error-navitem.jsx'; +import HelpNavItem from '../../navbar/help.navitem.jsx'; +import VaultNavItem from '../../navbar/vault.navitem.jsx'; +import PrintNavItem from '../../navbar/print.navitem.jsx'; +import { both as RecentNavItem } from '../../navbar/recent.navitem.jsx'; + +// Page specific imports +import { Meta } from 'vitreum/headtags'; + +const BREWKEY = 'homebrewery-new'; +const STYLEKEY = 'homebrewery-new-style'; +const SNIPKEY = 'homebrewery-new-snippets'; +const METAKEY = 'homebrewery-new-meta'; + +const useLocalStorage = false; +const neverSaved = true; const HomePage =(props)=>{ props = { brew : DEFAULT_BREW, ver : '0.0.0', - ...props - }; + ...props + }; - const [brew , setBrew] = useState(props.brew); - const [welcomeText , setWelcomeText] = useState(props.brew.text); + const [currentBrew , setCurrentBrew] = useState(props.brew); const [error , setError] = useState(undefined); + const [HTMLErrors , setHTMLErrors] = useState(Markdown.validate(props.brew.text)); const [currentEditorViewPageNum , setCurrentEditorViewPageNum] = useState(1); const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1); const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1); const [themeBundle , setThemeBundle] = useState({}); + const [unsavedChanges , setUnsavedChanges] = useState(false); const [isSaving , setIsSaving] = useState(false); + const [autoSaveEnabled , setAutoSaveEnable] = useState(false); - const editorRef = useRef(null); + const editorRef = useRef(null); + const lastSavedBrew = useRef(_.cloneDeep(props.brew)); useEffect(()=>{ - fetchThemeBundle(setError, setThemeBundle, brew.renderer, brew.theme); + fetchThemeBundle(setError, setThemeBundle, currentBrew.renderer, currentBrew.theme); + + const handleControlKeys = (e)=>{ + if(!(e.ctrlKey || e.metaKey)) return; + if(e.keyCode === 83) trySaveRef.current(true); + if(e.keyCode === 80) printCurrentBrew(); + if([83, 80].includes(e.keyCode)) { + e.stopPropagation(); + e.preventDefault(); + } + }; + + document.addEventListener('keydown', handleControlKeys); + + return ()=>{ + document.removeEventListener('keydown', handleControlKeys); + }; }, []); const save = ()=>{ request.post('/api') - .send(brew) + .send(currentBrew) .end((err, res)=>{ if(err) { setError(err); @@ -56,24 +89,73 @@ const HomePage =(props)=>{ }); }; + useEffect(()=>{ + const hasChange = !_.isEqual(currentBrew, lastSavedBrew.current); + setUnsavedChanges(hasChange); + + if(autoSaveEnabled) trySave(false, hasChange); + }, [currentBrew]); + const handleSplitMove = ()=>{ editorRef.current.update(); }; - const handleEditorViewPageChange = (pageNumber)=>{ - setCurrentEditorViewPageNum(pageNumber); - }; - - const handleEditorCursorPageChange = (pageNumber)=>{ - setCurrentEditorCursorPageNum(pageNumber); - }; - - const handleBrewRendererPageChange = (pageNumber)=>{ - setCurrentBrewRendererPageNum(pageNumber); + const handleBrewChange = (field)=>(value, subfield)=>{ //'text', 'style', 'snippets', 'metadata' + if(subfield == 'renderer' || subfield == 'theme') + fetchThemeBundle(setError, setThemeBundle, value.renderer, value.theme); + + //If there are HTML errors, run the validator on every change to give quick feedback + if(HTMLErrors.length && (field == 'text' || field == 'snippets')) + setHTMLErrors(Markdown.validate(value)); + + if(field == 'metadata') setCurrentBrew((prev)=>({ ...prev, ...value })); + else setCurrentBrew((prev)=>({ ...prev, [field]: value })); + + if(useLocalStorage) { + if(field == 'text') localStorage.setItem(BREWKEY, value); + if(field == 'style') localStorage.setItem(STYLEKEY, value); + if(field == 'snippets') localStorage.setItem(SNIPKEY, value); + if(field == 'metadata') localStorage.setItem(METAKEY, JSON.stringify({ + renderer : value.renderer, + theme : value.theme, + lang : value.lang + })); + } }; - const handleTextChange = (text)=>{ - setBrew((prevBrew) => ({ ...prevBrew, text })); + const renderSaveButton = ()=>{ + // #1 - Currently saving, show SAVING + if(isSaving) + return saving...; + + // #2 - Unsaved changes exist, autosave is OFF and warning timer has expired, show AUTOSAVE WARNING + // if(unsavedChanges && warnUnsavedChanges) { + // resetWarnUnsavedTimer(); + // const elapsedTime = Math.round((new Date() - lastSavedTime) / 1000 / 60); + // const text = elapsedTime === 0 + // ? 'Autosave is OFF.' + // : `Autosave is OFF, and you haven't saved for ${elapsedTime} minutes.`; + + // return + // Reminder... + //
{text}
+ //
; + // } + + // #3 - Unsaved changes exist, click to save, show SAVE NOW + if(unsavedChanges) + return save now; + + // #4 - No unsaved changes, autosave is ON, show AUTO-SAVED + if(autoSaveEnabled) + return auto-saved; + + // #5 - No unsaved changes, and has never been saved, hide the button + if(neverSaved) + return save now; + + // DEFAULT - No unsaved changes, show SAVED + return saved; }; const clearError = ()=>{ @@ -84,11 +166,11 @@ const HomePage =(props)=>{ const renderNavbar = ()=>{ return - {error ? - : - null - } + {error + ? + : renderSaveButton()} + @@ -105,22 +187,22 @@ const HomePage =(props)=>{ { />
-
+
Save current
@@ -136,7 +218,7 @@ const HomePage =(props)=>{ Create your own
- ) + ); }; module.exports = HomePage; diff --git a/client/homebrew/pages/homePage/homePage.less b/client/homebrew/pages/homePage/homePage.less index 4cf9ff4fe..c3ec1815c 100644 --- a/client/homebrew/pages/homePage/homePage.less +++ b/client/homebrew/pages/homePage/homePage.less @@ -34,7 +34,13 @@ } .navItem.save { + .fadeInRight(); + .transition(opacity); background-color : @orange; &:hover { background-color : @green; } + &.neverSaved { + .fadeOutRight(); + opacity: 0; + } } } diff --git a/client/homebrew/pages/newPage/newPage.jsx b/client/homebrew/pages/newPage/newPage.jsx index bb21441cf..dccf7deb7 100644 --- a/client/homebrew/pages/newPage/newPage.jsx +++ b/client/homebrew/pages/newPage/newPage.jsx @@ -1,33 +1,44 @@ -/*eslint max-lines: ["warn", {"max": 300, "skipBlankLines": true, "skipComments": true}]*/ +/* eslint-disable max-lines */ import './newPage.less'; +// Common imports import React, { useState, useEffect, useRef } from 'react'; import request from '../../utils/request-middleware.js'; import Markdown from 'naturalcrit/markdown.js'; +import _ from 'lodash'; -import Nav from 'naturalcrit/nav/nav.jsx'; -import Navbar from '../../navbar/navbar.jsx'; -import AccountNavItem from '../../navbar/account.navitem.jsx'; -import ErrorNavItem from '../../navbar/error-navitem.jsx'; -import HelpNavItem from '../../navbar/help.navitem.jsx'; -import PrintNavItem from '../../navbar/print.navitem.jsx'; -import { both as RecentNavItem } from '../../navbar/recent.navitem.jsx'; +import { DEFAULT_BREW } from '../../../../server/brewDefaults.js'; +import { printCurrentBrew, fetchThemeBundle, splitTextStyleAndMetadata } from '../../../../shared/helpers.js'; import SplitPane from 'client/components/splitPane/splitPane.jsx'; import Editor from '../../editor/editor.jsx'; import BrewRenderer from '../../brewRenderer/brewRenderer.jsx'; -import { DEFAULT_BREW } from '../../../../server/brewDefaults.js'; -import { printCurrentBrew, fetchThemeBundle, splitTextStyleAndMetadata } from '../../../../shared/helpers.js'; +import Nav from 'naturalcrit/nav/nav.jsx'; +import Navbar from '../../navbar/navbar.jsx'; +import NewBrewItem from '../../navbar/newbrew.navitem.jsx'; +import AccountNavItem from '../../navbar/account.navitem.jsx'; +import ErrorNavItem from '../../navbar/error-navitem.jsx'; +import HelpNavItem from '../../navbar/help.navitem.jsx'; +import VaultNavItem from '../../navbar/vault.navitem.jsx'; +import PrintNavItem from '../../navbar/print.navitem.jsx'; +import { both as RecentNavItem } from '../../navbar/recent.navitem.jsx'; -const BREWKEY = 'homebrewery-new'; -const STYLEKEY = 'homebrewery-new-style'; -const METAKEY = 'homebrewery-new-meta'; -const SAVEKEY = `HOMEBREWERY-DEFAULT-SAVE-LOCATION-${global.account?.username || ''}`; +// Page specific imports +import { Meta } from 'vitreum/headtags'; -const NewPage = (props) => { +const BREWKEY = 'HB_newPage_content'; +const STYLEKEY = 'HB_newPage_style'; +const METAKEY = 'HB_newPage_metadata'; +const SNIPKEY = 'HB_newPage_snippets'; +const SAVEKEYPREFIX = 'HB_editor_defaultSave_'; + +const useLocalStorage = true; +const neverSaved = true; + +const NewPage = (props)=>{ props = { - brew: DEFAULT_BREW, + brew : DEFAULT_BREW, ...props }; @@ -40,15 +51,29 @@ const NewPage = (props) => { const [currentEditorCursorPageNum, setCurrentEditorCursorPageNum] = useState(1); const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1); const [themeBundle , setThemeBundle ] = useState({}); + const [unsavedChanges , setUnsavedChanges ] = useState(false); + const [autoSaveEnabled , setAutoSaveEnabled ] = useState(false); - const editorRef = useRef(null); + const editorRef = useRef(null); + const lastSavedBrew = useRef(_.cloneDeep(props.brew)); - useEffect(() => { - document.addEventListener('keydown', handleControlKeys); + useEffect(()=>{ loadBrew(); fetchThemeBundle(setError, setThemeBundle, currentBrew.renderer, currentBrew.theme); - return () => { + const handleControlKeys = (e)=>{ + if(!(e.ctrlKey || e.metaKey)) return; + if(e.keyCode === 83) trySaveRef.current(true); + if(e.keyCode === 80) printCurrentBrew(); + if([83, 80].includes(e.keyCode)) { + e.stopPropagation(); + e.preventDefault(); + } + }; + + document.addEventListener('keydown', handleControlKeys); + + return ()=>{ document.removeEventListener('keydown', handleControlKeys); }; }, []); @@ -67,9 +92,11 @@ const NewPage = (props) => { brew.lang = metaStorage?.lang ?? brew.lang; } + const SAVEKEY = `${SAVEKEYPREFIX}${global.account?.username}`; const saveStorage = localStorage.getItem(SAVEKEY) || 'HOMEBREWERY'; setCurrentBrew(brew); + lastSavedBrew.current = brew; setSaveGoogle(saveStorage == 'GOOGLE-DRIVE' && saveGoogle); localStorage.setItem(BREWKEY, brew.text); @@ -80,74 +107,44 @@ const NewPage = (props) => { window.history.replaceState({}, window.location.title, '/new/'); }; - const handleControlKeys = (e) => { - if (!(e.ctrlKey || e.metaKey)) return; - const S_KEY = 83; - const P_KEY = 80; - if (e.keyCode === S_KEY) save(); - if (e.keyCode === P_KEY) printCurrentBrew(); - if (e.keyCode === S_KEY || e.keyCode === P_KEY) { - e.preventDefault(); - e.stopPropagation(); - } - }; + useEffect(()=>{ + const hasChange = !_.isEqual(currentBrew, lastSavedBrew.current); + setUnsavedChanges(hasChange); + + if(autoSaveEnabled) trySave(false, hasChange); + }, [currentBrew]); const handleSplitMove = ()=>{ editorRef.current.update(); }; - const handleEditorViewPageChange = (pageNumber)=>{ - setCurrentEditorViewPageNum(pageNumber); - }; - - const handleEditorCursorPageChange = (pageNumber)=>{ - setCurrentEditorCursorPageNum(pageNumber); - }; - - const handleBrewRendererPageChange = (pageNumber)=>{ - setCurrentBrewRendererPageNum(pageNumber); - }; + const handleBrewChange = (field)=>(value, subfield)=>{ //'text', 'style', 'snippets', 'metadata' + if(subfield == 'renderer' || subfield == 'theme') + fetchThemeBundle(setError, setThemeBundle, value.renderer, value.theme); - const handleTextChange = (text)=>{ //If there are HTML errors, run the validator on every change to give quick feedback - if(HTMLErrors.length) - HTMLErrors = Markdown.validate(text); + if(HTMLErrors.length && (field == 'text' || field == 'snippets')) + setHTMLErrors(Markdown.validate(value)); - setHTMLErrors(HTMLErrors); - setCurrentBrew((prevBrew) => ({ ...prevBrew, text })); - localStorage.setItem(BREWKEY, text); + if(field == 'metadata') setCurrentBrew((prev)=>({ ...prev, ...value })); + else setCurrentBrew((prev)=>({ ...prev, [field]: value })); + + if(useLocalStorage) { + if(field == 'text') localStorage.setItem(BREWKEY, value); + if(field == 'style') localStorage.setItem(STYLEKEY, value); + if(field == 'snippets') localStorage.setItem(SNIPKEY, value); + if(field == 'metadata') localStorage.setItem(METAKEY, JSON.stringify({ + renderer : value.renderer, + theme : value.theme, + lang : value.lang + })); + } }; - const handleStyleChange = (style) => { - setCurrentBrew(prevBrew => ({ ...prevBrew, style })); - localStorage.setItem(STYLEKEY, style); - }; - - const handleSnipChange = (snippet)=>{ - //If there are HTML errors, run the validator on every change to give quick feedback - if(HTMLErrors.length) - HTMLErrors = Markdown.validate(snippet); - - setHTMLErrors(HTMLErrors); - setCurrentBrew((prevBrew) => ({ ...prevBrew, snippets: snippet })); - }; - - const handleMetaChange = (metadata, field = undefined) => { - if (field === 'theme' || field === 'renderer') - fetchThemeBundle(setError, setThemeBundle, metadata.renderer, metadata.theme); - - setCurrentBrew(prev => ({ ...prev, ...metadata })); - localStorage.setItem(METAKEY, JSON.stringify({ - renderer : metadata.renderer, - theme : metadata.theme, - lang : metadata.lang - })); - }; - - const save = async () => { + const save = async ()=>{ setIsSaving(true); - let updatedBrew = { ...currentBrew }; + const updatedBrew = { ...currentBrew }; splitTextStyleAndMetadata(updatedBrew); const pageRegex = updatedBrew.renderer === 'legacy' ? /\\page/g : /^\\page$/gm; @@ -156,13 +153,13 @@ const NewPage = (props) => { const res = await request .post(`/api${saveGoogle ? '?saveToGoogle=true' : ''}`) .send(updatedBrew) - .catch((err) => { + .catch((err)=>{ setIsSaving(false); setError(err); }); - setIsSaving(false) - if (!res) return; + setIsSaving(false); + if(!res) return; const savedBrew = res.body; @@ -173,15 +170,38 @@ const NewPage = (props) => { }; const renderSaveButton = ()=>{ - if(isSaving){ - return - save... - ; - } else { - return - save - ; - } + // #1 - Currently saving, show SAVING + if(isSaving) + return saving...; + + // #2 - Unsaved changes exist, autosave is OFF and warning timer has expired, show AUTOSAVE WARNING + // if(unsavedChanges && warnUnsavedChanges) { + // resetWarnUnsavedTimer(); + // const elapsedTime = Math.round((new Date() - lastSavedTime) / 1000 / 60); + // const text = elapsedTime === 0 + // ? 'Autosave is OFF.' + // : `Autosave is OFF, and you haven't saved for ${elapsedTime} minutes.`; + + // return + // Reminder... + //
{text}
+ //
; + // } + + // #3 - Unsaved changes exist, click to save, show SAVE NOW + if(unsavedChanges) + return save now; + + // #4 - No unsaved changes, autosave is ON, show AUTO-SAVED + if(autoSaveEnabled) + return auto-saved; + + // #5 - No unsaved changes, and has never been saved, hide the button + if(neverSaved) + return save now; + + // DEFAULT - No unsaved changes, show SAVED + return saved; }; const clearError = ()=>{ @@ -189,7 +209,7 @@ const NewPage = (props) => { setIsSaving(false); }; - const renderNavbar = () => ( + const renderNavbar = ()=>( {currentBrew.title} @@ -199,8 +219,10 @@ const NewPage = (props) => { {error ? : renderSaveButton()} + + @@ -208,22 +230,19 @@ const NewPage = (props) => { ); return ( -
+
{renderNavbar()}
{ themeBundle={themeBundle} errors={HTMLErrors} lang={currentBrew.lang} - onPageChange={handleBrewRendererPageChange} + onPageChange={setCurrentBrewRendererPageNum} currentEditorViewPageNum={currentEditorViewPageNum} currentEditorCursorPageNum={currentEditorCursorPageNum} currentBrewRendererPageNum={currentBrewRendererPageNum} diff --git a/client/homebrew/pages/newPage/newPage.less b/client/homebrew/pages/newPage/newPage.less index ebc44d543..083e1ee09 100644 --- a/client/homebrew/pages/newPage/newPage.less +++ b/client/homebrew/pages/newPage/newPage.less @@ -1,6 +1,12 @@ .newPage { .navItem.save { + .fadeInRight(); + .transition(opacity); background-color : @orange; &:hover { background-color : @green; } + &.neverSaved { + .fadeOutRight(); + opacity: 0; + } } } diff --git a/client/homebrew/pages/vaultPage/vaultPage.less b/client/homebrew/pages/vaultPage/vaultPage.less index 8a5f3a714..304fefc72 100644 --- a/client/homebrew/pages/vaultPage/vaultPage.less +++ b/client/homebrew/pages/vaultPage/vaultPage.less @@ -1,14 +1,16 @@ .vaultPage { height : 100%; overflow-y : hidden; - background-color : #2C3E50; *:not(input) { user-select : none; } + .form { + background:white; + } + :where(.content .dataGroup) { width : 100%; height : 100%; - background : white; &.form .brewLookup { position : relative; @@ -171,7 +173,6 @@ max-height : 100%; padding : 70px 50px; overflow-y : scroll; - background-color : #2C3E50; container-type : inline-size; h3 { font-size : 25px; } diff --git a/client/homebrew/utils/updateLocalStorage/localStorageKeyMap.js b/client/homebrew/utils/updateLocalStorage/localStorageKeyMap.js new file mode 100644 index 000000000..f6e01c54f --- /dev/null +++ b/client/homebrew/utils/updateLocalStorage/localStorageKeyMap.js @@ -0,0 +1,35 @@ + +const getLocalStorageMap = function(){ + const localStorageMap = { + 'AUTOSAVE_ON' : 'HB_editor_autoSaveOn', + 'HOMEBREWERY-EDITOR-THEME' : 'HB_editor_theme', + 'liveScroll' : 'HB_editor_liveScroll', + 'naturalcrit-pane-split' : 'HB_editor_splitWidth', + + 'HOMEBREWERY-LISTPAGE-SORTDIR' : 'HB_listPage_sortDir', + 'HOMEBREWERY-LISTPAGE-SORTTYPE' : 'HB_listPage_sortType', + 'HOMEBREWERY-LISTPAGE-VISIBILITY-published' : 'HB_listPage_visibility_group_published', + 'HOMEBREWERY-LISTPAGE-VISIBILITY-unpublished' : 'HB_listPage_visibility_group_unpublished', + + 'hbAdminTab' : 'HB_adminPage_currentTab', + + 'homebrewery-new' : 'HB_newPage_content', + 'homebrewery-new-meta' : 'HB_newPage_metadata', + 'homebrewery-new-style' : 'HB_newPage_style', + + 'homebrewery-recently-edited' : 'HB_nav_recentlyEdited', + 'homebrewery-recently-viewed' : 'HB_nav_recentlyViewed', + + 'hb_toolbarState' : 'HB_renderer_toolbarState', + 'hb_toolbarVisibility' : 'HB_renderer_toolbarVisibility' + }; + + if(global?.account?.username){ + const username = global.account.username; + localStorageMap[`HOMEBREWERY-DEFAULT-SAVE-LOCATION-${username}`] = `HB_editor_defaultSave_${username}`; + } + + return localStorageMap; +}; + +export default getLocalStorageMap; \ No newline at end of file diff --git a/client/homebrew/utils/updateLocalStorage/updateLocalStorageKeys.js b/client/homebrew/utils/updateLocalStorage/updateLocalStorageKeys.js new file mode 100644 index 000000000..912c4297b --- /dev/null +++ b/client/homebrew/utils/updateLocalStorage/updateLocalStorageKeys.js @@ -0,0 +1,22 @@ +import getLocalStorageMap from './localStorageKeyMap.js'; + +const updateLocalStorage = function(){ + // Return if no window and thus no local storage + if(typeof window === 'undefined') return; + + const localStorageKeyMap = getLocalStorageMap(); + const storage = window.localStorage; + + Object.keys(localStorageKeyMap).forEach((key)=>{ + if(storage[key]){ + if(!storage[localStorageKeyMap[key]]){ + const data = storage.getItem(key); + storage.setItem(localStorageKeyMap[key], data); + }; + storage.removeItem(key); + } + }); + +}; + +export { updateLocalStorage }; \ No newline at end of file diff --git a/config/default.json b/config/default.json index bea3b2663..6be4ce7ce 100644 --- a/config/default.json +++ b/config/default.json @@ -1,10 +1,9 @@ { + "development": true, "host" : "homebrewery.local.naturalcrit.com:8000", "naturalcrit_url" : "local.naturalcrit.com:8010", "secret" : "secret", "web_port" : 8000, - "enable_v3" : true, - "enable_themes" : true, "local_environments" : ["docker", "local"], "publicUrl" : "https://homebrewery.naturalcrit.com", "hb_images" : null, diff --git a/package-lock.json b/package-lock.json index fb815cdc6..4181aefa4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,19 +10,19 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.27.1", - "@babel/plugin-transform-runtime": "^7.28.0", - "@babel/preset-env": "^7.28.0", + "@babel/core": "^7.28.4", + "@babel/plugin-transform-runtime": "^7.28.3", + "@babel/preset-env": "^7.28.3", "@babel/preset-react": "^7.27.1", - "@babel/runtime": "^7.27.6", + "@babel/runtime": "^7.28.4", "@dmsnell/diff-match-patch": "^1.1.0", - "@googleapis/drive": "^13.0.1", + "@googleapis/drive": "^18.0.0", "@sanity/diff-match-patch": "^3.2.0", "body-parser": "^2.2.0", "classnames": "^2.5.1", "codemirror": "^5.65.6", "cookie-parser": "^1.4.7", - "core-js": "^3.44.0", + "core-js": "^3.46.0", "cors": "^2.8.5", "create-react-class": "^15.7.0", "dedent-tabs": "^0.10.3", @@ -31,7 +31,7 @@ "express-async-handler": "^1.2.0", "express-static-gzip": "3.0.0", "fflate": "^0.8.2", - "fs-extra": "11.3.0", + "fs-extra": "11.3.2", "hash-wasm": "^4.12.0", "idb-keyval": "^6.2.2", "js-yaml": "^4.1.0", @@ -46,16 +46,16 @@ "marked-gfm-heading-id": "^4.1.2", "marked-nonbreaking-spaces": "^1.0.1", "marked-smartypants-lite": "^1.0.3", - "marked-subsuper-text": "^1.0.3", + "marked-subsuper-text": "^1.0.4", "markedLegacy": "npm:marked@^0.3.19", "moment": "^2.30.1", - "mongoose": "^8.16.3", - "nanoid": "5.1.5", + "mongoose": "^8.19.1", + "nanoid": "5.1.6", "nconf": "^0.13.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-frame-component": "^4.1.3", - "react-router": "^7.6.3", + "react-router": "^7.9.4", "romans": "^3.1.0", "sanitize-filename": "1.6.3", "superagent": "^10.2.1", @@ -65,15 +65,15 @@ "devDependencies": { "@stylistic/stylelint-plugin": "^4.0.0", "babel-plugin-transform-import-meta": "^2.3.3", - "eslint": "^9.35.0", + "eslint": "^9.37.0", "eslint-plugin-jest": "^29.0.1", "eslint-plugin-react": "^7.37.5", - "globals": "^16.3.0", - "jest": "^30.1.3", + "globals": "^16.4.0", + "jest": "^30.2.0", "jest-expect-message": "^1.1.3", "jsdom-global": "^3.0.2", "postcss-less": "^6.0.0", - "stylelint": "^16.24.0", + "stylelint": "^16.25.0", "stylelint-config-recess-order": "^7.3.0", "stylelint-config-recommended": "^17.0.0", "supertest": "^7.1.4" @@ -83,19 +83,6 @@ "npm": "^10.8.x" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -119,20 +106,21 @@ } }, "node_modules/@babel/core": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", - "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", + "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.0", + "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.6", - "@babel/parser": "^7.28.0", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.0", - "@babel/types": "^7.28.0", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -148,12 +136,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", - "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.0", - "@babel/types": "^7.28.0", + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -190,17 +179,17 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", - "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", + "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.27.1", + "@babel/traverse": "^7.28.3", "semver": "^6.3.1" }, "engines": { @@ -277,13 +266,14 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", - "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.3" + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -402,23 +392,25 @@ } }, "node_modules/@babel/helpers": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", - "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "license": "MIT", "dependencies": { "@babel/template": "^7.27.2", - "@babel/types": "^7.27.6" + "@babel/types": "^7.28.4" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", - "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.28.0" + "@babel/types": "^7.28.4" }, "bin": { "parser": "bin/babel-parser.js" @@ -491,13 +483,13 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.27.1.tgz", - "integrity": "sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", + "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -880,12 +872,12 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.27.1.tgz", - "integrity": "sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", + "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.28.3", "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { @@ -896,16 +888,17 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.0.tgz", - "integrity": "sha512-IjM1IoJNw72AZFlj33Cu8X0q2XK/6AaVC3jQu+cgQ5lThWD5ajnuUAml80dqRmOhmPkTH8uAwnpMu9Rvj0LTRA==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", + "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", + "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-globals": "^7.28.0", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", - "@babel/traverse": "^7.28.0" + "@babel/traverse": "^7.28.4" }, "engines": { "node": ">=6.9.0" @@ -1465,9 +1458,10 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.0.tgz", - "integrity": "sha512-LOAozRVbqxEVjSKfhGnuLoE4Kz4Oc5UJzuvFUhSsQzdCdaAQu06mG8zDv2GFSerM62nImUZ7K92vxnQcLSDlCQ==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz", + "integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1510,9 +1504,10 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.0.tgz", - "integrity": "sha512-dGopk9nZrtCs2+nfIem25UuHyt5moSJamArzIoh9/vezUQPmYDOzjaHDCkAzuGJibCIkPup8rMT2+wYB6S73cA==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.3.tgz", + "integrity": "sha512-Y6ab1kGqZ0u42Zv/4a7l0l72n9DKP/MKoKWaUSBylrhNZO2prYuqFOLbn5aW5SIFXwSH93yfjbgllL8lxuGKLg==", + "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", @@ -1668,9 +1663,10 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.0.tgz", - "integrity": "sha512-VmaxeGOwuDqzLl5JUkIRM1X2Qu2uKGxHEQWh+cvvbl7JuJRgKGJSfsEF/bUaxFhJl/XAyxBe7q7qSuTbKFuCyg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.3.tgz", + "integrity": "sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==", + "license": "MIT", "dependencies": { "@babel/compat-data": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", @@ -1680,7 +1676,7 @@ "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-import-assertions": "^7.27.1", "@babel/plugin-syntax-import-attributes": "^7.27.1", @@ -1691,8 +1687,8 @@ "@babel/plugin-transform-block-scoped-functions": "^7.27.1", "@babel/plugin-transform-block-scoping": "^7.28.0", "@babel/plugin-transform-class-properties": "^7.27.1", - "@babel/plugin-transform-class-static-block": "^7.27.1", - "@babel/plugin-transform-classes": "^7.28.0", + "@babel/plugin-transform-class-static-block": "^7.28.3", + "@babel/plugin-transform-classes": "^7.28.3", "@babel/plugin-transform-computed-properties": "^7.27.1", "@babel/plugin-transform-destructuring": "^7.28.0", "@babel/plugin-transform-dotall-regex": "^7.27.1", @@ -1724,7 +1720,7 @@ "@babel/plugin-transform-private-methods": "^7.27.1", "@babel/plugin-transform-private-property-in-object": "^7.27.1", "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.28.0", + "@babel/plugin-transform-regenerator": "^7.28.3", "@babel/plugin-transform-regexp-modifiers": "^7.27.1", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", @@ -1785,9 +1781,10 @@ } }, "node_modules/@babel/runtime": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", - "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -1807,16 +1804,17 @@ } }, "node_modules/@babel/traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", - "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", + "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.0", + "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.0", + "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", - "@babel/types": "^7.28.0", + "@babel/types": "^7.28.4", "debug": "^4.3.1" }, "engines": { @@ -1824,9 +1822,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", - "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -1915,14 +1913,14 @@ "license": "Apache-2.0" }, "node_modules/@dual-bundle/import-meta-resolve": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", - "integrity": "sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.2.1.tgz", + "integrity": "sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==", "dev": true, "license": "MIT", "funding": { "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://github.com/sponsors/JounQin" } }, "node_modules/@emnapi/core": { @@ -2026,19 +2024,22 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", - "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.0.tgz", + "integrity": "sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.16.0" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", - "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz", + "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2086,9 +2087,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.35.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.35.0.tgz", - "integrity": "sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw==", + "version": "9.37.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.37.0.tgz", + "integrity": "sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==", "dev": true, "license": "MIT", "engines": { @@ -2108,13 +2109,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", - "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz", + "integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.15.2", + "@eslint/core": "^0.16.0", "levn": "^0.4.1" }, "engines": { @@ -2122,11 +2123,12 @@ } }, "node_modules/@googleapis/drive": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/@googleapis/drive/-/drive-13.0.1.tgz", - "integrity": "sha512-n6smJQyKTllRbXI8Xe/9IsCI+tuY20bhs9lircO+t2+a4k2t08NCZuujsBgIBTiE29v2kDzrrKdsuPxg5lUmXw==", + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/@googleapis/drive/-/drive-18.0.0.tgz", + "integrity": "sha512-nk4TirsHttwZSOjBEBjltCPDKUqwFso59G3WitNE+EGNVSVseSEq981f8Dmjq2ah0/fk3i206wuCU4PUCwcoTQ==", + "license": "Apache-2.0", "dependencies": { - "googleapis-common": "^8.0.2-rc.0" + "googleapis-common": "^8.0.0" }, "engines": { "node": ">=12.0.0" @@ -2214,9 +2216,9 @@ } }, "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", - "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", "engines": { @@ -2227,9 +2229,9 @@ } }, "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", "engines": { @@ -2265,9 +2267,9 @@ } }, "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, "license": "MIT", "dependencies": { @@ -2416,17 +2418,17 @@ } }, "node_modules/@jest/console": { - "version": "30.1.2", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.1.2.tgz", - "integrity": "sha512-BGMAxj8VRmoD0MoA/jo9alMXSRoqW8KPeqOfEo1ncxnRLatTBCpRoOwlwlEMdudp68Q6WSGwYrrLtTGOh8fLzw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz", + "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", - "jest-message-util": "30.1.0", - "jest-util": "30.0.5", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", "slash": "^3.0.0" }, "engines": { @@ -2434,39 +2436,39 @@ } }, "node_modules/@jest/core": { - "version": "30.1.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.1.3.tgz", - "integrity": "sha512-LIQz7NEDDO1+eyOA2ZmkiAyYvZuo6s1UxD/e2IHldR6D7UYogVq3arTmli07MkENLq6/3JEQjp0mA8rrHHJ8KQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.2.0.tgz", + "integrity": "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.1.2", + "@jest/console": "30.2.0", "@jest/pattern": "30.0.1", - "@jest/reporters": "30.1.3", - "@jest/test-result": "30.1.3", - "@jest/transform": "30.1.2", - "@jest/types": "30.0.5", + "@jest/reporters": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "ci-info": "^4.2.0", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", - "jest-changed-files": "30.0.5", - "jest-config": "30.1.3", - "jest-haste-map": "30.1.0", - "jest-message-util": "30.1.0", + "jest-changed-files": "30.2.0", + "jest-config": "30.2.0", + "jest-haste-map": "30.2.0", + "jest-message-util": "30.2.0", "jest-regex-util": "30.0.1", - "jest-resolve": "30.1.3", - "jest-resolve-dependencies": "30.1.3", - "jest-runner": "30.1.3", - "jest-runtime": "30.1.3", - "jest-snapshot": "30.1.2", - "jest-util": "30.0.5", - "jest-validate": "30.1.0", - "jest-watcher": "30.1.3", + "jest-resolve": "30.2.0", + "jest-resolve-dependencies": "30.2.0", + "jest-runner": "30.2.0", + "jest-runtime": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "jest-watcher": "30.2.0", "micromatch": "^4.0.8", - "pretty-format": "30.0.5", + "pretty-format": "30.2.0", "slash": "^3.0.0" }, "engines": { @@ -2492,39 +2494,39 @@ } }, "node_modules/@jest/environment": { - "version": "30.1.2", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.1.2.tgz", - "integrity": "sha512-N8t1Ytw4/mr9uN28OnVf0SYE2dGhaIxOVYcwsf9IInBKjvofAjbFRvedvBBlyTYk2knbJTiEjEJ2PyyDIBnd9w==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz", + "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/fake-timers": "30.1.2", - "@jest/types": "30.0.5", + "@jest/fake-timers": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", - "jest-mock": "30.0.5" + "jest-mock": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect": { - "version": "30.1.2", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.1.2.tgz", - "integrity": "sha512-tyaIExOwQRCxPCGNC05lIjWJztDwk2gPDNSDGg1zitXJJ8dC3++G/CRjE5mb2wQsf89+lsgAgqxxNpDLiCViTA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==", "dev": true, "license": "MIT", "dependencies": { - "expect": "30.1.2", - "jest-snapshot": "30.1.2" + "expect": "30.2.0", + "jest-snapshot": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "30.1.2", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.1.2.tgz", - "integrity": "sha512-HXy1qT/bfdjCv7iC336ExbqqYtZvljrV8odNdso7dWK9bSeHtLlvwWWC3YSybSPL03Gg5rug6WLCZAZFH72m0A==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", + "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", "dev": true, "license": "MIT", "dependencies": { @@ -2535,18 +2537,18 @@ } }, "node_modules/@jest/fake-timers": { - "version": "30.1.2", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.1.2.tgz", - "integrity": "sha512-Beljfv9AYkr9K+ETX9tvV61rJTY706BhBUtiaepQHeEGfe0DbpvUA5Z3fomwc5Xkhns6NWrcFDZn+72fLieUnA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz", + "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "@sinonjs/fake-timers": "^13.0.0", "@types/node": "*", - "jest-message-util": "30.1.0", - "jest-mock": "30.0.5", - "jest-util": "30.0.5" + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -2563,16 +2565,16 @@ } }, "node_modules/@jest/globals": { - "version": "30.1.2", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.1.2.tgz", - "integrity": "sha512-teNTPZ8yZe3ahbYnvnVRDeOjr+3pu2uiAtNtrEsiMjVPPj+cXd5E/fr8BL7v/T7F31vYdEHrI5cC/2OoO/vM9A==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz", + "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.1.2", - "@jest/expect": "30.1.2", - "@jest/types": "30.0.5", - "jest-mock": "30.0.5" + "@jest/environment": "30.2.0", + "@jest/expect": "30.2.0", + "@jest/types": "30.2.0", + "jest-mock": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -2593,17 +2595,17 @@ } }, "node_modules/@jest/reporters": { - "version": "30.1.3", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.1.3.tgz", - "integrity": "sha512-VWEQmJWfXMOrzdFEOyGjUEOuVXllgZsoPtEHZzfdNz18RmzJ5nlR6kp8hDdY8dDS1yGOXAY7DHT+AOHIPSBV0w==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz", + "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.1.2", - "@jest/test-result": "30.1.3", - "@jest/transform": "30.1.2", - "@jest/types": "30.0.5", + "@jest/console": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", "@jridgewell/trace-mapping": "^0.3.25", "@types/node": "*", "chalk": "^4.1.2", @@ -2616,9 +2618,9 @@ "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "30.1.0", - "jest-util": "30.0.5", - "jest-worker": "30.1.0", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "jest-worker": "30.2.0", "slash": "^3.0.0", "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" @@ -2696,13 +2698,13 @@ } }, "node_modules/@jest/snapshot-utils": { - "version": "30.1.2", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.1.2.tgz", - "integrity": "sha512-vHoMTpimcPSR7OxS2S0V1Cpg8eKDRxucHjoWl5u4RQcnxqQrV3avETiFpl8etn4dqxEGarBeHbIBety/f8mLXw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz", + "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "natural-compare": "^1.4.0" @@ -2727,14 +2729,14 @@ } }, "node_modules/@jest/test-result": { - "version": "30.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.1.3.tgz", - "integrity": "sha512-P9IV8T24D43cNRANPPokn7tZh0FAFnYS2HIfi5vK18CjRkTDR9Y3e1BoEcAJnl4ghZZF4Ecda4M/k41QkvurEQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz", + "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.1.2", - "@jest/types": "30.0.5", + "@jest/console": "30.2.0", + "@jest/types": "30.2.0", "@types/istanbul-lib-coverage": "^2.0.6", "collect-v8-coverage": "^1.0.2" }, @@ -2743,15 +2745,15 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "30.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.1.3.tgz", - "integrity": "sha512-82J+hzC0qeQIiiZDThh+YUadvshdBswi5nuyXlEmXzrhw5ZQSRHeQ5LpVMD/xc8B3wPePvs6VMzHnntxL+4E3w==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz", + "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.1.3", + "@jest/test-result": "30.2.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.1.0", + "jest-haste-map": "30.2.0", "slash": "^3.0.0" }, "engines": { @@ -2759,23 +2761,23 @@ } }, "node_modules/@jest/transform": { - "version": "30.1.2", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.1.2.tgz", - "integrity": "sha512-UYYFGifSgfjujf1Cbd3iU/IQoSd6uwsj8XHj5DSDf5ERDcWMdJOPTkHWXj4U+Z/uMagyOQZ6Vne8C4nRIrCxqA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz", + "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "@jridgewell/trace-mapping": "^0.3.25", - "babel-plugin-istanbul": "^7.0.0", + "babel-plugin-istanbul": "^7.0.1", "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.1.0", + "jest-haste-map": "30.2.0", "jest-regex-util": "30.0.1", - "jest-util": "30.0.5", + "jest-util": "30.2.0", "micromatch": "^4.0.8", "pirates": "^4.0.7", "slash": "^3.0.0", @@ -2786,9 +2788,9 @@ } }, "node_modules/@jest/types": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", - "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", + "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", "dev": true, "license": "MIT", "dependencies": { @@ -2813,6 +2815,16 @@ "@jridgewell/trace-mapping": "^0.3.24" } }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -2845,9 +2857,10 @@ "license": "MIT" }, "node_modules/@mongodb-js/saslprep": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.0.tgz", - "integrity": "sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.1.tgz", + "integrity": "sha512-6nZrq5kfAz0POWyhljnbWQQJQ5uT8oE2ddX303q1uY0tWsivWKgBDXBBvuFPwOqRRalXJuVO9EjOdVtuhLX0zg==", + "license": "MIT", "dependencies": { "sparse-bitfield": "^3.0.3" } @@ -3007,9 +3020,9 @@ } }, "node_modules/@tybys/wasm-util": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz", - "integrity": "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", "dev": true, "license": "MIT", "optional": true, @@ -3111,13 +3124,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", - "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", + "version": "24.7.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.0.tgz", + "integrity": "sha512-IbKooQVqUBrlzWTi79E8Fw78l8k1RNtlDDNWsFZs7XonuQSJ8oNYfEeclhprUldXISRMLzBpILuKgPlIxm+/Yw==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.10.0" + "undici-types": "~7.14.0" } }, "node_modules/@types/stack-utils": { @@ -3130,12 +3143,14 @@ "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": "*" } @@ -4045,16 +4060,16 @@ } }, "node_modules/babel-jest": { - "version": "30.1.2", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.1.2.tgz", - "integrity": "sha512-IQCus1rt9kaSh7PQxLYRY5NmkNrNlU2TpabzwV7T2jljnpdHOcmnYYv8QmE04Li4S3a2Lj8/yXyET5pBarPr6g==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", + "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/transform": "30.1.2", + "@jest/transform": "30.2.0", "@types/babel__core": "^7.20.5", - "babel-plugin-istanbul": "^7.0.0", - "babel-preset-jest": "30.0.1", + "babel-plugin-istanbul": "^7.0.1", + "babel-preset-jest": "30.2.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "slash": "^3.0.0" @@ -4063,15 +4078,18 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.11.0" + "@babel/core": "^7.11.0 || ^8.0.0-0" } }, "node_modules/babel-plugin-istanbul": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.0.tgz", - "integrity": "sha512-C5OzENSx/A+gt7t4VH1I2XsflxyPUmXRFPKBxt33xncdOmq7oROVM3bZv9Ysjjkv8OJYDMa+tKuKMvqU/H3xdw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", "dev": true, "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", @@ -4084,14 +4102,12 @@ } }, "node_modules/babel-plugin-jest-hoist": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.0.1.tgz", - "integrity": "sha512-zTPME3pI50NsFW8ZBaVIOeAxzEY7XHlmWeXXu9srI+9kNfzCUTy8MFan46xOGZY8NZThMqq+e3qZUKsvXbasnQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz", + "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", "@types/babel__core": "^7.20.5" }, "engines": { @@ -4182,20 +4198,20 @@ } }, "node_modules/babel-preset-jest": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.0.1.tgz", - "integrity": "sha512-+YHejD5iTWI46cZmcc/YtX4gaKBtdqCHCVfuVinizVpbmyjO3zYmeuyFdfA8duRqQZfgCAMlsfmkVbJ+e2MAJw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz", + "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==", "dev": true, "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "30.0.1", - "babel-preset-current-node-syntax": "^1.1.0" + "babel-plugin-jest-hoist": "30.2.0", + "babel-preset-current-node-syntax": "^1.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.11.0" + "@babel/core": "^7.11.0 || ^8.0.0-beta.1" } }, "node_modules/balanced-match": { @@ -4588,6 +4604,7 @@ "version": "6.10.4", "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", + "license": "Apache-2.0", "engines": { "node": ">=16.20.1" } @@ -4605,7 +4622,8 @@ "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" }, "node_modules/buffer-from": { "version": "1.1.2", @@ -4851,9 +4869,9 @@ } }, "node_modules/ci-info": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", - "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", "dev": true, "funding": [ { @@ -5145,9 +5163,9 @@ } }, "node_modules/core-js": { - "version": "3.44.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.44.0.tgz", - "integrity": "sha512-aFCtd4l6GvAXwVEh3XbbVqJGHDJt0OZRa+5ePGx3LLwi12WfexqQxcsohb2wgsa/92xtl19Hd66G/L+TaAxDMw==", + "version": "3.46.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.46.0.tgz", + "integrity": "sha512-vDMm9B0xnqqZ8uSBpZ8sNtRtOdmfShrvT6h2TuQGLs0Is+cR0DYbj/KWP6ALVNbWPpqA/qPLoOuppJN07humpA==", "hasInstallScript": true, "license": "MIT", "funding": { @@ -5363,6 +5381,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", "engines": { "node": ">= 12" } @@ -5477,9 +5496,10 @@ } }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -5510,9 +5530,9 @@ } }, "node_modules/dedent": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", - "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", + "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -5767,6 +5787,7 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" } @@ -6065,20 +6086,20 @@ } }, "node_modules/eslint": { - "version": "9.35.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.35.0.tgz", - "integrity": "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==", + "version": "9.37.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz", + "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", - "@eslint/config-helpers": "^0.3.1", - "@eslint/core": "^0.15.2", + "@eslint/config-helpers": "^0.4.0", + "@eslint/core": "^0.16.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.35.0", - "@eslint/plugin-kit": "^0.3.5", + "@eslint/js": "9.37.0", + "@eslint/plugin-kit": "^0.4.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -6451,18 +6472,18 @@ "license": "MIT" }, "node_modules/expect": { - "version": "30.1.2", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.1.2.tgz", - "integrity": "sha512-xvHszRavo28ejws8FpemjhwswGj4w/BetHIL8cU49u4sGyXDw2+p3YbeDbj6xzlxi6kWTjIRSTJ+9sNXPnF0Zg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/expect-utils": "30.1.2", + "@jest/expect-utils": "30.2.0", "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.1.2", - "jest-message-util": "30.1.0", - "jest-mock": "30.0.5", - "jest-util": "30.0.5" + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -6754,6 +6775,7 @@ "url": "https://paypal.me/jimmywarting" } ], + "license": "MIT", "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" @@ -6922,6 +6944,7 @@ "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", "dependencies": { "fetch-blob": "^3.1.2" }, @@ -6977,9 +7000,9 @@ } }, "node_modules/fs-extra": { - "version": "11.3.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", - "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", + "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", @@ -7051,9 +7074,10 @@ } }, "node_modules/gaxios": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.1.tgz", - "integrity": "sha512-Odju3uBUJyVCkW64nLD4wKLhbh93bh6vIg/ZIXkWiLPBrdgtc65+tls/qml+un3pr6JqYVFDZbbmLDQT68rTOQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.2.tgz", + "integrity": "sha512-/Szrn8nr+2TsQT1Gp8iIe/BEytJmbyfrbFh419DfGQSkEgNEhbPi7JRJuughjkTzPWgU9gBQf5AVu3DbHt0OXA==", + "license": "Apache-2.0", "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", @@ -7067,6 +7091,7 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", @@ -7084,6 +7109,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-7.0.1.tgz", "integrity": "sha512-UcO3kefx6dCcZkgcTGgVOTFb7b1LlQ02hY1omMjjrrBzkajRMCFgYOjs7J71WqnuG1k2b+9ppGL7FsOfhZMQKQ==", + "license": "Apache-2.0", "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", @@ -7293,10 +7319,11 @@ } }, "node_modules/globals": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz", - "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==", + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -7350,9 +7377,10 @@ "license": "MIT" }, "node_modules/google-auth-library": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.1.0.tgz", - "integrity": "sha512-GspVjZj1RbyRWpQ9FbAXMKjFGzZwDKnUHi66JJ+tcjcu5/xYAP1pdlWotCuIkMwjfVsxxDvsGZXGLzRt72D0sQ==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.4.0.tgz", + "integrity": "sha512-CmIrSy1bqMQUsPmA9+hcSbAXL80cFhu40cGMUjCaLpNKVzzvi+0uAHq8GNZxkoGYIsTX4ZQ7e4aInAqWxgn4fg==", + "license": "Apache-2.0", "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", @@ -7370,18 +7398,20 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.1.tgz", "integrity": "sha512-rcX58I7nqpu4mbKztFeOAObbomBbHU2oIb/d3tJfF3dizGSApqtSwYJigGCooHdnMyQBIw8BrWyK96w3YXgr6A==", + "license": "Apache-2.0", "engines": { "node": ">=14" } }, "node_modules/googleapis-common": { - "version": "8.0.2-rc.0", - "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-8.0.2-rc.0.tgz", - "integrity": "sha512-JTcxRvmFa9Ec1uyfMEimEMeeKq1sHNZX3vn2qmoUMtnvixXXvcqTcbDZvEZXkEWpGlPlOf4joyep6/qs0BrLyg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-8.0.0.tgz", + "integrity": "sha512-66if47It7y+Sab3HMkwEXx1kCq9qUC9px8ZXoj1CMrmLmUw81GpbnsNlXnlyZyGbGPGcj+tDD9XsZ23m7GLaJQ==", + "license": "Apache-2.0", "dependencies": { "extend": "^3.0.2", "gaxios": "^7.0.0-rc.4", - "google-auth-library": "^10.0.0-rc.1", + "google-auth-library": "^10.1.0", "qs": "^6.7.0", "url-template": "^2.0.8" }, @@ -7411,6 +7441,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==", + "license": "MIT", "dependencies": { "gaxios": "^7.0.0", "jws": "^4.0.0" @@ -8611,16 +8642,16 @@ } }, "node_modules/jest": { - "version": "30.1.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.1.3.tgz", - "integrity": "sha512-Ry+p2+NLk6u8Agh5yVqELfUJvRfV51hhVBRIB5yZPY7mU0DGBmOuFG5GebZbMbm86cdQNK0fhJuDX8/1YorISQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz", + "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.1.3", - "@jest/types": "30.0.5", + "@jest/core": "30.2.0", + "@jest/types": "30.2.0", "import-local": "^3.2.0", - "jest-cli": "30.1.3" + "jest-cli": "30.2.0" }, "bin": { "jest": "bin/jest.js" @@ -8638,14 +8669,14 @@ } }, "node_modules/jest-changed-files": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.0.5.tgz", - "integrity": "sha512-bGl2Ntdx0eAwXuGpdLdVYVr5YQHnSZlQ0y9HVDu565lCUAe9sj6JOtBbMmBBikGIegne9piDDIOeiLVoqTkz4A==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz", + "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==", "dev": true, "license": "MIT", "dependencies": { "execa": "^5.1.1", - "jest-util": "30.0.5", + "jest-util": "30.2.0", "p-limit": "^3.1.0" }, "engines": { @@ -8653,29 +8684,29 @@ } }, "node_modules/jest-circus": { - "version": "30.1.3", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.1.3.tgz", - "integrity": "sha512-Yf3dnhRON2GJT4RYzM89t/EXIWNxKTpWTL9BfF3+geFetWP4XSvJjiU1vrWplOiUkmq8cHLiwuhz+XuUp9DscA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz", + "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.1.2", - "@jest/expect": "30.1.2", - "@jest/test-result": "30.1.3", - "@jest/types": "30.0.5", + "@jest/environment": "30.2.0", + "@jest/expect": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "co": "^4.6.0", "dedent": "^1.6.0", "is-generator-fn": "^2.1.0", - "jest-each": "30.1.0", - "jest-matcher-utils": "30.1.2", - "jest-message-util": "30.1.0", - "jest-runtime": "30.1.3", - "jest-snapshot": "30.1.2", - "jest-util": "30.0.5", + "jest-each": "30.2.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-runtime": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", "p-limit": "^3.1.0", - "pretty-format": "30.0.5", + "pretty-format": "30.2.0", "pure-rand": "^7.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" @@ -8685,21 +8716,21 @@ } }, "node_modules/jest-cli": { - "version": "30.1.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.1.3.tgz", - "integrity": "sha512-G8E2Ol3OKch1DEeIBl41NP7OiC6LBhfg25Btv+idcusmoUSpqUkbrneMqbW9lVpI/rCKb/uETidb7DNteheuAQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz", + "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.1.3", - "@jest/test-result": "30.1.3", - "@jest/types": "30.0.5", + "@jest/core": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", "chalk": "^4.1.2", "exit-x": "^0.2.2", "import-local": "^3.2.0", - "jest-config": "30.1.3", - "jest-util": "30.0.5", - "jest-validate": "30.1.0", + "jest-config": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", "yargs": "^17.7.2" }, "bin": { @@ -8718,34 +8749,34 @@ } }, "node_modules/jest-config": { - "version": "30.1.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.1.3.tgz", - "integrity": "sha512-M/f7gqdQEPgZNA181Myz+GXCe8jXcJsGjCMXUzRj22FIXsZOyHNte84e0exntOvdPaeh9tA0w+B8qlP2fAezfw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz", + "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", "@jest/get-type": "30.1.0", "@jest/pattern": "30.0.1", - "@jest/test-sequencer": "30.1.3", - "@jest/types": "30.0.5", - "babel-jest": "30.1.2", + "@jest/test-sequencer": "30.2.0", + "@jest/types": "30.2.0", + "babel-jest": "30.2.0", "chalk": "^4.1.2", "ci-info": "^4.2.0", "deepmerge": "^4.3.1", "glob": "^10.3.10", "graceful-fs": "^4.2.11", - "jest-circus": "30.1.3", - "jest-docblock": "30.0.1", - "jest-environment-node": "30.1.2", + "jest-circus": "30.2.0", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.2.0", "jest-regex-util": "30.0.1", - "jest-resolve": "30.1.3", - "jest-runner": "30.1.3", - "jest-util": "30.0.5", - "jest-validate": "30.1.0", + "jest-resolve": "30.2.0", + "jest-runner": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", "micromatch": "^4.0.8", "parse-json": "^5.2.0", - "pretty-format": "30.0.5", + "pretty-format": "30.2.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -8817,25 +8848,25 @@ } }, "node_modules/jest-diff": { - "version": "30.1.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.1.2.tgz", - "integrity": "sha512-4+prq+9J61mOVXCa4Qp8ZjavdxzrWQXrI80GNxP8f4tkI2syPuPrJgdRPZRrfUTRvIoUwcmNLbqEJy9W800+NQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", + "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", "dev": true, "license": "MIT", "dependencies": { "@jest/diff-sequences": "30.0.1", "@jest/get-type": "30.1.0", "chalk": "^4.1.2", - "pretty-format": "30.0.5" + "pretty-format": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-docblock": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.0.1.tgz", - "integrity": "sha512-/vF78qn3DYphAaIc3jy4gA7XSAz167n9Bm/wn/1XhTLW7tTBIzXtCJpb/vcmc73NIIeeohCbdL94JasyXUZsGA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", + "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", "dev": true, "license": "MIT", "dependencies": { @@ -8846,36 +8877,36 @@ } }, "node_modules/jest-each": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.1.0.tgz", - "integrity": "sha512-A+9FKzxPluqogNahpCv04UJvcZ9B3HamqpDNWNKDjtxVRYB8xbZLFuCr8JAJFpNp83CA0anGQFlpQna9Me+/tQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz", + "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "chalk": "^4.1.2", - "jest-util": "30.0.5", - "pretty-format": "30.0.5" + "jest-util": "30.2.0", + "pretty-format": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-environment-node": { - "version": "30.1.2", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.1.2.tgz", - "integrity": "sha512-w8qBiXtqGWJ9xpJIA98M0EIoq079GOQRQUyse5qg1plShUCQ0Ek1VTTcczqKrn3f24TFAgFtT+4q3aOXvjbsuA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz", + "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.1.2", - "@jest/fake-timers": "30.1.2", - "@jest/types": "30.0.5", + "@jest/environment": "30.2.0", + "@jest/fake-timers": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", - "jest-mock": "30.0.5", - "jest-util": "30.0.5", - "jest-validate": "30.1.0" + "jest-mock": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -8889,20 +8920,20 @@ "license": "MIT" }, "node_modules/jest-haste-map": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.1.0.tgz", - "integrity": "sha512-JLeM84kNjpRkggcGpQLsV7B8W4LNUWz7oDNVnY1Vjj22b5/fAb3kk3htiD+4Na8bmJmjJR7rBtS2Rmq/NEcADg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz", + "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "@types/node": "*", "anymatch": "^3.1.3", "fb-watchman": "^2.0.2", "graceful-fs": "^4.2.11", "jest-regex-util": "30.0.1", - "jest-util": "30.0.5", - "jest-worker": "30.1.0", + "jest-util": "30.2.0", + "jest-worker": "30.2.0", "micromatch": "^4.0.8", "walker": "^1.0.8" }, @@ -8914,49 +8945,49 @@ } }, "node_modules/jest-leak-detector": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.1.0.tgz", - "integrity": "sha512-AoFvJzwxK+4KohH60vRuHaqXfWmeBATFZpzpmzNmYTtmRMiyGPVhkXpBqxUQunw+dQB48bDf4NpUs6ivVbRv1g==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz", + "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", - "pretty-format": "30.0.5" + "pretty-format": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "30.1.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.1.2.tgz", - "integrity": "sha512-7ai16hy4rSbDjvPTuUhuV8nyPBd6EX34HkBsBcBX2lENCuAQ0qKCPb/+lt8OSWUa9WWmGYLy41PrEzkwRwoGZQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", + "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", "chalk": "^4.1.2", - "jest-diff": "30.1.2", - "pretty-format": "30.0.5" + "jest-diff": "30.2.0", + "pretty-format": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-message-util": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.1.0.tgz", - "integrity": "sha512-HizKDGG98cYkWmaLUHChq4iN+oCENohQLb7Z5guBPumYs+/etonmNFlg1Ps6yN9LTPyZn+M+b/9BbnHx3WTMDg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", + "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "@types/stack-utils": "^2.0.3", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "micromatch": "^4.0.8", - "pretty-format": "30.0.5", + "pretty-format": "30.2.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" }, @@ -8965,15 +8996,15 @@ } }, "node_modules/jest-mock": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.5.tgz", - "integrity": "sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", + "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "@types/node": "*", - "jest-util": "30.0.5" + "jest-util": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -9008,18 +9039,18 @@ } }, "node_modules/jest-resolve": { - "version": "30.1.3", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.1.3.tgz", - "integrity": "sha512-DI4PtTqzw9GwELFS41sdMK32Ajp3XZQ8iygeDMWkxlRhm7uUTOFSZFVZABFuxr0jvspn8MAYy54NxZCsuCTSOw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz", + "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.2", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.1.0", + "jest-haste-map": "30.2.0", "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.0.5", - "jest-validate": "30.1.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", "slash": "^3.0.0", "unrs-resolver": "^1.7.11" }, @@ -9028,46 +9059,46 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "30.1.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.1.3.tgz", - "integrity": "sha512-DNfq3WGmuRyHRHfEet+Zm3QOmVFtIarUOQHHryKPc0YL9ROfgWZxl4+aZq/VAzok2SS3gZdniP+dO4zgo59hBg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz", + "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==", "dev": true, "license": "MIT", "dependencies": { "jest-regex-util": "30.0.1", - "jest-snapshot": "30.1.2" + "jest-snapshot": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-runner": { - "version": "30.1.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.1.3.tgz", - "integrity": "sha512-dd1ORcxQraW44Uz029TtXj85W11yvLpDuIzNOlofrC8GN+SgDlgY4BvyxJiVeuabA1t6idjNbX59jLd2oplOGQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz", + "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.1.2", - "@jest/environment": "30.1.2", - "@jest/test-result": "30.1.3", - "@jest/transform": "30.1.2", - "@jest/types": "30.0.5", + "@jest/console": "30.2.0", + "@jest/environment": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "emittery": "^0.13.1", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", - "jest-docblock": "30.0.1", - "jest-environment-node": "30.1.2", - "jest-haste-map": "30.1.0", - "jest-leak-detector": "30.1.0", - "jest-message-util": "30.1.0", - "jest-resolve": "30.1.3", - "jest-runtime": "30.1.3", - "jest-util": "30.0.5", - "jest-watcher": "30.1.3", - "jest-worker": "30.1.0", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.2.0", + "jest-haste-map": "30.2.0", + "jest-leak-detector": "30.2.0", + "jest-message-util": "30.2.0", + "jest-resolve": "30.2.0", + "jest-runtime": "30.2.0", + "jest-util": "30.2.0", + "jest-watcher": "30.2.0", + "jest-worker": "30.2.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -9076,32 +9107,32 @@ } }, "node_modules/jest-runtime": { - "version": "30.1.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.1.3.tgz", - "integrity": "sha512-WS8xgjuNSphdIGnleQcJ3AKE4tBKOVP+tKhCD0u+Tb2sBmsU8DxfbBpZX7//+XOz81zVs4eFpJQwBNji2Y07DA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz", + "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.1.2", - "@jest/fake-timers": "30.1.2", - "@jest/globals": "30.1.2", + "@jest/environment": "30.2.0", + "@jest/fake-timers": "30.2.0", + "@jest/globals": "30.2.0", "@jest/source-map": "30.0.1", - "@jest/test-result": "30.1.3", - "@jest/transform": "30.1.2", - "@jest/types": "30.0.5", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "cjs-module-lexer": "^2.1.0", "collect-v8-coverage": "^1.0.2", "glob": "^10.3.10", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.1.0", - "jest-message-util": "30.1.0", - "jest-mock": "30.0.5", + "jest-haste-map": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", "jest-regex-util": "30.0.1", - "jest-resolve": "30.1.3", - "jest-snapshot": "30.1.2", - "jest-util": "30.0.5", + "jest-resolve": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -9157,9 +9188,9 @@ } }, "node_modules/jest-snapshot": { - "version": "30.1.2", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.1.2.tgz", - "integrity": "sha512-4q4+6+1c8B6Cy5pGgFvjDy/Pa6VYRiGu0yQafKkJ9u6wQx4G5PqI2QR6nxTl43yy7IWsINwz6oT4o6tD12a8Dg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz", + "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==", "dev": true, "license": "MIT", "dependencies": { @@ -9168,20 +9199,20 @@ "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.1.2", + "@jest/expect-utils": "30.2.0", "@jest/get-type": "30.1.0", - "@jest/snapshot-utils": "30.1.2", - "@jest/transform": "30.1.2", - "@jest/types": "30.0.5", - "babel-preset-current-node-syntax": "^1.1.0", + "@jest/snapshot-utils": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "babel-preset-current-node-syntax": "^1.2.0", "chalk": "^4.1.2", - "expect": "30.1.2", + "expect": "30.2.0", "graceful-fs": "^4.2.11", - "jest-diff": "30.1.2", - "jest-matcher-utils": "30.1.2", - "jest-message-util": "30.1.0", - "jest-util": "30.0.5", - "pretty-format": "30.0.5", + "jest-diff": "30.2.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "pretty-format": "30.2.0", "semver": "^7.7.2", "synckit": "^0.11.8" }, @@ -9203,13 +9234,13 @@ } }, "node_modules/jest-util": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.5.tgz", - "integrity": "sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", + "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", @@ -9234,18 +9265,18 @@ } }, "node_modules/jest-validate": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.1.0.tgz", - "integrity": "sha512-7P3ZlCFW/vhfQ8pE7zW6Oi4EzvuB4sgR72Q1INfW9m0FGo0GADYlPwIkf4CyPq7wq85g+kPMtPOHNAdWHeBOaA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz", + "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "camelcase": "^6.3.0", "chalk": "^4.1.2", "leven": "^3.1.0", - "pretty-format": "30.0.5" + "pretty-format": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -9265,19 +9296,19 @@ } }, "node_modules/jest-watcher": { - "version": "30.1.3", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.1.3.tgz", - "integrity": "sha512-6jQUZCP1BTL2gvG9E4YF06Ytq4yMb4If6YoQGRR6PpjtqOXSP3sKe2kqwB6SQ+H9DezOfZaSLnmka1NtGm3fCQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz", + "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.1.3", - "@jest/types": "30.0.5", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "emittery": "^0.13.1", - "jest-util": "30.0.5", + "jest-util": "30.2.0", "string-length": "^4.0.2" }, "engines": { @@ -9285,15 +9316,15 @@ } }, "node_modules/jest-worker": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.1.0.tgz", - "integrity": "sha512-uvWcSjlwAAgIu133Tt77A05H7RIk3Ho8tZL50bQM2AkvLdluw9NG48lRCl3Dt+MOH719n/0nnb5YxUwcuJiKRA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz", + "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.0.5", + "jest-util": "30.2.0", "merge-stream": "^2.0.0", "supports-color": "^8.1.1" }, @@ -9595,6 +9626,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", @@ -9605,6 +9637,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "license": "MIT", "dependencies": { "jwa": "^2.0.0", "safe-buffer": "^5.0.1" @@ -9973,12 +10006,12 @@ } }, "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==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/marked-subsuper-text/-/marked-subsuper-text-1.0.4.tgz", + "integrity": "sha512-sSNZpQZUNyV6j6i40Sip207ywbvMwoTY8md94t+snBGMahhOz9QiAke738dJCz+um3d4QkpGBI21ke17c56fUQ==", "license": "MIT", "peerDependencies": { - "marked": ">=3 <16" + "marked": ">=3 <17" } }, "node_modules/markedLegacy": { @@ -10044,7 +10077,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", @@ -10311,6 +10345,7 @@ "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": "^14.1.0 || ^13.0.0" @@ -10320,6 +10355,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "license": "MIT", "dependencies": { "punycode": "^2.3.1" }, @@ -10331,6 +10367,7 @@ "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" } @@ -10339,6 +10376,7 @@ "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": "^5.1.0", "webidl-conversions": "^7.0.0" @@ -10348,14 +10386,14 @@ } }, "node_modules/mongoose": { - "version": "8.16.3", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.16.3.tgz", - "integrity": "sha512-p2JOsRQG7j0vXhLpsWw5Slm2VnDeJK8sRyqSyegk5jQujuP9BTOZ1Di9VX/0lYfBhZ2DpAExi51QTd4pIqSgig==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.19.1.tgz", + "integrity": "sha512-oB7hGQJn4f8aebqE7mhE54EReb5cxVgpCxQCQj0K/cK3q4J3Tg08nFP6sM52nJ4Hlm8jsDnhVYpqIITZUAhckQ==", "license": "MIT", "dependencies": { "bson": "^6.10.4", "kareem": "2.6.3", - "mongodb": "~6.17.0", + "mongodb": "~6.20.0", "mpath": "0.9.0", "mquery": "5.0.0", "ms": "2.1.3", @@ -10373,6 +10411,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": { @@ -10386,6 +10425,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": { @@ -10402,6 +10442,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": { @@ -10416,6 +10457,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": { @@ -10427,13 +10469,14 @@ } }, "node_modules/mongoose/node_modules/mongodb": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.17.0.tgz", - "integrity": "sha512-neerUzg/8U26cgruLysKEjJvoNSXhyID3RvzvdcpsIi2COYM3FS3o9nlH7fxFtefTb942dX3W9i37oPfCVj4wA==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz", + "integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==", + "license": "Apache-2.0", "dependencies": { - "@mongodb-js/saslprep": "^1.1.9", + "@mongodb-js/saslprep": "^1.3.0", "bson": "^6.10.4", - "mongodb-connection-string-url": "^3.0.0" + "mongodb-connection-string-url": "^3.0.2" }, "engines": { "node": ">=16.20.1" @@ -10444,7 +10487,7 @@ "gcp-metadata": "^5.2.0", "kerberos": "^2.0.1", "mongodb-client-encryption": ">=6.0.0 <7", - "snappy": "^7.2.2", + "snappy": "^7.3.2", "socks": "^2.7.1" }, "peerDependenciesMeta": { @@ -10505,9 +10548,9 @@ "optional": true }, "node_modules/nanoid": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", - "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", + "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", "funding": [ { "type": "github", @@ -10608,9 +10651,9 @@ } }, "node_modules/napi-postinstall": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.3.tgz", - "integrity": "sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", "dev": true, "license": "MIT", "bin": { @@ -10714,6 +10757,7 @@ "url": "https://paypal.me/jimmywarting" } ], + "license": "MIT", "engines": { "node": ">=10.5.0" } @@ -11605,9 +11649,9 @@ } }, "node_modules/pretty-format": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz", - "integrity": "sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", + "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", "dev": true, "license": "MIT", "dependencies": { @@ -11867,9 +11911,10 @@ "license": "MIT" }, "node_modules/react-router": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.3.tgz", - "integrity": "sha512-zf45LZp5skDC6I3jDLXQUu0u26jtuP4lEGbc7BbdyxenBN1vJSTA18czM2D+h5qyMBuMrD+9uB+mU37HIoKGRA==", + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.4.tgz", + "integrity": "sha512-SD3G8HKviFHg9xj7dNODUKDFgpG4xqD5nhyd0mYoB5iISepuZAvzSr8ywxgxKJ52yRzf/HWtVHc9AWwoTbljvA==", + "license": "MIT", "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" @@ -12918,6 +12963,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" } @@ -13287,9 +13333,9 @@ "license": "ISC" }, "node_modules/stylelint": { - "version": "16.24.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.24.0.tgz", - "integrity": "sha512-7ksgz3zJaSbTUGr/ujMXvLVKdDhLbGl3R/3arNudH7z88+XZZGNLMTepsY28WlnvEFcuOmUe7fg40Q3lfhOfSQ==", + "version": "16.25.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.25.0.tgz", + "integrity": "sha512-Li0avYWV4nfv1zPbdnxLYBGq4z8DVZxbRgx4Kn6V+Uftz1rMoF1qiEI3oL4kgWqyYgCgs7gT5maHNZ82Gk03vQ==", "dev": true, "funding": [ { @@ -13307,13 +13353,13 @@ "@csstools/css-tokenizer": "^3.0.4", "@csstools/media-query-list-parser": "^4.0.3", "@csstools/selector-specificity": "^5.0.0", - "@dual-bundle/import-meta-resolve": "^4.1.0", + "@dual-bundle/import-meta-resolve": "^4.2.1", "balanced-match": "^2.0.0", "colord": "^2.9.3", "cosmiconfig": "^9.0.0", "css-functions-list": "^3.2.3", "css-tree": "^3.1.0", - "debug": "^4.4.1", + "debug": "^4.4.3", "fast-glob": "^3.3.3", "fastest-levenshtein": "^1.0.16", "file-entry-cache": "^10.1.4", @@ -14162,9 +14208,9 @@ "license": "MIT" }, "node_modules/undici-types": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", - "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz", + "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", "dev": true, "license": "MIT" }, @@ -14403,7 +14449,8 @@ "node_modules/url-template": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", - "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==" + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==", + "license": "BSD" }, "node_modules/url/node_modules/punycode": { "version": "1.4.1", @@ -14861,6 +14908,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", "engines": { "node": ">= 8" } diff --git a/package.json b/package.json index b36b9959a..666832db8 100644 --- a/package.json +++ b/package.json @@ -83,19 +83,19 @@ ] }, "dependencies": { - "@babel/core": "^7.27.1", - "@babel/plugin-transform-runtime": "^7.28.0", - "@babel/preset-env": "^7.28.0", + "@babel/core": "^7.28.4", + "@babel/plugin-transform-runtime": "^7.28.3", + "@babel/preset-env": "^7.28.3", "@babel/preset-react": "^7.27.1", - "@babel/runtime": "^7.27.6", + "@babel/runtime": "^7.28.4", "@dmsnell/diff-match-patch": "^1.1.0", - "@googleapis/drive": "^13.0.1", + "@googleapis/drive": "^18.0.0", "@sanity/diff-match-patch": "^3.2.0", "body-parser": "^2.2.0", "classnames": "^2.5.1", "codemirror": "^5.65.6", "cookie-parser": "^1.4.7", - "core-js": "^3.44.0", + "core-js": "^3.46.0", "cors": "^2.8.5", "create-react-class": "^15.7.0", "dedent-tabs": "^0.10.3", @@ -104,7 +104,7 @@ "express-async-handler": "^1.2.0", "express-static-gzip": "3.0.0", "fflate": "^0.8.2", - "fs-extra": "11.3.0", + "fs-extra": "11.3.2", "hash-wasm": "^4.12.0", "idb-keyval": "^6.2.2", "js-yaml": "^4.1.0", @@ -119,16 +119,16 @@ "marked-gfm-heading-id": "^4.1.2", "marked-nonbreaking-spaces": "^1.0.1", "marked-smartypants-lite": "^1.0.3", - "marked-subsuper-text": "^1.0.3", + "marked-subsuper-text": "^1.0.4", "markedLegacy": "npm:marked@^0.3.19", "moment": "^2.30.1", - "mongoose": "^8.16.3", - "nanoid": "5.1.5", + "mongoose": "^8.19.1", + "nanoid": "5.1.6", "nconf": "^0.13.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-frame-component": "^4.1.3", - "react-router": "^7.6.3", + "react-router": "^7.9.4", "romans": "^3.1.0", "sanitize-filename": "1.6.3", "superagent": "^10.2.1", @@ -138,15 +138,15 @@ "devDependencies": { "@stylistic/stylelint-plugin": "^4.0.0", "babel-plugin-transform-import-meta": "^2.3.3", - "eslint": "^9.35.0", + "eslint": "^9.37.0", "eslint-plugin-jest": "^29.0.1", "eslint-plugin-react": "^7.37.5", - "globals": "^16.3.0", - "jest": "^30.1.3", + "globals": "^16.4.0", + "jest": "^30.2.0", "jest-expect-message": "^1.1.3", "jsdom-global": "^3.0.2", "postcss-less": "^6.0.0", - "stylelint": "^16.24.0", + "stylelint": "^16.25.0", "stylelint-config-recess-order": "^7.3.0", "stylelint-config-recommended": "^17.0.0", "supertest": "^7.1.4" diff --git a/server/app.js b/server/app.js index afba0997b..1bdb5aac3 100644 --- a/server/app.js +++ b/server/app.js @@ -35,6 +35,7 @@ import contentNegotiation from './middleware/content-negotiation.js'; import bodyParser from 'body-parser'; import cookieParser from 'cookie-parser'; import forceSSL from './forcessl.mw.js'; +import dbCheck from './middleware/dbCheck.js'; const sanitizeBrew = (brew, accessType)=>{ @@ -274,7 +275,7 @@ app.get('/metadata/:id', asyncHandler(getBrew('share')), (req, res)=>{ app.get('/css/:id', asyncHandler(getBrew('share')), (req, res)=>{getCSS(req, res);}); //User Page -app.get('/user/:username', async (req, res, next)=>{ +app.get('/user/:username', dbCheck, async (req, res, next)=>{ const ownAccount = req.account && (req.account.username == req.params.username); req.ogMeta = { ...defaultMetaTags, @@ -346,7 +347,7 @@ app.get('/user/:username', async (req, res, next)=>{ }); //Change author name on brews -app.put('/api/user/rename', async (req, res)=>{ +app.put('/api/user/rename', dbCheck, async (req, res)=>{ const { username, newUsername } = req.body; const ownAccount = req.account && (req.account.username == newUsername); @@ -432,7 +433,7 @@ app.get('/new', asyncHandler(async(req, res, next)=>{ })); //Share Page -app.get('/share/:id', asyncHandler(getBrew('share')), asyncHandler(async (req, res, next)=>{ +app.get('/share/:id', dbCheck, asyncHandler(getBrew('share')), asyncHandler(async (req, res, next)=>{ const { brew } = req; req.ogMeta = { ...defaultMetaTags, title : `${req.brew.title || 'Untitled Brew'} - ${req.brew.authors[0] || 'No author.'}`, @@ -459,7 +460,7 @@ app.get('/share/:id', asyncHandler(getBrew('share')), asyncHandler(async (req, r })); //Account Page -app.get('/account', asyncHandler(async (req, res, next)=>{ +app.get('/account', dbCheck, asyncHandler(async (req, res, next)=>{ const data = {}; data.title = 'Account Information Page'; @@ -562,8 +563,6 @@ const renderPage = async (req, res)=>{ brews : req.brews, googleBrews : req.googleBrews, account : req.account, - enable_v3 : config.get('enable_v3'), - enable_themes : config.get('enable_themes'), config : configuration, ogMeta : req.ogMeta, userThemes : req.userThemes diff --git a/server/db.js b/server/db.js index 97da56a08..4930e4cd6 100644 --- a/server/db.js +++ b/server/db.js @@ -22,16 +22,29 @@ const handleConnectionError = (error)=>{ } }; +const addListeners = (conn)=>{ + conn.connection.on('disconnecting', ()=>{console.log('Mongo disconnecting...');}); + conn.connection.on('disconnected', ()=>{console.log('Mongo disconnected!');}); + conn.connection.on('connecting', ()=>{console.log('Mongo connecting...');}); + conn.connection.on('connected', ()=>{console.log('Mongo connected!');}); + return conn; +}; + const disconnect = async ()=>{ return await Mongoose.disconnect(); }; const connect = async (config)=>{ - return await Mongoose.connect(getMongoDBURL(config), { retryWrites: false }) - .catch((error)=>handleConnectionError(error)); + return await Mongoose.connect(getMongoDBURL(config), { + retryWrites : false, + autoIndex : (config.get('local_environments').includes(config.get('node_env'))) + }) + .then(addListeners(Mongoose)) + .catch((error)=>handleConnectionError(error)); }; export default { connect, disconnect }; + diff --git a/server/homebrew.api.js b/server/homebrew.api.js index 3221638ab..2cedb5972 100644 --- a/server/homebrew.api.js +++ b/server/homebrew.api.js @@ -8,11 +8,12 @@ import Markdown from '../shared/naturalcrit/markdown.js'; import yaml from 'js-yaml'; import asyncHandler from 'express-async-handler'; import { nanoid } from 'nanoid'; -import {makePatches, applyPatches, stringifyPatches, parsePatch} from '@sanity/diff-match-patch'; +import { makePatches, applyPatches, stringifyPatches, parsePatch } from '@sanity/diff-match-patch'; import { md5 } from 'hash-wasm'; -import { splitTextStyleAndMetadata, +import { splitTextStyleAndMetadata, brewSnippetsToJSON, debugTextMismatch } from '../shared/helpers.js'; import checkClientVersion from './middleware/check-client-version.js'; +import dbCheck from './middleware/dbCheck.js'; const router = express.Router(); @@ -377,14 +378,14 @@ const api = { // Patch to a throwaway variable while parallelizing - we're more concerned with error/no error. const patchedResult = decodeURI(applyPatches(patches, encodeURI(brewFromServer.text))[0]); if(patchedResult != brewFromClient.text) - throw("Patches did not apply cleanly, text mismatch detected"); + throw ('Patches did not apply cleanly, text mismatch detected'); // brew.text = applyPatches(patches, brewFromServer.text)[0]; } catch (err) { //debugTextMismatch(brewFromClient.text, brewFromServer.text, `edit/${brewFromClient.editId}`); console.error('Failed to apply patches:', { //patches : brewFromClient.patches, - brewId : brewFromClient.editId || 'unknown', - error : err + brewId : brewFromClient.editId || 'unknown', + error : err }); // While running in parallel, don't throw the error upstream. // throw err; // rethrow to preserve the 500 behavior @@ -480,6 +481,7 @@ const api = { await HomebrewModel.deleteOne({ editId: id }); return next(); } + throw(err); } let brew = req.brew; @@ -530,6 +532,8 @@ const api = { } }; +router.use(dbCheck); + router.post('/api', checkClientVersion, asyncHandler(api.newBrew)); router.put('/api/:id', checkClientVersion, asyncHandler(api.getBrew('edit', false)), asyncHandler(api.updateBrew)); router.put('/api/update/:id', checkClientVersion, asyncHandler(api.getBrew('edit', false)), asyncHandler(api.updateBrew)); diff --git a/server/homebrew.model.js b/server/homebrew.model.js index 2e74b1de2..ff371ee42 100644 --- a/server/homebrew.model.js +++ b/server/homebrew.model.js @@ -7,29 +7,29 @@ import zlib from 'zlib'; const HomebrewSchema = mongoose.Schema({ shareId : { type: String, default: ()=>{return nanoid(12);}, index: { unique: true } }, editId : { type: String, default: ()=>{return nanoid(12);}, index: { unique: true } }, - googleId : { type: String }, - title : { type: String, default: '' }, + googleId : { type: String, index: true }, + title : { type: String, default: '', index: true }, text : { type: String, default: '' }, textBin : { type: Buffer }, - pageCount : { type: Number, default: 1 }, + pageCount : { type: Number, default: 1, index: true }, description : { type: String, default: '' }, - tags : [String], + tags : { type: [String], index: true }, systems : [String], - lang : { type: String, default: 'en' }, - renderer : { type: String, default: '' }, - authors : [String], + lang : { type: String, default: 'en', index: true }, + renderer : { type: String, default: '', index: true }, + authors : { type: [String], index: true }, invitedAuthors : [String], - published : { type: Boolean, default: false }, - thumbnail : { type: String, default: '' }, + published : { type: Boolean, default: false, index: true }, + thumbnail : { type: String, default: '', index: true }, - createdAt : { type: Date, default: Date.now }, - updatedAt : { type: Date, default: Date.now }, - lastViewed : { type: Date, default: Date.now }, + createdAt : { type: Date, default: Date.now, index: true }, + updatedAt : { type: Date, default: Date.now, index: true }, + lastViewed : { type: Date, default: Date.now, index: true }, views : { type: Number, default: 0 }, - version : { type: Number, default: 1 }, + version : { type: Number, default: 1, index: true }, - lock : { type: Object } + lock : { type: Object, index: true } }, { versionKey: false }); HomebrewSchema.statics.increaseView = async function(query) { @@ -43,6 +43,8 @@ HomebrewSchema.statics.increaseView = async function(query) { return brew; }; +// STATIC FUNCTIONS + HomebrewSchema.statics.get = async function(query, fields=null){ const brew = await Homebrew.findOne(query, fields).orFail() .catch((error)=>{throw 'Can not find brew';}); @@ -63,6 +65,15 @@ HomebrewSchema.statics.getByUser = async function(username, allowAccess=false, f return brews; }; +// INDEXES + +HomebrewSchema.index({ updatedAt: -1, lastViewed: -1 }); +HomebrewSchema.index({ published: 1, title: 'text' }); + +HomebrewSchema.index({ lock: 1, sparse: true }); +HomebrewSchema.path('lock.reviewRequested').index({ sparse: true }); + + const Homebrew = mongoose.model('Homebrew', HomebrewSchema); export { diff --git a/server/middleware/dbCheck.js b/server/middleware/dbCheck.js new file mode 100644 index 000000000..f486eab52 --- /dev/null +++ b/server/middleware/dbCheck.js @@ -0,0 +1,15 @@ +import mongoose from 'mongoose'; +import config from '../config.js'; + +export default (req, res, next)=>{ + // Bypass DB checks during testing + if(config.get('node_env') == 'test') return next(); + + if(mongoose.connection.readyState == 1) return next(); + throw { + HBErrorCode : '13', + name : 'Database Connection Error', + message : 'Unable to connect to database', + status : mongoose.connection.readyState + }; +}; diff --git a/server/middleware/dbCheck.spec.js b/server/middleware/dbCheck.spec.js new file mode 100644 index 000000000..0c37d40ab --- /dev/null +++ b/server/middleware/dbCheck.spec.js @@ -0,0 +1,28 @@ +import mongoose from 'mongoose'; +import dbCheck from './dbCheck.js'; +import config from '../config.js'; + +describe('dbCheck middleware', ()=>{ + const next = jest.fn(); + + afterEach(()=>jest.clearAllMocks()); + + it('should skip check in test mode', ()=>{ + config.get = jest.fn(()=>'test'); + expect(()=>dbCheck({}, {}, next)).not.toThrow(); + expect(next).toHaveBeenCalled(); + }); + + it('should call next if readyState == 1', ()=>{ + config.get = jest.fn(()=>'production'); + mongoose.connection.readyState = 1; + dbCheck({}, {}, next); + expect(next).toHaveBeenCalled(); + }); + + it('should throw if readyState != 1', ()=>{ + config.get = jest.fn(()=>'production'); + mongoose.connection.readyState = 99; + expect(()=>dbCheck({}, {}, next)).toThrow(/Unable to connect/); + }); +}); \ No newline at end of file diff --git a/shared/helpers.js b/shared/helpers.js index 3f91583d6..adf5b889a 100644 --- a/shared/helpers.js +++ b/shared/helpers.js @@ -8,7 +8,7 @@ const brewSnippetsToJSON = (menuTitle, userBrewSnippets, themeBundleSnippets=nul const mpAsSnippets = []; // Snippets from Themes first. if(themeBundleSnippets) { - for (let themes of themeBundleSnippets) { + for (const themes of themeBundleSnippets) { if(typeof themes !== 'string') { const userSnippets = []; const snipSplit = themes.snippets.trim().split(textSplit).slice(1); @@ -76,9 +76,9 @@ const yamlSnippetsToText = (yamlObj)=>{ if(typeof yamlObj == 'string') return yamlObj; let snippetsText = ''; - - for (let snippet of yamlObj) { - for (let subSnippet of snippet.subsnippets) { + + for (const snippet of yamlObj) { + for (const subSnippet of snippet.subsnippets) { snippetsText = `${snippetsText}\\snippet ${subSnippet.name}\n${subSnippet.gen || ''}\n`; } } @@ -121,7 +121,7 @@ const fetchThemeBundle = async (setError, setThemeBundle, renderer, theme)=>{ const res = await request .get(`/api/theme/${renderer}/${theme}`) .catch((err)=>{ - setError(err) + setError(err); }); if(!res) { setThemeBundle({}); @@ -133,14 +133,14 @@ const fetchThemeBundle = async (setError, setThemeBundle, renderer, theme)=>{ setError(null); }; -const debugTextMismatch = (clientTextRaw, serverTextRaw, label) => { +const debugTextMismatch = (clientTextRaw, serverTextRaw, label)=>{ const clientText = clientTextRaw?.normalize('NFC') || ''; const serverText = serverTextRaw?.normalize('NFC') || ''; const clientBuffer = Buffer.from(clientText, 'utf8'); const serverBuffer = Buffer.from(serverText, 'utf8'); - if (clientBuffer.equals(serverBuffer)) { + if(clientBuffer.equals(serverBuffer)) { console.log(`✅ ${label} text matches byte-for-byte.`); return; } @@ -151,7 +151,7 @@ const debugTextMismatch = (clientTextRaw, serverTextRaw, label) => { // Byte-level diff for (let i = 0; i < Math.min(clientBuffer.length, serverBuffer.length); i++) { - if (clientBuffer[i] !== serverBuffer[i]) { + if(clientBuffer[i] !== serverBuffer[i]) { console.log(`Byte mismatch at offset ${i}: client=0x${clientBuffer[i].toString(16)} server=0x${serverBuffer[i].toString(16)}`); break; } @@ -159,14 +159,14 @@ const debugTextMismatch = (clientTextRaw, serverTextRaw, label) => { // Char-level diff for (let i = 0; i < Math.min(clientText.length, serverText.length); i++) { - if (clientText[i] !== serverText[i]) { + if(clientText[i] !== serverText[i]) { console.log(`Char mismatch at index ${i}:`); console.log(` Client: '${clientText[i]}' (U+${clientText.charCodeAt(i).toString(16).toUpperCase()})`); console.log(` Server: '${serverText[i]}' (U+${serverText.charCodeAt(i).toString(16).toUpperCase()})`); break; } } -} +}; export { splitTextStyleAndMetadata, diff --git a/shared/naturalcrit/codeEditor/codeEditor.less b/shared/naturalcrit/codeEditor/codeEditor.less index 84a5c63f1..c8e60974b 100644 --- a/shared/naturalcrit/codeEditor/codeEditor.less +++ b/shared/naturalcrit/codeEditor/codeEditor.less @@ -38,15 +38,11 @@ animation-duration : 0.4s; } - .CodeMirror-vscrollbar { - &::-webkit-scrollbar { width : 20px; } - &::-webkit-scrollbar-thumb { - width : 20px; - background : linear-gradient(90deg, #858585 15px, #808080 15px); - } + .CodeMirror-search-field { + width:25em !important; + outline:1px inset #00000055 !important; } - //.cm-tab { // background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAQAAACOs/baAAAARUlEQVR4nGJgIAG8JkXxUAcCtDWemcGR1lY4MvgzCEKY7jSBjgxBDAG09UEQzAe0AMwMHrSOAwEGRtpaMIwAAAAA//8DAG4ID9EKs6YqAAAAAElFTkSuQmCC) no-repeat right; //} diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index 78107dcf4..5331a5f6e 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -185,7 +185,7 @@ const mustacheSpans = { start(src) { return src.match(/{{[^{]/)?.index; }, // Hint to Marked.js to stop and check for a match tokenizer(src, tokens) { const completeSpan = /^{{[^\n]*}}/; // Regex for the complete token - const inlineRegex = /{{(?=((?:[:=](?:"['\w,\-+*/()#%=?. ]*"|[\w\-+*/()#%.]*)|[^"=':{}\s]*)*))\1 *|}}/g; + const inlineRegex = /{{(?=((?:[:=](?:"['\w,\-+*/()#%=?.&:!@$^;:\[\]_= ]*"|[\w\-+*/()#%.]*)|[^"=':{}\s]*)*))\1 *|}}/g; const match = completeSpan.exec(src); if(match) { //Find closing delimiter @@ -242,7 +242,7 @@ const mustacheDivs = { start(src) { return src.match(/\n *{{[^{]/m)?.index; }, // Hint to Marked.js to stop and check for a match tokenizer(src, tokens) { const completeBlock = /^ *{{[^\n}]* *\n.*\n *}}/s; // Regex for the complete token - const blockRegex = /^ *{{(?=((?:[:=](?:"['\w,\-+*/()#%=?. ]*"|[\w\-+*/()#%.]*)|[^"=':{}\s]*)*))\1 *$|^ *}}$/gm; + const blockRegex = /^ *{{(?=((?:[:=](?:"['\w,\-+*/()#%=?.&:!@$^;:\[\]_= ]*"|[\w\-()#%.]*)|[^"=':{}\s]*)*))\1 *$|^ *}}$/gm; const match = completeBlock.exec(src); if(match) { //Find closing delimiter @@ -297,7 +297,7 @@ const mustacheInjectInline = { level : 'inline', start(src) { return src.match(/ *{[^{\n]/)?.index; }, // Hint to Marked.js to stop and check for a match tokenizer(src, tokens) { - const inlineRegex = /^ *{(?=((?:[:=](?:"['\w,\-+*/()#%=?. ]*"|[\w\-+*/()#%.]*)|[^"=':{}\s]*)*))\1}/g; + const inlineRegex = /^ *{(?=((?:[:=](?:"['\w,\-+*/()#%=?.&:!@$^;:\[\]_= ]*"|[\w\-()#%.]*)|[^"=':{}\s]*)*))\1}/g; const match = inlineRegex.exec(src); if(match) { const lastToken = tokens[tokens.length - 1]; @@ -343,7 +343,7 @@ const mustacheInjectBlock = { level : 'block', start(src) { return src.match(/\n *{[^{\n]/m)?.index; }, // Hint to Marked.js to stop and check for a match tokenizer(src, tokens) { - const inlineRegex = /^ *{(?=((?:[:=](?:"['\w,\-+*/()#%=?. ]*"|[\w\-+*/()#%.]*)|[^"=':{}\s]*)*))\1}/ym; + const inlineRegex = /^ *{(?=((?:[:=](?:"['\w,\-+*/()#%=?.&:!@$^;:\[\]_= ]*"|[\w\-+*/()#%.]*)|[^"=':{}\s]*)*))\1}/ym; const match = inlineRegex.exec(src); if(match) { const lastToken = tokens[tokens.length - 1]; @@ -435,7 +435,7 @@ const replaceVar = function(input, hoist=false, allowUnresolved=false) { try { return mathParser.evaluate(replacedLabel); - } catch (error) { + } catch { return undefined; // Return undefined if invalid math result } } @@ -680,7 +680,7 @@ const tableTerminators = [ Marked.use(MarkedVariables()); Marked.use(MarkedDefinitionLists()); -Marked.use({ extensions : [forcedParagraphBreaks, mustacheSpans, mustacheDivs, mustacheInjectInline] }); +Marked.use({ extensions: [forcedParagraphBreaks, mustacheSpans, mustacheDivs, mustacheInjectInline] }); Marked.use(mustacheInjectBlock); Marked.use(MarkedAlignedParagraphs()); Marked.use(MarkedSubSuperText()); diff --git a/shared/naturalcrit/markdownLegacy.js b/shared/naturalcrit/markdownLegacy.js index a6a37d639..5a8108297 100644 --- a/shared/naturalcrit/markdownLegacy.js +++ b/shared/naturalcrit/markdownLegacy.js @@ -49,7 +49,7 @@ const cleanUrl = function (sanitize, base, href) { prot = decodeURIComponent(unescape(href)) .replace(nonWordAndColonTest, '') .toLowerCase(); - } catch (e) { + } catch { return null; } if(prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) { @@ -58,7 +58,7 @@ const cleanUrl = function (sanitize, base, href) { } try { href = encodeURI(href).replace(/%25/g, '%'); - } catch (e) { + } catch { return null; } return href; diff --git a/themes/V3/5ePHB/style.less b/themes/V3/5ePHB/style.less index 555866ba4..74856cdac 100644 --- a/themes/V3/5ePHB/style.less +++ b/themes/V3/5ePHB/style.less @@ -418,6 +418,7 @@ color : var(--HB_Color_Footnotes); } .footnote { + text-transform: uppercase; position : absolute; right : 80px; bottom : 32px; diff --git a/themes/V3/Blank/style.less b/themes/V3/Blank/style.less index 183a85daf..858ecebfb 100644 --- a/themes/V3/Blank/style.less +++ b/themes/V3/Blank/style.less @@ -677,3 +677,17 @@ h6, } .toc.wide li { break-inside : auto; } } + + +/********************************** +Firefox endruns +**********************************/ + +@supports (-moz-user-select: none) { // This section will only apply to Firefox; it's the only browser that supports `-mos-xyz...` + .page { + blockquote, table { + page-break-inside: auto; + break-inside: auto; + } + } +} \ No newline at end of file diff --git a/themes/fonts/iconFonts/diceFont.js b/themes/fonts/iconFonts/diceFont.js index a349d7b0a..65b56a62a 100644 --- a/themes/fonts/iconFonts/diceFont.js +++ b/themes/fonts/iconFonts/diceFont.js @@ -14,6 +14,7 @@ const diceFont = { 'df_d10_7' : 'df d10-7', 'df_d10_8' : 'df d10-8', 'df_d10_9' : 'df d10-9', + 'df_d10_0' : 'df d10-0', 'df_d12' : 'df d12', 'df_d12_1' : 'df d12-1', 'df_d12_10' : 'df d12-10', @@ -90,7 +91,108 @@ const diceFont = { 'df_solid_small_dot_d6_3' : 'df solid-small-dot-d6-3', 'df_solid_small_dot_d6_4' : 'df solid-small-dot-d6-4', 'df_solid_small_dot_d6_5' : 'df solid-small-dot-d6-5', - 'df_solid_small_dot_d6_6' : 'df solid-small-dot-d6-6' + 'df_solid_small_dot_d6_6' : 'df solid-small-dot-d6-6', + 'df_d10_00' : 'df d10-00', + 'df_d10_01' : 'df d10-01', + 'df_d10_02' : 'df d10-02', + 'df_d10_03' : 'df d10-03', + 'df_d10_04' : 'df d10-04', + 'df_d10_05' : 'df d10-05', + 'df_d10_06' : 'df d10-06', + 'df_d10_07' : 'df d10-07', + 'df_d10_08' : 'df d10-08', + 'df_d10_09' : 'df d10-09', + 'df_d10_10' : 'df d10-10', + 'df_d10_11' : 'df d10-11', + 'df_d10_12' : 'df d10-12', + 'df_d10_13' : 'df d10-13', + 'df_d10_14' : 'df d10-14', + 'df_d10_15' : 'df d10-15', + 'df_d10_16' : 'df d10-16', + 'df_d10_17' : 'df d10-17', + 'df_d10_18' : 'df d10-18', + 'df_d10_19' : 'df d10-19', + 'df_d10_20' : 'df d10-20', + 'df_d10_21' : 'df d10-21', + 'df_d10_22' : 'df d10-22', + 'df_d10_23' : 'df d10-23', + 'df_d10_24' : 'df d10-24', + 'df_d10_25' : 'df d10-25', + 'df_d10_26' : 'df d10-26', + 'df_d10_27' : 'df d10-27', + 'df_d10_28' : 'df d10-28', + 'df_d10_29' : 'df d10-29', + 'df_d10_30' : 'df d10-30', + 'df_d10_31' : 'df d10-31', + 'df_d10_32' : 'df d10-32', + 'df_d10_33' : 'df d10-33', + 'df_d10_34' : 'df d10-34', + 'df_d10_35' : 'df d10-35', + 'df_d10_36' : 'df d10-36', + 'df_d10_37' : 'df d10-37', + 'df_d10_38' : 'df d10-38', + 'df_d10_39' : 'df d10-39', + 'df_d10_40' : 'df d10-40', + 'df_d10_41' : 'df d10-41', + 'df_d10_42' : 'df d10-42', + 'df_d10_43' : 'df d10-43', + 'df_d10_44' : 'df d10-44', + 'df_d10_45' : 'df d10-45', + 'df_d10_46' : 'df d10-46', + 'df_d10_47' : 'df d10-47', + 'df_d10_48' : 'df d10-48', + 'df_d10_49' : 'df d10-49', + 'df_d10_50' : 'df d10-50', + 'df_d10_51' : 'df d10-51', + 'df_d10_52' : 'df d10-52', + 'df_d10_53' : 'df d10-53', + 'df_d10_54' : 'df d10-54', + 'df_d10_55' : 'df d10-55', + 'df_d10_56' : 'df d10-56', + 'df_d10_57' : 'df d10-57', + 'df_d10_58' : 'df d10-58', + 'df_d10_59' : 'df d10-59', + 'df_d10_60' : 'df d10-60', + 'df_d10_61' : 'df d10-61', + 'df_d10_62' : 'df d10-62', + 'df_d10_63' : 'df d10-63', + 'df_d10_64' : 'df d10-64', + 'df_d10_65' : 'df d10-65', + 'df_d10_66' : 'df d10-66', + 'df_d10_67' : 'df d10-67', + 'df_d10_68' : 'df d10-68', + 'df_d10_69' : 'df d10-69', + 'df_d10_70' : 'df d10-70', + 'df_d10_71' : 'df d10-71', + 'df_d10_72' : 'df d10-72', + 'df_d10_73' : 'df d10-73', + 'df_d10_74' : 'df d10-74', + 'df_d10_75' : 'df d10-75', + 'df_d10_76' : 'df d10-76', + 'df_d10_77' : 'df d10-77', + 'df_d10_78' : 'df d10-78', + 'df_d10_79' : 'df d10-79', + 'df_d10_80' : 'df d10-80', + 'df_d10_81' : 'df d10-81', + 'df_d10_82' : 'df d10-82', + 'df_d10_83' : 'df d10-83', + 'df_d10_84' : 'df d10-84', + 'df_d10_85' : 'df d10-85', + 'df_d10_86' : 'df d10-86', + 'df_d10_87' : 'df d10-87', + 'df_d10_88' : 'df d10-88', + 'df_d10_89' : 'df d10-89', + 'df_d10_90' : 'df d10-90', + 'df_d10_91' : 'df d10-91', + 'df_d10_92' : 'df d10-92', + 'df_d10_93' : 'df d10-93', + 'df_d10_94' : 'df d10-94', + 'df_d10_95' : 'df d10-95', + 'df_d10_96' : 'df d10-96', + 'df_d10_97' : 'df d10-97', + 'df_d10_98' : 'df d10-98', + 'df_d10_99' : 'df d10-99', + 'df_d10_100' : 'df d10-100' }; export default diceFont; \ No newline at end of file diff --git a/themes/fonts/iconFonts/diceFont.less b/themes/fonts/iconFonts/diceFont.less index 3b60093d0..a49be2da3 100644 --- a/themes/fonts/iconFonts/diceFont.less +++ b/themes/fonts/iconFonts/diceFont.less @@ -6,6 +6,14 @@ src : url('../../../fonts/iconFonts/diceFont.woff2'); } +@font-face { + font-family : 'DiceFontD100'; + src : url('../../../fonts/iconFonts/diceFontD100.woff2') format('woff2'); + font-weight : normal; + font-style : normal; + font-display : block; + } + .df { display : inline; font-family : 'DiceFont'; @@ -26,17 +34,6 @@ &.F-plus::before { content : '\f192'; } &.F-zero::before { content : '\f193'; } &.d10::before { content : '\f194'; } - &.d10-0::before { content : '\f100'; } - &.d10-1::before { content : '\f101'; } - &.d10-10::before { content : '\f102'; } - &.d10-2::before { content : '\f103'; } - &.d10-3::before { content : '\f104'; } - &.d10-4::before { content : '\f105'; } - &.d10-5::before { content : '\f106'; } - &.d10-6::before { content : '\f107'; } - &.d10-7::before { content : '\f108'; } - &.d10-8::before { content : '\f109'; } - &.d10-9::before { content : '\f10a'; } &.d12::before { content : '\f195'; } &.d12-1::before { content : '\f10b'; } &.d12-10::before { content : '\f10c'; } @@ -114,4 +111,568 @@ &.solid-small-dot-d6-4::before { content : '\f18c'; } &.solid-small-dot-d6-5::before { content : '\f18d'; } &.solid-small-dot-d6-6::before { content : '\f18e'; } + + // Replacement d10 + + &.d10-0::before { + font-family : 'DiceFontD100'; + content : '\e900'; + } + + &.d10-1::before { + font-family : 'DiceFontD100'; + content : '\e901'; + } + + &.d10-2::before { + font-family : 'DiceFontD100'; + content : '\e902'; + } + + &.d10-3::before { + font-family : 'DiceFontD100'; + content : '\e903'; + } + + &.d10-4::before { + font-family : 'DiceFontD100'; + content : '\e904'; + } + + &.d10-5::before { + font-family : 'DiceFontD100'; + content : '\e905'; + } + + &.d10-6::before { + font-family : 'DiceFontD100'; + content : '\e906'; + } + + &.d10-7::before { + font-family : 'DiceFontD100'; + content : '\e907'; + } + + &.d10-8::before { + font-family : 'DiceFontD100'; + content : '\e908'; + } + + &.d10-9::before { + font-family : 'DiceFontD100'; + content : '\e909'; + } + + &.d10-10::before { + font-family : 'DiceFontD100'; + content : '\e90a'; + } + + // d100 + + &.d10-00::before { + font-family : 'DiceFontD100'; + content : '\e90b'; + } + + &.d10-01::before { + font-family : 'DiceFontD100'; + content : '\e90c'; + } + + &.d10-02::before { + font-family : 'DiceFontD100'; + content : '\e90d'; + } + + &.d10-03::before { + font-family : 'DiceFontD100'; + content : '\e90e'; + } + + &.d10-04::before { + font-family : 'DiceFontD100'; + content : '\e90f'; + } + + &.d10-05::before { + font-family : 'DiceFontD100'; + content : '\e910'; + } + + &.d10-06::before { + font-family : 'DiceFontD100'; + content : '\e911'; + } + + &.d10-07::before { + font-family : 'DiceFontD100'; + content : '\e912'; + } + + &.d10-08::before { + font-family : 'DiceFontD100'; + content : '\e913'; + } + + &.d10-09::before { + font-family : 'DiceFontD100'; + content : '\e914'; + } + + &.d10-10::before { + font-family : 'DiceFontD100'; + content : '\e915'; + } + + &.d10-11::before { + font-family : 'DiceFontD100'; + content : '\e916'; + } + + &.d10-12::before { + font-family : 'DiceFontD100'; + content : '\e917'; + } + + &.d10-13::before { + font-family : 'DiceFontD100'; + content : '\e918'; + } + + &.d10-14::before { + font-family : 'DiceFontD100'; + content : '\e919'; + } + + &.d10-15::before { + font-family : 'DiceFontD100'; + content : '\e91a'; + } + + &.d10-16::before { + font-family : 'DiceFontD100'; + content : '\e91b'; + } + + &.d10-17::before { + font-family : 'DiceFontD100'; + content : '\e91c'; + } + + &.d10-18::before { + font-family : 'DiceFontD100'; + content : '\e91d'; + } + + &.d10-19::before { + font-family : 'DiceFontD100'; + content : '\e91e'; + } + + &.d10-20::before { + font-family : 'DiceFontD100'; + content : '\e91f'; + } + + &.d10-21::before { + font-family : 'DiceFontD100'; + content : '\e920'; + } + + &.d10-22::before { + font-family : 'DiceFontD100'; + content : '\e921'; + } + + &.d10-23::before { + font-family : 'DiceFontD100'; + content : '\e922'; + } + + &.d10-24::before { + font-family : 'DiceFontD100'; + content : '\e923'; + } + + &.d10-25::before { + font-family : 'DiceFontD100'; + content : '\e924'; + } + + &.d10-26::before { + font-family : 'DiceFontD100'; + content : '\e925'; + } + + &.d10-27::before { + font-family : 'DiceFontD100'; + content : '\e926'; + } + + &.d10-28::before { + font-family : 'DiceFontD100'; + content : '\e927'; + } + + &.d10-29::before { + font-family : 'DiceFontD100'; + content : '\e928'; + } + + &.d10-30::before { + font-family : 'DiceFontD100'; + content : '\e929'; + } + + &.d10-31::before { + font-family : 'DiceFontD100'; + content : '\e92a'; + } + + &.d10-32::before { + font-family : 'DiceFontD100'; + content : '\e92b'; + } + + &.d10-33::before { + font-family : 'DiceFontD100'; + content : '\e92c'; + } + + &.d10-34::before { + font-family : 'DiceFontD100'; + content : '\e92d'; + } + + &.d10-35::before { + font-family : 'DiceFontD100'; + content : '\e92e'; + } + + &.d10-36::before { + font-family : 'DiceFontD100'; + content : '\e92f'; + } + + &.d10-37::before { + font-family : 'DiceFontD100'; + content : '\e930'; + } + + &.d10-38::before { + font-family : 'DiceFontD100'; + content : '\e931'; + } + + &.d10-39::before { + font-family : 'DiceFontD100'; + content : '\e932'; + } + + &.d10-40::before { + font-family : 'DiceFontD100'; + content : '\e933'; + } + + &.d10-41::before { + font-family : 'DiceFontD100'; + content : '\e934'; + } + + &.d10-42::before { + font-family : 'DiceFontD100'; + content : '\e935'; + } + + &.d10-43::before { + font-family : 'DiceFontD100'; + content : '\e936'; + } + + &.d10-44::before { + font-family : 'DiceFontD100'; + content : '\e937'; + } + + &.d10-45::before { + font-family : 'DiceFontD100'; + content : '\e938'; + } + + &.d10-46::before { + font-family : 'DiceFontD100'; + content : '\e939'; + } + + &.d10-47::before { + font-family : 'DiceFontD100'; + content : '\e93a'; + } + + &.d10-48::before { + font-family : 'DiceFontD100'; + content : '\e93b'; + } + + &.d10-49::before { + font-family : 'DiceFontD100'; + content : '\e93c'; + } + + &.d10-50::before { + font-family : 'DiceFontD100'; + content : '\e93d'; + } + + &.d10-51::before { + font-family : 'DiceFontD100'; + content : '\e93e'; + } + + &.d10-52::before { + font-family : 'DiceFontD100'; + content : '\e93f'; + } + + &.d10-53::before { + font-family : 'DiceFontD100'; + content : '\e940'; + } + + &.d10-54::before { + font-family : 'DiceFontD100'; + content : '\e941'; + } + + &.d10-55::before { + font-family : 'DiceFontD100'; + content : '\e942'; + } + + &.d10-56::before { + font-family : 'DiceFontD100'; + content : '\e943'; + } + + &.d10-57::before { + font-family : 'DiceFontD100'; + content : '\e944'; + } + + &.d10-58::before { + font-family : 'DiceFontD100'; + content : '\e945'; + } + + &.d10-59::before { + font-family : 'DiceFontD100'; + content : '\e946'; + } + + &.d10-60::before { + font-family : 'DiceFontD100'; + content : '\e947'; + } + + &.d10-61::before { + font-family : 'DiceFontD100'; + content : '\e948'; + } + + &.d10-62::before { + font-family : 'DiceFontD100'; + content : '\e949'; + } + + &.d10-63::before { + font-family : 'DiceFontD100'; + content : '\e94a'; + } + + &.d10-64::before { + font-family : 'DiceFontD100'; + content : '\e94b'; + } + + &.d10-65::before { + font-family : 'DiceFontD100'; + content : '\e94c'; + } + + &.d10-66::before { + font-family : 'DiceFontD100'; + content : '\e94d'; + } + + &.d10-67::before { + font-family : 'DiceFontD100'; + content : '\e94e'; + } + + &.d10-68::before { + font-family : 'DiceFontD100'; + content : '\e94f'; + } + + &.d10-69::before { + font-family : 'DiceFontD100'; + content : '\e950'; + } + + &.d10-70::before { + font-family : 'DiceFontD100'; + content : '\e951'; + } + + &.d10-71::before { + font-family : 'DiceFontD100'; + content : '\e952'; + } + + &.d10-72::before { + font-family : 'DiceFontD100'; + content : '\e953'; + } + + &.d10-73::before { + font-family : 'DiceFontD100'; + content : '\e954'; + } + + &.d10-74::before { + font-family : 'DiceFontD100'; + content : '\e955'; + } + + &.d10-75::before { + font-family : 'DiceFontD100'; + content : '\e956'; + } + + &.d10-76::before { + font-family : 'DiceFontD100'; + content : '\e957'; + } + + &.d10-77::before { + font-family : 'DiceFontD100'; + content : '\e958'; + } + + &.d10-78::before { + font-family : 'DiceFontD100'; + content : '\e959'; + } + + &.d10-79::before { + font-family : 'DiceFontD100'; + content : '\e95a'; + } + + &.d10-80::before { + font-family : 'DiceFontD100'; + content : '\e95b'; + } + + &.d10-81::before { + font-family : 'DiceFontD100'; + content : '\e95c'; + } + + &.d10-82::before { + font-family : 'DiceFontD100'; + content : '\e95d'; + } + + &.d10-83::before { + font-family : 'DiceFontD100'; + content : '\e95e'; + } + + &.d10-84::before { + font-family : 'DiceFontD100'; + content : '\e95f'; + } + + &.d10-85::before { + font-family : 'DiceFontD100'; + content : '\e960'; + } + + &.d10-86::before { + font-family : 'DiceFontD100'; + content : '\e961'; + } + + &.d10-87::before { + font-family : 'DiceFontD100'; + content : '\e962'; + } + + &.d10-88::before { + font-family : 'DiceFontD100'; + content : '\e963'; + } + + &.d10-89::before { + font-family : 'DiceFontD100'; + content : '\e964'; + } + + &.d10-90::before { + font-family : 'DiceFontD100'; + content : '\e965'; + } + + &.d10-91::before { + font-family : 'DiceFontD100'; + content : '\e966'; + } + + &.d10-92::before { + font-family : 'DiceFontD100'; + content : '\e967'; + } + + &.d10-93::before { + font-family : 'DiceFontD100'; + content : '\e968'; + } + + &.d10-94::before { + font-family : 'DiceFontD100'; + content : '\e969'; + } + + &.d10-95::before { + font-family : 'DiceFontD100'; + content : '\e96a'; + } + + &.d10-96::before { + font-family : 'DiceFontD100'; + content : '\e96b'; + } + + &.d10-97::before { + font-family : 'DiceFontD100'; + content : '\e96c'; + } + + &.d10-98::before { + font-family : 'DiceFontD100'; + content : '\e96d'; + } + + &.d10-99::before { + font-family : 'DiceFontD100'; + content : '\e96e'; + } + + &.d10-100::before { + font-family : 'DiceFontD100'; + content : '\e96f'; + } } \ No newline at end of file diff --git a/themes/fonts/iconFonts/diceFontD100.woff2 b/themes/fonts/iconFonts/diceFontD100.woff2 new file mode 100644 index 000000000..6f7404cb8 Binary files /dev/null and b/themes/fonts/iconFonts/diceFontD100.woff2 differ