0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-03 10:32:39 +00:00

Merge branch 'master' into Issue_1958

This commit is contained in:
David Bolack
2024-01-25 00:18:47 -06:00
committed by GitHub
24 changed files with 15980 additions and 15634 deletions

View File

@@ -45,12 +45,13 @@ let rawPages = [];
const BrewRenderer = (props)=>{ const BrewRenderer = (props)=>{
props = { props = {
text : '', text : '',
style : '', style : '',
renderer : 'legacy', renderer : 'legacy',
theme : '5ePHB', theme : '5ePHB',
lang : '', lang : '',
errors : [], errors : [],
currentEditorPage : 0,
...props ...props
}; };
@@ -94,6 +95,9 @@ const BrewRenderer = (props)=>{
if(Math.abs(index - state.viewablePageNumber) <= 3) if(Math.abs(index - state.viewablePageNumber) <= 3)
return true; return true;
if(index + 1 == props.currentEditorPage)
return true;
return false; return false;
}; };
@@ -143,6 +147,9 @@ const BrewRenderer = (props)=>{
if(props.errors && props.errors.length) if(props.errors && props.errors.length)
return renderedPages; return renderedPages;
if(rawPages.length != renderedPages.length) // Re-render all pages when page count changes
renderedPages.length = 0;
_.forEach(rawPages, (page, index)=>{ _.forEach(rawPages, (page, index)=>{
if((shouldRender(index) || !renderedPages[index]) && typeof window !== 'undefined'){ if((shouldRender(index) || !renderedPages[index]) && typeof window !== 'undefined'){
renderedPages[index] = renderPage(page, index); // Render any page not yet rendered, but only re-render those in PPR range renderedPages[index] = renderPage(page, index); // Render any page not yet rendered, but only re-render those in PPR range

View File

@@ -3,9 +3,9 @@
.brewRenderer { .brewRenderer {
will-change : transform; will-change : transform;
overflow-y : scroll; overflow-y : scroll;
.pages { :where(.pages) {
margin : 30px 0px; margin : 30px 0px;
& > .page { & > :where(.page) {
width : 215.9mm; width : 215.9mm;
height : 279.4mm; height : 279.4mm;
margin-right : auto; margin-right : auto;

View File

@@ -180,7 +180,7 @@ const Editor = createClass({
// Highlight injectors {style} // Highlight injectors {style}
if(line.includes('{') && line.includes('}')){ if(line.includes('{') && line.includes('}')){
const regex = /(?:^|[^{\n])({(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\2})/gm; const regex = /(?:^|[^{\n])({(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\2})/gm;
let match; let match;
while ((match = regex.exec(line)) != null) { while ((match = regex.exec(line)) != null) {
codeMirror.markText({ line: lineNumber, ch: line.indexOf(match[1]) }, { line: lineNumber, ch: line.indexOf(match[1]) + match[1].length }, { className: 'injection' }); codeMirror.markText({ line: lineNumber, ch: line.indexOf(match[1]) }, { line: lineNumber, ch: line.indexOf(match[1]) + match[1].length }, { className: 'injection' });
@@ -188,7 +188,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,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1 *|}}/g; const regex = /{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1 *|}}/g;
let match; let match;
let blockCount = 0; let blockCount = 0;
while ((match = regex.exec(line)) != null) { while ((match = regex.exec(line)) != null) {
@@ -207,7 +207,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,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1 *$|^ *}}$/); const match = line.match(/^ *{{(?=((?:[:=](?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':={}\s]*)*))\1 *$|^ *}}$/);
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' });

View File

@@ -2,7 +2,7 @@
.metadataEditor{ .metadataEditor{
position : absolute; position : absolute;
z-index : 10000; z-index : 5;
box-sizing : border-box; box-sizing : border-box;
width : 100%; width : 100%;
padding : 25px; padding : 25px;

View File

@@ -74,6 +74,7 @@ const Snippetbar = createClass({
} }
}, },
mergeCustomizer : function(valueA, valueB, key) { mergeCustomizer : function(valueA, valueB, key) {
if(key == 'snippets') { if(key == 'snippets') {
const result = _.reverse(_.unionBy(_.reverse(valueB), _.reverse(valueA), 'name')); // Join snippets together, with preference for the current theme over the base theme const result = _.reverse(_.unionBy(_.reverse(valueB), _.reverse(valueA), 'name')); // Join snippets together, with preference for the current theme over the base theme
@@ -102,10 +103,12 @@ const Snippetbar = createClass({
this.props.onInject(injectedText); this.props.onInject(injectedText);
}, },
toggleThemeSelector : function(){ toggleThemeSelector : function(e){
this.setState({ if(e.target.tagName != 'SELECT'){
themeSelector : !this.state.themeSelector this.setState({
}); themeSelector : !this.state.themeSelector
});
}
}, },
changeTheme : function(e){ changeTheme : function(e){
@@ -119,7 +122,7 @@ const Snippetbar = createClass({
renderThemeSelector : function(){ renderThemeSelector : function(){
return <div className='themeSelector'> return <div className='themeSelector'>
<select value={this.props.currentEditorTheme} onChange={this.changeTheme} onMouseDown={(this.changeTheme)}> <select value={this.props.currentEditorTheme} onChange={this.changeTheme} >
{EditorThemes.map((theme, key)=>{ {EditorThemes.map((theme, key)=>{
return <option key={key} value={theme}>{theme}</option>; return <option key={key} value={theme}>{theme}</option>;
})} })}
@@ -176,8 +179,9 @@ const Snippetbar = createClass({
<div className={`editorTool editorTheme ${this.state.themeSelector ? 'active' : ''}`} <div className={`editorTool editorTheme ${this.state.themeSelector ? 'active' : ''}`}
onClick={this.toggleThemeSelector} > onClick={this.toggleThemeSelector} >
<i className='fas fa-palette' /> <i className='fas fa-palette' />
{this.state.themeSelector && this.renderThemeSelector()}
</div> </div>
{this.state.themeSelector && this.renderThemeSelector()}
<div className='divider'></div> <div className='divider'></div>
<div className={cx('text', { selected: this.props.view === 'text' })} <div className={cx('text', { selected: this.props.view === 'text' })}
onClick={()=>this.props.onViewChange('text')}> onClick={()=>this.props.onViewChange('text')}>
@@ -228,7 +232,7 @@ const SnippetGroup = createClass({
return _.map(snippets, (snippet)=>{ return _.map(snippets, (snippet)=>{
return <div className='snippet' key={snippet.name} onClick={(e)=>this.handleSnippetClick(e, snippet)}> return <div className='snippet' key={snippet.name} onClick={(e)=>this.handleSnippetClick(e, snippet)}>
<i className={snippet.icon} /> <i className={snippet.icon} />
<span className='name'>{snippet.name}</span> <span className='name'title={snippet.name}>{snippet.name}</span>
{snippet.experimental && <span className='beta'>beta</span>} {snippet.experimental && <span className='beta'>beta</span>}
{snippet.subsnippets && <> {snippet.subsnippets && <>
<i className='fas fa-caret-right'></i> <i className='fas fa-caret-right'></i>

View File

@@ -1,177 +1,190 @@
@import (less) './client/icons/customIcons.less'; @import (less) './client/icons/customIcons.less';
.snippetBar{ @import (less) '././././themes/fonts/5e/fonts.less';
.snippetBar {
@menuHeight : 25px; @menuHeight : 25px;
position : relative; position : relative;
height : @menuHeight; height : @menuHeight;
background-color : #ddd; color : black;
.editors{ background-color : #DDDDDD;
.editors {
position : absolute; position : absolute;
display : flex;
top : 0px; top : 0px;
right : 0px; right : 0px;
height : @menuHeight; display : flex;
justify-content : space-between; justify-content : space-between;
&>div{ height : @menuHeight;
height : @menuHeight; & > div {
width : @menuHeight; width : @menuHeight;
cursor : pointer; height : @menuHeight;
line-height : @menuHeight; line-height : @menuHeight;
text-align : center; text-align : center;
&:hover,&.selected{ cursor : pointer;
background-color : #999; &:hover,&.selected { background-color : #999999; }
} &.text {
&.text{
.tooltipLeft('Brew Editor'); .tooltipLeft('Brew Editor');
} }
&.style{ &.style {
.tooltipLeft('Style Editor'); .tooltipLeft('Style Editor');
} }
&.meta{ &.meta {
.tooltipLeft('Properties'); .tooltipLeft('Properties');
} }
&.undo{ &.undo {
.tooltipLeft('Undo'); .tooltipLeft('Undo');
font-size : 0.75em; font-size : 0.75em;
color : grey; color : grey;
&.active{ &.active { color : inherit; }
color : black;
}
} }
&.redo{ &.redo {
.tooltipLeft('Redo'); .tooltipLeft('Redo');
font-size : 0.75em; font-size : 0.75em;
color : grey; color : grey;
&.active{ &.active { color : inherit; }
color : black;
}
} }
&.foldAll{ &.foldAll {
.tooltipLeft('Fold All'); .tooltipLeft('Fold All');
font-size : 0.75em; font-size : 0.75em;
color : grey; color : inherit;
&.active{
color : black;
}
} }
&.unfoldAll{ &.unfoldAll {
.tooltipLeft('Unfold All'); .tooltipLeft('Unfold All');
font-size : 0.75em; font-size : 0.75em;
color : grey; color : inherit;
&.active{
color : black;
}
} }
&.editorTheme{ &.editorTheme {
.tooltipLeft('Editor Themes'); .tooltipLeft('Editor Themes');
font-size : 0.75em; font-size : 0.75em;
color : black; color : black;
&.active{ &.active {
color : white; position : relative;
background-color: black; background-color : #999999;
} }
} }
&.divider { &.divider {
background: linear-gradient(#000, #000) no-repeat center/1px 100%; width : 5px;
width: 5px; background : linear-gradient(currentColor, currentColor) no-repeat center/1px 100%;
&:hover{ &:hover { background-color : inherit; }
background-color: inherit;
}
} }
} }
.themeSelector{ .themeSelector {
position: absolute; position : absolute;
left: -65px; top : 25px;
top: 30px; right : 0;
z-index: 999; z-index : 10;
width: 170px; display : flex;
background-color: black; align-items : center;
border-radius: 5px; justify-content : center;
width : 170px;
height : inherit;
background-color : inherit;
} }
} }
.snippetBarButton{ .snippetBarButton {
height : @menuHeight;
line-height : @menuHeight;
display : inline-block; display : inline-block;
height : @menuHeight;
padding : 0px 5px; padding : 0px 5px;
font-weight : 800;
font-size : 0.625em; font-size : 0.625em;
font-weight : 800;
line-height : @menuHeight;
text-transform : uppercase; text-transform : uppercase;
cursor : pointer; cursor : pointer;
&:hover, &.selected{ &:hover, &.selected { background-color : #999999; }
background-color : #999; i {
}
i{
vertical-align : middle;
margin-right : 3px; margin-right : 3px;
font-size : 1.4em; font-size : 1.4em;
vertical-align : middle;
} }
} }
.toggleMeta{ .toggleMeta {
position : absolute; position : absolute;
top : 0px; top : 0px;
right : 0px; right : 0px;
border-left : 1px solid black; border-left : 1px solid black;
.tooltipLeft("Edit Brew Properties"); .tooltipLeft('Edit Brew Properties');
} }
.snippetGroup{ .snippetGroup {
border-right : 1px solid black; border-right : 1px solid currentColor;
&:hover{ &:hover {
&>.dropdown{ & > .dropdown { visibility : visible; }
visibility : visible;
}
} }
.dropdown{ .dropdown {
position : absolute; position : absolute;
top : 100%; top : 100%;
visibility : hidden;
z-index : 1000; z-index : 1000;
margin-left : -5px;
padding : 0px; padding : 0px;
background-color : #ddd; margin-left : -5px;
.snippet{ visibility : hidden;
position: relative; background-color : #DDDDDD;
.animate(background-color); .snippet {
position : relative;
display : flex; display : flex;
align-items : center; align-items : center;
min-width : max-content; min-width : max-content;
padding : 5px; padding : 5px;
cursor : pointer;
font-size : 10px; font-size : 10px;
i{ cursor : pointer;
.animate(background-color);
i {
height : 1.2em;
margin-right : 8px; margin-right : 8px;
font-size : 1.2em; font-size : 1.2em;
height : 1.2em; & ~ i {
&~i{ margin-right : 0;
margin-right: 0; margin-left : 5px;
margin-left: 5px; }
/* Fonts */
&.font {
height : auto;
&::before {
font-size : 1.4em;
content : 'ABC';
}
&.OpenSans {font-family : 'OpenSans';}
&.CodeBold {font-family : 'CodeBold';}
&.CodeLight {font-family : 'CodeLight';}
&.ScalySansRemake {font-family : 'ScalySansRemake';}
&.BookInsanityRemake {font-family : 'BookInsanityRemake';}
&.MrEavesRemake {font-family : 'MrEavesRemake';}
&.SolberaImitationRemake {font-family : 'SolberaImitationRemake';}
&.ScalySansSmallCapsRemake {font-family : 'ScalySansSmallCapsRemake';}
&.WalterTurncoat {font-family : 'WalterTurncoat';}
&.Lato {font-family : 'Lato';}
&.Courier {font-family : 'Courier';}
&.NodestoCapsCondensed {font-family : 'NodestoCapsCondensed';}
&.Overpass {font-family : 'Overpass';}
&.Davek {font-family : 'Davek';}
&.Iokharic {font-family : 'Iokharic';}
&.Rellanic {font-family : 'Rellanic';}
&.TimesNewRoman {font-family : 'Times New Roman';}
} }
} }
.name { .name { margin-right : auto; }
margin-right : auto;
}
.beta { .beta {
color : white;
padding : 4px 6px;
line-height : 1em;
margin-left : 5px;
align-self : center; align-self : center;
padding : 4px 6px;
margin-left : 5px;
font-family : monospace;
line-height : 1em;
color : white;
background : grey; background : grey;
border-radius : 12px; border-radius : 12px;
font-family : monospace;
} }
&:hover{ &:hover {
background-color : #999; background-color : #999999;
&>.dropdown{ & > .dropdown {
visibility : visible; visibility : visible;
&.side { &.side {
left: 100%; top : 0%;
top: 0%; left : 100%;
margin-left:0; margin-left : 0;
box-shadow: -1px 1px 2px 0px #999; box-shadow : -1px 1px 2px 0px #999999;
} }
} }
} }
} }
} }
} }
} }

View File

@@ -21,10 +21,11 @@ const ErrorNavItem = createClass({
this.props.parent.setState(state); this.props.parent.setState(state);
}; };
const error = this.props.error; const error = this.props.error;
const response = error.response; const response = error.response;
const status = response.status; const status = response.status;
const message = response.body?.message; const HBErrorCode = response.body?.HBErrorCode;
const message = response.body?.message;
let errMsg = ''; let errMsg = '';
try { try {
errMsg += `${error.toString()}\n\n`; errMsg += `${error.toString()}\n\n`;
@@ -40,7 +41,9 @@ const ErrorNavItem = createClass({
{message ?? 'Conflict: please refresh to get latest changes'} {message ?? 'Conflict: please refresh to get latest changes'}
</div> </div>
</Nav.item>; </Nav.item>;
} else if(status === 412) { }
if(status === 412) {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'> return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops! Oops!
<div className='errorContainer' onClick={clearError}> <div className='errorContainer' onClick={clearError}>
@@ -48,6 +51,36 @@ const ErrorNavItem = createClass({
</div> </div>
</Nav.item>; </Nav.item>;
} }
if(HBErrorCode === '04') {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
You are no longer signed in as an author of
this brew! Were you signed out from a different
window? Visit our log in page, then try again!
<br></br>
<a target='_blank' rel='noopener noreferrer'
href={`https://www.naturalcrit.com/login?redirect=${window.location.href}`}>
<div className='confirm'>
Sign In
</div>
</a>
<div className='deny'>
Not Now
</div>
</div>
</Nav.item>;
}
if(response.body?.errors?.[0].reason == 'storageQuotaExceeded') {
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
Oops!
<div className='errorContainer' onClick={clearError}>
Can't save because your Google Drive seems to be full!
</div>
</Nav.item>;
}
if(response.req.url.match(/^\/api.*Google.*$/m)){ if(response.req.url.match(/^\/api.*Google.*$/m)){
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'> return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
@@ -57,6 +90,7 @@ const ErrorNavItem = createClass({
expired! Visit our log in page to sign out expired! Visit our log in page to sign out
and sign back in with Google, and sign back in with Google,
then try saving again! then try saving again!
<br></br>
<a target='_blank' rel='noopener noreferrer' <a target='_blank' rel='noopener noreferrer'
href={`https://www.naturalcrit.com/login?redirect=${window.location.href}`}> href={`https://www.naturalcrit.com/login?redirect=${window.location.href}`}>
<div className='confirm'> <div className='confirm'>

View File

@@ -4,6 +4,7 @@ const Nav = require('naturalcrit/nav/nav.jsx');
module.exports = function(props){ module.exports = function(props){
return <Nav.item return <Nav.item
href='/new' href='/new'
newTab={true}
color='purple' color='purple'
icon='fas fa-plus-square'> icon='fas fa-plus-square'>
new new

View File

@@ -220,6 +220,7 @@ const ListPage = createClass({
render : function(){ render : function(){
return <div className='listPage sitePage'> return <div className='listPage sitePage'>
{/*<style>@layer V3_5ePHB, bundle;</style>*/} {/*<style>@layer V3_5ePHB, bundle;</style>*/}
<link href='/themes/V3/Blank/style.css' rel='stylesheet'/>
<link href='/themes/V3/5ePHB/style.css' rel='stylesheet'/> <link href='/themes/V3/5ePHB/style.css' rel='stylesheet'/>
{this.props.navItems} {this.props.navItems}
{this.renderSortOptions()} {this.renderSortOptions()}

View File

@@ -2,17 +2,18 @@
.noColumns(){ .noColumns(){
column-count : auto; column-count : auto;
column-fill : auto; column-fill : auto;
column-gap : auto; column-gap : normal;
column-width : auto; column-width : auto;
-webkit-column-count : auto; -webkit-column-count : auto;
-moz-column-count : auto; -moz-column-count : auto;
-webkit-column-width : auto; -webkit-column-width : auto;
-moz-column-width : auto; -moz-column-width : auto;
-webkit-column-gap : auto; -webkit-column-gap : normal;
-moz-column-gap : auto; -moz-column-gap : normal;
height : auto; height : auto;
min-height : 279.4mm; min-height : 279.4mm;
margin : 20px auto; margin : 20px auto;
contain : unset;
} }
.listPage{ .listPage{
.content{ .content{

View File

@@ -50,7 +50,8 @@ const EditPage = createClass({
url : '', url : '',
autoSave : true, autoSave : true,
autoSaveWarning : false, autoSaveWarning : false,
unsavedTime : new Date() unsavedTime : new Date(),
currentEditorPage : 0
}; };
}, },
savedBrew : null, savedBrew : null,
@@ -109,9 +110,10 @@ const EditPage = createClass({
if(htmlErrors.length) htmlErrors = Markdown.validate(text); if(htmlErrors.length) htmlErrors = Markdown.validate(text);
this.setState((prevState)=>({ this.setState((prevState)=>({
brew : { ...prevState.brew, text: text }, brew : { ...prevState.brew, text: text },
isPending : true, isPending : true,
htmlErrors : htmlErrors htmlErrors : htmlErrors,
currentEditorPage : this.refs.editor.getCurrentPage()
}), ()=>{if(this.state.autoSave) this.trySave();}); }), ()=>{if(this.state.autoSave) this.trySave();});
}, },
@@ -405,6 +407,7 @@ const EditPage = createClass({
theme={this.state.brew.theme} theme={this.state.brew.theme}
errors={this.state.htmlErrors} errors={this.state.htmlErrors}
lang={this.state.brew.lang} lang={this.state.brew.lang}
currentEditorPage={this.state.currentEditorPage}
/> />
</SplitPane> </SplitPane>
</div> </div>

30032
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@
"description": "Create authentic looking D&D homebrews using only markdown", "description": "Create authentic looking D&D homebrews using only markdown",
"version": "3.10.0", "version": "3.10.0",
"engines": { "engines": {
"npm": "^10.2.x", "npm": "^10.2.x",
"node": "^20.8.x" "node": "^20.8.x"
}, },
"repository": { "repository": {
@@ -79,11 +79,11 @@
] ]
}, },
"dependencies": { "dependencies": {
"@babel/core": "^7.23.5", "@babel/core": "^7.23.7",
"@babel/plugin-transform-runtime": "^7.23.4", "@babel/plugin-transform-runtime": "^7.23.7",
"@babel/preset-env": "^7.23.5", "@babel/preset-env": "^7.23.8",
"@babel/preset-react": "^7.23.3", "@babel/preset-react": "^7.23.3",
"@googleapis/drive": "^8.4.0", "@googleapis/drive": "^8.6.0",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
"classnames": "^2.3.2", "classnames": "^2.3.2",
"codemirror": "^5.65.6", "codemirror": "^5.65.6",
@@ -98,26 +98,26 @@
"jwt-simple": "^0.5.6", "jwt-simple": "^0.5.6",
"less": "^3.13.1", "less": "^3.13.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"marked": "5.1.1", "marked": "11.1.1",
"marked-extended-tables": "^1.0.7", "marked-extended-tables": "^1.0.8",
"marked-gfm-heading-id": "^3.1.2", "marked-gfm-heading-id": "^3.1.2",
"marked-smartypants-lite": "^1.0.1", "marked-smartypants-lite": "^1.0.2",
"markedLegacy": "npm:marked@^0.3.19", "markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.29.4", "moment": "^2.30.1",
"mongoose": "^8.0.2", "mongoose": "^8.1.0",
"nanoid": "3.3.4", "nanoid": "3.3.4",
"nconf": "^0.12.1", "nconf": "^0.12.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-frame-component": "^4.1.3", "react-frame-component": "^4.1.3",
"react-router-dom": "6.20.1", "react-router-dom": "6.21.3",
"sanitize-filename": "1.6.3", "sanitize-filename": "1.6.3",
"superagent": "^8.1.2", "superagent": "^8.1.2",
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git" "vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^8.55.0", "eslint": "^8.56.0",
"eslint-plugin-jest": "^27.6.0", "eslint-plugin-jest": "^27.6.3",
"eslint-plugin-react": "^7.33.2", "eslint-plugin-react": "^7.33.2",
"jest": "^29.7.0", "jest": "^29.7.0",
"jest-expect-message": "^1.1.3", "jest-expect-message": "^1.1.3",
@@ -126,6 +126,6 @@
"stylelint-config-recess-order": "^4.4.0", "stylelint-config-recess-order": "^4.4.0",
"stylelint-config-recommended": "^13.0.0", "stylelint-config-recommended": "^13.0.0",
"stylelint-stylistic": "^0.4.3", "stylelint-stylistic": "^0.4.3",
"supertest": "^6.3.3" "supertest": "^6.3.4"
} }
} }

View File

@@ -26,7 +26,6 @@
"codemirror/addon/edit/trailingspace.js", "codemirror/addon/edit/trailingspace.js",
"codemirror/addon/selection/active-line.js", "codemirror/addon/selection/active-line.js",
"moment", "moment",
"superagent", "superagent"
"marked"
] ]
} }

View File

@@ -476,9 +476,18 @@ const getPureError = (error)=>{
}; };
app.use(async (err, req, res, next)=>{ app.use(async (err, req, res, next)=>{
const status = err.status || err.code || 500; err.originalUrl = req.originalUrl;
console.error(err); console.error(err);
if(err.originalUrl?.startsWith('/api/')) {
// console.log('API error');
res.status(err.status || err.response?.status || 500).send(err);
return;
}
// console.log('non-API error');
const status = err.status || err.code || 500;
req.ogMeta = { ...defaultMetaTags, req.ogMeta = { ...defaultMetaTags,
title : 'Error Page', title : 'Error Page',
description : 'Something went wrong!' description : 'Something went wrong!'

View File

@@ -7,7 +7,7 @@ const cx = require('classnames');
const closeTag = require('./close-tag'); const closeTag = require('./close-tag');
let CodeMirror; let CodeMirror;
if(typeof navigator !== 'undefined'){ if(typeof window !== 'undefined'){
CodeMirror = require('codemirror'); CodeMirror = require('codemirror');
//Language Modes //Language Modes

View File

@@ -28,13 +28,35 @@ renderer.paragraph = function(text){
return `<p>${text}</p>\n`; return `<p>${text}</p>\n`;
}; };
//Fix local links in the Preview iFrame to link inside the frame
renderer.link = function (href, title, text) {
let self = false;
if(href[0] == '#') {
self = true;
}
href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
if(href === null) {
return text;
}
let out = `<a href="${escape(href)}"`;
if(title) {
out += ` title="${title}"`;
}
if(self) {
out += ' target="_self"';
}
out += `>${text}</a>`;
return out;
};
const mustacheSpans = { const mustacheSpans = {
name : 'mustacheSpans', name : 'mustacheSpans',
level : 'inline', // Is this a block-level or inline-level tokenizer? level : 'inline', // Is this a block-level or inline-level tokenizer?
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,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1 *|}}/g; const inlineRegex = /{{(?=((?:[:=](?:"['\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"=':{}\s]*)*))\1 *|}}/g;
const match = completeSpan.exec(src); const match = completeSpan.exec(src);
if(match) { if(match) {
//Find closing delimiter //Find closing delimiter
@@ -45,7 +67,7 @@ const mustacheSpans = {
let delim; let delim;
while (delim = inlineRegex.exec(match[0])) { while (delim = inlineRegex.exec(match[0])) {
if(!tags) { if(!tags) {
tags = ` ${processStyleTags(delim[0].substring(2))}`; tags = `${processStyleTags(delim[0].substring(2))}`;
endTags = delim[0].length; endTags = delim[0].length;
} }
if(delim[0].startsWith('{{')) { if(delim[0].startsWith('{{')) {
@@ -84,7 +106,7 @@ const mustacheDivs = {
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 *{{[^{]/m)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) { tokenizer(src, tokens) {
const completeBlock = /^ *{{[^\n}]* *\n.*\n *}}/s; // Regex for the complete token const completeBlock = /^ *{{[^\n}]* *\n.*\n *}}/s; // Regex for the complete token
const blockRegex = /^ *{{(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1 *$|^ *}}$/gm; const blockRegex = /^ *{{(?=((?:[:=](?:"['\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"=':{}\s]*)*))\1 *$|^ *}}$/gm;
const match = completeBlock.exec(src); const match = completeBlock.exec(src);
if(match) { if(match) {
//Find closing delimiter //Find closing delimiter
@@ -95,8 +117,8 @@ const mustacheDivs = {
let delim; let delim;
while (delim = blockRegex.exec(match[0])?.[0].trim()) { while (delim = blockRegex.exec(match[0])?.[0].trim()) {
if(!tags) { if(!tags) {
tags = ` ${processStyleTags(delim.substring(2))}`; tags = `${processStyleTags(delim.substring(2))}`;
endTags = delim.length; endTags = delim.length + src.indexOf(delim);
} }
if(delim.startsWith('{{')) { if(delim.startsWith('{{')) {
blockCount++; blockCount++;
@@ -132,14 +154,14 @@ 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,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1}/g; const inlineRegex = /^ *{(?=((?:[:=](?:"['\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"=':{}\s]*)*))\1}/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];
if(!lastToken || lastToken.type == 'mustacheInjectInline') if(!lastToken || lastToken.type == 'mustacheInjectInline')
return false; return false;
const tags = ` ${processStyleTags(match[1])}`; const tags = `${processStyleTags(match[1])}`;
lastToken.originalType = lastToken.type; lastToken.originalType = lastToken.type;
lastToken.type = 'mustacheInjectInline'; lastToken.type = 'mustacheInjectInline';
lastToken.tags = tags; lastToken.tags = tags;
@@ -167,7 +189,7 @@ const mustacheInjectBlock = {
level : 'block', level : 'block',
start(src) { return src.match(/\n *{[^{\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,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1}/ym; const inlineRegex = /^ *{(?=((?:[:=](?:"['\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"=':{}\s]*)*))\1}/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];
@@ -175,7 +197,7 @@ const mustacheInjectBlock = {
return false; return false;
lastToken.originalType = 'mustacheInjectBlock'; lastToken.originalType = 'mustacheInjectBlock';
lastToken.tags = ` ${processStyleTags(match[1])}`; lastToken.tags = `${processStyleTags(match[1])}`;
return { return {
type : 'mustacheInjectBlock', // Should match "name" above type : 'mustacheInjectBlock', // Should match "name" above
raw : match[0], // Text to consume from the source raw : match[0], // Text to consume from the source
@@ -271,28 +293,6 @@ Marked.use(mustacheInjectBlock);
Marked.use({ renderer: renderer, mangle: false }); Marked.use({ renderer: renderer, mangle: false });
Marked.use(MarkedExtendedTables(), MarkedGFMHeadingId(), MarkedSmartypantsLite()); Marked.use(MarkedExtendedTables(), MarkedGFMHeadingId(), MarkedSmartypantsLite());
//Fix local links in the Preview iFrame to link inside the frame
renderer.link = function (href, title, text) {
let self = false;
if(href[0] == '#') {
self = true;
}
href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
if(href === null) {
return text;
}
let out = `<a href="${escape(href)}"`;
if(title) {
out += ` title="${title}"`;
}
if(self) {
out += ' target="_self"';
}
out += `>${text}</a>`;
return out;
};
const nonWordAndColonTest = /[^\w:]/g; const nonWordAndColonTest = /[^\w:]/g;
const cleanUrl = function (sanitize, base, href) { const cleanUrl = function (sanitize, base, href) {
if(sanitize) { if(sanitize) {
@@ -354,16 +354,19 @@ const voidTags = new Set([
]); ]);
const processStyleTags = (string)=>{ const processStyleTags = (string)=>{
//split tags up. quotes can only occur right after colons. //split tags up. quotes can only occur right after : or =.
//TODO: can we simplify to just split on commas? //TODO: can we simplify to just split on commas?
const tags = string.match(/(?:[^, ":]+|:(?:"[^"]*"|))+/g); 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(':')) && (!tag.includes('=')));
const attributes = _.remove(tags, (tag)=>(tag.includes('='))).map((tag)=>tag.replace(/="?([^"]*)"?/g, '="$1"'));
const styles = tags?.length ? tags.map((tag)=>tag.replace(/:"?([^"]*)"?/g, ':$1;').trim()) : [];
const id = _.remove(tags, (tag)=>tag.startsWith('#')).map((tag)=>tag.slice(1))[0]; return `${classes?.length ? ` ${classes.join(' ')}` : ''}"` +
const classes = _.remove(tags, (tag)=>!tag.includes(':')); `${id ? ` id="${id}"` : ''}` +
const styles = tags.map((tag)=>tag.replace(/:"?([^"]*)"?/g, ':$1;')); `${styles?.length ? ` style="${styles.join(' ')}"` : ''}` +
return `${classes.join(' ')}" ${id ? `id="${id}"` : ''} ${styles.length ? `style="${styles.join(' ')}"` : ''}`; `${attributes?.length ? ` ${attributes.join(' ')}` : ''}`;
}; };
module.exports = { module.exports = {

View File

@@ -13,137 +13,134 @@ String.prototype.trimReturns = function(){
// Remove the `.failing()` method once you have fixed the issue. // Remove the `.failing()` method once you have fixed the issue.
describe('Inline: When using the Inline syntax {{ }}', ()=>{ describe('Inline: When using the Inline syntax {{ }}', ()=>{
it.failing('Renders a mustache span with text only', function() { it('Renders a mustache span with text only', function() {
const source = '{{ text}}'; const source = '{{ text}}';
const rendered = Markdown.render(source); const rendered = Markdown.render(source);
// FIXME: adds extra \s after class names
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block">text</span>');
}); });
it.failing('Renders a mustache span with text only, but with spaces', function() { it('Renders a mustache span with text only, but with spaces', function() {
const source = '{{ this is a text}}'; const source = '{{ this is a text}}';
const rendered = Markdown.render(source); const rendered = Markdown.render(source);
// FIXME: adds extra \s after class names
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block">this is a text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block">this is a text</span>');
}); });
it.failing('Renders an empty mustache span', function() { it('Renders an empty mustache span', function() {
const source = '{{}}'; const source = '{{}}';
const rendered = Markdown.render(source); const rendered = Markdown.render(source);
// FIXME: adds extra \s after class names
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block"></span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block"></span>');
}); });
it.failing('Renders a mustache span with just a space', function() { it('Renders a mustache span with just a space', function() {
const source = '{{ }}'; const source = '{{ }}';
const rendered = Markdown.render(source); const rendered = Markdown.render(source);
// FIXME: adds extra \s after class names
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block"></span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block"></span>');
}); });
it.failing('Renders a mustache span with a few spaces only', function() { it('Renders a mustache span with a few spaces only', function() {
const source = '{{ }}'; const source = '{{ }}';
const rendered = Markdown.render(source); const rendered = Markdown.render(source);
// FIXME: adds extra \s after class names
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block"></span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block"></span>');
}); });
it.failing('Renders a mustache span with text and class', function() { it('Renders a mustache span with text and class', function() {
const source = '{{my-class text}}'; const source = '{{my-class text}}';
const rendered = Markdown.render(source); const rendered = Markdown.render(source);
// FIXME: adds two extra \s before closing `>` in opening tag.
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block my-class">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block my-class">text</span>');
}); });
it.failing('Renders a mustache span with text and two classes', function() { it('Renders a mustache span with text and two classes', function() {
const source = '{{my-class,my-class2 text}}'; const source = '{{my-class,my-class2 text}}';
const rendered = Markdown.render(source); const rendered = Markdown.render(source);
// FIXME: adds two extra \s before closing `>` in opening tag.
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block my-class my-class2">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block my-class my-class2">text</span>');
}); });
it.failing('Renders a mustache span with text with spaces and class', function() { it('Renders a mustache span with text with spaces and class', function() {
const source = '{{my-class this is a text}}'; const source = '{{my-class this is a text}}';
const rendered = Markdown.render(source); const rendered = Markdown.render(source);
// FIXME: adds two extra \s before closing `>` in opening tag
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block my-class">this is a text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block my-class">this is a text</span>');
}); });
it.failing('Renders a mustache span with text and id', function() { it('Renders a mustache span with text and id', function() {
const source = '{{#my-span text}}'; const source = '{{#my-span text}}';
const rendered = Markdown.render(source); const rendered = Markdown.render(source);
// FIXME: adds extra \s before closing `>` in opening tag, and another after class names
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" id="my-span">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" id="my-span">text</span>');
}); });
it.failing('Renders a mustache span with text and two ids', function() { it('Renders a mustache span with text and two ids', function() {
const source = '{{#my-span,#my-favorite-span text}}'; const source = '{{#my-span,#my-favorite-span text}}';
const rendered = Markdown.render(source); const rendered = Markdown.render(source);
// FIXME: adds extra \s before closing `>` in opening tag, and another after class names
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" id="my-span">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" id="my-span">text</span>');
}); });
it.failing('Renders a mustache span with text and css property', function() { it('Renders a mustache span with text and css property', function() {
const source = '{{color:red text}}'; const source = '{{color:red text}}';
const rendered = Markdown.render(source); const rendered = Markdown.render(source);
// FIXME: adds extra \s after class names
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:red;">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:red;">text</span>');
}); });
it.failing('Renders a mustache span with text and two css properties', function() { it('Renders a mustache span with text and two css properties', function() {
const source = '{{color:red,padding:5px text}}'; const source = '{{color:red,padding:5px text}}';
const rendered = Markdown.render(source); const rendered = Markdown.render(source);
// FIXME: adds extra \s after class names
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:red; padding:5px;">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:red; padding:5px;">text</span>');
}); });
it.failing('Renders a mustache span with text and css property which contains quotes', function() { it('Renders a mustache span with text and css property which contains quotes', function() {
const source = '{{font-family:"trebuchet ms" text}}'; const source = '{{font-family:"trebuchet ms" text}}';
const rendered = Markdown.render(source); const rendered = Markdown.render(source);
// FIXME: adds extra \s after class names
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="font-family:trebuchet ms;">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="font-family:trebuchet ms;">text</span>');
}); });
it.failing('Renders a mustache span with text and two css properties which contains quotes', function() { it('Renders a mustache span with text and two css properties which contains quotes', function() {
const source = '{{font-family:"trebuchet ms",padding:"5px 10px" text}}'; const source = '{{font-family:"trebuchet ms",padding:"5px 10px" text}}';
const rendered = Markdown.render(source); const rendered = Markdown.render(source);
// FIXME: adds extra \s after class names
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="font-family:trebuchet ms; padding:5px 10px;">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="font-family:trebuchet ms; padding:5px 10px;">text</span>');
}); });
it.failing('Renders a mustache span with text with quotes and css property which contains quotes', function() { it('Renders a mustache span with text with quotes and css property which contains double quotes', function() {
const source = '{{font-family:"trebuchet ms" text "with quotes"}}'; const source = '{{font-family:"trebuchet ms" text "with quotes"}}';
const rendered = Markdown.render(source); const rendered = Markdown.render(source);
// FIXME: adds extra \s after class names
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="font-family:trebuchet ms;">text “with quotes”</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="font-family:trebuchet ms;">text “with quotes”</span>');
}); });
it('Renders a mustache span with text with quotes and css property which contains double and simple quotes', function() {
const source = `{{--stringVariable:"'string'" text "with quotes"}}`;
const rendered = Markdown.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<span class="inline-block" style="--stringVariable:'string';">text “with quotes”</span>`);
});
it('Renders a mustache span with text, id, class and a couple of css properties', function() { it('Renders a mustache span with text, id, class and a couple of css properties', function() {
const source = '{{pen,#author,color:orange,font-family:"trebuchet ms" text}}'; const source = '{{pen,#author,color:orange,font-family:"trebuchet ms" text}}';
const rendered = Markdown.render(source); const rendered = Markdown.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block pen" id="author" style="color:orange; font-family:trebuchet ms;">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block pen" id="author" style="color:orange; font-family:trebuchet ms;">text</span>');
}); });
it('Renders a span with added attributes', function() {
const source = 'Text and {{pen,#author,color:orange,font-family:"trebuchet ms",a="b and c",d=e, text}} and more text!';
const rendered = Markdown.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>Text and <span class="inline-block pen" id="author" style="color:orange; font-family:trebuchet ms;" a="b and c" d="e">text</span> and more text!</p>\n');
});
}); });
// BLOCK SYNTAX // BLOCK SYNTAX
describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
it.failing('Renders a div with text only', function() { it('Renders a div with text only', function() {
const source = dedent`{{ const source = dedent`{{
text text
}}`; }}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = Markdown.render(source).trimReturns();
// FIXME: adds extra \s after class names
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block"><p>text</p></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block"><p>text</p></div>`);
}); });
it.failing('Renders an empty div', function() { it('Renders an empty div', function() {
const source = dedent`{{ const source = dedent`{{
}}`; }}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = Markdown.render(source).trimReturns();
// FIXME: adds extra \s after class names
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block"></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block"></div>`);
}); });
@@ -151,52 +148,62 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
const source = dedent`{{ const source = dedent`{{
}}`; }}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = Markdown.render(source).trimReturns();
// this actually renders in HB as '{{ }}'...
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>{{}}</p>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>{{}}</p>`);
}); });
it.failing('Renders a div with a single class', function() { it('Renders a div with a single class', function() {
const source = dedent`{{cat const source = dedent`{{cat
}}`; }}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = Markdown.render(source).trimReturns();
// FIXME: adds two extra \s before closing `>` in opening tag
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat"></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat"></div>`);
}); });
it.failing('Renders a div with a single class and text', function() { it('Renders a div with a single class and text', function() {
const source = dedent`{{cat const source = dedent`{{cat
Sample text. Sample text.
}}`; }}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = Markdown.render(source).trimReturns();
// FIXME: adds two extra \s before closing `>` in opening tag
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat"><p>Sample text.</p></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat"><p>Sample text.</p></div>`);
}); });
it.failing('Renders a div with two classes and text', function() { it('Renders a div with two classes and text', function() {
const source = dedent`{{cat,dog const source = dedent`{{cat,dog
Sample text. Sample text.
}}`; }}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = Markdown.render(source).trimReturns();
// FIXME: adds two extra \s before closing `>` in opening tag
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat dog"><p>Sample text.</p></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat dog"><p>Sample text.</p></div>`);
}); });
it.failing('Renders a div with a style and text', function() { it('Renders a div with a style and text', function() {
const source = dedent`{{color:red const source = dedent`{{color:red
Sample text. Sample text.
}}`; }}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = Markdown.render(source).trimReturns();
// FIXME: adds two extra \s before closing `>` in opening tag
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="color:red;"><p>Sample text.</p></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="color:red;"><p>Sample text.</p></div>`);
}); });
it.failing('Renders a div with a class, style and text', function() { it('Renders a div with a style that has a string variable, and text', function() {
const source = dedent`{{--stringVariable:"'string'"
Sample text.
}}`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="--stringVariable:'string';"><p>Sample text.</p></div>`);
});
it('Renders a div with a style that has a string variable, and text', function() {
const source = dedent`{{--stringVariable:"'string'"
Sample text.
}}`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="--stringVariable:'string';"><p>Sample text.</p></div>`);
});
it('Renders a div with a class, style and text', function() {
const source = dedent`{{cat,color:red const source = dedent`{{cat,color:red
Sample text. Sample text.
}}`; }}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = Markdown.render(source).trimReturns();
// FIXME: adds extra \s after the class attribute
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat" style="color:red;"><p>Sample text.</p></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat" style="color:red;"><p>Sample text.</p></div>`);
}); });
@@ -208,14 +215,27 @@ describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat" id="dog" style="color:red;"><p>Sample text.</p></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block cat" id="dog" style="color:red;"><p>Sample text.</p></div>`);
}); });
it.failing('Renders a div with a single ID', function() { it('Renders a div with a single ID', function() {
const source = dedent`{{#cat,#dog const source = dedent`{{#cat,#dog
Sample text. Sample text.
}}`; }}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = Markdown.render(source).trimReturns();
// FIXME: adds extra \s before closing `>` in opening tag, and another after class names
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" id="cat"><p>Sample text.</p></div>`); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" id="cat"><p>Sample text.</p></div>`);
}); });
it('Renders a div with an ID, class, style and text, and a variable assignment', function() {
const source = dedent`{{color:red,cat,#dog,a="b and c",d="e"
Sample text.
}}`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class=\"block cat\" id=\"dog\" style=\"color:red;\" a=\"b and c\" d=\"e\"><p>Sample text.</p></div>`);
});
it('Renders a div with added attributes', function() {
const source = '{{pen,#author,color:orange,font-family:"trebuchet ms",a="b and c",d=e\nText and text and more text!\n}}\n';
const rendered = Markdown.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block pen" id="author" style="color:orange; font-family:trebuchet ms;" a="b and c" d="e"><p>Text and text and more text!</p>\n</div>');
});
}); });
// MUSTACHE INJECTION SYNTAX // MUSTACHE INJECTION SYNTAX
@@ -235,12 +255,24 @@ describe('Injection: When an injection tag follows an element', ()=>{
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block ClassName">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block ClassName">text</span>');
}); });
it.failing('Renders a span "text" with injected attribute', function() {
const source = '{{ text}}{a="b and c"}';
const rendered = Markdown.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span a="b and c" class="inline-block ">text</span>');
});
it.failing('Renders a span "text" with injected style', function() { it.failing('Renders a span "text" with injected style', function() {
const source = '{{ text}}{color:red}'; const source = '{{ text}}{color:red}';
const rendered = Markdown.render(source); const rendered = Markdown.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:red;">text</span>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:red;">text</span>');
}); });
it.failing('Renders a span "text" with injected style using a string variable', function() {
const source = `{{ text}}{--stringVariable:"'string'"}`;
const rendered = Markdown.render(source);
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<span class="inline-block" style="--stringVariable:'string';">text</span>`);
});
it.failing('Renders a span "text" with two injected styles', function() { it.failing('Renders a span "text" with two injected styles', function() {
const source = '{{ text}}{color:red,background:blue}'; const source = '{{ text}}{color:red,background:blue}';
const rendered = Markdown.render(source); const rendered = Markdown.render(source);
@@ -270,6 +302,12 @@ describe('Injection: When an injection tag follows an element', ()=>{
const rendered = Markdown.render(source).trimReturns(); const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p><span class="inline-block" style="color:red;">text</span>{background:blue}</p>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p><span class="inline-block" style="color:red;">text</span>{background:blue}</p>');
}); });
it('Renders an image with added attributes', function() {
const source = `![homebrew mug](https://i.imgur.com/hMna6G0.png) {position:absolute,bottom:20px,left:130px,width:220px,a="b and c",d=e}`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p><img class="" style="position:absolute; bottom:20px; left:130px; width:220px;" a="b and c" d="e" src="https://i.imgur.com/hMna6G0.png" alt="homebrew mug"></p>`);
});
}); });
describe('and that element is a block', ()=>{ describe('and that element is a block', ()=>{
@@ -297,7 +335,16 @@ describe('Injection: When an injection tag follows an element', ()=>{
}} }}
{color:red,background:blue}`; {color:red,background:blue}`;
const rendered = Markdown.render(source).trimReturns(); const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block" style="color:red; background:blue;"><p>text</p></div>'); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="color:red; background:blue"><p>text</p></div>`);
});
it.failing('renders a div "text" with injected variable string', function() {
const source = dedent`{{
text
}}
{--stringVariable:"'string'"}`;
const rendered = Markdown.render(source).trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<div class="block" style="--stringVariable:'string'"><p>text</p></div>`);
}); });
it.failing('renders an h2 header "text" with injected class name', function() { it.failing('renders an h2 header "text" with injected class name', function() {

View File

@@ -42,8 +42,8 @@ module.exports = function(classname){
#### Equipment #### Equipment
You start with the following equipment, in addition to the equipment granted by your background: You start with the following equipment, in addition to the equipment granted by your background:
- *(a)* a martial weapon and a shield or *(b)* two martial weapons - (*a*) a martial weapon and a shield or (*b*) two martial weapons
- *(a)* five javelins or *(b)* any simple melee weapon - (*a*) five javelins or (*b*) any simple melee weapon
- ${_.sample(['10 lint fluffs', '1 button', 'a cherished lost sock'])} - ${_.sample(['10 lint fluffs', '1 button', 'a cherished lost sock'])}
`; `;
}; };

View File

@@ -1,5 +1,6 @@
@import (less) './themes/fonts/5e/fonts.less'; @import (less) './themes/fonts/5e/fonts.less';
@import (less) './themes/assets/assets.less'; @import (less) './themes/assets/assets.less';
@import (less) './themes/fonts/icon fonts/font-icons.less';
:root { :root {
//Colors //Colors
@@ -14,16 +15,13 @@
--HB_Color_Footnotes : #C9AD6A; // Gold --HB_Color_Footnotes : #C9AD6A; // Gold
} }
@page { margin : 0; }
body { counter-reset : phb-page-numbers; }
* { -webkit-print-color-adjust : exact; }
.useSansSerif() { .useSansSerif() {
font-family : 'ScalySansRemake'; font-family : 'ScalySansRemake';
font-size : 0.318cm; font-size : 0.318cm;
line-height : 1.2em; line-height : 1.2em;
p,dl,ul,ol { line-height : 1.2em; } p,dl,ul,ol { line-height : 1.2em; }
ul, ol { padding-left : 1em; } ul, ol { padding-left : 1em; }
em { font-style : italic; } em { font-style : italic; }
strong { strong {
font-weight : 800; font-weight : 800;
letter-spacing : -0.02em; letter-spacing : -0.02em;
@@ -42,55 +40,31 @@ body { counter-reset : phb-page-numbers; }
-webkit-column-gap : 0.9cm; -webkit-column-gap : 0.9cm;
-moz-column-gap : 0.9cm; -moz-column-gap : 0.9cm;
} }
.columnWrapper {
column-gap : inherit;
max-height : 100%;
column-span : all;
columns : inherit;
}
.page { .page {
.useColumns(); .useColumns();
position : relative; font-family : 'BookInsanityRemake';
z-index : 15; font-size : 0.34cm;
box-sizing : border-box; background-image : @backgroundImage;
width : 215.9mm;
height : 279.4mm;
padding : 1.4cm 1.9cm 1.7cm;
overflow : hidden;
font-family : 'BookInsanityRemake';
font-size : 0.34cm;
counter-increment : phb-page-numbers;
background-color : var(--HB_Color_Background);
background-image : @backgroundImage;
text-rendering : optimizeLegibility;
page-break-before : always;
page-break-after : always;
} }
//***************************** // *****************************
// * BASE // * BASE
// *****************************/ // *****************************/
.page { .page {
p { p {
display : block; line-height : 1.25em;
line-height : 1.25em; & + * { margin-top : 0.325cm; } //TODO: MAKE ALL MARGINS TOP-ONLY. USE * + * STYLE SELECTORS
overflow-wrap : break-word; //TODO: MAKE ALL MARGINS TOP-ONLY. USE * + * STYLE SELECTORS
& + * { margin-top : 0.325cm; }
& + p { margin-top : 0; } & + p { margin-top : 0; }
} }
ul { ul {
padding-left : 1.4em; padding-left : 1.4em;
margin-bottom : 0.8em; margin-bottom : 0.8em;
line-height : 1.25em; line-height : 1.25em;
list-style-position : outside;
list-style-type : disc;
} }
ol { ol {
padding-left : 1.4em; padding-left : 1.4em;
margin-bottom : 0.8em; margin-bottom : 0.8em;
line-height : 1.25em; line-height : 1.25em;
list-style-position : outside;
list-style-type : decimal;
} }
//Indents after p or lists //Indents after p or lists
p + p, ul + p, ol + p { text-indent : 1em; } p + p, ul + p, ol + p { text-indent : 1em; }
@@ -99,24 +73,12 @@ body { counter-reset : phb-page-numbers; }
font-weight : bold; font-weight : bold;
letter-spacing : -0.02em; letter-spacing : -0.02em;
} }
em { font-style : italic; } // *****************************
sup {
font-size : smaller;
line-height : 0;
vertical-align : super;
}
sub {
font-size : smaller;
line-height : 0;
vertical-align : sub;
}
//*****************************
// * HEADERS // * HEADERS
// *****************************/ // *****************************/
h1,h2,h3,h4 { h1,h2,h3,h4 {
font-family : 'MrEavesRemake'; font-family : 'MrEavesRemake';
font-weight : 800; color : var(--HB_Color_HeaderText);
color : var(--HB_Color_HeaderText);
} }
h1 { h1 {
margin-bottom : 0.18cm; //Margin-bottom only because this is WIDE margin-bottom : 0.18cm; //Margin-bottom only because this is WIDE
@@ -145,8 +107,8 @@ body { counter-reset : phb-page-numbers; }
h2 { h2 {
//margin-top : 0px; //Font is misaligned. Shift up slightly //margin-top : 0px; //Font is misaligned. Shift up slightly
//margin-bottom : 0.05cm; //margin-bottom : 0.05cm;
font-size : 0.75cm; font-size : 0.75cm;
line-height : 0.988em; //Font is misaligned. Shift up slightly line-height : 0.988em; //Font is misaligned. Shift up slightly
} }
h3 { h3 {
//margin-top : -0.1cm; //Font is misaligned. Shift up slightly //margin-top : -0.1cm; //Font is misaligned. Shift up slightly
@@ -162,8 +124,8 @@ body { counter-reset : phb-page-numbers; }
h4 { h4 {
//margin-top : -0.02cm; //Font is misaligned. Shift up slightly //margin-top : -0.02cm; //Font is misaligned. Shift up slightly
//margin-bottom : 0.02cm; //margin-bottom : 0.02cm;
font-size : 0.458cm; font-size : 0.458cm;
line-height : 0.971em; //Font is misaligned. Shift up slightly line-height : 0.971em; //Font is misaligned. Shift up slightly
& + * { margin-top : 0.09cm; } & + * { margin-top : 0.09cm; }
} }
* + h4 { * + h4 {
@@ -172,19 +134,17 @@ body { counter-reset : phb-page-numbers; }
h5 { h5 {
//margin-top : -0.02cm; //Font is misaligned. Shift up slightly //margin-top : -0.02cm; //Font is misaligned. Shift up slightly
//margin-bottom : 0.02cm; //margin-bottom : 0.02cm;
font-family : 'ScalySansSmallCapsRemake'; font-family : 'ScalySansSmallCapsRemake';
font-size : 0.423cm; font-size : 0.423cm;
font-weight : 900; line-height : 0.951em; //Font is misaligned. Shift up slightly
line-height : 0.951em; //Font is misaligned. Shift up slightly
& + * { margin-top : 0.2cm; } & + * { margin-top : 0.2cm; }
} }
//***************************** // *****************************
// * TABLE // * TABLE
// *****************************/ // *****************************/
table { table {
.useSansSerif(); .useSansSerif();
width : 100%; line-height : 16px;
line-height : 16px;
& + * { margin-top : 0.325cm; } & + * { margin-top : 0.325cm; }
thead { thead {
display : table-row-group; display : table-row-group;
@@ -200,15 +160,15 @@ body { counter-reset : phb-page-numbers; }
tr { tr {
td { td {
//padding : 0.14em 0.4em; //padding : 0.14em 0.4em;
padding : 0px 1.5px; // Both of these are temporary, just to force padding : 0px 1.5px; // Both of these are temporary, just to force
//line-height : 16px; // PDF to render at same height until Chrome 108 //line-height : 16px; // PDF to render at same height until Chrome 108
} }
&:nth-child(odd) { background-color : var(--HB_Color_Accent); } &:nth-child(odd) { background-color : var(--HB_Color_Accent); }
} }
} }
} }
//***************************** // *****************************
// * QUOTE // * QUOTE
// *****************************/ // *****************************/
.quote { .quote {
@@ -241,9 +201,7 @@ body { counter-reset : phb-page-numbers; }
} }
// *****************************
//*****************************
// * NOTE // * NOTE
// *****************************/ // *****************************/
.note { .note {
@@ -257,7 +215,7 @@ body { counter-reset : phb-page-numbers; }
border-image-outset : 9px 0px; border-image-outset : 9px 0px;
box-shadow : 1px 4px 14px #888888; box-shadow : 1px 4px 14px #888888;
.page :where(&) { .page :where(&) {
margin-top : 9px; //Prevent top border getting cut off on colbreak margin-top : 9px; //Prevent top border getting cut off on colbreak
} }
& + * { margin-top : 0.45cm; } & + * { margin-top : 0.45cm; }
h5 { font-size : 0.375cm; } h5 { font-size : 0.375cm; }
@@ -267,7 +225,7 @@ body { counter-reset : phb-page-numbers; }
} }
:last-child { margin-bottom : 0; } :last-child { margin-bottom : 0; }
} }
//************************************ // ************************************
// * DESCRIPTIVE TEXT BOX // * DESCRIPTIVE TEXT BOX
// ************************************/ // ************************************/
.descriptive { .descriptive {
@@ -291,7 +249,7 @@ body { counter-reset : phb-page-numbers; }
} }
:last-child { margin-bottom : 0; } :last-child { margin-bottom : 0; }
} }
//***************************** // *****************************
// * Images Snippets // * Images Snippets
// *****************************/ // *****************************/
@@ -320,42 +278,10 @@ body { counter-reset : phb-page-numbers; }
} }
/* Watermark */ /* Watermark */
.watermark { .watermark { color : black; }
position : absolute;
top : 0;
left : 0;
z-index : 500;
display : grid !important;
place-items : center;
justify-content : center;
width : 100%;
height : 100%;
font-size : 120px;
color : black;
text-transform : uppercase;
mix-blend-mode : overlay;
opacity : 30%;
transform : rotate(-45deg);
p { margin-bottom : none; }
}
/* Watercolor */ /* Watercolor */
[class*='watercolor'] {
position : absolute;
z-index : -2;
width : 2000px; /* dimensions need to be real big so the user can set */
height : 2000px; /* height or width and the image will maintain aspect ratio */
background-color : var(--HB_Color_WatercolorStain); /* default color */
background-size : cover;
-webkit-mask-image : var(--wc);
-webkit-mask-size : contain;
-webkit-mask-repeat : no-repeat;
mask-image : var(--wc);
mask-size : contain;
mask-repeat : no-repeat;
--wc : @watercolor1; /* default image */
}
.watercolor1 { --wc : @watercolor1; } .watercolor1 { --wc : @watercolor1; }
.watercolor2 { --wc : @watercolor2; } .watercolor2 { --wc : @watercolor2; }
.watercolor3 { --wc : @watercolor3; } .watercolor3 { --wc : @watercolor3; }
@@ -369,7 +295,7 @@ body { counter-reset : phb-page-numbers; }
.watercolor11 { --wc : @watercolor11; } .watercolor11 { --wc : @watercolor11; }
.watercolor12 { --wc : @watercolor12; } .watercolor12 { --wc : @watercolor12; }
//***************************** // *****************************
// * MONSTER STAT BLOCK // * MONSTER STAT BLOCK
// *****************************/ // *****************************/
.monster { .monster {
@@ -390,24 +316,24 @@ body { counter-reset : phb-page-numbers; }
box-shadow : 1px 4px 14px #888888; box-shadow : 1px 4px 14px #888888;
} }
position : relative; position : relative;
padding : 0px; padding : 0px;
margin-bottom : 0.325cm; margin-bottom : 0.325cm;
//Headers //Headers
h2 { h2 {
margin : 0; margin : 0;
font-size : 0.62cm; font-size : 0.62cm;
line-height : 1em; line-height : 1em;
& + p { & + p {
margin-bottom : 0; margin-bottom : 0;
font-size : 0.304cm; //Monster size and type subtext font-size : 0.304cm; //Monster size and type subtext
} }
} }
h3 { h3 {
font-family : 'ScalySansSmallCapsRemake'; font-family : 'ScalySansSmallCapsRemake';
font-size : 0.45cm; font-size : 0.45cm;
border-bottom : 1.5px solid var(--HB_Color_HeaderText); border-bottom : 1.5px solid var(--HB_Color_HeaderText);
} }
//Triangle dividers //Triangle dividers
@@ -454,10 +380,10 @@ body { counter-reset : phb-page-numbers; }
.useColumns(0.96, @fillMode: balance); .useColumns(0.96, @fillMode: balance);
} }
//***************************** // *****************************
// * FOOTER // * FOOTER
// *****************************/ // *****************************/
&:after { &::after {
position : absolute; position : absolute;
bottom : 0px; bottom : 0px;
left : 0px; left : 0px;
@@ -497,23 +423,18 @@ body { counter-reset : phb-page-numbers; }
color : var(--HB_Color_Footnotes); color : var(--HB_Color_Footnotes);
text-align : right; text-align : right;
} }
//************************************ // ************************************
// * CODE BLOCKS // * CODE BLOCKS
// ************************************/ // ************************************/
code { code {
padding : 0px 4px; padding : 0px 4px;
font-family : 'Courier New', 'Courier', monospace; font-size : 0.325cm;
font-size : 0.325;
color : #58180D; color : #58180D;
overflow-wrap : break-word;
white-space : pre-wrap;
background-color : #FAF7EA; background-color : #FAF7EA;
border-radius : 4px; border-radius : 4px;
} }
pre code { pre code {
display : inline-block;
width : 100%;
padding : 0.15cm; padding : 0.15cm;
margin-bottom : 2px; margin-bottom : 2px;
border-style : solid; border-style : solid;
@@ -527,26 +448,13 @@ body { counter-reset : phb-page-numbers; }
} }
& + * { margin-top : 0.325cm; } & + * { margin-top : 0.325cm; }
} }
//***************************** // *****************************
// * EXTRAS // * EXTRAS
// *****************************/ // *****************************/
hr { hr {
margin : 0px; margin : 0px;
visibility : hidden; visibility : hidden;
} }
.columnSplit {
visibility : hidden;
-webkit-column-break-after : always;
break-after : always;
-moz-column-break-after : always;
}
//Avoid breaking up
blockquote,table {
z-index : 15;
-webkit-column-break-inside : avoid;
page-break-inside : avoid;
break-inside : avoid;
}
//Text indent right after table //Text indent right after table
table + p { text-indent : 1em; } table + p { text-indent : 1em; }
// Nested lists // Nested lists
@@ -554,18 +462,13 @@ body { counter-reset : phb-page-numbers; }
margin-bottom : 0px; margin-bottom : 0px;
margin-left : 1.5em; margin-left : 1.5em;
} }
li {
-webkit-column-break-inside : avoid;
page-break-inside : avoid;
break-inside : avoid;
}
} }
//***************************** // *****************************
// * SPELL LIST // * SPELL LIST
// *****************************/ // *****************************/
.page .spellList { .page .spellList {
.useSansSerif(); .useSansSerif();
column-count : 2; column-count : 2;
ul + h5 { margin-top : 15px; } ul + h5 { margin-top : 15px; }
p, ul { p, ul {
font-size : 0.352cm; font-size : 0.352cm;
@@ -583,7 +486,7 @@ body { counter-reset : phb-page-numbers; }
&.wide { column-count : 4; } &.wide { column-count : 4; }
} }
//***************************** // *****************************
// * CLASS TABLE // * CLASS TABLE
// *****************************/ // *****************************/
.page .classTable { .page .classTable {
@@ -630,7 +533,7 @@ body { counter-reset : phb-page-numbers; }
} }
h5 + table { margin-top : 0.2cm; } h5 + table { margin-top : 0.2cm; }
} }
//***************************** // *****************************
// * FRONT COVER PAGE // * FRONT COVER PAGE
// *****************************/ // *****************************/
.page:has(.frontCover) { .page:has(.frontCover) {
@@ -724,7 +627,7 @@ body { counter-reset : phb-page-numbers; }
} }
} }
} }
//***************************** // *****************************
// * INSIDE COVER PAGE // * INSIDE COVER PAGE
// *****************************/ // *****************************/
.page:has(.insideCover) { .page:has(.insideCover) {
@@ -769,7 +672,7 @@ body { counter-reset : phb-page-numbers; }
} }
} }
} }
//***************************** // *****************************
// * BACK COVER // * BACK COVER
// *****************************/ // *****************************/
.page:has(.backCover) { .page:has(.backCover) {
@@ -851,10 +754,10 @@ body { counter-reset : phb-page-numbers; }
} }
} }
//***************************** // *****************************
// * PART COVER // * PART COVER
// *****************************/ // *****************************/
.page:has(.partCover) { .page:has(.partCover) {
padding-top : 0; padding-top : 0;
text-align : center; text-align : center;
columns : 1; columns : 1;
@@ -890,7 +793,7 @@ body { counter-reset : phb-page-numbers; }
} }
} }
//***************************** // *****************************
// * TABLE OF CONTENTS // * TABLE OF CONTENTS
// *****************************/ // *****************************/
.page { .page {
@@ -959,33 +862,25 @@ body { counter-reset : phb-page-numbers; }
} }
} }
//***************************** // *****************************
// * DEFINITION LISTS // * DEFINITION LISTS
// *****************************/ // *****************************/
.page { .page {
dl { dl {
padding-left : 1em;
line-height : 1.25em; line-height : 1.25em;
white-space : pre-line; & + * { margin-top : 0.17cm; }
& + * { margin-top : 0.17cm; }
} }
p + dl { margin-top : 0.17cm; } p + dl { margin-top : 0.17cm; }
dt { dt {
display : inline;
margin-right : 5px; margin-right : 5px;
margin-left : -1em; margin-left : -1em;
} }
dd {
display : inline;
margin-left : 0px;
text-indent : 0px;
}
} }
//***************************** // *****************************
// * WIDE // * WIDE
// *****************************/ // *****************************/
.page .wide { margin-bottom : 0.325cm; } .page .wide { margin-bottom : 0.325cm; }
.page h1 + * { margin-top : 0; } .page h1 + * { margin-top : 0; }
@@ -1024,7 +919,7 @@ body { counter-reset : phb-page-numbers; }
} }
} }
} }
//***************************** // *****************************
// * INDEX // * INDEX
// *****************************/ // *****************************/
.page { .page {

View File

@@ -111,6 +111,21 @@ module.exports = [
icon : 'fas fa-code', icon : 'fas fa-code',
gen : '<!-- This is a comment that will not be rendered into your brew. Hotkey (Ctrl/Cmd + /). -->' gen : '<!-- This is a comment that will not be rendered into your brew. Hotkey (Ctrl/Cmd + /). -->'
}, },
{
name : 'Homebrewery Credit',
icon : 'fas fa-dice-d20',
gen : function(){
return dedent`
{{homebreweryCredits
Made With
{{homebreweryIcon}}
The Homebrewery
[Homebrewery.Naturalcrit.com](https://homebrewery.naturalcrit.com)
}}\n\n`;
},
}
] ]
}, },
{ {
@@ -289,6 +304,99 @@ module.exports = [
} }
] ]
}, },
/**************** FONTS *************/
{
groupName : 'Fonts',
icon : 'fas fa-keyboard',
view : 'text',
snippets : [
{
name : 'Open Sans',
icon : 'font OpenSans',
gen : dedent`{{font-family:OpenSans Dummy Text}}`
},
{
name : 'Code Bold',
icon : 'font CodeBold',
gen : dedent`{{font-family:CodeBold Dummy Text}}`
},
{
name : 'Code Light',
icon : 'font CodeLight',
gen : dedent`{{font-family:CodeLight Dummy Text}}`
},
{
name : 'Scaly Sans Remake',
icon : 'font ScalySansRemake',
gen : dedent`{{font-family:ScalySansRemake Dummy Text}}`
},
{
name : 'Book Insanity Remake',
icon : 'font BookInsanityRemake',
gen : dedent`{{font-family:BookInsanityRemake Dummy Text}}`
},
{
name : 'Mr Eaves Remake',
icon : 'font MrEavesRemake',
gen : dedent`{{font-family:MrEavesRemake Dummy Text}}`
},
{
name: 'Solbera Imitation Remake',
icon: 'font SolberaImitationRemake',
gen: dedent`{{font-family:SolberaImitationRemake Dummy Text}}`
},
{
name: 'Scaly Sans Small Caps Remake',
icon: 'font ScalySansSmallCapsRemake',
gen: dedent`{{font-family:ScalySansSmallCapsRemake Dummy Text}}`
},
{
name: 'Walter Turncoat',
icon: 'font WalterTurncoat',
gen: dedent`{{font-family:WalterTurncoat Dummy Text}}`
},
{
name: 'Lato',
icon: 'font Lato',
gen: dedent`{{font-family:Lato Dummy Text}}`
},
{
name: 'Courier',
icon: 'font Courier',
gen: dedent`{{font-family:Courier Dummy Text}}`
},
{
name: 'Nodesto Caps Condensed',
icon: 'font NodestoCapsCondensed',
gen: dedent`{{font-family:NodestoCapsCondensed Dummy Text}}`
},
{
name: 'Overpass',
icon: 'font Overpass',
gen: dedent`{{font-family:Overpass Dummy Text}}`
},
{
name: 'Davek',
icon: 'font Davek',
gen: dedent`{{font-family:Davek Dummy Text}}`
},
{
name: 'Iokharic',
icon: 'font Iokharic',
gen: dedent`{{font-family:Iokharic Dummy Text}}`
},
{
name: 'Rellanic',
icon: 'font Rellanic',
gen: dedent`{{font-family:Rellanic Dummy Text}}`
},
{
name: 'Times New Roman',
icon: 'font TimesNewRoman',
gen: dedent`{{font-family:"Times New Roman" Dummy Text}}`
}
]
},
/**************** PAGE *************/ /**************** PAGE *************/

View File

@@ -7,13 +7,9 @@
--HB_Color_WatercolorStain : #000000; // Black --HB_Color_WatercolorStain : #000000; // Black
} }
@page { margin: 0; } @page { margin : 0; }
body { body { counter-reset : phb-page-numbers; }
counter-reset : phb-page-numbers; * { -webkit-print-color-adjust : exact; }
}
*{
-webkit-print-color-adjust : exact;
}
//***************************** //*****************************
// * MUSTACHE DIVS/SPANS // * MUSTACHE DIVS/SPANS
@@ -23,9 +19,7 @@ body {
break-inside : avoid; break-inside : avoid;
display : inline-block; display : inline-block;
width : 100%; width : 100%;
img { img { z-index : 0; }
z-index : 0;
}
} }
.inline-block { .inline-block {
display : inline-block; display : inline-block;
@@ -33,99 +27,81 @@ body {
} }
} }
.useColumns(@multiplier : 1, @fillMode: auto){ .useColumns(@multiplier : 1, @fillMode: auto) {
column-fill : @fillMode; column-fill : @fillMode;
column-count : 2; column-count : 2;
} }
.columnWrapper{ .columnWrapper {
column-gap : inherit;
max-height : 100%; max-height : 100%;
column-span : all; column-span : all;
columns : inherit; columns : inherit;
column-gap : inherit;
column-fill : inherit; column-fill : inherit;
} }
.page{ .page {
.useColumns(); .useColumns();
height : 279.4mm;
width : 215.9mm;
padding : 1.4cm 1.9cm 1.7cm;
counter-increment : phb-page-numbers;
background-color : var(--HB_Color_Background);
position : relative; position : relative;
z-index : 15; z-index : 15;
box-sizing : border-box; box-sizing : border-box;
width : 215.9mm;
height : 279.4mm;
padding : 1.4cm 1.9cm 1.7cm;
overflow : hidden; overflow : hidden;
counter-increment : phb-page-numbers;
background-color : var(--HB_Color_Background);
text-rendering : optimizeLegibility; text-rendering : optimizeLegibility;
page-break-before : always;
page-break-after : always;
contain : size; contain : size;
} }
//***************************** //*****************************
// * BASE // * BASE
// *****************************/ // *****************************/
.page{ .page {
p{ p {
overflow-wrap : break-word;
display : block; display : block;
overflow-wrap : break-word;
} }
strong{ strong { font-weight : bold; }
font-weight : bold; em { font-style : italic; }
} sup {
em{ font-size : smaller;
font-style : italic; line-height : 0;
}
sup{
vertical-align : super; vertical-align : super;
font-size : smaller;
line-height : 0;
} }
sub{ sub {
vertical-align : sub;
font-size : smaller; font-size : smaller;
line-height : 0; line-height : 0;
vertical-align : sub;
} }
ul { ul {
padding-left : 1.4em;
list-style-position : outside; //Needed for multiline list items list-style-position : outside; //Needed for multiline list items
list-style-type : disc; list-style-type : disc;
padding-left : 1.4em;
} }
ol { ol {
padding-left : 1.4em;
list-style-position : outside; list-style-position : outside;
list-style-type : decimal; list-style-type : decimal;
padding-left : 1.4em;
}
img{
z-index : -1;
} }
img { z-index : -1; }
//***************************** //*****************************
// * HEADERS // * HEADERS
// *****************************/ // *****************************/
h1,h2,h3,h4,h5,h6{ h1,h2,h3,h4,h5,h6 {
font-weight : bold; font-weight : bold;
line-height : 1.2em; line-height : 1.2em;
} }
h1{ h1 { font-size : 2em; }
font-size : 2em; h2 { font-size : 1.5em; }
} h3 { font-size : 1.17em; }
h2{ h4 { font-size : 1em; }
font-size : 1.5em; h5 { font-size : 0.83em; }
}
h3{
font-size : 1.17em;
}
h4{
font-size : 1em;
}
h5{
font-size : 0.83em;
}
//***************************** //*****************************
// * TABLE // * TABLE
// *****************************/ // *****************************/
table{ table {
width : 100%; width : 100%;
thead{ thead {
display : table-row-group; display : table-row-group;
font-weight : bold; font-weight : bold;
} }
@@ -137,42 +113,40 @@ body {
//************************************ //************************************
// * CODE BLOCKS // * CODE BLOCKS
// ************************************/ // ************************************/
code{ code {
font-family : "Courier New", Courier, monospace; font-family : 'Courier New', "Courier", monospace;
white-space : pre-wrap;
overflow-wrap : break-word; overflow-wrap : break-word;
white-space : pre-wrap;
} }
pre code{ pre code {
width : 100%;
display : inline-block; display : inline-block;
width : 100%;
} }
//***************************** //*****************************
// * EXTRAS // * EXTRAS
// *****************************/ // *****************************/
.columnSplit { .columnSplit {
margin-top : 0;
visibility : hidden; visibility : hidden;
-webkit-column-break-after : always; -webkit-column-break-after : always;
break-after : always; break-after : always;
-moz-column-break-after : always; -moz-column-break-after : always;
margin-top : 0; & + * { margin-top : 0; }
& + * {
margin-top : 0;
}
} }
//Avoid breaking up //Avoid breaking up
blockquote,table{ blockquote,table {
z-index : 15; z-index : 15;
-webkit-column-break-inside : avoid; -webkit-column-break-inside : avoid;
page-break-inside : avoid; page-break-inside : avoid;
break-inside : avoid; break-inside : avoid;
} }
// Nested lists // Nested lists
ul ul,ol ol,ul ol,ol ul{ ul ul,ol ol,ul ol,ol ul {
margin-bottom : 0px; margin-bottom : 0px;
margin-left : 1.5em; margin-left : 1.5em;
} }
li{ li {
-webkit-column-break-inside : avoid; -webkit-column-break-inside : avoid;
page-break-inside : avoid; page-break-inside : avoid;
break-inside : avoid; break-inside : avoid;
@@ -180,69 +154,66 @@ body {
/* Watermark */ /* Watermark */
.watermark { .watermark {
display : grid !important;
place-items : center;
justify-content : center;
position : absolute; position : absolute;
margin : 0;
top : 0; top : 0;
left : 0; left : 0;
z-index : 500;
display : grid !important;
place-items : center;
justify-content : center;
width : 100%; width : 100%;
height : 100%; height : 100%;
margin : 0;
font-size : 120px; font-size : 120px;
text-transform : uppercase; text-transform : uppercase;
mix-blend-mode : overlay; mix-blend-mode : overlay;
opacity : 30%; opacity : 30%;
transform : rotate(-45deg); transform : rotate(-45deg);
z-index : 500; p { margin-bottom : none; }
p {
margin-bottom : none;
}
} }
/* Watercolor */ /* Watercolor */
[class*="watercolor"] { [class*='watercolor'] {
position : absolute; position : absolute;
z-index : -2;
width : 2000px; /* dimensions need to be real big so the user can set */ width : 2000px; /* dimensions need to be real big so the user can set */
height : 2000px; /* height or width and the image will maintain aspect ratio */ height : 2000px; /* height or width and the image will maintain aspect ratio */
background-color : var(--HB_Color_WatercolorStain); /* default color */
background-size : cover;
-webkit-mask-image : var(--wc); -webkit-mask-image : var(--wc);
-webkit-mask-size : contain; -webkit-mask-size : contain;
-webkit-mask-repeat : no-repeat; -webkit-mask-repeat : no-repeat;
mask-image : var(--wc); mask-image : var(--wc);
mask-size : contain; mask-size : contain;
mask-repeat : no-repeat; mask-repeat : no-repeat;
background-size : cover; --wc : @watercolor1; /* default image */
background-color : var(--HB_Color_WatercolorStain); /*default color*/
--wc : @watercolor1; /*default image*/
z-index : -2;
} }
.watercolor1 { --wc : @watercolor1; } .watercolor1 { --wc : @watercolor1; }
.watercolor2 { --wc : @watercolor2; } .watercolor2 { --wc : @watercolor2; }
.watercolor3 { --wc : @watercolor3; } .watercolor3 { --wc : @watercolor3; }
.watercolor4 { --wc : @watercolor4; } .watercolor4 { --wc : @watercolor4; }
.watercolor5 { --wc : @watercolor5; } .watercolor5 { --wc : @watercolor5; }
.watercolor6 { --wc : @watercolor6; } .watercolor6 { --wc : @watercolor6; }
.watercolor7 { --wc : @watercolor7; } .watercolor7 { --wc : @watercolor7; }
.watercolor8 { --wc : @watercolor8; } .watercolor8 { --wc : @watercolor8; }
.watercolor9 { --wc : @watercolor9; } .watercolor9 { --wc : @watercolor9; }
.watercolor10 { --wc : @watercolor10; } .watercolor10 { --wc : @watercolor10; }
.watercolor11 { --wc : @watercolor11; } .watercolor11 { --wc : @watercolor11; }
.watercolor12 { --wc : @watercolor12; } .watercolor12 { --wc : @watercolor12; }
/* Image Masks */ /* Image Masks */
[class*="imageMask"] { [class*='imageMask'] {
position : absolute; position : absolute;
height : 200%;
width : 200%;
left : 50%;
bottom : 50%; bottom : 50%;
--rotation : 0; left : 50%;
--revealer : none; z-index : -1;
--checkerboard : none; width : 200%;
--scaleX : 1; height : 200%;
--scaleY : 1; background-image : var(--checkerboard);
background-size : 20px;
transform : translateY(50%) translateX(-50%) rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY));
-webkit-mask-image : var(--wc), var(--revealer); -webkit-mask-image : var(--wc), var(--revealer);
-webkit-mask-repeat : repeat-x; -webkit-mask-repeat : repeat-x;
-webkit-mask-size : 50%; //Scale only X to fit page width, leave height at aspect ratio, designed to hang off the edge -webkit-mask-size : 50%; //Scale only X to fit page width, leave height at aspect ratio, designed to hang off the edge
@@ -251,61 +222,63 @@ body {
mask-repeat : repeat-x; mask-repeat : repeat-x;
mask-size : 50%; mask-size : 50%;
mask-position : 50% calc(50% - var(--offset)); mask-position : 50% calc(50% - var(--offset));
background-image : var(--checkerboard); --rotation : 0;
background-size : 20px; --revealer : none;
z-index : -1; --checkerboard : none;
transform : translateY(50%) translateX(-50%) rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY)); --scaleX : 1;
--scaleY : 1;
& > p:has(img) { & > p:has(img) {
position : absolute; position : absolute;
width : 50%;
height : 50%;
bottom : 50%; bottom : 50%;
left : 50%; left : 50%;
width : 50%;
height : 50%;
transform : translateX(-50%) translateY(50%) rotate(calc(-1deg * var(--rotation))) scaleX(calc(1 / var(--scaleX))) scaleY(calc(1 / var(--scaleY))); transform : translateX(-50%) translateY(50%) rotate(calc(-1deg * var(--rotation))) scaleX(calc(1 / var(--scaleX))) scaleY(calc(1 / var(--scaleY)));
} }
& img { & img {
position : absolute; position : absolute;
display : block;
bottom : 0; bottom : 0;
display : block;
} }
&.bottom { &.bottom {
--rotation : 0; --rotation : 0;
& img {bottom: 0;} & img {bottom : 0;}
} }
&.top { &.top {
--rotation : 180; --rotation : 180;
& img {top: 0;} & img {top : 0;}
} }
&.left { &.left {
--rotation : 90; --rotation : 90;
& img {left: 0;} & img {left : 0;}
} }
&.right { &.right {
--rotation : -90; --rotation : -90;
& img {right: 0;} & img {right : 0;}
} }
&.revealImage { &.revealImage {
--revealer : linear-gradient(0deg, rgba(0,0,0,.2) 0%, rgba(0,0,0,0.2)); --revealer : linear-gradient(0deg, rgba(0,0,0,0.2) 0%, rgba(0,0,0,0.2));
--checkerboard : url(/assets/waterColorMasks/missingImage.png); //shows any masked regions not filled by image --checkerboard : url("/assets/waterColorMasks/missingImage.png"); //shows any masked regions not filled by image
} }
} }
.imageMaskEdge { .imageMaskEdge {
&1 { --wc : url(/assets/waterColorMasks/edge/0001.webp); } &1 { --wc : url("/assets/waterColorMasks/edge/0001.webp"); }
&2 { --wc : url(/assets/waterColorMasks/edge/0002.webp); } &2 { --wc : url("/assets/waterColorMasks/edge/0002.webp"); }
&3 { --wc : url(/assets/waterColorMasks/edge/0003.webp); } &3 { --wc : url("/assets/waterColorMasks/edge/0003.webp"); }
&4 { --wc : url(/assets/waterColorMasks/edge/0004.webp); } &4 { --wc : url("/assets/waterColorMasks/edge/0004.webp"); }
&5 { --wc : url(/assets/waterColorMasks/edge/0005.webp); } &5 { --wc : url("/assets/waterColorMasks/edge/0005.webp"); }
&6 { --wc : url(/assets/waterColorMasks/edge/0006.webp); } &6 { --wc : url("/assets/waterColorMasks/edge/0006.webp"); }
&7 { --wc : url(/assets/waterColorMasks/edge/0007.webp); } &7 { --wc : url("/assets/waterColorMasks/edge/0007.webp"); }
&8 { --wc : url(/assets/waterColorMasks/edge/0008.webp); } &8 { --wc : url("/assets/waterColorMasks/edge/0008.webp"); }
} }
[class*="imageMaskCenter"] { [class*='imageMaskCenter'] {
bottom : calc(var(--offsetY));
left : calc(var(--offsetX));
width : 100%; width : 100%;
height : 100%; height : 100%;
left : calc(var(--offsetX)); transform : rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY));
bottom : calc(var(--offsetY));
-webkit-mask-image : var(--wc), var(--revealer); -webkit-mask-image : var(--wc), var(--revealer);
-webkit-mask-repeat : no-repeat; -webkit-mask-repeat : no-repeat;
-webkit-mask-size : 100% 100%; //Scale both dimensions to fit page size -webkit-mask-size : 100% 100%; //Scale both dimensions to fit page size
@@ -314,48 +287,48 @@ body {
mask-repeat : no-repeat; mask-repeat : no-repeat;
mask-size : 100% 100%; //Scale both dimensions to fit page size mask-size : 100% 100%; //Scale both dimensions to fit page size
mask-position : 50% 50%; mask-position : 50% 50%;
transform : rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY));
& > p:has(img) { & > p:has(img) {
position : absolute; position : absolute;
width : 100%;
height : 100%;
bottom : 0; bottom : 0;
left : 0; left : 0;
width : 100%;
height : 100%;
transform : unset; transform : unset;
transform : scaleX(calc(1 / var(--scaleX))) scaleY(calc(1 / var(--scaleY))) transform : scaleX(calc(1 / var(--scaleX))) scaleY(calc(1 / var(--scaleY)))
rotate(calc(-1deg * var(--rotation))) rotate(calc(-1deg * var(--rotation)))
translateX(calc(-1 * var(--offsetX))) translateX(calc(-1 * var(--offsetX)))
translateY(calc(1 * var(--offsetY))); translateY(calc(1 * var(--offsetY)));
} }
} }
.imageMaskCenter { .imageMaskCenter {
&1 { --wc : url(/assets/waterColorMasks/center/0001.webp); } &1 { --wc : url("/assets/waterColorMasks/center/0001.webp"); }
&2 { --wc : url(/assets/waterColorMasks/center/0002.webp); } &2 { --wc : url("/assets/waterColorMasks/center/0002.webp"); }
&3 { --wc : url(/assets/waterColorMasks/center/0003.webp); } &3 { --wc : url("/assets/waterColorMasks/center/0003.webp"); }
&4 { --wc : url(/assets/waterColorMasks/center/0004.webp); } &4 { --wc : url("/assets/waterColorMasks/center/0004.webp"); }
&5 { --wc : url(/assets/waterColorMasks/center/0005.webp); } &5 { --wc : url("/assets/waterColorMasks/center/0005.webp"); }
&6 { --wc : url(/assets/waterColorMasks/center/0006.webp); } &6 { --wc : url("/assets/waterColorMasks/center/0006.webp"); }
&7 { --wc : url(/assets/waterColorMasks/center/0007.webp); } &7 { --wc : url("/assets/waterColorMasks/center/0007.webp"); }
&8 { --wc : url(/assets/waterColorMasks/center/0008.webp); } &8 { --wc : url("/assets/waterColorMasks/center/0008.webp"); }
&9 { --wc : url(/assets/waterColorMasks/center/0009.webp); } &9 { --wc : url("/assets/waterColorMasks/center/0009.webp"); }
&10 { --wc : url(/assets/waterColorMasks/center/0010.webp); } &10 { --wc : url("/assets/waterColorMasks/center/0010.webp"); }
&11 { --wc : url(/assets/waterColorMasks/center/0011.webp); } &11 { --wc : url("/assets/waterColorMasks/center/0011.webp"); }
&12 { --wc : url(/assets/waterColorMasks/center/0012.webp); } &12 { --wc : url("/assets/waterColorMasks/center/0012.webp"); }
&13 { --wc : url(/assets/waterColorMasks/center/0013.webp); } &13 { --wc : url("/assets/waterColorMasks/center/0013.webp"); }
&14 { --wc : url(/assets/waterColorMasks/center/0014.webp); } &14 { --wc : url("/assets/waterColorMasks/center/0014.webp"); }
&15 { --wc : url(/assets/waterColorMasks/center/0015.webp); } &15 { --wc : url("/assets/waterColorMasks/center/0015.webp"); }
&16 { --wc : url(/assets/waterColorMasks/center/0016.webp); } &16 { --wc : url("/assets/waterColorMasks/center/0016.webp"); }
&special { --wc : url(/assets/waterColorMasks/center/special.webp); } &special { --wc : url("/assets/waterColorMasks/center/special.webp"); }
} }
[class*="imageMaskCorner"] { [class*='imageMaskCorner'] {
height : 200%;
width : 200%;
left : calc(-50% + var(--offsetX));
bottom : calc(-50% + var(--offsetY)); bottom : calc(-50% + var(--offsetY));
left : calc(-50% + var(--offsetX));
width : 200%;
height : 200%;
transform : rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY));
-webkit-mask-image : var(--wc), var(--revealer); -webkit-mask-image : var(--wc), var(--revealer);
-webkit-mask-repeat : no-repeat; -webkit-mask-repeat : no-repeat;
-webkit-mask-size : 100% 100%; //Scale both dimensions to fit page size -webkit-mask-size : 100% 100%; //Scale both dimensions to fit page size
@@ -364,56 +337,55 @@ body {
mask-repeat : no-repeat; mask-repeat : no-repeat;
mask-size : 100% 100%; //Scale both dimensions to fit page size mask-size : 100% 100%; //Scale both dimensions to fit page size
mask-position : 50% 50%; mask-position : 50% 50%;
transform : rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY));
& > p:has(img) { & > p:has(img) {
bottom : 25%;
left : 25%;
width : 50%; width : 50%;
height : 50%; //Complex transform below to handle mix of % and cm offsets height : 50%; //Complex transform below to handle mix of % and cm offsets
left : 25%;
bottom : 25%;
transform : scaleX(calc(1 / var(--scaleX))) scaleY(calc(1 / var(--scaleY))) transform : scaleX(calc(1 / var(--scaleX))) scaleY(calc(1 / var(--scaleY)))
rotate(calc(-1deg * var(--rotation))) rotate(calc(-1deg * var(--rotation)))
translateX(calc(-1 * var(--offsetX))) translateX(calc(-1 * var(--offsetX)))
translateY(calc(1 * var(--offsetY))); translateY(calc(1 * var(--offsetY)));
} }
} }
.imageMaskCorner { .imageMaskCorner {
&1 { --wc : url(/assets/waterColorMasks/corner/0001.webp); } &1 { --wc : url("/assets/waterColorMasks/corner/0001.webp"); }
&2 { --wc : url(/assets/waterColorMasks/corner/0002.webp); } &2 { --wc : url("/assets/waterColorMasks/corner/0002.webp"); }
&3 { --wc : url(/assets/waterColorMasks/corner/0003.webp); } &3 { --wc : url("/assets/waterColorMasks/corner/0003.webp"); }
&4 { --wc : url(/assets/waterColorMasks/corner/0004.webp); } &4 { --wc : url("/assets/waterColorMasks/corner/0004.webp"); }
&5 { --wc : url(/assets/waterColorMasks/corner/0005.webp); } &5 { --wc : url("/assets/waterColorMasks/corner/0005.webp"); }
&6 { --wc : url(/assets/waterColorMasks/corner/0006.webp); } &6 { --wc : url("/assets/waterColorMasks/corner/0006.webp"); }
&7 { --wc : url(/assets/waterColorMasks/corner/0007.webp); } &7 { --wc : url("/assets/waterColorMasks/corner/0007.webp"); }
&8 { --wc : url(/assets/waterColorMasks/corner/0008.webp); } &8 { --wc : url("/assets/waterColorMasks/corner/0008.webp"); }
&9 { --wc : url(/assets/waterColorMasks/corner/0009.webp); } &9 { --wc : url("/assets/waterColorMasks/corner/0009.webp"); }
&10 { --wc : url(/assets/waterColorMasks/corner/0010.webp); } &10 { --wc : url("/assets/waterColorMasks/corner/0010.webp"); }
&11 { --wc : url(/assets/waterColorMasks/corner/0011.webp); } &11 { --wc : url("/assets/waterColorMasks/corner/0011.webp"); }
&12 { --wc : url(/assets/waterColorMasks/corner/0012.webp); } &12 { --wc : url("/assets/waterColorMasks/corner/0012.webp"); }
&13 { --wc : url(/assets/waterColorMasks/corner/0013.webp); } &13 { --wc : url("/assets/waterColorMasks/corner/0013.webp"); }
&14 { --wc : url(/assets/waterColorMasks/corner/0014.webp); } &14 { --wc : url("/assets/waterColorMasks/corner/0014.webp"); }
&15 { --wc : url(/assets/waterColorMasks/corner/0015.webp); } &15 { --wc : url("/assets/waterColorMasks/corner/0015.webp"); }
&16 { --wc : url(/assets/waterColorMasks/corner/0016.webp); } &16 { --wc : url("/assets/waterColorMasks/corner/0016.webp"); }
&17 { --wc : url(/assets/waterColorMasks/corner/0017.webp); } &17 { --wc : url("/assets/waterColorMasks/corner/0017.webp"); }
&18 { --wc : url(/assets/waterColorMasks/corner/0018.webp); } &18 { --wc : url("/assets/waterColorMasks/corner/0018.webp"); }
&19 { --wc : url(/assets/waterColorMasks/corner/0019.webp); } &19 { --wc : url("/assets/waterColorMasks/corner/0019.webp"); }
&20 { --wc : url(/assets/waterColorMasks/corner/0020.webp); } &20 { --wc : url("/assets/waterColorMasks/corner/0020.webp"); }
&21 { --wc : url(/assets/waterColorMasks/corner/0021.webp); } &21 { --wc : url("/assets/waterColorMasks/corner/0021.webp"); }
&22 { --wc : url(/assets/waterColorMasks/corner/0022.webp); } &22 { --wc : url("/assets/waterColorMasks/corner/0022.webp"); }
&23 { --wc : url(/assets/waterColorMasks/corner/0023.webp); } &23 { --wc : url("/assets/waterColorMasks/corner/0023.webp"); }
&24 { --wc : url(/assets/waterColorMasks/corner/0024.webp); } &24 { --wc : url("/assets/waterColorMasks/corner/0024.webp"); }
&25 { --wc : url(/assets/waterColorMasks/corner/0025.webp); } &25 { --wc : url("/assets/waterColorMasks/corner/0025.webp"); }
&26 { --wc : url(/assets/waterColorMasks/corner/0026.webp); } &26 { --wc : url("/assets/waterColorMasks/corner/0026.webp"); }
&27 { --wc : url(/assets/waterColorMasks/corner/0027.webp); } &27 { --wc : url("/assets/waterColorMasks/corner/0027.webp"); }
&28 { --wc : url(/assets/waterColorMasks/corner/0028.webp); } &28 { --wc : url("/assets/waterColorMasks/corner/0028.webp"); }
&29 { --wc : url(/assets/waterColorMasks/corner/0029.webp); } &29 { --wc : url("/assets/waterColorMasks/corner/0029.webp"); }
&30 { --wc : url(/assets/waterColorMasks/corner/0030.webp); } &30 { --wc : url("/assets/waterColorMasks/corner/0030.webp"); }
&31 { --wc : url(/assets/waterColorMasks/corner/0031.webp); } &31 { --wc : url("/assets/waterColorMasks/corner/0031.webp"); }
&32 { --wc : url(/assets/waterColorMasks/corner/0032.webp); } &32 { --wc : url("/assets/waterColorMasks/corner/0032.webp"); }
&33 { --wc : url(/assets/waterColorMasks/corner/0033.webp); } &33 { --wc : url("/assets/waterColorMasks/corner/0033.webp"); }
&34 { --wc : url(/assets/waterColorMasks/corner/0034.webp); } &34 { --wc : url("/assets/waterColorMasks/corner/0034.webp"); }
&35 { --wc : url(/assets/waterColorMasks/corner/0035.webp); } &35 { --wc : url("/assets/waterColorMasks/corner/0035.webp"); }
&36 { --wc : url(/assets/waterColorMasks/corner/0036.webp); } &36 { --wc : url("/assets/waterColorMasks/corner/0036.webp"); }
&37 { --wc : url(/assets/waterColorMasks/corner/0037.webp); } &37 { --wc : url("/assets/waterColorMasks/corner/0037.webp"); }
} }
} }
@@ -425,16 +397,16 @@ body {
padding-left : 1em; padding-left : 1em;
white-space : pre-line; white-space : pre-line;
} }
dt { dt {
display : inline; display : inline;
margin-right : 0.5ch; margin-right : 0.5ch;
margin-left : -1em; margin-left : -1em;
} }
dd { dd {
display : inline; display : inline;
margin-left : 0; margin-left : 0;
text-indent : 0; text-indent : 0;
} }
} }
//***************************** //*****************************
@@ -444,9 +416,7 @@ body {
.blank { .blank {
height : 1em; height : 1em;
margin-top : 0; margin-top : 0;
& + * { & + * { margin-top : 0; }
margin-top : 0;
}
} }
} }
@@ -454,12 +424,39 @@ body {
// * WIDE // * WIDE
// *****************************/ // *****************************/
.page { .page {
.wide{ .wide {
column-span : all; column-span : all;
display : block; display : block;
margin-bottom : 1em; margin-bottom : 1em;
&+* { & + * { margin-top : 0; }
margin-top : 0;
}
} }
} }
//*****************************
//* CREDITS
//*****************************/
.page .homebreweryCredits {
p {
font-family : 'NodestoCapsWide';
font-size : 0.4cm;
line-height : 1em;
text-align : center;
text-indent : 0;
letter-spacing : 0.08em;
}
a {
color : inherit;
text-decoration : none;
&:hover { text-decoration : underline; }
}
.homebreweryIcon {
display : block;
height : 1.5cm;
margin : 0 auto;
background-color : black;
-webkit-mask : url("/assets/naturalCritLogoWhite.svg") center / contain no-repeat;
mask : url("/assets/naturalCritLogoWhite.svg") center / contain no-repeat;
}
.homebreweryIcon.red { background-color : red; }
.homebreweryIcon.gold { background-image : linear-gradient(to top left, brown 22.5%, gold 40%, white 60%, gold 67.5%, brown 82.5%); }
}

Binary file not shown.

View File

@@ -0,0 +1,224 @@
/* Main Font, serif */
@font-face {
font-family : 'Eldeberry-Inn';
font-style : normal;
font-weight : normal;
src : url('../../../fonts/icon fonts/Elderberry-Inn-Icons.woff2');
}
.page {
span.ei {
display : inline-block;
margin-right : 3px;
font-family : 'Eldeberry-Inn';
line-height : 1;
vertical-align : baseline;
-moz-osx-font-smoothing : grayscale;
-webkit-font-smoothing : antialiased;
text-rendering : auto;
&.book::before { content : '\E900'; }
&.screen::before { content : '\E901'; }
/* Spell levels */
&.spell-0::before { content : '\E902'; }
&.spell-1::before { content : '\E903'; }
&.spell-2::before { content : '\E904'; }
&.spell-3::before { content : '\E905'; }
&.spell-4::before { content : '\E906'; }
&.spell-5::before { content : '\E907'; }
&.spell-6::before { content : '\E908'; }
&.spell-7::before { content : '\E909'; }
&.spell-8::before { content : '\E90A'; }
&.spell-9::before { content : '\E90B'; }
/* Damage types */
&.acid::before { content : '\E90C'; }
&.bludgeoning::before { content : '\E90D'; }
&.cold::before { content : '\E90E'; }
&.fire::before { content : '\E90F'; }
&.force::before { content : '\E910'; }
&.lightning::before { content : '\E911'; }
&.necrotic::before { content : '\E912'; }
&.piercing::before { content : '\E914'; }
&.poison::before { content : '\E913'; }
&.psychic::before { content : '\E915'; }
&.radiant::before { content : '\E916'; }
&.slashing::before { content : '\E917'; }
&.thunder::before { content : '\E918'; }
/* DnD Conditions */
&.blinded::before { content : '\E919'; }
&.charmed::before { content : '\E91A'; }
&.deafened::before { content : '\E91B'; }
&.exhaust-1::before { content : '\E91C'; }
&.exhaust-2::before { content : '\E91D'; }
&.exhaust-3::before { content : '\E91E'; }
&.exhaust-4::before { content : '\E91F'; }
&.exhaust-5::before { content : '\E920'; }
&.exhaust-6::before { content : '\E921'; }
&.frightened::before { content : '\E922'; }
&.grappled::before { content : '\E923'; }
&.incapacitated::before { content : '\E924'; }
&.invisible::before { content : '\E926'; }
&.paralyzed::before { content : '\E927'; }
&.petrified::before { content : '\E928'; }
&.poisoned::before { content : '\E929'; }
&.prone::before { content : '\E92A'; }
&.restrained::before { content : '\E92B'; }
&.stunned::before { content : '\E92C'; }
&.unconscious::before { content : '\E925'; }
/* Character Classes and Features */
&.barbarian-rage::before { content : '\E92D'; }
&.barbarian-reckless-attack::before { content : '\E92E'; }
&.bardic-inspiration::before { content : '\E92F'; }
&.cleric-channel-divinity::before { content : '\E930'; }
&.druid-wild-shape::before { content : '\E931'; }
&.fighter-action-surge::before { content : '\E932'; }
&.fighter-second-wind::before { content : '\E933'; }
&.monk-flurry-blows::before { content : '\E934'; }
&.monk-patient-defense::before { content : '\E935'; }
&.monk-step-of-the-wind::before { content : '\E936'; }
&.monk-step-of-the-wind-2::before { content : '\E937'; }
&.monk-step-of-the-wind-3::before { content : '\E938'; }
&.monk-stunning-strike::before { content : '\E939'; }
&.monk-stunning-strike-2::before { content : '\E939'; }
&.paladin-divine-smite::before { content : '\E93B'; }
&.paladin-lay-on-hands::before { content : '\E93C'; }
&.barbarian-abilities::before { content : '\E93D'; }
&.barbarian::before { content : '\E93E'; }
&.bard-abilities::before { content : '\E93F'; }
&.bard::before { content : '\E940'; }
&.cleric-abilities::before { content : '\E941'; }
&.cleric::before { content : '\E942'; }
&.druid-abilities::before { content : '\E943'; }
&.druid::before { content : '\E944'; }
&.fighter-abilities::before { content : '\E945'; }
&.fighter::before { content : '\E946'; }
&.monk-abilities::before { content : '\E947'; }
&.monk::before { content : '\E948'; }
&.paladin-abilities::before { content : '\E949'; }
&.paladin::before { content : '\E94A'; }
&.ranger-abilities::before { content : '\E94B'; }
&.ranger::before { content : '\E94C'; }
&.rogue-abilities::before { content : '\E94D'; }
&.rogue::before { content : '\E94E'; }
&.sorcerer-abilities::before { content : '\E94F'; }
&.sorcerer::before { content : '\E950'; }
&.warlock-abilities::before { content : '\E951'; }
&.warlock::before { content : '\E952'; }
&.wizard-abilities::before { content : '\E953'; }
&.wizard::before { content : '\E954'; }
/* Types of actions */
&.movement::before { content : '\E955'; }
&.action::before { content : '\E956'; }
&.bonus-action::before { content : '\E957'; }
&.reaction::before { content : '\E958'; }
/* SRD Spells */
&.acid-arrow::before { content : '\E959'; }
&.action-1::before { content : '\E95A'; }
&.alter-self::before { content : '\E95B'; }
&.alter-self-2::before { content : '\E95C'; }
&.animal-friendship::before { content : '\E95E'; }
&.animate-dead::before { content : '\E95F'; }
&.animate-objects::before { content : '\E960'; }
&.animate-objects-2::before { content : '\E961'; }
&.bane::before { content : '\E962'; }
&.bless::before { content : '\E963'; }
&.blur::before { content : '\E964'; }
&.bonus::before { content : '\E965'; }
&.branding-smite::before { content : '\E966'; }
&.burning-hands::before { content : '\E967'; }
&.charm-person::before { content : '\E968'; }
&.chill-touch::before { content : '\E969'; }
&.cloudkill::before { content : '\E96A'; }
&.comprehend-languages::before { content : '\E96B'; }
&.cone-of-cold::before { content : '\E96C'; }
&.conjure-elemental::before { content : '\E96D'; }
&.conjure-minor-elemental::before { content : '\E96E'; }
&.control-water::before { content : '\E96F'; }
&.counterspell::before { content : '\E970'; }
&.cure-wounds::before { content : '\E971'; }
&.dancing-lights::before { content : '\E972'; }
&.darkness::before { content : '\E973'; }
&.detect-magic::before { content : '\E974'; }
&.disguise-self::before { content : '\E975'; }
&.disintegrate::before { content : '\E976'; }
&.dispel-evil-and-good::before { content : '\E977'; }
&.dispel-magic::before { content : '\E978'; }
&.dominate-monster::before { content : '\E979'; }
&.dominate-person::before { content : '\E97A'; }
&.eldritch-blast::before { content : '\E97B'; }
&.enlarge-reduce::before { content : '\E97C'; }
&.entangle::before { content : '\E97D'; }
&.faerie-fire::before { content : '\E97E'; }
&.faerie-fire2::before { content : '\E97F'; }
&.feather-fall::before { content : '\E980'; }
&.find-familiar::before { content : '\E981'; }
&.finger-of-death::before { content : '\E982'; }
&.fireball::before { content : '\E983'; }
&.floating-disk::before { content : '\E984'; }
&.fly::before { content : '\E985'; }
&.fog-cloud::before { content : '\E986'; }
&.gaseous-form::before { content : '\E987'; }
&.gaseous-form2::before { content : '\E988'; }
&.gentle-repose::before { content : '\E989'; }
&.gentle-repose2::before { content : '\E98A'; }
&.globe-of-invulnerability::before { content : '\E98B'; }
&.guiding-bolt::before { content : '\E98C'; }
&.healing-word::before { content : '\E98D'; }
&.heat-metal::before { content : '\E98E'; }
&.hellish-rebuke::before { content : '\E98F'; }
&.heroes-feast::before { content : '\E990'; }
&.heroism::before { content : '\E991'; }
&.hideous-laughter::before { content : '\E992'; }
&.identify::before { content : '\E993'; }
&.illusory-script::before { content : '\E994'; }
&.inflict-wounds::before { content : '\E995'; }
&.light::before { content : '\E996'; }
&.longstrider::before { content : '\E997'; }
&.mage-armor::before { content : '\E998'; }
&.mage-hand::before { content : '\E999'; }
&.magic-missile::before { content : '\E99A'; }
&.mass-cure-wounds::before { content : '\E99B'; }
&.mass-healing-word::before { content : '\E99C'; }
&.Mending::before { content : '\E99D'; }
&.message::before { content : '\E99E'; }
&.Minor-illusion::before { content : '\E99F'; }
&.movement1::before { content : '\E9A0'; }
&.polymorph::before { content : '\E9A1'; }
&.power-word-kill::before { content : '\E9A2'; }
&.power-word-stun::before { content : '\E9A3'; }
&.prayer-of-healing::before { content : '\E9A4'; }
&.prestidigitation::before { content : '\E9A5'; }
&.protection-from-evil-and-good::before { content : '\E9A6'; }
&.raise-read::before { content : '\E9A7'; }
&.raise-read2::before { content : '\E9A8'; }
&.reaction1::before { content : '\E9A9'; }
&.resurrection::before { content : '\E9AA'; }
&.resurrection2::before { content : '\E9AB'; }
&.revivify::before { content : '\E9AC'; }
&.revivify2::before { content : '\E9AD'; }
&.sacred-flame::before { content : '\E9AE'; }
&.sanctuary::before { content : '\E9AF'; }
&.scorching-ray::before { content : '\E9B0'; }
&.sending::before { content : '\E9B1'; }
&.shatter::before { content : '\E9B2'; }
&.shield::before { content : '\E9B3'; }
&.silent-image::before { content : '\E9B4'; }
&.sleep::before { content : '\E9B5'; }
&.speak-with-animals::before { content : '\E9B6'; }
&.telekinesis::before { content : '\E9B7'; }
&.true-strike::before { content : '\E9B8'; }
&.vicious-mockery::before { content : '\E9B9'; }
&.wall-of-fire::before { content : '\E9BA'; }
&.wall-of-force::before { content : '\E9BB'; }
&.wall-of-ice::before { content : '\E9BC'; }
&.wall-of-stone::before { content : '\E9BD'; }
&.wall-of-thorns::before { content : '\E9BE'; }
&.wish::before { content : '\E9BF'; }
}
}