From 63bebe1efd9b21d441d57f01618f1bc9525ca91f Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Mon, 6 Oct 2025 00:02:24 -0400 Subject: [PATCH 1/2] Lint everything Catching up on a bunch of linting so random changes stop showing up on PRs when the linter is run. --- .../editor/metadataEditor/validations.js | 2 +- client/homebrew/navbar/error-navitem.jsx | 6 ++-- client/homebrew/navbar/newbrew.navitem.jsx | 4 +-- client/homebrew/navbar/share.navitem.jsx | 20 ++++++------- client/homebrew/pages/editPage/editPage.jsx | 8 ++--- client/homebrew/pages/homePage/homePage.jsx | 16 +++++----- client/homebrew/pages/newPage/newPage.jsx | 30 +++++++++---------- server/homebrew.api.js | 10 +++---- shared/helpers.js | 20 ++++++------- shared/naturalcrit/markdown.js | 4 +-- shared/naturalcrit/markdownLegacy.js | 4 +-- 11 files changed, 62 insertions(+), 62 deletions(-) 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/navbar/error-navitem.jsx b/client/homebrew/navbar/error-navitem.jsx index 6d9bec444..0271224b7 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 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/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/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index d4e0061b1..524f31ac9 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -123,16 +123,16 @@ const EditPage = (props)=>{ editorRef.current?.update(); }; - const handleBrewChange = (field) => (value, subfield) => { //'text', 'style', 'snippets', 'metadata' - if (subfield == 'renderer' || subfield == 'theme') + 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(field == 'metadata') setCurrentBrew((prev)=>({ ...prev, ...value })); + else setCurrentBrew((prev)=>({ ...prev, [field]: value })); if(useLocalStorage) { if(field == 'text') localStorage.setItem(BREWKEY, value); diff --git a/client/homebrew/pages/homePage/homePage.jsx b/client/homebrew/pages/homePage/homePage.jsx index fe57a9913..82c5b7084 100644 --- a/client/homebrew/pages/homePage/homePage.jsx +++ b/client/homebrew/pages/homePage/homePage.jsx @@ -39,8 +39,8 @@ const HomePage =(props)=>{ props = { brew : DEFAULT_BREW, ver : '0.0.0', - ...props - }; + ...props + }; const [currentBrew , setCurrentBrew] = useState(props.brew); const [error , setError] = useState(undefined); @@ -71,7 +71,7 @@ const HomePage =(props)=>{ document.addEventListener('keydown', handleControlKeys); - return () => { + return ()=>{ document.removeEventListener('keydown', handleControlKeys); }; }, []); @@ -100,16 +100,16 @@ const HomePage =(props)=>{ editorRef.current.update(); }; - const handleBrewChange = (field) => (value, subfield) => { //'text', 'style', 'snippets', 'metadata' - if (subfield == 'renderer' || subfield == 'theme') + 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(field == 'metadata') setCurrentBrew((prev)=>({ ...prev, ...value })); + else setCurrentBrew((prev)=>({ ...prev, [field]: value })); if(useLocalStorage) { if(field == 'text') localStorage.setItem(BREWKEY, value); @@ -218,7 +218,7 @@ const HomePage =(props)=>{ Create your own - ) + ); }; module.exports = HomePage; diff --git a/client/homebrew/pages/newPage/newPage.jsx b/client/homebrew/pages/newPage/newPage.jsx index bc2b39c44..dccf7deb7 100644 --- a/client/homebrew/pages/newPage/newPage.jsx +++ b/client/homebrew/pages/newPage/newPage.jsx @@ -36,9 +36,9 @@ const SAVEKEYPREFIX = 'HB_editor_defaultSave_'; const useLocalStorage = true; const neverSaved = true; -const NewPage = (props) => { +const NewPage = (props)=>{ props = { - brew: DEFAULT_BREW, + brew : DEFAULT_BREW, ...props }; @@ -57,7 +57,7 @@ const NewPage = (props) => { const editorRef = useRef(null); const lastSavedBrew = useRef(_.cloneDeep(props.brew)); - useEffect(() => { + useEffect(()=>{ loadBrew(); fetchThemeBundle(setError, setThemeBundle, currentBrew.renderer, currentBrew.theme); @@ -73,7 +73,7 @@ const NewPage = (props) => { document.addEventListener('keydown', handleControlKeys); - return () => { + return ()=>{ document.removeEventListener('keydown', handleControlKeys); }; }, []); @@ -118,16 +118,16 @@ const NewPage = (props) => { editorRef.current.update(); }; - const handleBrewChange = (field) => (value, subfield) => { //'text', 'style', 'snippets', 'metadata' - if (subfield == 'renderer' || subfield == 'theme') + 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(field == 'metadata') setCurrentBrew((prev)=>({ ...prev, ...value })); + else setCurrentBrew((prev)=>({ ...prev, [field]: value })); if(useLocalStorage) { if(field == 'text') localStorage.setItem(BREWKEY, value); @@ -141,10 +141,10 @@ const NewPage = (props) => { } }; - 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; @@ -153,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; @@ -209,7 +209,7 @@ const NewPage = (props) => { setIsSaving(false); }; - const renderNavbar = () => ( + const renderNavbar = ()=>( {currentBrew.title} @@ -230,7 +230,7 @@ const NewPage = (props) => { ); return ( -
+
{renderNavbar()}
diff --git a/server/homebrew.api.js b/server/homebrew.api.js index 3221638ab..049dac1ce 100644 --- a/server/homebrew.api.js +++ b/server/homebrew.api.js @@ -8,9 +8,9 @@ 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'; @@ -377,14 +377,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 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/markdown.js b/shared/naturalcrit/markdown.js index 78107dcf4..b0b44a079 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -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; From 811f27496813cdc75064b8e1f6e308c3118b866c Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Mon, 6 Oct 2025 00:06:34 -0400 Subject: [PATCH 2/2] Move badly-placed scrollingTimeout that was doing nothing When a user clicks brewJump or sourceJump, we disallow new jumps until the current scroll operation has finished for 150 ms. Unfortunately the timer being checked was always undefined, so you could keep clicking the jump button and get the brewRenderer or editor to keep bouncing around without finishing the jump action. This just moves the scrollingTimeout up outside of the listener function so it isn't being reset to undefined every loop. --- client/homebrew/editor/editor.jsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index a067ac4af..8b7f6ccf5 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -325,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); @@ -369,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;