mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-09-24 03:02: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_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m;
|
||||||
const PAGEBREAK_REGEX_LEGACY = /\\page(?:break)?/m;
|
const PAGEBREAK_REGEX_LEGACY = /\\page(?:break)?/m;
|
||||||
const COLUMNBREAK_REGEX_LEGACY = /\\column(:?break)?/m;
|
const COLUMNBREAK_REGEX_LEGACY = /\\column(:?break)?/m;
|
||||||
const PAGE_HEIGHT = 1056;
|
|
||||||
|
|
||||||
const TOOLBAR_STATE_KEY = 'HB_renderer_toolbarState';
|
const TOOLBAR_STATE_KEY = 'HB_renderer_toolbarState';
|
||||||
|
|
||||||
@@ -47,31 +46,25 @@ const BrewPage = (props)=>{
|
|||||||
};
|
};
|
||||||
const pageRef = useRef(null);
|
const pageRef = useRef(null);
|
||||||
const cleanText = safeHTML(props.contents);
|
const cleanText = safeHTML(props.contents);
|
||||||
|
const pageNum = props.index + 1;
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
if(!pageRef.current) return;
|
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(
|
const visibleObserver = new IntersectionObserver(
|
||||||
(entries)=>{
|
(entries)=>entries.forEach((entry)=>{
|
||||||
entries.forEach((entry)=>{
|
props.onVisibilityChange(pageNum, entry.isIntersecting, false); // add page to array of visible pages.
|
||||||
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);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
{ threshold: .3, rootMargin: '0px 0px 0px 0px' } // detect when >30% of page is within bounds.
|
{ 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.
|
// Observer for tracking the page at the center of the iframe.
|
||||||
const centerObserver = new IntersectionObserver(
|
const centerObserver = new IntersectionObserver(
|
||||||
(entries)=>{
|
(entries)=>entries.forEach((entry)=>{
|
||||||
entries.forEach((entry)=>{
|
if(entry.isIntersecting)
|
||||||
if(entry.isIntersecting)
|
props.onVisibilityChange(pageNum, true, true); // Set this page as the center page
|
||||||
props.onVisibilityChange(props.index + 1, true, true); // Set this page as the center page
|
}),
|
||||||
});
|
|
||||||
},
|
|
||||||
{ threshold: 0, rootMargin: '-50% 0px -50% 0px' } // Detect when the page is at the center
|
{ 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//
|
//v=====--------------------< Brew Renderer Component >-------------------=====v//
|
||||||
let renderedPages = [];
|
let renderedPages = [];
|
||||||
let pageTemplates = [];
|
const pageTemplates = [];
|
||||||
let rawPages = [];
|
let rawPages = [];
|
||||||
|
|
||||||
const BrewRenderer = (props)=>{
|
const BrewRenderer = (props)=>{
|
||||||
@@ -100,22 +93,21 @@ const BrewRenderer = (props)=>{
|
|||||||
text : '',
|
text : '',
|
||||||
style : '',
|
style : '',
|
||||||
renderer : 'legacy',
|
renderer : 'legacy',
|
||||||
theme : '5ePHB',
|
|
||||||
lang : '',
|
lang : '',
|
||||||
errors : [],
|
errors : [],
|
||||||
currentEditorCursorPageNum : 1,
|
currentEditorCursorPageNum : 1,
|
||||||
currentEditorViewPageNum : 1,
|
|
||||||
currentBrewRendererPageNum : 1,
|
currentBrewRendererPageNum : 1,
|
||||||
themeBundle : {},
|
themeBundle : {},
|
||||||
onPageChange : ()=>{},
|
onPageChange : ()=>{},
|
||||||
...props
|
...props
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [visiblePages, setVisiblePages] = useState([]);
|
||||||
|
const [centerPage , setCenterPage ] = useState(1);
|
||||||
|
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
isMounted : false,
|
isMounted : false,
|
||||||
visibility : 'hidden',
|
visibility : 'hidden'
|
||||||
visiblePages : [],
|
|
||||||
centerPage : 1
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const [displayOptions, setDisplayOptions] = useState({
|
const [displayOptions, setDisplayOptions] = useState({
|
||||||
@@ -135,9 +127,7 @@ const BrewRenderer = (props)=>{
|
|||||||
|
|
||||||
const [headerState, setHeaderState] = useState(false);
|
const [headerState, setHeaderState] = useState(false);
|
||||||
|
|
||||||
const mainRef = useRef(null);
|
|
||||||
const pagesRef = useRef(null);
|
const pagesRef = useRef(null);
|
||||||
const urlRef = useRef('');
|
|
||||||
|
|
||||||
if(props.renderer == 'legacy') {
|
if(props.renderer == 'legacy') {
|
||||||
rawPages = props.text.split(PAGEBREAK_REGEX_LEGACY);
|
rawPages = props.text.split(PAGEBREAK_REGEX_LEGACY);
|
||||||
@@ -146,20 +136,16 @@ const BrewRenderer = (props)=>{
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handlePageVisibilityChange = (pageNum, isVisible, isCenter)=>{
|
const handlePageVisibilityChange = (pageNum, isVisible, isCenter)=>{
|
||||||
setState((prevState)=>{
|
setVisiblePages((prev)=>{
|
||||||
const updatedVisiblePages = new Set(prevState.visiblePages);
|
const updatedVisiblePages = new Set(prev);
|
||||||
if(!isCenter)
|
isVisible ? updatedVisiblePages.add(pageNum) : updatedVisiblePages.delete(pageNum);
|
||||||
isVisible ? updatedVisiblePages.add(pageNum) : updatedVisiblePages.delete(pageNum);
|
return [...updatedVisiblePages].sort((a, b)=>a - b);
|
||||||
|
|
||||||
return {
|
|
||||||
...prevState,
|
|
||||||
visiblePages : [...updatedVisiblePages].sort((a, b)=>a - b),
|
|
||||||
centerPage : isCenter ? pageNum : prevState.centerPage
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if(isCenter)
|
if(isCenter) {
|
||||||
|
setCenterPage(pageNum);
|
||||||
props.onPageChange(pageNum);
|
props.onPageChange(pageNum);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isInView = (index)=>{
|
const isInView = (index)=>{
|
||||||
@@ -341,19 +327,19 @@ const BrewRenderer = (props)=>{
|
|||||||
: null}
|
: null}
|
||||||
|
|
||||||
<ErrorBar errors={props.errors} />
|
<ErrorBar errors={props.errors} />
|
||||||
<div className='popups' ref={mainRef}>
|
<div className='popups'>
|
||||||
<RenderWarnings />
|
<RenderWarnings />
|
||||||
<NotificationPopup />
|
<NotificationPopup />
|
||||||
</div>
|
</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.*/}
|
{/*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 }}
|
style={{ width: '100%', height: '100%', visibility: state.visibility }}
|
||||||
contentDidMount={frameDidMount}
|
contentDidMount={frameDidMount}
|
||||||
onClick={()=>{emitClick();}}
|
onClick={()=>{emitClick();}}
|
||||||
sandbox="allow-same-origin allow-modals allow-top-navigation"
|
sandbox='allow-same-origin allow-modals allow-top-navigation'
|
||||||
>
|
>
|
||||||
<div className='brewRenderer'
|
<div className='brewRenderer'
|
||||||
onKeyDown={handleControlKeys}
|
onKeyDown={handleControlKeys}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'core-js/es/string/to-well-formed.js'; // Polyfill for older browsers
|
import 'core-js/es/string/to-well-formed.js'; // Polyfill for older browsers
|
||||||
import './homebrew.less';
|
import './homebrew.less';
|
||||||
import React from 'react';
|
|
||||||
import { BrowserRouter as Router, Routes, Route, useParams, useSearchParams } from 'react-router';
|
import { BrowserRouter as Router, Routes, Route, useParams, useSearchParams } from 'react-router';
|
||||||
|
|
||||||
import { updateLocalStorage } from './utils/updateLocalStorage/updateLocalStorageKeys.js';
|
import { updateLocalStorage } from './utils/updateLocalStorage/updateLocalStorageKeys.js';
|
||||||
|
|||||||
@@ -1,34 +1,28 @@
|
|||||||
import './navbar.less';
|
import './navbar.less';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import createReactClass from 'create-react-class';
|
|
||||||
|
|
||||||
import Nav from './nav.jsx';
|
import Nav from './nav.jsx';
|
||||||
import PatreonNavItem from './patreon.navitem.jsx';
|
import PatreonNavItem from './patreon.navitem.jsx';
|
||||||
|
|
||||||
const Navbar = createReactClass({
|
const Navbar = ({ children })=>{
|
||||||
displayName : 'Navbar',
|
const version = global.version || '0.0.0';
|
||||||
getInitialState : function() {
|
|
||||||
return {
|
|
||||||
ver : global.version || '0.0.0'
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
render : function(){
|
return (
|
||||||
return <Nav.base>
|
<Nav.base>
|
||||||
<Nav.section>
|
<Nav.section>
|
||||||
<Nav.logo />
|
<Nav.logo />
|
||||||
<Nav.item href='/' className='homebrewLogo'>
|
<Nav.item href='/' className='homebrewLogo'>
|
||||||
<div>The Homebrewery</div>
|
<div>The Homebrewery</div>
|
||||||
</Nav.item>
|
</Nav.item>
|
||||||
<Nav.item newTab={true} href='/changelog' color='purple' icon='far fa-file-alt'>
|
<Nav.item newTab={true} href='/changelog' color='purple' icon='far fa-file-alt'>
|
||||||
{`v${this.state.ver}`}
|
{`v${version}`}
|
||||||
</Nav.item>
|
</Nav.item>
|
||||||
<PatreonNavItem />
|
<PatreonNavItem />
|
||||||
{/*this.renderChromeWarning()*/}
|
{/* this.renderChromeWarning() */}
|
||||||
</Nav.section>
|
</Nav.section>
|
||||||
{this.props.children}
|
{children}
|
||||||
</Nav.base>;
|
</Nav.base>
|
||||||
}
|
);
|
||||||
});
|
};
|
||||||
|
|
||||||
export default Navbar;
|
export default Navbar;
|
||||||
@@ -289,12 +289,10 @@ const EditPage = (props)=>{
|
|||||||
text={currentBrew.text}
|
text={currentBrew.text}
|
||||||
style={currentBrew.style}
|
style={currentBrew.style}
|
||||||
renderer={currentBrew.renderer}
|
renderer={currentBrew.renderer}
|
||||||
theme={currentBrew.theme}
|
|
||||||
themeBundle={themeBundle}
|
themeBundle={themeBundle}
|
||||||
errors={HTMLErrors}
|
errors={HTMLErrors}
|
||||||
lang={currentBrew.lang}
|
lang={currentBrew.lang}
|
||||||
onPageChange={setCurrentBrewRendererPageNum}
|
onPageChange={setCurrentBrewRendererPageNum}
|
||||||
currentEditorViewPageNum={currentEditorViewPageNum}
|
|
||||||
currentEditorCursorPageNum={currentEditorCursorPageNum}
|
currentEditorCursorPageNum={currentEditorCursorPageNum}
|
||||||
currentBrewRendererPageNum={currentBrewRendererPageNum}
|
currentBrewRendererPageNum={currentBrewRendererPageNum}
|
||||||
allowPrint={true}
|
allowPrint={true}
|
||||||
|
|||||||
@@ -138,11 +138,10 @@ const HomePage =(props)=>{
|
|||||||
text={currentBrew.text}
|
text={currentBrew.text}
|
||||||
style={currentBrew.style}
|
style={currentBrew.style}
|
||||||
renderer={currentBrew.renderer}
|
renderer={currentBrew.renderer}
|
||||||
|
themeBundle={themeBundle}
|
||||||
onPageChange={setCurrentBrewRendererPageNum}
|
onPageChange={setCurrentBrewRendererPageNum}
|
||||||
currentEditorViewPageNum={currentEditorViewPageNum}
|
|
||||||
currentEditorCursorPageNum={currentEditorCursorPageNum}
|
currentEditorCursorPageNum={currentEditorCursorPageNum}
|
||||||
currentBrewRendererPageNum={currentBrewRendererPageNum}
|
currentBrewRendererPageNum={currentBrewRendererPageNum}
|
||||||
themeBundle={themeBundle}
|
|
||||||
/>
|
/>
|
||||||
</SplitPane>
|
</SplitPane>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -183,12 +183,10 @@ const NewPage = (props)=>{
|
|||||||
text={currentBrew.text}
|
text={currentBrew.text}
|
||||||
style={currentBrew.style}
|
style={currentBrew.style}
|
||||||
renderer={currentBrew.renderer}
|
renderer={currentBrew.renderer}
|
||||||
theme={currentBrew.theme}
|
|
||||||
themeBundle={themeBundle}
|
themeBundle={themeBundle}
|
||||||
errors={HTMLErrors}
|
errors={HTMLErrors}
|
||||||
lang={currentBrew.lang}
|
lang={currentBrew.lang}
|
||||||
onPageChange={setCurrentBrewRendererPageNum}
|
onPageChange={setCurrentBrewRendererPageNum}
|
||||||
currentEditorViewPageNum={currentEditorViewPageNum}
|
|
||||||
currentEditorCursorPageNum={currentEditorCursorPageNum}
|
currentEditorCursorPageNum={currentEditorCursorPageNum}
|
||||||
currentBrewRendererPageNum={currentBrewRendererPageNum}
|
currentBrewRendererPageNum={currentBrewRendererPageNum}
|
||||||
allowPrint={true}
|
allowPrint={true}
|
||||||
|
|||||||
@@ -39,6 +39,18 @@ const SharePage = (props)=>{
|
|||||||
document.addEventListener('keydown', handleControlKeys);
|
document.addEventListener('keydown', handleControlKeys);
|
||||||
fetchThemeBundle(undefined, setThemeBundle, brew.renderer, brew.theme);
|
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 ()=>{
|
return ()=>{
|
||||||
document.removeEventListener('keydown', handleControlKeys);
|
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
|
// Set working directory to project root
|
||||||
import { dirname } from 'path';
|
import { dirname } from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
@@ -30,6 +30,8 @@ import contentNegotiation from './middleware/content-negotiation.js';
|
|||||||
import bodyParser from 'body-parser';
|
import bodyParser from 'body-parser';
|
||||||
import cookieParser from 'cookie-parser';
|
import cookieParser from 'cookie-parser';
|
||||||
import forceSSL from './forcessl.mw.js';
|
import forceSSL from './forcessl.mw.js';
|
||||||
|
|
||||||
|
import Stream from './eventStreamSource.js';
|
||||||
import dbCheck from './middleware/dbCheck.js';
|
import dbCheck from './middleware/dbCheck.js';
|
||||||
|
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
@@ -124,10 +126,10 @@ export default async function createApp(vite) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
app.use(pageRoutes({
|
app.use(pageRoutes({
|
||||||
defaultMetaTags,
|
defaultMetaTags,
|
||||||
HomebrewModel,
|
HomebrewModel,
|
||||||
sanitizeBrew,
|
sanitizeBrew,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
//Robots.txt
|
//Robots.txt
|
||||||
app.get('/robots.txt', (req, res)=>{
|
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
|
// Local only
|
||||||
if(isLocalEnvironment){
|
if(isLocalEnvironment){
|
||||||
// Login
|
// 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 { DEFAULT_BREW, DEFAULT_BREW_LOAD } from './brewDefaults.js';
|
||||||
import Themes from '../themes/themes.json' with { type: 'json' };
|
import Themes from '../themes/themes.json' with { type: 'json' };
|
||||||
|
|
||||||
|
import Stream from './eventStreamSource.js';
|
||||||
|
|
||||||
const isStaticTheme = (renderer, themeName)=>{
|
const isStaticTheme = (renderer, themeName)=>{
|
||||||
return Themes[renderer]?.[themeName] !== undefined;
|
return Themes[renderer]?.[themeName] !== undefined;
|
||||||
};
|
};
|
||||||
@@ -168,8 +170,7 @@ const api = {
|
|||||||
|
|
||||||
const googleBrew = await GoogleActions.getGoogleBrew(oAuth2Client, googleId, id, accessType)
|
const googleBrew = await GoogleActions.getGoogleBrew(oAuth2Client, googleId, id, accessType)
|
||||||
.catch((googleError)=>{
|
.catch((googleError)=>{
|
||||||
const reason = googleError.errors?.[0].reason;
|
if(googleError.code === 404 || googleError.status === 404)
|
||||||
if(reason == 'notFound')
|
|
||||||
throw { ...googleError, HBErrorCode: '02', authors: stub?.authors, account: req.account?.username };
|
throw { ...googleError, HBErrorCode: '02', authors: stub?.authors, account: req.account?.username };
|
||||||
else
|
else
|
||||||
throw { ...googleError, HBErrorCode: '01' };
|
throw { ...googleError, HBErrorCode: '01' };
|
||||||
@@ -501,6 +502,8 @@ const api = {
|
|||||||
|
|
||||||
saved.textBin = undefined; // Remove textBin from the saved object to save bandwidth
|
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);
|
res.status(200).send(saved);
|
||||||
},
|
},
|
||||||
deleteGoogleBrew : async (account, id, editId, res)=>{
|
deleteGoogleBrew : async (account, id, editId, res)=>{
|
||||||
|
|||||||
Reference in New Issue
Block a user