/* eslint-disable react/no-multi-comp */ import React from 'react'; import PropTypes from 'prop-types'; import { Subscribe } from 'unstated'; import SplitButton from 'react-bootstrap/es/SplitButton'; import MenuItem from 'react-bootstrap/es/MenuItem'; import * as toastr from 'toastr'; import AppContainer from '../services/AppContainer'; import HackmdEditor from './PageEditorByHackmd/HackmdEditor'; class PageEditorByHackmd extends React.PureComponent { constructor(props) { super(props); this.state = { markdown: this.props.markdown, isInitialized: false, isInitializing: false, revisionIdHackmdSynced: this.props.revisionIdHackmdSynced, pageIdOnHackmd: this.props.pageIdOnHackmd, hasDraftOnHackmd: this.props.hasDraftOnHackmd, }; this.getHackmdUri = this.getHackmdUri.bind(this); this.startToEdit = this.startToEdit.bind(this); this.resumeToEdit = this.resumeToEdit.bind(this); this.hackmdEditorChangeHandler = this.hackmdEditorChangeHandler.bind(this); this.apiErrorHandler = this.apiErrorHandler.bind(this); } componentWillMount() { this.props.appContainer.registerComponentInstance(this); } /** * return markdown document of HackMD * @return {Promise} */ getMarkdown() { if (!this.state.isInitialized) { return Promise.reject(new Error('HackmdEditor component has not initialized')); } return this.hackmdEditor.getValue() .then((document) => { this.setState({ markdown: document }); return document; }); } /** * reset initialized status */ reset() { this.setState({ isInitialized: false }); } /** * update revisionId of state * @param {string} revisionId * @param {string} revisionIdHackmdSynced */ setRevisionId(revisionId, revisionIdHackmdSynced) { this.setState({ revisionId, revisionIdHackmdSynced }); } getRevisionIdHackmdSynced() { return this.state.revisionIdHackmdSynced; } /** * update hasDraftOnHackmd of state * @param {bool} hasDraftOnHackmd */ setHasDraftOnHackmd(hasDraftOnHackmd) { this.setState({ hasDraftOnHackmd }); } getHackmdUri() { const envVars = this.props.crowi.config.env; return envVars.HACKMD_URI; } /** * Start integration with HackMD */ startToEdit() { const hackmdUri = this.getHackmdUri(); if (hackmdUri == null) { // do nothing return; } this.setState({ isInitialized: false, isInitializing: true, }); const params = { pageId: this.props.pageId, }; this.props.crowi.apiPost('/hackmd.integrate', params) .then((res) => { if (!res.ok) { throw new Error(res.error); } this.setState({ isInitialized: true, pageIdOnHackmd: res.pageIdOnHackmd, revisionIdHackmdSynced: res.revisionIdHackmdSynced, }); }) .catch(this.apiErrorHandler) .then(() => { this.setState({ isInitializing: false }); }); } /** * Start to edit w/o any api request */ resumeToEdit() { this.setState({ isInitialized: true }); } /** * Reset draft */ discardChanges() { this.setState({ hasDraftOnHackmd: false }); } /** * onChange event of HackmdEditor handler */ hackmdEditorChangeHandler(body) { const hackmdUri = this.getHackmdUri(); if (hackmdUri == null) { // do nothing return; } // do nothing if contents are same if (this.props.markdown === body) { return; } const params = { pageId: this.props.pageId, }; this.props.crowi.apiPost('/hackmd.saveOnHackmd', params) .then((res) => { // do nothing }) .catch((err) => { // do nothing }); } apiErrorHandler(error) { toastr.error(error.message, 'Error occured', { closeButton: true, progressBar: true, newestOnTop: false, showDuration: '100', hideDuration: '100', timeOut: '3000', }); } render() { const hackmdUri = this.getHackmdUri(); const isPageExistsOnHackmd = (this.state.pageIdOnHackmd != null); const isResume = isPageExistsOnHackmd && this.state.hasDraftOnHackmd; if (this.state.isInitialized) { return ( { this.hackmdEditor = c }} hackmdUri={hackmdUri} pageIdOnHackmd={this.state.pageIdOnHackmd} initializationMarkdown={isResume ? null : this.state.markdown} onChange={this.hackmdEditorChangeHandler} onSaveWithShortcut={(document) => { this.props.onSaveWithShortcut(document); }} > ); } const isRevisionOutdated = this.state.initialRevisionId !== this.state.revisionId; const isHackmdDocumentOutdated = this.state.revisionId !== this.state.revisionIdHackmdSynced; let content; /* * HackMD is not setup */ if (hackmdUri == null) { content = (

HackMD is not set up.

); } /* * Resume to edit or discard changes */ else if (isResume) { const revisionIdHackmdSynced = this.state.revisionIdHackmdSynced; const title = ( Resume to edit with HackMD ); content = (

HackMD is READY!

{ return this.resumeToEdit() }} > { return this.discardChanges() }}> Discard changes

Click to edit from the previous continuation
or .

{ isHackmdDocumentOutdated && (
DRAFT MAY BE OUTDATED
The current draft on HackMD is based on  {revisionIdHackmdSynced.substr(-8)}.
to start to edit with current revision.
) }
); } /* * Start to edit */ else { content = (

HackMD is READY!

Click to clone page content and start to edit.

); } return (
{content}
); } } /** * Wrapper component for using unstated */ class PageEditorByHackmdWrapper extends React.Component { render() { return ( { appContainer => ( // eslint-disable-next-line arrow-body-style )} ); } } PageEditorByHackmd.propTypes = { appContainer: PropTypes.instanceOf(AppContainer).isRequired, crowi: PropTypes.object.isRequired, markdown: PropTypes.string.isRequired, onSaveWithShortcut: PropTypes.func.isRequired, pageId: PropTypes.string, revisionId: PropTypes.string, pageIdOnHackmd: PropTypes.string, revisionIdHackmdSynced: PropTypes.string, hasDraftOnHackmd: PropTypes.bool, }; PageEditorByHackmdWrapper.propTypes = { crowi: PropTypes.object.isRequired, markdown: PropTypes.string.isRequired, onSaveWithShortcut: PropTypes.func.isRequired, pageId: PropTypes.string, revisionId: PropTypes.string, pageIdOnHackmd: PropTypes.string, revisionIdHackmdSynced: PropTypes.string, hasDraftOnHackmd: PropTypes.bool, }; export default PageEditorByHackmdWrapper;