0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-03 01:52:42 +00:00

Merge branch 'master' into dependabot/npm_and_yarn/classnames-2.5.1

This commit is contained in:
Trevor Buckner
2024-03-27 10:04:11 -04:00
committed by GitHub
36 changed files with 1811 additions and 1362 deletions

View File

@@ -64,6 +64,12 @@ jobs:
- run: - run:
name: Test - Mustache Spans name: Test - Mustache Spans
command: npm run test:mustache-syntax command: npm run test:mustache-syntax
- run:
name: Test - Definition Lists
command: npm run test:definition-lists
- run:
name: Test - Variables
command: npm run test:variables
- run: - run:
name: Test - Routes name: Test - Routes
command: npm run test:route command: npm run test:route

View File

@@ -84,7 +84,70 @@ pre {
## changelog ## changelog
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery). For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
### Wednesday 21/2/2024 - v3.11.0 ### Monday 18/3/2024 - v3.12.0
{{taskList
##### 5e-Cleric
* [x] Fix language-specific hyphenation on print page
Fixes issue [#3294](https://github.com/naturalcrit/homebrewery/issues/3294)
* [x] Upgrade Font-Awesome to v6.51
* [x] Allow downloaded files to be uploaded via {{openSans **NEW {{fa,fa-plus-square}} → FROM UPLOAD {{fa,fa-upload}}**}}
##### G-Ambatte
* [x] Fix an edge case crash with empty documents
Fixes issue [#3315](https://github.com/naturalcrit/homebrewery/issues/3315)
* [x] Brews on the user page can be searched by tag; clicking a tag adds it to the filter
Fixes issue [#3164](https://github.com/naturalcrit/homebrewery/issues/3164)
* [x] Add *DiceFont* icons {{df,d20-20}} `{{df,icon-name}}`
##### abquintic
* [x] Fix ^super^ and ^^sub^^ highlighting in the text editor
* [x] Add new syntax for multiline Definition Lists:
```
Term
::Definition 1
::Definition 2
with more text
```
produces:
Term
::Definition 1
::Definition 2
with more text
Fixes issue [#2340](https://github.com/naturalcrit/homebrewery/issues/2340)
##### RKuerten :
* [x] Fix monster stat block backgrounds on print page
Fixes issue [#3275](https://github.com/naturalcrit/homebrewery/issues/3275)
* [x] Added new text editor theme: "Darkvision".
##### calculuschild, G-Ambatte, 5e-Cleric
* [x] Codebase and UI cleanup
}}
\page
### Friday 21/2/2024 - v3.11.0
{{taskList {{taskList
##### Gazook89 ##### Gazook89
@@ -166,14 +229,16 @@ Fixes issue [1488](https://github.com/naturalcrit/homebrewery/issues/1488)
Fixes issues [2510](https://github.com/naturalcrit/homebrewery/issues/2510), Fixes issues [2510](https://github.com/naturalcrit/homebrewery/issues/2510),
[2975](https://github.com/naturalcrit/homebrewery/issues/2975) [2975](https://github.com/naturalcrit/homebrewery/issues/2975)
* [x] New Variables syntax. See below for details. * [x] Brew Variables
}} }}
\
{{wide {{wide
### Brew Variable Syntax ### Brew Variable Syntax
You may already be familiar with `[link](url)` and `![image](url)` syntax. We have expanded this to include a third `$[variable](text)` syntax. All three of these syntaxes now share a common set of features: You may already be familiar with `[link](url)` and `![image](url)` synax. We have expanded this to include a third `$[variable](text)` syntax. All three of these syntaxes now share a common set of features:
{{varSyntaxTable {{varSyntaxTable
| syntax | description | | syntax | description |
@@ -1512,7 +1577,7 @@ myStyle {color: black}
### Sunday, 29/05/2016 - v2.1.0 ### Sunday, 29/05/2016 - v2.1.0
- Finally added a syntax for doing spell lists. A bit in-depth about why this took so long. Essentially I'm running out of syntax to use in stardard Markdown. There are too many unique elements in the PHB-style to be mapped. I solved this earlier by stacking certain elements together (eg. an `<hr>` before a `blockquote` turns it into moster state block), but those are getting unweildly. I would like to simply wrap these in `div`s with classes, but unfortunately Markdown stops processing when within HTML blocks. To get around this I wrote my own override to the Markdown parser and lexer to process Markdown within a simple div class wrapper. This should open the door for more unique syntaxes in the future. Big step! - Finally added a syntax for doing spell lists. A bit in-depth about why this took so long. Essentially I'm running out of syntax to use in stardard Markdown. There are too many unique elements in the PHB-style to be mapped. I solved this earlier by stacking certain elements together (eg. an `<hr>` before a `blockquote` turns it into moster state block), but those are getting unweildly. I would like to simply wrap these in `div`s with classes, but unfortunately Markdown stops processing when within HTML blocks. To get around this I wrote my own override to the Markdown parser and lexer to process Markdown within a simple div class wrapper. This should open the door for more unique syntaxes in the future. Big step!
- Override Ctrl+P (and cmd+P) to launch to the print page. Many people try to just print either the editing or share page to get a PDF. While this dones;t make much sense, I do get a ton of issues about it. So now if you try to do this, it'll just bring you imediately to the print page. Everybody wins! - Override Ctrl+P (and cmd+P) to launch to the print page. Many people try to just print either the editing or share page to get a PDF. While this dones;t make much sense, I do get a ton of issues about it. So now if you try to do this, it'll just bring you imediately to the print page. Everybody wins!
- The onboarding flow has also been confusing a few users (Homepage -> new -> save -> edit page). If you edit the Homepage text now, a Call to Action to save your work will pop-up. - The onboarding flow has also been confusing a few users (Homepage new save edit page). If you edit the Homepage text now, a Call to Action to save your work will pop-up.
- Added a 'Recently Edited' and 'Recently Viewed' nav item to the edit and share page respectively. Each will remember the last 8 items you edited or viewed and when you viewed it. Makes use of the new title attribute of brews to easy navigatation. - Added a 'Recently Edited' and 'Recently Viewed' nav item to the edit and share page respectively. Each will remember the last 8 items you edited or viewed and when you viewed it. Makes use of the new title attribute of brews to easy navigatation.
- Paragraphs now indent properly after lists (thanks u/slitjen!) - Paragraphs now indent properly after lists (thanks u/slitjen!)

View File

@@ -20,9 +20,9 @@ const PAGE_HEIGHT = 1056;
const INITIAL_CONTENT = dedent` const INITIAL_CONTENT = dedent`
<!DOCTYPE html><html><head> <!DOCTYPE html><html><head>
<link href="//use.fontawesome.com/releases/v5.15.1/css/all.css" rel="stylesheet" /> <link href="//use.fontawesome.com/releases/v6.5.1/css/all.css" rel="stylesheet" type="text/css" />
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" /> <link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
<link href='/homebrew/bundle.css' rel='stylesheet' /> <link href='/homebrew/bundle.css' type="text/css" rel='stylesheet' />
<base target=_blank> <base target=_blank>
</head><body style='overflow: hidden'><div></div></body></html>`; </head><body style='overflow: hidden'><div></div></body></html>`;
@@ -211,11 +211,11 @@ const BrewRenderer = (props)=>{
<RenderWarnings /> <RenderWarnings />
<NotificationPopup /> <NotificationPopup />
</div> </div>
<link href={`/themes/${rendererPath}/Blank/style.css`} rel='stylesheet'/> <link href={`/themes/${rendererPath}/Blank/style.css`} type="text/css" rel='stylesheet'/>
{baseThemePath && {baseThemePath &&
<link href={`/themes/${rendererPath}/${baseThemePath}/style.css`} rel='stylesheet'/> <link href={`/themes/${rendererPath}/${baseThemePath}/style.css`} type="text/css" rel='stylesheet'/>
} }
<link href={`/themes/${rendererPath}/${themePath}/style.css`} rel='stylesheet'/> <link href={`/themes/${rendererPath}/${themePath}/style.css`} type="text/css" rel='stylesheet'/>
{/* Apply CSS from Style tab and render pages from Markdown tab */} {/* Apply CSS from Style tab and render pages from Markdown tab */}
{state.isMounted {state.isMounted

View File

@@ -14,6 +14,15 @@
box-shadow : 1px 4px 14px #000000; box-shadow : 1px 4px 14px #000000;
} }
} }
&::-webkit-scrollbar {
width: 20px;
}
&::-webkit-scrollbar-thumb {
width:20px;
background: linear-gradient(90deg, #d3c1af 15px, #00000000 15px);
}
} }
.pane { position : relative; } .pane { position : relative; }
.pageInfo { .pageInfo {

View File

@@ -151,30 +151,37 @@ const Editor = createClass({
// definition lists // definition lists
if(line.includes('::')){ if(line.includes('::')){
const regex = /^([^\n]*?)::([^\n]*)(?:\n|$)/ym; if(/^:*$/.test(line) == true){ return };
const regex = /^([^\n]*?:?\s?)(::[^\n]*)(?:\n|$)/ymd; // the `d` flag, for match indices, throws an ESLint error.
let match; let match;
while ((match = regex.exec(line)) != null){ while ((match = regex.exec(line)) != null){
codeMirror.markText({ line: lineNumber, ch: line.indexOf(match[0]) }, { line: lineNumber, ch: line.indexOf(match[0]) + match[0].length }, { className: 'define' }); codeMirror.markText({ line: lineNumber, ch: match.indices[0][0] }, { line: lineNumber, ch: match.indices[0][1] }, { className: 'dl-highlight' });
codeMirror.markText({ line: lineNumber, ch: line.indexOf(match[1]) }, { line: lineNumber, ch: line.indexOf(match[1]) + match[1].length }, { className: 'term' }); codeMirror.markText({ line: lineNumber, ch: match.indices[1][0] }, { line: lineNumber, ch: match.indices[1][1] }, { className: 'dt-highlight' });
codeMirror.markText({ line: lineNumber, ch: line.indexOf(match[2]) }, { line: lineNumber, ch: line.indexOf(match[2]) + match[2].length }, { className: 'definition' }); codeMirror.markText({ line: lineNumber, ch: match.indices[2][0] }, { line: lineNumber, ch: match.indices[2][1] }, { className: 'dd-highlight' });
const ddIndex = match.indices[2][0];
let colons = /::/g;
let colonMatches = colons.exec(match[2]);
if(colonMatches !== null){
codeMirror.markText({ line: lineNumber, ch: colonMatches.index + ddIndex }, { line: lineNumber, ch: colonMatches.index + colonMatches[0].length + ddIndex }, { className: 'dl-colon-highlight'} )
}
} }
} }
// Superscript // Subscript & Superscript
if(line.includes('\^')) { if(line.includes('^')) {
const regex = /\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^/g; let startIndex = line.indexOf('^');
let match; const superRegex = /\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^/gy;
while ((match = regex.exec(line)) != null) { const subRegex = /\^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^\^/gy;
codeMirror.markText({ line: lineNumber, ch: line.indexOf(match[1]) - 1 }, { line: lineNumber, ch: line.indexOf(match[1]) + match[1].length + 1 }, { className: 'superscript' });
} while (startIndex >= 0) {
} superRegex.lastIndex = subRegex.lastIndex = startIndex;
let isSuper = false;
// Subscript let match = subRegex.exec(line) || superRegex.exec(line);
if(line.includes('^^')) { if (match) {
const regex = /\^\^(?!\s)(?=([^\n\^]*[^\s\^]))\1\^\^/g; isSuper = !subRegex.lastIndex;
let match; codeMirror.markText({ line: lineNumber, ch: match.index }, { line: lineNumber, ch: match.index + match[0].length }, { className: isSuper ? 'superscript' : 'subscript' });
while ((match = regex.exec(line)) != null) { }
codeMirror.markText({ line: lineNumber, ch: line.indexOf(match[1]) - 2 }, { line: lineNumber, ch: line.indexOf(match[1]) + match[1].length + 2 }, { className: 'subscript' }); startIndex = line.indexOf('^', Math.max(startIndex + 1, subRegex.lastIndex, superRegex.lastIndex));
} }
} }

View File

@@ -55,6 +55,16 @@
vertical-align : sub; vertical-align : sub;
font-size : 0.9em; font-size : 0.9em;
} }
.dl-highlight {
&.dl-colon-highlight {
font-weight : bold;
color : #949494;
background : #E5E5E5;
border-radius : 3px;
}
&.dt-highlight { color : rgb(96, 117, 143); }
&.dd-highlight { color : rgb(97, 57, 178); }
}
} }
.brewJump { .brewJump {

View File

@@ -15,6 +15,14 @@
} }
&.listPage .content { &.listPage .content {
overflow-y : scroll; overflow-y : scroll;
&::-webkit-scrollbar {
width: 20px;
}
&::-webkit-scrollbar-thumb {
width:20px;
background: linear-gradient(90deg, #d3c1af 15px, #00000000 15px);
}
} }
} }
} }

View File

@@ -1,272 +1,351 @@
@import "naturalcrit/styles/colors.less"; @import 'naturalcrit/styles/colors.less';
@navbarHeight : 28px;
@keyframes pinkColoring {
0% {color : pink;}
50% {color : pink;}
75% {color : red;}
100% {color : pink;}
}
.homebrew nav {
.homebrewLogo {
.animate(color);
font-family : CodeBold;
font-size : 12px;
color : white;
div {
margin-top : 2px;
margin-bottom : -2px;
}
&:hover {
color : @blue;
}
}
.editTitle.navItem {
padding : 2px 12px;
input {
font-family : "Open Sans", sans-serif;
font-size : 12px;
font-weight : 800;
width : 250px;
margin : 0;
padding : 2px;
text-align : center;
color : white;
border : 1px solid @blue;
outline : none;
background-color : transparent;
}
.charCount {
display : inline-block;
margin-left : 8px;
text-align : right;
vertical-align : bottom;
color : #666;
&.max {
color : @red;
}
}
}
.brewTitle.navItem {
font-size : 12px;
font-weight : 800;
height : 100%;
text-align : center;
text-transform : initial;
color : white;
background-color : transparent;
flex-grow : 1;
}
.save-menu {
.dropdown {
z-index : 1000;
}
.navItem i.fa-power-off {
color : red;
&.active {
color : rgb(0, 182, 52);
filter : drop-shadow(0 0 2px rgba(0, 182, 52, 0.765));
}
}
}
.patreon.navItem {
border-right : 1px solid #666;
border-left : 1px solid #666;
&:hover i {
color : red;
}
i {
.animate(color);
animation-name : pinkColoring;
animation-duration : 2s;
color : pink;
}
}
.recent.navDropdownContainer {
position : relative;
.navDropdown .navItem {
overflow : hidden auto;
max-height : ~"calc(100vh - 28px)";
scrollbar-color : #666 #333;
scrollbar-width : thin;
#backgroundColorsHover; @navbarHeight : 28px;
.animate(background-color);
position : relative; @keyframes pinkColoring {
display : block; 0% { color : pink; }
overflow : clip; 50% { color : pink; }
box-sizing : border-box; 75% { color : red; }
padding : 8px 5px 13px; 100% { color : pink; }
text-decoration : none; }
color : white;
border-top : 1px solid #888; @keyframes glideDropDown {
background-color : #333; 0% {
.clear { background-color : #333333;
position : absolute; opacity : 0;
top : 50%; transform : translate(0px, -100%);
right : 0;
display : none;
width : 20px;
height : 100%;
transform : translateY(-50%);
opacity : 70%;
border-radius : 3px;
background-color : #333;
&:hover {
opacity : 100%;
}
i {
font-size : 10px;
width : 100%;
height : 100%;
margin : 0;
text-align : center;
}
}
&:hover {
background-color : @blue;
.clear {
display : grid;
place-content : center;
}
}
.title {
display : inline-block;
overflow : hidden;
width : 100%;
white-space : nowrap;
text-overflow : ellipsis;
}
.time {
font-size : 0.7em;
position : absolute;
right : 2px;
bottom : 2px;
color : #888;
}
&.header {
display : block;
box-sizing : border-box;
padding : 5px 0;
text-align : center;
color : #BBB;
border-top : 1px solid #888;
background-color : #333;
&:nth-of-type(1) {
background-color : darken(@teal, 20%);
}
&:nth-of-type(2) {
background-color : darken(@purple, 30%);
}
}
}
} }
.metadata.navItem { 100% {
position : relative; background-color : #333333;
opacity : 1;
transform : translate(0px, 0px);
}
}
.homebrew nav {
background-color : #333333;
.navContent {
position : relative;
z-index : 2;
display : flex;
justify-content : space-between;
}
.navSection {
display : flex; display : flex;
align-items : center; align-items : center;
height : 100%; &:last-child .navItem { border-left : 1px solid #666666; }
padding : 0; }
flex-grow : 1; // "NaturalCrit" logo
i { .navLogo {
margin-right : 10px; display : block;
margin-top : 0px;
margin-right : 8px;
margin-left : 8px;
color : white;
text-decoration : none;
&:hover {
.name { color : @orange; }
svg { fill : @orange; }
} }
.window { svg {
position : absolute; height : 13px;
z-index : -1; margin-right : 0.2em;
bottom : 0; cursor : pointer;
left : 50%; fill : white;
display : flex; }
justify-content : flex-start; span.name {
width : 440px; font-family : 'CodeLight';
max-height : ~"calc(100vh - 28px)"; font-size : 15px;
margin : 0 auto; span.crit { font-family : 'CodeBold'; }
padding : 0 10px 5px; small {
transition : transform 0.4s, opacity 0.4s; font-family : 'Open Sans';
border : 3px solid #444; font-size : 0.3em;
border-top : unset; font-weight : 800;
border-radius : 0 0 5px 5px; text-transform : uppercase;
background-color : #333;
box-shadow : inset 0 7px 9px -7px #111;
flex-flow : row wrap;
align-content : baseline;
&.active {
transform : translateX(-50%) translateY(100%);
opacity : 1;
} }
&.inactive { }
transform : translateX(-50%) translateY(0%); }
opacity : 0; .navItem {
#backgroundColorsHover;
.animate(background-color);
padding : 8px 12px;
font-size : 10px;
font-weight : 800;
line-height : 13px;
color : white;
text-decoration : none;
text-transform : uppercase;
cursor : pointer;
background-color : #333333;
i {
float : right;
margin-left : 5px;
font-size : 13px;
}
&.patreon {
border-right : 1px solid #666666;
border-left : 1px solid #666666;
&:hover i { color : red; }
i {
color : pink;
.animate(color);
animation-name : pinkColoring;
animation-duration : 2s;
} }
.row { }
display : flex; &.editTitle { // this is not needed at all currently - you used to be able to edit the title via the navbar.
width : 100%; padding : 2px 12px;
flex-flow : row wrap; input {
h4 { width : 250px;
display : block; padding : 2px;
box-sizing : border-box; margin : 0;
min-width : 76px; font-family : 'Open Sans', sans-serif;
padding : 5px 0; font-size : 12px;
text-align : center; font-weight : 800;
color : #BBB; color : white;
flex-basis : 20%; text-align : center;
flex-grow : 1; background-color : transparent;
border : 1px solid @blue;
outline : none;
}
.charCount {
display : inline-block;
margin-left : 8px;
color : #666666;
text-align : right;
vertical-align : bottom;
&.max { color : @red; }
}
}
&.brewTitle {
flex-grow : 1;
font-size : 12px;
font-weight : 800;
color : white;
text-align : center;
text-transform : initial;
background-color : transparent;
}
// "The Homebrewery" logo
&.homebrewLogo {
.animate(color);
font-family : 'CodeBold';
font-size : 12px;
color : white;
div {
margin-top : 2px;
margin-bottom : -2px;
}
&:hover { color : @blue; }
}
&.metadata {
position : relative;
display : flex;
flex-grow : 1;
align-items : center;
height : 100%;
padding : 0;
i { margin-right : 10px;}
.window {
position : absolute;
bottom : 0;
left : 50%;
z-index : -1;
display : flex;
flex-flow : row wrap;
align-content : baseline;
justify-content : flex-start;
width : 440px;
max-height : ~'calc(100vh - 28px)';
padding : 0 10px 5px;
margin : 0 auto;
background-color : #333333;
border : 3px solid #444444;
border-top : unset;
border-radius : 0 0 5px 5px;
box-shadow : inset 0 7px 9px -7px #111111;
transition : transform 0.4s, opacity 0.4s;
&.active {
opacity : 1;
transform : translateX(-50%) translateY(100%);
} }
p { &.inactive {
font-family : "Open Sans", sans-serif; opacity : 0;
font-size : 10px; transform : translateX(-50%) translateY(0%);
font-weight : normal; }
padding : 5px 0; .row {
text-transform : initial; display : flex;
flex-basis : 80%; flex-flow : row wrap;
flex-grow : 1; width : 100%;
.tag { h4 {
display : inline-block; box-sizing : border-box;
margin : 2px 2px; display : block;
padding : 2px; flex-basis : 20%;
border : 2px solid grey; flex-grow : 1;
border-radius : 5px; min-width : 76px;
background-color : #444; padding : 5px 0;
color : #BBBBBB;
text-align : center;
} }
a.userPageLink { p {
text-decoration : none; flex-basis : 80%;
color : white; flex-grow : 1;
&:hover { padding : 5px 0;
text-decoration : underline; font-family : 'Open Sans', sans-serif;
font-size : 10px;
font-weight : normal;
text-transform : initial;
.tag {
display : inline-block;
padding : 2px;
margin : 2px 2px;
background-color : #444444;
border : 2px solid grey;
border-radius : 5px;
}
a.userPageLink {
color : white;
text-decoration : none;
&:hover { text-decoration : underline; }
} }
} }
&:nth-of-type(even) { background-color : #555555; }
} }
&:nth-of-type(even) { }
background-color : #555; }
&.warning {
position : relative;
color : white;
background-color : @orange;
&:hover > .dropdown { visibility : visible; }
.dropdown {
position : absolute;
top : 28px;
left : 0;
z-index : 10000;
box-sizing : border-box;
display : block;
width : 100%;
padding : 13px 5px;
text-align : center;
visibility : hidden;
background-color : #333333;
}
}
&.account {
min-width : 100px;
&.username { text-transform : none;}
}
}
.navDropdownContainer {
position : relative;
.navDropdown {
position: absolute;
top: 28px;
right: 0px;
z-index: 10000;
width: max-content;
min-width:100%;
max-height: calc(100vh - 28px);
overflow: hidden auto;
display: flex;
flex-direction: column;
align-items: flex-end;
.navItem {
position : relative;
display : flex;
justify-content : space-between;
align-items : center;
width : 100%;
border : 1px solid #888888;
border-bottom : 0;
animation-name : glideDropDown;
animation-duration : 0.4s;
}
}
&.recent {
position : relative;
.navDropdown .navItem {
#backgroundColorsHover;
.animate(background-color);
position : relative;
box-sizing : border-box;
display : block;
max-width : 15em;
max-height : ~'calc(100vh - 28px)';
padding : 8px 5px 13px;
overflow : hidden auto;
color : white;
text-decoration : none;
background-color : #333333;
border-top : 1px solid #888888;
scrollbar-color : #666666 #333333;
scrollbar-width : thin;
.clear {
position : absolute;
top : 50%;
right : 0;
display : none;
width : 20px;
height : 100%;
background-color : #333333;
border-radius : 3px;
opacity : 70%;
transform : translateY(-50%);
&:hover { opacity : 100%; }
i {
width : 100%;
height : 100%;
margin : 0;
font-size : 10px;
text-align : center;
}
}
&:hover {
background-color : @blue;
.clear {
display : grid;
place-content : center;
}
}
.title {
display : inline-block;
width : 100%;
overflow : hidden auto;
text-overflow : ellipsis;
white-space : nowrap;
}
.time {
position : absolute;
right : 2px;
bottom : 2px;
font-size : 0.7em;
color : #888888;
}
&.header {
box-sizing : border-box;
display : block;
padding : 5px 0;
color : #BBBBBB;
text-align : center;
background-color : #333333;
border-top : 1px solid #888888;
&:nth-of-type(1) { background-color : darken(@teal, 20%); }
&:nth-of-type(2) { background-color : darken(@purple, 30%); }
} }
} }
} }
} }
.warning.navItem { }
position : relative;
color : white; // this should likely be refactored into .navDropdownContainer
background-color : @orange; .save-menu {
&:hover > .dropdown { .dropdown { z-index : 1000; }
visibility : visible; .navItem i.fa-power-off {
color : red;
&.active {
color : rgb(0, 182, 52);
filter : drop-shadow(0 0 2px rgba(0, 182, 52, 0.765));
} }
.dropdown {
position : absolute;
z-index : 10000;
top : 28px;
left : 0;
display : block;
visibility : hidden;
box-sizing : border-box;
width : 100%;
padding : 13px 5px;
text-align : center;
background-color : #333;
}
}
.account.navItem {
min-width : 100px;
}
.account.username.navItem {
text-transform : none;
} }
} }

View File

@@ -1,12 +1,64 @@
const React = require('react'); const React = require('react');
const _ = require('lodash');
const Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');
const { splitTextStyleAndMetadata } = require('../../../shared/helpers.js'); // Importing the function from helpers.js
module.exports = function(props){ const BREWKEY = 'homebrewery-new';
return <Nav.item const STYLEKEY = 'homebrewery-new-style';
href='/new' const METAKEY = 'homebrewery-new-meta';
newTab={true}
color='purple' const NewBrew = () => {
icon='fas fa-plus-square'> const handleFileChange = (e) => {
new const file = e.target.files[0];
</Nav.item>; if (file) {
const reader = new FileReader();
reader.onload = (e) => {
const fileContent = e.target.result;
const newBrew = {
text: fileContent,
style: ''
};
if(fileContent.startsWith('```metadata')) {
splitTextStyleAndMetadata(newBrew); // Modify newBrew directly
localStorage.setItem(BREWKEY, newBrew.text);
localStorage.setItem(STYLEKEY, newBrew.style);
localStorage.setItem(METAKEY, JSON.stringify(_.pick(newBrew, ['title', 'description', 'tags', 'systems', 'renderer', 'theme', 'lang'])));
window.location.href = '/new';
} else {
alert('This file is invalid, please, enter a valid file');
}
};
reader.readAsText(file);
}
};
return (
<Nav.dropdown>
<Nav.item
className='new'
color='purple'
icon='fa-solid fa-plus-square'>
new
</Nav.item>
<Nav.item
className='fromBlank'
href='/new'
newTab={true}
color='purple'
icon='fa-solid fa-file'>
from blank
</Nav.item>
<Nav.item
className='fromFile'
color='purple'
icon='fa-solid fa-upload'
onClick={() => { document.getElementById('uploadTxt').click(); }}>
<input id="uploadTxt" className='newFromLocal' type="file" onChange={handleFileChange} style={{ display: 'none' }} />
from file
</Nav.item>
</Nav.dropdown>
);
}; };
module.exports = NewBrew;

View File

@@ -262,8 +262,8 @@ 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/Blank/style.css' type="text/css" rel='stylesheet'/>
<link href='/themes/V3/5ePHB/style.css' rel='stylesheet'/> <link href='/themes/V3/5ePHB/style.css' type="text/css" rel='stylesheet'/>
{this.props.navItems} {this.props.navItems}
{this.renderSortOptions()} {this.renderSortOptions()}
{this.renderTagsOptions()} {this.renderTagsOptions()}

View File

@@ -122,6 +122,16 @@ const errorIndex = (props)=>{
An error occurred while attempting to remove the user from the Homebrewery document author list! An error occurred while attempting to remove the user from the Homebrewery document author list!
**Brew ID:** ${props.brew.brewId}`, **Brew ID:** ${props.brew.brewId}`,
// Brew locked by Administrators error
'100' : dedent`
## This brew has been locked.
Please contact the Administrators to unlock this document.
**Brew ID:** ${props.brew.brewId}
**Brew Title:** ${props.brew.brewTitle}`,
}; };
}; };

View File

@@ -96,11 +96,11 @@ const PrintPage = createClass({
return <div> return <div>
<Meta name='robots' content='noindex, nofollow' /> <Meta name='robots' content='noindex, nofollow' />
<link href={`/themes/${rendererPath}/Blank/style.css`} rel='stylesheet'/> <link href={`/themes/${rendererPath}/Blank/style.css`} type="text/css" rel='stylesheet'/>
{baseThemePath && {baseThemePath &&
<link href={`/themes/${rendererPath}/${baseThemePath}/style.css`} rel='stylesheet'/> <link href={`/themes/${rendererPath}/${baseThemePath}/style.css`} type="text/css" rel='stylesheet'/>
} }
<link href={`/themes/${rendererPath}/${themePath}/style.css`} rel='stylesheet'/> <link href={`/themes/${rendererPath}/${themePath}/style.css`} type="text/css" rel='stylesheet'/>
{/* Apply CSS from Style tab */} {/* Apply CSS from Style tab */}
{this.renderStyle()} {this.renderStyle()}
<div className='pages' ref='pages' lang={this.state.brew.lang}> <div className='pages' ref='pages' lang={this.state.brew.lang}>

View File

@@ -12,9 +12,9 @@ const template = async function(name, title='', props = {}){
<html> <html>
<head> <head>
<meta name="viewport" content="width=device-width, initial-scale=1, height=device-height, interactive-widget=resizes-visual" /> <meta name="viewport" content="width=device-width, initial-scale=1, height=device-height, interactive-widget=resizes-visual" />
<link href="//use.fontawesome.com/releases/v5.15.1/css/all.css" rel="stylesheet" /> <link href="//use.fontawesome.com/releases/v6.5.1/css/all.css" rel="stylesheet" type="text/css" />
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" /> <link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
<link href=${`/${name}/bundle.css`} rel='stylesheet' /> <link href=${`/${name}/bundle.css`} type="text/css" rel='stylesheet' />
<link rel="icon" href="/assets/favicon.ico" type="image/x-icon" /> <link rel="icon" href="/assets/favicon.ico" type="image/x-icon" />
${ogMetaTags} ${ogMetaTags}
<meta name="twitter:card" content="summary"> <meta name="twitter:card" content="summary">

1708
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"name": "homebrewery", "name": "homebrewery",
"description": "Create authentic looking D&D homebrews using only markdown", "description": "Create authentic looking D&D homebrews using only markdown",
"version": "3.11.0", "version": "3.12.0",
"engines": { "engines": {
"npm": "^10.2.x", "npm": "^10.2.x",
"node": "^20.8.x" "node": "^20.8.x"
@@ -31,6 +31,7 @@
"test:mustache-syntax:inline": "jest '.*(mustache-syntax).*' -t '^Inline:.*' --verbose --noStackTrace", "test:mustache-syntax:inline": "jest '.*(mustache-syntax).*' -t '^Inline:.*' --verbose --noStackTrace",
"test:mustache-syntax:block": "jest '.*(mustache-syntax).*' -t '^Block:.*' --verbose --noStackTrace", "test:mustache-syntax:block": "jest '.*(mustache-syntax).*' -t '^Block:.*' --verbose --noStackTrace",
"test:mustache-syntax:injection": "jest '.*(mustache-syntax).*' -t '^Injection:.*' --verbose --noStackTrace", "test:mustache-syntax:injection": "jest '.*(mustache-syntax).*' -t '^Injection:.*' --verbose --noStackTrace",
"test:definition-lists": "jest tests/markdown/definition-lists.test.js --verbose --noStackTrace",
"test:route": "jest tests/routes/static-pages.test.js --verbose", "test:route": "jest tests/routes/static-pages.test.js --verbose",
"phb": "node scripts/phb.js", "phb": "node scripts/phb.js",
"prod": "set NODE_ENV=production && npm run build", "prod": "set NODE_ENV=production && npm run build",
@@ -80,10 +81,10 @@
] ]
}, },
"dependencies": { "dependencies": {
"@babel/core": "^7.23.9", "@babel/core": "^7.24.3",
"@babel/plugin-transform-runtime": "^7.23.9", "@babel/plugin-transform-runtime": "^7.24.3",
"@babel/preset-env": "^7.23.9", "@babel/preset-env": "^7.24.3",
"@babel/preset-react": "^7.23.3", "@babel/preset-react": "^7.24.1",
"@googleapis/drive": "^8.7.0", "@googleapis/drive": "^8.7.0",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
"classnames": "^2.5.1", "classnames": "^2.5.1",
@@ -92,7 +93,7 @@
"create-react-class": "^15.7.0", "create-react-class": "^15.7.0",
"dedent-tabs": "^0.10.3", "dedent-tabs": "^0.10.3",
"expr-eval": "^2.0.2", "expr-eval": "^2.0.2",
"express": "^4.18.2", "express": "^4.19.2",
"express-async-handler": "^1.2.0", "express-async-handler": "^1.2.0",
"express-static-gzip": "2.1.7", "express-static-gzip": "2.1.7",
"fs-extra": "11.2.0", "fs-extra": "11.2.0",
@@ -106,13 +107,13 @@
"marked-smartypants-lite": "^1.0.2", "marked-smartypants-lite": "^1.0.2",
"markedLegacy": "npm:marked@^0.3.19", "markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.30.1", "moment": "^2.30.1",
"mongoose": "^8.2.0", "mongoose": "^8.2.3",
"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.22.1", "react-router-dom": "6.22.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"
@@ -120,7 +121,7 @@
"devDependencies": { "devDependencies": {
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-plugin-jest": "^27.9.0", "eslint-plugin-jest": "^27.9.0",
"eslint-plugin-react": "^7.33.2", "eslint-plugin-react": "^7.34.1",
"jest": "^29.7.0", "jest": "^29.7.0",
"jest-expect-message": "^1.1.3", "jest-expect-message": "^1.1.3",
"postcss-less": "^6.0.0", "postcss-less": "^6.0.0",

View File

@@ -17,21 +17,8 @@ const asyncHandler = require('express-async-handler');
const { DEFAULT_BREW } = require('./brewDefaults.js'); const { DEFAULT_BREW } = require('./brewDefaults.js');
const splitTextStyleAndMetadata = (brew)=>{ const { splitTextStyleAndMetadata } = require('../shared/helpers.js');
brew.text = brew.text.replaceAll('\r\n', '\n');
if(brew.text.startsWith('```metadata')) {
const index = brew.text.indexOf('```\n\n');
const metadataSection = brew.text.slice(12, index - 1);
const metadata = yaml.load(metadataSection);
Object.assign(brew, _.pick(metadata, ['title', 'description', 'tags', 'systems', 'renderer', 'theme', 'lang']));
brew.text = brew.text.slice(index + 5);
}
if(brew.text.startsWith('```css')) {
const index = brew.text.indexOf('```\n\n');
brew.style = brew.text.slice(7, index - 1);
brew.text = brew.text.slice(index + 5);
}
};
const sanitizeBrew = (brew, accessType)=>{ const sanitizeBrew = (brew, accessType)=>{
brew._id = undefined; brew._id = undefined;

View File

@@ -54,6 +54,10 @@ const api = {
}); });
stub = stub?.toObject(); stub = stub?.toObject();
if(stub?.lock?.locked && accessType != 'edit') {
throw { HBErrorCode: '100', code: stub.lock.code, message: stub.lock.message, brewId: stub.shareId, brewTitle: stub.title };
}
// If there is a google id, try to find the google brew // If there is a google id, try to find the google brew
if(!stubOnly && (googleId || stub?.googleId)) { if(!stubOnly && (googleId || stub?.googleId)) {
let googleError; let googleError;

View File

@@ -117,7 +117,7 @@ describe('Tests for api', ()=>{
id : '123456789012345678901234567890123abcdefghijkl' id : '123456789012345678901234567890123abcdefghijkl'
} }
}); });
expect(googleId).toEqual('123456789012345678901234567890123'); expect(googleId).toEqual('123456789012345678901234567890123');
expect(id).toEqual('abcdefghijkl'); expect(id).toEqual('abcdefghijkl');
}); });
@@ -128,7 +128,7 @@ describe('Tests for api', ()=>{
id : '123456789012345678901234567890123abcdefghij' id : '123456789012345678901234567890123abcdefghij'
} }
}); });
expect(googleId).toEqual('123456789012345678901234567890123'); expect(googleId).toEqual('123456789012345678901234567890123');
expect(id).toEqual('abcdefghij'); expect(id).toEqual('abcdefghij');
}); });
@@ -298,6 +298,18 @@ describe('Tests for api', ()=>{
expect(model.get).toHaveBeenCalledWith({ shareId: '1' }); expect(model.get).toHaveBeenCalledWith({ shareId: '1' });
expect(google.getGoogleBrew).toHaveBeenCalledWith('2', '1', 'share'); expect(google.getGoogleBrew).toHaveBeenCalledWith('2', '1', 'share');
}); });
it('access is denied to a locked brew', async()=>{
const lockBrew = { title: 'test brew', shareId: '1', lock: { locked: true, code: 404, message: 'brew locked' } };
model.get = jest.fn(()=>toBrewPromise(lockBrew));
api.getId = jest.fn(()=>({ id: '1', googleId: undefined }));
const fn = api.getBrew('share', false);
const req = { brew: {} };
const next = jest.fn();
await expect(fn(req, null, next)).rejects.toEqual({ 'HBErrorCode': '100', 'brewId': '1', 'brewTitle': 'test brew', 'code': 404, 'message': 'brew locked' });
});
}); });
describe('mergeBrewText', ()=>{ describe('mergeBrewText', ()=>{

22
shared/helpers.js Normal file
View File

@@ -0,0 +1,22 @@
const _ = require('lodash');
const yaml = require('js-yaml');
const splitTextStyleAndMetadata = (brew) => {
brew.text = brew.text.replaceAll('\r\n', '\n');
if (brew.text.startsWith('```metadata')) {
const index = brew.text.indexOf('```\n\n');
const metadataSection = brew.text.slice(12, index - 1);
const metadata = yaml.load(metadataSection);
Object.assign(brew, _.pick(metadata, ['title', 'description', 'tags', 'systems', 'renderer', 'theme', 'lang']));
brew.text = brew.text.slice(index + 5);
}
if (brew.text.startsWith('```css')) {
const index = brew.text.indexOf('```\n\n');
brew.style = brew.text.slice(7, index - 1);
brew.text = brew.text.slice(index + 5);
}
};
module.exports = {
splitTextStyleAndMetadata
};

View File

@@ -436,7 +436,7 @@ const CodeEditor = createClass({
render : function(){ render : function(){
return <> return <>
<link href={`../homebrew/cm-themes/${this.props.editorTheme}.css`} rel='stylesheet' /> <link href={`../homebrew/cm-themes/${this.props.editorTheme}.css`} type="text/css" rel='stylesheet' />
<div className='codeEditor' ref='editor' style={this.props.style}/> <div className='codeEditor' ref='editor' style={this.props.style}/>
</>; </>;
} }

View File

@@ -17,13 +17,28 @@
text-shadow: none; text-shadow: none;
font-weight: 600; font-weight: 600;
color: grey; color: grey;
} }
.sourceMoveFlash .CodeMirror-line{ .sourceMoveFlash .CodeMirror-line{
animation-name: sourceMoveAnimation; animation-name: sourceMoveAnimation;
animation-duration: 0.4s; animation-duration: 0.4s;
} }
.CodeMirror-sizer {
padding-right: 0 !important;
//this setting must be !important, because CodeMirror sets it inline. Achieves overlay scrollbar
}
.CodeMirror-vscrollbar {
&::-webkit-scrollbar {
width: 20px;
}
&::-webkit-scrollbar-thumb {
width: 20px;
background: linear-gradient(90deg, #85858599 15px, #80808000 15px);
}
}
//.cm-tab { //.cm-tab {
// background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAQAAACOs/baAAAARUlEQVR4nGJgIAG8JkXxUAcCtDWemcGR1lY4MvgzCEKY7jSBjgxBDAG09UEQzAe0AMwMHrSOAwEGRtpaMIwAAAAA//8DAG4ID9EKs6YqAAAAAElFTkSuQmCC) no-repeat right; // background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAQAAACOs/baAAAARUlEQVR4nGJgIAG8JkXxUAcCtDWemcGR1lY4MvgzCEKY7jSBjgxBDAG09UEQzAe0AMwMHrSOAwEGRtpaMIwAAAAA//8DAG4ID9EKs6YqAAAAAElFTkSuQmCC) no-repeat right;
//} //}

View File

@@ -294,10 +294,10 @@ const superSubScripts = {
} }
}; };
const definitionLists = { const definitionListsInline = {
name : 'definitionLists', name : 'definitionListsInline',
level : 'block', level : 'block',
start(src) { return src.match(/^.*?::.*/m)?.index; }, // Hint to Marked.js to stop and check for a match start(src) { return src.match(/^[^\n]*?::[^\n]*/m)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) { tokenizer(src, tokens) {
const regex = /^([^\n]*?)::([^\n]*)(?:\n|$)/ym; const regex = /^([^\n]*?)::([^\n]*)(?:\n|$)/ym;
let match; let match;
@@ -312,7 +312,7 @@ const definitionLists = {
} }
if(definitions.length) { if(definitions.length) {
return { return {
type : 'definitionLists', type : 'definitionListsInline',
raw : src.slice(0, endIndex), raw : src.slice(0, endIndex),
definitions definitions
}; };
@@ -326,6 +326,51 @@ const definitionLists = {
} }
}; };
const definitionListsMultiline = {
name : 'definitionListsMultiline',
level : 'block',
start(src) { return src.match(/^[^\n]*\n::/m)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const regex = /(\n?\n?(?!::)[^\n]+?(?=\n::))|\n::(.(?:.|\n)*?(?=(?:\n::)|(?:\n\n)|$))/y;
let match;
let endIndex = 0;
const definitions = [];
while (match = regex.exec(src)) {
if(match[1]) {
if(this.lexer.blockTokens(match[1].trim())[0]?.type !== 'paragraph') // DT must not be another block-level token besides <p>
break;
definitions.push({
dt : this.lexer.inlineTokens(match[1].trim()),
dds : []
});
}
if(match[2] && definitions.length) {
definitions[definitions.length - 1].dds.push(
this.lexer.inlineTokens(match[2].trim().replace(/\s/g, ' '))
);
}
endIndex = regex.lastIndex;
}
if(definitions.length) {
return {
type : 'definitionListsMultiline',
raw : src.slice(0, endIndex),
definitions
};
}
},
renderer(token) {
let returnVal = `<dl>`;
token.definitions.forEach((def)=>{
const dds = def.dds.map((s)=>{
return `\n<dd>${this.parser.parseInline(s).trim()}</dd>`;
}).join('');
returnVal += `<dt>${this.parser.parseInline(def.dt)}</dt>${dds}\n`;
});
returnVal = returnVal.trim();
return `${returnVal}</dl>`;
}
};
//v=====--------------------< Variable Handling >-------------------=====v// 242 lines //v=====--------------------< Variable Handling >-------------------=====v// 242 lines
const replaceVar = function(input, hoist=false, allowUnresolved=false) { const replaceVar = function(input, hoist=false, allowUnresolved=false) {
@@ -572,7 +617,7 @@ function MarkedVariables() {
//^=====--------------------< Variable Handling >-------------------=====^// //^=====--------------------< Variable Handling >-------------------=====^//
Marked.use(MarkedVariables()); Marked.use(MarkedVariables());
Marked.use({ extensions: [mustacheSpans, mustacheDivs, mustacheInjectInline, definitionLists, superSubScripts] }); Marked.use({ extensions: [definitionListsMultiline, definitionListsInline, superSubScripts, mustacheSpans, mustacheDivs, mustacheInjectInline] });
Marked.use(mustacheInjectBlock); Marked.use(mustacheInjectBlock);
Marked.use({ renderer: renderer, tokenizer: tokenizer, mangle: false }); Marked.use({ renderer: renderer, tokenizer: tokenizer, mangle: false });
Marked.use(MarkedExtendedTables(), MarkedGFMHeadingId(), MarkedSmartypantsLite()); Marked.use(MarkedExtendedTables(), MarkedGFMHeadingId(), MarkedSmartypantsLite());

View File

@@ -1,4 +1,4 @@
require('./nav.less'); require('client/homebrew/navbar/navbar.less');
const React = require('react'); const React = require('react');
const { useState, useRef, useEffect } = React; const { useState, useRef, useEffect } = React;
const createClass = require('create-react-class'); const createClass = require('create-react-class');
@@ -104,7 +104,7 @@ const Nav = {
}); });
return ( return (
<div className={`navDropdownContainer ${props.className}`} <div className={`navDropdownContainer ${props.className ?? ''}`}
ref={myRef} ref={myRef}
onMouseEnter = { props.trigger.includes('hover') ? ()=>handleDropdown(true) : undefined } onMouseEnter = { props.trigger.includes('hover') ? ()=>handleDropdown(true) : undefined }
onMouseLeave = { props.trigger.includes('hover') ? ()=>handleDropdown(false) : undefined } onMouseLeave = { props.trigger.includes('hover') ? ()=>handleDropdown(false) : undefined }

View File

@@ -1,97 +0,0 @@
@import '../styles/colors';
@keyframes glideDropDown {
0% {transform : translate(0px, -100%);
opacity : 0;
background-color: #333;}
100% {transform : translate(0px, 0px);
opacity : 1;
background-color: #333;}
}
nav{
background-color : #333;
.navContent{
position : relative;
display : flex;
justify-content : space-between;
z-index : 2;
}
.navSection{
display : flex;
align-items : center;
}
.navLogo{
display : block;
margin-top : 0px;
margin-right : 8px;
margin-left : 8px;
color : white;
text-decoration : none;
&:hover{
.name{ color : @orange; }
svg{ fill : @orange }
}
svg{
height : 13px;
margin-right : 0.2em;
cursor : pointer;
fill : white;
}
span.name{
font-family : 'CodeLight';
font-size : 15px;
span.crit{
font-family : 'CodeBold';
}
small{
font-family : 'Open Sans';
font-size : 0.3em;
font-weight : 800;
text-transform : uppercase;
}
}
}
.navItem{
#backgroundColorsHover;
.animate(background-color);
padding : 8px 12px;
cursor : pointer;
background-color : #333;
font-size : 10px;
font-weight : 800;
color : white;
text-decoration : none;
text-transform : uppercase;
line-height : 13px;
i{
margin-left : 5px;
font-size : 13px;
float : right;
}
}
.navSection:last-child .navItem{
border-left : 1px solid #666;
}
.navDropdownContainer{
position: relative;
.navDropdown {
position : absolute;
top : 28px;
left : 0px;
z-index : 10000;
width : 100%;
overflow : hidden auto;
max-height : calc(100vh - 28px);
.navItem{
animation-name: glideDropDown;
animation-duration: 0.4s;
position : relative;
display : block;
width : 100%;
vertical-align : middle;
padding : 8px 5px;
border : 1px solid #888;
border-bottom : 0;
}
}
}
}

View File

@@ -0,0 +1,91 @@
/* eslint-disable max-lines */
const Markdown = require('naturalcrit/markdown.js');
describe('Inline Definition Lists', ()=>{
test('No Term 1 Definition', function() {
const source = ':: My First Definition\n\n';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt></dt><dd>My First Definition</dd>\n</dl>');
});
test('Single Definition Term', function() {
const source = 'My term :: My First Definition\n\n';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>My term</dt><dd>My First Definition</dd>\n</dl>');
});
test('Multiple Definition Terms', function() {
const source = 'Term 1::Definition of Term 1\nTerm 2::Definition of Term 2\n\n';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt><dd>Definition of Term 1</dd>\n<dt>Term 2</dt><dd>Definition of Term 2</dd>\n</dl>');
});
});
describe('Multiline Definition Lists', ()=>{
test('Single Term, Single Definition', function() {
const source = 'Term 1\n::Definition 1\n\n';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1</dd></dl>');
});
test('Single Term, Plural Definitions', function() {
const source = 'Term 1\n::Definition 1\n::Definition 2\n\n';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd></dl>');
});
test('Multiple Term, Single Definitions', function() {
const source = 'Term 1\n::Definition 1\n\nTerm 2\n::Definition 1\n\n';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1</dd>\n<dt>Term 2</dt>\n<dd>Definition 1</dd></dl>');
});
test('Multiple Term, Plural Definitions', function() {
const source = 'Term 1\n::Definition 1\n::Definition 2\n\nTerm 2\n::Definition 1\n::Definition 2\n\n';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd>\n<dt>Term 2</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd></dl>');
});
test('Single Term, Single multi-line definition', function() {
const source = 'Term 1\n::Definition 1\nand more and\nmore and more\n\n';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1 and more and more and more</dd></dl>');
});
test('Single Term, Plural multi-line definitions', function() {
const source = 'Term 1\n::Definition 1\nand more and more\n::Definition 2\nand more\nand more\n::Definition 3\n\n';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1 and more and more</dd>\n<dd>Definition 2 and more and more</dd>\n<dd>Definition 3</dd></dl>');
});
test('Multiple Term, Single multi-line definition', function() {
const source = 'Term 1\n::Definition 1\nand more and more\n\nTerm 2\n::Definition 1\n::Definition 2\n\n';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1 and more and more</dd>\n<dt>Term 2</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd></dl>');
});
test('Multiple Term, Single multi-line definition, followed by an inline dl', function() {
const source = 'Term 1\n::Definition 1\nand more and more\n\nTerm 2\n::Definition 1\n::Definition 2\n\n::Inline Definition (no term)';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1 and more and more</dd>\n<dt>Term 2</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd></dl><dl><dt></dt><dd>Inline Definition (no term)</dd>\n</dl>');
});
test('Multiple Term, Single multi-line definition, followed by paragraph', function() {
const source = 'Term 1\n::Definition 1\nand more and more\n\nTerm 2\n::Definition 1\n::Definition 2\n\nParagraph';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt>\n<dd>Definition 1 and more and more</dd>\n<dt>Term 2</dt>\n<dd>Definition 1</dd>\n<dd>Definition 2</dd></dl><p>Paragraph</p>');
});
test('Block Token cannot be the Term of a multi-line definition', function() {
const source = '## Header\n::Definition 1 of a single-line DL\n::Definition 1 of another single-line DL';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<h2 id="header">Header</h2>\n<dl><dt></dt><dd>Definition 1 of a single-line DL</dd>\n<dt></dt><dd>Definition 1 of another single-line DL</dd>\n</dl>');
});
test('Inline DL has priority over Multiline', function() {
const source = 'Term 1 :: Inline definition 1\n:: Inline definition 2 (no DT)';
const rendered = Markdown.render(source).trim();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<dl><dt>Term 1</dt><dd>Inline definition 1</dd>\n<dt></dt><dd>Inline definition 2 (no DT)</dd>\n</dl>');
});
});

View File

@@ -78,7 +78,7 @@ module.exports = function(props){
return dedent` return dedent`
{{toc,wide {{toc,wide
# Table Of Contents # Contents
${markdown} ${markdown}
}} }}

View File

@@ -1,6 +1,6 @@
@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'; @import (less) './themes/fonts/icon fonts/font-icons.less';
@import (less) './themes/fonts/icon fonts/dicefont.less';
:root { :root {
//Colors //Colors
@@ -402,15 +402,9 @@
} }
} }
.pageNumber { .pageNumber {
position : absolute;
right : 2px; right : 2px;
bottom : 22px; bottom : 22px;
width : 50px;
font-size : 0.9em;
color : var(--HB_Color_Footnotes); color : var(--HB_Color_Footnotes);
text-align : center;
text-indent : 0;
&.auto::after { content : counter(phb-page-numbers); }
} }
.footnote { .footnote {
position : absolute; position : absolute;

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/dicefont.less';
:root { :root {
//Colors //Colors
@@ -8,7 +9,7 @@
} }
@page { margin : 0; } @page { margin : 0; }
body { counter-reset : phb-page-numbers; } body { counter-reset : page-numbers; }
* { -webkit-print-color-adjust : exact; } * { -webkit-print-color-adjust : exact; }
//***************************** //*****************************
@@ -47,7 +48,7 @@ body { counter-reset : phb-page-numbers; }
height : 279.4mm; height : 279.4mm;
padding : 1.4cm 1.9cm 1.7cm; padding : 1.4cm 1.9cm 1.7cm;
overflow : hidden; overflow : hidden;
counter-increment : phb-page-numbers; counter-increment : page-numbers;
background-color : var(--HB_Color_Background); background-color : var(--HB_Color_Background);
text-rendering : optimizeLegibility; text-rendering : optimizeLegibility;
contain : size; contain : size;
@@ -166,7 +167,6 @@ body { counter-reset : phb-page-numbers; }
margin : 0; margin : 0;
font-size : 120px; font-size : 120px;
text-transform : uppercase; text-transform : uppercase;
mix-blend-mode : overlay;
opacity : 30%; opacity : 30%;
transform : rotate(-45deg); transform : rotate(-45deg);
p { margin-bottom : none; } p { margin-bottom : none; }
@@ -460,3 +460,22 @@ body { counter-reset : phb-page-numbers; }
.homebreweryIcon.red { background-color : red; } .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%); } .homebreweryIcon.gold { background-image : linear-gradient(to top left, brown 22.5%, gold 40%, white 60%, gold 67.5%, brown 82.5%); }
} }
//*****************************
//* Page Number
//*****************************/
.page {
.pageNumber {
position : absolute;
right : 30px;
bottom : 30px;
width : 50px;
font-size : 0.9em;
text-align : center;
&.auto::after { content : counter(page-numbers); }
}
&:nth-child(even) {
.pageNumber { left : 30px; }
}
}

View File

@@ -374,17 +374,9 @@
} }
.pageNumber{ .pageNumber{
font-family : FrederickaTheGreat; font-family : FrederickaTheGreat;
position : absolute;
right : 3cm; right : 3cm;
bottom : 1.25cm; bottom : 1.25cm;
width : 50px;
font-size : 0.9em;
color : var(--HB_Color_HeaderText); color : var(--HB_Color_HeaderText);
text-align : center;
text-indent : 0;
&.auto::after {
content : counter(phb-page-numbers);
}
} }
.footnote{ .footnote{
position : absolute; position : absolute;

View File

@@ -1,118 +0,0 @@
/*
Icon Font: dicefont
*/
@font-face {
font-family: 'DiceFont';
src: url('../../../fonts/5e/dicefont.woff2') format('woff2'),
url('../../../fonts/5e/dicefont.woff') format('woff');
font-weight: normal;
font-style: normal;
}
.df {
display: inline-block;
font-family: 'DiceFont';
font-style: normal;
font-weight: normal;
font-variant: normal;
line-height: 1;
text-decoration: inherit;
text-rendering: optimizeLegibility;
text-transform: none;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
font-smooth: antialiased;
&.F:before { content: '\f190'; }
&.F-minus:before { content: '\f191'; }
&.F-plus:before { content: '\f192'; }
&.F-zero:before { content: '\f193'; }
&.d10:before { content: '\f194'; }
&.d10-0:before { content: '\f100'; }
&.d10-1:before { content: '\f101'; }
&.d10-10:before { content: '\f102'; }
&.d10-2:before { content: '\f103'; }
&.d10-3:before { content: '\f104'; }
&.d10-4:before { content: '\f105'; }
&.d10-5:before { content: '\f106'; }
&.d10-6:before { content: '\f107'; }
&.d10-7:before { content: '\f108'; }
&.d10-8:before { content: '\f109'; }
&.d10-9:before { content: '\f10a'; }
&.d12:before { content: '\f195'; }
&.d12-1:before { content: '\f10b'; }
&.d12-10:before { content: '\f10c'; }
&.d12-11:before { content: '\f10d'; }
&.d12-12:before { content: '\f10e'; }
&.d12-2:before { content: '\f10f'; }
&.d12-3:before { content: '\f110'; }
&.d12-4:before { content: '\f111'; }
&.d12-5:before { content: '\f112'; }
&.d12-6:before { content: '\f113'; }
&.d12-7:before { content: '\f114'; }
&.d12-8:before { content: '\f115'; }
&.d12-9:before { content: '\f116'; }
&.d2:before { content: '\f196'; }
&.d2-1:before { content: '\f117'; }
&.d2-2:before { content: '\f118'; }
&.d20:before { content: '\f197'; }
&.d20-1:before { content: '\f119'; }
&.d20-10:before { content: '\f11a'; }
&.d20-11:before { content: '\f11b'; }
&.d20-12:before { content: '\f11c'; }
&.d20-13:before { content: '\f11d'; }
&.d20-14:before { content: '\f11e'; }
&.d20-15:before { content: '\f11f'; }
&.d20-16:before { content: '\f120'; }
&.d20-17:before { content: '\f121'; }
&.d20-18:before { content: '\f122'; }
&.d20-19:before { content: '\f123'; }
&.d20-2:before { content: '\f124'; }
&.d20-20:before { content: '\f125'; }
&.d20-3:before { content: '\f126'; }
&.d20-4:before { content: '\f127'; }
&.d20-5:before { content: '\f128'; }
&.d20-6:before { content: '\f129'; }
&.d20-7:before { content: '\f12a'; }
&.d20-8:before { content: '\f12b'; }
&.d20-9:before { content: '\f12c'; }
&.d4:before { content: '\f198'; }
&.d4-1:before { content: '\f12d'; }
&.d4-2:before { content: '\f12e'; }
&.d4-3:before { content: '\f12f'; }
&.d4-4:before { content: '\f130'; }
&.d6:before { content: '\f199'; }
&.d6-1:before { content: '\f131'; }
&.d6-2:before { content: '\f132'; }
&.d6-3:before { content: '\f133'; }
&.d6-4:before { content: '\f134'; }
&.d6-5:before { content: '\f135'; }
&.d6-6:before { content: '\f136'; }
&.d8:before { content: '\f19a'; }
&.d8-1:before { content: '\f137'; }
&.d8-2:before { content: '\f138'; }
&.d8-3:before { content: '\f139'; }
&.d8-4:before { content: '\f13a'; }
&.d8-5:before { content: '\f13b'; }
&.d8-6:before { content: '\f13c'; }
&.d8-7:before { content: '\f13d'; }
&.d8-8:before { content: '\f13e'; }
&.dot-d6:before { content: '\f19b'; }
&.dot-d6-1:before { content: '\f13f'; }
&.dot-d6-2:before { content: '\f140'; }
&.dot-d6-3:before { content: '\f141'; }
&.dot-d6-4:before { content: '\f142'; }
&.dot-d6-5:before { content: '\f143'; }
&.dot-d6-6:before { content: '\f18f'; }
&.small-dot-d6-1:before { content: '\f183'; }
&.small-dot-d6-2:before { content: '\f184'; }
&.small-dot-d6-3:before { content: '\f185'; }
&.small-dot-d6-4:before { content: '\f186'; }
&.small-dot-d6-5:before { content: '\f187'; }
&.small-dot-d6-6:before { content: '\f188'; }
&.solid-small-dot-d6-1:before { content: '\f189'; }
&.solid-small-dot-d6-2:before { content: '\f18a'; }
&.solid-small-dot-d6-3:before { content: '\f18b'; }
&.solid-small-dot-d6-4:before { content: '\f18c'; }
&.solid-small-dot-d6-5:before { content: '\f18d'; }
&.solid-small-dot-d6-6:before { content: '\f18e'; }
}

Binary file not shown.

View File

@@ -1,5 +1,3 @@
@import url('./dicefont.less');
/* Main Font, serif */ /* Main Font, serif */
@font-face { @font-face {
font-family: BookInsanityRemake; font-family: BookInsanityRemake;

View File

@@ -0,0 +1,114 @@
/* Icon Font: dicefont */
@font-face {
font-family : 'DiceFont';
font-style : normal;
font-weight : normal;
src : url('../../../fonts/icon fonts/dicefont.woff2');
}
.df {
display : inline-block;
font-family : 'DiceFont';
font-style : normal;
font-weight : normal;
font-variant : normal;
line-height : 1;
text-decoration : inherit;
text-transform : none;
text-rendering : optimizeLegibility;
-moz-osx-font-smoothing : grayscale;
-webkit-font-smoothing : antialiased;
&.F::before { content : '\f190'; }
&.F-minus::before { content : '\f191'; }
&.F-plus::before { content : '\f192'; }
&.F-zero::before { content : '\f193'; }
&.d10::before { content : '\f194'; }
&.d10-0::before { content : '\f100'; }
&.d10-1::before { content : '\f101'; }
&.d10-10::before { content : '\f102'; }
&.d10-2::before { content : '\f103'; }
&.d10-3::before { content : '\f104'; }
&.d10-4::before { content : '\f105'; }
&.d10-5::before { content : '\f106'; }
&.d10-6::before { content : '\f107'; }
&.d10-7::before { content : '\f108'; }
&.d10-8::before { content : '\f109'; }
&.d10-9::before { content : '\f10a'; }
&.d12::before { content : '\f195'; }
&.d12-1::before { content : '\f10b'; }
&.d12-10::before { content : '\f10c'; }
&.d12-11::before { content : '\f10d'; }
&.d12-12::before { content : '\f10e'; }
&.d12-2::before { content : '\f10f'; }
&.d12-3::before { content : '\f110'; }
&.d12-4::before { content : '\f111'; }
&.d12-5::before { content : '\f112'; }
&.d12-6::before { content : '\f113'; }
&.d12-7::before { content : '\f114'; }
&.d12-8::before { content : '\f115'; }
&.d12-9::before { content : '\f116'; }
&.d2::before { content : '\f196'; }
&.d2-1::before { content : '\f117'; }
&.d2-2::before { content : '\f118'; }
&.d20::before { content : '\f197'; }
&.d20-1::before { content : '\f119'; }
&.d20-10::before { content : '\f11a'; }
&.d20-11::before { content : '\f11b'; }
&.d20-12::before { content : '\f11c'; }
&.d20-13::before { content : '\f11d'; }
&.d20-14::before { content : '\f11e'; }
&.d20-15::before { content : '\f11f'; }
&.d20-16::before { content : '\f120'; }
&.d20-17::before { content : '\f121'; }
&.d20-18::before { content : '\f122'; }
&.d20-19::before { content : '\f123'; }
&.d20-2::before { content : '\f124'; }
&.d20-20::before { content : '\f125'; }
&.d20-3::before { content : '\f126'; }
&.d20-4::before { content : '\f127'; }
&.d20-5::before { content : '\f128'; }
&.d20-6::before { content : '\f129'; }
&.d20-7::before { content : '\f12a'; }
&.d20-8::before { content : '\f12b'; }
&.d20-9::before { content : '\f12c'; }
&.d4::before { content : '\f198'; }
&.d4-1::before { content : '\f12d'; }
&.d4-2::before { content : '\f12e'; }
&.d4-3::before { content : '\f12f'; }
&.d4-4::before { content : '\f130'; }
&.d6::before { content : '\f199'; }
&.d6-1::before { content : '\f131'; }
&.d6-2::before { content : '\f132'; }
&.d6-3::before { content : '\f133'; }
&.d6-4::before { content : '\f134'; }
&.d6-5::before { content : '\f135'; }
&.d6-6::before { content : '\f136'; }
&.d8::before { content : '\f19a'; }
&.d8-1::before { content : '\f137'; }
&.d8-2::before { content : '\f138'; }
&.d8-3::before { content : '\f139'; }
&.d8-4::before { content : '\f13a'; }
&.d8-5::before { content : '\f13b'; }
&.d8-6::before { content : '\f13c'; }
&.d8-7::before { content : '\f13d'; }
&.d8-8::before { content : '\f13e'; }
&.dot-d6::before { content : '\f19b'; }
&.dot-d6-1::before { content : '\f13f'; }
&.dot-d6-2::before { content : '\f140'; }
&.dot-d6-3::before { content : '\f141'; }
&.dot-d6-4::before { content : '\f142'; }
&.dot-d6-5::before { content : '\f143'; }
&.dot-d6-6::before { content : '\f18f'; }
&.small-dot-d6-1::before { content : '\f183'; }
&.small-dot-d6-2::before { content : '\f184'; }
&.small-dot-d6-3::before { content : '\f185'; }
&.small-dot-d6-4::before { content : '\f186'; }
&.small-dot-d6-5::before { content : '\f187'; }
&.small-dot-d6-6::before { content : '\f188'; }
&.solid-small-dot-d6-1::before { content : '\f189'; }
&.solid-small-dot-d6-2::before { content : '\f18a'; }
&.solid-small-dot-d6-3::before { content : '\f18b'; }
&.solid-small-dot-d6-4::before { content : '\f18c'; }
&.solid-small-dot-d6-5::before { content : '\f18d'; }
&.solid-small-dot-d6-6::before { content : '\f18e'; }
}

View File

@@ -1,6 +1,6 @@
/* Main Font, serif */ /* Icon Font: Elderberry Inn */
@font-face { @font-face {
font-family : 'Eldeberry-Inn'; font-family : 'Elderberry-Inn';
font-style : normal; font-style : normal;
font-weight : normal; font-weight : normal;
src : url('../../../fonts/icon fonts/Elderberry-Inn-Icons.woff2'); src : url('../../../fonts/icon fonts/Elderberry-Inn-Icons.woff2');
@@ -10,7 +10,7 @@
span.ei { span.ei {
display : inline-block; display : inline-block;
margin-right : 3px; margin-right : 3px;
font-family : 'Eldeberry-Inn'; font-family : 'Elderberry-Inn';
line-height : 1; line-height : 1;
vertical-align : baseline; vertical-align : baseline;
-moz-osx-font-smoothing : grayscale; -moz-osx-font-smoothing : grayscale;