mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-01-06 12:12:42 +00:00
Merge branch 'master' into pr/1353
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
FROM node:8
|
FROM node:14.15
|
||||||
|
|
||||||
ENV NODE_ENV=docker
|
ENV NODE_ENV=docker
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,11 @@ h5 {
|
|||||||
|
|
||||||
# changelog
|
# changelog
|
||||||
|
|
||||||
|
### Thursday, 10/6/2021 - v2.12.0
|
||||||
|
|
||||||
|
- New "style" tab to better organize custom CSS in preparation for new themes and sharable styles.
|
||||||
|
- Your own Google brews will no longer show up in the list when viewing someone else's profile.
|
||||||
|
|
||||||
### Saturday, 02/5/2021 - v2.11.2
|
### Saturday, 02/5/2021 - v2.11.2
|
||||||
|
|
||||||
- Fix for edge case where brews could accidentally transfer from Google Drive back to Homebrewery.
|
- Fix for edge case where brews could accidentally transfer from Google Drive back to Homebrewery.
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const BrewRenderer = createClass({
|
|||||||
getDefaultProps : function() {
|
getDefaultProps : function() {
|
||||||
return {
|
return {
|
||||||
text : '',
|
text : '',
|
||||||
|
style : '',
|
||||||
renderer : 'legacy',
|
renderer : 'legacy',
|
||||||
errors : []
|
errors : []
|
||||||
};
|
};
|
||||||
@@ -123,9 +124,9 @@ const BrewRenderer = createClass({
|
|||||||
|
|
||||||
renderPage : function(pageText, index){
|
renderPage : function(pageText, index){
|
||||||
if(this.props.renderer == 'legacy')
|
if(this.props.renderer == 'legacy')
|
||||||
return <div className='phb' id={`p${index + 1}`} dangerouslySetInnerHTML={{ __html: MarkdownLegacy.render(pageText) }} key={index} />;
|
return <div className='phb page' id={`p${index + 1}`} dangerouslySetInnerHTML={{ __html: MarkdownLegacy.render(pageText) }} key={index} />;
|
||||||
else
|
else
|
||||||
return <div className='phb3' id={`p${index + 1}`} dangerouslySetInnerHTML={{ __html: Markdown.render(pageText) }} key={index} />;
|
return <div className='phb3 page' id={`p${index + 1}`} dangerouslySetInnerHTML={{ __html: Markdown.render(pageText) }} key={index} />;
|
||||||
},
|
},
|
||||||
|
|
||||||
renderPages : function(){
|
renderPages : function(){
|
||||||
@@ -187,6 +188,10 @@ const BrewRenderer = createClass({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='pages' ref='pages'>
|
<div className='pages' ref='pages'>
|
||||||
|
{/* Apply CSS from Style tab */}
|
||||||
|
<div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style> ${this.props.style} </style>` }} />
|
||||||
|
|
||||||
|
{/* Render pages from Markdown tab */}
|
||||||
{this.state.isMounted
|
{this.state.isMounted
|
||||||
? this.renderPages()
|
? this.renderPages()
|
||||||
: null}
|
: null}
|
||||||
|
|||||||
@@ -19,57 +19,62 @@ const Editor = createClass({
|
|||||||
getDefaultProps : function() {
|
getDefaultProps : function() {
|
||||||
return {
|
return {
|
||||||
brew : {
|
brew : {
|
||||||
text : ''
|
text : '',
|
||||||
|
style : ''
|
||||||
},
|
},
|
||||||
onChange : ()=>{},
|
|
||||||
|
|
||||||
onMetadataChange : ()=>{},
|
onTextChange : ()=>{},
|
||||||
showMetaButton : true,
|
onStyleChange : ()=>{},
|
||||||
renderer : 'legacy'
|
onMetaChange : ()=>{},
|
||||||
|
|
||||||
|
renderer : 'legacy'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getInitialState : function() {
|
getInitialState : function() {
|
||||||
return {
|
return {
|
||||||
showMetadataEditor : false
|
view : 'text' //'text', 'style', 'meta'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
cursorPosition : {
|
|
||||||
line : 0,
|
isText : function() {return this.state.view == 'text';},
|
||||||
ch : 0
|
isStyle : function() {return this.state.view == 'style';},
|
||||||
},
|
isMeta : function() {return this.state.view == 'meta';},
|
||||||
|
|
||||||
componentDidMount : function() {
|
componentDidMount : function() {
|
||||||
this.updateEditorSize();
|
this.updateEditorSize();
|
||||||
this.highlightCustomMarkdown();
|
this.highlightCustomMarkdown();
|
||||||
window.addEventListener('resize', this.updateEditorSize);
|
window.addEventListener('resize', this.updateEditorSize);
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount : function() {
|
componentWillUnmount : function() {
|
||||||
window.removeEventListener('resize', this.updateEditorSize);
|
window.removeEventListener('resize', this.updateEditorSize);
|
||||||
},
|
},
|
||||||
|
|
||||||
updateEditorSize : function() {
|
updateEditorSize : function() {
|
||||||
let paneHeight = this.refs.main.parentNode.clientHeight;
|
if(this.refs.codeEditor) {
|
||||||
paneHeight -= SNIPPETBAR_HEIGHT + 1;
|
let paneHeight = this.refs.main.parentNode.clientHeight;
|
||||||
this.refs.codeEditor.codeMirror.setSize(null, paneHeight);
|
paneHeight -= SNIPPETBAR_HEIGHT + 1;
|
||||||
|
this.refs.codeEditor.codeMirror.setSize(null, paneHeight);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
handleTextChange : function(text){
|
|
||||||
this.props.onChange(text);
|
|
||||||
},
|
|
||||||
handleCursorActivty : function(curpos){
|
|
||||||
this.cursorPosition = curpos;
|
|
||||||
},
|
|
||||||
handleInject : function(injectText){
|
handleInject : function(injectText){
|
||||||
const lines = this.props.brew.text.split('\n');
|
const text = (this.isText() ? this.props.brew.text : this.props.brew.style);
|
||||||
lines[this.cursorPosition.line] = splice(lines[this.cursorPosition.line], this.cursorPosition.ch, injectText);
|
|
||||||
|
|
||||||
this.handleTextChange(lines.join('\n'));
|
const lines = text.split('\n');
|
||||||
this.refs.codeEditor.setCursorPosition(this.cursorPosition.line + injectText.split('\n').length, this.cursorPosition.ch + injectText.length);
|
const cursorPos = this.refs.codeEditor.getCursorPosition();
|
||||||
|
lines[cursorPos.line] = splice(lines[cursorPos.line], cursorPos.ch, injectText);
|
||||||
|
|
||||||
|
this.refs.codeEditor.setCursorPosition(cursorPos.line + injectText.split('\n').length, cursorPos.ch + injectText.length);
|
||||||
|
|
||||||
|
if(this.isText()) this.props.onTextChange(lines.join('\n'));
|
||||||
|
if(this.isStyle()) this.props.onStyleChange(lines.join('\n'));
|
||||||
},
|
},
|
||||||
handgleToggle : function(){
|
|
||||||
|
handleViewChange : function(newView){
|
||||||
this.setState({
|
this.setState({
|
||||||
showMetadataEditor : !this.state.showMetadataEditor
|
view : newView
|
||||||
});
|
}, this.updateEditorSize); //TODO: not sure if updateeditorsize needed
|
||||||
},
|
},
|
||||||
|
|
||||||
getCurrentPage : function(){
|
getCurrentPage : function(){
|
||||||
@@ -82,72 +87,73 @@ const Editor = createClass({
|
|||||||
|
|
||||||
highlightCustomMarkdown : function(){
|
highlightCustomMarkdown : function(){
|
||||||
if(!this.refs.codeEditor) return;
|
if(!this.refs.codeEditor) return;
|
||||||
const codeMirror = this.refs.codeEditor.codeMirror;
|
if(this.state.view === 'text') {
|
||||||
|
const codeMirror = this.refs.codeEditor.codeMirror;
|
||||||
|
|
||||||
//reset custom text styles
|
//reset custom text styles
|
||||||
const customHighlights = codeMirror.getAllMarks();
|
const customHighlights = codeMirror.getAllMarks();
|
||||||
for (let i=0;i<customHighlights.length;i++) customHighlights[i].clear();
|
for (let i=0;i<customHighlights.length;i++) customHighlights[i].clear();
|
||||||
|
|
||||||
const lineNumbers = _.reduce(this.props.brew.text.split('\n'), (r, line, lineNumber)=>{
|
const lineNumbers = _.reduce(this.props.brew.text.split('\n'), (r, line, lineNumber)=>{
|
||||||
|
|
||||||
//reset custom line styles
|
//reset custom line styles
|
||||||
codeMirror.removeLineClass(lineNumber, 'background');
|
codeMirror.removeLineClass(lineNumber, 'background');
|
||||||
codeMirror.removeLineClass(lineNumber, 'text');
|
codeMirror.removeLineClass(lineNumber, 'text');
|
||||||
|
|
||||||
// Legacy Codemirror styling
|
// Legacy Codemirror styling
|
||||||
if(this.props.renderer == 'legacy') {
|
if(this.props.renderer == 'legacy') {
|
||||||
if(line.includes('\\page')){
|
if(line.includes('\\page')){
|
||||||
codeMirror.addLineClass(lineNumber, 'background', 'pageLine');
|
codeMirror.addLineClass(lineNumber, 'background', 'pageLine');
|
||||||
r.push(lineNumber);
|
r.push(lineNumber);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// New Codemirror styling for V3 renderer
|
|
||||||
if(this.props.renderer == 'V3') {
|
|
||||||
if(line.startsWith('\\page')){
|
|
||||||
codeMirror.addLineClass(lineNumber, 'background', 'pageLine');
|
|
||||||
r.push(lineNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(line.match(/^\\column$/)){
|
|
||||||
codeMirror.addLineClass(lineNumber, 'text', 'columnSplit');
|
|
||||||
r.push(lineNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Highlight inline spans {{content}}
|
|
||||||
if(line.includes('{{') && line.includes('}}')){
|
|
||||||
const regex = /{{(?:="[\w,\-. ]*"|[^"'\s])*\s*|}}/g;
|
|
||||||
let match;
|
|
||||||
let blockCount = 0;
|
|
||||||
while ((match = regex.exec(line)) != null) {
|
|
||||||
if(match[0].startsWith('{')) {
|
|
||||||
blockCount += 1;
|
|
||||||
} else {
|
|
||||||
blockCount -= 1;
|
|
||||||
}
|
|
||||||
if(blockCount < 0) {
|
|
||||||
blockCount = 0;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
codeMirror.markText({ line: lineNumber, ch: match.index }, { line: lineNumber, ch: match.index + match[0].length }, { className: 'inline-block' });
|
|
||||||
}
|
}
|
||||||
} else if(line.trimLeft().startsWith('{{') || line.trimLeft().startsWith('}}')){
|
|
||||||
// Highlight block divs {{\n Content \n}}
|
|
||||||
let endCh = line.length+1;
|
|
||||||
|
|
||||||
const match = line.match(/^ *{{(?:="[\w,\-. ]*"|[^"'\s])*$|^ *}}$/);
|
|
||||||
if(match)
|
|
||||||
endCh = match.index+match[0].length;
|
|
||||||
codeMirror.markText({ line: lineNumber, ch: 0 }, { line: lineNumber, ch: endCh }, { className: 'block' });
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return r;
|
// New Codemirror styling for V3 renderer
|
||||||
}, []);
|
if(this.props.renderer == 'V3') {
|
||||||
return lineNumbers;
|
if(line.startsWith('\\page')){
|
||||||
|
codeMirror.addLineClass(lineNumber, 'background', 'pageLine');
|
||||||
|
r.push(lineNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(line.match(/^\\column$/)){
|
||||||
|
codeMirror.addLineClass(lineNumber, 'text', 'columnSplit');
|
||||||
|
r.push(lineNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Highlight inline spans {{content}}
|
||||||
|
if(line.includes('{{') && line.includes('}}')){
|
||||||
|
const regex = /{{(?:="[\w,\-. ]*"|[^"'\s])*\s*|}}/g;
|
||||||
|
let match;
|
||||||
|
let blockCount = 0;
|
||||||
|
while ((match = regex.exec(line)) != null) {
|
||||||
|
if(match[0].startsWith('{')) {
|
||||||
|
blockCount += 1;
|
||||||
|
} else {
|
||||||
|
blockCount -= 1;
|
||||||
|
}
|
||||||
|
if(blockCount < 0) {
|
||||||
|
blockCount = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
codeMirror.markText({ line: lineNumber, ch: match.index }, { line: lineNumber, ch: match.index + match[0].length }, { className: 'inline-block' });
|
||||||
|
}
|
||||||
|
} else if(line.trimLeft().startsWith('{{') || line.trimLeft().startsWith('}}')){
|
||||||
|
// Highlight block divs {{\n Content \n}}
|
||||||
|
let endCh = line.length+1;
|
||||||
|
|
||||||
|
const match = line.match(/^ *{{(?:="[\w,\-. ]*"|[^"'\s])*$|^ *}}$/);
|
||||||
|
if(match)
|
||||||
|
endCh = match.index+match[0].length;
|
||||||
|
codeMirror.markText({ line: lineNumber, ch: 0 }, { line: lineNumber, ch: endCh }, { className: 'block' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}, []);
|
||||||
|
return lineNumbers;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
brewJump : function(){
|
brewJump : function(){
|
||||||
const currentPage = this.getCurrentPage();
|
const currentPage = this.getCurrentPage();
|
||||||
window.location.hash = `p${currentPage}`;
|
window.location.hash = `p${currentPage}`;
|
||||||
@@ -158,12 +164,26 @@ const Editor = createClass({
|
|||||||
this.refs.codeEditor.updateSize();
|
this.refs.codeEditor.updateSize();
|
||||||
},
|
},
|
||||||
|
|
||||||
renderMetadataEditor : function(){
|
renderEditor : function(){
|
||||||
if(!this.state.showMetadataEditor) return;
|
if(this.isText()){
|
||||||
return <MetadataEditor
|
return <CodeEditor key='text'
|
||||||
metadata={this.props.brew}
|
ref='codeEditor'
|
||||||
onChange={this.props.onMetadataChange}
|
language='gfm'
|
||||||
/>;
|
value={this.props.brew.text}
|
||||||
|
onChange={this.props.onTextChange} />;
|
||||||
|
}
|
||||||
|
if(this.isStyle()){
|
||||||
|
return <CodeEditor key='style'
|
||||||
|
ref='codeEditor'
|
||||||
|
language='css'
|
||||||
|
value={this.props.brew.style}
|
||||||
|
onChange={this.props.onStyleChange} />;
|
||||||
|
}
|
||||||
|
if(this.isMeta()){
|
||||||
|
return <MetadataEditor
|
||||||
|
metadata={this.props.brew}
|
||||||
|
onChange={this.props.onMetaChange} />;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
@@ -172,25 +192,13 @@ const Editor = createClass({
|
|||||||
<div className='editor' ref='main'>
|
<div className='editor' ref='main'>
|
||||||
<SnippetBar
|
<SnippetBar
|
||||||
brew={this.props.brew}
|
brew={this.props.brew}
|
||||||
|
view={this.state.view}
|
||||||
|
onViewChange={this.handleViewChange}
|
||||||
onInject={this.handleInject}
|
onInject={this.handleInject}
|
||||||
onToggle={this.handgleToggle}
|
showEditButtons={this.props.showEditButtons}
|
||||||
showmeta={this.state.showMetadataEditor}
|
|
||||||
showMetaButton={this.props.showMetaButton}
|
|
||||||
renderer={this.props.renderer} />
|
renderer={this.props.renderer} />
|
||||||
{this.renderMetadataEditor()}
|
|
||||||
<CodeEditor
|
|
||||||
ref='codeEditor'
|
|
||||||
wrap={true}
|
|
||||||
language='gfm'
|
|
||||||
value={this.props.brew.text}
|
|
||||||
onChange={this.handleTextChange}
|
|
||||||
onCursorActivity={this.handleCursorActivty} />
|
|
||||||
|
|
||||||
{/*
|
{this.renderEditor()}
|
||||||
<div className='brewJump' onClick={this.brewJump}>
|
|
||||||
<i className='fas fa-arrow-right' />
|
|
||||||
</div>
|
|
||||||
*/}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,13 @@ const execute = function(val, brew){
|
|||||||
const Snippetbar = createClass({
|
const Snippetbar = createClass({
|
||||||
getDefaultProps : function() {
|
getDefaultProps : function() {
|
||||||
return {
|
return {
|
||||||
brew : {},
|
brew : {},
|
||||||
onInject : ()=>{},
|
view : 'text',
|
||||||
onToggle : ()=>{},
|
onViewChange : ()=>{},
|
||||||
showmeta : false,
|
onInject : ()=>{},
|
||||||
showMetaButton : true,
|
onToggle : ()=>{},
|
||||||
renderer : ''
|
showEditButtons : true,
|
||||||
|
renderer : 'legacy'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -36,12 +37,16 @@ const Snippetbar = createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
renderSnippetGroups : function(){
|
renderSnippetGroups : function(){
|
||||||
if(this.props.renderer == 'V3')
|
let snippets = [];
|
||||||
Snippets = SnippetsV3;
|
|
||||||
else
|
|
||||||
Snippets = SnippetsLegacy;
|
|
||||||
|
|
||||||
return _.map(Snippets, (snippetGroup)=>{
|
if(this.props.view === 'text') {
|
||||||
|
if(this.props.renderer === 'V3')
|
||||||
|
snippets = SnippetsV3;
|
||||||
|
else
|
||||||
|
snippets = SnippetsLegacy;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _.map(snippets, (snippetGroup)=>{
|
||||||
return <SnippetGroup
|
return <SnippetGroup
|
||||||
brew={this.props.brew}
|
brew={this.props.brew}
|
||||||
groupName={snippetGroup.groupName}
|
groupName={snippetGroup.groupName}
|
||||||
@@ -53,19 +58,29 @@ const Snippetbar = createClass({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
renderMetadataButton : function(){
|
renderEditorButtons : function(){
|
||||||
if(!this.props.showMetaButton) return;
|
if(!this.props.showEditButtons) return;
|
||||||
return <div className={cx('snippetBarButton', 'toggleMeta', { selected: this.props.showmeta })}
|
|
||||||
onClick={this.props.onToggle}>
|
return <div className='editors'>
|
||||||
<i className='fas fa-info-circle' />
|
<div className={cx('text', { selected: this.props.view === 'text' })}
|
||||||
<span className='groupName'>Properties</span>
|
onClick={()=>this.props.onViewChange('text')}>
|
||||||
|
<i className='fa fa-beer' />
|
||||||
|
</div>
|
||||||
|
<div className={cx('style', { selected: this.props.view === 'style' })}
|
||||||
|
onClick={()=>this.props.onViewChange('style')}>
|
||||||
|
<i className='fa fa-paint-brush' />
|
||||||
|
</div>
|
||||||
|
<div className={cx('meta', { selected: this.props.view === 'meta' })}
|
||||||
|
onClick={()=>this.props.onViewChange('meta')}>
|
||||||
|
<i className='fas fa-info-circle' />
|
||||||
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
},
|
},
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
return <div className='snippetBar'>
|
return <div className='snippetBar'>
|
||||||
{this.renderSnippetGroups()}
|
{this.renderSnippetGroups()}
|
||||||
{this.renderMetadataButton()}
|
{this.renderEditorButtons()}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,40 @@
|
|||||||
|
|
||||||
.snippetBar{
|
.snippetBar{
|
||||||
@height : 25px;
|
@menuHeight : 25px;
|
||||||
position : relative;
|
position : relative;
|
||||||
height : @height;
|
height : @menuHeight;
|
||||||
background-color : #ddd;
|
background-color : #ddd;
|
||||||
|
.editors{
|
||||||
|
position : absolute;
|
||||||
|
display : flex;
|
||||||
|
top : 0px;
|
||||||
|
right : 0px;
|
||||||
|
height : @menuHeight;
|
||||||
|
width : 90px;
|
||||||
|
justify-content : space-between;
|
||||||
|
&>div{
|
||||||
|
height : @menuHeight;
|
||||||
|
width : @menuHeight;
|
||||||
|
cursor : pointer;
|
||||||
|
line-height : @menuHeight;
|
||||||
|
text-align : center;
|
||||||
|
&:hover,&.selected{
|
||||||
|
background-color : #999;
|
||||||
|
}
|
||||||
|
&.text{
|
||||||
|
.tooltipLeft('Brew Editor');
|
||||||
|
}
|
||||||
|
&.style{
|
||||||
|
.tooltipLeft('Style Editor');
|
||||||
|
}
|
||||||
|
&.meta{
|
||||||
|
.tooltipLeft('Properties');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.snippetBarButton{
|
.snippetBarButton{
|
||||||
height : @height;
|
height : @menuHeight;
|
||||||
line-height : @height;
|
line-height : @menuHeight;
|
||||||
display : inline-block;
|
display : inline-block;
|
||||||
padding : 0px 5px;
|
padding : 0px 5px;
|
||||||
font-weight : 800;
|
font-weight : 800;
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ module.exports = [
|
|||||||
{
|
{
|
||||||
name : 'Auto-incrementing Page Number',
|
name : 'Auto-incrementing Page Number',
|
||||||
icon : 'fas fa-sort-numeric-down',
|
icon : 'fas fa-sort-numeric-down',
|
||||||
gen : '{{\npageNumber,auto\n}}\n\n'
|
gen : '{{pageNumber,auto\n}}\n\n'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name : 'Link to page',
|
name : 'Link to page',
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ const Homebrew = createClass({
|
|||||||
global.account = this.props.account;
|
global.account = this.props.account;
|
||||||
global.version = this.props.version;
|
global.version = this.props.version;
|
||||||
global.enable_v3 = this.props.enable_v3;
|
global.enable_v3 = this.props.enable_v3;
|
||||||
|
|
||||||
},
|
},
|
||||||
render : function (){
|
render : function (){
|
||||||
return (
|
return (
|
||||||
@@ -45,7 +44,7 @@ const Homebrew = createClass({
|
|||||||
<Route path='/edit/:id' component={(routeProps)=><EditPage id={routeProps.match.params.id} brew={this.props.brew} />}/>
|
<Route path='/edit/:id' component={(routeProps)=><EditPage id={routeProps.match.params.id} brew={this.props.brew} />}/>
|
||||||
<Route path='/share/:id' component={(routeProps)=><SharePage id={routeProps.match.params.id} brew={this.props.brew} />}/>
|
<Route path='/share/:id' component={(routeProps)=><SharePage id={routeProps.match.params.id} brew={this.props.brew} />}/>
|
||||||
<Route path='/new/:id' component={(routeProps)=><NewPage id={routeProps.match.params.id} brew={this.props.brew} />}/>
|
<Route path='/new/:id' component={(routeProps)=><NewPage id={routeProps.match.params.id} brew={this.props.brew} />}/>
|
||||||
<Route path='/new' exact component={NewPage}/>
|
<Route path='/new' exact component={(routeProps)=><NewPage/>}/>
|
||||||
<Route path='/user/:username' component={(routeProps)=><UserPage username={routeProps.match.params.username} brews={this.props.brews} />}/>
|
<Route path='/user/:username' component={(routeProps)=><UserPage username={routeProps.match.params.username} brews={this.props.brews} />}/>
|
||||||
<Route path='/print/:id' component={(routeProps)=><PrintPage brew={this.props.brew} query={queryString.parse(routeProps.location.search)} /> } />
|
<Route path='/print/:id' component={(routeProps)=><PrintPage brew={this.props.brew} query={queryString.parse(routeProps.location.search)} /> } />
|
||||||
<Route path='/print' exact component={(routeProps)=><PrintPage query={queryString.parse(routeProps.location.search)} /> } />
|
<Route path='/print' exact component={(routeProps)=><PrintPage query={queryString.parse(routeProps.location.search)} /> } />
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ const EditPage = createClass({
|
|||||||
return {
|
return {
|
||||||
brew : {
|
brew : {
|
||||||
text : '',
|
text : '',
|
||||||
|
style : '',
|
||||||
shareId : null,
|
shareId : null,
|
||||||
editId : null,
|
editId : null,
|
||||||
createdAt : null,
|
createdAt : null,
|
||||||
@@ -106,17 +107,8 @@ const EditPage = createClass({
|
|||||||
this.refs.editor.update();
|
this.refs.editor.update();
|
||||||
},
|
},
|
||||||
|
|
||||||
handleMetadataChange : function(metadata){
|
|
||||||
this.setState((prevState)=>({
|
|
||||||
brew : _.merge({}, prevState.brew, metadata),
|
|
||||||
isPending : true,
|
|
||||||
}), ()=>this.trySave());
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
handleTextChange : function(text){
|
handleTextChange : function(text){
|
||||||
|
//If there are errors, run the validator on every change to give quick feedback
|
||||||
//If there are errors, run the validator on everychange to give quick feedback
|
|
||||||
let htmlErrors = this.state.htmlErrors;
|
let htmlErrors = this.state.htmlErrors;
|
||||||
if(htmlErrors.length) htmlErrors = Markdown.validate(text);
|
if(htmlErrors.length) htmlErrors = Markdown.validate(text);
|
||||||
|
|
||||||
@@ -127,6 +119,21 @@ const EditPage = createClass({
|
|||||||
}), ()=>this.trySave());
|
}), ()=>this.trySave());
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleStyleChange : function(style){
|
||||||
|
this.setState((prevState)=>({
|
||||||
|
brew : _.merge({}, prevState.brew, { style: style }),
|
||||||
|
isPending : true
|
||||||
|
}), ()=>this.trySave());
|
||||||
|
},
|
||||||
|
|
||||||
|
handleMetaChange : function(metadata){
|
||||||
|
this.setState((prevState)=>({
|
||||||
|
brew : _.merge({}, prevState.brew, metadata),
|
||||||
|
isPending : true,
|
||||||
|
}), ()=>this.trySave());
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
hasChanges : function(){
|
hasChanges : function(){
|
||||||
return !_.isEqual(this.state.brew, this.savedBrew);
|
return !_.isEqual(this.state.brew, this.savedBrew);
|
||||||
},
|
},
|
||||||
@@ -141,7 +148,6 @@ const EditPage = createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
handleGoogleClick : function(){
|
handleGoogleClick : function(){
|
||||||
console.log('handlegoogleclick');
|
|
||||||
if(!global.account?.googleId) {
|
if(!global.account?.googleId) {
|
||||||
this.setState({
|
this.setState({
|
||||||
alertLoginToTransfer : true
|
alertLoginToTransfer : true
|
||||||
@@ -409,11 +415,12 @@ const EditPage = createClass({
|
|||||||
<Editor
|
<Editor
|
||||||
ref='editor'
|
ref='editor'
|
||||||
brew={this.state.brew}
|
brew={this.state.brew}
|
||||||
onChange={this.handleTextChange}
|
onTextChange={this.handleTextChange}
|
||||||
onMetadataChange={this.handleMetadataChange}
|
onStyleChange={this.handleStyleChange}
|
||||||
|
onMetaChange={this.handleMetaChange}
|
||||||
renderer={this.state.brew.renderer}
|
renderer={this.state.brew.renderer}
|
||||||
/>
|
/>
|
||||||
<BrewRenderer text={this.state.brew.text} errors={this.state.htmlErrors} renderer={this.state.brew.renderer} />
|
<BrewRenderer text={this.state.brew.text} style={this.state.brew.style} renderer={this.state.brew.renderer} errors={this.state.htmlErrors} />
|
||||||
</SplitPane>
|
</SplitPane>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
|
|||||||
@@ -78,7 +78,13 @@ const HomePage = createClass({
|
|||||||
|
|
||||||
<div className='content'>
|
<div className='content'>
|
||||||
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
|
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
|
||||||
<Editor brew={this.state.brew} onChange={this.handleTextChange} showMetaButton={false} ref='editor'/>
|
<Editor
|
||||||
|
ref='editor'
|
||||||
|
brew={this.state.brew}
|
||||||
|
onTextChange={this.handleTextChange}
|
||||||
|
renderer={this.state.brew.renderer}
|
||||||
|
showEditButtons={false}
|
||||||
|
/>
|
||||||
<BrewRenderer text={this.state.brew.text} />
|
<BrewRenderer text={this.state.brew.text} />
|
||||||
</SplitPane>
|
</SplitPane>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ const React = require('react');
|
|||||||
const createClass = require('create-react-class');
|
const createClass = require('create-react-class');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const request = require('superagent');
|
const request = require('superagent');
|
||||||
|
const dedent = require('dedent-tabs').default;
|
||||||
|
|
||||||
const Markdown = require('naturalcrit/markdown.js');
|
const Markdown = require('naturalcrit/markdown.js');
|
||||||
|
|
||||||
@@ -16,14 +17,20 @@ const SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
|
|||||||
const Editor = require('../../editor/editor.jsx');
|
const Editor = require('../../editor/editor.jsx');
|
||||||
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||||
|
|
||||||
|
|
||||||
const KEY = 'homebrewery-new';
|
const KEY = 'homebrewery-new';
|
||||||
|
|
||||||
const NewPage = createClass({
|
const NewPage = createClass({
|
||||||
getDefaultProps : function() {
|
getDefaultProps : function() {
|
||||||
return {
|
return {
|
||||||
brew : {
|
brew : {
|
||||||
text : '',
|
text : '',
|
||||||
|
style : dedent`
|
||||||
|
/*=======--- Example CSS styling ---=======*/
|
||||||
|
/* Any CSS here will apply to your document! */
|
||||||
|
|
||||||
|
.myExampleClass {
|
||||||
|
color: black;
|
||||||
|
}`,
|
||||||
shareId : null,
|
shareId : null,
|
||||||
editId : null,
|
editId : null,
|
||||||
createdAt : null,
|
createdAt : null,
|
||||||
@@ -44,18 +51,21 @@ const NewPage = createClass({
|
|||||||
return {
|
return {
|
||||||
brew : {
|
brew : {
|
||||||
text : this.props.brew.text || '',
|
text : this.props.brew.text || '',
|
||||||
|
style : this.props.brew.style || '',
|
||||||
gDrive : false,
|
gDrive : false,
|
||||||
title : this.props.brew.title || '',
|
title : this.props.brew.title || '',
|
||||||
description : this.props.brew.description || '',
|
description : this.props.brew.description || '',
|
||||||
tags : this.props.brew.tags || '',
|
tags : this.props.brew.tags || '',
|
||||||
published : false,
|
published : false,
|
||||||
authors : [],
|
authors : [],
|
||||||
systems : this.props.brew.systems || []
|
systems : this.props.brew.systems || [],
|
||||||
|
renderer : this.props.brew.renderer || 'legacy'
|
||||||
},
|
},
|
||||||
|
|
||||||
isSaving : false,
|
isSaving : false,
|
||||||
saveGoogle : (global.account && global.account.googleId ? true : false),
|
saveGoogle : (global.account && global.account.googleId ? true : false),
|
||||||
errors : []
|
errors : [],
|
||||||
|
htmlErrors : Markdown.validate(this.props.brew.text)
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -66,6 +76,11 @@ const NewPage = createClass({
|
|||||||
brew : { text: storage }
|
brew : { text: storage }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.setState((prevState)=>({
|
||||||
|
htmlErrors : Markdown.validate(prevState.brew.text)
|
||||||
|
}));
|
||||||
|
|
||||||
document.addEventListener('keydown', this.handleControlKeys);
|
document.addEventListener('keydown', this.handleControlKeys);
|
||||||
},
|
},
|
||||||
componentWillUnmount : function() {
|
componentWillUnmount : function() {
|
||||||
@@ -88,18 +103,29 @@ const NewPage = createClass({
|
|||||||
this.refs.editor.update();
|
this.refs.editor.update();
|
||||||
},
|
},
|
||||||
|
|
||||||
handleMetadataChange : function(metadata){
|
handleTextChange : function(text){
|
||||||
this.setState({
|
//If there are errors, run the validator on every change to give quick feedback
|
||||||
brew : _.merge({}, this.state.brew, metadata)
|
let htmlErrors = this.state.htmlErrors;
|
||||||
});
|
if(htmlErrors.length) htmlErrors = Markdown.validate(text);
|
||||||
|
|
||||||
|
this.setState((prevState)=>({
|
||||||
|
brew : _.merge({}, prevState.brew, { text: text }),
|
||||||
|
htmlErrors : htmlErrors
|
||||||
|
}));
|
||||||
|
localStorage.setItem(KEY, text);
|
||||||
},
|
},
|
||||||
|
|
||||||
handleTextChange : function(text){
|
handleStyleChange : function(style){
|
||||||
this.setState({
|
this.setState((prevState)=>({
|
||||||
brew : { text: text },
|
brew : _.merge({}, prevState.brew, { style: style }),
|
||||||
errors : Markdown.validate(text)
|
}));
|
||||||
});
|
},
|
||||||
localStorage.setItem(KEY, text);
|
|
||||||
|
handleMetaChange : function(metadata){
|
||||||
|
this.setState((prevState)=>({
|
||||||
|
brew : _.merge({}, prevState.brew, metadata),
|
||||||
|
}));
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
save : async function(){
|
save : async function(){
|
||||||
@@ -190,10 +216,12 @@ const NewPage = createClass({
|
|||||||
<Editor
|
<Editor
|
||||||
ref='editor'
|
ref='editor'
|
||||||
brew={this.state.brew}
|
brew={this.state.brew}
|
||||||
onChange={this.handleTextChange}
|
onTextChange={this.handleTextChange}
|
||||||
onMetadataChange={this.handleMetadataChange}
|
onStyleChange={this.handleStyleChange}
|
||||||
|
onMetaChange={this.handleMetaChange}
|
||||||
|
renderer={this.state.brew.renderer}
|
||||||
/>
|
/>
|
||||||
<BrewRenderer text={this.state.brew.text} errors={this.state.errors} />
|
<BrewRenderer text={this.state.brew.text} style={this.state.brew.style} renderer={this.state.brew.renderer} errors={this.state.htmlErrors}/>
|
||||||
</SplitPane>
|
</SplitPane>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const PrintPage = createClass({
|
|||||||
query : {},
|
query : {},
|
||||||
brew : {
|
brew : {
|
||||||
text : '',
|
text : '',
|
||||||
|
style : '',
|
||||||
renderer : 'legacy'
|
renderer : 'legacy'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -58,6 +59,8 @@ const PrintPage = createClass({
|
|||||||
render : function(){
|
render : function(){
|
||||||
return <div>
|
return <div>
|
||||||
<Meta name='robots' content='noindex, nofollow' />
|
<Meta name='robots' content='noindex, nofollow' />
|
||||||
|
{/* Apply CSS from Style tab */}
|
||||||
|
<div style={{ display: 'none' }} dangerouslySetInnerHTML={{ __html: `<style> ${this.props.brew.style} </style>` }} />
|
||||||
{this.renderPages()}
|
{this.renderPages()}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const SharePage = createClass({
|
|||||||
brew : {
|
brew : {
|
||||||
title : '',
|
title : '',
|
||||||
text : '',
|
text : '',
|
||||||
|
style : '',
|
||||||
shareId : null,
|
shareId : null,
|
||||||
createdAt : null,
|
createdAt : null,
|
||||||
updatedAt : null,
|
updatedAt : null,
|
||||||
@@ -72,7 +73,7 @@ const SharePage = createClass({
|
|||||||
</Navbar>
|
</Navbar>
|
||||||
|
|
||||||
<div className='content'>
|
<div className='content'>
|
||||||
<BrewRenderer text={this.props.brew.text} renderer={this.props.brew.renderer} />
|
<BrewRenderer text={this.props.brew.text} style={this.props.brew.style} renderer={this.props.brew.renderer} />
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|||||||
11320
package-lock.json
generated
11320
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
27
package.json
27
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "homebrewery",
|
"name": "homebrewery",
|
||||||
"description": "Create authentic looking D&D homebrews using only markdown",
|
"description": "Create authentic looking D&D homebrews using only markdown",
|
||||||
"version": "2.11.2",
|
"version": "2.12.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "14.15.x"
|
"node": "14.15.x"
|
||||||
},
|
},
|
||||||
@@ -35,17 +35,18 @@
|
|||||||
},
|
},
|
||||||
"babel": {
|
"babel": {
|
||||||
"presets": [
|
"presets": [
|
||||||
"env",
|
"@babel/preset-env",
|
||||||
"react"
|
"@babel/preset-react"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.14.0",
|
"@babel/core": "^7.14.3",
|
||||||
"@babel/preset-env": "^7.13.15",
|
"@babel/plugin-transform-runtime": "^7.14.5",
|
||||||
|
"@babel/preset-env": "^7.14.4",
|
||||||
"@babel/preset-react": "^7.13.13",
|
"@babel/preset-react": "^7.13.13",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"codemirror": "^5.61.0",
|
"codemirror": "^5.61.1",
|
||||||
"cookie-parser": "^1.4.5",
|
"cookie-parser": "^1.4.5",
|
||||||
"create-react-class": "^15.7.0",
|
"create-react-class": "^15.7.0",
|
||||||
"dedent-tabs": "^0.9.0",
|
"dedent-tabs": "^0.9.0",
|
||||||
@@ -53,15 +54,15 @@
|
|||||||
"express-async-handler": "^1.1.4",
|
"express-async-handler": "^1.1.4",
|
||||||
"express-static-gzip": "2.1.1",
|
"express-static-gzip": "2.1.1",
|
||||||
"fs-extra": "9.1.0",
|
"fs-extra": "9.1.0",
|
||||||
"googleapis": "73.0.0",
|
"googleapis": "75.0.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": "2.0.3",
|
"marked": "2.0.6",
|
||||||
"markedLegacy": "npm:marked@^0.3.19",
|
"markedLegacy": "npm:marked@^0.3.19",
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
"mongoose": "^5.12.7",
|
"mongoose": "^5.12.12",
|
||||||
"nanoid": "3.1.22",
|
"nanoid": "3.1.23",
|
||||||
"nconf": "^0.11.2",
|
"nconf": "^0.11.2",
|
||||||
"prop-types": "15.7.2",
|
"prop-types": "15.7.2",
|
||||||
"query-string": "7.0.0",
|
"query-string": "7.0.0",
|
||||||
@@ -71,11 +72,11 @@
|
|||||||
"react-router-dom": "5.2.0",
|
"react-router-dom": "5.2.0",
|
||||||
"sanitize-filename": "1.6.3",
|
"sanitize-filename": "1.6.3",
|
||||||
"superagent": "^6.1.0",
|
"superagent": "^6.1.0",
|
||||||
"vitreum": "git+https://github.com/calculuschild/vitreum.git"
|
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^7.25.0",
|
"eslint": "^7.27.0",
|
||||||
"eslint-plugin-react": "^7.23.2",
|
"eslint-plugin-react": "^7.23.2",
|
||||||
"pico-check": "^2.0.3"
|
"pico-check": "^2.1.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,13 @@ const isDev = !!process.argv.find((arg)=>arg=='--dev');
|
|||||||
|
|
||||||
const lessTransform = require('vitreum/transforms/less.js');
|
const lessTransform = require('vitreum/transforms/less.js');
|
||||||
const assetTransform = require('vitreum/transforms/asset.js');
|
const assetTransform = require('vitreum/transforms/asset.js');
|
||||||
//const Meta = require('vitreum/headtags');
|
const babel = require('@babel/core');
|
||||||
|
|
||||||
|
const babelify = async (code)=>(await babel.transformAsync(code, { presets: ['@babel/preset-env', '@babel/preset-react'], plugins: ['@babel/plugin-transform-runtime'] })).code;
|
||||||
|
|
||||||
const transforms = {
|
const transforms = {
|
||||||
|
'.js' : (code, filename, opts)=>babelify(code),
|
||||||
|
'.jsx' : (code, filename, opts)=>babelify(code),
|
||||||
'.less' : lessTransform,
|
'.less' : lessTransform,
|
||||||
'*' : assetTransform('./build')
|
'*' : assetTransform('./build')
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"classnames",
|
"classnames",
|
||||||
"codemirror",
|
"codemirror",
|
||||||
"codemirror/mode/gfm/gfm.js",
|
"codemirror/mode/gfm/gfm.js",
|
||||||
|
"codemirror/mode/css/css.js",
|
||||||
"codemirror/mode/javascript/javascript.js",
|
"codemirror/mode/javascript/javascript.js",
|
||||||
"moment",
|
"moment",
|
||||||
"superagent",
|
"superagent",
|
||||||
|
|||||||
59
server.js
59
server.js
@@ -9,6 +9,7 @@ const GoogleActions = require('./server/googleActions.js');
|
|||||||
const serveCompressedStaticAssets = require('./server/static-assets.mv.js');
|
const serveCompressedStaticAssets = require('./server/static-assets.mv.js');
|
||||||
const sanitizeFilename = require('sanitize-filename');
|
const sanitizeFilename = require('sanitize-filename');
|
||||||
const asyncHandler = require('express-async-handler');
|
const asyncHandler = require('express-async-handler');
|
||||||
|
const dedent = require('dedent-tabs').default;
|
||||||
|
|
||||||
//Get the brew object from the HB database or Google Drive
|
//Get the brew object from the HB database or Google Drive
|
||||||
const getBrewFromId = asyncHandler(async (id, accessType)=>{
|
const getBrewFromId = asyncHandler(async (id, accessType)=>{
|
||||||
@@ -21,11 +22,35 @@ const getBrewFromId = asyncHandler(async (id, accessType)=>{
|
|||||||
brew = await GoogleActions.readFileMetadata(config.get('google_api_key'), googleId, id, accessType);
|
brew = await GoogleActions.readFileMetadata(config.get('google_api_key'), googleId, id, accessType);
|
||||||
} else {
|
} else {
|
||||||
brew = await HomebrewModel.get(accessType == 'edit' ? { editId: id } : { shareId: id });
|
brew = await HomebrewModel.get(accessType == 'edit' ? { editId: id } : { shareId: id });
|
||||||
brew.sanatize(true);
|
}
|
||||||
|
|
||||||
|
brew = sanitizeBrew(brew, accessType === 'edit' ? false : true);
|
||||||
|
//Split brew.text into text and style
|
||||||
|
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;
|
return brew;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const sanitizeBrew = (brew, full=false)=>{
|
||||||
|
delete brew._id;
|
||||||
|
delete brew.__v;
|
||||||
|
if(full){
|
||||||
|
delete brew.editId;
|
||||||
|
}
|
||||||
|
return brew;
|
||||||
|
};
|
||||||
|
|
||||||
app.use('/', serveCompressedStaticAssets(`${__dirname}/build`));
|
app.use('/', serveCompressedStaticAssets(`${__dirname}/build`));
|
||||||
|
|
||||||
process.chdir(__dirname);
|
process.chdir(__dirname);
|
||||||
@@ -112,25 +137,26 @@ app.get('/download/:id', asyncHandler(async (req, res)=>{
|
|||||||
|
|
||||||
//User Page
|
//User Page
|
||||||
app.get('/user/:username', async (req, res, next)=>{
|
app.get('/user/:username', async (req, res, next)=>{
|
||||||
const fullAccess = req.account && (req.account.username == req.params.username);
|
const ownAccount = req.account && (req.account.username == req.params.username);
|
||||||
|
|
||||||
let googleBrews = [];
|
let brews = await HomebrewModel.getByUser(req.params.username, ownAccount)
|
||||||
|
|
||||||
if(req.account && req.account.googleId){
|
|
||||||
googleBrews = await GoogleActions.listGoogleBrews(req, res)
|
|
||||||
.catch((err)=>{
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const brews = await HomebrewModel.getByUser(req.params.username, fullAccess)
|
|
||||||
.catch((err)=>{
|
.catch((err)=>{
|
||||||
console.log(err);
|
console.log(err);
|
||||||
});
|
});
|
||||||
|
|
||||||
if(googleBrews) {
|
if(ownAccount && req?.account?.googleId){
|
||||||
req.brews = _.concat(brews, googleBrews);
|
const googleBrews = await GoogleActions.listGoogleBrews(req, res)
|
||||||
} else {req.brews = brews;}
|
.catch((err)=>{
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
if(googleBrews)
|
||||||
|
brews = _.concat(brews, googleBrews);
|
||||||
|
}
|
||||||
|
|
||||||
|
req.brews = _.map(brews, (brew)=>{
|
||||||
|
return sanitizeBrew(brew, !ownAccount);
|
||||||
|
});
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
@@ -160,7 +186,7 @@ app.get('/share/:id', asyncHandler(async (req, res, next)=>{
|
|||||||
await GoogleActions.increaseView(googleId, shareId, 'share', brew)
|
await GoogleActions.increaseView(googleId, shareId, 'share', brew)
|
||||||
.catch((err)=>{next(err);});
|
.catch((err)=>{next(err);});
|
||||||
} else {
|
} else {
|
||||||
await brew.increaseView();
|
await HomebrewModel.increaseView({ shareId: brew.shareId });
|
||||||
}
|
}
|
||||||
|
|
||||||
req.brew = brew;
|
req.brew = brew;
|
||||||
@@ -191,7 +217,6 @@ app.use((req, res)=>{
|
|||||||
templateFn('homebrew', title = req.brew ? req.brew.title : '', props)
|
templateFn('homebrew', title = req.brew ? req.brew.title : '', props)
|
||||||
.then((page)=>{ res.send(page); })
|
.then((page)=>{ res.send(page); })
|
||||||
.catch((err)=>{
|
.catch((err)=>{
|
||||||
console.log('TEMPLATE ERROR');
|
|
||||||
console.log(err);
|
console.log(err);
|
||||||
return res.sendStatus(500);
|
return res.sendStatus(500);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ GoogleActions = {
|
|||||||
})
|
})
|
||||||
.catch((err)=>{
|
.catch((err)=>{
|
||||||
return console.error(`Error Listing Google Brews: ${err}`);
|
return console.error(`Error Listing Google Brews: ${err}`);
|
||||||
|
//TODO: Should break out here, but continues on for some reason.
|
||||||
});
|
});
|
||||||
|
|
||||||
if(!obj.data.files.length) {
|
if(!obj.data.files.length) {
|
||||||
@@ -152,17 +153,17 @@ GoogleActions = {
|
|||||||
fileId : brew.googleId,
|
fileId : brew.googleId,
|
||||||
resource : { name : `${brew.title}.txt`,
|
resource : { name : `${brew.title}.txt`,
|
||||||
description : `${brew.description}`,
|
description : `${brew.description}`,
|
||||||
properties : { title : brew.title,
|
properties : { title : brew.title,
|
||||||
published : brew.published,
|
published : brew.published,
|
||||||
lastViewed : brew.lastViewed,
|
lastViewed : brew.lastViewed,
|
||||||
views : brew.views,
|
views : brew.views,
|
||||||
version : brew.version,
|
version : brew.version,
|
||||||
renderer : brew.renderer,
|
renderer : brew.renderer,
|
||||||
tags : brew.tags,
|
tags : brew.tags,
|
||||||
systems : brew.systems.join() }
|
systems : brew.systems.join() }
|
||||||
},
|
},
|
||||||
media : { mimeType : 'text/plain',
|
media : { mimeType : 'text/plain',
|
||||||
body : brew.text }
|
body : brew.text }
|
||||||
})
|
})
|
||||||
.catch((err)=>{
|
.catch((err)=>{
|
||||||
console.log('Error saving to google');
|
console.log('Error saving to google');
|
||||||
|
|||||||
@@ -19,14 +19,24 @@ const getGoodBrewTitle = (text)=>{
|
|||||||
.slice(0, MAX_TITLE_LENGTH);
|
.slice(0, MAX_TITLE_LENGTH);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mergeBrewText = (text, style)=>{
|
||||||
|
text = `\`\`\`css\n` +
|
||||||
|
`${style}\n` +
|
||||||
|
`\`\`\`\n\n` +
|
||||||
|
`${text}`;
|
||||||
|
return text;
|
||||||
|
};
|
||||||
|
|
||||||
const newBrew = (req, res)=>{
|
const newBrew = (req, res)=>{
|
||||||
const brew = req.body;
|
const brew = req.body;
|
||||||
brew.authors = (req.account) ? [req.account.username] : [];
|
|
||||||
|
|
||||||
if(!brew.title) {
|
if(!brew.title) {
|
||||||
brew.title = getGoodBrewTitle(brew.text);
|
brew.title = getGoodBrewTitle(brew.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
brew.authors = (req.account) ? [req.account.username] : [];
|
||||||
|
brew.text = mergeBrewText(brew.text, brew.style);
|
||||||
|
|
||||||
delete brew.editId;
|
delete brew.editId;
|
||||||
delete brew.shareId;
|
delete brew.shareId;
|
||||||
delete brew.googleId;
|
delete brew.googleId;
|
||||||
@@ -53,8 +63,10 @@ const updateBrew = (req, res)=>{
|
|||||||
HomebrewModel.get({ editId: req.params.id })
|
HomebrewModel.get({ editId: req.params.id })
|
||||||
.then((brew)=>{
|
.then((brew)=>{
|
||||||
brew = _.merge(brew, req.body);
|
brew = _.merge(brew, req.body);
|
||||||
|
brew.text = mergeBrewText(brew.text, brew.style);
|
||||||
|
|
||||||
// Compress brew text to binary before saving
|
// Compress brew text to binary before saving
|
||||||
brew.textBin = zlib.deflateRawSync(req.body.text);
|
brew.textBin = zlib.deflateRawSync(brew.text);
|
||||||
// Delete the non-binary text field since it's not needed anymore
|
// Delete the non-binary text field since it's not needed anymore
|
||||||
brew.text = undefined;
|
brew.text = undefined;
|
||||||
brew.updatedAt = new Date();
|
brew.updatedAt = new Date();
|
||||||
@@ -113,12 +125,14 @@ const newGoogleBrew = async (req, res, next)=>{
|
|||||||
try { oAuth2Client = GoogleActions.authCheck(req.account, res); } catch (err) { return res.status(err.status).send(err.message); }
|
try { oAuth2Client = GoogleActions.authCheck(req.account, res); } catch (err) { return res.status(err.status).send(err.message); }
|
||||||
|
|
||||||
const brew = req.body;
|
const brew = req.body;
|
||||||
brew.authors = (req.account) ? [req.account.username] : [];
|
|
||||||
|
|
||||||
if(!brew.title) {
|
if(!brew.title) {
|
||||||
brew.title = getGoodBrewTitle(brew.text);
|
brew.title = getGoodBrewTitle(brew.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
brew.authors = (req.account) ? [req.account.username] : [];
|
||||||
|
brew.text = mergeBrewText(brew.text, brew.style);
|
||||||
|
|
||||||
delete brew.editId;
|
delete brew.editId;
|
||||||
delete brew.shareId;
|
delete brew.shareId;
|
||||||
delete brew.googleId;
|
delete brew.googleId;
|
||||||
@@ -135,7 +149,10 @@ const updateGoogleBrew = async (req, res, next)=>{
|
|||||||
|
|
||||||
try { oAuth2Client = GoogleActions.authCheck(req.account, res); } catch (err) { return res.status(err.status).send(err.message); }
|
try { oAuth2Client = GoogleActions.authCheck(req.account, res); } catch (err) { return res.status(err.status).send(err.message); }
|
||||||
|
|
||||||
const updatedBrew = await GoogleActions.updateGoogleBrew(oAuth2Client, req.body);
|
const brew = req.body;
|
||||||
|
brew.text = mergeBrewText(brew.text, brew.style);
|
||||||
|
|
||||||
|
const updatedBrew = await GoogleActions.updateGoogleBrew(oAuth2Client, brew);
|
||||||
|
|
||||||
return res.status(200).send(updatedBrew);
|
return res.status(200).send(updatedBrew);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,28 +24,15 @@ const HomebrewSchema = mongoose.Schema({
|
|||||||
version : { type: Number, default: 1 }
|
version : { type: Number, default: 1 }
|
||||||
}, { versionKey: false });
|
}, { versionKey: false });
|
||||||
|
|
||||||
|
HomebrewSchema.statics.increaseView = async function(query) {
|
||||||
HomebrewSchema.methods.sanatize = function(full=false){
|
const brew = await Homebrew.findOne(query).exec();
|
||||||
const brew = this.toJSON();
|
brew.lastViewed = new Date();
|
||||||
delete brew._id;
|
brew.views = brew.views + 1;
|
||||||
delete brew.__v;
|
await brew.save()
|
||||||
if(full){
|
|
||||||
delete brew.editId;
|
|
||||||
}
|
|
||||||
return brew;
|
|
||||||
};
|
|
||||||
|
|
||||||
HomebrewSchema.methods.increaseView = async function(){
|
|
||||||
this.lastViewed = new Date();
|
|
||||||
this.views = this.views + 1;
|
|
||||||
const text = this.text;
|
|
||||||
this.text = undefined;
|
|
||||||
await this.save()
|
|
||||||
.catch((err)=>{
|
.catch((err)=>{
|
||||||
return err;
|
return err;
|
||||||
});
|
});
|
||||||
this.text = text;
|
return brew;
|
||||||
return this;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
HomebrewSchema.statics.get = function(query){
|
HomebrewSchema.statics.get = function(query){
|
||||||
@@ -58,7 +45,7 @@ HomebrewSchema.statics.get = function(query){
|
|||||||
}
|
}
|
||||||
if(!brews[0].renderer)
|
if(!brews[0].renderer)
|
||||||
brews[0].renderer = 'legacy';
|
brews[0].renderer = 'legacy';
|
||||||
return resolve(brews[0]);
|
return resolve(brews[0].toObject()); //Convert Mongo Object to JSObject
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -69,11 +56,9 @@ HomebrewSchema.statics.getByUser = function(username, allowAccess=false){
|
|||||||
if(allowAccess){
|
if(allowAccess){
|
||||||
delete query.published;
|
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');
|
if(err) return reject('Can not find brew');
|
||||||
return resolve(_.map(brews, (brew)=>{
|
return resolve(brews);
|
||||||
return brew.sanatize(!allowAccess);
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,28 +11,42 @@ if(typeof navigator !== 'undefined'){
|
|||||||
|
|
||||||
//Language Modes
|
//Language Modes
|
||||||
require('codemirror/mode/gfm/gfm.js'); //Github flavoured markdown
|
require('codemirror/mode/gfm/gfm.js'); //Github flavoured markdown
|
||||||
|
require('codemirror/mode/css/css.js');
|
||||||
require('codemirror/mode/javascript/javascript.js');
|
require('codemirror/mode/javascript/javascript.js');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const CodeEditor = createClass({
|
const CodeEditor = createClass({
|
||||||
getDefaultProps : function() {
|
getDefaultProps : function() {
|
||||||
return {
|
return {
|
||||||
language : '',
|
language : '',
|
||||||
value : '',
|
value : '',
|
||||||
wrap : false,
|
wrap : true,
|
||||||
onChange : function(){},
|
onChange : ()=>{}
|
||||||
onCursorActivity : function(){},
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount : function() {
|
componentDidMount : function() {
|
||||||
|
this.buildEditor();
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidUpdate : function(prevProps) {
|
||||||
|
if(prevProps.language !== this.props.language){ //rebuild editor when switching tabs
|
||||||
|
this.buildEditor();
|
||||||
|
}
|
||||||
|
if(this.codeMirror && this.codeMirror.getValue() != this.props.value) { //update editor contents if brew.text is changed from outside
|
||||||
|
this.codeMirror.setValue(this.props.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
buildEditor : function() {
|
||||||
this.codeMirror = CodeMirror(this.refs.editor, {
|
this.codeMirror = CodeMirror(this.refs.editor, {
|
||||||
value : this.props.value,
|
value : this.props.value,
|
||||||
lineNumbers : true,
|
lineNumbers : true,
|
||||||
lineWrapping : this.props.wrap,
|
lineWrapping : this.props.wrap,
|
||||||
mode : this.props.language,
|
mode : this.props.language, //TODO: CSS MODE DOESN'T SEEM TO LOAD PROPERLY
|
||||||
extraKeys : {
|
indentWithTabs : true,
|
||||||
|
tabSize : 2,
|
||||||
|
extraKeys : {
|
||||||
'Ctrl-B' : this.makeBold,
|
'Ctrl-B' : this.makeBold,
|
||||||
'Cmd-B' : this.makeBold,
|
'Cmd-B' : this.makeBold,
|
||||||
'Ctrl-I' : this.makeItalic,
|
'Ctrl-I' : this.makeItalic,
|
||||||
@@ -42,8 +56,8 @@ const CodeEditor = createClass({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.codeMirror.on('change', this.handleChange);
|
// Note: codeMirror passes a copy of itself in this callback. cm === this.codeMirror. Either one works.
|
||||||
this.codeMirror.on('cursorActivity', this.handleCursorActivity);
|
this.codeMirror.on('change', (cm)=>{this.props.onChange(cm.getValue());});
|
||||||
this.updateSize();
|
this.updateSize();
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -74,29 +88,20 @@ const CodeEditor = createClass({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidUpdate : function(prevProps) {
|
//=-- Externally used -==//
|
||||||
if(this.codeMirror && this.codeMirror.getValue() != this.props.value) {
|
|
||||||
this.codeMirror.setValue(this.props.value);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setCursorPosition : function(line, char){
|
setCursorPosition : function(line, char){
|
||||||
setTimeout(()=>{
|
setTimeout(()=>{
|
||||||
this.codeMirror.focus();
|
this.codeMirror.focus();
|
||||||
this.codeMirror.doc.setCursor(line, char);
|
this.codeMirror.doc.setCursor(line, char);
|
||||||
}, 10);
|
}, 10);
|
||||||
},
|
},
|
||||||
|
getCursorPosition : function(){
|
||||||
|
return this.codeMirror.getCursor();
|
||||||
|
},
|
||||||
updateSize : function(){
|
updateSize : function(){
|
||||||
this.codeMirror.refresh();
|
this.codeMirror.refresh();
|
||||||
},
|
},
|
||||||
|
//----------------------//
|
||||||
handleChange : function(editor){
|
|
||||||
this.props.onChange(editor.getValue());
|
|
||||||
},
|
|
||||||
handleCursorActivity : function(){
|
|
||||||
this.props.onCursorActivity(this.codeMirror.doc.getCursor());
|
|
||||||
},
|
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
return <div className='codeEditor' ref='editor' />;
|
return <div className='codeEditor' ref='editor' />;
|
||||||
|
|||||||
Reference in New Issue
Block a user