diff --git a/changelog.md b/changelog.md index 9cc5a98de..b72024e3a 100644 --- a/changelog.md +++ b/changelog.md @@ -6,6 +6,17 @@ h5 { # changelog +### Tuesday, 17/08/2021 - v2.13.4 +- Fixed user page crashing when user has untitled brew + +##### G-Ambatte: +- Tweaks to user page tool tips +- Fix view counts being reset on Google Drive files + +##### Gazook89 : +- New **PHB → Artist Credit** snippet +- **PRINT** snippets moved to the **Style Editor** tab + ### Monday, 09/08/2021 - v2.13.3 ##### G-Ambatte : @@ -48,6 +59,8 @@ myStyle {color: black} - Pasting your brew into a "New" page and saving will transfer any CSS in the code fence to the Style tab. - Unsaved work in the New page Style tab is now cached to your browser storage if you navigate away. +\page + ### Thursday, 10/6/2021 - v2.12.0 diff --git a/client/homebrew/brewRenderer/brewRenderer.jsx b/client/homebrew/brewRenderer/brewRenderer.jsx index e3ab31742..2b8019f73 100644 --- a/client/homebrew/brewRenderer/brewRenderer.jsx +++ b/client/homebrew/brewRenderer/brewRenderer.jsx @@ -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, @@ -130,8 +130,14 @@ const BrewRenderer = createClass({ renderPage : function(pageText, index){ if(this.props.renderer == 'legacy') return
; - else - return
; + else { + pageText += `\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 ( +
+
+
+ ); + } }, renderPages : function(){ diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index d43ae7c61..213e35f71 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -68,7 +68,9 @@ 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(); @@ -76,7 +78,7 @@ const Editor = createClass({ this.refs.codeEditor.setCursorPosition(cursorPos.line + injectText.split('\n').length, cursorPos.ch + injectText.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 +121,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); } diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.less b/client/homebrew/editor/metadataEditor/metadataEditor.less index 4cc1b8d4a..e49f4f712 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.less +++ b/client/homebrew/editor/metadataEditor/metadataEditor.less @@ -67,9 +67,6 @@ .button(@silver); } small{ - position : absolute; - bottom : -15px; - left : 0px; font-size : 0.6em; font-style : italic; } diff --git a/client/homebrew/editor/snippetbar/snippets/classtable.gen.js b/client/homebrew/editor/snippetbar/snippets/classtable.gen.js index c8a2d051f..c1f6254f9 100644 --- a/client/homebrew/editor/snippetbar/snippets/classtable.gen.js +++ b/client/homebrew/editor/snippetbar/snippets/classtable.gen.js @@ -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 `
\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
\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`; } }; diff --git a/client/homebrew/editor/snippetbar/snippets/snippets.js b/client/homebrew/editor/snippetbar/snippets/snippets.js index 4aa706316..02f0432a6 100644 --- a/client/homebrew/editor/snippetbar/snippets/snippets.js +++ b/client/homebrew/editor/snippetbar/snippets/snippets.js @@ -44,8 +44,8 @@ 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` }, @@ -262,12 +262,32 @@ module.exports = [ { name : 'Class Table', icon : 'fas fa-table', - gen : ClassTableGen.full, + gen : ClassTableGen.full('classTable,frame,wide'), }, { - name : 'Half Class Table', + 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, + gen : ClassTableGen.half('classTable,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'), }, { name : 'Table', diff --git a/client/homebrew/editor/snippetbar/snippetsLegacy/snippets.js b/client/homebrew/editor/snippetbar/snippetsLegacy/snippets.js index 9d31ddde5..7ea2f236c 100644 --- a/client/homebrew/editor/snippetbar/snippetsLegacy/snippets.js +++ b/client/homebrew/editor/snippetbar/snippetsLegacy/snippets.js @@ -305,7 +305,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; diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index 0967e05e2..d55c2ef4f 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -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 }); diff --git a/client/homebrew/pages/homePage/welcome_msg_v3.md b/client/homebrew/pages/homePage/welcome_msg_v3.md index 471932ed1..553d7aa2e 100644 --- a/client/homebrew/pages/homePage/welcome_msg_v3.md +++ b/client/homebrew/pages/homePage/welcome_msg_v3.md @@ -4,7 +4,7 @@ } .page { - padding-bottom : 1.6cm; + padding-bottom : 1.3cm; } @@ -37,7 +37,10 @@ After clicking the "Print" item in the navbar a new page will open and a print d 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 + +
1
+
PART 1 | FANCINESS
\column @@ -75,15 +78,6 @@ If you'd like to credit The Homebrewery in your brew, I'd be flattered! Just ref 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/). - - - -
1
-
PART 1 | FANCINESS
- - - - \page ## Markdown+ diff --git a/client/homebrew/pages/newPage/newPage.jsx b/client/homebrew/pages/newPage/newPage.jsx index e77fbc987..04004034e 100644 --- a/client/homebrew/pages/newPage/newPage.jsx +++ b/client/homebrew/pages/newPage/newPage.jsx @@ -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/') diff --git a/client/homebrew/pages/printPage/printPage.jsx b/client/homebrew/pages/printPage/printPage.jsx index d4f15c91b..b77fbb8f7 100644 --- a/client/homebrew/pages/printPage/printPage.jsx +++ b/client/homebrew/pages/printPage/printPage.jsx @@ -37,20 +37,21 @@ const PrintPage = createClass({ 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
; }); } else { - return _.map(this.state.brewText.split(/^\\page/gm), (page, index)=>{ - return
; + return _.map(this.state.brewText.split(/^\\page$/gm), (pageText, index)=>{ + pageText += `\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 ( +
+
+
+ ); }); } diff --git a/client/homebrew/pages/userPage/brewItem/brewItem.jsx b/client/homebrew/pages/userPage/brewItem/brewItem.jsx index f0557c47e..be0583f88 100644 --- a/client/homebrew/pages/userPage/brewItem/brewItem.jsx +++ b/client/homebrew/pages/userPage/brewItem/brewItem.jsx @@ -48,7 +48,7 @@ const BrewItem = createClass({ if(!this.props.brew.editId) return; return - + ; }, @@ -61,7 +61,7 @@ const BrewItem = createClass({ } return - + ; }, @@ -74,7 +74,7 @@ const BrewItem = createClass({ } return - + ; }, @@ -87,7 +87,7 @@ const BrewItem = createClass({ } return - + ; }, @@ -99,31 +99,33 @@ const BrewItem = createClass({ ; }, - getTooltipData : function(){ - const dateFormatString = 'YYYY-MM-DD HH:mm:ss'; - let outputString = `Created: ${this.props.brew.createdAt ? moment(this.props.brew.createdAt).local().format(dateFormatString) : 'UNKNOWN'}\n`; - outputString += `Last updated: ${this.props.brew.updatedAt ? moment(this.props.brew.updatedAt).local().format(dateFormatString) : 'UNKNOWN'}`; - return outputString; - }, - render : function(){ const brew = this.props.brew; - return
-

{brew.title}

-

{brew.description}

-
+ const dateFormatString = 'YYYY-MM-DD HH:mm:ss'; + return
+
+

{brew.title}

+

{brew.description}

+
+
- - {brew.authors.join(', ')} - - - {brew.views} + + {brew.views} + {brew.pageCount && + + {brew.pageCount} + + } {moment(brew.updatedAt).fromNow()} {this.renderGoogleDriveIcon()} +
+ + {brew.authors.join(', ')} +
diff --git a/client/homebrew/pages/userPage/brewItem/brewItem.less b/client/homebrew/pages/userPage/brewItem/brewItem.less index efae9d8d3..b5250ae8c 100644 --- a/client/homebrew/pages/userPage/brewItem/brewItem.less +++ b/client/homebrew/pages/userPage/brewItem/brewItem.less @@ -10,24 +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; + position: initial; + bottom: 2px; margin-bottom: 4px; font-family : ScalySans; font-size : 1.2em; &>span{ + display : float; margin-right : 12px; } } diff --git a/client/homebrew/pages/userPage/userPage.jsx b/client/homebrew/pages/userPage/userPage.jsx index 9e78ee6ea..df96b9804 100644 --- a/client/homebrew/pages/userPage/userPage.jsx +++ b/client/homebrew/pages/userPage/userPage.jsx @@ -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
No Brews.
; - const sortedBrews = this.sortBrews(brews, this.state.sortType); + const sortedBrews = this.sortBrews(brews); return _.map(sortedBrews, (brew, idx)=>{ return ; @@ -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({ ; }, + handleFilterTextChange : function(e){ + this.setState({ + filterString : e.target.value + }); + return; + }, + + renderFilterOption : function(){ + return + + ; + }, + renderSortOptions : function(){ return
@@ -114,6 +136,7 @@ const UserPage = createClass({ {`${(this.state.sortDir == 'asc' ? '\u25B2 ASC' : '\u25BC DESC')}`} + {this.renderFilterOption()}
@@ -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'); }); }, diff --git a/client/homebrew/pages/userPage/userPage.less b/client/homebrew/pages/userPage/userPage.less index c7e8621e3..d968aab9a 100644 --- a/client/homebrew/pages/userPage/userPage.less +++ b/client/homebrew/pages/userPage/userPage.less @@ -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; diff --git a/package-lock.json b/package-lock.json index b96f158e9..5476b98d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "homebrewery", - "version": "2.13.3", + "version": "2.13.4", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "2.13.3", + "version": "2.13.4", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -15,7 +15,7 @@ "@babel/preset-react": "^7.14.5", "body-parser": "^1.19.0", "classnames": "^2.3.1", - "codemirror": "^5.62.2", + "codemirror": "^5.62.3", "cookie-parser": "^1.4.5", "create-react-class": "^15.7.0", "dedent-tabs": "^0.9.0", @@ -27,7 +27,7 @@ "jwt-simple": "^0.5.6", "less": "^3.13.1", "lodash": "^4.17.21", - "marked": "2.1.3", + "marked": "3.0.2", "markedLegacy": "npm:marked@^0.3.19", "moment": "^2.29.1", "mongoose": "^5.13.7", @@ -38,14 +38,14 @@ "react": "^16.14.0", "react-dom": "^16.14.0", "react-frame-component": "4.1.3", - "react-router-dom": "5.2.0", + "react-router-dom": "5.2.1", "sanitize-filename": "1.6.3", "superagent": "^6.1.0", "vitreum": "git+https://git@github.com/calculuschild/vitreum.git" }, "devDependencies": { "eslint": "^7.32.0", - "eslint-plugin-react": "^7.24.0", + "eslint-plugin-react": "^7.25.1", "pico-check": "^2.1.3" }, "engines": { @@ -1665,11 +1665,14 @@ } }, "node_modules/@babel/runtime": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.6.tgz", - "integrity": "sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ==", + "version": "7.15.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.3.tgz", + "integrity": "sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA==", "dependencies": { "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/runtime/node_modules/regenerator-runtime": { @@ -3126,9 +3129,9 @@ } }, "node_modules/codemirror": { - "version": "5.62.2", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.2.tgz", - "integrity": "sha512-tVFMUa4J3Q8JUd1KL9yQzQB0/BJt7ZYZujZmTPgo/54Lpuq3ez4C8x/ATUY/wv7b7X3AUq8o3Xd+2C5ZrCGWHw==" + "version": "5.62.3", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.3.tgz", + "integrity": "sha512-zZAyOfN8TU67ngqrxhOgtkSAGV9jSpN1snbl8elPtnh9Z5A11daR405+dhLzLnuXrwX0WCShWlybxPN3QC/9Pg==" }, "node_modules/collection-visit": { "version": "1.0.0", @@ -3856,14 +3859,15 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.24.0.tgz", - "integrity": "sha512-KJJIx2SYx7PBx3ONe/mEeMz4YE0Lcr7feJTCMyyKb/341NcjuAgim3Acgan89GfPv7nxXK2+0slu0CWXYM4x+Q==", + "version": "7.25.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.25.1.tgz", + "integrity": "sha512-P4j9K1dHoFXxDNP05AtixcJEvIT6ht8FhYKsrkY0MPCPaUMYijhpWwNiRDZVtA8KFuZOkGSeft6QwH8KuVpJug==", "dev": true, "dependencies": { "array-includes": "^3.1.3", "array.prototype.flatmap": "^1.2.4", "doctrine": "^2.1.0", + "estraverse": "^5.2.0", "has": "^1.0.3", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.0.4", @@ -3893,6 +3897,15 @@ "node": ">=0.10.0" } }, + "node_modules/eslint-plugin-react/node_modules/estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, "node_modules/eslint-plugin-react/node_modules/resolve": { "version": "2.0.0-next.3", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz", @@ -5961,14 +5974,14 @@ } }, "node_modules/marked": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/marked/-/marked-2.1.3.tgz", - "integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-3.0.2.tgz", + "integrity": "sha512-TMJQQ79Z0e3rJYazY0tIoMsFzteUGw9fB3FD+gzuIT3zLuG9L9ckIvUfF51apdJkcqc208jJN2KbtPbOvXtbjA==", "bin": { "marked": "bin/marked" }, "engines": { - "node": ">= 10" + "node": ">= 12" } }, "node_modules/markedLegacy": { @@ -6185,15 +6198,6 @@ "node": ">=4" } }, - "node_modules/mini-create-react-context": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.0.tgz", - "integrity": "sha512-b0TytUgFSbgFJGzJqXPKCFCBWigAjpjo+Fl7Vf7ZbKRDptszpppKxXH6DRXEABZ/gcEQczeb0iZ7JvL8e8jjCA==", - "dependencies": { - "@babel/runtime": "^7.5.5", - "tiny-warning": "^1.0.3" - } - }, "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -7287,12 +7291,42 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==" }, - "node_modules/react-router": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", - "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", + "node_modules/react-router-dom": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.1.tgz", + "integrity": "sha512-xhFFkBGVcIVPbWM2KEYzED+nuHQPmulVa7sqIs3ESxzYd1pYg8N8rxPnQ4T2o1zu/2QeDUWcaqST131SO1LR3w==", "dependencies": { - "@babel/runtime": "^7.1.2", + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.2.1", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/react-router-dom/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "node_modules/react-router-dom/node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/react-router-dom/node_modules/react-router": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.1.tgz", + "integrity": "sha512-lIboRiOtDLFdg1VTemMwud9vRVuOCZmUIT/7lUoZiSpPODiiH1UQlfXy+vPLC/7IWdFYnhRwAyNqA/+I7wnvKQ==", + "dependencies": { + "@babel/runtime": "^7.12.13", "history": "^4.9.0", "hoist-non-react-statics": "^3.1.0", "loose-envify": "^1.3.1", @@ -7302,75 +7336,22 @@ "react-is": "^16.6.0", "tiny-invariant": "^1.0.2", "tiny-warning": "^1.0.0" - } - }, - "node_modules/react-router-dom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", - "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", - "dependencies": { - "@babel/runtime": "^7.1.2", - "history": "^4.9.0", - "loose-envify": "^1.3.1", - "prop-types": "^15.6.2", - "react-router": "5.2.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - } - }, - "node_modules/react-router-dom/node_modules/prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - }, - "node_modules/react-router-dom/node_modules/prop-types/node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" }, - "bin": { - "loose-envify": "cli.js" + "peerDependencies": { + "react": ">=15" } }, - "node_modules/react-router/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "node_modules/react-router/node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "node_modules/react-router-dom/node_modules/react-router/node_modules/mini-create-react-context": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz", + "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==", "dependencies": { - "isarray": "0.0.1" - } - }, - "node_modules/react-router/node_modules/prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - }, - "node_modules/react-router/node_modules/prop-types/node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" + "@babel/runtime": "^7.12.1", + "tiny-warning": "^1.0.3" }, - "bin": { - "loose-envify": "cli.js" + "peerDependencies": { + "prop-types": "^15.0.0", + "react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "node_modules/read-only-stream": { @@ -10616,9 +10597,9 @@ } }, "@babel/runtime": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.6.tgz", - "integrity": "sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ==", + "version": "7.15.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.3.tgz", + "integrity": "sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA==", "requires": { "regenerator-runtime": "^0.13.4" }, @@ -11828,9 +11809,9 @@ } }, "codemirror": { - "version": "5.62.2", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.2.tgz", - "integrity": "sha512-tVFMUa4J3Q8JUd1KL9yQzQB0/BJt7ZYZujZmTPgo/54Lpuq3ez4C8x/ATUY/wv7b7X3AUq8o3Xd+2C5ZrCGWHw==" + "version": "5.62.3", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.3.tgz", + "integrity": "sha512-zZAyOfN8TU67ngqrxhOgtkSAGV9jSpN1snbl8elPtnh9Z5A11daR405+dhLzLnuXrwX0WCShWlybxPN3QC/9Pg==" }, "collection-visit": { "version": "1.0.0", @@ -12517,14 +12498,15 @@ } }, "eslint-plugin-react": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.24.0.tgz", - "integrity": "sha512-KJJIx2SYx7PBx3ONe/mEeMz4YE0Lcr7feJTCMyyKb/341NcjuAgim3Acgan89GfPv7nxXK2+0slu0CWXYM4x+Q==", + "version": "7.25.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.25.1.tgz", + "integrity": "sha512-P4j9K1dHoFXxDNP05AtixcJEvIT6ht8FhYKsrkY0MPCPaUMYijhpWwNiRDZVtA8KFuZOkGSeft6QwH8KuVpJug==", "dev": true, "requires": { "array-includes": "^3.1.3", "array.prototype.flatmap": "^1.2.4", "doctrine": "^2.1.0", + "estraverse": "^5.2.0", "has": "^1.0.3", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.0.4", @@ -12545,6 +12527,12 @@ "esutils": "^2.0.2" } }, + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + }, "resolve": { "version": "2.0.0-next.3", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz", @@ -14071,9 +14059,9 @@ } }, "marked": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/marked/-/marked-2.1.3.tgz", - "integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA==" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-3.0.2.tgz", + "integrity": "sha512-TMJQQ79Z0e3rJYazY0tIoMsFzteUGw9fB3FD+gzuIT3zLuG9L9ckIvUfF51apdJkcqc208jJN2KbtPbOvXtbjA==" }, "markedLegacy": { "version": "npm:marked@0.3.19", @@ -14247,15 +14235,6 @@ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" }, - "mini-create-react-context": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.0.tgz", - "integrity": "sha512-b0TytUgFSbgFJGzJqXPKCFCBWigAjpjo+Fl7Vf7ZbKRDptszpppKxXH6DRXEABZ/gcEQczeb0iZ7JvL8e8jjCA==", - "requires": { - "@babel/runtime": "^7.5.5", - "tiny-warning": "^1.0.3" - } - }, "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -15108,19 +15087,16 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==" }, - "react-router": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", - "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", + "react-router-dom": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.1.tgz", + "integrity": "sha512-xhFFkBGVcIVPbWM2KEYzED+nuHQPmulVa7sqIs3ESxzYd1pYg8N8rxPnQ4T2o1zu/2QeDUWcaqST131SO1LR3w==", "requires": { - "@babel/runtime": "^7.1.2", + "@babel/runtime": "^7.12.13", "history": "^4.9.0", - "hoist-non-react-statics": "^3.1.0", "loose-envify": "^1.3.1", - "mini-create-react-context": "^0.4.0", - "path-to-regexp": "^1.7.0", "prop-types": "^15.6.2", - "react-is": "^16.6.0", + "react-router": "5.2.1", "tiny-invariant": "^1.0.2", "tiny-warning": "^1.0.0" }, @@ -15138,58 +15114,30 @@ "isarray": "0.0.1" } }, - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "react-router": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.1.tgz", + "integrity": "sha512-lIboRiOtDLFdg1VTemMwud9vRVuOCZmUIT/7lUoZiSpPODiiH1UQlfXy+vPLC/7IWdFYnhRwAyNqA/+I7wnvKQ==", "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "mini-create-react-context": "^0.4.0", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" }, "dependencies": { - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "mini-create-react-context": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz", + "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==", "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - } - } - } - } - }, - "react-router-dom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", - "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", - "requires": { - "@babel/runtime": "^7.1.2", - "history": "^4.9.0", - "loose-envify": "^1.3.1", - "prop-types": "^15.6.2", - "react-router": "5.2.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - }, - "dependencies": { - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - }, - "dependencies": { - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" + "@babel/runtime": "^7.12.1", + "tiny-warning": "^1.0.3" } } } diff --git a/package.json b/package.json index 988c36f55..2aafba0a8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "homebrewery", "description": "Create authentic looking D&D homebrews using only markdown", - "version": "2.13.3", + "version": "2.13.4", "engines": { "node": "14.15.x" }, @@ -46,7 +46,7 @@ "@babel/preset-react": "^7.14.5", "body-parser": "^1.19.0", "classnames": "^2.3.1", - "codemirror": "^5.62.2", + "codemirror": "^5.62.3", "cookie-parser": "^1.4.5", "create-react-class": "^15.7.0", "dedent-tabs": "^0.9.0", @@ -58,7 +58,7 @@ "jwt-simple": "^0.5.6", "less": "^3.13.1", "lodash": "^4.17.21", - "marked": "2.1.3", + "marked": "3.0.2", "markedLegacy": "npm:marked@^0.3.19", "moment": "^2.29.1", "mongoose": "^5.13.7", @@ -69,14 +69,14 @@ "react": "^16.14.0", "react-dom": "^16.14.0", "react-frame-component": "4.1.3", - "react-router-dom": "5.2.0", + "react-router-dom": "5.2.1", "sanitize-filename": "1.6.3", "superagent": "^6.1.0", "vitreum": "git+https://git@github.com/calculuschild/vitreum.git" }, "devDependencies": { "eslint": "^7.32.0", - "eslint-plugin-react": "^7.24.0", + "eslint-plugin-react": "^7.25.1", "pico-check": "^2.1.3" } } diff --git a/server.js b/server.js index fbf741ec0..2f1564b20 100644 --- a/server.js +++ b/server.js @@ -134,8 +134,9 @@ app.get('/v3_preview', async (req, res, next)=>{ //Changelog page app.get('/changelog', async (req, res, next)=>{ const brew = { - title : 'Changelog', - text : changelogText + title : 'Changelog', + text : changelogText, + renderer : 'V3' }; req.brew = brew; return next(); diff --git a/server/googleActions.js b/server/googleActions.js index eab6babf3..aca84daa3 100644 --- a/server/googleActions.js +++ b/server/googleActions.js @@ -17,7 +17,7 @@ GoogleActions = { if(!account || !account.googleId){ // If not signed into Google const err = new Error('Not Signed In'); err.status = 401; - throw err; + throw (err); } const oAuth2Client = new google.auth.OAuth2( @@ -60,6 +60,7 @@ GoogleActions = { .catch((err)=>{ console.log('Error searching Google Drive Folders'); console.error(err); + throw (err); }); let folderId; @@ -69,8 +70,9 @@ GoogleActions = { resource : fileMetadata }) .catch((err)=>{ - console.log('Error creating google app folder'); + console.log('Error creating Google Drive folder'); console.error(err); + throw (err); }); folderId = obj.data.id; @@ -99,7 +101,9 @@ GoogleActions = { q : 'mimeType != \'application/vnd.google-apps.folder\' and trashed = false' }) .catch((err)=>{ - return console.error(`Error Listing Google Brews: ${err}`); + console.log(`Error Listing Google Brews`); + console.error(err); + throw (err); //TODO: Should break out here, but continues on for some reason. }); @@ -109,24 +113,23 @@ GoogleActions = { const brews = obj.data.files.map((file)=>{ return { - text : '', - shareId : file.properties.shareId, - editId : file.properties.editId, - createdAt : file.createdTime, - updatedAt : file.modifiedTime, - gDrive : true, - googleId : file.id, - - title : file.properties.title, - description : file.description, + text : '', + shareId : file.properties.shareId, + editId : file.properties.editId, + createdAt : file.createdTime, + updatedAt : file.modifiedTime, + gDrive : true, + googleId : file.id, + pageCount : file.properties.pageCount, + title : file.properties.title, + description : file.description, views : file.properties.views, - tags : '', - published : file.properties.published ? file.properties.published == 'true' : false, - authors : [req.account.username], //TODO: properly save and load authors to google drive - systems : [] - }; - }); - + tags : '', + published : file.properties.published ? file.properties.published == 'true' : false, + authors : [req.account.username], //TODO: properly save and load authors to google drive + systems : [] + }; + }); return brews; }, @@ -136,7 +139,7 @@ GoogleActions = { const result = await drive.files.get({ fileId: id }) .catch((err)=>{ console.log('error checking file exists...'); - console.log(err); + console.error(err); return false; }); @@ -151,19 +154,22 @@ GoogleActions = { if(await GoogleActions.existsGoogleBrew(auth, brew.googleId) == true) { await drive.files.update({ fileId : brew.googleId, - resource : { name : `${brew.title}.txt`, - description : `${brew.description}`, - properties : { title : brew.title, - published : brew.published, - lastViewed : brew.lastViewed, - views : brew.views, - version : brew.version, - renderer : brew.renderer, - tags : brew.tags, - systems : brew.systems.join() } - }, - media : { mimeType : 'text/plain', - body : brew.text } + resource : { + name : `${brew.title}.txt`, + description : `${brew.description}`, + properties : { + title : brew.title, + published : brew.published, + version : brew.version, + renderer : brew.renderer, + tags : brew.tags, + systems : brew.systems.join() + } + }, + media : { + mimeType : 'text/plain', + body : brew.text + } }) .catch((err)=>{ console.log('Error saving to google'); @@ -191,11 +197,12 @@ GoogleActions = { 'description' : `${brew.description}`, 'parents' : [folderId], 'properties' : { //AppProperties is not accessible - 'shareId' : nanoid(12), - 'editId' : nanoid(12), - 'title' : brew.title, - 'views' : '0', - 'renderer' : brew.renderer || 'legacy' + 'shareId' : nanoid(12), + 'editId' : nanoid(12), + 'title' : brew.title, + 'views' : '0', + 'pageCount' : brew.pageCount, + 'renderer' : brew.renderer || 'legacy' } }; @@ -230,6 +237,7 @@ GoogleActions = { updatedAt : new Date(), gDrive : true, googleId : obj.data.id, + pageCount : fileMetadata.properties.pageCount, title : brew.title, description : brew.description, @@ -301,6 +309,7 @@ GoogleActions = { createdAt : obj.data.createdTime, updatedAt : obj.data.modifiedTime, lastViewed : obj.data.properties.lastViewed, + pageCount : obj.data.properties.pageCount, views : parseInt(obj.data.properties.views) || 0, //brews with no view parameter will return undefined version : parseInt(obj.data.properties.version) || 0, renderer : obj.data.properties.renderer ? obj.data.properties.renderer : 'legacy', @@ -361,8 +370,13 @@ GoogleActions = { await drive.files.update({ fileId : brew.googleId, - resource : { properties : { views : brew.views + 1, - lastViewed : new Date() } } + resource : { + modifiedTime : brew.updatedAt, + properties : { + views : brew.views + 1, + lastViewed : new Date() + } + } }) .catch((err)=>{ console.log('Error updating Google views'); diff --git a/server/homebrew.api.js b/server/homebrew.api.js index 36d0aa576..59d4f6d68 100644 --- a/server/homebrew.api.js +++ b/server/homebrew.api.js @@ -19,6 +19,15 @@ const getGoodBrewTitle = (text)=>{ .slice(0, MAX_TITLE_LENGTH); }; +const excludePropsFromUpdate = (brew)=>{ + // Remove undesired properties + const propsToExclude = ['views', 'lastViewed']; + for (const prop of propsToExclude) { + delete brew[prop]; + }; + return brew; +}; + const mergeBrewText = (text, style)=>{ if(typeof style !== 'undefined') { text = `\`\`\`css\n` + @@ -64,7 +73,8 @@ const newBrew = (req, res)=>{ const updateBrew = (req, res)=>{ HomebrewModel.get({ editId: req.params.id }) .then((brew)=>{ - brew = _.merge(brew, req.body); + const updateBrew = excludePropsFromUpdate(req.body); + brew = _.merge(brew, updateBrew); brew.text = mergeBrewText(brew.text, brew.style); // Compress brew text to binary before saving @@ -154,7 +164,7 @@ const updateGoogleBrew = async (req, res, next)=>{ try { oAuth2Client = GoogleActions.authCheck(req.account, res); } catch (err) { return res.status(err.status).send(err.message); } - const brew = req.body; + const brew = excludePropsFromUpdate(req.body); brew.text = mergeBrewText(brew.text, brew.style); try { diff --git a/server/homebrew.model.js b/server/homebrew.model.js index c2abdf199..acc78a624 100644 --- a/server/homebrew.model.js +++ b/server/homebrew.model.js @@ -4,11 +4,12 @@ const _ = require('lodash'); const zlib = require('zlib'); const HomebrewSchema = mongoose.Schema({ - shareId : { type: String, default: ()=>{return nanoid(12);}, index: { unique: true } }, - editId : { type: String, default: ()=>{return nanoid(12);}, index: { unique: true } }, - title : { type: String, default: '' }, - text : { type: String, default: '' }, - textBin : { type: Buffer }, + shareId : { type: String, default: ()=>{return nanoid(12);}, index: { unique: true } }, + editId : { type: String, default: ()=>{return nanoid(12);}, index: { unique: true } }, + title : { type: String, default: '' }, + text : { type: String, default: '' }, + textBin : { type: Buffer }, + pageCount : { type: Number, default: 1 }, description : { type: String, default: '' }, tags : { type: String, default: '' }, diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index eee0e982b..8f01f0147 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -65,13 +65,13 @@ const mustacheSpans = { raw : raw, // Text to consume from the source text : text, // Additional custom properties tags : tags, - tokens : this.inlineTokens(text) // inlineTokens to process **bold**, *italics*, etc. + tokens : this.lexer.inlineTokens(text) // inlineTokens to process **bold**, *italics*, etc. }; } } }, renderer(token) { - return `${this.parser.parseInline(token.tokens)}`; // parseInline to turn child tokens into HTML } }; @@ -114,13 +114,13 @@ const mustacheDivs = { raw : raw, // Text to consume from the source text : text, // Additional custom properties tags : tags, - tokens : this.inline(this.blockTokens(text)) + tokens : this.lexer.blockTokens(text) }; } } }, renderer(token) { - return `
${this.parser.parse(token.tokens)}
`; // parseInline to turn child tokens into HTML } }; @@ -149,7 +149,7 @@ const mustacheInjectInline = { }, renderer(token) { token.type = token.originalType; - const text = this.parseInline([token]); + const text = this.parser.parseInline([token]); const openingTag = /(<[^\s<>]+)([^\n<>]*>.*)/s.exec(text); if(openingTag) { return `${openingTag[1]} class="${token.tags}${openingTag[2]}`; @@ -182,7 +182,7 @@ const mustacheInjectBlock = { }, renderer(token) { token.type = token.originalType; - const text = this.parse([token]); + const text = this.parser.parse([token]); const openingTag = /(<[^\s<>]+)([^\n<>]*>.*)/s.exec(text); if(openingTag) { return `${openingTag[1]} class="${token.tags}${openingTag[2]}`; @@ -211,8 +211,8 @@ const definitionLists = { const definitions = []; while (match = regex.exec(src)) { definitions.push({ - dt : this.inlineTokens(match[1].trim()), - dd : this.inlineTokens(match[2].trim()) + dt : this.lexer.inlineTokens(match[1].trim()), + dd : this.lexer.inlineTokens(match[2].trim()) }); endIndex = regex.lastIndex; } @@ -227,8 +227,8 @@ const definitionLists = { renderer(token) { return `
${token.definitions.reduce((html, def)=>{ - return `${html}
${this.parseInline(def.dt)}
` - + `
${this.parseInline(def.dd)}
\n`; + return `${html}
${this.parser.parseInline(def.dt)}
` + + `
${this.parser.parseInline(def.dd)}
\n`; }, '')}
`; } @@ -302,7 +302,7 @@ const spanTable = { row = item.header[j]; for (k = 0; k < row.length; k++) { row[k].tokens = []; - this.inlineTokens(row[k].text, row[k].tokens); + this.lexer.inlineTokens(row[k].text, row[k].tokens); } } @@ -312,7 +312,7 @@ const spanTable = { row = item.rows[j]; for (k = 0; k < row.length; k++) { row[k].tokens = []; - this.inlineTokens(row[k].text, row[k].tokens); + this.lexer.inlineTokens(row[k].text, row[k].tokens); } } return item; @@ -329,7 +329,7 @@ const spanTable = { output += ``; for (j = 0; j < row.length; j++) { cell = row[j]; - text = this.parseInline(cell.tokens); + text = this.parser.parseInline(cell.tokens); output += getTableCell(text, cell, 'th', token.align[col]); col += cell.colspan; } @@ -344,7 +344,7 @@ const spanTable = { output += ``; for (j = 0; j < row.length; j++) { cell = row[j]; - text = this.parseInline(cell.tokens); + text = this.parser.parseInline(cell.tokens); output += getTableCell(text, cell, 'td', token.align[col]); col += cell.colspan; } @@ -527,7 +527,7 @@ const processStyleTags = (string)=>{ module.exports = { marked : Markdown, render : (rawBrewText)=>{ - rawBrewText = rawBrewText.replace(/^\\column$/gm, `
`) + rawBrewText = rawBrewText.replace(/^\\column$/gm, `\n
\n`) .replace(/^(:+)$/gm, (match)=>`${`
`.repeat(match.length)}\n`); return Markdown( sanatizeScriptTags(rawBrewText), diff --git a/themes/5ePhb.style.less b/themes/5ePhb.style.less index 2e3ef96c1..3c6d0485e 100644 --- a/themes/5ePhb.style.less +++ b/themes/5ePhb.style.less @@ -34,9 +34,9 @@ body { letter-spacing : -0.02em; } } -.useColumns(@multiplier : 1){ +.useColumns(@multiplier : 1, @fillMode: balance){ column-count : 2; - column-fill : auto; + column-fill : @fillMode; column-gap : 0.9cm; column-width : 8cm * @multiplier; -webkit-column-count : 2; @@ -46,6 +46,11 @@ body { -webkit-column-gap : 0.9cm; -moz-column-gap : 0.9cm; } +.columnWrapper{ + max-height : 100%; + column-span : all; + columns : inherit; +} .page{ .useColumns(); counter-increment : phb-page-numbers; @@ -55,9 +60,9 @@ body { overflow : hidden; height : 279.4mm; width : 215.9mm; - padding : 1.4cm 1.9cm 1.7cm; background-color : @background; background-image : @backgroundImage; + padding : 1.4cm 1.9cm 1.7cm; font-family : BookInsanityRemake; font-size : 0.34cm; text-rendering : optimizeLegibility; @@ -68,10 +73,13 @@ body { // *****************************/ p{ overflow-wrap : break-word; //TODO: MAKE ALL MARGINS TOP-ONLY. USE * + * STYLE SELECTORS - margin-bottom : 0.8em; + display : block; line-height : 1.3em; + &+* { + margin-top : 0.27cm; + } &+p{ - margin-top : -0.8em; + margin-top : 0; } } ul{ @@ -121,50 +129,49 @@ body { color : @headerText; } h1{ - margin-bottom : 0.18cm; + margin-bottom : 0.18cm; //Margin-bottom only because this is WIDE column-span : all; font-size : 0.89cm; -webkit-column-span : all; -moz-column-span : all; &+p::first-letter{ - float : left; - font-family : SolberaImitationRemake; - line-height : 0.8em; - font-size: 3.5cm; - padding-left: 40px; - margin-left: -40px; - padding-top:10px; - margin-top:-8px; - padding-bottom:10px; - margin-bottom:-20px; - background-image: linear-gradient(-45deg, #322814, #998250, #322814); - background-clip: text; - -webkit-background-clip: text; - color: rgba(0, 0, 0, 0); + float : left; + font-family : SolberaImitationRemake; + line-height : 1em; + font-size : 3.5cm; + padding-left : 40px; //Allow background color to extend into margins + margin-left : -40px; + margin-top :-0.3cm; + padding-bottom :2px; + margin-bottom :-20px; + background-image : linear-gradient(-45deg, #322814, #998250, #322814); + background-clip : text; + -webkit-background-clip : text; + color : rgba(0, 0, 0, 0); } &+p::first-line{ font-variant : small-caps; } } h2{ - margin-top : 0px; - margin-bottom : 0.05cm; + //margin-top : 0px; //Font is misaligned. Shift up slightly + //margin-bottom : 0.05cm; font-size : 0.75cm; } h3{ - margin-top : -0.1cm; - margin-bottom : 0.1cm; + //margin-top : -0.1cm; //Font is misaligned. Shift up slightly + //margin-bottom : 0.1cm; font-size : 0.575cm; border-bottom : 2px solid @headerUnderline; } h4{ - margin-top : -0.02cm; - margin-bottom : 0.02cm; + //margin-top : -0.02cm; //Font is misaligned. Shift up slightly + //margin-bottom : 0.02cm; font-size : 0.458cm; } h5{ - margin-top : -0.02cm; - margin-bottom : 0.02cm; + //margin-top : -0.02cm; //Font is misaligned. Shift up slightly + //margin-bottom : 0.02cm; font-family : ScalySansSmallCapsRemake; font-size : 0.423cm; font-weight : 900; @@ -206,7 +213,6 @@ body { border-width : 11px; border-image : @noteBorderImage 12; border-image-outset : 9px 0px; - box-shadow : 1px 4px 14px #888; position : absolute; width : 100%; height : 100%; @@ -219,6 +225,7 @@ body { margin-left : -0.1em; margin-right : -0.1em; background-color : @noteGreen; + filter : drop-shadow(1px 4px 6px #888); padding : 0.5em 0.6em; & + * { margin-top : 1.3em; @@ -239,7 +246,7 @@ body { // ************************************/ .descriptive{ .useSansSerif(); - display : block-inline; + display : inline-block; margin-top : 1.4em; background-color : #faf7ea; font-family : ScalySansRemake; @@ -247,7 +254,7 @@ body { border-width : 7px; border-image : @descriptiveBoxImage 12 stretch; border-image-outset : 4px; - box-shadow : 0px 0px 6px #faf7ea; + filter : drop-shadow(0 0 3px #faf7ea); padding : 0.1em; & + * { margin-top : 1.4em; @@ -329,7 +336,7 @@ body { border-image-outset : 0px 2px; background-blend-mode : overlay; background-attachment : fixed; - box-shadow : 1px 4px 14px #888; + filter : drop-shadow(1px 4px 6px #888); padding : 4px 2px; margin : 0px -6px 1em; } @@ -383,16 +390,19 @@ body { dl { color : @headerText; } + hr:last-of-type~dl{ + color : inherit; // After the HRs, hanging indents remain black. + } // Monster Ability table hr + table:first-of-type{ margin : 0; - column-span : 1; + column-span : none; color : @headerText; background-color : transparent; border-style : none; border-image : none; - -webkit-column-span : 1; + -webkit-column-span : none; tr { background-color : transparent; } @@ -404,7 +414,7 @@ body { //Full Width .monster.wide{ - .useColumns(0.96); + .useColumns(0.96, @fillMode: balance); } //***************************** @@ -501,6 +511,9 @@ body { break-after : always; -moz-column-break-after : always; break-before : column; + &+* { + margin-top: 0; + } } //Avoid breaking up blockquote,table{ @@ -561,22 +574,32 @@ body { column-span : all; -webkit-column-span : all; -moz-column-span : all; + display : block; + margin-bottom : 0.34cm; + &+* { + margin-top : 0; + } } //***************************** // * CLASS TABLE // *****************************/ .page .classTable{ - margin-top : 25px; - margin-bottom : 40px; - border-collapse : separate; - background-color : white; - border : initial; - border-style : solid; - border-image-outset : 25px 17px; - border-image-repeat : stretch; - border-image-slice : 150 200 150 200; - border-image-source : @frameBorderImage; - border-image-width : 47px; + th[colspan]:not([rowspan]) { + white-space : nowrap; + } + &.frame { + margin-top : 25px; + margin-bottom : 40px; + border-collapse : separate; + background-color : white; + border : initial; + border-style : solid; + border-image-outset : 25px 17px; + border-image-repeat : stretch; + border-image-slice : 150 200 150 200; + border-image-source : @frameBorderImage; + border-image-width : 47px; + } h5{ margin-bottom : 10px; } @@ -650,7 +673,7 @@ body { } } &.wide{ - .useColumns(0.96); + .useColumns(0.96, @fillMode: balance); } } @@ -665,7 +688,6 @@ body { .inline-block { display : inline-block; text-indent : initial; - line-height : 1.3em; } div { column-gap : 0.5cm; //Default spacing if a div uses multicolumns @@ -680,15 +702,18 @@ body { line-height : 1.3em; padding-left : 1em; text-indent : -1em; + & + * { + margin-top : 0.28cm; + } + & + dl { + margin-top : 0; + } } dl + * { - margin-top : 0.28cm; - } - dl + p { - margin-top : 0.5em; + margin-top : 0.17cm; } p + dl { - margin-top: -0.5em; + margin-top: 0.17cm; } dt { display : inline; diff --git a/themes/5ePhbLegacy.style.less b/themes/5ePhbLegacy.style.less index d814d4036..254e612c3 100644 --- a/themes/5ePhbLegacy.style.less +++ b/themes/5ePhbLegacy.style.less @@ -231,11 +231,11 @@ body { // Monster Ability table hr+table{ margin : 0; - column-span : 1; + column-span : none; background-color : transparent; border-style : none; border-image : none; - -webkit-column-span : 1; + -webkit-column-span : none; tbody{ tr:nth-child(odd), tr:nth-child(even){ background-color : transparent; @@ -416,7 +416,7 @@ body { // * DESCRIPTIVE TEXT BOX // ************************************/ .phb .descriptive{ - display : block-inline; + display : inline-block; margin-bottom : 1em; background-color : #faf7ea; font-family : ScalySans;