0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2025-12-25 20:33:51 +00:00

Merge branch 'master' into pr/2745

This commit is contained in:
Trevor Buckner
2023-03-22 14:11:19 -04:00
30 changed files with 16121 additions and 29247 deletions

View File

@@ -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:

View File

@@ -80,6 +80,37 @@ pre {
## changelog
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
### 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!)
* [x] Changed `filter: drop-shadow` to `box-shadow` on text boxes, making text selectable in PDFs again.
Fixes issues [#1569](https://github.com/naturalcrit/homebrewery/issues/1569)
{{note
**NOTE:** If you create your PDF on a computer with an old version of Mac Preview (v10 or older) you may see shadows appear as solid gray.
}}
##### MichielDeMey
* [x] Updated the Google Drive icon
* [x] Backend fix to unit tests failing intermittently
##### Calculuschild
* [x] Fix PDF pixelation on CoverPage text outlines
}}
### Tuesday 28/02/2023 - v3.7.0
{{taskList
@@ -115,7 +146,6 @@ Fixes issues [#2687](https://github.com/naturalcrit/homebrewery/issues/2687)
Fixes issues [#2674](https://github.com/naturalcrit/homebrewery/issues/2674)
}}
### Monday 23/01/2023 - v3.6.0
{{taskList
##### calculuschild
@@ -141,8 +171,6 @@ Fixes issues [#2583](https://github.com/naturalcrit/homebrewery/issues/2583)
* [x] Fix cloned brews inheriting the parent view count
}}
\column
### Friday 23/12/2022 - v3.5.0
{{taskList

View 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;

View 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;
}
}
}
}

View File

@@ -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>
</>

View File

@@ -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/>

View File

@@ -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;

View File

@@ -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;
}
]
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 305 KiB

View File

@@ -0,0 +1,8 @@
<svg viewBox="0 0 87.3 78" xmlns="http://www.w3.org/2000/svg">
<path d="m6.6 66.85 3.85 6.65c.8 1.4 1.95 2.5 3.3 3.3l13.75-23.8h-27.5c0 1.55.4 3.1 1.2 4.5z" fill="#0066da"/>
<path d="m43.65 25-13.75-23.8c-1.35.8-2.5 1.9-3.3 3.3l-25.4 44a9.06 9.06 0 0 0 -1.2 4.5h27.5z" fill="#00ac47"/>
<path d="m73.55 76.8c1.35-.8 2.5-1.9 3.3-3.3l1.6-2.75 7.65-13.25c.8-1.4 1.2-2.95 1.2-4.5h-27.502l5.852 11.5z" fill="#ea4335"/>
<path d="m43.65 25 13.75-23.8c-1.35-.8-2.9-1.2-4.5-1.2h-18.5c-1.6 0-3.15.45-4.5 1.2z" fill="#00832d"/>
<path d="m59.8 53h-32.3l-13.75 23.8c1.35.8 2.9 1.2 4.5 1.2h50.8c1.6 0 3.15-.45 4.5-1.2z" fill="#2684fc"/>
<path d="m73.4 26.5-12.7-22c-.8-1.4-1.95-2.5-3.3-3.3l-13.75 23.8 16.15 28h27.45c0-1.55-.4-3.1-1.2-4.5z" fill="#ffba00"/>
</svg>

After

Width:  |  Height:  |  Size: 755 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -47,6 +47,7 @@ const Homebrew = createClass({
editId : null,
createdAt : null,
updatedAt : null,
lang : ''
}
};
},

View File

@@ -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'
>

View File

@@ -187,4 +187,7 @@
.account.navItem{
min-width: 100px;
}
.account.username.navItem{
text-transform: none;
}
}

View File

@@ -6,7 +6,7 @@ const cx = require('classnames');
const moment = require('moment');
const request = require('../../../../utils/request-middleware.js');
const googleDriveIcon = require('../../../../googleDrive.png');
const googleDriveIcon = require('../../../../googleDrive.svg');
const dedent = require('dedent-tabs').default;
const BrewItem = createClass({

View File

@@ -94,7 +94,7 @@
}
}
.googleDriveIcon {
height : 20px;
height : 18px;
padding : 0px;
margin : -5px;
}

View File

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

View File

@@ -24,8 +24,7 @@ const Markdown = require('naturalcrit/markdown.js');
const { DEFAULT_BREW_LOAD } = require('../../../../server/brewDefaults.js');
const googleDriveActive = require('../../googleDrive.png');
const googleDriveInactive = require('../../googleDriveMono.png');
const googleDriveIcon = require('../../googleDrive.svg');
const SAVE_TIMEOUT = 3000;
@@ -222,10 +221,7 @@ const EditPage = createClass({
renderGoogleDriveIcon : function(){
return <Nav.item className='googleDriveStorage' onClick={this.handleGoogleClick}>
{this.state.saveGoogle
? <img src={googleDriveActive} alt='googleDriveActive'/>
: <img src={googleDriveInactive} alt='googleDriveInactive'/>
}
<img src={googleDriveIcon} className={this.state.saveGoogle ? '' : 'inactive'} alt='Google Drive icon'/>
{this.state.confirmGoogleTransfer &&
<div className='errorContainer' onClick={this.closeAlerts}>
@@ -402,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>;

View File

@@ -18,8 +18,12 @@
position : relative;
}
.googleDriveStorage img{
height : 20px;
height : 18px;
padding : 0px;
margin : -5px;
&.inactive {
filter: grayscale(1);
}
}
}

View File

@@ -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>;

44843
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "homebrewery",
"description": "Create authentic looking D&D homebrews using only markdown",
"version": "3.7.0",
"version": "3.7.2",
"engines": {
"node": "16.11.x"
},
@@ -19,9 +19,9 @@
"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",
@@ -37,12 +37,15 @@
"build/*"
],
"jest": {
"testTimeout": 15000,
"testTimeout": 30000,
"modulePaths": [
"node_modules",
"shared",
"server"
],
"coveragePathIgnorePatterns": [
"build/*"
],
"coverageThreshold" : {
"global" : {
"statements" : 25,
@@ -68,10 +71,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": "^5.0.1",
"body-parser": "^1.20.2",
"classnames": "^2.3.2",
"codemirror": "^5.65.6",
@@ -81,8 +85,7 @@
"express": "^4.18.2",
"express-async-handler": "^1.2.0",
"express-static-gzip": "2.1.7",
"fs-extra": "11.1.0",
"googleapis": "111.0.0",
"fs-extra": "11.1.1",
"js-yaml": "^4.1.0",
"jwt-simple": "^0.5.6",
"less": "^3.13.1",
@@ -91,22 +94,22 @@
"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.4.3",
"jest": "^29.5.0",
"supertest": "^6.3.3"
}
}

View File

@@ -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',

View File

@@ -15,6 +15,7 @@ const DEFAULT_BREW = {
authors : [],
tags : [],
systems : [],
lang : 'en',
thumbnail : '',
views : 0,
published : false,

View File

@@ -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 = {

View File

@@ -1,6 +1,6 @@
/* eslint-disable max-lines */
const _ = require('lodash');
const { google } = require('googleapis');
const googleDrive = require('@googleapis/drive');
const { nanoid } = require('nanoid');
const token = require('./token.js');
const config = require('./config.js');
@@ -14,7 +14,7 @@ if(!config.get('service_account')){
config.get('service_account');
try {
serviceAuth = google.auth.fromJSON(keys);
serviceAuth = googleDrive.auth.fromJSON(keys);
serviceAuth.scopes = ['https://www.googleapis.com/auth/drive'];
} catch (err) {
console.warn(err);
@@ -22,7 +22,7 @@ if(!config.get('service_account')){
}
}
google.options({ auth: serviceAuth || config.get('google_api_key') });
const defaultAuth = serviceAuth || config.get('google_api_key');
const GoogleActions = {
@@ -33,7 +33,7 @@ const GoogleActions = {
throw (err);
}
const oAuth2Client = new google.auth.OAuth2(
const oAuth2Client = new googleDrive.auth.OAuth2(
config.get('google_client_id'),
config.get('google_client_secret'),
'/auth/google/redirect'
@@ -60,7 +60,7 @@ const GoogleActions = {
},
getGoogleFolder : async (auth)=>{
const drive = google.drive({ version: 'v3', auth });
const drive = googleDrive.drive({ version: 'v3', auth });
fileMetadata = {
'name' : 'Homebrewery',
@@ -97,7 +97,7 @@ const GoogleActions = {
},
listGoogleBrews : async (auth)=>{
const drive = google.drive({ version: 'v3', auth });
const drive = googleDrive.drive({ version: 'v3', auth });
const obj = await drive.files.list({
pageSize : 1000,
@@ -129,14 +129,16 @@ 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;
},
updateGoogleBrew : async (brew)=>{
const drive = google.drive({ version: 'v3' });
const drive = googleDrive.drive({ version: 'v3', auth: defaultAuth });
await drive.files.update({
fileId : brew.googleId,
@@ -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 : {
@@ -167,7 +170,7 @@ const GoogleActions = {
},
newGoogleBrew : async (auth, brew)=>{
const drive = google.drive({ version: 'v3', auth });
const drive = googleDrive.drive({ version: 'v3', auth });
const media = {
mimeType : 'text/plain',
@@ -187,7 +190,8 @@ const GoogleActions = {
pageCount : brew.pageCount,
renderer : brew.renderer || 'legacy',
isStubbed : true,
version : 1
version : 1,
lang : brew.lang || 'en'
}
};
@@ -218,7 +222,7 @@ const GoogleActions = {
},
getGoogleBrew : async (id, accessId, accessType)=>{
const drive = google.drive({ version: 'v3' });
const drive = googleDrive.drive({ version: 'v3', auth: defaultAuth });
const obj = await drive.files.get({
fileId : id,
@@ -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,
@@ -274,7 +279,7 @@ const GoogleActions = {
},
deleteGoogleBrew : async (auth, id, accessId)=>{
const drive = google.drive({ version: 'v3', auth });
const drive = googleDrive.drive({ version: 'v3', auth });
const obj = await drive.files.get({
fileId : id,
@@ -300,7 +305,7 @@ const GoogleActions = {
},
increaseView : async (id, accessId, accessType, brew)=>{
const drive = google.drive({ version: 'v3' });
const drive = googleDrive.drive({ version: 'v3', auth: defaultAuth });
await drive.files.update({
fileId : brew.googleId,

View File

@@ -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,

View File

@@ -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);

View File

@@ -262,6 +262,7 @@ body {
//Full Width
hr+hr+blockquote{
.useColumns(0.96);
column-fill : balance;
}
//*****************************
// * FOOTER

View File

@@ -253,7 +253,7 @@ body {
border-image-outset : 9px 0px;
border-image-width : 11px;
padding : 0.13cm 0.16cm;
filter : drop-shadow(1px 4px 6px #888);
box-shadow : 1px 4px 14px #888;
.page :where(&) {
margin-top : 9px; //Prevent top border getting cut off on colbreak
}
@@ -282,7 +282,7 @@ body {
border-image : @descriptiveBoxImage 12 stretch;
border-image-outset : 4px;
padding : 0.1em;
filter : drop-shadow(0 0 3px #faf7ea);
box-shadow : 0 0 6px #faf7ea;
.page :where(&) {
margin-top : 4px; //Prevent top border getting cut off on colbreak
}
@@ -397,7 +397,7 @@ body {
border-image-outset : 0px 2px;
background-blend-mode : overlay;
background-attachment : fixed;
filter : drop-shadow(1px 4px 6px #888);
box-shadow : 1px 4px 14px #888;
padding : 4px 2px;
margin-left : -0.16cm;
margin-right : -0.16cm;
@@ -687,11 +687,11 @@ h5 + table{
all: unset;
}
.logo {
position : absolute;
top : 0.5cm;
left : 0;
right : 0;
filter : drop-shadow(0 0 0.075cm black);
position : absolute;
top : 0.5cm;
left : 0;
right : 0;
filter :drop-shadow(0 0 0.075cm black);
img {
height : 2cm;
width : 100%;
@@ -706,11 +706,11 @@ h5 + table{
z-index : -1;
}
h1 {
--shadow-x0 : #000 0px 0px 0.1cm;
--shadow-x1 : var(--shadow-x0), var(--shadow-x0), var(--shadow-x0);
--shadow-x2 : var(--shadow-x1), var(--shadow-x1), var(--shadow-x1);
--shadow-x3 : var(--shadow-x2), var(--shadow-x2), var(--shadow-x2);
text-shadow : var(--shadow-x3), var(--shadow-x3), var(--shadow-x3);
text-shadow: unset;
filter : drop-shadow(0 0 1.5px black) drop-shadow(0 0 0 black)
drop-shadow(0 0 0 black) drop-shadow(0 0 0 black)
drop-shadow(0 0 0 black) drop-shadow(0 0 0 black)
drop-shadow(0 0 0 black) drop-shadow(0 0 0 black);
text-transform : uppercase;
font-weight : normal;
display : block;
@@ -722,11 +722,10 @@ h5 + table{
line-height : 0.85em;
}
h2 {
--shadow-x0 : #000 0px 0px 2.5px;
--shadow-x1 : var(--shadow-x0), var(--shadow-x0), var(--shadow-x0);
--shadow-x2 : var(--shadow-x1), var(--shadow-x1), var(--shadow-x1);
--shadow-x3 : var(--shadow-x2), var(--shadow-x2), var(--shadow-x2);
text-shadow : var(--shadow-x3), var(--shadow-x3), var(--shadow-x3);
filter : drop-shadow(0 0 1px black) drop-shadow(0 0 0 black)
drop-shadow(0 0 0 black) drop-shadow(0 0 0 black)
drop-shadow(0 0 0 black) drop-shadow(0 0 0 black)
drop-shadow(0 0 0 black) drop-shadow(0 0 0 black);
font-family : NodestoCapsCondensed;
font-weight : normal;
font-size : 0.85cm;
@@ -746,7 +745,7 @@ h5 + table{
filter : drop-shadow(0 0 3px black);
}
.banner {
filter : drop-shadow(2px 2px 2px #000);
filter : drop-shadow(2px 2px 2px black);
position : absolute;
left : 0;
bottom : 4.2cm;
@@ -766,10 +765,10 @@ h5 + table{
padding-top : 0.1cm;
}
.footnote {
--shadow-x0 : #000 0px 0px 0.05cm;
--shadow-x1 : var(--shadow-x0), var(--shadow-x0), var(--shadow-x0);
--shadow-x2 : var(--shadow-x1), var(--shadow-x1), var(--shadow-x1);
text-shadow : var(--shadow-x2), var(--shadow-x2), var(--shadow-x2);
filter : drop-shadow(0 0 0.7px black) drop-shadow(0 0 0 black)
drop-shadow(0 0 0 black) drop-shadow(0 0 0 black)
drop-shadow(0 0 0 black) drop-shadow(0 0 0 black)
drop-shadow(0 0 0 black) drop-shadow(0 0 0 black);
position : absolute;
text-align : center;
color : white;