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/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index bda7e3b73..230a30e1f 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -156,6 +156,31 @@ const CodeEditor = forwardRef( return folds; }; + const foldAll = (view = viewRef.current)=>{ + if(!view) return false; + + const doc = view.state.doc; + const pages = pageMap.current; + + const effects = pages.map((start, i)=>{ + const next = pages[i + 1] || doc.length; + const from = i ? doc.line(doc.lineAt(start).number + 1).from : 0; + const to = doc.line(doc.lineAt(next).number).from - 1; + + return to > from ? foldEffect.of({ from, to }) : null; + }).filter(Boolean); + + view.dispatch({ effects }); + return true; + }; + + const unfoldAll = (view = viewRef.current)=>{ + if(!view) return false; + + view.dispatch(unfoldAllCmd(view)); + return true; + }; + const createExtensions = ({ onChange, language, editorTheme })=>{ const setEventListeners = EditorView.updateListener.of((update)=>{ if(update.docChanged) { @@ -199,7 +224,7 @@ const CodeEditor = forwardRef( //keyboard shortcut keymap.of([...defaultKeymap, foldKeymap, ...searchKeymap]), - generalKeymap, + generalKeymap(foldAll, unfoldAll), ...(tab === 'brewStyles' ? [cssKeymap] : [markdownKeymap]), //multiple cursors and selections @@ -404,29 +429,8 @@ const CodeEditor = forwardRef( undone : redoDepth(view.state), }; }, - - foldAll : ()=>{ - const view = viewRef.current; - if(!view) return; - - const doc = view.state.doc; - const pages = pageMap.current; - - const effects = pages.map((start, i)=>{ - const next = pages[i + 1] || doc.length; - const from = i ? doc.line(doc.lineAt(start).number + 1).from : 0; - const to = doc.line(doc.lineAt(next).number).from - 1; - - return to > from ? foldEffect.of({ from, to }) : null; - }).filter(Boolean); - - view.dispatch({ effects }); - }, - unfoldAll : ()=>{ - const view = viewRef.current; - if(!view) return; - view.dispatch(unfoldAllCmd(view)); - }, + foldAll : ()=>foldAll(), + unfoldAll : ()=>unfoldAll(), focus : ()=>viewRef.current.focus(), })); diff --git a/client/components/codeEditor/extensions/customKeyMaps.js b/client/components/codeEditor/extensions/customKeyMaps.js index af7fa7131..e6adec71e 100644 --- a/client/components/codeEditor/extensions/customKeyMaps.js +++ b/client/components/codeEditor/extensions/customKeyMaps.js @@ -215,13 +215,15 @@ const newBreak = (type)=>(view)=>forEachSelection(view, (state, from, to, select }; }); -export const generalKeymap = Prec.high(keymap.of([ +export const generalKeymap = (foldAll, unfoldAll) => Prec.high(keymap.of([ { key: 'Tab', run: insertTab }, { key: 'Shift-Tab', run: indentLess }, { key: 'Mod-z', run: undo }, //it may be unnecessary { key: 'Mod-Shift-z', run: redo }, { key: 'Mod-y', run: redo }, //user asked, so double keybind { key: 'Mod-d', run: deleteLine }, //annoyingly overrides "selectNextOccurrence" because users asked + { key: 'Mod-Shift-[', run: foldAll }, + { key: 'Mod-Shift-]', run: unfoldAll }, ])); export const cssKeymap = Prec.highest(keymap.of([ diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.jsx b/client/homebrew/editor/metadataEditor/metadataEditor.jsx index d713c1cc9..d5c4387e9 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.jsx +++ b/client/homebrew/editor/metadataEditor/metadataEditor.jsx @@ -2,13 +2,14 @@ import '../uiEditor.less'; 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)=>{ @@ -19,32 +20,25 @@ const callIfExists = (val, fn, ...args)=>{ const MetadataEditor = ({ metadata = { - editId : null, shareId : null, + editId : null, title : '', description : '', - thumbnail : '', tags : [], - published : false, - authors : [], - renderer : 'legacy', - theme : '5ePHB', lang : 'en', + renderer : 'legacy', + authors : [], + published : false, + thumbnail : '', + theme : '5ePHB', }, - onChange = ()=>{}, reportError = ()=>{}, - ...props + userThemes = {}, + themeBundle = {} })=>{ const [showThumbnail, setShowThumbnail] = useState(true); - const toggleThumbnailDisplay = ()=>{ - setShowThumbnail(!showThumbnail); - }; - const renderThumbnail = ()=>{ - if(!showThumbnail) return; - return ; - }; const handleFieldChange = (name, e)=>{ // load validation rules, and check input value against them const inputRules = validations[name] ?? []; @@ -90,13 +84,11 @@ const MetadataEditor = ({ 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 metadata.theme = shareId; - onChange(metadata, 'theme'); }; const handleLanguage = (languageCode)=>{ @@ -141,18 +133,6 @@ const MetadataEditor = ({ ; } }; - const renderDelete = ()=>{ - if(!metadata.editId) return; - - return
- -
- -
-
; - }; const renderAuthors = ()=>{ const authors = metadata.authors; const isOwner = global.account?.username && global.account?.username === authors[0]; @@ -204,7 +184,7 @@ const MetadataEditor = ({ ); }; const renderThemeDropdown = ()=>{ - const mergedThemes = _.merge(Themes, props.userThemes); + const mergedThemes = _.merge(Themes, userThemes); const listThemes = (renderer)=>{ return _.map(_.values(mergedThemes[renderer]), (theme)=>{ @@ -225,7 +205,7 @@ const MetadataEditor = ({ }; const currentRenderer = metadata.renderer; - const currentThemeDisplay = props.themeBundle?.name ? `${props.themeBundle.author ?? currentRenderer} : ${props.themeBundle.name}` : 'No Theme Selected'; + const currentThemeDisplay = themeBundle?.name ? `${themeBundle.author ?? currentRenderer} : ${themeBundle.name}` : 'No Theme Selected'; let dropdown; if(currentRenderer == 'legacy') { @@ -296,7 +276,6 @@ const MetadataEditor = ({ }} /> - ; }; const renderRenderOptions = ()=>{ @@ -364,15 +343,14 @@ const MetadataEditor = ({ /> - {renderThumbnail()} + {showThumbnail && } -
@@ -388,17 +366,12 @@ const MetadataEditor = ({ />
- {renderLanguageDropdown()} - {renderThemeDropdown()} - {renderRenderOptions()}

Authors

- {renderAuthors()} -
@@ -418,7 +391,6 @@ const MetadataEditor = ({

Privacy

-
@@ -430,8 +402,14 @@ const MetadataEditor = ({
- - {renderDelete()} + {metadata.editId &&
+ +
+ +
+
}
); }; diff --git a/client/homebrew/homebrew.jsx b/client/homebrew/homebrew.jsx index 1ee5efa2a..1ff80a483 100644 --- a/client/homebrew/homebrew.jsx +++ b/client/homebrew/homebrew.jsx @@ -69,25 +69,29 @@ const Homebrew = (props)=>{ ); } - return (
- } /> - } /> - } /> - } /> - } /> - }/> - } /> - } /> - } /> - } /> + } /> + } /> } /> - } /> - } /> - } /> + + } /> + } /> + + } /> + + } /> + } /> + } /> + } /> + + } /> + } /> + + } /> + }/>
diff --git a/client/homebrew/pages/basePages/editPage/editorPage.jsx b/client/homebrew/pages/basePages/editPage/editorPage.jsx index b9fb34597..536f4e34b 100644 --- a/client/homebrew/pages/basePages/editPage/editorPage.jsx +++ b/client/homebrew/pages/basePages/editPage/editorPage.jsx @@ -112,12 +112,15 @@ const EditorPage = (props)=>{ const snipStorage = localStorage.getItem(SNIPKEY); const metaStorage = JSON.parse(localStorage.getItem(METAKEY)); - brew.text = brewStorage ?? brew.text; - brew.style = styleStorage ?? brew.style; - brew.snippets = snipStorage ?? brew.snippets; - brew.renderer = metaStorage?.renderer ?? brew.renderer; - brew.theme = metaStorage?.theme ?? brew.theme; - brew.lang = metaStorage?.lang ?? brew.lang; + brew.title = metaStorage?.title ?? brew.title; + brew.description = metaStorage?.description ?? brew.description; + brew.tags = metaStorage?.tags ?? brew.tags; + brew.text = brewStorage ?? brew.text; + brew.style = styleStorage ?? brew.style; + brew.snippets = snipStorage ?? brew.snippets; + brew.renderer = metaStorage?.renderer ?? brew.renderer; + brew.theme = metaStorage?.theme ?? brew.theme; + brew.lang = metaStorage?.lang ?? brew.lang; } const SAVEKEY = `${SAVEKEY_PREFIX}${global.account?.username}`; @@ -130,7 +133,7 @@ const EditorPage = (props)=>{ localStorage.setItem(BREWKEY, brew.text); if(brew.style) localStorage.setItem(STYLEKEY, brew.style); if(brew.snippets) localStorage.setItem(SNIPKEY, brew.snippets); - localStorage.setItem(METAKEY, JSON.stringify({ renderer: brew.renderer, theme: brew.theme, lang: brew.lang })); + localStorage.setItem(METAKEY, JSON.stringify({ title: brew.title, description: brew.description, tags: brew.tags, renderer: brew.renderer, theme: brew.theme, lang: brew.lang })); if(window.location.pathname !== '/new') window.history.replaceState({}, window.location.title, '/new/'); }; @@ -177,9 +180,12 @@ const EditorPage = (props)=>{ 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 + title : value.title, + description : value.description, + tags : value.tags, + renderer : value.renderer, + theme : value.theme, + lang : value.lang })); } }; diff --git a/index.html b/index.html index f30a42c91..1f79c174f 100644 --- a/index.html +++ b/index.html @@ -6,14 +6,11 @@ name="viewport" content="width=device-width, initial-scale=1, height=device-height, interactive-widget=resizes-visual" /> - The Homebrewery - NaturalCrit -
-