mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-05-07 20:58:40 +00:00
Merge branch 'master' of https://github.com/naturalcrit/homebrewery into update-Codemirror
This commit is contained in:
@@ -106,6 +106,7 @@ const BrewRenderer = (props)=>{
|
||||
currentBrewRendererPageNum : 1,
|
||||
themeBundle : {},
|
||||
onPageChange : ()=>{},
|
||||
showToolbar : true,
|
||||
...props
|
||||
};
|
||||
|
||||
@@ -271,7 +272,6 @@ const BrewRenderer = (props)=>{
|
||||
|
||||
const frameDidMount = ()=>{ //This triggers when iFrame finishes internal "componentDidMount"
|
||||
scrollToHash(window.location.hash);
|
||||
|
||||
setTimeout(()=>{ //We still see a flicker where the style isn't applied yet, so wait 100ms before showing iFrame
|
||||
renderPages(); //Make sure page is renderable before showing
|
||||
setState((prevState)=>({
|
||||
@@ -301,6 +301,53 @@ const BrewRenderer = (props)=>{
|
||||
const renderedStyle = useMemo(()=>renderStyle(), [props.style, props.themeBundle]);
|
||||
renderedPages = useMemo(()=>renderPages(), [props.text, displayOptions]);
|
||||
|
||||
const toolbarEl = <ToolBar displayOptions={displayOptions} onDisplayOptionsChange={handleDisplayOptionsChange} visiblePages={state.visiblePages.length > 0 ? state.visiblePages : [state.centerPage]} totalPages={rawPages.length} headerState={headerState} setHeaderState={setHeaderState}/>;
|
||||
|
||||
const brewRenderFrameContents = (
|
||||
<>
|
||||
<div className='brewRenderer'
|
||||
onKeyDown={handleControlKeys}
|
||||
tabIndex={-1}
|
||||
>
|
||||
|
||||
{/* Apply CSS from Style tab and render pages from Markdown tab */}
|
||||
{state.isMounted
|
||||
&&
|
||||
<>
|
||||
{renderedStyle}
|
||||
<div className={`pages ${displayOptions.startOnRight ? 'recto' : 'verso'} ${displayOptions.spread}`} lang={`${props.lang || 'en'}`} style={pagesStyle} ref={pagesRef}>
|
||||
{renderedPages}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
{headerState ? <HeaderNav ref={pagesRef} /> : <></>}
|
||||
</>
|
||||
);
|
||||
|
||||
const brewRenderFrameWrapper = (
|
||||
<>
|
||||
<Frame id='BrewRenderer' initialContent={INITIAL_CONTENT}
|
||||
style={{ width: '100%', height: '100%', visibility: state.visibility }}
|
||||
contentDidMount={frameDidMount}
|
||||
onClick={()=>{emitClick();}}
|
||||
>
|
||||
{brewRenderFrameContents}
|
||||
</Frame>
|
||||
</>
|
||||
);
|
||||
|
||||
const brewRenderDivWrapper = (
|
||||
<>
|
||||
<div id='BrewRendererFlat'
|
||||
style={{ width: '100%', height: '100%', visibility: state.visibility }}
|
||||
>
|
||||
{brewRenderFrameContents}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
if (!props.showToolbar && state.visibility != 'visible') { frameDidMount(); }
|
||||
return (
|
||||
<>
|
||||
{/*render dummy page while iFrame is mounting.*/}
|
||||
@@ -318,32 +365,13 @@ const BrewRenderer = (props)=>{
|
||||
<NotificationPopup />
|
||||
</div>
|
||||
|
||||
<ToolBar displayOptions={displayOptions} onDisplayOptionsChange={handleDisplayOptionsChange} visiblePages={state.visiblePages.length > 0 ? state.visiblePages : [state.centerPage]} totalPages={rawPages.length} headerState={headerState} setHeaderState={setHeaderState}/>
|
||||
{props.showToolbar ? toolbarEl : ''}
|
||||
|
||||
{/*render in iFrame so broken code doesn't crash the site.*/}
|
||||
<Frame id='BrewRenderer' initialContent={INITIAL_CONTENT}
|
||||
style={{ width: '100%', height: '100%', visibility: state.visibility }}
|
||||
contentDidMount={frameDidMount}
|
||||
onClick={()=>{emitClick();}}
|
||||
>
|
||||
<div className='brewRenderer'
|
||||
onKeyDown={handleControlKeys}
|
||||
tabIndex={-1}
|
||||
>
|
||||
|
||||
{/* Apply CSS from Style tab and render pages from Markdown tab */}
|
||||
{state.isMounted
|
||||
&&
|
||||
<>
|
||||
{renderedStyle}
|
||||
<div className={`pages ${displayOptions.startOnRight ? 'recto' : 'verso'} ${displayOptions.spread}`} lang={`${props.lang || 'en'}`} style={pagesStyle} ref={pagesRef}>
|
||||
{renderedPages}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
{headerState ? <HeaderNav ref={pagesRef} /> : <></>}
|
||||
</Frame>
|
||||
{props.showToolbar ? brewRenderFrameWrapper:brewRenderDivWrapper}
|
||||
{state.isMounted &&
|
||||
<div id='brewRendered'></div>
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ import HomePage from './pages/homePage/homePage.jsx';
|
||||
import EditPage from './pages/editPage/editPage.jsx';
|
||||
import UserPage from './pages/userPage/userPage.jsx';
|
||||
import SharePage from './pages/sharePage/sharePage.jsx';
|
||||
import EmbedPage from './pages/embedPage/embedPage.jsx';
|
||||
import NewPage from './pages/newPage/newPage.jsx';
|
||||
import ErrorPage from './pages/errorPage/errorPage.jsx';
|
||||
import VaultPage from './pages/vaultPage/vaultPage.jsx';
|
||||
@@ -70,7 +71,8 @@ const Homebrew = (props)=>{
|
||||
<div className={`homebrew${(config?.deployment || config?.local) ? ' deployment' : ''}`} style={backgroundObject()}>
|
||||
<Routes>
|
||||
<Route path='/edit/:id' element={<WithRoute el={EditPage} brew={brew} userThemes={userThemes}/>} />
|
||||
<Route path='/share/:id' element={<WithRoute el={SharePage} brew={brew} />} />
|
||||
<Route path='/share/:id' element={<WithRoute el={SharePage} brew={brew} share={true} />} />
|
||||
<Route path='/embed/:id' element={<WithRoute el={EmbedPage} brew={brew} share={false} />} />
|
||||
<Route path='/new/:id' element={<WithRoute el={NewPage} brew={brew} userThemes={userThemes}/>} />
|
||||
<Route path='/new' element={<WithRoute el={NewPage} userThemes={userThemes}/> } />
|
||||
<Route path='/user/:username' element={<WithRoute el={UserPage} brews={brews} />} />
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
const React = require('react');
|
||||
const Nav = require('client/homebrew/navbar/nav.jsx');
|
||||
const { printCurrentBrew } = require('../../../shared/helpers.js');
|
||||
|
||||
module.exports = function(props){
|
||||
return <Nav.item onClick={printCurrentBrew} color='purple' icon='far fa-file-pdf'>
|
||||
get PDF
|
||||
</Nav.item>;
|
||||
};
|
||||
@@ -1,9 +1,20 @@
|
||||
import React from 'react';
|
||||
import Nav from './nav.jsx';
|
||||
import { printCurrentBrew } from '@shared/helpers.js';
|
||||
import { printCurrentBrew, scrapeBrewHTML, scrapeBrewZip } from '@shared/helpers.js';
|
||||
|
||||
export default function(){
|
||||
return <Nav.item onClick={printCurrentBrew} color='purple' icon='far fa-file-pdf'>
|
||||
get PDF
|
||||
</Nav.item>;
|
||||
export default function(props){
|
||||
return <Nav.dropdown>
|
||||
<Nav.item color='grey' icon='fas fa-question-circle'>
|
||||
export
|
||||
</Nav.item>
|
||||
<Nav.item onClick={printCurrentBrew} color='purple' icon='far fa-file-pdf'>
|
||||
get PDF
|
||||
</Nav.item>
|
||||
<Nav.item onClick={scrapeBrewHTML} color='orange' icon='fas fa-file-code'>
|
||||
get HTML
|
||||
</Nav.item>
|
||||
<Nav.item onClick={scrapeBrewZip} color='orange' icon='fas fa-file-archive'>
|
||||
get HTML (Zip)
|
||||
</Nav.item>
|
||||
</Nav.dropdown>;
|
||||
};
|
||||
|
||||
@@ -362,7 +362,7 @@ const EditPage = (props)=>{
|
||||
{renderAutoSaveButton()}
|
||||
</Nav.dropdown>}
|
||||
<NewBrewItem />
|
||||
<PrintNavItem />
|
||||
<PrintNavItem brew={currentBrew}/>
|
||||
<HelpNavItem />
|
||||
<VaultNavItem />
|
||||
<ShareNavItem brew={currentBrew} />
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
import './embedPage.less';
|
||||
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import Headtags from '../../../../vitreum/headtags.js';
|
||||
import MarkdownLegacy from '@shared/markdownLegacy.js';
|
||||
import Markdown from '@shared/markdown.js';
|
||||
|
||||
const Meta = Headtags.Meta;
|
||||
|
||||
import Nav from '@navbar/nav.jsx';
|
||||
import Navbar from '@navbar/navbar.jsx';
|
||||
import MetadataNav from '@navbar/metadata.navitem.jsx';
|
||||
import PrintNavItem from '@navbar/print.navitem.jsx';
|
||||
import RecentNavItems from '@navbar/recent.navitem.jsx';
|
||||
const { both: RecentNavItem } = RecentNavItems;
|
||||
import Account from '@navbar/account.navitem.jsx';
|
||||
import safeHTML from '../../brewRenderer/safeHTML.js';
|
||||
|
||||
import { DEFAULT_BREW_LOAD } from '../../../../server/brewDefaults.js';
|
||||
import { printCurrentBrew, fetchThemeBundle } from '@shared/helpers.js';
|
||||
import _ from 'lodash';
|
||||
|
||||
const PAGEBREAK_REGEX_V3 = /^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m;
|
||||
const PAGEBREAK_REGEX_LEGACY = /\\page(?:break)?/m;
|
||||
const COLUMNBREAK_REGEX_LEGACY = /\\column(:?break)?/m;
|
||||
|
||||
let renderedPages = [];
|
||||
let rawPages = [];
|
||||
|
||||
const BrewPage = (props)=>{
|
||||
props = {
|
||||
contents : '',
|
||||
index : 0,
|
||||
...props
|
||||
};
|
||||
const pageRef = useRef(null);
|
||||
const cleanText = safeHTML(props.contents);
|
||||
|
||||
return <div className={props.className} id={`p${props.index + 1}`} data-index={props.index} ref={pageRef} style={props.style} {...props.attributes}>
|
||||
<div className='columnWrapper' dangerouslySetInnerHTML={{ __html: cleanText }} />
|
||||
</div>;
|
||||
};
|
||||
|
||||
|
||||
const EmbedPage = (props)=>{
|
||||
const [displayOptions, setDisplayOptions] = useState({
|
||||
zoomLevel : 100,
|
||||
spread : 'single',
|
||||
startOnRight : true,
|
||||
pageShadows : true,
|
||||
rowGap : 5,
|
||||
columnGap : 10,
|
||||
});
|
||||
|
||||
if(props.renderer == 'legacy') {
|
||||
rawPages = props.brew.text.split(PAGEBREAK_REGEX_LEGACY);
|
||||
} else {
|
||||
rawPages = props.brew.text.split(PAGEBREAK_REGEX_V3);
|
||||
}
|
||||
|
||||
const pagesStyle = {
|
||||
zoom : `${displayOptions.zoomLevel}%`,
|
||||
columnGap : `${displayOptions.columnGap}px`,
|
||||
rowGap : `${displayOptions.rowGap}px`,
|
||||
overflowY : 'auto'
|
||||
};
|
||||
|
||||
const { brew = DEFAULT_BREW_LOAD, disableMeta = false, share = true } = props;
|
||||
|
||||
const [themeBundle, setThemeBundle] = useState({});
|
||||
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
|
||||
|
||||
const handleBrewRendererPageChange = useCallback((pageNumber)=>{
|
||||
setCurrentBrewRendererPageNum(pageNumber);
|
||||
}, []);
|
||||
|
||||
const handleControlKeys = (e)=>{
|
||||
if(!(e.ctrlKey || e.metaKey)) return;
|
||||
const P_KEY = 80;
|
||||
if(e.keyCode === P_KEY) {
|
||||
printCurrentBrew();
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(()=>{
|
||||
document.addEventListener('keydown', handleControlKeys);
|
||||
fetchThemeBundle(undefined, setThemeBundle, brew.renderer, brew.theme);
|
||||
|
||||
return ()=>{
|
||||
document.removeEventListener('keydown', handleControlKeys);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const renderStyle = ()=>{
|
||||
const themeStyles = themeBundle?.joinedStyles ?? '<style>@import url("/themes/V3/Blank/style.css");</style>';
|
||||
const cleanStyle = safeHTML(`${themeStyles} \n\n <style> ${props.brew.style} </style>`);
|
||||
return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: cleanStyle }} />;
|
||||
};
|
||||
|
||||
const renderPage = (pageText, index)=>{
|
||||
|
||||
let styles = {
|
||||
...(!displayOptions.pageShadows ? { boxShadow: 'none' } : {})
|
||||
// Add more conditions as needed
|
||||
};
|
||||
let classes = 'page';
|
||||
let attributes = {};
|
||||
|
||||
if(props.renderer == 'legacy') {
|
||||
pageText.replace(COLUMNBREAK_REGEX_LEGACY, '```\n````\n'); // Allow Legacy brews to use `\column(break)`
|
||||
const html = MarkdownLegacy.render(pageText);
|
||||
|
||||
return <BrewPage className='page phb' index={index} key={index} contents={html} style={styles} />;
|
||||
} else {
|
||||
if(pageText.startsWith('\\page')) {
|
||||
const firstLineTokens = Markdown.marked.lexer(pageText.split('\n', 1)[0])[0].tokens;
|
||||
const injectedTags = firstLineTokens?.find((obj)=>obj.injectedTags !== undefined)?.injectedTags;
|
||||
if(injectedTags) {
|
||||
styles = { ...styles, ...injectedTags.styles };
|
||||
styles = _.mapKeys(styles, (v, k)=>k.startsWith('--') ? k : _.camelCase(k)); // Convert CSS to camelCase for React
|
||||
classes = [classes, injectedTags.classes].join(' ').trim();
|
||||
attributes = injectedTags.attributes;
|
||||
}
|
||||
pageText = pageText.includes('\n') ? pageText.substring(pageText.indexOf('\n') + 1) : ''; // Remove the \page line
|
||||
}
|
||||
|
||||
// DO NOT REMOVE!!! REQUIRED FOR BACKWARDS COMPATIBILITY WITH NON-UPGRADABLE VERSIONS OF CHROME.
|
||||
pageText += `\n\n \n\\column\n `; //Artificial column break at page end to emulate column-fill:auto (until `wide` is used, when column-fill:balance will reappear)
|
||||
|
||||
const html = Markdown.render(pageText, index);
|
||||
|
||||
return <BrewPage className={classes} index={index} key={index} contents={html} style={styles} attributes={attributes} />;
|
||||
}
|
||||
};
|
||||
|
||||
const renderPages = ()=>{
|
||||
if(props.errors && props.errors.length)
|
||||
return renderedPages;
|
||||
|
||||
renderedPages.length = 0;
|
||||
|
||||
_.forEach(rawPages, (page, index)=>{
|
||||
{
|
||||
renderedPages[index] = renderPage(page, index); // Render any page not yet rendered, but only re-render those in PPR range
|
||||
}
|
||||
});
|
||||
return renderedPages;
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Meta name='robots' content='noindex, nofollow' />
|
||||
{renderStyle()}
|
||||
<div className={`pages ${displayOptions.startOnRight ? 'recto' : 'verso'} ${displayOptions.spread}`} lang={`${props.lang || 'en'}`} style={pagesStyle}>
|
||||
{renderPages()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmbedPage;
|
||||
@@ -0,0 +1,8 @@
|
||||
.page {
|
||||
page-break-before : auto;
|
||||
page-break-after : auto;
|
||||
}
|
||||
|
||||
.homebrew:not(:has(>.sitePage)) {
|
||||
background: unset !important;
|
||||
}
|
||||
@@ -182,7 +182,7 @@ const HomePage =(props)=>{
|
||||
? <ErrorNavItem error={error} clearError={clearError} />
|
||||
: renderSaveButton()}
|
||||
<NewBrewItem />
|
||||
<PrintNavItem />
|
||||
<PrintNavItem brew={currentBrew}/>
|
||||
<HelpNavItem />
|
||||
<VaultNavItem />
|
||||
<RecentNavItem />
|
||||
|
||||
@@ -16,7 +16,7 @@ import { DEFAULT_BREW_LOAD } from '../../../../server/brewDefaults.js';
|
||||
import { printCurrentBrew, fetchThemeBundle } from '@shared/helpers.js';
|
||||
|
||||
const SharePage = (props)=>{
|
||||
const { brew = DEFAULT_BREW_LOAD, disableMeta = false } = props;
|
||||
const { brew = DEFAULT_BREW_LOAD, disableMeta = false, share = true } = props;
|
||||
|
||||
const [themeBundle, setThemeBundle] = useState({});
|
||||
const [currentBrewRendererPageNum, setCurrentBrewRendererPageNum] = useState(1);
|
||||
@@ -66,40 +66,43 @@ const SharePage = (props)=>{
|
||||
</Nav.item>
|
||||
);
|
||||
|
||||
const showNav = (
|
||||
<Navbar>
|
||||
<Nav.section className='titleSection'>
|
||||
{disableMeta ? titleEl : <MetadataNav brew={brew}>{titleEl}</MetadataNav>}
|
||||
</Nav.section>
|
||||
|
||||
<Nav.section>
|
||||
{brew.shareId && (
|
||||
<>
|
||||
<PrintNavItem brew={brew}/>
|
||||
<Nav.dropdown>
|
||||
<Nav.item color='red' icon='fas fa-code'>
|
||||
source
|
||||
</Nav.item>
|
||||
<Nav.item color='blue' icon='fas fa-eye' href={`/source/${processShareId()}`}>
|
||||
view
|
||||
</Nav.item>
|
||||
{renderEditLink()}
|
||||
<Nav.item color='blue' icon='fas fa-download' href={`/download/${processShareId()}`}>
|
||||
download
|
||||
</Nav.item>
|
||||
<Nav.item color='blue' icon='fas fa-clone' href={`/new/${processShareId()}`}>
|
||||
clone to new
|
||||
</Nav.item>
|
||||
</Nav.dropdown>
|
||||
</>
|
||||
)}
|
||||
<RecentNavItem brew={brew} storageKey='view' />
|
||||
<Account />
|
||||
</Nav.section>
|
||||
</Navbar>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='sharePage sitePage'>
|
||||
<Meta name='robots' content='noindex, nofollow' />
|
||||
<Navbar>
|
||||
<Nav.section className='titleSection'>
|
||||
{disableMeta ? titleEl : <MetadataNav brew={brew}>{titleEl}</MetadataNav>}
|
||||
</Nav.section>
|
||||
|
||||
<Nav.section>
|
||||
{brew.shareId && (
|
||||
<>
|
||||
<PrintNavItem />
|
||||
<Nav.dropdown>
|
||||
<Nav.item color='red' icon='fas fa-code'>
|
||||
source
|
||||
</Nav.item>
|
||||
<Nav.item color='blue' icon='fas fa-eye' href={`/source/${processShareId()}`}>
|
||||
view
|
||||
</Nav.item>
|
||||
{renderEditLink()}
|
||||
<Nav.item color='blue' icon='fas fa-download' href={`/download/${processShareId()}`}>
|
||||
download
|
||||
</Nav.item>
|
||||
<Nav.item color='blue' icon='fas fa-clone' href={`/new/${processShareId()}`}>
|
||||
clone to new
|
||||
</Nav.item>
|
||||
</Nav.dropdown>
|
||||
</>
|
||||
)}
|
||||
<RecentNavItem brew={brew} storageKey='view' />
|
||||
<Account />
|
||||
</Nav.section>
|
||||
</Navbar>
|
||||
|
||||
{share ? showNav : ''}
|
||||
<div className='content'>
|
||||
<BrewRenderer
|
||||
text={brew.text}
|
||||
@@ -111,6 +114,7 @@ const SharePage = (props)=>{
|
||||
onPageChange={handleBrewRendererPageChange}
|
||||
currentBrewRendererPageNum={currentBrewRendererPageNum}
|
||||
allowPrint={true}
|
||||
showToolbar={share}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user