0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2025-12-24 22:52:40 +00:00

Merge branch 'v1.3'

This commit is contained in:
Scott
2016-02-19 21:38:56 -05:00
17 changed files with 639 additions and 370 deletions

View File

@@ -1,19 +1,45 @@
## changelog
# changelog
#### Sunday, 17/01/2016
## v1.3.0
### Friday, 19/02/2016
* Improved the admin panel
* Added ability to clear away old empty brews
* Added delete button to the edit page
* Added a dynamically updating changelog page! Nifty!
* Added stlying for wide monster stat blocks and single column class tables
* Added snippets for wide monster stat blocks and single column class tables
### Tuesday, 16/02/2016
* Paragraphs right after tables now indent (thanks LikeAJi6!)
* Added a `@page` css rule to auto turn off margins when printing
* Added a `page-break` property on each `.phb` page to properly page the pages up when exporting (thanks Jokefury!)
* Improved first character rendering on Firefox
* Improved table spacing a bit
* Changed padding at page bottom for better fit and clipping of elements
* Improved spacing for bold text (thanks nickpunt!)
## v1.2.0
### Sunday, 17/01/2016
* Added a printer friendly snippet that injects some CSS to remove backbrounds and images
* Adjusted the styling specific to spell blocks to give it tighter spacing
* Added a changelog! How meta!
#### Thursday, 14/01/2016
## v1.1.0
### Thursday, 14/01/2016
* Added view source to see the markdown that made the page
* Added print view
* Fixed API issues that were causing the server to crash
* Increased padding on table cells
* Raw html now shows in view source
#### Wednesday, 3/01/2016
## v1.0.0 - Release
### Wednesday, 3/01/2016
* Added `phb.standalone.css` plus a build system for creating it
* Added page numbers and footer text

View File

@@ -1,11 +1,16 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var request = require('superagent');
var Moment = require('moment')
var Moment = require('moment');
//TODO: Add incremental React scrolling
var VIEW_LIMIT = 30;
var COLUMN_HEIGHT = 52;
var HomebrewAdmin = React.createClass({
getDefaultProps: function() {
return {
homebrews : [],
@@ -13,52 +18,83 @@ var HomebrewAdmin = React.createClass({
};
},
getInitialState: function() {
return {
viewStartIndex: 0
};
},
clearOldBrews : function(){
if(!confirm("Are you sure you want to clear out old brews?")) return;
request.get('/homebrew/clear_old/?admin_key=' + this.props.admin_key)
.send()
.end(function(err, res){
window.location.reload();
})
},
deleteBrew : function(brewId){
request.get('/homebrew/remove/' + brewId +'?admin_key=' + this.props.admin_key)
.send()
.end(function(err, res){
window.location.reload();
})
},
renderBrews : function(){
// return _.times(VIEW_LIMIT, (i)=>{
// var brew = this.props.homebrews[i + this.state.viewStartIndex];
// if(!brew) return null;
return _.map(this.props.homebrews, (brew)=>{
return <tr className={cx('brewRow', {'isEmpty' : brew.text == ""})} key={brew.sharedId}>
<td>{brew.editId}</td>
<td>{brew.shareId}</td>
<td><a href={'/homebrew/edit/' + brew.editId} target='_blank'>{brew.editId}</a></td>
<td><a href={'/homebrew/share/' + brew.shareId} target='_blank'>{brew.shareId}</a></td>
<td>{Moment(brew.createdAt).fromNow()}</td>
<td>{Moment(brew.updatedAt).fromNow()}</td>
<td>{Moment(brew.lastViewed).fromNow()}</td>
<td>{brew.views}</td>
<td className='preview'>
<a target="_blank" href={'/homebrew/share/' + brew.shareId}>view</a>
<div className='content'>
{brew.text.slice(0, 500)}
<td>
<div className='deleteButton' onClick={this.deleteBrew.bind(this, brew.editId)}>
<i className='fa fa-trash' />
</div>
</td>
<td><a href={'/homebrew/remove/' + brew.editId +'?admin_key=' + this.props.admin_key}><i className='fa fa-trash' /></a></td>
</tr>
});
},
})
renderBrewTable : function(){
return <div className='brewTable'>
<table>
<thead>
<tr>
<th>Edit Id</th>
<th>Share Id</th>
<th>Created At</th>
<th>Last Updated</th>
<th>Last Viewed</th>
<th>Number of Views</th>
</tr>
</thead>
<tbody>
{this.renderBrews()}
</tbody>
</table>
</div>
},
render : function(){
var self = this;
return(
<div className='homebrewAdmin'>
<h2>Homebrews - {this.props.homebrews.length}</h2>
<table>
<thead>
<tr>
<th>Edit Id</th>
<th>Share Id</th>
<th>Created At</th>
<th>Last Updated</th>
<th>Last Viewed</th>
<th>Number of Views</th>
<th>Preview</th>
</tr>
</thead>
<tbody>
{this.renderBrews()}
</tbody>
</table>
</div>
);
return <div className='homebrewAdmin'>
<h2>
Homebrews - {this.props.homebrews.length}
<button className='clearOldButton' onClick={this.clearOldBrews}>
Clear Old
</button>
</h2>
{this.renderBrewTable()}
</div>
}
});

View File

@@ -1,44 +1,54 @@
.homebrewAdmin{
table{
.brewTable{
overflow-y : scroll;
max-height : 800px;
th{
padding : 10px;
font-weight : 800;
}
tr:nth-child(even){
background-color : fade(@green, 10%);
}
tr.isEmpty{
background-color : fade(@red, 30%);
}
td{
min-width : 100px;
padding : 10px;
text-align : center;
&.preview{
position : relative;
&:hover{
.content{
display : block;
max-height : 500px;
table{
th{
padding : 10px;
font-weight : 800;
}
tr:nth-child(even){
background-color : fade(@green, 10%);
}
tr.isEmpty{
background-color : fade(@red, 30%);
}
td{
min-width : 100px;
padding : 10px;
text-align : center;
&.preview{
position : relative;
&:hover{
.content{
display : block;
}
}
.content{
position : absolute;
display : none;
top : 100%;
left : 0px;
z-index : 1000;
max-height : 500px;
width : 300px;
padding : 30px;
background-color : white;
font-family : monospace;
text-align : left;
pointer-events : none;
}
}
.content{
position : absolute;
display : none;
top : 100%;
left : 0px;
z-index : 1000;
max-height : 500px;
width : 300px;
padding : 30px;
background-color : white;
font-family : monospace;
text-align : left;
pointer-events : none;
}
}
}
}
.deleteButton{
cursor: pointer;
}
button.clearOldButton{
float : right;
}
}

View File

@@ -1,78 +1,105 @@
var _ = require('lodash');
module.exports = function(classname){
var features = [
"Astrological Botany",
"Astrological Chemistry",
"Biochemical Sorcery",
"Civil Alchemy",
"Consecrated Biochemistry",
"Demonic Anthropology",
"Divinatory Mineralogy",
"Genetic Banishing",
"Hermetic Geography",
"Immunological Incantations",
"Nuclear Illusionism",
"Ritual Astronomy",
"Seismological Divination",
"Spiritual Biochemistry",
"Statistical Occultism",
"Police Necromancer",
"Sixgun Poisoner",
"Pharmaceutical Gunslinger",
"Infernal Banker",
"Spell Analyst",
"Gunslinger Corruptor",
"Torque Interfacer",
"Exo Interfacer",
"Gunpowder Torturer",
"Orbital Gravedigger",
"Phased Linguist",
"Mathematical Pharmacist",
"Plasma Outlaw",
"Malefic Chemist",
"Police Cultist"
];
classname = classname || _.sample(['Archivist', 'Fancyman', 'Linguist', 'Fletcher',
'Notary', 'Berserker-Typist', 'Fishmongerer', 'Manicurist', 'Haberdasher', 'Concierge'])
var classnames = ['Archivist', 'Fancyman', 'Linguist', 'Fletcher',
'Notary', 'Berserker-Typist', 'Fishmongerer', 'Manicurist', 'Haberdasher', 'Concierge'];
var features = [
"Astrological Botany",
"Astrological Chemistry",
"Biochemical Sorcery",
"Civil Alchemy",
"Consecrated Biochemistry",
"Demonic Anthropology",
"Divinatory Mineralogy",
"Genetic Banishing",
"Hermetic Geography",
"Immunological Incantations",
"Nuclear Illusionism",
"Ritual Astronomy",
"Seismological Divination",
"Spiritual Biochemistry",
"Statistical Occultism",
"Police Necromancer",
"Sixgun Poisoner",
"Pharmaceutical Gunslinger",
"Infernal Banker",
"Spell Analyst",
"Gunslinger Corruptor",
"Torque Interfacer",
"Exo Interfacer",
"Gunpowder Torturer",
"Orbital Gravedigger",
"Phased Linguist",
"Mathematical Pharmacist",
"Plasma Outlaw",
"Malefic Chemist",
"Police Cultist"
];
var levels = ["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th", "11th", "12th", "13th", "14th", "15th", "16th", "17th", "18th", "19th", "20th"]
var maxes = [4,3,3,3,3,2,2,1,1]
var drawSlots = function(Slots){
var slots = Number(Slots);
return _.times(9, function(i){
var max = maxes[i];
if(slots < 1) return "—";
var res = _.min([max, slots]);
slots -= res;
return res;
}).join(' | ')
module.exports = {
full : function(classname){
classname = classname || _.sample(classnames)
var maxes = [4,3,3,3,3,2,2,1,1]
var drawSlots = function(Slots){
var slots = Number(Slots);
return _.times(9, function(i){
var max = maxes[i];
if(slots < 1) return "—";
var res = _.min([max, slots]);
slots -= res;
return res;
}).join(' | ')
}
var cantrips = 3;
var spells = 1;
var slots = 2;
return "##### The " + classname + "\n" +
"___\n" +
"| Level | Proficiency Bonus | Features | Cantrips Known | Spells Known | 1st | 2nd | 3rd | 4th | 5th | 6th | 7th | 8th | 9th |\n"+
"|:---:|:---:|:---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|\n" +
_.map(levels, function(levelName, level){
var res = [
levelName,
"+" + Math.ceil(level/5 + 1),
_.sample(features, _.sample([0,1,1])).join(', ') || "Ability Score Improvement",
cantrips,
spells,
drawSlots(slots)
].join(' | ');
cantrips += _.random(0,1);
spells += _.random(0,1);
slots += _.random(0,2);
return "| " + res + " |";
}).join('\n') +'\n\n';
},
half : function(classname){
classname = classname || _.sample(classnames)
var featureScore = 1
return "##### The " + classname + "\n" +
"___\n" + "___\n" +
"| Level | Proficiency Bonus | Features | " + _.sample(features) + "|\n" +
"|:---:|:---:|:---|:---:|\n" +
_.map(levels, function(levelName, level){
var res = [
levelName,
"+" + Math.ceil(level/5 + 1),
_.sample(features, _.sample([0,1,1])).join(', ') || "Ability Score Improvement",
"+" + featureScore
].join(' | ');
featureScore += _.random(0,1);
return "| " + res + " |";
}).join('\n') +'\n\n';
}
var cantrips = 3;
var spells = 1;
var slots = 2;
return "##### The " + classname + "\n" +
"___\n" +
"| Level | Proficiency Bonus | Features | Cantrips Known | Spells Known | 1st | 2nd | 3rd | 4th | 5th | 6th | 7th | 8th | 9th |\n"+
"|:---:|:---:|:---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|\n" +
_.map(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th", "11th", "12th", "13th", "14th", "15th", "16th", "17th", "18th", "19th", "20th"],function(levelName, level){
var res = [
levelName,
"+" + Math.ceil(level/5 + 1),
_.sample(features, _.sample([0,1,1])).join(', ') || "Ability Score Improvement",
cantrips,
spells,
drawSlots(slots)
].join(' | ');
cantrips += _.random(0,1);
spells += _.random(0,1);
slots += _.random(0,2);
return "| " + res + " |";
}).join('\n') +'\n\n';
};

View File

@@ -4,9 +4,8 @@ var genList = function(list, max){
return _.sample(list, _.random(0,max)).join(', ') || "None";
}
module.exports = function(){
var monsterName = _.sample([
var getMonsterName = function(){
return _.sample([
"All-devouring Baseball Imp",
"All-devouring Gumdrop Wraith",
"Chocolate Hydra",
@@ -59,10 +58,14 @@ module.exports = function(){
"Time Kangaroo",
"Tomb Poodle",
]);
}
var type = _.sample(['Tiny', 'Small', 'Medium', 'Large', 'Gargantuan', 'Stupidly vast']) + " " + _.sample(['beast', 'fiend', 'annoyance', 'guy', 'cutie'])
var getType = function(){
return _.sample(['Tiny', 'Small', 'Medium', 'Large', 'Gargantuan', 'Stupidly vast']) + " " + _.sample(['beast', 'fiend', 'annoyance', 'guy', 'cutie'])
}
var alignment =_.sample([
var getAlignment = function(){
return _.sample([
"annoying evil",
"chaotic gossipy",
"chaotic sloppy",
@@ -80,89 +83,114 @@ module.exports = function(){
"wordy evil",
"unaligned"
]);
};
var stats = '>|' + _.times(6, function(){
var getStats = function(){
return '>|' + _.times(6, function(){
var num = _.random(1,20);
var mod = Math.ceil(num/2 - 5)
return num + " (" + (mod >= 0 ? '+'+mod : mod ) + ")"
}).join('|') + '|';
var genAbilities = function(){
return _.sample([
"> ***Pack Tactics.*** These guys work together. Like super well, you don't even know.",
"> ***False Appearance. *** While the armor reamin motionless, it is indistinguishable from a normal suit of armor.",
]);
}
var genAction = function(){
var name = _.sample([
"Abdominal Drop",
"Airplane Hammer",
"Atomic Death Throw",
"Bulldog Rake",
"Corkscrew Strike",
"Crossed Splash",
"Crossface Suplex",
"DDT Powerbomb",
"Dual Cobra Wristlock",
"Dual Throw",
"Elbow Hold",
"Gory Body Sweep",
"Heel Jawbreaker",
"Jumping Driver",
"Open Chin Choke",
"Scorpion Flurry",
"Somersault Stump Fists",
"Suffering Wringer",
"Super Hip Submission",
"Super Spin",
"Team Elbow",
"Team Foot",
"Tilt-a-whirl Chin Sleeper",
"Tilt-a-whirl Eye Takedown",
"Turnbuckle Roll"
])
return "> ***" + name + ".*** *Melee Weapon Attack:* +4 to hit, reach 5ft., one target. *Hit* 5 (1d6 + 2) ";
}
return [
"___",
"> ## " + monsterName,
">*" + type + ", " + alignment+ "*",
"> ___",
"> - **Armor Class** " + _.random(10,20),
"> - **Hit Points** " + _.random(1, 150) + "(1d4 + 5)",
"> - **Speed** " + _.random(0,50) + "ft.",
">___",
">|STR|DEX|CON|INT|WIS|CHA|",
">|:---:|:---:|:---:|:---:|:---:|:---:|",
stats,
">___",
"> - **Condition Immunities** " + genList(["groggy", "swagged", "weak-kneed", "buzzed", "groovy", "melancholy", "drunk"], 3),
"> - **Senses** passive Perception " + _.random(3, 20),
"> - **Languages** " + genList(["Common", "Pottymouth", "Gibberish", "Latin", "Jive"], 2),
"> - **Challenge** " + _.random(0, 15) + " (" + _.random(10,10000)+ " XP)",
"> ___",
_.times(_.random(0,2), function(){
return genAbilities()
}).join('\n>\n'),
"> ### Actions",
_.times(_.random(1,2), function(){
return genAction()
}).join('\n>\n'),
].join('\n') + '\n\n\n';
}
/*
var genAbilities = function(){
return _.sample([
"> ***Pack Tactics.*** These guys work together. Like super well, you don't even know.",
"> ***False Appearance. *** While the armor reamin motionless, it is indistinguishable from a normal suit of armor.",
]);
}
*/
var genAction = function(){
var name = _.sample([
"Abdominal Drop",
"Airplane Hammer",
"Atomic Death Throw",
"Bulldog Rake",
"Corkscrew Strike",
"Crossed Splash",
"Crossface Suplex",
"DDT Powerbomb",
"Dual Cobra Wristlock",
"Dual Throw",
"Elbow Hold",
"Gory Body Sweep",
"Heel Jawbreaker",
"Jumping Driver",
"Open Chin Choke",
"Scorpion Flurry",
"Somersault Stump Fists",
"Suffering Wringer",
"Super Hip Submission",
"Super Spin",
"Team Elbow",
"Team Foot",
"Tilt-a-whirl Chin Sleeper",
"Tilt-a-whirl Eye Takedown",
"Turnbuckle Roll"
])
/*
return "> ***" + name + ".*** *Melee Weapon Attack:* +4 to hit, reach 5ft., one target. *Hit* 5 (1d6 + 2) ";
}
*/
module.exports = {
full : function(){
return [
"___",
"___",
"> ## " + getMonsterName(),
">*" + getType() + ", " + getAlignment() + "*",
"> ___",
"> - **Armor Class** " + _.random(10,20),
"> - **Hit Points** " + _.random(1, 150) + "(1d4 + 5)",
"> - **Speed** " + _.random(0,50) + "ft.",
">___",
">|STR|DEX|CON|INT|WIS|CHA|",
">|:---:|:---:|:---:|:---:|:---:|:---:|",
getStats(),
">___",
"> - **Condition Immunities** " + genList(["groggy", "swagged", "weak-kneed", "buzzed", "groovy", "melancholy", "drunk"], 3),
"> - **Senses** passive Perception " + _.random(3, 20),
"> - **Languages** " + genList(["Common", "Pottymouth", "Gibberish", "Latin", "Jive"], 2),
"> - **Challenge** " + _.random(0, 15) + " (" + _.random(10,10000)+ " XP)",
"> ___",
_.times(_.random(3,6), function(){
return genAbilities()
}).join('\n>\n'),
"> ### Actions",
_.times(_.random(4,6), function(){
return genAction()
}).join('\n>\n'),
].join('\n') + '\n\n\n';
},
half : function(){
return [
"___",
"> ## " + getMonsterName(),
">*" + getType() + ", " + getAlignment() + "*",
"> ___",
"> - **Armor Class** " + _.random(10,20),
"> - **Hit Points** " + _.random(1, 150) + "(1d4 + 5)",
"> - **Speed** " + _.random(0,50) + "ft.",
">___",
">|STR|DEX|CON|INT|WIS|CHA|",
">|:---:|:---:|:---:|:---:|:---:|:---:|",
getStats(),
">___",
"> - **Condition Immunities** " + genList(["groggy", "swagged", "weak-kneed", "buzzed", "groovy", "melancholy", "drunk"], 3),
"> - **Senses** passive Perception " + _.random(3, 20),
"> - **Languages** " + genList(["Common", "Pottymouth", "Gibberish", "Latin", "Jive"], 2),
"> - **Challenge** " + _.random(0, 15) + " (" + _.random(10,10000)+ " XP)",
"> ___",
_.times(_.random(0,2), function(){
return genAbilities()
}).join('\n>\n'),
"> ### Actions",
_.times(_.random(1,2), function(){
return genAction()
}).join('\n>\n'),
].join('\n') + '\n\n\n';
}
}

View File

@@ -36,7 +36,7 @@ module.exports = [
},
{
tooltip : 'Table',
icon : 'fa-list',
icon : 'fa-th-list',
snippet : function(){
return [
"##### Cookie Tastiness",
@@ -53,12 +53,22 @@ module.exports = [
{
tooltip : 'Monster Stat Block',
icon : 'fa-bug',
snippet : MonsterBlockGen,
snippet : MonsterBlockGen.half,
},
{
tooltip : 'Wide Monster Stat Block',
icon : 'fa-bullseye',
snippet : MonsterBlockGen.full,
},
{
tooltip : "Class Table",
icon : 'fa-table',
snippet : ClassTableGen,
snippet : ClassTableGen.full,
},
{
tooltip : "Half Class Table",
icon : 'fa-list-alt',
snippet : ClassTableGen.half,
},
{
tooltip : "Column Break",
@@ -98,7 +108,7 @@ module.exports = [
},
{
tooltip : "Printer Friendly",
tooltip : "Ink Friendly",
icon : 'fa-print',
snippet : function(){
return "<style>\n .phb{ background : white;}\n .phb img{ display : none;}\n .phb hr+blockquote{background : white;}\n</style>\n\n";

View File

@@ -1,9 +1,7 @@
# The Homebrewery
Welcome traveler from an antique land. Please sit and tell us of what you have seen. The unheard of monsters, who slither and bite.
Welcome traveler from an antique land. Please sit and tell us of what you have seen. The unheard of monsters, who slither and bite. Tell us of the wondrous items and and artifacts you have found, their mysteries yet to be unlocked. Of the vexing vocations and surprising skills you have seen.
Tell us of the wondrous items and and artifacts you have found, their mysteries yet to be unlocked. Of the vexing vocations and surprising skills you have seen.
### Homebrew made easy
### Homebrew D&D made easy
The Homebrewery allows for the creation and sharing of authentic looking Fifth-Edition homebrews, with just text editing. It accomplishes this by using [Markdown](https://help.github.com/articles/markdown-basics/) along with some custom CSS-styling.
Stop worrying about learning Photoshop, fiddling with spacing, or tracking down the PHB assets. Just focus on making your homebrew **great**.
@@ -11,6 +9,8 @@ Stop worrying about learning Photoshop, fiddling with spacing, or tracking down
**Try it! **Simply edit the text on the left and watch it *update live* on the right.
#### Features
* Monster Stat Blocks
* Full class tables
@@ -30,42 +30,42 @@ When you create your own homebrew you will be given a *edit url* and a *share ur
Anyone with the *share url* will be able to access a read-only version of your homebrew.
> ##### Words of Caution
> ___
> * **Concurrent Editing** The Homebrewery does not support concurrent user editing. It's best one user at a time makes edits to avoid overwriting each other.
> * **Back-up your brews** I can not guarantee that I will support this project indefinitely. So if you'd like to hang on to your creation be sure to back up it up.
```
```
## New Things!
What's new in the latest update? Check out the full changelog [here](https://github.com/stolksdorf/NaturalCrit/blob/master/changelog.md)
## New Things in v1.3!
What's new in the latest update? Check out the full changelog [here](/homebrew/changelog)
* **PDF Exporting works!** Check out the following note block to see how
* **Changelog Page** will track all the updates I've made
* **Delete brew** butotn has been added to the edit page
* **Wide Monster Stat Blocks** have been added with a snippet
* **Single Column Class Tables** have been added with a snippet
* **Improvements to stlying** to get it closer to PHB stlying
* **Better Firefox Compatibility**, although Chrome still works best.
* **View Source** on the share page to see the markdown text for the brew
* **Fixed Server Issues** should increase stability of the site greatly
* **Footnotes & Page Numbers**
* **Print View** displays your brew ready for printing, saving to PDF or image.
* **Footer Accent** now switches directions each page, neat!
* **Standalone Styling** the PHB-style has been reduced to a single file
* **Reduced asset sizes** This should help with page load times
>##### PDF Exporting
>The best way to do a PDF export is to use the **print view** of a brew, print that page and save as PDF.
> Follow these steps to export your brew to PDF
> * Create a breath-taking homebrew
> * Share it with a few friends for feedback and balance
> * Install [Chrome Canary](https://www.google.com/chrome/browser/canary.html)
> * Go to your brew on Chrome Canary
> * Hit the **Print View** button
> * Print that page. Make sure the paper size is **letter**
> * You're done!
>
>***"But there's no columns when I do this in Chrome!"***
>
>This is a known bug in Chrome for **five years**. When saving to PDF, it doesn't respect columns. Amazingly this was just fixed [last month](https://code.google.com/p/chromium/issues/detail?id=99358), but hasn't been deployed yet.
>
>Converting to PDF *precisely* is **very** difficult. There are many services and libraries out there, but none of them have gotten it right to the level I'm happy with. Most of them use Chrome's engine which has the aforementioned bug in it.
>
>This is why I made the **print view**. It gives you a single completely standalone HTML version of your brew; Download it, export it, screenshot it, print it, *do whatever you want*.
> Due to a bug in Chrome's Print To PDF feature, columns aren't supported. The fix to this has been released in Chrome Canary.
## Bugs, Issues, Suggestions?
Have an idea of how to make The Homebrewery better? Or did you find something that wasn't quite right? Head [here](https://github.com/stolksdorf/NaturalCrit/issues/new) and let me know!.
You can also checkout the [Changelog here](/homebrew/changelog).
@@ -109,7 +109,6 @@ You are free to use The Homebrewery is any way that you want, except for claimin
There are a few things I couldn't get right
* Spell save block, with centered text and sans serif are not support. Ran out of mark-up to use
* Full page monster stat blocks
* "Spell slots per level" text above the levels on a class table.
* I built this for Chrome, so if it looks weird to you, use Chrome instead.
@@ -117,4 +116,3 @@ There are a few things I couldn't get right
<div class='footnote'>PART 2 | BORING STUFF</div>

View File

@@ -14,6 +14,7 @@ var Homebrew = React.createClass({
return {
url : "",
welcomeText : "",
changelog : "",
brew : {
text : "",
shareId : null,
@@ -28,9 +29,13 @@ var Homebrew = React.createClass({
'/homebrew/edit/:id' : (args) => {
return <EditPage id={args.id} entry={this.props.brew} />
},
'/homebrew/share/:id' : (args) => {
return <SharePage id={args.id} entry={this.props.brew} />
},
'/homebrew/changelog' : (args) => {
return <SharePage entry={{text : this.props.changelog}} />
},
'/homebrew*' : <HomePage welcomeText={this.props.welcomeText} />,
});
},

View File

@@ -1,3 +1,4 @@
@import (less) 'shared/naturalCrit/styles/reset.less';
@import (less) './client/homebrew/phbStyle/phb.fonts.css';
@import (less) './client/homebrew/phbStyle/phb.assets.less';
@@ -8,6 +9,7 @@
@horizontalRule : #9c2b1b;
@headerText : #58180D;
@monsterStatBackground : #FDF1DC;
@page { margin: 0; }
.useSansSerif(){
font-family : ScalySans;
em{
@@ -15,32 +17,40 @@
font-style : italic;
}
strong{
font-family : ScalySans;
font-weight : 800;
font-family : ScalySans;
font-weight : 800;
letter-spacing : -0.02em;
}
}
.phb{
position : relative;
z-index : 15;
box-sizing : border-box;
overflow : hidden;
height : 279.4mm;
width : 215.9mm;
padding : 1.0cm 1.7cm;
.useColumns(){
column-count : 2;
column-fill : auto;
column-gap : 1cm;
column-width : 8cm;
background-color : @background;
background-image : @backgroundImage;
font-family : BookSanity;
font-size : 9pt;
-webkit-column-count : 2;
-moz-column-count : 2;
-webkit-column-width : 8cm;
-moz-column-width : 8cm;
-webkit-column-gap : 1cm;
text-rendering : optimizeLegibility;
-moz-column-gap : 1cm;
}
.phb{
.useColumns();
position : relative;
z-index : 15;
box-sizing : border-box;
overflow : hidden;
height : 279.4mm;
width : 215.9mm;
padding : 1.0cm 1.7cm;
padding-bottom : 1.5cm;
background-color : @background;
background-image : @backgroundImage;
font-family : BookSanity;
font-size : 9pt;
text-rendering : optimizeLegibility;
page-break-before : always;
page-break-after : always;
//*****************************
// * BASE
// *****************************/
@@ -58,11 +68,18 @@
list-style-position : inside;
list-style-type : disc;
}
ol{
margin-bottom : 0.8em;
line-height : 1.3em;
list-style-position : inside;
list-style-type : decimal;
}
img{
z-index : -1;
}
strong{
font-weight : bold;
font-weight : bold;
letter-spacing : 0.03em;
}
em{
font-style : italic;
@@ -78,16 +95,16 @@
color : @headerText;
}
h1{
column-span : all;
column-span : 2;
font-size : 28pt;
-webkit-column-span : all;
-webkit-column-span : 2;
-moz-column-span : 2;
&+p::first-letter{
float : left;
margin-top : 0.4em;
margin-bottom : 0.4em;
font-family : Solberry;
font-size : 10em;
color : #222;
float : left;
font-family : Solberry;
font-size : 10em;
color : #222;
line-height : 0.8em;
}
}
h2{
@@ -127,7 +144,7 @@
tbody{
tr{
td{
padding : 0.2em 0.1em;
padding : 0.3em 0.1em;
}
&:nth-child(odd){
background-color : @noteGreen;
@@ -159,7 +176,9 @@
position : relative;
background-color : @monsterStatBackground;
border : none;
padding-top: 15px;
h2{
margin-top: -8px;
margin-bottom : 0px;
&+p{
padding-bottom : 0px;
@@ -241,15 +260,18 @@
left : -3px;
}
}
//Full Width
hr+hr+blockquote{
.useColumns();
}
//*****************************
// * FULL CLASS TABLE
// * CLASS TABLE
// *****************************/
h5+hr+table{
hr+table{
margin-top : -5px;
margin-bottom : 50px;
padding-top : 10px;
border-collapse : separate;
column-span : all;
background-color : white;
border : initial;
border-image-outset : 37px 17px;
@@ -257,8 +279,14 @@
border-image-slice : 150 200 150 200;
border-image-source : @frameBorderImage;
border-image-width : 47px;
-webkit-column-span : all;
}
h5+hr+table{
column-span : all;
-webkit-column-span : all;
-moz-column-span : all;
}
//*****************************
// * FOOTER
// *****************************/
@@ -316,22 +344,32 @@
text-indent : -1em;
list-style-type : none;
}
//Double hr for full width elements
hr+hr+blockquote{
column-span : all;
-webkit-column-span : all;
-moz-column-span : all;
}
//Column Break
pre{
visibility : hidden;
-webkit-column-break-after : always;
break-after : always;
-moz-column-break-after : always;
}
//Avoid breaking up
p,ul,blockquote,table{
z-index : 15;
-webkit-column-break-inside : avoid;
-moz-column-break-inside : avoid;
-o-column-break-inside : avoid;
-ms-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
margin-top : -0.5em
}
//Text indent right after table
table+p{
text-indent : 1em;
}
}

View File

@@ -2,6 +2,7 @@ var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var Moment = require('moment');
var request = require('superagent')
var Logo = require('naturalCrit/logo/logo.jsx');
@@ -13,7 +14,7 @@ var Statusbar = React.createClass({
getDefaultProps: function() {
return {
//editId: null,
editId: null,
sourceText : null,
shareId : null,
printId : null,
@@ -38,14 +39,22 @@ var Statusbar = React.createClass({
},
deleteBrew : function(){
if(!confirm("are you sure you want to delete this brew?")) return;
if(!confirm("are you REALLY sure? You will not be able to recover it")) return;
request.get('/homebrew/remove/' + this.props.editId)
.send()
.end(function(err, res){
window.location.href = '/homebrew';
});
},
openSourceWindow : function(){
var sourceWindow = window.open();
var content = replaceAll(this.props.sourceText, '<', '&lt;');
content = replaceAll(content, '>', '&gt;');
console.log(content);
sourceWindow.document.write('<code><pre>' + content + '</pre></code>');
},
@@ -65,6 +74,16 @@ var Statusbar = React.createClass({
},
renderChromeTip : function(){
if(typeof window !== 'undefined' && window.chrome) return;
return <div
className='chromeField'
data-tooltip="If you are noticing rendering issues, try using Chrome instead.">
<i className='fa fa-exclamation-triangle' />
Optimized for Chrome
</div>
},
renderSourceButton : function(){
if(!this.props.sourceText) return null;
@@ -81,6 +100,14 @@ var Statusbar = React.createClass({
</a>
},
renderChangelogButton : function(){
if(this.props.editId || this.props.shareId) return null;
return <a className='changelogButton' target='_blank' href='/homebrew/changelog'>
Changelog <i className='fa fa-file-text-o' />
</a>
},
renderShare : function(){
if(!this.props.shareId) return null;
@@ -97,6 +124,15 @@ var Statusbar = React.createClass({
</a>
},
renderDeleteButton : function(){
if(!this.props.editId) return null;
return <div className='deleteButton' onClick={this.deleteBrew}>
Delete <i className='fa fa-trash' />
</div>
},
renderStatus : function(){
if(!this.props.editId) return null;
@@ -120,9 +156,12 @@ var Statusbar = React.createClass({
</a>
</div>
<div className='controls right'>
{this.renderChromeTip()}
{this.renderChangelogButton()}
{this.renderStatus()}
{this.renderInfo()}
{this.renderSourceButton()}
{this.renderDeleteButton()}
{this.renderPrintButton()}
{this.renderShare()}
{this.renderNewButton()}

View File

@@ -58,6 +58,31 @@
background-color : fade(@green, 70%);
}
}
.chromeField{
background-color: @orange;
color : white;
text-decoration : none;
i{
margin-right: 10px;
}
}
.changelogButton{
.animate(background-color);
color : white;
text-decoration : none;
&:hover{
background-color : fade(@purple, 70%);
}
}
.deleteButton{
.animate(background-color);
color : white;
text-decoration : none;
cursor: pointer;
&:hover{
background-color : fade(@red, 70%);
}
}
.shareField{
.animate(background-color);
cursor : pointer;

View File

@@ -18,13 +18,14 @@ var Router = CreateRouter({
var NaturalCrit = React.createClass({
getDefaultProps: function() {
return {
url : '/'
url : '/',
changelog : ''
};
},
render : function(){
return <div className='naturalCrit'>
<Router initialUrl={this.props.url} />
<Router initialUrl={this.props.url} scope={this}/>
</div>
},
});

View File

@@ -1,7 +1,7 @@
{
"name": "naturalCrit",
"description": "A super rad project!",
"version": "0.0.0",
"version": "1.3.0",
"scripts": {
"postinstall": "gulp prod",
"start": "node server.js"
@@ -21,7 +21,7 @@
"moment": "^2.11.0",
"mongoose": "^4.3.3",
"pico-flux": "^1.1.0",
"pico-router": "^1.0.0",
"pico-router": "^1.1.0",
"react": "^0.14.2",
"react-dom": "^0.14.2",
"shortid": "^2.2.4",

File diff suppressed because one or more lines are too long

View File

@@ -65,7 +65,7 @@ app.get('*', function (req, res) {
globals:{
},
//prerenderWith : './client/naturalCrit/naturalCrit.jsx',
prerenderWith : './client/naturalCrit/naturalCrit.jsx',
initialProps: {
url: req.originalUrl
},

View File

@@ -1,9 +1,27 @@
var _ = require('lodash');
var Moment = require('moment');
var vitreumRender = require('vitreum/render');
var HomebrewModel = require('./homebrew.model.js').model;
var changelogText = require('fs').readFileSync('./changelog.md', 'utf8');
var getTopBrews = function(cb){
HomebrewModel.find().sort({views: -1}).limit(5).exec(function(err, brews) {
cb(brews);
});
}
module.exports = function(app){
app.get('/homebrew/top', function(req, res){
getTopBrews(function(topBrews){
return res.json(topBrews);
});
})
app.get('/homebrew/new', function(req, res){
var newHomebrew = new HomebrewModel();
@@ -28,8 +46,9 @@ module.exports = function(app){
});
app.get('/homebrew/remove/:id', function(req, res){
if(req.query && req.query.admin_key == process.env.ADMIN_KEY){
//if(req.query && req.query.admin_key == process.env.ADMIN_KEY){
HomebrewModel.find({editId : req.params.id}, function(err, objs){
console.log(err);
if(!objs.length || err) return res.status(404).send("Can not find homebrew with that id");
var resEntry = objs[0];
resEntry.remove(function(err){
@@ -37,14 +56,28 @@ module.exports = function(app){
return res.status(200).send();
})
});
//}else{
// return res.status(401).send('Access denied');
//}
});
//Removes all empty brews that are older than 3 days
app.get('/homebrew/clear_old', function(req, res){
if(req.query && req.query.admin_key == process.env.ADMIN_KEY){
HomebrewModel.remove({
text : '',
createdAt: {
$lt: Moment().subtract(3, 'days').toDate()
}
}, function(err, objs){
return res.status(200).send();
});
}else{
return res.status(401).send('Access denied');
}
});
//Edit Page
app.get('/homebrew/edit/:id', function(req, res){
HomebrewModel.find({editId : req.params.id}, function(err, objs){
@@ -122,52 +155,9 @@ module.exports = function(app){
var page = '<html><head>' + title + PHBStyle + '</head><body>' + content +'</body></html>'
return res.send(page)
})
});
});
//PDF download
/*
var pdf = require('html-pdf');
app.get('/homebrew/pdf/:id', function(req, res){
HomebrewModel.find({shareId : req.params.id}, function(err, objs){
if(err) return res.status(404).send();
var resObj = null;
var errObj = {text: "# oops\nCould not find the homebrew."}
if(objs.length){
resObj = objs[0];
}
var content = _.map(resObj.text.split('\\page'), function(pageText){
return '<div class="phb">' + Markdown(pageText) + '</div>';
}).join('\n');
var title = '<title>' + resObj.text.split('\n')[0] + '</title>';
var page = '<html><head>' + title + PHBStyle + '</head><body>' + content +'</body></html>'
var config = {
"height": (279.4 - 56) + "mm",
"width": (215.9 - 43) + "mm",
"border": "0",
}
pdf.create(page, config).toStream(function(err, stream){
res.attachment('pdfname.pdf');
return stream.pipe(res);
});
})
});
*/
//Home and 404, etc.
var welcomeText = require('fs').readFileSync('./client/homebrew/homePage/welcome_msg.txt', 'utf8');
@@ -178,7 +168,8 @@ module.exports = function(app){
prerenderWith : './client/homebrew/homebrew.jsx',
initialProps: {
url: req.originalUrl,
welcomeText : welcomeText
welcomeText : welcomeText,
changelog : changelogText
},
clearRequireCache : true,
}, function (err, page) {

View File

@@ -1,2 +1 @@
/* Eric Meyer's Reset CSS v2.0 - http://cssreset.com */
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{border:0;font-size:100%;font:inherit;vertical-align:baseline;margin:0;padding:0}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:none}table{border-collapse:collapse;border-spacing:0}