mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-09-23 22:43:56 +00:00
Merge branch 'master' into name-drop-cap-style-5024
This commit is contained in:
@@ -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,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';
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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);}
|
||||
};
|
||||
@@ -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)=>{
|
||||
|
||||
Reference in New Issue
Block a user