diff --git a/changelog.md b/changelog.md index 698cbb128..08b9314f9 100644 --- a/changelog.md +++ b/changelog.md @@ -90,9 +90,30 @@ pre { } ``` + ## changelog For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery). +### Tuesday 10/20/2026 - v3.24.2 +{{taskList +##### G-Ambatte +* [x] Hotfix for memory leak on server + +Fixes issue [#5090](https://github.com/naturalcrit/homebrewery/issues/5090) + +##### Calculuschild +* [x] Fix brew variables not working across pages + +* [x] Allow brew variable definitions to be on adjacent lines (no blank line required anymore) + +##### 5e-Cleric +* [x] Partial rollout of some site UI changes (includes alternate views and darkmode in {{openSans **BREWS** :fas_beer_mug_empty:}} page, and changes to {{openSans **VAULT** :fas_dungeon:}}) + +##### Abquintic, Calculuschild, 5e-Cleric +* [x] Multiple background fixes and code cleanups +}} + + ### Tuesday 9/29/2026 - v3.24.1 {{taskList ##### calculuschild @@ -169,7 +190,7 @@ Fixes issue [#3840](https://github.com/naturalcrit/homebrewery/issues/3840) Fixes issue [#2037](https://github.com/naturalcrit/homebrewery/issues/2037) -* [x] Remember editor theme across tabs +* [x] Remember editor theme across tabs, and new {{openSans **SETTINGS :fas_gear:**}} tab with editor settings Fixes issue [#4858](https://github.com/naturalcrit/homebrewery/issues/4858) diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.jsx b/client/homebrew/editor/metadataEditor/metadataEditor.jsx index a14eb3f09..d5c4387e9 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.jsx +++ b/client/homebrew/editor/metadataEditor/metadataEditor.jsx @@ -1,15 +1,15 @@ /* eslint-disable max-lines */ import '../uiEditor.less'; -import React from 'react'; -import createReactClass from 'create-react-class'; +import React, { useState } from 'react'; import _ from 'lodash'; -import request from '../../utils/request-middleware.js'; -import Combobox from '@components/combobox.jsx'; -import TagInput from '../tagInput/tagInput.jsx'; +import request from '../../utils/request-middleware.js'; import Themes from '@themes/themes.json'; import validations from './validations.js'; +import Combobox from '@components/combobox.jsx'; +import TagInput from '../tagInput/tagInput.jsx'; + import homebreweryThumbnail from '../../thumbnail.png'; const callIfExists = (val, fn, ...args)=>{ @@ -18,48 +18,28 @@ const callIfExists = (val, fn, ...args)=>{ } }; -const MetadataEditor = createReactClass({ - displayName : 'MetadataEditor', - getDefaultProps : function() { - return { - metadata : { - editId : null, - shareId : null, - title : '', - description : '', - thumbnail : '', - tags : [], - published : false, - authors : [], - renderer : 'legacy', - theme : '5ePHB', - lang : 'en' - }, - - onChange : ()=>{}, - reportError : ()=>{} - }; +const MetadataEditor = ({ + metadata = { + shareId : null, + editId : null, + title : '', + description : '', + tags : [], + lang : 'en', + renderer : 'legacy', + authors : [], + published : false, + thumbnail : '', + theme : '5ePHB', }, + onChange = ()=>{}, + reportError = ()=>{}, + userThemes = {}, + themeBundle = {} +})=>{ + const [showThumbnail, setShowThumbnail] = useState(true); - getInitialState : function(){ - return { - isOwner : global.account?.username && global.account?.username === this.props.metadata?.authors[0], - showThumbnail : true - }; - }, - - toggleThumbnailDisplay : function(){ - this.setState({ - showThumbnail : !this.state.showThumbnail - }); - }, - - renderThumbnail : function(){ - if(!this.state.showThumbnail) return; - return ; - }, - - handleFieldChange : function(name, e){ + const handleFieldChange = (name, e)=>{ // load validation rules, and check input value against them const inputRules = validations[name] ?? []; const validationErr = inputRules.map((rule)=>rule(e.target.value)).filter(Boolean); @@ -72,8 +52,8 @@ const MetadataEditor = createReactClass({ // if no validation rules, save to props if(validationErr.length === 0){ callIfExists(e.target, 'setCustomValidity', ''); - this.props.onChange({ - ...this.props.metadata, + onChange({ + ...metadata, [name] : e.target.value }); return true; @@ -86,45 +66,37 @@ const MetadataEditor = createReactClass({ debouncedReportValidity(e.target, errMessage); return false; } - }, - - handleRenderer : function(renderer, e){ + }; + const handleRenderer = (renderer, e)=>{ if(e.target.checked){ - this.props.metadata.renderer = renderer; + metadata.renderer = renderer; if(renderer == 'legacy') - this.props.metadata.theme = '5ePHB'; + metadata.theme = '5ePHB'; } - this.props.onChange(this.props.metadata, 'renderer'); - }, - - handlePublish : function(val){ - this.props.onChange({ - ...this.props.metadata, + onChange(metadata, 'renderer'); + }; + const handlePublish = (val)=>{ + onChange({ + ...metadata, published : val }); - }, - - handleTheme : function(theme){ - this.props.metadata.renderer = theme.renderer; - this.props.metadata.theme = theme.path; - - this.props.onChange(this.props.metadata, 'theme'); - }, - - handleThemeWritein : function(e) { + }; + const handleTheme = (theme)=>{ + metadata.renderer = theme.renderer; + metadata.theme = theme.path; + onChange(metadata, 'theme'); + }; + const handleThemeWritein = (e)=>{ const shareId = e.target.value.split('/').pop(); //Extract just the ID if a URL was pasted in - this.props.metadata.theme = shareId; - - this.props.onChange(this.props.metadata, 'theme'); - }, - - handleLanguage : function(languageCode){ - this.props.metadata.lang = languageCode; - this.props.onChange(this.props.metadata); - }, - - handleDelete : function(){ - if(this.props.metadata.authors && this.props.metadata.authors.length <= 1){ + metadata.theme = shareId; + onChange(metadata, 'theme'); + }; + const handleLanguage = (languageCode)=>{ + metadata.lang = languageCode; + onChange(metadata); + }; + const handleDelete = ()=>{ + if(metadata.authors && metadata.authors.length <= 1){ if(!confirm('Are you sure you want to delete this brew? Because you are the only owner of this brew, the document will be deleted permanently.')) return; if(!confirm('Are you REALLY sure? You will not be able to recover the document.')) return; } else { @@ -132,54 +104,39 @@ const MetadataEditor = createReactClass({ if(!confirm('Are you REALLY sure? You will lose editor access to this document.')) return; } - request.delete(`/api/${this.props.metadata.googleId ?? ''}${this.props.metadata.editId}`) + request.delete(`/api/${metadata.googleId ?? ''}${metadata.editId}`) .send() .end((err, res)=>{ if(err) { - this.props.reportError(err); + reportError(err); } else { window.location.href = '/'; } }); - }, - - handleDeleteAuthor : function(author){ + }; + const handleDeleteAuthor = (author)=>{ if(!confirm('Are you sure you want to remove this author? They will lose all edit access to this brew, and it will dissapear from their userpage.')) return; - if(!this.props.metadata.authors.includes(author)) return; - this.props.onChange({ - ...this.props.metadata, - authors : this.props.metadata.authors.filter((a)=>a !== author) + if(!metadata.authors.includes(author)) return; + onChange({ + ...metadata, + authors : metadata.authors.filter((a)=>a !== author) }); - }, - - renderPublish : function(){ - if(this.props.metadata.published){ - return