0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-24 05:33:02 +00:00

Compare commits

..

5 Commits

Author SHA1 Message Date
Trevor Buckner
7fc03bb5a7 Merge branch 'master' into PreventInterruptedBrewJump 2025-10-06 00:07:43 -04:00
Trevor Buckner
ccf11661de Merge pull request #4461 from naturalcrit/LintEverythingOct2025
Lint everything
2025-10-06 00:07:29 -04:00
Trevor Buckner
811f274968 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.
2025-10-06 00:06:34 -04:00
Trevor Buckner
63bebe1efd Lint everything
Catching up on a bunch of linting so random changes stop showing up on PRs when the linter is run.
2025-10-06 00:02:24 -04:00
Trevor Buckner
22e26d635a Merge pull request #4460 from naturalcrit/cleanupLocalStorageKeysTest
Clean up localStorageMap code
2025-10-05 23:28:34 -04:00
12 changed files with 68 additions and 68 deletions

View File

@@ -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;

View File

@@ -18,7 +18,7 @@ module.exports = {
try {
Boolean(new URL(value));
return null;
} catch (e) {
} catch {
return 'Must be a valid URL';
}
}

View File

@@ -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 <Nav.item className='save error' icon='fas fa-exclamation-triangle'>

View File

@@ -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);

View File

@@ -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 })=>(
<Nav.dropdown>
<Nav.item color='teal' icon='fas fa-share-alt'>
share

View File

@@ -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);

View File

@@ -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 <i className='fas fa-magic' />
</a>
</div>
)
);
};
module.exports = HomePage;

View File

@@ -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 = ()=>(
<Navbar>
<Nav.section>
<Nav.item className='brewTitle'>{currentBrew.title}</Nav.item>
@@ -230,7 +230,7 @@ const NewPage = (props) => {
);
return (
<div className='newPage sitePage'>
<div className='newPage sitePage'>
{renderNavbar()}
<div className='content'>
<SplitPane onDragFinish={handleSplitMove}>

View File

@@ -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

View File

@@ -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,

View File

@@ -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());

View File

@@ -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;