diff --git a/client/homebrew/brewRenderer/brewRenderer.jsx b/client/homebrew/brewRenderer/brewRenderer.jsx index 65de45177..75a07c7d1 100644 --- a/client/homebrew/brewRenderer/brewRenderer.jsx +++ b/client/homebrew/brewRenderer/brewRenderer.jsx @@ -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} -
+
- 0 ? state.visiblePages : [state.centerPage]} totalPages={rawPages.length} headerState={headerState} setHeaderState={setHeaderState}/> + 0 ? visiblePages : [centerPage]} totalPages={rawPages.length} headerState={headerState} setHeaderState={setHeaderState}/> {/*render in iFrame so broken code doesn't crash the site.*/} - {emitClick();}} - sandbox="allow-same-origin allow-modals allow-top-navigation" + sandbox='allow-same-origin allow-modals allow-top-navigation' >
{ + const version = global.version || '0.0.0'; - render : function(){ - return + return ( +
The Homebrewery
- {`v${this.state.ver}`} + {`v${version}`} - {/*this.renderChromeWarning()*/} + {/* this.renderChromeWarning() */}
- {this.props.children} -
; - } -}); + {children} +
+ ); +}; -export default Navbar; +export default Navbar; \ No newline at end of file diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index 205cf3fcb..57667ca6e 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -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} diff --git a/client/homebrew/pages/homePage/homePage.jsx b/client/homebrew/pages/homePage/homePage.jsx index 0132a1147..0ac78f0e1 100644 --- a/client/homebrew/pages/homePage/homePage.jsx +++ b/client/homebrew/pages/homePage/homePage.jsx @@ -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} />
diff --git a/client/homebrew/pages/newPage/newPage.jsx b/client/homebrew/pages/newPage/newPage.jsx index 48d589c1e..c7b6c7160 100644 --- a/client/homebrew/pages/newPage/newPage.jsx +++ b/client/homebrew/pages/newPage/newPage.jsx @@ -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} diff --git a/client/homebrew/pages/sharePage/sharePage.jsx b/client/homebrew/pages/sharePage/sharePage.jsx index 8df241d7b..d81b4d878 100644 --- a/client/homebrew/pages/sharePage/sharePage.jsx +++ b/client/homebrew/pages/sharePage/sharePage.jsx @@ -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); }; diff --git a/server/app.js b/server/app.js index eb078a877..714c4beba 100644 --- a/server/app.js +++ b/server/app.js @@ -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 diff --git a/server/eventStreamSource.js b/server/eventStreamSource.js new file mode 100644 index 000000000..66fdb3354 --- /dev/null +++ b/server/eventStreamSource.js @@ -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);} +}; \ No newline at end of file diff --git a/server/homebrew.api.js b/server/homebrew.api.js index 4cca76193..90543ceaa 100644 --- a/server/homebrew.api.js +++ b/server/homebrew.api.js @@ -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)=>{