mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-01-03 12:42:41 +00:00
Update Production (#1249)
* Legacy renderer (#1184) * Include two versions of Marked.js * Include two versions of Marked.js * Working two different render pipelines Adds stylesheet "styleLegacy.less" Adds markdownHandler "markdownLegacy.js" The BrewRenderer will switch between these and the new pipeline dependent on the "version" prop passed in. * Mustache-style div blocks * Legacy snippets & columnbreak * Codemirror styling for Div Blocks * Lint * Codemirror highlights for inline Divs as well These will turn red `{{class Content}}` Multi-line divs will turn purple ``` {{class,class2 content }} ``` No real need for these to be different colors. Just for testing. * More lint * Update dependencies. * Adding Button to switch render pipelines * Update Marked.js * Popup alert to refresh page when renderer changed * Don't compress files in Development (very slow) * Block DIV or inline Span depending on {{ placement * \column emits a Div instead of Span * Allow share page to use new renderer * {{ divs no longer need empty lines. Spans work in lists. * Typo * Typo * Enforce \page must be at start of line. Code cleanup. * Inject newlines after/before {{/}} to avoid needing blank lines * Fixes issues with tables. * Remove console.log * Fix spacing issue for Spans * Move things from Brewrenderer to Markdown Try to keep all custom text fiddling in one spot. * Rename variables * Update Font-Awesome to v5.15. Fix style issues on popups. * Update {{ Divs/Spans, Fix nested hilighting * Fixed Spans/divs with no tags or just commas * Use blacklist for {{ to allow more characters * Update package-lock.json * Update all icons to Font-awesome 5 * V3 hidden behind config variable Add "globalThis.enable_v3 = true" in the console to enable. * lint * Legacy renderer (#1229) * Include two versions of Marked.js * Include two versions of Marked.js * Working two different render pipelines Adds stylesheet "styleLegacy.less" Adds markdownHandler "markdownLegacy.js" The BrewRenderer will switch between these and the new pipeline dependent on the "version" prop passed in. * Mustache-style div blocks * Legacy snippets & columnbreak * Codemirror styling for Div Blocks * Lint * Codemirror highlights for inline Divs as well These will turn red `{{class Content}}` Multi-line divs will turn purple ``` {{class,class2 content }} ``` No real need for these to be different colors. Just for testing. * More lint * Update dependencies. * Adding Button to switch render pipelines * Update Marked.js * Popup alert to refresh page when renderer changed * Don't compress files in Development (very slow) * Block DIV or inline Span depending on {{ placement * \column emits a Div instead of Span * Allow share page to use new renderer * {{ divs no longer need empty lines. Spans work in lists. * Typo * Typo * Enforce \page must be at start of line. Code cleanup. * Inject newlines after/before {{/}} to avoid needing blank lines * Fixes issues with tables. * Remove console.log * Fix spacing issue for Spans * Move things from Brewrenderer to Markdown Try to keep all custom text fiddling in one spot. * Rename variables * Update Font-Awesome to v5.15. Fix style issues on popups. * Update {{ Divs/Spans, Fix nested hilighting * Fixed Spans/divs with no tags or just commas * Use blacklist for {{ to allow more characters * Update package-lock.json * Update all icons to Font-awesome 5 * V3 hidden behind config variable Add "globalThis.enable_v3 = true" in the console to enable. * lint * Give user styles higher priority to still allow overrides * Apply style priority to *all* user styles * Change .legacy .v3 to .phb, .phb3 * Revert accidental color change * Fix brew styles overwriting each other. (#1230) * Fix /page not working in legacy mode. (#1233) * Fix brew styles overwriting each other. * Word wrapping, start fixing spacing on Title letter * Fix \page in legacy brews when not at line start * Default 'legacy' if not set. Auto-change styles. * Fix brew styles overwriting each other. * Word wrapping, start fixing spacing on Title letter * Fix \page in legacy brews when not at line start * Fix Page Padding * Set 'legacy' as default value if not set in brew saved file. * Apply Legacy\v3 renderer to print page (#1235)
This commit is contained in:
@@ -18,7 +18,7 @@ const Admin = createClass({
|
|||||||
|
|
||||||
<header>
|
<header>
|
||||||
<div className='container'>
|
<div className='container'>
|
||||||
<i className='fa fa-rocket' />
|
<i className='fas fa-rocket' />
|
||||||
homebrewery admin
|
homebrewery admin
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ const BrewCleanup = createClass({
|
|||||||
return <div className='removeBox'>
|
return <div className='removeBox'>
|
||||||
<button onClick={this.cleanup} className='remove'>
|
<button onClick={this.cleanup} className='remove'>
|
||||||
{this.state.pending
|
{this.state.pending
|
||||||
? <i className='fa fa-spin fa-spinner' />
|
? <i className='fas fa-spin fa-spinner' />
|
||||||
: <span><i className='fa fa-times' /> Remove</span>
|
: <span><i className='fas fa-times' /> Remove</span>
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
<span>Found {this.state.count} Brews that could be removed. </span>
|
<span>Found {this.state.count} Brews that could be removed. </span>
|
||||||
@@ -59,7 +59,7 @@ const BrewCleanup = createClass({
|
|||||||
|
|
||||||
<button onClick={this.prime} className='query'>
|
<button onClick={this.prime} className='query'>
|
||||||
{this.state.pending
|
{this.state.pending
|
||||||
? <i className='fa fa-spin fa-spinner' />
|
? <i className='fas fa-spin fa-spinner' />
|
||||||
: 'Query Brews'
|
: 'Query Brews'
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ const BrewCompress = createClass({
|
|||||||
return <div className='removeBox'>
|
return <div className='removeBox'>
|
||||||
<button onClick={this.cleanup} className='remove'>
|
<button onClick={this.cleanup} className='remove'>
|
||||||
{this.state.pending
|
{this.state.pending
|
||||||
? <i className='fa fa-spin fa-spinner' />
|
? <i className='fas fa-spin fa-spinner' />
|
||||||
: <span><i className='fa fa-compress' /> compress </span>
|
: <span><i className='fas fa-compress' /> compress </span>
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
{this.state.pending
|
{this.state.pending
|
||||||
@@ -76,7 +76,7 @@ const BrewCompress = createClass({
|
|||||||
|
|
||||||
<button onClick={this.prime} className='query'>
|
<button onClick={this.prime} className='query'>
|
||||||
{this.state.pending
|
{this.state.pending
|
||||||
? <i className='fa fa-spin fa-spinner' />
|
? <i className='fas fa-spin fa-spinner' />
|
||||||
: 'Query Brews'
|
: 'Query Brews'
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ const BrewLookup = createClass({
|
|||||||
<h2>Brew Lookup</h2>
|
<h2>Brew Lookup</h2>
|
||||||
<input type='text' value={this.state.query} onChange={this.handleChange} placeholder='edit or share id' />
|
<input type='text' value={this.state.query} onChange={this.handleChange} placeholder='edit or share id' />
|
||||||
<button onClick={this.lookup}>
|
<button onClick={this.lookup}>
|
||||||
<i className={cx('fa', {
|
<i className={cx('fas', {
|
||||||
'fa-search' : !this.state.searching,
|
'fa-search' : !this.state.searching,
|
||||||
'fa-spin fa-spinner' : this.state.searching,
|
'fa-spin fa-spinner' : this.state.searching,
|
||||||
})} />
|
})} />
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ const Stats = createClass({
|
|||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
{this.state.fetching
|
{this.state.fetching
|
||||||
&& <div className='pending'><i className='fa fa-spin fa-spinner' /></div>
|
&& <div className='pending'><i className='fas fa-spin fa-spinner' /></div>
|
||||||
}
|
}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const createClass = require('create-react-class');
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const cx = require('classnames');
|
const cx = require('classnames');
|
||||||
|
|
||||||
|
const MarkdownLegacy = require('naturalcrit/markdownLegacy.js');
|
||||||
const Markdown = require('naturalcrit/markdown.js');
|
const Markdown = require('naturalcrit/markdown.js');
|
||||||
const ErrorBar = require('./errorBar/errorBar.jsx');
|
const ErrorBar = require('./errorBar/errorBar.jsx');
|
||||||
|
|
||||||
@@ -18,12 +19,18 @@ const PPR_THRESHOLD = 50;
|
|||||||
const BrewRenderer = createClass({
|
const BrewRenderer = createClass({
|
||||||
getDefaultProps : function() {
|
getDefaultProps : function() {
|
||||||
return {
|
return {
|
||||||
text : '',
|
text : '',
|
||||||
errors : []
|
renderer : 'legacy',
|
||||||
|
errors : []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getInitialState : function() {
|
getInitialState : function() {
|
||||||
const pages = this.props.text.split('\\page');
|
let pages;
|
||||||
|
if(this.props.renderer == 'legacy') {
|
||||||
|
pages = this.props.text.split('\\page');
|
||||||
|
} else {
|
||||||
|
pages = this.props.text.split(/^\\page/gm);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
viewablePageNumber : 0,
|
viewablePageNumber : 0,
|
||||||
@@ -34,7 +41,7 @@ const BrewRenderer = createClass({
|
|||||||
usePPR : pages.length >= PPR_THRESHOLD,
|
usePPR : pages.length >= PPR_THRESHOLD,
|
||||||
visibility : 'hidden',
|
visibility : 'hidden',
|
||||||
initialContent : `<!DOCTYPE html><html><head>
|
initialContent : `<!DOCTYPE html><html><head>
|
||||||
<link href="//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
|
<link href="//use.fontawesome.com/releases/v5.15.1/css/all.css" rel="stylesheet" />
|
||||||
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
|
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
|
||||||
<link href='/homebrew/bundle.css' rel='stylesheet' />
|
<link href='/homebrew/bundle.css' rel='stylesheet' />
|
||||||
<base target=_blank>
|
<base target=_blank>
|
||||||
@@ -48,12 +55,19 @@ const BrewRenderer = createClass({
|
|||||||
window.removeEventListener('resize', this.updateSize);
|
window.removeEventListener('resize', this.updateSize);
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillReceiveProps : function(nextProps) {
|
componentDidUpdate : function(prevProps) {
|
||||||
const pages = nextProps.text.split('\\page');
|
if(prevProps.text !== this.props.text) {
|
||||||
this.setState({
|
let pages;
|
||||||
pages : pages,
|
if(this.props.renderer == 'legacy') {
|
||||||
usePPR : pages.length >= PPR_THRESHOLD
|
pages = this.props.text.split('\\page');
|
||||||
});
|
} else {
|
||||||
|
pages = this.props.text.split(/^\\page/gm);
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
pages : pages,
|
||||||
|
usePPR : pages.length >= PPR_THRESHOLD
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
updateSize : function() {
|
updateSize : function() {
|
||||||
@@ -103,12 +117,15 @@ const BrewRenderer = createClass({
|
|||||||
|
|
||||||
renderDummyPage : function(index){
|
renderDummyPage : function(index){
|
||||||
return <div className='phb' id={`p${index + 1}`} key={index}>
|
return <div className='phb' id={`p${index + 1}`} key={index}>
|
||||||
<i className='fa fa-spinner fa-spin' />
|
<i className='fas fa-spinner fa-spin' />
|
||||||
</div>;
|
</div>;
|
||||||
},
|
},
|
||||||
|
|
||||||
renderPage : function(pageText, index){
|
renderPage : function(pageText, index){
|
||||||
return <div className='phb' id={`p${index + 1}`} dangerouslySetInnerHTML={{ __html: Markdown.render(pageText) }} key={index} />;
|
if(this.props.renderer == 'legacy')
|
||||||
|
return <div className='phb' id={`p${index + 1}`} dangerouslySetInnerHTML={{ __html: MarkdownLegacy.render(pageText) }} key={index} />;
|
||||||
|
else
|
||||||
|
return <div className='phb3' id={`p${index + 1}`} dangerouslySetInnerHTML={{ __html: Markdown.render(pageText) }} key={index} />;
|
||||||
},
|
},
|
||||||
|
|
||||||
renderPages : function(){
|
renderPages : function(){
|
||||||
@@ -159,7 +176,7 @@ const BrewRenderer = createClass({
|
|||||||
: null}
|
: null}
|
||||||
|
|
||||||
<Frame initialContent={this.state.initialContent} style={{ width: '100%', height: '100%', visibility: this.state.visibility }} contentDidMount={this.frameDidMount}>
|
<Frame initialContent={this.state.initialContent} style={{ width: '100%', height: '100%', visibility: this.state.visibility }} contentDidMount={this.frameDidMount}>
|
||||||
<div className='brewRenderer'
|
<div className={'brewRenderer'}
|
||||||
onScroll={this.handleScroll}
|
onScroll={this.handleScroll}
|
||||||
style={{ height: this.state.height }}>
|
style={{ height: this.state.height }}>
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
|
@import (multiple, less) 'shared/naturalcrit/styles/reset.less';
|
||||||
|
& {@import (multiple, less) './client/homebrew/phbStyle/phb.styleLegacy.less';} //&{} keeps internal variables locally-scoped
|
||||||
|
& {@import (multiple, less) './client/homebrew/phbStyle/phb.style.less';}
|
||||||
|
|
||||||
@import (less) './client/homebrew/phbStyle/phb.style.less';
|
|
||||||
.pane{
|
|
||||||
position : relative;
|
|
||||||
}
|
|
||||||
.brewRenderer{
|
.brewRenderer{
|
||||||
will-change : transform;
|
will-change : transform;
|
||||||
overflow-y : scroll;
|
overflow-y : scroll;
|
||||||
@@ -14,8 +13,17 @@
|
|||||||
margin-left : auto;
|
margin-left : auto;
|
||||||
box-shadow : 1px 4px 14px #000;
|
box-shadow : 1px 4px 14px #000;
|
||||||
}
|
}
|
||||||
|
&>.phb3{
|
||||||
|
margin-right : auto;
|
||||||
|
margin-bottom : 30px;
|
||||||
|
margin-left : auto;
|
||||||
|
box-shadow : 1px 4px 14px #000;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.pane{
|
||||||
|
position : relative;
|
||||||
|
}
|
||||||
.pageInfo{
|
.pageInfo{
|
||||||
position : absolute;
|
position : absolute;
|
||||||
right : 17px;
|
right : 17px;
|
||||||
@@ -37,4 +45,4 @@
|
|||||||
font-size : 10px;
|
font-size : 10px;
|
||||||
font-weight : 800;
|
font-weight : 800;
|
||||||
color : white;
|
color : white;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ const ErrorBar = createClass({
|
|||||||
if(!this.props.errors.length) return null;
|
if(!this.props.errors.length) return null;
|
||||||
|
|
||||||
return <div className='errorBar'>
|
return <div className='errorBar'>
|
||||||
<i className='fa fa-exclamation-triangle' />
|
<i className='fas fa-exclamation-triangle' />
|
||||||
<h3> There are HTML errors in your markup</h3>
|
<h3> There are HTML errors in your markup</h3>
|
||||||
<small>If these aren't fixed your brew will not render properly when you print it to PDF or share it</small>
|
<small>If these aren't fixed your brew will not render properly when you print it to PDF or share it</small>
|
||||||
{this.renderErrors()}
|
{this.renderErrors()}
|
||||||
|
|||||||
@@ -60,8 +60,8 @@ const NotificationPopup = createClass({
|
|||||||
if(_.isEmpty(this.state.notifications)) return null;
|
if(_.isEmpty(this.state.notifications)) return null;
|
||||||
|
|
||||||
return <div className='notificationPopup'>
|
return <div className='notificationPopup'>
|
||||||
<i className='fa fa-times dismiss' onClick={this.dismiss}/>
|
<i className='fas fa-times dismiss' onClick={this.dismiss}/>
|
||||||
<i className='fa fa-info-circle info' />
|
<i className='fas fa-info-circle info' />
|
||||||
<h3>Notice</h3>
|
<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>
|
<small>This website is always improving and we are still adding new features and squashing bugs. Keep the following in mind:</small>
|
||||||
<ul>{_.values(this.state.notifications)}</ul>
|
<ul>{_.values(this.state.notifications)}</ul>
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ const Editor = createClass({
|
|||||||
|
|
||||||
metadata : {},
|
metadata : {},
|
||||||
onMetadataChange : ()=>{},
|
onMetadataChange : ()=>{},
|
||||||
showMetaButton : true
|
showMetaButton : true,
|
||||||
|
renderer : 'legacy'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getInitialState : function() {
|
getInitialState : function() {
|
||||||
@@ -38,7 +39,7 @@ const Editor = createClass({
|
|||||||
|
|
||||||
componentDidMount : function() {
|
componentDidMount : function() {
|
||||||
this.updateEditorSize();
|
this.updateEditorSize();
|
||||||
this.highlightPageLines();
|
this.highlightCustomMarkdown();
|
||||||
window.addEventListener('resize', this.updateEditorSize);
|
window.addEventListener('resize', this.updateEditorSize);
|
||||||
},
|
},
|
||||||
componentWillUnmount : function() {
|
componentWillUnmount : function() {
|
||||||
@@ -78,15 +79,67 @@ const Editor = createClass({
|
|||||||
}, 1);
|
}, 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
highlightPageLines : function(){
|
highlightCustomMarkdown : function(){
|
||||||
if(!this.refs.codeEditor) return;
|
if(!this.refs.codeEditor) return;
|
||||||
const codeMirror = this.refs.codeEditor.codeMirror;
|
const codeMirror = this.refs.codeEditor.codeMirror;
|
||||||
|
|
||||||
|
//reset custom text styles
|
||||||
|
const customHighlights = codeMirror.getAllMarks();
|
||||||
|
for (let i=0;i<customHighlights.length;i++) customHighlights[i].clear();
|
||||||
|
|
||||||
const lineNumbers = _.reduce(this.props.value.split('\n'), (r, line, lineNumber)=>{
|
const lineNumbers = _.reduce(this.props.value.split('\n'), (r, line, lineNumber)=>{
|
||||||
if(line.indexOf('\\page') !== -1){
|
|
||||||
codeMirror.addLineClass(lineNumber, 'background', 'pageLine');
|
//reset custom line styles
|
||||||
r.push(lineNumber);
|
codeMirror.removeLineClass(lineNumber, 'background');
|
||||||
|
codeMirror.removeLineClass(lineNumber, 'text');
|
||||||
|
|
||||||
|
// Legacy Codemirror styling
|
||||||
|
if(this.props.renderer == 'legacy') {
|
||||||
|
if(line.includes('\\page')){
|
||||||
|
codeMirror.addLineClass(lineNumber, 'background', 'pageLine');
|
||||||
|
r.push(lineNumber);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New Codemirror styling for V3 renderer
|
||||||
|
if(this.props.renderer == 'V3') {
|
||||||
|
if(line.startsWith('\\page')){
|
||||||
|
codeMirror.addLineClass(lineNumber, 'background', 'pageLine');
|
||||||
|
r.push(lineNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(line.startsWith('\\column')){
|
||||||
|
codeMirror.addLineClass(lineNumber, 'text', 'columnSplit');
|
||||||
|
r.push(lineNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(line.startsWith('{{') || line.startsWith('}}')){
|
||||||
|
let endCh = line.length+1;
|
||||||
|
const match = line.match(/{{(?:[\w,#-]|="[\w, ]*")*\s*|}}/);
|
||||||
|
if(match)
|
||||||
|
endCh = match.index+match[0].length;
|
||||||
|
codeMirror.markText({ line: lineNumber, ch: 0 }, { line: lineNumber, ch: endCh }, { className: 'block' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if(line.includes('{{') && line.includes('}}')){
|
||||||
|
const regex = /{{(?:[\w,#-]|="[\w, ]*")*\s*|}}/g;
|
||||||
|
let match;
|
||||||
|
let blockCount = 0;
|
||||||
|
while ((match = regex.exec(line)) != null) {
|
||||||
|
if(match[0].startsWith('{')) {
|
||||||
|
blockCount += 1;
|
||||||
|
} else {
|
||||||
|
blockCount -= 1;
|
||||||
|
}
|
||||||
|
if(blockCount < 0) {
|
||||||
|
blockCount = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
codeMirror.markText({ line: lineNumber, ch: match.index }, { line: lineNumber, ch: match.index + match[0].length }, { className: 'inline-block' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
}, []);
|
}, []);
|
||||||
return lineNumbers;
|
return lineNumbers;
|
||||||
@@ -112,7 +165,7 @@ const Editor = createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
this.highlightPageLines();
|
this.highlightCustomMarkdown();
|
||||||
return (
|
return (
|
||||||
<div className='editor' ref='main'>
|
<div className='editor' ref='main'>
|
||||||
<SnippetBar
|
<SnippetBar
|
||||||
@@ -120,7 +173,8 @@ const Editor = createClass({
|
|||||||
onInject={this.handleInject}
|
onInject={this.handleInject}
|
||||||
onToggle={this.handgleToggle}
|
onToggle={this.handgleToggle}
|
||||||
showmeta={this.state.showMetadataEditor}
|
showmeta={this.state.showMetadataEditor}
|
||||||
showMetaButton={this.props.showMetaButton} />
|
showMetaButton={this.props.showMetaButton}
|
||||||
|
renderer={this.props.renderer} />
|
||||||
{this.renderMetadataEditor()}
|
{this.renderMetadataEditor()}
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
ref='codeEditor'
|
ref='codeEditor'
|
||||||
@@ -132,7 +186,7 @@ const Editor = createClass({
|
|||||||
|
|
||||||
{/*
|
{/*
|
||||||
<div className='brewJump' onClick={this.brewJump}>
|
<div className='brewJump' onClick={this.brewJump}>
|
||||||
<i className='fa fa-arrow-right' />
|
<i className='fas fa-arrow-right' />
|
||||||
</div>
|
</div>
|
||||||
*/}
|
*/}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,6 +9,22 @@
|
|||||||
background-color : fade(#333, 15%);
|
background-color : fade(#333, 15%);
|
||||||
border-bottom : #333 solid 1px;
|
border-bottom : #333 solid 1px;
|
||||||
}
|
}
|
||||||
|
.columnSplit{
|
||||||
|
font-style : italic;
|
||||||
|
color : grey;
|
||||||
|
background-color : fade(#299, 15%);
|
||||||
|
border-bottom : #299 solid 1px;
|
||||||
|
}
|
||||||
|
.block{
|
||||||
|
color : purple;
|
||||||
|
font-weight : bold;
|
||||||
|
//font-style: italic;
|
||||||
|
}
|
||||||
|
.inline-block{
|
||||||
|
color : red;
|
||||||
|
font-weight : bold;
|
||||||
|
//font-style: italic;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.brewJump{
|
.brewJump{
|
||||||
@@ -26,4 +42,4 @@
|
|||||||
.tooltipLeft("Jump to brew page");
|
.tooltipLeft("Jump to brew page");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ const MetadataEditor = createClass({
|
|||||||
tags : '',
|
tags : '',
|
||||||
published : false,
|
published : false,
|
||||||
authors : [],
|
authors : [],
|
||||||
systems : []
|
systems : [],
|
||||||
|
renderer : 'legacy'
|
||||||
},
|
},
|
||||||
onChange : ()=>{}
|
onChange : ()=>{}
|
||||||
};
|
};
|
||||||
@@ -36,6 +37,12 @@ const MetadataEditor = createClass({
|
|||||||
}
|
}
|
||||||
this.props.onChange(this.props.metadata);
|
this.props.onChange(this.props.metadata);
|
||||||
},
|
},
|
||||||
|
handleRenderer : function(renderer, e){
|
||||||
|
if(e.target.checked){
|
||||||
|
this.props.metadata.renderer = renderer;
|
||||||
|
}
|
||||||
|
this.props.onChange(this.props.metadata);
|
||||||
|
},
|
||||||
handlePublish : function(val){
|
handlePublish : function(val){
|
||||||
this.props.onChange(_.merge({}, this.props.metadata, {
|
this.props.onChange(_.merge({}, this.props.metadata, {
|
||||||
published : val
|
published : val
|
||||||
@@ -83,11 +90,11 @@ const MetadataEditor = createClass({
|
|||||||
renderPublish : function(){
|
renderPublish : function(){
|
||||||
if(this.props.metadata.published){
|
if(this.props.metadata.published){
|
||||||
return <button className='unpublish' onClick={()=>this.handlePublish(false)}>
|
return <button className='unpublish' onClick={()=>this.handlePublish(false)}>
|
||||||
<i className='fa fa-ban' /> unpublish
|
<i className='fas fa-ban' /> unpublish
|
||||||
</button>;
|
</button>;
|
||||||
} else {
|
} else {
|
||||||
return <button className='publish' onClick={()=>this.handlePublish(true)}>
|
return <button className='publish' onClick={()=>this.handlePublish(true)}>
|
||||||
<i className='fa fa-globe' /> publish
|
<i className='fas fa-globe' /> publish
|
||||||
</button>;
|
</button>;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -99,7 +106,7 @@ const MetadataEditor = createClass({
|
|||||||
<label>delete</label>
|
<label>delete</label>
|
||||||
<div className='value'>
|
<div className='value'>
|
||||||
<button className='publish' onClick={this.handleDelete}>
|
<button className='publish' onClick={this.handleDelete}>
|
||||||
<i className='fa fa-trash' /> delete brew
|
<i className='fas fa-trash-alt' /> delete brew
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
@@ -126,13 +133,42 @@ const MetadataEditor = createClass({
|
|||||||
<div className='value'>
|
<div className='value'>
|
||||||
<a href={this.getRedditLink()} target='_blank' rel='noopener noreferrer'>
|
<a href={this.getRedditLink()} target='_blank' rel='noopener noreferrer'>
|
||||||
<button className='publish'>
|
<button className='publish'>
|
||||||
<i className='fa fa-reddit-alien' /> share to reddit
|
<i className='fab fa-reddit-alien' /> share to reddit
|
||||||
</button>
|
</button>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
renderRenderOptions : function(){
|
||||||
|
if(!global.enable_v3) return;
|
||||||
|
|
||||||
|
return <div className='field systems'>
|
||||||
|
<label>Renderer</label>
|
||||||
|
<div className='value'>
|
||||||
|
<label key='legacy'>
|
||||||
|
<input
|
||||||
|
type='radio'
|
||||||
|
value = 'legacy'
|
||||||
|
name = 'renderer'
|
||||||
|
checked={this.props.metadata.renderer === 'legacy'}
|
||||||
|
onChange={(e)=>this.handleRenderer('legacy', e)} />
|
||||||
|
Legacy
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label key='V3'>
|
||||||
|
<input
|
||||||
|
type='radio'
|
||||||
|
value = 'V3'
|
||||||
|
name = 'renderer'
|
||||||
|
checked={this.props.metadata.renderer === 'V3'}
|
||||||
|
onChange={(e)=>this.handleRenderer('V3', e)} />
|
||||||
|
V3
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
},
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
return <div className='metadataEditor'>
|
return <div className='metadataEditor'>
|
||||||
<div className='field title'>
|
<div className='field title'>
|
||||||
@@ -154,6 +190,8 @@ const MetadataEditor = createClass({
|
|||||||
</div>
|
</div>
|
||||||
*/}
|
*/}
|
||||||
|
|
||||||
|
{this.renderAuthors()}
|
||||||
|
|
||||||
<div className='field systems'>
|
<div className='field systems'>
|
||||||
<label>systems</label>
|
<label>systems</label>
|
||||||
<div className='value'>
|
<div className='value'>
|
||||||
@@ -161,7 +199,7 @@ const MetadataEditor = createClass({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{this.renderAuthors()}
|
{this.renderRenderOptions()}
|
||||||
|
|
||||||
<div className='field publish'>
|
<div className='field publish'>
|
||||||
<label>publish</label>
|
<label>publish</label>
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ const _ = require('lodash');
|
|||||||
const cx = require('classnames');
|
const cx = require('classnames');
|
||||||
|
|
||||||
|
|
||||||
const Snippets = require('./snippets/snippets.js');
|
const SnippetsLegacy = require('./snippetsLegacy/snippets.js');
|
||||||
|
const SnippetsV3 = require('./snippets/snippets.js');
|
||||||
|
|
||||||
const execute = function(val, brew){
|
const execute = function(val, brew){
|
||||||
if(_.isFunction(val)) return val(brew);
|
if(_.isFunction(val)) return val(brew);
|
||||||
@@ -19,7 +20,14 @@ const Snippetbar = createClass({
|
|||||||
onInject : ()=>{},
|
onInject : ()=>{},
|
||||||
onToggle : ()=>{},
|
onToggle : ()=>{},
|
||||||
showmeta : false,
|
showmeta : false,
|
||||||
showMetaButton : true
|
showMetaButton : true,
|
||||||
|
renderer : ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState : function() {
|
||||||
|
return {
|
||||||
|
renderer : this.props.renderer
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -28,6 +36,11 @@ const Snippetbar = createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
renderSnippetGroups : function(){
|
renderSnippetGroups : function(){
|
||||||
|
if(this.props.renderer == 'V3')
|
||||||
|
Snippets = SnippetsV3;
|
||||||
|
else
|
||||||
|
Snippets = SnippetsLegacy;
|
||||||
|
|
||||||
return _.map(Snippets, (snippetGroup)=>{
|
return _.map(Snippets, (snippetGroup)=>{
|
||||||
return <SnippetGroup
|
return <SnippetGroup
|
||||||
brew={this.props.brew}
|
brew={this.props.brew}
|
||||||
@@ -44,7 +57,7 @@ const Snippetbar = createClass({
|
|||||||
if(!this.props.showMetaButton) return;
|
if(!this.props.showMetaButton) return;
|
||||||
return <div className={cx('snippetBarButton', 'toggleMeta', { selected: this.props.showmeta })}
|
return <div className={cx('snippetBarButton', 'toggleMeta', { selected: this.props.showmeta })}
|
||||||
onClick={this.props.onToggle}>
|
onClick={this.props.onToggle}>
|
||||||
<i className='fa fa-info-circle' />
|
<i className='fas fa-info-circle' />
|
||||||
<span className='groupName'>Properties</span>
|
<span className='groupName'>Properties</span>
|
||||||
</div>;
|
</div>;
|
||||||
},
|
},
|
||||||
@@ -69,7 +82,7 @@ const SnippetGroup = createClass({
|
|||||||
return {
|
return {
|
||||||
brew : '',
|
brew : '',
|
||||||
groupName : '',
|
groupName : '',
|
||||||
icon : 'fa-rocket',
|
icon : 'fas fa-rocket',
|
||||||
snippets : [],
|
snippets : [],
|
||||||
onSnippetClick : function(){},
|
onSnippetClick : function(){},
|
||||||
};
|
};
|
||||||
@@ -80,7 +93,7 @@ const SnippetGroup = createClass({
|
|||||||
renderSnippets : function(){
|
renderSnippets : function(){
|
||||||
return _.map(this.props.snippets, (snippet)=>{
|
return _.map(this.props.snippets, (snippet)=>{
|
||||||
return <div className='snippet' key={snippet.name} onClick={()=>this.handleSnippetClick(snippet)}>
|
return <div className='snippet' key={snippet.name} onClick={()=>this.handleSnippetClick(snippet)}>
|
||||||
<i className={`fa fa-fw ${snippet.icon}`} />
|
<i className={snippet.icon} />
|
||||||
{snippet.name}
|
{snippet.name}
|
||||||
</div>;
|
</div>;
|
||||||
});
|
});
|
||||||
@@ -89,7 +102,7 @@ const SnippetGroup = createClass({
|
|||||||
render : function(){
|
render : function(){
|
||||||
return <div className='snippetGroup snippetBarButton'>
|
return <div className='snippetGroup snippetBarButton'>
|
||||||
<div className='text'>
|
<div className='text'>
|
||||||
<i className={`fa fa-fw ${this.props.icon}`} />
|
<i className={this.props.icon} />
|
||||||
<span className='groupName'>{this.props.groupName}</span>
|
<span className='groupName'>{this.props.groupName}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className='dropdown'>
|
<div className='dropdown'>
|
||||||
|
|||||||
@@ -12,73 +12,58 @@ module.exports = [
|
|||||||
|
|
||||||
{
|
{
|
||||||
groupName : 'Editor',
|
groupName : 'Editor',
|
||||||
icon : 'fa-pencil',
|
icon : 'fas fa-pencil-alt',
|
||||||
snippets : [
|
snippets : [
|
||||||
{
|
{
|
||||||
name : 'Column Break',
|
name : 'Column Break',
|
||||||
icon : 'fa-columns',
|
icon : 'fas fa-columns',
|
||||||
gen : '```\n```\n\n'
|
gen : '\n\\column\n'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'New Page',
|
name : 'New Page',
|
||||||
icon : 'fa-file-text',
|
icon : 'fas fa-file-alt',
|
||||||
gen : '\\page\n\n'
|
gen : '\n\\page\n'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Vertical Spacing',
|
name : 'Vertical Spacing',
|
||||||
icon : 'fa-arrows-v',
|
icon : 'fas fa-times-circle',
|
||||||
gen : '<div style=\'margin-top:140px\'></div>\n\n'
|
gen : ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Wide Block',
|
name : 'Wide Block',
|
||||||
icon : 'fa-arrows-h',
|
icon : 'fas fa-times-circle',
|
||||||
gen : '<div class=\'wide\'>\nEverything in here will be extra wide. Tables, text, everything! Beware though, CSS columns can behave a bit weird sometimes.\n</div>\n'
|
gen : ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Image',
|
name : 'Image',
|
||||||
icon : 'fa-image',
|
icon : 'fas fa-times-circle',
|
||||||
gen : [
|
gen : ''
|
||||||
'<img ',
|
|
||||||
' src=\'https://s-media-cache-ak0.pinimg.com/736x/4a/81/79/4a8179462cfdf39054a418efd4cb743e.jpg\' ',
|
|
||||||
' style=\'width:325px\' />',
|
|
||||||
'Credit: Kyounghwan Kim'
|
|
||||||
].join('\n')
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Background Image',
|
name : 'Background Image',
|
||||||
icon : 'fa-tree',
|
icon : 'fas fa-times-circle',
|
||||||
gen : [
|
gen : ''
|
||||||
'<img ',
|
|
||||||
' src=\'http://i.imgur.com/hMna6G0.png\' ',
|
|
||||||
' style=\'position:absolute; top:50px; right:30px; width:280px\' />'
|
|
||||||
].join('\n')
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name : 'Page Number',
|
name : 'Page Number',
|
||||||
icon : 'fa-bookmark',
|
icon : 'fas fa-bookmark',
|
||||||
gen : '<div class=\'pageNumber\'>1</div>\n<div class=\'footnote\'>PART 1 | FANCINESS</div>\n\n'
|
gen : '{{pageNumber\n1\n}}\n{{footnote\nPART 1 | FANCINESS\n}}\n\n'
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name : 'Auto-incrementing Page Number',
|
name : 'Auto-incrementing Page Number',
|
||||||
icon : 'fa-sort-numeric-asc',
|
icon : 'fas fa-sort-numeric-down',
|
||||||
gen : '<div class=\'pageNumber auto\'></div>\n'
|
gen : '{{\npageNumber,auto\n}}\n\n'
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name : 'Link to page',
|
name : 'Link to page',
|
||||||
icon : 'fa-link',
|
icon : 'fas fa-link',
|
||||||
gen : '[Click here](#p3) to go to page 3\n'
|
gen : '[Click here](#p3) to go to page 3\n'
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name : 'Table of Contents',
|
name : 'Table of Contents',
|
||||||
icon : 'fa-book',
|
icon : 'fas fa-book',
|
||||||
gen : TableOfContentsGen
|
gen : TableOfContentsGen
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -87,26 +72,26 @@ module.exports = [
|
|||||||
|
|
||||||
{
|
{
|
||||||
groupName : 'PHB',
|
groupName : 'PHB',
|
||||||
icon : 'fa-book',
|
icon : 'fas fa-book',
|
||||||
snippets : [
|
snippets : [
|
||||||
{
|
{
|
||||||
name : 'Spell',
|
name : 'Spell',
|
||||||
icon : 'fa-magic',
|
icon : 'fas fa-magic',
|
||||||
gen : MagicGen.spell,
|
gen : MagicGen.spell,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Spell List',
|
name : 'Spell List',
|
||||||
icon : 'fa-list',
|
icon : 'fas fa-scroll',
|
||||||
gen : MagicGen.spellList,
|
gen : MagicGen.spellList,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Class Feature',
|
name : 'Class Feature',
|
||||||
icon : 'fa-trophy',
|
icon : 'fas fa-mask',
|
||||||
gen : ClassFeatureGen,
|
gen : ClassFeatureGen,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Note',
|
name : 'Note',
|
||||||
icon : 'fa-sticky-note',
|
icon : 'fas fa-sticky-note',
|
||||||
gen : function(){
|
gen : function(){
|
||||||
return [
|
return [
|
||||||
'> ##### Time to Drop Knowledge',
|
'> ##### Time to Drop Knowledge',
|
||||||
@@ -118,7 +103,7 @@ module.exports = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Descriptive Text Box',
|
name : 'Descriptive Text Box',
|
||||||
icon : 'fa-sticky-note-o',
|
icon : 'fas fa-comment-alt',
|
||||||
gen : function(){
|
gen : function(){
|
||||||
return [
|
return [
|
||||||
'<div class=\'descriptive\'>',
|
'<div class=\'descriptive\'>',
|
||||||
@@ -132,17 +117,17 @@ module.exports = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Monster Stat Block',
|
name : 'Monster Stat Block',
|
||||||
icon : 'fa-bug',
|
icon : 'fas fa-spider',
|
||||||
gen : MonsterBlockGen.half,
|
gen : MonsterBlockGen.half,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Wide Monster Stat Block',
|
name : 'Wide Monster Stat Block',
|
||||||
icon : 'fa-paw',
|
icon : 'fas fa-dragon',
|
||||||
gen : MonsterBlockGen.full,
|
gen : MonsterBlockGen.full,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Cover Page',
|
name : 'Cover Page',
|
||||||
icon : 'fa-file-word-o',
|
icon : 'fas fa-file-word',
|
||||||
gen : CoverPageGen,
|
gen : CoverPageGen,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -154,21 +139,21 @@ module.exports = [
|
|||||||
|
|
||||||
{
|
{
|
||||||
groupName : 'Tables',
|
groupName : 'Tables',
|
||||||
icon : 'fa-table',
|
icon : 'fas fa-table',
|
||||||
snippets : [
|
snippets : [
|
||||||
{
|
{
|
||||||
name : 'Class Table',
|
name : 'Class Table',
|
||||||
icon : 'fa-table',
|
icon : 'fas fa-table',
|
||||||
gen : ClassTableGen.full,
|
gen : ClassTableGen.full,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Half Class Table',
|
name : 'Half Class Table',
|
||||||
icon : 'fa-list-alt',
|
icon : 'fas fa-list-alt',
|
||||||
gen : ClassTableGen.half,
|
gen : ClassTableGen.half,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Table',
|
name : 'Table',
|
||||||
icon : 'fa-th-list',
|
icon : 'fas fa-th-list',
|
||||||
gen : function(){
|
gen : function(){
|
||||||
return [
|
return [
|
||||||
'##### Cookie Tastiness',
|
'##### Cookie Tastiness',
|
||||||
@@ -184,7 +169,7 @@ module.exports = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Wide Table',
|
name : 'Wide Table',
|
||||||
icon : 'fa-list',
|
icon : 'fas fa-list',
|
||||||
gen : function(){
|
gen : function(){
|
||||||
return [
|
return [
|
||||||
'<div class=\'wide\'>',
|
'<div class=\'wide\'>',
|
||||||
@@ -202,7 +187,7 @@ module.exports = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Split Table',
|
name : 'Split Table',
|
||||||
icon : 'fa-th-large',
|
icon : 'fas fa-th-large',
|
||||||
gen : function(){
|
gen : function(){
|
||||||
return [
|
return [
|
||||||
'<div style=\'column-count:2\'>',
|
'<div style=\'column-count:2\'>',
|
||||||
@@ -238,11 +223,11 @@ module.exports = [
|
|||||||
|
|
||||||
{
|
{
|
||||||
groupName : 'Print',
|
groupName : 'Print',
|
||||||
icon : 'fa-print',
|
icon : 'fas fa-print',
|
||||||
snippets : [
|
snippets : [
|
||||||
{
|
{
|
||||||
name : 'A4 PageSize',
|
name : 'A4 PageSize',
|
||||||
icon : 'fa-file-o',
|
icon : 'far fa-file',
|
||||||
gen : ['<style>',
|
gen : ['<style>',
|
||||||
' .phb{',
|
' .phb{',
|
||||||
' width : 210mm;',
|
' width : 210mm;',
|
||||||
@@ -253,7 +238,7 @@ module.exports = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Ink Friendly',
|
name : 'Ink Friendly',
|
||||||
icon : 'fa-tint',
|
icon : 'fas fa-tint',
|
||||||
gen : ['<style>',
|
gen : ['<style>',
|
||||||
' .phb{ background : white;}',
|
' .phb{ background : white;}',
|
||||||
' .phb img{ display : none;}',
|
' .phb img{ display : none;}',
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
|
module.exports = function(classname){
|
||||||
|
|
||||||
|
classname = _.sample(['archivist', 'fancyman', 'linguist', 'fletcher',
|
||||||
|
'notary', 'berserker-typist', 'fishmongerer', 'manicurist', 'haberdasher', 'concierge']);
|
||||||
|
|
||||||
|
classname = classname.toLowerCase();
|
||||||
|
|
||||||
|
const hitDie = _.sample([4, 6, 8, 10, 12]);
|
||||||
|
|
||||||
|
const abilityList = ['Strength', 'Dexerity', 'Constitution', 'Wisdom', 'Charisma', 'Intelligence'];
|
||||||
|
const skillList = ['Acrobatics ', 'Animal Handling', 'Arcana', 'Athletics', 'Deception', 'History', 'Insight', 'Intimidation', 'Investigation', 'Medicine', 'Nature', 'Perception', 'Performance', 'Persuasion', 'Religion', 'Sleight of Hand', 'Stealth', 'Survival'];
|
||||||
|
|
||||||
|
|
||||||
|
return [
|
||||||
|
'## Class Features',
|
||||||
|
`As a ${classname}, you gain the following class features`,
|
||||||
|
'#### Hit Points',
|
||||||
|
'___',
|
||||||
|
`- **Hit Dice:** 1d${hitDie} per ${classname} level`,
|
||||||
|
`- **Hit Points at 1st Level:** ${hitDie} + your Constitution modifier`,
|
||||||
|
`- **Hit Points at Higher Levels:** 1d${hitDie} (or ${hitDie/2 + 1}) + your Constitution modifier per ${classname} level after 1st`,
|
||||||
|
'',
|
||||||
|
'#### Proficiencies',
|
||||||
|
'___',
|
||||||
|
`- **Armor:** ${_.sampleSize(['Light armor', 'Medium armor', 'Heavy armor', 'Shields'], _.random(0, 3)).join(', ') || 'None'}`,
|
||||||
|
`- **Weapons:** ${_.sampleSize(['Squeegee', 'Rubber Chicken', 'Simple weapons', 'Martial weapons'], _.random(0, 2)).join(', ') || 'None'}`,
|
||||||
|
`- **Tools:** ${_.sampleSize(['Artian\'s tools', 'one musical instrument', 'Thieve\'s tools'], _.random(0, 2)).join(', ') || 'None'}`,
|
||||||
|
'',
|
||||||
|
'___',
|
||||||
|
`- **Saving Throws:** ${_.sampleSize(abilityList, 2).join(', ')}`,
|
||||||
|
`- **Skills:** Choose two from ${_.sampleSize(skillList, _.random(4, 6)).join(', ')}`,
|
||||||
|
'',
|
||||||
|
'#### Equipment',
|
||||||
|
'You start with the following equipment, in addition to the equipment granted by your background:',
|
||||||
|
'- *(a)* a martial weapon and a shield or *(b)* two martial weapons',
|
||||||
|
'- *(a)* five javelins or *(b)* any simple melee weapon',
|
||||||
|
`- ${_.sample(['10 lint fluffs', '1 button', 'a cherished lost sock'])}`,
|
||||||
|
'\n\n\n'
|
||||||
|
].join('\n');
|
||||||
|
};
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
|
const features = [
|
||||||
|
'Astrological Botany',
|
||||||
|
'Astrological Chemistry',
|
||||||
|
'Biochemical Sorcery',
|
||||||
|
'Civil Alchemy',
|
||||||
|
'Consecrated Biochemistry',
|
||||||
|
'Demonic Anthropology',
|
||||||
|
'Divinatory Mineralogy',
|
||||||
|
'Genetic Banishing',
|
||||||
|
'Hermetic Geography',
|
||||||
|
'Immunological Incantations',
|
||||||
|
'Nuclear Illusionism',
|
||||||
|
'Ritual Astronomy',
|
||||||
|
'Seismological Divination',
|
||||||
|
'Spiritual Biochemistry',
|
||||||
|
'Statistical Occultism',
|
||||||
|
'Police Necromancer',
|
||||||
|
'Sixgun Poisoner',
|
||||||
|
'Pharmaceutical Gunslinger',
|
||||||
|
'Infernal Banker',
|
||||||
|
'Spell Analyst',
|
||||||
|
'Gunslinger Corruptor',
|
||||||
|
'Torque Interfacer',
|
||||||
|
'Exo Interfacer',
|
||||||
|
'Gunpowder Torturer',
|
||||||
|
'Orbital Gravedigger',
|
||||||
|
'Phased Linguist',
|
||||||
|
'Mathematical Pharmacist',
|
||||||
|
'Plasma Outlaw',
|
||||||
|
'Malefic Chemist',
|
||||||
|
'Police Cultist'
|
||||||
|
];
|
||||||
|
|
||||||
|
const classnames = ['Archivist', 'Fancyman', 'Linguist', 'Fletcher',
|
||||||
|
'Notary', 'Berserker-Typist', 'Fishmongerer', 'Manicurist', 'Haberdasher', 'Concierge'];
|
||||||
|
|
||||||
|
const levels = ['1st', '2nd', '3rd', '4th', '5th', '6th', '7th', '8th', '9th', '10th', '11th', '12th', '13th', '14th', '15th', '16th', '17th', '18th', '19th', '20th'];
|
||||||
|
|
||||||
|
const profBonus = [2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6];
|
||||||
|
|
||||||
|
const getFeature = (level)=>{
|
||||||
|
let res = [];
|
||||||
|
if(_.includes([4, 6, 8, 12, 14, 16, 19], level+1)){
|
||||||
|
res = ['Ability Score Improvement'];
|
||||||
|
}
|
||||||
|
res = _.union(res, _.sampleSize(features, _.sample([0, 1, 1, 1, 1, 1])));
|
||||||
|
if(!res.length) return '─';
|
||||||
|
return res.join(', ');
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
full : function(){
|
||||||
|
const classname = _.sample(classnames);
|
||||||
|
|
||||||
|
const maxes = [4, 3, 3, 3, 3, 2, 2, 1, 1];
|
||||||
|
const drawSlots = function(Slots){
|
||||||
|
let slots = Number(Slots);
|
||||||
|
return _.times(9, function(i){
|
||||||
|
const max = maxes[i];
|
||||||
|
if(slots < 1) return '—';
|
||||||
|
const res = _.min([max, slots]);
|
||||||
|
slots -= res;
|
||||||
|
return res;
|
||||||
|
}).join(' | ');
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
let cantrips = 3;
|
||||||
|
let spells = 1;
|
||||||
|
let slots = 2;
|
||||||
|
return `<div class='classTable wide'>\n##### The ${classname}\n` +
|
||||||
|
`| Level | Proficiency Bonus | Features | Cantrips Known | Spells Known | 1st | 2nd | 3rd | 4th | 5th | 6th | 7th | 8th | 9th |\n`+
|
||||||
|
`|:---:|:---:|:---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|\n${
|
||||||
|
_.map(levels, function(levelName, level){
|
||||||
|
const res = [
|
||||||
|
levelName,
|
||||||
|
`+${profBonus[level]}`,
|
||||||
|
getFeature(level),
|
||||||
|
cantrips,
|
||||||
|
spells,
|
||||||
|
drawSlots(slots)
|
||||||
|
].join(' | ');
|
||||||
|
|
||||||
|
cantrips += _.random(0, 1);
|
||||||
|
spells += _.random(0, 1);
|
||||||
|
slots += _.random(0, 2);
|
||||||
|
|
||||||
|
return `| ${res} |`;
|
||||||
|
}).join('\n')}\n</div>\n\n`;
|
||||||
|
},
|
||||||
|
|
||||||
|
half : function(){
|
||||||
|
const classname = _.sample(classnames);
|
||||||
|
|
||||||
|
let featureScore = 1;
|
||||||
|
return `<div class='classTable'>\n##### The ${classname}\n` +
|
||||||
|
`| Level | Proficiency Bonus | Features | ${_.sample(features)}|\n` +
|
||||||
|
`|:---:|:---:|:---|:---:|\n${
|
||||||
|
_.map(levels, function(levelName, level){
|
||||||
|
const res = [
|
||||||
|
levelName,
|
||||||
|
`+${profBonus[level]}`,
|
||||||
|
getFeature(level),
|
||||||
|
`+${featureScore}`
|
||||||
|
].join(' | ');
|
||||||
|
|
||||||
|
featureScore += _.random(0, 1);
|
||||||
|
|
||||||
|
return `| ${res} |`;
|
||||||
|
}).join('\n')}\n</div>\n\n`;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
|
const titles = [
|
||||||
|
'The Burning Gallows',
|
||||||
|
'The Ring of Nenlast',
|
||||||
|
'Below the Blind Tavern',
|
||||||
|
'Below the Hungering River',
|
||||||
|
'Before Bahamut\'s Land',
|
||||||
|
'The Cruel Grave from Within',
|
||||||
|
'The Strength of Trade Road',
|
||||||
|
'Through The Raven Queen\'s Worlds',
|
||||||
|
'Within the Settlement',
|
||||||
|
'The Crown from Within',
|
||||||
|
'The Merchant Within the Battlefield',
|
||||||
|
'Ioun\'s Fading Traveler',
|
||||||
|
'The Legion Ingredient',
|
||||||
|
'The Explorer Lure',
|
||||||
|
'Before the Charming Badlands',
|
||||||
|
'The Living Dead Above the Fearful Cage',
|
||||||
|
'Vecna\'s Hidden Sage',
|
||||||
|
'Bahamut\'s Demonspawn',
|
||||||
|
'Across Gruumsh\'s Elemental Chaos',
|
||||||
|
'The Blade of Orcus',
|
||||||
|
'Beyond Revenge',
|
||||||
|
'Brain of Insanity',
|
||||||
|
'Breed Battle!, A New Beginning',
|
||||||
|
'Evil Lake, A New Beginning',
|
||||||
|
'Invasion of the Gigantic Cat, Part II',
|
||||||
|
'Kraken War 2020',
|
||||||
|
'The Body Whisperers',
|
||||||
|
'The Diabolical Tales of the Ape-Women',
|
||||||
|
'The Doctor Immortal',
|
||||||
|
'The Doctor from Heaven',
|
||||||
|
'The Graveyard',
|
||||||
|
'Azure Core',
|
||||||
|
'Core Battle',
|
||||||
|
'Core of Heaven: The Guardian of Amazement',
|
||||||
|
'Deadly Amazement III',
|
||||||
|
'Dry Chaos IX',
|
||||||
|
'Gate Thunder',
|
||||||
|
'Guardian: Skies of the Dark Wizard',
|
||||||
|
'Lute of Eternity',
|
||||||
|
'Mercury\'s Planet: Brave Evolution',
|
||||||
|
'Ruby of Atlantis: The Quake of Peace',
|
||||||
|
'Sky of Zelda: The Thunder of Force',
|
||||||
|
'Vyse\'s Skies',
|
||||||
|
'White Greatness III',
|
||||||
|
'Yellow Divinity',
|
||||||
|
'Zidane\'s Ghost'
|
||||||
|
];
|
||||||
|
|
||||||
|
const subtitles = [
|
||||||
|
'In an ominous universe, a botanist opposes terrorism.',
|
||||||
|
'In a demon-haunted city, in an age of lies and hate, a physicist tries to find an ancient treasure and battles a mob of aliens.',
|
||||||
|
'In a land of corruption, two cyberneticists and a dungeon delver search for freedom.',
|
||||||
|
'In an evil empire of horror, two rangers battle the forces of hell.',
|
||||||
|
'In a lost city, in an age of sorcery, a librarian quests for revenge.',
|
||||||
|
'In a universe of illusions and danger, three time travellers and an adventurer search for justice.',
|
||||||
|
'In a forgotten universe of barbarism, in an era of terror and mysticism, a virtual reality programmer and a spy try to find vengance and battle crime.',
|
||||||
|
'In a universe of demons, in an era of insanity and ghosts, three bodyguards and a bodyguard try to find vengance.',
|
||||||
|
'In a kingdom of corruption and battle, seven artificial intelligences try to save the last living fertile woman.',
|
||||||
|
'In a universe of virutal reality and agony, in an age of ghosts and ghosts, a fortune-teller and a wanderer try to avert the apocalypse.',
|
||||||
|
'In a crime-infested kingdom, three martial artists quest for the truth and oppose evil.',
|
||||||
|
'In a terrifying universe of lost souls, in an era of lost souls, eight dancers fight evil.',
|
||||||
|
'In a galaxy of confusion and insanity, three martial artists and a duke battle a mob of psychics.',
|
||||||
|
'In an amazing kingdom, a wizard and a secretary hope to prevent the destruction of mankind.',
|
||||||
|
'In a kingdom of deception, a reporter searches for fame.',
|
||||||
|
'In a hellish empire, a swordswoman and a duke try to find the ultimate weapon and battle a conspiracy.',
|
||||||
|
'In an evil galaxy of illusion, in a time of technology and misery, seven psychiatrists battle crime.',
|
||||||
|
'In a dark city of confusion, three swordswomen and a singer battle lawlessness.',
|
||||||
|
'In an ominous empire, in an age of hate, two philosophers and a student try to find justice and battle a mob of mages intent on stealing the souls of the innocent.',
|
||||||
|
'In a kingdom of panic, six adventurers oppose lawlessness.',
|
||||||
|
'In a land of dreams and hopelessness, three hackers and a cyborg search for justice.',
|
||||||
|
'On a planet of mysticism, three travelers and a fire fighter quest for the ultimate weapon and oppose evil.',
|
||||||
|
'In a wicked universe, five seers fight lawlessness.',
|
||||||
|
'In a kingdom of death, in an era of illusion and blood, four colonists search for fame.',
|
||||||
|
'In an amazing kingdom, in an age of sorcery and lost souls, eight space pirates quest for freedom.',
|
||||||
|
'In a cursed empire, five inventors oppose terrorism.',
|
||||||
|
'On a crime-ridden planet of conspiracy, a watchman and an artificial intelligence try to find love and oppose lawlessness.',
|
||||||
|
'In a forgotten land, a reporter and a spy try to stop the apocalypse.',
|
||||||
|
'In a forbidden land of prophecy, a scientist and an archivist oppose a cabal of barbarians intent on stealing the souls of the innocent.',
|
||||||
|
'On an infernal world of illusion, a grave robber and a watchman try to find revenge and combat a syndicate of mages intent on stealing the source of all magic.',
|
||||||
|
'In a galaxy of dark magic, four fighters seek freedom.',
|
||||||
|
'In an empire of deception, six tomb-robbers quest for the ultimate weapon and combat an army of raiders.',
|
||||||
|
'In a kingdom of corruption and lost souls, in an age of panic, eight planetologists oppose evil.',
|
||||||
|
'In a galaxy of misery and hopelessness, in a time of agony and pain, five planetologists search for vengance.',
|
||||||
|
'In a universe of technology and insanity, in a time of sorcery, a computer techician quests for hope.',
|
||||||
|
'On a planet of dark magic and barbarism, in an age of horror and blasphemy, seven librarians search for fame.',
|
||||||
|
'In an empire of dark magic, in a time of blood and illusions, four monks try to find the ultimate weapon and combat terrorism.',
|
||||||
|
'In a forgotten empire of dark magic, six kings try to prevent the destruction of mankind.',
|
||||||
|
'In a galaxy of dark magic and horror, in an age of hopelessness, four marines and an outlaw combat evil.',
|
||||||
|
'In a mysterious city of illusion, in an age of computerization, a witch-hunter tries to find the ultimate weapon and opposes an evil corporation.',
|
||||||
|
'In a damned kingdom of technology, a virtual reality programmer and a fighter seek fame.',
|
||||||
|
'In a hellish kingdom, in an age of blasphemy and blasphemy, an astrologer searches for fame.',
|
||||||
|
'In a damned world of devils, an alien and a ranger quest for love and oppose a syndicate of demons.',
|
||||||
|
'In a cursed galaxy, in a time of pain, seven librarians hope to avert the apocalypse.',
|
||||||
|
'In a crime-infested galaxy, in an era of hopelessness and panic, three champions and a grave robber try to solve the ultimate crime.'
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = ()=>{
|
||||||
|
return `<style>
|
||||||
|
.phb#p1{ text-align:center; }
|
||||||
|
.phb#p1:after{ display:none; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div style='margin-top:450px;'></div>
|
||||||
|
|
||||||
|
# ${_.sample(titles)}
|
||||||
|
|
||||||
|
<div style='margin-top:25px'></div>
|
||||||
|
<div class='wide'>
|
||||||
|
##### ${_.sample(subtitles)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
\\page`;
|
||||||
|
};
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
|
const ClassFeatureGen = require('./classfeature.gen.js');
|
||||||
|
|
||||||
|
const ClassTableGen = require('./classtable.gen.js');
|
||||||
|
|
||||||
|
module.exports = function(){
|
||||||
|
|
||||||
|
const classname = _.sample(['Archivist', 'Fancyman', 'Linguist', 'Fletcher',
|
||||||
|
'Notary', 'Berserker-Typist', 'Fishmongerer', 'Manicurist', 'Haberdasher', 'Concierge']);
|
||||||
|
|
||||||
|
|
||||||
|
const image = _.sample(_.map([
|
||||||
|
'http://orig01.deviantart.net/4682/f/2007/099/f/c/bard_stick_figure_by_wrpigeek.png',
|
||||||
|
'http://img07.deviantart.net/a3c9/i/2007/099/3/a/archer_stick_figure_by_wrpigeek.png',
|
||||||
|
'http://pre04.deviantart.net/d596/th/pre/f/2007/099/5/2/adventurer_stick_figure_by_wrpigeek.png',
|
||||||
|
'http://img13.deviantart.net/d501/i/2007/099/d/4/black_mage_stick_figure_by_wrpigeek.png',
|
||||||
|
'http://img09.deviantart.net/5cf3/i/2007/099/d/d/dark_knight_stick_figure_by_wrpigeek.png',
|
||||||
|
'http://pre01.deviantart.net/7a34/th/pre/f/2007/099/6/3/monk_stick_figure_by_wrpigeek.png',
|
||||||
|
'http://img11.deviantart.net/5dcc/i/2007/099/d/1/mystic_knight_stick_figure_by_wrpigeek.png',
|
||||||
|
'http://pre08.deviantart.net/ad45/th/pre/f/2007/099/a/0/thief_stick_figure_by_wrpigeek.png',
|
||||||
|
], function(url){
|
||||||
|
return `<img src = '${url}' style='max-width:8cm;max-height:25cm' />`;
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
return `${[
|
||||||
|
image,
|
||||||
|
'',
|
||||||
|
'```',
|
||||||
|
'```',
|
||||||
|
'<div style=\'margin-top:240px\'></div>\n\n',
|
||||||
|
`## ${classname}`,
|
||||||
|
'Cool intro stuff will go here',
|
||||||
|
|
||||||
|
'\\page',
|
||||||
|
ClassTableGen(classname),
|
||||||
|
ClassFeatureGen(classname),
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
].join('\n')}\n\n\n`;
|
||||||
|
};
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
|
const spellNames = [
|
||||||
|
'Astral Rite of Acne',
|
||||||
|
'Create Acne',
|
||||||
|
'Cursed Ramen Erruption',
|
||||||
|
'Dark Chant of the Dentists',
|
||||||
|
'Erruption of Immaturity',
|
||||||
|
'Flaming Disc of Inconvenience',
|
||||||
|
'Heal Bad Hygene',
|
||||||
|
'Heavenly Transfiguration of the Cream Devil',
|
||||||
|
'Hellish Cage of Mucus',
|
||||||
|
'Irritate Peanut Butter Fairy',
|
||||||
|
'Luminous Erruption of Tea',
|
||||||
|
'Mystic Spell of the Poser',
|
||||||
|
'Sorcerous Enchantment of the Chimneysweep',
|
||||||
|
'Steak Sauce Ray',
|
||||||
|
'Talk to Groupie',
|
||||||
|
'Astonishing Chant of Chocolate',
|
||||||
|
'Astounding Pasta Puddle',
|
||||||
|
'Ball of Annoyance',
|
||||||
|
'Cage of Yarn',
|
||||||
|
'Control Noodles Elemental',
|
||||||
|
'Create Nervousness',
|
||||||
|
'Cure Baldness',
|
||||||
|
'Cursed Ritual of Bad Hair',
|
||||||
|
'Dispell Piles in Dentist',
|
||||||
|
'Eliminate Florists',
|
||||||
|
'Illusionary Transfiguration of the Babysitter',
|
||||||
|
'Necromantic Armor of Salad Dressing',
|
||||||
|
'Occult Transfiguration of Foot Fetish',
|
||||||
|
'Protection from Mucus Giant',
|
||||||
|
'Tinsel Blast',
|
||||||
|
'Alchemical Evocation of the Goths',
|
||||||
|
'Call Fangirl',
|
||||||
|
'Divine Spell of Crossdressing',
|
||||||
|
'Dominate Ramen Giant',
|
||||||
|
'Eliminate Vindictiveness in Gym Teacher',
|
||||||
|
'Extra-Planar Spell of Irritation',
|
||||||
|
'Induce Whining in Babysitter',
|
||||||
|
'Invoke Complaining',
|
||||||
|
'Magical Enchantment of Arrogance',
|
||||||
|
'Occult Globe of Salad Dressing',
|
||||||
|
'Overwhelming Enchantment of the Chocolate Fairy',
|
||||||
|
'Sorcerous Dandruff Globe',
|
||||||
|
'Spiritual Invocation of the Costumers',
|
||||||
|
'Ultimate Rite of the Confetti Angel',
|
||||||
|
'Ultimate Ritual of Mouthwash',
|
||||||
|
];
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
spellList : function(){
|
||||||
|
const levels = ['Cantrips (0 Level)', '2nd Level', '3rd Level', '4th Level', '5th Level', '6th Level', '7th Level', '8th Level', '9th Level'];
|
||||||
|
|
||||||
|
const content = _.map(levels, (level)=>{
|
||||||
|
const spells = _.map(_.sampleSize(spellNames, _.random(5, 15)), (spell)=>{
|
||||||
|
return `- ${spell}`;
|
||||||
|
}).join('\n');
|
||||||
|
return `##### ${level} \n${spells} \n`;
|
||||||
|
}).join('\n');
|
||||||
|
|
||||||
|
return `<div class='spellList'>\n${content}\n</div>`;
|
||||||
|
},
|
||||||
|
|
||||||
|
spell : function(){
|
||||||
|
const level = ['1st', '2nd', '3rd', '4th', '5th', '6th', '7th', '8th', '9th'];
|
||||||
|
const spellSchools = ['abjuration', 'conjuration', 'divination', 'enchantment', 'evocation', 'illusion', 'necromancy', 'transmutation'];
|
||||||
|
|
||||||
|
|
||||||
|
let components = _.sampleSize(['V', 'S', 'M'], _.random(1, 3)).join(', ');
|
||||||
|
if(components.indexOf('M') !== -1){
|
||||||
|
components += ` (${_.sampleSize(['a small doll', 'a crushed button worth at least 1cp', 'discarded gum wrapper'], _.random(1, 3)).join(', ')})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
`#### ${_.sample(spellNames)}`,
|
||||||
|
`*${_.sample(level)}-level ${_.sample(spellSchools)}*`,
|
||||||
|
'___',
|
||||||
|
'- **Casting Time:** 1 action',
|
||||||
|
`- **Range:** ${_.sample(['Self', 'Touch', '30 feet', '60 feet'])}`,
|
||||||
|
`- **Components:** ${components}`,
|
||||||
|
`- **Duration:** ${_.sample(['Until dispelled', '1 round', 'Instantaneous', 'Concentration, up to 10 minutes', '1 hour'])}`,
|
||||||
|
'',
|
||||||
|
'A flame, equivalent in brightness to a torch, springs from from an object that you touch. ',
|
||||||
|
'The effect look like a regular flame, but it creates no heat and doesn\'t use oxygen. ',
|
||||||
|
'A *continual flame* can be covered or hidden but not smothered or quenched.',
|
||||||
|
'\n\n\n'
|
||||||
|
].join('\n');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
|
const genList = function(list, max){
|
||||||
|
return _.sampleSize(list, _.random(0, max)).join(', ') || 'None';
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMonsterName = function(){
|
||||||
|
return _.sample([
|
||||||
|
'All-devouring Baseball Imp',
|
||||||
|
'All-devouring Gumdrop Wraith',
|
||||||
|
'Chocolate Hydra',
|
||||||
|
'Devouring Peacock',
|
||||||
|
'Economy-sized Colossus of the Lemonade Stand',
|
||||||
|
'Ghost Pigeon',
|
||||||
|
'Gibbering Duck',
|
||||||
|
'Sparklemuffin Peacock Spider',
|
||||||
|
'Gum Elemental',
|
||||||
|
'Illiterate Construct of the Candy Store',
|
||||||
|
'Ineffable Chihuahua',
|
||||||
|
'Irritating Death Hamster',
|
||||||
|
'Irritating Gold Mouse',
|
||||||
|
'Juggernaut Snail',
|
||||||
|
'Juggernaut of the Sock Drawer',
|
||||||
|
'Koala of the Cosmos',
|
||||||
|
'Mad Koala of the West',
|
||||||
|
'Milk Djinni of the Lemonade Stand',
|
||||||
|
'Mind Ferret',
|
||||||
|
'Mystic Salt Spider',
|
||||||
|
'Necrotic Halitosis Angel',
|
||||||
|
'Pinstriped Famine Sheep',
|
||||||
|
'Ritalin Leech',
|
||||||
|
'Shocker Kangaroo',
|
||||||
|
'Stellar Tennis Juggernaut',
|
||||||
|
'Wailing Quail of the Sun',
|
||||||
|
'Angel Pigeon',
|
||||||
|
'Anime Sphinx',
|
||||||
|
'Bored Avalanche Sheep of the Wasteland',
|
||||||
|
'Devouring Nougat Sphinx of the Sock Drawer',
|
||||||
|
'Djinni of the Footlocker',
|
||||||
|
'Ectoplasmic Jazz Devil',
|
||||||
|
'Flatuent Angel',
|
||||||
|
'Gelatinous Duck of the Dream-Lands',
|
||||||
|
'Gelatinous Mouse',
|
||||||
|
'Golem of the Footlocker',
|
||||||
|
'Lich Wombat',
|
||||||
|
'Mechanical Sloth of the Past',
|
||||||
|
'Milkshake Succubus',
|
||||||
|
'Puffy Bone Peacock of the East',
|
||||||
|
'Rainbow Manatee',
|
||||||
|
'Rune Parrot',
|
||||||
|
'Sand Cow',
|
||||||
|
'Sinister Vanilla Dragon',
|
||||||
|
'Snail of the North',
|
||||||
|
'Spider of the Sewer',
|
||||||
|
'Stellar Sawdust Leech',
|
||||||
|
'Storm Anteater of Hell',
|
||||||
|
'Stupid Spirit of the Brewery',
|
||||||
|
'Time Kangaroo',
|
||||||
|
'Tomb Poodle',
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getType = function(){
|
||||||
|
return `${_.sample(['Tiny', 'Small', 'Medium', 'Large', 'Gargantuan', 'Stupidly vast'])} ${_.sample(['beast', 'fiend', 'annoyance', 'guy', 'cutie'])}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAlignment = function(){
|
||||||
|
return _.sample([
|
||||||
|
'annoying evil',
|
||||||
|
'chaotic gossipy',
|
||||||
|
'chaotic sloppy',
|
||||||
|
'depressed neutral',
|
||||||
|
'lawful bogus',
|
||||||
|
'lawful coy',
|
||||||
|
'manic-depressive evil',
|
||||||
|
'narrow-minded neutral',
|
||||||
|
'neutral annoying',
|
||||||
|
'neutral ignorant',
|
||||||
|
'oedpipal neutral',
|
||||||
|
'silly neutral',
|
||||||
|
'unoriginal neutral',
|
||||||
|
'weird neutral',
|
||||||
|
'wordy evil',
|
||||||
|
'unaligned'
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStats = function(){
|
||||||
|
return `>|${_.times(6, function(){
|
||||||
|
const num = _.random(1, 20);
|
||||||
|
const mod = Math.ceil(num/2 - 5);
|
||||||
|
return `${num} (${mod >= 0 ? `+${mod}` : mod})`;
|
||||||
|
}).join('|')}|`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const genAbilities = function(){
|
||||||
|
return _.sample([
|
||||||
|
'> ***Pack Tactics.*** These guys work together. Like super well, you don\'t even know.',
|
||||||
|
'> ***Fowl Appearance.*** While the creature remains motionless, it is indistinguishable from a normal chicken.',
|
||||||
|
'> ***Onion Stench.*** Any creatures within 5 feet of this thing develops an irrational craving for onion rings.',
|
||||||
|
'> ***Enormous Nose.*** This creature gains advantage on any check involving putting things in its nose.',
|
||||||
|
'> ***Sassiness.*** When questioned, this creature will talk back instead of answering.',
|
||||||
|
'> ***Big Jerk.*** Thinks he is just *waaaay* better than you.',
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const genAction = function(){
|
||||||
|
const name = _.sample([
|
||||||
|
'Abdominal Drop',
|
||||||
|
'Airplane Hammer',
|
||||||
|
'Atomic Death Throw',
|
||||||
|
'Bulldog Rake',
|
||||||
|
'Corkscrew Strike',
|
||||||
|
'Crossed Splash',
|
||||||
|
'Crossface Suplex',
|
||||||
|
'DDT Powerbomb',
|
||||||
|
'Dual Cobra Wristlock',
|
||||||
|
'Dual Throw',
|
||||||
|
'Elbow Hold',
|
||||||
|
'Gory Body Sweep',
|
||||||
|
'Heel Jawbreaker',
|
||||||
|
'Jumping Driver',
|
||||||
|
'Open Chin Choke',
|
||||||
|
'Scorpion Flurry',
|
||||||
|
'Somersault Stump Fists',
|
||||||
|
'Suffering Wringer',
|
||||||
|
'Super Hip Submission',
|
||||||
|
'Super Spin',
|
||||||
|
'Team Elbow',
|
||||||
|
'Team Foot',
|
||||||
|
'Tilt-a-whirl Chin Sleeper',
|
||||||
|
'Tilt-a-whirl Eye Takedown',
|
||||||
|
'Turnbuckle Roll'
|
||||||
|
]);
|
||||||
|
|
||||||
|
return `> ***${name}.*** *Melee Weapon Attack:* +4 to hit, reach 5ft., one target. *Hit* 5 (1d6 + 2) `;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
full : function(){
|
||||||
|
return `${[
|
||||||
|
'___',
|
||||||
|
'___',
|
||||||
|
`> ## ${getMonsterName()}`,
|
||||||
|
`>*${getType()}, ${getAlignment()}*`,
|
||||||
|
'> ___',
|
||||||
|
`> - **Armor Class** ${_.random(10, 20)}`,
|
||||||
|
`> - **Hit Points** ${_.random(1, 150)}(1d4 + 5)`,
|
||||||
|
`> - **Speed** ${_.random(0, 50)}ft.`,
|
||||||
|
'>___',
|
||||||
|
'>|STR|DEX|CON|INT|WIS|CHA|',
|
||||||
|
'>|:---:|:---:|:---:|:---:|:---:|:---:|',
|
||||||
|
getStats(),
|
||||||
|
'>___',
|
||||||
|
`> - **Condition Immunities** ${genList(['groggy', 'swagged', 'weak-kneed', 'buzzed', 'groovy', 'melancholy', 'drunk'], 3)}`,
|
||||||
|
`> - **Senses** passive Perception ${_.random(3, 20)}`,
|
||||||
|
`> - **Languages** ${genList(['Common', 'Pottymouth', 'Gibberish', 'Latin', 'Jive'], 2)}`,
|
||||||
|
`> - **Challenge** ${_.random(0, 15)} (${_.random(10, 10000)} XP)`,
|
||||||
|
'> ___',
|
||||||
|
_.times(_.random(3, 6), function(){
|
||||||
|
return genAbilities();
|
||||||
|
}).join('\n>\n'),
|
||||||
|
'> ### Actions',
|
||||||
|
_.times(_.random(4, 6), function(){
|
||||||
|
return genAction();
|
||||||
|
}).join('\n>\n'),
|
||||||
|
].join('\n')}\n\n\n`;
|
||||||
|
},
|
||||||
|
|
||||||
|
half : function(){
|
||||||
|
return `${[
|
||||||
|
'___',
|
||||||
|
`> ## ${getMonsterName()}`,
|
||||||
|
`>*${getType()}, ${getAlignment()}*`,
|
||||||
|
'> ___',
|
||||||
|
`> - **Armor Class** ${_.random(10, 20)}`,
|
||||||
|
`> - **Hit Points** ${_.random(1, 150)}(1d4 + 5)`,
|
||||||
|
`> - **Speed** ${_.random(0, 50)}ft.`,
|
||||||
|
'>___',
|
||||||
|
'>|STR|DEX|CON|INT|WIS|CHA|',
|
||||||
|
'>|:---:|:---:|:---:|:---:|:---:|:---:|',
|
||||||
|
getStats(),
|
||||||
|
'>___',
|
||||||
|
`> - **Condition Immunities** ${genList(['groggy', 'swagged', 'weak-kneed', 'buzzed', 'groovy', 'melancholy', 'drunk'], 3)}`,
|
||||||
|
`> - **Senses** passive Perception ${_.random(3, 20)}`,
|
||||||
|
`> - **Languages** ${genList(['Common', 'Pottymouth', 'Gibberish', 'Latin', 'Jive'], 2)}`,
|
||||||
|
`> - **Challenge** ${_.random(0, 15)} (${_.random(10, 10000)} XP)`,
|
||||||
|
'> ___',
|
||||||
|
_.times(_.random(2, 3), function(){
|
||||||
|
return genAbilities();
|
||||||
|
}).join('\n>\n'),
|
||||||
|
'> ### Actions',
|
||||||
|
_.times(_.random(1, 2), function(){
|
||||||
|
return genAction();
|
||||||
|
}).join('\n>\n'),
|
||||||
|
].join('\n')}\n\n\n`;
|
||||||
|
}
|
||||||
|
};
|
||||||
268
client/homebrew/editor/snippetbar/snippetsLegacy/snippets.js
Normal file
268
client/homebrew/editor/snippetbar/snippetsLegacy/snippets.js
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
/* eslint-disable max-lines */
|
||||||
|
|
||||||
|
const MagicGen = require('./magic.gen.js');
|
||||||
|
const ClassTableGen = require('./classtable.gen.js');
|
||||||
|
const MonsterBlockGen = require('./monsterblock.gen.js');
|
||||||
|
const ClassFeatureGen = require('./classfeature.gen.js');
|
||||||
|
const CoverPageGen = require('./coverpage.gen.js');
|
||||||
|
const TableOfContentsGen = require('./tableOfContents.gen.js');
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
|
||||||
|
{
|
||||||
|
groupName : 'Editor',
|
||||||
|
icon : 'fas fa-pencil-alt',
|
||||||
|
snippets : [
|
||||||
|
{
|
||||||
|
name : 'Column Break',
|
||||||
|
icon : 'fas fa-columns',
|
||||||
|
gen : '```\n```\n\n'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'New Page',
|
||||||
|
icon : 'fas fa-file-alt',
|
||||||
|
gen : '\\page\n\n'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Vertical Spacing',
|
||||||
|
icon : 'fas fa-arrows-alt-v',
|
||||||
|
gen : '<div style=\'margin-top:140px\'></div>\n\n'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Wide Block',
|
||||||
|
icon : 'fas fa-arrows-alt-h',
|
||||||
|
gen : '<div class=\'wide\'>\nEverything in here will be extra wide. Tables, text, everything! Beware though, CSS columns can behave a bit weird sometimes.\n</div>\n'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Image',
|
||||||
|
icon : 'fas fa-image',
|
||||||
|
gen : [
|
||||||
|
'<img ',
|
||||||
|
' src=\'https://s-media-cache-ak0.pinimg.com/736x/4a/81/79/4a8179462cfdf39054a418efd4cb743e.jpg\' ',
|
||||||
|
' style=\'width:325px\' />',
|
||||||
|
'Credit: Kyounghwan Kim'
|
||||||
|
].join('\n')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Background Image',
|
||||||
|
icon : 'fas fa-tree',
|
||||||
|
gen : [
|
||||||
|
'<img ',
|
||||||
|
' src=\'http://i.imgur.com/hMna6G0.png\' ',
|
||||||
|
' style=\'position:absolute; top:50px; right:30px; width:280px\' />'
|
||||||
|
].join('\n')
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name : 'Page Number',
|
||||||
|
icon : 'fas fa-bookmark',
|
||||||
|
gen : '<div class=\'pageNumber\'>1</div>\n<div class=\'footnote\'>PART 1 | FANCINESS</div>\n\n'
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name : 'Auto-incrementing Page Number',
|
||||||
|
icon : 'fas fa-sort-numeric-down',
|
||||||
|
gen : '<div class=\'pageNumber auto\'></div>\n'
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name : 'Link to page',
|
||||||
|
icon : 'fas fa-link',
|
||||||
|
gen : '[Click here](#p3) to go to page 3\n'
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name : 'Table of Contents',
|
||||||
|
icon : 'fas fa-book',
|
||||||
|
gen : TableOfContentsGen
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/************************* PHB ********************/
|
||||||
|
|
||||||
|
{
|
||||||
|
groupName : 'PHB',
|
||||||
|
icon : 'fas fa-book',
|
||||||
|
snippets : [
|
||||||
|
{
|
||||||
|
name : 'Spell',
|
||||||
|
icon : 'fas fa-magic',
|
||||||
|
gen : MagicGen.spell,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Spell List',
|
||||||
|
icon : 'fas fa-list',
|
||||||
|
gen : MagicGen.spellList,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Class Feature',
|
||||||
|
icon : 'fas fa-trophy',
|
||||||
|
gen : ClassFeatureGen,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Note',
|
||||||
|
icon : 'fas fa-sticky-note',
|
||||||
|
gen : function(){
|
||||||
|
return [
|
||||||
|
'> ##### Time to Drop Knowledge',
|
||||||
|
'> Use notes to point out some interesting information. ',
|
||||||
|
'> ',
|
||||||
|
'> **Tables and lists** both work within a note.'
|
||||||
|
].join('\n');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Descriptive Text Box',
|
||||||
|
icon : 'far fa-sticky-note',
|
||||||
|
gen : function(){
|
||||||
|
return [
|
||||||
|
'<div class=\'descriptive\'>',
|
||||||
|
'##### Time to Drop Knowledge',
|
||||||
|
'Use notes to point out some interesting information. ',
|
||||||
|
'',
|
||||||
|
'**Tables and lists** both work within a note.',
|
||||||
|
'</div>'
|
||||||
|
].join('\n');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Monster Stat Block',
|
||||||
|
icon : 'fas fa-bug',
|
||||||
|
gen : MonsterBlockGen.half,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Wide Monster Stat Block',
|
||||||
|
icon : 'fas fa-paw',
|
||||||
|
gen : MonsterBlockGen.full,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Cover Page',
|
||||||
|
icon : 'far fa-file-word',
|
||||||
|
gen : CoverPageGen,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/********************* TABLES *********************/
|
||||||
|
|
||||||
|
{
|
||||||
|
groupName : 'Tables',
|
||||||
|
icon : 'fas fa-table',
|
||||||
|
snippets : [
|
||||||
|
{
|
||||||
|
name : 'Class Table',
|
||||||
|
icon : 'fas fa-table',
|
||||||
|
gen : ClassTableGen.full,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Half Class Table',
|
||||||
|
icon : 'fas fa-list-alt',
|
||||||
|
gen : ClassTableGen.half,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Table',
|
||||||
|
icon : 'fas fa-th-list',
|
||||||
|
gen : function(){
|
||||||
|
return [
|
||||||
|
'##### Cookie Tastiness',
|
||||||
|
'| Tastiness | Cookie Type |',
|
||||||
|
'|:----:|:-------------|',
|
||||||
|
'| -5 | Raisin |',
|
||||||
|
'| 8th | Chocolate Chip |',
|
||||||
|
'| 11th | 2 or lower |',
|
||||||
|
'| 14th | 3 or lower |',
|
||||||
|
'| 17th | 4 or lower |\n\n',
|
||||||
|
].join('\n');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Wide Table',
|
||||||
|
icon : 'fas fa-list',
|
||||||
|
gen : function(){
|
||||||
|
return [
|
||||||
|
'<div class=\'wide\'>',
|
||||||
|
'##### Cookie Tastiness',
|
||||||
|
'| Tastiness | Cookie Type |',
|
||||||
|
'|:----:|:-------------|',
|
||||||
|
'| -5 | Raisin |',
|
||||||
|
'| 8th | Chocolate Chip |',
|
||||||
|
'| 11th | 2 or lower |',
|
||||||
|
'| 14th | 3 or lower |',
|
||||||
|
'| 17th | 4 or lower |',
|
||||||
|
'</div>\n\n'
|
||||||
|
].join('\n');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Split Table',
|
||||||
|
icon : 'fas fa-th-large',
|
||||||
|
gen : function(){
|
||||||
|
return [
|
||||||
|
'<div style=\'column-count:2\'>',
|
||||||
|
'| d10 | Damage Type |',
|
||||||
|
'|:---:|:------------|',
|
||||||
|
'| 1 | Acid |',
|
||||||
|
'| 2 | Cold |',
|
||||||
|
'| 3 | Fire |',
|
||||||
|
'| 4 | Force |',
|
||||||
|
'| 5 | Lightning |',
|
||||||
|
'',
|
||||||
|
'```',
|
||||||
|
'```',
|
||||||
|
'',
|
||||||
|
'| d10 | Damage Type |',
|
||||||
|
'|:---:|:------------|',
|
||||||
|
'| 6 | Necrotic |',
|
||||||
|
'| 7 | Poison |',
|
||||||
|
'| 8 | Psychic |',
|
||||||
|
'| 9 | Radiant |',
|
||||||
|
'| 10 | Thunder |',
|
||||||
|
'</div>\n\n',
|
||||||
|
].join('\n');
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**************** PRINT *************/
|
||||||
|
|
||||||
|
{
|
||||||
|
groupName : 'Print',
|
||||||
|
icon : 'fas fa-print',
|
||||||
|
snippets : [
|
||||||
|
{
|
||||||
|
name : 'A4 PageSize',
|
||||||
|
icon : 'far fa-file',
|
||||||
|
gen : ['<style>',
|
||||||
|
' .phb{',
|
||||||
|
' width : 210mm;',
|
||||||
|
' height : 296.8mm;',
|
||||||
|
' }',
|
||||||
|
'</style>'
|
||||||
|
].join('\n')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'Ink Friendly',
|
||||||
|
icon : 'fas fa-tint',
|
||||||
|
gen : ['<style>',
|
||||||
|
' .phb{ background : white;}',
|
||||||
|
' .phb img{ display : none;}',
|
||||||
|
' .phb hr+blockquote{background : white;}',
|
||||||
|
'</style>',
|
||||||
|
''
|
||||||
|
].join('\n')
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
];
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
|
const getTOC = (pages)=>{
|
||||||
|
const add1 = (title, page)=>{
|
||||||
|
res.push({
|
||||||
|
title : title,
|
||||||
|
page : page + 1,
|
||||||
|
children : []
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const add2 = (title, page)=>{
|
||||||
|
if(!_.last(res)) add1('', page);
|
||||||
|
_.last(res).children.push({
|
||||||
|
title : title,
|
||||||
|
page : page + 1,
|
||||||
|
children : []
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const add3 = (title, page)=>{
|
||||||
|
if(!_.last(res)) add1('', page);
|
||||||
|
if(!_.last(_.last(res).children)) add2('', page);
|
||||||
|
_.last(_.last(res).children).children.push({
|
||||||
|
title : title,
|
||||||
|
page : page + 1,
|
||||||
|
children : []
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = [];
|
||||||
|
_.each(pages, (page, pageNum)=>{
|
||||||
|
const lines = page.split('\n');
|
||||||
|
_.each(lines, (line)=>{
|
||||||
|
if(_.startsWith(line, '# ')){
|
||||||
|
const title = line.replace('# ', '');
|
||||||
|
add1(title, pageNum);
|
||||||
|
}
|
||||||
|
if(_.startsWith(line, '## ')){
|
||||||
|
const title = line.replace('## ', '');
|
||||||
|
add2(title, pageNum);
|
||||||
|
}
|
||||||
|
if(_.startsWith(line, '### ')){
|
||||||
|
const title = line.replace('### ', '');
|
||||||
|
add3(title, pageNum);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = function(brew){
|
||||||
|
const pages = brew.split('\\page');
|
||||||
|
const TOC = getTOC(pages);
|
||||||
|
const markdown = _.reduce(TOC, (r, g1, idx1)=>{
|
||||||
|
r.push(`- **[${idx1 + 1} ${g1.title}](#p${g1.page})**`);
|
||||||
|
if(g1.children.length){
|
||||||
|
_.each(g1.children, (g2, idx2)=>{
|
||||||
|
r.push(` - [${idx1 + 1}.${idx2 + 1} ${g2.title}](#p${g2.page})`);
|
||||||
|
if(g2.children.length){
|
||||||
|
_.each(g2.children, (g3, idx3)=>{
|
||||||
|
r.push(` - [${idx1 + 1}.${idx2 + 1}.${idx3 + 1} ${g3.title}](#p${g3.page})`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}, []).join('\n');
|
||||||
|
|
||||||
|
return `<div class='toc'>
|
||||||
|
##### Table Of Contents
|
||||||
|
${markdown}
|
||||||
|
</div>\n`;
|
||||||
|
};
|
||||||
@@ -20,6 +20,7 @@ const Homebrew = createClass({
|
|||||||
changelog : '',
|
changelog : '',
|
||||||
version : '0.0.0',
|
version : '0.0.0',
|
||||||
account : null,
|
account : null,
|
||||||
|
enable_v3 : false,
|
||||||
brew : {
|
brew : {
|
||||||
title : '',
|
title : '',
|
||||||
text : '',
|
text : '',
|
||||||
@@ -33,6 +34,7 @@ const Homebrew = createClass({
|
|||||||
componentWillMount : function() {
|
componentWillMount : function() {
|
||||||
global.account = this.props.account;
|
global.account = this.props.account;
|
||||||
global.version = this.props.version;
|
global.version = this.props.version;
|
||||||
|
global.enable_v3 = this.props.enable_v3;
|
||||||
|
|
||||||
},
|
},
|
||||||
render : function (){
|
render : function (){
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ const Account = createClass({
|
|||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
if(global.account){
|
if(global.account){
|
||||||
return <Nav.item href={`/user/${global.account.username}`} color='yellow' icon='fa-user'>
|
return <Nav.item href={`/user/${global.account.username}`} color='yellow' icon='fas fa-user'>
|
||||||
{global.account.username}
|
{global.account.username}
|
||||||
</Nav.item>;
|
</Nav.item>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Nav.item href={`http://naturalcrit.com/login?redirect=${this.state.url}`} color='teal' icon='fa-sign-in'>
|
return <Nav.item href={`http://naturalcrit.com/login?redirect=${this.state.url}`} color='teal' icon='fas fa-sign-in-alt'>
|
||||||
login
|
login
|
||||||
</Nav.item>;
|
</Nav.item>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ module.exports = function(props){
|
|||||||
return <Nav.item
|
return <Nav.item
|
||||||
newTab={true}
|
newTab={true}
|
||||||
color='red'
|
color='red'
|
||||||
icon='fa-bug'
|
icon='fas fa-bug'
|
||||||
href={`https://www.reddit.com/r/homebrewery/submit?selftext=true&title=${encodeURIComponent('[Issue] Describe Your Issue Here')}`} >
|
href={`https://www.reddit.com/r/homebrewery/submit?selftext=true&title=${encodeURIComponent('[Issue] Describe Your Issue Here')}`} >
|
||||||
report issue
|
report issue
|
||||||
</Nav.item>;
|
</Nav.item>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ module.exports = function(props){
|
|||||||
newTab={true}
|
newTab={true}
|
||||||
href='https://www.patreon.com/NaturalCrit'
|
href='https://www.patreon.com/NaturalCrit'
|
||||||
color='green'
|
color='green'
|
||||||
icon='fa-heart'>
|
icon='fas fa-heart'>
|
||||||
help out
|
help out
|
||||||
</Nav.item>;
|
</Nav.item>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ const createClass = require('create-react-class');
|
|||||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||||
|
|
||||||
module.exports = function(props){
|
module.exports = function(props){
|
||||||
return <Nav.item newTab={true} href={`/print/${props.shareId}?dialog=true`} color='purple' icon='fa-file-pdf-o'>
|
return <Nav.item newTab={true} href={`/print/${props.shareId}?dialog=true`} color='purple' icon='far fa-file-pdf'>
|
||||||
get PDF
|
get PDF
|
||||||
</Nav.item>;
|
</Nav.item>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ const RecentItems = createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
return <Nav.item icon='fa-clock-o' color='grey' className='recent'
|
return <Nav.item icon='fas fa-history' color='grey' className='recent'
|
||||||
onMouseEnter={()=>this.handleDropdown(true)}
|
onMouseEnter={()=>this.handleDropdown(true)}
|
||||||
onMouseLeave={()=>this.handleDropdown(false)}>
|
onMouseLeave={()=>this.handleDropdown(false)}>
|
||||||
{this.props.text}
|
{this.props.text}
|
||||||
|
|||||||
@@ -41,17 +41,18 @@ const EditPage = createClass({
|
|||||||
tags : '',
|
tags : '',
|
||||||
published : false,
|
published : false,
|
||||||
authors : [],
|
authors : [],
|
||||||
systems : []
|
systems : [],
|
||||||
|
renderer : 'legacy'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState : function() {
|
getInitialState : function() {
|
||||||
return {
|
return {
|
||||||
brew : this.props.brew,
|
brew : this.props.brew,
|
||||||
|
|
||||||
isSaving : false,
|
isSaving : false,
|
||||||
isPending : false,
|
isPending : false,
|
||||||
|
alertRenderChange : false,
|
||||||
saveGoogle : this.props.brew.googleId ? true : false,
|
saveGoogle : this.props.brew.googleId ? true : false,
|
||||||
confirmGoogleTransfer : false,
|
confirmGoogleTransfer : false,
|
||||||
errors : null,
|
errors : null,
|
||||||
@@ -66,6 +67,8 @@ const EditPage = createClass({
|
|||||||
url : window.location.href
|
url : window.location.href
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.savedBrew = JSON.parse(JSON.stringify(this.props.brew)); //Deep copy
|
||||||
|
|
||||||
this.trySave();
|
this.trySave();
|
||||||
window.onbeforeunload = ()=>{
|
window.onbeforeunload = ()=>{
|
||||||
if(this.state.isSaving || this.state.isPending){
|
if(this.state.isSaving || this.state.isPending){
|
||||||
@@ -101,6 +104,11 @@ const EditPage = createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
handleMetadataChange : function(metadata){
|
handleMetadataChange : function(metadata){
|
||||||
|
if(metadata.renderer != this.savedBrew.renderer){
|
||||||
|
this.setState({
|
||||||
|
alertRenderChange : true
|
||||||
|
});
|
||||||
|
}
|
||||||
this.setState((prevState)=>({
|
this.setState((prevState)=>({
|
||||||
brew : _.merge({}, prevState.brew, metadata),
|
brew : _.merge({}, prevState.brew, metadata),
|
||||||
isPending : true,
|
isPending : true,
|
||||||
@@ -122,8 +130,7 @@ const EditPage = createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
hasChanges : function(){
|
hasChanges : function(){
|
||||||
const savedBrew = this.savedBrew ? this.savedBrew : this.props.brew;
|
return !_.isEqual(this.state.brew, this.savedBrew);
|
||||||
return !_.isEqual(this.state.brew, savedBrew);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
trySave : function(){
|
trySave : function(){
|
||||||
@@ -142,6 +149,12 @@ const EditPage = createClass({
|
|||||||
this.clearErrors();
|
this.clearErrors();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
closeAlerts : function(){
|
||||||
|
this.setState({
|
||||||
|
alertRenderChange : false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
toggleGoogleStorage : function(){
|
toggleGoogleStorage : function(){
|
||||||
this.setState((prevState)=>({
|
this.setState((prevState)=>({
|
||||||
saveGoogle : !prevState.saveGoogle,
|
saveGoogle : !prevState.saveGoogle,
|
||||||
@@ -294,7 +307,7 @@ const EditPage = createClass({
|
|||||||
} catch (e){}
|
} catch (e){}
|
||||||
|
|
||||||
if(this.state.errors.status == '401'){
|
if(this.state.errors.status == '401'){
|
||||||
return <Nav.item className='save error' icon='fa-warning'>
|
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||||
Oops!
|
Oops!
|
||||||
<div className='errorContainer' onClick={this.clearErrors}>
|
<div className='errorContainer' onClick={this.clearErrors}>
|
||||||
You must be signed in to a Google account
|
You must be signed in to a Google account
|
||||||
@@ -312,7 +325,7 @@ const EditPage = createClass({
|
|||||||
</Nav.item>;
|
</Nav.item>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Nav.item className='save error' icon='fa-warning'>
|
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||||
Oops!
|
Oops!
|
||||||
<div className='errorContainer'>
|
<div className='errorContainer'>
|
||||||
Looks like there was a problem saving. <br />
|
Looks like there was a problem saving. <br />
|
||||||
@@ -325,16 +338,25 @@ const EditPage = createClass({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(this.state.isSaving){
|
if(this.state.isSaving){
|
||||||
return <Nav.item className='save' icon='fa-spinner fa-spin'>saving...</Nav.item>;
|
return <Nav.item className='save' icon='fas fa-spinner fa-spin'>saving...</Nav.item>;
|
||||||
}
|
}
|
||||||
if(this.state.isPending && this.hasChanges()){
|
if(this.state.isPending && this.hasChanges()){
|
||||||
return <Nav.item className='save' onClick={this.save} color='blue' icon='fa-save'>Save Now</Nav.item>;
|
return <Nav.item className='save' onClick={this.save} color='blue' icon='fas fa-save'>Save Now</Nav.item>;
|
||||||
}
|
}
|
||||||
if(!this.state.isPending && !this.state.isSaving){
|
if(!this.state.isPending && !this.state.isSaving){
|
||||||
return <Nav.item className='save saved'>saved.</Nav.item>;
|
return <Nav.item className='save saved'>saved.</Nav.item>;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// {this.state.alertRenderChange &&
|
||||||
|
// <div className='errorContainer' onClick={this.closeAlerts}>
|
||||||
|
// Rendering mode for this brew has been changed! Refresh the page to load the new renderer.<br />
|
||||||
|
// <div className='confirm'>
|
||||||
|
// OK
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// }
|
||||||
|
|
||||||
processShareId : function() {
|
processShareId : function() {
|
||||||
return this.state.brew.googleId ?
|
return this.state.brew.googleId ?
|
||||||
this.state.brew.googleId + this.state.brew.shareId :
|
this.state.brew.googleId + this.state.brew.shareId :
|
||||||
@@ -351,7 +373,7 @@ const EditPage = createClass({
|
|||||||
{this.renderGoogleDriveIcon()}
|
{this.renderGoogleDriveIcon()}
|
||||||
{this.renderSaveButton()}
|
{this.renderSaveButton()}
|
||||||
<ReportIssue />
|
<ReportIssue />
|
||||||
<Nav.item newTab={true} href={`/share/${this.processShareId()}`} color='teal' icon='fa-share-alt'>
|
<Nav.item newTab={true} href={`/share/${this.processShareId()}`} color='teal' icon='fas fa-share-alt'>
|
||||||
Share
|
Share
|
||||||
</Nav.item>
|
</Nav.item>
|
||||||
<PrintLink shareId={this.processShareId()} />
|
<PrintLink shareId={this.processShareId()} />
|
||||||
@@ -374,8 +396,9 @@ const EditPage = createClass({
|
|||||||
onChange={this.handleTextChange}
|
onChange={this.handleTextChange}
|
||||||
metadata={this.state.brew}
|
metadata={this.state.brew}
|
||||||
onMetadataChange={this.handleMetadataChange}
|
onMetadataChange={this.handleMetadataChange}
|
||||||
|
renderer={this.state.brew.renderer}
|
||||||
/>
|
/>
|
||||||
<BrewRenderer text={this.state.brew.text} errors={this.state.htmlErrors} />
|
<BrewRenderer text={this.state.brew.text} errors={this.state.htmlErrors} renderer={this.state.brew.renderer} />
|
||||||
</SplitPane>
|
</SplitPane>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
|
@keyframes glideDown {
|
||||||
|
0% {transform : translate(-50% + 3px, 0px);
|
||||||
|
opacity : 0;}
|
||||||
|
100% {transform : translate(-50% + 3px, 10px);
|
||||||
|
opacity : 1;}
|
||||||
|
}
|
||||||
.editPage{
|
.editPage{
|
||||||
.navItem.save{
|
.navItem.save{
|
||||||
width : 106px;
|
width : 106px;
|
||||||
text-align : center;
|
text-align : center;
|
||||||
|
position : relative;
|
||||||
&.saved{
|
&.saved{
|
||||||
cursor : initial;
|
cursor : initial;
|
||||||
color : #666;
|
color : #666;
|
||||||
@@ -21,12 +27,15 @@
|
|||||||
margin : -5px;
|
margin : -5px;
|
||||||
}
|
}
|
||||||
.errorContainer{
|
.errorContainer{
|
||||||
|
animation-name: glideDown;
|
||||||
|
animation-duration: 0.4s;
|
||||||
position : absolute;
|
position : absolute;
|
||||||
top : 100%;
|
top : 100%;
|
||||||
left : 50%;
|
left : 50%;
|
||||||
z-index : 1000;
|
z-index : 100000;
|
||||||
width : 140px;
|
width : 140px;
|
||||||
padding : 3px;
|
padding : 3px;
|
||||||
|
color : white;
|
||||||
background-color : #333;
|
background-color : #333;
|
||||||
border : 3px solid #444;
|
border : 3px solid #444;
|
||||||
border-radius : 5px;
|
border-radius : 5px;
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ const HomePage = createClass({
|
|||||||
return <Navbar ver={this.props.ver}>
|
return <Navbar ver={this.props.ver}>
|
||||||
<Nav.section>
|
<Nav.section>
|
||||||
<IssueNavItem />
|
<IssueNavItem />
|
||||||
<Nav.item newTab={true} href='/changelog' color='purple' icon='fa-file-text-o'>
|
<Nav.item newTab={true} href='/changelog' color='purple' icon='far fa-file-alt'>
|
||||||
Changelog
|
Changelog
|
||||||
</Nav.item>
|
</Nav.item>
|
||||||
<RecentNavItem />
|
<RecentNavItem />
|
||||||
@@ -77,11 +77,11 @@ const HomePage = createClass({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={cx('floatingSaveButton', { show: this.props.welcomeText != this.state.text })} onClick={this.handleSave}>
|
<div className={cx('floatingSaveButton', { show: this.props.welcomeText != this.state.text })} onClick={this.handleSave}>
|
||||||
Save current <i className='fa fa-save' />
|
Save current <i className='fas fa-save' />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a href='/new' className='floatingNewButton'>
|
<a href='/new' className='floatingNewButton'>
|
||||||
Create your own <i className='fa fa-magic' />
|
Create your own <i className='fas fa-magic' />
|
||||||
</a>
|
</a>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,11 +127,11 @@ const NewPage = createClass({
|
|||||||
|
|
||||||
renderSaveButton : function(){
|
renderSaveButton : function(){
|
||||||
if(this.state.isSaving){
|
if(this.state.isSaving){
|
||||||
return <Nav.item icon='fa-spinner fa-spin' className='saveButton'>
|
return <Nav.item icon='fas fa-spinner fa-spin' className='saveButton'>
|
||||||
save...
|
save...
|
||||||
</Nav.item>;
|
</Nav.item>;
|
||||||
} else {
|
} else {
|
||||||
return <Nav.item icon='fa-save' className='saveButton' onClick={this.save}>
|
return <Nav.item icon='fas fa-save' className='saveButton' onClick={this.save}>
|
||||||
save
|
save
|
||||||
</Nav.item>;
|
</Nav.item>;
|
||||||
}
|
}
|
||||||
@@ -143,7 +143,7 @@ const NewPage = createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
renderLocalPrintButton : function(){
|
renderLocalPrintButton : function(){
|
||||||
return <Nav.item color='purple' icon='fa-file-pdf-o' onClick={this.print}>
|
return <Nav.item color='purple' icon='far fa-file-pdf' onClick={this.print}>
|
||||||
get PDF
|
get PDF
|
||||||
</Nav.item>;
|
</Nav.item>;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const createClass = require('create-react-class');
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const cx = require('classnames');
|
const cx = require('classnames');
|
||||||
const { Meta } = require('vitreum/headtags');
|
const { Meta } = require('vitreum/headtags');
|
||||||
|
const MarkdownLegacy = require('naturalcrit/markdownLegacy.js');
|
||||||
const Markdown = require('naturalcrit/markdown.js');
|
const Markdown = require('naturalcrit/markdown.js');
|
||||||
|
|
||||||
const PrintPage = createClass({
|
const PrintPage = createClass({
|
||||||
@@ -11,7 +12,8 @@ const PrintPage = createClass({
|
|||||||
return {
|
return {
|
||||||
query : {},
|
query : {},
|
||||||
brew : {
|
brew : {
|
||||||
text : '',
|
text : '',
|
||||||
|
renderer : 'legacy'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -33,13 +35,24 @@ const PrintPage = createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
renderPages : function(){
|
renderPages : function(){
|
||||||
return _.map(this.state.brewText.split('\\page'), (page, index)=>{
|
if(this.props.brew.renderer == 'legacy') {
|
||||||
return <div
|
return _.map(this.state.brewText.split('\\page'), (page, index)=>{
|
||||||
className='phb'
|
return <div
|
||||||
id={`p${index + 1}`}
|
className='phb'
|
||||||
dangerouslySetInnerHTML={{ __html: Markdown.render(page) }}
|
id={`p${index + 1}`}
|
||||||
key={index} />;
|
dangerouslySetInnerHTML={{ __html: MarkdownLegacy.render(page) }}
|
||||||
});
|
key={index} />;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return _.map(this.state.brewText.split(/^\\page/gm), (page, index)=>{
|
||||||
|
return <div
|
||||||
|
className='phb3'
|
||||||
|
id={`p${index + 1}`}
|
||||||
|
dangerouslySetInnerHTML={{ __html: Markdown.render(page) }}
|
||||||
|
key={index} />;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ const SharePage = createClass({
|
|||||||
shareId : null,
|
shareId : null,
|
||||||
createdAt : null,
|
createdAt : null,
|
||||||
updatedAt : null,
|
updatedAt : null,
|
||||||
views : 0
|
views : 0,
|
||||||
|
renderer : ''
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -59,7 +60,7 @@ const SharePage = createClass({
|
|||||||
|
|
||||||
<Nav.section>
|
<Nav.section>
|
||||||
<PrintLink shareId={this.processShareId()} />
|
<PrintLink shareId={this.processShareId()} />
|
||||||
<Nav.item href={`/source/${this.processShareId()}`} color='teal' icon='fa-code'>
|
<Nav.item href={`/source/${this.processShareId()}`} color='teal' icon='fas fa-code'>
|
||||||
source
|
source
|
||||||
</Nav.item>
|
</Nav.item>
|
||||||
<RecentNavItem brew={this.props.brew} storageKey='view' />
|
<RecentNavItem brew={this.props.brew} storageKey='view' />
|
||||||
@@ -68,7 +69,7 @@ const SharePage = createClass({
|
|||||||
</Navbar>
|
</Navbar>
|
||||||
|
|
||||||
<div className='content'>
|
<div className='content'>
|
||||||
<BrewRenderer text={this.props.brew.text} />
|
<BrewRenderer text={this.props.brew.text} renderer={this.props.brew.renderer} />
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ const BrewItem = createClass({
|
|||||||
if(!this.props.brew.editId) return;
|
if(!this.props.brew.editId) return;
|
||||||
|
|
||||||
return <a onClick={this.deleteBrew}>
|
return <a onClick={this.deleteBrew}>
|
||||||
<i className='fa fa-trash' />
|
<i className='fas fa-trash-alt' />
|
||||||
</a>;
|
</a>;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ const BrewItem = createClass({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return <a href={`/edit/${editLink}`} target='_blank' rel='noopener noreferrer'>
|
return <a href={`/edit/${editLink}`} target='_blank' rel='noopener noreferrer'>
|
||||||
<i className='fa fa-pencil' />
|
<i className='fas fa-pencil-alt' />
|
||||||
</a>;
|
</a>;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ const BrewItem = createClass({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return <a href={`/share/${shareLink}`} target='_blank' rel='noopener noreferrer'>
|
return <a href={`/share/${shareLink}`} target='_blank' rel='noopener noreferrer'>
|
||||||
<i className='fa fa-share-alt' />
|
<i className='fas fa-share-alt' />
|
||||||
</a>;
|
</a>;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -95,13 +95,13 @@ const BrewItem = createClass({
|
|||||||
|
|
||||||
<div className='info'>
|
<div className='info'>
|
||||||
<span>
|
<span>
|
||||||
<i className='fa fa-user' /> {brew.authors.join(', ')}
|
<i className='fas fa-user' /> {brew.authors.join(', ')}
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
<i className='fa fa-eye' /> {brew.views}
|
<i className='fas fa-eye' /> {brew.views}
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
<i className='fa fa-refresh' /> {moment(brew.updatedAt).fromNow()}
|
<i className='fas fa-sync-alt' /> {moment(brew.updatedAt).fromNow()}
|
||||||
</span>
|
</span>
|
||||||
{this.renderGoogleDriveIcon()}
|
{this.renderGoogleDriveIcon()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -22,6 +22,9 @@
|
|||||||
font-size : 2.2em;
|
font-size : 2.2em;
|
||||||
}
|
}
|
||||||
.info{
|
.info{
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0px;
|
||||||
|
margin-bottom: 4px;
|
||||||
font-family : ScalySans;
|
font-family : ScalySans;
|
||||||
font-size : 1.2em;
|
font-size : 1.2em;
|
||||||
&>span{
|
&>span{
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ const UserPage = createClass({
|
|||||||
</Nav.section>
|
</Nav.section>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
|
|
||||||
<div className='content'>
|
<div className='content V3'>
|
||||||
<div className='phb'>
|
<div className='phb'>
|
||||||
<div>
|
<div>
|
||||||
<h1>{this.getUsernameWithS()} brews</h1>
|
<h1>{this.getUsernameWithS()} brews</h1>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
@import (less) 'shared/naturalcrit/styles/reset.less';
|
|
||||||
@import (less) './client/homebrew/phbStyle/phb.fonts.css';
|
@import (less) './client/homebrew/phbStyle/phb.fonts.css';
|
||||||
@import (less) './client/homebrew/phbStyle/phb.assets.less';
|
@import (less) './client/homebrew/phbStyle/phb.assets.less';
|
||||||
@import (less) './client/homebrew/phbStyle/phb.depricated.less';
|
|
||||||
//Colors
|
//Colors
|
||||||
@background : #EEE5CE;
|
@background : #EEE5CE;
|
||||||
@noteGreen : #e0e5c1;
|
@noteGreen : #e0e5c1;
|
||||||
@@ -18,12 +17,11 @@ body {
|
|||||||
}
|
}
|
||||||
.useSansSerif(){
|
.useSansSerif(){
|
||||||
font-family : ScalySans;
|
font-family : ScalySans;
|
||||||
|
font-size : 10pt;
|
||||||
em{
|
em{
|
||||||
font-family : ScalySans;
|
|
||||||
font-style : italic;
|
font-style : italic;
|
||||||
}
|
}
|
||||||
strong{
|
strong{
|
||||||
font-family : ScalySans;
|
|
||||||
font-weight : 800;
|
font-weight : 800;
|
||||||
letter-spacing : -0.02em;
|
letter-spacing : -0.02em;
|
||||||
}
|
}
|
||||||
@@ -40,7 +38,7 @@ body {
|
|||||||
-webkit-column-gap : 1cm;
|
-webkit-column-gap : 1cm;
|
||||||
-moz-column-gap : 1cm;
|
-moz-column-gap : 1cm;
|
||||||
}
|
}
|
||||||
.phb{
|
.phb3{
|
||||||
.useColumns();
|
.useColumns();
|
||||||
counter-increment : phb-page-numbers;
|
counter-increment : phb-page-numbers;
|
||||||
position : relative;
|
position : relative;
|
||||||
@@ -49,8 +47,7 @@ body {
|
|||||||
overflow : hidden;
|
overflow : hidden;
|
||||||
height : 279.4mm;
|
height : 279.4mm;
|
||||||
width : 215.9mm;
|
width : 215.9mm;
|
||||||
padding : 1.0cm 1.7cm;
|
padding : 1.0cm 1.7cm 1.5cm;
|
||||||
padding-bottom : 1.5cm;
|
|
||||||
background-color : @background;
|
background-color : @background;
|
||||||
background-image : @backgroundImage;
|
background-image : @backgroundImage;
|
||||||
font-family : BookSanity;
|
font-family : BookSanity;
|
||||||
@@ -62,6 +59,7 @@ body {
|
|||||||
// * BASE
|
// * BASE
|
||||||
// *****************************/
|
// *****************************/
|
||||||
p{
|
p{
|
||||||
|
overflow-wrap : break-word;
|
||||||
padding-bottom : 0.8em;
|
padding-bottom : 0.8em;
|
||||||
line-height : 1.3em;
|
line-height : 1.3em;
|
||||||
&+p{
|
&+p{
|
||||||
@@ -124,9 +122,20 @@ body {
|
|||||||
&+p::first-letter{
|
&+p::first-letter{
|
||||||
float : left;
|
float : left;
|
||||||
font-family : Solberry;
|
font-family : Solberry;
|
||||||
font-size : 10em;
|
|
||||||
color : #222;
|
|
||||||
line-height : 0.8em;
|
line-height : 0.8em;
|
||||||
|
font-size: 3.1cm;
|
||||||
|
padding-top: 4px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
margin-top: -6px;
|
||||||
|
margin-left: -6px;
|
||||||
|
background-image: linear-gradient(-45deg, #322814, #998250, #322814);
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
color: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
&+p::first-line{
|
||||||
|
font-size : .385cm;
|
||||||
|
font-variant : small-caps;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
h2{
|
h2{
|
||||||
@@ -327,12 +336,12 @@ body {
|
|||||||
text-indent : -1em;
|
text-indent : -1em;
|
||||||
list-style-type : none;
|
list-style-type : none;
|
||||||
}
|
}
|
||||||
//Column Break
|
.columnSplit {
|
||||||
pre, code{
|
|
||||||
visibility : hidden;
|
visibility : hidden;
|
||||||
-webkit-column-break-after : always;
|
-webkit-column-break-after : always;
|
||||||
break-after : always;
|
break-after : always;
|
||||||
-moz-column-break-after : always;
|
-moz-column-break-after : always;
|
||||||
|
break-before : column;
|
||||||
}
|
}
|
||||||
//Avoid breaking up
|
//Avoid breaking up
|
||||||
p,blockquote,table{
|
p,blockquote,table{
|
||||||
@@ -363,7 +372,7 @@ body {
|
|||||||
//*****************************
|
//*****************************
|
||||||
// * SPELL LIST
|
// * SPELL LIST
|
||||||
// *****************************/
|
// *****************************/
|
||||||
.phb .spellList{
|
.phb3 .spellList{
|
||||||
.useSansSerif();
|
.useSansSerif();
|
||||||
column-count : 4;
|
column-count : 4;
|
||||||
column-span : all;
|
column-span : all;
|
||||||
@@ -389,7 +398,7 @@ body {
|
|||||||
//*****************************
|
//*****************************
|
||||||
// * WIDE
|
// * WIDE
|
||||||
// *****************************/
|
// *****************************/
|
||||||
.phb .wide{
|
.phb3 .wide{
|
||||||
column-span : all;
|
column-span : all;
|
||||||
-webkit-column-span : all;
|
-webkit-column-span : all;
|
||||||
-moz-column-span : all;
|
-moz-column-span : all;
|
||||||
@@ -397,7 +406,7 @@ body {
|
|||||||
//*****************************
|
//*****************************
|
||||||
// * CLASS TABLE
|
// * CLASS TABLE
|
||||||
// *****************************/
|
// *****************************/
|
||||||
.phb .classTable{
|
.phb3 .classTable{
|
||||||
margin-top : 25px;
|
margin-top : 25px;
|
||||||
margin-bottom : 40px;
|
margin-bottom : 40px;
|
||||||
border-collapse : separate;
|
border-collapse : separate;
|
||||||
@@ -416,7 +425,7 @@ body {
|
|||||||
//************************************
|
//************************************
|
||||||
// * DESCRIPTIVE TEXT BOX
|
// * DESCRIPTIVE TEXT BOX
|
||||||
// ************************************/
|
// ************************************/
|
||||||
.phb .descriptive{
|
.phb3 .descriptive{
|
||||||
display : block-inline;
|
display : block-inline;
|
||||||
margin-bottom : 1em;
|
margin-bottom : 1em;
|
||||||
background-color : #faf7ea;
|
background-color : #faf7ea;
|
||||||
@@ -444,13 +453,13 @@ body {
|
|||||||
letter-spacing : -0.02em;
|
letter-spacing : -0.02em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.phb pre+.descriptive{
|
.phb3 pre+.descriptive{
|
||||||
margin-top : 8px;
|
margin-top : 8px;
|
||||||
}
|
}
|
||||||
//*****************************
|
//*****************************
|
||||||
// * TABLE OF CONTENTS
|
// * TABLE OF CONTENTS
|
||||||
// *****************************/
|
// *****************************/
|
||||||
.phb .toc{
|
.phb3 .toc{
|
||||||
-webkit-column-break-inside : avoid;
|
-webkit-column-break-inside : avoid;
|
||||||
page-break-inside : avoid;
|
page-break-inside : avoid;
|
||||||
break-inside : avoid;
|
break-inside : avoid;
|
||||||
|
|||||||
469
client/homebrew/phbStyle/phb.styleLegacy.less
Normal file
469
client/homebrew/phbStyle/phb.styleLegacy.less
Normal file
@@ -0,0 +1,469 @@
|
|||||||
|
@import (less) './client/homebrew/phbStyle/phb.fonts.css';
|
||||||
|
@import (less) './client/homebrew/phbStyle/phb.assets.less';
|
||||||
|
@import (less) './client/homebrew/phbStyle/phb.depricated.less';
|
||||||
|
//Colors
|
||||||
|
@background : #EEE5CE;
|
||||||
|
@noteGreen : #e0e5c1;
|
||||||
|
@headerUnderline : #c9ad6a;
|
||||||
|
@horizontalRule : #9c2b1b;
|
||||||
|
@headerText : #58180D;
|
||||||
|
@monsterStatBackground : #FDF1DC;
|
||||||
|
@page { margin: 0; }
|
||||||
|
body {
|
||||||
|
counter-reset : phb-page-numbers;
|
||||||
|
}
|
||||||
|
*{
|
||||||
|
-webkit-print-color-adjust : exact;
|
||||||
|
}
|
||||||
|
.useSansSerif(){
|
||||||
|
font-family : ScalySans;
|
||||||
|
em{
|
||||||
|
font-family : ScalySans;
|
||||||
|
font-style : italic;
|
||||||
|
}
|
||||||
|
strong{
|
||||||
|
font-family : ScalySans;
|
||||||
|
font-weight : 800;
|
||||||
|
letter-spacing : -0.02em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.useColumns(@multiplier : 1){
|
||||||
|
column-count : 2;
|
||||||
|
column-fill : auto;
|
||||||
|
column-gap : 1cm;
|
||||||
|
column-width : 8cm * @multiplier;
|
||||||
|
-webkit-column-count : 2;
|
||||||
|
-moz-column-count : 2;
|
||||||
|
-webkit-column-width : 8cm * @multiplier;
|
||||||
|
-moz-column-width : 8cm * @multiplier;
|
||||||
|
-webkit-column-gap : 1cm;
|
||||||
|
-moz-column-gap : 1cm;
|
||||||
|
}
|
||||||
|
.phb{
|
||||||
|
.useColumns();
|
||||||
|
counter-increment : phb-page-numbers;
|
||||||
|
position : relative;
|
||||||
|
z-index : 15;
|
||||||
|
box-sizing : border-box;
|
||||||
|
overflow : hidden;
|
||||||
|
height : 279.4mm;
|
||||||
|
width : 215.9mm;
|
||||||
|
padding : 1.0cm 1.7cm;
|
||||||
|
padding-bottom : 1.5cm;
|
||||||
|
background-color : @background;
|
||||||
|
background-image : @backgroundImage;
|
||||||
|
font-family : BookSanity;
|
||||||
|
font-size : 0.317cm;
|
||||||
|
text-rendering : optimizeLegibility;
|
||||||
|
page-break-before : always;
|
||||||
|
page-break-after : always;
|
||||||
|
//*****************************
|
||||||
|
// * BASE
|
||||||
|
// *****************************/
|
||||||
|
p{
|
||||||
|
padding-bottom : 0.8em;
|
||||||
|
line-height : 1.3em;
|
||||||
|
&+p{
|
||||||
|
margin-top : -0.8em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ul{
|
||||||
|
margin-bottom : 0.8em;
|
||||||
|
padding-left : 1.4em;
|
||||||
|
line-height : 1.3em;
|
||||||
|
list-style-position : outside;
|
||||||
|
list-style-type : disc;
|
||||||
|
}
|
||||||
|
ol{
|
||||||
|
margin-bottom : 0.8em;
|
||||||
|
padding-left : 1.4em;
|
||||||
|
line-height : 1.3em;
|
||||||
|
list-style-position : outside;
|
||||||
|
list-style-type : decimal;
|
||||||
|
}
|
||||||
|
//Indents after p or lists
|
||||||
|
p+p, ul+p, ol+p{
|
||||||
|
text-indent : 1em;
|
||||||
|
}
|
||||||
|
img{
|
||||||
|
z-index : -1;
|
||||||
|
}
|
||||||
|
strong{
|
||||||
|
font-weight : bold;
|
||||||
|
letter-spacing : 0.03em;
|
||||||
|
}
|
||||||
|
em{
|
||||||
|
font-style : italic;
|
||||||
|
}
|
||||||
|
sup{
|
||||||
|
vertical-align : super;
|
||||||
|
font-size : smaller;
|
||||||
|
line-height : 0;
|
||||||
|
}
|
||||||
|
sub{
|
||||||
|
vertical-align : sub;
|
||||||
|
font-size : smaller;
|
||||||
|
line-height : 0;
|
||||||
|
}
|
||||||
|
//*****************************
|
||||||
|
// * HEADERS
|
||||||
|
// *****************************/
|
||||||
|
h1,h2,h3,h4{
|
||||||
|
margin-top : 0.2em;
|
||||||
|
margin-bottom : 0.2em;
|
||||||
|
font-family : MrJeeves;
|
||||||
|
font-weight : 800;
|
||||||
|
color : @headerText;
|
||||||
|
}
|
||||||
|
h1{
|
||||||
|
column-span : all;
|
||||||
|
font-size : 0.987cm;
|
||||||
|
-webkit-column-span : all;
|
||||||
|
-moz-column-span : all;
|
||||||
|
&+p::first-letter{
|
||||||
|
float : left;
|
||||||
|
font-family : Solberry;
|
||||||
|
font-size : 10em;
|
||||||
|
color : #222;
|
||||||
|
line-height : 0.8em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h2{
|
||||||
|
font-size : 0.705cm;
|
||||||
|
}
|
||||||
|
h3{
|
||||||
|
font-size : 0.529cm;
|
||||||
|
border-bottom : 2px solid @headerUnderline;
|
||||||
|
}
|
||||||
|
h4{
|
||||||
|
margin-bottom : 0.00em;
|
||||||
|
font-size : 0.458cm;
|
||||||
|
}
|
||||||
|
h5{
|
||||||
|
margin-bottom : 0.2em;
|
||||||
|
font-family : ScalySansSmallCaps;
|
||||||
|
font-size : 0.423cm;
|
||||||
|
font-weight : 900;
|
||||||
|
}
|
||||||
|
//*****************************
|
||||||
|
// * TABLE
|
||||||
|
// *****************************/
|
||||||
|
table{
|
||||||
|
.useSansSerif();
|
||||||
|
width : 100%;
|
||||||
|
margin-bottom : 1em;
|
||||||
|
font-size : 10pt;
|
||||||
|
thead{
|
||||||
|
display: table-row-group;
|
||||||
|
font-weight : 800;
|
||||||
|
th{
|
||||||
|
vertical-align : bottom;
|
||||||
|
padding-bottom : 0.3em;
|
||||||
|
padding-right : 0.1em;
|
||||||
|
padding-left : 0.1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbody{
|
||||||
|
tr{
|
||||||
|
td{
|
||||||
|
padding : 0.3em 0.1em;
|
||||||
|
}
|
||||||
|
&:nth-child(odd){
|
||||||
|
background-color : @noteGreen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//*****************************
|
||||||
|
// * NOTE
|
||||||
|
// *****************************/
|
||||||
|
blockquote{
|
||||||
|
.useSansSerif();
|
||||||
|
box-sizing : border-box;
|
||||||
|
margin-bottom : 1em;
|
||||||
|
padding : 5px 10px;
|
||||||
|
background-color : @noteGreen;
|
||||||
|
border-style : solid;
|
||||||
|
border-width : 11px;
|
||||||
|
border-image : @noteBorderImage 11;
|
||||||
|
border-image-outset : 9px 0px;
|
||||||
|
box-shadow : 1px 4px 14px #888;
|
||||||
|
p, ul{
|
||||||
|
font-size : 0.352cm;
|
||||||
|
line-height : 1.1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//If a note starts a column, give it space at the top to render border
|
||||||
|
pre+blockquote, h2+blockquote, h3+blockquote, h4+blockquote, h5+blockquote {
|
||||||
|
margin-top : 13px;
|
||||||
|
}
|
||||||
|
//*****************************
|
||||||
|
// * MONSTER STAT BLOCK
|
||||||
|
// *****************************/
|
||||||
|
hr+blockquote{
|
||||||
|
position : relative;
|
||||||
|
padding-top : 15px;
|
||||||
|
background-color : @monsterStatBackground;
|
||||||
|
border-style : solid;
|
||||||
|
border-width : 10px;
|
||||||
|
border-image : @monsterBorderImage 10;
|
||||||
|
h2{
|
||||||
|
margin-top : -8px;
|
||||||
|
margin-bottom : 0px;
|
||||||
|
&+p{
|
||||||
|
padding-bottom : 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h3{
|
||||||
|
font-family : ScalySans;
|
||||||
|
font-weight : 400;
|
||||||
|
border-bottom : 1px solid @headerText;
|
||||||
|
}
|
||||||
|
hr+ul{
|
||||||
|
color : @headerText;
|
||||||
|
}
|
||||||
|
ul{
|
||||||
|
.useSansSerif();
|
||||||
|
padding-left : 1em;
|
||||||
|
font-size : 0.352cm;
|
||||||
|
}
|
||||||
|
// Monster Ability table
|
||||||
|
hr+table{
|
||||||
|
margin : 0;
|
||||||
|
column-span : 1;
|
||||||
|
background-color : transparent;
|
||||||
|
border-style : none;
|
||||||
|
border-image : none;
|
||||||
|
-webkit-column-span : 1;
|
||||||
|
tbody{
|
||||||
|
tr:nth-child(odd), tr:nth-child(even){
|
||||||
|
background-color : transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table{
|
||||||
|
color : @headerText;
|
||||||
|
}
|
||||||
|
p+p{
|
||||||
|
margin-top : 0em;
|
||||||
|
padding-bottom : 0.5em;
|
||||||
|
text-indent : 0em;
|
||||||
|
}
|
||||||
|
//Triangle dividers
|
||||||
|
hr{
|
||||||
|
visibility : visible;
|
||||||
|
height : 6px;
|
||||||
|
margin : 4px 0px;
|
||||||
|
background-image : @redTriangleImage;
|
||||||
|
background-size : 100% 100%;
|
||||||
|
border : none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Full Width
|
||||||
|
hr+hr+blockquote{
|
||||||
|
.useColumns(0.96);
|
||||||
|
}
|
||||||
|
//*****************************
|
||||||
|
// * FOOTER
|
||||||
|
// *****************************/
|
||||||
|
&:after{
|
||||||
|
content : "";
|
||||||
|
position : absolute;
|
||||||
|
bottom : 0px;
|
||||||
|
left : 0px;
|
||||||
|
z-index : 100;
|
||||||
|
height : 50px;
|
||||||
|
width : 100%;
|
||||||
|
background-image : @footerAccentImage;
|
||||||
|
background-size : cover;
|
||||||
|
}
|
||||||
|
&:nth-child(even){
|
||||||
|
&:after{
|
||||||
|
transform : scaleX(-1);
|
||||||
|
}
|
||||||
|
.pageNumber{
|
||||||
|
left : 2px;
|
||||||
|
}
|
||||||
|
.footnote{
|
||||||
|
left : 80px;
|
||||||
|
text-align : left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.pageNumber{
|
||||||
|
position : absolute;
|
||||||
|
right : 2px;
|
||||||
|
bottom : 22px;
|
||||||
|
width : 50px;
|
||||||
|
font-size : 0.9em;
|
||||||
|
color : #c9ad6a;
|
||||||
|
text-align : center;
|
||||||
|
&.auto::after {
|
||||||
|
content : counter(phb-page-numbers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.footnote{
|
||||||
|
position : absolute;
|
||||||
|
right : 80px;
|
||||||
|
bottom : 32px;
|
||||||
|
z-index : 150;
|
||||||
|
width : 200px;
|
||||||
|
font-size : 0.8em;
|
||||||
|
color : #c9ad6a;
|
||||||
|
text-align : right;
|
||||||
|
}
|
||||||
|
//*****************************
|
||||||
|
// * EXTRAS
|
||||||
|
// *****************************/
|
||||||
|
hr{
|
||||||
|
visibility : hidden;
|
||||||
|
margin : 0px;
|
||||||
|
}
|
||||||
|
//Modified unorder list, used in spells
|
||||||
|
hr+ul{
|
||||||
|
margin-bottom : 0.5em;
|
||||||
|
padding-left : 1em;
|
||||||
|
text-indent : -1em;
|
||||||
|
list-style-type : none;
|
||||||
|
}
|
||||||
|
//Column Break
|
||||||
|
pre, code{
|
||||||
|
visibility : hidden;
|
||||||
|
-webkit-column-break-after : always;
|
||||||
|
break-after : always;
|
||||||
|
-moz-column-break-after : always;
|
||||||
|
}
|
||||||
|
//Avoid breaking up
|
||||||
|
p,blockquote,table{
|
||||||
|
z-index : 15;
|
||||||
|
-webkit-column-break-inside : avoid;
|
||||||
|
page-break-inside : avoid;
|
||||||
|
break-inside : avoid;
|
||||||
|
}
|
||||||
|
//Better spacing for spell blocks
|
||||||
|
h4+p+hr+ul{
|
||||||
|
margin-top : -0.5em
|
||||||
|
}
|
||||||
|
//Text indent right after table
|
||||||
|
table+p{
|
||||||
|
text-indent : 1em;
|
||||||
|
}
|
||||||
|
// Nested lists
|
||||||
|
ul ul,ol ol,ul ol,ol ul{
|
||||||
|
margin-bottom : 0px;
|
||||||
|
margin-left : 1.5em;
|
||||||
|
}
|
||||||
|
li{
|
||||||
|
-webkit-column-break-inside : avoid;
|
||||||
|
page-break-inside : avoid;
|
||||||
|
break-inside : avoid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//*****************************
|
||||||
|
// * SPELL LIST
|
||||||
|
// *****************************/
|
||||||
|
.phb .spellList{
|
||||||
|
.useSansSerif();
|
||||||
|
column-count : 4;
|
||||||
|
column-span : all;
|
||||||
|
-webkit-column-span : all;
|
||||||
|
-moz-column-span : all;
|
||||||
|
ul+h5{
|
||||||
|
margin-top : 15px;
|
||||||
|
}
|
||||||
|
p, ul{
|
||||||
|
font-size : 0.352cm;
|
||||||
|
line-height : 1.3em;
|
||||||
|
}
|
||||||
|
ul{
|
||||||
|
margin-bottom : 0.5em;
|
||||||
|
padding-left : 1em;
|
||||||
|
text-indent : -1em;
|
||||||
|
list-style-type : none;
|
||||||
|
-webkit-column-break-inside : auto;
|
||||||
|
page-break-inside : auto;
|
||||||
|
break-inside : auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//*****************************
|
||||||
|
// * WIDE
|
||||||
|
// *****************************/
|
||||||
|
.phb .wide{
|
||||||
|
column-span : all;
|
||||||
|
-webkit-column-span : all;
|
||||||
|
-moz-column-span : all;
|
||||||
|
}
|
||||||
|
//*****************************
|
||||||
|
// * CLASS TABLE
|
||||||
|
// *****************************/
|
||||||
|
.phb .classTable{
|
||||||
|
margin-top : 25px;
|
||||||
|
margin-bottom : 40px;
|
||||||
|
border-collapse : separate;
|
||||||
|
background-color : white;
|
||||||
|
border : initial;
|
||||||
|
border-style : solid;
|
||||||
|
border-image-outset : 25px 17px;
|
||||||
|
border-image-repeat : stretch;
|
||||||
|
border-image-slice : 150 200 150 200;
|
||||||
|
border-image-source : @frameBorderImage;
|
||||||
|
border-image-width : 47px;
|
||||||
|
h5{
|
||||||
|
margin-bottom : 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//************************************
|
||||||
|
// * DESCRIPTIVE TEXT BOX
|
||||||
|
// ************************************/
|
||||||
|
.phb .descriptive{
|
||||||
|
display : block-inline;
|
||||||
|
margin-bottom : 1em;
|
||||||
|
background-color : #faf7ea;
|
||||||
|
font-family : ScalySans;
|
||||||
|
border-style : solid;
|
||||||
|
border-width : 7px;
|
||||||
|
border-image : @descriptiveBoxImage 12 stretch;
|
||||||
|
border-image-outset : 4px;
|
||||||
|
box-shadow : 0px 0px 6px #faf7ea;
|
||||||
|
p{
|
||||||
|
display : block;
|
||||||
|
padding-bottom : 0px;
|
||||||
|
line-height : 1.5em;
|
||||||
|
}
|
||||||
|
p + p {
|
||||||
|
padding-top : .8em;
|
||||||
|
}
|
||||||
|
em {
|
||||||
|
font-family : ScalySans;
|
||||||
|
font-style : italic;
|
||||||
|
}
|
||||||
|
strong {
|
||||||
|
font-family : ScalySans;
|
||||||
|
font-weight : 800;
|
||||||
|
letter-spacing : -0.02em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.phb pre+.descriptive{
|
||||||
|
margin-top : 8px;
|
||||||
|
}
|
||||||
|
//*****************************
|
||||||
|
// * TABLE OF CONTENTS
|
||||||
|
// *****************************/
|
||||||
|
.phb .toc{
|
||||||
|
-webkit-column-break-inside : avoid;
|
||||||
|
page-break-inside : avoid;
|
||||||
|
break-inside : avoid;
|
||||||
|
a{
|
||||||
|
color : black;
|
||||||
|
text-decoration : none;
|
||||||
|
&:hover{
|
||||||
|
text-decoration : underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ul{
|
||||||
|
padding-left : 0;
|
||||||
|
list-style-type : none;
|
||||||
|
}
|
||||||
|
&>ul>li{
|
||||||
|
margin-bottom : 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ module.exports = async(name, title = '', props = {})=>{
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<link href="//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
|
<link href="//use.fontawesome.com/releases/v5.15.1/css/all.css" rel="stylesheet" />
|
||||||
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
|
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
|
||||||
<link href=${`/${name}/bundle.css`} rel='stylesheet'></link>
|
<link href=${`/${name}/bundle.css`} rel='stylesheet'></link>
|
||||||
<link rel="icon" href="/assets/homebrew/favicon.ico" type="image/x-icon" />
|
<link rel="icon" href="/assets/homebrew/favicon.ico" type="image/x-icon" />
|
||||||
@@ -16,4 +16,4 @@ module.exports = async(name, title = '', props = {})=>{
|
|||||||
<script>start_app(${JSON.stringify(props)})</script>
|
<script>start_app(${JSON.stringify(props)})</script>
|
||||||
</html>
|
</html>
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|||||||
7
package-lock.json
generated
7
package-lock.json
generated
@@ -4410,7 +4410,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"marked": {
|
"marked": {
|
||||||
"version": "0.3.19",
|
"version": "npm:marked@1.2.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/marked/-/marked-1.2.7.tgz",
|
||||||
|
"integrity": "sha512-No11hFYcXr/zkBvL6qFmAp1z6BKY3zqLMHny/JN/ey+al7qwCM2+CMBL9BOgqMxZU36fz4cCWfn2poWIf7QRXA=="
|
||||||
|
},
|
||||||
|
"markedLegacy": {
|
||||||
|
"version": "npm:marked@0.3.19",
|
||||||
"resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz",
|
"resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz",
|
||||||
"integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg=="
|
"integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg=="
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -55,10 +55,11 @@
|
|||||||
"jwt-simple": "^0.5.6",
|
"jwt-simple": "^0.5.6",
|
||||||
"less": "^3.13.1",
|
"less": "^3.13.1",
|
||||||
"lodash": "^4.17.20",
|
"lodash": "^4.17.20",
|
||||||
"marked": "^0.3.19",
|
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
"mongoose": "^5.11.13",
|
"mongoose": "^5.11.13",
|
||||||
"nanoid": "3.1.20",
|
"nanoid": "3.1.20",
|
||||||
|
"markedLegacy": "npm:marked@^0.3.19",
|
||||||
|
"marked": "npm:marked@^1.2.7",
|
||||||
"nconf": "^0.11.1",
|
"nconf": "^0.11.1",
|
||||||
"prop-types": "15.7.2",
|
"prop-types": "15.7.2",
|
||||||
"query-string": "6.13.8",
|
"query-string": "6.13.8",
|
||||||
|
|||||||
@@ -21,10 +21,16 @@ const build = async ({ bundle, render, ssr })=>{
|
|||||||
await fs.outputFile('./build/homebrew/ssr.js', ssr);
|
await fs.outputFile('./build/homebrew/ssr.js', ssr);
|
||||||
await fs.outputFile('./build/homebrew/render.js', render);
|
await fs.outputFile('./build/homebrew/render.js', render);
|
||||||
|
|
||||||
//compress files
|
//compress files in production
|
||||||
await fs.outputFile('./build/homebrew/bundle.css.br', zlib.brotliCompressSync(css));
|
if(!isDev){
|
||||||
await fs.outputFile('./build/homebrew/bundle.js.br', zlib.brotliCompressSync(bundle));
|
await fs.outputFile('./build/homebrew/bundle.css.br', zlib.brotliCompressSync(css));
|
||||||
await fs.outputFile('./build/homebrew/ssr.js.br', zlib.brotliCompressSync(ssr));
|
await fs.outputFile('./build/homebrew/bundle.js.br', zlib.brotliCompressSync(bundle));
|
||||||
|
await fs.outputFile('./build/homebrew/ssr.js.br', zlib.brotliCompressSync(ssr));
|
||||||
|
} else {
|
||||||
|
await fs.remove('./build/homebrew/bundle.css.br');
|
||||||
|
await fs.remove('./build/homebrew/bundle.js.br');
|
||||||
|
await fs.remove('./build/homebrew/ssr.js.br');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fs.emptyDirSync('./build/homebrew');
|
fs.emptyDirSync('./build/homebrew');
|
||||||
|
|||||||
@@ -222,6 +222,7 @@ app.use((req, res)=>{
|
|||||||
brews : req.brews,
|
brews : req.brews,
|
||||||
googleBrews : req.googleBrews,
|
googleBrews : req.googleBrews,
|
||||||
account : req.account,
|
account : req.account,
|
||||||
|
enable_v3 : config.get('enable_v3')
|
||||||
};
|
};
|
||||||
templateFn('homebrew', title = req.brew ? req.brew.title : '', props)
|
templateFn('homebrew', title = req.brew ? req.brew.title : '', props)
|
||||||
.then((page)=>{ res.send(page); })
|
.then((page)=>{ res.send(page); })
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ GoogleActions = {
|
|||||||
lastViewed : brew.lastViewed,
|
lastViewed : brew.lastViewed,
|
||||||
views : brew.views,
|
views : brew.views,
|
||||||
version : brew.version,
|
version : brew.version,
|
||||||
|
renderer : brew.renderer,
|
||||||
tags : brew.tags,
|
tags : brew.tags,
|
||||||
systems : brew.systems.join() }
|
systems : brew.systems.join() }
|
||||||
},
|
},
|
||||||
@@ -230,6 +231,7 @@ GoogleActions = {
|
|||||||
description : brew.description,
|
description : brew.description,
|
||||||
tags : '',
|
tags : '',
|
||||||
published : brew.published,
|
published : brew.published,
|
||||||
|
renderer : brew.renderer,
|
||||||
authors : [],
|
authors : [],
|
||||||
systems : []
|
systems : []
|
||||||
};
|
};
|
||||||
@@ -291,6 +293,7 @@ GoogleActions = {
|
|||||||
lastViewed : obj.data.properties.lastViewed,
|
lastViewed : obj.data.properties.lastViewed,
|
||||||
views : parseInt(obj.data.properties.views) || 0, //brews with no view parameter will return undefined
|
views : parseInt(obj.data.properties.views) || 0, //brews with no view parameter will return undefined
|
||||||
version : parseInt(obj.data.properties.version) || 0,
|
version : parseInt(obj.data.properties.version) || 0,
|
||||||
|
renderer : obj.data.properties.renderer ? obj.data.properties.renderer : 'legacy',
|
||||||
|
|
||||||
gDrive : true,
|
gDrive : true,
|
||||||
googleId : id
|
googleId : id
|
||||||
|
|||||||
@@ -122,8 +122,6 @@ const newGoogleBrew = async (req, res, next)=>{
|
|||||||
|
|
||||||
req.body = brew;
|
req.body = brew;
|
||||||
|
|
||||||
console.log(oAuth2Client);
|
|
||||||
|
|
||||||
const newBrew = await GoogleActions.newGoogleBrew(oAuth2Client, brew);
|
const newBrew = await GoogleActions.newGoogleBrew(oAuth2Client, brew);
|
||||||
|
|
||||||
return res.status(200).send(newBrew);
|
return res.status(200).send(newBrew);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const HomebrewSchema = mongoose.Schema({
|
|||||||
description : { type: String, default: '' },
|
description : { type: String, default: '' },
|
||||||
tags : { type: String, default: '' },
|
tags : { type: String, default: '' },
|
||||||
systems : [String],
|
systems : [String],
|
||||||
|
renderer : { type: String, default: '' },
|
||||||
authors : [String],
|
authors : [String],
|
||||||
published : { type: Boolean, default: false },
|
published : { type: Boolean, default: false },
|
||||||
|
|
||||||
@@ -55,6 +56,8 @@ HomebrewSchema.statics.get = function(query){
|
|||||||
unzipped = zlib.inflateRawSync(brews[0].textBin);
|
unzipped = zlib.inflateRawSync(brews[0].textBin);
|
||||||
brews[0].text = unzipped.toString();
|
brews[0].text = unzipped.toString();
|
||||||
}
|
}
|
||||||
|
if(!brews[0].renderer)
|
||||||
|
brews[0].renderer = 'legacy';
|
||||||
return resolve(brews[0]);
|
return resolve(brews[0]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -53,8 +53,8 @@ const RenderWarnings = createClass({
|
|||||||
if(_.isEmpty(this.state.warnings)) return null;
|
if(_.isEmpty(this.state.warnings)) return null;
|
||||||
|
|
||||||
return <div className='renderWarnings'>
|
return <div className='renderWarnings'>
|
||||||
<i className='fa fa-times dismiss' onClick={this.dismiss}/>
|
<i className='fas fa-times dismiss' onClick={this.dismiss}/>
|
||||||
<i className='fa 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>
|
||||||
|
|||||||
@@ -61,16 +61,12 @@ const CodeEditor = createClass({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillReceiveProps : function(nextProps){
|
componentDidUpdate : function(prevProps) {
|
||||||
if(this.codeMirror && nextProps.value !== undefined && this.codeMirror.getValue() != nextProps.value) {
|
if(this.codeMirror && this.codeMirror.getValue() != this.props.value) {
|
||||||
this.codeMirror.setValue(nextProps.value);
|
this.codeMirror.setValue(this.props.value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
shouldComponentUpdate : function(nextProps, nextState) {
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
setCursorPosition : function(line, char){
|
setCursorPosition : function(line, char){
|
||||||
setTimeout(()=>{
|
setTimeout(()=>{
|
||||||
this.codeMirror.focus();
|
this.codeMirror.focus();
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable max-lines */
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const Markdown = require('marked');
|
const Markdown = require('marked');
|
||||||
const renderer = new Markdown.Renderer();
|
const renderer = new Markdown.Renderer();
|
||||||
@@ -10,9 +11,87 @@ renderer.html = function (html) {
|
|||||||
html = html.substring(0, html.lastIndexOf('</div>'));
|
html = html.substring(0, html.lastIndexOf('</div>'));
|
||||||
return `${openTag} ${Markdown(html)} </div>`;
|
return `${openTag} ${Markdown(html)} </div>`;
|
||||||
}
|
}
|
||||||
|
// if(_.startsWith(_.trim(html), '<style') && _.endsWith(_.trim(html), '</style>')){
|
||||||
|
// const openTag = html.substring(0, html.indexOf('>')+1);
|
||||||
|
// html = html.substring(html.indexOf('>')+1);
|
||||||
|
// html = html.substring(0, html.lastIndexOf('</style>'));
|
||||||
|
// html = html.replaceAll(/\s(\.[^{]*)/gm, '.V3 $1');
|
||||||
|
// return `${openTag} ${html} </style>`;
|
||||||
|
// }
|
||||||
return html;
|
return html;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Ensure {{ Divs don't confuse paragraph parsing (else it renders empty paragraphs)
|
||||||
|
renderer.paragraph = function(text){
|
||||||
|
if(text.startsWith('<div') || text.startsWith('</div'))
|
||||||
|
return `${text}`;
|
||||||
|
else
|
||||||
|
return `<p>${text}</p>\n`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mustache-style Divs {{class \n content ... \n}}
|
||||||
|
let blockCount = 0;
|
||||||
|
const blockRegex = /^ *{{(?:="[\w, ]*"|[^"'\s])*$|^ *}}$/gm;
|
||||||
|
const inlineFullRegex = /{{[^\n]*}}/g;
|
||||||
|
const inlineRegex = /{{(?:="[\w, ]*"|[^"'\s])*\s*|}}/g;
|
||||||
|
|
||||||
|
renderer.text = function(text){
|
||||||
|
const newText = text.replaceAll('"', '"');
|
||||||
|
let matches;
|
||||||
|
|
||||||
|
//DIV - BLOCK-LEVEL
|
||||||
|
if(matches = newText.match(blockRegex)) {
|
||||||
|
let matchIndex = 0;
|
||||||
|
const res = _.reduce(newText.split(blockRegex), (r, splitText)=>{
|
||||||
|
if(splitText) r.push(Markdown.parseInline(splitText, { renderer: renderer }));
|
||||||
|
|
||||||
|
const block = matches[matchIndex] ? matches[matchIndex].trimLeft() : '';
|
||||||
|
if(block && block.startsWith('{')) {
|
||||||
|
const values = processStyleTags(block.substring(2));
|
||||||
|
r.push(`<div class="block ${values}">`);
|
||||||
|
blockCount++;
|
||||||
|
} else if(block == '}}' && blockCount !== 0){
|
||||||
|
r.push('</div>');
|
||||||
|
blockCount--;
|
||||||
|
}
|
||||||
|
|
||||||
|
matchIndex++;
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}, []).join('');
|
||||||
|
return res;
|
||||||
|
} else if(matches = newText.match(inlineFullRegex)) {
|
||||||
|
|
||||||
|
//SPAN - INLINE
|
||||||
|
matches = newText.match(inlineRegex);
|
||||||
|
let matchIndex = 0;
|
||||||
|
const res = _.reduce(newText.split(inlineRegex), (r, splitText)=>{
|
||||||
|
|
||||||
|
if(splitText) r.push(Markdown.parseInline(splitText, { renderer: renderer }));
|
||||||
|
|
||||||
|
const block = matches[matchIndex] ? matches[matchIndex].trimLeft() : '';
|
||||||
|
if(block && block.startsWith('{{')) {
|
||||||
|
const values = processStyleTags(block.substring(2));
|
||||||
|
r.push(`<span class="inline-block ${values}>`);
|
||||||
|
blockCount++;
|
||||||
|
} else if(block == '}}' && blockCount !== 0){
|
||||||
|
r.push('</span>');
|
||||||
|
blockCount--;
|
||||||
|
}
|
||||||
|
|
||||||
|
matchIndex++;
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}, []).join('');
|
||||||
|
return `${res}`;
|
||||||
|
} else {
|
||||||
|
if(!matches) {
|
||||||
|
return `${text}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//Fix local links in the Preview iFrame to link inside the frame
|
||||||
renderer.link = function (href, title, text) {
|
renderer.link = function (href, title, text) {
|
||||||
let self = false;
|
let self = false;
|
||||||
if(href[0] == '#') {
|
if(href[0] == '#') {
|
||||||
@@ -79,7 +158,6 @@ const escape = function (html, encode) {
|
|||||||
return html.replace(escapeReplaceNoEncode, getEscapeReplacement);
|
return html.replace(escapeReplaceNoEncode, getEscapeReplacement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return html;
|
return html;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -95,10 +173,24 @@ const tagRegex = new RegExp(`(${
|
|||||||
return `\\<${type}|\\</${type}>`;
|
return `\\<${type}|\\</${type}>`;
|
||||||
}).join('|')})`, 'g');
|
}).join('|')})`, 'g');
|
||||||
|
|
||||||
|
const processStyleTags = (string)=>{
|
||||||
|
const tags = string.match(/(?:[^, "=]+|="[^"]*")+/g);
|
||||||
|
|
||||||
|
if(!tags) return '"';
|
||||||
|
|
||||||
|
const id = _.remove(tags, (tag)=>tag.startsWith('#')).map((tag)=>tag.slice(1))[0];
|
||||||
|
const classes = _.remove(tags, (tag)=>!tag.includes('"'));
|
||||||
|
const styles = tags.map((tag)=>tag.replace(/="(.*)"/g, ':$1;'));
|
||||||
|
return `${classes.join(' ')}" ${id ? `id="${id}"` : ''} ${styles ? `style="${styles.join(' ')}"` : ''}`;
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
marked : Markdown,
|
marked : Markdown,
|
||||||
render : (rawBrewText)=>{
|
render : (rawBrewText)=>{
|
||||||
|
blockCount = 0;
|
||||||
|
rawBrewText = rawBrewText.replace(/^\\column/gm, `<div class='columnSplit'></div>`)
|
||||||
|
.replace(/^}}/gm, '\n}}')
|
||||||
|
.replace(/^({{[^\n]*)$/gm, '$1\n');
|
||||||
return Markdown(
|
return Markdown(
|
||||||
sanatizeScriptTags(rawBrewText),
|
sanatizeScriptTags(rawBrewText),
|
||||||
{ renderer: renderer }
|
{ renderer: renderer }
|
||||||
|
|||||||
166
shared/naturalcrit/markdownLegacy.js
Normal file
166
shared/naturalcrit/markdownLegacy.js
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
const Markdown = require('markedLegacy');
|
||||||
|
const renderer = new Markdown.Renderer();
|
||||||
|
|
||||||
|
//Processes the markdown within an HTML block if it's just a class-wrapper
|
||||||
|
renderer.html = function (html) {
|
||||||
|
if(_.startsWith(_.trim(html), '<div') && _.endsWith(_.trim(html), '</div>')){
|
||||||
|
const openTag = html.substring(0, html.indexOf('>')+1);
|
||||||
|
html = html.substring(html.indexOf('>')+1);
|
||||||
|
html = html.substring(0, html.lastIndexOf('</div>'));
|
||||||
|
return `${openTag} ${Markdown(html)} </div>`;
|
||||||
|
}
|
||||||
|
// if(_.startsWith(_.trim(html), '<style') && _.endsWith(_.trim(html), '</style>')){
|
||||||
|
// const openTag = html.substring(0, html.indexOf('>')+1);
|
||||||
|
// html = html.substring(html.indexOf('>')+1);
|
||||||
|
// html = html.substring(0, html.lastIndexOf('</style>'));
|
||||||
|
// html = html.replaceAll(/\s(\.[^{]*)/gm, '.legacy $1');
|
||||||
|
// return `${openTag} ${html} </style>`;
|
||||||
|
// }
|
||||||
|
return html;
|
||||||
|
};
|
||||||
|
|
||||||
|
renderer.link = function (href, title, text) {
|
||||||
|
let self = false;
|
||||||
|
if(href[0] == '#') {
|
||||||
|
self = true;
|
||||||
|
}
|
||||||
|
href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
|
||||||
|
|
||||||
|
if(href === null) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
let out = `<a href="${escape(href)}"`;
|
||||||
|
if(title) {
|
||||||
|
out += ` title="${title}"`;
|
||||||
|
}
|
||||||
|
if(self) {
|
||||||
|
out += ' target="_self"';
|
||||||
|
}
|
||||||
|
out += `>${text}</a>`;
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
const nonWordAndColonTest = /[^\w:]/g;
|
||||||
|
const cleanUrl = function (sanitize, base, href) {
|
||||||
|
if(sanitize) {
|
||||||
|
let prot;
|
||||||
|
try {
|
||||||
|
prot = decodeURIComponent(unescape(href))
|
||||||
|
.replace(nonWordAndColonTest, '')
|
||||||
|
.toLowerCase();
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if(prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
href = encodeURI(href).replace(/%25/g, '%');
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return href;
|
||||||
|
};
|
||||||
|
|
||||||
|
const escapeTest = /[&<>"']/;
|
||||||
|
const escapeReplace = /[&<>"']/g;
|
||||||
|
const escapeTestNoEncode = /[<>"']|&(?!#?\w+;)/;
|
||||||
|
const escapeReplaceNoEncode = /[<>"']|&(?!#?\w+;)/g;
|
||||||
|
const escapeReplacements = {
|
||||||
|
'&' : '&',
|
||||||
|
'<' : '<',
|
||||||
|
'>' : '>',
|
||||||
|
'"' : '"',
|
||||||
|
'\'' : '''
|
||||||
|
};
|
||||||
|
const getEscapeReplacement = (ch)=>escapeReplacements[ch];
|
||||||
|
const escape = function (html, encode) {
|
||||||
|
if(encode) {
|
||||||
|
if(escapeTest.test(html)) {
|
||||||
|
return html.replace(escapeReplace, getEscapeReplacement);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(escapeTestNoEncode.test(html)) {
|
||||||
|
return html.replace(escapeReplaceNoEncode, getEscapeReplacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return html;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sanatizeScriptTags = (content)=>{
|
||||||
|
return content
|
||||||
|
.replace(/<script/ig, '<script')
|
||||||
|
.replace(/<\/script>/ig, '</script>');
|
||||||
|
};
|
||||||
|
|
||||||
|
const tagTypes = ['div', 'span', 'a'];
|
||||||
|
const tagRegex = new RegExp(`(${
|
||||||
|
_.map(tagTypes, (type)=>{
|
||||||
|
return `\\<${type}|\\</${type}>`;
|
||||||
|
}).join('|')})`, 'g');
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
marked : Markdown,
|
||||||
|
render : (rawBrewText)=>{
|
||||||
|
return Markdown(
|
||||||
|
sanatizeScriptTags(rawBrewText),
|
||||||
|
{ renderer: renderer }
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
validate : (rawBrewText)=>{
|
||||||
|
const errors = [];
|
||||||
|
const leftovers = _.reduce(rawBrewText.split('\n'), (acc, line, _lineNumber)=>{
|
||||||
|
const lineNumber = _lineNumber + 1;
|
||||||
|
const matches = line.match(tagRegex);
|
||||||
|
if(!matches || !matches.length) return acc;
|
||||||
|
|
||||||
|
_.each(matches, (match)=>{
|
||||||
|
_.each(tagTypes, (type)=>{
|
||||||
|
if(match == `<${type}`){
|
||||||
|
acc.push({
|
||||||
|
type : type,
|
||||||
|
line : lineNumber
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if(match === `</${type}>`){
|
||||||
|
if(!acc.length){
|
||||||
|
errors.push({
|
||||||
|
line : lineNumber,
|
||||||
|
type : type,
|
||||||
|
text : 'Unmatched closing tag',
|
||||||
|
id : 'CLOSE'
|
||||||
|
});
|
||||||
|
} else if(_.last(acc).type == type){
|
||||||
|
acc.pop();
|
||||||
|
} else {
|
||||||
|
errors.push({
|
||||||
|
line : `${_.last(acc).line} to ${lineNumber}`,
|
||||||
|
type : type,
|
||||||
|
text : 'Type mismatch on closing tag',
|
||||||
|
id : 'MISMATCH'
|
||||||
|
});
|
||||||
|
acc.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
_.each(leftovers, (unmatched)=>{
|
||||||
|
errors.push({
|
||||||
|
line : unmatched.line,
|
||||||
|
type : unmatched.type,
|
||||||
|
text : 'Unmatched opening tag',
|
||||||
|
id : 'OPEN'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -50,7 +50,7 @@ const Nav = {
|
|||||||
const classes = cx('navItem', this.props.color, this.props.className);
|
const classes = cx('navItem', this.props.color, this.props.className);
|
||||||
|
|
||||||
let icon;
|
let icon;
|
||||||
if(this.props.icon) icon = <i className={`fa ${this.props.icon}`} />;
|
if(this.props.icon) icon = <i className={this.props.icon} />;
|
||||||
|
|
||||||
const props = _.omit(this.props, ['newTab']);
|
const props = _.omit(this.props, ['newTab']);
|
||||||
|
|
||||||
|
|||||||
@@ -56,9 +56,9 @@ const SplitPane = createClass({
|
|||||||
renderDivider : function(){
|
renderDivider : function(){
|
||||||
return <div className='divider' onMouseDown={this.handleDown} >
|
return <div className='divider' onMouseDown={this.handleDown} >
|
||||||
<div className='dots'>
|
<div className='dots'>
|
||||||
<i className='fa fa-circle' />
|
<i className='fas fa-circle' />
|
||||||
<i className='fa fa-circle' />
|
<i className='fas fa-circle' />
|
||||||
<i className='fa fa-circle' />
|
<i className='fas fa-circle' />
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user