mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-01-26 07:23:01 +00:00
Compare commits
16 Commits
new/edit/h
...
LintEveryt
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63bebe1efd | ||
|
|
22e26d635a | ||
|
|
643e0ac650 | ||
|
|
5395412ac5 | ||
|
|
dc4610ea1b | ||
|
|
1e71e9e18a | ||
|
|
4203e90d09 | ||
|
|
dc94555c94 | ||
|
|
74e17e154f | ||
|
|
8f715a6615 | ||
|
|
8f7ae35f08 | ||
|
|
f0bb06e706 | ||
|
|
c50c279ef3 | ||
|
|
cc246fb31a | ||
|
|
4c5eef46a0 | ||
|
|
a1ab27b57f |
@@ -18,7 +18,7 @@ module.exports = {
|
||||
try {
|
||||
Boolean(new URL(value));
|
||||
return null;
|
||||
} catch (e) {
|
||||
} catch {
|
||||
return 'Must be a valid URL';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -26,7 +26,7 @@ const getLocalStorageMap = function(){
|
||||
|
||||
if(global?.account?.username){
|
||||
const username = global.account.username;
|
||||
localStorageMap[`HOMEBREWERY-DEFAULT-SAVE-LOCATION-${username}`] = `HB_editor_defaultSave_${username}`;
|
||||
localStorageMap[`HOMEBREWERY-DEFAULT-SAVE-LOCATION-${username}`] = `HB_editor_defaultSave_${username}`;
|
||||
}
|
||||
|
||||
return localStorageMap;
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import getLocalStorageMap from './localStorageKeyMap.js';
|
||||
|
||||
describe('getLocalStorageMap', ()=>{
|
||||
it('no username', ()=>{
|
||||
const account = global.account;
|
||||
|
||||
delete global.account;
|
||||
|
||||
const map = getLocalStorageMap();
|
||||
|
||||
global.account = account;
|
||||
|
||||
expect(map).toBeInstanceOf(Object);
|
||||
expect(Object.entries(map)).toHaveLength(16);
|
||||
});
|
||||
|
||||
it('no username', ()=>{
|
||||
const account = global.account;
|
||||
|
||||
global.account = { username: 'test' };
|
||||
|
||||
const map = getLocalStorageMap();
|
||||
|
||||
global.account = account;
|
||||
|
||||
expect(map).toBeInstanceOf(Object);
|
||||
expect(Object.entries(map)).toHaveLength(17);
|
||||
expect(map).toHaveProperty('HOMEBREWERY-DEFAULT-SAVE-LOCATION-test', 'HB_editor_defaultSave_test');
|
||||
});
|
||||
});
|
||||
@@ -4,10 +4,7 @@ const updateLocalStorage = function(){
|
||||
// Return if no window and thus no local storage
|
||||
if(typeof window === 'undefined') return;
|
||||
|
||||
// Return if the local storage key map has no content
|
||||
const localStorageKeyMap = getLocalStorageMap();
|
||||
if(Object.keys(localStorageKeyMap).length == 0) return;
|
||||
|
||||
const storage = window.localStorage;
|
||||
|
||||
Object.keys(localStorageKeyMap).forEach((key)=>{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -611,3 +611,17 @@ h6,
|
||||
}
|
||||
.toc.wide li { break-inside : auto; }
|
||||
}
|
||||
|
||||
|
||||
/**********************************
|
||||
Firefox endruns
|
||||
**********************************/
|
||||
|
||||
@supports (-moz-user-select: none) { // This section will only apply to Firefox; it's the only browser that supports `-mos-xyz...`
|
||||
.page {
|
||||
blockquote, table {
|
||||
page-break-inside: auto;
|
||||
break-inside: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user