0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-07 22:52:39 +00:00

Merge branch 'master' into SwappableThemes-ReorganizeFolderStructure

This commit is contained in:
Trevor Buckner
2022-05-18 16:21:20 -04:00
19 changed files with 1898 additions and 2621 deletions

View File

@@ -11,6 +11,8 @@ const Themes = require('themes/themes.json');
const SYSTEMS = ['5e', '4e', '3.5e', 'Pathfinder']; const SYSTEMS = ['5e', '4e', '3.5e', 'Pathfinder'];
const homebreweryThumbnail = require('../../thumbnail.png');
const MetadataEditor = createClass({ const MetadataEditor = createClass({
displayName : 'MetadataEditor', displayName : 'MetadataEditor',
getDefaultProps : function() { getDefaultProps : function() {
@@ -30,6 +32,23 @@ const MetadataEditor = createClass({
}; };
}, },
getInitialState : function(){
return {
showThumbnail : true
};
},
toggleThumbnailDisplay : function(){
this.setState({
showThumbnail : !this.state.showThumbnail
});
},
renderThumbnail : function(){
if(!this.state.showThumbnail) return;
return <img className='thumbnail-preview' src={this.props.metadata.thumbnail || homebreweryThumbnail}></img>;
},
handleFieldChange : function(name, e){ handleFieldChange : function(name, e){
this.props.onChange(_.merge({}, this.props.metadata, { this.props.onChange(_.merge({}, this.props.metadata, {
[name] : e.target.value [name] : e.target.value
@@ -72,7 +91,7 @@ const MetadataEditor = createClass({
if(!confirm('Are you REALLY sure? You will lose editor access to this document.')) return; if(!confirm('Are you REALLY sure? You will lose editor access to this document.')) return;
} }
request.delete(`/api/${this.props.metadata.googleId}${this.props.metadata.editId}`) request.delete(`/api/${this.props.metadata.googleId ?? ''}${this.props.metadata.editId}`)
.send() .send()
.end(function(err, res){ .end(function(err, res){
window.location.href = '/'; window.location.href = '/';
@@ -213,6 +232,18 @@ const MetadataEditor = createClass({
<textarea value={this.props.metadata.description} className='value' <textarea value={this.props.metadata.description} className='value'
onChange={(e)=>this.handleFieldChange('description', e)} /> onChange={(e)=>this.handleFieldChange('description', e)} />
</div> </div>
<div className='field thumbnail'>
<label>thumbnail</label>
<input type='text'
value={this.props.metadata.thumbnail}
placeholder='my.thumbnail.url'
className='value'
onChange={(e)=>this.handleFieldChange('thumbnail', e)} />
<button className='display' onClick={this.toggleThumbnailDisplay}>
<i className={`fas fa-caret-${this.state.showThumbnail ? 'right' : 'left'}`} />
</button>
{this.renderThumbnail()}
</div>
{/*} {/*}
<div className='field tags'> <div className='field tags'>
<label>tags</label> <label>tags</label>

View File

@@ -24,6 +24,33 @@
flex : 1 1 auto; flex : 1 1 auto;
min-width : 200px; min-width : 200px;
} }
&.thumbnail{
height : 1.4em;
label{
line-height: 2.0em;
}
.value{
overflow: hidden;
text-overflow: ellipsis;
}
button{
border: 1px solid #999;
color: white;
padding: 0px 5px;
background-color: black;
&:hover{
background-color: #777;
}
}
.thumbnail-preview{
position : relative;
width : 80px;
height : min-content;
border : 2px solid white;
margin-left : 5px;
max-height : 115px;
}
}
} }
.description.field textarea.value{ .description.field textarea.value{
resize : none; resize : none;

View File

@@ -33,10 +33,12 @@ const Homebrew = createClass({
}; };
}, },
getInitialState : function(){ getInitialState : function() {
global.version = this.props.version;
global.account = this.props.account; global.account = this.props.account;
global.version = this.props.version;
global.enable_v3 = this.props.enable_v3; global.enable_v3 = this.props.enable_v3;
global.config = this.props.config;
return {}; return {};
}, },

View File

@@ -1,6 +1,7 @@
const React = require('react'); const React = require('react');
const createClass = require('create-react-class'); const createClass = require('create-react-class');
const Nav = require('naturalcrit/nav/nav.jsx'); const Nav = require('naturalcrit/nav/nav.jsx');
const request = require('superagent');
const Account = createClass({ const Account = createClass({
displayName : 'AccountNavItem', displayName : 'AccountNavItem',
@@ -36,7 +37,29 @@ const Account = createClass({
} }
}, },
localLogin : async function(){
const username = prompt('Enter username:');
if(!username) {return;}
const expiry = new Date;
expiry.setFullYear(expiry.getFullYear() + 1);
const token = await request.post('/local/login')
.send({ username })
.then((response)=>{
return response.body;
})
.catch((err)=>{
console.warn(err);
});
if(!token) return;
document.cookie = `nc_session=${token};expires=${expiry};path=/;samesite=lax;${window.domain ? `domain=${window.domain}` : ''}`;
window.location.reload(true);
},
render : function(){ render : function(){
// Logged in
if(global.account){ if(global.account){
return <Nav.dropdown> return <Nav.dropdown>
<Nav.item <Nav.item
@@ -64,6 +87,16 @@ const Account = createClass({
</Nav.dropdown>; </Nav.dropdown>;
} }
// Logged out
// LOCAL ONLY
if(global.config.local) {
return <Nav.item color='teal' icon='fas fa-sign-in-alt' onClick={this.localLogin}>
login
</Nav.item>;
};
// Logged out
// Production site
return <Nav.item href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`} color='teal' icon='fas fa-sign-in-alt'> return <Nav.item href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`} color='teal' icon='fas fa-sign-in-alt'>
login login
</Nav.item>; </Nav.item>;

View File

@@ -31,7 +31,7 @@ const BrewItem = createClass({
if(!confirm('Are you REALLY sure? You will lose editor access to this document.')) return; if(!confirm('Are you REALLY sure? You will lose editor access to this document.')) return;
} }
request.delete(`/api/${this.props.brew.googleId}${this.props.brew.editId}`) request.delete(`/api/${this.props.brew.googleId ?? ''}${this.props.brew.editId}`)
.send() .send()
.end(function(err, res){ .end(function(err, res){
location.reload(); location.reload();

View File

@@ -352,7 +352,7 @@ const EditPage = createClass({
const title = `${this.props.brew.title} ${systems}`; const title = `${this.props.brew.title} ${systems}`;
const text = `Hey guys! I've been working on this homebrew. I'd love your feedback. Check it out. const text = `Hey guys! I've been working on this homebrew. I'd love your feedback. Check it out.
**[Homebrewery Link](https://homebrewery.naturalcrit.com/share/${shareLink})**`; **[Homebrewery Link](${global.config.publicUrl}/share/${shareLink})**`;
return `https://www.reddit.com/r/UnearthedArcana/submit?title=${encodeURIComponent(title)}&text=${encodeURIComponent(text)}`; return `https://www.reddit.com/r/UnearthedArcana/submit?title=${encodeURIComponent(title)}&text=${encodeURIComponent(text)}`;
}, },
@@ -387,7 +387,7 @@ const EditPage = createClass({
<Nav.item color='blue' href={`/share/${shareLink}`}> <Nav.item color='blue' href={`/share/${shareLink}`}>
view view
</Nav.item> </Nav.item>
<Nav.item color='blue' onClick={()=>{navigator.clipboard.writeText(`https://homebrewery.naturalcrit.com/share/${shareLink}`);}}> <Nav.item color='blue' onClick={()=>{navigator.clipboard.writeText(`${global.config.publicUrl}/share/${shareLink}`);}}>
copy url copy url
</Nav.item> </Nav.item>
<Nav.item color='blue' href={this.getRedditLink()} newTab={true} rel='noopener noreferrer'> <Nav.item color='blue' href={this.getRedditLink()} newTab={true} rel='noopener noreferrer'>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@@ -1,4 +1,6 @@
module.exports = async(name, title = '', props = {})=>{ module.exports = async(name, title = '', props = {})=>{
const HOMEBREWERY_PUBLIC_URL=props.config.publicUrl;
return ` return `
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
@@ -7,6 +9,13 @@ module.exports = async(name, title = '', props = {})=>{
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" /> <link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
<link href=${`/${name}/bundle.css`} rel='stylesheet' /> <link href=${`/${name}/bundle.css`} rel='stylesheet' />
<link rel="icon" href="/assets/homebrew/favicon.ico" type="image/x-icon" /> <link rel="icon" href="/assets/homebrew/favicon.ico" type="image/x-icon" />
<meta property="og:title" content="${props.brew?.title || 'Homebrewery - Untitled Brew'}">
<meta property="og:url" content="${HOMEBREWERY_PUBLIC_URL}/${props.brew?.shareId ? `share/${props.brew.shareId}` : ''}">
<meta property="og:image" content="${props.brew?.thumbnail || `${HOMEBREWERY_PUBLIC_URL}/thumbnail.png`}">
<meta property="og:description" content="${props.brew?.description || 'No description.'}">
<meta property="og:site_name" content="The Homebrewery - Make your Homebrew content look legit!">
<meta property="og:type" content="article">
<meta name="twitter:card" content="summary_large_image">
<title>${title.length ? `${title} - The Homebrewery`: 'The Homebrewery - NaturalCrit'}</title> <title>${title.length ? `${title} - The Homebrewery`: 'The Homebrewery - NaturalCrit'}</title>
</head> </head>
<body> <body>

View File

@@ -3,5 +3,7 @@
"naturalcrit_url" : "local.naturalcrit.com:8010", "naturalcrit_url" : "local.naturalcrit.com:8010",
"secret" : "secret", "secret" : "secret",
"web_port" : 8000, "web_port" : 8000,
"enable_v3" : true "enable_v3" : true,
"local_environments" : ["docker", "local"],
"publicUrl" : "https://homebrewery.naturalcrit.com"
} }

4283
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -51,32 +51,32 @@
] ]
}, },
"dependencies": { "dependencies": {
"@babel/core": "^7.17.8", "@babel/core": "^7.17.10",
"@babel/plugin-transform-runtime": "^7.17.0", "@babel/plugin-transform-runtime": "^7.17.10",
"@babel/preset-env": "^7.16.11", "@babel/preset-env": "^7.17.10",
"@babel/preset-react": "^7.16.7", "@babel/preset-react": "^7.16.7",
"body-parser": "^1.19.2", "body-parser": "^1.20.0",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"codemirror": "^5.65.2", "codemirror": "^5.65.3",
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
"create-react-class": "^15.7.0", "create-react-class": "^15.7.0",
"dedent-tabs": "^0.10.1", "dedent-tabs": "^0.10.1",
"express": "^4.17.3", "express": "^4.18.1",
"express-async-handler": "^1.2.0", "express-async-handler": "^1.2.0",
"express-static-gzip": "2.1.5", "express-static-gzip": "2.1.5",
"fs-extra": "10.0.1", "fs-extra": "10.1.0",
"googleapis": "100.0.0", "googleapis": "100.0.0",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"jwt-simple": "^0.5.6", "jwt-simple": "^0.5.6",
"less": "^3.13.1", "less": "^3.13.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"marked": "4.0.12", "marked": "4.0.14",
"marked-extended-tables": "^1.0.3", "marked-extended-tables": "^1.0.3",
"markedLegacy": "npm:marked@^0.3.19", "markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.29.1", "moment": "^2.29.3",
"mongoose": "^6.2.9", "mongoose": "^6.3.1",
"nanoid": "3.3.2", "nanoid": "3.3.4",
"nconf": "^0.11.3", "nconf": "^0.12.0",
"query-string": "7.1.1", "query-string": "7.1.1",
"react": "^16.14.0", "react": "^16.14.0",
"react-dom": "^16.14.0", "react-dom": "^16.14.0",
@@ -87,9 +87,9 @@
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git" "vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^8.12.0", "eslint": "^8.15.0",
"eslint-plugin-react": "^7.29.4", "eslint-plugin-react": "^7.29.4",
"jest": "^27.5.1", "jest": "^28.0.3",
"supertest": "^6.2.2" "supertest": "^6.2.3"
} }
} }

View File

@@ -195,7 +195,20 @@ app.get('/download/:id', asyncHandler(async (req, res)=>{
app.get('/user/:username', async (req, res, next)=>{ app.get('/user/:username', async (req, res, next)=>{
const ownAccount = req.account && (req.account.username == req.params.username); const ownAccount = req.account && (req.account.username == req.params.username);
let brews = await HomebrewModel.getByUser(req.params.username, ownAccount) const fields = [
'title',
'pageCount',
'description',
'authors',
'views',
'shareId',
'editId',
'createdAt',
'updatedAt',
'lastViewed'
];
let brews = await HomebrewModel.getByUser(req.params.username, ownAccount, fields)
.catch((err)=>{ .catch((err)=>{
console.log(err); console.log(err);
}); });
@@ -260,9 +273,31 @@ app.get('/print/:id', asyncHandler(async (req, res, next)=>{
return next(); return next();
})); }));
const nodeEnv = config.get('node_env');
const isLocalEnvironment = config.get('local_environments').includes(nodeEnv);
// Local only
if(isLocalEnvironment){
// Login
app.post('/local/login', (req, res)=>{
const username = req.body.username;
if(!username) return;
const payload = jwt.encode({ username: username, issued: new Date }, config.get('secret'));
return res.json(payload);
});
}
//Render the page //Render the page
const templateFn = require('./../client/template.js'); const templateFn = require('./../client/template.js');
app.use((req, res)=>{ app.use((req, res)=>{
// Create configuration object
const configuration = {
local : isLocalEnvironment,
publicUrl : config.get('publicUrl') ?? '',
environment : nodeEnv
};
const props = { const props = {
version : require('./../package.json').version, version : require('./../package.json').version,
url : req.originalUrl, url : req.originalUrl,
@@ -270,7 +305,8 @@ app.use((req, res)=>{
brews : req.brews, brews : req.brews,
googleBrews : req.googleBrews, googleBrews : req.googleBrews,
account : req.account, account : req.account,
enable_v3 : config.get('enable_v3') enable_v3 : config.get('enable_v3'),
config : configuration
}; };
const title = req.brew ? req.brew.title : ''; const title = req.brew ? req.brew.title : '';
templateFn('homebrew', title, props) templateFn('homebrew', title, props)

View File

@@ -126,7 +126,8 @@ const GoogleActions = {
views : parseInt(file.properties.views), views : parseInt(file.properties.views),
tags : '', tags : '',
published : file.properties.published ? file.properties.published == 'true' : false, published : file.properties.published ? file.properties.published == 'true' : false,
systems : [] systems : [],
thumbnail : file.properties.thumbnail
}; };
}); });
return brews; return brews;
@@ -147,7 +148,8 @@ const GoogleActions = {
renderer : brew.renderer, renderer : brew.renderer,
tags : brew.tags, tags : brew.tags,
pageCount : brew.pageCount, pageCount : brew.pageCount,
systems : brew.systems.join() systems : brew.systems.join(),
thumbnail : brew.thumbnail
} }
}, },
media : { media : {
@@ -185,7 +187,8 @@ const GoogleActions = {
'title' : brew.title, 'title' : brew.title,
'views' : '0', 'views' : '0',
'pageCount' : brew.pageCount, 'pageCount' : brew.pageCount,
'renderer' : brew.renderer || 'legacy' 'renderer' : brew.renderer || 'legacy',
'thumbnail' : brew.thumbnail || ''
} }
}; };
@@ -286,6 +289,7 @@ const GoogleActions = {
views : parseInt(obj.data.properties.views) || 0, //brews with no view parameter will return undefined views : parseInt(obj.data.properties.views) || 0, //brews with no view parameter will return undefined
version : parseInt(obj.data.properties.version) || 0, version : parseInt(obj.data.properties.version) || 0,
renderer : obj.data.properties.renderer ? obj.data.properties.renderer : 'legacy', renderer : obj.data.properties.renderer ? obj.data.properties.renderer : 'legacy',
thumbnail : obj.data.properties.thumbnail || '',
gDrive : true, gDrive : true,
googleId : id googleId : id

View File

@@ -17,6 +17,7 @@ const HomebrewSchema = mongoose.Schema({
renderer : { type: String, default: '' }, renderer : { type: String, default: '' },
authors : [String], authors : [String],
published : { type: Boolean, default: false }, published : { type: Boolean, default: false },
thumbnail : { type: String, default: '' },
createdAt : { type: Date, default: Date.now }, createdAt : { type: Date, default: Date.now },
updatedAt : { type: Date, default: Date.now }, updatedAt : { type: Date, default: Date.now },
@@ -36,9 +37,9 @@ HomebrewSchema.statics.increaseView = async function(query) {
return brew; return brew;
}; };
HomebrewSchema.statics.get = function(query){ HomebrewSchema.statics.get = function(query, fields=null){
return new Promise((resolve, reject)=>{ return new Promise((resolve, reject)=>{
Homebrew.find(query, (err, brews)=>{ Homebrew.find(query, fields, null, (err, brews)=>{
if(err || !brews.length) return reject('Can not find brew'); if(err || !brews.length) return reject('Can not find brew');
if(!_.isNil(brews[0].textBin)) { // Uncompress zipped text field if(!_.isNil(brews[0].textBin)) { // Uncompress zipped text field
unzipped = zlib.inflateRawSync(brews[0].textBin); unzipped = zlib.inflateRawSync(brews[0].textBin);
@@ -51,13 +52,13 @@ HomebrewSchema.statics.get = function(query){
}); });
}; };
HomebrewSchema.statics.getByUser = function(username, allowAccess=false){ HomebrewSchema.statics.getByUser = function(username, allowAccess=false, fields=null){
return new Promise((resolve, reject)=>{ return new Promise((resolve, reject)=>{
const query = { authors: username, published: true }; const query = { authors: username, published: true };
if(allowAccess){ if(allowAccess){
delete query.published; delete query.published;
} }
Homebrew.find(query).lean().exec((err, brews)=>{ //lean() converts results to JSObjects Homebrew.find(query, fields).lean().exec((err, brews)=>{ //lean() converts results to JSObjects
if(err) return reject('Can not find brew'); if(err) return reject('Can not find brew');
return resolve(brews); return resolve(brews);
}); });

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -77,3 +77,32 @@
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }
/* Cover Page */
@font-face {
font-family: NodestoCapsCondensed;
src: url('../fonts/5e/Nodesto Caps Condensed.woff2');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: NodestoCapsCondensed;
src: url('../fonts/5e/Nodesto Caps Condensed Bold.woff2');
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: NodestoCapsCondensed;
src: url('../fonts/5e/Nodesto Caps Condensed Italic.woff2');
font-weight: normal;
font-style: italic;
}
@font-face {
font-family: NodestoCapsCondensed;
src: url('../fonts/5e/Nodesto Caps Condensed Bold Italic.woff2');
font-weight: bold;
font-style: italic;
}