1
0
mirror of https://github.com/stolksdorf/homebrewery.git synced 2026-01-09 07:59:14 +00:00

Merge branch 'borderShadows' into v3

This commit is contained in:
Scott Tolksdorf
2017-03-26 15:18:06 -04:00
70 changed files with 2000 additions and 607 deletions

View File

@@ -2,7 +2,12 @@ var React = require('react');
var Nav = require('naturalcrit/nav/nav.jsx');
module.exports = function(props){
return <Nav.item newTab={true} href='https://github.com/stolksdorf/homebrewery/issues' color='red' icon='fa-bug'>
return <Nav.item
{...props}
newTab={true}
href='https://github.com/stolksdorf/homebrewery/issues'
color='red'
icon='fa-bug'>
report issue
</Nav.item>
};

View File

@@ -3,6 +3,7 @@ var Nav = require('naturalcrit/nav/nav.jsx');
module.exports = function(props){
return <Nav.item
{...props}
className='patreon'
newTab={true}
href='https://www.patreon.com/stolksdorf'

View File

@@ -8,6 +8,9 @@ var Nav = require('naturalcrit/nav/nav.jsx');
const VIEW_KEY = 'homebrewery-recently-viewed';
const EDIT_KEY = 'homebrewery-recently-edited';
//DEPRICATED
var BaseItem = React.createClass({
getDefaultProps: function() {
return {
@@ -28,6 +31,8 @@ var BaseItem = React.createClass({
},
componentDidMount: function() {
console.log('Recent nav item is depricated');
var brews = JSON.parse(localStorage.getItem(this.props.storageKey) || '[]');
brews = _.filter(brews, (brew)=>{

View File

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

View File

@@ -24,10 +24,10 @@ const HomePage = React.createClass({
renderNavbar : function(){
return <Navbar>
<Nav.section>
<PatreonNavItem />
<IssueNavItem />
<Nav.item newTab={true} href='/changelog' color='purple' icon='fa-file-text-o'>
Changelog
<PatreonNavItem collaspe={true} />
<IssueNavItem collaspe={true} />
<Nav.item newTab={true} href='/changelog' color='purple' icon='fa-star' collaspe={true}>
What's new
</Nav.item>
<RecentNavItem.both />
<AccountNavItem />

View File

@@ -47,7 +47,7 @@ const NewPage = React.createClass({
<Nav.item color='purple' icon='fa-file-pdf-o' onClick={Actions.localPrint}>
get PDF
</Nav.item>
<Items.Issue />
<Items.Issue collaspe={true} />
<Items.Account />
</Nav.section>
</Navbar>

View File

@@ -3,38 +3,62 @@ const _ = require('lodash');
const cx = require('classnames');
const Markdown = require('homebrewery/markdown.js');
const Headtags = require('vitreum/headtags');
const PrintPage = React.createClass({
getDefaultProps: function() {
return {
query : {},
brew : {
text : '',
style : ''
}
};
},
getInitialState: function() {
return {
brewText: this.props.brew.text
brew: this.props.brew
};
},
componentDidMount: function() {
if(this.props.query.local){
this.setState({ brewText : localStorage.getItem(this.props.query.local)});
try{
this.setState({
brew : JSON.parse(
localStorage.getItem(this.props.query.local)
)
});
}catch(e){}
}
if(this.props.query.dialog) window.print();
},
//TODO: Print page shouldn't replicate functionality in brew renderer
renderStyle : function(){
if(!this.state.brew.style) return;
return <style>{this.state.brew.style.replace(/;/g, ' !important;')}</style>
},
renderPages : function(){
return _.map(this.state.brewText.split('\\page'), (page, index) => {
return _.map(this.state.brew.text.split('\\page'), (page, index) => {
return <div
className='phb'
className='phb v2'
id={`p${index + 1}`}
dangerouslySetInnerHTML={{__html:Markdown.render(page)}}
key={index} />;
});
},
renderPrintInstructions : function(){
return <div className='printInstructions'>
Hey, I'm really cool instructions!!!!!
</div>
},
render : function(){
return <div>
return <div className='printPage'>
<Headtags.title>{this.state.brew.title}</Headtags.title>
{this.renderPrintInstructions()}
{this.renderStyle()}
{this.renderPages()}
</div>
}

View File

@@ -1,3 +1,17 @@
.printPage{
.printPage{
position : relative;
@media print{
.printInstructions{
display : none;
}
}
.printInstructions{
position : absolute;
top : 0px;
right : 0px;
z-index : 100000;
padding : 30px;
background-color : @blue;
}
}

View File

@@ -6,11 +6,12 @@
"dev": "node scripts/dev.js",
"quick": "node scripts/quick.js",
"build": "node scripts/build.js",
"phb": "node scripts/phb.js",
"populate": "node scripts/populate.js",
"prod": "set NODE_ENV=production&& npm run build",
"postinstall": "npm run build",
"start": "node server.js",
"snippet": "nodemon scripts/snippet.test.js",
"todo": "./node_modules/.bin/fixme -i node_modules/** -i build/**",
"test": "mocha tests",
"test:dev": "nodemon -x mocha tests || exit 0",
"test:markdown": "nodemon -x mocha tests/markdown.test.js || exit 0"
@@ -46,6 +47,7 @@
"chai": "^3.5.0",
"chai-as-promised": "^6.0.0",
"chai-subset": "^1.4.0",
"fixme": "^0.4.3",
"mocha": "^3.2.0",
"supertest": "^2.0.1",
"supertest-as-promised": "^4.0.2"

View File

@@ -2,19 +2,20 @@ const label = 'build';
console.time(label);
const clean = require('vitreum/steps/clean.js');
const jsx = require('vitreum/steps/jsx.js').partial;
const lib = require('vitreum/steps/libs.js').partial;
const less = require('vitreum/steps/less.js').partial;
const asset = require('vitreum/steps/assets.js').partial;
const jsx = require('vitreum/steps/jsx.js');
const lib = require('vitreum/steps/libs.js');
const less = require('vitreum/steps/less.js');
const asset = require('vitreum/steps/assets.js');
const Proj = require('./project.json');
clean()
.then(lib(Proj.libs))
.then(jsx('homebrew', './client/homebrew/homebrew.jsx', Proj.libs, ['./shared']))
.then(less('homebrew', ['./shared']))
.then(jsx('admin', './client/admin/admin.jsx', Proj.libs, ['./shared']))
.then(less('admin', ['./shared']))
.then(asset(Proj.assets, ['./shared', './client']))
.then(console.timeEnd.bind(console, label))
Promise.resolve()
.then(()=>clean())
.then(()=>lib(Proj.libs))
.then(()=>jsx('homebrew', './client/homebrew/homebrew.jsx', Proj.libs, ['./shared']))
.then((deps)=>less('homebrew', ['./shared'], deps))
.then(()=>jsx('admin', './client/admin/admin.jsx', Proj.libs, ['./shared']))
.then((deps)=>less('admin', ['./shared'], deps))
.then(()=>asset(Proj.assets, ['./shared', './client']))
.then(()=>console.timeEnd.bind(console, label))
.catch(console.error);

View File

@@ -1,21 +1,21 @@
const label = 'dev';
console.time(label);
const jsx = require('vitreum/steps/jsx.watch.js').partial;
const less = require('vitreum/steps/less.watch.js').partial;
const assets = require('vitreum/steps/assets.watch.js').partial;
const server = require('vitreum/steps/server.watch.js').partial;
const livereload = require('vitreum/steps/livereload.js').partial;
const jsx = require('vitreum/steps/jsx.watch.js');
const less = require('vitreum/steps/less.watch.js');
const assets = require('vitreum/steps/assets.watch.js');
const server = require('vitreum/steps/server.watch.js');
const livereload = require('vitreum/steps/livereload.js');
const Proj = require('./project.json');
Promise.resolve()
.then(jsx('homebrew', './client/homebrew/homebrew.jsx', Proj.libs, './shared'))
.then(less('homebrew', './shared'))
.then(jsx('admin', './client/admin/admin.jsx', Proj.libs, './shared'))
.then(less('admin', './shared'))
.then(assets(Proj.assets, ['./shared', './client']))
.then(livereload())
.then(server('./server.js', ['server']))
.then(console.timeEnd.bind(console, label))
.then(()=>jsx('homebrew', './client/homebrew/homebrew.jsx', Proj.libs, './shared'))
.then((deps)=>less('homebrew', './shared', deps))
.then(()=>jsx('admin', './client/admin/admin.jsx', Proj.libs, './shared'))
.then((deps)=>less('admin', './shared', deps))
.then(()=>assets(Proj.assets, ['./shared', './client']))
.then(()=>livereload())
.then(()=>server('./server.js', ['server']))
.then(()=>console.timeEnd.bind(console, label))
.catch(console.error)

8
scripts/notes.js Normal file
View File

@@ -0,0 +1,8 @@
require('fixme')({
path: process.cwd(),
ignored_directories: ['node_modules/**', '.git/**', 'build/**'],
file_patterns: ['**/*.js', '**/*.jsx', '**/*.less'],
file_encoding: 'utf8',
line_length_limit: 200
});

View File

@@ -1,16 +0,0 @@
//DEPRICATE
const less = require('less');
const fs = require('fs');
console.log('you should not b using this');
less.render(fs.readFileSync('./client/homebrew/phbStyle/phb.style.less', 'utf8'), {compress : true})
.then((output) => {
fs.writeFileSync('./phb.standalone.css', output.css);
console.log('phb.standalone.css created!');
}, (err) => {
console.error(err);
});

8
scripts/snippet.test.js Normal file
View File

@@ -0,0 +1,8 @@
const snippets = require('../shared/homebrewery/snippets');
console.log(snippets);
//console.log(snippets.brew.spell());
//console.log(snippets.brew.table());
console.log(snippets.brew.noncasterTable());

View File

@@ -8,10 +8,11 @@ const mw = require('./middleware.js');
const statics = {
welcomeBrew : fs.readFileSync('./welcome.brew.md', 'utf8'),
changelog : fs.readFileSync('./changelog.md', 'utf8'),
testBrew : fs.readFileSync('./statics/test.brew.md', 'utf8'),
welcomeBrew : fs.readFileSync('./statics/welcome.brew.md', 'utf8'),
changelog : fs.readFileSync('./statics/changelog.md', 'utf8'),
faq : fs.readFileSync('./statics/faq.md', 'utf8'),
testBrew : fs.readFileSync('./statics/test.brew.md', 'utf8'),
oldTest : fs.readFileSync('./statics/oldTest.brew.md', 'utf8'),
};
@@ -44,6 +45,7 @@ router.get('/edit/:editId', mw.loadBrew, renderPage);
//Print Page
router.get('/print/:shareId', mw.viewBrew, renderPage);
router.get('/print', renderPage);
//Source page
router.get('/source/:sharedId', mw.viewBrew, (req, res, next)=>{
@@ -81,6 +83,17 @@ router.get('/changelog', (req, res, next) => {
return next();
}, renderPage);
//faq Page
router.get('/faq', (req, res, next) => {
req.brew = {
text : statics.faq,
title : 'FAQ',
editId : true
};
return next();
}, renderPage);
//New Page
router.get('/new', renderPage);

View File

@@ -48,7 +48,7 @@ const getTOC = (pages) => {
}
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})**`)

View File

@@ -70,8 +70,9 @@ const Actions = {
},
localPrint : ()=>{
localStorage.setItem('print', Store.getBrewText());
window.open('/print?dialog=true&local=print','_blank');
const key = 'print';
localStorage.setItem(key, JSON.stringify(Store.getBrew()));
window.open(`/print?dialog=true&local=${key}`,'_blank');
},
print : ()=>{
window.open(`/print/${Store.getBrew().shareId}?dialog=true`, '_blank').focus();

View File

@@ -3,9 +3,7 @@ const _ = require('lodash');
const cx = require('classnames');
const CodeEditor = require('naturalcrit/codeEditor/codeEditor.jsx');
const SnippetBar = require('./snippetbar/snippetbar.jsx');
const MetadataEditor = require('./metadataEditor/metadataEditor.jsx');
const Menubar = require('./menubar/menubar.jsx');
const splice = function(str, index, inject){
@@ -32,6 +30,10 @@ const BrewEditor = React.createClass({
view : 'code', //'code', 'style', 'meta'
};
},
isCode : function(){ return this.state.view == 'code' },
isStyle : function(){ return this.state.view == 'style' },
isMeta : function(){ return this.state.view == 'meta' },
componentDidMount: function() {
this.updateEditorSize();
@@ -53,11 +55,16 @@ const BrewEditor = React.createClass({
handleInject : function(injectText){
const lines = this.props.value.split('\n');
lines[this.cursorPosition.line] = splice(lines[this.cursorPosition.line], this.cursorPosition.ch, injectText);
const text = (this.isCode() ? this.props.brew.text : this.props.brew.style);
this.handleTextChange(lines.join('\n'));
this.refs.codeEditor.setCursorPosition(this.cursorPosition.line, this.cursorPosition.ch + injectText.length);
const lines = text.split('\n');
const cursorPos = this.refs.codeEditor.getCursorPosition();
lines[cursorPos.line] = splice(lines[cursorPos.line], cursorPos.ch, injectText);
this.refs.codeEditor.setCursorPosition(cursorPos.line, cursorPos.ch + injectText.length);
if(this.state.view == 'code') this.props.onCodeChange(lines.join('\n'));
if(this.state.view == 'style') this.props.onStyleChange(lines.join('\n'));
},
@@ -87,6 +94,9 @@ const BrewEditor = React.createClass({
//MOve this to a util.sj file
highlightPageLines : function(){
if(!this.refs.codeEditor) return;
if(!this.isCode()) return;
const codeMirror = this.refs.codeEditor.codeMirror;
const lineNumbers = _.reduce(this.props.brew.text.split('\n'), (r, line, lineNumber)=>{
@@ -95,6 +105,11 @@ const BrewEditor = React.createClass({
r.push(lineNumber);
}
if(line.indexOf('\\column') === 0){
codeMirror.addLineClass(lineNumber, 'text', 'columnSplit');
r.push(lineNumber);
}
if(_.startsWith(line, '{{') || _.startsWith(line, '}}')){
codeMirror.addLineClass(lineNumber, 'text', 'block');
}
@@ -116,19 +131,19 @@ const BrewEditor = React.createClass({
renderEditor : function(){
if(this.state.view == 'meta'){
if(this.isMeta()){
return <MetadataEditor
metadata={this.props.brew}
onChange={this.props.onMetaChange} />
}
if(this.state.view == 'style'){
if(this.isStyle()){
return <CodeEditor key='style'
ref='codeEditor'
language='css'
value={this.props.brew.style}
onChange={this.props.onStyleChange} />
}
if(this.state.view == 'code'){
if(this.isCode()){
return <CodeEditor key='code'
ref='codeEditor'
language='gfm'
@@ -140,16 +155,10 @@ const BrewEditor = React.createClass({
render : function(){
this.highlightPageLines();
return <div className='brewEditor' ref='main'>
{/*
<SnippetBar
brew={this.props.value}
onInject={this.handleInject}
onToggle={this.handgleToggle}
showmeta={this.state.showMetadataEditor} />
*/}
<Menubar
view={this.state.view}
onViewChange={this.handleViewChange}
onSnippetInject={this.handleInject}
/>

View File

@@ -9,9 +9,13 @@
border-bottom : #333 solid 1px;
}
.block{
color : blue;
color : purple;
//font-style: italic;
}
.columnSplit{
font-style : italic;
color : grey;
}
}
.brewJump{

View File

@@ -1,19 +1,60 @@
const React = require('react');
const _ = require('lodash');
const cx = require('classnames');
const SnippetMap = require('./snippet.map.js');
const SnippetGroup = require('./snippetGroup/snippetGroup.jsx');
const Menubar = React.createClass({
getDefaultProps: function() {
return {
view : '',
view : 'code',
onViewChange : ()=>{},
onSnippetInject : ()=>{},
};
},
//TODO: remove
renderDevGroup : function(){
const Snippets = require('homebrewery/snippets/brew');
const snippets = _.map(Snippets, (gen, name)=>{
return {
name,
gen,
icon : 'fa-question'
}
})
return <SnippetGroup
name='All'
icon='fa-rocket'
snippets={snippets}
onClick={this.props.onSnippetInject}
key='dev'
/>
},
renderSnippets : function(){
if(this.props.view == 'meta') return ;
let mapping;
if(this.props.view == 'code') mapping = SnippetMap.brew;
if(this.props.view == 'style') mapping = SnippetMap.style;
let groups = _.map(mapping, (group)=>{
return <SnippetGroup {...group} onClick={this.props.onSnippetInject} key={group.name} />
});
groups = groups.concat(this.renderDevGroup());
return <div className='snippets'>{groups} </div>
},
render: function(){
return <div className='menubar'>
{this.renderSnippets()}
<div className='editors'>
<div className={cx('code', {selected : this.props.view == 'code'})}
onClick={this.props.onViewChange.bind(null, 'code')}>

View File

@@ -32,4 +32,9 @@
}
}
}
.snippets{
display : flex;
height : 100%;
}
}

View File

@@ -0,0 +1,48 @@
const Snippets = require('homebrewery/snippets');
module.exports = {
brew : [
{
name : 'PHB',
icon : 'fa-book',
snippets : [
{
name : 'Spell',
icon : 'fa-magic',
gen : Snippets.brew.spell
},
{
name : 'Table',
icon : 'fa-table',
gen : Snippets.brew.table
},
]
},
{
name : 'Mods',
icon : 'fa-gear',
snippets : []
}
],
style : [
{
name : 'Print',
icon : 'fa-print',
snippets : [
{
name : 'Ink Friendly',
icon : 'fa-tint',
gen : Snippets.style.inkFriendly
},
{
name : 'A4 Page Size',
icon : 'fa-file',
gen : Snippets.style.a4
},
]
}
]
}

View File

@@ -0,0 +1,41 @@
const React = require('react');
const _ = require('lodash');
const cx = require('classnames');
const SnippetGroup = React.createClass({
getDefaultProps: function() {
return {
name : '',
icon : 'fa-rocket',
snippets : [],
onClick : function(){},
};
},
handleSnippetClick : function(snippet){
this.props.onClick(snippet.gen());
},
renderSnippets : function(){
return _.map(this.props.snippets, (snippet)=>{
return <div className='snippet' key={snippet.name} onClick={this.handleSnippetClick.bind(this, snippet)}>
<i className={'fa fa-fw ' + snippet.icon} />
{snippet.name}
</div>
})
},
render : function(){
return <div className='snippetGroup'>
<div className='text'>
<i className={'fa fa-fw ' + this.props.icon} />
<span className='groupName'>{this.props.name}</span>
</div>
<div className='dropdown'>
{this.renderSnippets()}
</div>
</div>
},
});
module.exports = SnippetGroup;

View File

@@ -0,0 +1,56 @@
.snippetGroup{
//display : inline-block;
display : flex;
height : 100%;
align-items : center;
//height : @menuHeight;
padding : 0px 5px;
cursor : pointer;
font-size : 0.6em;
font-weight : 800;
///line-height : @menuHeight;
text-transform : uppercase;
border-right : 1px solid black;
i{
vertical-align : middle;
margin-right : 3px;
font-size : 1.2em;
}
&:hover, &.selected{
background-color : #999;
}
.text{
//line-height : @menuHeight;
.groupName{
font-size : 10px;
}
}
&:hover{
.dropdown{
visibility : visible;
}
}
.dropdown{
position : absolute;
top : 100%;
visibility : hidden;
z-index : 1000;
margin-left : -5px;
padding : 0px;
background-color : #ddd;
.snippet{
.animate(background-color);
padding : 10px;
cursor : pointer;
font-size : 10px;
i{
margin-right : 8px;
font-size : 13px;
}
&:hover{
background-color : #999;
}
}
}
}

View File

@@ -1,94 +0,0 @@
const React = require('react');
const _ = require('lodash');
const cx = require('classnames');
const Snippets = require('./snippets/snippets.js');
const execute = function(val, brew){
if(_.isFunction(val)) return val(brew);
return val;
}
const Snippetbar = React.createClass({
getDefaultProps: function() {
return {
brew : '',
onInject : ()=>{},
onToggle : ()=>{},
showmeta : false
};
},
handleSnippetClick : function(injectedText){
this.props.onInject(injectedText)
},
renderSnippetGroups : function(){
return _.map(Snippets, (snippetGroup)=>{
return <SnippetGroup
brew={this.props.brew}
groupName={snippetGroup.groupName}
icon={snippetGroup.icon}
snippets={snippetGroup.snippets}
key={snippetGroup.groupName}
onSnippetClick={this.handleSnippetClick}
/>
})
},
render : function(){
return <div className='snippetBar'>
{this.renderSnippetGroups()}
<div className={cx('toggleMeta', {selected: this.props.showmeta})}
onClick={this.props.onToggle}>
<i className='fa fa-bars' />
</div>
</div>
}
});
module.exports = Snippetbar;
const SnippetGroup = React.createClass({
getDefaultProps: function() {
return {
brew : '',
groupName : '',
icon : 'fa-rocket',
snippets : [],
onSnippetClick : function(){},
};
},
handleSnippetClick : function(snippet){
this.props.onSnippetClick(execute(snippet.gen, this.props.brew));
},
renderSnippets : function(){
return _.map(this.props.snippets, (snippet)=>{
return <div className='snippet' key={snippet.name} onClick={this.handleSnippetClick.bind(this, snippet)}>
<i className={'fa fa-fw ' + snippet.icon} />
{snippet.name}
</div>
})
},
render : function(){
return <div className='snippetGroup'>
<div className='text'>
<i className={'fa fa-fw ' + this.props.icon} />
<span className='groupName'>{this.props.groupName}</span>
</div>
<div className='dropdown'>
{this.renderSnippets()}
</div>
</div>
},
});

View File

@@ -1,72 +0,0 @@
.snippetBar{
@height : 25px;
position : relative;
height : @height;
background-color : #ddd;
.toggleMeta{
position : absolute;
top : 0px;
right : 0px;
height : @height;
width : @height;
cursor : pointer;
line-height : @height;
text-align : center;
&:hover, &.selected{
background-color : #999;
}
}
.snippetGroup{
display : inline-block;
height : @height;
padding : 0px 5px;
cursor : pointer;
font-size : 0.6em;
font-weight : 800;
line-height : @height;
text-transform : uppercase;
border-right : 1px solid black;
i{
vertical-align : middle;
margin-right : 3px;
font-size : 1.2em;
}
&:hover, &.selected{
background-color : #999;
}
.text{
line-height : @height;
.groupName{
font-size : 10px;
}
}
&:hover{
.dropdown{
visibility : visible;
}
}
.dropdown{
position : absolute;
top : 100%;
visibility : hidden;
z-index : 1000;
margin-left : -5px;
padding : 0px;
background-color : #ddd;
.snippet{
.animate(background-color);
padding : 5px;
cursor : pointer;
font-size : 10px;
i{
margin-right : 8px;
font-size : 13px;
}
&:hover{
background-color : #999;
}
}
}
}
}

View File

@@ -7,9 +7,9 @@ const BrewRenderer = require('../brewRenderer/brewRenderer.smart.jsx');
const BrewInterface = React.createClass({
handleSplitMove : function(){
console.log('split move!');
const BrewEditor = this.refs.editor.refs.wrappedComponent;
BrewEditor.updateEditorSize();
},
render: function(){
return <SplitPane onDragFinish={this.handleSplitMove} ref='pane'>

View File

@@ -133,6 +133,11 @@ const BrewRenderer = React.createClass({
return this.lastRender;
},
//TODO: This is pretty bad
renderStyle : function(){
return <style>{this.props.brew.style.replace(/;/g, ' !important;')}</style>
},
render : function(){
if(this.props.brew.version == 1) return <OldBrewRenderer value={this.props.brew.text} />;
@@ -146,7 +151,7 @@ const BrewRenderer = React.createClass({
<RenderWarnings />
<style>{this.props.brew.style}</style>
{this.renderStyle()}
<div className='pages' ref='pages'>
{this.renderPages()}

View File

@@ -26,11 +26,18 @@ renderer.paragraph = function(text){
return res;
};
renderer.image = function(href, title, text){
return `<img src="${href}" class="${text.split(',').join(' ')}"></img>`;
};
module.exports = {
marked : Markdown,
render : (rawBrewText)=>{
blockCount = 0;
rawBrewText = rawBrewText.replace(/\\column/g, '{{columnSplit }}')
let html = Markdown(rawBrewText, {renderer : renderer, sanitize: true});
//Close all hanging block tags
html += _.times(blockCount, ()=>{return '</div>'}).join('\n');

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 KiB

View File

Before

Width:  |  Height:  |  Size: 327 B

After

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 530 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,211 @@
///////////////////
.spell{
ul:first-of-type{
margin-top : -0.5em;
margin-bottom : 0.5em;
list-style-type : none;
&+p{
text-indent : 0em;
}
}
}
.monster{
.breakAvoid();
.pseudoBorder();
.pseudoShadow();
padding : 17px 14px;
table:nth-of-type(1){
margin-bottom : 0.4em;
margin-top : 0.4em;
color : @crimson;
tbody tr { background-color: transparent };
}
ul:nth-of-type(1),ul:nth-of-type(2){
list-style: none;
padding-left : 1em;
text-indent : -1em;
margin-bottom : 0.5em;
strong{
color : @crimson;
}
}
&:before{
top : 8px;
right : 7px;
bottom : 19px;
left : 7px;
background-color : #FDF1DC;
border-image-slice : 8;
border-image-source : @monsterBorder;
border-image-width : 8px;
}
&.wide{
column-count : 2;
}
}
.note{
.useSansSerif();
.breakAvoid();
.pseudoBorder();
.pseudoShadow();
margin : 9px 0px;
padding : 17px 17px;
&:before{
top : 9px;
right : 9px;
bottom : 19px;
left : 9px;
background-color : @green;
border-width : 11px;
border-image-outset : 9px 0px;
border-image-slice : 11;
border-image-source : @noteBorder;
}
h2,h3,h4{
.useSansSerif();
color : black;
}
p, ul{
font-size : 0.352cm;
line-height : 1.1em;
}
&.alt{
&:before{
border-style : solid;
border-width : 7px;
border-image-outset : 4px;
border-image-slice : 12;
border-image-source : @descriptiveBorder;
}
}
}
.frame{
.breakAvoid();
.pseudoBorder();
padding : 25px 17px;
&:before{
top : 25px;
right : 17px;
bottom : 25px;
left : 17px;
background-color : white;
border-image-outset : 25px 17px;
border-image-slice : 150 200 150 200;
border-image-source : @frameBorder;
border-image-width : 47px;
}
}
.footnote{
position : absolute;
right : 80px;
bottom : 28px;
z-index : 150;
width : 200px;
font-size : 0.9em;
color : @gold;
text-align : right;
}
//*****************************
// * TABLE OF CONTENTS
// *****************************/
.toc{
h1{
text-align : center;
}
li{
margin-bottom : 3px;
strong, em::after{
font-family : BookInsanity;
font-size : 13px;
font-style : normal;
font-weight : 500;
color : black;
}
em{
display : block;
overflow : hidden;
width : auto;
font-style : normal;
white-space : nowrap;
&:after{
content : " ..............................................................................................................";
}
}
strong{
float : right;
margin-left : 4px;
}
h3{
margin-top : 15px;
em{ color : @crimson; }
em::after{ display : none; }
}
h4{
margin-top : 10px;
em{ color : @crimson; }
}
}
a{
color : black;
text-decoration : none;
&:hover{
text-decoration : underline;
}
}
ul{
padding-left : 0;
list-style-type : none;
}
}
.wide{
column-span : all;
-webkit-column-span : all;
-moz-column-span : all;
}
.oneColumn{
column-count : 1;
// column-gap : 1cm;
}
.twoColumn{
column-count : 2;
//column-fill: auto;
////column-gap : 1cm;
}
.threeColumn{
column-count : 3;
//column-gap : 1cm;
}
.fourColumn{
column-count : 4;
//column-gap : 1cm;
}
.columnSplit{
visibility : hidden;
-webkit-column-break-bfore : always;
break-before : column;
}
.brushed{
border-image-outset : 25px 17px;
border-image-repeat : round;
border-image-slice : 1250 1250 1250 1250;
border-image-width : 1250px;
border-image-source : url('http : //i.imgur.com/nzPYZyD.png');
}
//basics
.left{
text-align : left;
}
.right{
text-align : right;
}
.center{
text-align : center;
}
.bold{
font-weight : 800;
}
.sansSerif{
.useSansSerif();
}

View File

@@ -8,8 +8,6 @@
@monsterStatBackground : #FDF1DC;
@teal : blue;
.colorElements(@color){
table tbody{
@@ -17,8 +15,24 @@
background-color : @color;
}
}
&.note:before{
background-color: @color;
}
}
@crimson : #58180D;
@red : #9c2b1b;
@gold : #c9ad6a; //brown?
@green : #e0e5c1;
@yellow : #faf7ea; //same as background?
@teal : blue;
@blue : blue;
//TODO make a color mixin generator
.teal{ .colorElements(@teal); }
.blue{ .colorElements(@blue); }
.green{ .colorElements(@green); }
.yellow{ .colorElements(@yellow); }
.gold{ .colorElements(@gold); }
.red{ .colorElements(@red); }

View File

@@ -0,0 +1,172 @@
pre{
font-family : monospace;
background-color : @yellow;
padding : 12px;
border: 1px solid #bfbfbf;
white-space: pre-wrap;
color : #333;
-webkit-column-break-inside : avoid;
column-break-inside : avoid;
}
hr{
visibility : visible;
height : 6px;
margin : 4px 0px;
background-image : @dividerImg;
background-size : 100% 100%;
border : none;
}
p{
padding-bottom : 0.8em;
line-height : 1.3em;
&+p{
margin-top : -0.8em;
}
}
blockquote{
font-style : italic;
&>p{
line-height: 1.8em;
&:first-child::first-line{
//TODO: Find the right font for block quotes
font-style: normal;
font-family: ScalySansSmallCaps;
}
}
.cite{
font-style: normal;
text-align: right;
}
}
//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 : MrEaves;
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 : Solbera;
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;
}
//******************************
// LISTS
//******************************
ul ul,ol ol,ul ol,ol ul{
margin-bottom : 0px;
margin-left : 1.5em;
}
li{
-webkit-column-break-inside : avoid;
column-break-inside : avoid;
}
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;
}
//*****************************
// * TABLE
// *****************************/
table{
.useSansSerif();
width : 100%;
margin-bottom : 1em;
font-size : 10pt;
thead{
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 : @green;
}
}
}
}

View File

@@ -53,18 +53,3 @@
font-weight: normal;
font-style: normal;
}
//TODO: move the useSansSerif into here
.useSansSerif(){
font-family : ScalySans;
em{
font-family : ScalySans;
font-style : italic;
}
strong{
font-family : ScalySans;
font-weight : 800;
letter-spacing : -0.02em;
}
}

View File

@@ -1,12 +1,16 @@
@footerImg : url('/assets/homebrewery/phb_style/img/footer.png');
@footerFlipImg : url('/assets/homebrewery/phb_style/img/footer_flip.png');
@dividerImg : url('/assets/homebrewery/phb_style/img/divider.png');
@frameBorder : url('/assets/homebrewery/phb_style/img/frame_border.png');
@monsterBorder : url('/assets/homebrewery/phb_style/img/monster_border.png');
@noteBorder : url('/assets/homebrewery/phb_style/img/note_border.png');
@descriptiveBorder : url('/assets/homebrewery/phb_style/img/desc_border.png');
@shadowBorder : url('/assets/homebrewery/phb_style/img/shadow_border.png');
@phbBG : url('/assets/homebrewery/phb_style/img/phb_bg.jpg');
@darkBG : url('/assets/homebrewery/phb_style/img/phb_dark_bg.jpg');
@dmgBG : url('/assets/homebrewery/phb_style/img/dmg_bg.jpg');
@monsterBG : url('/assets/homebrewery/phb_style/img/monster_bg.jpg');

View File

@@ -1,3 +1,6 @@
//TODO: Remove this
/*
@media print {
.phb.v2{
.descriptive, blockquote{
@@ -5,34 +8,16 @@
}
}
}
*/
.phb.v2{
@import './phb.mixins.less';
@import './phb.fonts.less';
@import './phb.colors.less';
@import './phb.img.less';
@page { margin: 0; }
.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;
}
& *{
-webkit-print-color-adjust : exact;
}
.useColumns();
@import './phb.blocks.less';
@import './phb.elements.less';
counter-increment : phb-page-numbers;
position : relative;
z-index : 15;
@@ -42,6 +27,10 @@
width : 215.9mm;
padding : 1.0cm 1.7cm;
padding-bottom : 1.5cm;
column-count : 2;
column-fill : auto;
column-gap : 1cm;
column-width : 8cm;
background-color : @background;
background-image : @phbBG;
font-family : BookInsanity;
@@ -49,150 +38,64 @@
text-rendering : optimizeLegibility;
page-break-before : always;
page-break-after : always;
@page { margin: 0; } //TODO: ????
& *{
-webkit-print-color-adjust : exact;
}
//*****************************
// * FOOTER
// *****************************/
&:after{
content : "Made with The Homebrewery";
position : absolute;
bottom : 0px;
left : 0px;
z-index : 100;
height : 50px;
width : 100%;
background-image : @footerImg;
background-size : cover;
padding: 28px 63px;
box-sizing: border-box;
color : lighten(@gold, 0%);
font-size: 0.7em;
}
&:nth-child(even){
&:after{
background-image: @footerFlipImg;
text-align: right;
}
&:before{
left : 2px;
}
.footnote{
left : 80px;
text-align : left;
}
}
&:before{
content : counter(phb-page-numbers);
position : absolute;
right : 2px;
bottom : 22px;
width : 50px;
font-size : 0.9em;
color : @gold;
text-align : center;
}
//*****************************
// * 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 : MrEaves;
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 : Solbera;
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{
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 : @noteBorder 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;
}
//pre+blockquote, h2+blockquote, h3+blockquote, h4+blockquote, h5+blockquote {
// margin-top : 13px;
//}
//*****************************
// * MONSTER STAT BLOCK
// *****************************/
/*
hr+blockquote{
position : relative;
padding-top : 15px;
@@ -252,107 +155,46 @@
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 : @footerImg;
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;
}
// 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;
column-break-inside : avoid;
overflow: hidden; /* Firefox fix */
}
//Better spacing for spell blocks
h4+p+hr+ul{
margin-top : -0.5em
}
//Text indent right after table
table+p{
text-indent : 1em;
}
// 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;
column-break-inside : avoid;
}
//*****************************
// * SPELL LIST
// *****************************/
/*
.spellList{
.useSansSerif();
column-count : 4;
@@ -378,42 +220,43 @@
//*****************************
// * PRINT
// *****************************/
&.print{
blockquote{
box-shadow : none;
}
}
// &.print{
// blockquote{
// box-shadow : none;
// }
// }
//*****************************
// * WIDE
// *****************************/
/*
.wide{
column-span : all;
-webkit-column-span : all;
-moz-column-span : all;
}
}*/
//*****************************
// * CLASS TABLE
// *****************************/
.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 : round;
border-image-slice : 150 200 150 200;
border-image-source : @frameBorder;
border-image-width : 47px;
h5{
margin-bottom : 10px;
}
}
// .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 : round;
// border-image-slice : 150 200 150 200;
// border-image-source : @frameBorder;
// border-image-width : 47px;
// h5{
// margin-bottom : 10px;
// }
// }
//*****************************
// * CLASS TABLE
// *****************************/
/*
.descriptive{
display : block-inline;
margin-bottom : 1em;
@@ -445,61 +288,36 @@
pre+.descriptive{
margin-top : 8px;
}
//*****************************
// * TABLE OF CONTENTS
// *****************************/
.toc{
-webkit-column-break-inside : avoid;
column-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;
}
}
*/
//*****************************
// * Old Stuff
// *****************************/
//Double hr for full width elements
hr+hr+blockquote{
column-span : all;
-webkit-column-span : all;
-moz-column-span : all;
}
// //Double hr for full width elements
// hr+hr+blockquote{
// column-span : all;
// -webkit-column-span : all;
// -moz-column-span : all;
// }
//*****************************
// * CLASS TABLE
// *****************************/
hr+table{
margin-top : -5px;
margin-bottom : 50px;
padding-top : 10px;
border-collapse : separate;
background-color : white;
border : initial;
border-style : solid;
border-image-outset : 37px 17px;
border-image-repeat : round;
border-image-slice : 150 200 150 200;
border-image-source : @frameBorder;
border-image-width : 47px;
}
h5+hr+table{
column-span : all;
-webkit-column-span : all;
-moz-column-span : all;
}
// hr+table{
// margin-top : -5px;
// margin-bottom : 50px;
// padding-top : 10px;
// border-collapse : separate;
// background-color : white;
// border : initial;
// border-style : solid;
// border-image-outset : 37px 17px;
// border-image-repeat : round;
// border-image-slice : 150 200 150 200;
// border-image-source : @frameBorder;
// border-image-width : 47px;
// }
// h5+hr+table{
// column-span : all;
// -webkit-column-span : all;
// -moz-column-span : all;
// }
}

View File

@@ -0,0 +1,48 @@
.breakAvoid(){
column-break-inside : avoid;
-webkit-column-break-inside : avoid;
}
.pseudoBorder(){
position : relative;
&:before{
content : '';
position : absolute;
z-index : -2;
box-sizing : border-box;
border-style : solid;
border-image-repeat : round;
}
}
.pseudoShadow(){
position : relative;
&:after{
content : '';
position : absolute;
z-index : -1;
box-sizing : border-box;
top : 4px;
right : 0px;
bottom : 10px;
left : 0px;
border-style : solid;
border-image-repeat : round;
border-image-slice : 13 13;
border-image-source : @shadowBorder;
border-image-width : 11px;
}
}
.useSansSerif(){
font-family : ScalySans;
em{
font-family : ScalySans;
font-style : italic;
}
strong{
font-family : ScalySans;
font-weight : 800;
letter-spacing : -0.02em;
}
}

View File

@@ -0,0 +1,82 @@
const _ = require('lodash');
const Data = require('./random.data.js');
const getFeature = (level)=>{
let res = []
if(_.includes([4,6,8,12,14,16,19], level+1)){
res = ['Ability Score Improvement']
}
res = _.union(res, _.sampleSize(Data.abilities, _.sample([0,1,1,1,1,1])));
if(!res.length) return '─';
return res.join(', ');
};
module.exports = {
casterTable : ()=>{
let featureScore = 1
const rows = _.map(Data.levels, (lvlText, level)=>{
featureScore += _.random(0,1);
return '| ' + [
lvlText,
'+'+Math.floor(level/4 + 2),
getFeature(level),
'+'+featureScore
].join(' | ') + ' |';
}).join('\n');
return `{{frame,wide
##### ${Data.rand('classes')}
| Level | Proficiency Bonus | Features | Cantrips Known | Spells Known | 1st | 2nd | 3rd | 4th | 5th | 6th | 7th | 8th | 9th |
|:---:|:---:|:---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
${rows}
}}`;
},
halfcasterTable : ()=>{
let featureScore = 1
const rows = _.map(Data.levels, (lvlText, level)=>{
featureScore += _.random(0,1);
return '| ' + [
lvlText,
'+'+Math.floor(level/4 + 2),
getFeature(level),
'+'+featureScore
].join(' | ') + ' |';
}).join('\n');
return `{{frame,wide
##### ${Data.rand('classes')}
| Level | Proficiency Bonus | Features | 1st | 2nd | 3rd | 4th | 5th |
|:---:|:---:|:---|:---:|:---:|:---:|:---:|:---:|
${rows}
}}`;
},
noncasterTable : ()=>{
let featureScore = 1
const rows = _.map(Data.levels, (lvlText, level)=>{
featureScore += _.random(0,1);
return '| ' + [
lvlText,
'+'+Math.floor(level/4 + 2),
getFeature(level),
'+'+featureScore
].join(' | ') + ' |';
}).join('\n');
return `{{frame
##### ${Data.rand('classes')}
| Level | Proficiency Bonus | Features | ${Data.rand('abilities')} |
|:---:|:---:|:---|:---:|
${rows}
}}`;
}
}

View File

@@ -0,0 +1,19 @@
const _ = require('lodash');
module.exports = _.merge(
require('./spell.snippet.js'),
require('./table.snippet.js'),
require('./class.snippet.js'),
require('./note.snippet.js'),
require('./monster.snippet.js'),
require('./toc.snippet.js')
//wide
//colors
//brushed
//font
//alignment
);

View File

@@ -0,0 +1,74 @@
const _ = require('lodash');
const Data = require('./random.data.js');
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 getAttributes = ()=>{
return `
- **Saving Throws**
- **Condition Immunities** " + genList(["groggy", "swagged", "weak-kneed", "buzzed", "groovy", "melancholy", "drunk"], 3),
- **Senses** passive Perception " + _.random(3, 20),
- **Languages** ${Data.rand(["Common", "Pottymouth", "Gibberish", "Latin", "Jive"], 2).join(', ')}
- **Challenge** ${_.random(0, 15)} (${_.random(10,10000)} XP)
`;
}
const getAbilities = ()=>{
}
const getActions = ()=>{
}
module.exports = {
monster : ()=>{
const stats = '';
return `{{monster
## ${Data.rand('creatures')}
*${Data.rand('sizes')}, ${Data.rand('alignments')}*
---
- **Armor Class** ${_.random(10,20)}
- **Hit Points** ${_.random(1, 150)} (1d4 + 5)
- **Speed** ${ _.random(0,50)} ft
---
|STR|DEX|CON|INT|WIS|CHA|
|:---:|:---:|:---:|:---:|:---:|:---:|
${getStats()}
---
${getAttributes()}
---
Abilities
### Actions
}}`
}
};

View File

@@ -0,0 +1,22 @@
const _ = require('lodash');
const Data = require('./random.data.js');
module.exports = {
note : ()=>{
return `{{note
##### ${Data.rand('abilities')}
${Data.rand('sentences', 6, 4).join(' ')}
}}`
},
altnote : ()=>{
return `{{note,alt
##### ${Data.rand('abilities')}
${Data.rand('sentences', 6, 4).join(' ')}
}}`
}
}

View File

@@ -0,0 +1,421 @@
const _ = require('lodash');
const Data = {
rand : (name, max = 1, min = 1)=>{
const data = (Data[name] ? Data[name] : name);
return _.sampleSize(data, _.random(min, max));
},
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`,
`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`,
`Vyse's Skies`,
`White Greatness III`,
`Yellow Divinity`,
`Zidane's Ghost`
],
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.`
],
classes : [
'Archivist',
'Armadillomaster',
'Beat Priest',
'Beer Mentalist',
'Berserker-Typist',
'Bonsai Hooligan',
'Candy Finder',
'Coffeemancer',
'Concierge',
'Corn Theif',
'Cottonsmith',
'Dirtmistress',
'Fancyman',
'Fishmongerer',
'Fletcher',
'Flow Robber',
'Haberdasher',
'Hamster Lady',
'Jam Robber',
'Linguist',
'Lizard Trainer',
'Manicurist',
'Markermaster',
'Mint Handler',
'Narwhalologer',
'Notary',
'Otter Mentalist',
'Plastic Diviner',
'Rhymemancer',
'Rum Buster',
'Whaleologer',
],
gear : [
`a squeegee`,
'6 rubber chickens',
'10 lint fluffs',
'1 button',
'a cherished lost sock',
'a small doll',
'hopes and dreams',
'1st born child',
'3rd born child',
'a crushed button worth at least 1cp',
'discarded gum wrapper',
`Broch of Air Blasts`,
`Elven Leather Armor`,
`Glaive of the Deathly Viper`,
`Mystical Eagle's Ointment of the Eagles`,
`Mystical Scintillating Cudgel`,
`Wise Thinker's Anklet`,
`The four fragments of the Disk of Madness`
],
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",
],
effects : [
'Induces politicians to parade through the streets naked, and makes the nearest unbetrothed prince or princess dance around the maypole making dirty jokes.',
'Tricks enchanted princesses to spin straw into gold, and makes princesses trapped in towers steal from the rich and give to the poor.',
'Drives the man or woman of your dreams to jump up and down on the spot, and makes angry dragons grow onions wherever they walk.',
'Causes enchanted talking animals to fall down dead, and makes large pumpkins attract love-struck unicorns.',
'Induces officers of the law to adopt small, fluffy bunnies as pets, and makes enchanted wooden puppets vomit gold coins.',
'Causes accountants to give you all of their possessions, and makes officers of the law grow mushrooms out of their ears.',
'Induces goats to eat until they burst, and makes men with small heads vomit gold coins.',
'Tricks enchanted princesses to turn into small pumpkins, and makes evil landlords declare themselves king.',
'Induces your enemies to steal from the palace cook, and makes rich merchants propose marriage.',
'Causes evil landlords to vomit gold coins, and makes the nearest unbetrothed prince or princess drink beer.',
'Induces men with small heads to grow mushrooms out of their ears, and makes witches steal from the rich and give to the poor.',
`Conjures food with energy equal to whatever was used to cast the spell.`,
`Allows a living target to withstand extreme cold.`,
`Conjures a thick fog that acts as a smoke screen.`,
`Creates a bubble in which time is stopped for a short period.`,
`Creates several bolts of shadowy energy.`,
`Causes a living target to panic for a period of time.`,
`Creates a floating scroll and quill that'll write down everything the caster or target says for a period of time.`,
`Causes whoever is targeted to enter a state of confusion for a period of time.`,
`Creates a magical barrier that blocks all with dark intentions or dark influences over them.`,
`Creates a bolt of demonic energy.`,
`Causes whoever is targeted to drop whatever they're holding.`
],
effects2 : [
'Unless they pass a Constitution save, the creature gains 1 level of Exhaustion.',
'Pushed 5 feet unless they pass a Strength save. ',
'Unless they pass a Wisdom save, the creature is Charmed.',
'Unless they pass a Wisdom save, the creature is Frightened. The creature can remake this save on each of their turns.',
'Unless they pass a Wisdom save, the creature is Frightened. The creature can remake this save on each of their turns.',
'Unless they pass a Wisdom save, the creature is Paralyzed. The creature can remake this save on each of their turns.',
'Pushed 25 feet unless they pass a Strength save. ',
'Unless they pass a Constitution save, the creature is Poisoned. The creature can remake this save on each of their turns.',
'Unless they pass a Wisdom save, the creature is Charmed.',
'Unless they pass a Constitution save, the creature is Slowed. The creature can remake this save on each of their turns.',
'Unless they pass a Constitution save, the creature is Slowed. The creature can remake this save on each of their turns.',
'Knocked Prone unless they pass a Dexterity save. ',
'Unless they pass a Constitution save, the creature is Deafened. The creature can remake this save on each of their turns.',
'Knocked Prone unless they pass a Dexterity save. ',
'Unless they pass a Constitution save, the creature gains 1 level of Exhaustion.',
'Knocked Prone unless they pass a Dexterity save. ',
'Unless they pass a Constitution save, the creature is Deafened. The creature can remake this save on each of their turns.',
'Unless they pass a Constitution save, the creature gains 1 level of Exhaustion.',
'Pushed 20 feet unless they pass a Strength save. ',
'Resistance to Radiant damage until 1 round'
],
attacks : [
`Aquatic Press of the Romantic Demons`,
`Barbarian Raider Pinch of the Cemetary`,
`Beetle Hold of the Fangs`,
`Confident Badger Pinch of Lyres`,
`Emperor's Roll of the Nine Volcanos`,
`Firey Rake of the Endings`,
`Fortuitous Underhook of the Wolves`,
`God's Knee of Blessings`,
`Hawk Dance`,
`Heavenly Rat's Roll`,
`Hellish Meteor`,
`High Noose of the Ruthless Guardian`,
`Hold of Poisons`,
`King Drop of the Fighting Protectors`,
`Leg Clap of the Dogs`,
`Northeastern Seventeen Cats Claw`,
`Phantasmal Plague Finger`,
`Pose of Perfect Sunsets`,
`Seal Hammer of the Forty Sages`,
`Shaman Pull of Destructions`,
`Southeastern Automaton Pull`,
`Southwestern Eighty Chants Clap`,
`Tackle of Foul Leaves`,
`Tornado of the Uncounted Hawks`,
`Yielding Throw of the Mills`,
],
abilities : [
"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"
],
alignments : [
"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",
"Lawful Gossipy",
"Neurotic Good",
"Sarcastic Evil",
"Snotty Neutral",
"Wannabe Good"
],
sizes : ['Microscopic', 'Tiny', 'Small', 'Medium', 'Large', 'Gargantuan', 'Stupidly vast'],
levels : ["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th", "11th", "12th", "13th", "14th", "15th", "16th", "17th", "18th", "19th", "20th"],
sentences : [
`The suspicion arises the narrator of the tale is actually a demon.`,
`There is a predicted hurricane - but it's not what was expected, and this complicates the plans of the protagonist.`,
`The antagonist's believes their life has changed for the strange - this turns out to be this is due to being lied to by others`,
`An accidental cuddle leads to complications.`,
`It's revealed that everything that is happening is all a dream.`,
`There is a sudden hurricane.`,
`The alternate protagonist is revealed to be a different race/species than thought, which suddenly makes what's going on much clearer.`,
`Thanks to alien forces, the characters end up in the earth's past.`,
`Thanks to alien forces, the secondary protagonist ends up in a world after an apocalypse.`,
`Due to a panic attack a character has to get psychological therapy.`,
],
creatures : [
"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"
]
};
module.exports = Data;

View File

@@ -0,0 +1,41 @@
const _ = require('lodash');
const Data = require('./random.data.js');
const levels = ['1st', '2nd', '3rd', '4th', '5th', '6th', '7th', '8th', '9th'];
const schools = ['abjuration', 'conjuration', 'divination', 'enchantment', 'evocation', 'illusion', 'necromancy', 'transmutation'];
module.exports = {
spell : ()=>{
let components = _.sampleSize(['V', 'S', 'M'], _.random(1,3)).join(', ');
if(components.indexOf('M') !== -1){
components += ` (${Data.rand('gear',3).join(', ')})`
}
const duration = _.sample([
'Until dispelled',
'1 round',
'Instantaneous',
'Concentration, up to 10 minutes',
'1 hour'
]);
const description = Data.rand('effects', 2).concat(Data.rand('effects2')).join(' ');
return `{{spell
#### ${_.sample(Data.spellNames)}
*${_.sample(levels)}-level ${_.sample(schools)}*
- **Casting Time:** ${_.sample(['1 action', 'Reaction', '10 minutes', '1 hour'])}
- **Range:** ${_.sample(['Self', 'Touch', '30 feet', '60 feet'])}
- **Components:** ${components}
- **Duration:** ${duration}
${description}
}}`;
}
}

View File

@@ -0,0 +1,62 @@
const _ = require('lodash');
const Data = require('./random.data.js');
/*
- Roll
- Level
- Cost
- spell lists
- cost
- Class
*/
const columns = {
roll : (rows)=>{
return _.concat([`d${rows}`, ':---:'], _.times(rows, (i)=>i+1));
},
level : (rows)=>{
return _.concat([`${_.sample(Data.classes)} Level`, ':---:'], _.times(rows, (i)=>Data.levels[i*2]));
},
spell : (rows)=>{
return _.concat(['Spells', ':---'], _.times(rows, (i)=>{
return `_${Data.rand('spellNames', 2).join(', ')}_`
}));
},
cost : (rows)=>{
return _.concat([`Cost`, '---:'], _.times(rows, (i)=>{
return _.sample(['1 gp', '10 gp', '5 cp', '10,000 gp', '200 sp', '1 pp', '2 gp']);
}));
},
gear : (rows)=>{
return _.concat([_.sample(['Equipment', 'Reward', 'Treasure']), ':---'], _.times(rows, (i)=>{
return Data.rand('gear');
}));
}
}
module.exports = {
table : () => {
const rows = _.sample([4,6,8,10]);
const cols = [
columns.roll(rows),
columns.level(rows),
columns.gear(rows)
];
return _.times(rows + 2, (i)=>{
if(i==1){
return '|' + _.map(cols, (col)=>col[i]).join('|') + '|';
}else{
return '| ' + _.map(cols, (col)=>col[i]).join(' | ') + ' |';
}
}).join('\n');
}
}

View File

@@ -0,0 +1,112 @@
const _ = require('lodash');
const Store = require('homebrewery/brew.store.js');
const getTOC = (text) => {
const pages = text.split('\\page');
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 : []
});
}
let 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 = {
//TODO: TOC not perfect yet
toc : (text)=>{
text = text || Store.getBrewCode();
console.log(getTOC(text));
const TOC = getTOC(text)
const markdown = _.reduce(TOC, (r, g1, idx1)=>{
r.push(`- ### [**${g1.page}** *${g1.title}*](#p${g1.page})`)
if(g1.children.length){
_.each(g1.children, (g2, idx2) => {
r.push(` - #### [**${g2.page}** *${g2.title}*](#p${g2.page})`)
if(g2.children.length){
_.each(g2.children, (g3, idx3) => {
r.push(` - [**${g3.page}** *${g3.title}*](#p${g3.page})`)
});
}
});
}
return r;
}, []).join('\n');
return `{{toc
# Contents
${markdown}
}}`;
/*
- ### [**4** *Preface*](#p3)
- ### [**5** *Introduction*](#p3)
- [**5** *Worlds of Adventure*](#p5)
- [**6** *Using This Book*](#p5)
- [**6** *How to Play*](#p5)
- [**7** *Adventures*](#p5)
- ### [**5** *Introduction*](#p3)
- #### [**5** *Worlds of Adventure*](#p5)
- [**6** *Using This Book*](#p5)
- [**6** *How to Play*](#p5)
- #### [**7** *Adventures*](#p5)
}}
`;*/
}
}

View File

@@ -0,0 +1,4 @@
module.exports = {
brew : require('./brew'),
style : require('./style')
}

View File

@@ -0,0 +1,8 @@
module.exports = {
a4 : ()=>{
return `.phb{
width : 210mm;
height : 296.8mm;
}`;
}
}

View File

@@ -0,0 +1,12 @@
module.exports = {
dmg : ()=>{
return `.phb{
background-image: url('/assets/homebrewery/phb_style/img/dmg_bg.jpg');
}`;
},
dark: ()=>{
return `.phb{
background-image: url('/assets/homebrewery/phb_style/img/phb_dark_bg.jpg');
}`;
}
}

View File

@@ -0,0 +1,7 @@
const _ = require('lodash');
module.exports = _.merge(
require('./ink.snippet.js'),
require('./a4.snippet.js'),
require('./bg.snippet.js')
);

View File

@@ -0,0 +1,9 @@
module.exports = {
inkFriendly : ()=>{
return `.phb{ background : white;}
.phb img{ display : none;}
.phb hr+blockquote{background : white;}`;
}
}

View File

@@ -38,28 +38,29 @@ var Nav = {
href : null,
newTab : false,
onClick : function(){},
color : null
color : null,
collaspe : false
};
},
handleClick : function(){
this.props.onClick();
},
render : function(){
var classes = cx('navItem', this.props.color, this.props.className);
var classes = cx('navItem', this.props.color, this.props.className, {collaspe : this.props.collaspe});
var icon;
if(this.props.icon) icon = <i className={'fa ' + this.props.icon} />;
const props = _.omit(this.props, ['newTab']);
const props = _.omit(this.props, ['newTab', 'collaspe']);
if(this.props.href){
return <a {...props} className={classes} target={this.props.newTab ? '_blank' : '_self'} >
{this.props.children}
<span>{this.props.children}</span>
{icon}
</a>
}else{
return <div {...props} className={classes} onClick={this.handleClick} >
{this.props.children}
<span>{this.props.children}</span>
{icon}
</div>
}

View File

@@ -1,3 +1,4 @@
nav{
background-color : #333;
.navContent{
@@ -41,6 +42,7 @@ nav{
}
.navItem{
.animate(background-color);
display : inline-block;
padding : 8px 12px;
cursor : pointer;
background-color : #333;
@@ -53,6 +55,28 @@ nav{
margin-left : 5px;
font-size : 13px;
}
&.collaspe{
overflow : hidden;
i{
margin-left : 0px;
}
span{
display : inline-block;
visibility : hidden;
overflow : hidden;
width : 0px;
white-space : nowrap;
}
&:hover{
span{
visibility : visible;
width : auto;
}
i{
margin-left : 5px;
}
}
}
&.tealLight:hover{ background-color : @tealLight };
&.teal:hover{ background-color : @teal };
&.greenLight:hover{ background-color : @greenLight };

View File

@@ -1,5 +1,9 @@
# changelog
The self-discovery aspect of the snippets isn't working out.
## BIG NEWS
With the next major release of Homebrewery, v3.0.0, this tool *will no longer support raw HTML input for brew code*. Most issues and errors users are having are because of this feature and it's become too taxing to help and fix these issues.

107
statics/faq.md Normal file
View File

@@ -0,0 +1,107 @@
- Submitting work created on this site to DMs Guild
# I lost my brew?
- If you made any edits with an account, you can go to that account's page
- Homebrewery stores the last handful of brews you've viewed or edited under the recent brews tab
- Check your browser history for the edit link
- If all of that fails, find the share link, open the source and copy it into a new brew.
# Images
Image basics
- background images
- Adding brushes
How to make spacers
- How to skip page numbers
- How to set page number
- How to hide footers
#p1:before, #p1:after{ display:none }
#p2:before{ counter-reset: phb-page-numbers 30; }
- blockquotes, cite
styling images
# Print
- Saving ink
- Changing page size
- Printing to PDF
## Changing backgrounds
{{wide
In style
```
#p3{
background-image : url('/assets/homebrewery/phb_style/img/dmg_bg.jpg')
}
```
}}
## Changes in v3
``` ``` -> \column
\page
## Columns
{{wide,twoColumn
This is how columns work sdfsdfsdf
```
{{twoColumn
| d4 | Manicurist Level | Equipment |
|:---:|:---:|:---|
| 1 | 1st | The four fragments of the Disk of Madness |
| 2 | 3rd | Broch of Air Blasts |
| 3 | 5th | The four fragments of the Disk of Madness |
| 4 | 7th | 3rd born child |
| d4 | Manicurist Level | Equipment |
|:---:|:---:|:---|
| 1 | 1st | The four fragments of the Disk of Madness |
| 2 | 3rd | Broch of Air Blasts |
| 3 | 5th | The four fragments of the Disk of Madness |
| 4 | 7th | 3rd born child |
}}
```
\column
{{twoColumn
| d4 | Manicurist Level | Equipment |
|:---:|:---:|:---|
| 1 | 1st | The four fragments of the Disk of Madness |
| 2 | 3rd | Broch of Air Blasts |
| 3 | 5th | The four fragments of the Disk of Madness |
| 4 | 7th | 3rd born child |
| d4 | Manicurist Level | Equipment |
|:---:|:---:|:---|
| 1 | 1st | The four fragments of the Disk of Madness |
| 2 | 3rd | Broch of Air Blasts |
| 3 | 5th | The four fragments of the Disk of Madness |
| 4 | 7th | 3rd born child |
}}
}}
this is after

View File

@@ -18,22 +18,53 @@ Like this tool? Want to buy me a beer? [Head here](https://www.patreon.com/stolk
This tool will **always** be free, never have ads, and I will never offer any "premium" features or whatever.
{{note,yellow,alt
##### PDF Exporting
PDF Printing works best in Chrome. If you are having quality/consistency issues, try using Chrome to print instead.
After clicking the "Print" item in the navbar a new page will open and a print dialog will pop-up.
* Set the **Destination** to "Save as PDF"
* Set **Paper Size** to "Letter"
* If you are printing on A4 paper, make sure to have the "A4 page size snippet" in your brew
* In **Options** make sure "Background Images" is selected.
* Hit print and enjoy! You're done!
If you want to save ink or have a monochrome printer, add the **Ink Friendly** snippet to your brew before you print
>##### PDF Exporting
> PDF Printing works best in Chrome. If you are having quality/consistency issues, try using Chrome to print instead.
}}
> This is a cool blockquote fdgfgsfg sfd sdfsdfsdfsdfsdfsdf sdfsdfsdfssdfsdffgsdfgsdfg
> You know?
>
> After clicking the "Print" item in the navbar a new page will open and a print dialog will pop-up.
> * Set the **Destination** to "Save as PDF"
> * Set **Paper Size** to "Letter"
> * If you are printing on A4 paper, make sure to have the "A4 page size snippet" in your brew
> * In **Options** make sure "Background Images" is selected.
> * Hit print and enjoy! You're done!
>
> If you want to save ink or have a monochrome printer, add the **Ink Friendly** snippet to your brew before you print
> One with quotes and what not
> yeah yeah yeah
> {{cite -- Very cool person }}
{{note
##### PDF Exporting
PDF Printing works best in Chrome. If you are having quality/consistency issues, try using Chrome to print instead.
After clicking the "Print" item in the navbar a new page will open and a print dialog will pop-up.
* Set the **Destination** to "Save as PDF"
* Set **Paper Size** to "Letter"
* If you are printing on A4 paper, make sure to have the "A4 page size snippet" in your brew
* In **Options** make sure "Background Images" is selected.
* Hit print and enjoy! You're done!
If you want to save ink or have a monochrome printer, add the **Ink Friendly** snippet to your brew before you print
}}
![test](http://i.imgur.com/hMna6G0.png)
```
cool stuff
```
## Big things coming in v3.0.0
@@ -55,18 +86,15 @@ If you are looking for more 5e Homebrew resources check out [r/UnearthedArcana](
<img src='http://i.imgur.com/hMna6G0.png' style='position:absolute;bottom:50px;right:30px;width:280px' />
<div class='pageNumber'>1</div>
<div class='footnote'>PART 1 | FANCINESS</div>
{{footnote PART 1 | FANCINESS }}
\page
{{classTable,wide
{{frame,wide
##### The Archivist
| Level | Proficiency Bonus | Features | Statistical Occultism|
|:---:|:---:|:---|:---:|