Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b39f9041c2 | ||
|
|
2023ae4f6a | ||
|
|
d5f04ca2b6 | ||
|
|
f99bcabad0 | ||
|
|
32317bfa6f | ||
|
|
1946a50ce0 | ||
|
|
ee1827eab0 | ||
|
|
20371a8b3d | ||
|
|
28a3f31caa | ||
|
|
c647bdf5ee | ||
|
|
eb1827cedb | ||
|
|
a3251dfa19 | ||
|
|
30d3fcf168 | ||
|
|
393df1b181 | ||
|
|
94a3a96960 | ||
|
|
bfb2cea48e | ||
|
|
00f2703d0b | ||
|
|
4c874149fb | ||
|
|
0705e08381 | ||
|
|
e112808706 | ||
|
|
234d216d64 | ||
|
|
ef0265f4fa | ||
|
|
fbc18a017c | ||
|
|
9d4d337bb9 | ||
|
|
0d0ce101f3 | ||
|
|
540c00cb0c | ||
|
|
446ae9cbcf | ||
|
|
dc486cfba9 | ||
|
|
a6a1f41e77 | ||
|
|
fd567352a4 | ||
|
|
a33b1d845d | ||
|
|
b20f4ffb46 | ||
|
|
2f69ef3fe8 | ||
|
|
1da1f90a35 | ||
|
|
bd08858745 | ||
|
|
304cd0ffcd | ||
|
|
b40e5bc4c4 | ||
|
|
0663737e1c | ||
|
|
307dd2d9ba | ||
|
|
95c91b6ba8 | ||
|
|
c8c46725a2 | ||
|
|
7001b71d91 | ||
|
|
cbab4f4959 | ||
|
|
22d9982888 | ||
|
|
76ced9ca49 | ||
|
|
b1db8040a4 | ||
|
|
c8b089f7fb | ||
|
|
97c0443c76 | ||
|
|
c470bed591 | ||
|
|
4593099914 |
13
.github/issue_template.md
vendored
@@ -1,16 +1,5 @@
|
||||
Share link to issue brew: http://homebrewery.naturalcrit.com/share/XXXXXXX
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### Additional Details
|
||||
|
||||
**Share Link** :
|
||||
|
||||
or
|
||||
|
||||
**Brew code to reproduce** : <details><summary>Click to expand</summary><code><pre>
|
||||
|
||||
PASTE BREW CODE HERE
|
||||
|
||||
</pre></code></details>
|
||||
@@ -53,7 +53,13 @@ const Homebrew = React.createClass({
|
||||
return <PrintPage query={query}/>;
|
||||
},
|
||||
'/new' : <NewPage />,
|
||||
|
||||
|
||||
'/changelog' : <SharePage />,
|
||||
'/test' : <SharePage />,
|
||||
'/test_old' : <SharePage />,
|
||||
|
||||
|
||||
'*' : <HomePage />,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -33,7 +33,7 @@ const ContinousSave = React.createClass({
|
||||
window.onbeforeunload = function(){};
|
||||
},
|
||||
actionHandler : function(actionType){
|
||||
if(actionType == 'UPDATE_BREW_TEXT' || actionType == 'UPDATE_META'){
|
||||
if(actionType == 'UPDATE_BREW_CODE' || actionType == 'UPDATE_META' || actionType == 'UPDATE_BREW_STYLE'){
|
||||
Actions.pendingSave();
|
||||
}
|
||||
},
|
||||
@@ -51,6 +51,9 @@ const ContinousSave = React.createClass({
|
||||
Oops!
|
||||
<div className='errorContainer'>
|
||||
Looks like there was a problem saving. <br />
|
||||
Back up your brew in a text file, just in case.
|
||||
<br /><br />
|
||||
|
||||
Report the issue <a target='_blank' href={'https://github.com/stolksdorf/naturalcrit/issues/new?body='+ encodeURIComponent(errMsg)}>
|
||||
here
|
||||
</a>.
|
||||
|
||||
@@ -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>
|
||||
};
|
||||
@@ -120,7 +120,7 @@
|
||||
top : 29px;
|
||||
left : -20px;
|
||||
z-index : 1000;
|
||||
width : 120px;
|
||||
width : 170px;
|
||||
padding : 8px;
|
||||
background-color : #333;
|
||||
a{
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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)=>{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -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 />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -15,20 +15,9 @@ const Utils = require('homebrewery/utils.js');
|
||||
const Actions = require('homebrewery/brew.actions.js');
|
||||
const Store = require('homebrewery/brew.store.js');
|
||||
|
||||
const SharePage = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
brew : {
|
||||
title : '',
|
||||
text : '',
|
||||
shareId : null,
|
||||
createdAt : null,
|
||||
updatedAt : null,
|
||||
views : 0
|
||||
}
|
||||
};
|
||||
},
|
||||
const Headtags = require('vitreum/headtags');
|
||||
|
||||
const SharePage = React.createClass({
|
||||
componentDidMount: function() {
|
||||
document.addEventListener('keydown', this.handleControlKeys);
|
||||
},
|
||||
@@ -39,9 +28,28 @@ const SharePage = React.createClass({
|
||||
p : Actions.print
|
||||
}),
|
||||
|
||||
renderMetatags : function(brew){
|
||||
let metatags = [
|
||||
<Headtags.meta key='site_name' property='og:site_name' content='Homebrewery'/>,
|
||||
<Headtags.meta key='type' property='og:type' content='article' />
|
||||
];
|
||||
if(brew.title){
|
||||
metatags.push(<Headtags.meta key='title' property='og:title' content={brew.title} />);
|
||||
}
|
||||
if(brew.description){
|
||||
metatags.push(<Headtags.meta key='description' name='description' content={brew.description} />);
|
||||
}
|
||||
if(brew.thumbnail){
|
||||
metatags.push(<Headtags.meta key='image' property='og:image' content={brew.thumbnail} />);
|
||||
}
|
||||
return metatags;
|
||||
},
|
||||
|
||||
render : function(){
|
||||
const brew = Store.getBrew();
|
||||
return <div className='sharePage page'>
|
||||
{this.renderMetatags(brew)}
|
||||
|
||||
<Navbar>
|
||||
<Nav.section>
|
||||
<Nav.item className='brewTitle'>{brew.title}</Nav.item>
|
||||
@@ -57,7 +65,7 @@ const SharePage = React.createClass({
|
||||
</Nav.section>
|
||||
</Navbar>
|
||||
<div className='content'>
|
||||
<BrewRenderer brewText={brew.text} />
|
||||
<BrewRenderer brew={brew} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
.phb{
|
||||
//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 : @frameBorderImage;
|
||||
border-image-width : 47px;
|
||||
}
|
||||
h5+hr+table{
|
||||
column-span : all;
|
||||
-webkit-column-span : all;
|
||||
-moz-column-span : all;
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 864 B |
@@ -6,6 +6,7 @@ module.exports = function(vitreum){
|
||||
<link href="//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
|
||||
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
|
||||
<link rel="icon" href="/assets/homebrew/favicon.ico" type="image/x-icon" />
|
||||
|
||||
<title>The Homebrewery - NaturalCrit</title>
|
||||
${vitreum.head}
|
||||
</head>
|
||||
|
||||
@@ -6,13 +6,15 @@
|
||||
"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",
|
||||
"test": "mocha test",
|
||||
"test:dev": "nodemon -x mocha test || exit 0"
|
||||
"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"
|
||||
},
|
||||
"author": "stolksdorf",
|
||||
"license": "MIT",
|
||||
@@ -45,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"
|
||||
|
||||
@@ -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);
|
||||
@@ -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
@@ -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
|
||||
});
|
||||
@@ -1,10 +0,0 @@
|
||||
const less = require('less');
|
||||
const fs = require('fs');
|
||||
|
||||
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);
|
||||
});
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"assets": ["*.png","*.otf", "*.ico"],
|
||||
"shared":[
|
||||
],
|
||||
"assets": ["*.png","*.otf", "*.ico", "*.jpg"],
|
||||
"shared":["./shared"],
|
||||
"libs" : [
|
||||
"react",
|
||||
"react-dom",
|
||||
@@ -10,6 +9,7 @@
|
||||
"codemirror",
|
||||
"codemirror/mode/gfm/gfm.js",
|
||||
"codemirror/mode/javascript/javascript.js",
|
||||
"codemirror/mode/css/css.js",
|
||||
"moment",
|
||||
"superagent",
|
||||
"marked",
|
||||
|
||||
8
scripts/snippet.test.js
Normal 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());
|
||||
@@ -7,9 +7,6 @@ const config = require('nconf')
|
||||
const log = require('loglevel');
|
||||
log.setLevel(config.get('log_level'));
|
||||
|
||||
//DB
|
||||
require('./server/db.js').connect();
|
||||
|
||||
//Server
|
||||
const app = require('./server/app.js');
|
||||
|
||||
@@ -29,7 +26,11 @@ app.use((req, res, next) => {
|
||||
});
|
||||
*/
|
||||
|
||||
require('./server/db.js').connect()
|
||||
.then(()=>{
|
||||
const PORT = process.env.PORT || 8000;
|
||||
const httpServer = app.listen(PORT, () => {
|
||||
log.info(`server on port:${PORT}`);
|
||||
});
|
||||
})
|
||||
.catch((err)=>console.error(err))
|
||||
|
||||
@@ -10,10 +10,12 @@ const BrewSchema = mongoose.Schema({
|
||||
editId : {type : String, default: shortid.generate, index: { unique: true }},
|
||||
|
||||
text : {type : String, default : ""},
|
||||
style : {type : String, default : ""},
|
||||
|
||||
title : {type : String, default : ""},
|
||||
description : {type : String, default : ""},
|
||||
tags : {type : String, default : ""},
|
||||
thumbnail : {type : String, default : ""},
|
||||
systems : [String],
|
||||
authors : [String],
|
||||
published : {type : Boolean, default : false},
|
||||
@@ -22,7 +24,7 @@ const BrewSchema = mongoose.Schema({
|
||||
updatedAt : { type: Date, default: Date.now},
|
||||
lastViewed : { type: Date, default: Date.now},
|
||||
views : {type:Number, default:0},
|
||||
version : {type: Number, default:1}
|
||||
version : {type: Number, default:2}
|
||||
}, {
|
||||
versionKey: false,
|
||||
toJSON : {
|
||||
|
||||
@@ -7,7 +7,7 @@ const dbPath = process.env.MONGODB_URI || process.env.MONGOLAB_URI || 'mongodb:/
|
||||
module.exports = {
|
||||
connect : ()=>{
|
||||
return new Promise((resolve, reject)=>{
|
||||
if(mongoose.connection.readyState == 1){
|
||||
if(mongoose.connection.readyState !== 0){
|
||||
log.warn('DB already connected');
|
||||
return resolve();
|
||||
}
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
const _ = require('lodash');
|
||||
const Moment = require('moment');
|
||||
const HomebrewModel = require('./homebrew.model.js').model;
|
||||
const router = require('express').Router();
|
||||
|
||||
|
||||
|
||||
//TODO: Possiblity remove
|
||||
let homebrewTotal = 0;
|
||||
const refreshCount = ()=>{
|
||||
HomebrewModel.count({}, (err, total)=>{
|
||||
homebrewTotal = total;
|
||||
});
|
||||
};
|
||||
refreshCount();
|
||||
|
||||
|
||||
|
||||
const getTopBrews = (cb)=>{
|
||||
HomebrewModel.find().sort({views: -1}).limit(5).exec(function(err, brews) {
|
||||
cb(brews);
|
||||
});
|
||||
}
|
||||
|
||||
const getGoodBrewTitle = (text) => {
|
||||
const titlePos = text.indexOf('# ');
|
||||
if(titlePos !== -1){
|
||||
const ending = text.indexOf('\n', titlePos);
|
||||
return text.substring(titlePos + 2, ending);
|
||||
}else{
|
||||
return _.find(text.split('\n'), (line)=>{
|
||||
return line;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
router.post('/api', (req, res)=>{
|
||||
|
||||
let authors = [];
|
||||
if(req.account) authors = [req.account.username];
|
||||
|
||||
const newHomebrew = new HomebrewModel(_.merge({},
|
||||
req.body,
|
||||
{authors : authors}
|
||||
));
|
||||
if(!newHomebrew.title){
|
||||
newHomebrew.title = getGoodBrewTitle(newHomebrew.text);
|
||||
}
|
||||
newHomebrew.save((err, obj)=>{
|
||||
if(err){
|
||||
console.error(err, err.toString(), err.stack);
|
||||
return res.status(500).send(`Error while creating new brew, ${err.toString()}`);
|
||||
}
|
||||
return res.json(obj);
|
||||
})
|
||||
});
|
||||
|
||||
router.put('/api/update/:id', (req, res)=>{
|
||||
HomebrewModel.get({editId : req.params.id})
|
||||
.then((brew)=>{
|
||||
brew = _.merge(brew, req.body);
|
||||
brew.updatedAt = new Date();
|
||||
if(req.account) brew.authors = _.uniq(_.concat(brew.authors, req.account.username));
|
||||
|
||||
brew.markModified('authors');
|
||||
brew.markModified('systems');
|
||||
|
||||
brew.save((err, obj)=>{
|
||||
if(err) throw err;
|
||||
return res.status(200).send(obj);
|
||||
})
|
||||
})
|
||||
.catch((err)=>{
|
||||
console.log(err);
|
||||
return res.status(500).send("Error while saving");
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/api/remove/:id', (req, res)=>{
|
||||
HomebrewModel.find({editId : req.params.id}, (err, objs)=>{
|
||||
if(!objs.length || err) return res.status(404).send("Can not find homebrew with that id");
|
||||
var resEntry = objs[0];
|
||||
resEntry.remove((err)=>{
|
||||
if(err) return res.status(500).send("Error while removing");
|
||||
return res.status(200).send();
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
||||
|
||||
/*
|
||||
|
||||
|
||||
|
||||
module.exports = function(app){
|
||||
|
||||
app;
|
||||
|
||||
|
||||
|
||||
|
||||
app.get('/api/search', mw.adminOnly, function(req, res){
|
||||
|
||||
var page = req.query.page || 0;
|
||||
var count = req.query.count || 20;
|
||||
|
||||
var query = {};
|
||||
if(req.query && req.query.id){
|
||||
query = {
|
||||
"$or" : [{
|
||||
editId : req.query.id
|
||||
},{
|
||||
shareId : req.query.id
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
HomebrewModel.find(query, {
|
||||
text : 0 //omit the text
|
||||
}, {
|
||||
skip: page*count,
|
||||
limit: count*1
|
||||
}, function(err, objs){
|
||||
if(err) console.log(err);
|
||||
return res.json({
|
||||
page : page,
|
||||
count : count,
|
||||
total : homebrewTotal,
|
||||
brews : objs
|
||||
});
|
||||
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
return app;
|
||||
}
|
||||
*/
|
||||
@@ -1,81 +0,0 @@
|
||||
var mongoose = require('mongoose');
|
||||
var shortid = require('shortid');
|
||||
var _ = require('lodash');
|
||||
|
||||
var HomebrewSchema = mongoose.Schema({
|
||||
shareId : {type : String, default: shortid.generate, index: { unique: true }},
|
||||
editId : {type : String, default: shortid.generate, index: { unique: true }},
|
||||
title : {type : String, default : ""},
|
||||
text : {type : String, default : ""},
|
||||
|
||||
description : {type : String, default : ""},
|
||||
tags : {type : String, default : ""},
|
||||
systems : [String],
|
||||
authors : [String],
|
||||
published : {type : Boolean, default : false},
|
||||
|
||||
createdAt : { type: Date, default: Date.now },
|
||||
updatedAt : { type: Date, default: Date.now},
|
||||
lastViewed : { type: Date, default: Date.now},
|
||||
views : {type:Number, default:0},
|
||||
version : {type: Number, default:1}
|
||||
}, { versionKey: false });
|
||||
|
||||
|
||||
|
||||
HomebrewSchema.methods.sanatize = function(full=false){
|
||||
const brew = this.toJSON();
|
||||
delete brew._id;
|
||||
delete brew.__v;
|
||||
if(full){
|
||||
delete brew.editId;
|
||||
}
|
||||
return brew;
|
||||
};
|
||||
|
||||
|
||||
HomebrewSchema.methods.increaseView = function(){
|
||||
return new Promise((resolve, reject) => {
|
||||
this.lastViewed = new Date();
|
||||
this.views = this.views + 1;
|
||||
this.save((err) => {
|
||||
if(err) return reject(err);
|
||||
return resolve(this);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
HomebrewSchema.statics.get = function(query){
|
||||
return new Promise((resolve, reject) => {
|
||||
Homebrew.find(query, (err, brews)=>{
|
||||
if(err || !brews.length) return reject('Can not find brew');
|
||||
return resolve(brews[0]);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
HomebrewSchema.statics.getByUser = function(username, allowAccess=false){
|
||||
return new Promise((resolve, reject) => {
|
||||
let query = {authors : username, published : true};
|
||||
if(allowAccess){
|
||||
delete query.published;
|
||||
}
|
||||
Homebrew.find(query, (err, brews)=>{
|
||||
if(err) return reject('Can not find brew');
|
||||
return resolve(_.map(brews, (brew)=>{
|
||||
return brew.sanatize(!allowAccess);
|
||||
}));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
var Homebrew = mongoose.model('Homebrew', HomebrewSchema);
|
||||
|
||||
module.exports = {
|
||||
schema : HomebrewSchema,
|
||||
model : Homebrew,
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
const _ = require('lodash');
|
||||
const fs = require('fs');
|
||||
const config = require('nconf');
|
||||
const utils = require('./utils.js');
|
||||
const BrewData = require('./brew.data.js');
|
||||
@@ -6,9 +7,13 @@ const router = require('express').Router();
|
||||
const mw = require('./middleware.js');
|
||||
|
||||
|
||||
const docs = {
|
||||
welcomeBrew : require('fs').readFileSync('./welcome.brew.md', 'utf8'),
|
||||
changelog : require('fs').readFileSync('./changelog.md', 'utf8'),
|
||||
const statics = {
|
||||
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'),
|
||||
};
|
||||
|
||||
|
||||
@@ -40,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)=>{
|
||||
@@ -59,6 +65,7 @@ router.get('/user/:username', (req, res, next) => {
|
||||
|
||||
//Search Page
|
||||
router.get('/search', (req, res, next) => {
|
||||
//TODO: Double check that the defaults are okay
|
||||
BrewData.search()
|
||||
.then((brews) => {
|
||||
req.brews = brews;
|
||||
@@ -70,19 +77,48 @@ router.get('/search', (req, res, next) => {
|
||||
//Changelog Page
|
||||
router.get('/changelog', (req, res, next) => {
|
||||
req.brew = {
|
||||
text : docs.changelog,
|
||||
text : statics.changelog,
|
||||
title : 'Changelog'
|
||||
};
|
||||
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);
|
||||
|
||||
//Home Page
|
||||
router.get('/', (req, res, next) => {
|
||||
req.brew = { text : docs.welcomeBrew };
|
||||
req.brew = { text : statics.welcomeBrew };
|
||||
return next();
|
||||
}, renderPage);
|
||||
|
||||
|
||||
//Test pages
|
||||
router.get('/test', (req, res, next) => {
|
||||
req.brew = {
|
||||
text : statics.testBrew
|
||||
};
|
||||
return next();
|
||||
}, renderPage);
|
||||
router.get('/test_old', (req, res, next) => {
|
||||
req.brew = {
|
||||
text : statics.oldTest,
|
||||
version : 1
|
||||
};
|
||||
return next();
|
||||
}, renderPage);
|
||||
|
||||
|
||||
|
||||
module.exports = router;
|
||||
147
shared/depricated/brewRendererOld/brewRendererOld.jsx
Normal file
@@ -0,0 +1,147 @@
|
||||
const React = require('react');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
const Markdown = require('depricated/markdown.old.js');
|
||||
const ErrorBar = require('./errorBar/errorBar.jsx');
|
||||
|
||||
const RenderWarnings = require('homebrewery/renderWarnings/renderWarnings.jsx')
|
||||
const Store = require('homebrewery/brew.store.js');
|
||||
|
||||
|
||||
const PAGE_HEIGHT = 1056;
|
||||
const PPR_THRESHOLD = 50;
|
||||
|
||||
const OLD_BrewRenderer = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
value : '',
|
||||
style : '',
|
||||
errors : []
|
||||
};
|
||||
},
|
||||
getInitialState: function() {
|
||||
const pages = this.props.value.split('\\page');
|
||||
|
||||
return {
|
||||
viewablePageNumber: 0,
|
||||
height : 0,
|
||||
isMounted : false,
|
||||
pages : pages,
|
||||
usePPR : pages.length >= PPR_THRESHOLD
|
||||
};
|
||||
},
|
||||
height : 0,
|
||||
pageHeight : PAGE_HEIGHT,
|
||||
lastRender : <div></div>,
|
||||
|
||||
componentDidMount: function() {
|
||||
this.updateSize();
|
||||
window.addEventListener("resize", this.updateSize);
|
||||
},
|
||||
componentWillUnmount: function() {
|
||||
window.removeEventListener("resize", this.updateSize);
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
if(this.refs.pages && this.refs.pages.firstChild) this.pageHeight = this.refs.pages.firstChild.clientHeight;
|
||||
|
||||
const pages = nextProps.value.split('\\page');
|
||||
this.setState({
|
||||
pages : pages,
|
||||
usePPR : pages.length >= PPR_THRESHOLD
|
||||
})
|
||||
},
|
||||
|
||||
updateSize : function() {
|
||||
setTimeout(()=>{
|
||||
if(this.refs.pages && this.refs.pages.firstChild) this.pageHeight = this.refs.pages.firstChild.clientHeight;
|
||||
}, 1);
|
||||
|
||||
const parentNode = document.querySelector('.page .content');
|
||||
this.setState({
|
||||
height : parentNode.clientHeight,
|
||||
isMounted : true
|
||||
});
|
||||
},
|
||||
|
||||
handleScroll : function(e){
|
||||
this.setState({
|
||||
viewablePageNumber : Math.floor(e.target.scrollTop / this.pageHeight)
|
||||
});
|
||||
},
|
||||
|
||||
shouldRender : function(pageText, index){
|
||||
if(!this.state.isMounted) return false;
|
||||
|
||||
var viewIndex = this.state.viewablePageNumber;
|
||||
if(index == viewIndex - 1) return true;
|
||||
if(index == viewIndex) return true;
|
||||
if(index == viewIndex + 1) return true;
|
||||
|
||||
//Check for style tages
|
||||
if(pageText.indexOf('<style>') !== -1) return true;
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
renderPageInfo : function(){
|
||||
return <div className='pageInfo'>
|
||||
{this.state.viewablePageNumber + 1} / {this.state.pages.length}
|
||||
</div>
|
||||
},
|
||||
|
||||
renderPPRmsg : function(){
|
||||
if(!this.state.usePPR) return;
|
||||
|
||||
return <div className='ppr_msg'>
|
||||
Partial Page Renderer enabled, because your brew is so large. May effect rendering.
|
||||
</div>
|
||||
},
|
||||
|
||||
renderDummyPage : function(index){
|
||||
return <div className='phb v1' id={`p${index + 1}`} key={index}>
|
||||
<i className='fa fa-spinner fa-spin' />
|
||||
</div>
|
||||
},
|
||||
|
||||
renderPage : function(pageText, index){
|
||||
return <div className='phb v1' id={`p${index + 1}`} dangerouslySetInnerHTML={{__html:Markdown.render(pageText)}} key={index} />
|
||||
},
|
||||
|
||||
renderPages : function(){
|
||||
if(this.state.usePPR){
|
||||
return _.map(this.state.pages, (page, index)=>{
|
||||
if(this.shouldRender(page, index)){
|
||||
return this.renderPage(page, index);
|
||||
}else{
|
||||
return this.renderDummyPage(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
if(this.props.errors && this.props.errors.length) return this.lastRender;
|
||||
this.lastRender = _.map(this.state.pages, (page, index)=>{
|
||||
return this.renderPage(page, index);
|
||||
});
|
||||
return this.lastRender;
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <div className='brewRendererOld'
|
||||
onScroll={this.handleScroll}
|
||||
ref='main'
|
||||
style={{height : this.state.height}}>
|
||||
|
||||
<ErrorBar errors={this.props.errors} />
|
||||
<RenderWarnings />
|
||||
|
||||
<div className='pages' ref='pages'>
|
||||
{this.renderPages()}
|
||||
</div>
|
||||
{this.renderPageInfo()}
|
||||
{this.renderPPRmsg()}
|
||||
</div>
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = OLD_BrewRenderer;
|
||||
40
shared/depricated/brewRendererOld/brewRendererOld.less
Normal file
@@ -0,0 +1,40 @@
|
||||
|
||||
@import 'shared/depricated/phb_style_v1/phb.v1.less';
|
||||
|
||||
.pane{
|
||||
position : relative;
|
||||
}
|
||||
.brewRendererOld{
|
||||
overflow-y : scroll;
|
||||
.pageInfo{
|
||||
position : absolute;
|
||||
right : 17px;
|
||||
bottom : 0;
|
||||
z-index : 1000;
|
||||
padding : 8px 10px;
|
||||
background-color : #333;
|
||||
font-size : 10px;
|
||||
font-weight : 800;
|
||||
color : white;
|
||||
}
|
||||
.ppr_msg{
|
||||
position : absolute;
|
||||
left : 0px;
|
||||
bottom : 0;
|
||||
z-index : 1000;
|
||||
padding : 8px 10px;
|
||||
background-color : #333;
|
||||
font-size : 10px;
|
||||
font-weight : 800;
|
||||
color : white;
|
||||
}
|
||||
.pages{
|
||||
margin : 30px 0px;
|
||||
&>.phb{
|
||||
margin-right : auto;
|
||||
margin-bottom : 30px;
|
||||
margin-left : auto;
|
||||
box-shadow : 1px 4px 14px #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
73
shared/depricated/brewRendererOld/errorBar/errorBar.jsx
Normal file
@@ -0,0 +1,73 @@
|
||||
var React = require('react');
|
||||
var _ = require('lodash');
|
||||
var cx = require('classnames');
|
||||
|
||||
var ErrorBar = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
errors : []
|
||||
};
|
||||
},
|
||||
|
||||
hasOpenError : false,
|
||||
hasCloseError : false,
|
||||
hasMatchError : false,
|
||||
|
||||
renderErrors : function(){
|
||||
this.hasOpenError = false;
|
||||
this.hasCloseError = false;
|
||||
this.hasMatchError = false;
|
||||
|
||||
|
||||
var errors = _.map(this.props.errors, (err, idx) => {
|
||||
if(err.id == 'OPEN') this.hasOpenError = true;
|
||||
if(err.id == 'CLOSE') this.hasCloseError = true;
|
||||
if(err.id == 'MISMATCH') this.hasMatchError = true;
|
||||
return <li key={idx}>
|
||||
Line {err.line} : {err.text}, '{err.type}' tag
|
||||
</li>
|
||||
});
|
||||
|
||||
return <ul>{errors}</ul>
|
||||
},
|
||||
|
||||
renderProtip : function(){
|
||||
var msg = [];
|
||||
if(this.hasOpenError){
|
||||
msg.push(<div>
|
||||
An unmatched opening tag means there's an opened tag that isn't closed, you need to close a tag, like this {'</div>'}. Make sure to match types!
|
||||
</div>);
|
||||
}
|
||||
|
||||
if(this.hasCloseError){
|
||||
msg.push(<div>
|
||||
An unmatched closing tag means you closed a tag without opening it. Either remove it, you check to where you think you opened it.
|
||||
</div>);
|
||||
}
|
||||
|
||||
if(this.hasMatchError){
|
||||
msg.push(<div>
|
||||
A type mismatch means you closed a tag, but the last open tag was a different type.
|
||||
</div>);
|
||||
}
|
||||
return <div className='protips'>
|
||||
<h4>Protips!</h4>
|
||||
{msg}
|
||||
</div>
|
||||
},
|
||||
|
||||
render : function(){
|
||||
if(!this.props.errors.length) return null;
|
||||
|
||||
return <div className='errorBar'>
|
||||
<i className='fa fa-exclamation-triangle' />
|
||||
<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>
|
||||
{this.renderErrors()}
|
||||
<hr />
|
||||
{this.renderProtip()}
|
||||
</div>
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = ErrorBar;
|
||||
60
shared/depricated/brewRendererOld/errorBar/errorBar.less
Normal file
@@ -0,0 +1,60 @@
|
||||
|
||||
.errorBar{
|
||||
position : absolute;
|
||||
z-index : 10000;
|
||||
box-sizing : border-box;
|
||||
width : 100%;
|
||||
margin-right : 13px;
|
||||
padding : 20px;
|
||||
padding-bottom : 10px;
|
||||
padding-left : 100px;
|
||||
background-color : @red;
|
||||
color : white;
|
||||
i{
|
||||
position : absolute;
|
||||
left : 30px;
|
||||
opacity : 0.8;
|
||||
font-size : 3em;
|
||||
}
|
||||
h3{
|
||||
font-size : 1.1em;
|
||||
font-weight : 800;
|
||||
}
|
||||
ul{
|
||||
margin-top : 15px;
|
||||
font-size : 0.8em;
|
||||
list-style-position : inside;
|
||||
list-style-type : disc;
|
||||
li{
|
||||
line-height : 1.6em;
|
||||
}
|
||||
}
|
||||
hr{
|
||||
box-sizing : border-box;
|
||||
height : 2px;
|
||||
width : 150%;
|
||||
margin-top : 25px;
|
||||
margin-bottom : 15px;
|
||||
margin-left : -100px;
|
||||
background-color : darken(@red, 8%);
|
||||
border : none;
|
||||
}
|
||||
small{
|
||||
font-size: 0.6em;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.protips{
|
||||
margin-left : -80px;
|
||||
font-size : 0.6em;
|
||||
&>div{
|
||||
margin-bottom : 10px;
|
||||
line-height : 1.2em;
|
||||
}
|
||||
h4{
|
||||
opacity : 0.8;
|
||||
font-weight : 800;
|
||||
line-height : 1.5em;
|
||||
text-transform : uppercase;
|
||||
}
|
||||
}
|
||||
}
|
||||
82
shared/depricated/markdown.old.js
Normal file
@@ -0,0 +1,82 @@
|
||||
var _ = require('lodash');
|
||||
var Markdown = require('marked');
|
||||
var 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>')){
|
||||
var 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>`;
|
||||
}
|
||||
return html;
|
||||
};
|
||||
|
||||
|
||||
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(rawBrewText, {renderer : renderer})
|
||||
},
|
||||
|
||||
validate : (rawBrewText) => {
|
||||
var errors = [];
|
||||
var leftovers = _.reduce(rawBrewText.split('\n'), (acc, line, _lineNumber) => {
|
||||
var lineNumber = _lineNumber + 1;
|
||||
var 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;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
@media print {
|
||||
.phb.v1{
|
||||
.descriptive, blockquote{
|
||||
box-shadow : none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.phb.v1{
|
||||
@import (less) './phb.fonts.v1.css';
|
||||
@import (less) './phb.assets.v1.less';
|
||||
|
||||
|
||||
@import (less) 'shared/naturalcrit/styles/reset.less';
|
||||
@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;
|
||||
@@ -10,13 +18,10 @@
|
||||
@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{
|
||||
@@ -41,7 +46,9 @@ body {
|
||||
-webkit-column-gap : 1cm;
|
||||
-moz-column-gap : 1cm;
|
||||
}
|
||||
.phb{
|
||||
& *{
|
||||
-webkit-print-color-adjust : exact;
|
||||
}
|
||||
.useColumns();
|
||||
counter-increment : phb-page-numbers;
|
||||
position : relative;
|
||||
@@ -59,6 +66,7 @@ body {
|
||||
text-rendering : optimizeLegibility;
|
||||
page-break-before : always;
|
||||
page-break-after : always;
|
||||
|
||||
//*****************************
|
||||
// * BASE
|
||||
// *****************************/
|
||||
@@ -357,11 +365,11 @@ body {
|
||||
-webkit-column-break-inside : avoid;
|
||||
column-break-inside : avoid;
|
||||
}
|
||||
}
|
||||
|
||||
//*****************************
|
||||
// * SPELL LIST
|
||||
// *****************************/
|
||||
.phb .spellList{
|
||||
.spellList{
|
||||
.useSansSerif();
|
||||
column-count : 4;
|
||||
column-span : all;
|
||||
@@ -386,20 +394,16 @@ body {
|
||||
//*****************************
|
||||
// * PRINT
|
||||
// *****************************/
|
||||
.phb.print{
|
||||
&.print{
|
||||
blockquote{
|
||||
box-shadow : none;
|
||||
}
|
||||
}
|
||||
@media print {
|
||||
.phb .descriptive, .phb blockquote{
|
||||
box-shadow : none;
|
||||
}
|
||||
}
|
||||
|
||||
//*****************************
|
||||
// * WIDE
|
||||
// *****************************/
|
||||
.phb .wide{
|
||||
.wide{
|
||||
column-span : all;
|
||||
-webkit-column-span : all;
|
||||
-moz-column-span : all;
|
||||
@@ -407,7 +411,7 @@ body {
|
||||
//*****************************
|
||||
// * CLASS TABLE
|
||||
// *****************************/
|
||||
.phb .classTable{
|
||||
.classTable{
|
||||
margin-top : 25px;
|
||||
margin-bottom : 40px;
|
||||
border-collapse : separate;
|
||||
@@ -426,7 +430,7 @@ body {
|
||||
//*****************************
|
||||
// * CLASS TABLE
|
||||
// *****************************/
|
||||
.phb .descriptive{
|
||||
.descriptive{
|
||||
display : block-inline;
|
||||
margin-bottom : 1em;
|
||||
background-color : #faf7ea;
|
||||
@@ -454,13 +458,13 @@ body {
|
||||
letter-spacing : -0.02em;
|
||||
}
|
||||
}
|
||||
.phb pre+.descriptive{
|
||||
pre+.descriptive{
|
||||
margin-top : 8px;
|
||||
}
|
||||
//*****************************
|
||||
// * TABLE OF CONTENTS
|
||||
// *****************************/
|
||||
.phb .toc{
|
||||
.toc{
|
||||
-webkit-column-break-inside : avoid;
|
||||
column-break-inside : avoid;
|
||||
a{
|
||||
@@ -478,3 +482,40 @@ body {
|
||||
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;
|
||||
}
|
||||
|
||||
//*****************************
|
||||
// * 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 : @frameBorderImage;
|
||||
border-image-width : 47px;
|
||||
}
|
||||
h5+hr+table{
|
||||
column-span : all;
|
||||
-webkit-column-span : all;
|
||||
-moz-column-span : all;
|
||||
}
|
||||
}
|
||||
@@ -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})**`)
|
||||
@@ -54,10 +54,13 @@ const Actions = {
|
||||
setBrew : (brew) => {
|
||||
dispatch('SET_BREW', brew);
|
||||
},
|
||||
updateBrewText : (brewText) => {
|
||||
dispatch('UPDATE_BREW_TEXT', brewText)
|
||||
updateBrewCode : (brewCode) => {
|
||||
dispatch('UPDATE_BREW_CODE', brewCode)
|
||||
},
|
||||
updateMetaData : (meta) => {
|
||||
updateBrewStyle : (style) => {
|
||||
dispatch('UPDATE_BREW_STYLE', style)
|
||||
},
|
||||
updateMetadata : (meta) => {
|
||||
dispatch('UPDATE_META', meta);
|
||||
},
|
||||
pendingSave : () => {
|
||||
@@ -67,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();
|
||||
|
||||
@@ -8,6 +8,7 @@ let State = {
|
||||
|
||||
brew : {
|
||||
text : '',
|
||||
style : '',
|
||||
shareId : undefined,
|
||||
editId : undefined,
|
||||
createdAt : undefined,
|
||||
@@ -29,9 +30,15 @@ const Store = flux.createStore({
|
||||
SET_BREW : (brew) => {
|
||||
State.brew = brew;
|
||||
},
|
||||
UPDATE_BREW_TEXT : (brewText) => {
|
||||
State.brew.text = brewText;
|
||||
State.errors = Markdown.validate(brewText);
|
||||
UPDATE_BREW_CODE : (brewCode) => {
|
||||
State.brew.text = brewCode;
|
||||
|
||||
//TODO: Remove?
|
||||
State.errors = Markdown.validate(brewCode);
|
||||
},
|
||||
UPDATE_BREW_STYLE : (style) => {
|
||||
//TODO: add in an error checker?
|
||||
State.brew.style = style;
|
||||
},
|
||||
UPDATE_META : (meta) => {
|
||||
State.brew = _.merge({}, State.brew, meta);
|
||||
@@ -50,11 +57,14 @@ Store.init = (state)=>{
|
||||
Store.getBrew = ()=>{
|
||||
return State.brew;
|
||||
};
|
||||
Store.getBrewText = ()=>{
|
||||
Store.getBrewCode = ()=>{
|
||||
return State.brew.text;
|
||||
};
|
||||
Store.getBrewStyle = ()=>{
|
||||
return State.brew.style;
|
||||
};
|
||||
Store.getMetaData = ()=>{
|
||||
return _.omit(State.brew, ['text']);
|
||||
return _.omit(State.brew, ['text', 'style']);
|
||||
};
|
||||
Store.getErrors = ()=>{
|
||||
return State.errors;
|
||||
|
||||
@@ -3,34 +3,37 @@ 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){
|
||||
return str.slice(0, index) + inject + str.slice(index);
|
||||
};
|
||||
|
||||
const SNIPPETBAR_HEIGHT = 25;
|
||||
const MENUBAR_HEIGHT = 25;
|
||||
|
||||
const BrewEditor = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
value : '',
|
||||
onChange : ()=>{},
|
||||
brew : {
|
||||
text : '',
|
||||
style : '',
|
||||
},
|
||||
|
||||
metadata : {},
|
||||
onMetadataChange : ()=>{},
|
||||
onCodeChange : ()=>{},
|
||||
onStyleChange : ()=>{},
|
||||
onMetaChange : ()=>{},
|
||||
};
|
||||
},
|
||||
getInitialState: function() {
|
||||
return {
|
||||
showMetadataEditor: false
|
||||
view : 'code', //'code', 'style', 'meta'
|
||||
};
|
||||
},
|
||||
cursorPosition : {
|
||||
line : 0,
|
||||
ch : 0
|
||||
},
|
||||
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();
|
||||
@@ -42,54 +45,80 @@ const BrewEditor = React.createClass({
|
||||
},
|
||||
|
||||
updateEditorSize : function() {
|
||||
if(this.refs.codeEditor){
|
||||
let paneHeight = this.refs.main.parentNode.clientHeight;
|
||||
paneHeight -= SNIPPETBAR_HEIGHT + 1;
|
||||
paneHeight -= MENUBAR_HEIGHT + 1;
|
||||
this.refs.codeEditor.codeMirror.setSize(null, paneHeight);
|
||||
}
|
||||
},
|
||||
|
||||
handleTextChange : function(text){
|
||||
this.props.onChange(text);
|
||||
},
|
||||
handleCursorActivty : function(curpos){
|
||||
this.cursorPosition = curpos;
|
||||
},
|
||||
|
||||
|
||||
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'));
|
||||
},
|
||||
handgleToggle : function(){
|
||||
|
||||
|
||||
|
||||
handleViewChange : function(newView){
|
||||
this.setState({
|
||||
showMetadataEditor : !this.state.showMetadataEditor
|
||||
})
|
||||
view : newView
|
||||
}, this.updateEditorSize);
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
brewJump : function(){
|
||||
const currentPage = this.getCurrentPage();
|
||||
window.location.hash = 'p' + currentPage;
|
||||
},
|
||||
|
||||
//Called when there are changes to the editor's dimensions
|
||||
/*
|
||||
update : function(){
|
||||
this.refs.codeEditor.updateSize();
|
||||
if(this.refs.codeEditor) this.refs.codeEditor.updateSize();
|
||||
},
|
||||
*/
|
||||
|
||||
//TODO: convert this into a generic function for columns and blocks
|
||||
//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.value.split('\n'), (r, line, lineNumber)=>{
|
||||
const lineNumbers = _.reduce(this.props.brew.text.split('\n'), (r, line, lineNumber)=>{
|
||||
if(line.indexOf('\\page') !== -1){
|
||||
codeMirror.addLineClass(lineNumber, 'background', 'pageLine');
|
||||
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');
|
||||
}
|
||||
return r;
|
||||
}, []);
|
||||
return lineNumbers
|
||||
},
|
||||
|
||||
/*
|
||||
renderMetadataEditor : function(){
|
||||
if(!this.state.showMetadataEditor) return;
|
||||
return <MetadataEditor
|
||||
@@ -97,25 +126,44 @@ const BrewEditor = React.createClass({
|
||||
onChange={this.props.onMetadataChange}
|
||||
/>
|
||||
},
|
||||
*/
|
||||
|
||||
|
||||
|
||||
renderEditor : function(){
|
||||
if(this.isMeta()){
|
||||
return <MetadataEditor
|
||||
metadata={this.props.brew}
|
||||
onChange={this.props.onMetaChange} />
|
||||
}
|
||||
if(this.isStyle()){
|
||||
return <CodeEditor key='style'
|
||||
ref='codeEditor'
|
||||
language='css'
|
||||
value={this.props.brew.style}
|
||||
onChange={this.props.onStyleChange} />
|
||||
}
|
||||
if(this.isCode()){
|
||||
return <CodeEditor key='code'
|
||||
ref='codeEditor'
|
||||
language='gfm'
|
||||
value={this.props.brew.text}
|
||||
onChange={this.props.onCodeChange} />
|
||||
}
|
||||
},
|
||||
|
||||
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} />
|
||||
{this.renderMetadataEditor()}
|
||||
<CodeEditor
|
||||
ref='codeEditor'
|
||||
wrap={true}
|
||||
language='gfm'
|
||||
value={this.props.value}
|
||||
onChange={this.handleTextChange}
|
||||
onCursorActivity={this.handleCursorActivty} />
|
||||
<Menubar
|
||||
view={this.state.view}
|
||||
onViewChange={this.handleViewChange}
|
||||
onSnippetInject={this.handleInject}
|
||||
|
||||
/>
|
||||
|
||||
{this.renderEditor()}
|
||||
|
||||
</div>
|
||||
|
||||
/*
|
||||
|
||||
@@ -8,6 +8,14 @@
|
||||
background-color : fade(#333, 15%);
|
||||
border-bottom : #333 solid 1px;
|
||||
}
|
||||
.block{
|
||||
color : purple;
|
||||
//font-style: italic;
|
||||
}
|
||||
.columnSplit{
|
||||
font-style : italic;
|
||||
color : grey;
|
||||
}
|
||||
}
|
||||
|
||||
.brewJump{
|
||||
|
||||
@@ -5,9 +5,10 @@ const BrewEditor = require('./brewEditor.jsx')
|
||||
|
||||
module.exports = Store.createSmartComponent(BrewEditor, ()=>{
|
||||
return {
|
||||
value : Store.getBrewText(),
|
||||
onChange : Actions.updateBrewText,
|
||||
metadata : Store.getMetaData(),
|
||||
onMetadataChange : Actions.updateMetaData,
|
||||
brew : Store.getBrew(),
|
||||
|
||||
onCodeChange : Actions.updateBrewCode,
|
||||
onStyleChange : Actions.updateBrewStyle,
|
||||
onMetaChange : Actions.updateMetadata,
|
||||
};
|
||||
});
|
||||
76
shared/homebrewery/brewEditor/menubar/menubar.jsx
Normal file
@@ -0,0 +1,76 @@
|
||||
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 : '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')}>
|
||||
<i className='fa fa-beer' />
|
||||
</div>
|
||||
<div className={cx('style', {selected : this.props.view == 'style'})}
|
||||
onClick={this.props.onViewChange.bind(null, 'style')}>
|
||||
<i className='fa fa-paint-brush' />
|
||||
</div>
|
||||
<div className={cx('meta', {selected : this.props.view == 'meta'})}
|
||||
onClick={this.props.onViewChange.bind(null, 'meta')}>
|
||||
<i className='fa fa-bars' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Menubar;
|
||||
40
shared/homebrewery/brewEditor/menubar/menubar.less
Normal file
@@ -0,0 +1,40 @@
|
||||
|
||||
.menubar{
|
||||
@menuHeight : 25px;
|
||||
position : relative;
|
||||
height : @menuHeight;
|
||||
background-color : #ddd;
|
||||
.editors{
|
||||
position : absolute;
|
||||
display : flex;
|
||||
top : 0px;
|
||||
right : 0px;
|
||||
height : @menuHeight;
|
||||
width : 90px;
|
||||
justify-content : space-between;
|
||||
&>div{
|
||||
height : @menuHeight;
|
||||
width : @menuHeight;
|
||||
cursor : pointer;
|
||||
line-height : @menuHeight;
|
||||
text-align : center;
|
||||
&:hover,&.selected{
|
||||
background-color : #999;
|
||||
}
|
||||
&.code{
|
||||
.tooltipLeft('Brew Editor');
|
||||
}
|
||||
&.style{
|
||||
.tooltipLeft('Style Editor');
|
||||
}
|
||||
&.meta{
|
||||
.tooltipLeft('Metadata');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.snippets{
|
||||
display : flex;
|
||||
height : 100%;
|
||||
}
|
||||
}
|
||||
48
shared/homebrewery/brewEditor/menubar/snippet.map.js
Normal 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
|
||||
},
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,7 @@ const MetadataEditor = React.createClass({
|
||||
},
|
||||
|
||||
renderPublish : function(){
|
||||
//TODO: Move the publish element into here
|
||||
if(this.props.metadata.published){
|
||||
return <button className='unpublish' onClick={this.handlePublish.bind(null, false)}>
|
||||
<i className='fa fa-ban' /> unpublish
|
||||
@@ -139,13 +140,12 @@ const MetadataEditor = React.createClass({
|
||||
<textarea value={this.props.metadata.description} className='value'
|
||||
onChange={this.handleFieldChange.bind(null, 'description')} />
|
||||
</div>
|
||||
{/*}
|
||||
<div className='field tags'>
|
||||
<label>tags</label>
|
||||
<textarea value={this.props.metadata.tags}
|
||||
onChange={this.handleFieldChange.bind(null, 'tags')} />
|
||||
<div className='field thumbnail'>
|
||||
<label>thumbnail</label>
|
||||
<input type='text' className='value'
|
||||
value={this.props.metadata.thumbnail}
|
||||
onChange={this.handleFieldChange.bind(null, 'thumbnail')} />
|
||||
</div>
|
||||
*/}
|
||||
|
||||
<div className='field systems'>
|
||||
<label>systems</label>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
|
||||
.metadataEditor{
|
||||
position : absolute;
|
||||
z-index : 10000;
|
||||
box-sizing : border-box;
|
||||
width : 100%;
|
||||
padding : 25px;
|
||||
background-color : #999;
|
||||
// background-color : #999;
|
||||
background-color: white;
|
||||
.field{
|
||||
display : flex;
|
||||
width : 100%;
|
||||
|
||||
@@ -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>
|
||||
},
|
||||
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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'>
|
||||
|
||||
@@ -2,6 +2,8 @@ const React = require('react');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
const OldBrewRenderer = require('depricated/brewRendererOld/brewRendererOld.jsx');
|
||||
|
||||
const Markdown = require('homebrewery/markdown.js');
|
||||
const ErrorBar = require('./errorBar/errorBar.jsx');
|
||||
|
||||
@@ -15,12 +17,18 @@ const PPR_THRESHOLD = 50;
|
||||
const BrewRenderer = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
brewText : '',
|
||||
brew : {
|
||||
text : '',
|
||||
style : ''
|
||||
},
|
||||
|
||||
|
||||
//TODO: maybe remove?
|
||||
errors : []
|
||||
};
|
||||
},
|
||||
getInitialState: function() {
|
||||
const pages = this.props.brewText.split('\\page');
|
||||
const pages = this.props.brew.text.split('\\page');
|
||||
|
||||
return {
|
||||
viewablePageNumber: 0,
|
||||
@@ -36,16 +44,16 @@ const BrewRenderer = React.createClass({
|
||||
|
||||
componentDidMount: function() {
|
||||
this.updateSize();
|
||||
window.addEventListener("resize", this.updateSize);
|
||||
window.addEventListener('resize', this.updateSize);
|
||||
},
|
||||
componentWillUnmount: function() {
|
||||
window.removeEventListener("resize", this.updateSize);
|
||||
window.removeEventListener('resize', this.updateSize);
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
if(this.refs.pages && this.refs.pages.firstChild) this.pageHeight = this.refs.pages.firstChild.clientHeight;
|
||||
|
||||
const pages = nextProps.brewText.split('\\page');
|
||||
const pages = nextProps.brew.text.split('\\page');
|
||||
this.setState({
|
||||
pages : pages,
|
||||
usePPR : pages.length >= PPR_THRESHOLD
|
||||
@@ -57,6 +65,7 @@ const BrewRenderer = React.createClass({
|
||||
if(this.refs.pages && this.refs.pages.firstChild) this.pageHeight = this.refs.pages.firstChild.clientHeight;
|
||||
}, 1);
|
||||
|
||||
|
||||
this.setState({
|
||||
height : this.refs.main.parentNode.clientHeight,
|
||||
isMounted : true
|
||||
@@ -98,13 +107,13 @@ const BrewRenderer = React.createClass({
|
||||
},
|
||||
|
||||
renderDummyPage : function(index){
|
||||
return <div className='phb' id={`p${index + 1}`} key={index}>
|
||||
return <div className='phb v2' id={`p${index + 1}`} key={index}>
|
||||
<i className='fa fa-spinner fa-spin' />
|
||||
</div>
|
||||
},
|
||||
|
||||
renderPage : function(pageText, index){
|
||||
return <div className='phb' id={`p${index + 1}`} dangerouslySetInnerHTML={{__html:Markdown.render(pageText)}} key={index} />
|
||||
return <div className='phb v2' id={`p${index + 1}`} dangerouslySetInnerHTML={{__html:Markdown.render(pageText)}} key={index} />
|
||||
},
|
||||
|
||||
renderPages : function(){
|
||||
@@ -124,7 +133,15 @@ 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} />;
|
||||
|
||||
|
||||
return <div className='brewRenderer'
|
||||
onScroll={this.handleScroll}
|
||||
ref='main'
|
||||
@@ -133,6 +150,9 @@ const BrewRenderer = React.createClass({
|
||||
<ErrorBar errors={this.props.errors} />
|
||||
<RenderWarnings />
|
||||
|
||||
|
||||
{this.renderStyle()}
|
||||
|
||||
<div className='pages' ref='pages'>
|
||||
{this.renderPages()}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
@import (less) './client/homebrew/phbStyle/phb.style.less';
|
||||
@import (less) './shared/homebrewery/phb_style/phb.less';
|
||||
.pane{
|
||||
position : relative;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,16 @@ const BrewRenderer = require('./brewRenderer.jsx');
|
||||
|
||||
|
||||
module.exports = Store.createSmartComponent(BrewRenderer, () => {
|
||||
const brew = Store.getBrew();
|
||||
|
||||
|
||||
return {
|
||||
brewText : Store.getBrewText(),
|
||||
brew : Store.getBrew(),
|
||||
|
||||
brewText : Store.getBrewCode(),
|
||||
style : Store.getBrewStyle(),
|
||||
|
||||
|
||||
errors : Store.getErrors()
|
||||
}
|
||||
});
|
||||
@@ -1,82 +1,51 @@
|
||||
var _ = require('lodash');
|
||||
var Markdown = require('marked');
|
||||
var renderer = new Markdown.Renderer();
|
||||
const _ = require('lodash');
|
||||
const Markdown = require('marked');
|
||||
|
||||
//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>')){
|
||||
var 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>`;
|
||||
|
||||
const renderer = new Markdown.Renderer();
|
||||
let blockCount = 0;
|
||||
renderer.paragraph = function(text){
|
||||
const blockReg = /{{[\w|,]+|}}/g;
|
||||
const matches = text.match(blockReg);
|
||||
if(!matches) return `\n<p>${text}</p>\n`;
|
||||
let matchIndex = 0;
|
||||
const res = _.reduce(text.split(blockReg), (r, text) => {
|
||||
if(text) r.push(Markdown(text, {renderer : renderer, sanitize: true}));
|
||||
const block = matches[matchIndex];
|
||||
if(block && block[0] == '{'){
|
||||
r.push(`\n\n<div class="block ${block.substring(2).split(',').join(' ')}">`);
|
||||
blockCount++;
|
||||
}
|
||||
return html;
|
||||
if(block == '}}' && blockCount !== 0){
|
||||
r.push('</div>\n\n');
|
||||
blockCount--;
|
||||
}
|
||||
matchIndex++;
|
||||
return r;
|
||||
}, []).join('\n');
|
||||
return res;
|
||||
};
|
||||
|
||||
|
||||
const tagTypes = ['div', 'span', 'a'];
|
||||
const tagRegex = new RegExp('(' +
|
||||
_.map(tagTypes, (type)=>{
|
||||
return `\\<${type}|\\</${type}>`;
|
||||
}).join('|') + ')', 'g');
|
||||
renderer.image = function(href, title, text){
|
||||
return `<img src="${href}" class="${text.split(',').join(' ')}"></img>`;
|
||||
};
|
||||
|
||||
|
||||
module.exports = {
|
||||
marked : Markdown,
|
||||
render : (rawBrewText)=>{
|
||||
return Markdown(rawBrewText, {renderer : renderer})
|
||||
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');
|
||||
return html;
|
||||
},
|
||||
|
||||
validate : (rawBrewText) => {
|
||||
var errors = [];
|
||||
var leftovers = _.reduce(rawBrewText.split('\n'), (acc, line, _lineNumber) => {
|
||||
var lineNumber = _lineNumber + 1;
|
||||
var 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;
|
||||
return [];
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
BIN
shared/homebrewery/phb_style/fonts/Bookinsanity Bold Italic.otf
Normal file
BIN
shared/homebrewery/phb_style/fonts/Bookinsanity Bold.otf
Normal file
BIN
shared/homebrewery/phb_style/fonts/Bookinsanity Italic.otf
Normal file
BIN
shared/homebrewery/phb_style/fonts/Bookinsanity.otf
Normal file
BIN
shared/homebrewery/phb_style/fonts/Mr Eaves Small Caps.otf
Normal file
BIN
shared/homebrewery/phb_style/fonts/Scaly Sans Caps.otf
Normal file
BIN
shared/homebrewery/phb_style/fonts/Scaly Sans.otf
Normal file
BIN
shared/homebrewery/phb_style/fonts/Solbera Imitation.otf
Normal file
BIN
shared/homebrewery/phb_style/img/desc_border.png
Normal file
|
After Width: | Height: | Size: 311 B |
BIN
shared/homebrewery/phb_style/img/divider.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
shared/homebrewery/phb_style/img/dmg_bg.jpg
Normal file
|
After Width: | Height: | Size: 187 KiB |
BIN
shared/homebrewery/phb_style/img/footer.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
shared/homebrewery/phb_style/img/footer_flip.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
shared/homebrewery/phb_style/img/frame_border.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
shared/homebrewery/phb_style/img/monster_bg.jpg
Normal file
|
After Width: | Height: | Size: 339 KiB |
|
Before Width: | Height: | Size: 327 B After Width: | Height: | Size: 327 B |
|
Before Width: | Height: | Size: 530 B After Width: | Height: | Size: 530 B |
BIN
shared/homebrewery/phb_style/img/note_border.pdn
Normal file
BIN
shared/homebrewery/phb_style/img/note_border.png
Normal file
|
After Width: | Height: | Size: 530 B |
BIN
shared/homebrewery/phb_style/img/note_border_shadow.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
shared/homebrewery/phb_style/img/phb_bg.jpg
Normal file
|
After Width: | Height: | Size: 171 KiB |
BIN
shared/homebrewery/phb_style/img/phb_dark_bg.jpg
Normal file
|
After Width: | Height: | Size: 172 KiB |
BIN
shared/homebrewery/phb_style/img/shadow_border.pdn
Normal file
BIN
shared/homebrewery/phb_style/img/shadow_border.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
211
shared/homebrewery/phb_style/phb.blocks.less
Normal 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();
|
||||
}
|
||||
38
shared/homebrewery/phb_style/phb.colors.less
Normal file
@@ -0,0 +1,38 @@
|
||||
//TODO: come up with fun color names
|
||||
|
||||
@background : #EEE5CE;
|
||||
@noteGreen : #e0e5c1;
|
||||
@headerUnderline : #c9ad6a;
|
||||
@horizontalRule : #9c2b1b;
|
||||
@headerText : #58180D;
|
||||
@monsterStatBackground : #FDF1DC;
|
||||
|
||||
|
||||
|
||||
.colorElements(@color){
|
||||
table tbody{
|
||||
tr:nth-child(odd){
|
||||
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); }
|
||||
172
shared/homebrewery/phb_style/phb.elements.less
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
55
shared/homebrewery/phb_style/phb.fonts.less
Normal file
@@ -0,0 +1,55 @@
|
||||
/* Main Font */
|
||||
@font-face {
|
||||
font-family: BookInsanity;
|
||||
src: url('/assets/homebrewery/phb_style/fonts/Bookinsanity.otf');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: BookInsanity;
|
||||
src: url('/assets/homebrewery/phb_style/fonts/Bookinsanity Bold.otf');
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: BookInsanity;
|
||||
src: url('/assets/homebrewery/phb_style/fonts/Bookinsanity Italic.otf');
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
}
|
||||
@font-face {
|
||||
font-family: BookInsanity;
|
||||
src: url('/assets/homebrewery/phb_style/fonts/Bookinsanity Bold Italic.otf');
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Notes and Tables */
|
||||
@font-face {
|
||||
font-family: ScalySans;
|
||||
src: url('/assets/homebrewery/phb_style/fonts/Scaly Sans.otf');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: ScalySansSmallCaps;
|
||||
src: url('/assets/homebrewery/phb_style/fonts/Scaly Sans Caps.otf');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* Fancy First Letter */
|
||||
@font-face {
|
||||
font-family: Solbera;
|
||||
src: url('/assets/homebrewery/phb_style/fonts/Solbera Imitation.otf');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* Headers */
|
||||
@font-face {
|
||||
font-family: MrEaves;
|
||||
src: url('/assets/homebrewery/phb_style/fonts/Mr Eaves Small Caps.otf');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
16
shared/homebrewery/phb_style/phb.img.less
Normal file
@@ -0,0 +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');
|
||||
|
||||
323
shared/homebrewery/phb_style/phb.less
Normal file
@@ -0,0 +1,323 @@
|
||||
|
||||
//TODO: Remove this
|
||||
/*
|
||||
@media print {
|
||||
.phb.v2{
|
||||
.descriptive, blockquote{
|
||||
box-shadow : none;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
.phb.v2{
|
||||
@import './phb.mixins.less';
|
||||
@import './phb.fonts.less';
|
||||
@import './phb.colors.less';
|
||||
@import './phb.img.less';
|
||||
@import './phb.blocks.less';
|
||||
@import './phb.elements.less';
|
||||
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;
|
||||
column-count : 2;
|
||||
column-fill : auto;
|
||||
column-gap : 1cm;
|
||||
column-width : 8cm;
|
||||
background-color : @background;
|
||||
background-image : @phbBG;
|
||||
font-family : BookInsanity;
|
||||
font-size : 0.317cm;
|
||||
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
|
||||
// *****************************/
|
||||
//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 : @monsterBorder 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 : @dividerImg;
|
||||
background-size : 100% 100%;
|
||||
border : none;
|
||||
}
|
||||
}
|
||||
|
||||
//*****************************
|
||||
// * EXTRAS
|
||||
// *****************************/
|
||||
//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;
|
||||
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;
|
||||
// }
|
||||
// Nested lists
|
||||
//*****************************
|
||||
// * SPELL LIST
|
||||
// *****************************/
|
||||
/*
|
||||
.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;
|
||||
column-break-inside : auto;
|
||||
}
|
||||
}
|
||||
//*****************************
|
||||
// * PRINT
|
||||
// *****************************/
|
||||
// &.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;
|
||||
// }
|
||||
// }
|
||||
//*****************************
|
||||
// * CLASS TABLE
|
||||
// *****************************/
|
||||
/*
|
||||
.descriptive{
|
||||
display : block-inline;
|
||||
margin-bottom : 1em;
|
||||
background-color : #faf7ea;
|
||||
font-family : ScalySans;
|
||||
border-style : solid;
|
||||
border-width : 7px;
|
||||
border-image : @descriptiveBorder 12 round;
|
||||
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;
|
||||
}
|
||||
}
|
||||
pre+.descriptive{
|
||||
margin-top : 8px;
|
||||
}
|
||||
*/
|
||||
//*****************************
|
||||
// * Old Stuff
|
||||
// *****************************/
|
||||
// //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;
|
||||
// }
|
||||
}
|
||||
48
shared/homebrewery/phb_style/phb.mixins.less
Normal 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;
|
||||
}
|
||||
}
|
||||
82
shared/homebrewery/snippets/brew/class.snippet.js
Normal 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}
|
||||
}}`;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
19
shared/homebrewery/snippets/brew/index.js
Normal 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
|
||||
|
||||
|
||||
);
|
||||
74
shared/homebrewery/snippets/brew/monster.snippet.js
Normal 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
|
||||
|
||||
}}`
|
||||
|
||||
|
||||
}
|
||||
};
|
||||
22
shared/homebrewery/snippets/brew/note.snippet.js
Normal 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(' ')}
|
||||
}}`
|
||||
}
|
||||
|
||||
|
||||
}
|
||||