0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-25 16:15:53 +00:00

Merge commit '2661e2cf' into PRODUCTION

This commit is contained in:
Trevor Buckner
2021-07-12 21:44:17 -04:00
20 changed files with 1925 additions and 2577 deletions

View File

@@ -6,6 +6,27 @@ h5 {
# changelog
### Saturday, 28/6/2021 - v2.13.1
- Fixed the issue with new brews not saving!
### Saturday, 26/6/2021 - v2.13.0
- "Share to Reddit" button now works with Google brews
- Downloading or viewing the source of your brew will now show the contents of the Style tab at the top of the document in a backtick code fence like this:
\`\`\`css
myStyle {color: black}
\`\`\`
##### G-Ambatte :
- New **Download**, **View**, and **Clone to New** buttons in the "Source" dropdown on the Share page.
- Pasting your brew into a "New" page and saving will transfer any CSS in the code fence to the Style tab.
- Unsaved work in the New page Style tab is now cached to your browser storage if you navigate away.
### Thursday, 10/6/2021 - v2.12.0
- New "style" tab to better organize custom CSS in preparation for new themes and sharable styles.

View File

@@ -23,7 +23,7 @@ const NotificationPopup = createClass({
psa : function(){
return <li key='psa'>
<em>Google Drive Integration!</em> <br />
We have added Google Drive integration to the Homebrewery! <a target='_blank' href='http://naturalcrit.com/login'>Sign in</a> with
We have added Google Drive integration to the Homebrewery! <a target='_blank' href='https://www.naturalcrit.com/login'>Sign in</a> with
your Google account to link it with your Homebrewery profile. A new button in the Edit page will let you transfer your file to your personal
Google Drive storage, and Google will keep a backup of each version! No more lost work surprises!
<br /><br />

View File

@@ -3,17 +3,26 @@ const React = require('react');
const createClass = require('create-react-class');
const _ = require('lodash');
const cx = require('classnames');
const dedent = require('dedent-tabs').default;
const CodeEditor = require('naturalcrit/codeEditor/codeEditor.jsx');
const SnippetBar = require('./snippetbar/snippetbar.jsx');
const MetadataEditor = require('./metadataEditor/metadataEditor.jsx');
const SNIPPETBAR_HEIGHT = 25;
const DEFAULT_STYLE_TEXT = dedent`
/*=======--- Example CSS styling ---=======*/
/* Any CSS here will apply to your document! */
.myExampleClass {
color: black;
}`;
const splice = function(str, index, inject){
return str.slice(0, index) + inject + str.slice(index);
};
const SNIPPETBAR_HEIGHT = 25;
const Editor = createClass({
getDefaultProps : function() {
@@ -176,7 +185,7 @@ const Editor = createClass({
return <CodeEditor key='style'
ref='codeEditor'
language='css'
value={this.props.brew.style}
value={this.props.brew.style ?? DEFAULT_STYLE_TEXT}
onChange={this.props.onStyleChange} />;
}
if(this.isMeta()){

View File

@@ -67,10 +67,12 @@ const MetadataEditor = createClass({
getRedditLink : function(){
const meta = this.props.metadata;
const shareLink = (meta.googleId || '') + meta.shareId;
const title = `${meta.title} [${meta.systems.join(' ')}]`;
const text = `Hey guys! I've been working on this homebrew. I'd love your feedback. Check it out.
**[Homebrewery Link](http://homebrewery.naturalcrit.com/share/${meta.shareId})**`;
**[Homebrewery Link](https://homebrewery.naturalcrit.com/share/${shareLink})**`;
return `https://www.reddit.com/r/UnearthedArcana/submit?title=${encodeURIComponent(title)}&text=${encodeURIComponent(text)}`;
},

View File

@@ -1,7 +1,7 @@
@import 'naturalcrit/styles/core.less';
.homebrew{
height : 100%;
.page{
.sitePage{
display : flex;
height : 100%;
background-color : @steel;

View File

@@ -25,7 +25,7 @@ const Account = createClass({
</Nav.item>;
}
return <Nav.item href={`http://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
</Nav.item>;
}

View File

@@ -304,7 +304,7 @@ const EditPage = createClass({
You must be signed in to a Google account to transfer
between the homebrewery and Google Drive!
<a target='_blank' rel='noopener noreferrer'
href={`http://naturalcrit.com/login?redirect=${this.state.url}`}>
href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}>
<div className='confirm'>
Sign In
</div>
@@ -332,7 +332,7 @@ const EditPage = createClass({
You must be signed in to a Google account
to save this to<br />Google Drive!<br />
<a target='_blank' rel='noopener noreferrer'
href={`http://naturalcrit.com/login?redirect=${this.state.url}`}>
href={`https://www.naturalcrit.com/login?redirect=${this.state.url}`}>
<div className='confirm'>
Sign In
</div>
@@ -349,7 +349,7 @@ const EditPage = createClass({
<div className='errorContainer'>
Looks like there was a problem saving. <br />
Report the issue <a target='_blank' rel='noopener noreferrer'
href={`https://github.com/naturalcrt/naturalcrit/issues/new?body=${encodeURIComponent(errMsg)}`}>
href={`https://github.com/naturalcrit/homebrewery/issues/new?body=${encodeURIComponent(errMsg)}`}>
here
</a>.
</div>
@@ -406,7 +406,7 @@ const EditPage = createClass({
},
render : function(){
return <div className='editPage page'>
return <div className='editPage sitePage'>
<Meta name='robots' content='noindex, nofollow' />
{this.renderNavbar()}

View File

@@ -23,7 +23,7 @@ const ErrorPage = createClass({
text : '# Oops \n We could not find a brew with that id. **Sorry!**',
render : function(){
return <div className='errorPage page'>
return <div className='errorPage sitePage'>
<Navbar ver={this.props.ver}>
<Nav.section>
<Nav.item className='errorTitle'>

View File

@@ -72,7 +72,7 @@ const HomePage = createClass({
},
render : function(){
return <div className='homePage page'>
return <div className='homePage sitePage'>
<Meta name='google-site-verification' content='NwnAQSSJZzAT7N-p5MY6ydQ7Njm67dtbu73ZSyE5Fy4' />
{this.renderNavbar()}

View File

@@ -1,9 +1,9 @@
/*eslint max-lines: ["warn", {"max": 300, "skipBlankLines": true, "skipComments": true}]*/
require('./newPage.less');
const React = require('react');
const createClass = require('create-react-class');
const _ = require('lodash');
const request = require('superagent');
const dedent = require('dedent-tabs').default;
const Markdown = require('naturalcrit/markdown.js');
@@ -17,20 +17,15 @@ const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
const Editor = require('../../editor/editor.jsx');
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
const KEY = 'homebrewery-new';
const BREWKEY = 'homebrewery-new';
const STYLEKEY = 'homebrewery-new-style';
const NewPage = createClass({
getDefaultProps : function() {
return {
brew : {
text : '',
style : dedent`
/*=======--- Example CSS styling ---=======*/
/* Any CSS here will apply to your document! */
.myExampleClass {
color: black;
}`,
text : '',
shareId : null,
editId : null,
createdAt : null,
@@ -51,7 +46,6 @@ const NewPage = createClass({
return {
brew : {
text : this.props.brew.text || '',
style : this.props.brew.style || '',
gDrive : false,
title : this.props.brew.title || '',
description : this.props.brew.description || '',
@@ -70,10 +64,15 @@ const NewPage = createClass({
},
componentDidMount : function() {
const storage = localStorage.getItem(KEY);
if(!this.props.brew.text && storage){
const brewStorage = localStorage.getItem(BREWKEY);
const styleStorage = localStorage.getItem(STYLEKEY);
if(!this.props.brew.text || !this.props.brew.style){
this.setState({
brew : { text: storage }
brew : {
text : this.props.brew.text || (brewStorage ?? ''),
style : this.props.brew.style || (styleStorage ?? undefined)
}
});
}
@@ -112,13 +111,14 @@ const NewPage = createClass({
brew : _.merge({}, prevState.brew, { text: text }),
htmlErrors : htmlErrors
}));
localStorage.setItem(KEY, text);
localStorage.setItem(BREWKEY, text);
},
handleStyleChange : function(style){
this.setState((prevState)=>({
brew : _.merge({}, prevState.brew, { style: style }),
}));
localStorage.setItem(STYLEKEY, style);
},
handleMetaChange : function(metadata){
@@ -135,10 +135,18 @@ const NewPage = createClass({
console.log('saving new brew');
let brew = this.state.brew;
// Split out CSS to Style if CSS codefence exists
if(brew.text.startsWith('```css') && brew.text.indexOf('```\n\n') > 0) {
const index = brew.text.indexOf('```\n\n');
brew.style = `${brew.style ? `${brew.style}\n` : ''}${brew.text.slice(7, index - 1)}`;
brew.text = brew.text.slice(index + 5);
};
if(this.state.saveGoogle) {
const res = await request
.post('/api/newGoogle/')
.send(this.state.brew)
.send(brew)
.catch((err)=>{
console.log(err.status === 401
? 'Not signed in!'
@@ -147,12 +155,13 @@ const NewPage = createClass({
return;
});
const brew = res.body;
localStorage.removeItem(KEY);
brew = res.body;
localStorage.removeItem(BREWKEY);
localStorage.removeItem(STYLEKEY);
window.location = `/edit/${brew.googleId}${brew.editId}`;
} else {
request.post('/api')
.send(this.state.brew)
.send(brew)
.end((err, res)=>{
if(err){
this.setState({
@@ -161,8 +170,9 @@ const NewPage = createClass({
return;
}
window.onbeforeunload = function(){};
const brew = res.body;
localStorage.removeItem(KEY);
brew = res.body;
localStorage.removeItem(BREWKEY);
localStorage.removeItem(STYLEKEY);
window.location = `/edit/${brew.editId}`;
});
}
@@ -181,7 +191,7 @@ const NewPage = createClass({
},
print : function(){
localStorage.setItem('print', this.state.brew.text);
localStorage.setItem('print', `<style>\n${this.state.brew.style}\n</style>\n\n${this.state.brew.text}`);
window.open('/print?dialog=true&local=print', '_blank');
},
@@ -209,7 +219,7 @@ const NewPage = createClass({
},
render : function(){
return <div className='newPage page'>
return <div className='newPage sitePage'>
{this.renderNavbar()}
<div className='content'>
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>

View File

@@ -39,7 +39,7 @@ const PrintPage = createClass({
if(this.props.brew.renderer == 'legacy') {
return _.map(this.state.brewText.split('\\page'), (page, index)=>{
return <div
className='phb'
className='phb page'
id={`p${index + 1}`}
dangerouslySetInnerHTML={{ __html: MarkdownLegacy.render(page) }}
key={index} />;
@@ -47,7 +47,7 @@ const PrintPage = createClass({
} else {
return _.map(this.state.brewText.split(/^\\page/gm), (page, index)=>{
return <div
className='phb3'
className='phb3 page'
id={`p${index + 1}`}
dangerouslySetInnerHTML={{ __html: Markdown.render(page) }}
key={index} />;

View File

@@ -29,6 +29,12 @@ const SharePage = createClass({
};
},
getInitialState : function() {
return {
showDropdown : false
};
},
componentDidMount : function() {
document.addEventListener('keydown', this.handleControlKeys);
},
@@ -51,8 +57,30 @@ const SharePage = createClass({
this.props.brew.shareId;
},
handleDropdown : function(show){
this.setState({
showDropdown : show
});
},
renderDropdown : function(){
if(!this.state.showDropdown) return null;
return <div className='dropdown'>
<a href={`/source/${this.processShareId()}`} className='item'>
view
</a>
<a href={`/download/${this.processShareId()}`} className='item'>
download
</a>
<a href={`/new/${this.processShareId()}`} className='item'>
clone to new
</a>
</div>;
},
render : function(){
return <div className='sharePage page'>
return <div className='sharePage sitePage'>
<Meta name='robots' content='noindex, nofollow' />
<Navbar>
<Nav.section>
@@ -61,11 +89,11 @@ const SharePage = createClass({
<Nav.section>
<PrintLink shareId={this.processShareId()} />
<Nav.item href={`/source/${this.processShareId()}`} color='teal' icon='fas fa-code'>
<Nav.item icon='fas fa-code' color='red' className='source'
onMouseEnter={()=>this.handleDropdown(true)}
onMouseLeave={()=>this.handleDropdown(false)}>
source
</Nav.item>
<Nav.item href={`/download/${this.processShareId()}`} color='red' icon='fas fa-download'>
download
{this.renderDropdown()}
</Nav.item>
<RecentNavItem brew={this.props.brew} storageKey='view' />
<Account />

View File

@@ -2,4 +2,49 @@
.content{
overflow-y : hidden;
}
.source.navItem{
position : relative;
.dropdown{
position : absolute;
top : 28px;
left : 0px;
z-index : 10000;
width : 100%;
h4{
display : block;
box-sizing : border-box;
padding : 5px 0px;
background-color : #333;
font-size : 0.8em;
color : #bbb;
text-align : center;
border-top : 1px solid #888;
&:nth-of-type(1){ background-color: darken(@teal, 20%); }
&:nth-of-type(2){ background-color: darken(@purple, 30%); }
}
.item{
.animate(background-color);
position : relative;
display : block;
width : 100%;
vertical-align : middle;
padding : 13px 5px;
box-sizing : border-box;
background-color : #333;
color : white;
text-decoration : none;
border-top : 1px solid #888;
&:hover{
background-color : @blue;
}
.title{
display : inline-block;
overflow : hidden;
width : 100%;
text-overflow : ellipsis;
white-space : nowrap;
}
}
}
}
}

View File

@@ -52,7 +52,7 @@ const UserPage = createClass({
render : function(){
const brews = this.getSortedBrews();
return <div className='userPage page'>
return <div className='userPage sitePage'>
<Navbar>
<Nav.section>
<NewBrew />

4238
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": "2.12.0",
"version": "2.13.1",
"engines": {
"node": "14.15.x"
},
@@ -40,32 +40,32 @@
]
},
"dependencies": {
"@babel/core": "^7.14.3",
"@babel/core": "^7.14.6",
"@babel/plugin-transform-runtime": "^7.14.5",
"@babel/preset-env": "^7.14.4",
"@babel/preset-react": "^7.13.13",
"@babel/preset-env": "^7.14.7",
"@babel/preset-react": "^7.14.5",
"body-parser": "^1.19.0",
"classnames": "^2.3.1",
"codemirror": "^5.61.1",
"codemirror": "^5.62.0",
"cookie-parser": "^1.4.5",
"create-react-class": "^15.7.0",
"dedent-tabs": "^0.9.0",
"express": "^4.17.1",
"express-async-handler": "^1.1.4",
"express-static-gzip": "2.1.1",
"fs-extra": "9.1.0",
"googleapis": "75.0.0",
"fs-extra": "10.0.0",
"googleapis": "78.0.0",
"jwt-simple": "^0.5.6",
"less": "^3.13.1",
"lodash": "^4.17.21",
"marked": "2.0.6",
"marked": "2.1.3",
"markedLegacy": "npm:marked@^0.3.19",
"moment": "^2.29.1",
"mongoose": "^5.12.12",
"mongoose": "^5.12.15",
"nanoid": "3.1.23",
"nconf": "^0.11.2",
"nconf": "^0.11.3",
"prop-types": "15.7.2",
"query-string": "7.0.0",
"query-string": "7.0.1",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"react-frame-component": "4.1.3",
@@ -75,8 +75,8 @@
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
},
"devDependencies": {
"eslint": "^7.27.0",
"eslint-plugin-react": "^7.23.2",
"eslint": "^7.29.0",
"eslint-plugin-react": "^7.24.0",
"pico-check": "^2.1.3"
}
}

View File

@@ -9,11 +9,12 @@ const GoogleActions = require('./server/googleActions.js');
const serveCompressedStaticAssets = require('./server/static-assets.mv.js');
const sanitizeFilename = require('sanitize-filename');
const asyncHandler = require('express-async-handler');
const dedent = require('dedent-tabs').default;
const brewAccessTypes = ['edit', 'share', 'raw'];
//Get the brew object from the HB database or Google Drive
const getBrewFromId = asyncHandler(async (id, accessType)=>{
if(accessType !== 'edit' && accessType !== 'share')
if(!brewAccessTypes.includes(accessType))
throw ('Invalid Access Type when getting brew');
let brew;
if(id.length > 12) {
@@ -22,23 +23,19 @@ const getBrewFromId = asyncHandler(async (id, accessType)=>{
brew = await GoogleActions.readFileMetadata(config.get('google_api_key'), googleId, id, accessType);
} else {
brew = await HomebrewModel.get(accessType == 'edit' ? { editId: id } : { shareId: id });
brew = brew.toObject();
brew = brew.toObject(); // Convert MongoDB object to standard Javascript Object
}
brew = sanitizeBrew(brew, accessType === 'edit' ? false : true);
//Split brew.text into text and style
//unless the Access Type is RAW, in which case return immediately
if(accessType == 'raw') {
return brew;
}
if(brew.text.startsWith('```css')) {
const index = brew.text.indexOf('```\n\n');
brew.style = brew.text.slice(7, index - 1);
brew.text = brew.text.slice(index + 5);
} else {
brew.style = dedent`
/*=======--- Example CSS styling ---=======*/
/* Any CSS here will apply to your document! */
.myExampleClass {
color: black;
}`;
}
return brew;
});
@@ -110,7 +107,7 @@ app.get('/robots.txt', (req, res)=>{
//Source page
app.get('/source/:id', asyncHandler(async (req, res)=>{
const brew = await getBrewFromId(req.params.id, 'share');
const brew = await getBrewFromId(req.params.id, 'raw');
const replaceStrings = { '&': '&amp;', '<': '&lt;', '>': '&gt;' };
let text = brew.text;
@@ -123,7 +120,7 @@ app.get('/source/:id', asyncHandler(async (req, res)=>{
//Download brew source page
app.get('/download/:id', asyncHandler(async (req, res)=>{
const brew = await getBrewFromId(req.params.id, 'share');
const brew = await getBrewFromId(req.params.id, 'raw');
const prefix = 'HB - ';
let fileName = sanitizeFilename(`${prefix}${brew.title}`).replaceAll(' ', '');

View File

@@ -20,10 +20,12 @@ const getGoodBrewTitle = (text)=>{
};
const mergeBrewText = (text, style)=>{
text = `\`\`\`css\n` +
`${style}\n` +
`\`\`\`\n\n` +
`${text}`;
if(typeof style !== 'undefined') {
text = `\`\`\`css\n` +
`${style}\n` +
`\`\`\`\n\n` +
`${text}`;
}
return text;
};

View File

@@ -56,7 +56,7 @@ HomebrewSchema.statics.getByUser = function(username, allowAccess=false){
if(allowAccess){
delete query.published;
}
Homebrew.find(query, (err, brews)=>{
Homebrew.find(query).lean().exec((err, brews)=>{ //lean() converts results to JSObjects
if(err) return reject('Can not find brew');
return resolve(brews);
});

View File

@@ -17,7 +17,7 @@ const Nav = {
}
}),
logo : function(){
return <a className='navLogo' href='http://naturalcrit.com'>
return <a className='navLogo' href='https://www.naturalcrit.com'>
<NaturalCritIcon />
<span className='name'>
Natural<span className='crit'>Crit</span>