diff --git a/changelog.md b/changelog.md index 1cd0cd028..ea0cb7993 100644 --- a/changelog.md +++ b/changelog.md @@ -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. diff --git a/client/homebrew/brewRenderer/brewRenderer.jsx b/client/homebrew/brewRenderer/brewRenderer.jsx index e95ea3a9d..e3ab31742 100644 --- a/client/homebrew/brewRenderer/brewRenderer.jsx +++ b/client/homebrew/brewRenderer/brewRenderer.jsx @@ -122,6 +122,11 @@ const BrewRenderer = createClass({ ; }, + renderStyle : function() { + if(!this.props.style) return; + return
${this.props.style} ` }} />; + }, + renderPage : function(pageText, index){ if(this.props.renderer == 'legacy') return
; @@ -191,14 +196,14 @@ const BrewRenderer = createClass({
- {/* Apply CSS from Style tab */} - -
${this.props.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()} + + }
diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index 12cb0aa37..d43ae7c61 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -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(){ diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.less b/client/homebrew/editor/metadataEditor/metadataEditor.less index c3141f0d4..a58df14cd 100644 --- a/client/homebrew/editor/metadataEditor/metadataEditor.less +++ b/client/homebrew/editor/metadataEditor/metadataEditor.less @@ -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; } -} \ No newline at end of file +} diff --git a/client/homebrew/editor/snippetbar/snippets/snippets.js b/client/homebrew/editor/snippetbar/snippets/snippets.js index 9abedcf28..2ac8c7ea0 100644 --- a/client/homebrew/editor/snippetbar/snippets/snippets.js +++ b/client/homebrew/editor/snippetbar/snippets/snippets.js @@ -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 ``; + `&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 | diff --git a/client/homebrew/homebrew.jsx b/client/homebrew/homebrew.jsx index 94131b1dd..118c21e91 100644 --- a/client/homebrew/homebrew.jsx +++ b/client/homebrew/homebrew.jsx @@ -44,12 +44,12 @@ const Homebrew = createClass({ }/> }/> }/> - }/> + }/> }/> - } /> - } /> - }/> - }/> + }/> + }/> + }/> + }/>
diff --git a/client/homebrew/pages/homePage/homePage.jsx b/client/homebrew/pages/homePage/homePage.jsx index d1e749482..b8ee5634b 100644 --- a/client/homebrew/pages/homePage/homePage.jsx +++ b/client/homebrew/pages/homePage/homePage.jsx @@ -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({ -
+
Save current
diff --git a/client/homebrew/pages/newPage/newPage.jsx b/client/homebrew/pages/newPage/newPage.jsx index bbbf663ec..76cab5292 100644 --- a/client/homebrew/pages/newPage/newPage.jsx +++ b/client/homebrew/pages/newPage/newPage.jsx @@ -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) })); diff --git a/package-lock.json b/package-lock.json index 140fd0a27..4cb47f5c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index e31d91cea..b002c1965 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.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", diff --git a/server.js b/server.js index 45e073065..ce4ee046e 100644 --- a/server.js +++ b/server.js @@ -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, diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index 580ba5b5a..9ae71214a 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -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(' ')}"` : ''}`; }; diff --git a/themes/5ePhb.style.less b/themes/5ePhb.style.less index a1395554d..a5a3b6aa5 100644 --- a/themes/5ePhb.style.less +++ b/themes/5ePhb.style.less @@ -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