mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2025-12-26 18:12:40 +00:00
Merge branch 'master' into experimentalNotificationDB
This commit is contained in:
@@ -48,7 +48,7 @@ jobs:
|
||||
- image: cimg/node:16.11.0
|
||||
|
||||
working_directory: ~/homebrewery
|
||||
parallelism: 4
|
||||
parallelism: 1
|
||||
|
||||
steps:
|
||||
- attach_workspace:
|
||||
@@ -61,15 +61,15 @@ jobs:
|
||||
- run:
|
||||
name: Test - Basic
|
||||
command: npm run test:basic
|
||||
- run:
|
||||
name: Test - Coverage
|
||||
command: npm run test:coverage
|
||||
- run:
|
||||
name: Test - Mustache Spans
|
||||
command: npm run test:mustache-span
|
||||
- run:
|
||||
name: Test - Routes
|
||||
command: npm run test:route
|
||||
- run:
|
||||
name: Test - Coverage
|
||||
command: npm run test:coverage
|
||||
|
||||
workflows:
|
||||
build_and_test:
|
||||
|
||||
23
changelog.md
23
changelog.md
@@ -80,7 +80,28 @@ pre {
|
||||
## changelog
|
||||
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
|
||||
|
||||
### Thursday 09/02/2023 - v3.7.1
|
||||
### XXXXday DD/MM/2023 - v3.8.0
|
||||
{{taskList
|
||||
##### 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
|
||||
|
||||
Fixes issues [#2680](https://github.com/naturalcrit/homebrewery/issues/2680)
|
||||
}}
|
||||
|
||||
### Monday 13/03/2023 - v3.7.2
|
||||
{{taskList
|
||||
|
||||
##### Calculuschild
|
||||
|
||||
* [x] Fix wide Monster Stat Blocks not spanning columns on Legacy
|
||||
}}
|
||||
|
||||
### Thursday 09/03/2023 - v3.7.1
|
||||
{{taskList
|
||||
|
||||
##### Lucastucious (new contributor!)
|
||||
|
||||
129
client/components/combobox.jsx
Normal file
129
client/components/combobox.jsx
Normal file
@@ -0,0 +1,129 @@
|
||||
const React = require('react');
|
||||
const createClass = require('create-react-class');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
require('./combobox.less');
|
||||
|
||||
const Combobox = createClass({
|
||||
displayName : 'Combobox',
|
||||
getDefaultProps : function() {
|
||||
return {
|
||||
className : '',
|
||||
trigger : 'hover',
|
||||
default : '',
|
||||
placeholder : '',
|
||||
autoSuggest : {
|
||||
clearAutoSuggestOnClick : true,
|
||||
suggestMethod : 'includes',
|
||||
filterOn : [] // should allow as array to filter on multiple attributes, or even custom filter
|
||||
},
|
||||
};
|
||||
},
|
||||
getInitialState : function() {
|
||||
return {
|
||||
showDropdown : false,
|
||||
value : '',
|
||||
options : [...this.props.options],
|
||||
inputFocused : false
|
||||
};
|
||||
},
|
||||
componentDidMount : function() {
|
||||
if(this.props.trigger == 'click')
|
||||
document.addEventListener('click', this.handleClickOutside);
|
||||
this.setState({
|
||||
value : this.props.default
|
||||
});
|
||||
},
|
||||
componentWillUnmount : function() {
|
||||
if(this.props.trigger == 'click')
|
||||
document.removeEventListener('click', this.handleClickOutside);
|
||||
},
|
||||
handleClickOutside : function(e){
|
||||
// Close dropdown when clicked outside
|
||||
if(this.refs.dropdown && !this.refs.dropdown.contains(e.target)) {
|
||||
this.handleDropdown(false);
|
||||
}
|
||||
},
|
||||
handleDropdown : function(show){
|
||||
this.setState({
|
||||
showDropdown : show,
|
||||
inputFocused : this.props.autoSuggest.clearAutoSuggestOnClick ? show : false
|
||||
});
|
||||
},
|
||||
handleInput : function(e){
|
||||
e.persist();
|
||||
this.setState({
|
||||
value : e.target.value,
|
||||
inputFocused : false
|
||||
}, ()=>{
|
||||
this.props.onEntry(e);
|
||||
});
|
||||
},
|
||||
handleSelect : function(e){
|
||||
this.setState({
|
||||
value : e.currentTarget.getAttribute('data-value')
|
||||
}, ()=>{this.props.onSelect(this.state.value);});
|
||||
;
|
||||
},
|
||||
renderTextInput : function(){
|
||||
return (
|
||||
<div className='dropdown-input item'
|
||||
onMouseEnter={this.props.trigger == 'hover' ? ()=>{this.handleDropdown(true);} : undefined}
|
||||
onClick= {this.props.trigger == 'click' ? ()=>{this.handleDropdown(true);} : undefined}>
|
||||
<input
|
||||
type='text'
|
||||
onChange={(e)=>this.handleInput(e)}
|
||||
value={this.state.value || ''}
|
||||
placeholder={this.props.placeholder}
|
||||
onBlur={(e)=>{
|
||||
if(!e.target.checkValidity()){
|
||||
this.setState({
|
||||
value : this.props.default
|
||||
}, ()=>this.props.onEntry(e));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
renderDropdown : function(dropdownChildren){
|
||||
if(!this.state.showDropdown) return null;
|
||||
if(this.props.autoSuggest && !this.state.inputFocused){
|
||||
const suggestMethod = this.props.autoSuggest.suggestMethod;
|
||||
const filterOn = _.isString(this.props.autoSuggest.filterOn) ? [this.props.autoSuggest.filterOn] : this.props.autoSuggest.filterOn;
|
||||
const filteredArrays = filterOn.map((attr)=>{
|
||||
const children = dropdownChildren.filter((item)=>{
|
||||
if(suggestMethod === 'includes'){
|
||||
return item.props[attr]?.toLowerCase().includes(this.state.value.toLowerCase());
|
||||
} else if(suggestMethod === 'startsWith'){
|
||||
return item.props[attr]?.toLowerCase().startsWith(this.state.value.toLowerCase());
|
||||
}
|
||||
});
|
||||
return children;
|
||||
});
|
||||
dropdownChildren = _.uniq(filteredArrays.flat(1));
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='dropdown-options'>
|
||||
{dropdownChildren}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
render : function () {
|
||||
const dropdownChildren = this.state.options.map((child, i)=>{
|
||||
const clone = React.cloneElement(child, { onClick: (e)=>this.handleSelect(e) });
|
||||
return clone;
|
||||
});
|
||||
return (
|
||||
<div className={`dropdown-container ${this.props.className}`}
|
||||
ref='dropdown'
|
||||
onMouseLeave={this.props.trigger == 'hover' ? ()=>{this.handleDropdown(false);} : undefined}>
|
||||
{this.renderTextInput()}
|
||||
{this.renderDropdown(dropdownChildren)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Combobox;
|
||||
50
client/components/combobox.less
Normal file
50
client/components/combobox.less
Normal file
@@ -0,0 +1,50 @@
|
||||
.dropdown-container {
|
||||
position:relative;
|
||||
input {
|
||||
width: 100%;
|
||||
}
|
||||
.dropdown-options {
|
||||
position:absolute;
|
||||
background-color: white;
|
||||
z-index: 100;
|
||||
width: 100%;
|
||||
border: 1px solid gray;
|
||||
overflow-y: auto;
|
||||
max-height: 200px;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 14px;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #ffffff;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: #949494;
|
||||
border-radius: 10px;
|
||||
border: 3px solid #ffffff;
|
||||
}
|
||||
|
||||
.item {
|
||||
position:relative;
|
||||
font-size: 11px;
|
||||
font-family: Open Sans;
|
||||
padding: 5px;
|
||||
cursor: default;
|
||||
margin: 0 3px;
|
||||
//border-bottom: 1px solid darkgray;
|
||||
&:hover {
|
||||
filter: brightness(120%);
|
||||
background-color: rgb(163, 163, 163);
|
||||
}
|
||||
.detail {
|
||||
width:100%;
|
||||
text-align: left;
|
||||
color: rgb(124, 124, 124);
|
||||
font-style:italic;
|
||||
font-size: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,6 +27,7 @@ const BrewRenderer = createClass({
|
||||
style : '',
|
||||
renderer : 'legacy',
|
||||
theme : '5ePHB',
|
||||
lang : '',
|
||||
errors : []
|
||||
};
|
||||
},
|
||||
@@ -190,7 +191,6 @@ const BrewRenderer = createClass({
|
||||
const rendererPath = this.props.renderer == 'V3' ? 'V3' : 'Legacy';
|
||||
const themePath = this.props.theme ?? '5ePHB';
|
||||
const baseThemePath = Themes[rendererPath][themePath].baseTheme;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{!this.state.isMounted
|
||||
@@ -223,7 +223,7 @@ const BrewRenderer = createClass({
|
||||
&&
|
||||
<>
|
||||
{this.renderStyle()}
|
||||
<div className='pages' ref='pages'>
|
||||
<div className='pages' ref='pages' lang={`${this.props.lang || 'en'}`}>
|
||||
{this.renderPages()}
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -6,6 +6,7 @@ const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
const request = require('../../utils/request-middleware.js');
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
const Combobox = require('client/components/combobox.jsx');
|
||||
const StringArrayEditor = require('../stringArrayEditor/stringArrayEditor.jsx');
|
||||
|
||||
const Themes = require('themes/themes.json');
|
||||
@@ -35,7 +36,8 @@ const MetadataEditor = createClass({
|
||||
authors : [],
|
||||
systems : [],
|
||||
renderer : 'legacy',
|
||||
theme : '5ePHB'
|
||||
theme : '5ePHB',
|
||||
lang : 'en'
|
||||
},
|
||||
onChange : ()=>{},
|
||||
reportError : ()=>{}
|
||||
@@ -76,6 +78,7 @@ const MetadataEditor = createClass({
|
||||
const errMessage = validationErr.map((err)=>{
|
||||
return `- ${err}`;
|
||||
}).join('\n');
|
||||
|
||||
callIfExists(e.target, 'setCustomValidity', errMessage);
|
||||
callIfExists(e.target, 'reportValidity');
|
||||
}
|
||||
@@ -111,6 +114,11 @@ const MetadataEditor = createClass({
|
||||
this.props.onChange(this.props.metadata);
|
||||
},
|
||||
|
||||
handleLanguage : function(languageCode){
|
||||
this.props.metadata.lang = languageCode;
|
||||
this.props.onChange(this.props.metadata);
|
||||
},
|
||||
|
||||
handleDelete : function(){
|
||||
if(this.props.metadata.authors && this.props.metadata.authors.length <= 1){
|
||||
if(!confirm('Are you sure you want to delete this brew? Because you are the only owner of this brew, the document will be deleted permanently.')) return;
|
||||
@@ -224,6 +232,47 @@ const MetadataEditor = createClass({
|
||||
</div>;
|
||||
},
|
||||
|
||||
renderLanguageDropdown : function(){
|
||||
const langCodes = ['en', 'de', 'de-ch', 'fr', 'ja', 'es', 'it', 'sv', 'ru', 'zh-Hans', 'zh-Hant'];
|
||||
const listLanguages = ()=>{
|
||||
return _.map(langCodes.sort(), (code, index)=>{
|
||||
const localName = new Intl.DisplayNames([code], { type: 'language' });
|
||||
const englishName = new Intl.DisplayNames('en', { type: 'language' });
|
||||
return <div className='item' title={`${englishName.of(code)}`} key={`${index}`} data-value={`${code}`} data-detail={`${localName.of(code)}`}>
|
||||
{`${code}`}
|
||||
<div className='detail'>{`${localName.of(code)}`}</div>
|
||||
</div>;
|
||||
});
|
||||
};
|
||||
|
||||
const debouncedHandleFieldChange = _.debounce(this.handleFieldChange, 500);
|
||||
|
||||
return <div className='field language'>
|
||||
<label>language</label>
|
||||
<div className='value'>
|
||||
<Combobox trigger='click'
|
||||
className='language-dropdown'
|
||||
default={this.props.metadata.lang || ''}
|
||||
placeholder='en'
|
||||
onSelect={(value)=>this.handleLanguage(value)}
|
||||
onEntry={(e)=>{
|
||||
e.target.setCustomValidity(''); //Clear the validation popup while typing
|
||||
debouncedHandleFieldChange('lang', e);
|
||||
}}
|
||||
options={listLanguages()}
|
||||
autoSuggest={{
|
||||
suggestMethod : 'startsWith',
|
||||
clearAutoSuggestOnClick : true,
|
||||
filterOn : ['data-value', 'data-detail', 'title']
|
||||
}}
|
||||
>
|
||||
</Combobox>
|
||||
<small>Sets the HTML Lang property for your brew. May affect hyphenation or spellcheck.</small>
|
||||
</div>
|
||||
|
||||
</div>;
|
||||
},
|
||||
|
||||
renderRenderOptions : function(){
|
||||
if(!global.enable_v3) return;
|
||||
|
||||
@@ -301,6 +350,8 @@ const MetadataEditor = createClass({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{this.renderLanguageDropdown()}
|
||||
|
||||
{this.renderThemeDropdown()}
|
||||
|
||||
{this.renderRenderOptions()}
|
||||
@@ -315,7 +366,7 @@ const MetadataEditor = createClass({
|
||||
validators={[(v)=>!this.props.metadata.authors?.includes(v)]}
|
||||
placeholder='invite author' unique={true}
|
||||
values={this.props.metadata.invitedAuthors}
|
||||
notes={['Invited authors are case sensitive.', 'After adding an invited author, send them the edit link. There, they can choose to accept or decline the invitation.']}
|
||||
notes={['Invited author usernames are case sensitive.', 'After adding an invited author, send them the edit link. There, they can choose to accept or decline the invitation.']}
|
||||
onChange={(e)=>this.handleFieldChange('invitedAuthors', e)}/>
|
||||
|
||||
<hr/>
|
||||
|
||||
@@ -36,11 +36,15 @@
|
||||
flex: 5 0 200px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.field{
|
||||
display : flex;
|
||||
flex-wrap : wrap;
|
||||
width : 100%;
|
||||
min-width : 200px;
|
||||
position : relative;
|
||||
&>label{
|
||||
width : 80px;
|
||||
font-size : 11px;
|
||||
@@ -57,6 +61,9 @@
|
||||
}
|
||||
input[type='text'], textarea {
|
||||
border : 1px solid gray;
|
||||
&:focus {
|
||||
outline: 1px solid #444;
|
||||
}
|
||||
}
|
||||
&.thumbnail{
|
||||
height : 1.4em;
|
||||
@@ -88,9 +95,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.language .language-dropdown {
|
||||
max-width : 150px;
|
||||
z-index : 200;
|
||||
}
|
||||
small {
|
||||
font-size : 0.6em;
|
||||
font-style : italic;
|
||||
font-size : 0.6em;
|
||||
font-style : italic;
|
||||
line-height : 1.4em;
|
||||
display : inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +172,7 @@
|
||||
.navDropdownContainer {
|
||||
background-color : white;
|
||||
position : relative;
|
||||
z-index : 500;
|
||||
z-index : 100;
|
||||
&.disabled {
|
||||
font-style :italic;
|
||||
font-style : italic;
|
||||
|
||||
@@ -23,9 +23,9 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
],
|
||||
language : [
|
||||
lang : [
|
||||
(value)=>{
|
||||
return new RegExp(/[a-z]{2,3}(-.*)?/).test(value || '') === false ? 'Invalid language code.' : null;
|
||||
return new RegExp(/^([a-zA-Z]{2,3})(-[a-zA-Z]{4})?(-(?:[0-9]{3}|[a-zA-Z]{2}))?$/).test(value) === false && (value.length > 0) ? 'Invalid language code.' : null;
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
padding : 0px;
|
||||
background-color : #ddd;
|
||||
.snippet{
|
||||
position: relative;
|
||||
.animate(background-color);
|
||||
display : flex;
|
||||
align-items : center;
|
||||
|
||||
@@ -47,6 +47,7 @@ const Homebrew = createClass({
|
||||
editId : null,
|
||||
createdAt : null,
|
||||
updatedAt : null,
|
||||
lang : ''
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
@@ -63,7 +63,7 @@ const Account = createClass({
|
||||
if(global.account){
|
||||
return <Nav.dropdown>
|
||||
<Nav.item
|
||||
className='account'
|
||||
className='account username'
|
||||
color='orange'
|
||||
icon='fas fa-user'
|
||||
>
|
||||
|
||||
@@ -187,4 +187,7 @@
|
||||
.account.navItem{
|
||||
min-width: 100px;
|
||||
}
|
||||
.account.username.navItem{
|
||||
text-transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,7 +398,14 @@ const EditPage = createClass({
|
||||
reportError={this.errorReported}
|
||||
renderer={this.state.brew.renderer}
|
||||
/>
|
||||
<BrewRenderer text={this.state.brew.text} style={this.state.brew.style} renderer={this.state.brew.renderer} theme={this.state.brew.theme} errors={this.state.htmlErrors} />
|
||||
<BrewRenderer
|
||||
text={this.state.brew.text}
|
||||
style={this.state.brew.style}
|
||||
renderer={this.state.brew.renderer}
|
||||
theme={this.state.brew.theme}
|
||||
errors={this.state.htmlErrors}
|
||||
lang={this.state.brew.lang}
|
||||
/>
|
||||
</SplitPane>
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
@@ -61,6 +61,7 @@ const NewPage = createClass({
|
||||
// brew.description = metaStorage?.description || this.state.brew.description;
|
||||
brew.renderer = metaStorage?.renderer ?? brew.renderer;
|
||||
brew.theme = metaStorage?.theme ?? brew.theme;
|
||||
brew.lang = metaStorage?.lang ?? brew.lang;
|
||||
|
||||
this.setState({
|
||||
brew : brew
|
||||
@@ -70,7 +71,7 @@ const NewPage = createClass({
|
||||
localStorage.setItem(BREWKEY, brew.text);
|
||||
if(brew.style)
|
||||
localStorage.setItem(STYLEKEY, brew.style);
|
||||
localStorage.setItem(METAKEY, JSON.stringify({ 'renderer': brew.renderer, 'theme': brew.theme }));
|
||||
localStorage.setItem(METAKEY, JSON.stringify({ 'renderer': brew.renderer, 'theme': brew.theme, 'lang': brew.lang }));
|
||||
},
|
||||
componentWillUnmount : function() {
|
||||
document.removeEventListener('keydown', this.handleControlKeys);
|
||||
@@ -114,13 +115,16 @@ const NewPage = createClass({
|
||||
handleMetaChange : function(metadata){
|
||||
this.setState((prevState)=>({
|
||||
brew : { ...prevState.brew, ...metadata },
|
||||
}));
|
||||
localStorage.setItem(METAKEY, JSON.stringify({
|
||||
// 'title' : this.state.brew.title,
|
||||
// 'description' : this.state.brew.description,
|
||||
'renderer' : this.state.brew.renderer,
|
||||
'theme' : this.state.brew.theme
|
||||
}));
|
||||
}), ()=>{
|
||||
localStorage.setItem(METAKEY, JSON.stringify({
|
||||
// 'title' : this.state.brew.title,
|
||||
// 'description' : this.state.brew.description,
|
||||
'renderer' : this.state.brew.renderer,
|
||||
'theme' : this.state.brew.theme,
|
||||
'lang' : this.state.brew.lang
|
||||
}));
|
||||
});
|
||||
;
|
||||
},
|
||||
|
||||
save : async function(){
|
||||
@@ -211,7 +215,7 @@ const NewPage = createClass({
|
||||
onMetaChange={this.handleMetaChange}
|
||||
renderer={this.state.brew.renderer}
|
||||
/>
|
||||
<BrewRenderer text={this.state.brew.text} style={this.state.brew.style} renderer={this.state.brew.renderer} theme={this.state.brew.theme} errors={this.state.htmlErrors}/>
|
||||
<BrewRenderer text={this.state.brew.text} style={this.state.brew.style} renderer={this.state.brew.renderer} theme={this.state.brew.theme} lang={this.state.brew.lang} errors={this.state.htmlErrors}/>
|
||||
</SplitPane>
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
1
client/icons/Davek.svg
Normal file
1
client/icons/Davek.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 791.04 953.29"><title>Davek</title><g id="Layer_2" data-name="Layer 2"><g id="Davek"><path d="M178.41,13.46a19.33,19.33,0,0,0-4.71,5.38q8.07,6.07,13.46,6.07a8.27,8.27,0,0,0,4.71-1.35,130.23,130.23,0,0,0,16.83-7.07,74.55,74.55,0,0,1,18.85-6.39h2.7q8.07,0,14.81,8.74a944.19,944.19,0,0,0,95.6,4.72q19.5,0,38.37-.67,69.33-2,139.68-5.72t139.7-5.06q16.82-.64,34.34-.66,50.49,0,98.29,3.36-17.5,12.12-22.55,31.64t-5,33.66q.64,22.89.66,45.1,0,47.13-3.36,97-6.07,74.05-9.78,148.11t-5,146.09v17.51a766.1,766.1,0,0,0,8.75,118.48,38.57,38.57,0,0,0-4,17.51,30.94,30.94,0,0,0,.67,6.06q2,12.12,3.36,23.22c.9,7.42,1.57,14.92,2,22.55v3.37a57.93,57.93,0,0,1-3.36,19.52c.43,4.5.67,8.77.67,12.8a260.65,260.65,0,0,1-2.7,37,344.26,344.26,0,0,0-4,52.52,133.5,133.5,0,0,0,8.09,45.44q8.07,22.57,33,36.68-6.06,8.78-20.19,8.77H762.1c-4.5-.45-8.53-.69-12.12-.69a78.11,78.11,0,0,0-21.54,2.7,579.1,579.1,0,0,0-63.64,3.71q-33.31,3.71-67.65,6.39t-68.66,3.37h-4a188.05,188.05,0,0,1-59.92-9.43q20.19-4,39.06-23.22t20.19-47.46q11.44-22.21,11.45-49.82a320.44,320.44,0,0,1,3.36-49.15q-9.45-4.69-10.09-8.75v-2.7a73,73,0,0,1,.66-8.74,105.81,105.81,0,0,0,3.37-12.8,7.49,7.49,0,0,0,.68-3.37q0-4.7-4.05-10.09c.45-4.93.69-10.1.69-15.48a311.71,311.71,0,0,0-3.37-46.45,207.31,207.31,0,0,1-1.35-24.25,274.58,274.58,0,0,1,4-45.1l15.5,6.73q-3.37-17.49-3.37-41.07,0-24.89,8.75-44.44a27.73,27.73,0,0,0,2-9.43,15.32,15.32,0,0,0-3.36-10.09,60.75,60.75,0,0,1-10.1-15.48l-7.39,6.73q2.67-47.79,8.74-99,3.35-33.63,3.37-65.29,0-14.81-.69-29a205.09,205.09,0,0,1-4-41.74,190.26,190.26,0,0,1,2-26.92q4-37,14.81-67.33a25.14,25.14,0,0,1-2.68-11.43,31.13,31.13,0,0,1,.66-6.07V140q0-6.72-8.74-10.09-3.37-16.83-5.73-31.3T521.07,77.41q-55.2,2.7-115.78,4.71-19.55.7-39.72.69-38.38,0-74.06-2.7c-5.4,4.5-8.08,9.21-8.08,14.14v1.34a41.5,41.5,0,0,0,4.37,15.49q3.7,7.4,7.4,15.16a35,35,0,0,1,3.71,15.13q32.31,34.35,64,68.68a335.89,335.89,0,0,1,51.83,73.38q13.46,7.4,18.51,17.49t10.11,19.87q5.06,9.78,10.1,18.85t16.5,11.78v12.12a194.5,194.5,0,0,1-37.38-4q-20.52-4-40.73-6.73a114.48,114.48,0,0,0-17.49-1.35,97.2,97.2,0,0,0-20.2,2q-17.52,4.05-31,20.19-16.84-1.35-27.27-9.75a76.13,76.13,0,0,1-17.51-20.2q-7.06-11.76-14.47-24.9a79.77,79.77,0,0,0-18.84-22.57A305.87,305.87,0,0,1,177.73,237q-28.29-33.67-54.54-69T68,99.31A381.16,381.16,0,0,0,0,38.37q12.79,0,22.89-9.75A190.69,190.69,0,0,1,44.76,10.44Q56.54,2,68.66,0H72Q82.8,0,97,10.76Z"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
1
client/icons/Iokharic.svg
Normal file
1
client/icons/Iokharic.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 428.05 941.17"><title>Iokharic</title><g id="Layer_2" data-name="Layer 2"><g id="Iokharic"><path d="M334.76,909.61V259.3l2.74-89.18c3.43,0,6.18-8.23,7.55-24.69,3.43,0,7.55-8.92,13.72-27.44,13-11,19.89-21.27,19.89-31.56,0-13-5.48-20.58-17.15-23.32l-30.87,2.74H320.36c-21.27,13-39.79,22.64-56.94,27.44h-37c-11.67,0-26.76,7.55-46,22q-12.34,0-30.86,16.46c-10.29,0-40.48,26.75-91.93,80.95,0,8.23-6.17,21.26-18.52,38.41l-3.43,15.78v41.84L67.23,343c2.74,0,9.6,6.86,19.89,19.9,24,18.52,36.36,30.86,36.36,38.41l-12.35,10.29H105c-24.7-15.78-45.28-32.93-62.43-52.13L15.78,316.92,0,266.85c3.43-17.84,7.55-29.5,13.72-35v-11c0-18.52,7.55-39.79,22-63.8,0-9.6,8.23-21.27,24.7-34.3,0-9.6,15.77-26.07,46.64-50.08,19.9-16.46,46-28.12,76.83-35,5.49-6.86,21.27-14.41,46.65-21.95C238,5.49,251.07,0,270.28,0h137.2c8.91,0,15.77,8.23,20.57,24V40.47l-5.48,8.23V166c0,17.15-7.55,31.55-21.95,43.22v41.15l-2.75,24.7q0,9.26,24.7,30.87v38.41c0,10.29-4.81,19.9-15.09,28.82h-6.86V558.39c0,55.57-4.81,97.41-15.1,124.16-4.8,2.75-7.54,19.21-9.6,48.71l2.74,17.15-2.74,76.14v30.19q0,32.93-32.93,86.43C337.5,937.74,334.76,926.76,334.76,909.61Z"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
1
client/icons/Rellanic.svg
Normal file
1
client/icons/Rellanic.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 527.7 940.25"><title>Rellanic</title><g id="Layer_2" data-name="Layer 2"><g id="Rellanic"><path d="M527.7,5.45q-3.83,19.65-15,30.56a129.61,129.61,0,0,1-26.46,19.64q-9.84,6.56-31.66,15.28-19.63,7.65-31.64,16.38Q380.33,103.69,342.16,108a468.46,468.46,0,0,1-54,3.28q-15.83,0-30.56-1.1a53.19,53.19,0,0,0-20.19-6.55H217.74q-7.12,1.11-21.29,1.1a51.67,51.67,0,0,1-20.18-4.36q8.72,19.65,25.63,29.46,14.19,8.74,28.38,29.47a634.05,634.05,0,0,1,98.78,90.58l91.12,103.69a65.1,65.1,0,0,0-.54,8.19,42.47,42.47,0,0,0,.54,7.09c.73,1.82,1.27,3.29,1.64,4.37q7.08,8.75,10.92,12,1.62,1.1,12.55,14.19a14,14,0,0,1,3.27,6.55,9.75,9.75,0,0,1,1.1,4.37,9.62,9.62,0,0,1-1.1,4.36q35.46,43.66,51.3,89.5,3.25,9.82,5.45,19.64a288.59,288.59,0,0,1,10.37,68.75v8.19a296,296,0,0,1-9.81,76.94q-7.12,27.3-24,77.5L418,831.65Q383,872,344.88,899.31a243.27,243.27,0,0,1-90.59,38.19,179.84,179.84,0,0,1-31.64,2.75q-38.78,0-81.87-15.84A293.78,293.78,0,0,1,78,886.22a312.61,312.61,0,0,1-51.85-48,300.52,300.52,0,0,0-18-46.94,60.18,60.18,0,0,1-4.92-13.64,82.36,82.36,0,0,1-2.19-19.11,104.89,104.89,0,0,1,.56-10.91,176.12,176.12,0,0,1-1.64-24,199.79,199.79,0,0,1,2.72-32.74Q5.45,663,5.45,645a103.71,103.71,0,0,0-.54-10.92,242.44,242.44,0,0,1,50.74-67.66,646.83,646.83,0,0,0,57.86-61.12q11.44-10.89,25.09-13.1A88.3,88.3,0,0,1,163.71,489q14.17-1.11,29.46-1.1a108.11,108.11,0,0,0,28.38-7.63q17.44,8.75,27.29,12a124.47,124.47,0,0,1,28.38,13.1q8.71,4.38,23.46,17.46,9.29,9.86,17.47,28.38,7.07,12,9.27,21.83a35.16,35.16,0,0,1,1.64,9.83V585a80.23,80.23,0,0,1-8.73,27.28q-8.2,14.19-18,22.93a166.18,166.18,0,0,1-19.65,19.64q-13.1,8.74-20.72,13.1l-7.65-4.37v-1.64q0-12,6.55-18-8.17-6.55-10.36-10.92l-2.18-8.73c0-2.18-.74-5.81-2.19-10.91v-3.29a38,38,0,0,0-3.82-7.63,196.53,196.53,0,0,0-33.84-40.39Q185.53,542.43,162.61,537a163.71,163.71,0,0,0-50.75,9.81q-25.08,8.76-32.2,36Q67.12,615.56,67.13,654.3a256,256,0,0,0,3.26,39.83,176.75,176.75,0,0,0,5.47,28.38Q88.37,770,122.78,812a452.22,452.22,0,0,0,103.13,58.94,153.57,153.57,0,0,0,107,5.45q25.63-12,37.66-27.28,13.62-14.21,23.46-34.93,10.36-18.57,20.2-39.29Q426.72,753.05,437.1,740q3.27-44.76,5.47-61.12a228.17,228.17,0,0,0,3.26-38.21,213.15,213.15,0,0,0-1.64-26.19,245.3,245.3,0,0,0-8.17-48q-2.2-8.17-4.93-16.36-9.27-30.55-34.92-61.12a70,70,0,0,0-2.18-18,29.12,29.12,0,0,0-4.37-10.37,175.28,175.28,0,0,0-17.46-29.48l-18.55-27.27q-12-16.38-16.38-28.38a282.35,282.35,0,0,1-27.81-28.37q-20.22-26.2-24-31.66Q269,295.76,260.29,286q-10.92-12-31.1-25.11-36.56-31.65-79.12-70.94-45.31-39.28-88.41-66.58-14.74-8.17-17.46-16.9a16.93,16.93,0,0,0-.54-3.83V99.87q0-8.73,6.54-19.11A102.47,102.47,0,0,1,63.3,61.12q9.27-9.82,12.56-18.56a223.6,223.6,0,0,1,38.73-3.27,271,271,0,0,1,40.93,3.27A367.15,367.15,0,0,0,215,47.48c6.91,0,13.64-.17,20.2-.56a45,45,0,0,0,21.27,5.47q17.44,0,25.65-1.1h22.93a77.75,77.75,0,0,1,24,7.65,114,114,0,0,1,27.82-3.29H364q27.25,2.2,39.29,2.19,16.34,0,36.55-5.45,19.1-6.55,27.83-22.93h2.72A20.48,20.48,0,0,0,484.58,24c2.17-4.71,6.17-7.09,12-7.09a26.6,26.6,0,0,1,4.92.54v-.54c0-1.08.72-3.46,2.19-7.11a36.74,36.74,0,0,1,6-6.54C512.57,1.1,515.12,0,517.32,0,521,0,524.41,1.82,527.7,5.45Z"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
@@ -37,3 +37,12 @@
|
||||
.book-front-cover {
|
||||
content: url('../icons/book-front-cover.svg');
|
||||
}
|
||||
.davek {
|
||||
content: url('../icons/Davek.svg');
|
||||
}
|
||||
.rellanic {
|
||||
content: url('../icons/Rellanic.svg');
|
||||
}
|
||||
.iokharic {
|
||||
content: url('../icons/Iokharic.svg');
|
||||
}
|
||||
|
||||
43
install/README.WINDOWS.md
Normal file
43
install/README.WINDOWS.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Windows Installation Instructions
|
||||
|
||||
## Before Installing
|
||||
|
||||
These instructions assume that you are installing to a completely new, fresh Windows 10 installation. As such, some steps may not be necessary if you are installing to an existing Windows 10 instance.
|
||||
|
||||
## Installation instructions
|
||||
|
||||
1. Download the installation script from https://raw.githubusercontent.com/naturalcrit/homebrewery/master/install/windows/install.ps1.
|
||||
|
||||
2. Run Powershell as an Administrator.
|
||||
a. Click the Start menu or press the Windows key.
|
||||
b. Type `powershell` into the Search box.
|
||||
c. Right click on the Powershell app and select "Run As Administrator".
|
||||
d. Click YES in the prompt that appears.
|
||||
|
||||
3. Change the script execution policy.
|
||||
a. Run the Powershell command `Set-ExecutionPolicy Bypass -Scope Process`.
|
||||
b. Allow the change to be made - press Y at the prompt that appears.
|
||||
|
||||
4. Run the installation script.
|
||||
a. Navigate to the location of the script, e.g. `cd C:\Users\ExampleUser\Downloads`.
|
||||
b. Start the script - `.\install.ps1`
|
||||
|
||||
5. Once the script has completed, it will start the Homebrewery server. This will normally cause a Network Access prompt for NodeJS - if this appears, click "Allow".
|
||||
|
||||
**NOTE:** At this time, the script **ONLY** installs HomeBrewery. It does **NOT** install the NaturalCrit login system, as that is currently a completely separate project.
|
||||
|
||||
---
|
||||
|
||||
### Testing
|
||||
|
||||
These installation instructions have been tested on the following Ubuntu releases:
|
||||
|
||||
- *Windows 10 Home - OS Build 19045.2546*
|
||||
|
||||
## Final Notes
|
||||
|
||||
While this installation process works successfully at the time of writing (January 23, 2023), it relies on all of the Node.JS packages used in the HomeBrewery project retaining their cross-platform capabilities to continue to function. This is one of the inherent advantages of Node.JS, but it is by no means guaranteed and as such, functionality or even installation may fail without warning at some point in the future.
|
||||
|
||||
Regards,
|
||||
G
|
||||
January 23, 2023
|
||||
51
install/windows/install.ps1
Normal file
51
install/windows/install.ps1
Normal file
@@ -0,0 +1,51 @@
|
||||
Write-Host Homebrewery Install -BackgroundColor Black -ForegroundColor Yellow
|
||||
Write-Host =================== -BackgroundColor Black -ForegroundColor Yellow
|
||||
Write-Host Install Chocolatey -BackgroundColor Black -ForegroundColor Yellow
|
||||
Write-Host Instructions from https://chocolate.org/install -BackgroundColor Black -ForegroundColor Yellow
|
||||
|
||||
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
|
||||
|
||||
Write-Host Install Node JS v16.11.1 -BackgroundColor Black -ForegroundColor Yellow
|
||||
|
||||
choco install nodejs --version=16.11.1 -y
|
||||
|
||||
Write-Host Install MongoDB v 4.4.4 -BackgroundColor Black -ForegroundColor Yellow
|
||||
|
||||
choco install mongodb --version=4.4.4 -y
|
||||
|
||||
Write-Host Install GIT -BackgroundColor Black -ForegroundColor Yellow
|
||||
|
||||
choco install git -y
|
||||
|
||||
Write-Host Refresh Environment -BackgroundColor Black -ForegroundColor Yellow
|
||||
|
||||
Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1"
|
||||
Update-SessionEnvironment
|
||||
|
||||
Write-Host Create Homebrewery directory - C:\Homebrewery -BackgroundColor Black -ForegroundColor Yellow
|
||||
|
||||
mkdir C:\Hombrewery
|
||||
cd C:\Hombrewery
|
||||
|
||||
Write-Host Download Homebrewery project files -BackgroundColor Black -ForegroundColor Yellow
|
||||
|
||||
git clone https://github.com/naturalcrit/homebrewery.git
|
||||
|
||||
Write-Host Install Homebrewery files -BackgroundColor Black -ForegroundColor Yellow
|
||||
|
||||
cd homebrewery
|
||||
|
||||
npm install
|
||||
npm audit fix
|
||||
|
||||
Write-Host Set install type to 'local' -BackgroundColor Black -ForegroundColor Yellow
|
||||
|
||||
[System.Environment]::SetEnvironmentVariable('NODE_ENV', 'local')
|
||||
|
||||
Write-Host INSTALL COMPLETE -BackgroundColor Black -ForegroundColor Yellow
|
||||
Write-Host To start Homebrewery in the future, open a terminal in the Homebrewery directory and run npm start -BackgroundColor Black -ForegroundColor Yellow
|
||||
Write-Host ================================================================================================== -BackgroundColor Black -ForegroundColor Yellow
|
||||
|
||||
Write-Host Start Homebrewery -BackgroundColor Black -ForegroundColor Yellow
|
||||
|
||||
npm start
|
||||
17255
package-lock.json
generated
17255
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
32
package.json
32
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "homebrewery",
|
||||
"description": "Create authentic looking D&D homebrews using only markdown",
|
||||
"version": "3.7.1",
|
||||
"version": "3.7.2",
|
||||
"engines": {
|
||||
"node": "16.11.x"
|
||||
},
|
||||
@@ -12,23 +12,22 @@
|
||||
"scripts": {
|
||||
"dev": "node scripts/dev.js",
|
||||
"quick": "node scripts/quick.js",
|
||||
"build": "node scripts/buildHomebrew.js",
|
||||
"buildall": "node scripts/buildHomebrew.js && node scripts/buildAdmin.js",
|
||||
"build": "node scripts/buildHomebrew.js && node scripts/buildAdmin.js",
|
||||
"builddev": "node scripts/buildHomebrew.js --dev",
|
||||
"lint": "eslint --fix **/*.{js,jsx}",
|
||||
"lint:dry": "eslint **/*.{js,jsx}",
|
||||
"circleci": "npm test && eslint **/*.{js,jsx} --max-warnings=0",
|
||||
"verify": "npm run lint && npm test",
|
||||
"test": "jest",
|
||||
"test": "jest --runInBand",
|
||||
"test:api-unit": "jest server/*.spec.js --verbose",
|
||||
"test:coverage": "jest --coverage --silent",
|
||||
"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:route": "jest tests/routes/static-pages.test.js --verbose",
|
||||
"phb": "node scripts/phb.js",
|
||||
"prod": "set NODE_ENV=production && npm run build",
|
||||
"postinstall": "npm run buildall",
|
||||
"postinstall": "npm run build",
|
||||
"start": "node server.js"
|
||||
},
|
||||
"author": "stolksdorf",
|
||||
@@ -37,12 +36,15 @@
|
||||
"build/*"
|
||||
],
|
||||
"jest": {
|
||||
"testTimeout": 15000,
|
||||
"testTimeout": 30000,
|
||||
"modulePaths": [
|
||||
"node_modules",
|
||||
"shared",
|
||||
"server"
|
||||
],
|
||||
"coveragePathIgnorePatterns": [
|
||||
"build/*"
|
||||
],
|
||||
"coverageThreshold" : {
|
||||
"global" : {
|
||||
"statements" : 25,
|
||||
@@ -68,11 +70,11 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.21.0",
|
||||
"@babel/core": "^7.21.3",
|
||||
"@babel/plugin-transform-runtime": "^7.21.0",
|
||||
"@babel/preset-env": "^7.19.4",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@googleapis/drive": "^4.0.2",
|
||||
"@googleapis/drive": "^5.0.1",
|
||||
"body-parser": "^1.20.2",
|
||||
"classnames": "^2.3.2",
|
||||
"codemirror": "^5.65.6",
|
||||
@@ -82,29 +84,29 @@
|
||||
"express": "^4.18.2",
|
||||
"express-async-handler": "^1.2.0",
|
||||
"express-static-gzip": "2.1.7",
|
||||
"fs-extra": "11.1.0",
|
||||
"fs-extra": "11.1.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jwt-simple": "^0.5.6",
|
||||
"less": "^3.13.1",
|
||||
"lodash": "^4.17.21",
|
||||
"marked": "4.2.12",
|
||||
"marked": "4.3.0",
|
||||
"marked-extended-tables": "^1.0.5",
|
||||
"markedLegacy": "npm:marked@^0.3.19",
|
||||
"moment": "^2.29.4",
|
||||
"mongoose": "^6.9.2",
|
||||
"mongoose": "^7.0.2",
|
||||
"nanoid": "3.3.4",
|
||||
"nconf": "^0.12.0",
|
||||
"npm": "^8.10.0",
|
||||
"npm": "^9.6.2",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-frame-component": "4.1.3",
|
||||
"react-router-dom": "6.8.2",
|
||||
"react-router-dom": "6.9.0",
|
||||
"sanitize-filename": "1.6.3",
|
||||
"superagent": "^6.1.0",
|
||||
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.35.0",
|
||||
"eslint": "^8.36.0",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"jest": "^29.5.0",
|
||||
"supertest": "^6.3.3"
|
||||
|
||||
@@ -23,7 +23,7 @@ const splitTextStyleAndMetadata = (brew)=>{
|
||||
const index = brew.text.indexOf('```\n\n');
|
||||
const metadataSection = brew.text.slice(12, index - 1);
|
||||
const metadata = yaml.load(metadataSection);
|
||||
Object.assign(brew, _.pick(metadata, ['title', 'description', 'tags', 'systems', 'renderer', 'theme']));
|
||||
Object.assign(brew, _.pick(metadata, ['title', 'description', 'tags', 'systems', 'renderer', 'theme', 'lang']));
|
||||
brew.text = brew.text.slice(index + 5);
|
||||
}
|
||||
if(brew.text.startsWith('```css')) {
|
||||
@@ -225,6 +225,7 @@ app.get('/user/:username', async (req, res, next)=>{
|
||||
'pageCount',
|
||||
'description',
|
||||
'authors',
|
||||
'lang',
|
||||
'published',
|
||||
'views',
|
||||
'shareId',
|
||||
|
||||
@@ -15,6 +15,7 @@ const DEFAULT_BREW = {
|
||||
authors : [],
|
||||
tags : [],
|
||||
systems : [],
|
||||
lang : 'en',
|
||||
thumbnail : '',
|
||||
views : 0,
|
||||
published : false,
|
||||
|
||||
@@ -27,8 +27,8 @@ const disconnect = async ()=>{
|
||||
};
|
||||
|
||||
const connect = async (config)=>{
|
||||
return await Mongoose.connect(getMongoDBURL(config),
|
||||
{ retryWrites: false }, handleConnectionError);
|
||||
return await Mongoose.connect(getMongoDBURL(config), { retryWrites: false })
|
||||
.catch((error)=>handleConnectionError(error));
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -129,7 +129,9 @@ const GoogleActions = {
|
||||
description : file.description,
|
||||
views : parseInt(file.properties.views),
|
||||
published : file.properties.published ? file.properties.published == 'true' : false,
|
||||
systems : []
|
||||
systems : [],
|
||||
lang : file.properties.lang,
|
||||
thumbnail : file.properties.thumbnail
|
||||
};
|
||||
});
|
||||
return brews;
|
||||
@@ -149,7 +151,8 @@ const GoogleActions = {
|
||||
editId : brew.editId || nanoid(12),
|
||||
pageCount : brew.pageCount,
|
||||
renderer : brew.renderer || 'legacy',
|
||||
isStubbed : true
|
||||
isStubbed : true,
|
||||
lang : brew.lang || 'en'
|
||||
}
|
||||
},
|
||||
media : {
|
||||
@@ -187,7 +190,8 @@ const GoogleActions = {
|
||||
pageCount : brew.pageCount,
|
||||
renderer : brew.renderer || 'legacy',
|
||||
isStubbed : true,
|
||||
version : 1
|
||||
version : 1,
|
||||
lang : brew.lang || 'en'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -255,6 +259,7 @@ const GoogleActions = {
|
||||
description : obj.data.description,
|
||||
systems : obj.data.properties.systems ? obj.data.properties.systems.split(',') : [],
|
||||
authors : [],
|
||||
lang : obj.data.properties.lang,
|
||||
published : obj.data.properties.published ? obj.data.properties.published == 'true' : false,
|
||||
trashed : obj.data.trashed,
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ describe('Tests for api', ()=>{
|
||||
description : 'this is a description',
|
||||
tags : ['something', 'fun'],
|
||||
systems : ['D&D 5e'],
|
||||
lang : 'en',
|
||||
renderer : 'v3',
|
||||
theme : 'phb',
|
||||
published : true,
|
||||
@@ -255,6 +256,7 @@ If you believe you should have access to this brew, ask the file owner to invite
|
||||
pageCount : 1,
|
||||
published : false,
|
||||
renderer : 'legacy',
|
||||
lang : 'en',
|
||||
shareId : undefined,
|
||||
systems : [],
|
||||
tags : [],
|
||||
@@ -448,6 +450,7 @@ brew`);
|
||||
pageCount : 1,
|
||||
published : false,
|
||||
renderer : 'V3',
|
||||
lang : 'en',
|
||||
shareId : expect.any(String),
|
||||
style : undefined,
|
||||
systems : [],
|
||||
@@ -506,6 +509,7 @@ brew`);
|
||||
pageCount : undefined,
|
||||
published : false,
|
||||
renderer : undefined,
|
||||
lang : 'en',
|
||||
shareId : expect.any(String),
|
||||
googleId : expect.any(String),
|
||||
style : undefined,
|
||||
|
||||
@@ -15,6 +15,7 @@ const HomebrewSchema = mongoose.Schema({
|
||||
description : { type: String, default: '' },
|
||||
tags : [String],
|
||||
systems : [String],
|
||||
lang : { type: String, default: 'en' },
|
||||
renderer : { type: String, default: '' },
|
||||
authors : [String],
|
||||
invitedAuthors : [String],
|
||||
@@ -39,30 +40,24 @@ HomebrewSchema.statics.increaseView = async function(query) {
|
||||
return brew;
|
||||
};
|
||||
|
||||
HomebrewSchema.statics.get = function(query, fields=null){
|
||||
return new Promise((resolve, reject)=>{
|
||||
Homebrew.find(query, fields, null, (err, brews)=>{
|
||||
if(err || !brews.length) return reject('Can not find brew');
|
||||
if(!_.isNil(brews[0].textBin)) { // Uncompress zipped text field
|
||||
unzipped = zlib.inflateRawSync(brews[0].textBin);
|
||||
brews[0].text = unzipped.toString();
|
||||
}
|
||||
return resolve(brews[0]);
|
||||
});
|
||||
});
|
||||
HomebrewSchema.statics.get = async function(query, fields=null){
|
||||
const brew = await Homebrew.findOne(query, fields).orFail()
|
||||
.catch((error)=>{throw 'Can not find brew';});
|
||||
if(!_.isNil(brew.textBin)) { // Uncompress zipped text field
|
||||
unzipped = zlib.inflateRawSync(brew.textBin);
|
||||
brew.text = unzipped.toString();
|
||||
}
|
||||
return brew;
|
||||
};
|
||||
|
||||
HomebrewSchema.statics.getByUser = function(username, allowAccess=false, fields=null){
|
||||
return new Promise((resolve, reject)=>{
|
||||
const query = { authors: username, published: true };
|
||||
if(allowAccess){
|
||||
delete query.published;
|
||||
}
|
||||
Homebrew.find(query, fields).lean().exec((err, brews)=>{ //lean() converts results to JSObjects
|
||||
if(err) return reject('Can not find brew');
|
||||
return resolve(brews);
|
||||
});
|
||||
});
|
||||
HomebrewSchema.statics.getByUser = async function(username, allowAccess=false, fields=null){
|
||||
const query = { authors: username, published: true };
|
||||
if(allowAccess){
|
||||
delete query.published;
|
||||
}
|
||||
const brews = await Homebrew.find(query, fields).lean().exec() //lean() converts results to JSObjects
|
||||
.catch((error)=>{throw 'Can not find brews';});
|
||||
return brews;
|
||||
};
|
||||
|
||||
const Homebrew = mongoose.model('Homebrew', HomebrewSchema);
|
||||
|
||||
@@ -239,7 +239,7 @@ const definitionLists = {
|
||||
Marked.use({ extensions: [mustacheSpans, mustacheDivs, mustacheInjectInline, definitionLists] });
|
||||
Marked.use(MarkedExtendedTables());
|
||||
Marked.use(mustacheInjectBlock);
|
||||
Marked.use({ smartypants: true });
|
||||
Marked.use({ renderer: renderer, smartypants: true });
|
||||
|
||||
//Fix local links in the Preview iFrame to link inside the frame
|
||||
renderer.link = function (href, title, text) {
|
||||
@@ -347,10 +347,7 @@ module.exports = {
|
||||
render : (rawBrewText)=>{
|
||||
rawBrewText = rawBrewText.replace(/^\\column$/gm, `\n<div class='columnSplit'></div>\n`)
|
||||
.replace(/^(:+)$/gm, (match)=>`${`<div class='blank'></div>`.repeat(match.length)}\n`);
|
||||
return Marked.parse(
|
||||
sanatizeScriptTags(rawBrewText),
|
||||
{ renderer: renderer }
|
||||
);
|
||||
return Marked.parse(sanatizeScriptTags(rawBrewText));
|
||||
},
|
||||
|
||||
validate : (rawBrewText)=>{
|
||||
|
||||
@@ -13,3 +13,9 @@ test('Processes the markdown within an HTML block if its just a class wrapper',
|
||||
const rendered = Markdown.render(source);
|
||||
expect(rendered).toBe('<div> <p><em>Bold text</em></p>\n </div>');
|
||||
});
|
||||
|
||||
test('Check markdown is using the custom renderer; specifically that it adds target=_self attribute to internal links in HTML blocks', function() {
|
||||
const source = '<div>[Has _self Attribute?](#p1)</div>';
|
||||
const rendered = Markdown.render(source);
|
||||
expect(rendered).toBe('<div> <p><a href="#p1" target="_self">Has _self Attribute?</a></p>\n </div>');
|
||||
});
|
||||
|
||||
@@ -262,6 +262,7 @@ body {
|
||||
//Full Width
|
||||
hr+hr+blockquote{
|
||||
.useColumns(0.96);
|
||||
column-fill : balance;
|
||||
}
|
||||
//*****************************
|
||||
// * FOOTER
|
||||
|
||||
@@ -3,6 +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 ClassFeatureGen = require('./snippets/classfeature.gen.js');
|
||||
const CoverPageGen = require('./snippets/coverpage.gen.js');
|
||||
const TableOfContentsGen = require('./snippets/tableOfContents.gen.js');
|
||||
@@ -232,7 +233,30 @@ module.exports = [
|
||||
name : '1/3 Class Table (unframed)',
|
||||
icon : 'fas fa-border-none',
|
||||
gen : ClassTableGen.third('classTable'),
|
||||
}
|
||||
},
|
||||
{
|
||||
name : 'Rune Table',
|
||||
icon : 'fas fa-language',
|
||||
gen : scriptGen.dwarvish,
|
||||
experimental : true,
|
||||
subsnippets : [
|
||||
{
|
||||
name : 'Dwarvish',
|
||||
icon : 'fac davek',
|
||||
gen : scriptGen.dwarvish,
|
||||
},
|
||||
{
|
||||
name : 'Elvish',
|
||||
icon : 'fac rellanic',
|
||||
gen : scriptGen.elvish,
|
||||
},
|
||||
{
|
||||
name : 'Draconic',
|
||||
icon : 'fac iokharic',
|
||||
gen : scriptGen.draconic,
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
48
themes/V3/5ePHB/snippets/script.gen.js
Normal file
48
themes/V3/5ePHB/snippets/script.gen.js
Normal file
@@ -0,0 +1,48 @@
|
||||
const _ = require('lodash');
|
||||
const dedent = require('dedent-tabs').default;
|
||||
|
||||
module.exports = {
|
||||
dwarvish : ()=>{
|
||||
return dedent `##### Dwarvish Runes: Sample Alphabet
|
||||
{{runeTable,wide,frame,font-family:Davek
|
||||
| a | b | c | d | e | f | g | h | i | j | k | l | m |
|
||||
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|
||||
| a | b | c | d | e | f | g | h | i | j | k | l | m |
|
||||
:
|
||||
| n | o | p | q | r | s | t | u | v | w | x | y | z |
|
||||
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|
||||
| n | o | p | q | r | s | t | u | v | w | x | y | z |
|
||||
}}\n\n`;
|
||||
},
|
||||
elvish : ()=>{
|
||||
return dedent `##### Elvish Runes: Sample Alphabet
|
||||
{{runeTable,wide,frame,font-family:Rellanic
|
||||
| a | b | c | d | e | f | g | h | i | j | k | l | m |
|
||||
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|
||||
| a | b | c | d | e | f | g | h | i | j | k | l | m |
|
||||
:
|
||||
| n | o | p | q | r | s | t | u | v | w | x | y | z |
|
||||
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|
||||
| n | o | p | q | r | s | t | u | v | w | x | y | z |
|
||||
}}\n\n`;
|
||||
},
|
||||
draconic : ()=>{
|
||||
return dedent `##### Draconic Runes: Sample Alphabet
|
||||
{{runeTable,wide,frame,font-family:Iokharic
|
||||
| a | b | c | d | e | f | g | h | i | j | k | l | m |
|
||||
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|
||||
| a | b | c | d | e | f | g | h | i | j | k | l | m |
|
||||
:
|
||||
| n | o | p | q | r | s | t | u | v | w | x | y | z |
|
||||
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|
||||
| n | o | p | q | r | s | t | u | v | w | x | y | z |
|
||||
}}\n\n`;
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
()=>{
|
||||
|
||||
};
|
||||
@@ -901,3 +901,43 @@ break-inside : avoid;
|
||||
.page h1 + *{
|
||||
margin-top : 0;
|
||||
}
|
||||
|
||||
//*****************************
|
||||
// * RUNE TABLE
|
||||
// *****************************/
|
||||
.page {
|
||||
.runeTable {
|
||||
margin-block: 0.7cm;
|
||||
table {
|
||||
font-family : inherit;
|
||||
tbody tr {
|
||||
background: unset;
|
||||
}
|
||||
th, td {
|
||||
width: 1.3cm;
|
||||
height: 1.3cm;
|
||||
vertical-align: middle;
|
||||
text-transform: uppercase;
|
||||
outline: 1px solid #000;
|
||||
font-weight: normal;
|
||||
}
|
||||
th{
|
||||
font-family: BookInsanityRemake;
|
||||
font-size: 0.45cm;
|
||||
}
|
||||
td {
|
||||
font-size: 0.7cm;
|
||||
}
|
||||
}
|
||||
|
||||
&.frame {
|
||||
border: initial;
|
||||
border-style: solid;
|
||||
border-image-outset: .45cm .35cm .4cm .4cm;
|
||||
border-image-repeat: stretch;
|
||||
border-image-slice: 170;
|
||||
border-image-source: @scriptBorder;
|
||||
border-image-width: 1.4cm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
@naturalCritLogo : url('/assets/naturalCritLogo.svg');
|
||||
@coverPageBanner : url('/assets/coverPageBanner.svg');
|
||||
@horizontalRule : url('/assets/horizontalRule.svg');
|
||||
@scriptBorder : url('/assets/scriptBorder.png');
|
||||
|
||||
// Watercolor Images
|
||||
@watercolor1 : url('/assets/watercolor/watercolor1.png');
|
||||
|
||||
BIN
themes/assets/scriptBorder.png
Normal file
BIN
themes/assets/scriptBorder.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
BIN
themes/fonts/5e/Davek.woff2
Normal file
BIN
themes/fonts/5e/Davek.woff2
Normal file
Binary file not shown.
BIN
themes/fonts/5e/Iokharic.woff2
Normal file
BIN
themes/fonts/5e/Iokharic.woff2
Normal file
Binary file not shown.
BIN
themes/fonts/5e/Rellanic.woff2
Normal file
BIN
themes/fonts/5e/Rellanic.woff2
Normal file
Binary file not shown.
@@ -113,3 +113,23 @@
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Davek;
|
||||
src: url('../../../fonts/5e/Davek.woff2');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Iokharic;
|
||||
src: url('../../../fonts/5e/Iokharic.woff2');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: Rellanic;
|
||||
src: url('../../../fonts/5e/Rellanic.woff2');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user