Merge branch 'master' into pr/2755
@@ -63,7 +63,7 @@ jobs:
|
||||
command: npm run test:basic
|
||||
- run:
|
||||
name: Test - Mustache Spans
|
||||
command: npm run test:mustache-span
|
||||
command: npm run test:mustache-syntax
|
||||
- run:
|
||||
name: Test - Routes
|
||||
command: npm run test:route
|
||||
|
||||
@@ -11,7 +11,7 @@ module.exports = {
|
||||
browser : true,
|
||||
node : true
|
||||
},
|
||||
plugins : ['react'],
|
||||
plugins : ['react', 'jest'],
|
||||
rules : {
|
||||
/** Errors **/
|
||||
'camelcase' : ['error', { properties: 'never' }],
|
||||
@@ -24,6 +24,7 @@ module.exports = {
|
||||
'react/jsx-no-bind' : ['error', { allowArrowFunctions: true }],
|
||||
'react/jsx-uses-react' : 'error',
|
||||
'react/prefer-es6-class' : ['error', 'never'],
|
||||
'jest/valid-expect' : ['error', { maxArgs: 3 }],
|
||||
|
||||
/** Warnings **/
|
||||
'max-lines' : ['warn', {
|
||||
|
||||
42
.stylelintrc.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"extends": ["stylelint-config-recess-order"],
|
||||
"plugins": [
|
||||
"stylelint-stylistic",
|
||||
"./stylelint_plugins/align-colons.js",
|
||||
"./stylelint_plugins/declaration-block-multi-line-min-declarations",
|
||||
"./stylelint_plugins/declaration-colon-min-space-before"
|
||||
],
|
||||
"customSyntax": "postcss-less",
|
||||
"rules": {
|
||||
"stylistic/indentation": "tab",
|
||||
"stylistic/string-quotes": "double",
|
||||
"no-duplicate-selectors": true,
|
||||
"stylistic/color-hex-case": "upper",
|
||||
"color-hex-length": "long",
|
||||
"stylistic/selector-combinator-space-after": "always",
|
||||
"stylistic/selector-combinator-space-before": "always",
|
||||
"selector-attribute-quotes": "always",
|
||||
"stylistic/selector-attribute-operator-space-before": "always",
|
||||
"stylistic/selector-attribute-operator-space-after": "always",
|
||||
"stylistic/selector-attribute-brackets-space-inside": "never",
|
||||
"stylistic/block-opening-brace-space-before": "always",
|
||||
"stylistic/declaration-block-trailing-semicolon": "always",
|
||||
"naturalcrit/declaration-colon-min-space-before": 1,
|
||||
"stylistic/declaration-colon-space-after": "always",
|
||||
"stylistic/number-leading-zero": "always",
|
||||
"function-url-quotes": "always",
|
||||
"function-url-scheme-disallowed-list": ["data"],
|
||||
"font-weight-notation": "named-where-possible",
|
||||
"font-family-name-quotes": "always-unless-keyword",
|
||||
"comment-whitespace-inside": "always",
|
||||
"selector-pseudo-element-colon-notation": "double",
|
||||
"stylistic/selector-pseudo-class-parentheses-space-inside": "never",
|
||||
"stylistic/media-feature-range-operator-space-before": "always",
|
||||
"stylistic/media-feature-range-operator-space-after": "always",
|
||||
"stylistic/media-feature-parentheses-space-inside": "never",
|
||||
"stylistic/media-feature-colon-space-before": "always",
|
||||
"stylistic/media-feature-colon-space-after": "always",
|
||||
"naturalcrit/align-colons": true,
|
||||
"naturalcrit/declaration-block-multi-line-min-declarations" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:16.11-alpine
|
||||
FROM node:16.13-alpine
|
||||
RUN apk --no-cache add git
|
||||
|
||||
ENV NODE_ENV=docker
|
||||
@@ -10,11 +10,11 @@ WORKDIR /usr/src/app
|
||||
# This improves caching so we don't have to download the dependencies every time the code changes
|
||||
COPY package.json ./
|
||||
# --ignore-scripts tells yarn not to run postbuild. We run it explicitly later
|
||||
RUN yarn install --ignore-scripts
|
||||
RUN npm install --ignore-scripts
|
||||
|
||||
# Bundle app source and build application
|
||||
COPY . .
|
||||
RUN yarn build
|
||||
RUN npm run build
|
||||
|
||||
EXPOSE 8000
|
||||
CMD [ "yarn", "start" ]
|
||||
CMD [ "npm", "start" ]
|
||||
|
||||
63
changelog.md
@@ -80,19 +80,69 @@ pre {
|
||||
## changelog
|
||||
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
|
||||
|
||||
### XXXXday DD/MM/2023 - v3.8.0
|
||||
### Wednesday 12/04/2023 - v3.8.0
|
||||
{{taskList
|
||||
|
||||
##### calculuschild
|
||||
|
||||
* [x] Rename `{{coverPage}}` to `{{frontCover}}`. Those using this {{beta BETA}} feature will need to update that text to make the cover page appear again.
|
||||
|
||||
* [x] Several background fixes to test scripts
|
||||
|
||||
##### Jeddai
|
||||
|
||||
* [X] Add content negotiation to exclude image requests from our API calls
|
||||
|
||||
Fixes issue [#2595](https://github.com/naturalcrit/homebrewery/issues/2595)
|
||||
|
||||
##### G-Ambatte
|
||||
|
||||
* [x] Update server build scripts to fix Admin page
|
||||
|
||||
Fixes issues [#2657](https://github.com/naturalcrit/homebrewery/issues/2657)
|
||||
|
||||
* [x] Fix internal links inside `<div>` blocks not automatically receiving the `target=_self` attribute
|
||||
* [x] Fix internal links inside `<div>` blocks not receiving the `target=_self` attribute
|
||||
|
||||
Fixes issues [#2680](https://github.com/naturalcrit/homebrewery/issues/2680)
|
||||
|
||||
* [x] See brew details on `/share` pages by clicking the brew title (author, last update, tags, etc.)
|
||||
|
||||
Fixes issues [#1679](https://github.com/naturalcrit/homebrewery/issues/1679)
|
||||
|
||||
* [x] Add local Windows install script via Chocolatey
|
||||
|
||||
##### 5e-Clerc
|
||||
|
||||
* [x] New {{openSans **TABLES → {{fas,fa-language}} RUNE TABLE**}} snippets for V3. Adds an alphabetic script translation table.
|
||||
|
||||
* [x] New {{openSans **IMAGES → {{fac,mask-center}} WATERCOLOR CENTER** }} snippets for V3, which adds a stylish watercolor texture to the center of your images!
|
||||
|
||||
* [x] New {{openSans **PHB → {{fac,book-inside-cover}} INSIDE COVER PAGE** }} snippet for V3! (Thanks to /u/Kaiburr_Kath-Hound on Reddit for providing some of these resources!)
|
||||
|
||||
* [x] Add some missing characters {{font-family:scalySansRemake Ñ ñ ç Ç Ý ý # ^ ¿ ' " ¡ ·}} to the "scalySansRemake" font in V3.
|
||||
|
||||
Fixes issues [#2280](https://github.com/naturalcrit/homebrewery/issues/2280)
|
||||
|
||||
##### Gazook89
|
||||
|
||||
* [x] Add "Language" selector in {{fa,fa-info-circle}} **Properties** menu. Sets the HTML Lang attribute for your brew to better handle hyphenation or spellcheck.
|
||||
|
||||
Fixes issues [#1343](https://github.com/naturalcrit/homebrewery/issues/1343)
|
||||
|
||||
* [x] Fix a crash when multiple `{injection}` tags appear in sequence
|
||||
|
||||
Fixes issues [#2712](https://github.com/naturalcrit/homebrewery/issues/2712)
|
||||
|
||||
##### MichielDeMey
|
||||
|
||||
* [x] Remove all-caps display on Account button since usernames are case-sensitive.
|
||||
|
||||
Fixes issues [#2731](https://github.com/naturalcrit/homebrewery/issues/2731)
|
||||
|
||||
}}
|
||||
|
||||
\page
|
||||
|
||||
### Monday 13/03/2023 - v3.7.2
|
||||
{{taskList
|
||||
|
||||
@@ -106,7 +156,7 @@ Fixes issues [#2680](https://github.com/naturalcrit/homebrewery/issues/2680)
|
||||
|
||||
##### Lucastucious (new contributor!)
|
||||
|
||||
* [x] Changed `filter: drop-shadow` to `box-shadow` on text boxes, making text selectable in PDFs again.
|
||||
* [x] Changed `filter: drop-shadow` to `box-shadow` on text boxes, making PDF text selectable
|
||||
|
||||
Fixes issues [#1569](https://github.com/naturalcrit/homebrewery/issues/1569)
|
||||
|
||||
@@ -124,7 +174,6 @@ Fixes issues [#1569](https://github.com/naturalcrit/homebrewery/issues/1569)
|
||||
* [x] Fix PDF pixelation on CoverPage text outlines
|
||||
}}
|
||||
|
||||
|
||||
### Tuesday 28/02/2023 - v3.7.0
|
||||
{{taskList
|
||||
|
||||
@@ -154,7 +203,7 @@ Fixes issues [#2687](https://github.com/naturalcrit/homebrewery/issues/2687)
|
||||
{{taskList
|
||||
##### G-Ambatte
|
||||
|
||||
* [x] Fix users not being removed from Authors list correctly
|
||||
* [x] Fix users not being removed from Authors list
|
||||
|
||||
Fixes issues [#2674](https://github.com/naturalcrit/homebrewery/issues/2674)
|
||||
}}
|
||||
@@ -184,6 +233,8 @@ Fixes issues [#2583](https://github.com/naturalcrit/homebrewery/issues/2583)
|
||||
* [x] Fix cloned brews inheriting the parent view count
|
||||
}}
|
||||
|
||||
\page
|
||||
|
||||
### Friday 23/12/2022 - v3.5.0
|
||||
{{taskList
|
||||
|
||||
@@ -197,8 +248,6 @@ Fixes issues [#2583](https://github.com/naturalcrit/homebrewery/issues/2583)
|
||||
Fixes issues [#1987](https://github.com/naturalcrit/homebrewery/issues/1987)
|
||||
}}
|
||||
|
||||
\page
|
||||
|
||||
### Saturday 10/12/2022 - v3.4.2
|
||||
{{taskList
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames'); //Unused variable
|
||||
|
||||
const DISMISS_KEY = 'dismiss_notification08-27-22';
|
||||
const DISMISS_KEY = 'dismiss_notification12-04-23';
|
||||
|
||||
const NotificationPopup = createClass({
|
||||
displayName : 'NotificationPopup',
|
||||
@@ -25,21 +25,14 @@ const NotificationPopup = createClass({
|
||||
return (
|
||||
<>
|
||||
<li key='psa'>
|
||||
<em>V3.2.0 Released!</em> <br />
|
||||
We are happy to announce that after nearly a year of use by our many users,
|
||||
we are making the V3 render mode the default setting for all new brews.
|
||||
This mode has become quite popular, and has proven to be stable and powerful.
|
||||
Of course, we will always keep the option to use the Legacy renderer for any
|
||||
brew, which can still be accessed from the Properties menu.
|
||||
</li>
|
||||
|
||||
<li key='stubs'>
|
||||
<em>Change to Google Drive Storage!</em> <br />
|
||||
We have made a change to the process of tranferring brews between Google
|
||||
Drive and the Homebrewery storage. Starting now, any time a brew is
|
||||
transferred, it will keep the same links instead of generating new ones!
|
||||
We hope this change will help reduce issues where people "lost" their work
|
||||
by trying to visit old links.
|
||||
<em>Broken <b>CoverPage</b> snippet</em> <br />
|
||||
Those of you who have been trying out our Cover Page snippet may
|
||||
notice that the cover page no longer displays correctly. Due to some
|
||||
small tweaks of this BETA feature, the CSS class has been renamed
|
||||
from "coverPage" to "frontCover". Simply change the text to "frontCover"
|
||||
and it should again function as before. Remember that any snippet
|
||||
marked "beta" may have a similar change in the future as we
|
||||
encounter any bugs or reworks.
|
||||
</li>
|
||||
|
||||
<li key='googleDriveFolder'>
|
||||
|
||||
@@ -19,17 +19,17 @@
|
||||
background-color : fade(#299, 15%);
|
||||
border-bottom : #299 solid 1px;
|
||||
}
|
||||
.block{
|
||||
.block:not(.cm-comment){
|
||||
color : purple;
|
||||
font-weight : bold;
|
||||
//font-style: italic;
|
||||
}
|
||||
.inline-block{
|
||||
.inline-block:not(.cm-comment){
|
||||
color : red;
|
||||
font-weight : bold;
|
||||
//font-style: italic;
|
||||
}
|
||||
.injection{
|
||||
.injection:not(.cm-comment){
|
||||
color : green;
|
||||
font-weight : bold;
|
||||
}
|
||||
|
||||
@@ -82,4 +82,4 @@ const ErrorNavItem = createClass({
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = ErrorNavItem;
|
||||
module.exports = ErrorNavItem;
|
||||
|
||||
@@ -1,77 +1,75 @@
|
||||
.navItem {
|
||||
&.error {
|
||||
position : relative;
|
||||
background-color : @red;
|
||||
}
|
||||
.navItem.error {
|
||||
position : relative;
|
||||
background-color : @red;
|
||||
}
|
||||
|
||||
.errorContainer{
|
||||
animation-name: glideDown;
|
||||
animation-duration: 0.4s;
|
||||
position : absolute;
|
||||
top : 100%;
|
||||
left : 50%;
|
||||
z-index : 1000;
|
||||
width : 140px;
|
||||
padding : 3px;
|
||||
color : white;
|
||||
.errorContainer{
|
||||
animation-name: glideDown;
|
||||
animation-duration: 0.4s;
|
||||
position : absolute;
|
||||
top : 100%;
|
||||
left : 50%;
|
||||
z-index : 1000;
|
||||
width : 140px;
|
||||
padding : 3px;
|
||||
color : white;
|
||||
background-color : #333;
|
||||
border : 3px solid #444;
|
||||
border-radius : 5px;
|
||||
transform : translate(-50% + 3px, 10px);
|
||||
text-align : center;
|
||||
font-size : 10px;
|
||||
font-weight : 800;
|
||||
text-transform : uppercase;
|
||||
a{
|
||||
color : @teal;
|
||||
}
|
||||
&:before {
|
||||
content: "";
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
position: absolute;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-top: 10px solid transparent;
|
||||
border-bottom: 10px solid #444;
|
||||
left: 53px;
|
||||
top: -23px;
|
||||
}
|
||||
&:after {
|
||||
content: "";
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
position: absolute;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-top: 10px solid transparent;
|
||||
border-bottom: 10px solid #333;
|
||||
left: 53px;
|
||||
top: -19px;
|
||||
}
|
||||
.deny {
|
||||
width : 48%;
|
||||
margin : 1px;
|
||||
padding : 5px;
|
||||
background-color : #333;
|
||||
border : 3px solid #444;
|
||||
border-radius : 5px;
|
||||
transform : translate(-50% + 3px, 10px);
|
||||
text-align : center;
|
||||
font-size : 10px;
|
||||
font-weight : 800;
|
||||
text-transform : uppercase;
|
||||
a{
|
||||
color : @teal;
|
||||
}
|
||||
&:before {
|
||||
content: "";
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
position: absolute;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-top: 10px solid transparent;
|
||||
border-bottom: 10px solid #444;
|
||||
left: 53px;
|
||||
top: -23px;
|
||||
}
|
||||
&:after {
|
||||
content: "";
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
position: absolute;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-top: 10px solid transparent;
|
||||
border-bottom: 10px solid #333;
|
||||
left: 53px;
|
||||
top: -19px;
|
||||
}
|
||||
.deny {
|
||||
width : 48%;
|
||||
margin : 1px;
|
||||
padding : 5px;
|
||||
background-color : #333;
|
||||
display : inline-block;
|
||||
border-left : 1px solid #666;
|
||||
.animate(background-color);
|
||||
&:hover{
|
||||
background-color : red;
|
||||
}
|
||||
}
|
||||
.confirm {
|
||||
width : 48%;
|
||||
margin : 1px;
|
||||
padding : 5px;
|
||||
background-color : #333;
|
||||
display : inline-block;
|
||||
color : white;
|
||||
.animate(background-color);
|
||||
&:hover{
|
||||
background-color : teal;
|
||||
}
|
||||
display : inline-block;
|
||||
border-left : 1px solid #666;
|
||||
.animate(background-color);
|
||||
&:hover{
|
||||
background-color : red;
|
||||
}
|
||||
}
|
||||
}
|
||||
.confirm {
|
||||
width : 48%;
|
||||
margin : 1px;
|
||||
padding : 5px;
|
||||
background-color : #333;
|
||||
display : inline-block;
|
||||
color : white;
|
||||
.animate(background-color);
|
||||
&:hover{
|
||||
background-color : teal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
90
client/homebrew/navbar/metadata.navitem.jsx
Normal file
@@ -0,0 +1,90 @@
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const Moment = require('moment');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
|
||||
const MetadataNav = createClass({
|
||||
displayName : 'MetadataNav',
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState : function() {
|
||||
return {
|
||||
showMetaWindow : false
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount : function() {
|
||||
},
|
||||
|
||||
toggleMetaWindow : function(){
|
||||
this.setState((prevProps)=>({
|
||||
showMetaWindow : !prevProps.showMetaWindow
|
||||
}));
|
||||
},
|
||||
|
||||
getAuthors : function(){
|
||||
if(!this.props.brew.authors || this.props.brew.authors.length == 0) return 'No authors';
|
||||
return <>
|
||||
{this.props.brew.authors.map((author, idx, arr)=>{
|
||||
const spacer = arr.length - 1 == idx ? <></> : <span>, </span>;
|
||||
return <span key={idx}><a className='userPageLink' href={`/user/${author}`}>{author}</a>{spacer}</span>;
|
||||
})}
|
||||
</>;
|
||||
},
|
||||
|
||||
getTags : function(){
|
||||
if(!this.props.brew.tags || this.props.brew.tags.length == 0) return 'No tags';
|
||||
return <>
|
||||
{this.props.brew.tags.map((tag, idx)=>{
|
||||
return <span className='tag' key={idx}>{tag}</span>;
|
||||
})}
|
||||
</>;
|
||||
},
|
||||
|
||||
getSystems : function(){
|
||||
if(!this.props.brew.systems || this.props.brew.systems.length == 0) return 'No systems';
|
||||
return this.props.brew.systems.join(', ');
|
||||
},
|
||||
|
||||
renderMetaWindow : function(){
|
||||
return <div className={`window ${this.state.showMetaWindow ? 'active' : 'inactive'}`}>
|
||||
<div className='row'>
|
||||
<h4>Description</h4>
|
||||
<p>{this.props.brew.description || 'No description.'}</p>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<h4>Authors</h4>
|
||||
<p>{this.getAuthors()}</p>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<h4>Tags</h4>
|
||||
<p>{this.getTags()}</p>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<h4>Systems</h4>
|
||||
<p>{this.getSystems()}</p>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<h4>Updated</h4>
|
||||
<p>{Moment(this.props.brew.updatedAt).fromNow()}</p>
|
||||
</div>
|
||||
</div>;
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <Nav.item icon='fas fa-info-circle' color='grey' className='metadata'
|
||||
onClick={()=>this.toggleMetaWindow()}>
|
||||
{this.props.children}
|
||||
{this.renderMetaWindow()}
|
||||
</Nav.item>;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
module.exports = MetadataNav;
|
||||
@@ -1,193 +1,278 @@
|
||||
@import 'naturalcrit/styles/colors.less';
|
||||
@navbarHeight : 28px;
|
||||
@keyframes pinkColoring {
|
||||
//from {color: white;}
|
||||
//to {color: red;}
|
||||
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{
|
||||
width : 250px;
|
||||
margin : 0;
|
||||
padding : 2px;
|
||||
background-color : #444;
|
||||
font-family : 'Open Sans', sans-serif;
|
||||
font-size : 12px;
|
||||
font-weight : 800;
|
||||
color : white;
|
||||
text-align : center;
|
||||
border : 1px solid @blue;
|
||||
outline : none;
|
||||
}
|
||||
.charCount{
|
||||
display : inline-block;
|
||||
vertical-align : bottom;
|
||||
margin-left : 8px;
|
||||
color : #666;
|
||||
text-align : right;
|
||||
&.max{
|
||||
color : @red;
|
||||
}
|
||||
}
|
||||
}
|
||||
.brewTitle.navItem{
|
||||
font-size : 12px;
|
||||
font-weight : 800;
|
||||
color : white;
|
||||
text-align : center;
|
||||
text-transform : initial;
|
||||
}
|
||||
.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-left : 1px solid #666;
|
||||
border-right : 1px solid #666;
|
||||
&:hover i {
|
||||
color: red;
|
||||
}
|
||||
i{
|
||||
.animate(color);
|
||||
animation-name: pinkColoring;
|
||||
animation-duration: 2s;
|
||||
color: pink;
|
||||
}
|
||||
}
|
||||
.recent.navItem {
|
||||
position : relative;
|
||||
.dropdown{
|
||||
position : absolute;
|
||||
top : 28px;
|
||||
left : 0px;
|
||||
z-index : 10000;
|
||||
width : 100%;
|
||||
overflow : hidden auto;
|
||||
max-height : ~"calc(100vh - 28px)";
|
||||
scrollbar-color : #666 #333;
|
||||
scrollbar-width : thin;
|
||||
h4{
|
||||
display : block;
|
||||
box-sizing : border-box;
|
||||
padding : 5px 0px;
|
||||
background-color : #333;
|
||||
font-size : 0.8em;
|
||||
color : #bbb;
|
||||
text-align : center;
|
||||
border-top : 1px solid #888;
|
||||
&:nth-of-type(1){ background-color: darken(@teal, 20%); }
|
||||
&:nth-of-type(2){ background-color: darken(@purple, 30%); }
|
||||
}
|
||||
.item{
|
||||
@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.navItem {
|
||||
position : relative;
|
||||
.dropdown {
|
||||
position : absolute;
|
||||
z-index : 10000;
|
||||
top : 28px;
|
||||
left : 0;
|
||||
overflow : hidden auto;
|
||||
width : 100%;
|
||||
max-height : ~"calc(100vh - 28px)";
|
||||
scrollbar-color : #666 #333;
|
||||
scrollbar-width : thin;
|
||||
h4 {
|
||||
font-size : 0.8em;
|
||||
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%);
|
||||
}
|
||||
}
|
||||
.item {
|
||||
#backgroundColorsHover;
|
||||
.animate(background-color);
|
||||
position : relative;
|
||||
display : block;
|
||||
box-sizing : border-box;
|
||||
padding : 8px 5px 13px;
|
||||
background-color : #333;
|
||||
color : white;
|
||||
text-decoration : none;
|
||||
border-top : 1px solid #888;
|
||||
overflow : clip;
|
||||
.clear{
|
||||
display : none;
|
||||
position : absolute;
|
||||
top : 50%;
|
||||
transform : translateY(-50%);
|
||||
right : 0px;
|
||||
width : 20px;
|
||||
height : 100%;
|
||||
background-color : #333;
|
||||
opacity : 70%;
|
||||
border-radius : 3px;
|
||||
&:hover {
|
||||
opacity : 100%;
|
||||
}
|
||||
i {
|
||||
text-align : center;
|
||||
font-size : 10px;
|
||||
margin : 0;
|
||||
height :100%;
|
||||
width :100%;
|
||||
}
|
||||
}
|
||||
&:hover{
|
||||
background-color : @blue;
|
||||
|
||||
.clear{
|
||||
display : grid;
|
||||
place-content : center;
|
||||
}
|
||||
}
|
||||
.title{
|
||||
display : inline-block;
|
||||
overflow : hidden;
|
||||
width : 100%;
|
||||
text-overflow : ellipsis;
|
||||
white-space : nowrap;
|
||||
}
|
||||
.time{
|
||||
position : absolute;
|
||||
right : 2px;
|
||||
bottom : 2px;
|
||||
font-size : 0.7em;
|
||||
color : #888;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.warning.navItem{
|
||||
position : relative;
|
||||
background-color : @orange;
|
||||
color : white;
|
||||
&:hover>.dropdown{
|
||||
visibility : visible;
|
||||
}
|
||||
.dropdown{
|
||||
position : absolute;
|
||||
display : block;
|
||||
top : 28px;
|
||||
left : 0px;
|
||||
visibility : hidden;
|
||||
z-index : 10000;
|
||||
box-sizing : border-box;
|
||||
width : 100%;
|
||||
padding : 13px 5px;
|
||||
background-color : #333;
|
||||
text-align : center;
|
||||
}
|
||||
}
|
||||
.account.navItem{
|
||||
min-width: 100px;
|
||||
}
|
||||
.account.username.navItem{
|
||||
text-transform: none;
|
||||
}
|
||||
}
|
||||
.animate(background-color);
|
||||
position : relative;
|
||||
display : block;
|
||||
overflow : clip;
|
||||
box-sizing : border-box;
|
||||
padding : 8px 5px 13px;
|
||||
text-decoration : none;
|
||||
color : white;
|
||||
border-top : 1px solid #888;
|
||||
background-color : #333;
|
||||
.clear {
|
||||
position : absolute;
|
||||
top : 50%;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.metadata.navItem {
|
||||
position : relative;
|
||||
display : flex;
|
||||
align-items : center;
|
||||
height : 100%;
|
||||
padding : 0;
|
||||
flex-grow : 1;
|
||||
i {
|
||||
margin-right : 10px;
|
||||
}
|
||||
.window {
|
||||
position : absolute;
|
||||
z-index : -1;
|
||||
bottom : 0;
|
||||
left : 50%;
|
||||
display : flex;
|
||||
justify-content : flex-start;
|
||||
width : 440px;
|
||||
max-height : ~"calc(100vh - 28px)";
|
||||
margin : 0 auto;
|
||||
padding : 0 10px 5px;
|
||||
transition : transform 0.4s, opacity 0.4s;
|
||||
border : 3px solid #444;
|
||||
border-top : unset;
|
||||
border-radius : 0 0 5px 5px;
|
||||
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;
|
||||
}
|
||||
.row {
|
||||
display : flex;
|
||||
width : 100%;
|
||||
flex-flow : row wrap;
|
||||
h4 {
|
||||
display : block;
|
||||
box-sizing : border-box;
|
||||
min-width : 76px;
|
||||
padding : 5px 0;
|
||||
text-align : center;
|
||||
color : #BBB;
|
||||
flex-basis : 20%;
|
||||
flex-grow : 1;
|
||||
}
|
||||
p {
|
||||
font-family : "Open Sans", sans-serif;
|
||||
font-size : 10px;
|
||||
font-weight : normal;
|
||||
padding : 5px 0;
|
||||
text-transform : initial;
|
||||
flex-basis : 80%;
|
||||
flex-grow : 1;
|
||||
.tag {
|
||||
display : inline-block;
|
||||
margin : 2px 2px;
|
||||
padding : 2px;
|
||||
border : 2px solid grey;
|
||||
border-radius : 5px;
|
||||
background-color : #444;
|
||||
}
|
||||
a.userPageLink {
|
||||
text-decoration : none;
|
||||
color : white;
|
||||
&:hover {
|
||||
text-decoration : underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:nth-of-type(even) {
|
||||
background-color : #555;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.warning.navItem {
|
||||
position : relative;
|
||||
color : white;
|
||||
background-color : @orange;
|
||||
&:hover > .dropdown {
|
||||
visibility : visible;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
}
|
||||
.listPage{
|
||||
.content{
|
||||
z-index : 1;
|
||||
.page{
|
||||
.noColumns() !important; //Needed to override PHB Theme since this is on a lower @layer
|
||||
&::after{
|
||||
@@ -63,7 +64,7 @@
|
||||
border-bottom : 1px solid #666;
|
||||
color : white;
|
||||
text-align : center;
|
||||
z-index : 500;
|
||||
z-index : 1;
|
||||
display : flex;
|
||||
justify-content : center;
|
||||
align-items : baseline;
|
||||
|
||||
@@ -254,6 +254,15 @@ const EditPage = createClass({
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
{this.state.alertTrashedGoogleBrew &&
|
||||
<div className='errorContainer' onClick={this.closeAlerts}>
|
||||
This brew is currently in your Trash folder on Google Drive!<br />If you want to keep it, make sure to move it before it is deleted permanently!<br />
|
||||
<div className='confirm'>
|
||||
OK
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</Nav.item>;
|
||||
},
|
||||
|
||||
@@ -335,16 +344,6 @@ const EditPage = createClass({
|
||||
const shareLink = this.processShareId();
|
||||
|
||||
return <Navbar>
|
||||
|
||||
{this.state.alertTrashedGoogleBrew &&
|
||||
<div className='errorContainer' onClick={this.closeAlerts}>
|
||||
This brew is currently in your Trash folder on Google Drive!<br />If you want to keep it, make sure to move it before it is deleted permanently!<br />
|
||||
<div className='confirm'>
|
||||
OK
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<Nav.section>
|
||||
<Nav.item className='brewTitle'>{this.state.brew.title}</Nav.item>
|
||||
</Nav.section>
|
||||
|
||||
@@ -5,6 +5,7 @@ const { Meta } = require('vitreum/headtags');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
const Navbar = require('../../navbar/navbar.jsx');
|
||||
const MetadataNav = require('../../navbar/metadata.navitem.jsx');
|
||||
const PrintLink = require('../../navbar/print.navitem.jsx');
|
||||
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
|
||||
const Account = require('../../navbar/account.navitem.jsx');
|
||||
@@ -50,8 +51,10 @@ const SharePage = createClass({
|
||||
return <div className='sharePage sitePage'>
|
||||
<Meta name='robots' content='noindex, nofollow' />
|
||||
<Navbar>
|
||||
<Nav.section>
|
||||
<Nav.item className='brewTitle'>{this.props.brew.title}</Nav.item>
|
||||
<Nav.section className='titleSection'>
|
||||
<MetadataNav brew={this.props.brew}>
|
||||
<Nav.item className='brewTitle'>{this.props.brew.title}</Nav.item>
|
||||
</MetadataNav>
|
||||
</Nav.section>
|
||||
|
||||
<Nav.section>
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
.sharePage{
|
||||
.navContent .navSection.titleSection {
|
||||
flex-grow: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
.content{
|
||||
overflow-y : hidden;
|
||||
}
|
||||
|
||||
52
client/icons/book-back-cover.svg
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 541.53217 512"
|
||||
version="1.1"
|
||||
id="back-cover-icon"
|
||||
sodipodi:docname="book-front-cover.svg"
|
||||
width="541.53217"
|
||||
height="512"
|
||||
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs22131" />
|
||||
<sodipodi:namedview
|
||||
id="namedview22129"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#111111"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.39257813"
|
||||
inkscape:cx="-263.64179"
|
||||
inkscape:cy="444.49751"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg22127" />
|
||||
<!--! Font Awesome Pro 6.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
|
||||
<g id="g20308" transform="matrix(3.7795276,0,0,3.7795276,-201.76367,-251.58203)">
|
||||
<path id="rect20232" d="M95.1,66.6h-8.5c-4.7,0-8.5,3.8-8.5,8.5v21.4c3.5-0.4,7.4-0.5,12-0.5c0.7,0,0.6,0,1.2,0
|
||||
c0-2.4,0-4.2,0.3-6.2c0.3-2.2,2.2-5.8,3.5-7c0.9-0.9,3-3.2,7-3.7c1-0.1,2-0.1,2.8,0c2.6,0.3,4.6,1.6,6.1,2.6
|
||||
c3.9,2.7,7.4,6.4,14.8,13.8c6.3,6.3,9.8,9.8,12,12.4c1.1,1.3,2.1,2.4,2.9,4c0.9,1.7,1.4,4.2,1.4,5.6c0,1.4-0.5,4-1.4,5.6
|
||||
c-0.9,1.6-1.8,2.7-2.9,4c-2.2,2.6-5.6,6-11.8,12.2c-3.8,3.8-7.4,7.3-10.2,9.9c-1.4,1.3-2.6,2.4-3.6,3.3c-0.5,0.4-1,0.8-1.5,1.2
|
||||
c-0.3,0.2-0.5,0.4-1,0.7s-0.7,0.7-2.8,1.2c-4.3,1.1-6.3,0.4-9.4-1.3c-0.5-0.3-1.9-0.9-3.3-2.6c-1.4-1.7-2.1-3.7-2.4-5.1
|
||||
c-0.5-2.4-0.5-4.3-0.6-7.2c-3.9,0-6,0.1-6.5,0.1c-0.5,0.1,0.2-0.2-1.2,0.5c-1.7,0.8-3.6,2.8-4.4,4.5c-0.3,0.8-0.5,1-0.6,6.6
|
||||
c-0.1,2.2-0.2,4.3-0.4,6c0,0.3-0.1,0.6-0.1,0.8v1.9c0,4.7,3.8,8.5,8.5,8.5v16.9c-4.7,0-8.5,3.8-8.5,8.5c0,4.7,3.8,8.5,8.5,8.5h8.5
|
||||
h76.2c14,0,25.4-11.4,25.4-25.4V92c0-14-11.4-25.4-25.4-25.4L95.1,66.6z M171.3,168.2c4.7,0,8.5,3.8,8.5,8.5c0,4.7-3.8,8.5-8.5,8.5
|
||||
h-67.7v-16.9L171.3,168.2L171.3,168.2z"/>
|
||||
<path id="path20297" d="M63.4,158c1.8,1.6,4.5,1.9,5.5,0.7c0.3-0.4,0.7-4,0.8-8.1c0.2-5.9,0.5-7.9,1.4-10c1.7-3.7,4.9-7,8.6-8.9
|
||||
c3.1-1.5,3.6-1.6,11.7-1.6h8.5l0.3,7.6c0.3,7.5,0.3,7.7,1.7,8.5c0.8,0.5,2.1,0.7,2.8,0.5c0.8-0.2,7.4-6.4,14.9-13.9
|
||||
c12.4-12.4,13.5-13.7,13.5-15.5c0-1.8-1.1-3.1-13.8-15.7c-14.7-14.7-15.4-15.2-18-12.7c-1,1-1.1,1.9-1.1,7.6c0,3.6-0.2,6.9-0.3,7.4
|
||||
c-0.3,0.8-1.7,0.9-9.8,0.9c-15.6,0-21.1,1.7-27.9,8.5c-6.5,6.5-8.8,12-8.8,21.1c0,4.7,0.3,6.8,1.3,9.8
|
||||
C56.2,148.6,60.7,155.7,63.4,158L63.4,158z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
@@ -2,7 +2,7 @@
|
||||
<svg
|
||||
viewBox="0 0 541.53217 512"
|
||||
version="1.1"
|
||||
id="svg22127"
|
||||
id="front-cover-icon"
|
||||
sodipodi:docname="book-front-cover.svg"
|
||||
width="541.53217"
|
||||
height="512"
|
||||
|
||||
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
53
client/icons/book-inside-cover.svg
Normal file
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 704.00001 512"
|
||||
version="1.1"
|
||||
id="svg22127"
|
||||
sodipodi:docname="book-inside-cover.svg"
|
||||
width="704"
|
||||
height="512"
|
||||
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||
inkscape:export-filename="InsideCover3.png"
|
||||
inkscape:export-xdpi="300"
|
||||
inkscape:export-ydpi="300"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs22131" />
|
||||
<sodipodi:namedview
|
||||
id="namedview22129"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#111111"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.47274833"
|
||||
inkscape:cx="83.55397"
|
||||
inkscape:cy="178.74204"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg22127" />
|
||||
<!--! Font Awesome Pro 6.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
|
||||
<path
|
||||
id="path2161-6"
|
||||
style="color:#000000;fill:#000000;stroke-width:1;-inkscape-stroke:none;paint-order:stroke fill markers"
|
||||
d="M 208,0 C 147.0078,0 94.429433,14.25071 60.367188,26.66992 23.520854,39.96036 0,76.16076 0,112.95896 v 317.8321 c 0,59.8499 56.949847,92.6546 107.47266,76.6035 l -0.1543,0.049 c 26.46715,-8.335 74.84649,-18.3965 100.68164,-18.3965 17.25807,0 61.31688,10.6183 85.14453,18.8438 l 0.0508,0.018 0.0527,0.018 c 19.82627,6.5858 40.84117,4.9222 58.99804,-3.0762 18.04267,7.8799 38.84257,9.6126 58.33594,3.1328 l 0.13672,-0.045 0.13672,-0.047 c 23.88445,-8.0588 67.88646,-18.8437 85.14453,-18.8437 25.83515,0 74.22549,10.0266 100.68164,18.3964 l 0.1543,0.049 0.15625,0.049 C 647.13371,523.05316 704,490.64216 704,430.79226 v -317.8321 c 0,-36.8274 -23.49583,-72.8235 -60.00977,-86.25583 l -0.16015,-0.0606 -0.16211,-0.0566 C 609.79193,14.33005 557.11269,0.0012 496,0.0012 434.5671,0.0012 387.12553,14.01354 352,34.94261 316.87446,14.01344 269.4331,0.0012 208,0.0012 Z m 0,32.00977 c 58.3999,0 103.40004,18.89469 123,33.63279 3.3,2.4564 5,6.4246 5,10.3926 v 356.5508 c 0,10.7702 -11.70041,18.2326 -22.40039,14.6426 -26.59996,-8.9751 -71.69966,-22.2012 -105.59961,-22.2012 -38.49993,0 -88.40045,11.4317 -119.900391,21.3516 C 76.799621,449.96896 64,442.03166 64,430.78906 V 80.94726 C 64,69.51586 70.799631,58.93546 82.099609,54.87306 110.29956,44.57516 157.50009,32.00977 208,32.00977 Z m 288,0 c 50.49991,0 97.70044,12.56619 125.90039,22.76949 C 633.20037,58.93616 640,69.51586 640,80.94726 v 349.8418 c 0,11.2426 -12.79963,19.0854 -24.09961,15.5899 -31.49995,-9.9199 -81.40046,-21.3516 -119.90039,-21.3516 -33.89995,0 -78.99966,13.2261 -105.59961,22.2012 C 379.60041,450.81856 368,443.35616 368,432.58596 V 76.03516 c 0,-3.968 1.60001,-7.9362 5,-10.3926 19.59997,-14.7381 64.6001,-33.63279 123,-33.63279 z M 335.52734,45.75386 c -0.1289,0.093 -0.23137,0.2032 -0.35937,0.2969 -0.198,0.1477 -0.428,0.2796 -0.625,0.4278 z m 33.67969,0.5372 0.24805,0.1875 c -0.0427,-0.033 -0.0937,-0.061 -0.13672,-0.094 -0.0393,-0.03 -0.0713,-0.064 -0.11133,-0.094 z" />
|
||||
<path
|
||||
style="color:#000000;fill:#000000;fill-opacity:1;stroke-width:1;-inkscape-stroke:none"
|
||||
d="m 206.76992,184 c -36.98368,0 -73.07301,9.2343 -94.76923,16.9066 v 185.1887 c 27.62799,-7.7405 62.70503,-15.0804 94.76923,-15.0804 28.33376,0 58.16312,7.6425 81.23077,14.806 V 203.0154 C 273.60322,195.1776 243.44241,184 206.76992,184 Z"
|
||||
id="path4372-8"
|
||||
sodipodi:nodetypes="sccsccs" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:63.9999;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 255.99995,122.53007 c -31.8285,-15.342 -80.43462,-15.4137 -112,0"
|
||||
id="path2371-6"
|
||||
sodipodi:nodetypes="cc" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
54
client/icons/book-part-cover.svg
Normal file
@@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 704.00001 512"
|
||||
version="1.1"
|
||||
id="svg22127"
|
||||
sodipodi:docname="book-part-cover.svg"
|
||||
width="704"
|
||||
height="512"
|
||||
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||
inkscape:export-filename="InsideCover3.png"
|
||||
inkscape:export-xdpi="300"
|
||||
inkscape:export-ydpi="300"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs22131" />
|
||||
<sodipodi:namedview
|
||||
id="namedview22129"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#111111"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.6685671"
|
||||
inkscape:cx="299.8951"
|
||||
inkscape:cy="80.021886"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg22127" />
|
||||
<!--! Font Awesome Pro 6.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
|
||||
<path
|
||||
id="path2161-6"
|
||||
style="color:#000000;fill:#000000;stroke-width:1;-inkscape-stroke:none;paint-order:stroke fill markers"
|
||||
d="M 208,0 C 147.0078,0 94.429433,14.25071 60.367188,26.66992 23.520854,39.96036 0,76.16076 0,112.95896 v 317.8321 c 0,59.8499 56.949847,92.6546 107.47266,76.6035 l -0.1543,0.049 c 26.46715,-8.335 74.84649,-18.3965 100.68164,-18.3965 17.25807,0 61.31688,10.6183 85.14453,18.8438 l 0.0508,0.018 0.0527,0.018 c 19.82627,6.5858 40.84117,4.9222 58.99804,-3.0762 18.04267,7.8799 38.84257,9.6126 58.33594,3.1328 l 0.13672,-0.045 0.13672,-0.047 c 23.88445,-8.0588 67.88646,-18.8437 85.14453,-18.8437 25.83515,0 74.22549,10.0266 100.68164,18.3964 l 0.1543,0.049 0.15625,0.049 C 647.13371,523.05316 704,490.64216 704,430.79226 v -317.8321 c 0,-36.8274 -23.49583,-72.8235 -60.00977,-86.25583 l -0.16015,-0.0606 -0.16211,-0.0566 C 609.79193,14.33005 557.11269,0.0012 496,0.0012 434.5671,0.0012 387.12553,14.01354 352,34.94261 316.87446,14.01344 269.4331,0.0012 208,0.0012 Z m 0,32.00977 c 58.3999,0 103.40004,18.89469 123,33.63279 3.3,2.4564 5,6.4246 5,10.3926 v 356.5508 c 0,10.7702 -11.70041,18.2326 -22.40039,14.6426 -26.59996,-8.9751 -71.69966,-22.2012 -105.59961,-22.2012 -38.49993,0 -88.40045,11.4317 -119.900391,21.3516 C 76.799621,449.96896 64,442.03166 64,430.78906 V 80.94726 C 64,69.51586 70.799631,58.93546 82.099609,54.87306 110.29956,44.57516 157.50009,32.00977 208,32.00977 Z m 288,0 c 50.49991,0 97.70044,12.56619 125.90039,22.76949 C 633.20037,58.93616 640,69.51586 640,80.94726 v 349.8418 c 0,11.2426 -12.79963,19.0854 -24.09961,15.5899 -31.49995,-9.9199 -81.40046,-21.3516 -119.90039,-21.3516 -33.89995,0 -78.99966,13.2261 -105.59961,22.2012 C 379.60041,450.81856 368,443.35616 368,432.58596 V 76.03516 c 0,-3.968 1.60001,-7.9362 5,-10.3926 19.59997,-14.7381 64.6001,-33.63279 123,-33.63279 z M 335.52734,45.75386 c -0.1289,0.093 -0.23137,0.2032 -0.35937,0.2969 -0.198,0.1477 -0.428,0.2796 -0.625,0.4278 z m 33.67969,0.5372 0.24805,0.1875 c -0.0427,-0.033 -0.0937,-0.061 -0.13672,-0.094 -0.0393,-0.03 -0.0713,-0.064 -0.11133,-0.094 z" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:64;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 417.64553,213.53304 c 88.71546,-18.9285 95.50522,-18.6158 172.79707,0.054"
|
||||
id="path2371-8"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
id="path2315"
|
||||
style="stroke-width:67.6532;stroke-linejoin:bevel;paint-order:stroke markers fill;stop-color:#000000"
|
||||
inkscape:transform-center-x="-3.4164388e-06"
|
||||
inkscape:transform-center-y="-8.443352"
|
||||
d="m 505.27489,52.89544 25.98603,52.6535 58.10652,8.4434 -42.04628,40.985 9.92578,57.8717 -51.97205,-27.3234 -51.97204,27.3234 9.92578,-57.8717 -42.04627,-40.985 58.10651,-8.4434 z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
@@ -34,12 +34,18 @@
|
||||
.mask-center {
|
||||
content: url('../icons/mask-center.svg');
|
||||
}
|
||||
.fa-file-c {
|
||||
content: url('../icons/fa-file-c.svg');
|
||||
}
|
||||
.book-front-cover {
|
||||
content: url('../icons/book-front-cover.svg');
|
||||
}
|
||||
.book-back-cover {
|
||||
content: url('../icons/book-back-cover.svg');
|
||||
}
|
||||
.book-inside-cover {
|
||||
content: url('../icons/book-inside-cover.svg');
|
||||
}
|
||||
.book-part-cover {
|
||||
content: url('../icons/book-part-cover.svg');
|
||||
}
|
||||
.davek {
|
||||
content: url('../icons/Davek.svg');
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 610.4 816.5" style="enable-background:new 0 0 610.4 816.5;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st1{fill:#FFFFFF;stroke:#FFFFFF;stroke-width:20;stroke-miterlimit:10;}
|
||||
</style>
|
||||
<title>fa-file-c</title>
|
||||
<g id="Layer_2_1_">
|
||||
<g id="Layer_1-2">
|
||||
<g id="page">
|
||||
<path id="page-2" d="M610.3,468.3c0,77.3,0.2,154.5,0,231.8s-39.8,116.5-116.8,116.4c-127.6,0-255.1,0-382.7,0
|
||||
c-68.1,0-110.5-41.7-110.6-109.8c-0.2-197.7-0.2-395.5,0-593.2c0-68.4,43.2-110.9,112.1-111c90-0.1,180,0.2,270-0.2
|
||||
c12.8,0,21.5,0.6,32.9,4c17.1,5,152.7,150.7,190.7,188.8c-0.7,18-6,5.7,1.4,35.1c0,6.8,3.1,11.2,3.1,18.1
|
||||
C610.2,320.8,610.3,395.7,610.3,468.3z"/>
|
||||
<path id="white_corner" class="st0" d="M364.1,0v200c0,9.3,1.7,25.6,13.1,36.8c12,11.7,28.8,12.1,37.5,12.2
|
||||
c119.8,1.3,195.6,0.4,195.6,0.4l0,0l-0.3-54.3l-197,1l3-192L364.1,0z"/>
|
||||
</g>
|
||||
<path class="st1" d="M317.7,719.8c-38.3,0-71-8.1-98.3-24.3c-27.2-16.2-48.1-39.2-62.7-69C142.3,596.8,135,561.2,135,520
|
||||
c0-30.9,4.1-58.6,12.4-83.1c8.3-24.5,20.2-45.3,35.9-62.4c15.6-17.1,34.9-30.4,57.7-39.8s48.4-14.1,76.7-14.1
|
||||
c22.1-0.1,44,3.1,65.1,9.7c20.6,6.4,38.4,15.9,53.5,28.4c4.8,3.7,8,7.8,9.7,12.4c1.6,4.2,1.8,8.9,0.6,13.2
|
||||
c-1.2,4.1-3.5,7.7-6.6,10.5c-3.1,2.8-7.2,4.2-11.3,4.1c-4.4,0-9.4-1.8-14.9-5.5c-13-10.5-27.7-18.6-43.6-23.7
|
||||
c-16.6-5.3-33.9-7.9-51.3-7.7c-29.1,0-53.7,6.2-74,18.5s-35.5,30.3-45.8,53.8c-10.3,23.6-15.4,52.1-15.4,85.5s5.1,62.1,15.4,85.9
|
||||
c10.3,23.7,25.6,41.8,45.8,54.1c20.2,12.3,44.9,18.5,74,18.5c17.4,0.1,34.8-2.6,51.3-8c16.2-5.3,31.3-13.5,44.7-24
|
||||
c5.5-3.7,10.5-5.4,14.9-5.3c4,0.1,7.9,1.5,11,4.1c3,2.7,5.2,6.1,6.4,9.9c1.3,4.1,1.3,8.6,0,12.7c-1.3,4.4-4.1,8.3-8.6,11.6
|
||||
c-15.5,13.3-33.6,23.3-54.4,30.1C362.7,716.6,340.3,720,317.7,719.8z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.0 KiB |
2542
package-lock.json
generated
65
package.json
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "homebrewery",
|
||||
"description": "Create authentic looking D&D homebrews using only markdown",
|
||||
"version": "3.7.2",
|
||||
"version": "3.8.0",
|
||||
"engines": {
|
||||
"node": "16.11.x"
|
||||
"node": ">=18.16.x"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -16,6 +16,8 @@
|
||||
"builddev": "node scripts/buildHomebrew.js --dev",
|
||||
"lint": "eslint --fix **/*.{js,jsx}",
|
||||
"lint:dry": "eslint **/*.{js,jsx}",
|
||||
"stylelint": "stylelint --fix **/*.{less}",
|
||||
"stylelint:dry": "stylelint **/*.less",
|
||||
"circleci": "npm test && eslint **/*.{js,jsx} --max-warnings=0",
|
||||
"verify": "npm run lint && npm test",
|
||||
"test": "jest --runInBand",
|
||||
@@ -23,7 +25,10 @@
|
||||
"test:coverage": "jest --coverage --silent --runInBand",
|
||||
"test:dev": "jest --verbose --watch",
|
||||
"test:basic": "jest tests/markdown/basic.test.js --verbose",
|
||||
"test:mustache-span": "jest tests/markdown/mustache-span.test.js --verbose",
|
||||
"test:mustache-syntax": "jest '.*(mustache-syntax).*' --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:injection": "jest '.*(mustache-syntax).*' -t '^Injection:.*' --verbose --noStackTrace",
|
||||
"test:route": "jest tests/routes/static-pages.test.js --verbose",
|
||||
"phb": "node scripts/phb.js",
|
||||
"prod": "set NODE_ENV=production && npm run build",
|
||||
@@ -45,20 +50,23 @@
|
||||
"coveragePathIgnorePatterns": [
|
||||
"build/*"
|
||||
],
|
||||
"coverageThreshold" : {
|
||||
"global" : {
|
||||
"statements" : 25,
|
||||
"branches" : 10,
|
||||
"functions" : 22,
|
||||
"lines" : 25
|
||||
"coverageThreshold": {
|
||||
"global": {
|
||||
"statements": 25,
|
||||
"branches": 10,
|
||||
"functions": 22,
|
||||
"lines": 25
|
||||
},
|
||||
"server/homebrew.api.js" : {
|
||||
"statements" : 65,
|
||||
"branches" : 50,
|
||||
"functions" : 60,
|
||||
"lines" : 70
|
||||
"server/homebrew.api.js": {
|
||||
"statements": 65,
|
||||
"branches": 50,
|
||||
"functions": 60,
|
||||
"lines": 70
|
||||
}
|
||||
}
|
||||
},
|
||||
"setupFilesAfterEnv": [
|
||||
"jest-expect-message"
|
||||
]
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
@@ -70,11 +78,11 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.21.3",
|
||||
"@babel/plugin-transform-runtime": "^7.21.0",
|
||||
"@babel/preset-env": "^7.19.4",
|
||||
"@babel/core": "^7.21.8",
|
||||
"@babel/plugin-transform-runtime": "^7.21.4",
|
||||
"@babel/preset-env": "^7.21.5",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@googleapis/drive": "^5.0.2",
|
||||
"@googleapis/drive": "^5.1.0",
|
||||
"body-parser": "^1.20.2",
|
||||
"classnames": "^2.3.2",
|
||||
"codemirror": "^5.65.6",
|
||||
@@ -90,25 +98,32 @@
|
||||
"less": "^3.13.1",
|
||||
"lodash": "^4.17.21",
|
||||
"marked": "4.3.0",
|
||||
"marked-extended-tables": "^1.0.5",
|
||||
"marked-extended-tables": "^1.0.6",
|
||||
"markedLegacy": "npm:marked@^0.3.19",
|
||||
"moment": "^2.29.4",
|
||||
"mongoose": "^7.0.3",
|
||||
"mongoose": "^7.1.2",
|
||||
"nanoid": "3.3.4",
|
||||
"nconf": "^0.12.0",
|
||||
"npm": "^9.6.2",
|
||||
"npm": "^9.6.7",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-frame-component": "5.2.6",
|
||||
"react-router-dom": "6.9.0",
|
||||
"react-frame-component": "^4.1.3",
|
||||
"react-router-dom": "6.11.2",
|
||||
"sanitize-filename": "1.6.3",
|
||||
"superagent": "^6.1.0",
|
||||
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.36.0",
|
||||
"eslint": "^8.40.0",
|
||||
"eslint-plugin-jest": "^27.2.1",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"jest": "^29.5.0",
|
||||
"jest-expect-message": "^1.1.3",
|
||||
"postcss-less": "^6.0.0",
|
||||
"stylelint": "^15.6.2",
|
||||
"stylelint-config-recess-order": "^4.0.0",
|
||||
"stylelint-config-standard": "^33.0.0",
|
||||
"stylelint-stylistic": "^0.4.2",
|
||||
"supertest": "^6.3.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +43,7 @@ const sanitizeBrew = (brew, accessType)=>{
|
||||
};
|
||||
|
||||
app.use('/', serveCompressedStaticAssets(`build`));
|
||||
|
||||
//app.use(express.static(`${__dirname}/build`));
|
||||
app.use(require('./middleware/content-negotiation.js'));
|
||||
app.use(require('body-parser').json({ limit: '25mb' }));
|
||||
app.use(require('cookie-parser')());
|
||||
app.use(require('./forcessl.mw.js'));
|
||||
|
||||
@@ -99,23 +99,31 @@ const GoogleActions = {
|
||||
listGoogleBrews : async (auth)=>{
|
||||
const drive = googleDrive.drive({ version: 'v3', auth });
|
||||
|
||||
const obj = await drive.files.list({
|
||||
pageSize : 1000,
|
||||
fields : 'nextPageToken, files(id, name, description, createdTime, modifiedTime, properties)',
|
||||
q : 'mimeType != \'application/vnd.google-apps.folder\' and trashed = false'
|
||||
})
|
||||
.catch((err)=>{
|
||||
console.log(`Error Listing Google Brews`);
|
||||
console.error(err);
|
||||
throw (err);
|
||||
//TODO: Should break out here, but continues on for some reason.
|
||||
});
|
||||
const fileList = [];
|
||||
let NextPageToken = "";
|
||||
|
||||
if(!obj.data.files.length) {
|
||||
do {
|
||||
const obj = await drive.files.list({
|
||||
pageSize : 1000,
|
||||
pageToken : NextPageToken || "",
|
||||
fields : 'nextPageToken, files(id, name, description, createdTime, modifiedTime, properties)',
|
||||
q : 'mimeType != \'application/vnd.google-apps.folder\' and trashed = false'
|
||||
})
|
||||
.catch((err)=>{
|
||||
console.log(`Error Listing Google Brews`);
|
||||
console.error(err);
|
||||
throw (err);
|
||||
//TODO: Should break out here, but continues on for some reason.
|
||||
});
|
||||
fileList.push(...obj.data.files);
|
||||
NextPageToken = obj.data.nextPageToken;
|
||||
} while (NextPageToken);
|
||||
|
||||
if(!fileList.length) {
|
||||
console.log('No files found.');
|
||||
}
|
||||
|
||||
const brews = obj.data.files.map((file)=>{
|
||||
const brews = fileList.map((file)=>{
|
||||
return {
|
||||
text : '',
|
||||
shareId : file.properties.shareId,
|
||||
|
||||
@@ -305,7 +305,7 @@ If you believe you should have access to this brew, ask the file owner to invite
|
||||
|
||||
if(brew.authors.length === 0) {
|
||||
// Delete brew if there are no authors left
|
||||
await brew.remove()
|
||||
await HomebrewModel.deleteOne({ _id: brew._id })
|
||||
.catch((err)=>{
|
||||
console.error(err);
|
||||
throw { status: 500, message: 'Error while removing' };
|
||||
|
||||
@@ -10,7 +10,6 @@ describe('Tests for api', ()=>{
|
||||
|
||||
let modelBrew;
|
||||
let saveFunc;
|
||||
let removeFunc;
|
||||
let markModifiedFunc;
|
||||
let saved;
|
||||
|
||||
@@ -20,18 +19,15 @@ describe('Tests for api', ()=>{
|
||||
saved = { ...this, _id: '1' };
|
||||
return saved;
|
||||
});
|
||||
removeFunc = jest.fn(async function() {});
|
||||
markModifiedFunc = jest.fn(()=>true);
|
||||
|
||||
modelBrew = (brew)=>({
|
||||
...brew,
|
||||
save : saveFunc,
|
||||
remove : removeFunc,
|
||||
markModified : markModifiedFunc,
|
||||
toObject : function() {
|
||||
delete this.save;
|
||||
delete this.toObject;
|
||||
delete this.remove;
|
||||
delete this.markModified;
|
||||
return this;
|
||||
}
|
||||
@@ -569,13 +565,14 @@ brew`);
|
||||
req.brew = brew;
|
||||
});
|
||||
model.findOne = jest.fn(async ()=>modelBrew(brew));
|
||||
model.deleteOne = jest.fn(async ()=>{});
|
||||
const req = {};
|
||||
|
||||
await api.deleteBrew(req, res);
|
||||
|
||||
expect(api.getBrew).toHaveBeenCalled();
|
||||
expect(model.findOne).toHaveBeenCalled();
|
||||
expect(removeFunc).toHaveBeenCalled();
|
||||
expect(model.deleteOne).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should throw on delete error', async ()=>{
|
||||
@@ -587,7 +584,7 @@ brew`);
|
||||
req.brew = brew;
|
||||
});
|
||||
model.findOne = jest.fn(async ()=>modelBrew(brew));
|
||||
removeFunc = jest.fn(async ()=>{ throw 'err'; });
|
||||
model.deleteOne = jest.fn(async ()=>{ throw 'err'; });
|
||||
const req = {};
|
||||
|
||||
let err;
|
||||
@@ -600,7 +597,7 @@ brew`);
|
||||
expect(err).not.toBeUndefined();
|
||||
expect(api.getBrew).toHaveBeenCalled();
|
||||
expect(model.findOne).toHaveBeenCalled();
|
||||
expect(removeFunc).toHaveBeenCalled();
|
||||
expect(model.deleteOne).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should delete when one author', async ()=>{
|
||||
@@ -612,13 +609,14 @@ brew`);
|
||||
req.brew = brew;
|
||||
});
|
||||
model.findOne = jest.fn(async ()=>modelBrew(brew));
|
||||
model.deleteOne = jest.fn(async ()=>{});
|
||||
const req = { account: { username: 'test' } };
|
||||
|
||||
await api.deleteBrew(req, res);
|
||||
|
||||
expect(api.getBrew).toHaveBeenCalled();
|
||||
expect(model.findOne).toHaveBeenCalled();
|
||||
expect(removeFunc).toHaveBeenCalled();
|
||||
expect(model.deleteOne).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should remove one author when multiple present', async ()=>{
|
||||
@@ -630,6 +628,7 @@ brew`);
|
||||
req.brew = brew;
|
||||
});
|
||||
model.findOne = jest.fn(async ()=>modelBrew(brew));
|
||||
model.deleteOne = jest.fn(async ()=>{});
|
||||
const req = { account: { username: 'test' } };
|
||||
|
||||
await api.deleteBrew(req, res);
|
||||
@@ -637,7 +636,7 @@ brew`);
|
||||
expect(api.getBrew).toHaveBeenCalled();
|
||||
expect(markModifiedFunc).toHaveBeenCalled();
|
||||
expect(model.findOne).toHaveBeenCalled();
|
||||
expect(removeFunc).not.toHaveBeenCalled();
|
||||
expect(model.deleteOne).not.toHaveBeenCalled();
|
||||
expect(saveFunc).toHaveBeenCalled();
|
||||
expect(saved.authors).toEqual(['test2']);
|
||||
});
|
||||
@@ -651,6 +650,7 @@ brew`);
|
||||
req.brew = brew;
|
||||
});
|
||||
model.findOne = jest.fn(async ()=>modelBrew(brew));
|
||||
model.deleteOne = jest.fn(async ()=>{});
|
||||
saveFunc = jest.fn(async ()=>{ throw 'err'; });
|
||||
const req = { account: { username: 'test' } };
|
||||
|
||||
@@ -664,7 +664,7 @@ brew`);
|
||||
expect(err).not.toBeUndefined();
|
||||
expect(api.getBrew).toHaveBeenCalled();
|
||||
expect(model.findOne).toHaveBeenCalled();
|
||||
expect(removeFunc).not.toHaveBeenCalled();
|
||||
expect(model.deleteOne).not.toHaveBeenCalled();
|
||||
expect(saveFunc).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -677,6 +677,7 @@ brew`);
|
||||
req.brew = brew;
|
||||
});
|
||||
model.findOne = jest.fn(async ()=>modelBrew(brew));
|
||||
model.deleteOne = jest.fn(async ()=>{});
|
||||
api.deleteGoogleBrew = jest.fn(async ()=>true);
|
||||
const req = { account: { username: 'test' } };
|
||||
|
||||
@@ -684,7 +685,7 @@ brew`);
|
||||
|
||||
expect(api.getBrew).toHaveBeenCalled();
|
||||
expect(model.findOne).toHaveBeenCalled();
|
||||
expect(removeFunc).toHaveBeenCalled();
|
||||
expect(model.deleteOne).toHaveBeenCalled();
|
||||
expect(api.deleteGoogleBrew).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -697,6 +698,7 @@ brew`);
|
||||
req.brew = brew;
|
||||
});
|
||||
model.findOne = jest.fn(async ()=>modelBrew(brew));
|
||||
model.deleteOne = jest.fn(async ()=>{});
|
||||
api.deleteGoogleBrew = jest.fn(async ()=>{
|
||||
throw 'err';
|
||||
});
|
||||
@@ -706,7 +708,7 @@ brew`);
|
||||
|
||||
expect(api.getBrew).toHaveBeenCalled();
|
||||
expect(model.findOne).toHaveBeenCalled();
|
||||
expect(removeFunc).toHaveBeenCalled();
|
||||
expect(model.deleteOne).toHaveBeenCalled();
|
||||
expect(api.deleteGoogleBrew).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -719,6 +721,7 @@ brew`);
|
||||
req.brew = brew;
|
||||
});
|
||||
model.findOne = jest.fn(async ()=>modelBrew(brew));
|
||||
model.deleteOne = jest.fn(async ()=>{});
|
||||
api.deleteGoogleBrew = jest.fn(async ()=>true);
|
||||
const req = { account: { username: 'test' } };
|
||||
|
||||
@@ -727,7 +730,7 @@ brew`);
|
||||
expect(api.getBrew).toHaveBeenCalled();
|
||||
expect(markModifiedFunc).toHaveBeenCalled();
|
||||
expect(model.findOne).toHaveBeenCalled();
|
||||
expect(removeFunc).not.toHaveBeenCalled();
|
||||
expect(model.deleteOne).not.toHaveBeenCalled();
|
||||
expect(api.deleteGoogleBrew).toHaveBeenCalled();
|
||||
expect(saveFunc).toHaveBeenCalled();
|
||||
expect(saved.authors).toEqual(['test2']);
|
||||
@@ -745,6 +748,7 @@ brew`);
|
||||
req.brew = brew;
|
||||
});
|
||||
model.findOne = jest.fn(async ()=>modelBrew(brew));
|
||||
model.deleteOne = jest.fn(async ()=>{});
|
||||
api.deleteGoogleBrew = jest.fn(async ()=>true);
|
||||
const req = { account: { username: 'test2' } };
|
||||
|
||||
@@ -752,7 +756,7 @@ brew`);
|
||||
|
||||
expect(api.getBrew).toHaveBeenCalled();
|
||||
expect(model.findOne).toHaveBeenCalled();
|
||||
expect(removeFunc).not.toHaveBeenCalled();
|
||||
expect(model.deleteOne).not.toHaveBeenCalled();
|
||||
expect(api.deleteGoogleBrew).not.toHaveBeenCalled();
|
||||
expect(saveFunc).toHaveBeenCalled();
|
||||
expect(saved.authors).toEqual(['test']);
|
||||
|
||||
12
server/middleware/content-negotiation.js
Normal file
@@ -0,0 +1,12 @@
|
||||
module.exports = (req, res, next)=>{
|
||||
const isImageRequest = req.get('Accept')?.split(',')
|
||||
?.filter((h)=>!h.includes('q='))
|
||||
?.every((h)=>/image\/.*/.test(h));
|
||||
if(isImageRequest) {
|
||||
return res.status(406).send({
|
||||
message : 'Request for image at this URL is not supported'
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
41
server/middleware/content-negotiation.spec.js
Normal file
@@ -0,0 +1,41 @@
|
||||
const contentNegotiationMiddleware = require('./content-negotiation.js');
|
||||
|
||||
describe('content-negotiation-middleware', ()=>{
|
||||
let request;
|
||||
let response;
|
||||
let next;
|
||||
|
||||
beforeEach(()=>{
|
||||
request = {
|
||||
get : function(key) {
|
||||
return this[key];
|
||||
}
|
||||
};
|
||||
response = {
|
||||
status : jest.fn(()=>response),
|
||||
send : jest.fn(()=>{})
|
||||
};
|
||||
next = jest.fn();
|
||||
});
|
||||
|
||||
it('should return 406 on image request', ()=>{
|
||||
contentNegotiationMiddleware({
|
||||
Accept : 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8',
|
||||
...request
|
||||
}, response);
|
||||
|
||||
expect(response.status).toHaveBeenLastCalledWith(406);
|
||||
expect(response.send).toHaveBeenCalledWith({
|
||||
message : 'Request for image at this URL is not supported'
|
||||
});
|
||||
});
|
||||
|
||||
it('should call next on non-image request', ()=>{
|
||||
contentNegotiationMiddleware({
|
||||
Accept : 'text,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8',
|
||||
...request
|
||||
}, response, next);
|
||||
|
||||
expect(next).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -134,7 +134,7 @@ const mustacheInjectInline = {
|
||||
const match = inlineRegex.exec(src);
|
||||
if(match) {
|
||||
const lastToken = tokens[tokens.length - 1];
|
||||
if(!lastToken)
|
||||
if(!lastToken || lastToken.type == 'mustacheInjectInline')
|
||||
return false;
|
||||
|
||||
const tags = ` ${processStyleTags(match[1])}`;
|
||||
@@ -169,7 +169,7 @@ const mustacheInjectBlock = {
|
||||
const match = inlineRegex.exec(src);
|
||||
if(match) {
|
||||
const lastToken = tokens[tokens.length - 1];
|
||||
if(!lastToken)
|
||||
if(!lastToken || lastToken.type == 'mustacheInjectBlock')
|
||||
return false;
|
||||
|
||||
lastToken.originalType = 'mustacheInjectBlock';
|
||||
|
||||
@@ -29,7 +29,7 @@ const Nav = {
|
||||
section : createClass({
|
||||
displayName : 'Nav.section',
|
||||
render : function(){
|
||||
return <div className='navSection'>
|
||||
return <div className={`navSection ${this.props.className ?? ''}`}>
|
||||
{this.props.children}
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ nav{
|
||||
position : relative;
|
||||
display : flex;
|
||||
justify-content : space-between;
|
||||
z-index : 2;
|
||||
}
|
||||
.navSection{
|
||||
display : flex;
|
||||
|
||||
68
stylelint_plugins/align-colons.js
Normal file
@@ -0,0 +1,68 @@
|
||||
const stylelint = require('stylelint');
|
||||
|
||||
const { report, ruleMessages, validateOptions } = stylelint.utils;
|
||||
const ruleName = 'naturalcrit/align-colons';
|
||||
const messages = ruleMessages(ruleName, {
|
||||
expected: (rule) => `Expected colons aligned within rule "${rule}"`,
|
||||
});
|
||||
|
||||
|
||||
module.exports = stylelint.createPlugin(ruleName, function getPlugin(primaryOption, secondaryOptionObject, context) {
|
||||
return function lint(postcssRoot, postcssResult) {
|
||||
|
||||
const validOptions = validateOptions(
|
||||
postcssResult,
|
||||
ruleName,
|
||||
{
|
||||
actual: primaryOption,
|
||||
possible: [
|
||||
true,
|
||||
false
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
if (!validOptions) { //If the options are invalid, don't lint
|
||||
return;
|
||||
}
|
||||
const isAutoFixing = Boolean(context.fix);
|
||||
postcssRoot.walkRules(rule => { //Iterate CSS rules
|
||||
|
||||
let maxColonPos = 0;
|
||||
let misaligned = false;
|
||||
rule.each(declaration => {
|
||||
|
||||
if(declaration.type != "decl")
|
||||
return;
|
||||
|
||||
let colonPos = declaration.prop.length + declaration.raws.between.indexOf(":");
|
||||
if (maxColonPos > 0 && colonPos != maxColonPos) {
|
||||
misaligned = true;
|
||||
}
|
||||
maxColonPos = Math.max(maxColonPos, colonPos);
|
||||
});
|
||||
|
||||
if(misaligned) {
|
||||
if (isAutoFixing) { //We are in “fix” mode
|
||||
rule.each(declaration => {
|
||||
if(declaration.type != "decl")
|
||||
return;
|
||||
|
||||
declaration.raws.between = " ".repeat(maxColonPos - declaration.prop.length) + ":" + declaration.raws.between.split(":")[1];
|
||||
})
|
||||
} else { //We are in “report only” mode
|
||||
report({
|
||||
ruleName,
|
||||
result: postcssResult,
|
||||
message: messages.expected(rule.selector), // Build the reported message
|
||||
node: rule, // Specify the reported node
|
||||
word: rule.selector, // Which exact word caused the error? This positions the error properly
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
module.exports.ruleName = ruleName;
|
||||
module.exports.messages = messages;
|
||||
@@ -0,0 +1,54 @@
|
||||
const stylelint = require('stylelint');
|
||||
const { isNumber } = require('stylelint/lib/utils/validateTypes');
|
||||
|
||||
const { report, ruleMessages, validateOptions } = stylelint.utils;
|
||||
const ruleName = 'naturalcrit/declaration-block-multi-line-min-declarations';
|
||||
const messages = ruleMessages(ruleName, {
|
||||
expected: (decls) => `Rule with ${decls} declaration${decls == 1 ? '' : 's'} should be single line`,
|
||||
});
|
||||
|
||||
|
||||
module.exports = stylelint.createPlugin(ruleName, function getPlugin(primaryOption, secondaryOptionObject, context) {
|
||||
return function lint(postcssRoot, postcssResult) {
|
||||
|
||||
const validOptions = validateOptions(
|
||||
postcssResult,
|
||||
ruleName,
|
||||
{
|
||||
actual: primaryOption,
|
||||
possible: [isNumber],
|
||||
}
|
||||
);
|
||||
|
||||
if (!validOptions) { //If the options are invalid, don't lint
|
||||
return;
|
||||
}
|
||||
const isAutoFixing = Boolean(context.fix);
|
||||
|
||||
postcssRoot.walkRules(rule => { //Iterate CSS rules
|
||||
|
||||
//Apply rule only if all children are decls (no nested rules)
|
||||
if (rule.nodes.length > primaryOption || !rule.nodes.every((node) => node.type === 'decl')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAutoFixing) { //We are in “fix” mode
|
||||
rule.each((decl) => {
|
||||
decl.raws.before = " ";
|
||||
});
|
||||
rule.raws.after = ' ';
|
||||
} else {
|
||||
report({
|
||||
ruleName,
|
||||
result: postcssResult,
|
||||
message: messages.expected(rule.nodes.length), // Build the reported message
|
||||
node: rule, // Specify the reported node
|
||||
word: rule.selector, // Which exact word caused the error? This positions the error properly
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
module.exports.ruleName = ruleName;
|
||||
module.exports.messages = messages;
|
||||
52
stylelint_plugins/declaration-colon-min-space-before.js
Normal file
@@ -0,0 +1,52 @@
|
||||
const stylelint = require('stylelint');
|
||||
const { isNumber } = require('stylelint/lib/utils/validateTypes');
|
||||
|
||||
const { report, ruleMessages, validateOptions } = stylelint.utils;
|
||||
const ruleName = 'naturalcrit/declaration-colon-min-space-before';
|
||||
const messages = ruleMessages(ruleName, {
|
||||
expected: (num) => `Expected at least ${num} space${num == 1 ? '' : 's'} before ":"`
|
||||
});
|
||||
|
||||
|
||||
module.exports = stylelint.createPlugin(ruleName, function getPlugin(primaryOption, secondaryOptionObject, context) {
|
||||
return function lint(postcssRoot, postcssResult) {
|
||||
|
||||
const validOptions = validateOptions(
|
||||
postcssResult,
|
||||
ruleName,
|
||||
{
|
||||
actual: primaryOption,
|
||||
possible: [isNumber],
|
||||
}
|
||||
);
|
||||
|
||||
if (!validOptions) { //If the options are invalid, don't lint
|
||||
return;
|
||||
}
|
||||
const isAutoFixing = Boolean(context.fix);
|
||||
|
||||
postcssRoot.walkDecls(decl => { //Iterate CSS declarations
|
||||
|
||||
let between = decl.raws.between;
|
||||
const colonIndex = between.indexOf(":");
|
||||
|
||||
if (between.slice(0, colonIndex).length >= primaryOption) {
|
||||
return;
|
||||
}
|
||||
if (isAutoFixing) { //We are in “fix” mode
|
||||
decl.raws.between = between.slice(0, colonIndex).replace(/\s*$/, ' '.repeat(primaryOption)) + between.slice(colonIndex)
|
||||
} else {
|
||||
report({
|
||||
ruleName,
|
||||
result: postcssResult,
|
||||
message: messages.expected(primaryOption), // Build the reported message
|
||||
node: decl, // Specify the reported node
|
||||
word: ":", // Which exact word caused the error? This positions the error properly
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
module.exports.ruleName = ruleName;
|
||||
module.exports.messages = messages;
|
||||
@@ -1,128 +0,0 @@
|
||||
/* eslint-disable max-lines */
|
||||
|
||||
const Markdown = require('naturalcrit/markdown.js');
|
||||
|
||||
test('Renders a mustache span with text only', function() {
|
||||
const source = '{{ text}}';
|
||||
const rendered = Markdown.render(source);
|
||||
expect(rendered).toBe('<span class="inline-block ">text</span>');
|
||||
});
|
||||
|
||||
test('Renders a mustache span with text only, but with spaces', function() {
|
||||
const source = '{{ this is a text}}';
|
||||
const rendered = Markdown.render(source);
|
||||
expect(rendered).toBe('<span class="inline-block ">this is a text</span>');
|
||||
});
|
||||
|
||||
test('Renders an empty mustache span', function() {
|
||||
const source = '{{}}';
|
||||
const rendered = Markdown.render(source);
|
||||
expect(rendered).toBe('<span class="inline-block "></span>');
|
||||
});
|
||||
|
||||
test('Renders a mustache span with just a space', function() {
|
||||
const source = '{{ }}';
|
||||
const rendered = Markdown.render(source);
|
||||
expect(rendered).toBe('<span class="inline-block "></span>');
|
||||
});
|
||||
|
||||
test('Renders a mustache span with a few spaces only', function() {
|
||||
const source = '{{ }}';
|
||||
const rendered = Markdown.render(source);
|
||||
expect(rendered).toBe('<span class="inline-block "></span>');
|
||||
});
|
||||
|
||||
test('Renders a mustache span with text and class', function() {
|
||||
const source = '{{my-class text}}';
|
||||
const rendered = Markdown.render(source);
|
||||
// FIXME: why do we have those two extra spaces after closing "?
|
||||
expect(rendered).toBe('<span class="inline-block my-class" >text</span>');
|
||||
});
|
||||
|
||||
test('Renders a mustache span with text and two classes', function() {
|
||||
const source = '{{my-class,my-class2 text}}';
|
||||
const rendered = Markdown.render(source);
|
||||
// FIXME: why do we have those two extra spaces after closing "?
|
||||
expect(rendered).toBe('<span class="inline-block my-class my-class2" >text</span>');
|
||||
});
|
||||
|
||||
test('Renders a mustache span with text with spaces and class', function() {
|
||||
const source = '{{my-class this is a text}}';
|
||||
const rendered = Markdown.render(source);
|
||||
// FIXME: why do we have those two extra spaces after closing "?
|
||||
expect(rendered).toBe('<span class="inline-block my-class" >this is a text</span>');
|
||||
});
|
||||
|
||||
test('Renders a mustache span with text and id', function() {
|
||||
const source = '{{#my-span text}}';
|
||||
const rendered = Markdown.render(source);
|
||||
// FIXME: why do we have that one extra space after closing "?
|
||||
expect(rendered).toBe('<span class="inline-block " id="my-span" >text</span>');
|
||||
});
|
||||
|
||||
test('Renders a mustache span with text and two ids', function() {
|
||||
const source = '{{#my-span,#my-favorite-span text}}';
|
||||
const rendered = Markdown.render(source);
|
||||
// FIXME: do we need to report an error here somehow?
|
||||
expect(rendered).toBe('<span class="inline-block " id="my-span" >text</span>');
|
||||
});
|
||||
|
||||
test('Renders a mustache span with text and css property', function() {
|
||||
const source = '{{color:red text}}';
|
||||
const rendered = Markdown.render(source);
|
||||
expect(rendered).toBe('<span class="inline-block " style="color:red;">text</span>');
|
||||
});
|
||||
|
||||
test('Renders a mustache span with text and two css properties', function() {
|
||||
const source = '{{color:red,padding:5px text}}';
|
||||
const rendered = Markdown.render(source);
|
||||
expect(rendered).toBe('<span class="inline-block " style="color:red; padding:5px;">text</span>');
|
||||
});
|
||||
|
||||
test('Renders a mustache span with text and css property which contains quotes', function() {
|
||||
const source = '{{font:"trebuchet ms" text}}';
|
||||
const rendered = Markdown.render(source);
|
||||
// FIXME: is it correct to remove quotes surrounding css property value?
|
||||
expect(rendered).toBe('<span class="inline-block " style="font:trebuchet ms;">text</span>');
|
||||
});
|
||||
|
||||
test('Renders a mustache span with text and two css properties which contains quotes', function() {
|
||||
const source = '{{font:"trebuchet ms",padding:"5px 10px" text}}';
|
||||
const rendered = Markdown.render(source);
|
||||
expect(rendered).toBe('<span class="inline-block " style="font:trebuchet ms; padding:5px 10px;">text</span>');
|
||||
});
|
||||
|
||||
|
||||
test('Renders a mustache span with text with quotes and css property which contains quotes', function() {
|
||||
const source = '{{font:"trebuchet ms" text "with quotes"}}';
|
||||
const rendered = Markdown.render(source);
|
||||
expect(rendered).toBe('<span class="inline-block " style="font:trebuchet ms;">text “with quotes”</span>');
|
||||
});
|
||||
|
||||
test('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 rendered = Markdown.render(source);
|
||||
expect(rendered).toBe('<span class="inline-block pen" id="author" style="color:orange; font-family:trebuchet ms;">text</span>');
|
||||
});
|
||||
|
||||
// TODO: add tests for ID with accordance to CSS spec:
|
||||
//
|
||||
// From https://drafts.csswg.org/selectors/#id-selectors:
|
||||
//
|
||||
// > An ID selector consists of a “number sign” (U+0023, #) immediately followed by the ID value, which must be a CSS identifier.
|
||||
//
|
||||
// From: https://www.w3.org/TR/CSS21/syndata.html#value-def-identifier:
|
||||
//
|
||||
// > In CSS, identifiers (including element names, classes, and IDs in selectors) can contain only the characters [a-zA-Z0-9]
|
||||
// > and ISO 10646 characters U+00A0 and higher, plus the hyphen (-) and the underscore (_);
|
||||
// > they cannot start with a digit, two hyphens, or a hyphen followed by a digit.
|
||||
// > Identifiers can also contain escaped characters and any ISO 10646 character as a numeric code (see next item).
|
||||
// > For instance, the identifier "B&W?" may be written as "B\&W\?" or "B\26 W\3F".
|
||||
// > Note that Unicode is code-by-code equivalent to ISO 10646 (see [UNICODE] and [ISO10646]).
|
||||
|
||||
// TODO: add tests for class with accordance to CSS spec:
|
||||
//
|
||||
// From: https://drafts.csswg.org/selectors/#class-html:
|
||||
//
|
||||
// > The class selector is given as a full stop (. U+002E) immediately followed by an identifier.
|
||||
|
||||
375
tests/markdown/mustache-syntax.test.js
Normal file
@@ -0,0 +1,375 @@
|
||||
/* eslint-disable max-lines */
|
||||
|
||||
const dedent = require('dedent-tabs').default;
|
||||
const Markdown = require('naturalcrit/markdown.js');
|
||||
|
||||
// Marked.js adds line returns after closing tags on some default tokens.
|
||||
// This removes those line returns for comparison sake.
|
||||
String.prototype.trimReturns = function(){
|
||||
return this.replace(/\r?\n|\r/g, '');
|
||||
};
|
||||
|
||||
// Adding `.failing()` method to `describe` or `it` will make failing tests "pass" as long as they continue to fail.
|
||||
// Remove the `.failing()` method once you have fixed the issue.
|
||||
|
||||
describe('Inline: When using the Inline syntax {{ }}', ()=>{
|
||||
it.failing('Renders a mustache span with text only', function() {
|
||||
const source = '{{ text}}';
|
||||
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>');
|
||||
});
|
||||
|
||||
it.failing('Renders a mustache span with text only, but with spaces', function() {
|
||||
const source = '{{ this is a text}}';
|
||||
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>');
|
||||
});
|
||||
|
||||
it.failing('Renders an empty mustache span', function() {
|
||||
const 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>');
|
||||
});
|
||||
|
||||
it.failing('Renders a mustache span with just a space', function() {
|
||||
const 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>');
|
||||
});
|
||||
|
||||
it.failing('Renders a mustache span with a few spaces only', function() {
|
||||
const 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>');
|
||||
});
|
||||
|
||||
it.failing('Renders a mustache span with text and class', function() {
|
||||
const source = '{{my-class text}}';
|
||||
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>');
|
||||
});
|
||||
|
||||
it.failing('Renders a mustache span with text and two classes', function() {
|
||||
const source = '{{my-class,my-class2 text}}';
|
||||
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>');
|
||||
});
|
||||
|
||||
it.failing('Renders a mustache span with text with spaces and class', function() {
|
||||
const source = '{{my-class this is a text}}';
|
||||
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>');
|
||||
});
|
||||
|
||||
it.failing('Renders a mustache span with text and id', function() {
|
||||
const source = '{{#my-span text}}';
|
||||
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>');
|
||||
});
|
||||
|
||||
it.failing('Renders a mustache span with text and two ids', function() {
|
||||
const source = '{{#my-span,#my-favorite-span text}}';
|
||||
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>');
|
||||
});
|
||||
|
||||
it.failing('Renders a mustache span with text and css property', function() {
|
||||
const source = '{{color:red text}}';
|
||||
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>');
|
||||
});
|
||||
|
||||
it.failing('Renders a mustache span with text and two css properties', function() {
|
||||
const source = '{{color:red,padding:5px text}}';
|
||||
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>');
|
||||
});
|
||||
|
||||
it.failing('Renders a mustache span with text and css property which contains quotes', function() {
|
||||
const source = '{{font-family:"trebuchet ms" text}}';
|
||||
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>');
|
||||
});
|
||||
|
||||
it.failing('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 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>');
|
||||
});
|
||||
|
||||
|
||||
it.failing('Renders a mustache span with text with quotes and css property which contains quotes', function() {
|
||||
const source = '{{font-family:"trebuchet ms" text "with quotes"}}';
|
||||
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>');
|
||||
});
|
||||
|
||||
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 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>');
|
||||
});
|
||||
});
|
||||
|
||||
// BLOCK SYNTAX
|
||||
|
||||
describe(`Block: When using the Block syntax {{tags\\ntext\\n}}`, ()=>{
|
||||
it.failing('Renders a div with text only', function() {
|
||||
const source = dedent`{{
|
||||
text
|
||||
}}`;
|
||||
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>`);
|
||||
});
|
||||
|
||||
it.failing('Renders an empty div', function() {
|
||||
const source = dedent`{{
|
||||
|
||||
}}`;
|
||||
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>`);
|
||||
});
|
||||
|
||||
it('Renders a single paragraph with opening and closing brackets', function() {
|
||||
const source = dedent`{{
|
||||
}}`;
|
||||
const rendered = Markdown.render(source).trimReturns();
|
||||
// this actually renders in HB as '{{ }}'...
|
||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>{{}}</p>`);
|
||||
});
|
||||
|
||||
it.failing('Renders a div with a single class', function() {
|
||||
const source = dedent`{{cat
|
||||
|
||||
}}`;
|
||||
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>`);
|
||||
});
|
||||
|
||||
it.failing('Renders a div with a single class and text', function() {
|
||||
const source = dedent`{{cat
|
||||
Sample text.
|
||||
}}`;
|
||||
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>`);
|
||||
});
|
||||
|
||||
it.failing('Renders a div with two classes and text', function() {
|
||||
const source = dedent`{{cat,dog
|
||||
Sample text.
|
||||
}}`;
|
||||
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>`);
|
||||
});
|
||||
|
||||
it.failing('Renders a div with a style and text', function() {
|
||||
const source = dedent`{{color:red
|
||||
Sample text.
|
||||
}}`;
|
||||
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>`);
|
||||
});
|
||||
|
||||
it.failing('Renders a div with a class, style and text', function() {
|
||||
const source = dedent`{{cat,color:red
|
||||
Sample text.
|
||||
}}`;
|
||||
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>`);
|
||||
});
|
||||
|
||||
it('Renders a div with an ID, class, style and text (different order)', function() {
|
||||
const source = dedent`{{color:red,cat,#dog
|
||||
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;"><p>Sample text.</p></div>`);
|
||||
});
|
||||
|
||||
it.failing('Renders a div with a single ID', function() {
|
||||
const source = dedent`{{#cat,#dog
|
||||
Sample text.
|
||||
}}`;
|
||||
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>`);
|
||||
});
|
||||
});
|
||||
|
||||
// MUSTACHE INJECTION SYNTAX
|
||||
|
||||
describe('Injection: When an injection tag follows an element', ()=>{
|
||||
// FIXME: Most of these fail because injections currently replace attributes, rather than append to. Or just minor extra whitespace issues.
|
||||
describe('and that element is an inline-block', ()=>{
|
||||
it.failing('Renders a span "text" with no injection', function() {
|
||||
const source = '{{ text}}{}';
|
||||
const rendered = Markdown.render(source);
|
||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block">text</span>');
|
||||
});
|
||||
|
||||
it.failing('Renders a span "text" with injected Class name', function() {
|
||||
const source = '{{ text}}{ClassName}';
|
||||
const rendered = Markdown.render(source);
|
||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block ClassName">text</span>');
|
||||
});
|
||||
|
||||
it.failing('Renders a span "text" with injected style', function() {
|
||||
const source = '{{ text}}{color:red}';
|
||||
const rendered = Markdown.render(source);
|
||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:red;">text</span>');
|
||||
});
|
||||
|
||||
it.failing('Renders a span "text" with two injected styles', function() {
|
||||
const source = '{{ text}}{color:red,background:blue}';
|
||||
const rendered = Markdown.render(source);
|
||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<span class="inline-block" style="color:red; background:blue;">text</span>');
|
||||
});
|
||||
|
||||
it.failing('Renders an emphasis element with injected Class name', function() {
|
||||
const source = '*emphasis*{big}';
|
||||
const rendered = Markdown.render(source).trimReturns();
|
||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p><em class="big">emphasis</em></p>');
|
||||
});
|
||||
|
||||
it.failing('Renders a code element with injected style', function() {
|
||||
const source = '`code`{background:gray}';
|
||||
const rendered = Markdown.render(source).trimReturns();
|
||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p><code style="background:gray;">code</code></p>');
|
||||
});
|
||||
|
||||
it.failing('Renders an image element with injected style', function() {
|
||||
const source = '{position:absolute}';
|
||||
const rendered = Markdown.render(source).trimReturns();
|
||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p><img src="http://i.imgur.com/hMna6G0.png" alt="homebrew mug" style="position:absolute;"></p>');
|
||||
});
|
||||
|
||||
it.failing('Renders an element modified by only the first of two consecutive injections', function() {
|
||||
const source = '{{ text}}{color:red}{background:blue}';
|
||||
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>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('and that element is a block', ()=>{
|
||||
it.failing('renders a div "text" with no injection', function() {
|
||||
const source = '{{\ntext\n}}\n{}';
|
||||
const rendered = Markdown.render(source).trimReturns();
|
||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block"><p>text</p></div>');
|
||||
});
|
||||
|
||||
it.failing('renders a div "text" with injected Class name', function() {
|
||||
const source = '{{\ntext\n}}\n{ClassName}';
|
||||
const rendered = Markdown.render(source).trimReturns();
|
||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block ClassName"><p>text</p></div>');
|
||||
});
|
||||
|
||||
it.failing('renders a div "text" with injected style', function() {
|
||||
const source = '{{\ntext\n}}\n{color:red}';
|
||||
const rendered = Markdown.render(source).trimReturns();
|
||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block" style="color:red;"><p>text</p></div>');
|
||||
});
|
||||
|
||||
it.failing('renders a div "text" with two injected styles', function() {
|
||||
const source = dedent`{{
|
||||
text
|
||||
}}
|
||||
{color:red,background:blue}`;
|
||||
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>');
|
||||
});
|
||||
|
||||
it.failing('renders an h2 header "text" with injected class name', function() {
|
||||
const source = dedent`## text
|
||||
{ClassName}`;
|
||||
const rendered = Markdown.render(source).trimReturns();
|
||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<h2 class="ClassName">text</h2>');
|
||||
});
|
||||
|
||||
it.failing('renders a table with injected class name', function() {
|
||||
const source = dedent`| Experience Points | Level |
|
||||
|:------------------|:-----:|
|
||||
| 0 | 1 |
|
||||
| 300 | 2 |
|
||||
|
||||
{ClassName}`;
|
||||
const rendered = Markdown.render(source).trimReturns();
|
||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<table class="ClassName"><thead><tr><th align=left>Experience Points</th><th align=center>Level</th></tr></thead><tbody><tr><td align=left>0</td><td align=center>1</td></tr><tr><td align=left>300</td><td align=center>2</td></tr></tbody></table>`);
|
||||
});
|
||||
|
||||
// it('renders a list with with a style injected into the <ul> tag', function() {
|
||||
// const source = dedent`- Cursed Ritual of Bad Hair
|
||||
// - Eliminate Vindictiveness in Gym Teacher
|
||||
// - Ultimate Rite of the Confetti Angel
|
||||
// - Dark Chant of the Dentists
|
||||
// - Divine Spell of Crossdressing
|
||||
// {color:red}`;
|
||||
// const rendered = Markdown.render(source).trimReturns();
|
||||
// expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`...`); // FIXME: expect this to be injected into <ul>? Currently injects into last <li>
|
||||
// });
|
||||
|
||||
it.failing('renders an h2 header "text" with injected class name, and "secondInjection" as regular text on the next line.', function() {
|
||||
const source = dedent`## text
|
||||
{ClassName}
|
||||
{secondInjection}`;
|
||||
const rendered = Markdown.render(source).trimReturns();
|
||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<h2 class="ClassName">text</h2><p>{secondInjection}</p>');
|
||||
});
|
||||
|
||||
it.failing('renders a div nested into another div, the inner with class=innerDiv and the other class=outerDiv', function() {
|
||||
const source = dedent`{{
|
||||
outer text
|
||||
{{
|
||||
inner text
|
||||
}}
|
||||
{innerDiv}
|
||||
}}
|
||||
{outerDiv}`;
|
||||
const rendered = Markdown.render(source).trimReturns();
|
||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<div class="block outerDiv"><p>outer text</p><div class="block innerDiv"><p>inner text</p></div></div>');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// TODO: add tests for ID with accordance to CSS spec:
|
||||
//
|
||||
// From https://drafts.csswg.org/selectors/#id-selectors:
|
||||
//
|
||||
// > An ID selector consists of a “number sign” (U+0023, #) immediately followed by the ID value, which must be a CSS identifier.
|
||||
//
|
||||
// From: https://www.w3.org/TR/CSS21/syndata.html#value-def-identifier:
|
||||
//
|
||||
// > In CSS, identifiers (including element names, classes, and IDs in selectors) can contain only the characters [a-zA-Z0-9]
|
||||
// > and ISO 10646 characters U+00A0 and higher, plus the hyphen (-) and the underscore (_);
|
||||
// > they cannot start with a digit, two hyphens, or a hyphen followed by a digit.
|
||||
// > Identifiers can also contain escaped characters and any ISO 10646 character as a numeric code (see next item).
|
||||
// > For instance, the identifier "B&W?" may be written as "B\&W\?" or "B\26 W\3F".
|
||||
// > Note that Unicode is code-by-code equivalent to ISO 10646 (see [UNICODE] and [ISO10646]).
|
||||
|
||||
// TODO: add tests for class with accordance to CSS spec:
|
||||
//
|
||||
// From: https://drafts.csswg.org/selectors/#class-html:
|
||||
//
|
||||
// > The class selector is given as a full stop (. U+002E) immediately followed by an identifier.
|
||||
@@ -1,3 +1,5 @@
|
||||
@import (less) './themes/assets/assets.less';
|
||||
|
||||
:root {
|
||||
//Colors
|
||||
--HB_Color_Accent : #EBCEC3; // Salmon
|
||||
@@ -17,3 +19,10 @@
|
||||
bottom : 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.page:has(.partCover) {
|
||||
|
||||
.partCover {
|
||||
background-image: @partCoverHeaderDMG;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
const MagicGen = require('./snippets/magic.gen.js');
|
||||
const ClassTableGen = require('./snippets/classtable.gen.js');
|
||||
const MonsterBlockGen = require('./snippets/monsterblock.gen.js');
|
||||
const scriptGen = require('./snippets/script.gen.js');
|
||||
const scriptGen = require('./snippets/script.gen.js');
|
||||
const ClassFeatureGen = require('./snippets/classfeature.gen.js');
|
||||
const CoverPageGen = require('./snippets/coverpage.gen.js');
|
||||
const TableOfContentsGen = require('./snippets/tableOfContents.gen.js');
|
||||
@@ -171,9 +171,27 @@ module.exports = [
|
||||
gen : MonsterBlockGen.monster('monster,frame,wide', 4),
|
||||
},
|
||||
{
|
||||
name : 'Cover Page',
|
||||
name : 'Front Cover Page',
|
||||
icon : 'fac book-front-cover',
|
||||
gen : CoverPageGen,
|
||||
gen : CoverPageGen.front,
|
||||
experimental : true
|
||||
},
|
||||
{
|
||||
name : 'Inside Cover Page',
|
||||
icon : 'fac book-inside-cover',
|
||||
gen : CoverPageGen.inside,
|
||||
experimental : true
|
||||
},
|
||||
{
|
||||
name : 'Part Cover Page',
|
||||
icon : 'fac book-part-cover',
|
||||
gen : CoverPageGen.part,
|
||||
experimental : true
|
||||
},
|
||||
{
|
||||
name : 'Back Cover Page',
|
||||
icon : 'fac book-back-cover',
|
||||
gen : CoverPageGen.back,
|
||||
experimental : true
|
||||
},
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@ const titles = [
|
||||
'The Living Dead Above the Fearful Cage', 'Bahamut\'s Demonspawn',
|
||||
'Across Gruumsh\'s Elemental Chaos', 'The Blade of Orcus',
|
||||
'Beyond Revenge', 'Brain of Insanity',
|
||||
'Breed Battle!, A New Beginning', 'Evil Lake, A New Beginning',
|
||||
'A New Beginning', 'Evil Lake of the Merfolk',
|
||||
'Invasion of the Gigantic Cat, Part II', 'Kraken War 2020',
|
||||
'The Body Whisperers', 'The Doctor from Heaven',
|
||||
'The Diabolical Tales of the Ape-Women', 'The Doctor Immortal',
|
||||
@@ -23,7 +23,7 @@ const titles = [
|
||||
'Sky of Zelda: The Thunder of Force', 'Core Battle',
|
||||
'Ruby of Atlantis: The Quake of Peace', 'Deadly Amazement III',
|
||||
'Dry Chaos IX', 'Gate Thunder',
|
||||
'Vyse\'s Skies', 'White Greatness III',
|
||||
'Vyse\'s Skies', 'Blue Greatness III',
|
||||
'Yellow Divinity', 'Zidane\'s Ghost'
|
||||
];
|
||||
|
||||
@@ -68,23 +68,89 @@ const footnote = [
|
||||
'In an amazing kingdom, in an age of sorcery and lost souls, eight space pirates quest for freedom.'
|
||||
];
|
||||
|
||||
module.exports = ()=>{
|
||||
return dedent`
|
||||
{{coverPage }}
|
||||
const coverText = [
|
||||
'Embark on a thrilling journey across a vast and varied world, where magic and mystery await you at every turn. Encounter strange creatures and ancient secrets, and forge your own destiny with your choices. The world is yours to shape and explore.',
|
||||
'Join a band of brave adventurers and set out to explore the unknown lands beyond the horizon. Along the way, you’ll face perilous challenges, make new friends and enemies, and uncover a plot that threatens to destroy everything you hold dear. The fate of the world rests in your hands.',
|
||||
'Create your own character and enter a realm of endless possibilities, where you can be whoever you want to be. Whether you prefer to fight, sneak, charm, or craft your way through the game, you’ll find a style that suits you. The only limit is your imagination.',
|
||||
'Experience a rich and immersive story that adapts to your actions and decisions. Every choice you make has consequences, for good or ill. Will you be a hero or a villain? A leader or a follower? A friend or a foe? The choice is yours.',
|
||||
'Dive into a world of epic fantasy and adventure, where you can explore ancient civilizations, dark dungeons, and hidden secrets. Along the way, you’ll meet colorful characters, collect powerful items, and learn new skills. The more you play, the more you’ll discover.',
|
||||
'Explore a vast and dynamic world that changes according to your actions. You can shape the environment, influence the politics, and alter the history of the game world. But be careful, as every change has a ripple effect that may have unforeseen consequences.',
|
||||
'Enter a world of wonder and danger, where you can find allies and enemies among the various races and factions that inhabit it. You can choose to join or oppose any of them, or forge your own path. The game world is alive and responsive to your actions.'
|
||||
];
|
||||
|
||||
{{logo }}
|
||||
module.exports = {
|
||||
|
||||
# ${_.sample(titles)}
|
||||
## ${_.sample(subtitles)}
|
||||
__________
|
||||
front : function() {
|
||||
return dedent`
|
||||
{{frontCover}}
|
||||
|
||||
{{banner HOMEBREW}}
|
||||
{{logo }}
|
||||
|
||||
{{footnote
|
||||
${_.sample(footnote)}
|
||||
}}
|
||||
# ${_.sample(titles)}
|
||||
## ${_.sample(subtitles)}
|
||||
___
|
||||
|
||||

|
||||
{{banner HOMEBREW}}
|
||||
|
||||
\page`;
|
||||
{{footnote
|
||||
${_.sample(footnote)}
|
||||
}}
|
||||
|
||||
{position:absolute,bottom:0,left:0,height:100%}
|
||||
|
||||
\page`;
|
||||
},
|
||||
|
||||
inside : function() {
|
||||
return dedent`
|
||||
{{insideCover}}
|
||||
|
||||
# ${_.sample(titles)}
|
||||
## ${_.sample(subtitles)}
|
||||
___
|
||||
|
||||
{{imageMaskCenter${_.random(1, 16)},--offsetX:0%,--offsetY:0%,--rotation:0
|
||||
{position:absolute,bottom:0,left:0,height:100%}
|
||||
}}
|
||||
|
||||
{{logo }}
|
||||
|
||||
\page`;
|
||||
},
|
||||
|
||||
part : function() {
|
||||
return dedent`
|
||||
{{partCover}}
|
||||
|
||||
# PART X
|
||||
## ${_.sample(subtitles)}
|
||||
|
||||
{{imageMaskEdge${_.random(1, 8)},--offset:10cm,--rotation:180
|
||||
{position:absolute,bottom:0,left:0,height:100%}
|
||||
}}
|
||||
|
||||
\page`;
|
||||
},
|
||||
|
||||
back : function() {
|
||||
return dedent`
|
||||
{{backCover}}
|
||||
|
||||
# ${_.sample(subtitles)}
|
||||
|
||||
${_.sampleSize(coverText, 3).join('\n:\n')}
|
||||
___
|
||||
|
||||
For use with any fantasy roleplaying ruleset. Play the best game of your life!
|
||||
|
||||
{position:absolute,bottom:0,left:0,height:100%}
|
||||
|
||||
{{logo
|
||||

|
||||
|
||||
Homebrewery.Naturalcrit.com
|
||||
}}
|
||||
|
||||
\page`;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -678,33 +678,14 @@ h5 + table{
|
||||
}
|
||||
}
|
||||
//*****************************
|
||||
// * COVER PAGE
|
||||
// * FRONT COVER PAGE
|
||||
// *****************************/
|
||||
.page:has(.coverPage) {
|
||||
.page:has(.frontCover) {
|
||||
columns : 1;
|
||||
text-align : center;
|
||||
&:after {
|
||||
all: unset;
|
||||
}
|
||||
.logo {
|
||||
position : absolute;
|
||||
top : 0.5cm;
|
||||
left : 0;
|
||||
right : 0;
|
||||
filter :drop-shadow(0 0 0.075cm black);
|
||||
img {
|
||||
height : 2cm;
|
||||
width : 100%;
|
||||
}
|
||||
}
|
||||
.columnWrapper > p img {
|
||||
position : absolute;
|
||||
bottom : 0;
|
||||
left : 0;
|
||||
height : 100%;
|
||||
min-width : 100%;
|
||||
z-index : -1;
|
||||
}
|
||||
h1 {
|
||||
text-shadow: unset;
|
||||
filter : drop-shadow(0 0 1.5px black) drop-shadow(0 0 0 black)
|
||||
@@ -713,7 +694,6 @@ h5 + table{
|
||||
drop-shadow(0 0 0 black) drop-shadow(0 0 0 black);
|
||||
text-transform : uppercase;
|
||||
font-weight : normal;
|
||||
display : block;
|
||||
margin-top : 1.2cm;
|
||||
margin-bottom : 0;
|
||||
color : white;
|
||||
@@ -781,8 +761,193 @@ h5 + table{
|
||||
width : 70%;
|
||||
font-family : Overpass;
|
||||
}
|
||||
.logo {
|
||||
position : absolute;
|
||||
top : 0.5cm;
|
||||
left : 0;
|
||||
right : 0;
|
||||
filter : drop-shadow(0 0 0.075cm black);
|
||||
img {
|
||||
height : 2cm;
|
||||
width : 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
//*****************************
|
||||
// * INSIDE COVER PAGE
|
||||
// *****************************/
|
||||
.page:has(.insideCover) {
|
||||
columns : 1;
|
||||
text-align : center;
|
||||
&:after {
|
||||
all : unset;
|
||||
}
|
||||
h1 {
|
||||
font-family : NodestoCapsCondensed;
|
||||
font-weight : normal;
|
||||
font-size : 2.1cm;
|
||||
margin-top : 1.2cm;
|
||||
margin-bottom : 0;
|
||||
text-transform : uppercase;
|
||||
line-height : 0.85em;
|
||||
}
|
||||
h2 {
|
||||
font-family : NodestoCapsCondensed;
|
||||
font-weight : normal;
|
||||
font-size : 0.85cm;
|
||||
letter-spacing : 0.5cm;
|
||||
}
|
||||
hr {
|
||||
display : block;
|
||||
position : relative;
|
||||
background-image : @horizontalRule;
|
||||
background-size : 100% 100%;
|
||||
visibility : visible;
|
||||
height : 0.5cm;
|
||||
width : 12cm;
|
||||
border : none;
|
||||
margin : auto;
|
||||
}
|
||||
.logo {
|
||||
position : absolute;
|
||||
bottom : 1cm;
|
||||
left : 0;
|
||||
right : 0;
|
||||
height : 2cm;
|
||||
img {
|
||||
height : 2cm;
|
||||
width : 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
//*****************************
|
||||
// * BACK COVER
|
||||
// *****************************/
|
||||
.page:has(.backCover) {
|
||||
color: #fff;
|
||||
columns: 1;
|
||||
padding: 2.25cm 1.3cm 2cm 1.3cm;
|
||||
&:after {
|
||||
all: unset;
|
||||
}
|
||||
.columnWrapper {
|
||||
width: 7.6cm;
|
||||
}
|
||||
.backCover {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 11cm;
|
||||
background-image: @backCover;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
z-index: -1;
|
||||
}
|
||||
.blank {
|
||||
height: 1.4em;
|
||||
}
|
||||
h1 {
|
||||
margin-bottom: .3cm;
|
||||
font-size: 1.35cm;
|
||||
line-height: 0.95em;
|
||||
font-family: NodestoCapsCondensed;
|
||||
text-align: center;
|
||||
color: #ED1C24;
|
||||
}
|
||||
h1+p::first-line,
|
||||
h1+p::first-letter {
|
||||
all: unset;
|
||||
}
|
||||
img {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
height: 100%;
|
||||
z-index: -2;
|
||||
}
|
||||
hr {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 1.1cm;
|
||||
height: .53cm;
|
||||
width: 4.5cm;
|
||||
visibility: visible;
|
||||
background-image: @horizontalRule;
|
||||
background-size: 100% 100%;
|
||||
border: none;
|
||||
}
|
||||
p {
|
||||
font-family: Overpass;
|
||||
line-height: 1.5em;
|
||||
font-size: 0.332cm;
|
||||
}
|
||||
hr+p {
|
||||
text-align: center;
|
||||
margin-top: .6cm;
|
||||
}
|
||||
.logo {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
height: 1.5cm;
|
||||
left: 1.2cm;
|
||||
bottom: 2cm;
|
||||
width: 7.6cm;
|
||||
img {
|
||||
position: relative;
|
||||
height : 1.5cm;
|
||||
width : 100%;
|
||||
z-index : 0;
|
||||
}
|
||||
p {
|
||||
position: relative;
|
||||
color: #fff;
|
||||
font-family: NodestoCapsWide;
|
||||
font-size: .4cm;
|
||||
letter-spacing: 0.08em;
|
||||
line-height: 1em;
|
||||
text-align: center;
|
||||
text-indent: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//*****************************
|
||||
// * PART COVER
|
||||
// *****************************/
|
||||
.page:has(.partCover) {
|
||||
columns : 1;
|
||||
text-align : center;
|
||||
padding-top: 0;
|
||||
|
||||
.partCover {
|
||||
background-image: @partCoverHeaderPHB;
|
||||
background-repeat: no-repeat;
|
||||
position: absolute;
|
||||
background-size: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 6cm;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
font-size: 2.3cm;
|
||||
font-family: NodestoCapsCondensed;
|
||||
margin-top: .4cm;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: Overpass;
|
||||
font-size: 0.45cm;
|
||||
position: relative;
|
||||
margin-top: -0.7em;
|
||||
line-height: 1.1em;
|
||||
margin-left : auto;
|
||||
margin-right : auto;
|
||||
}
|
||||
}
|
||||
|
||||
//*****************************
|
||||
// * TABLE OF CONTENTS
|
||||
|
||||
@@ -2,9 +2,9 @@ const _ = require('lodash');
|
||||
const dedent = require('dedent-tabs').default;
|
||||
|
||||
module.exports = {
|
||||
center :()=>{
|
||||
center : ()=>{
|
||||
return dedent`
|
||||
{{imageMaskCenter${_.random(1, 16)},--offsetX:0%,--offsetY:0%,--rotation:0;
|
||||
{{imageMaskCenter${_.random(1, 16)},--offsetX:0%,--offsetY:0%,--rotation:0
|
||||
{height:100%}
|
||||
}}
|
||||
<!-- Use --offsetX to shift the mask left or right (can use cm instead of %)
|
||||
|
||||
@@ -23,6 +23,9 @@ body {
|
||||
break-inside : avoid;
|
||||
display : inline-block;
|
||||
width : 100%;
|
||||
img {
|
||||
z-index : 0;
|
||||
}
|
||||
}
|
||||
.inline-block {
|
||||
display : inline-block;
|
||||
@@ -251,7 +254,6 @@ body {
|
||||
background-size : 20px;
|
||||
z-index : -1;
|
||||
transform : translateY(50%) translateX(-50%) rotate(calc(1deg * var(--rotation))) scaleX(var(--scaleX)) scaleY(var(--scaleY));
|
||||
transition : transform 2s;
|
||||
& > p:has(img) {
|
||||
position : absolute;
|
||||
width : 50%;
|
||||
@@ -259,7 +261,6 @@ body {
|
||||
bottom : 50%;
|
||||
left : 50%;
|
||||
transform : translateX(-50%) translateY(50%) rotate(calc(-1deg * var(--rotation))) scaleX(calc(1 / var(--scaleX))) scaleY(calc(1 / var(--scaleY)));
|
||||
transition : transform 2s;
|
||||
}
|
||||
& img {
|
||||
position : absolute;
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
@naturalCritLogo : url('/assets/naturalCritLogo.svg');
|
||||
@coverPageBanner : url('/assets/coverPageBanner.svg');
|
||||
@horizontalRule : url('/assets/horizontalRule.svg');
|
||||
@partCoverHeaderPHB : url('/assets/partCoverHeaderPHB.png');
|
||||
@partCoverHeaderDMG : url('/assets/partCoverHeaderDMG.svg');
|
||||
@insideCoverMask : url('/assets/insideCoverMask.png');
|
||||
@backCover : url('/assets/backCover.png');
|
||||
@scriptBorder : url('/assets/scriptBorder.png');
|
||||
|
||||
// Watercolor Images
|
||||
|
||||
BIN
themes/assets/backCover.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
themes/assets/insideCoverMask.png
Normal file
|
After Width: | Height: | Size: 262 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
50
themes/assets/naturalCritLogoWhite.svg
Normal file
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 94.65 94.6"
|
||||
version="1.1"
|
||||
id="svg11"
|
||||
sodipodi:docname="naturalCritLogoWhite.svg"
|
||||
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview13"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#111111"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="8.4989431"
|
||||
inkscape:cx="38.887188"
|
||||
inkscape:cy="47.35883"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg11" />
|
||||
<defs
|
||||
id="defs4">
|
||||
<style
|
||||
id="style2">.cls-1{fill:#ed1f24;}</style>
|
||||
</defs>
|
||||
<title
|
||||
id="title6">NaturalCritLogo</title>
|
||||
<g
|
||||
id="Layer_2"
|
||||
data-name="Layer 2">
|
||||
<g
|
||||
id="base">
|
||||
<path
|
||||
id="D20"
|
||||
class="cls-1"
|
||||
d="M63.45.09s-45.91,12.4-46,12.45a.71.71,0,0,0-.15.08l-.15.1-.12.11a1.07,1.07,0,0,0-.14.16l-.09.11-.12.23,0,.06L.2,54.9a1.59,1.59,0,0,0,.11,1.69L29.36,94h0l0,0,.08.08.08.08.09.09.08.06.13.07a0,0,0,0,0,0,0,1.59,1.59,0,0,0,.27.12l.13.05.06,0a1.55,1.55,0,0,0,.37,0,1.63,1.63,0,0,0,.31,0l45.67-8.3.16,0,.11,0,.12,0,.06,0s0,0,0,0l.06,0a1.65,1.65,0,0,0,.36-.28l0-.06a1.6,1.6,0,0,0,.26-.38s0,0,0,0v0h0a.14.14,0,0,1,0-.06L94.52,43.74a1.4,1.4,0,0,0,.11-.4.41.41,0,0,0,0-.11,1.13,1.13,0,0,0,0-.26.66.66,0,0,0,0-.14,2,2,0,0,0-.06-.26l0-.11a2.68,2.68,0,0,0-.18-.33v0L65.29.6C64.77-.31,63.45.09,63.45.09ZM74.9,81.7l-28.81-18L78.5,38.49ZM44.1,61l-11-40.17L77,35.39ZM82,37.78l8.92,5.95L79,73.48Zm4.46-1.1-4.6-3.06L75.69,21.36Zm-9.26-4.8-42.07-14,28.05-14ZM30.56,16.34l-6.49-2.16L47.85,7.7Zm-11.35-.21L27.88,19,7.64,45Zm10.73,5.76L40.78,61.64,4.64,54.42Zm10.82,43.2L30.26,89.6,5.75,58.09Zm3.16,1.24L71.74,83.72l-38.26,7Z"
|
||||
style="fill:#ffffff;fill-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
1
themes/assets/partCoverHeaderDMG.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 611.99 154.72"><defs><style>.cls-1{fill:#f4f0f1;}.cls-2{fill:#d0d4d0;stroke:#888;stroke-linecap:round;stroke-miterlimit:35;stroke-width:0.5px;}</style></defs><title>Asset 2</title><g id="Layer_2" data-name="Layer 2"><g id="svg"><polygon id="background" class="cls-1" points="221.58 0 221.58 86.65 305.82 136.18 389.8 86.91 389.8 0 221.58 0"/><path id="right_triangle_compound_path" data-name="right triangle compound path" class="cls-2" d="M526.31.25c0,14.18-.08,26.67-.12,40.85,0,4.47-2.72,11-11.77,11-3.25.16-13.9-1.11-23.74-6.57L410.79.25h-2.53L527.82,69.38V.25Zm-.06,66.27L498.44,50.67c1,.39,14.21,4,19.29,2.81a13.9,13.9,0,0,0,8.52-6Z"/><path id="main_shape" data-name="main shape" class="cls-2" d="M598.88,123.48l-.13-65.27a81.69,81.69,0,0,1-24,20.31,79.73,79.73,0,0,1-26.8,9L544.82,86a78.57,78.57,0,0,0,42.52-18.09c15.33-13,22.12-30.25,24.4-37.06v-7a80.45,80.45,0,0,1-15.39,31.7c.63-1,16.49-26.4-2.42-55.2h-2.37c3.37,5,12.23,19.65,9.21,37.64-4,24-27.74,43.84-58.54,46.25L471.67,43.29a3.24,3.24,0,0,0-1.37-3,3.2,3.2,0,0,0-3.55,0L447.21,29a7,7,0,0,0-2.25-2.7,7,7,0,0,0-4.73-1.19l-16.59-9.57C414.47,10.29,404,4.25,397.06.25h-3.32L436.1,25.46s-36.92.06-40.94.11c-2.79-1.71-2.7,3.24,0,1.62h41.71l-46.4.37L390.3.26h-1.57L388.79,86l-41.1,24.5c-6.35,3.73-24.5,13.91-42.86,14.1s-36.47-11.3-44-15.49C248,101.8,235.15,93.85,222.33,86.52L222.46.25h-2V28l-46.09-.37h41.7c2.71,1.62,2.79-3.33,0-1.62-4-.05-40.93-.11-40.93-.11L218.32.25H215c-7,4-18.25,10.52-27.42,15.8L171,25.62a7,7,0,0,0-4.73,1.19,7,7,0,0,0-2.25,2.7L144.46,40.72a3.19,3.19,0,0,0-3.54.05,3.24,3.24,0,0,0-1.37,3L69,84.62c-30.8-2.41-54.53-22.3-58.54-46.25-3-18,5.84-32.62,9.2-37.64H17.29c-18.91,28.8-3,54.19-2.42,55.2C2.75,41,.27,26.28.27,26.28l0,7.08a78.31,78.31,0,0,0,23.63,35A78.57,78.57,0,0,0,66.4,86.43L63.22,88a79.66,79.66,0,0,1-26.8-9,81.76,81.76,0,0,1-24-20.31L12.34,124C12.46,141,.25,147.51.25,147.51v1.72a27.5,27.5,0,0,0,8.43-8A29,29,0,0,0,13,131.51v10.41L.25,152v2.1L26,133.61v-22l20.48-12c62.61,1.71,93.58-40,99.08-46.72,21.86-20.39,70.66-22.25,74.93-22.37V87.7l85.25,49.11,85.12-49.4-.33-57.36s52.35,1.05,75.23,22.38c5.49,6.67,36.47,48.43,99.08,46.72l20.48,12v22l26.47,21.07v-2.1l-13.55-10.66V131a29,29,0,0,0,4.35,9.72,32.12,32.12,0,0,0,9.2,8.6v-1.72A27.69,27.69,0,0,1,598.88,123.48ZM139.64,46.06c-.25.47-8.48,15.47-26.11,27.94-17.91,12.67-40,10.81-40,10.81ZM24.06,110.34l.1,22.47-7.09,6.1V93.77a99.79,99.79,0,0,0,12.3,3.52,99.74,99.74,0,0,0,13.8,2ZM48.19,97A102.43,102.43,0,0,1,30,95.38a103,103,0,0,1-15-3.87l-.09,48.35L13.76,141q.22-39,.47-77.9A83.48,83.48,0,0,0,36.08,80.34a82.84,82.84,0,0,0,25.15,8.85Zm30.54-3a121.63,121.63,0,0,1-26.57,3l12-7.14a96,96,0,0,0,15.51.29A97,97,0,0,0,102.19,86,122.56,122.56,0,0,1,78.73,94ZM140,55.86C135.3,61.42,126.49,71.79,114.11,79,96,89.52,77.09,89.38,66.63,88.34l2.65-1.62A77.74,77.74,0,0,0,112,77.14c17.82-9.9,26.42-24.69,29.83-30.8.2,0,2.07.45,3.05-.86a3.75,3.75,0,0,0,.64-2.86l6.71-3.76A128.27,128.27,0,0,1,140,55.86ZM164.8,39.57a119.38,119.38,0,0,0-15.51,8.29l7-11.33,11-6.29,36.42-.38C190.52,32.15,179.34,33.15,164.8,39.57Zm140.83,95.27-23.08-13.27c3.87,1.5,15.25,4.45,22.63,4.45s18.72-2.93,23.34-4.45Zm232.1-50.51s-22.14,1.87-40-10.81c-17.63-12.47-25.85-27.47-26.1-27.94ZM446.43,39.1c-14.55-6.43-25.73-7.43-38.88-9.72l36.41.38,11,6.29,7,11.33A118,118,0,0,0,446.43,39.1ZM471.2,55.38a128.27,128.27,0,0,1-12.2-17l6.71,3.76a3.73,3.73,0,0,0,.65,2.86c1,1.31,2.84.85,3.05.86,3.4,6.11,12,20.9,29.83,30.8A77.73,77.73,0,0,0,542,86.24l2.64,1.62c-10.47,1-29.4,1.18-47.48-9.34C484.73,71.32,475.92,60.94,471.2,55.38Zm61.29,38.1A123,123,0,0,1,509,85.54a96.54,96.54,0,0,0,22.51,4.13,96,96,0,0,0,15.51-.29l12,7.14A121.63,121.63,0,0,1,532.49,93.48Zm61.67,45-7.1-6.1.09-22.47-19.1-11a99.66,99.66,0,0,0,13.81-2,100.3,100.3,0,0,0,12.3-3.52Zm2.17.95L596.23,91a102.68,102.68,0,0,1-15,3.87A102.43,102.43,0,0,1,563,96.52l-13-7.81a82.61,82.61,0,0,0,25.16-8.85A83.14,83.14,0,0,0,597,62.62q.22,39,.47,77.9Z"/><path id="left_triangle_compound_path" data-name="left triangle compound path" class="cls-2" d="M83.4.73V69.86L203.77.25h-2.53l-80.7,45.8c-9.84,5.46-20.49,6.73-23.74,6.57-9,0-11.77-6.57-11.77-11C85,27.4,85,14.91,84.91.73ZM85,48a13.87,13.87,0,0,0,8.51,6c5.08,1.18,18.26-2.42,19.29-2.81L85,67Z"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
BIN
themes/assets/partCoverHeaderPHB.png
Normal file
|
After Width: | Height: | Size: 164 KiB |
BIN
themes/fonts/5e/Martel Sans Black.woff2
Normal file
BIN
themes/fonts/5e/Nodesto Caps Wide.woff2
Normal file
@@ -107,6 +107,13 @@
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: NodestoCapsWide;
|
||||
src: url('../../../fonts/5e/Nodesto Caps Wide.woff2');
|
||||
font-weight: normal;
|
||||
font-style: normal
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Overpass;
|
||||
src: url('../../../fonts/5e/Overpass Medium.woff2');
|
||||
|
||||