mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2025-12-28 20:03:02 +00:00
Merge branch 'master' into pr/1586
This commit is contained in:
@@ -30,7 +30,7 @@ const BrewRenderer = createClass({
|
||||
if(this.props.renderer == 'legacy') {
|
||||
pages = this.props.text.split('\\page');
|
||||
} else {
|
||||
pages = this.props.text.split(/^\\page/gm);
|
||||
pages = this.props.text.split(/^\\page$/gm);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -62,7 +62,7 @@ const BrewRenderer = createClass({
|
||||
if(this.props.renderer == 'legacy') {
|
||||
pages = this.props.text.split('\\page');
|
||||
} else {
|
||||
pages = this.props.text.split(/^\\page/gm);
|
||||
pages = this.props.text.split(/^\\page$/gm);
|
||||
}
|
||||
this.setState({
|
||||
pages : pages,
|
||||
@@ -117,7 +117,7 @@ const BrewRenderer = createClass({
|
||||
},
|
||||
|
||||
renderDummyPage : function(index){
|
||||
return <div className='phb' id={`p${index + 1}`} key={index}>
|
||||
return <div className='phb page' id={`p${index + 1}`} key={index}>
|
||||
<i className='fas fa-spinner fa-spin' />
|
||||
</div>;
|
||||
},
|
||||
@@ -130,8 +130,14 @@ const BrewRenderer = createClass({
|
||||
renderPage : function(pageText, index){
|
||||
if(this.props.renderer == 'legacy')
|
||||
return <div className='phb page' id={`p${index + 1}`} dangerouslySetInnerHTML={{ __html: MarkdownLegacy.render(pageText) }} key={index} />;
|
||||
else
|
||||
return <div className='phb3 page' id={`p${index + 1}`} dangerouslySetInnerHTML={{ __html: Markdown.render(pageText) }} key={index} />;
|
||||
else {
|
||||
pageText += `\n\n \n\\column\n `; //Artificial column break at page end to emulate column-fill:auto (until `wide` is used, when column-fill:balance will reappear)
|
||||
return (
|
||||
<div className='page' id={`p${index + 1}`} key={index} >
|
||||
<div className='columnWrapper' dangerouslySetInnerHTML={{ __html: Markdown.render(pageText) }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
renderPages : function(){
|
||||
@@ -182,7 +188,6 @@ const BrewRenderer = createClass({
|
||||
: null}
|
||||
|
||||
<Frame initialContent={this.state.initialContent}
|
||||
head = <link href={`${this.props.renderer == 'legacy' ? '/themes/5ePhbLegacy.style.css' : '/themes/5ePhb.style.css'}`} rel='stylesheet'/>
|
||||
style={{ width: '100%', height: '100%', visibility: this.state.visibility }}
|
||||
contentDidMount={this.frameDidMount}>
|
||||
<div className={'brewRenderer'}
|
||||
@@ -194,17 +199,17 @@ const BrewRenderer = createClass({
|
||||
<RenderWarnings />
|
||||
<NotificationPopup />
|
||||
</div>
|
||||
|
||||
<div className='pages' ref='pages'>
|
||||
{/* Apply CSS from Style tab and render pages from Markdown tab */}
|
||||
{this.state.isMounted
|
||||
&&
|
||||
<>
|
||||
{this.renderStyle()}
|
||||
<link href={`${this.props.renderer == 'legacy' ? '/themes/5ePhbLegacy.style.css' : '/themes/5ePhb.style.css'}`} rel='stylesheet'/>
|
||||
{/* Apply CSS from Style tab and render pages from Markdown tab */}
|
||||
{this.state.isMounted
|
||||
&&
|
||||
<>
|
||||
{this.renderStyle()}
|
||||
<div className='pages' ref='pages'>
|
||||
{this.renderPages()}
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
</Frame>
|
||||
{this.renderPageInfo()}
|
||||
|
||||
@@ -4,7 +4,7 @@ const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames'); //Unused variable
|
||||
|
||||
const DISMISS_KEY = 'dismiss_notification7-10-20';
|
||||
const DISMISS_KEY = 'dismiss_notification09-9-21';
|
||||
|
||||
const NotificationPopup = createClass({
|
||||
getInitialState : function() {
|
||||
@@ -22,22 +22,39 @@ const NotificationPopup = createClass({
|
||||
notifications : {
|
||||
psa : function(){
|
||||
return <li key='psa'>
|
||||
<em>Google Drive Integration!</em> <br />
|
||||
We have added Google Drive integration to the Homebrewery! <a target='_blank' href='https://www.naturalcrit.com/login'>Sign in</a> with
|
||||
your Google account to link it with your Homebrewery profile. A new button in the Edit page will let you transfer your file to your personal
|
||||
Google Drive storage, and Google will keep a backup of each version! No more lost work surprises!
|
||||
<em>V3.0.0 Released!</em> <br />
|
||||
After a long and bumpy road, we decided it was high time we finally release version 3 of the homebrewery into the wild. You can check out a
|
||||
brief overview and see how to opt-in to the new features here:
|
||||
<a target='_blank' href='https://homebrewery.naturalcrit.com/v3_preview'>V3 Welcome Page</a> and
|
||||
<a target='_blank' href='https://homebrewery.naturalcrit.com/changelog'>the Changelog</a>.
|
||||
<br /><br />
|
||||
However, we are aware that there may be uncaught bugs. We encourage you to copy your brew into a text document before transferring to Google
|
||||
Drive just in case any issues arise as this update is rolled out.
|
||||
<em>BE WARNED:</em> As we continue to develop V3, expect small tweaks in the styling, fonts, and snippets; your brews may look slightly
|
||||
different from day-to-day. All of your old documents will continue to work as normal; we are not touching them. If you don't want to deal
|
||||
with the possibility of slight formatting changes, you may choose to stick with the Legacy renderer on any of your brews for as long as you like.
|
||||
<br /><br />
|
||||
<b>Note:</b> Transferring an existing brew to Google Drive will change the edit and share links of your document. If you have shared your
|
||||
document online, remember to update the links there as well.
|
||||
With this in mind, if you still wish to try out V3, you can opt-in any of your brews to the the V3 renderer.
|
||||
This will likely break much of your formatting as a lot of the Markdown code has been updated, and starting from scratch may be cleaner.
|
||||
(Don't worry, you can always change the renderer back to Legacy for any brew at any time).
|
||||
</li>;
|
||||
},
|
||||
refreshGoogle : function (){
|
||||
return <li key='refreshGoogle'>
|
||||
<em>Refresh your Google Drive Credentials!</em> <br />
|
||||
Currently a lot of people are striking issues with their Google credentials expiring, which happens one year after the last sign in via
|
||||
Google. This can cause errors when trying to save your brews. If this happens, simply visit the
|
||||
<a target='_blank' href='https://www.naturalcrit.com/login'>
|
||||
logout page
|
||||
</a>
|
||||
, sign out, and then sign back in "with Google" to refresh your credentials. See
|
||||
<a target='_blank' href='https://github.com/naturalcrit/homebrewery/discussions/1580'>
|
||||
this discussion on Github
|
||||
</a> for more details.
|
||||
</li>;
|
||||
},
|
||||
faq : function(){
|
||||
return <li key='faq'>
|
||||
<em>Protect your work! </em> <br />
|
||||
If you opt not to use your Google Drive, keep in mind that we do not save a history of your projects. Please make frequent backups of your brews!
|
||||
If you opt not to use your Google Drive, keep in mind that we do not save a history of your projects. Please make frequent backups of your brews!
|
||||
<a target='_blank' href='https://www.reddit.com/r/homebrewery/comments/adh6lh/faqs_psas_announcements/'>
|
||||
See the FAQ
|
||||
</a> to learn how to avoid losing your work!
|
||||
@@ -62,8 +79,10 @@ const NotificationPopup = createClass({
|
||||
return <div className='notificationPopup'>
|
||||
<i className='fas fa-times dismiss' onClick={this.dismiss}/>
|
||||
<i className='fas fa-info-circle info' />
|
||||
<h3>Notice</h3>
|
||||
<small>This website is always improving and we are still adding new features and squashing bugs. Keep the following in mind:</small>
|
||||
<div className='header'>
|
||||
<h3>Notice</h3>
|
||||
<small>This website is always improving and we are still adding new features and squashing bugs. Keep the following in mind:</small>
|
||||
</div>
|
||||
<ul>{_.values(this.state.notifications)}</ul>
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
.popups{
|
||||
position : fixed;
|
||||
top : @navbarHeight;
|
||||
top : @navbarHeight;
|
||||
right : 15px;
|
||||
z-index : 10001;
|
||||
width : 350px;
|
||||
width : 450px;
|
||||
}
|
||||
|
||||
.notificationPopup{
|
||||
position : relative;
|
||||
float : right;
|
||||
position : relative;
|
||||
display : inline-block;
|
||||
width : 350px;
|
||||
width : 100%;
|
||||
padding : 15px;
|
||||
padding-bottom : 10px;
|
||||
padding-left : 55px;
|
||||
padding-left : 25px;
|
||||
background-color : @blue;
|
||||
color : white;
|
||||
a{
|
||||
color : @steel;
|
||||
color : #e0e5c1;
|
||||
font-weight : 800;
|
||||
}
|
||||
i.info{
|
||||
@@ -37,6 +36,9 @@
|
||||
opacity : 1;
|
||||
}
|
||||
}
|
||||
.header {
|
||||
padding-left : 50px;
|
||||
}
|
||||
small{
|
||||
opacity : 0.7;
|
||||
font-size : 0.6em;
|
||||
|
||||
@@ -68,15 +68,18 @@ const Editor = createClass({
|
||||
},
|
||||
|
||||
handleInject : function(injectText){
|
||||
const text = (this.isText() ? this.props.brew.text : this.props.brew.style);
|
||||
let text;
|
||||
if(this.isText()) text = this.props.brew.text;
|
||||
if(this.isStyle()) text = this.props.brew.style ?? DEFAULT_STYLE_TEXT;
|
||||
|
||||
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 + injectText.split('\n').length, cursorPos.ch + injectText.length);
|
||||
const injectLines = injectText.split('\n');
|
||||
this.refs.codeEditor.setCursorPosition(cursorPos.line + injectLines.length, cursorPos.ch + injectLines[injectLines.length - 1].length);
|
||||
|
||||
if(this.isText()) this.props.onTextChange(lines.join('\n'));
|
||||
if(this.isText()) this.props.onTextChange(lines.join('\n'));
|
||||
if(this.isStyle()) this.props.onStyleChange(lines.join('\n'));
|
||||
},
|
||||
|
||||
@@ -119,7 +122,7 @@ const Editor = createClass({
|
||||
|
||||
// New Codemirror styling for V3 renderer
|
||||
if(this.props.renderer == 'V3') {
|
||||
if(line.startsWith('\\page')){
|
||||
if(line.match(/^\\page$/)){
|
||||
codeMirror.addLineClass(lineNumber, 'background', 'pageLine');
|
||||
r.push(lineNumber);
|
||||
}
|
||||
|
||||
@@ -67,9 +67,6 @@
|
||||
.button(@silver);
|
||||
}
|
||||
small{
|
||||
position : absolute;
|
||||
bottom : -15px;
|
||||
left : 0px;
|
||||
font-size : 0.6em;
|
||||
font-style : italic;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const _ = require('lodash');
|
||||
const dedent = require('dedent-tabs').default;
|
||||
|
||||
module.exports = function(classname){
|
||||
|
||||
@@ -10,33 +11,32 @@ module.exports = function(classname){
|
||||
const hitDie = _.sample([4, 6, 8, 10, 12]);
|
||||
|
||||
const abilityList = ['Strength', 'Dexerity', 'Constitution', 'Wisdom', 'Charisma', 'Intelligence'];
|
||||
const skillList = ['Acrobatics ', 'Animal Handling', 'Arcana', 'Athletics', 'Deception', 'History', 'Insight', 'Intimidation', 'Investigation', 'Medicine', 'Nature', 'Perception', 'Performance', 'Persuasion', 'Religion', 'Sleight of Hand', 'Stealth', 'Survival'];
|
||||
const skillList = ['Acrobatics', 'Animal Handling', 'Arcana', 'Athletics', 'Deception', 'History', 'Insight', 'Intimidation', 'Investigation', 'Medicine', 'Nature', 'Perception', 'Performance', 'Persuasion', 'Religion', 'Sleight of Hand', 'Stealth', 'Survival'];
|
||||
|
||||
|
||||
return [
|
||||
'## Class Features',
|
||||
`As a ${classname}, you gain the following class features`,
|
||||
'#### Hit Points',
|
||||
'___',
|
||||
`- **Hit Dice:** 1d${hitDie} per ${classname} level`,
|
||||
`- **Hit Points at 1st Level:** ${hitDie} + your Constitution modifier`,
|
||||
`- **Hit Points at Higher Levels:** 1d${hitDie} (or ${hitDie/2 + 1}) + your Constitution modifier per ${classname} level after 1st`,
|
||||
'',
|
||||
'#### Proficiencies',
|
||||
'___',
|
||||
`- **Armor:** ${_.sampleSize(['Light armor', 'Medium armor', 'Heavy armor', 'Shields'], _.random(0, 3)).join(', ') || 'None'}`,
|
||||
`- **Weapons:** ${_.sampleSize(['Squeegee', 'Rubber Chicken', 'Simple weapons', 'Martial weapons'], _.random(0, 2)).join(', ') || 'None'}`,
|
||||
`- **Tools:** ${_.sampleSize(['Artian\'s tools', 'one musical instrument', 'Thieve\'s tools'], _.random(0, 2)).join(', ') || 'None'}`,
|
||||
'',
|
||||
'___',
|
||||
`- **Saving Throws:** ${_.sampleSize(abilityList, 2).join(', ')}`,
|
||||
`- **Skills:** Choose two from ${_.sampleSize(skillList, _.random(4, 6)).join(', ')}`,
|
||||
'',
|
||||
'#### Equipment',
|
||||
'You start with the following equipment, in addition to the equipment granted by your background:',
|
||||
'- *(a)* a martial weapon and a shield or *(b)* two martial weapons',
|
||||
'- *(a)* five javelins or *(b)* any simple melee weapon',
|
||||
`- ${_.sample(['10 lint fluffs', '1 button', 'a cherished lost sock'])}`,
|
||||
'\n\n\n'
|
||||
].join('\n');
|
||||
return dedent`
|
||||
## Class Features
|
||||
As a ${classname}, you gain the following class features
|
||||
#### Hit Points
|
||||
|
||||
**Hit Dice:** :: 1d${hitDie} per ${classname} level
|
||||
**Hit Points at 1st Level:** :: ${hitDie} + your Constitution modifier
|
||||
**Hit Points at Higher Levels:** :: 1d${hitDie} (or ${hitDie/2 + 1}) + your Constitution modifier per ${classname} level after 1st
|
||||
|
||||
#### Proficiencies
|
||||
|
||||
**Armor:** :: ${_.sampleSize(['Light armor', 'Medium armor', 'Heavy armor', 'Shields'], _.random(0, 3)).join(', ') || 'None'}
|
||||
**Weapons:** :: ${_.sampleSize(['Squeegee', 'Rubber Chicken', 'Simple weapons', 'Martial weapons'], _.random(0, 2)).join(', ') || 'None'}
|
||||
**Tools:** :: ${_.sampleSize(['Artian\'s tools', 'one musical instrument', 'Thieve\'s tools'], _.random(0, 2)).join(', ') || 'None'}
|
||||
|
||||
**Saving Throws:** :: ${_.sampleSize(abilityList, 2).join(', ')}
|
||||
**Skills:** :: Choose two from ${_.sampleSize(skillList, _.random(4, 6)).join(', ')}
|
||||
|
||||
#### Equipment
|
||||
You start with the following equipment, in addition to the equipment granted by your background:
|
||||
- *(a)* a martial weapon and a shield or *(b)* two martial weapons
|
||||
- *(a)* five javelins or *(b)* any simple melee weapon
|
||||
- ${_.sample(['10 lint fluffs', '1 button', 'a cherished lost sock'])}
|
||||
|
||||
`;
|
||||
};
|
||||
|
||||
@@ -2,86 +2,77 @@ const _ = require('lodash');
|
||||
|
||||
const features = [
|
||||
'Astrological Botany',
|
||||
'Astrological Chemistry',
|
||||
'Biochemical Sorcery',
|
||||
'Civil Alchemy',
|
||||
'Consecrated Biochemistry',
|
||||
'Civil Divination',
|
||||
'Consecrated Augury',
|
||||
'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',
|
||||
'Genetic Banishing',
|
||||
'Gunpowder Torturer',
|
||||
'Orbital Gravedigger',
|
||||
'Phased Linguist',
|
||||
'Mathematical Pharmacist',
|
||||
'Plasma Outlaw',
|
||||
'Gunslinger Corruptor',
|
||||
'Hermetic Geography',
|
||||
'Immunological Cultist',
|
||||
'Malefic Chemist',
|
||||
'Police Cultist'
|
||||
'Mathematical Pharmacy',
|
||||
'Nuclear Biochemistry',
|
||||
'Orbital Gravedigger',
|
||||
'Pharmaceutical Outlaw',
|
||||
'Phased Linguist',
|
||||
'Plasma Gunslinger',
|
||||
'Police Necromancer',
|
||||
'Ritual Astronomy',
|
||||
'Sixgun Poisoner',
|
||||
'Seismological Alchemy',
|
||||
'Spiritual Illusionism',
|
||||
'Statistical Occultism',
|
||||
'Spell Analyst',
|
||||
'Torque Interfacer'
|
||||
];
|
||||
|
||||
const classnames = ['Archivist', 'Fancyman', 'Linguist', 'Fletcher',
|
||||
'Notary', 'Berserker-Typist', 'Fishmongerer', 'Manicurist', 'Haberdasher', 'Concierge'];
|
||||
const classnames = ['Ackerman', 'Berserker-Typist', 'Concierge', 'Fishmonger',
|
||||
'Haberdasher', 'Manicurist', 'Netrunner', 'Weirkeeper'];
|
||||
|
||||
const levels = ['1st', '2nd', '3rd', '4th', '5th', '6th', '7th', '8th', '9th', '10th', '11th', '12th', '13th', '14th', '15th', '16th', '17th', '18th', '19th', '20th'];
|
||||
const levels = ['1st', '2nd', '3rd', '4th', '5th',
|
||||
'6th', '7th', '8th', '9th', '10th',
|
||||
'11th', '12th', '13th', '14th', '15th',
|
||||
'16th', '17th', '18th', '19th', '20th'];
|
||||
|
||||
const profBonus = [2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6];
|
||||
|
||||
const getFeature = (level)=>{
|
||||
let res = [];
|
||||
if(_.includes([4, 6, 8, 12, 14, 16, 19], level+1)){
|
||||
res = ['Ability Score Improvement'];
|
||||
}
|
||||
res = _.union(res, _.sampleSize(features, _.sample([0, 1, 1, 1, 1, 1])));
|
||||
if(!res.length) return '─';
|
||||
return res.join(', ');
|
||||
const maxes = [4, 3, 3, 3, 3, 2, 2, 1, 1];
|
||||
|
||||
const drawSlots = function(Slots, rows, padding){
|
||||
let slots = Number(Slots);
|
||||
return _.times(rows, function(i){
|
||||
const max = maxes[i];
|
||||
if(slots < 1) return _.pad('—', padding);
|
||||
const res = _.min([max, slots]);
|
||||
slots -= res;
|
||||
return _.pad(res.toString(), padding);
|
||||
}).join(' | ');
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
full : function(){
|
||||
full : function(classes){
|
||||
const classname = _.sample(classnames);
|
||||
|
||||
const maxes = [4, 3, 3, 3, 3, 2, 2, 1, 1];
|
||||
const drawSlots = function(Slots){
|
||||
let slots = Number(Slots);
|
||||
return _.times(9, function(i){
|
||||
const max = maxes[i];
|
||||
if(slots < 1) return '—';
|
||||
const res = _.min([max, slots]);
|
||||
slots -= res;
|
||||
return res;
|
||||
}).join(' | ');
|
||||
};
|
||||
|
||||
|
||||
let cantrips = 3;
|
||||
let spells = 1;
|
||||
let slots = 2;
|
||||
return `{{classTable,wide\n##### The ${classname}\n` +
|
||||
`| Level | Proficiency | Features | Cantrips | Spells | --- Spell Slots Per Level --- |||||||||\n`+
|
||||
`| ^| Bonus ^| ^| Known ^| Known ^| 1st | 2nd | 3rd | 4th | 5th | 6th | 7th | 8th | 9th |\n`+
|
||||
`|:-----:|:-----------:|:---------|:--------:|:------:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|\n${
|
||||
return `{{${classes}\n##### The ${classname}\n` +
|
||||
`| Level | Proficiency | Features | Cantrips | Spells | --- Spell Slots Per Spell Level ---|||||||||\n`+
|
||||
`| ^| Bonus ^| ^| Known ^| Known ^|1st |2nd |3rd |4th |5th |6th |7th |8th |9th |\n`+
|
||||
`|:-----:|:-----------:|:-------------|:--------:|:------:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|\n${
|
||||
_.map(levels, function(levelName, level){
|
||||
const res = [
|
||||
levelName,
|
||||
`+${profBonus[level]}`,
|
||||
getFeature(level),
|
||||
cantrips,
|
||||
spells,
|
||||
drawSlots(slots)
|
||||
_.pad(levelName, 5),
|
||||
_.pad(`+${profBonus[level]}`, 2),
|
||||
_.padEnd(_.sample(features), 21),
|
||||
_.pad(cantrips.toString(), 8),
|
||||
_.pad(spells.toString(), 6),
|
||||
drawSlots(slots, 9, 2),
|
||||
].join(' | ');
|
||||
|
||||
cantrips += _.random(0, 1);
|
||||
@@ -92,24 +83,50 @@ module.exports = {
|
||||
}).join('\n')}\n}}\n\n`;
|
||||
},
|
||||
|
||||
half : function(){
|
||||
half : function(classes){
|
||||
const classname = _.sample(classnames);
|
||||
|
||||
let featureScore = 1;
|
||||
return `<div class='classTable'>\n##### The ${classname}\n` +
|
||||
`| Level | Proficiency Bonus | Features | ${_.sample(features)}|\n` +
|
||||
`|:---:|:---:|:---|:---:|\n${
|
||||
return `{{${classes}\n##### The ${classname}\n` +
|
||||
`| Level | Proficiency Bonus | Features | ${_.pad(_.sample(features), 21)} |\n` +
|
||||
`|:-----:|:-----------------:|:---------|:---------------------:|\n${
|
||||
_.map(levels, function(levelName, level){
|
||||
const res = [
|
||||
levelName,
|
||||
`+${profBonus[level]}`,
|
||||
getFeature(level),
|
||||
`+${featureScore}`
|
||||
_.pad(levelName, 5),
|
||||
_.pad(`+${profBonus[level]}`, 2),
|
||||
_.padEnd(_.sample(features), 23),
|
||||
_.pad(`+${featureScore}`, 21),
|
||||
].join(' | ');
|
||||
|
||||
featureScore += _.random(0, 1);
|
||||
|
||||
return `| ${res} |`;
|
||||
}).join('\n')}\n</div>\n\n`;
|
||||
}).join('\n')}\n}}\n\n`;
|
||||
},
|
||||
|
||||
third : function(classes){
|
||||
const classname = _.sample(classnames);
|
||||
|
||||
let cantrips = 3;
|
||||
let spells = 1;
|
||||
let slots = 2;
|
||||
return `{{${classes}\n##### ${classname} Spellcasting\n` +
|
||||
`| Class | Cantrips | Spells |--- Spells Slots per Spell Level ---||||\n` +
|
||||
`| Level ^| Known ^| Known ^| 1st | 2nd | 3rd | 4th |\n` +
|
||||
`|:------:|:--------:|:-------:|:-------:|:-------:|:-------:|:-------:|\n${
|
||||
_.map(levels, function(levelName, level){
|
||||
const res = [
|
||||
_.pad(levelName, 6),
|
||||
_.pad(cantrips.toString(), 8),
|
||||
_.pad(spells.toString(), 7),
|
||||
drawSlots(slots, 4, 7),
|
||||
].join(' | ');
|
||||
|
||||
cantrips += _.random(0, 1);
|
||||
spells += _.random(0, 1);
|
||||
slots += _.random(0, 1);
|
||||
|
||||
return `| ${res} |`;
|
||||
}).join('\n')}\n}}\n\n`;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -60,13 +60,13 @@ module.exports = {
|
||||
const levels = ['Cantrips (0 Level)', '1st Level', '2nd Level', '3rd Level', '4th Level', '5th Level', '6th Level', '7th Level', '8th Level', '9th Level'];
|
||||
|
||||
const content = _.map(levels, (level)=>{
|
||||
const spells = _.map(_.sampleSize(spellNames, _.random(5, 15)), (spell)=>{
|
||||
const spells = _.map(_.sampleSize(spellNames, _.random(4, 10)), (spell)=>{
|
||||
return `- ${spell}`;
|
||||
}).join('\n');
|
||||
return `##### ${level} \n${spells} \n`;
|
||||
}).join('\n');
|
||||
|
||||
return `{{spellList\n${content}\n}}`;
|
||||
return `{{spellList,wide\n${content}\n}}`;
|
||||
},
|
||||
|
||||
spell : function(){
|
||||
|
||||
@@ -105,6 +105,20 @@ const genAbilities = function(){
|
||||
]);
|
||||
};
|
||||
|
||||
const genLongAbilities = function(){
|
||||
return _.sample([
|
||||
dedent`***Pack Tactics.*** These guys work together like peanut butter and jelly. Jelly and peanut butter.
|
||||
|
||||
When one of these guys attacks, the target is covered with, well, peanut butter and jelly.`,
|
||||
dedent`***Hangriness.*** This creature is angry, and hungry. It will refuse to do anything with you until its hunger is satisfied.
|
||||
|
||||
When in visual contact with this creature, you must purchase an extra order of fries, even if they say they aren't hungry.`,
|
||||
dedent`***Full of Detergent.*** This creature has swallowed an entire bottle of dish detergent and is actually having a pretty good time.
|
||||
|
||||
While walking near this creature, you must make a dexterity check or become "a soapy mess" for three hours, after which your skin will get all dry and itchy.`
|
||||
]);
|
||||
};
|
||||
|
||||
const genAction = function(){
|
||||
const name = _.sample([
|
||||
'Abdominal Drop',
|
||||
@@ -159,11 +173,11 @@ module.exports = {
|
||||
**Languages** :: ${genList(['Common', 'Pottymouth', 'Gibberish', 'Latin', 'Jive'], 2)}
|
||||
**Challenge** :: ${_.random(0, 15)} (${_.random(10, 10000)} XP)
|
||||
___
|
||||
${_.times(_.random(genLines, genLines + 2), function(){return genAbilities();}).join('\n:\n')}
|
||||
:
|
||||
${_.times(_.random(genLines, genLines + 2), function(){return genAbilities();}).join('\n\t\t\t\n\t\t\t')}
|
||||
:
|
||||
${genLongAbilities()}
|
||||
### Actions
|
||||
${_.times(_.random(genLines, genLines + 2), function(){return genAction();}).join('\n\t\t\t\n\t\t\t')}
|
||||
${_.times(_.random(genLines, genLines + 2), function(){return genAction();}).join('\n:\n')}
|
||||
}}
|
||||
\n`;
|
||||
}
|
||||
|
||||
@@ -7,12 +7,13 @@ const ClassFeatureGen = require('./classfeature.gen.js');
|
||||
const CoverPageGen = require('./coverpage.gen.js');
|
||||
const TableOfContentsGen = require('./tableOfContents.gen.js');
|
||||
const dedent = require('dedent-tabs').default;
|
||||
const watercolorGen = require('./watercolor.gen.js');
|
||||
|
||||
|
||||
module.exports = [
|
||||
|
||||
{
|
||||
groupName : 'Editor',
|
||||
groupName : 'Text Editor',
|
||||
icon : 'fas fa-pencil-alt',
|
||||
view : 'text',
|
||||
snippets : [
|
||||
@@ -43,33 +44,11 @@ module.exports = [
|
||||
{{wide
|
||||
Everything in here will be extra wide. Tables, text, everything!
|
||||
Beware though, CSS columns can behave a bit weird sometimes. You may
|
||||
have to rely on the automatic column-break rather than \`\column\` if
|
||||
you mix columns and wide blocks on the same page.
|
||||
have to manually place column breaks with \`\column\` to make the
|
||||
surrounding text flow with this wide block the way you want.
|
||||
}}
|
||||
\n`
|
||||
},
|
||||
{
|
||||
name : 'Image',
|
||||
icon : 'fas fa-image',
|
||||
gen : dedent`
|
||||
 {width:325px,mix-blend-mode:multiply}
|
||||
|
||||
{{artist,position:relative,top:-230px,left:-100px,margin-bottom:-30px
|
||||
##### Cat Warrior
|
||||
[Kyoung Hwan Kim](https://www.artstation.com/tahra)
|
||||
}}`
|
||||
},
|
||||
{
|
||||
name : 'Background Image',
|
||||
icon : 'fas fa-tree',
|
||||
gen : dedent`
|
||||
 {position:absolute,top:50px,right:30px,width:280px}
|
||||
|
||||
{{artist,top:90px,right:30px
|
||||
##### Homebrew Mug
|
||||
[naturalcrit](https://homebrew.naturalcrit.com)
|
||||
}}`
|
||||
},
|
||||
{
|
||||
name : 'QR Code',
|
||||
icon : 'fas fa-qrcode',
|
||||
@@ -79,7 +58,6 @@ module.exports = [
|
||||
`https://homebrewery.naturalcrit.com/share/${brew.shareId}` +
|
||||
`&size=100x100) {width:100px;mix-blend-mode:multiply}`;
|
||||
}
|
||||
|
||||
},
|
||||
{
|
||||
name : 'Page Number',
|
||||
@@ -101,35 +79,83 @@ module.exports = [
|
||||
icon : 'fas fa-book',
|
||||
gen : TableOfContentsGen
|
||||
},
|
||||
{
|
||||
name : 'Add Comment',
|
||||
icon : 'fas fa-code',
|
||||
gen : '<!-- This is a comment that will not be rendered into your brew. Hotkey (Ctrl/Cmd + /). -->'
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
groupName : 'Style Editor',
|
||||
icon : 'fas fa-pencil-alt',
|
||||
view : 'style',
|
||||
snippets : [
|
||||
{
|
||||
name : 'Remove Drop Cap',
|
||||
icon : 'fas fa-remove-format',
|
||||
gen : '<style>\n' +
|
||||
' .phb3 h1+p:first-letter {\n' +
|
||||
' all: unset;\n' +
|
||||
' }\n' +
|
||||
'</style>'
|
||||
gen : dedent`/* Removes Drop Caps */
|
||||
.page h1+p:first-letter {
|
||||
all: unset;
|
||||
}\n\n`
|
||||
},
|
||||
{
|
||||
name : 'Tweak Drop Cap',
|
||||
icon : 'fas fa-sliders-h',
|
||||
gen : '<style>\n' +
|
||||
' /* Drop Cap settings */\n' +
|
||||
' .phb3 h1 + p::first-letter {\n' +
|
||||
' float: left;\n' +
|
||||
' font-family: SolberaImitationRemake;\n' +
|
||||
' font-size: 3.5cm;\n' +
|
||||
' color: #222;\n' +
|
||||
' line-height: .8em;\n' +
|
||||
' }\n' +
|
||||
'</style>'
|
||||
gen : dedent`/* Drop Cap settings */
|
||||
.page h1 + p::first-letter {
|
||||
font-family: SolberaImitationRemake;
|
||||
font-size: 3.5cm;
|
||||
background-image: linear-gradient(-45deg, #322814, #998250, #322814);
|
||||
line-height: 1em;
|
||||
}\n\n`
|
||||
},
|
||||
{
|
||||
name : 'Add Comment',
|
||||
icon : 'fas fa-code', /* might need to be fa-solid fa-comment-code --not sure, Gazook */
|
||||
gen : dedent`\n
|
||||
<!-- This is a comment that will not be rendered into your brew. Hotkey (Ctrl/Cmd + /). -->
|
||||
`
|
||||
icon : 'fas fa-code',
|
||||
gen : '/* This is a comment that will not be rendered into your brew. */'
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
/*********************** IMAGES *******************/
|
||||
{
|
||||
groupName : 'Images',
|
||||
icon : 'fas fa-images',
|
||||
view : 'text',
|
||||
snippets : [
|
||||
{
|
||||
name : 'Image',
|
||||
icon : 'fas fa-image',
|
||||
gen : dedent`
|
||||
 {width:325px,mix-blend-mode:multiply}
|
||||
|
||||
{{artist,position:relative,top:-230px,left:10px,margin-bottom:-30px
|
||||
##### Cat Warrior
|
||||
[Kyoung Hwan Kim](https://www.artstation.com/tahra)
|
||||
}}`
|
||||
},
|
||||
{
|
||||
name : 'Background Image',
|
||||
icon : 'fas fa-tree',
|
||||
gen : dedent`
|
||||
 {position:absolute,top:50px,right:30px,width:280px}
|
||||
|
||||
{{artist,top:80px,right:30px
|
||||
##### Homebrew Mug
|
||||
[naturalcrit](https://homebrew.naturalcrit.com)
|
||||
}}`
|
||||
},
|
||||
{
|
||||
name : 'Watercolor Splatter',
|
||||
icon : 'fas fa-fill-drip',
|
||||
gen : watercolorGen,
|
||||
},
|
||||
{
|
||||
name : 'Watermark',
|
||||
icon : 'fas fa-id-card',
|
||||
gen : dedent`
|
||||
{{watermark Homebrewery}}\n`
|
||||
},
|
||||
]
|
||||
},
|
||||
@@ -234,16 +260,6 @@ module.exports = [
|
||||
icon : 'fas fa-table',
|
||||
view : 'text',
|
||||
snippets : [
|
||||
{
|
||||
name : 'Class Table',
|
||||
icon : 'fas fa-table',
|
||||
gen : ClassTableGen.full,
|
||||
},
|
||||
{
|
||||
name : 'Half Class Table',
|
||||
icon : 'fas fa-list-alt',
|
||||
gen : ClassTableGen.half,
|
||||
},
|
||||
{
|
||||
name : 'Table',
|
||||
icon : 'fas fa-th-list',
|
||||
@@ -303,6 +319,36 @@ module.exports = [
|
||||
}}
|
||||
\n`;
|
||||
}
|
||||
},
|
||||
{
|
||||
name : 'Class Table',
|
||||
icon : 'fas fa-table',
|
||||
gen : ClassTableGen.full('classTable,frame,decoration,wide'),
|
||||
},
|
||||
{
|
||||
name : 'Class Table (unframed)',
|
||||
icon : 'fas fa-border-none',
|
||||
gen : ClassTableGen.full('classTable,wide'),
|
||||
},
|
||||
{
|
||||
name : '1/2 Class Table',
|
||||
icon : 'fas fa-list-alt',
|
||||
gen : ClassTableGen.half('classTable,decoration,frame'),
|
||||
},
|
||||
{
|
||||
name : '1/2 Class Table (unframed)',
|
||||
icon : 'fas fa-border-none',
|
||||
gen : ClassTableGen.half('classTable'),
|
||||
},
|
||||
{
|
||||
name : '1/3 Class Table',
|
||||
icon : 'fas fa-border-all',
|
||||
gen : ClassTableGen.third('classTable,frame'),
|
||||
},
|
||||
{
|
||||
name : '1/3 Class Table (unframed)',
|
||||
icon : 'fas fa-border-none',
|
||||
gen : ClassTableGen.third('classTable'),
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -320,44 +366,36 @@ module.exports = [
|
||||
{
|
||||
name : 'A4 Page Size',
|
||||
icon : 'far fa-file',
|
||||
gen : ['/* A4 Page Size */',
|
||||
'.page{',
|
||||
' width : 210mm;',
|
||||
' height : 296.8mm;',
|
||||
'}',
|
||||
''
|
||||
].join('\n')
|
||||
gen : dedent`/* A4 Page Size */
|
||||
.page{
|
||||
width : 210mm;
|
||||
height : 296.8mm;
|
||||
}\n\n`
|
||||
},
|
||||
{
|
||||
name : 'Square Page Size',
|
||||
icon : 'far fa-file',
|
||||
gen : ['/* Square Page Size */',
|
||||
'.page {',
|
||||
' width : 125mm;',
|
||||
' height : 125mm;',
|
||||
' padding : 12.5mm;',
|
||||
' columns : unset;',
|
||||
'}',
|
||||
''
|
||||
].join('\n')
|
||||
gen : dedent`/* Square Page Size */
|
||||
.page {
|
||||
width : 125mm;
|
||||
height : 125mm;
|
||||
padding : 12.5mm;
|
||||
columns : unset;
|
||||
}\n\n`
|
||||
},
|
||||
{
|
||||
name : 'Ink Friendly',
|
||||
icon : 'fas fa-tint',
|
||||
gen : dedent`
|
||||
/* Ink Friendly */
|
||||
.pages *:is(.page,.monster,.note,.descriptive) {
|
||||
*:is(.page,.monster,.note,.descriptive) {
|
||||
background : white !important;
|
||||
box-shadow : 0px 0px 3px !important;
|
||||
}
|
||||
|
||||
.page .note:before {
|
||||
box-shadow : 0px 0px 3px;
|
||||
filter : drop-shadow(0px 0px 3px #888) !important;
|
||||
}
|
||||
|
||||
.page img {
|
||||
visibility : hidden;
|
||||
}`
|
||||
}\n\n`
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
@@ -53,19 +53,19 @@ module.exports = function(brew){
|
||||
const TOC = getTOC(pages);
|
||||
const markdown = _.reduce(TOC, (r, g1, idx1)=>{
|
||||
if(g1.title !== null) {
|
||||
r.push(`\t\t- ### [{{ ${g1.title}}}{{ ${g1.page}}}](#p${g1.page})`);
|
||||
r.push(`- ### [{{ ${g1.title}}}{{ ${g1.page}}}](#p${g1.page})`);
|
||||
}
|
||||
if(g1.children.length){
|
||||
_.each(g1.children, (g2, idx2)=>{
|
||||
if(g2.title !== null) {
|
||||
r.push(`\t\t - #### [{{ ${g2.title}}}{{ ${g2.page}}}](#p${g2.page})`);
|
||||
r.push(` - #### [{{ ${g2.title}}}{{ ${g2.page}}}](#p${g2.page})`);
|
||||
}
|
||||
if(g2.children.length){
|
||||
_.each(g2.children, (g3, idx3)=>{
|
||||
if(g2.title !== null) {
|
||||
r.push(`\t\t - [{{ ${g3.title}}}{{ ${g3.page}}}](#p${g3.page})`);
|
||||
r.push(` - [{{ ${g3.title}}}{{ ${g3.page}}}](#p${g3.page})`);
|
||||
} else { // Don't over-indent if no level-2 parent entry
|
||||
r.push(`\t\t - [{{ ${g3.title}}}{{ ${g3.page}}}](#p${g3.page})`);
|
||||
r.push(` - [{{ ${g3.title}}}{{ ${g3.page}}}](#p${g3.page})`);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -78,7 +78,7 @@ module.exports = function(brew){
|
||||
{{toc,wide
|
||||
# Table Of Contents
|
||||
|
||||
${markdown}
|
||||
${markdown}
|
||||
}}
|
||||
\n`;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = ()=>{
|
||||
return `{{watercolor${_.random(1, 12)},top:20px,left:30px,width:300px,background-color:#BBAD82,opacity:80%}}\n\n`;
|
||||
};
|
||||
@@ -11,7 +11,7 @@ const dedent = require('dedent-tabs').default;
|
||||
module.exports = [
|
||||
|
||||
{
|
||||
groupName : 'Editor',
|
||||
groupName : 'Text Editor',
|
||||
icon : 'fas fa-pencil-alt',
|
||||
view : 'text',
|
||||
snippets : [
|
||||
@@ -78,33 +78,44 @@ module.exports = [
|
||||
icon : 'fas fa-book',
|
||||
gen : TableOfContentsGen
|
||||
},
|
||||
{
|
||||
name : 'Add Comment',
|
||||
icon : 'fas fa-code',
|
||||
gen : '<!-- This is a comment that will not be rendered into your brew. Hotkey (Ctrl/Cmd + /). -->'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
groupName : 'Style Editor',
|
||||
icon : 'fas fa-pencil-alt',
|
||||
view : 'style',
|
||||
snippets : [
|
||||
{
|
||||
name : 'Remove Drop Cap',
|
||||
icon : 'fas fa-remove-format',
|
||||
gen : '<style>\n' +
|
||||
' .phb h1+p:first-letter {\n' +
|
||||
' all: unset;\n' +
|
||||
' }\n' +
|
||||
'</style>'
|
||||
gen : dedent`/* Removes Drop Caps */
|
||||
.phb h1+p:first-letter {
|
||||
all: unset;
|
||||
}\n\n`
|
||||
},
|
||||
{
|
||||
name : 'Tweak Drop Cap',
|
||||
icon : 'fas fa-sliders-h',
|
||||
gen : '<style>\n' +
|
||||
' /* Drop Cap settings */\n' +
|
||||
' .phb h1 + p::first-letter {\n' +
|
||||
' float: left;\n' +
|
||||
' font-family: Solberry;\n' +
|
||||
' font-size: 10em;\n' +
|
||||
' color: #222;\n' +
|
||||
' line-height: .8em;\n' +
|
||||
' }\n' +
|
||||
'</style>'
|
||||
gen : dedent`/* Drop Cap Settings */
|
||||
.phb h1 + p::first-letter {
|
||||
float: left;
|
||||
font-family: Solberry;
|
||||
font-size: 10em;
|
||||
color: #222;
|
||||
line-height: .8em;
|
||||
}\n\n`
|
||||
},
|
||||
{
|
||||
name : 'Add Comment',
|
||||
icon : 'fas fa-code',
|
||||
gen : `\n<!-- This is a comment that will not be rendered into your brew. Hotkey (Ctrl/Cmd + /). -->\n\n`
|
||||
gen : '/* This is a comment that will not be rendered into your brew. */'
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -240,30 +251,25 @@ module.exports = [
|
||||
{
|
||||
name : 'Split Table',
|
||||
icon : 'fas fa-th-large',
|
||||
gen : function(){
|
||||
return [
|
||||
'<div style=\'column-count:2\'>',
|
||||
'| d10 | Damage Type |',
|
||||
'|:---:|:------------|',
|
||||
'| 1 | Acid |',
|
||||
'| 2 | Cold |',
|
||||
'| 3 | Fire |',
|
||||
'| 4 | Force |',
|
||||
'| 5 | Lightning |',
|
||||
'',
|
||||
'```',
|
||||
'```',
|
||||
'',
|
||||
'| d10 | Damage Type |',
|
||||
'|:---:|:------------|',
|
||||
'| 6 | Necrotic |',
|
||||
'| 7 | Poison |',
|
||||
'| 8 | Psychic |',
|
||||
'| 9 | Radiant |',
|
||||
'| 10 | Thunder |',
|
||||
'</div>\n\n',
|
||||
].join('\n');
|
||||
},
|
||||
gen : dedent`\n
|
||||
<div style='column-count:2'>
|
||||
| d10 | Damage Type |
|
||||
|:---:|:------------|
|
||||
| 1 | Acid |
|
||||
| 2 | Cold |
|
||||
| 3 | Fire |
|
||||
| 4 | Force |
|
||||
| 5 | Lightning |
|
||||
|
||||
| d10 | Damage Type |
|
||||
|:---:|:------------|
|
||||
| 6 | Necrotic |
|
||||
| 7 | Poison |
|
||||
| 8 | Psychic |
|
||||
| 9 | Radiant |
|
||||
| 10 | Thunder |
|
||||
</div>
|
||||
\n`
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -305,7 +311,7 @@ module.exports = [
|
||||
name : 'Ink Friendly',
|
||||
icon : 'fas fa-tint',
|
||||
gen : dedent`
|
||||
/* Ink Friendly */',
|
||||
/* Ink Friendly */
|
||||
.phb, .phb blockquote, .phb hr+blockquote {
|
||||
background : white;
|
||||
box-shadow : 0px 0px 3px;
|
||||
|
||||
@@ -49,6 +49,7 @@ const Homebrew = createClass({
|
||||
<Route path='/print/:id' component={(routeProps)=><PrintPage brew={this.props.brew} query={queryString.parse(routeProps.location.search)} />}/>
|
||||
<Route path='/print' exact component={(routeProps)=><PrintPage query={queryString.parse(routeProps.location.search)} />}/>
|
||||
<Route path='/changelog' exact component={()=><SharePage brew={this.props.brew} />}/>
|
||||
<Route path='/faq' exact component={()=><SharePage brew={this.props.brew} />}/>
|
||||
<Route path='/v3_preview' exact component={()=><HomePage brew={this.props.brew} />}/>
|
||||
<Route path='/' component={()=><HomePage brew={this.props.brew} />}/>
|
||||
</Switch>
|
||||
|
||||
@@ -196,11 +196,14 @@ const EditPage = createClass({
|
||||
|
||||
const transfer = this.state.saveGoogle == _.isNil(this.state.brew.googleId);
|
||||
|
||||
const brew = this.state.brew;
|
||||
brew.pageCount = ((brew.renderer=='legacy' ? brew.text.match(/\\page/g) : brew.text.match(/^\\page$/gm)) || []).length + 1;
|
||||
|
||||
if(this.state.saveGoogle) {
|
||||
if(transfer) {
|
||||
const res = await request
|
||||
.post('/api/newGoogle/')
|
||||
.send(this.state.brew)
|
||||
.send(brew)
|
||||
.catch((err)=>{
|
||||
console.log(err.status === 401
|
||||
? 'Not signed in!'
|
||||
@@ -211,7 +214,7 @@ const EditPage = createClass({
|
||||
if(!res) { return; }
|
||||
|
||||
console.log('Deleting Local Copy');
|
||||
await request.delete(`/api/${this.state.brew.editId}`)
|
||||
await request.delete(`/api/${brew.editId}`)
|
||||
.send()
|
||||
.catch((err)=>{
|
||||
console.log('Error deleting Local Copy');
|
||||
@@ -221,8 +224,8 @@ const EditPage = createClass({
|
||||
history.replaceState(null, null, `/edit/${this.savedBrew.googleId}${this.savedBrew.editId}`); //update URL to match doc ID
|
||||
} else {
|
||||
const res = await request
|
||||
.put(`/api/updateGoogle/${this.state.brew.editId}`)
|
||||
.send(this.state.brew)
|
||||
.put(`/api/updateGoogle/${brew.editId}`)
|
||||
.send(brew)
|
||||
.catch((err)=>{
|
||||
console.log(err.status === 401
|
||||
? 'Not signed in!'
|
||||
@@ -236,14 +239,14 @@ const EditPage = createClass({
|
||||
} else {
|
||||
if(transfer) {
|
||||
const res = await request.post('/api')
|
||||
.send(this.state.brew)
|
||||
.send(brew)
|
||||
.catch((err)=>{
|
||||
console.log('Error creating Local Copy');
|
||||
this.setState({ errors: err });
|
||||
return;
|
||||
});
|
||||
|
||||
await request.get(`/api/removeGoogle/${this.state.brew.googleId}${this.state.brew.editId}`)
|
||||
await request.get(`/api/removeGoogle/${brew.googleId}${brew.editId}`)
|
||||
.send()
|
||||
.catch((err)=>{
|
||||
console.log('Error Deleting Google Brew');
|
||||
@@ -253,8 +256,8 @@ const EditPage = createClass({
|
||||
history.replaceState(null, null, `/edit/${this.savedBrew.editId}`); //update URL to match doc ID
|
||||
} else {
|
||||
const res = await request
|
||||
.put(`/api/update/${this.state.brew.editId}`)
|
||||
.send(this.state.brew)
|
||||
.put(`/api/update/${brew.editId}`)
|
||||
.send(brew)
|
||||
.catch((err)=>{
|
||||
console.log('Error Updating Local Brew');
|
||||
this.setState({ errors: err });
|
||||
|
||||
@@ -4,7 +4,7 @@ Welcome traveler from an antique land. Please sit and tell us of what you have s
|
||||
### Homebrew D&D made easy
|
||||
The Homebrewery makes the creation and sharing of authentic looking Fifth-Edition homebrews easy. It uses [Markdown](https://help.github.com/articles/markdown-basics/) with a little CSS magic to make your brews come to life.
|
||||
|
||||
**Try it! **Simply edit the text on the left and watch it *update live* on the right.
|
||||
**Try it!** Simply edit the text on the left and watch it *update live* on the right.
|
||||
|
||||
|
||||
|
||||
@@ -36,8 +36,10 @@ This tool will **always** be free, never have ads, and I will never offer any "p
|
||||
```
|
||||
```
|
||||
|
||||
## Big things coming in v3.0.0
|
||||
With the next major release of Homebrewery, v3.0.0, this tool *will no longer support raw HTML input for brew code*. All brews made previous to the release of v3.0.0 will still render normally.
|
||||
## V3.0.0 Released!
|
||||
With the latest major update to *The Homebrewery* we've implemented an extended Markdown-like syntax for block and span elements, plus a few other changes, eliminating the need for HTML tags like **div** and **span** in most cases. No raw HTML tags should be needed in a brew, and going forward, raw HTML will no longer receive debugging support (*but can still be used if you insist*).
|
||||
|
||||
**You can enable V3 via the <span class="fa fa-info-circle" style="text-indent:0"></span> Properties button!**
|
||||
|
||||
## New Things All The Time!
|
||||
What's new in the latest update? Check out the full changelog [here](/changelog)
|
||||
@@ -46,9 +48,9 @@ What's new in the latest update? Check out the full changelog [here](/changelog)
|
||||
Have an idea of how to make The Homebrewery better? Or did you find something that wasn't quite right? Head [here](https://www.reddit.com/r/homebrewery/submit?selftext=true&title=%5BIssue%5D%20Describe%20Your%20Issue%20Here) and let me know!.
|
||||
|
||||
### Legal Junk
|
||||
The Homebrewery is licensed using the [MIT License](https://github.com/naturalcrit/homebrewery/blob/master/license). Which means you are free to use The Homebrewery is any way that you want, except for claiming that you made it yourself.
|
||||
The Homebrewery is licensed using the [MIT License](https://github.com/naturalcrit/homebrewery/blob/master/license). This means you are free to use The Homebrewery codebase any way that you want, except for claiming that you made it yourself.
|
||||
|
||||
If you wish to sell or in some way gain profit for what's created on this site, it's your responsibility to ensure you have the proper licenses/rights for any images or resources used.
|
||||
If you wish to sell or in some way gain profit for what you make on this site, it's your responsibility to ensure you have the proper licenses/rights for any images or resources used.
|
||||
|
||||
### More Resources
|
||||
If you are looking for more 5e Homebrew resources check out [r/UnearthedArcana](https://www.reddit.com/r/UnearthedArcana/) and their list of useful resources [here](https://www.reddit.com/r/UnearthedArcana/comments/3uwxx9/resources_open_to_the_community/).
|
||||
|
||||
@@ -4,10 +4,8 @@
|
||||
}
|
||||
|
||||
.page {
|
||||
padding-bottom : 1.6cm;
|
||||
padding-bottom : 1.1cm;
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
# The Homebrewery *V3*
|
||||
@@ -25,26 +23,31 @@ Any changes you make while on the *edit url* will be automatically saved to the
|
||||
|
||||
Anyone with the *share url* will be able to access a read-only version of your homebrew.
|
||||
|
||||
### PDF Creation
|
||||
{{note
|
||||
##### PDF Creation
|
||||
PDF Printing works best in Google Chrome. If you are having quality/consistency issues, try using Chrome to print instead.
|
||||
|
||||
After clicking the "Print" item in the navbar a new page will open and a print dialog will pop-up.
|
||||
* Set the **Destination** to "Save as PDF"
|
||||
* Set **Paper Size** to "Letter"
|
||||
* If you are printing on A4 paper, make sure to have the {{far,fa-file}} **A4 Pagesize** snippet in your brew
|
||||
* If you are printing on A4 paper, make sure to have the **PRINT → {{far,fa-file}} A4 Pagesize** snippet in your brew
|
||||
* In **Options** make sure "Background Images" is selected.
|
||||
* Hit print and enjoy! You're done!
|
||||
|
||||
If you want to save ink or have a monochrome printer, add the {{fas,fa-tint}} **Ink Friendly** snippet to your brew before you print
|
||||
If you want to save ink or have a monochrome printer, add the **PRINT → {{fas,fa-tint}} Ink Friendly** snippet to your brew before you print
|
||||
}}
|
||||
|
||||
<img src='https://i.imgur.com/hMna6G0.png' style='position:absolute;bottom:50px;left:120px;width:180px' />
|
||||
|
||||
<div class='pageNumber'>1</div>
|
||||
<div class='footnote'>PART 1 | FANCINESS</div>
|
||||
|
||||
\column
|
||||
|
||||
## New in V3.0.0
|
||||
With the latest major update to *The Homebrewery* we've implemented an extended Markdown-like syntax for block and span elements, plus a few other changes, eliminating the need for HTML tags like `div` and `span` in most cases. No raw HTML tags should be needed in a brew, and going forward, raw HTML will no longer receive debugging support (*but can still be used if you insist*).
|
||||
|
||||
All brews made prior to the release of v3.0.0 will still render normally, and you may switch between the "legacy" brew renderer and the newer "V3" renderer via the {{fa,fa-info-circle}} **Properties** button on your brew. Much of the syntax and styling has changed in V3, so code in one version may be broken in the other.
|
||||
Much of the syntax and styling has changed in V3. Code in one version may be broken in the other, and updating an older brew to V3 will require more than just a copy and paste. *However*, all brews made prior to the release of v3.0.0 will still render normally, and you may switch between the "Legacy" brew renderer and the newer "V3" renderer via the {{fa,fa-info-circle}} **Properties** button on your brew at any time.
|
||||
|
||||
Scroll down to the next page for a brief summary of the changes and new features available in V3!
|
||||
|
||||
@@ -61,29 +64,18 @@ Need help getting started or just the right look for your brew? Head to [r/Homeb
|
||||
|
||||
Have an idea to make The Homebrewery better? Or did you find something that wasn't quite right? Check out the [GitHub Repo](https://github.com/naturalcrit/homebrewery/) to report technical issues.
|
||||
|
||||
|
||||
|
||||
### Legal Junk
|
||||
The Homebrewery is licensed using the [MIT License](https://github.com/naturalcrit/homebrewery/blob/master/license). Which means you are free to use The Homebrewery codebase any way that you want, except for claiming that you made it yourself.
|
||||
|
||||
If you wish to sell or in some way gain profit for what's created on this site, it's your responsibility to ensure you have the proper licenses/rights for any images or resources used.
|
||||
|
||||
#### Crediting Me
|
||||
If you'd like to credit The Homebrewery in your brew, I'd be flattered! Just reference that you made it with The Homebrewery.
|
||||
If you'd like to credit me in your brew, I'd be flattered! Just reference that you made it with The Homebrewery.
|
||||
|
||||
### More Resources
|
||||
If you are looking for more 5e Homebrew resources check out [r/UnearthedArcana](https://www.reddit.com/r/UnearthedArcana/) and their list of useful resources [here](https://www.reddit.com/r/UnearthedArcana/comments/3uwxx9/resources_open_to_the_community/).
|
||||
|
||||
|
||||
|
||||
<img src='https://i.imgur.com/hMna6G0.png' style='position:absolute;bottom:50px;left:120px;width:180px' />
|
||||
|
||||
<div class='pageNumber'>1</div>
|
||||
<div class='footnote'>PART 1 | FANCINESS</div>
|
||||
|
||||
|
||||
|
||||
|
||||
\page
|
||||
|
||||
## Markdown+
|
||||
@@ -145,9 +137,9 @@ Row spanning is achieved by adding a `^` at the end of a cell just before the `|
|
||||
These can be combined to span a cell across both columns and rows. Cells must have the same colspan if they are to be rowspan'd.
|
||||
|
||||
##### Example
|
||||
| | Spanned Header ||
|
||||
| Head A | Head B | Head C |
|
||||
|:-------|:-------|:-------|
|
||||
| Head A | Spanned Header ||
|
||||
| Head B | Head C | Head D |
|
||||
|:-------|:------:|:------:|
|
||||
| 1A | 1B | 1C |
|
||||
| 2A ^| 2B | 2C |
|
||||
| 3A ^| 3B 3C ||
|
||||
@@ -155,7 +147,6 @@ These can be combined to span a cell across both columns and rows. Cells must ha
|
||||
| 5A ^| 5B | 5C |
|
||||
| 6A | 6B ^| 6C |
|
||||
|
||||
|
||||
## Images
|
||||
Images must be hosted online somewhere, like [Imgur](https://www.imgur.com). You use the address to that image to reference it in your brew\*. Images can be included using Markdown-style images.
|
||||
|
||||
|
||||
@@ -161,6 +161,8 @@ const NewPage = createClass({
|
||||
brew.text = brew.text.slice(index + 5);
|
||||
};
|
||||
|
||||
brew.pageCount=((brew.renderer=='legacy' ? brew.text.match(/\\page/g) : brew.text.match(/^\\page$/gm)) || []).length + 1;
|
||||
|
||||
if(this.state.saveGoogle) {
|
||||
const res = await request
|
||||
.post('/api/newGoogle/')
|
||||
|
||||
@@ -35,22 +35,28 @@ const PrintPage = createClass({
|
||||
if(this.props.query.dialog) window.print();
|
||||
},
|
||||
|
||||
renderStyle : function() {
|
||||
if(!this.props.brew.style) return;
|
||||
return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style> ${this.props.brew.style} </style>` }} />;
|
||||
},
|
||||
|
||||
renderPages : function(){
|
||||
if(this.props.brew.renderer == 'legacy') {
|
||||
return _.map(this.state.brewText.split('\\page'), (page, index)=>{
|
||||
return _.map(this.state.brewText.split('\\page'), (pageText, index)=>{
|
||||
return <div
|
||||
className='phb page'
|
||||
id={`p${index + 1}`}
|
||||
dangerouslySetInnerHTML={{ __html: MarkdownLegacy.render(page) }}
|
||||
dangerouslySetInnerHTML={{ __html: MarkdownLegacy.render(pageText) }}
|
||||
key={index} />;
|
||||
});
|
||||
} else {
|
||||
return _.map(this.state.brewText.split(/^\\page/gm), (page, index)=>{
|
||||
return <div
|
||||
className='phb3 page'
|
||||
id={`p${index + 1}`}
|
||||
dangerouslySetInnerHTML={{ __html: Markdown.render(page) }}
|
||||
key={index} />;
|
||||
return _.map(this.state.brewText.split(/^\\page$/gm), (pageText, index)=>{
|
||||
pageText += `\n\n \n\\column\n `; //Artificial column break at page end to emulate column-fill:auto (until `wide` is used, when column-fill:balance will reappear)
|
||||
return (
|
||||
<div className='page' id={`p${index + 1}`} key={index} >
|
||||
<div className='columnWrapper' dangerouslySetInnerHTML={{ __html: Markdown.render(pageText) }} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -61,8 +67,10 @@ const PrintPage = createClass({
|
||||
<Meta name='robots' content='noindex, nofollow' />
|
||||
<link href={`${this.props.brew.renderer == 'legacy' ? '/themes/5ePhbLegacy.style.css' : '/themes/5ePhb.style.css'}`} rel='stylesheet'/>
|
||||
{/* Apply CSS from Style tab */}
|
||||
<div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style> ${this.props.brew.style} </style>` }} />
|
||||
{this.renderPages()}
|
||||
{this.renderStyle()}
|
||||
<div className='pages' ref='pages'>
|
||||
{this.renderPages()}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ const moment = require('moment');
|
||||
const request = require('superagent');
|
||||
|
||||
const googleDriveIcon = require('../../../googleDrive.png');
|
||||
const dedent = require('dedent-tabs').default;
|
||||
|
||||
const BrewItem = createClass({
|
||||
getDefaultProps : function() {
|
||||
@@ -104,24 +105,28 @@ const BrewItem = createClass({
|
||||
const dateFormatString = 'YYYY-MM-DD HH:mm:ss';
|
||||
|
||||
return <div className='brewItem'>
|
||||
<h2>{brew.title}</h2>
|
||||
<p className='description'>{brew.description}</p>
|
||||
<div className='text'>
|
||||
<h2>{brew.title}</h2>
|
||||
<p className='description'>{brew.description}</p>
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
<div className='info'>
|
||||
<span title={`Authors:\n${brew.authors.join('\n')}`}>
|
||||
<i className='fas fa-user'/> {brew.authors.join(', ')}
|
||||
</span>
|
||||
<br />
|
||||
<span title={`Last viewed: ${moment(brew.lastViewed).local().format(dateFormatString)}`}>
|
||||
<i className='fas fa-eye'/> {brew.views}
|
||||
</span>
|
||||
<span
|
||||
title={
|
||||
`Created: ${brew.createdAt ? moment(brew.createdAt).local().format(dateFormatString) : 'UNKNOWN'}\n` +
|
||||
`Last updated: ${brew.updatedAt ? moment(brew.updatedAt).local().format(dateFormatString) : 'UNKNOWN'}`
|
||||
}>
|
||||
<i className='fas fa-sync-alt' />
|
||||
{moment(brew.updatedAt).fromNow()}
|
||||
{brew.pageCount &&
|
||||
<span title={`Page count: ${brew.pageCount}`}>
|
||||
<i className='far fa-file' /> {brew.pageCount}
|
||||
</span>
|
||||
}
|
||||
<span title={dedent`
|
||||
Created: ${moment(brew.createdAt).local().format(dateFormatString)}
|
||||
Last updated: ${moment(brew.updatedAt).local().format(dateFormatString)}`}>
|
||||
<i className='fas fa-sync-alt' /> {moment(brew.updatedAt).fromNow()}
|
||||
</span>
|
||||
{this.renderGoogleDriveIcon()}
|
||||
</div>
|
||||
|
||||
@@ -10,25 +10,28 @@
|
||||
min-height : 105px;
|
||||
margin-right : 15px;
|
||||
margin-bottom : 15px;
|
||||
padding : 5px 15px 5px 8px;
|
||||
padding : 5px 15px 2px 8px;
|
||||
padding-right : 15px;
|
||||
border : 1px solid #c9ad6a;
|
||||
border-radius : 5px;
|
||||
-webkit-column-break-inside : avoid;
|
||||
page-break-inside : avoid;
|
||||
break-inside : avoid;
|
||||
h4{
|
||||
margin-bottom : 5px;
|
||||
font-size : 2.2em;
|
||||
.text {
|
||||
min-height : 54px;
|
||||
h4{
|
||||
margin-bottom : 5px;
|
||||
font-size : 2.2em;
|
||||
}
|
||||
}
|
||||
.info{
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
margin-bottom: 4px;
|
||||
position: initial;
|
||||
bottom: 2px;
|
||||
font-family : ScalySans;
|
||||
font-size : 1.2em;
|
||||
&>span{
|
||||
margin-right : 12px;
|
||||
line-height : 1.5em;
|
||||
}
|
||||
}
|
||||
&:hover{
|
||||
|
||||
@@ -31,8 +31,9 @@ const UserPage = createClass({
|
||||
},
|
||||
getInitialState : function() {
|
||||
return {
|
||||
sortType : 'alpha',
|
||||
sortDir : 'asc'
|
||||
sortType : 'alpha',
|
||||
sortDir : 'asc',
|
||||
filterString : ''
|
||||
};
|
||||
},
|
||||
getUsernameWithS : function() {
|
||||
@@ -44,7 +45,7 @@ const UserPage = createClass({
|
||||
renderBrews : function(brews){
|
||||
if(!brews || !brews.length) return <div className='noBrews'>No Brews.</div>;
|
||||
|
||||
const sortedBrews = this.sortBrews(brews, this.state.sortType);
|
||||
const sortedBrews = this.sortBrews(brews);
|
||||
|
||||
return _.map(sortedBrews, (brew, idx)=>{
|
||||
return <BrewItem brew={brew} key={idx}/>;
|
||||
@@ -52,6 +53,7 @@ const UserPage = createClass({
|
||||
},
|
||||
|
||||
sortBrewOrder : function(brew){
|
||||
if(!brew.title){brew.title = 'No Title';}
|
||||
const mapping = {
|
||||
'alpha' : _.deburr(brew.title.toLowerCase()),
|
||||
'created' : moment(brew.createdAt).format(),
|
||||
@@ -90,6 +92,26 @@ const UserPage = createClass({
|
||||
</td>;
|
||||
},
|
||||
|
||||
handleFilterTextChange : function(e){
|
||||
this.setState({
|
||||
filterString : e.target.value
|
||||
});
|
||||
return;
|
||||
},
|
||||
|
||||
renderFilterOption : function(){
|
||||
return <td>
|
||||
<label>
|
||||
<i className='fas fa-search'></i>
|
||||
<input
|
||||
type='search'
|
||||
placeholder='search title/description'
|
||||
onChange={this.handleFilterTextChange}
|
||||
/>
|
||||
</label>
|
||||
</td>;
|
||||
},
|
||||
|
||||
renderSortOptions : function(){
|
||||
return <div className='sort-container'>
|
||||
<table>
|
||||
@@ -114,6 +136,7 @@ const UserPage = createClass({
|
||||
{`${(this.state.sortDir == 'asc' ? '\u25B2 ASC' : '\u25BC DESC')}`}
|
||||
</button>
|
||||
</td>
|
||||
{this.renderFilterOption()}
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -121,7 +144,12 @@ const UserPage = createClass({
|
||||
},
|
||||
|
||||
getSortedBrews : function(){
|
||||
return _.groupBy(this.props.brews, (brew)=>{
|
||||
const testString = _.deburr(this.state.filterString).toLowerCase();
|
||||
const brewCollection = this.state.filterString ? _.filter(this.props.brews, (brew)=>{
|
||||
return (_.deburr(brew.title).toLowerCase().includes(testString)) ||
|
||||
(_.deburr(brew.description).toLowerCase().includes(testString));
|
||||
}) : this.props.brews;
|
||||
return _.groupBy(brewCollection, (brew)=>{
|
||||
return (brew.published ? 'published' : 'private');
|
||||
});
|
||||
},
|
||||
|
||||
@@ -34,8 +34,9 @@
|
||||
font-family : 'Open Sans', sans-serif;
|
||||
position : fixed;
|
||||
top : 35px;
|
||||
left : calc(50vw - 408px);
|
||||
border : 2px solid #58180D;
|
||||
width : 675px;
|
||||
width : 800px;
|
||||
background-color : #EEE5CE;
|
||||
padding : 2px;
|
||||
text-align : center;
|
||||
@@ -52,6 +53,9 @@
|
||||
vertical-align : middle;
|
||||
tbody tr{
|
||||
background-color: transparent !important;
|
||||
i{
|
||||
padding-right : 5px
|
||||
}
|
||||
button{
|
||||
background-color : transparent;
|
||||
color : #58180D;
|
||||
|
||||
Reference in New Issue
Block a user