0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-16 10:22:42 +00:00

Merge branch 'master' of https://github.com/naturalcrit/homebrewery into experimental-development

This commit is contained in:
Víctor Losada Hernández
2024-06-10 23:25:42 +02:00
25 changed files with 1078 additions and 1027 deletions

View File

@@ -0,0 +1,29 @@
// Dialog box, for popups and modal blocking messages
const React = require('react');
const { useRef, useEffect } = React;
function Dialog({ dismissKey, closeText = 'Close', blocking = false, ...rest }) {
const dialogRef = useRef(null);
useEffect(()=>{
if(!dismissKey || !localStorage.getItem(dismissKey)) {
blocking ? dialogRef.current?.showModal() : dialogRef.current?.show();
}
}, []);
const dismiss = ()=>{
dismissKey && localStorage.setItem(dismissKey, true);
dialogRef.current?.close();
};
return (
<dialog ref={dialogRef} onCancel={dismiss} {...rest}>
{rest.children}
<button className='dismiss' onClick={dismiss}>
{closeText}
</button>
</dialog>
);
};
export default Dialog;

View File

@@ -13,6 +13,7 @@ const RenderWarnings = require('homebrewery/renderWarnings/renderWarnings.jsx');
const NotificationPopup = require('./notificationPopup/notificationPopup.jsx'); const NotificationPopup = require('./notificationPopup/notificationPopup.jsx');
const Frame = require('react-frame-component').default; const Frame = require('react-frame-component').default;
const dedent = require('dedent-tabs').default; const dedent = require('dedent-tabs').default;
const { printCurrentBrew } = require('../../../shared/helpers.js');
const DOMPurify = require('dompurify'); const DOMPurify = require('dompurify');
const purifyConfig = { FORCE_BODY: true, SANITIZE_DOM: false }; const purifyConfig = { FORCE_BODY: true, SANITIZE_DOM: false };
@@ -159,6 +160,16 @@ const BrewRenderer = (props)=>{
return renderedPages; return renderedPages;
}; };
const handleControlKeys = (e)=>{
if(!(e.ctrlKey || e.metaKey)) return;
const P_KEY = 80;
if(e.keyCode == P_KEY && props.allowPrint) printCurrentBrew();
if(e.keyCode == P_KEY) {
e.stopPropagation();
e.preventDefault();
}
};
const frameDidMount = ()=>{ //This triggers when iFrame finishes internal "componentDidMount" const frameDidMount = ()=>{ //This triggers when iFrame finishes internal "componentDidMount"
setTimeout(()=>{ //We still see a flicker where the style isn't applied yet, so wait 100ms before showing iFrame setTimeout(()=>{ //We still see a flicker where the style isn't applied yet, so wait 100ms before showing iFrame
updateSize(); updateSize();
@@ -192,6 +203,12 @@ const BrewRenderer = (props)=>{
</div> </div>
: null} : null}
<ErrorBar errors={props.errors} />
<div className='popups'>
<RenderWarnings />
<NotificationPopup />
</div>
{/*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' initialContent={INITIAL_CONTENT} <Frame id='BrewRenderer' initialContent={INITIAL_CONTENT}
style={{ width: '100%', height: '100%', visibility: state.visibility }} style={{ width: '100%', height: '100%', visibility: state.visibility }}
@@ -200,13 +217,10 @@ const BrewRenderer = (props)=>{
> >
<div className={'brewRenderer'} <div className={'brewRenderer'}
onScroll={handleScroll} onScroll={handleScroll}
onKeyDown={handleControlKeys}
tabIndex={-1}
style={{ height: state.height }}> style={{ height: state.height }}>
<ErrorBar errors={props.errors} />
<div className='popups'>
<RenderWarnings />
<NotificationPopup />
</div>
<link href={`/themes/${rendererPath}/Blank/style.css`} type='text/css' rel='stylesheet'/> <link href={`/themes/${rendererPath}/Blank/style.css`} type='text/css' rel='stylesheet'/>
{baseThemePath && {baseThemePath &&
<link href={`/themes/${rendererPath}/${baseThemePath}/style.css`} type='text/css' rel='stylesheet'/> <link href={`/themes/${rendererPath}/${baseThemePath}/style.css`} type='text/css' rel='stylesheet'/>

View File

@@ -64,3 +64,16 @@
color : white; color : white;
background-color : #333333; background-color : #333333;
} }
@media print {
.brewRenderer {
height: 100%;
overflow-y: unset;
.pages {
margin: 0px;
&>.page {
box-shadow: unset;
}
}
}
}

View File

@@ -1,28 +1,20 @@
require('./notificationPopup.less'); require('./notificationPopup.less');
const React = require('react'); const React = require('react');
const createClass = require('create-react-class');
const _ = require('lodash'); const _ = require('lodash');
const DISMISS_KEY = 'dismiss_notification12-04-23'; import Dialog from '../../../components/dialog.jsx';
const NotificationPopup = createClass({ const DISMISS_KEY = 'dismiss_notification12-04-23';
displayName : 'NotificationPopup', const DISMISS_BUTTON = <i className='fas fa-times dismiss' />;
getInitialState : function() {
return { const NotificationPopup = ()=>{
notifications : {} return <Dialog className='notificationPopup' dismissKey={DISMISS_KEY} closeText={DISMISS_BUTTON} >
}; <div className='header'>
}, <i className='fas fa-info-circle info'></i>
componentDidMount : function() { <h3>Notice</h3>
this.checkNotifications(); <small>This website is always improving and we are still adding new features and squashing bugs. Keep the following in mind:</small>
window.addEventListener('resize', this.checkNotifications); </div>
}, <ul>
componentWillUnmount : function() {
window.removeEventListener('resize', this.checkNotifications);
},
notifications : {
psa : function(){
return (
<>
<li key='psa'> <li key='psa'>
<em>Don't store IMAGES in Google Drive</em><br /> <em>Don't store IMAGES in Google Drive</em><br />
Google Drive is not an image service, and will block images from being used Google Drive is not an image service, and will block images from being used
@@ -46,35 +38,8 @@ const NotificationPopup = createClass({
See the FAQ See the FAQ
</a> to learn how to avoid losing your work! </a> to learn how to avoid losing your work!
</li> </li>
</> </ul>
); </Dialog>;
} };
},
checkNotifications : function(){
const hideDismiss = localStorage.getItem(DISMISS_KEY);
if(hideDismiss) return this.setState({ notifications: {} });
this.setState({
notifications : _.mapValues(this.notifications, (fn)=>{ return fn(); }) //Convert notification functions into their return text value
});
},
dismiss : function(){
localStorage.setItem(DISMISS_KEY, true);
this.checkNotifications();
},
render : function(){
if(_.isEmpty(this.state.notifications)) return null;
return <div className='notificationPopup'>
<i className='fas fa-times dismiss' onClick={this.dismiss}/>
<i className='fas fa-info-circle info' />
<div className='header'>
<h3>Notice</h3>
<small>This website is always improving and we are still adding new features and squashing bugs. Keep the following in mind:</small>
</div>
<ul>{_.values(this.state.notifications)}</ul>
</div>;
}
});
module.exports = NotificationPopup; module.exports = NotificationPopup;

View File

@@ -1,47 +1,45 @@
.popups { .popups {
position : fixed; position : fixed;
top : @navbarHeight; top : @navbarHeight;
right : 15px; right : 24px;
z-index : 10001; z-index : 10001;
width : 450px; width : 450px;
} }
.notificationPopup { .notificationPopup {
position : relative; position : relative;
display : inline-block;
width : 100%; width : 100%;
padding : 15px; padding : 15px;
padding-bottom : 10px; padding-bottom : 10px;
padding-left : 25px; padding-left : 25px;
background-color : @blue;
color : white; color : white;
background-color : @blue;
border : none;
&[open] { display : inline-block; }
a { a {
color : #e0e5c1;
font-weight : 800; font-weight : 800;
color : #E0E5C1;
} }
i.info { i.info {
position : absolute; position : absolute;
top : 12px; top : 12px;
left : 12px; left : 12px;
opacity : 0.8;
font-size : 2.5em; font-size : 2.5em;
opacity : 0.8;
} }
i.dismiss{ button.dismiss {
position : absolute; position : absolute;
top : 10px; top : 10px;
right : 10px; right : 10px;
cursor : pointer; cursor : pointer;
background-color : transparent;
opacity : 0.6; opacity : 0.6;
&:hover{ &:hover { opacity : 1; }
opacity : 1;
}
}
.header {
padding-left : 50px;
} }
.header { padding-left : 50px; }
small { small {
opacity : 0.7;
font-size : 0.6em; font-size : 0.6em;
opacity : 0.7;
} }
h3 { h3 {
font-size : 1.1em; font-size : 1.1em;
@@ -53,12 +51,10 @@
list-style-position : outside; list-style-position : outside;
list-style-type : disc; list-style-type : disc;
li { li {
margin-top : 1.4em;
font-size : 0.8em; font-size : 0.8em;
line-height : 1.4em; line-height : 1.4em;
margin-top : 1.4em; em { font-weight : 800; }
em{
font-weight : 800;
}
} }
} }
} }

View File

@@ -11,7 +11,6 @@ const SharePage = require('./pages/sharePage/sharePage.jsx');
const NewPage = require('./pages/newPage/newPage.jsx'); const NewPage = require('./pages/newPage/newPage.jsx');
const ErrorPage = require('./pages/errorPage/errorPage.jsx'); const ErrorPage = require('./pages/errorPage/errorPage.jsx');
const PrintPage = require('./pages/printPage/printPage.jsx'); const PrintPage = require('./pages/printPage/printPage.jsx');
const ArchivePage = require('./pages/archivePage/archivePage.jsx');
const AccountPage = require('./pages/accountPage/accountPage.jsx'); const AccountPage = require('./pages/accountPage/accountPage.jsx');
const WithRoute = (props)=>{ const WithRoute = (props)=>{
@@ -75,7 +74,6 @@ const Homebrew = createClass({
<Route path='/user/:username' element={<WithRoute el={UserPage} brews={this.props.brews} />} /> <Route path='/user/:username' element={<WithRoute el={UserPage} brews={this.props.brews} />} />
<Route path='/print/:id' element={<WithRoute el={PrintPage} brew={this.props.brew} />} /> <Route path='/print/:id' element={<WithRoute el={PrintPage} brew={this.props.brew} />} />
<Route path='/print' element={<WithRoute el={PrintPage} />} /> <Route path='/print' element={<WithRoute el={PrintPage} />} />
<Route path='/archive' element={<WithRoute el={ArchivePage}/>}/>
<Route path='/changelog' element={<WithRoute el={SharePage} brew={this.props.brew} />} /> <Route path='/changelog' element={<WithRoute el={SharePage} brew={this.props.brew} />} />
<Route path='/faq' element={<WithRoute el={SharePage} brew={this.props.brew} />} /> <Route path='/faq' element={<WithRoute el={SharePage} brew={this.props.brew} />} />
<Route path='/account' element={<WithRoute el={AccountPage} brew={this.props.brew} accountDetails={this.props.brew.accountDetails} />} /> <Route path='/account' element={<WithRoute el={AccountPage} brew={this.props.brew} accountDetails={this.props.brew.accountDetails} />} />
@@ -91,14 +89,3 @@ const Homebrew = createClass({
}); });
module.exports = Homebrew; module.exports = Homebrew;
//TODO: Nicer Error page instead of just "cant get that"
// '/share/:id' : (args)=>{
// if(!this.props.brew.shareId){
// return <ErrorPage errorId={args.id}/>;
// }
//
// return <SharePage
// id={args.id}
// brew={this.props.brew} />;
// },

View File

@@ -1,8 +1,9 @@
const React = require('react'); const React = require('react');
const Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');
const { printCurrentBrew } = require('../../../shared/helpers.js');
module.exports = function(props){ module.exports = function(){
return <Nav.item newTab={true} href={`/print/${props.shareId}?dialog=true`} color='purple' icon='far fa-file-pdf'> return <Nav.item onClick={printCurrentBrew} color='purple' icon='far fa-file-pdf'>
get PDF get PDF
</Nav.item>; </Nav.item>;
}; };

View File

@@ -11,7 +11,7 @@ const Navbar = require('../../navbar/navbar.jsx');
const NewBrew = require('../../navbar/newbrew.navitem.jsx'); const NewBrew = require('../../navbar/newbrew.navitem.jsx');
const HelpNavItem = require('../../navbar/help.navitem.jsx'); const HelpNavItem = require('../../navbar/help.navitem.jsx');
const PrintLink = require('../../navbar/print.navitem.jsx'); const PrintNavItem = require('../../navbar/print.navitem.jsx');
const ErrorNavItem = require('../../navbar/error-navitem.jsx'); const ErrorNavItem = require('../../navbar/error-navitem.jsx');
const Account = require('../../navbar/account.navitem.jsx'); const Account = require('../../navbar/account.navitem.jsx');
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both; const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
@@ -20,9 +20,12 @@ const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
const Editor = require('../../editor/editor.jsx'); const Editor = require('../../editor/editor.jsx');
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx'); const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
const LockNotification = require('./lockNotification/lockNotification.jsx');
const Markdown = require('naturalcrit/markdown.js'); const Markdown = require('naturalcrit/markdown.js');
const { DEFAULT_BREW_LOAD } = require('../../../../server/brewDefaults.js'); const { DEFAULT_BREW_LOAD } = require('../../../../server/brewDefaults.js');
const { printCurrentBrew } = require('../../../../shared/helpers.js');
const googleDriveIcon = require('../../googleDrive.svg'); const googleDriveIcon = require('../../googleDrive.svg');
@@ -51,7 +54,8 @@ const EditPage = createClass({
autoSave : true, autoSave : true,
autoSaveWarning : false, autoSaveWarning : false,
unsavedTime : new Date(), unsavedTime : new Date(),
currentEditorPage : 0 currentEditorPage : 0,
displayLockMessage : this.props.brew.lock || false
}; };
}, },
@@ -95,7 +99,7 @@ const EditPage = createClass({
const S_KEY = 83; const S_KEY = 83;
const P_KEY = 80; const P_KEY = 80;
if(e.keyCode == S_KEY) this.trySave(true); if(e.keyCode == S_KEY) this.trySave(true);
if(e.keyCode == P_KEY) window.open(`/print/${this.processShareId()}?dialog=true`, '_blank').focus(); if(e.keyCode == P_KEY) printCurrentBrew();
if(e.keyCode == P_KEY || e.keyCode == S_KEY){ if(e.keyCode == P_KEY || e.keyCode == S_KEY){
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
@@ -378,7 +382,7 @@ const EditPage = createClass({
post to reddit post to reddit
</Nav.item> </Nav.item>
</Nav.dropdown> </Nav.dropdown>
<PrintLink shareId={this.processShareId()} /> <PrintNavItem />
<RecentNavItem brew={this.state.brew} storageKey='edit' /> <RecentNavItem brew={this.state.brew} storageKey='edit' />
<Account /> <Account />
</Nav.section> </Nav.section>
@@ -392,6 +396,7 @@ const EditPage = createClass({
{this.renderNavbar()} {this.renderNavbar()}
<div className='content'> <div className='content'>
{this.props.brew.lock && <LockNotification shareId={this.props.brew.shareId} message={this.props.brew.lock.editMessage} />}
<SplitPane onDragFinish={this.handleSplitMove}> <SplitPane onDragFinish={this.handleSplitMove}>
<Editor <Editor
ref={this.editor} ref={this.editor}
@@ -410,6 +415,7 @@ const EditPage = createClass({
errors={this.state.htmlErrors} errors={this.state.htmlErrors}
lang={this.state.brew.lang} lang={this.state.brew.lang}
currentEditorPage={this.state.currentEditorPage} currentEditorPage={this.state.currentEditorPage}
allowPrint={true}
/> />
</SplitPane> </SplitPane>
</div> </div>

View File

@@ -0,0 +1,30 @@
require('./lockNotification.less');
const React = require('react');
import Dialog from '../../../../components/dialog.jsx';
function LockNotification(props) {
props = {
shareId : 0,
disableLock : ()=>{},
message : '',
...props
};
const removeLock = ()=>{
alert(`Not yet implemented - ID ${props.shareId}`);
};
return <Dialog className='lockNotification' blocking closeText='CONTINUE TO EDITOR' >
<h1>BREW LOCKED</h1>
<p>This brew been locked by the Administrators. It will not be accessible by any method other than the Editor until the lock is removed.</p>
<hr />
<h3>LOCK REASON</h3>
<p>{props.message || 'Unable to retrieve Lock Message'}</p>
<hr />
<p>Once you have resolved this issue, click REQUEST LOCK REMOVAL to notify the Administrators for review.</p>
<p>Click CONTINUE TO EDITOR to temporarily hide this notification; it will reappear the next time the page is reloaded.</p>
<button onClick={removeLock}>REQUEST LOCK REMOVAL</button>
</Dialog>;
};
module.exports = LockNotification;

View File

@@ -0,0 +1,27 @@
.lockNotification {
z-index : 1;
width : 80%;
padding : 10px;
margin : 5% 10%;
line-height : 1.5em;
color : black;
text-align : center;
background-color : #CCCCCC;
&::backdrop { background-color : #000000AA; }
button {
margin : 10px;
color : white;
background-color : #333333;
&:hover { background-color : #777777; }
}
h1, h3 {
font-family : 'Open Sans', sans-serif;
font-weight : 800;
}
h1 { font-size : 24px; }
h3 { font-size : 18px; }
}

View File

@@ -140,7 +140,7 @@ const errorIndex = (props)=>{
'100' : dedent` '100' : dedent`
## This brew has been locked. ## This brew has been locked.
Please contact the Administrators to unlock this document. Only an author may request that this lock is removed.
: :

View File

@@ -1,12 +0,0 @@
//TODO: Depricate
module.exports = function(shareId){
return function(event){
event = event || window.event;
if((event.ctrlKey || event.metaKey) && event.keyCode == 80){
const win = window.open(`/homebrew/print/${shareId}?dialog=true`, '_blank');
win.focus();
event.preventDefault();
}
};
};

View File

@@ -7,6 +7,7 @@ const request = require('../../utils/request-middleware.js');
const Markdown = require('naturalcrit/markdown.js'); const Markdown = require('naturalcrit/markdown.js');
const Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');
const PrintNavItem = require('../../navbar/print.navitem.jsx');
const Navbar = require('../../navbar/navbar.jsx'); const Navbar = require('../../navbar/navbar.jsx');
const AccountNavItem = require('../../navbar/account.navitem.jsx'); const AccountNavItem = require('../../navbar/account.navitem.jsx');
const ErrorNavItem = require('../../navbar/error-navitem.jsx'); const ErrorNavItem = require('../../navbar/error-navitem.jsx');
@@ -18,6 +19,7 @@ const Editor = require('../../editor/editor.jsx');
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx'); const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
const { DEFAULT_BREW } = require('../../../../server/brewDefaults.js'); const { DEFAULT_BREW } = require('../../../../server/brewDefaults.js');
const { printCurrentBrew } = require('../../../../shared/helpers.js');
const BREWKEY = 'homebrewery-new'; const BREWKEY = 'homebrewery-new';
const STYLEKEY = 'homebrewery-new-style'; const STYLEKEY = 'homebrewery-new-style';
@@ -89,7 +91,7 @@ const NewPage = createClass({
const S_KEY = 83; const S_KEY = 83;
const P_KEY = 80; const P_KEY = 80;
if(e.keyCode == S_KEY) this.save(); if(e.keyCode == S_KEY) this.save();
if(e.keyCode == P_KEY) this.print(); if(e.keyCode == P_KEY) printCurrentBrew();
if(e.keyCode == P_KEY || e.keyCode == S_KEY){ if(e.keyCode == P_KEY || e.keyCode == S_KEY){
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
@@ -180,16 +182,6 @@ const NewPage = createClass({
} }
}, },
print : function(){
window.open('/print?dialog=true&local=print', '_blank');
},
renderLocalPrintButton : function(){
return <Nav.item color='purple' icon='far fa-file-pdf' onClick={this.print}>
get PDF
</Nav.item>;
},
renderNavbar : function(){ renderNavbar : function(){
return <Navbar> return <Navbar>
@@ -202,7 +194,7 @@ const NewPage = createClass({
<ErrorNavItem error={this.state.error} parent={this}></ErrorNavItem> : <ErrorNavItem error={this.state.error} parent={this}></ErrorNavItem> :
this.renderSaveButton() this.renderSaveButton()
} }
{this.renderLocalPrintButton()} <PrintNavItem />
<HelpNavItem /> <HelpNavItem />
<RecentNavItem /> <RecentNavItem />
<AccountNavItem /> <AccountNavItem />
@@ -231,6 +223,7 @@ const NewPage = createClass({
errors={this.state.htmlErrors} errors={this.state.htmlErrors}
lang={this.state.brew.lang} lang={this.state.brew.lang}
currentEditorPage={this.state.currentEditorPage} currentEditorPage={this.state.currentEditorPage}
allowPrint={true}
/> />
</SplitPane> </SplitPane>
</div> </div>

View File

@@ -1,112 +0,0 @@
require('./printPage.less');
const React = require('react');
const createClass = require('create-react-class');
const _ = require('lodash');
const { Meta } = require('vitreum/headtags');
const MarkdownLegacy = require('naturalcrit/markdownLegacy.js');
const Markdown = require('naturalcrit/markdown.js');
const Themes = require('themes/themes.json');
const BREWKEY = 'homebrewery-new';
const STYLEKEY = 'homebrewery-new-style';
const METAKEY = 'homebrewery-new-meta';
const PrintPage = createClass({
displayName : 'PrintPage',
getDefaultProps : function() {
return {
query : {},
brew : {
text : '',
style : '',
renderer : 'legacy',
lang : ''
}
};
},
getInitialState : function() {
return {
brew : {
text : this.props.brew.text || '',
style : this.props.brew.style || undefined,
renderer : this.props.brew.renderer || 'legacy',
theme : this.props.brew.theme || '5ePHB',
lang : this.props.brew.lang || 'en'
}
};
},
componentDidMount : function() {
if(this.props.query.local == 'print'){
const brewStorage = localStorage.getItem(BREWKEY);
const styleStorage = localStorage.getItem(STYLEKEY);
const metaStorage = JSON.parse(localStorage.getItem(METAKEY));
this.setState((prevState, prevProps)=>{
return {
brew : {
text : brewStorage,
style : styleStorage,
renderer : metaStorage?.renderer || 'legacy',
theme : metaStorage?.theme || '5ePHB',
lang : metaStorage?.lang || 'en'
}
};
});
}
if(this.props.query.dialog) window.print();
},
renderStyle : function() {
if(!this.state.brew.style) return;
//return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style>@layer styleTab {\n${this.state.brew.style}\n} </style>` }} />;
return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style>\n${this.state.brew.style}\n</style>` }} />;
},
renderPages : function(){
if(this.state.brew.renderer == 'legacy') {
return _.map(this.state.brew.text.split('\\page'), (pageText, index)=>{
return <div
className='phb page'
id={`p${index + 1}`}
dangerouslySetInnerHTML={{ __html: MarkdownLegacy.render(pageText) }}
key={index} />;
});
} else {
return _.map(this.state.brew.text.split(/^\\page$/gm), (pageText, index)=>{
pageText += `\n\n&nbsp;\n\\column\n&nbsp;`; //Artificial column break at page end to emulate column-fill:auto (until `wide` is used, when column-fill:balance will reappear)
return (
<div className='page' id={`p${index + 1}`} key={index} >
<div className='columnWrapper' dangerouslySetInnerHTML={{ __html: Markdown.render(pageText) }} />
</div>
);
});
}
},
render : function(){
const rendererPath = this.state.brew.renderer == 'V3' ? 'V3' : 'Legacy';
const themePath = this.state.brew.theme ?? '5ePHB';
const baseThemePath = Themes[rendererPath][themePath].baseTheme;
return <div>
<Meta name='robots' content='noindex, nofollow' />
<link href={`/themes/${rendererPath}/Blank/style.css`} type='text/css' rel='stylesheet'/>
{baseThemePath &&
<link href={`/themes/${rendererPath}/${baseThemePath}/style.css`} type='text/css' rel='stylesheet'/>
}
<link href={`/themes/${rendererPath}/${themePath}/style.css`} type='text/css' rel='stylesheet'/>
{/* Apply CSS from Style tab */}
{this.renderStyle()}
<div className='pages' lang={this.state.brew.lang}>
{this.renderPages()}
</div>
</div>;
}
});
module.exports = PrintPage;

View File

@@ -1,3 +0,0 @@
.printPage{
}

View File

@@ -6,14 +6,13 @@ const { Meta } = require('vitreum/headtags');
const Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');
const Navbar = require('../../navbar/navbar.jsx'); const Navbar = require('../../navbar/navbar.jsx');
const MetadataNav = require('../../navbar/metadata.navitem.jsx'); const MetadataNav = require('../../navbar/metadata.navitem.jsx');
const PrintLink = require('../../navbar/print.navitem.jsx'); const PrintNavItem = require('../../navbar/print.navitem.jsx');
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both; const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
const Account = require('../../navbar/account.navitem.jsx'); const Account = require('../../navbar/account.navitem.jsx');
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx'); const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
const { DEFAULT_BREW_LOAD } = require('../../../../server/brewDefaults.js'); const { DEFAULT_BREW_LOAD } = require('../../../../server/brewDefaults.js');
const { printCurrentBrew } = require('../../../../shared/helpers.js');
const SharePage = createClass({ const SharePage = createClass({
displayName : 'SharePage', displayName : 'SharePage',
@@ -35,7 +34,7 @@ const SharePage = createClass({
if(!(e.ctrlKey || e.metaKey)) return; if(!(e.ctrlKey || e.metaKey)) return;
const P_KEY = 80; const P_KEY = 80;
if(e.keyCode == P_KEY){ if(e.keyCode == P_KEY){
window.open(`/print/${this.processShareId()}?dialog=true`, '_blank').focus(); if(e.keyCode == P_KEY) printCurrentBrew();
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
} }
@@ -72,7 +71,7 @@ const SharePage = createClass({
<Nav.section> <Nav.section>
{this.props.brew.shareId && <> {this.props.brew.shareId && <>
<PrintLink shareId={this.processShareId()} /> <PrintNavItem/>
<Nav.dropdown> <Nav.dropdown>
<Nav.item color='red' icon='fas fa-code'> <Nav.item color='red' icon='fas fa-code'>
source source
@@ -95,7 +94,13 @@ const SharePage = createClass({
</Navbar> </Navbar>
<div className='content'> <div className='content'>
<BrewRenderer text={this.props.brew.text} style={this.props.brew.style} renderer={this.props.brew.renderer} theme={this.props.brew.theme} /> <BrewRenderer
text={this.props.brew.text}
style={this.props.brew.style}
renderer={this.props.brew.renderer}
theme={this.props.brew.theme}
allowPrint={true}
/>
</div> </div>
</div>; </div>;
} }

1505
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -82,18 +82,18 @@
] ]
}, },
"dependencies": { "dependencies": {
"@babel/core": "^7.24.5", "@babel/core": "^7.24.7",
"@babel/plugin-transform-runtime": "^7.24.3", "@babel/plugin-transform-runtime": "^7.24.7",
"@babel/preset-env": "^7.24.5", "@babel/preset-env": "^7.24.7",
"@babel/preset-react": "^7.24.1", "@babel/preset-react": "^7.24.7",
"@googleapis/drive": "^8.8.0", "@googleapis/drive": "^8.10.0",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
"classnames": "^2.5.1", "classnames": "^2.5.1",
"codemirror": "^5.65.6", "codemirror": "^5.65.6",
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
"create-react-class": "^15.7.0", "create-react-class": "^15.7.0",
"dedent-tabs": "^0.10.3", "dedent-tabs": "^0.10.3",
"dompurify": "^3.1.4", "dompurify": "^3.1.5",
"expr-eval": "^2.0.2", "expr-eval": "^2.0.2",
"express": "^4.19.2", "express": "^4.19.2",
"express-async-handler": "^1.2.0", "express-async-handler": "^1.2.0",
@@ -110,7 +110,7 @@
"marked-smartypants-lite": "^1.0.2", "marked-smartypants-lite": "^1.0.2",
"markedLegacy": "npm:marked@^0.3.19", "markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.30.1", "moment": "^2.30.1",
"mongoose": "^8.4.0", "mongoose": "^8.4.1",
"nanoid": "3.3.4", "nanoid": "3.3.4",
"nconf": "^0.12.1", "nconf": "^0.12.1",
"react": "^18.3.1", "react": "^18.3.1",
@@ -123,8 +123,8 @@
}, },
"devDependencies": { "devDependencies": {
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-plugin-jest": "^28.5.0", "eslint-plugin-jest": "^28.6.0",
"eslint-plugin-react": "^7.34.1", "eslint-plugin-react": "^7.34.2",
"jest": "^29.7.0", "jest": "^29.7.0",
"jest-expect-message": "^1.1.3", "jest-expect-message": "^1.1.3",
"postcss-less": "^6.0.0", "postcss-less": "^6.0.0",

View File

@@ -332,13 +332,6 @@ app.get('/share/:id', asyncHandler(getBrew('share')), asyncHandler(async (req, r
return next(); return next();
})); }));
//Print Page
app.get('/print/:id', asyncHandler(getBrew('share')), (req, res, next)=>{
sanitizeBrew(req.brew, 'share');
splitTextStyleAndMetadata(req.brew);
next();
});
//Account Page //Account Page
app.get('/account', asyncHandler(async (req, res, next)=>{ app.get('/account', asyncHandler(async (req, res, next)=>{
const data = {}; const data = {};

View File

@@ -55,7 +55,7 @@ const api = {
stub = stub?.toObject(); stub = stub?.toObject();
if(stub?.lock?.locked && accessType != 'edit') { if(stub?.lock?.locked && accessType != 'edit') {
throw { HBErrorCode: '100', code: stub.lock.code, message: stub.lock.message, brewId: stub.shareId, brewTitle: stub.title }; throw { HBErrorCode: '100', code: stub.lock.code, message: stub.lock.shareMessage, brewId: stub.shareId, brewTitle: stub.title };
} }
// If there is a google id, try to find the google brew // If there is a google id, try to find the google brew

View File

@@ -300,7 +300,7 @@ describe('Tests for api', ()=>{
}); });
it('access is denied to a locked brew', async()=>{ it('access is denied to a locked brew', async()=>{
const lockBrew = { title: 'test brew', shareId: '1', lock: { locked: true, code: 404, message: 'brew locked' } }; const lockBrew = { title: 'test brew', shareId: '1', lock: { locked: true, code: 404, shareMessage: 'brew locked' } };
model.get = jest.fn(()=>toBrewPromise(lockBrew)); model.get = jest.fn(()=>toBrewPromise(lockBrew));
api.getId = jest.fn(()=>({ id: '1', googleId: undefined })); api.getId = jest.fn(()=>({ id: '1', googleId: undefined }));

View File

@@ -17,6 +17,18 @@ const splitTextStyleAndMetadata = (brew)=>{
} }
}; };
module.exports = { const printCurrentBrew = ()=>{
splitTextStyleAndMetadata if(window.typeof !== 'undefined') {
window.frames['BrewRenderer'].contentWindow.print();
//Force DOM reflow; Print dialog causes a repaint, and @media print CSS somehow makes out-of-view pages disappear
const node = window.frames['BrewRenderer'].contentDocument.getElementsByClassName('brewRenderer').item(0);
node.style.display='none';
node.offsetHeight; // accessing this is enough to trigger a reflow
node.style.display='';
}
};
module.exports = {
splitTextStyleAndMetadata,
printCurrentBrew
}; };

View File

@@ -3,7 +3,7 @@ const React = require('react');
const createClass = require('create-react-class'); const createClass = require('create-react-class');
const _ = require('lodash'); const _ = require('lodash');
const DISMISS_KEY = 'dismiss_render_warning'; import Dialog from '../../../client/components/dialog.jsx';
const RenderWarnings = createClass({ const RenderWarnings = createClass({
displayName : 'RenderWarnings', displayName : 'RenderWarnings',
@@ -34,9 +34,6 @@ const RenderWarnings = createClass({
}, },
}, },
checkWarnings : function(){ checkWarnings : function(){
const hideDismiss = localStorage.getItem(DISMISS_KEY);
if(hideDismiss) return this.setState({ warnings: {} });
this.setState({ this.setState({
warnings : _.reduce(this.warnings, (r, fn, type)=>{ warnings : _.reduce(this.warnings, (r, fn, type)=>{
const element = fn(); const element = fn();
@@ -45,20 +42,18 @@ const RenderWarnings = createClass({
}, {}) }, {})
}); });
}, },
dismiss : function(){
localStorage.setItem(DISMISS_KEY, true);
this.checkWarnings();
},
render : function(){ render : function(){
if(_.isEmpty(this.state.warnings)) return null; if(_.isEmpty(this.state.warnings)) return null;
return <div className='renderWarnings'> const DISMISS_KEY = 'dismiss_render_warning';
<i className='fas fa-times dismiss' onClick={this.dismiss}/> const DISMISS_TEXT = <i className='fas fa-times dismiss' />;
return <Dialog className='renderWarnings' dismissKey={DISMISS_KEY} closeText={DISMISS_TEXT}>
<i className='fas fa-exclamation-triangle ohno' /> <i className='fas fa-exclamation-triangle ohno' />
<h3>Render Warnings</h3> <h3>Render Warnings</h3>
<small>If this homebrew is rendering badly if might be because of the following:</small> <small>If this homebrew is rendering badly if might be because of the following:</small>
<ul>{_.values(this.state.warnings)}</ul> <ul>{_.values(this.state.warnings)}</ul>
</div>; </Dialog>;
} }
}); });

View File

@@ -1,37 +1,34 @@
.renderWarnings { .renderWarnings {
position : relative; position : relative;
float : right; float : right;
display : inline-block;
width : 350px; width : 350px;
padding : 20px; padding : 20px;
padding-bottom : 10px; padding-bottom : 10px;
padding-left : 85px; padding-left : 85px;
margin-bottom : 10px; margin-bottom : 10px;
background-color : @yellow;
color : white; color : white;
a{ background-color : @yellow;
font-weight : 800; border : none;
} a { font-weight : 800; }
i.ohno { i.ohno {
position : absolute; position : absolute;
top : 24px; top : 24px;
left : 24px; left : 24px;
opacity : 0.8;
font-size : 2.5em; font-size : 2.5em;
opacity : 0.8;
} }
i.dismiss{ button.dismiss {
position : absolute; position : absolute;
top : 10px; top : 10px;
right : 10px; right : 10px;
cursor : pointer; cursor : pointer;
background-color : transparent;
opacity : 0.6; opacity : 0.6;
&:hover{ &:hover { opacity : 1; }
opacity : 1;
}
} }
small { small {
opacity : 0.7;
font-size : 0.6em; font-size : 0.6em;
opacity : 0.7;
} }
h3 { h3 {
font-size : 1.1em; font-size : 1.1em;
@@ -45,9 +42,7 @@
li { li {
font-size : 0.8em; font-size : 0.8em;
line-height : 1.6em; line-height : 1.6em;
em{ em { font-weight : 800; }
font-weight : 800;
}
} }
} }
} }

View File

@@ -47,8 +47,8 @@ const Nav = {
color : null color : null
}; };
}, },
handleClick : function(){ handleClick : function(e){
this.props.onClick(); this.props.onClick(e);
}, },
render : function(){ render : function(){
const classes = cx('navItem', this.props.color, this.props.className); const classes = cx('navItem', this.props.color, this.props.className);