From fd0eb4ca7da707eff86d124ee270c72ed5f1a365 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Thu, 4 Nov 2021 10:42:27 +1300 Subject: [PATCH 1/5] Add & implement ListPage --- .../basePages/listPage/brewItem/brewItem.jsx | 144 +++++++++++++ .../basePages/listPage/brewItem/brewItem.less | 75 +++++++ .../pages/basePages/listPage/listPage.jsx | 189 ++++++++++++++++++ .../pages/basePages/listPage/listPage.less | 77 +++++++ client/homebrew/pages/userPage/userPage.jsx | 162 ++------------- 5 files changed, 504 insertions(+), 143 deletions(-) create mode 100644 client/homebrew/pages/basePages/listPage/brewItem/brewItem.jsx create mode 100644 client/homebrew/pages/basePages/listPage/brewItem/brewItem.less create mode 100644 client/homebrew/pages/basePages/listPage/listPage.jsx create mode 100644 client/homebrew/pages/basePages/listPage/listPage.less diff --git a/client/homebrew/pages/basePages/listPage/brewItem/brewItem.jsx b/client/homebrew/pages/basePages/listPage/brewItem/brewItem.jsx new file mode 100644 index 000000000..c7c4d6f94 --- /dev/null +++ b/client/homebrew/pages/basePages/listPage/brewItem/brewItem.jsx @@ -0,0 +1,144 @@ +require('./brewItem.less'); +const React = require('react'); +const createClass = require('create-react-class'); +const _ = require('lodash'); +const cx = require('classnames'); +const moment = require('moment'); +const request = require('superagent'); + +const googleDriveIcon = require('../../../../googleDrive.png'); +const dedent = require('dedent-tabs').default; + +const BrewItem = createClass({ + getDefaultProps : function() { + return { + brew : { + title : '', + description : '', + + authors : [] + } + }; + }, + + deleteBrew : function(){ + if(this.props.brew.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; + if(!confirm('Are you REALLY sure? You will not be able to recover the document.')) return; + } else { + if(!confirm('Are you sure you want to remove this brew from your collection? This will remove you as an editor, but other owners will still be able to access the document.')) return; + if(!confirm('Are you REALLY sure? You will lose editor access to this document.')) return; + } + + if(this.props.brew.googleId) { + request.get(`/api/removeGoogle/${this.props.brew.googleId}${this.props.brew.editId}`) + .send() + .end(function(err, res){ + location.reload(); + }); + } else { + request.delete(`/api/${this.props.brew.editId}`) + .send() + .end(function(err, res){ + location.reload(); + }); + } + }, + + renderDeleteBrewLink : function(){ + if(!this.props.brew.editId) return; + + return + + ; + }, + + renderEditLink : function(){ + if(!this.props.brew.editId) return; + + let editLink = this.props.brew.editId; + if(this.props.brew.googleId) { + editLink = this.props.brew.googleId + editLink; + } + + return + + ; + }, + + renderShareLink : function(){ + if(!this.props.brew.shareId) return; + + let shareLink = this.props.brew.shareId; + if(this.props.brew.googleId) { + shareLink = this.props.brew.googleId + shareLink; + } + + return + + ; + }, + + renderDownloadLink : function(){ + if(!this.props.brew.shareId) return; + + let shareLink = this.props.brew.shareId; + if(this.props.brew.googleId) { + shareLink = this.props.brew.googleId + shareLink; + } + + return + + ; + }, + + renderGoogleDriveIcon : function(){ + if(!this.props.brew.gDrive) return; + + return + googleDriveIcon + ; + }, + + render : function(){ + const brew = this.props.brew; + const dateFormatString = 'YYYY-MM-DD HH:mm:ss'; + + return
+
+

{brew.title}

+

{brew.description}

+
+
+
+ + {brew.authors.join(', ')} + +
+ + {brew.views} + + {brew.pageCount && + + {brew.pageCount} + + } + + {moment(brew.updatedAt).fromNow()} + + {this.renderGoogleDriveIcon()} +
+ +
+ {this.renderShareLink()} + {this.renderEditLink()} + {this.renderDownloadLink()} + {this.renderDeleteBrewLink()} +
+
; + } +}); + +module.exports = BrewItem; diff --git a/client/homebrew/pages/basePages/listPage/brewItem/brewItem.less b/client/homebrew/pages/basePages/listPage/brewItem/brewItem.less new file mode 100644 index 000000000..d323874f5 --- /dev/null +++ b/client/homebrew/pages/basePages/listPage/brewItem/brewItem.less @@ -0,0 +1,75 @@ + +.brewItem{ + position : relative; + display : inline-block; + vertical-align : top; + box-sizing : border-box; + box-sizing : border-box; + overflow : hidden; + width : 48%; + min-height : 105px; + margin-right : 15px; + margin-bottom : 15px; + padding : 5px 15px 2px 8px; + padding-right : 15px; + border : 1px solid #c9ad6a; + border-radius : 5px; + -webkit-column-break-inside : avoid; + page-break-inside : avoid; + break-inside : avoid; + .text { + min-height : 54px; + h4{ + margin-bottom : 5px; + font-size : 2.2em; + } + } + .info{ + position: initial; + bottom: 2px; + font-family : ScalySans; + font-size : 1.2em; + &>span{ + margin-right : 12px; + line-height : 1.5em; + } + } + &:hover{ + .links{ + opacity : 1; + } + } + &:nth-child(2n + 1){ + margin-right : 0px; + } + .links{ + .animate(opacity); + position : absolute; + top : 0px; + right : 0px; + height : 100%; + width : 2em; + opacity : 0; + background-color : fade(black, 60%); + text-align : center; + a{ + .animate(opacity); + display : block; + margin : 8px 0px; + opacity : 0.6; + font-size : 1.3em; + color : white; + &:hover{ + opacity : 1; + } + i{ + cursor : pointer; + } + } + } + .googleDriveIcon { + height : 20px; + padding : 0px; + margin : -5px; + } +} diff --git a/client/homebrew/pages/basePages/listPage/listPage.jsx b/client/homebrew/pages/basePages/listPage/listPage.jsx new file mode 100644 index 000000000..e4beb4a94 --- /dev/null +++ b/client/homebrew/pages/basePages/listPage/listPage.jsx @@ -0,0 +1,189 @@ +require('./listPage.less'); +const React = require('react'); +const createClass = require('create-react-class'); +const _ = require('lodash'); +const cx = require('classnames'); + +const moment = require('moment'); + +const Nav = require('naturalcrit/nav/nav.jsx'); +const Navbar = require('../../../navbar/navbar.jsx'); + +const RecentNavItem = require('../../../navbar/recent.navitem.jsx').both; +const Account = require('../../../navbar/account.navitem.jsx'); +const NewBrew = require('../../../navbar/newbrew.navitem.jsx'); +const BrewItem = require('./brewItem/brewItem.jsx'); +const ReportIssue = require('../../../navbar/issue.navitem.jsx'); + +// const brew = { +// title : 'SUPER Long title woah now', +// authors : [] +// }; + +//const BREWS = _.times(25, ()=>{ return brew;}); + + +const ListPage = createClass({ + getDefaultProps : function() { + return { + brewCollection : [ + { + title : '', + class : '', + brews : [] + } + ] + }; + }, + getInitialState : function() { + return { + sortType : 'alpha', + sortDir : 'asc', + filterString : '' + }; + }, + + renderBrews : function(brews){ + if(!brews || !brews.length) return
No Brews.
; + + const sortedBrews = this.sortBrews(brews); + + return _.map(sortedBrews, (brew, idx)=>{ + return ; + }); + }, + + sortBrewOrder : function(brew){ + if(!brew.title){brew.title = 'No Title';} + const mapping = { + 'alpha' : _.deburr(brew.title.toLowerCase()), + 'created' : moment(brew.createdAt).format(), + 'updated' : moment(brew.updatedAt).format(), + 'views' : brew.views, + 'latest' : moment(brew.lastViewed).format() + }; + return mapping[this.state.sortType]; + }, + + sortBrews : function(brews){ + return _.orderBy(brews, (brew)=>{ return this.sortBrewOrder(brew); }, this.state.sortDir); + }, + + handleSortOptionChange : function(event){ + this.setState({ + sortType : event.target.value + }); + }, + + handleSortDirChange : function(event){ + this.setState({ + sortDir : `${(this.state.sortDir == 'asc' ? 'desc' : 'asc')}` + }); + }, + + renderSortOption : function(sortTitle, sortValue){ + return + + ; + }, + + handleFilterTextChange : function(e){ + this.setState({ + filterString : e.target.value + }); + return; + }, + + renderFilterOption : function(){ + return + + ; + }, + + renderSortOptions : function(){ + return
+ + + + + {this.renderSortOption('Title', 'alpha')} + {this.renderSortOption('Created Date', 'created')} + {this.renderSortOption('Updated Date', 'updated')} + {this.renderSortOption('Views', 'views')} + {/* {this.renderSortOption('Latest', 'latest')} */} + + + {this.renderFilterOption()} + + +
+
Sort by :
+
+
Direction :
+
+ +
+
; + }, + + getSortedBrews : function(brewCollection){ + const testString = _.deburr(this.state.filterString).toLowerCase(); + const brews = this.state.filterString ? _.filter(brewCollection.brews, (brew)=>{ + return (_.deburr(brew.title).toLowerCase().includes(testString)) || + (_.deburr(brew.description).toLowerCase().includes(testString)); + }) : this.props.brewCollection.brews; + return _.groupBy(brews, (brew)=>{ + return (brew.published ? 'published' : 'private'); + }); + }, + + renderBrewCollection : function(brewCollection){ + return _.map(brewCollection, (brewItem, idx)=>{ + return
+

{brewItem.title || 'No Title'}

+ {this.renderBrews(brewItem.brews)} +
; + }); + }, + + render : function(){ + return
+ + + + + + + + + + +
+
+ {this.renderSortOptions()} + {this.renderBrewCollection(this.props.brewCollection)} +
+
+
; + } +}); + +module.exports = ListPage; diff --git a/client/homebrew/pages/basePages/listPage/listPage.less b/client/homebrew/pages/basePages/listPage/listPage.less new file mode 100644 index 000000000..6be946404 --- /dev/null +++ b/client/homebrew/pages/basePages/listPage/listPage.less @@ -0,0 +1,77 @@ + +.noColumns(){ + column-count : auto; + column-fill : auto; + column-gap : auto; + column-width : auto; + -webkit-column-count : auto; + -moz-column-count : auto; + -webkit-column-width : auto; + -moz-column-width : auto; + -webkit-column-gap : auto; + -moz-column-gap : auto; +} +.listPage{ + .content{ + overflow-y : scroll; + .phb{ + .noColumns(); + height : auto; + min-height : 279.4mm; + margin : 20px auto; + &::after{ + display : none; + } + .noBrews{ + margin : 10px 0px; + font-size : 1.3em; + font-style : italic; + } + + } + } + .sort-container{ + font-family : 'Open Sans', sans-serif; + position : fixed; + top : 35px; + left : calc(50vw - 408px); + border : 2px solid #58180D; + width : 800px; + background-color : #EEE5CE; + padding : 2px; + text-align : center; + z-index : 15; + h6{ + text-transform : uppercase; + font-family : 'Open Sans', sans-serif; + font-size : 11px; + font-weight : bold; + color : #58180D; + } + table{ + margin : 0px; + vertical-align : middle; + tbody tr{ + background-color: transparent !important; + i{ + padding-right : 5px + } + button{ + background-color : transparent; + color : #58180D; + font-family : 'Open Sans', sans-serif; + font-size : 11px; + text-transform : uppercase; + font-weight : normal; + &.active{ + font-weight : bold; + border : 2px solid #58180D; + } + &.sortDir{ + width : 75px; + } + } + } + } + } +} diff --git a/client/homebrew/pages/userPage/userPage.jsx b/client/homebrew/pages/userPage/userPage.jsx index bd84ce6b5..db069a7ac 100644 --- a/client/homebrew/pages/userPage/userPage.jsx +++ b/client/homebrew/pages/userPage/userPage.jsx @@ -4,16 +4,7 @@ const createClass = require('create-react-class'); const _ = require('lodash'); const cx = require('classnames'); -const moment = require('moment'); - -const Nav = require('naturalcrit/nav/nav.jsx'); -const Navbar = require('../../navbar/navbar.jsx'); - -const RecentNavItem = require('../../navbar/recent.navitem.jsx').both; -const Account = require('../../navbar/account.navitem.jsx'); -const NewBrew = require('../../navbar/newbrew.navitem.jsx'); -const BrewItem = require('./brewItem/brewItem.jsx'); -const ReportIssue = require('../../navbar/issue.navitem.jsx'); +const ListPage = require('../basePages/listPage/listPage.jsx'); // const brew = { // title : 'SUPER Long title woah now', @@ -43,114 +34,8 @@ const UserPage = createClass({ return `${this.props.username}'s`; }, - renderBrews : function(brews){ - if(!brews || !brews.length) return
No Brews.
; - - const sortedBrews = this.sortBrews(brews); - - return _.map(sortedBrews, (brew, idx)=>{ - return ; - }); - }, - - sortBrewOrder : function(brew){ - if(!brew.title){brew.title = 'No Title';} - const mapping = { - 'alpha' : _.deburr(brew.title.toLowerCase()), - 'created' : moment(brew.createdAt).format(), - 'updated' : moment(brew.updatedAt).format(), - 'views' : brew.views, - 'latest' : moment(brew.lastViewed).format() - }; - return mapping[this.state.sortType]; - }, - - sortBrews : function(brews){ - return _.orderBy(brews, (brew)=>{ return this.sortBrewOrder(brew); }, this.state.sortDir); - }, - - handleSortOptionChange : function(event){ - this.setState({ - sortType : event.target.value - }); - }, - - handleSortDirChange : function(event){ - this.setState({ - sortDir : `${(this.state.sortDir == 'asc' ? 'desc' : 'asc')}` - }); - }, - - renderSortOption : function(sortTitle, sortValue){ - return - - ; - }, - - handleFilterTextChange : function(e){ - this.setState({ - filterString : e.target.value - }); - return; - }, - - renderFilterOption : function(){ - return - - ; - }, - - renderSortOptions : function(){ - return
- - - - - {this.renderSortOption('Title', 'alpha')} - {this.renderSortOption('Created Date', 'created')} - {this.renderSortOption('Updated Date', 'updated')} - {this.renderSortOption('Views', 'views')} - {/* {this.renderSortOption('Latest', 'latest')} */} - - - {this.renderFilterOption()} - - -
-
Sort by :
-
-
Direction :
-
- -
-
; - }, - getSortedBrews : function(){ - const testString = _.deburr(this.state.filterString).toLowerCase(); - const brewCollection = this.state.filterString ? _.filter(this.props.brews, (brew)=>{ - return (_.deburr(brew.title).toLowerCase().includes(testString)) || - (_.deburr(brew.description).toLowerCase().includes(testString)); - }) : this.props.brews; - return _.groupBy(brewCollection, (brew)=>{ + return _.groupBy(this.props.brews, (brew)=>{ return (brew.published ? 'published' : 'private'); }); }, @@ -158,33 +43,24 @@ const UserPage = createClass({ render : function(){ const brews = this.getSortedBrews(); - return
- - - - - - - - - + const brewCollections = [ + { + title : `${this.getUsernameWithS()} published brews`, + class : 'published', + brews : brews.published + } + ]; + if(this.props.username == global.account?.username){ + brewCollections.push( + { + title : `${this.getUsernameWithS()} unpublished brews`, + class : 'unpublished', + brews : brews.private + } + ); + } -
-
- {this.renderSortOptions()} -
-

{this.getUsernameWithS()} published brews

- {this.renderBrews(brews.published)} -
- {this.props.username == global.account?.username && -
-

{this.getUsernameWithS()} unpublished brews

- {this.renderBrews(brews.private)} -
- } -
-
-
; + return ; } }); From 4fc0bbc9d7f079f8255aea4ed8a36b11ff49ec01 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Thu, 4 Nov 2021 11:44:05 +1300 Subject: [PATCH 2/5] Remove unnecessary `userPage.less` --- client/homebrew/pages/userPage/userPage.jsx | 2 +- client/homebrew/pages/userPage/userPage.less | 77 -------------------- 2 files changed, 1 insertion(+), 78 deletions(-) delete mode 100644 client/homebrew/pages/userPage/userPage.less diff --git a/client/homebrew/pages/userPage/userPage.jsx b/client/homebrew/pages/userPage/userPage.jsx index db069a7ac..708f45a61 100644 --- a/client/homebrew/pages/userPage/userPage.jsx +++ b/client/homebrew/pages/userPage/userPage.jsx @@ -1,4 +1,4 @@ -require('./userPage.less'); +//require('./userPage.less'); const React = require('react'); const createClass = require('create-react-class'); const _ = require('lodash'); diff --git a/client/homebrew/pages/userPage/userPage.less b/client/homebrew/pages/userPage/userPage.less deleted file mode 100644 index d968aab9a..000000000 --- a/client/homebrew/pages/userPage/userPage.less +++ /dev/null @@ -1,77 +0,0 @@ - -.noColumns(){ - column-count : auto; - column-fill : auto; - column-gap : auto; - column-width : auto; - -webkit-column-count : auto; - -moz-column-count : auto; - -webkit-column-width : auto; - -moz-column-width : auto; - -webkit-column-gap : auto; - -moz-column-gap : auto; -} -.userPage{ - .content{ - overflow-y : scroll; - .phb{ - .noColumns(); - height : auto; - min-height : 279.4mm; - margin : 20px auto; - &::after{ - display : none; - } - .noBrews{ - margin : 10px 0px; - font-size : 1.3em; - font-style : italic; - } - - } - } - .sort-container{ - font-family : 'Open Sans', sans-serif; - position : fixed; - top : 35px; - left : calc(50vw - 408px); - border : 2px solid #58180D; - width : 800px; - background-color : #EEE5CE; - padding : 2px; - text-align : center; - z-index : 15; - h6{ - text-transform : uppercase; - font-family : 'Open Sans', sans-serif; - font-size : 11px; - font-weight : bold; - color : #58180D; - } - table{ - margin : 0px; - vertical-align : middle; - tbody tr{ - background-color: transparent !important; - i{ - padding-right : 5px - } - button{ - background-color : transparent; - color : #58180D; - font-family : 'Open Sans', sans-serif; - font-size : 11px; - text-transform : uppercase; - font-weight : normal; - &.active{ - font-weight : bold; - border : 2px solid #58180D; - } - &.sortDir{ - width : 75px; - } - } - } - } - } -} From fe708e0a0bb09a7badb88dcdc50c9ca3569bb07d Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Thu, 4 Nov 2021 11:44:41 +1300 Subject: [PATCH 3/5] Remove line rather than comment it out --- client/homebrew/pages/userPage/userPage.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/client/homebrew/pages/userPage/userPage.jsx b/client/homebrew/pages/userPage/userPage.jsx index 708f45a61..1168e0982 100644 --- a/client/homebrew/pages/userPage/userPage.jsx +++ b/client/homebrew/pages/userPage/userPage.jsx @@ -1,4 +1,3 @@ -//require('./userPage.less'); const React = require('react'); const createClass = require('create-react-class'); const _ = require('lodash'); From 6cd56dfd6213011969937e962e0b0ca5d8109d97 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Thu, 4 Nov 2021 14:03:57 +1300 Subject: [PATCH 4/5] Initial pass at `editorPage.jsx`. --- .../pages/basePages/editorPage/editorPage.jsx | 578 ++++++++++++++++++ .../basePages/editorPage/editorPage.less | 99 +++ client/homebrew/pages/editPage/editPage.jsx | 454 +------------- client/homebrew/pages/newPage/newPage.jsx | 250 +------- 4 files changed, 695 insertions(+), 686 deletions(-) create mode 100644 client/homebrew/pages/basePages/editorPage/editorPage.jsx create mode 100644 client/homebrew/pages/basePages/editorPage/editorPage.less diff --git a/client/homebrew/pages/basePages/editorPage/editorPage.jsx b/client/homebrew/pages/basePages/editorPage/editorPage.jsx new file mode 100644 index 000000000..99a5c2b52 --- /dev/null +++ b/client/homebrew/pages/basePages/editorPage/editorPage.jsx @@ -0,0 +1,578 @@ +/* eslint-disable max-lines */ +require('./editorPage.less'); +const React = require('react'); +const createClass = require('create-react-class'); +const _ = require('lodash'); +const request = require('superagent'); +const { Meta } = require('vitreum/headtags'); + +const Nav = require('naturalcrit/nav/nav.jsx'); +const Navbar = require('../../../navbar/navbar.jsx'); + +const NewBrew = require('../../../navbar/newbrew.navitem.jsx'); +const ReportIssue = require('../../../navbar/issue.navitem.jsx'); +const PrintLink = require('../../../navbar/print.navitem.jsx'); +const Account = require('../../../navbar/account.navitem.jsx'); +const RecentNavItem = require('../../../navbar/recent.navitem.jsx').both; + +const SplitPane = require('naturalcrit/splitPane/splitPane.jsx'); +const Editor = require('../../../editor/editor.jsx'); +const BrewRenderer = require('../../../brewRenderer/brewRenderer.jsx'); + +const Markdown = require('naturalcrit/markdown.js'); + +const googleDriveActive = require('../../../googleDrive.png'); +const googleDriveInactive = require('../../../googleDriveMono.png'); + +const SAVE_TIMEOUT = 3000; + +const BREWKEY = 'homebrewery-new'; +const STYLEKEY = 'homebrewery-new-style'; +const METAKEY = 'homebrewery-new-meta'; + +const EditorPage = createClass({ + getDefaultProps : function() { + return { + brew : { + text : '', + style : '', + shareId : null, + editId : null, + createdAt : null, + updatedAt : null, + gDrive : false, + trashed : false, + + title : '', + description : '', + tags : '', + published : false, + authors : [], + systems : [], + renderer : 'legacy' + }, + pageType : 'edit', + googleDriveOptions : [ + 'DRIVE > HB', + 'HB > DRIVE' + ] + }; + }, + + getInitialState : function() { + return { + brew : this.props.brew, + isSaving : false, + isPending : false, + alertTrashedGoogleBrew : this.props.brew.trashed, + alertLoginToTransfer : false, + saveGoogle : this.props.brew.googleId ? true : false, + confirmGoogleTransfer : false, + errors : null, + htmlErrors : Markdown.validate(this.props.brew.text), + url : '' + }; + // return { + // brew : { + // text : brew.text || '', + // style : brew.style || undefined, + // gDrive : false, + // title : brew.title || '', + // description : brew.description || '', + // tags : brew.tags || '', + // published : false, + // authors : [], + // systems : brew.systems || [], + // renderer : brew.renderer || 'legacy' + // }, + + // isSaving : false, + // isPending : false, + // alertTrashedGoogleBrew : this.props.brew.trashed, + // alertLoginToTransfer : false, + // saveGoogle : (global.account && global.account.googleId ? true : false), + // confirmGoogleTransfer : false, + // errors : null, + // htmlErrors : Markdown.validate(brew.text), + // url : '' + // }; + }, + savedBrew : null, + + componentDidMount : function(){ + this.setState({ + url : window.location.href + }); + + this.savedBrew = JSON.parse(JSON.stringify(this.props.brew)); //Deep copy + + this.trySave(); + window.onbeforeunload = ()=>{ + if(this.state.isSaving || this.state.isPending){ + return 'You have unsaved changes!'; + } + }; + + this.setState((prevState)=>({ + htmlErrors : Markdown.validate(prevState.brew.text) + })); + + document.addEventListener('keydown', this.handleControlKeys); + }, + componentWillUnmount : function() { + window.onbeforeunload = function(){}; + document.removeEventListener('keydown', this.handleControlKeys); + }, + + handleControlKeys : function(e){ + if(!(e.ctrlKey || e.metaKey)) return; + const S_KEY = 83; + const P_KEY = 80; + if(e.keyCode == S_KEY) this.save(); + if(e.keyCode == P_KEY) window.open(`/print/${this.processShareId()}?dialog=true`, '_blank').focus(); + if(e.keyCode == P_KEY || e.keyCode == S_KEY){ + e.stopPropagation(); + e.preventDefault(); + } + }, + + isEdit : function(){ + return this.props.pageType == 'edit'; + }, + + isNew : function(){ + return this.props.pageType == 'new'; + }, + + handleSplitMove : function(){ + this.refs.editor.update(); + }, + + handleTextChange : function(text){ + //If there are errors, run the validator on every change to give quick feedback + let htmlErrors = this.state.htmlErrors; + if(htmlErrors.length) htmlErrors = Markdown.validate(text); + + this.setState((prevState)=>({ + brew : _.merge({}, prevState.brew, { text: text }), + isPending : true, + htmlErrors : htmlErrors + }), ()=>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(){ + return !_.isEqual(this.state.brew, this.savedBrew); + }, + + trySave : function(){ + if(!this.isEdit()) return; + if(!this.debounceSave) this.debounceSave = _.debounce(this.save, SAVE_TIMEOUT); + if(this.hasChanges()){ + this.debounceSave(); + } else { + this.debounceSave.cancel(); + } + }, + + handleGoogleClick : function(){ + if(!global.account?.googleId) { + this.setState({ + alertLoginToTransfer : true + }); + return; + } + this.setState((prevState)=>({ + confirmGoogleTransfer : !prevState.confirmGoogleTransfer + })); + this.clearErrors(); + }, + + closeAlerts : function(event){ + event.stopPropagation(); //Only handle click once so alert doesn't reopen + this.setState({ + alertTrashedGoogleBrew : false, + alertLoginToTransfer : false, + confirmGoogleTransfer : false + }); + }, + + toggleGoogleStorage : function(){ + this.setState((prevState)=>({ + saveGoogle : !prevState.saveGoogle, + isSaving : false, + errors : null + }), ()=>this.isEdit() && this.save()); + }, + + clearErrors : function(){ + this.setState({ + errors : null, + isSaving : false + + }); + }, + + save : async function(){ + this.setState((prevState)=>({ + isSaving : true, + errors : null, + htmlErrors : Markdown.validate(prevState.brew.text) + })); + + if(this.isEdit()){ + if(this.debounceSave && this.debounceSave.cancel) this.debounceSave.cancel(); + + const transfer = this.state.saveGoogle == _.isNil(this.state.brew.googleId); + + const brew = this.state.brew; + brew.pageCount = ((brew.renderer=='legacy' ? brew.text.match(/\\page/g) : brew.text.match(/^\\page$/gm)) || []).length + 1; + + if(this.state.saveGoogle) { + if(transfer) { + const res = await request + .post('/api/newGoogle/') + .send(brew) + .catch((err)=>{ + console.log(err.status === 401 + ? 'Not signed in!' + : 'Error Transferring to Google!'); + this.setState({ errors: err, saveGoogle: false }); + }); + + if(!res) { return; } + + console.log('Deleting Local Copy'); + await request.delete(`/api/${brew.editId}`) + .send() + .catch((err)=>{ + console.log('Error deleting Local Copy'); + }); + + this.savedBrew = res.body; + history.replaceState(null, null, `/edit/${this.savedBrew.googleId}${this.savedBrew.editId}`); //update URL to match doc ID + } else { + const res = await request + .put(`/api/updateGoogle/${brew.editId}`) + .send(brew) + .catch((err)=>{ + console.log(err.status === 401 + ? 'Not signed in!' + : 'Error Saving to Google!'); + this.setState({ errors: err }); + return; + }); + + this.savedBrew = res.body; + } + } else { + if(transfer) { + const res = await request.post('/api') + .send(brew) + .catch((err)=>{ + console.log('Error creating Local Copy'); + this.setState({ errors: err }); + return; + }); + + await request.get(`/api/removeGoogle/${brew.googleId}${brew.editId}`) + .send() + .catch((err)=>{ + console.log('Error Deleting Google Brew'); + }); + + this.savedBrew = res.body; + history.replaceState(null, null, `/edit/${this.savedBrew.editId}`); //update URL to match doc ID + } else { + const res = await request + .put(`/api/update/${brew.editId}`) + .send(brew) + .catch((err)=>{ + console.log('Error Updating Local Brew'); + this.setState({ errors: err }); + return; + }); + + this.savedBrew = res.body; + } + } + + this.setState((prevState)=>({ + brew : _.merge({}, prevState.brew, { + googleId : this.savedBrew.googleId ? this.savedBrew.googleId : null, + editId : this.savedBrew.editId, + shareId : this.savedBrew.shareId + }), + isPending : false, + isSaving : false, + })); + } + + if(this.isNew()){ + 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); + }; + + brew.pageCount=((brew.renderer=='legacy' ? brew.text.match(/\\page/g) : brew.text.match(/^\\page$/gm)) || []).length + 1; + + if(this.state.saveGoogle) { + const res = await request + .post('/api/newGoogle/') + .send(brew) + .catch((err)=>{ + console.log(err.status === 401 + ? 'Not signed in!' + : 'Error Creating New Google Brew!'); + this.setState({ isSaving: false, errors: err }); + return; + }); + + brew = res.body; + localStorage.removeItem(BREWKEY); + localStorage.removeItem(STYLEKEY); + localStorage.removeItem(METAKEY); + window.location = `/edit/${brew.googleId}${brew.editId}`; + } else { + request.post('/api') + .send(brew) + .end((err, res)=>{ + if(err){ + this.setState({ + isSaving : false + }); + return; + } + window.onbeforeunload = function(){}; + brew = res.body; + localStorage.removeItem(BREWKEY); + localStorage.removeItem(STYLEKEY); + localStorage.removeItem(METAKEY); + window.location = `/edit/${brew.editId}`; + }); + } + } + }, + + renderGoogleDriveIcon : function(){ + return + {this.state.saveGoogle + ? googleDriveActive + : googleDriveInactive + } + + {this.state.confirmGoogleTransfer && +
+ { this.state.saveGoogle + ? this.props.googleDriveOptions[0] + : this.props.googleDriveOptions[1] + } +
+
+ Yes +
+
+ No +
+
+ } + + {this.state.alertLoginToTransfer && +
+ You must be signed in to a Google account to transfer + between the homebrewery and Google Drive! + +
+ Sign In +
+
+
+ Not Now +
+
+ } +
; + }, + + renderSaveButton : function(){ + if(this.state.errors){ + let errMsg = ''; + try { + errMsg += `${this.state.errors.toString()}\n\n`; + errMsg += `\`\`\`\n${this.state.errors.stack}\n`; + errMsg += `${JSON.stringify(this.state.errors.response.error, null, ' ')}\n\`\`\``; + console.log(errMsg); + } catch (e){} + + if(this.state.errors.status == '401'){ + return + Oops! +
+ You must be signed in to a Google account + to save this to
Google Drive!
+ +
+ Sign In +
+
+
+ Not Now +
+
+
; + } + + if(this.state.errors.status == '403' && this.state.errors.response.body.errors[0].reason == 'insufficientPermissions'){ + return + Oops! +
+ Looks like your Google credentials have + expired! Visit the log in page to sign out + and sign back in with Google + to save this to Google Drive! + +
+ Sign In +
+
+
+ Not Now +
+
+
; + } + + return + Oops! +
+ Looks like there was a problem saving.
+ Report the issue + here + . +
+
; + } + + if(this.state.isSaving){ + return saving...; + } + if(this.state.isPending && this.hasChanges()){ + return Save Now; + } + if(!this.state.isPending && !this.state.isSaving){ + return saved.; + } + }, + + processShareId : function() { + return this.state.brew.googleId ? + this.state.brew.googleId + this.state.brew.shareId : + this.state.brew.shareId; + }, + + getRedditLink : function(){ + + const shareLink = this.processShareId(); + const systems = this.props.brew.systems.length > 0 ? ` [${this.props.brew.systems.join(' - ')}]` : ''; + 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. + +**[Homebrewery Link](https://homebrewery.naturalcrit.com/share/${shareLink})**`; + + return `https://www.reddit.com/r/UnearthedArcana/submit?title=${encodeURIComponent(title)}&text=${encodeURIComponent(text)}`; + }, + + renderNavbar : function(){ + const shareLink = this.processShareId(); + + return + + {this.state.alertTrashedGoogleBrew && +
+ This brew is currently in your Trash folder on Google Drive!
If you want to keep it, make sure to move it before it is deleted permanently!
+
+ OK +
+
+ } + + + {this.state.brew.title} + + + + {this.renderGoogleDriveIcon()} + {this.renderSaveButton()} + + + {this.isEdit() && <> + + + share + + + view + + {navigator.clipboard.writeText(`https://homebrewery.naturalcrit.com/share/${shareLink}`);}}> + copy url + + + post to reddit + + + + + } + + + + +
; + }, + + render : function(){ + return
+ + {this.renderNavbar()} + +
+ + + + +
+
; + } +}); + +module.exports = EditorPage; diff --git a/client/homebrew/pages/basePages/editorPage/editorPage.less b/client/homebrew/pages/basePages/editorPage/editorPage.less new file mode 100644 index 000000000..0cbfadcbd --- /dev/null +++ b/client/homebrew/pages/basePages/editorPage/editorPage.less @@ -0,0 +1,99 @@ +@keyframes glideDown { + 0% {transform : translate(-50% + 3px, 0px); + opacity : 0;} + 100% {transform : translate(-50% + 3px, 10px); + opacity : 1;} +} +.editPage{ + .navItem.save{ + width : 106px; + text-align : center; + position : relative; + &.saved{ + cursor : initial; + color : #666; + } + &.error{ + position : relative; + background-color : @red; + } + } + .googleDriveStorage { + position : relative; + } + .googleDriveStorage img{ + height : 20px; + padding : 0px; + margin : -5px; + } + .errorContainer{ + animation-name: glideDown; + animation-duration: 0.4s; + position : absolute; + top : 100%; + left : 50%; + z-index : 100000; + width : 140px; + padding : 3px; + color : white; + background-color : #333; + border : 3px solid #444; + border-radius : 5px; + transform : translate(-50% + 3px, 10px); + text-align : center; + font-size : 10px; + font-weight : 800; + text-transform : uppercase; + a{ + color : @teal; + } + &:before { + content: ""; + width: 0px; + height: 0px; + position: absolute; + border-left: 10px solid transparent; + border-right: 10px solid transparent; + border-top: 10px solid transparent; + border-bottom: 10px solid #444; + left: 53px; + top: -23px; + } + &:after { + content: ""; + width: 0px; + height: 0px; + position: absolute; + border-left: 10px solid transparent; + border-right: 10px solid transparent; + border-top: 10px solid transparent; + border-bottom: 10px solid #333; + left: 53px; + top: -19px; + } + .deny { + width : 48%; + margin : 1px; + padding : 5px; + background-color : #333; + display : inline-block; + border-left : 1px solid #666; + .animate(background-color); + &:hover{ + background-color : red; + } + } + .confirm { + width : 48%; + margin : 1px; + padding : 5px; + background-color : #333; + display : inline-block; + color : white; + .animate(background-color); + &:hover{ + background-color : teal; + } + } + } +} diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index ee4f41f5b..8e70cee32 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -3,28 +3,8 @@ require('./editPage.less'); const React = require('react'); const createClass = require('create-react-class'); const _ = require('lodash'); -const request = require('superagent'); -const { Meta } = require('vitreum/headtags'); -const Nav = require('naturalcrit/nav/nav.jsx'); -const Navbar = require('../../navbar/navbar.jsx'); - -const NewBrew = require('../../navbar/newbrew.navitem.jsx'); -const ReportIssue = require('../../navbar/issue.navitem.jsx'); -const PrintLink = require('../../navbar/print.navitem.jsx'); -const Account = require('../../navbar/account.navitem.jsx'); -const RecentNavItem = require('../../navbar/recent.navitem.jsx').both; - -const SplitPane = require('naturalcrit/splitPane/splitPane.jsx'); -const Editor = require('../../editor/editor.jsx'); -const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx'); - -const Markdown = require('naturalcrit/markdown.js'); - -const googleDriveActive = require('../../googleDrive.png'); -const googleDriveInactive = require('../../googleDriveMono.png'); - -const SAVE_TIMEOUT = 3000; +const EditorPage = require('../basePages/editorPage/editorPage.jsx'); const EditPage = createClass({ getDefaultProps : function() { @@ -50,431 +30,17 @@ const EditPage = createClass({ }; }, - getInitialState : function() { - return { - brew : this.props.brew, - isSaving : false, - isPending : false, - alertTrashedGoogleBrew : this.props.brew.trashed, - alertLoginToTransfer : false, - saveGoogle : this.props.brew.googleId ? true : false, - confirmGoogleTransfer : false, - errors : null, - htmlErrors : Markdown.validate(this.props.brew.text), - url : '' - }; - }, - savedBrew : null, - - componentDidMount : function(){ - this.setState({ - url : window.location.href - }); - - this.savedBrew = JSON.parse(JSON.stringify(this.props.brew)); //Deep copy - - this.trySave(); - window.onbeforeunload = ()=>{ - if(this.state.isSaving || this.state.isPending){ - return 'You have unsaved changes!'; - } - }; - - this.setState((prevState)=>({ - htmlErrors : Markdown.validate(prevState.brew.text) - })); - - document.addEventListener('keydown', this.handleControlKeys); - }, - componentWillUnmount : function() { - window.onbeforeunload = function(){}; - document.removeEventListener('keydown', this.handleControlKeys); - }, - - handleControlKeys : function(e){ - if(!(e.ctrlKey || e.metaKey)) return; - const S_KEY = 83; - const P_KEY = 80; - if(e.keyCode == S_KEY) this.save(); - if(e.keyCode == P_KEY) window.open(`/print/${this.processShareId()}?dialog=true`, '_blank').focus(); - if(e.keyCode == P_KEY || e.keyCode == S_KEY){ - e.stopPropagation(); - e.preventDefault(); - } - }, - - handleSplitMove : function(){ - this.refs.editor.update(); - }, - - handleTextChange : function(text){ - //If there are errors, run the validator on every change to give quick feedback - let htmlErrors = this.state.htmlErrors; - if(htmlErrors.length) htmlErrors = Markdown.validate(text); - - this.setState((prevState)=>({ - brew : _.merge({}, prevState.brew, { text: text }), - isPending : true, - htmlErrors : htmlErrors - }), ()=>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(){ - return !_.isEqual(this.state.brew, this.savedBrew); - }, - - trySave : function(){ - if(!this.debounceSave) this.debounceSave = _.debounce(this.save, SAVE_TIMEOUT); - if(this.hasChanges()){ - this.debounceSave(); - } else { - this.debounceSave.cancel(); - } - }, - - handleGoogleClick : function(){ - if(!global.account?.googleId) { - this.setState({ - alertLoginToTransfer : true - }); - return; - } - this.setState((prevState)=>({ - confirmGoogleTransfer : !prevState.confirmGoogleTransfer - })); - this.clearErrors(); - }, - - closeAlerts : function(event){ - event.stopPropagation(); //Only handle click once so alert doesn't reopen - this.setState({ - alertTrashedGoogleBrew : false, - alertLoginToTransfer : false, - confirmGoogleTransfer : false - }); - }, - - toggleGoogleStorage : function(){ - this.setState((prevState)=>({ - saveGoogle : !prevState.saveGoogle, - isSaving : false, - errors : null - }), ()=>this.save()); - }, - - clearErrors : function(){ - this.setState({ - errors : null, - isSaving : false - - }); - }, - - save : async function(){ - if(this.debounceSave && this.debounceSave.cancel) this.debounceSave.cancel(); - - this.setState((prevState)=>({ - isSaving : true, - errors : null, - htmlErrors : Markdown.validate(prevState.brew.text) - })); - - const transfer = this.state.saveGoogle == _.isNil(this.state.brew.googleId); - - const brew = this.state.brew; - brew.pageCount = ((brew.renderer=='legacy' ? brew.text.match(/\\page/g) : brew.text.match(/^\\page$/gm)) || []).length + 1; - - if(this.state.saveGoogle) { - if(transfer) { - const res = await request - .post('/api/newGoogle/') - .send(brew) - .catch((err)=>{ - console.log(err.status === 401 - ? 'Not signed in!' - : 'Error Transferring to Google!'); - this.setState({ errors: err, saveGoogle: false }); - }); - - if(!res) { return; } - - console.log('Deleting Local Copy'); - await request.delete(`/api/${brew.editId}`) - .send() - .catch((err)=>{ - console.log('Error deleting Local Copy'); - }); - - this.savedBrew = res.body; - history.replaceState(null, null, `/edit/${this.savedBrew.googleId}${this.savedBrew.editId}`); //update URL to match doc ID - } else { - const res = await request - .put(`/api/updateGoogle/${brew.editId}`) - .send(brew) - .catch((err)=>{ - console.log(err.status === 401 - ? 'Not signed in!' - : 'Error Saving to Google!'); - this.setState({ errors: err }); - return; - }); - - this.savedBrew = res.body; - } - } else { - if(transfer) { - const res = await request.post('/api') - .send(brew) - .catch((err)=>{ - console.log('Error creating Local Copy'); - this.setState({ errors: err }); - return; - }); - - await request.get(`/api/removeGoogle/${brew.googleId}${brew.editId}`) - .send() - .catch((err)=>{ - console.log('Error Deleting Google Brew'); - }); - - this.savedBrew = res.body; - history.replaceState(null, null, `/edit/${this.savedBrew.editId}`); //update URL to match doc ID - } else { - const res = await request - .put(`/api/update/${brew.editId}`) - .send(brew) - .catch((err)=>{ - console.log('Error Updating Local Brew'); - this.setState({ errors: err }); - return; - }); - - this.savedBrew = res.body; - } - } - - this.setState((prevState)=>({ - brew : _.merge({}, prevState.brew, { - googleId : this.savedBrew.googleId ? this.savedBrew.googleId : null, - editId : this.savedBrew.editId, - shareId : this.savedBrew.shareId - }), - isPending : false, - isSaving : false, - })); - }, - - renderGoogleDriveIcon : function(){ - return - {this.state.saveGoogle - ? googleDriveActive - : googleDriveInactive - } - - {this.state.confirmGoogleTransfer && -
- { this.state.saveGoogle - ? `Would you like to transfer this brew from your Google Drive storage back to the Homebrewery?` - : `Would you like to transfer this brew from the Homebrewery to your personal Google Drive storage?` - } -
-
- Yes -
-
- No -
-
- } - - {this.state.alertLoginToTransfer && -
- You must be signed in to a Google account to transfer - between the homebrewery and Google Drive! - -
- Sign In -
-
-
- Not Now -
-
- } -
; - }, - - renderSaveButton : function(){ - if(this.state.errors){ - let errMsg = ''; - try { - errMsg += `${this.state.errors.toString()}\n\n`; - errMsg += `\`\`\`\n${this.state.errors.stack}\n`; - errMsg += `${JSON.stringify(this.state.errors.response.error, null, ' ')}\n\`\`\``; - console.log(errMsg); - } catch (e){} - - if(this.state.errors.status == '401'){ - return - Oops! -
- You must be signed in to a Google account - to save this to
Google Drive!
- -
- Sign In -
-
-
- Not Now -
-
-
; - } - - if(this.state.errors.status == '403' && this.state.errors.response.body.errors[0].reason == 'insufficientPermissions'){ - return - Oops! -
- Looks like your Google credentials have - expired! Visit the log in page to sign out - and sign back in with Google - to save this to Google Drive! - -
- Sign In -
-
-
- Not Now -
-
-
; - } - - return - Oops! -
- Looks like there was a problem saving.
- Report the issue - here - . -
-
; - } - - if(this.state.isSaving){ - return saving...; - } - if(this.state.isPending && this.hasChanges()){ - return Save Now; - } - if(!this.state.isPending && !this.state.isSaving){ - return saved.; - } - }, - - processShareId : function() { - return this.state.brew.googleId ? - this.state.brew.googleId + this.state.brew.shareId : - this.state.brew.shareId; - }, - - getRedditLink : function(){ - - const shareLink = this.processShareId(); - const systems = this.props.brew.systems.length > 0 ? ` [${this.props.brew.systems.join(' - ')}]` : ''; - 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. - -**[Homebrewery Link](https://homebrewery.naturalcrit.com/share/${shareLink})**`; - - return `https://www.reddit.com/r/UnearthedArcana/submit?title=${encodeURIComponent(title)}&text=${encodeURIComponent(text)}`; - }, - - renderNavbar : function(){ - const shareLink = this.processShareId(); - - return - - {this.state.alertTrashedGoogleBrew && -
- This brew is currently in your Trash folder on Google Drive!
If you want to keep it, make sure to move it before it is deleted permanently!
-
- OK -
-
- } - - - {this.state.brew.title} - - - - {this.renderGoogleDriveIcon()} - {this.renderSaveButton()} - - - - - share - - - view - - {navigator.clipboard.writeText(`https://homebrewery.naturalcrit.com/share/${shareLink}`);}}> - copy url - - - post to reddit - - - - - - - -
; - }, - render : function(){ - return
- - {this.renderNavbar()} + const googleDriveOptions = [ + 'Would you like to transfer this brew from your Google Drive storage back to the Homebrewery?', + 'Would you like to transfer this brew from the Homebrewery to your personal Google Drive storage?' + ]; -
- - - - -
-
; + return ; } }); diff --git a/client/homebrew/pages/newPage/newPage.jsx b/client/homebrew/pages/newPage/newPage.jsx index 3f09cac4e..0e424a998 100644 --- a/client/homebrew/pages/newPage/newPage.jsx +++ b/client/homebrew/pages/newPage/newPage.jsx @@ -3,20 +3,11 @@ require('./newPage.less'); const React = require('react'); const createClass = require('create-react-class'); const _ = require('lodash'); -const request = require('superagent'); + +const EditorPage = require('../basePages/editorPage/editorPage.jsx'); const Markdown = require('naturalcrit/markdown.js'); -const Nav = require('naturalcrit/nav/nav.jsx'); -const Navbar = require('../../navbar/navbar.jsx'); -const AccountNavItem = require('../../navbar/account.navitem.jsx'); -const RecentNavItem = require('../../navbar/recent.navitem.jsx').both; -const IssueNavItem = require('../../navbar/issue.navitem.jsx'); - -const SplitPane = require('naturalcrit/splitPane/splitPane.jsx'); -const Editor = require('../../editor/editor.jsx'); -const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx'); - const BREWKEY = 'homebrewery-new'; const STYLEKEY = 'homebrewery-new-style'; const METAKEY = 'homebrewery-new-meta'; @@ -82,238 +73,13 @@ const NewPage = createClass({ }; }, - componentDidMount : function() { - document.addEventListener('keydown', this.handleControlKeys); - }, - componentWillUnmount : function() { - document.removeEventListener('keydown', this.handleControlKeys); - }, - - handleControlKeys : function(e){ - if(!(e.ctrlKey || e.metaKey)) return; - const S_KEY = 83; - const P_KEY = 80; - if(e.keyCode == S_KEY) this.save(); - if(e.keyCode == P_KEY) this.print(); - if(e.keyCode == P_KEY || e.keyCode == S_KEY){ - e.stopPropagation(); - e.preventDefault(); - } - }, - - handleSplitMove : function(){ - this.refs.editor.update(); - }, - - handleTextChange : function(text){ - //If there are errors, run the validator on every change to give quick feedback - 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(BREWKEY, text); - }, - - handleStyleChange : function(style){ - this.setState((prevState)=>({ - brew : _.merge({}, prevState.brew, { style: style }), - })); - localStorage.setItem(STYLEKEY, style); - }, - - handleMetaChange : function(metadata){ - this.setState((prevState)=>({ - brew : _.merge({}, prevState.brew, metadata), - })); - localStorage.setItem(METAKEY, JSON.stringify({ - // 'title' : this.state.brew.title, - // 'description' : this.state.brew.description, - 'renderer' : this.state.brew.renderer - })); - }, - - clearErrors : function(){ - this.setState({ - errors : null, - isSaving : false - - }); - }, - - save : async function(){ - this.setState({ - isSaving : true - }); - - 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); - }; - - brew.pageCount=((brew.renderer=='legacy' ? brew.text.match(/\\page/g) : brew.text.match(/^\\page$/gm)) || []).length + 1; - - if(this.state.saveGoogle) { - const res = await request - .post('/api/newGoogle/') - .send(brew) - .catch((err)=>{ - console.log(err.status === 401 - ? 'Not signed in!' - : 'Error Creating New Google Brew!'); - this.setState({ isSaving: false, errors: err }); - return; - }); - - brew = res.body; - localStorage.removeItem(BREWKEY); - localStorage.removeItem(STYLEKEY); - localStorage.removeItem(METAKEY); - window.location = `/edit/${brew.googleId}${brew.editId}`; - } else { - request.post('/api') - .send(brew) - .end((err, res)=>{ - if(err){ - this.setState({ - isSaving : false - }); - return; - } - window.onbeforeunload = function(){}; - brew = res.body; - localStorage.removeItem(BREWKEY); - localStorage.removeItem(STYLEKEY); - localStorage.removeItem(METAKEY); - window.location = `/edit/${brew.editId}`; - }); - } - }, - - renderSaveButton : function(){ - if(this.state.errors){ - let errMsg = ''; - try { - errMsg += `${this.state.errors.toString()}\n\n`; - errMsg += `\`\`\`\n${this.state.errors.stack}\n`; - errMsg += `${JSON.stringify(this.state.errors.response.error, null, ' ')}\n\`\`\``; - console.log(errMsg); - } catch (e){} - - if(this.state.errors.status == '401'){ - return - Oops! -
- You must be signed in to a Google account - to save this to
Google Drive!
- -
- Sign In -
-
-
- Not Now -
-
-
; - } - - if(this.state.errors.status == '403' && this.state.errors.response.body.errors[0].reason == 'insufficientPermissions'){ - return - Oops! -
- Looks like your Google credentials have - expired! Visit the log in page to sign out - and sign back in with Google - to save this to Google Drive! - -
- Sign In -
-
-
- Not Now -
-
-
; - } - - return - Oops! -
- Looks like there was a problem saving.
- Report the issue - here - . -
-
; - } - - if(this.state.isSaving){ - return - save... - ; - } else { - return - save - ; - } - }, - - print : function(){ - window.open('/print?dialog=true&local=print', '_blank'); - }, - - renderLocalPrintButton : function(){ - return - get PDF - ; - }, - - renderNavbar : function(){ - return - - - {this.state.brew.title} - - - - {this.renderSaveButton()} - {this.renderLocalPrintButton()} - - - - - ; - }, - render : function(){ - return
- {this.renderNavbar()} -
- - - - -
-
; + const googleDriveOptions = [ + 'Set save location to the Homebrewery?', + 'Set save location to your personal Google Drive storage?' + ]; + + return ; } }); From f422b22af14898352054f915e2bd0b4895fec56e Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Thu, 4 Nov 2021 14:07:11 +1300 Subject: [PATCH 5/5] Revert "Initial pass at `editorPage.jsx`." This reverts commit 6cd56dfd6213011969937e962e0b0ca5d8109d97. --- .../pages/basePages/editorPage/editorPage.jsx | 578 ------------------ .../basePages/editorPage/editorPage.less | 99 --- client/homebrew/pages/editPage/editPage.jsx | 456 +++++++++++++- client/homebrew/pages/newPage/newPage.jsx | 250 +++++++- 4 files changed, 687 insertions(+), 696 deletions(-) delete mode 100644 client/homebrew/pages/basePages/editorPage/editorPage.jsx delete mode 100644 client/homebrew/pages/basePages/editorPage/editorPage.less diff --git a/client/homebrew/pages/basePages/editorPage/editorPage.jsx b/client/homebrew/pages/basePages/editorPage/editorPage.jsx deleted file mode 100644 index 99a5c2b52..000000000 --- a/client/homebrew/pages/basePages/editorPage/editorPage.jsx +++ /dev/null @@ -1,578 +0,0 @@ -/* eslint-disable max-lines */ -require('./editorPage.less'); -const React = require('react'); -const createClass = require('create-react-class'); -const _ = require('lodash'); -const request = require('superagent'); -const { Meta } = require('vitreum/headtags'); - -const Nav = require('naturalcrit/nav/nav.jsx'); -const Navbar = require('../../../navbar/navbar.jsx'); - -const NewBrew = require('../../../navbar/newbrew.navitem.jsx'); -const ReportIssue = require('../../../navbar/issue.navitem.jsx'); -const PrintLink = require('../../../navbar/print.navitem.jsx'); -const Account = require('../../../navbar/account.navitem.jsx'); -const RecentNavItem = require('../../../navbar/recent.navitem.jsx').both; - -const SplitPane = require('naturalcrit/splitPane/splitPane.jsx'); -const Editor = require('../../../editor/editor.jsx'); -const BrewRenderer = require('../../../brewRenderer/brewRenderer.jsx'); - -const Markdown = require('naturalcrit/markdown.js'); - -const googleDriveActive = require('../../../googleDrive.png'); -const googleDriveInactive = require('../../../googleDriveMono.png'); - -const SAVE_TIMEOUT = 3000; - -const BREWKEY = 'homebrewery-new'; -const STYLEKEY = 'homebrewery-new-style'; -const METAKEY = 'homebrewery-new-meta'; - -const EditorPage = createClass({ - getDefaultProps : function() { - return { - brew : { - text : '', - style : '', - shareId : null, - editId : null, - createdAt : null, - updatedAt : null, - gDrive : false, - trashed : false, - - title : '', - description : '', - tags : '', - published : false, - authors : [], - systems : [], - renderer : 'legacy' - }, - pageType : 'edit', - googleDriveOptions : [ - 'DRIVE > HB', - 'HB > DRIVE' - ] - }; - }, - - getInitialState : function() { - return { - brew : this.props.brew, - isSaving : false, - isPending : false, - alertTrashedGoogleBrew : this.props.brew.trashed, - alertLoginToTransfer : false, - saveGoogle : this.props.brew.googleId ? true : false, - confirmGoogleTransfer : false, - errors : null, - htmlErrors : Markdown.validate(this.props.brew.text), - url : '' - }; - // return { - // brew : { - // text : brew.text || '', - // style : brew.style || undefined, - // gDrive : false, - // title : brew.title || '', - // description : brew.description || '', - // tags : brew.tags || '', - // published : false, - // authors : [], - // systems : brew.systems || [], - // renderer : brew.renderer || 'legacy' - // }, - - // isSaving : false, - // isPending : false, - // alertTrashedGoogleBrew : this.props.brew.trashed, - // alertLoginToTransfer : false, - // saveGoogle : (global.account && global.account.googleId ? true : false), - // confirmGoogleTransfer : false, - // errors : null, - // htmlErrors : Markdown.validate(brew.text), - // url : '' - // }; - }, - savedBrew : null, - - componentDidMount : function(){ - this.setState({ - url : window.location.href - }); - - this.savedBrew = JSON.parse(JSON.stringify(this.props.brew)); //Deep copy - - this.trySave(); - window.onbeforeunload = ()=>{ - if(this.state.isSaving || this.state.isPending){ - return 'You have unsaved changes!'; - } - }; - - this.setState((prevState)=>({ - htmlErrors : Markdown.validate(prevState.brew.text) - })); - - document.addEventListener('keydown', this.handleControlKeys); - }, - componentWillUnmount : function() { - window.onbeforeunload = function(){}; - document.removeEventListener('keydown', this.handleControlKeys); - }, - - handleControlKeys : function(e){ - if(!(e.ctrlKey || e.metaKey)) return; - const S_KEY = 83; - const P_KEY = 80; - if(e.keyCode == S_KEY) this.save(); - if(e.keyCode == P_KEY) window.open(`/print/${this.processShareId()}?dialog=true`, '_blank').focus(); - if(e.keyCode == P_KEY || e.keyCode == S_KEY){ - e.stopPropagation(); - e.preventDefault(); - } - }, - - isEdit : function(){ - return this.props.pageType == 'edit'; - }, - - isNew : function(){ - return this.props.pageType == 'new'; - }, - - handleSplitMove : function(){ - this.refs.editor.update(); - }, - - handleTextChange : function(text){ - //If there are errors, run the validator on every change to give quick feedback - let htmlErrors = this.state.htmlErrors; - if(htmlErrors.length) htmlErrors = Markdown.validate(text); - - this.setState((prevState)=>({ - brew : _.merge({}, prevState.brew, { text: text }), - isPending : true, - htmlErrors : htmlErrors - }), ()=>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(){ - return !_.isEqual(this.state.brew, this.savedBrew); - }, - - trySave : function(){ - if(!this.isEdit()) return; - if(!this.debounceSave) this.debounceSave = _.debounce(this.save, SAVE_TIMEOUT); - if(this.hasChanges()){ - this.debounceSave(); - } else { - this.debounceSave.cancel(); - } - }, - - handleGoogleClick : function(){ - if(!global.account?.googleId) { - this.setState({ - alertLoginToTransfer : true - }); - return; - } - this.setState((prevState)=>({ - confirmGoogleTransfer : !prevState.confirmGoogleTransfer - })); - this.clearErrors(); - }, - - closeAlerts : function(event){ - event.stopPropagation(); //Only handle click once so alert doesn't reopen - this.setState({ - alertTrashedGoogleBrew : false, - alertLoginToTransfer : false, - confirmGoogleTransfer : false - }); - }, - - toggleGoogleStorage : function(){ - this.setState((prevState)=>({ - saveGoogle : !prevState.saveGoogle, - isSaving : false, - errors : null - }), ()=>this.isEdit() && this.save()); - }, - - clearErrors : function(){ - this.setState({ - errors : null, - isSaving : false - - }); - }, - - save : async function(){ - this.setState((prevState)=>({ - isSaving : true, - errors : null, - htmlErrors : Markdown.validate(prevState.brew.text) - })); - - if(this.isEdit()){ - if(this.debounceSave && this.debounceSave.cancel) this.debounceSave.cancel(); - - const transfer = this.state.saveGoogle == _.isNil(this.state.brew.googleId); - - const brew = this.state.brew; - brew.pageCount = ((brew.renderer=='legacy' ? brew.text.match(/\\page/g) : brew.text.match(/^\\page$/gm)) || []).length + 1; - - if(this.state.saveGoogle) { - if(transfer) { - const res = await request - .post('/api/newGoogle/') - .send(brew) - .catch((err)=>{ - console.log(err.status === 401 - ? 'Not signed in!' - : 'Error Transferring to Google!'); - this.setState({ errors: err, saveGoogle: false }); - }); - - if(!res) { return; } - - console.log('Deleting Local Copy'); - await request.delete(`/api/${brew.editId}`) - .send() - .catch((err)=>{ - console.log('Error deleting Local Copy'); - }); - - this.savedBrew = res.body; - history.replaceState(null, null, `/edit/${this.savedBrew.googleId}${this.savedBrew.editId}`); //update URL to match doc ID - } else { - const res = await request - .put(`/api/updateGoogle/${brew.editId}`) - .send(brew) - .catch((err)=>{ - console.log(err.status === 401 - ? 'Not signed in!' - : 'Error Saving to Google!'); - this.setState({ errors: err }); - return; - }); - - this.savedBrew = res.body; - } - } else { - if(transfer) { - const res = await request.post('/api') - .send(brew) - .catch((err)=>{ - console.log('Error creating Local Copy'); - this.setState({ errors: err }); - return; - }); - - await request.get(`/api/removeGoogle/${brew.googleId}${brew.editId}`) - .send() - .catch((err)=>{ - console.log('Error Deleting Google Brew'); - }); - - this.savedBrew = res.body; - history.replaceState(null, null, `/edit/${this.savedBrew.editId}`); //update URL to match doc ID - } else { - const res = await request - .put(`/api/update/${brew.editId}`) - .send(brew) - .catch((err)=>{ - console.log('Error Updating Local Brew'); - this.setState({ errors: err }); - return; - }); - - this.savedBrew = res.body; - } - } - - this.setState((prevState)=>({ - brew : _.merge({}, prevState.brew, { - googleId : this.savedBrew.googleId ? this.savedBrew.googleId : null, - editId : this.savedBrew.editId, - shareId : this.savedBrew.shareId - }), - isPending : false, - isSaving : false, - })); - } - - if(this.isNew()){ - 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); - }; - - brew.pageCount=((brew.renderer=='legacy' ? brew.text.match(/\\page/g) : brew.text.match(/^\\page$/gm)) || []).length + 1; - - if(this.state.saveGoogle) { - const res = await request - .post('/api/newGoogle/') - .send(brew) - .catch((err)=>{ - console.log(err.status === 401 - ? 'Not signed in!' - : 'Error Creating New Google Brew!'); - this.setState({ isSaving: false, errors: err }); - return; - }); - - brew = res.body; - localStorage.removeItem(BREWKEY); - localStorage.removeItem(STYLEKEY); - localStorage.removeItem(METAKEY); - window.location = `/edit/${brew.googleId}${brew.editId}`; - } else { - request.post('/api') - .send(brew) - .end((err, res)=>{ - if(err){ - this.setState({ - isSaving : false - }); - return; - } - window.onbeforeunload = function(){}; - brew = res.body; - localStorage.removeItem(BREWKEY); - localStorage.removeItem(STYLEKEY); - localStorage.removeItem(METAKEY); - window.location = `/edit/${brew.editId}`; - }); - } - } - }, - - renderGoogleDriveIcon : function(){ - return - {this.state.saveGoogle - ? googleDriveActive - : googleDriveInactive - } - - {this.state.confirmGoogleTransfer && -
- { this.state.saveGoogle - ? this.props.googleDriveOptions[0] - : this.props.googleDriveOptions[1] - } -
-
- Yes -
-
- No -
-
- } - - {this.state.alertLoginToTransfer && -
- You must be signed in to a Google account to transfer - between the homebrewery and Google Drive! - -
- Sign In -
-
-
- Not Now -
-
- } -
; - }, - - renderSaveButton : function(){ - if(this.state.errors){ - let errMsg = ''; - try { - errMsg += `${this.state.errors.toString()}\n\n`; - errMsg += `\`\`\`\n${this.state.errors.stack}\n`; - errMsg += `${JSON.stringify(this.state.errors.response.error, null, ' ')}\n\`\`\``; - console.log(errMsg); - } catch (e){} - - if(this.state.errors.status == '401'){ - return - Oops! -
- You must be signed in to a Google account - to save this to
Google Drive!
- -
- Sign In -
-
-
- Not Now -
-
-
; - } - - if(this.state.errors.status == '403' && this.state.errors.response.body.errors[0].reason == 'insufficientPermissions'){ - return - Oops! -
- Looks like your Google credentials have - expired! Visit the log in page to sign out - and sign back in with Google - to save this to Google Drive! - -
- Sign In -
-
-
- Not Now -
-
-
; - } - - return - Oops! -
- Looks like there was a problem saving.
- Report the issue - here - . -
-
; - } - - if(this.state.isSaving){ - return saving...; - } - if(this.state.isPending && this.hasChanges()){ - return Save Now; - } - if(!this.state.isPending && !this.state.isSaving){ - return saved.; - } - }, - - processShareId : function() { - return this.state.brew.googleId ? - this.state.brew.googleId + this.state.brew.shareId : - this.state.brew.shareId; - }, - - getRedditLink : function(){ - - const shareLink = this.processShareId(); - const systems = this.props.brew.systems.length > 0 ? ` [${this.props.brew.systems.join(' - ')}]` : ''; - 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. - -**[Homebrewery Link](https://homebrewery.naturalcrit.com/share/${shareLink})**`; - - return `https://www.reddit.com/r/UnearthedArcana/submit?title=${encodeURIComponent(title)}&text=${encodeURIComponent(text)}`; - }, - - renderNavbar : function(){ - const shareLink = this.processShareId(); - - return - - {this.state.alertTrashedGoogleBrew && -
- This brew is currently in your Trash folder on Google Drive!
If you want to keep it, make sure to move it before it is deleted permanently!
-
- OK -
-
- } - - - {this.state.brew.title} - - - - {this.renderGoogleDriveIcon()} - {this.renderSaveButton()} - - - {this.isEdit() && <> - - - share - - - view - - {navigator.clipboard.writeText(`https://homebrewery.naturalcrit.com/share/${shareLink}`);}}> - copy url - - - post to reddit - - - - - } - - - - -
; - }, - - render : function(){ - return
- - {this.renderNavbar()} - -
- - - - -
-
; - } -}); - -module.exports = EditorPage; diff --git a/client/homebrew/pages/basePages/editorPage/editorPage.less b/client/homebrew/pages/basePages/editorPage/editorPage.less deleted file mode 100644 index 0cbfadcbd..000000000 --- a/client/homebrew/pages/basePages/editorPage/editorPage.less +++ /dev/null @@ -1,99 +0,0 @@ -@keyframes glideDown { - 0% {transform : translate(-50% + 3px, 0px); - opacity : 0;} - 100% {transform : translate(-50% + 3px, 10px); - opacity : 1;} -} -.editPage{ - .navItem.save{ - width : 106px; - text-align : center; - position : relative; - &.saved{ - cursor : initial; - color : #666; - } - &.error{ - position : relative; - background-color : @red; - } - } - .googleDriveStorage { - position : relative; - } - .googleDriveStorage img{ - height : 20px; - padding : 0px; - margin : -5px; - } - .errorContainer{ - animation-name: glideDown; - animation-duration: 0.4s; - position : absolute; - top : 100%; - left : 50%; - z-index : 100000; - width : 140px; - padding : 3px; - color : white; - background-color : #333; - border : 3px solid #444; - border-radius : 5px; - transform : translate(-50% + 3px, 10px); - text-align : center; - font-size : 10px; - font-weight : 800; - text-transform : uppercase; - a{ - color : @teal; - } - &:before { - content: ""; - width: 0px; - height: 0px; - position: absolute; - border-left: 10px solid transparent; - border-right: 10px solid transparent; - border-top: 10px solid transparent; - border-bottom: 10px solid #444; - left: 53px; - top: -23px; - } - &:after { - content: ""; - width: 0px; - height: 0px; - position: absolute; - border-left: 10px solid transparent; - border-right: 10px solid transparent; - border-top: 10px solid transparent; - border-bottom: 10px solid #333; - left: 53px; - top: -19px; - } - .deny { - width : 48%; - margin : 1px; - padding : 5px; - background-color : #333; - display : inline-block; - border-left : 1px solid #666; - .animate(background-color); - &:hover{ - background-color : red; - } - } - .confirm { - width : 48%; - margin : 1px; - padding : 5px; - background-color : #333; - display : inline-block; - color : white; - .animate(background-color); - &:hover{ - background-color : teal; - } - } - } -} diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index 8e70cee32..ee4f41f5b 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -3,8 +3,28 @@ require('./editPage.less'); const React = require('react'); const createClass = require('create-react-class'); const _ = require('lodash'); +const request = require('superagent'); +const { Meta } = require('vitreum/headtags'); -const EditorPage = require('../basePages/editorPage/editorPage.jsx'); +const Nav = require('naturalcrit/nav/nav.jsx'); +const Navbar = require('../../navbar/navbar.jsx'); + +const NewBrew = require('../../navbar/newbrew.navitem.jsx'); +const ReportIssue = require('../../navbar/issue.navitem.jsx'); +const PrintLink = require('../../navbar/print.navitem.jsx'); +const Account = require('../../navbar/account.navitem.jsx'); +const RecentNavItem = require('../../navbar/recent.navitem.jsx').both; + +const SplitPane = require('naturalcrit/splitPane/splitPane.jsx'); +const Editor = require('../../editor/editor.jsx'); +const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx'); + +const Markdown = require('naturalcrit/markdown.js'); + +const googleDriveActive = require('../../googleDrive.png'); +const googleDriveInactive = require('../../googleDriveMono.png'); + +const SAVE_TIMEOUT = 3000; const EditPage = createClass({ getDefaultProps : function() { @@ -30,17 +50,431 @@ const EditPage = createClass({ }; }, - render : function(){ - const googleDriveOptions = [ - 'Would you like to transfer this brew from your Google Drive storage back to the Homebrewery?', - 'Would you like to transfer this brew from the Homebrewery to your personal Google Drive storage?' - ]; + getInitialState : function() { + return { + brew : this.props.brew, + isSaving : false, + isPending : false, + alertTrashedGoogleBrew : this.props.brew.trashed, + alertLoginToTransfer : false, + saveGoogle : this.props.brew.googleId ? true : false, + confirmGoogleTransfer : false, + errors : null, + htmlErrors : Markdown.validate(this.props.brew.text), + url : '' + }; + }, + savedBrew : null, - return ; + componentDidMount : function(){ + this.setState({ + url : window.location.href + }); + + this.savedBrew = JSON.parse(JSON.stringify(this.props.brew)); //Deep copy + + this.trySave(); + window.onbeforeunload = ()=>{ + if(this.state.isSaving || this.state.isPending){ + return 'You have unsaved changes!'; + } + }; + + this.setState((prevState)=>({ + htmlErrors : Markdown.validate(prevState.brew.text) + })); + + document.addEventListener('keydown', this.handleControlKeys); + }, + componentWillUnmount : function() { + window.onbeforeunload = function(){}; + document.removeEventListener('keydown', this.handleControlKeys); + }, + + handleControlKeys : function(e){ + if(!(e.ctrlKey || e.metaKey)) return; + const S_KEY = 83; + const P_KEY = 80; + if(e.keyCode == S_KEY) this.save(); + if(e.keyCode == P_KEY) window.open(`/print/${this.processShareId()}?dialog=true`, '_blank').focus(); + if(e.keyCode == P_KEY || e.keyCode == S_KEY){ + e.stopPropagation(); + e.preventDefault(); + } + }, + + handleSplitMove : function(){ + this.refs.editor.update(); + }, + + handleTextChange : function(text){ + //If there are errors, run the validator on every change to give quick feedback + let htmlErrors = this.state.htmlErrors; + if(htmlErrors.length) htmlErrors = Markdown.validate(text); + + this.setState((prevState)=>({ + brew : _.merge({}, prevState.brew, { text: text }), + isPending : true, + htmlErrors : htmlErrors + }), ()=>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(){ + return !_.isEqual(this.state.brew, this.savedBrew); + }, + + trySave : function(){ + if(!this.debounceSave) this.debounceSave = _.debounce(this.save, SAVE_TIMEOUT); + if(this.hasChanges()){ + this.debounceSave(); + } else { + this.debounceSave.cancel(); + } + }, + + handleGoogleClick : function(){ + if(!global.account?.googleId) { + this.setState({ + alertLoginToTransfer : true + }); + return; + } + this.setState((prevState)=>({ + confirmGoogleTransfer : !prevState.confirmGoogleTransfer + })); + this.clearErrors(); + }, + + closeAlerts : function(event){ + event.stopPropagation(); //Only handle click once so alert doesn't reopen + this.setState({ + alertTrashedGoogleBrew : false, + alertLoginToTransfer : false, + confirmGoogleTransfer : false + }); + }, + + toggleGoogleStorage : function(){ + this.setState((prevState)=>({ + saveGoogle : !prevState.saveGoogle, + isSaving : false, + errors : null + }), ()=>this.save()); + }, + + clearErrors : function(){ + this.setState({ + errors : null, + isSaving : false + + }); + }, + + save : async function(){ + if(this.debounceSave && this.debounceSave.cancel) this.debounceSave.cancel(); + + this.setState((prevState)=>({ + isSaving : true, + errors : null, + htmlErrors : Markdown.validate(prevState.brew.text) + })); + + const transfer = this.state.saveGoogle == _.isNil(this.state.brew.googleId); + + const brew = this.state.brew; + brew.pageCount = ((brew.renderer=='legacy' ? brew.text.match(/\\page/g) : brew.text.match(/^\\page$/gm)) || []).length + 1; + + if(this.state.saveGoogle) { + if(transfer) { + const res = await request + .post('/api/newGoogle/') + .send(brew) + .catch((err)=>{ + console.log(err.status === 401 + ? 'Not signed in!' + : 'Error Transferring to Google!'); + this.setState({ errors: err, saveGoogle: false }); + }); + + if(!res) { return; } + + console.log('Deleting Local Copy'); + await request.delete(`/api/${brew.editId}`) + .send() + .catch((err)=>{ + console.log('Error deleting Local Copy'); + }); + + this.savedBrew = res.body; + history.replaceState(null, null, `/edit/${this.savedBrew.googleId}${this.savedBrew.editId}`); //update URL to match doc ID + } else { + const res = await request + .put(`/api/updateGoogle/${brew.editId}`) + .send(brew) + .catch((err)=>{ + console.log(err.status === 401 + ? 'Not signed in!' + : 'Error Saving to Google!'); + this.setState({ errors: err }); + return; + }); + + this.savedBrew = res.body; + } + } else { + if(transfer) { + const res = await request.post('/api') + .send(brew) + .catch((err)=>{ + console.log('Error creating Local Copy'); + this.setState({ errors: err }); + return; + }); + + await request.get(`/api/removeGoogle/${brew.googleId}${brew.editId}`) + .send() + .catch((err)=>{ + console.log('Error Deleting Google Brew'); + }); + + this.savedBrew = res.body; + history.replaceState(null, null, `/edit/${this.savedBrew.editId}`); //update URL to match doc ID + } else { + const res = await request + .put(`/api/update/${brew.editId}`) + .send(brew) + .catch((err)=>{ + console.log('Error Updating Local Brew'); + this.setState({ errors: err }); + return; + }); + + this.savedBrew = res.body; + } + } + + this.setState((prevState)=>({ + brew : _.merge({}, prevState.brew, { + googleId : this.savedBrew.googleId ? this.savedBrew.googleId : null, + editId : this.savedBrew.editId, + shareId : this.savedBrew.shareId + }), + isPending : false, + isSaving : false, + })); + }, + + renderGoogleDriveIcon : function(){ + return + {this.state.saveGoogle + ? googleDriveActive + : googleDriveInactive + } + + {this.state.confirmGoogleTransfer && +
+ { this.state.saveGoogle + ? `Would you like to transfer this brew from your Google Drive storage back to the Homebrewery?` + : `Would you like to transfer this brew from the Homebrewery to your personal Google Drive storage?` + } +
+
+ Yes +
+
+ No +
+
+ } + + {this.state.alertLoginToTransfer && +
+ You must be signed in to a Google account to transfer + between the homebrewery and Google Drive! + +
+ Sign In +
+
+
+ Not Now +
+
+ } +
; + }, + + renderSaveButton : function(){ + if(this.state.errors){ + let errMsg = ''; + try { + errMsg += `${this.state.errors.toString()}\n\n`; + errMsg += `\`\`\`\n${this.state.errors.stack}\n`; + errMsg += `${JSON.stringify(this.state.errors.response.error, null, ' ')}\n\`\`\``; + console.log(errMsg); + } catch (e){} + + if(this.state.errors.status == '401'){ + return + Oops! +
+ You must be signed in to a Google account + to save this to
Google Drive!
+ +
+ Sign In +
+
+
+ Not Now +
+
+
; + } + + if(this.state.errors.status == '403' && this.state.errors.response.body.errors[0].reason == 'insufficientPermissions'){ + return + Oops! +
+ Looks like your Google credentials have + expired! Visit the log in page to sign out + and sign back in with Google + to save this to Google Drive! + +
+ Sign In +
+
+
+ Not Now +
+
+
; + } + + return + Oops! +
+ Looks like there was a problem saving.
+ Report the issue + here + . +
+
; + } + + if(this.state.isSaving){ + return saving...; + } + if(this.state.isPending && this.hasChanges()){ + return Save Now; + } + if(!this.state.isPending && !this.state.isSaving){ + return saved.; + } + }, + + processShareId : function() { + return this.state.brew.googleId ? + this.state.brew.googleId + this.state.brew.shareId : + this.state.brew.shareId; + }, + + getRedditLink : function(){ + + const shareLink = this.processShareId(); + const systems = this.props.brew.systems.length > 0 ? ` [${this.props.brew.systems.join(' - ')}]` : ''; + 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. + +**[Homebrewery Link](https://homebrewery.naturalcrit.com/share/${shareLink})**`; + + return `https://www.reddit.com/r/UnearthedArcana/submit?title=${encodeURIComponent(title)}&text=${encodeURIComponent(text)}`; + }, + + renderNavbar : function(){ + const shareLink = this.processShareId(); + + return + + {this.state.alertTrashedGoogleBrew && +
+ This brew is currently in your Trash folder on Google Drive!
If you want to keep it, make sure to move it before it is deleted permanently!
+
+ OK +
+
+ } + + + {this.state.brew.title} + + + + {this.renderGoogleDriveIcon()} + {this.renderSaveButton()} + + + + + share + + + view + + {navigator.clipboard.writeText(`https://homebrewery.naturalcrit.com/share/${shareLink}`);}}> + copy url + + + post to reddit + + + + + + + +
; + }, + + render : function(){ + return
+ + {this.renderNavbar()} + +
+ + + + +
+
; } }); diff --git a/client/homebrew/pages/newPage/newPage.jsx b/client/homebrew/pages/newPage/newPage.jsx index 0e424a998..3f09cac4e 100644 --- a/client/homebrew/pages/newPage/newPage.jsx +++ b/client/homebrew/pages/newPage/newPage.jsx @@ -3,11 +3,20 @@ require('./newPage.less'); const React = require('react'); const createClass = require('create-react-class'); const _ = require('lodash'); - -const EditorPage = require('../basePages/editorPage/editorPage.jsx'); +const request = require('superagent'); const Markdown = require('naturalcrit/markdown.js'); +const Nav = require('naturalcrit/nav/nav.jsx'); +const Navbar = require('../../navbar/navbar.jsx'); +const AccountNavItem = require('../../navbar/account.navitem.jsx'); +const RecentNavItem = require('../../navbar/recent.navitem.jsx').both; +const IssueNavItem = require('../../navbar/issue.navitem.jsx'); + +const SplitPane = require('naturalcrit/splitPane/splitPane.jsx'); +const Editor = require('../../editor/editor.jsx'); +const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx'); + const BREWKEY = 'homebrewery-new'; const STYLEKEY = 'homebrewery-new-style'; const METAKEY = 'homebrewery-new-meta'; @@ -73,13 +82,238 @@ const NewPage = createClass({ }; }, - render : function(){ - const googleDriveOptions = [ - 'Set save location to the Homebrewery?', - 'Set save location to your personal Google Drive storage?' - ]; + componentDidMount : function() { + document.addEventListener('keydown', this.handleControlKeys); + }, + componentWillUnmount : function() { + document.removeEventListener('keydown', this.handleControlKeys); + }, - return ; + handleControlKeys : function(e){ + if(!(e.ctrlKey || e.metaKey)) return; + const S_KEY = 83; + const P_KEY = 80; + if(e.keyCode == S_KEY) this.save(); + if(e.keyCode == P_KEY) this.print(); + if(e.keyCode == P_KEY || e.keyCode == S_KEY){ + e.stopPropagation(); + e.preventDefault(); + } + }, + + handleSplitMove : function(){ + this.refs.editor.update(); + }, + + handleTextChange : function(text){ + //If there are errors, run the validator on every change to give quick feedback + 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(BREWKEY, text); + }, + + handleStyleChange : function(style){ + this.setState((prevState)=>({ + brew : _.merge({}, prevState.brew, { style: style }), + })); + localStorage.setItem(STYLEKEY, style); + }, + + handleMetaChange : function(metadata){ + this.setState((prevState)=>({ + brew : _.merge({}, prevState.brew, metadata), + })); + localStorage.setItem(METAKEY, JSON.stringify({ + // 'title' : this.state.brew.title, + // 'description' : this.state.brew.description, + 'renderer' : this.state.brew.renderer + })); + }, + + clearErrors : function(){ + this.setState({ + errors : null, + isSaving : false + + }); + }, + + save : async function(){ + this.setState({ + isSaving : true + }); + + 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); + }; + + brew.pageCount=((brew.renderer=='legacy' ? brew.text.match(/\\page/g) : brew.text.match(/^\\page$/gm)) || []).length + 1; + + if(this.state.saveGoogle) { + const res = await request + .post('/api/newGoogle/') + .send(brew) + .catch((err)=>{ + console.log(err.status === 401 + ? 'Not signed in!' + : 'Error Creating New Google Brew!'); + this.setState({ isSaving: false, errors: err }); + return; + }); + + brew = res.body; + localStorage.removeItem(BREWKEY); + localStorage.removeItem(STYLEKEY); + localStorage.removeItem(METAKEY); + window.location = `/edit/${brew.googleId}${brew.editId}`; + } else { + request.post('/api') + .send(brew) + .end((err, res)=>{ + if(err){ + this.setState({ + isSaving : false + }); + return; + } + window.onbeforeunload = function(){}; + brew = res.body; + localStorage.removeItem(BREWKEY); + localStorage.removeItem(STYLEKEY); + localStorage.removeItem(METAKEY); + window.location = `/edit/${brew.editId}`; + }); + } + }, + + renderSaveButton : function(){ + if(this.state.errors){ + let errMsg = ''; + try { + errMsg += `${this.state.errors.toString()}\n\n`; + errMsg += `\`\`\`\n${this.state.errors.stack}\n`; + errMsg += `${JSON.stringify(this.state.errors.response.error, null, ' ')}\n\`\`\``; + console.log(errMsg); + } catch (e){} + + if(this.state.errors.status == '401'){ + return + Oops! +
+ You must be signed in to a Google account + to save this to
Google Drive!
+ +
+ Sign In +
+
+
+ Not Now +
+
+
; + } + + if(this.state.errors.status == '403' && this.state.errors.response.body.errors[0].reason == 'insufficientPermissions'){ + return + Oops! +
+ Looks like your Google credentials have + expired! Visit the log in page to sign out + and sign back in with Google + to save this to Google Drive! + +
+ Sign In +
+
+
+ Not Now +
+
+
; + } + + return + Oops! +
+ Looks like there was a problem saving.
+ Report the issue + here + . +
+
; + } + + if(this.state.isSaving){ + return + save... + ; + } else { + return + save + ; + } + }, + + print : function(){ + window.open('/print?dialog=true&local=print', '_blank'); + }, + + renderLocalPrintButton : function(){ + return + get PDF + ; + }, + + renderNavbar : function(){ + return + + + {this.state.brew.title} + + + + {this.renderSaveButton()} + {this.renderLocalPrintButton()} + + + + + ; + }, + + render : function(){ + return
+ {this.renderNavbar()} +
+ + + + +
+
; } });