Merge branch 'master' into name-drop-cap-style-5024

This commit is contained in:
Eric
2026-09-21 14:11:14 +10:00
committed by GitHub
10 changed files with 90 additions and 69 deletions
+26 -40
View File
@@ -23,7 +23,6 @@ import safeHTML from './safeHTML.js';
const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m;
const PAGEBREAK_REGEX_LEGACY = /\\page(?:break)?/m;
const COLUMNBREAK_REGEX_LEGACY = /\\column(:?break)?/m;
const PAGE_HEIGHT = 1056;
const TOOLBAR_STATE_KEY = 'HB_renderer_toolbarState';
@@ -47,31 +46,25 @@ const BrewPage = (props)=>{
};
const pageRef = useRef(null);
const cleanText = safeHTML(props.contents);
const pageNum = props.index + 1;
useEffect(()=>{
if(!pageRef.current) return;
// Observer for tracking pages within the `.pages` div
// Observer for tracking which pages are at least 30% visible in the iframe
const visibleObserver = new IntersectionObserver(
(entries)=>{
entries.forEach((entry)=>{
if(entry.isIntersecting)
props.onVisibilityChange(props.index + 1, true, false); // add page to array of visible pages.
else
props.onVisibilityChange(props.index + 1, false, false);
});
},
(entries)=>entries.forEach((entry)=>{
props.onVisibilityChange(pageNum, entry.isIntersecting, false); // add page to array of visible pages.
}),
{ threshold: .3, rootMargin: '0px 0px 0px 0px' } // detect when >30% of page is within bounds.
);
// Observer for tracking the page at the center of the iframe.
const centerObserver = new IntersectionObserver(
(entries)=>{
entries.forEach((entry)=>{
if(entry.isIntersecting)
props.onVisibilityChange(props.index + 1, true, true); // Set this page as the center page
});
},
(entries)=>entries.forEach((entry)=>{
if(entry.isIntersecting)
props.onVisibilityChange(pageNum, true, true); // Set this page as the center page
}),
{ threshold: 0, rootMargin: '-50% 0px -50% 0px' } // Detect when the page is at the center
);
@@ -92,7 +85,7 @@ const BrewPage = (props)=>{
//v=====--------------------< Brew Renderer Component >-------------------=====v//
let renderedPages = [];
let pageTemplates = [];
const pageTemplates = [];
let rawPages = [];
const BrewRenderer = (props)=>{
@@ -100,22 +93,21 @@ const BrewRenderer = (props)=>{
text : '',
style : '',
renderer : 'legacy',
theme : '5ePHB',
lang : '',
errors : [],
currentEditorCursorPageNum : 1,
currentEditorViewPageNum : 1,
currentBrewRendererPageNum : 1,
themeBundle : {},
onPageChange : ()=>{},
...props
};
const [visiblePages, setVisiblePages] = useState([]);
const [centerPage , setCenterPage ] = useState(1);
const [state, setState] = useState({
isMounted : false,
visibility : 'hidden',
visiblePages : [],
centerPage : 1
isMounted : false,
visibility : 'hidden'
});
const [displayOptions, setDisplayOptions] = useState({
@@ -135,9 +127,7 @@ const BrewRenderer = (props)=>{
const [headerState, setHeaderState] = useState(false);
const mainRef = useRef(null);
const pagesRef = useRef(null);
const urlRef = useRef('');
if(props.renderer == 'legacy') {
rawPages = props.text.split(PAGEBREAK_REGEX_LEGACY);
@@ -146,20 +136,16 @@ const BrewRenderer = (props)=>{
}
const handlePageVisibilityChange = (pageNum, isVisible, isCenter)=>{
setState((prevState)=>{
const updatedVisiblePages = new Set(prevState.visiblePages);
if(!isCenter)
isVisible ? updatedVisiblePages.add(pageNum) : updatedVisiblePages.delete(pageNum);
return {
...prevState,
visiblePages : [...updatedVisiblePages].sort((a, b)=>a - b),
centerPage : isCenter ? pageNum : prevState.centerPage
};
setVisiblePages((prev)=>{
const updatedVisiblePages = new Set(prev);
isVisible ? updatedVisiblePages.add(pageNum) : updatedVisiblePages.delete(pageNum);
return [...updatedVisiblePages].sort((a, b)=>a - b);
});
if(isCenter)
if(isCenter) {
setCenterPage(pageNum);
props.onPageChange(pageNum);
}
};
const isInView = (index)=>{
@@ -341,19 +327,19 @@ const BrewRenderer = (props)=>{
: null}
<ErrorBar errors={props.errors} />
<div className='popups' ref={mainRef}>
<div className='popups'>
<RenderWarnings />
<NotificationPopup />
</div>
<ToolBar displayOptions={displayOptions} onDisplayOptionsChange={handleDisplayOptionsChange} visiblePages={state.visiblePages.length > 0 ? state.visiblePages : [state.centerPage]} totalPages={rawPages.length} headerState={headerState} setHeaderState={setHeaderState}/>
<ToolBar displayOptions={displayOptions} onDisplayOptionsChange={handleDisplayOptionsChange} visiblePages={visiblePages.length > 0 ? visiblePages : [centerPage]} totalPages={rawPages.length} headerState={headerState} setHeaderState={setHeaderState}/>
{/*render in iFrame so broken code doesn't crash the site.*/}
<Frame id='BrewRenderer' title="Rendered Brew Content" initialContent={INITIAL_CONTENT}
<Frame id='BrewRenderer' title='Rendered Brew Content' initialContent={INITIAL_CONTENT}
style={{ width: '100%', height: '100%', visibility: state.visibility }}
contentDidMount={frameDidMount}
onClick={()=>{emitClick();}}
sandbox="allow-same-origin allow-modals allow-top-navigation"
sandbox='allow-same-origin allow-modals allow-top-navigation'
>
<div className='brewRenderer'
onKeyDown={handleControlKeys}
-1
View File
@@ -1,6 +1,5 @@
import 'core-js/es/string/to-well-formed.js'; // Polyfill for older browsers
import './homebrew.less';
import React from 'react';
import { BrowserRouter as Router, Routes, Route, useParams, useSearchParams } from 'react-router';
import { updateLocalStorage } from './utils/updateLocalStorage/updateLocalStorageKeys.js';
+11 -17
View File
@@ -1,34 +1,28 @@
import './navbar.less';
import React from 'react';
import createReactClass from 'create-react-class';
import Nav from './nav.jsx';
import PatreonNavItem from './patreon.navitem.jsx';
const Navbar = createReactClass({
displayName : 'Navbar',
getInitialState : function() {
return {
ver : global.version || '0.0.0'
};
},
const Navbar = ({ children })=>{
const version = global.version || '0.0.0';
render : function(){
return <Nav.base>
return (
<Nav.base>
<Nav.section>
<Nav.logo />
<Nav.item href='/' className='homebrewLogo'>
<div>The Homebrewery</div>
</Nav.item>
<Nav.item newTab={true} href='/changelog' color='purple' icon='far fa-file-alt'>
{`v${this.state.ver}`}
{`v${version}`}
</Nav.item>
<PatreonNavItem />
{/*this.renderChromeWarning()*/}
{/* this.renderChromeWarning() */}
</Nav.section>
{this.props.children}
</Nav.base>;
}
});
{children}
</Nav.base>
);
};
export default Navbar;
export default Navbar;
@@ -289,12 +289,10 @@ const EditPage = (props)=>{
text={currentBrew.text}
style={currentBrew.style}
renderer={currentBrew.renderer}
theme={currentBrew.theme}
themeBundle={themeBundle}
errors={HTMLErrors}
lang={currentBrew.lang}
onPageChange={setCurrentBrewRendererPageNum}
currentEditorViewPageNum={currentEditorViewPageNum}
currentEditorCursorPageNum={currentEditorCursorPageNum}
currentBrewRendererPageNum={currentBrewRendererPageNum}
allowPrint={true}
+1 -2
View File
@@ -138,11 +138,10 @@ const HomePage =(props)=>{
text={currentBrew.text}
style={currentBrew.style}
renderer={currentBrew.renderer}
themeBundle={themeBundle}
onPageChange={setCurrentBrewRendererPageNum}
currentEditorViewPageNum={currentEditorViewPageNum}
currentEditorCursorPageNum={currentEditorCursorPageNum}
currentBrewRendererPageNum={currentBrewRendererPageNum}
themeBundle={themeBundle}
/>
</SplitPane>
</div>
@@ -183,12 +183,10 @@ const NewPage = (props)=>{
text={currentBrew.text}
style={currentBrew.style}
renderer={currentBrew.renderer}
theme={currentBrew.theme}
themeBundle={themeBundle}
errors={HTMLErrors}
lang={currentBrew.lang}
onPageChange={setCurrentBrewRendererPageNum}
currentEditorViewPageNum={currentEditorViewPageNum}
currentEditorCursorPageNum={currentEditorCursorPageNum}
currentBrewRendererPageNum={currentBrewRendererPageNum}
allowPrint={true}
@@ -39,6 +39,18 @@ const SharePage = (props)=>{
document.addEventListener('keydown', handleControlKeys);
fetchThemeBundle(undefined, setThemeBundle, brew.renderer, brew.theme);
// listen for changes in the brew version
const eventSource = new EventSource('/stream');
eventSource.addEventListener('message', (evt)=>{
const messageData = JSON.parse(evt.data);
if(messageData.eventType == 'brewUpdated'){
if(messageData.shareId == brew.shareId && messageData.version != brew.version) {
console.log(`brew has been updated, viewing ${brew.version}, new version is ${messageData.version}`);
}
}
});
return ()=>{
document.removeEventListener('keydown', handleControlKeys);
};
+26 -3
View File
@@ -1,4 +1,4 @@
/*eslint max-lines: ["warn", {"max": 500, "skipBlankLines": true, "skipComments": true}]*/
/*eslint max-lines: ["warn", {"max": 400, "skipBlankLines": true, "skipComments": true}]*/
// Set working directory to project root
import { dirname } from 'path';
import { fileURLToPath } from 'url';
@@ -30,6 +30,8 @@ import contentNegotiation from './middleware/content-negotiation.js';
import bodyParser from 'body-parser';
import cookieParser from 'cookie-parser';
import forceSSL from './forcessl.mw.js';
import Stream from './eventStreamSource.js';
import dbCheck from './middleware/dbCheck.js';
import cors from 'cors';
@@ -124,10 +126,10 @@ export default async function createApp(vite) {
};
app.use(pageRoutes({
defaultMetaTags,
defaultMetaTags,
HomebrewModel,
sanitizeBrew,
}));
}));
//Robots.txt
app.get('/robots.txt', (req, res)=>{
@@ -182,6 +184,27 @@ export default async function createApp(vite) {
}
});
// Create Event Stream source for pages to listen to
app.get('/stream', (req, res)=>{
res.writeHead(200, {
'Content-Type' : 'text/event-stream',
'Cache-Control' : 'no-cache',
'Connection' : 'keep-alive',
'Content-Encoding' : 'none'
});
Stream.on('sendUpdate', (event, data)=>{
console.log('Event:', event, '\nData:', data);
res.write(`data: ${JSON.stringify({ ...data, eventType: event })}\n\n`);
});
});
// After Stream starts, send initStream event
setTimeout(()=>{
Stream.emit('sendUpdate', 'initStream', { time: new Date });
}, 1000);
// Local only
if(isLocalEnvironment){
// Login
+9
View File
@@ -0,0 +1,9 @@
import { EventEmitter } from 'events';
const Stream = new EventEmitter;
export default {
emit : function(event) {return Stream.emit(event, ...([...arguments].slice(1)));}, // Arguments doesn't work for arrow functions
on : (event, listener)=>{return Stream.on(event, listener);},
off : (event, listener)=>{return Stream.off(event, listener);}
};
+5 -2
View File
@@ -21,6 +21,8 @@ const router = express.Router();
import { DEFAULT_BREW, DEFAULT_BREW_LOAD } from './brewDefaults.js';
import Themes from '../themes/themes.json' with { type: 'json' };
import Stream from './eventStreamSource.js';
const isStaticTheme = (renderer, themeName)=>{
return Themes[renderer]?.[themeName] !== undefined;
};
@@ -168,8 +170,7 @@ const api = {
const googleBrew = await GoogleActions.getGoogleBrew(oAuth2Client, googleId, id, accessType)
.catch((googleError)=>{
const reason = googleError.errors?.[0].reason;
if(reason == 'notFound')
if(googleError.code === 404 || googleError.status === 404)
throw { ...googleError, HBErrorCode: '02', authors: stub?.authors, account: req.account?.username };
else
throw { ...googleError, HBErrorCode: '01' };
@@ -501,6 +502,8 @@ const api = {
saved.textBin = undefined; // Remove textBin from the saved object to save bandwidth
Stream.emit('sendUpdate', 'brewUpdated', { time: new Date, shareId: brew.shareId, version: brew.version });
res.status(200).send(saved);
},
deleteGoogleBrew : async (account, id, editId, res)=>{