0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2025-12-24 20:42:43 +00:00

Merge branch 'naturalcrit:master' into Legacy-SpellList-Update

This commit is contained in:
Gazook89
2021-08-01 11:07:16 -05:00
committed by GitHub
13 changed files with 147 additions and 97 deletions

View File

@@ -6,6 +6,19 @@ h5 {
# changelog
### Friday, 30/07/2021 - v2.13.2
- Background work to allow new themes in the future
- Fixed cursor getting stuck when resizing divider bar
##### G-Ambatte :
- Fix Style tab not copying when Cloned To New
- Basic brew sorting on User page
- Reduced data sent on each request from server
##### Gazook89 :
- Cleaned up styling on menus
### Saturday, 28/6/2021 - v2.13.1
- Fixed the issue with new brews not saving!
@@ -44,6 +57,9 @@ myStyle {color: black}
##### G-Ambatte :
- Snippet to remove drop caps (fancy first letter after title)
```
```
### Saturday, 13/3/2021 - v2.11.0
- Many background things for upcoming v3. Get pumped.
@@ -87,6 +103,8 @@ myStyle {color: black}
- Fixed issue with users unable to create new brews
- Fixing brews being lost when loaded via back button
\page
### Wednesday, 07/10/2020 - v2.10.0
- Google Drive integration -- Sign in 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!
@@ -100,13 +118,6 @@ myStyle {color: black}
### Wednesday, 20/05/2020 - v2.9.0
- Major refactoring of site backend to work with updated dependencies for security (should be invisible to users)
\page
### Wednesday, 11/03/2020 - v2.8.2
- Fixed delete button removing everyone's copy for brews with multiple authors
- Compressed homebrew text in database
@@ -180,6 +191,8 @@ myStyle {color: black}
- Added a hover tooltip to fully read the brew description
- Made the brew items take up only 25% allowing you to view more per row.
\page
### Wednesday, 23/11/2016 - v2.5.0
- Metadata can now be added to brews
- Added a metadata editor onto the edit and new pages
@@ -195,8 +208,6 @@ myStyle {color: black}
- You can now print from a new page without saving
- Added the ability to use ctrl+p and ctrl+s to print and save respectively.
\page
### Monday, 07/11/2016
- Added final touches to the html validator and updating the rest of the branch
- If anyone finds issues with the new HTML validator, please let me know. I hope this will bring a more consistent feel to Homebrewery rendering.
@@ -253,6 +264,8 @@ myStyle {color: black}
### Wednesday, 25/05/2016 -v2.0.5
- The class table generators have the proper ability score improvement progression.
\page
### Tuesday, 24/05/2016 - v2.0.4
- Fixed extra wide monster stat blocks sometimes only being one column
- The class table generators now follow the proper progression from the PHB (thakns u/IrishBandit)
@@ -263,8 +276,6 @@ myStyle {color: black}
- Bumped up the allowed entity size for extra-large brew (Thanks for reporting it dickboner93)
- Added a little error box when a save fails with a custom link to reporting the issue on github.
\page
### Saturday, 14/05/2016 - v2.0.0 (finally!)
I've been working on v2 for a *very* long time. I want to thank you guys for being paitent.

View File

@@ -122,6 +122,11 @@ const BrewRenderer = createClass({
</div>;
},
renderStyle : function() {
if(!this.props.style) return;
return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style> ${this.props.style} </style>` }} />;
},
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} />;
@@ -191,14 +196,14 @@ const BrewRenderer = createClass({
</div>
<div className='pages' ref='pages'>
{/* Apply CSS from Style tab */}
<div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style> ${this.props.style} </style>` }} />
{/* Render pages from Markdown tab */}
{/* Apply CSS from Style tab and render pages from Markdown tab */}
{this.state.isMounted
? this.renderPages()
: null}
&&
<>
{this.renderStyle()}
{this.renderPages()}
</>
}
</div>
</div>
</Frame>

View File

@@ -131,7 +131,7 @@ const Editor = createClass({
// Highlight inline spans {{content}}
if(line.includes('{{') && line.includes('}}')){
const regex = /{{(?:="[\w,\-. ]*"|[^"'\s])*\s*|}}/g;
const regex = /{{(?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])*\s*|}}/g;
let match;
let blockCount = 0;
while ((match = regex.exec(line)) != null) {
@@ -150,7 +150,7 @@ const Editor = createClass({
// Highlight block divs {{\n Content \n}}
let endCh = line.length+1;
const match = line.match(/^ *{{(?:="[\w,\-. ]*"|[^"'\s])*$|^ *}}$/);
const match = line.match(/^ *{{(?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])* *$|^ *}}$/);
if(match)
endCh = match.index+match[0].length;
codeMirror.markText({ line: lineNumber, ch: 0 }, { line: lineNumber, ch: endCh }, { className: 'block' });
@@ -170,7 +170,7 @@ const Editor = createClass({
//Called when there are changes to the editor's dimensions
update : function(){
this.refs.codeEditor.updateSize();
this.refs.codeEditor?.updateSize();
},
renderEditor : function(){

View File

@@ -18,10 +18,11 @@
font-weight : 800;
line-height : 1.8em;
text-transform : uppercase;
flex-grow : 0;
flex : 0 0 auto;
}
&>.value{
flex-grow : 1;
flex : 1 1 auto;
min-width : 200px;
}
}
.description.field textarea.value{
@@ -38,15 +39,22 @@
font-size : 0.7em;
font-weight : 800;
user-select : none;
white-space : nowrap;
display : inline-flex;
align-items : center;
}
input{
vertical-align : middle;
cursor : pointer;
margin : 3px;
}
}
.publish.field .value{
position : relative;
margin-bottom: 15px;
button{
width:100%;
}
button.publish{
.button(@blueLight);
}
@@ -76,4 +84,4 @@
font-size: 0.8em;
line-height : 1.5em;
}
}
}

View File

@@ -33,7 +33,7 @@ module.exports = [
{
name : 'Horizontal Spacing',
icon : 'fas fa-arrows-alt-h',
gen : ' {{width="100px"}} '
gen : ' {{width:100px}} '
},
{
name : 'Wide Block',
@@ -51,35 +51,34 @@ module.exports = [
name : 'Image',
icon : 'fas fa-image',
gen : dedent`
![cat warrior](https://s-media-cache-ak0.pinimg.com/736x/4a/81/79/4a8179462cfdf39054a418efd4cb743e.jpg) {width="325px"}
![cat warrior](https://s-media-cache-ak0.pinimg.com/736x/4a/81/79/4a8179462cfdf39054a418efd4cb743e.jpg) {width:325px}
Credit: Kyounghwan Kim`
},
{
name : 'Background Image',
icon : 'fas fa-tree',
gen : `![homebrew mug](http://i.imgur.com/hMna6G0.png) {position="absolute",top="50px",right="30px",width="280px"}`
gen : `![homebrew mug](http://i.imgur.com/hMna6G0.png) {position:absolute,top:50px,right:30px,width:280px}`
},
{
name : 'QR Code',
icon : 'fas fa-qrcode',
gen : (brew)=>{
return `<img ` +
`src='https://api.qrserver.com/v1/create-qr-code/?data=` +
return `![]` +
`(https://api.qrserver.com/v1/create-qr-code/?data=` +
`https://homebrewery.naturalcrit.com/share/${brew.shareId}` +
`&amp;size=100x100' ` +
`style='width:100px;mix-blend-mode:multiply'/>`;
`&amp;size=100x100) {width:100px;mix-blend-mode:multiply}`;
}
},
{
name : 'Page Number',
icon : 'fas fa-bookmark',
gen : '{{pageNumber\n1\n}}\n{{footnote\nPART 1 | FANCINESS\n}}\n\n'
gen : '{{pageNumber 1}}\n{{footnote PART 1 | SECTION NAME}}\n\n'
},
{
name : 'Auto-incrementing Page Number',
icon : 'fas fa-sort-numeric-down',
gen : '{{pageNumber,auto\n}}\n\n'
gen : '{{pageNumber,auto}}\n{{footnote PART 1 | SECTION NAME}}\n\n'
},
{
name : 'Link to page',
@@ -257,7 +256,7 @@ module.exports = [
gen : function(){
return dedent`
##### Typical Difficulty Classes
{{column-count="2"
{{column-count:2
| Task Difficulty | DC |
|:----------------|:--:|
| Very easy | 5 |

View File

@@ -44,12 +44,12 @@ const Homebrew = createClass({
<Route path='/edit/:id' component={(routeProps)=><EditPage id={routeProps.match.params.id} brew={this.props.brew} />}/>
<Route path='/share/:id' component={(routeProps)=><SharePage id={routeProps.match.params.id} brew={this.props.brew} />}/>
<Route path='/new/:id' component={(routeProps)=><NewPage id={routeProps.match.params.id} brew={this.props.brew} />}/>
<Route path='/new' exact component={(routeProps)=><NewPage/>}/>
<Route path='/new' exact component={(routeProps)=><NewPage />}/>
<Route path='/user/:username' component={(routeProps)=><UserPage username={routeProps.match.params.username} brews={this.props.brews} />}/>
<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={{ title: 'Changelog', text: this.props.changelog }} />}/>
<Route path='/' component={()=><HomePage welcomeText={this.props.welcomeText}/>}/>
<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='/' component={()=><HomePage brew={this.props.brew} />}/>
</Switch>
</div>
</Router>

View File

@@ -23,19 +23,15 @@ const HomePage = createClass({
getDefaultProps : function() {
return {
brew : {
text : ''
text : '',
},
welcomeText : '',
ver : '0.0.0'
ver : '0.0.0'
};
},
getInitialState : function() {
return {
brew : {
text : this.props.welcomeText
}
brew : this.props.brew,
welcomeText : this.props.brew.text
};
},
handleSave : function(){
@@ -89,7 +85,7 @@ const HomePage = createClass({
</SplitPane>
</div>
<div className={cx('floatingSaveButton', { show: this.props.welcomeText != this.state.brew.text })} onClick={this.handleSave}>
<div className={cx('floatingSaveButton', { show: this.state.welcomeText != this.state.brew.text })} onClick={this.handleSave}>
Save current <i className='fas fa-save' />
</div>

View File

@@ -69,16 +69,15 @@ const NewPage = createClass({
const brewStorage = localStorage.getItem(BREWKEY);
const styleStorage = localStorage.getItem(STYLEKEY);
const brew = this.state.brew;
if(!this.props.brew.text || !this.props.brew.style){
this.setState({
brew : {
text : this.props.brew.text || (brewStorage ?? ''),
style : this.props.brew.style || (styleStorage ?? undefined)
}
});
brew.text = this.props.brew.text || (brewStorage ?? '');
brew.style = this.props.brew.style || (styleStorage ?? undefined);
}
this.setState((prevState)=>({
brew : brew,
htmlErrors : Markdown.validate(prevState.brew.text)
}));

20
package-lock.json generated
View File

@@ -1,11 +1,11 @@
{
"name": "homebrewery",
"version": "2.13.1",
"version": "2.13.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "2.13.1",
"version": "2.13.2",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -30,7 +30,7 @@
"marked": "2.1.3",
"markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.29.1",
"mongoose": "^5.13.3",
"mongoose": "^5.13.4",
"nanoid": "3.1.23",
"nconf": "^0.11.3",
"prop-types": "15.7.2",
@@ -6376,9 +6376,9 @@
}
},
"node_modules/mongoose": {
"version": "5.13.3",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.13.3.tgz",
"integrity": "sha512-q+zX6kqHAvwxf5speMWhq6qF4vdj+x6/kfD5RSKdZKNm52yGmaUygN+zgrtQjBZPFEzG0B3vF6GP0PoAGadE+w==",
"version": "5.13.4",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.13.4.tgz",
"integrity": "sha512-D1yVHAOa+G8iQZsC/nNzZe+CI1FCYu6Qk384s1vSyaSfKCu/alKeyL78BA73SsxeRKT9zmswSIueLbGBURjrKg==",
"dependencies": {
"@types/mongodb": "^3.5.27",
"@types/node": "14.x || 15.x",
@@ -6389,6 +6389,7 @@
"mpath": "0.8.3",
"mquery": "3.2.5",
"ms": "2.1.2",
"optional-require": "1.0.x",
"regexp-clone": "1.0.0",
"safe-buffer": "5.2.1",
"sift": "13.5.2",
@@ -14455,9 +14456,9 @@
}
},
"mongoose": {
"version": "5.13.3",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.13.3.tgz",
"integrity": "sha512-q+zX6kqHAvwxf5speMWhq6qF4vdj+x6/kfD5RSKdZKNm52yGmaUygN+zgrtQjBZPFEzG0B3vF6GP0PoAGadE+w==",
"version": "5.13.4",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.13.4.tgz",
"integrity": "sha512-D1yVHAOa+G8iQZsC/nNzZe+CI1FCYu6Qk384s1vSyaSfKCu/alKeyL78BA73SsxeRKT9zmswSIueLbGBURjrKg==",
"requires": {
"@types/mongodb": "^3.5.27",
"@types/node": "14.x || 15.x",
@@ -14468,6 +14469,7 @@
"mpath": "0.8.3",
"mquery": "3.2.5",
"ms": "2.1.2",
"optional-require": "1.0.x",
"regexp-clone": "1.0.0",
"safe-buffer": "5.2.1",
"sift": "13.5.2",

View File

@@ -1,7 +1,7 @@
{
"name": "homebrewery",
"description": "Create authentic looking D&D homebrews using only markdown",
"version": "2.13.1",
"version": "2.13.2",
"engines": {
"node": "14.15.x"
},
@@ -61,7 +61,7 @@
"marked": "2.1.3",
"markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.29.1",
"mongoose": "^5.13.3",
"mongoose": "^5.13.4",
"nanoid": "3.1.23",
"nconf": "^0.11.3",
"prop-types": "15.7.2",

View File

@@ -105,6 +105,25 @@ app.get('/robots.txt', (req, res)=>{
return res.sendFile(`${__dirname}/robots.txt`);
});
//Home page
app.get('/', async (req, res, next)=>{
const brew = {
text : welcomeText
};
req.brew = brew;
return next();
});
//Changelog page
app.get('/changelog', async (req, res, next)=>{
const brew = {
title : 'Changelog',
text : changelogText
};
req.brew = brew;
return next();
});
//Source page
app.get('/source/:id', asyncHandler(async (req, res)=>{
const brew = await getBrewFromId(req.params.id, 'raw');
@@ -170,6 +189,7 @@ app.get('/edit/:id', asyncHandler(async (req, res, next)=>{
//New Page
app.get('/new/:id', asyncHandler(async (req, res, next)=>{
const brew = await getBrewFromId(req.params.id, 'share');
brew.title = `CLONE - ${brew.title}`;
req.brew = brew;
return next();
}));
@@ -204,8 +224,6 @@ app.use((req, res)=>{
const props = {
version : require('./package.json').version,
url : req.originalUrl,
welcomeText : welcomeText,
changelog : changelogText,
brew : req.brew,
brews : req.brews,
googleBrews : req.googleBrews,

View File

@@ -31,30 +31,34 @@ const mustacheSpans = {
start(src) { return src.match(/{{[^{]/)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const completeSpan = /^{{[^\n]*}}/; // Regex for the complete token
const inlineRegex = /{{(?:="[\w,\-(). ]*"|[^"'{}\s])*\s*|}}/g;
const inlineRegex = /{{(?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])*\s*|}}/g;
const match = completeSpan.exec(src);
if(match) {
//Find closing delimiter
let blockCount = 0;
let tags = '';
let endIndex = 0;
let endTags = 0;
let endToken = 0;
let delim;
while (delim = inlineRegex.exec(match[0])) {
if(!tags) {
tags = ` ${processStyleTags(delim[0].substring(2))}`;
endTags = delim[0].length;
}
if(delim[0].startsWith('{{')) {
tags = tags || ` ${processStyleTags(delim[0].substring(2))}`;
blockCount++;
} else if(delim[0] == '}}' && blockCount !== 0) {
blockCount--;
if(blockCount == 0) {
endIndex = inlineRegex.lastIndex;
endToken = inlineRegex.lastIndex;
break;
}
}
}
if(endIndex) {
const raw = src.slice(0, endIndex);
const text = raw.slice((raw.indexOf(' ')+1) || -2, -2);
if(endToken) {
const raw = src.slice(0, endToken);
const text = raw.slice(endTags || -2, -2);
return { // Token to generate
type : 'mustacheSpans', // Should match "name" above
@@ -74,33 +78,37 @@ const mustacheSpans = {
const mustacheDivs = {
name : 'mustacheDivs',
level : 'block',
start(src) { return src.match(/^ *{{[^{]/m)?.index; }, // Hint to Marked.js to stop and check for a match
start(src) { return src.match(/\n *{{[^{]/m)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const completeBlock = /^ *{{.*\n *}}/s; // Regex for the complete token
const blockRegex = /^ *{{(?:="[\w,\-(). ]*"|[^"'{}\s])*$|^ *}}$/gm;
const completeBlock = /^ *{{[^\n}]* *\n.*\n *}}/s; // Regex for the complete token
const blockRegex = /^ *{{(?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])* *$|^ *}}$/gm;
const match = completeBlock.exec(src);
if(match) {
//Find closing delimiter
let blockCount = 0;
let tags = '';
let endIndex = 0;
let endTags = 0;
let endToken = 0;
let delim;
while (delim = blockRegex.exec(match[0])?.[0].trim()) {
if(!tags) {
tags = ` ${processStyleTags(delim.substring(2))}`;
endTags = delim.length;
}
if(delim.startsWith('{{')) {
tags = tags || ` ${processStyleTags(delim.substring(2))}`;
blockCount++;
} else if(delim == '}}' && blockCount !== 0) {
blockCount--;
if(blockCount == 0) {
endIndex = blockRegex.lastIndex;
endToken = blockRegex.lastIndex;
break;
}
}
}
if(endIndex) {
const raw = src.slice(0, endIndex);
const text = raw.slice((raw.indexOf('\n')+1) || -2, -2);
if(endToken) {
const raw = src.slice(0, endToken);
const text = raw.slice(endTags || -2, -2);
return { // Token to generate
type : 'mustacheDivs', // Should match "name" above
raw : raw, // Text to consume from the source
@@ -121,7 +129,7 @@ const mustacheInjectInline = {
level : 'inline',
start(src) { return src.match(/ *{[^{\n]/)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const inlineRegex = /^ *{((?:="[\w,\-(). ]*"|[^"'{}\s])*)}/g;
const inlineRegex = /^ *{((?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])*)}/g;
const match = inlineRegex.exec(src);
if(match) {
const lastToken = tokens[tokens.length - 1];
@@ -154,9 +162,9 @@ const mustacheInjectBlock = {
extensions : [{
name : 'mustacheInjectBlock',
level : 'block',
start(src) { return src.match(/^ *{[^{\n]/m)?.index; }, // Hint to Marked.js to stop and check for a match
start(src) { return src.match(/\n *{[^{\n]/m)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const inlineRegex = /^ *{((?:="[\w,\-(). ]*"|[^"'{}\s])*)}/ym;
const inlineRegex = /^ *{((?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])*)}/ym;
const match = inlineRegex.exec(src);
if(match) {
const lastToken = tokens[tokens.length - 1];
@@ -313,13 +321,15 @@ const tagRegex = new RegExp(`(${
}).join('|')})`, 'g');
const processStyleTags = (string)=>{
const tags = string.match(/(?:[^, "=]+|="[^"]*")+/g);
//split tags up. quotes can only occur right after colons.
//TODO: can we simplify to just split on commas?
const tags = string.match(/(?:[^, ":]+|:(?:"[^"]*"|))+/g);
if(!tags) return '"';
const id = _.remove(tags, (tag)=>tag.startsWith('#')).map((tag)=>tag.slice(1))[0];
const classes = _.remove(tags, (tag)=>!tag.includes('"'));
const styles = tags.map((tag)=>tag.replace(/="(.*)"/g, ':$1;'));
const classes = _.remove(tags, (tag)=>!tag.includes(':'));
const styles = tags.map((tag)=>tag.replace(/:"?([^"]*)"?/g, ':$1;'));
return `${classes.join(' ')}" ${id ? `id="${id}"` : ''} ${styles.length ? `style="${styles.join(' ')}"` : ''}`;
};

View File

@@ -385,13 +385,14 @@ body {
}
}
.pageNumber{
position : absolute;
right : 2px;
bottom : 22px;
width : 50px;
font-size : 0.9em;
color : #c9ad6a;
text-align : center;
position : absolute;
right : 2px;
bottom : 22px;
width : 50px;
font-size : 0.9em;
color : #c9ad6a;
text-align : center;
text-indent : 0;
&.auto::after {
content : counter(phb-page-numbers);
}
@@ -602,9 +603,10 @@ body {
break-inside : avoid;
-webkit-transform : translateZ(0); //Prevents shadows from breaking across columns
}
.inline {
.inline-block {
display : inline-block;
text-indent : initial;
line-height : 1.3em;
}
div {
column-gap : 0.5cm; //Default spacing if a div uses multicolumns