/* eslint-disable max-lines */ require('./editPage.less'); const React = require('react'); const createClass = require('create-react-class'); const _ = require('lodash'); const request = require('../../utils/request-middleware.js'); 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 HelpNavItem = require('../../navbar/help.navitem.jsx'); const PrintNavItem = require('../../navbar/print.navitem.jsx'); const ErrorNavItem = require('../../navbar/error-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 LockNotification = require('./lockNotification/lockNotification.jsx'); const Markdown = require('naturalcrit/markdown.js'); const { DEFAULT_BREW_LOAD } = require('../../../../server/brewDefaults.js'); const { printCurrentBrew, fetchThemeBundle } = require('../../../../shared/helpers.js'); const googleDriveIcon = require('../../googleDrive.svg'); const SAVE_TIMEOUT = 3000; const EditPage = createClass({ displayName : 'EditPage', getDefaultProps : function() { return { brew : DEFAULT_BREW_LOAD }; }, 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, error : null, htmlErrors : Markdown.validate(this.props.brew.text), url : '', autoSave : true, autoSaveWarning : false, unsavedTime : new Date(), currentEditorPage : 0, displayLockMessage : this.props.brew.lock || false, themeBundle : {} }; }, editor : React.createRef(null), savedBrew : null, componentDidMount : function(){ this.setState({ url : window.location.href }); this.savedBrew = JSON.parse(JSON.stringify(this.props.brew)); //Deep copy this.setState({ autoSave: JSON.parse(localStorage.getItem('AUTOSAVE_ON')) ?? true }, ()=>{ if(this.state.autoSave){ this.trySave(); } else { this.setState({ autoSaveWarning: true }); } }); window.onbeforeunload = ()=>{ if(this.state.isSaving || this.state.isPending){ return 'You have unsaved changes!'; } }; this.setState((prevState)=>({ htmlErrors : Markdown.validate(prevState.brew.text) })); fetchThemeBundle(this, this.props.brew.renderer, this.props.brew.theme); 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.trySave(true); if(e.keyCode == P_KEY) printCurrentBrew(); if(e.keyCode == P_KEY || e.keyCode == S_KEY){ e.stopPropagation(); e.preventDefault(); } }, handleSplitMove : function(){ this.editor.current.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 : { ...prevState.brew, text: text }, isPending : true, htmlErrors : htmlErrors, currentEditorPage : this.editor.current.getCurrentPage() - 1 //Offset index since Marked starts pages at 0 }), ()=>{if(this.state.autoSave) this.trySave();}); }, handleStyleChange : function(style){ this.setState((prevState)=>({ brew : { ...prevState.brew, style: style }, isPending : true }), ()=>{if(this.state.autoSave) this.trySave();}); }, handleMetaChange : function(metadata, field=undefined){ if(field == 'theme' || field == 'renderer') // Fetch theme bundle only if theme or renderer was changed fetchThemeBundle(this, metadata.renderer, metadata.theme); this.setState((prevState)=>({ brew : { ...prevState.brew, ...metadata }, isPending : true, }), ()=>{if(this.state.autoSave) this.trySave();}); }, hasChanges : function(){ return !_.isEqual(this.state.brew, this.savedBrew); }, trySave : function(immediate=false){ if(!this.debounceSave) this.debounceSave = _.debounce(this.save, SAVE_TIMEOUT); if(this.hasChanges()){ this.debounceSave(); } else { this.debounceSave.cancel(); } if(immediate) this.debounceSave.flush(); }, handleGoogleClick : function(){ if(!global.account?.googleId) { this.setState({ alertLoginToTransfer : true }); return; } this.setState((prevState)=>({ confirmGoogleTransfer : !prevState.confirmGoogleTransfer })); this.setState({ error : null, isSaving : false }); }, 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, error : null }), ()=>this.save()); }, save : async function(){ if(this.debounceSave && this.debounceSave.cancel) this.debounceSave.cancel(); this.setState((prevState)=>({ isSaving : true, error : 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; const params = `${transfer ? `?${this.state.saveGoogle ? 'saveToGoogle' : 'removeFromGoogle'}=true` : ''}`; const res = await request .put(`/api/update/${brew.editId}${params}`) .send(brew) .catch((err)=>{ console.log('Error Updating Local Brew'); this.setState({ error: err }); }); if(!res) return; this.savedBrew = res.body; history.replaceState(null, null, `/edit/${this.savedBrew.editId}`); this.setState((prevState)=>({ brew : { ...prevState.brew, googleId : this.savedBrew.googleId ? this.savedBrew.googleId : null, editId : this.savedBrew.editId, shareId : this.savedBrew.shareId, version : this.savedBrew.version }, isPending : false, isSaving : false, unsavedTime : new Date() })); }, renderGoogleDriveIcon : function(){ return Google Drive icon {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
} {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
}
; }, renderSaveButton : function(){ if(this.state.autoSaveWarning && this.hasChanges()){ this.setAutosaveWarning(); const elapsedTime = Math.round((new Date() - this.state.unsavedTime) / 1000 / 60); const text = elapsedTime == 0 ? 'Autosave is OFF.' : `Autosave is OFF, and you haven't saved for ${elapsedTime} minutes.`; return Reminder...
{text}
; } if(this.state.isSaving){ return saving...; } if(this.state.isPending && this.hasChanges()){ return Save Now; } if(!this.state.isPending && !this.state.isSaving && this.state.autoSave){ return auto-saved.; } if(!this.state.isPending && !this.state.isSaving){ return saved.; } }, handleAutoSave : function(){ if(this.warningTimer) clearTimeout(this.warningTimer); this.setState((prevState)=>({ autoSave : !prevState.autoSave, autoSaveWarning : prevState.autoSave }), ()=>{ localStorage.setItem('AUTOSAVE_ON', JSON.stringify(this.state.autoSave)); }); }, setAutosaveWarning : function(){ setTimeout(()=>this.setState({ autoSaveWarning: false }), 4000); // 4 seconds to display this.warningTimer = setTimeout(()=>{this.setState({ autoSaveWarning: true });}, 900000); // 15 minutes between warnings this.warningTimer; }, errorReported : function(error) { this.setState({ error }); }, renderAutoSaveButton : function(){ return Autosave ; }, processShareId : function() { return this.state.brew.googleId && !this.state.brew.stubbed ? 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](${global.config.publicUrl}/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.brew.title} {this.renderGoogleDriveIcon()} {this.state.error ? : {this.renderSaveButton()} {this.renderAutoSaveButton()} } share view {navigator.clipboard.writeText(`${global.config.publicUrl}/share/${shareLink}`);}}> copy url post to reddit ; }, render : function(){ return
{this.renderNavbar()}
{this.props.brew.lock && }
; } }); module.exports = EditPage;