0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-07 01:12:44 +00:00

Merge branch 'master' into Issue_241

This commit is contained in:
David Bolack
2024-06-19 18:41:02 -05:00
59 changed files with 2345 additions and 1254 deletions

View File

@@ -1,7 +1,6 @@
require('./brewCleanup.less'); require('./brewCleanup.less');
const React = require('react'); const React = require('react');
const createClass = require('create-react-class'); const createClass = require('create-react-class');
const cx = require('classnames');
const request = require('superagent'); const request = require('superagent');

View File

@@ -1,7 +1,6 @@
require('./brewCompress.less'); require('./brewCompress.less');
const React = require('react'); const React = require('react');
const createClass = require('create-react-class'); const createClass = require('create-react-class');
const cx = require('classnames');
const request = require('superagent'); const request = require('superagent');

View File

@@ -1,7 +1,6 @@
const React = require('react'); const React = require('react');
const createClass = require('create-react-class'); const createClass = require('create-react-class');
const _ = require('lodash'); const _ = require('lodash');
const cx = require('classnames');
require('./combobox.less'); require('./combobox.less');
const Combobox = createClass({ const Combobox = createClass({

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

@@ -2,7 +2,6 @@ require('./errorBar.less');
const React = require('react'); const React = require('react');
const createClass = require('create-react-class'); const createClass = require('create-react-class');
const _ = require('lodash'); const _ = require('lodash');
const cx = require('classnames');
const ErrorBar = createClass({ const ErrorBar = createClass({
displayName : 'ErrorBar', displayName : 'ErrorBar',

View File

@@ -1,81 +1,45 @@
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 cx = require('classnames'); //Unused variable
import Dialog from '../../../components/dialog.jsx';
const DISMISS_KEY = 'dismiss_notification12-04-23'; const DISMISS_KEY = 'dismiss_notification12-04-23';
const DISMISS_BUTTON = <i className='fas fa-times dismiss' />;
const NotificationPopup = createClass({ const NotificationPopup = ()=>{
displayName : 'NotificationPopup', return <Dialog className='notificationPopup' dismissKey={DISMISS_KEY} closeText={DISMISS_BUTTON} >
getInitialState : function() { <div className='header'>
return { <i className='fas fa-info-circle info'></i>
notifications : {} <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>
componentDidMount : function() { <ul>
this.checkNotifications(); <li key='psa'>
window.addEventListener('resize', this.checkNotifications); <em>Don't store IMAGES in Google Drive</em><br />
}, Google Drive is not an image service, and will block images from being used
componentWillUnmount : function() { in brews if they get more views than expected. Google has confirmed they won't fix
window.removeEventListener('resize', this.checkNotifications); this, so we recommend you look for another image hosting service such as imgur, ImgBB or Google Photos.
}, </li>
notifications : {
psa : function(){
return (
<>
<li key='psa'>
<em>Don't store IMAGES in Google Drive</em><br />
Google Drive is not an image service, and will block images from being used
in brews if they get more views than expected. Google has confirmed they won't fix
this, so we recommend you look for another image hosting service such as imgur, ImgBB or Google Photos.
</li>
<li key='googleDriveFolder'> <li key='googleDriveFolder'>
<em>Don't delete your Homebrewery folder on Google Drive!</em> <br /> <em>Don't delete your Homebrewery folder on Google Drive!</em> <br />
We have had several reports of users losing their brews, not realizing We have had several reports of users losing their brews, not realizing
that they had deleted the files on their Google Drive. If you have a Homebrewery folder that they had deleted the files on their Google Drive. If you have a Homebrewery folder
on your Google Drive with *.txt files inside, <em>do not delete it</em>! on your Google Drive with *.txt files inside, <em>do not delete it</em>!
We cannot help you recover files that you have deleted from your own We cannot help you recover files that you have deleted from your own
Google Drive. Google Drive.
</li> </li>
<li key='faq'> <li key='faq'>
<em>Protect your work! </em> <br /> <em>Protect your work! </em> <br />
If you opt not to use your Google Drive, keep in mind that we do not save a history of your projects. Please make frequent backups of your brews!&nbsp; If you opt not to use your Google Drive, keep in mind that we do not save a history of your projects. Please make frequent backups of your brews!&nbsp;
<a target='_blank' href='https://www.reddit.com/r/homebrewery/comments/adh6lh/faqs_psas_announcements/'> <a target='_blank' href='https://www.reddit.com/r/homebrewery/comments/adh6lh/faqs_psas_announcements/'>
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,64 +1,60 @@
.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;
a{ background-color : @blue;
color : #e0e5c1; border : none;
&[open] { display : inline-block; }
a {
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;
opacity : 0.6; background-color : transparent;
&:hover{ opacity : 0.6;
opacity : 1; &:hover { opacity : 1; }
}
} }
.header { .header { padding-left : 50px; }
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;
font-weight : 800; font-weight : 800;
} }
ul{ ul {
margin-top : 15px; margin-top : 15px;
font-size : 0.8em; font-size : 0.8em;
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

@@ -48,6 +48,9 @@ const Editor = createClass({
}; };
}, },
editor : React.createRef(null),
codeEditor : React.createRef(null),
isText : function() {return this.state.view == 'text';}, isText : function() {return this.state.view == 'text';},
isStyle : function() {return this.state.view == 'style';}, isStyle : function() {return this.state.view == 'style';},
isMeta : function() {return this.state.view == 'meta';}, isMeta : function() {return this.state.view == 'meta';},
@@ -100,15 +103,15 @@ const Editor = createClass({
updateEditorSize : function() { updateEditorSize : function() {
if(this.refs.codeEditor) { if(this.codeEditor.current) {
let paneHeight = this.refs.main.parentNode.clientHeight; let paneHeight = this.editor.current.parentNode.clientHeight;
paneHeight -= SNIPPETBAR_HEIGHT; paneHeight -= SNIPPETBAR_HEIGHT;
this.refs.codeEditor.codeMirror.setSize(null, paneHeight); this.codeEditor.current.codeMirror.setSize(null, paneHeight);
} }
}, },
handleInject : function(injectText){ handleInject : function(injectText){
this.refs.codeEditor?.injectText(injectText, false); this.codeEditor.current?.injectText(injectText, false);
}, },
handleViewChange : function(newView){ handleViewChange : function(newView){
@@ -119,7 +122,7 @@ const Editor = createClass({
}, },
getCurrentPage : function(){ getCurrentPage : function(){
const lines = this.props.brew.text.split('\n').slice(0, this.refs.codeEditor.getCursorPosition().line + 1); const lines = this.props.brew.text.split('\n').slice(0, this.codeEditor.current.getCursorPosition().line + 1);
return _.reduce(lines, (r, line)=>{ return _.reduce(lines, (r, line)=>{
if( if(
(this.props.renderer == 'legacy' && line.indexOf('\\page') !== -1) (this.props.renderer == 'legacy' && line.indexOf('\\page') !== -1)
@@ -131,9 +134,9 @@ const Editor = createClass({
}, },
highlightCustomMarkdown : function(){ highlightCustomMarkdown : function(){
if(!this.refs.codeEditor) return; if(!this.codeEditor.current) return;
if(this.state.view === 'text') { if(this.state.view === 'text') {
const codeMirror = this.refs.codeEditor.codeMirror; const codeMirror = this.codeEditor.current.codeMirror;
codeMirror.operation(()=>{ // Batch CodeMirror styling codeMirror.operation(()=>{ // Batch CodeMirror styling
@@ -323,23 +326,23 @@ const Editor = createClass({
targetLine = lineCount - 1; //Scroll to `\page`, which is one line back. targetLine = lineCount - 1; //Scroll to `\page`, which is one line back.
let currentY = this.refs.codeEditor.codeMirror.getScrollInfo().top; let currentY = this.codeEditor.current.codeMirror.getScrollInfo().top;
let targetY = this.refs.codeEditor.codeMirror.heightAtLine(targetLine, 'local', true); let targetY = this.codeEditor.current.codeMirror.heightAtLine(targetLine, 'local', true);
//Scroll 1/10 of the way every 10ms until 1px off. //Scroll 1/10 of the way every 10ms until 1px off.
const incrementalScroll = setInterval(()=>{ const incrementalScroll = setInterval(()=>{
currentY += (targetY - currentY) / 10; currentY += (targetY - currentY) / 10;
this.refs.codeEditor.codeMirror.scrollTo(null, currentY); this.codeEditor.current.codeMirror.scrollTo(null, currentY);
// Update target: target height is not accurate until within +-10 lines of the visible window // Update target: target height is not accurate until within +-10 lines of the visible window
if(Math.abs(targetY - currentY > 100)) if(Math.abs(targetY - currentY > 100))
targetY = this.refs.codeEditor.codeMirror.heightAtLine(targetLine, 'local', true); targetY = this.codeEditor.current.codeMirror.heightAtLine(targetLine, 'local', true);
// End when close enough // End when close enough
if(Math.abs(targetY - currentY) < 1) { if(Math.abs(targetY - currentY) < 1) {
this.refs.codeEditor.codeMirror.scrollTo(null, targetY); // Scroll any remaining difference this.codeEditor.current.codeMirror.scrollTo(null, targetY); // Scroll any remaining difference
this.refs.codeEditor.setCursorPosition({ line: targetLine + 1, ch: 0 }); this.codeEditor.current.setCursorPosition({ line: targetLine + 1, ch: 0 });
this.refs.codeEditor.codeMirror.addLineClass(targetLine + 1, 'wrap', 'sourceMoveFlash'); this.codeEditor.current.codeMirror.addLineClass(targetLine + 1, 'wrap', 'sourceMoveFlash');
clearInterval(incrementalScroll); clearInterval(incrementalScroll);
} }
}, 10); }, 10);
@@ -349,7 +352,7 @@ const Editor = createClass({
//Called when there are changes to the editor's dimensions //Called when there are changes to the editor's dimensions
update : function(){ update : function(){
this.refs.codeEditor?.updateSize(); this.codeEditor.current?.updateSize();
}, },
updateEditorTheme : function(newTheme){ updateEditorTheme : function(newTheme){
@@ -368,7 +371,7 @@ const Editor = createClass({
if(this.isText()){ if(this.isText()){
return <> return <>
<CodeEditor key='codeEditor' <CodeEditor key='codeEditor'
ref='codeEditor' ref={this.codeEditor}
language='gfm' language='gfm'
view={this.state.view} view={this.state.view}
value={this.props.brew.text} value={this.props.brew.text}
@@ -380,7 +383,7 @@ const Editor = createClass({
if(this.isStyle()){ if(this.isStyle()){
return <> return <>
<CodeEditor key='codeEditor' <CodeEditor key='codeEditor'
ref='codeEditor' ref={this.codeEditor}
language='css' language='css'
view={this.state.view} view={this.state.view}
value={this.props.brew.style ?? DEFAULT_STYLE_TEXT} value={this.props.brew.style ?? DEFAULT_STYLE_TEXT}
@@ -405,28 +408,28 @@ const Editor = createClass({
}, },
redo : function(){ redo : function(){
return this.refs.codeEditor?.redo(); return this.codeEditor.current?.redo();
}, },
historySize : function(){ historySize : function(){
return this.refs.codeEditor?.historySize(); return this.codeEditor.current?.historySize();
}, },
undo : function(){ undo : function(){
return this.refs.codeEditor?.undo(); return this.codeEditor.current?.undo();
}, },
foldCode : function(){ foldCode : function(){
return this.refs.codeEditor?.foldAllCode(); return this.codeEditor.current?.foldAllCode();
}, },
unfoldCode : function(){ unfoldCode : function(){
return this.refs.codeEditor?.unfoldAllCode(); return this.codeEditor.current?.unfoldAllCode();
}, },
render : function(){ render : function(){
return ( return (
<div className='editor' ref='main'> <div className='editor' ref={this.editor}>
<SnippetBar <SnippetBar
brew={this.props.brew} brew={this.props.brew}
view={this.state.view} view={this.state.view}
@@ -442,7 +445,7 @@ const Editor = createClass({
historySize={this.historySize()} historySize={this.historySize()}
currentEditorTheme={this.state.editorTheme} currentEditorTheme={this.state.editorTheme}
updateEditorTheme={this.updateEditorTheme} updateEditorTheme={this.updateEditorTheme}
cursorPos={this.refs.codeEditor?.getCursorPosition() || {}} /> cursorPos={this.codeEditor.current?.getCursorPosition() || {}} />
{this.renderEditor()} {this.renderEditor()}
</div> </div>

View File

@@ -3,7 +3,6 @@ require('./metadataEditor.less');
const React = require('react'); const React = require('react');
const createClass = require('create-react-class'); const createClass = require('create-react-class');
const _ = require('lodash'); const _ = require('lodash');
const cx = require('classnames');
const request = require('../../utils/request-middleware.js'); const request = require('../../utils/request-middleware.js');
const Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');
const Combobox = require('client/components/combobox.jsx'); const Combobox = require('client/components/combobox.jsx');

View File

@@ -161,7 +161,7 @@ const Snippetbar = createClass({
onClick={this.props.unfoldCode} > onClick={this.props.unfoldCode} >
<i className='fas fa-expand-alt' /> <i className='fas fa-expand-alt' />
</div> </div>
</> </>;
} }
@@ -181,7 +181,7 @@ const Snippetbar = createClass({
<i className='fas fa-palette' /> <i className='fas fa-palette' />
{this.state.themeSelector && this.renderThemeSelector()} {this.state.themeSelector && this.renderThemeSelector()}
</div> </div>
<div className='divider'></div> <div className='divider'></div>
<div className={cx('text', { selected: this.props.view === 'text' })} <div className={cx('text', { selected: this.props.view === 'text' })}
onClick={()=>this.props.onViewChange('text')}> onClick={()=>this.props.onViewChange('text')}>

View File

@@ -10,7 +10,6 @@ const UserPage = require('./pages/userPage/userPage.jsx');
const SharePage = require('./pages/sharePage/sharePage.jsx'); 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 AccountPage = require('./pages/accountPage/accountPage.jsx'); const AccountPage = require('./pages/accountPage/accountPage.jsx');
const WithRoute = (props)=>{ const WithRoute = (props)=>{
@@ -72,15 +71,13 @@ const Homebrew = createClass({
<Route path='/new/:id' element={<WithRoute el={NewPage} brew={this.props.brew} />} /> <Route path='/new/:id' element={<WithRoute el={NewPage} brew={this.props.brew} />} />
<Route path='/new' element={<WithRoute el={NewPage}/>} /> <Route path='/new' element={<WithRoute el={NewPage}/>} />
<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' element={<WithRoute el={PrintPage} />} />
<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} />} />
<Route path='/legacy' element={<WithRoute el={HomePage} brew={this.props.brew} />} /> <Route path='/legacy' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
<Route path='/error' element={<WithRoute el={ErrorPage} brew={this.props.brew} />} /> <Route path='/error' element={<WithRoute el={ErrorPage} brew={this.props.brew} />} />
<Route path='/' element={<WithRoute el={HomePage} brew={this.props.brew} />} /> <Route path='/' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
<Route path='/*' element={<WithRoute el={HomePage} brew={this.props.brew} />} /> <Route path='/*' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
</Routes> </Routes>
</div> </div>
</Router> </Router>
@@ -88,15 +85,4 @@ 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

@@ -42,7 +42,7 @@ const ErrorNavItem = createClass({
</div> </div>
</Nav.item>; </Nav.item>;
} }
if(status === 412) { if(status === 412) {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'> return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops! Oops!
@@ -51,7 +51,7 @@ const ErrorNavItem = createClass({
</div> </div>
</Nav.item>; </Nav.item>;
} }
if(HBErrorCode === '04') { if(HBErrorCode === '04') {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'> return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops! Oops!
@@ -76,10 +76,10 @@ const ErrorNavItem = createClass({
if(response.body?.errors?.[0].reason == 'storageQuotaExceeded') { if(response.body?.errors?.[0].reason == 'storageQuotaExceeded') {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'> return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops! Oops!
<div className='errorContainer' onClick={clearError}> <div className='errorContainer' onClick={clearError}>
Can't save because your Google Drive seems to be full! Can't save because your Google Drive seems to be full!
</div> </div>
</Nav.item>; </Nav.item>;
} }
if(response.req.url.match(/^\/api.*Google.*$/m)){ if(response.req.url.match(/^\/api.*Google.*$/m)){

View File

@@ -1,6 +1,4 @@
const React = require('react'); const React = require('react');
const createClass = require('create-react-class');
const _ = require('lodash');
const dedent = require('dedent-tabs').default; const dedent = require('dedent-tabs').default;
const Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');

View File

@@ -1,6 +1,5 @@
const React = require('react'); const React = require('react');
const createClass = require('create-react-class'); const createClass = require('create-react-class');
const _ = require('lodash');
const Moment = require('moment'); const Moment = require('moment');
const Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');

View File

@@ -7,58 +7,58 @@ const BREWKEY = 'homebrewery-new';
const STYLEKEY = 'homebrewery-new-style'; const STYLEKEY = 'homebrewery-new-style';
const METAKEY = 'homebrewery-new-meta'; const METAKEY = 'homebrewery-new-meta';
const NewBrew = () => { const NewBrew = ()=>{
const handleFileChange = (e) => { const handleFileChange = (e)=>{
const file = e.target.files[0]; const file = e.target.files[0];
if (file) { if(file) {
const reader = new FileReader(); const reader = new FileReader();
reader.onload = (e) => { reader.onload = (e)=>{
const fileContent = e.target.result; const fileContent = e.target.result;
const newBrew = { const newBrew = {
text: fileContent, text : fileContent,
style: '' style : ''
}; };
if(fileContent.startsWith('```metadata')) { if(fileContent.startsWith('```metadata')) {
splitTextStyleAndMetadata(newBrew); // Modify newBrew directly splitTextStyleAndMetadata(newBrew); // Modify newBrew directly
localStorage.setItem(BREWKEY, newBrew.text); localStorage.setItem(BREWKEY, newBrew.text);
localStorage.setItem(STYLEKEY, newBrew.style); localStorage.setItem(STYLEKEY, newBrew.style);
localStorage.setItem(METAKEY, JSON.stringify(_.pick(newBrew, ['title', 'description', 'tags', 'systems', 'renderer', 'theme', 'lang']))); localStorage.setItem(METAKEY, JSON.stringify(_.pick(newBrew, ['title', 'description', 'tags', 'systems', 'renderer', 'theme', 'lang'])));
window.location.href = '/new'; window.location.href = '/new';
} else { } else {
alert('This file is invalid, please, enter a valid file'); alert('This file is invalid, please, enter a valid file');
} }
}; };
reader.readAsText(file); reader.readAsText(file);
} }
}; };
return ( return (
<Nav.dropdown> <Nav.dropdown>
<Nav.item <Nav.item
className='new' className='new'
color='purple' color='purple'
icon='fa-solid fa-plus-square'> icon='fa-solid fa-plus-square'>
new new
</Nav.item> </Nav.item>
<Nav.item <Nav.item
className='fromBlank' className='fromBlank'
href='/new' href='/new'
newTab={true} newTab={true}
color='purple' color='purple'
icon='fa-solid fa-file'> icon='fa-solid fa-file'>
from blank from blank
</Nav.item> </Nav.item>
<Nav.item <Nav.item
className='fromFile' className='fromFile'
color='purple' color='purple'
icon='fa-solid fa-upload' icon='fa-solid fa-upload'
onClick={() => { document.getElementById('uploadTxt').click(); }}> onClick={()=>{ document.getElementById('uploadTxt').click(); }}>
<input id="uploadTxt" className='newFromLocal' type="file" onChange={handleFileChange} style={{ display: 'none' }} /> <input id='uploadTxt' className='newFromLocal' type='file' onChange={handleFileChange} style={{ display: 'none' }} />
from file from file
</Nav.item> </Nav.item>
</Nav.dropdown> </Nav.dropdown>
); );
}; };
module.exports = NewBrew; module.exports = NewBrew;

View File

@@ -1,9 +1,9 @@
const React = require('react'); const React = require('react');
const createClass = require('create-react-class');
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

@@ -1,9 +1,7 @@
const React = require('react'); const React = require('react');
const createClass = require('create-react-class'); const createClass = require('create-react-class');
const cx = require('classnames');
const Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');
const MAX_URL_SIZE = 2083;
const MAIN_URL = 'https://www.reddit.com/r/UnearthedArcana/submit?selftext=true'; const MAIN_URL = 'https://www.reddit.com/r/UnearthedArcana/submit?selftext=true';

View File

@@ -1,8 +1,6 @@
require('./brewItem.less'); require('./brewItem.less');
const React = require('react'); const React = require('react');
const createClass = require('create-react-class'); const createClass = require('create-react-class');
const _ = require('lodash');
const cx = require('classnames');
const moment = require('moment'); const moment = require('moment');
const request = require('../../../../utils/request-middleware.js'); const request = require('../../../../utils/request-middleware.js');

View File

@@ -262,8 +262,8 @@ const ListPage = createClass({
render : function(){ render : function(){
return <div className='listPage sitePage'> return <div className='listPage sitePage'>
{/*<style>@layer V3_5ePHB, bundle;</style>*/} {/*<style>@layer V3_5ePHB, bundle;</style>*/}
<link href='/themes/V3/Blank/style.css' type="text/css" rel='stylesheet'/> <link href='/themes/V3/Blank/style.css' type='text/css' rel='stylesheet'/>
<link href='/themes/V3/5ePHB/style.css' type="text/css" rel='stylesheet'/> <link href='/themes/V3/5ePHB/style.css' type='text/css' rel='stylesheet'/>
{this.props.navItems} {this.props.navItems}
{this.renderSortOptions()} {this.renderSortOptions()}
{this.renderTagsOptions()} {this.renderTagsOptions()}

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,9 +54,12 @@ 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
}; };
}, },
editor : React.createRef(null),
savedBrew : null, savedBrew : null,
componentDidMount : function(){ componentDidMount : function(){
@@ -93,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();
@@ -101,7 +107,7 @@ const EditPage = createClass({
}, },
handleSplitMove : function(){ handleSplitMove : function(){
this.refs.editor.update(); this.editor.current.update();
}, },
handleTextChange : function(text){ handleTextChange : function(text){
@@ -113,7 +119,7 @@ const EditPage = createClass({
brew : { ...prevState.brew, text: text }, brew : { ...prevState.brew, text: text },
isPending : true, isPending : true,
htmlErrors : htmlErrors, htmlErrors : htmlErrors,
currentEditorPage : this.refs.editor.getCurrentPage() - 1 //Offset index since Marked starts pages at 0 currentEditorPage : this.editor.current.getCurrentPage() - 1 //Offset index since Marked starts pages at 0
}), ()=>{if(this.state.autoSave) this.trySave();}); }), ()=>{if(this.state.autoSave) this.trySave();});
}, },
@@ -376,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>
@@ -390,9 +396,10 @@ const EditPage = createClass({
{this.renderNavbar()} {this.renderNavbar()}
<div className='content'> <div className='content'>
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'> {this.props.brew.lock && <LockNotification shareId={this.props.brew.shareId} message={this.props.brew.lock.editMessage} />}
<SplitPane onDragFinish={this.handleSplitMove}>
<Editor <Editor
ref='editor' ref={this.editor}
brew={this.state.brew} brew={this.state.brew}
onTextChange={this.handleTextChange} onTextChange={this.handleTextChange}
onStyleChange={this.handleStyleChange} onStyleChange={this.handleStyleChange}
@@ -408,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

@@ -95,7 +95,7 @@ const errorIndex = (props)=>{
**Current Authors:** ${props.brew.authors?.map((author)=>{return `[${author}](/user/${author})`;}).join(', ') || 'Unable to list authors'} **Current Authors:** ${props.brew.authors?.map((author)=>{return `[${author}](/user/${author})`;}).join(', ') || 'Unable to list authors'}
[Click here to be redirected to the brew's share page.](/share/${props.brew.shareId})`, [Click here to be redirected to the brew's share page.](/share/${props.brew.shareId})`,
// Brew load error // Brew load error
'05' : dedent` '05' : dedent`
@@ -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

@@ -37,6 +37,9 @@ const HomePage = createClass({
currentEditorPage : 0 currentEditorPage : 0
}; };
}, },
editor : React.createRef(null),
handleSave : function(){ handleSave : function(){
request.post('/api') request.post('/api')
.send(this.state.brew) .send(this.state.brew)
@@ -50,12 +53,12 @@ const HomePage = createClass({
}); });
}, },
handleSplitMove : function(){ handleSplitMove : function(){
this.refs.editor.update(); this.editor.current.update();
}, },
handleTextChange : function(text){ handleTextChange : function(text){
this.setState((prevState)=>({ this.setState((prevState)=>({
brew : { ...prevState.brew, text: text }, brew : { ...prevState.brew, text: text },
currentEditorPage : this.refs.editor.getCurrentPage() - 1 //Offset index since Marked starts pages at 0 currentEditorPage : this.editor.current.getCurrentPage() - 1 //Offset index since Marked starts pages at 0
})); }));
}, },
renderNavbar : function(){ renderNavbar : function(){
@@ -79,9 +82,9 @@ const HomePage = createClass({
{this.renderNavbar()} {this.renderNavbar()}
<div className='content'> <div className='content'>
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'> <SplitPane onDragFinish={this.handleSplitMove}>
<Editor <Editor
ref='editor' ref={this.editor}
brew={this.state.brew} brew={this.state.brew}
onTextChange={this.handleTextChange} onTextChange={this.handleTextChange}
renderer={this.state.brew.renderer} renderer={this.state.brew.renderer}

View File

@@ -2,12 +2,12 @@
require('./newPage.less'); require('./newPage.less');
const React = require('react'); const React = require('react');
const createClass = require('create-react-class'); const createClass = require('create-react-class');
const _ = require('lodash');
const request = require('../../utils/request-middleware.js'); 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');
@@ -19,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';
@@ -47,6 +48,8 @@ const NewPage = createClass({
}; };
}, },
editor : React.createRef(null),
componentDidMount : function() { componentDidMount : function() {
document.addEventListener('keydown', this.handleControlKeys); document.addEventListener('keydown', this.handleControlKeys);
@@ -88,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();
@@ -96,7 +99,7 @@ const NewPage = createClass({
}, },
handleSplitMove : function(){ handleSplitMove : function(){
this.refs.editor.update(); this.editor.current.update();
}, },
handleTextChange : function(text){ handleTextChange : function(text){
@@ -107,7 +110,7 @@ const NewPage = createClass({
this.setState((prevState)=>({ this.setState((prevState)=>({
brew : { ...prevState.brew, text: text }, brew : { ...prevState.brew, text: text },
htmlErrors : htmlErrors, htmlErrors : htmlErrors,
currentEditorPage : this.refs.editor.getCurrentPage() - 1 //Offset index since Marked starts pages at 0 currentEditorPage : this.editor.current.getCurrentPage() - 1 //Offset index since Marked starts pages at 0
})); }));
localStorage.setItem(BREWKEY, text); localStorage.setItem(BREWKEY, text);
}, },
@@ -179,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>
@@ -201,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 />
@@ -213,9 +206,9 @@ const NewPage = createClass({
return <div className='newPage sitePage'> return <div className='newPage sitePage'>
{this.renderNavbar()} {this.renderNavbar()}
<div className='content'> <div className='content'>
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'> <SplitPane onDragFinish={this.handleSplitMove}>
<Editor <Editor
ref='editor' ref={this.editor}
brew={this.state.brew} brew={this.state.brew}
onTextChange={this.handleTextChange} onTextChange={this.handleTextChange}
onStyleChange={this.handleStyleChange} onStyleChange={this.handleStyleChange}
@@ -230,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,113 +0,0 @@
require('./printPage.less');
const React = require('react');
const createClass = require('create-react-class');
const _ = require('lodash');
const cx = require('classnames');
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' ref='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();
} }
@@ -57,7 +56,7 @@ const SharePage = createClass({
return <Nav.item color='orange' icon='fas fa-pencil-alt' href={`/edit/${editLink}`}> return <Nav.item color='orange' icon='fas fa-pencil-alt' href={`/edit/${editLink}`}>
edit edit
</Nav.item>; </Nav.item>;
}, },
render : function(){ render : function(){
@@ -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>;
} }

View File

@@ -1,7 +1,6 @@
const React = require('react'); const React = require('react');
const createClass = require('create-react-class'); const createClass = require('create-react-class');
const _ = require('lodash'); const _ = require('lodash');
const cx = require('classnames');
const ListPage = require('../basePages/listPage/listPage.jsx'); const ListPage = require('../basePages/listPage/listPage.jsx');

1531
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.1", "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,21 +110,21 @@
"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",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-frame-component": "^4.1.3", "react-frame-component": "^4.1.3",
"react-router-dom": "6.23.0", "react-router-dom": "6.23.1",
"sanitize-filename": "1.6.3", "sanitize-filename": "1.6.3",
"superagent": "^9.0.2", "superagent": "^9.0.2",
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git" "vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
}, },
"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

@@ -14,7 +14,7 @@ DB.connect(config).then(()=>{
console.log(`\n\tserver started at: ${new Date().toLocaleString()}`); console.log(`\n\tserver started at: ${new Date().toLocaleString()}`);
console.log(`\tserver on port: ${PORT}`); console.log(`\tserver on port: ${PORT}`);
console.log(`\t${bright + cyan}Open in browser: ${reset}${underline + bright + cyan}http://localhost:${PORT}${reset}\n\n`) console.log(`\t${bright + cyan}Open in browser: ${reset}${underline + bright + cyan}http://localhost:${PORT}${reset}\n\n`);
}); });
}); });

View File

@@ -30,7 +30,7 @@ const junkBrewPipeline = [
{ $match : { { $match : {
updatedAt : { $lt: Moment().subtract(30, 'days').toDate() }, updatedAt : { $lt: Moment().subtract(30, 'days').toDate() },
lastViewed : { $lt: Moment().subtract(30, 'days').toDate() } lastViewed : { $lt: Moment().subtract(30, 'days').toDate() }
}}, } },
{ $project: { textBinSize: { $binarySize: '$textBin' } } }, { $project: { textBinSize: { $binarySize: '$textBin' } } },
{ $match: { textBinSize: { $lt: 140 } } }, { $match: { textBinSize: { $lt: 140 } } },
{ $limit: 100 } { $limit: 100 }

View File

@@ -25,7 +25,7 @@ const sanitizeBrew = (brew, accessType)=>{
brew.__v = undefined; brew.__v = undefined;
if(accessType !== 'edit' && accessType !== 'shareAuthor') { if(accessType !== 'edit' && accessType !== 'shareAuthor') {
brew.editId = undefined; brew.editId = undefined;
} }
return brew; return brew;
}; };
@@ -331,13 +331,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

@@ -1,5 +1,4 @@
/* eslint-disable max-lines */ /* eslint-disable max-lines */
const _ = require('lodash');
const googleDrive = require('@googleapis/drive'); const googleDrive = require('@googleapis/drive');
const { nanoid } = require('nanoid'); const { nanoid } = require('nanoid');
const token = require('./token.js'); const token = require('./token.js');

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
@@ -83,9 +83,9 @@ const api = {
if(accessType === 'edit' && (authorsExist && !(isAuthor || isInvited))) { if(accessType === 'edit' && (authorsExist && !(isAuthor || isInvited))) {
const accessError = { name: 'Access Error', status: 401 }; const accessError = { name: 'Access Error', status: 401 };
if(req.account){ if(req.account){
throw { ...accessError, message: 'User is not an Author', HBErrorCode: '03', authors: stub.authors, brewTitle: stub.title, shareId: stub.shareId}; throw { ...accessError, message: 'User is not an Author', HBErrorCode: '03', authors: stub.authors, brewTitle: stub.title, shareId: stub.shareId };
} }
throw { ...accessError, message: 'User is not logged in', HBErrorCode: '04', authors: stub.authors, brewTitle: stub.title, shareId: stub.shareId}; throw { ...accessError, message: 'User is not logged in', HBErrorCode: '04', authors: stub.authors, brewTitle: stub.title, shareId: stub.shareId };
} }
// If after all of that we still don't have a brew, throw an exception // If after all of that we still don't have a brew, throw an exception

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

@@ -1,22 +1,34 @@
const _ = require('lodash'); const _ = require('lodash');
const yaml = require('js-yaml'); const yaml = require('js-yaml');
const splitTextStyleAndMetadata = (brew) => { const splitTextStyleAndMetadata = (brew)=>{
brew.text = brew.text.replaceAll('\r\n', '\n'); brew.text = brew.text.replaceAll('\r\n', '\n');
if (brew.text.startsWith('```metadata')) { if(brew.text.startsWith('```metadata')) {
const index = brew.text.indexOf('```\n\n'); const index = brew.text.indexOf('```\n\n');
const metadataSection = brew.text.slice(12, index - 1); const metadataSection = brew.text.slice(12, index - 1);
const metadata = yaml.load(metadataSection); const metadata = yaml.load(metadataSection);
Object.assign(brew, _.pick(metadata, ['title', 'description', 'tags', 'systems', 'renderer', 'theme', 'lang'])); Object.assign(brew, _.pick(metadata, ['title', 'description', 'tags', 'systems', 'renderer', 'theme', 'lang']));
brew.text = brew.text.slice(index + 5); brew.text = brew.text.slice(index + 5);
} }
if (brew.text.startsWith('```css')) { if(brew.text.startsWith('```css')) {
const index = brew.text.indexOf('```\n\n'); const index = brew.text.indexOf('```\n\n');
brew.style = brew.text.slice(7, index - 1); brew.style = brew.text.slice(7, index - 1);
brew.text = brew.text.slice(index + 5); brew.text = brew.text.slice(index + 5);
} }
};
const printCurrentBrew = ()=>{
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 = { module.exports = {
splitTextStyleAndMetadata splitTextStyleAndMetadata,
printCurrentBrew
}; };

View File

@@ -2,9 +2,8 @@ require('./renderWarnings.less');
const React = require('react'); const React = require('react');
const createClass = require('create-react-class'); const createClass = require('create-react-class');
const _ = require('lodash'); const _ = require('lodash');
const cx = require('classnames');
const DISMISS_KEY = 'dismiss_render_warning'; import Dialog from '../../../client/components/dialog.jsx';
const RenderWarnings = createClass({ const RenderWarnings = createClass({
displayName : 'RenderWarnings', displayName : 'RenderWarnings',
@@ -35,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();
@@ -46,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,53 +1,48 @@
.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;
opacity : 0.6; background-color : transparent;
&:hover{ opacity : 0.6;
opacity : 1; &:hover { 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;
font-weight : 800; font-weight : 800;
} }
ul{ ul {
margin-top : 15px; margin-top : 15px;
font-size : 0.8em; font-size : 0.8em;
list-style-position : outside; list-style-position : outside;
list-style-type : disc; list-style-type : disc;
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

@@ -1,11 +1,13 @@
const diceFont = require('../../../themes/fonts/iconFonts/diceFont.js'); const diceFont = require('../../../themes/fonts/iconFonts/diceFont.js');
const elderberryInn = require('../../../themes/fonts/iconFonts/elderberryInn.js'); const elderberryInn = require('../../../themes/fonts/iconFonts/elderberryInn.js');
const fontAwesome = require('../../../themes/fonts/iconFonts/fontAwesome.js'); const fontAwesome = require('../../../themes/fonts/iconFonts/fontAwesome.js');
const gameIcons = require('../../../themes/fonts/iconFonts/gameIcons.js');
const emojis = { const emojis = {
...diceFont, ...diceFont,
...elderberryInn, ...elderberryInn,
...fontAwesome ...fontAwesome,
...gameIcons
}; };
const showAutocompleteEmoji = function(CodeMirror, editor) { const showAutocompleteEmoji = function(CodeMirror, editor) {

View File

@@ -3,7 +3,6 @@ require('./codeEditor.less');
const React = require('react'); const React = require('react');
const createClass = require('create-react-class'); const createClass = require('create-react-class');
const _ = require('lodash'); const _ = require('lodash');
const cx = require('classnames');
const closeTag = require('./close-tag'); const closeTag = require('./close-tag');
const autoCompleteEmoji = require('./autocompleteEmoji'); const autoCompleteEmoji = require('./autocompleteEmoji');
@@ -63,6 +62,8 @@ const CodeEditor = createClass({
}; };
}, },
editor : React.createRef(null),
componentDidMount : function() { componentDidMount : function() {
this.buildEditor(); this.buildEditor();
const newDoc = CodeMirror.Doc(this.props.value, this.props.language); const newDoc = CodeMirror.Doc(this.props.value, this.props.language);
@@ -102,7 +103,7 @@ const CodeEditor = createClass({
}, },
buildEditor : function() { buildEditor : function() {
this.codeMirror = CodeMirror(this.refs.editor, { this.codeMirror = CodeMirror(this.editor.current, {
lineNumbers : true, lineNumbers : true,
lineWrapping : this.props.wrap, lineWrapping : this.props.wrap,
indentWithTabs : false, indentWithTabs : false,
@@ -443,7 +444,7 @@ const CodeEditor = createClass({
render : function(){ render : function(){
return <> return <>
<link href={`../homebrew/cm-themes/${this.props.editorTheme}.css`} type='text/css' rel='stylesheet' /> <link href={`../homebrew/cm-themes/${this.props.editorTheme}.css`} type='text/css' rel='stylesheet' />
<div className='codeEditor' ref='editor' style={this.props.style}/> <div className='codeEditor' ref={this.editor} style={this.props.style}/>
</>; </>;
} }
}); });

View File

@@ -7,6 +7,7 @@
//Icon fonts included so they can appear in emoji autosuggest dropdown //Icon fonts included so they can appear in emoji autosuggest dropdown
@import (less) './themes/fonts/iconFonts/diceFont.less'; @import (less) './themes/fonts/iconFonts/diceFont.less';
@import (less) './themes/fonts/iconFonts/elderberryInn.less'; @import (less) './themes/fonts/iconFonts/elderberryInn.less';
@import (less) './themes/fonts/iconFonts/gameIcons.less';
@keyframes sourceMoveAnimation { @keyframes sourceMoveAnimation {
50% {background-color: red; color: white;} 50% {background-color: red; color: white;}

View File

@@ -10,6 +10,7 @@ const { markedEmoji: MarkedEmojis } = require('marked-emoji');
const diceFont = require('../../themes/fonts/iconFonts/diceFont.js'); const diceFont = require('../../themes/fonts/iconFonts/diceFont.js');
const elderberryInn = require('../../themes/fonts/iconFonts/elderberryInn.js'); const elderberryInn = require('../../themes/fonts/iconFonts/elderberryInn.js');
const fontAwesome = require('../../themes/fonts/iconFonts/fontAwesome.js'); const fontAwesome = require('../../themes/fonts/iconFonts/fontAwesome.js');
const gameIcons = require('../../themes/fonts/iconFonts/gameIcons.js');
const MathParser = require('expr-eval').Parser; const MathParser = require('expr-eval').Parser;
const renderer = new Marked.Renderer(); const renderer = new Marked.Renderer();
@@ -688,7 +689,8 @@ const MarkedEmojiOptions = {
emojis : { emojis : {
...diceFont, ...diceFont,
...elderberryInn, ...elderberryInn,
...fontAwesome ...fontAwesome,
...gameIcons,
}, },
renderer : (token)=>`<i class="${token.emoji}"></i>` renderer : (token)=>`<i class="${token.emoji}"></i>`
}; };

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);

View File

@@ -1,7 +1,6 @@
require('./splitPane.less'); require('./splitPane.less');
const React = require('react'); const React = require('react');
const createClass = require('create-react-class'); const createClass = require('create-react-class');
const _ = require('lodash');
const cx = require('classnames'); const cx = require('classnames');
const SplitPane = createClass({ const SplitPane = createClass({
@@ -24,6 +23,9 @@ const SplitPane = createClass({
}; };
}, },
pane1 : React.createRef(null),
pane2 : React.createRef(null),
componentDidMount : function() { componentDidMount : function() {
const dividerPos = window.localStorage.getItem(this.props.storageKey); const dividerPos = window.localStorage.getItem(this.props.storageKey);
if(dividerPos){ if(dividerPos){
@@ -137,7 +139,6 @@ const SplitPane = createClass({
render : function(){ render : function(){
return <div className='splitPane' onPointerMove={this.handleMove} onPointerUp={this.handleUp}> return <div className='splitPane' onPointerMove={this.handleMove} onPointerUp={this.handleUp}>
<Pane <Pane
ref='pane1'
width={this.state.currentDividerPos} width={this.state.currentDividerPos}
> >
{React.cloneElement(this.props.children[0], { {React.cloneElement(this.props.children[0], {
@@ -147,7 +148,7 @@ const SplitPane = createClass({
})} })}
</Pane> </Pane>
{this.renderDivider()} {this.renderDivider()}
<Pane ref='pane2' isDragging={this.state.isDragging}>{this.props.children[1]}</Pane> <Pane isDragging={this.state.isDragging}>{this.props.children[1]}</Pane>
</div>; </div>;
} }
}); });

View File

@@ -1,47 +1,47 @@
const _ = require("lodash"); const _ = require('lodash');
const quotes = [ const quotes = [
"The sword glinted in the dim light, its edges keen and deadly. As the adventurer reached for it, he couldn't help but feel a surge of excitement mixed with fear. This was no ordinary blade.", 'The sword glinted in the dim light, its edges keen and deadly. As the adventurer reached for it, he couldn\'t help but feel a surge of excitement mixed with fear. This was no ordinary blade.',
"The dragon's roar shook the ground beneath their feet, and the brave knight stood tall, his sword at the ready. He knew that this would be the battle of his life, but he was determined to emerge victorious.", 'The dragon\'s roar shook the ground beneath their feet, and the brave knight stood tall, his sword at the ready. He knew that this would be the battle of his life, but he was determined to emerge victorious.',
"The wizard's laboratory was a sight to behold, filled with bubbling cauldrons, ancient tomes, and strange artifacts from distant lands. As the apprentice gazed around in wonder, she knew that she was about to embark on a journey unlike any other.", 'The wizard\'s laboratory was a sight to behold, filled with bubbling cauldrons, ancient tomes, and strange artifacts from distant lands. As the apprentice gazed around in wonder, she knew that she was about to embark on a journey unlike any other.',
"The tavern was packed with rowdy patrons, their voices raised in song and laughter. The bard took center stage, strumming his lute and launching into a tale of adventure and heroism that had the crowd hanging on his every word.", 'The tavern was packed with rowdy patrons, their voices raised in song and laughter. The bard took center stage, strumming his lute and launching into a tale of adventure and heroism that had the crowd hanging on his every word.',
"The thief crept through the shadows, his eyes scanning the room for any sign of danger. He knew that one false move could mean the difference between success and failure, and he was determined to come out on top.", 'The thief crept through the shadows, his eyes scanning the room for any sign of danger. He knew that one false move could mean the difference between success and failure, and he was determined to come out on top.',
"The elf queen stood atop her castle walls, surveying the kingdom below with a mix of pride and sadness. She knew that the coming war would be brutal, but she was determined to protect her people at all costs.", 'The elf queen stood atop her castle walls, surveying the kingdom below with a mix of pride and sadness. She knew that the coming war would be brutal, but she was determined to protect her people at all costs.',
"The necromancer's tower loomed in the distance, its dark spires piercing the sky. As the adventurers approached, they could feel the chill of death emanating from within", 'The necromancer\'s tower loomed in the distance, its dark spires piercing the sky. As the adventurers approached, they could feel the chill of death emanating from within',
"The ranger moved through the forest like a shadow, his senses attuned to every sound and movement around him. He knew that danger lurked behind every tree, but he was ready for whatever came his way.", 'The ranger moved through the forest like a shadow, his senses attuned to every sound and movement around him. He knew that danger lurked behind every tree, but he was ready for whatever came his way.',
"The paladin knelt before the altar, his hands clasped in prayer. He knew that his faith would be tested in the days ahead, but he was ready to face whatever trials lay in store for him.", 'The paladin knelt before the altar, his hands clasped in prayer. He knew that his faith would be tested in the days ahead, but he was ready to face whatever trials lay in store for him.',
"The druid communed with the spirits of nature, his mind merging with the trees, the animals, and the very earth itself. He knew that his power came with a great responsibility, and he was determined to use it for the greater good.", 'The druid communed with the spirits of nature, his mind merging with the trees, the animals, and the very earth itself. He knew that his power came with a great responsibility, and he was determined to use it for the greater good.',
]; ];
const authors = [ const authors = [
"Unknown", 'Unknown',
"James Wyatt", 'James Wyatt',
"Eolande Blackwood", 'Eolande Blackwood',
"Ragnar Ironheart", 'Ragnar Ironheart',
"Lyra Nightshade", 'Lyra Nightshade',
"Valtorius Darkstar", 'Valtorius Darkstar',
"Isadora Fireheart", 'Isadora Fireheart',
"Theron Shadowbane", 'Theron Shadowbane',
"Lirien Starweaver", 'Lirien Starweaver',
"Drogathar Bonecrusher", 'Drogathar Bonecrusher',
"Kaelen Frostblade", 'Kaelen Frostblade',
]; ];
const books = [ const books = [
"The Blade of Destiny", 'The Blade of Destiny',
"Dragonfire and Steel", 'Dragonfire and Steel',
"The Bard's Tale", 'The Bard\'s Tale',
"Darkness Rising", 'Darkness Rising',
"The Sacred Quest", 'The Sacred Quest',
"Shadows in the Forest", 'Shadows in the Forest',
"The Starweaver Chronicles", 'The Starweaver Chronicles',
"Beneath the Bones", 'Beneath the Bones',
"Moonlit Magic", 'Moonlit Magic',
"Frost and Fury", 'Frost and Fury',
]; ];
module.exports = () => { module.exports = ()=>{
return ` return `
{{quote {{quote
${_.sample(quotes)} ${_.sample(quotes)}

View File

@@ -29,7 +29,7 @@ const getTOC = (pages)=>{
const res = []; const res = [];
_.each(pages, (page, pageNum)=>{ _.each(pages, (page, pageNum)=>{
if(!page.includes("{{frontCover}}") && !page.includes("{{insideCover}}") && !page.includes("{{partCover}}") && !page.includes("{{backCover}}")) { if(!page.includes('{{frontCover}}') && !page.includes('{{insideCover}}') && !page.includes('{{partCover}}') && !page.includes('{{backCover}}')) {
const lines = page.split('\n'); const lines = page.split('\n');
_.each(lines, (line)=>{ _.each(lines, (line)=>{
if(_.startsWith(line, '# ')){ if(_.startsWith(line, '# ')){

View File

@@ -307,8 +307,8 @@ module.exports = [
/**************** FONTS *************/ /**************** FONTS *************/
{ {
groupName : 'Fonts', groupName : 'Fonts',
icon : 'fas fa-keyboard', icon : 'fas fa-keyboard',
view : 'text', view : 'text',
snippets : [ snippets : [
{ {
name : 'Open Sans', name : 'Open Sans',
@@ -341,59 +341,59 @@ module.exports = [
gen : dedent`{{font-family:MrEavesRemake Dummy Text}}` gen : dedent`{{font-family:MrEavesRemake Dummy Text}}`
}, },
{ {
name: 'Solbera Imitation', name : 'Solbera Imitation',
icon: 'font SolberaImitationRemake', icon : 'font SolberaImitationRemake',
gen: dedent`{{font-family:SolberaImitationRemake Dummy Text}}` gen : dedent`{{font-family:SolberaImitationRemake Dummy Text}}`
}, },
{ {
name: 'Scaly Sans Small Caps', name : 'Scaly Sans Small Caps',
icon: 'font ScalySansSmallCapsRemake', icon : 'font ScalySansSmallCapsRemake',
gen: dedent`{{font-family:ScalySansSmallCapsRemake Dummy Text}}` gen : dedent`{{font-family:ScalySansSmallCapsRemake Dummy Text}}`
}, },
{ {
name: 'Walter Turncoat', name : 'Walter Turncoat',
icon: 'font WalterTurncoat', icon : 'font WalterTurncoat',
gen: dedent`{{font-family:WalterTurncoat Dummy Text}}` gen : dedent`{{font-family:WalterTurncoat Dummy Text}}`
}, },
{ {
name: 'Lato', name : 'Lato',
icon: 'font Lato', icon : 'font Lato',
gen: dedent`{{font-family:Lato Dummy Text}}` gen : dedent`{{font-family:Lato Dummy Text}}`
}, },
{ {
name: 'Courier', name : 'Courier',
icon: 'font Courier', icon : 'font Courier',
gen: dedent`{{font-family:Courier Dummy Text}}` gen : dedent`{{font-family:Courier Dummy Text}}`
}, },
{ {
name: 'Nodesto Caps Condensed', name : 'Nodesto Caps Condensed',
icon: 'font NodestoCapsCondensed', icon : 'font NodestoCapsCondensed',
gen: dedent`{{font-family:NodestoCapsCondensed Dummy Text}}` gen : dedent`{{font-family:NodestoCapsCondensed Dummy Text}}`
}, },
{ {
name: 'Overpass', name : 'Overpass',
icon: 'font Overpass', icon : 'font Overpass',
gen: dedent`{{font-family:Overpass Dummy Text}}` gen : dedent`{{font-family:Overpass Dummy Text}}`
}, },
{ {
name: 'Davek', name : 'Davek',
icon: 'font Davek', icon : 'font Davek',
gen: dedent`{{font-family:Davek Dummy Text}}` gen : dedent`{{font-family:Davek Dummy Text}}`
}, },
{ {
name: 'Iokharic', name : 'Iokharic',
icon: 'font Iokharic', icon : 'font Iokharic',
gen: dedent`{{font-family:Iokharic Dummy Text}}` gen : dedent`{{font-family:Iokharic Dummy Text}}`
}, },
{ {
name: 'Rellanic', name : 'Rellanic',
icon: 'font Rellanic', icon : 'font Rellanic',
gen: dedent`{{font-family:Rellanic Dummy Text}}` gen : dedent`{{font-family:Rellanic Dummy Text}}`
}, },
{ {
name: 'Times New Roman', name : 'Times New Roman',
icon: 'font TimesNewRoman', icon : 'font TimesNewRoman',
gen: dedent`{{font-family:"Times New Roman" Dummy Text}}` gen : dedent`{{font-family:"Times New Roman" Dummy Text}}`
} }
] ]
}, },

View File

@@ -2,6 +2,7 @@
@import (less) './themes/assets/assets.less'; @import (less) './themes/assets/assets.less';
@import (less) './themes/fonts/iconFonts/elderberryInn.less'; @import (less) './themes/fonts/iconFonts/elderberryInn.less';
@import (less) './themes/fonts/iconFonts/diceFont.less'; @import (less) './themes/fonts/iconFonts/diceFont.less';
@import (less) './themes/fonts/iconFonts/gameIcons.less';
:root { :root {
//Colors //Colors

View File

@@ -58,7 +58,7 @@
background-color: rgba(35,153,153,0.5); background-color: rgba(35,153,153,0.5);
} }
.pageLine { .pageLine {
background-color: rgba(255,255,255,0.75); background-color: rgba(255,255,255,0.5);
& ~ pre.CodeMirror-line { & ~ pre.CodeMirror-line {
color: black; color: black;
} }
@@ -85,4 +85,4 @@
// Future styling for themes with light backgrounds // Future styling for themes with light backgrounds
--dummyVar: 'currently unused'; --dummyVar: 'currently unused';
} }
} }

View File

@@ -29,7 +29,7 @@ const elderberryInn = {
'ei_slashing' : 'ei slashing', 'ei_slashing' : 'ei slashing',
'ei_thunder' : 'ei thunder', 'ei_thunder' : 'ei thunder',
/* DnD Donditions */ /* DnD Conditions */
'ei_blinded' : 'ei blinded', 'ei_blinded' : 'ei blinded',
'ei_charmed' : 'ei charmed', 'ei_charmed' : 'ei charmed',
'ei_deafened' : 'ei deafened', 'ei_deafened' : 'ei deafened',

View File

@@ -0,0 +1,13 @@
# Game Icons License
The font used in gameIcons.woff / gameIcons.less / gameIcons.js, and usable in the Homebrewery as "gi" icons, are a subset of the Game-Icons.net library of icons.
## The license
Game-Icons has this to say about their license:
> Game-icons.net is an online repository providing heaps of cool game related graphics.
>
> They are provided provided under the terms of the Creative Commons 3.0 BY license.
>
> It means that you can use them freely as long as you credit the original author in your creation(see below). A mention like "Icons made by {author;}. Available on https://game-icons.net" is fine.

View File

@@ -0,0 +1,509 @@
/* eslint-disable max-lines */
// This is a subset of the library of icons at game-icons.net -- the subset is from RPG-Awesome repo
// The entire font can be downloaded as svg from game-icons.net,
// and then loaded through icomoon.io to create webfont from svg,
// and the css font file can be turned into below list using regex
// regex used: \.([^:-]*)-([^:]*)(.*)
// substitution: "$1$2" : "$1-$2",
const gameIcons = {
'gi_zigzag_leaf' : 'gi zigzag-leaf',
'gi_zebra_shield' : 'gi zebra-shield',
'gi_x_mark' : 'gi x-mark',
'gi_wyvern' : 'gi wyvern',
'gi_wrench' : 'gi wrench',
'gi_wooden_sign' : 'gi wooden-sign',
'gi_wolf_howl' : 'gi wolf-howl',
'gi_wolf_head' : 'gi wolf-head',
'gi_wireless_signal' : 'gi wireless-signal',
'gi_wifi' : 'gi wifi',
'gi_water_drop' : 'gi water-drop',
'gi_virgo' : 'gi virgo',
'gi_vine_whip' : 'gi vine-whip',
'gi_vial' : 'gi vial',
'gi_vest' : 'gi vest',
'gi_venomous_snake' : 'gi venomous-snake',
'gi_vase' : 'gi vase',
'gi_unplugged' : 'gi unplugged',
'gi_underhand' : 'gi underhand',
'gi_uncertainty' : 'gi uncertainty',
'gi_two_hearts' : 'gi two-hearts',
'gi_two_dragons' : 'gi two-dragons',
'gi_turd' : 'gi turd',
'gi_trophy' : 'gi trophy',
'gi_triforce' : 'gi triforce',
'gi_trident' : 'gi trident',
'gi_trefoil_lily' : 'gi trefoil-lily',
'gi_trail' : 'gi trail',
'gi_tower' : 'gi tower',
'gi_torch' : 'gi torch',
'gi_tooth' : 'gi tooth',
'gi_tombstone' : 'gi tombstone',
'gi_toast' : 'gi toast',
'gi_tic_tac_toe' : 'gi tic-tac-toe',
'gi_three_keys' : 'gi three-keys',
'gi_thorny_vine' : 'gi thorny-vine',
'gi_thorn_arrow' : 'gi thorn-arrow',
'gi_tesla' : 'gi tesla',
'gi_tentacle' : 'gi tentacle',
'gi_telescope' : 'gi telescope',
'gi_taurus' : 'gi taurus',
'gi_targeted' : 'gi targeted',
'gi_target_laser' : 'gi target-laser',
'gi_target_arrows' : 'gi target-arrows',
'gi_syringe' : 'gi syringe',
'gi_surveillance_camera' : 'gi surveillance-camera',
'gi_supersonic_arrow' : 'gi supersonic-arrow',
'gi_super_mushroom' : 'gi super-mushroom',
'gi_sunbeams' : 'gi sunbeams',
'gi_sun' : 'gi sun',
'gi_sun_symbol' : 'gi sun-symbol',
'gi_suits' : 'gi suits',
'gi_suckered_tentacle' : 'gi suckered-tentacle',
'gi_stopwatch' : 'gi stopwatch',
'gi_sprout' : 'gi sprout',
'gi_sprout_emblem' : 'gi sprout-emblem',
'gi_spray_can' : 'gi spray-can',
'gi_splash' : 'gi splash',
'gi_spiral_shell' : 'gi spiral-shell',
'gi_spinning_sword' : 'gi spinning-sword',
'gi_spiked_tentacle' : 'gi spiked-tentacle',
'gi_spiked_mace' : 'gi spiked-mace',
'gi_spikeball' : 'gi spikeball',
'gi_spider_face' : 'gi spider-face',
'gi_speech_bubbles' : 'gi speech-bubbles',
'gi_speech_bubble' : 'gi speech-bubble',
'gi_spear_head' : 'gi spear-head',
'gi_spawn_node' : 'gi spawn-node',
'gi_spades' : 'gi spades',
'gi_spades_card' : 'gi spades-card',
'gi_soccer_ball' : 'gi soccer-ball',
'gi_snowflake' : 'gi snowflake',
'gi_snorkel' : 'gi snorkel',
'gi_snake' : 'gi snake',
'gi_snail' : 'gi snail',
'gi_small_fire' : 'gi small-fire',
'gi_slash_ring' : 'gi slash-ring',
'gi_skull' : 'gi skull',
'gi_skull_trophy' : 'gi skull-trophy',
'gi_site' : 'gi site',
'gi_sideswipe' : 'gi sideswipe',
'gi_sickle' : 'gi sickle',
'gi_shuriken' : 'gi shuriken',
'gi_shovel' : 'gi shovel',
'gi_shotgun_shell' : 'gi shotgun-shell',
'gi_shot_through_the_heart' : 'gi shot-through-the-heart',
'gi_shoe_prints' : 'gi shoe-prints',
'gi_ship_emblem' : 'gi ship-emblem',
'gi_shield' : 'gi shield',
'gi_sheriff' : 'gi sheriff',
'gi_sheep' : 'gi sheep',
'gi_shark' : 'gi shark',
'gi_seagull' : 'gi seagull',
'gi_sea_serpent' : 'gi sea-serpent',
'gi_scythe' : 'gi scythe',
'gi_scroll_unfurled' : 'gi scroll-unfurled',
'gi_scorpio' : 'gi scorpio',
'gi_save' : 'gi save',
'gi_satellite' : 'gi satellite',
'gi_sapphire' : 'gi sapphire',
'gi_sagittarius' : 'gi sagittarius',
'gi_rune_stone' : 'gi rune-stone',
'gi_rss' : 'gi rss',
'gi_round_shield' : 'gi round-shield',
'gi_round_bottom_flask' : 'gi round-bottom-flask',
'gi_robot_arm' : 'gi robot-arm',
'gi_roast_chicken' : 'gi roast-chicken',
'gi_ringing_bell' : 'gi ringing-bell',
'gi_rifle' : 'gi rifle',
'gi_revolver' : 'gi revolver',
'gi_reverse' : 'gi reverse',
'gi_repair' : 'gi repair',
'gi_relic_blade' : 'gi relic-blade',
'gi_regeneration' : 'gi regeneration',
'gi_recycle' : 'gi recycle',
'gi_reactor' : 'gi reactor',
'gi_raven' : 'gi raven',
'gi_radioactive' : 'gi radioactive',
'gi_radial_balance' : 'gi radial-balance',
'gi_radar_dish' : 'gi radar-dish',
'gi_rabbit' : 'gi rabbit',
'gi_quill_ink' : 'gi quill-ink',
'gi_queen_crown' : 'gi queen-crown',
'gi_pyramids' : 'gi pyramids',
'gi_potion' : 'gi potion',
'gi_poison_cloud' : 'gi poison-cloud',
'gi_podium' : 'gi podium',
'gi_player' : 'gi player',
'gi_player_thunder_struck' : 'gi player-thunder-struck',
'gi_player_teleport' : 'gi player-teleport',
'gi_player_shot' : 'gi player-shot',
'gi_player_pyromaniac' : 'gi player-pyromaniac',
'gi_player_pain' : 'gi player-pain',
'gi_player_lift' : 'gi player-lift',
'gi_player_king' : 'gi player-king',
'gi_player_dodge' : 'gi player-dodge',
'gi_player_despair' : 'gi player-despair',
'gi_plain_dagger' : 'gi plain-dagger',
'gi_pisces' : 'gi pisces',
'gi_ping_pong' : 'gi ping-pong',
'gi_pine_tree' : 'gi pine-tree',
'gi_pills' : 'gi pills',
'gi_pill' : 'gi pill',
'gi_perspective_dice_three' : 'gi perspective-dice-three',
'gi_perspective_dice_six' : 'gi perspective-dice-six',
'gi_perspective_dice_six_two' : 'gi perspective-dice-six-two',
'gi_perspective_dice_random' : 'gi perspective-dice-random',
'gi_perspective_dice_one' : 'gi perspective-dice-one',
'gi_perspective_dice_four' : 'gi perspective-dice-four',
'gi_perspective_dice_five' : 'gi perspective-dice-five',
'gi_pawprint' : 'gi pawprint',
'gi_pawn' : 'gi pawn',
'gi_palm_tree' : 'gi palm-tree',
'gi_overmind' : 'gi overmind',
'gi_overhead' : 'gi overhead',
'gi_ophiuchus' : 'gi ophiuchus',
'gi_on_target' : 'gi on-target',
'gi_omega' : 'gi omega',
'gi_octopus' : 'gi octopus',
'gi_ocean_emblem' : 'gi ocean-emblem',
'gi_ocarina' : 'gi ocarina',
'gi_nuclear' : 'gi nuclear',
'gi_noose' : 'gi noose',
'gi_nodular' : 'gi nodular',
'gi_nails' : 'gi nails',
'gi_musket' : 'gi musket',
'gi_muscle_up' : 'gi muscle-up',
'gi_muscle_fat' : 'gi muscle-fat',
'gi_mp5' : 'gi mp5',
'gi_moon_sun' : 'gi moon-sun',
'gi_montains' : 'gi montains',
'gi_monster_skull' : 'gi monster-skull',
'gi_mirror' : 'gi mirror',
'gi_mining_diamonds' : 'gi mining-diamonds',
'gi_mine_wagon' : 'gi mine-wagon',
'gi_microphone' : 'gi microphone',
'gi_metal_gate' : 'gi metal-gate',
'gi_medical_pack' : 'gi medical-pack',
'gi_meat' : 'gi meat',
'gi_meat_hook' : 'gi meat-hook',
'gi_match' : 'gi match',
'gi_mass_driver' : 'gi mass-driver',
'gi_magnet' : 'gi magnet',
'gi_maggot' : 'gi maggot',
'gi_love_howl' : 'gi love-howl',
'gi_locked_fortress' : 'gi locked-fortress',
'gi_load' : 'gi load',
'gi_lit_candelabra' : 'gi lit-candelabra',
'gi_lion' : 'gi lion',
'gi_lightning' : 'gi lightning',
'gi_lightning_trio' : 'gi lightning-trio',
'gi_lightning_sword' : 'gi lightning-sword',
'gi_lightning_storm' : 'gi lightning-storm',
'gi_lightning_bolt' : 'gi lightning-bolt',
'gi_lighthouse' : 'gi lighthouse',
'gi_light_bulb' : 'gi light-bulb',
'gi_libra' : 'gi libra',
'gi_lever' : 'gi lever',
'gi_level_two' : 'gi level-two',
'gi_level_two_advanced' : 'gi level-two-advanced',
'gi_level_three' : 'gi level-three',
'gi_level_three_advanced' : 'gi level-three-advanced',
'gi_level_four' : 'gi level-four',
'gi_level_four_advanced' : 'gi level-four-advanced',
'gi_leo' : 'gi leo',
'gi_leaf' : 'gi leaf',
'gi_lava' : 'gi lava',
'gi_laser_site' : 'gi laser-site',
'gi_laser_blast' : 'gi laser-blast',
'gi_large_hammer' : 'gi large-hammer',
'gi_lantern_flame' : 'gi lantern-flame',
'gi_kunai' : 'gi kunai',
'gi_knight_helmet' : 'gi knight-helmet',
'gi_knife' : 'gi knife',
'gi_knife_fork' : 'gi knife-fork',
'gi_kitchen_knives' : 'gi kitchen-knives',
'gi_key' : 'gi key',
'gi_key_basic' : 'gi key-basic',
'gi_kettlebell' : 'gi kettlebell',
'gi_kaleidoscope' : 'gi kaleidoscope',
'gi_jigsaw_piece' : 'gi jigsaw-piece',
'gi_jetpack' : 'gi jetpack',
'gi_interdiction' : 'gi interdiction',
'gi_insect_jaws' : 'gi insect-jaws',
'gi_incense' : 'gi incense',
'gi_implosion' : 'gi implosion',
'gi_ice_cube' : 'gi ice-cube',
'gi_hydra' : 'gi hydra',
'gi_hydra_shot' : 'gi hydra-shot',
'gi_hourglass' : 'gi hourglass',
'gi_hot_surface' : 'gi hot-surface',
'gi_hospital_cross' : 'gi hospital-cross',
'gi_horseshoe' : 'gi horseshoe',
'gi_horns' : 'gi horns',
'gi_horn_call' : 'gi horn-call',
'gi_hood' : 'gi hood',
'gi_honeycomb' : 'gi honeycomb',
'gi_hole_ladder' : 'gi hole-ladder',
'gi_hive_emblem' : 'gi hive-emblem',
'gi_help' : 'gi help',
'gi_helmet' : 'gi helmet',
'gi_heavy_shield' : 'gi heavy-shield',
'gi_heavy_fall' : 'gi heavy-fall',
'gi_heat_haze' : 'gi heat-haze',
'gi_hearts' : 'gi hearts',
'gi_hearts_card' : 'gi hearts-card',
'gi_heartburn' : 'gi heartburn',
'gi_heart_tower' : 'gi heart-tower',
'gi_heart_bottle' : 'gi heart-bottle',
'gi_health' : 'gi health',
'gi_health_increase' : 'gi health-increase',
'gi_health_decrease' : 'gi health-decrease',
'gi_harpoon_trident' : 'gi harpoon-trident',
'gi_hand' : 'gi hand',
'gi_hand_saw' : 'gi hand-saw',
'gi_hand_emblem' : 'gi hand-emblem',
'gi_hammer' : 'gi hammer',
'gi_hammer_drop' : 'gi hammer-drop',
'gi_halberd' : 'gi halberd',
'gi_guillotine' : 'gi guillotine',
'gi_guarded_tower' : 'gi guarded-tower',
'gi_groundbreaker' : 'gi groundbreaker',
'gi_grenade' : 'gi grenade',
'gi_grass' : 'gi grass',
'gi_grass_patch' : 'gi grass-patch',
'gi_grappling_hook' : 'gi grappling-hook',
'gi_gold_bar' : 'gi gold-bar',
'gi_gloop' : 'gi gloop',
'gi_glass_heart' : 'gi glass-heart',
'gi_gemini' : 'gi gemini',
'gi_gem' : 'gi gem',
'gi_gem_pendant' : 'gi gem-pendant',
'gi_gecko' : 'gi gecko',
'gi_gears' : 'gi gears',
'gi_gear_heart' : 'gi gear-heart',
'gi_gear_hammer' : 'gi gear-hammer',
'gi_gavel' : 'gi gavel',
'gi_gamepad_cross' : 'gi gamepad-cross',
'gi_frozen_arrow' : 'gi frozen-arrow',
'gi_frostfire' : 'gi frostfire',
'gi_frost_emblem' : 'gi frost-emblem',
'gi_fox' : 'gi fox',
'gi_forward' : 'gi forward',
'gi_forging' : 'gi forging',
'gi_footprint' : 'gi footprint',
'gi_food_chain' : 'gi food-chain',
'gi_focused_lightning' : 'gi focused-lightning',
'gi_fluffy_swirl' : 'gi fluffy-swirl',
'gi_flowers' : 'gi flowers',
'gi_flower' : 'gi flower',
'gi_flat_hammer' : 'gi flat-hammer',
'gi_flask' : 'gi flask',
'gi_flaming_trident' : 'gi flaming-trident',
'gi_flaming_claw' : 'gi flaming-claw',
'gi_flaming_arrow' : 'gi flaming-arrow',
'gi_flame_symbol' : 'gi flame-symbol',
'gi_fizzing_flask' : 'gi fizzing-flask',
'gi_fish' : 'gi fish',
'gi_fireball_sword' : 'gi fireball-sword',
'gi_fire' : 'gi fire',
'gi_fire_symbol' : 'gi fire-symbol',
'gi_fire_shield' : 'gi fire-shield',
'gi_fire_ring' : 'gi fire-ring',
'gi_fire_breath' : 'gi fire-breath',
'gi_fire_bomb' : 'gi fire-bomb',
'gi_fedora' : 'gi fedora',
'gi_feathered_wing' : 'gi feathered-wing',
'gi_feather_wing' : 'gi feather-wing',
'gi_fast_ship' : 'gi fast-ship',
'gi_falling' : 'gi falling',
'gi_fall_down' : 'gi fall-down',
'gi_fairy' : 'gi fairy',
'gi_fairy_wand' : 'gi fairy-wand',
'gi_eyeball' : 'gi eyeball',
'gi_eye_shield' : 'gi eye-shield',
'gi_eye_monster' : 'gi eye-monster',
'gi_explosive_materials' : 'gi explosive-materials',
'gi_explosion' : 'gi explosion',
'gi_energise' : 'gi energise',
'gi_emerald' : 'gi emerald',
'gi_eggplant' : 'gi eggplant',
'gi_egg' : 'gi egg',
'gi_egg_pod' : 'gi egg-pod',
'gi_duel' : 'gi duel',
'gi_droplets' : 'gi droplets',
'gi_droplet' : 'gi droplet',
'gi_droplet_splash' : 'gi droplet-splash',
'gi_dripping_sword' : 'gi dripping-sword',
'gi_dripping_knife' : 'gi dripping-knife',
'gi_dripping_blade' : 'gi dripping-blade',
'gi_drill' : 'gi drill',
'gi_dragonfly' : 'gi dragonfly',
'gi_dragon' : 'gi dragon',
'gi_dragon_wing' : 'gi dragon-wing',
'gi_dragon_breath' : 'gi dragon-breath',
'gi_doubled' : 'gi doubled',
'gi_double_team' : 'gi double-team',
'gi_diving_dagger' : 'gi diving-dagger',
'gi_divert' : 'gi divert',
'gi_dinosaur' : 'gi dinosaur',
'gi_dice_two' : 'gi dice-two',
'gi_dice_three' : 'gi dice-three',
'gi_dice_six' : 'gi dice-six',
'gi_dice_one' : 'gi dice-one',
'gi_dice_four' : 'gi dice-four',
'gi_dice_five' : 'gi dice-five',
'gi_diamonds' : 'gi diamonds',
'gi_diamonds_card' : 'gi diamonds-card',
'gi_diamond' : 'gi diamond',
'gi_desert_skull' : 'gi desert-skull',
'gi_dervish_swords' : 'gi dervish-swords',
'gi_demolish' : 'gi demolish',
'gi_defibrillate' : 'gi defibrillate',
'gi_decapitation' : 'gi decapitation',
'gi_death_skull' : 'gi death-skull',
'gi_dead_tree' : 'gi dead-tree',
'gi_daisy' : 'gi daisy',
'gi_daggers' : 'gi daggers',
'gi_cycle' : 'gi cycle',
'gi_cut_palm' : 'gi cut-palm',
'gi_cubes' : 'gi cubes',
'gi_crystals' : 'gi crystals',
'gi_crystal_wand' : 'gi crystal-wand',
'gi_crystal_cluster' : 'gi crystal-cluster',
'gi_crystal_ball' : 'gi crystal-ball',
'gi_crush' : 'gi crush',
'gi_crowned_heart' : 'gi crowned-heart',
'gi_crown' : 'gi crown',
'gi_crown_of_thorns' : 'gi crown-of-thorns',
'gi_crossed_swords' : 'gi crossed-swords',
'gi_crossed_sabres' : 'gi crossed-sabres',
'gi_crossed_pistols' : 'gi crossed-pistols',
'gi_crossed_bones' : 'gi crossed-bones',
'gi_crossed_axes' : 'gi crossed-axes',
'gi_crossbow' : 'gi crossbow',
'gi_croc_sword' : 'gi croc-sword',
'gi_cracked_shield' : 'gi cracked-shield',
'gi_cracked_helm' : 'gi cracked-helm',
'gi_crab_claw' : 'gi crab-claw',
'gi_corked_tube' : 'gi corked-tube',
'gi_compass' : 'gi compass',
'gi_cold_heart' : 'gi cold-heart',
'gi_cog' : 'gi cog',
'gi_cog_wheel' : 'gi cog-wheel',
'gi_coffee_mug' : 'gi coffee-mug',
'gi_cluster_bomb' : 'gi cluster-bomb',
'gi_clovers' : 'gi clovers',
'gi_clovers_card' : 'gi clovers-card',
'gi_clover' : 'gi clover',
'gi_clockwork' : 'gi clockwork',
'gi_cloak_and_dagger' : 'gi cloak-and-dagger',
'gi_circular_shield' : 'gi circular-shield',
'gi_circular_saw' : 'gi circular-saw',
'gi_circle_of_circles' : 'gi circle-of-circles',
'gi_chicken_leg' : 'gi chicken-leg',
'gi_chessboard' : 'gi chessboard',
'gi_chemical_arrow' : 'gi chemical-arrow',
'gi_cheese' : 'gi cheese',
'gi_chain' : 'gi chain',
'gi_cat' : 'gi cat',
'gi_castle_flag' : 'gi castle-flag',
'gi_castle_emblem' : 'gi castle-emblem',
'gi_carrot' : 'gi carrot',
'gi_capricorn' : 'gi capricorn',
'gi_capitol' : 'gi capitol',
'gi_cannon_shot' : 'gi cannon-shot',
'gi_candle' : 'gi candle',
'gi_candle_fire' : 'gi candle-fire',
'gi_cancer' : 'gi cancer',
'gi_cancel' : 'gi cancel',
'gi_campfire' : 'gi campfire',
'gi_butterfly' : 'gi butterfly',
'gi_burst_blob' : 'gi burst-blob',
'gi_burning_meteor' : 'gi burning-meteor',
'gi_burning_eye' : 'gi burning-eye',
'gi_burning_embers' : 'gi burning-embers',
'gi_burning_book' : 'gi burning-book',
'gi_bullets' : 'gi bullets',
'gi_bubbling_potion' : 'gi bubbling-potion',
'gi_broken_skull' : 'gi broken-skull',
'gi_broken_shield' : 'gi broken-shield',
'gi_broken_heart' : 'gi broken-heart',
'gi_broken_bottle' : 'gi broken-bottle',
'gi_broken_bone' : 'gi broken-bone',
'gi_broadsword' : 'gi broadsword',
'gi_broadhead_arrow' : 'gi broadhead-arrow',
'gi_bridge' : 'gi bridge',
'gi_brandy_bottle' : 'gi brandy-bottle',
'gi_brain_freeze' : 'gi brain-freeze',
'gi_bowling_pin' : 'gi bowling-pin',
'gi_bowie_knife' : 'gi bowie-knife',
'gi_bottom_right' : 'gi bottom-right',
'gi_bottled_bolt' : 'gi bottled-bolt',
'gi_bottle_vapors' : 'gi bottle-vapors',
'gi_boot_stomp' : 'gi boot-stomp',
'gi_boomerang' : 'gi boomerang',
'gi_book' : 'gi book',
'gi_bone_knife' : 'gi bone-knife',
'gi_bone_bite' : 'gi bone-bite',
'gi_bombs' : 'gi bombs',
'gi_bomb_explosion' : 'gi bomb-explosion',
'gi_bolt_shield' : 'gi bolt-shield',
'gi_bleeding_hearts' : 'gi bleeding-hearts',
'gi_bleeding_eye' : 'gi bleeding-eye',
'gi_blaster' : 'gi blaster',
'gi_blast' : 'gi blast',
'gi_blade_bite' : 'gi blade-bite',
'gi_bird_mask' : 'gi bird-mask',
'gi_bird_claw' : 'gi bird-claw',
'gi_biohazard' : 'gi biohazard',
'gi_bell' : 'gi bell',
'gi_beetle' : 'gi beetle',
'gi_beer' : 'gi beer',
'gi_bear_trap' : 'gi bear-trap',
'gi_beam_wake' : 'gi beam-wake',
'gi_batwings' : 'gi batwings',
'gi_battery_white' : 'gi battery-white',
'gi_battery_positive' : 'gi battery-positive',
'gi_battery_negative' : 'gi battery-negative',
'gi_battery_black' : 'gi battery-black',
'gi_battery_75' : 'gi battery-75',
'gi_battery_50' : 'gi battery-50',
'gi_battery_25' : 'gi battery-25',
'gi_battery_100' : 'gi battery-100',
'gi_battery_0' : 'gi battery-0',
'gi_batteries' : 'gi batteries',
'gi_battered_axe' : 'gi battered-axe',
'gi_bat_sword' : 'gi bat-sword',
'gi_barrier' : 'gi barrier',
'gi_barbed_arrow' : 'gi barbed-arrow',
'gi_ball' : 'gi ball',
'gi_axe' : 'gi axe',
'gi_axe_swing' : 'gi axe-swing',
'gi_aware' : 'gi aware',
'gi_aura' : 'gi aura',
'gi_arson' : 'gi arson',
'gi_arrow_flights' : 'gi arrow-flights',
'gi_arrow_cluster' : 'gi arrow-cluster',
'gi_aries' : 'gi aries',
'gi_arena' : 'gi arena',
'gi_archery_target' : 'gi archery-target',
'gi_archer' : 'gi archer',
'gi_arcane_mask' : 'gi arcane-mask',
'gi_aquarius' : 'gi aquarius',
'gi_apple' : 'gi apple',
'gi_anvil' : 'gi anvil',
'gi_ankh' : 'gi ankh',
'gi_angel_wings' : 'gi angel-wings',
'gi_anchor' : 'gi anchor',
'gi_ammo_bag' : 'gi ammo-bag',
'gi_alligator_clip' : 'gi alligator-clip',
'gi_all_for_one' : 'gi all-for-one',
'gi_alien_fire' : 'gi alien-fire',
'gi_acorn' : 'gi acorn',
'gi_acid' : 'gi acid'
};
module.exports = gameIcons;

View File

@@ -0,0 +1,520 @@
@font-face {
font-family : 'Game-Icons';
font-style : normal;
font-weight : normal;
src : url('../../../fonts/iconFonts/gameIcons.woff') format('woff');
font-display : block;
}
.gi {
/* use !important to prevent issues with browser extensions that change fonts */
display : inline-block;
margin-right : 3px;
font-family : 'Game-Icons' !important;
line-height : 1;
vertical-align : baseline;
-moz-osx-font-smoothing : grayscale;
-webkit-font-smoothing : antialiased;
text-rendering : auto;
/* Better Font Rendering =========== */
-webkit-font-smoothing : antialiased;
-moz-osx-font-smoothing : grayscale;
&.zigzag-leaf::before { content : '\e900'; }
&.zebra-shield::before { content : '\e901'; }
&.x-mark::before { content : '\e902'; }
&.wyvern::before { content : '\e903'; }
&.wrench::before { content : '\e904'; }
&.wooden-sign::before { content : '\e905'; }
&.wolf-howl::before { content : '\e906'; }
&.wolf-head::before { content : '\e907'; }
&.wireless-signal::before { content : '\e908'; }
&.wifi::before { content : '\e909'; }
&.water-drop::before { content : '\e90a'; }
&.virgo::before { content : '\e90b'; }
&.vine-whip::before { content : '\e90c'; }
&.vial::before { content : '\e90d'; }
&.vest::before { content : '\e90e'; }
&.venomous-snake::before { content : '\e90f'; }
&.vase::before { content : '\e910'; }
&.unplugged::before { content : '\e911'; }
&.underhand::before { content : '\e912'; }
&.uncertainty::before { content : '\e913'; }
&.two-hearts::before { content : '\e914'; }
&.two-dragons::before { content : '\e915'; }
&.turd::before { content : '\e916'; }
&.trophy::before { content : '\e917'; }
&.triforce::before { content : '\e918'; }
&.trident::before { content : '\e919'; }
&.trefoil-lily::before { content : '\e91a'; }
&.trail::before { content : '\e91b'; }
&.tower::before { content : '\e91c'; }
&.torch::before { content : '\e91d'; }
&.tooth::before { content : '\e91e'; }
&.tombstone::before { content : '\e91f'; }
&.toast::before { content : '\e920'; }
&.tic-tac-toe::before { content : '\e921'; }
&.three-keys::before { content : '\e922'; }
&.thorny-vine::before { content : '\e923'; }
&.thorn-arrow::before { content : '\e924'; }
&.tesla::before { content : '\e925'; }
&.tentacle::before { content : '\e926'; }
&.telescope::before { content : '\e927'; }
&.taurus::before { content : '\e928'; }
&.targeted::before { content : '\e929'; }
&.target-laser::before { content : '\e92a'; }
&.target-arrows::before { content : '\e92b'; }
&.syringe::before { content : '\e92c'; }
&.surveillance-camera::before { content : '\e92d'; }
&.supersonic-arrow::before { content : '\e92e'; }
&.super-mushroom::before { content : '\e92f'; }
&.sunbeams::before { content : '\e930'; }
&.sun::before { content : '\e931'; }
&.sun-symbol::before { content : '\e932'; }
&.suits::before { content : '\e933'; }
&.suckered-tentacle::before { content : '\e934'; }
&.stopwatch::before { content : '\e935'; }
&.sprout::before { content : '\e936'; }
&.sprout-emblem::before { content : '\e937'; }
&.spray-can::before { content : '\e938'; }
&.splash::before { content : '\e939'; }
&.spiral-shell::before { content : '\e93a'; }
&.spinning-sword::before { content : '\e93b'; }
&.spiked-tentacle::before { content : '\e93c'; }
&.spiked-mace::before { content : '\e93d'; }
&.spikeball::before { content : '\e93e'; }
&.spider-face::before { content : '\e93f'; }
&.speech-bubbles::before { content : '\e940'; }
&.speech-bubble::before { content : '\e941'; }
&.spear-head::before { content : '\e942'; }
&.spawn-node::before { content : '\e943'; }
&.spades::before { content : '\e944'; }
&.spades-card::before { content : '\e945'; }
&.soccer-ball::before { content : '\e946'; }
&.snowflake::before { content : '\e947'; }
&.snorkel::before { content : '\e948'; }
&.snake::before { content : '\e949'; }
&.snail::before { content : '\e94a'; }
&.small-fire::before { content : '\e94b'; }
&.slash-ring::before { content : '\e94c'; }
&.skull::before { content : '\e94d'; }
&.skull-trophy::before { content : '\e94e'; }
&.site::before { content : '\e94f'; }
&.sideswipe::before { content : '\e950'; }
&.sickle::before { content : '\e951'; }
&.shuriken::before { content : '\e952'; }
&.shovel::before { content : '\e953'; }
&.shotgun-shell::before { content : '\e954'; }
&.shot-through-the-heart::before { content : '\e955'; }
&.shoe-prints::before { content : '\e956'; }
&.ship-emblem::before { content : '\e957'; }
&.shield::before { content : '\e958'; }
&.sheriff::before { content : '\e959'; }
&.sheep::before { content : '\e95a'; }
&.shark::before { content : '\e95b'; }
&.seagull::before { content : '\e95c'; }
&.sea-serpent::before { content : '\e95d'; }
&.scythe::before { content : '\e95e'; }
&.scroll-unfurled::before { content : '\e95f'; }
&.scorpio::before { content : '\e960'; }
&.save::before { content : '\e961'; }
&.satellite::before { content : '\e962'; }
&.sapphire::before { content : '\e963'; }
&.sagittarius::before { content : '\e964'; }
&.rune-stone::before { content : '\e965'; }
&.rss::before { content : '\e966'; }
&.round-shield::before { content : '\e967'; }
&.round-bottom-flask::before { content : '\e968'; }
&.robot-arm::before { content : '\e969'; }
&.roast-chicken::before { content : '\e96a'; }
&.ringing-bell::before { content : '\e96b'; }
&.rifle::before { content : '\e96c'; }
&.revolver::before { content : '\e96d'; }
&.reverse::before { content : '\e96e'; }
&.repair::before { content : '\e96f'; }
&.relic-blade::before { content : '\e970'; }
&.regeneration::before { content : '\e971'; }
&.recycle::before { content : '\e972'; }
&.reactor::before { content : '\e973'; }
&.raven::before { content : '\e974'; }
&.radioactive::before { content : '\e975'; }
&.radial-balance::before { content : '\e976'; }
&.radar-dish::before { content : '\e977'; }
&.rabbit::before { content : '\e978'; }
&.quill-ink::before { content : '\e979'; }
&.queen-crown::before { content : '\e97a'; }
&.pyramids::before { content : '\e97b'; }
&.potion::before { content : '\e97c'; }
&.poison-cloud::before { content : '\e97d'; }
&.podium::before { content : '\e97e'; }
&.player::before { content : '\e97f'; }
&.player-thunder-struck::before { content : '\e980'; }
&.player-teleport::before { content : '\e981'; }
&.player-shot::before { content : '\e982'; }
&.player-pyromaniac::before { content : '\e983'; }
&.player-pain::before { content : '\e984'; }
&.player-lift::before { content : '\e985'; }
&.player-king::before { content : '\e986'; }
&.player-dodge::before { content : '\e987'; }
&.player-despair::before { content : '\e988'; }
&.plain-dagger::before { content : '\e989'; }
&.pisces::before { content : '\e98a'; }
&.ping-pong::before { content : '\e98b'; }
&.pine-tree::before { content : '\e98c'; }
&.pills::before { content : '\e98d'; }
&.pill::before { content : '\e98e'; }
&.perspective-dice-three::before { content : '\e98f'; }
&.perspective-dice-six::before { content : '\e990'; }
&.perspective-dice-six-two::before { content : '\e991'; }
&.perspective-dice-random::before { content : '\e992'; }
&.perspective-dice-one::before { content : '\e993'; }
&.perspective-dice-four::before { content : '\e994'; }
&.perspective-dice-five::before { content : '\e995'; }
&.pawprint::before { content : '\e996'; }
&.pawn::before { content : '\e997'; }
&.palm-tree::before { content : '\e998'; }
&.overmind::before { content : '\e999'; }
&.overhead::before { content : '\e99a'; }
&.ophiuchus::before { content : '\e99b'; }
&.on-target::before { content : '\e99c'; }
&.omega::before { content : '\e99d'; }
&.octopus::before { content : '\e99e'; }
&.ocean-emblem::before { content : '\e99f'; }
&.ocarina::before { content : '\e9a0'; }
&.nuclear::before { content : '\e9a1'; }
&.noose::before { content : '\e9a2'; }
&.nodular::before { content : '\e9a3'; }
&.nails::before { content : '\e9a4'; }
&.musket::before { content : '\e9a5'; }
&.muscle-up::before { content : '\e9a6'; }
&.muscle-fat::before { content : '\e9a7'; }
&.mp5::before { content : '\e9a8'; }
&.moon-sun::before { content : '\e9a9'; }
&.montains::before { content : '\e9aa'; }
&.monster-skull::before { content : '\e9ab'; }
&.mirror::before { content : '\e9ac'; }
&.mining-diamonds::before { content : '\e9ad'; }
&.mine-wagon::before { content : '\e9ae'; }
&.microphone::before { content : '\e9af'; }
&.metal-gate::before { content : '\e9b0'; }
&.medical-pack::before { content : '\e9b1'; }
&.meat::before { content : '\e9b2'; }
&.meat-hook::before { content : '\e9b3'; }
&.match::before { content : '\e9b4'; }
&.mass-driver::before { content : '\e9b5'; }
&.magnet::before { content : '\e9b6'; }
&.maggot::before { content : '\e9b7'; }
&.love-howl::before { content : '\e9b8'; }
&.locked-fortress::before { content : '\e9b9'; }
&.load::before { content : '\e9ba'; }
&.lit-candelabra::before { content : '\e9bb'; }
&.lion::before { content : '\e9bc'; }
&.lightning::before { content : '\e9bd'; }
&.lightning-trio::before { content : '\e9be'; }
&.lightning-sword::before { content : '\e9bf'; }
&.lightning-storm::before { content : '\e9c0'; }
&.lightning-bolt::before { content : '\e9c1'; }
&.lighthouse::before { content : '\e9c2'; }
&.light-bulb::before { content : '\e9c3'; }
&.libra::before { content : '\e9c4'; }
&.lever::before { content : '\e9c5'; }
&.level-two::before { content : '\e9c6'; }
&.level-two-advanced::before { content : '\e9c7'; }
&.level-three::before { content : '\e9c8'; }
&.level-three-advanced::before { content : '\e9c9'; }
&.level-four::before { content : '\e9ca'; }
&.level-four-advanced::before { content : '\e9cb'; }
&.leo::before { content : '\e9cc'; }
&.leaf::before { content : '\e9cd'; }
&.lava::before { content : '\e9ce'; }
&.laser-site::before { content : '\e9cf'; }
&.laser-blast::before { content : '\e9d0'; }
&.large-hammer::before { content : '\e9d1'; }
&.lantern-flame::before { content : '\e9d2'; }
&.kunai::before { content : '\e9d3'; }
&.knight-helmet::before { content : '\e9d4'; }
&.knife::before { content : '\e9d5'; }
&.knife-fork::before { content : '\e9d6'; }
&.kitchen-knives::before { content : '\e9d7'; }
&.key::before { content : '\e9d8'; }
&.key-basic::before { content : '\e9d9'; }
&.kettlebell::before { content : '\e9da'; }
&.kaleidoscope::before { content : '\e9db'; }
&.jigsaw-piece::before { content : '\e9dc'; }
&.jetpack::before { content : '\e9dd'; }
&.interdiction::before { content : '\e9de'; }
&.insect-jaws::before { content : '\e9df'; }
&.incense::before { content : '\e9e0'; }
&.implosion::before { content : '\e9e1'; }
&.ice-cube::before { content : '\e9e2'; }
&.hydra::before { content : '\e9e3'; }
&.hydra-shot::before { content : '\e9e4'; }
&.hourglass::before { content : '\e9e5'; }
&.hot-surface::before { content : '\e9e6'; }
&.hospital-cross::before { content : '\e9e7'; }
&.horseshoe::before { content : '\e9e8'; }
&.horns::before { content : '\e9e9'; }
&.horn-call::before { content : '\e9ea'; }
&.hood::before { content : '\e9eb'; }
&.honeycomb::before { content : '\e9ec'; }
&.hole-ladder::before { content : '\e9ed'; }
&.hive-emblem::before { content : '\e9ee'; }
&.help::before { content : '\e9ef'; }
&.helmet::before { content : '\e9f0'; }
&.heavy-shield::before { content : '\e9f1'; }
&.heavy-fall::before { content : '\e9f2'; }
&.heat-haze::before { content : '\e9f3'; }
&.hearts::before { content : '\e9f4'; }
&.hearts-card::before { content : '\e9f5'; }
&.heartburn::before { content : '\e9f6'; }
&.heart-tower::before { content : '\e9f7'; }
&.heart-bottle::before { content : '\e9f8'; }
&.health::before { content : '\e9f9'; }
&.health-increase::before { content : '\e9fa'; }
&.health-decrease::before { content : '\e9fb'; }
&.harpoon-trident::before { content : '\e9fc'; }
&.hand::before { content : '\e9fd'; }
&.hand-saw::before { content : '\e9fe'; }
&.hand-emblem::before { content : '\e9ff'; }
&.hammer::before { content : '\ea00'; }
&.hammer-drop::before { content : '\ea01'; }
&.halberd::before { content : '\ea02'; }
&.guillotine::before { content : '\ea03'; }
&.guarded-tower::before { content : '\ea04'; }
&.groundbreaker::before { content : '\ea05'; }
&.grenade::before { content : '\ea06'; }
&.grass::before { content : '\ea07'; }
&.grass-patch::before { content : '\ea08'; }
&.grappling-hook::before { content : '\ea09'; }
&.gold-bar::before { content : '\ea0a'; }
&.gloop::before { content : '\ea0b'; }
&.glass-heart::before { content : '\ea0c'; }
&.gemini::before { content : '\ea0d'; }
&.gem::before { content : '\ea0e'; }
&.gem-pendant::before { content : '\ea0f'; }
&.gecko::before { content : '\ea10'; }
&.gears::before { content : '\ea11'; }
&.gear-heart::before { content : '\ea12'; }
&.gear-hammer::before { content : '\ea13'; }
&.gavel::before { content : '\ea14'; }
&.gamepad-cross::before { content : '\ea15'; }
&.frozen-arrow::before { content : '\ea16'; }
&.frostfire::before { content : '\ea17'; }
&.frost-emblem::before { content : '\ea18'; }
&.fox::before { content : '\ea19'; }
&.forward::before { content : '\ea1a'; }
&.forging::before { content : '\ea1b'; }
&.footprint::before { content : '\ea1c'; }
&.food-chain::before { content : '\ea1d'; }
&.focused-lightning::before { content : '\ea1e'; }
&.fluffy-swirl::before { content : '\ea1f'; }
&.flowers::before { content : '\ea20'; }
&.flower::before { content : '\ea21'; }
&.flat-hammer::before { content : '\ea22'; }
&.flask::before { content : '\ea23'; }
&.flaming-trident::before { content : '\ea24'; }
&.flaming-claw::before { content : '\ea25'; }
&.flaming-arrow::before { content : '\ea26'; }
&.flame-symbol::before { content : '\ea27'; }
&.fizzing-flask::before { content : '\ea28'; }
&.fish::before { content : '\ea29'; }
&.fireball-sword::before { content : '\ea2a'; }
&.fire::before { content : '\ea2b'; }
&.fire-symbol::before { content : '\ea2c'; }
&.fire-shield::before { content : '\ea2d'; }
&.fire-ring::before { content : '\ea2e'; }
&.fire-breath::before { content : '\ea2f'; }
&.fire-bomb::before { content : '\ea30'; }
&.fedora::before { content : '\ea31'; }
&.feathered-wing::before { content : '\ea32'; }
&.feather-wing::before { content : '\ea33'; }
&.fast-ship::before { content : '\ea34'; }
&.falling::before { content : '\ea35'; }
&.fall-down::before { content : '\ea36'; }
&.fairy::before { content : '\ea37'; }
&.fairy-wand::before { content : '\ea38'; }
&.eyeball::before { content : '\ea39'; }
&.eye-shield::before { content : '\ea3a'; }
&.eye-monster::before { content : '\ea3b'; }
&.explosive-materials::before { content : '\ea3c'; }
&.explosion::before { content : '\ea3d'; }
&.energise::before { content : '\ea3e'; }
&.emerald::before { content : '\ea3f'; }
&.eggplant::before { content : '\ea40'; }
&.egg::before { content : '\ea41'; }
&.egg-pod::before { content : '\ea42'; }
&.duel::before { content : '\ea43'; }
&.droplets::before { content : '\ea44'; }
&.droplet::before { content : '\ea45'; }
&.droplet-splash::before { content : '\ea46'; }
&.dripping-sword::before { content : '\ea47'; }
&.dripping-knife::before { content : '\ea48'; }
&.dripping-blade::before { content : '\ea49'; }
&.drill::before { content : '\ea4a'; }
&.dragonfly::before { content : '\ea4b'; }
&.dragon::before { content : '\ea4c'; }
&.dragon-wing::before { content : '\ea4d'; }
&.dragon-breath::before { content : '\ea4e'; }
&.doubled::before { content : '\ea4f'; }
&.double-team::before { content : '\ea50'; }
&.diving-dagger::before { content : '\ea51'; }
&.divert::before { content : '\ea52'; }
&.dinosaur::before { content : '\ea53'; }
&.dice-two::before { content : '\ea54'; }
&.dice-three::before { content : '\ea55'; }
&.dice-six::before { content : '\ea56'; }
&.dice-one::before { content : '\ea57'; }
&.dice-four::before { content : '\ea58'; }
&.dice-five::before { content : '\ea59'; }
&.diamonds::before { content : '\ea5a'; }
&.diamonds-card::before { content : '\ea5b'; }
&.diamond::before { content : '\ea5c'; }
&.desert-skull::before { content : '\ea5d'; }
&.dervish-swords::before { content : '\ea5e'; }
&.demolish::before { content : '\ea5f'; }
&.defibrillate::before { content : '\ea60'; }
&.decapitation::before { content : '\ea61'; }
&.death-skull::before { content : '\ea62'; }
&.dead-tree::before { content : '\ea63'; }
&.daisy::before { content : '\ea64'; }
&.daggers::before { content : '\ea65'; }
&.cycle::before { content : '\ea66'; }
&.cut-palm::before { content : '\ea67'; }
&.cubes::before { content : '\ea68'; }
&.crystals::before { content : '\ea69'; }
&.crystal-wand::before { content : '\ea6a'; }
&.crystal-cluster::before { content : '\ea6b'; }
&.crystal-ball::before { content : '\ea6c'; }
&.crush::before { content : '\ea6d'; }
&.crowned-heart::before { content : '\ea6e'; }
&.crown::before { content : '\ea6f'; }
&.crown-of-thorns::before { content : '\ea70'; }
&.crossed-swords::before { content : '\ea71'; }
&.crossed-sabres::before { content : '\ea72'; }
&.crossed-pistols::before { content : '\ea73'; }
&.crossed-bones::before { content : '\ea74'; }
&.crossed-axes::before { content : '\ea75'; }
&.crossbow::before { content : '\ea76'; }
&.croc-sword::before { content : '\ea77'; }
&.cracked-shield::before { content : '\ea78'; }
&.cracked-helm::before { content : '\ea79'; }
&.crab-claw::before { content : '\ea7a'; }
&.corked-tube::before { content : '\ea7b'; }
&.compass::before { content : '\ea7c'; }
&.cold-heart::before { content : '\ea7d'; }
&.cog::before { content : '\ea7e'; }
&.cog-wheel::before { content : '\ea7f'; }
&.coffee-mug::before { content : '\ea80'; }
&.cluster-bomb::before { content : '\ea81'; }
&.clovers::before { content : '\ea82'; }
&.clovers-card::before { content : '\ea83'; }
&.clover::before { content : '\ea84'; }
&.clockwork::before { content : '\ea85'; }
&.cloak-and-dagger::before { content : '\ea86'; }
&.circular-shield::before { content : '\ea87'; }
&.circular-saw::before { content : '\ea88'; }
&.circle-of-circles::before { content : '\ea89'; }
&.chicken-leg::before { content : '\ea8a'; }
&.chessboard::before { content : '\ea8b'; }
&.chemical-arrow::before { content : '\ea8c'; }
&.cheese::before { content : '\ea8d'; }
&.chain::before { content : '\ea8e'; }
&.cat::before { content : '\ea8f'; }
&.castle-flag::before { content : '\ea90'; }
&.castle-emblem::before { content : '\ea91'; }
&.carrot::before { content : '\ea92'; }
&.capricorn::before { content : '\ea93'; }
&.capitol::before { content : '\ea94'; }
&.cannon-shot::before { content : '\ea95'; }
&.candle::before { content : '\ea96'; }
&.candle-fire::before { content : '\ea97'; }
&.cancer::before { content : '\ea98'; }
&.cancel::before { content : '\ea99'; }
&.campfire::before { content : '\ea9a'; }
&.butterfly::before { content : '\ea9b'; }
&.burst-blob::before { content : '\ea9c'; }
&.burning-meteor::before { content : '\ea9d'; }
&.burning-eye::before { content : '\ea9e'; }
&.burning-embers::before { content : '\ea9f'; }
&.burning-book::before { content : '\eaa0'; }
&.bullets::before { content : '\eaa1'; }
&.bubbling-potion::before { content : '\eaa2'; }
&.broken-skull::before { content : '\eaa3'; }
&.broken-shield::before { content : '\eaa4'; }
&.broken-heart::before { content : '\eaa5'; }
&.broken-bottle::before { content : '\eaa6'; }
&.broken-bone::before { content : '\eaa7'; }
&.broadsword::before { content : '\eaa8'; }
&.broadhead-arrow::before { content : '\eaa9'; }
&.bridge::before { content : '\eaaa'; }
&.brandy-bottle::before { content : '\eaab'; }
&.brain-freeze::before { content : '\eaac'; }
&.bowling-pin::before { content : '\eaad'; }
&.bowie-knife::before { content : '\eaae'; }
&.bottom-right::before { content : '\eaaf'; }
&.bottled-bolt::before { content : '\eab0'; }
&.bottle-vapors::before { content : '\eab1'; }
&.boot-stomp::before { content : '\eab2'; }
&.boomerang::before { content : '\eab3'; }
&.book::before { content : '\eab4'; }
&.bone-knife::before { content : '\eab5'; }
&.bone-bite::before { content : '\eab6'; }
&.bombs::before { content : '\eab7'; }
&.bomb-explosion::before { content : '\eab8'; }
&.bolt-shield::before { content : '\eab9'; }
&.bleeding-hearts::before { content : '\eaba'; }
&.bleeding-eye::before { content : '\eabb'; }
&.blaster::before { content : '\eabc'; }
&.blast::before { content : '\eabd'; }
&.blade-bite::before { content : '\eabe'; }
&.bird-mask::before { content : '\eabf'; }
&.bird-claw::before { content : '\eac0'; }
&.biohazard::before { content : '\eac1'; }
&.bell::before { content : '\eac2'; }
&.beetle::before { content : '\eac3'; }
&.beer::before { content : '\eac4'; }
&.bear-trap::before { content : '\eac5'; }
&.beam-wake::before { content : '\eac6'; }
&.batwings::before { content : '\eac7'; }
&.battery-white::before { content : '\eac8'; }
&.battery-positive::before { content : '\eac9'; }
&.battery-negative::before { content : '\eaca'; }
&.battery-black::before { content : '\eacb'; }
&.battery-75::before { content : '\eacc'; }
&.battery-50::before { content : '\eacd'; }
&.battery-25::before { content : '\eace'; }
&.battery-100::before { content : '\eacf'; }
&.battery-0::before { content : '\ead0'; }
&.batteries::before { content : '\ead1'; }
&.battered-axe::before { content : '\ead2'; }
&.bat-sword::before { content : '\ead3'; }
&.barrier::before { content : '\ead4'; }
&.barbed-arrow::before { content : '\ead5'; }
&.ball::before { content : '\ead6'; }
&.axe::before { content : '\ead7'; }
&.axe-swing::before { content : '\ead8'; }
&.aware::before { content : '\ead9'; }
&.aura::before { content : '\eada'; }
&.arson::before { content : '\eadb'; }
&.arrow-flights::before { content : '\eadc'; }
&.arrow-cluster::before { content : '\eadd'; }
&.aries::before { content : '\eade'; }
&.arena::before { content : '\eadf'; }
&.archery-target::before { content : '\eae0'; }
&.archer::before { content : '\eae1'; }
&.arcane-mask::before { content : '\eae2'; }
&.aquarius::before { content : '\eae3'; }
&.apple::before { content : '\eae4'; }
&.anvil::before { content : '\eae5'; }
&.ankh::before { content : '\eae6'; }
&.angel-wings::before { content : '\eae7'; }
&.anchor::before { content : '\eae8'; }
&.ammo-bag::before { content : '\eae9'; }
&.alligator-clip::before { content : '\eaea'; }
&.all-for-one::before { content : '\eaeb'; }
&.alien-fire::before { content : '\eaec'; }
&.acorn::before { content : '\eaed'; }
&.acid::before { content : '\eaee'; }
}

Binary file not shown.