From 73c68fd11c7517e465bf5bcf690ee24f190bb6f9 Mon Sep 17 00:00:00 2001 From: David Bolack Date: Wed, 27 Nov 2024 21:35:29 -0600 Subject: [PATCH 01/24] Functional first pass. Needs: - [ ] opinions on UI placement - [ ] opinions on best choice for displaying a write-in based User Brew ( flip to writin box? Add to drop-down list? ) --- .../editor/metadataEditor/metadataEditor.jsx | 35 ++++++++++++++++--- server/homebrew.api.js | 4 +-- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.jsx b/client/homebrew/editor/metadataEditor/metadataEditor.jsx index bfc3b8b61..0405b942b 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.jsx +++ b/client/homebrew/editor/metadataEditor/metadataEditor.jsx @@ -47,7 +47,8 @@ const MetadataEditor = createClass({ getInitialState : function(){ return { - showThumbnail : true + showThumbnail : true, + showThemeWritein : false }; }, @@ -57,6 +58,12 @@ const MetadataEditor = createClass({ }); }, + toggleThemeWritein : function(){ + this.setState({ + showThemeWritein : !this.state.showThemeWritein + }); + }, + renderThumbnail : function(){ if(!this.state.showThumbnail) return; return ; @@ -115,6 +122,12 @@ const MetadataEditor = createClass({ this.props.onChange(this.props.metadata, 'theme'); }, + handleThemeWritein : function(e) { + this.props.metadata.renderer = 'V3'; + this.props.metadata.theme = e.target.value; + this.props.onChange(this.props.metadata, 'theme'); + }, + handleLanguage : function(languageCode){ this.props.metadata.lang = languageCode; this.props.onChange(this.props.metadata); @@ -213,6 +226,7 @@ const MetadataEditor = createClass({ }); }; + const currentRenderer = this.props.metadata.renderer; const currentTheme = mergedThemes[`${_.upperFirst(this.props.metadata.renderer)}`][this.props.metadata.theme] ?? { name: `!!! THEME MISSING !!! ID=${this.props.metadata.theme}` }; @@ -234,10 +248,23 @@ const MetadataEditor = createClass({ return
- {dropdown} + {!this.state.showThemeWritein?dropdown:''} + + {this.renderThemeWritein()}
; }, + renderThemeWritein : function(){ + if(!this.state.showThemeWritein) return; + return this.handleThemeWritein(e)} />; + }, + renderLanguageDropdown : function(){ const langCodes = ['en', 'de', 'de-ch', 'fr', 'ja', 'es', 'it', 'sv', 'ru', 'zh-Hans', 'zh-Hant']; const listLanguages = ()=>{ @@ -345,7 +372,7 @@ const MetadataEditor = createClass({ placeholder='add tag' unique={true} values={this.props.metadata.tags} onChange={(e)=>this.handleFieldChange('tags', e)} - /> + />
@@ -370,7 +397,7 @@ const MetadataEditor = createClass({ values={this.props.metadata.invitedAuthors} notes={['Invited author usernames are case sensitive.', 'After adding an invited author, send them the edit link. There, they can choose to accept or decline the invitation.']} onChange={(e)=>this.handleFieldChange('invitedAuthors', e)} - /> + />

Privacy

diff --git a/server/homebrew.api.js b/server/homebrew.api.js index b8d4024ad..f997de1c4 100644 --- a/server/homebrew.api.js +++ b/server/homebrew.api.js @@ -1,6 +1,6 @@ /* eslint-disable max-lines */ import _ from 'lodash'; -import {model as HomebrewModel} from './homebrew.model.js'; +import { model as HomebrewModel } from './homebrew.model.js'; import express from 'express'; import zlib from 'zlib'; import GoogleActions from './googleActions.js'; @@ -302,7 +302,7 @@ const api = { splitTextStyleAndMetadata(currentTheme); // If there is anything in the snippets or style members, append them to the appropriate array - if(currentTheme?.snippets) completeSnippets.push(JSON.parse(currentTheme.snippets)); + // if(currentTheme?.snippets) completeSnippets.push(JSON.parse(currentTheme.snippets)); if(currentTheme?.style) completeStyles.push(`/* From Brew: ${req.protocol}://${req.get('host')}/share/${req.params.id} */\n\n${currentTheme.style}`); req.params.id = currentTheme.theme; From 7c357a2aa130a072cad58779c377d0888e61992c Mon Sep 17 00:00:00 2001 From: David Bolack Date: Sun, 29 Dec 2024 23:23:48 -0600 Subject: [PATCH 02/24] Attempt to save state but seems to break brew. --- .../editor/metadataEditor/metadataEditor.jsx | 13 ++++++++++--- .../editor/metadataEditor/metadataEditor.less | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.jsx b/client/homebrew/editor/metadataEditor/metadataEditor.jsx index 0405b942b..0bd91ae35 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.jsx +++ b/client/homebrew/editor/metadataEditor/metadataEditor.jsx @@ -40,6 +40,7 @@ const MetadataEditor = createClass({ theme : '5ePHB', lang : 'en' }, + onChange : ()=>{}, reportError : ()=>{} }; @@ -47,8 +48,10 @@ const MetadataEditor = createClass({ getInitialState : function(){ return { - showThumbnail : true, - showThemeWritein : false + showThumbnail : true, + showThemeWritein : false, + lastThemePulldown : '', + lastThemeWriteIn : '' }; }, @@ -60,8 +63,12 @@ const MetadataEditor = createClass({ toggleThemeWritein : function(){ this.setState({ - showThemeWritein : !this.state.showThemeWritein + showThemeWritein : !this.state.showThemeWritein, + // lastThemePulldown : !this.state.showThemeWritein ? this.props.metadata.theme : this.state.lastThemePulldown, + // lastThemeWriteIn : !this.state.showThemeWritein ? this.state.lastThemePulldown : this.props.metadata.theme }); + // if(this.state.showThemeWritein) this.props.metadata.theme = this.state.lastThemeWriteIn; + // else this.props.metadata.theme = this.state.lastThemePulldown; }, renderThumbnail : function(){ diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.less b/client/homebrew/editor/metadataEditor/metadataEditor.less index 2cff01cfe..13397c89f 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.less +++ b/client/homebrew/editor/metadataEditor/metadataEditor.less @@ -71,7 +71,7 @@ border : 1px solid gray; &:focus { outline : 1px solid #444444; } } - &.thumbnail { + &.thumbnail, &.themes{ height : 1.4em; label { line-height : 2.0em; } .value { From fe2d02a24c570c829eca48a7481cf7f99e245cbf Mon Sep 17 00:00:00 2001 From: David Bolack Date: Mon, 30 Dec 2024 12:28:47 -0600 Subject: [PATCH 03/24] Work in progress. Still issues with saving the state of the theme pulldowns and collecting the written in theme. --- .../editor/metadataEditor/metadataEditor.jsx | 27 +++++++++++++------ .../editor/metadataEditor/metadataEditor.less | 11 ++++++++ client/homebrew/pages/editPage/editPage.jsx | 5 ++-- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.jsx b/client/homebrew/editor/metadataEditor/metadataEditor.jsx index 0bd91ae35..08b9c7165 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.jsx +++ b/client/homebrew/editor/metadataEditor/metadataEditor.jsx @@ -62,13 +62,17 @@ const MetadataEditor = createClass({ }, toggleThemeWritein : function(){ + console.log('toggleThemeWritein'); this.setState({ showThemeWritein : !this.state.showThemeWritein, - // lastThemePulldown : !this.state.showThemeWritein ? this.props.metadata.theme : this.state.lastThemePulldown, - // lastThemeWriteIn : !this.state.showThemeWritein ? this.state.lastThemePulldown : this.props.metadata.theme + lastThemePulldown : this.state.showThemeWritein ? this.props.metadata.theme : this.state.lastThemePulldown, + lastThemeWriteIn : this.state.showThemeWritein ? this.state.lastThemePulldown : this.props.metadata.theme }); - // if(this.state.showThemeWritein) this.props.metadata.theme = this.state.lastThemeWriteIn; - // else this.props.metadata.theme = this.state.lastThemePulldown; + console.log(this.state); + console.log(this.props.metadata); + if(!this.state.showThemeWritein) this.props.metadata.theme = this.state.lastThemeWriteIn; + else this.props.metadata.theme = this.state.lastThemePulldown; + this.props.onChange(this.props.metadata, 'theme'); }, renderThumbnail : function(){ @@ -130,9 +134,13 @@ const MetadataEditor = createClass({ }, handleThemeWritein : function(e) { + console.log('Enter!'); this.props.metadata.renderer = 'V3'; this.props.metadata.theme = e.target.value; + console.log(e.target.value); + this.props.onChange(this.props.metadata, 'renderer'); this.props.onChange(this.props.metadata, 'theme'); + console.log(this.props.metadata); }, handleLanguage : function(languageCode){ @@ -214,6 +222,7 @@ const MetadataEditor = createClass({ if(!global.enable_themes) return; const mergedThemes = _.merge(Themes, this.props.userThemes); + console.log(mergedThemes); const listThemes = (renderer)=>{ return _.map(_.values(mergedThemes[renderer]), (theme)=>{ @@ -230,11 +239,11 @@ const MetadataEditor = createClass({
; - }); + }).filter(Boolean); }; - const currentRenderer = this.props.metadata.renderer; + console.log(this.props.metadata.theme); const currentTheme = mergedThemes[`${_.upperFirst(this.props.metadata.renderer)}`][this.props.metadata.theme] ?? { name: `!!! THEME MISSING !!! ID=${this.props.metadata.theme}` }; let dropdown; @@ -250,13 +259,14 @@ const MetadataEditor = createClass({
{currentTheme.author ?? _.upperFirst(currentRenderer)} : {currentTheme.name}
{listThemes(currentRenderer)} + {console.log(listThemes(currentRenderer))} ; } return
{!this.state.showThemeWritein?dropdown:''} - {this.renderThemeWritein()} @@ -268,7 +278,8 @@ const MetadataEditor = createClass({ return this.handleThemeWritein(e)} />; }, diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.less b/client/homebrew/editor/metadataEditor/metadataEditor.less index 13397c89f..9f13fbc49 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.less +++ b/client/homebrew/editor/metadataEditor/metadataEditor.less @@ -88,6 +88,17 @@ } } + &.themes{ + .value { + overflow : visible; + text-overflow : auto; + } + button { + padding-left: 5px; + padding-right: 5px; + } + } + &.description { flex : 1; textarea.value { diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index cf2b559c3..585d5205d 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -151,9 +151,10 @@ const EditPage = createClass({ }, handleMetaChange : function(metadata, field=undefined){ - if(field == 'theme' || field == 'renderer') // Fetch theme bundle only if theme or renderer was changed + if(field == 'theme' || field == 'renderer') {// Fetch theme bundle only if theme or renderer was changed + console.log(metadata); fetchThemeBundle(this, metadata.renderer, metadata.theme); - + } this.setState((prevState)=>({ brew : { ...prevState.brew, From b9f7e820c7d9f4320386941c54e8b3de1ebc384a Mon Sep 17 00:00:00 2001 From: David Bolack Date: Tue, 7 Jan 2025 20:36:38 -0600 Subject: [PATCH 04/24] Functionally? working. --- .../editor/metadataEditor/metadataEditor.jsx | 40 +++++++++---------- client/homebrew/pages/editPage/editPage.jsx | 1 - 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.jsx b/client/homebrew/editor/metadataEditor/metadataEditor.jsx index 08b9c7165..6d8755aba 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.jsx +++ b/client/homebrew/editor/metadataEditor/metadataEditor.jsx @@ -16,6 +16,8 @@ const SYSTEMS = ['5e', '4e', '3.5e', 'Pathfinder']; const homebreweryThumbnail = require('../../thumbnail.png'); +let mergedThemes; + const callIfExists = (val, fn, ...args)=>{ if(val[fn]) { val[fn](...args); @@ -47,11 +49,12 @@ const MetadataEditor = createClass({ }, getInitialState : function(){ + mergedThemes = _.merge(Themes, this.props.userThemes); return { showThumbnail : true, - showThemeWritein : false, - lastThemePulldown : '', - lastThemeWriteIn : '' + showThemeWritein : mergedThemes[this.props.metadata.renderer][this.props.metadata.theme] ? false : true, + lastThemePulldown : mergedThemes[this.props.metadata.renderer][this.props.metadata.theme] ? mergedThemes[this.props.metadata.renderer][this.props.metadata.theme] : '', + lastThemeWriteIn : mergedThemes[this.props.metadata.renderer][this.props.metadata.theme] ? '' : mergedThemes[this.props.metadata.renderer][this.props.metadata.theme] }; }, @@ -62,17 +65,12 @@ const MetadataEditor = createClass({ }, toggleThemeWritein : function(){ - console.log('toggleThemeWritein'); - this.setState({ - showThemeWritein : !this.state.showThemeWritein, - lastThemePulldown : this.state.showThemeWritein ? this.props.metadata.theme : this.state.lastThemePulldown, - lastThemeWriteIn : this.state.showThemeWritein ? this.state.lastThemePulldown : this.props.metadata.theme - }); - console.log(this.state); - console.log(this.props.metadata); if(!this.state.showThemeWritein) this.props.metadata.theme = this.state.lastThemeWriteIn; else this.props.metadata.theme = this.state.lastThemePulldown; this.props.onChange(this.props.metadata, 'theme'); + this.setState({ + showThemeWritein : !this.state.showThemeWritein + }); }, renderThumbnail : function(){ @@ -130,17 +128,22 @@ const MetadataEditor = createClass({ handleTheme : function(theme){ this.props.metadata.renderer = theme.renderer; this.props.metadata.theme = theme.path; + this.setState({ + lastThemePulldown : theme.path + }); + this.props.onChange(this.props.metadata, 'theme'); }, handleThemeWritein : function(e) { - console.log('Enter!'); this.props.metadata.renderer = 'V3'; this.props.metadata.theme = e.target.value; - console.log(e.target.value); + this.setState({ + lastThemeWriteIn : e.target.value + }); + this.props.onChange(this.props.metadata, 'renderer'); this.props.onChange(this.props.metadata, 'theme'); - console.log(this.props.metadata); }, handleLanguage : function(languageCode){ @@ -221,9 +224,6 @@ const MetadataEditor = createClass({ renderThemeDropdown : function(){ if(!global.enable_themes) return; - const mergedThemes = _.merge(Themes, this.props.userThemes); - console.log(mergedThemes); - const listThemes = (renderer)=>{ return _.map(_.values(mergedThemes[renderer]), (theme)=>{ if(theme.path == this.props.metadata.shareId) return; @@ -243,9 +243,8 @@ const MetadataEditor = createClass({ }; const currentRenderer = this.props.metadata.renderer; - console.log(this.props.metadata.theme); const currentTheme = mergedThemes[`${_.upperFirst(this.props.metadata.renderer)}`][this.props.metadata.theme] - ?? { name: `!!! THEME MISSING !!! ID=${this.props.metadata.theme}` }; + ?? { name: `!!! Manually selected theme !!! ID=${this.props.metadata.theme}` }; let dropdown; if(currentRenderer == 'legacy') { @@ -259,7 +258,6 @@ const MetadataEditor = createClass({
{currentTheme.author ?? _.upperFirst(currentRenderer)} : {currentTheme.name}
{listThemes(currentRenderer)} - {console.log(listThemes(currentRenderer))} ; } @@ -279,7 +277,7 @@ const MetadataEditor = createClass({ default='' placeholder='Enter share id' className='value' - defaultValue={this.state.lastThemeWriteIn} + defaultValue={this.state.lastThemeWriteIn || this.props.metadata.theme} onChange={(e)=>this.handleThemeWritein(e)} />; }, diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index 585d5205d..ef6da5099 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -152,7 +152,6 @@ const EditPage = createClass({ handleMetaChange : function(metadata, field=undefined){ if(field == 'theme' || field == 'renderer') {// Fetch theme bundle only if theme or renderer was changed - console.log(metadata); fetchThemeBundle(this, metadata.renderer, metadata.theme); } this.setState((prevState)=>({ From 74122d90579812916b526626144f85ff9f7debba Mon Sep 17 00:00:00 2001 From: David Bolack Date: Tue, 7 Jan 2025 22:11:01 -0600 Subject: [PATCH 05/24] Display name of write in theme next to write-in Clear user's active ThemeBundle when an incomplete/broken/invalid writein. Needs theming help. --- client/homebrew/editor/editor.jsx | 1 + .../editor/metadataEditor/metadataEditor.jsx | 13 ++++++++----- .../editor/metadataEditor/metadataEditor.less | 4 ++++ client/homebrew/pages/editPage/editPage.jsx | 1 + server/homebrew.api.js | 6 +++++- shared/helpers.js | 15 +++++++++++++-- 6 files changed, 32 insertions(+), 8 deletions(-) diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index bba5f3ad9..3e37aba57 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -454,6 +454,7 @@ const Editor = createClass({ rerenderParent={this.rerenderParent} /> diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.jsx b/client/homebrew/editor/metadataEditor/metadataEditor.jsx index 6d8755aba..694444bc9 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.jsx +++ b/client/homebrew/editor/metadataEditor/metadataEditor.jsx @@ -53,8 +53,8 @@ const MetadataEditor = createClass({ return { showThumbnail : true, showThemeWritein : mergedThemes[this.props.metadata.renderer][this.props.metadata.theme] ? false : true, - lastThemePulldown : mergedThemes[this.props.metadata.renderer][this.props.metadata.theme] ? mergedThemes[this.props.metadata.renderer][this.props.metadata.theme] : '', - lastThemeWriteIn : mergedThemes[this.props.metadata.renderer][this.props.metadata.theme] ? '' : mergedThemes[this.props.metadata.renderer][this.props.metadata.theme] + lastThemePulldown : mergedThemes[this.props.metadata.renderer][this.props.metadata.theme] ? mergedThemes[this.props.metadata.renderer][this.props.metadata.theme].path : '', + lastThemeWriteIn : mergedThemes[this.props.metadata.renderer][this.props.metadata.theme] ? '' : this.props.metadata.theme }; }, @@ -244,7 +244,7 @@ const MetadataEditor = createClass({ const currentRenderer = this.props.metadata.renderer; const currentTheme = mergedThemes[`${_.upperFirst(this.props.metadata.renderer)}`][this.props.metadata.theme] - ?? { name: `!!! Manually selected theme !!! ID=${this.props.metadata.theme}` }; + ?? { name: `${this.props.themeBundle?.path || ''}`, author: '!!!' }; let dropdown; if(currentRenderer == 'legacy') { @@ -273,12 +273,15 @@ const MetadataEditor = createClass({ renderThemeWritein : function(){ if(!this.state.showThemeWritein) return; - return + this.handleThemeWritein(e)} />; + onChange={(e)=>this.handleThemeWritein(e)} /> + {`${this.state.lastThemeWriteIn ? this.props.themeBundle?.path || '' : ''}`} +
; }, renderLanguageDropdown : function(){ diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.less b/client/homebrew/editor/metadataEditor/metadataEditor.less index 9f13fbc49..d06e420ae 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.less +++ b/client/homebrew/editor/metadataEditor/metadataEditor.less @@ -1,5 +1,9 @@ @import 'naturalcrit/styles/colors.less'; +.userThemeName { + padding-left: 10px; + padding-right: 10px; +} .metadataEditor { position : absolute; diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index ef6da5099..7c7044832 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -443,6 +443,7 @@ const EditPage = createClass({ reportError={this.errorReported} renderer={this.state.brew.renderer} userThemes={this.props.userThemes} + themeBundle={this.state.themeBundle} snippetBundle={this.state.themeBundle.snippets} updateBrew={this.updateBrew} onCursorPageChange={this.handleEditorCursorPageChange} diff --git a/server/homebrew.api.js b/server/homebrew.api.js index 8f0d143fa..8b4175491 100644 --- a/server/homebrew.api.js +++ b/server/homebrew.api.js @@ -279,6 +279,7 @@ const api = { let currentTheme; const completeStyles = []; const completeSnippets = []; + let themePath = ''; while (req.params.id) { //=== User Themes ===// @@ -292,6 +293,7 @@ const api = { currentTheme = req.brew; splitTextStyleAndMetadata(currentTheme); + if(themePath === '') themePath = currentTheme.title; // If there is anything in the snippets or style members, append them to the appropriate array // if(currentTheme?.snippets) completeSnippets.push(JSON.parse(currentTheme.snippets)); @@ -301,6 +303,7 @@ const api = { req.params.renderer = currentTheme.renderer; } else { //=== Static Themes ===// + if(themePath === '') themePath = req.params.id; const localSnippets = `${req.params.renderer}_${req.params.id}`; // Just log the name for loading on client const localStyle = `@import url(\"/themes/${req.params.renderer}/${req.params.id}/style.css\");`; completeSnippets.push(localSnippets); @@ -313,7 +316,8 @@ const api = { const returnObj = { // Reverse the order of the arrays so they are listed oldest parent to youngest child. styles : completeStyles.reverse(), - snippets : completeSnippets.reverse() + snippets : completeSnippets.reverse(), + path : themePath }; res.setHeader('Content-Type', 'application/json'); diff --git a/shared/helpers.js b/shared/helpers.js index b2190cdcd..3aff9538a 100644 --- a/shared/helpers.js +++ b/shared/helpers.js @@ -44,8 +44,13 @@ const fetchThemeBundle = async (obj, renderer, theme)=>{ .catch((err)=>{ obj.setState({ error: err }); }); - if(!res) return; - + if(!res) { + obj.setState((prevState)=>({ + ...prevState, + themeBundle : {} + })); + return; + } const themeBundle = res.body; themeBundle.joinedStyles = themeBundle.styles.map((style)=>``).join('\n\n'); obj.setState((prevState)=>({ @@ -54,8 +59,14 @@ const fetchThemeBundle = async (obj, renderer, theme)=>{ })); }; +const fetchBrewAsThemeJSON = async (renderer, theme)=>{ + if(!renderer || !theme) return; + +} + export { splitTextStyleAndMetadata, printCurrentBrew, fetchThemeBundle, + fetchBrewAsThemeJSON }; From 94a431eec8ffeed1340615d15e56a5136529040f Mon Sep 17 00:00:00 2001 From: David Bolack Date: Tue, 7 Jan 2025 22:28:12 -0600 Subject: [PATCH 06/24] Update tests. --- server/homebrew.api.spec.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/homebrew.api.spec.js b/server/homebrew.api.spec.js index 8270b1568..d6433bced 100644 --- a/server/homebrew.api.spec.js +++ b/server/homebrew.api.spec.js @@ -587,6 +587,7 @@ brew`); expect(res.status).toHaveBeenCalledWith(200); expect(res.send).toHaveBeenCalledWith({ + path : 'User Theme A', styles : ['/* From Brew: https://localhost/share/userThemeAID */\n\nUser Theme A Style'], snippets : [] }); @@ -607,6 +608,7 @@ brew`); expect(res.status).toHaveBeenCalledWith(200); expect(res.send).toHaveBeenCalledWith({ + path : 'User Theme A', styles : [ '/* From Brew: https://localhost/share/userThemeCID */\n\nUser Theme C Style', '/* From Brew: https://localhost/share/userThemeBID */\n\nUser Theme B Style', @@ -623,6 +625,7 @@ brew`); expect(res.status).toHaveBeenCalledWith(200); expect(res.send).toHaveBeenCalledWith({ + path : '5ePHB', styles : [ `/* From Theme Blank */\n\n@import url("/themes/V3/Blank/style.css");`, `/* From Theme 5ePHB */\n\n@import url("/themes/V3/5ePHB/style.css");` @@ -649,6 +652,7 @@ brew`); expect(res.status).toHaveBeenCalledWith(200); expect(res.send).toHaveBeenCalledWith({ + path : 'User Theme A', styles : [ `/* From Theme Blank */\n\n@import url("/themes/V3/Blank/style.css");`, `/* From Theme 5ePHB */\n\n@import url("/themes/V3/5ePHB/style.css");`, From 6eb938bb373b2d15428f9c3425c0ce8bdcddd8ce Mon Sep 17 00:00:00 2001 From: David Bolack Date: Wed, 8 Jan 2025 09:25:16 -0600 Subject: [PATCH 07/24] Change theme button toggle to be a bit more obvious. --- client/homebrew/editor/metadataEditor/metadataEditor.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.jsx b/client/homebrew/editor/metadataEditor/metadataEditor.jsx index 694444bc9..0fd17db3e 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.jsx +++ b/client/homebrew/editor/metadataEditor/metadataEditor.jsx @@ -265,7 +265,7 @@ const MetadataEditor = createClass({ {!this.state.showThemeWritein?dropdown:''} {this.renderThemeWritein()} ; From 2d47cd2a767f7e2f258bd057efc09ad4a58eb3e6 Mon Sep 17 00:00:00 2001 From: David Bolack Date: Wed, 8 Jan 2025 09:28:37 -0600 Subject: [PATCH 08/24] Formatting cleanup --- .../editor/metadataEditor/metadataEditor.jsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.jsx b/client/homebrew/editor/metadataEditor/metadataEditor.jsx index 0fd17db3e..92e737764 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.jsx +++ b/client/homebrew/editor/metadataEditor/metadataEditor.jsx @@ -265,7 +265,7 @@ const MetadataEditor = createClass({ {!this.state.showThemeWritein?dropdown:''} {this.renderThemeWritein()} ; @@ -275,11 +275,11 @@ const MetadataEditor = createClass({ if(!this.state.showThemeWritein) return; return
this.handleThemeWritein(e)} /> + default='' + placeholder='Enter share id' + className='value' + defaultValue={this.state.lastThemeWriteIn || this.props.metadata.theme} + onChange={(e)=>this.handleThemeWritein(e)} /> {`${this.state.lastThemeWriteIn ? this.props.themeBundle?.path || '' : ''}`}
; }, From 659510e364c68ea5a3e1199a3f74498c61119376 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Sun, 9 Feb 2025 12:07:09 -0500 Subject: [PATCH 09/24] Remove unused function --- shared/helpers.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/shared/helpers.js b/shared/helpers.js index 3aff9538a..c31516ac9 100644 --- a/shared/helpers.js +++ b/shared/helpers.js @@ -59,14 +59,8 @@ const fetchThemeBundle = async (obj, renderer, theme)=>{ })); }; -const fetchBrewAsThemeJSON = async (renderer, theme)=>{ - if(!renderer || !theme) return; - -} - export { splitTextStyleAndMetadata, printCurrentBrew, fetchThemeBundle, - fetchBrewAsThemeJSON }; From cfbf4021dccb57df02cbd64db116a5a0ab54f651 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Sun, 9 Feb 2025 12:23:42 -0500 Subject: [PATCH 10/24] Remove unneeded filter --- client/homebrew/editor/metadataEditor/metadataEditor.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.jsx b/client/homebrew/editor/metadataEditor/metadataEditor.jsx index 92e737764..8b7c31e53 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.jsx +++ b/client/homebrew/editor/metadataEditor/metadataEditor.jsx @@ -239,7 +239,7 @@ const MetadataEditor = createClass({ ; - }).filter(Boolean); + }); }; const currentRenderer = this.props.metadata.renderer; From c080e5b1915eea919c2f683aa4445a759b381ff4 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Mon, 10 Feb 2025 22:20:12 -0500 Subject: [PATCH 11/24] Add author to snippetBundle --- server/homebrew.api.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/server/homebrew.api.js b/server/homebrew.api.js index 8b4175491..17e9ae7fe 100644 --- a/server/homebrew.api.js +++ b/server/homebrew.api.js @@ -279,7 +279,8 @@ const api = { let currentTheme; const completeStyles = []; const completeSnippets = []; - let themePath = ''; + let themeName; + let themeAuthor; while (req.params.id) { //=== User Themes ===// @@ -293,17 +294,18 @@ const api = { currentTheme = req.brew; splitTextStyleAndMetadata(currentTheme); - if(themePath === '') themePath = currentTheme.title; + themeName ??= currentTheme.title; + themeAuthor ??= currentTheme.authors?.[0]; // If there is anything in the snippets or style members, append them to the appropriate array - // if(currentTheme?.snippets) completeSnippets.push(JSON.parse(currentTheme.snippets)); + if(currentTheme?.snippets) completeSnippets.push(JSON.parse(currentTheme.snippets)); if(currentTheme?.style) completeStyles.push(`/* From Brew: ${req.protocol}://${req.get('host')}/share/${req.params.id} */\n\n${currentTheme.style}`); req.params.id = currentTheme.theme; req.params.renderer = currentTheme.renderer; } else { //=== Static Themes ===// - if(themePath === '') themePath = req.params.id; + themeName ??= req.params.id; const localSnippets = `${req.params.renderer}_${req.params.id}`; // Just log the name for loading on client const localStyle = `@import url(\"/themes/${req.params.renderer}/${req.params.id}/style.css\");`; completeSnippets.push(localSnippets); @@ -317,7 +319,8 @@ const api = { // Reverse the order of the arrays so they are listed oldest parent to youngest child. styles : completeStyles.reverse(), snippets : completeSnippets.reverse(), - path : themePath + name : themeName, + author : themeAuthor }; res.setHeader('Content-Type', 'application/json'); From 1aed7539110badb8c7b3429f4dd30db113238905 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Mon, 10 Feb 2025 22:20:54 -0500 Subject: [PATCH 12/24] Use ComboBox component for Theme Selector --- .../editor/metadataEditor/metadataEditor.jsx | 86 +++++------ .../editor/metadataEditor/metadataEditor.less | 136 ++++++++---------- 2 files changed, 94 insertions(+), 128 deletions(-) diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.jsx b/client/homebrew/editor/metadataEditor/metadataEditor.jsx index 199d2c4b7..141e1ab92 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.jsx +++ b/client/homebrew/editor/metadataEditor/metadataEditor.jsx @@ -49,12 +49,8 @@ const MetadataEditor = createClass({ }, getInitialState : function(){ - mergedThemes = _.merge(Themes, this.props.userThemes); return { - showThumbnail : true, - showThemeWritein : mergedThemes[this.props.metadata.renderer][this.props.metadata.theme] ? false : true, - lastThemePulldown : mergedThemes[this.props.metadata.renderer][this.props.metadata.theme] ? mergedThemes[this.props.metadata.renderer][this.props.metadata.theme].path : '', - lastThemeWriteIn : mergedThemes[this.props.metadata.renderer][this.props.metadata.theme] ? '' : this.props.metadata.theme + showThumbnail : true }; }, @@ -64,15 +60,6 @@ const MetadataEditor = createClass({ }); }, - toggleThemeWritein : function(){ - if(!this.state.showThemeWritein) this.props.metadata.theme = this.state.lastThemeWriteIn; - else this.props.metadata.theme = this.state.lastThemePulldown; - this.props.onChange(this.props.metadata, 'theme'); - this.setState({ - showThemeWritein : !this.state.showThemeWritein - }); - }, - renderThumbnail : function(){ if(!this.state.showThumbnail) return; return ; @@ -128,21 +115,15 @@ const MetadataEditor = createClass({ handleTheme : function(theme){ this.props.metadata.renderer = theme.renderer; this.props.metadata.theme = theme.path; - this.setState({ - lastThemePulldown : theme.path - }); this.props.onChange(this.props.metadata, 'theme'); }, handleThemeWritein : function(e) { - this.props.metadata.renderer = 'V3'; this.props.metadata.theme = e.target.value; - this.setState({ - lastThemeWriteIn : e.target.value - }); - this.props.onChange(this.props.metadata, 'renderer'); + + this.props.onChange(this.props.metadata, 'theme'); }, @@ -224,12 +205,14 @@ const MetadataEditor = createClass({ renderThemeDropdown : function(){ if(!global.enable_themes) return; + const mergedThemes = _.merge(Themes, this.props.userThemes); + const listThemes = (renderer)=>{ return _.map(_.values(mergedThemes[renderer]), (theme)=>{ if(theme.path == this.props.metadata.shareId) return; const preview = theme.thumbnail || `/themes/${theme.renderer}/${theme.path}/dropdownPreview.png`; const texture = theme.thumbnail || `/themes/${theme.renderer}/${theme.path}/dropdownTexture.png`; - return
this.handleTheme(theme)} title={''}> + return
{theme.author ?? renderer} : {theme.name}
@@ -242,45 +225,45 @@ const MetadataEditor = createClass({ }); }; + console.log(this.props.themeBundle); + const currentRenderer = this.props.metadata.renderer; const currentTheme = mergedThemes[`${_.upperFirst(this.props.metadata.renderer)}`][this.props.metadata.theme] - ?? { name: `${this.props.themeBundle?.path || ''}`, author: '!!!' }; + ?? { name: `${this.props.themeBundle?.name || ''}`, author: `${this.props.themeBundle?.author || ''}` }; let dropdown; if(currentRenderer == 'legacy') { dropdown = - -
{`Themes are not supported in the Legacy Renderer`}
-
; +
+
Themes are not supported in the Legacy Renderer
+
; } else { dropdown = - -
{currentTheme.author ?? _.upperFirst(currentRenderer)} : {currentTheme.name}
- - {listThemes(currentRenderer)} -
; +
+ this.handleTheme(value)} + onEntry={(e)=>{ + e.target.setCustomValidity(''); //Clear the validation popup while typing + //debouncedHandleFieldChange('theme', e); + this.handleThemeWritein(e); + }} + options={listThemes(currentRenderer)} + autoSuggest={{ + suggestMethod : 'includes', + clearAutoSuggestOnClick : true, + filterOn : ['value', 'title'] + }} + /> + Select from the list below (built-in themes and brews you have tagged "meta:theme"), or paste in the Share URL or Share ID of any brew. +
; } return
- {!this.state.showThemeWritein?dropdown:''} - - {this.renderThemeWritein()} -
; - }, - - renderThemeWritein : function(){ - if(!this.state.showThemeWritein) return; - return
- this.handleThemeWritein(e)} /> - {`${this.state.lastThemeWriteIn ? this.props.themeBundle?.path || '' : ''}`} + {dropdown}
; }, @@ -317,8 +300,7 @@ const MetadataEditor = createClass({ clearAutoSuggestOnClick : true, filterOn : ['value', 'detail', 'title'] }} - > - + /> Sets the HTML Lang property for your brew. May affect hyphenation or spellcheck.
diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.less b/client/homebrew/editor/metadataEditor/metadataEditor.less index d06e420ae..d76220e48 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.less +++ b/client/homebrew/editor/metadataEditor/metadataEditor.less @@ -171,89 +171,73 @@ } .themes.field { - .navDropdownContainer { + & .dropdown-container { position : relative; z-index : 100; background-color : white; - &.disabled { - font-style : italic; - color : dimgray; - background-color : darkgray; - } - & > div:first-child { - padding : 3px 3px; - background-color : inherit; - border : 1px solid gray; - i { float : right; } - &:hover { - color : white; - background-color : @blue; + } + & .dropdown-options { + overflow-y : visible; + } + .disabled { + font-style : italic; + color : dimgray; + background-color : darkgray; + } + .item { + position : relative; + padding : 3px 3px; + overflow : visible; + background-color : white; + border-top : 1px solid rgb(118, 118, 118); + .preview { + position : absolute; + top : 0; + right : 0; + z-index : 1; + display : flex; + flex-direction : column; + width : 200px; + overflow : hidden; + color : black; + background : #CCCCCC; + border-radius : 5px; + box-shadow : 0 0 5px black; + opacity : 0; + transition : opacity 250ms ease; + h6 { + padding-block : 0.5em; + padding-inline : 1em; + font-weight : 900; + border-bottom : 2px solid hsl(0,0%,40%); } } - .navDropdown .item > p { - width : 45%; - height : 1.1em; - overflow : hidden; - text-overflow : ellipsis; - white-space : nowrap; - } - .navDropdown { - position : absolute; - width : 100%; - box-shadow : 0px 5px 10px rgba(0, 0, 0, 0.3); - .item { - position : relative; - padding : 3px 3px; - overflow : visible; - background-color : white; - border-top : 1px solid rgb(118, 118, 118); - .preview { - position : absolute; - top : 0; - right : 0; - z-index : 1; - display : flex; - flex-direction : column; - width : 200px; - overflow : hidden; - color : black; - background : #CCCCCC; - border-radius : 5px; - box-shadow : 0 0 5px black; - opacity : 0; - transition : opacity 250ms ease; - h6 { - padding-block : 0.5em; - padding-inline : 1em; - font-weight : 900; - border-bottom : 2px solid hsl(0,0%,40%); - } - } - &:hover { - color : white; - background-color : @blue; - } - &:hover > .preview { opacity : 1; } - .texture-container { - position : absolute; - top : 0; - left : 0; - width : 100%; - height : 100%; - min-height : 100%; - overflow : hidden; - > img { - position : absolute; - top : 0px; - right : 0; - width : 50%; - min-height : 100%; - -webkit-mask-image : linear-gradient(90deg, transparent, black 20%); - mask-image : linear-gradient(90deg, transparent, black 20%); - } - } + + .texture-container { + position : absolute; + top : 0; + left : 0; + width : 100%; + height : 100%; + min-height : 100%; + overflow : hidden; + > img { + position : absolute; + top : 0; + right : 0; + width : 50%; + min-height : 100%; + -webkit-mask-image : linear-gradient(90deg, transparent, black 20%); + mask-image : linear-gradient(90deg, transparent, black 20%); } } + + &:hover { + color : white; + background-color : @blue; + filter : unset; + } + &:hover > .preview { opacity : 1; } } } From f326d1123247831eff13ade79b04afeb31f81886 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Thu, 13 Feb 2025 00:05:30 -0500 Subject: [PATCH 13/24] Added input validation (allows Share ID or Share URL) --- .../editor/metadataEditor/metadataEditor.jsx | 13 ++++++------- .../homebrew/editor/metadataEditor/validations.js | 13 +++++++++++++ client/homebrew/pages/editPage/editPage.jsx | 4 ++-- server/app.js | 1 + 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.jsx b/client/homebrew/editor/metadataEditor/metadataEditor.jsx index 141e1ab92..0656b08e7 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.jsx +++ b/client/homebrew/editor/metadataEditor/metadataEditor.jsx @@ -77,6 +77,7 @@ const MetadataEditor = createClass({ ...this.props.metadata, [name] : e.target.value }); + return true; } else { // if validation issues, display built-in browser error popup with each error. const errMessage = validationErr.map((err)=>{ @@ -85,6 +86,7 @@ const MetadataEditor = createClass({ callIfExists(e.target, 'setCustomValidity', errMessage); callIfExists(e.target, 'reportValidity'); + return false; } }, @@ -120,9 +122,8 @@ const MetadataEditor = createClass({ }, handleThemeWritein : function(e) { - this.props.metadata.theme = e.target.value; - - + 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'); }, @@ -225,8 +226,6 @@ const MetadataEditor = createClass({ }); }; - console.log(this.props.themeBundle); - const currentRenderer = this.props.metadata.renderer; const currentTheme = mergedThemes[`${_.upperFirst(this.props.metadata.renderer)}`][this.props.metadata.theme] ?? { name: `${this.props.themeBundle?.name || ''}`, author: `${this.props.themeBundle?.author || ''}` }; @@ -247,8 +246,8 @@ const MetadataEditor = createClass({ onSelect={(value)=>this.handleTheme(value)} onEntry={(e)=>{ e.target.setCustomValidity(''); //Clear the validation popup while typing - //debouncedHandleFieldChange('theme', e); - this.handleThemeWritein(e); + if(this.handleFieldChange('theme', e)) + this.handleThemeWritein(e); }} options={listThemes(currentRenderer)} autoSuggest={{ diff --git a/client/homebrew/editor/metadataEditor/validations.js b/client/homebrew/editor/metadataEditor/validations.js index 32c8131f6..b475783a4 100644 --- a/client/homebrew/editor/metadataEditor/validations.js +++ b/client/homebrew/editor/metadataEditor/validations.js @@ -27,6 +27,19 @@ module.exports = { (value)=>{ return new RegExp(/^([a-zA-Z]{2,3})(-[a-zA-Z]{4})?(-(?:[0-9]{3}|[a-zA-Z]{2}))?$/).test(value) === false && (value.length > 0) ? 'Invalid language code.' : null; } + ], + theme: [ + (value) => { + const URL = global.config.baseUrl.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); //Escape any regex characters + const shareIDPattern = '[a-zA-Z0-9-_]{12}'; + const shareURLRegex = new RegExp(`^${URL}\\/share\\/${shareIDPattern}$`); + const shareIDRegex = new RegExp(`^${shareIDPattern}$`); + if (value?.length === 0) return null; + if (shareURLRegex.test(value)) return null; + if (shareIDRegex.test(value)) return null; + + return 'Must be a valid Share URL or a 12-character ID.'; + } ] }; diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index c784edc8e..bee813f46 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -379,7 +379,7 @@ const EditPage = createClass({ const title = `${this.props.brew.title} ${systems}`; const text = `Hey guys! I've been working on this homebrew. I'd love your feedback. Check it out. -**[Homebrewery Link](${global.config.publicUrl}/share/${shareLink})**`; +**[Homebrewery Link](${global.config.baseUrl}/share/${shareLink})**`; return `https://www.reddit.com/r/UnearthedArcana/submit?title=${encodeURIComponent(title.toWellFormed())}&text=${encodeURIComponent(text)}`; }, @@ -410,7 +410,7 @@ const EditPage = createClass({ view - {navigator.clipboard.writeText(`${global.config.publicUrl}/share/${shareLink}`);}}> + {navigator.clipboard.writeText(`${global.config.baseUrl}/share/${shareLink}`);}}> copy url diff --git a/server/app.js b/server/app.js index 4dec6b4c4..76caf6fed 100644 --- a/server/app.js +++ b/server/app.js @@ -552,6 +552,7 @@ const renderPage = async (req, res)=>{ const configuration = { local : isLocalEnvironment, publicUrl : config.get('publicUrl') ?? '', + baseUrl : `${req.protocol}://${req.get('host')}`, environment : nodeEnv, deployment : config.get('heroku_app_name') ?? '' }; From 0bd5ac42b600fccdae7c7c3ee4d65eaca67f0330 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Thu, 13 Feb 2025 00:40:59 -0500 Subject: [PATCH 14/24] Remove too-small height for themes + thumbnail --- client/homebrew/editor/metadataEditor/metadataEditor.less | 1 - 1 file changed, 1 deletion(-) diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.less b/client/homebrew/editor/metadataEditor/metadataEditor.less index d76220e48..1f6e228f8 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.less +++ b/client/homebrew/editor/metadataEditor/metadataEditor.less @@ -76,7 +76,6 @@ &:focus { outline : 1px solid #444444; } } &.thumbnail, &.themes{ - height : 1.4em; label { line-height : 2.0em; } .value { overflow : hidden; From 45e98debbd61bd0ddbe278ec6edc96430c42629a Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Thu, 13 Feb 2025 00:43:14 -0500 Subject: [PATCH 15/24] Remove z-index for metadata editor to not cover nav bar (errors/dropdowns/etc) The other editor panels don't have a z-index. For some reason the metadata editor does, and it covers items from the navbar --- client/homebrew/editor/metadataEditor/metadataEditor.less | 1 - 1 file changed, 1 deletion(-) diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.less b/client/homebrew/editor/metadataEditor/metadataEditor.less index 1f6e228f8..c5658a796 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.less +++ b/client/homebrew/editor/metadataEditor/metadataEditor.less @@ -7,7 +7,6 @@ .metadataEditor { position : absolute; - z-index : 5; box-sizing : border-box; width : 100%; height : calc(100vh - 54px); // 54px is the height of the navbar + snippet bar. probably a better way to dynamic get this. From e584eec8c225e9689266185e34400ff0fd6d6ff1 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Thu, 13 Feb 2025 14:51:45 -0500 Subject: [PATCH 16/24] When clicked, combobox textbox clears --- client/components/combobox.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/client/components/combobox.jsx b/client/components/combobox.jsx index 56633bbdd..3f1134bd6 100644 --- a/client/components/combobox.jsx +++ b/client/components/combobox.jsx @@ -45,6 +45,7 @@ const Combobox = createClass({ }, handleDropdown : function(show){ this.setState({ + value : show ? '' : this.props.default, showDropdown : show, inputFocused : this.props.autoSuggest.clearAutoSuggestOnClick ? show : false }); From df563b929429182f1436e9b89a2cd54334f4009e Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Thu, 13 Feb 2025 14:53:02 -0500 Subject: [PATCH 17/24] Change combobox default text --- .../homebrew/editor/metadataEditor/metadataEditor.jsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.jsx b/client/homebrew/editor/metadataEditor/metadataEditor.jsx index 0656b08e7..d45f75932 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.jsx +++ b/client/homebrew/editor/metadataEditor/metadataEditor.jsx @@ -16,8 +16,6 @@ const SYSTEMS = ['5e', '4e', '3.5e', 'Pathfinder']; const homebreweryThumbnail = require('../../thumbnail.png'); -let mergedThemes; - const callIfExists = (val, fn, ...args)=>{ if(val[fn]) { val[fn](...args); @@ -223,12 +221,11 @@ const MetadataEditor = createClass({
; - }); + }).filter(Boolean); }; const currentRenderer = this.props.metadata.renderer; - const currentTheme = mergedThemes[`${_.upperFirst(this.props.metadata.renderer)}`][this.props.metadata.theme] - ?? { name: `${this.props.themeBundle?.name || ''}`, author: `${this.props.themeBundle?.author || ''}` }; + const currentThemeDisplay = this.props.themeBundle?.name ? `${this.props.themeBundle.author ?? currentRenderer} : ${this.props.themeBundle.name}` : 'No Theme Selected'; let dropdown; if(currentRenderer == 'legacy') { @@ -241,8 +238,8 @@ const MetadataEditor = createClass({
this.handleTheme(value)} onEntry={(e)=>{ e.target.setCustomValidity(''); //Clear the validation popup while typing From bf297939dccf2efb5b0d14a6664d6e32b98fe58e Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Thu, 13 Feb 2025 15:01:35 -0500 Subject: [PATCH 18/24] Debounce validation popup --- client/components/combobox.jsx | 2 +- .../editor/metadataEditor/metadataEditor.jsx | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/client/components/combobox.jsx b/client/components/combobox.jsx index 3f1134bd6..ae9f1d7f8 100644 --- a/client/components/combobox.jsx +++ b/client/components/combobox.jsx @@ -79,7 +79,7 @@ const Combobox = createClass({ if(!e.target.checkValidity()){ this.setState({ value : this.props.default - }, ()=>this.props.onEntry(e)); + }); } }} /> diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.jsx b/client/homebrew/editor/metadataEditor/metadataEditor.jsx index d45f75932..2a65d9384 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.jsx +++ b/client/homebrew/editor/metadataEditor/metadataEditor.jsx @@ -68,6 +68,11 @@ const MetadataEditor = createClass({ const inputRules = validations[name] ?? []; const validationErr = inputRules.map((rule)=>rule(e.target.value)).filter(Boolean); + const debouncedReportValidity = _.debounce((target, errMessage) => { + callIfExists(target, 'setCustomValidity', errMessage); + callIfExists(target, 'reportValidity'); + }, 300); // 300ms debounce delay, adjust as needed + // if no validation rules, save to props if(validationErr.length === 0){ callIfExists(e.target, 'setCustomValidity', ''); @@ -82,8 +87,8 @@ const MetadataEditor = createClass({ return `- ${err}`; }).join('\n'); - callIfExists(e.target, 'setCustomValidity', errMessage); - callIfExists(e.target, 'reportValidity'); + + debouncedReportValidity(e.target, errMessage); return false; } }, @@ -276,8 +281,6 @@ const MetadataEditor = createClass({ }); }; - const debouncedHandleFieldChange = _.debounce(this.handleFieldChange, 500); - return
@@ -288,7 +291,7 @@ const MetadataEditor = createClass({ onSelect={(value)=>this.handleLanguage(value)} onEntry={(e)=>{ e.target.setCustomValidity(''); //Clear the validation popup while typing - debouncedHandleFieldChange('lang', e); + this.handleFieldChange('lang', e); }} options={listLanguages()} autoSuggest={{ From d8d672fada688aa616997288b38027b39acfa0b5 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Thu, 13 Feb 2025 15:51:06 -0500 Subject: [PATCH 19/24] Error message if chosen theme does not have "meta:theme" tag. --- client/homebrew/navbar/error-navitem.jsx | 13 +++++++++++++ .../homebrew/pages/errorPage/errors/errorIndex.js | 8 ++++++++ server/homebrew.api.js | 2 ++ 3 files changed, 23 insertions(+) diff --git a/client/homebrew/navbar/error-navitem.jsx b/client/homebrew/navbar/error-navitem.jsx index f6788e6d5..3de26ca56 100644 --- a/client/homebrew/navbar/error-navitem.jsx +++ b/client/homebrew/navbar/error-navitem.jsx @@ -116,6 +116,19 @@ const ErrorNavItem = createClass({ ; } + if(HBErrorCode === '10') { + return + Oops! +
+ Looks like the brew you have selected + as a theme is not tagged for use as a + theme. Verify that + brew + {response.body.brewId} has the meta:theme tag! +
+
; + } + return Oops!
diff --git a/client/homebrew/pages/errorPage/errors/errorIndex.js b/client/homebrew/pages/errorPage/errors/errorIndex.js index 2c7be5d4b..c2c49f958 100644 --- a/client/homebrew/pages/errorPage/errors/errorIndex.js +++ b/client/homebrew/pages/errorPage/errors/errorIndex.js @@ -167,6 +167,14 @@ const errorIndex = (props)=>{ **Requested access:** ${props.brew.accessType} **Brew ID:** ${props.brew.brewId}`, + + // Theme Not Valid + '10' : dedent` + ## The selected theme is not tagged as a theme. + + The brew selected as a theme exists, but has not been marked for use as a theme with the \`theme:meta\` tag. + + If the selected brew is your document, you may designate it as a theme by adding the \`theme:meta\` tag.`, //account page when account is not defined '50' : dedent` diff --git a/server/homebrew.api.js b/server/homebrew.api.js index 17e9ae7fe..7c5460803 100644 --- a/server/homebrew.api.js +++ b/server/homebrew.api.js @@ -294,6 +294,8 @@ const api = { currentTheme = req.brew; splitTextStyleAndMetadata(currentTheme); + if(!currentTheme.tags.some(tag => tag === "meta:theme" || tag === "meta:Theme")) + throw { brewId: req.params.id, name: 'Invalid Theme Selected', message: 'Selected theme does not have the meta:theme tag', HBErrorCode: '10' }; themeName ??= currentTheme.title; themeAuthor ??= currentTheme.authors?.[0]; From 50bda9455f4e7a2dc9defcfa18322575c511302a Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Thu, 13 Feb 2025 15:51:22 -0500 Subject: [PATCH 20/24] Immediately clear errors if a theme successfully loads --- shared/helpers.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shared/helpers.js b/shared/helpers.js index c31516ac9..2ad9218fa 100644 --- a/shared/helpers.js +++ b/shared/helpers.js @@ -55,7 +55,8 @@ const fetchThemeBundle = async (obj, renderer, theme)=>{ themeBundle.joinedStyles = themeBundle.styles.map((style)=>``).join('\n\n'); obj.setState((prevState)=>({ ...prevState, - themeBundle : themeBundle + themeBundle : themeBundle, + error : null })); }; From 94f478477d01ce586106f1ee3adae8eefab0636e Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Thu, 13 Feb 2025 16:20:50 -0500 Subject: [PATCH 21/24] Add test for missing meta:theme tag --- server/homebrew.api.js | 2 +- server/homebrew.api.spec.js | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/server/homebrew.api.js b/server/homebrew.api.js index 7c5460803..7bd88cbdb 100644 --- a/server/homebrew.api.js +++ b/server/homebrew.api.js @@ -295,7 +295,7 @@ const api = { currentTheme = req.brew; splitTextStyleAndMetadata(currentTheme); if(!currentTheme.tags.some(tag => tag === "meta:theme" || tag === "meta:Theme")) - throw { brewId: req.params.id, name: 'Invalid Theme Selected', message: 'Selected theme does not have the meta:theme tag', HBErrorCode: '10' }; + throw { brewId: req.params.id, name: 'Invalid Theme Selected', message: 'Selected theme does not have the meta:theme tag', status: 422, HBErrorCode: '10' }; themeName ??= currentTheme.title; themeAuthor ??= currentTheme.authors?.[0]; diff --git a/server/homebrew.api.spec.js b/server/homebrew.api.spec.js index d6433bced..cfa7d13cc 100644 --- a/server/homebrew.api.spec.js +++ b/server/homebrew.api.spec.js @@ -690,6 +690,27 @@ brew`); name : 'ThemeLoad Error', status : 404 }); }); + + it('should fail for a User Theme not tagged with meta:theme', async ()=>{ + const brews = { + userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: null, shareId: 'userThemeAID', style: 'User Theme A Style' } + }; + + const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew })); + model.get = jest.fn((getParams)=>toBrewPromise(brews[getParams.shareId])); + const req = { params: { renderer: 'V3', id: 'userThemeAID' }, get: ()=>{ return 'localhost'; }, protocol: 'https' }; + + let err; + await api.getThemeBundle(req, res) + .catch((e)=>err = e); + + expect(err).toEqual({ + HBErrorCode : '10', + brewId : 'userThemeAID', + message : 'Selected theme does not have the meta:theme tag', + name : 'Invalid Theme Selected', + status : 422 }); + }); }); describe('deleteBrew', ()=>{ From ed30a1cd7d9a484fa0f5f7002e5a439a1fc5bee7 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Thu, 13 Feb 2025 16:20:59 -0500 Subject: [PATCH 22/24] Update other tests to pass --- server/homebrew.api.spec.js | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/server/homebrew.api.spec.js b/server/homebrew.api.spec.js index cfa7d13cc..1c42cb4be 100644 --- a/server/homebrew.api.spec.js +++ b/server/homebrew.api.spec.js @@ -576,7 +576,7 @@ brew`); describe('Theme bundle', ()=>{ it('should return Theme Bundle for a User Theme', async ()=>{ const brews = { - userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: null, shareId: 'userThemeAID', style: 'User Theme A Style' } + userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: null, shareId: 'userThemeAID', style: 'User Theme A Style', tags: ['meta:theme'], authors: ['authorName'] } }; const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew })); @@ -587,7 +587,8 @@ brew`); expect(res.status).toHaveBeenCalledWith(200); expect(res.send).toHaveBeenCalledWith({ - path : 'User Theme A', + name : 'User Theme A', + author : 'authorName', styles : ['/* From Brew: https://localhost/share/userThemeAID */\n\nUser Theme A Style'], snippets : [] }); @@ -595,9 +596,9 @@ brew`); it('should return Theme Bundle for nested User Themes', async ()=>{ const brews = { - userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: 'userThemeBID', shareId: 'userThemeAID', style: 'User Theme A Style' }, - userThemeBID : { title: 'User Theme B', renderer: 'V3', theme: 'userThemeCID', shareId: 'userThemeBID', style: 'User Theme B Style' }, - userThemeCID : { title: 'User Theme C', renderer: 'V3', theme: null, shareId: 'userThemeCID', style: 'User Theme C Style' } + userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: 'userThemeBID', shareId: 'userThemeAID', style: 'User Theme A Style', tags: ['meta:theme'], authors: ['authorName'] }, + userThemeBID : { title: 'User Theme B', renderer: 'V3', theme: 'userThemeCID', shareId: 'userThemeBID', style: 'User Theme B Style', tags: ['meta:theme'], authors: ['authorName'] }, + userThemeCID : { title: 'User Theme C', renderer: 'V3', theme: null, shareId: 'userThemeCID', style: 'User Theme C Style', tags: ['meta:theme'], authors: ['authorName'] } }; const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew })); @@ -608,7 +609,8 @@ brew`); expect(res.status).toHaveBeenCalledWith(200); expect(res.send).toHaveBeenCalledWith({ - path : 'User Theme A', + name : 'User Theme A', + author : 'authorName', styles : [ '/* From Brew: https://localhost/share/userThemeCID */\n\nUser Theme C Style', '/* From Brew: https://localhost/share/userThemeBID */\n\nUser Theme B Style', @@ -625,7 +627,8 @@ brew`); expect(res.status).toHaveBeenCalledWith(200); expect(res.send).toHaveBeenCalledWith({ - path : '5ePHB', + name : '5ePHB', + author : undefined, styles : [ `/* From Theme Blank */\n\n@import url("/themes/V3/Blank/style.css");`, `/* From Theme 5ePHB */\n\n@import url("/themes/V3/5ePHB/style.css");` @@ -639,9 +642,9 @@ brew`); it('should return Theme Bundle for nested User and Static Themes together', async ()=>{ const brews = { - userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: 'userThemeBID', shareId: 'userThemeAID', style: 'User Theme A Style' }, - userThemeBID : { title: 'User Theme B', renderer: 'V3', theme: 'userThemeCID', shareId: 'userThemeBID', style: 'User Theme B Style' }, - userThemeCID : { title: 'User Theme C', renderer: 'V3', theme: '5eDMG', shareId: 'userThemeCID', style: 'User Theme C Style' } + userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: 'userThemeBID', shareId: 'userThemeAID', style: 'User Theme A Style', tags: ['meta:theme'], authors: ['authorName'] }, + userThemeBID : { title: 'User Theme B', renderer: 'V3', theme: 'userThemeCID', shareId: 'userThemeBID', style: 'User Theme B Style', tags: ['meta:theme'], authors: ['authorName'] }, + userThemeCID : { title: 'User Theme C', renderer: 'V3', theme: '5eDMG', shareId: 'userThemeCID', style: 'User Theme C Style', tags: ['meta:theme'], authors: ['authorName'] } }; const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew })); @@ -652,7 +655,8 @@ brew`); expect(res.status).toHaveBeenCalledWith(200); expect(res.send).toHaveBeenCalledWith({ - path : 'User Theme A', + name : 'User Theme A', + author : 'authorName', styles : [ `/* From Theme Blank */\n\n@import url("/themes/V3/Blank/style.css");`, `/* From Theme 5ePHB */\n\n@import url("/themes/V3/5ePHB/style.css");`, @@ -669,9 +673,9 @@ brew`); }); }); - it('should fail for an invalid Theme in the chain', async()=>{ + it('should fail for a missing Theme in the chain', async()=>{ const brews = { - userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: 'missingTheme', shareId: 'userThemeAID', style: 'User Theme A Style' }, + userThemeAID : { title: 'User Theme A', renderer: 'V3', theme: 'missingTheme', shareId: 'userThemeAID', style: 'User Theme A Style', tags: ['meta:theme'], authors: ['authorName'] }, }; const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew })); From 42accdb54fbc167d12278d0ca94a32438076073b Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Thu, 13 Feb 2025 17:54:37 -0500 Subject: [PATCH 23/24] linting --- client/homebrew/pages/editPage/editPage.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index bee813f46..4db6d6e23 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -151,9 +151,9 @@ const EditPage = createClass({ }, handleMetaChange : function(metadata, field=undefined){ - if(field == 'theme' || field == 'renderer') {// Fetch theme bundle only if theme or renderer was changed + if(field == 'theme' || field == 'renderer') // Fetch theme bundle only if theme or renderer was changed fetchThemeBundle(this, metadata.renderer, metadata.theme); - } + this.setState((prevState)=>({ brew : { ...prevState.brew, From 854a2ab35eb873cf93e2f7b5d9ba6dd26f8265a5 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Thu, 13 Feb 2025 17:56:39 -0500 Subject: [PATCH 24/24] Fix /new --- client/homebrew/pages/newPage/newPage.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/client/homebrew/pages/newPage/newPage.jsx b/client/homebrew/pages/newPage/newPage.jsx index ee2c67d5f..1d5887b8a 100644 --- a/client/homebrew/pages/newPage/newPage.jsx +++ b/client/homebrew/pages/newPage/newPage.jsx @@ -233,6 +233,7 @@ const NewPage = createClass({ onMetaChange={this.handleMetaChange} renderer={this.state.brew.renderer} userThemes={this.props.userThemes} + themeBundle={this.state.themeBundle} snippetBundle={this.state.themeBundle.snippets} onCursorPageChange={this.handleEditorCursorPageChange} onViewPageChange={this.handleEditorViewPageChange}