0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-07 18:32:40 +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 # 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 ### Saturday, 28/6/2021 - v2.13.1
- Fixed the issue with new brews not saving! - Fixed the issue with new brews not saving!
@@ -44,6 +57,9 @@ myStyle {color: black}
##### G-Ambatte : ##### G-Ambatte :
- Snippet to remove drop caps (fancy first letter after title) - Snippet to remove drop caps (fancy first letter after title)
```
```
### Saturday, 13/3/2021 - v2.11.0 ### Saturday, 13/3/2021 - v2.11.0
- Many background things for upcoming v3. Get pumped. - Many background things for upcoming v3. Get pumped.
@@ -87,6 +103,8 @@ myStyle {color: black}
- Fixed issue with users unable to create new brews - Fixed issue with users unable to create new brews
- Fixing brews being lost when loaded via back button - Fixing brews being lost when loaded via back button
\page
### Wednesday, 07/10/2020 - v2.10.0 ### 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! - 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 ### Wednesday, 20/05/2020 - v2.9.0
- Major refactoring of site backend to work with updated dependencies for security (should be invisible to users) - 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 ### Wednesday, 11/03/2020 - v2.8.2
- Fixed delete button removing everyone's copy for brews with multiple authors - Fixed delete button removing everyone's copy for brews with multiple authors
- Compressed homebrew text in database - Compressed homebrew text in database
@@ -180,6 +191,8 @@ myStyle {color: black}
- Added a hover tooltip to fully read the brew description - 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. - Made the brew items take up only 25% allowing you to view more per row.
\page
### Wednesday, 23/11/2016 - v2.5.0 ### Wednesday, 23/11/2016 - v2.5.0
- Metadata can now be added to brews - Metadata can now be added to brews
- Added a metadata editor onto the edit and new pages - 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 - 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. - Added the ability to use ctrl+p and ctrl+s to print and save respectively.
\page
### Monday, 07/11/2016 ### Monday, 07/11/2016
- Added final touches to the html validator and updating the rest of the branch - 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. - 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 ### Wednesday, 25/05/2016 -v2.0.5
- The class table generators have the proper ability score improvement progression. - The class table generators have the proper ability score improvement progression.
\page
### Tuesday, 24/05/2016 - v2.0.4 ### Tuesday, 24/05/2016 - v2.0.4
- Fixed extra wide monster stat blocks sometimes only being one column - 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) - 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) - 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. - 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!) ### 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. 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>; </div>;
}, },
renderStyle : function() {
if(!this.props.style) return;
return <div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style> ${this.props.style} </style>` }} />;
},
renderPage : function(pageText, index){ renderPage : function(pageText, index){
if(this.props.renderer == 'legacy') if(this.props.renderer == 'legacy')
return <div className='phb page' id={`p${index + 1}`} dangerouslySetInnerHTML={{ __html: MarkdownLegacy.render(pageText) }} key={index} />; 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>
<div className='pages' ref='pages'> <div className='pages' ref='pages'>
{/* Apply CSS from Style tab */} {/* Apply CSS from Style tab and render pages from Markdown tab */}
<div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style> ${this.props.style} </style>` }} />
{/* Render pages from Markdown tab */}
{this.state.isMounted {this.state.isMounted
? this.renderPages() &&
: null} <>
{this.renderStyle()}
{this.renderPages()}
</>
}
</div> </div>
</div> </div>
</Frame> </Frame>

View File

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

View File

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

View File

@@ -33,7 +33,7 @@ module.exports = [
{ {
name : 'Horizontal Spacing', name : 'Horizontal Spacing',
icon : 'fas fa-arrows-alt-h', icon : 'fas fa-arrows-alt-h',
gen : ' {{width="100px"}} ' gen : ' {{width:100px}} '
}, },
{ {
name : 'Wide Block', name : 'Wide Block',
@@ -51,35 +51,34 @@ module.exports = [
name : 'Image', name : 'Image',
icon : 'fas fa-image', icon : 'fas fa-image',
gen : dedent` 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` Credit: Kyounghwan Kim`
}, },
{ {
name : 'Background Image', name : 'Background Image',
icon : 'fas fa-tree', 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', name : 'QR Code',
icon : 'fas fa-qrcode', icon : 'fas fa-qrcode',
gen : (brew)=>{ gen : (brew)=>{
return `<img ` + return `![]` +
`src='https://api.qrserver.com/v1/create-qr-code/?data=` + `(https://api.qrserver.com/v1/create-qr-code/?data=` +
`https://homebrewery.naturalcrit.com/share/${brew.shareId}` + `https://homebrewery.naturalcrit.com/share/${brew.shareId}` +
`&amp;size=100x100' ` + `&amp;size=100x100) {width:100px;mix-blend-mode:multiply}`;
`style='width:100px;mix-blend-mode:multiply'/>`;
} }
}, },
{ {
name : 'Page Number', name : 'Page Number',
icon : 'fas fa-bookmark', 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', name : 'Auto-incrementing Page Number',
icon : 'fas fa-sort-numeric-down', 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', name : 'Link to page',
@@ -257,7 +256,7 @@ module.exports = [
gen : function(){ gen : function(){
return dedent` return dedent`
##### Typical Difficulty Classes ##### Typical Difficulty Classes
{{column-count="2" {{column-count:2
| Task Difficulty | DC | | Task Difficulty | DC |
|:----------------|:--:| |:----------------|:--:|
| Very easy | 5 | | 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='/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='/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/: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='/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/: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='/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='/changelog' exact component={()=><SharePage brew={this.props.brew} />}/>
<Route path='/' component={()=><HomePage welcomeText={this.props.welcomeText}/>}/> <Route path='/' component={()=><HomePage brew={this.props.brew} />}/>
</Switch> </Switch>
</div> </div>
</Router> </Router>

View File

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

View File

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

20
package-lock.json generated
View File

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

View File

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

View File

@@ -105,6 +105,25 @@ app.get('/robots.txt', (req, res)=>{
return res.sendFile(`${__dirname}/robots.txt`); 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 //Source page
app.get('/source/:id', asyncHandler(async (req, res)=>{ app.get('/source/:id', asyncHandler(async (req, res)=>{
const brew = await getBrewFromId(req.params.id, 'raw'); const brew = await getBrewFromId(req.params.id, 'raw');
@@ -170,6 +189,7 @@ app.get('/edit/:id', asyncHandler(async (req, res, next)=>{
//New Page //New Page
app.get('/new/:id', asyncHandler(async (req, res, next)=>{ app.get('/new/:id', asyncHandler(async (req, res, next)=>{
const brew = await getBrewFromId(req.params.id, 'share'); const brew = await getBrewFromId(req.params.id, 'share');
brew.title = `CLONE - ${brew.title}`;
req.brew = brew; req.brew = brew;
return next(); return next();
})); }));
@@ -204,8 +224,6 @@ app.use((req, res)=>{
const props = { const props = {
version : require('./package.json').version, version : require('./package.json').version,
url : req.originalUrl, url : req.originalUrl,
welcomeText : welcomeText,
changelog : changelogText,
brew : req.brew, brew : req.brew,
brews : req.brews, brews : req.brews,
googleBrews : req.googleBrews, 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 start(src) { return src.match(/{{[^{]/)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) { tokenizer(src, tokens) {
const completeSpan = /^{{[^\n]*}}/; // Regex for the complete token 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); const match = completeSpan.exec(src);
if(match) { if(match) {
//Find closing delimiter //Find closing delimiter
let blockCount = 0; let blockCount = 0;
let tags = ''; let tags = '';
let endIndex = 0; let endTags = 0;
let endToken = 0;
let delim; let delim;
while (delim = inlineRegex.exec(match[0])) { while (delim = inlineRegex.exec(match[0])) {
if(!tags) {
tags = ` ${processStyleTags(delim[0].substring(2))}`;
endTags = delim[0].length;
}
if(delim[0].startsWith('{{')) { if(delim[0].startsWith('{{')) {
tags = tags || ` ${processStyleTags(delim[0].substring(2))}`;
blockCount++; blockCount++;
} else if(delim[0] == '}}' && blockCount !== 0) { } else if(delim[0] == '}}' && blockCount !== 0) {
blockCount--; blockCount--;
if(blockCount == 0) { if(blockCount == 0) {
endIndex = inlineRegex.lastIndex; endToken = inlineRegex.lastIndex;
break; break;
} }
} }
} }
if(endIndex) { if(endToken) {
const raw = src.slice(0, endIndex); const raw = src.slice(0, endToken);
const text = raw.slice((raw.indexOf(' ')+1) || -2, -2); const text = raw.slice(endTags || -2, -2);
return { // Token to generate return { // Token to generate
type : 'mustacheSpans', // Should match "name" above type : 'mustacheSpans', // Should match "name" above
@@ -74,33 +78,37 @@ const mustacheSpans = {
const mustacheDivs = { const mustacheDivs = {
name : 'mustacheDivs', name : 'mustacheDivs',
level : 'block', 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) { tokenizer(src, tokens) {
const completeBlock = /^ *{{.*\n *}}/s; // Regex for the complete token const completeBlock = /^ *{{[^\n}]* *\n.*\n *}}/s; // Regex for the complete token
const blockRegex = /^ *{{(?:="[\w,\-(). ]*"|[^"'{}\s])*$|^ *}}$/gm; const blockRegex = /^ *{{(?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])* *$|^ *}}$/gm;
const match = completeBlock.exec(src); const match = completeBlock.exec(src);
if(match) { if(match) {
//Find closing delimiter //Find closing delimiter
let blockCount = 0; let blockCount = 0;
let tags = ''; let tags = '';
let endIndex = 0; let endTags = 0;
let endToken = 0;
let delim; let delim;
while (delim = blockRegex.exec(match[0])?.[0].trim()) { while (delim = blockRegex.exec(match[0])?.[0].trim()) {
if(!tags) {
tags = ` ${processStyleTags(delim.substring(2))}`;
endTags = delim.length;
}
if(delim.startsWith('{{')) { if(delim.startsWith('{{')) {
tags = tags || ` ${processStyleTags(delim.substring(2))}`;
blockCount++; blockCount++;
} else if(delim == '}}' && blockCount !== 0) { } else if(delim == '}}' && blockCount !== 0) {
blockCount--; blockCount--;
if(blockCount == 0) { if(blockCount == 0) {
endIndex = blockRegex.lastIndex; endToken = blockRegex.lastIndex;
break; break;
} }
} }
} }
if(endIndex) { if(endToken) {
const raw = src.slice(0, endIndex); const raw = src.slice(0, endToken);
const text = raw.slice((raw.indexOf('\n')+1) || -2, -2); const text = raw.slice(endTags || -2, -2);
return { // Token to generate return { // Token to generate
type : 'mustacheDivs', // Should match "name" above type : 'mustacheDivs', // Should match "name" above
raw : raw, // Text to consume from the source raw : raw, // Text to consume from the source
@@ -121,7 +129,7 @@ const mustacheInjectInline = {
level : 'inline', level : 'inline',
start(src) { return src.match(/ *{[^{\n]/)?.index; }, // Hint to Marked.js to stop and check for a match start(src) { return src.match(/ *{[^{\n]/)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) { tokenizer(src, tokens) {
const inlineRegex = /^ *{((?:="[\w,\-(). ]*"|[^"'{}\s])*)}/g; const inlineRegex = /^ *{((?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])*)}/g;
const match = inlineRegex.exec(src); const match = inlineRegex.exec(src);
if(match) { if(match) {
const lastToken = tokens[tokens.length - 1]; const lastToken = tokens[tokens.length - 1];
@@ -154,9 +162,9 @@ const mustacheInjectBlock = {
extensions : [{ extensions : [{
name : 'mustacheInjectBlock', name : 'mustacheInjectBlock',
level : 'block', 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) { tokenizer(src, tokens) {
const inlineRegex = /^ *{((?:="[\w,\-(). ]*"|[^"'{}\s])*)}/ym; const inlineRegex = /^ *{((?::(?:"[\w,\-()#%. ]*"|[\w\,\-()#%.]*)|[^"'{}\s])*)}/ym;
const match = inlineRegex.exec(src); const match = inlineRegex.exec(src);
if(match) { if(match) {
const lastToken = tokens[tokens.length - 1]; const lastToken = tokens[tokens.length - 1];
@@ -313,13 +321,15 @@ const tagRegex = new RegExp(`(${
}).join('|')})`, 'g'); }).join('|')})`, 'g');
const processStyleTags = (string)=>{ 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 '"'; if(!tags) return '"';
const id = _.remove(tags, (tag)=>tag.startsWith('#')).map((tag)=>tag.slice(1))[0]; const id = _.remove(tags, (tag)=>tag.startsWith('#')).map((tag)=>tag.slice(1))[0];
const classes = _.remove(tags, (tag)=>!tag.includes('"')); const classes = _.remove(tags, (tag)=>!tag.includes(':'));
const styles = tags.map((tag)=>tag.replace(/="(.*)"/g, ':$1;')); const styles = tags.map((tag)=>tag.replace(/:"?([^"]*)"?/g, ':$1;'));
return `${classes.join(' ')}" ${id ? `id="${id}"` : ''} ${styles.length ? `style="${styles.join(' ')}"` : ''}`; return `${classes.join(' ')}" ${id ? `id="${id}"` : ''} ${styles.length ? `style="${styles.join(' ')}"` : ''}`;
}; };

View File

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