Просмотр исходного кода

WIP: transplant methods to save page to PageContainer

Yuki Takei 6 лет назад
Родитель
Сommit
24c671c7af

+ 3 - 99
src/client/js/app.js

@@ -75,102 +75,6 @@ appContainer.injectToWindow();
 
 const i18n = appContainer.i18n;
 
-/**
- * save success handler when reloading is not needed
- * @param {object} page Page instance
- */
-const saveWithShortcutSuccessHandler = function(result) {
-  const { page, tags } = result;
-  const { editorMode } = appContainer.state;
-
-  // show toastr
-  toastr.success(undefined, 'Saved successfully', {
-    closeButton: true,
-    progressBar: true,
-    newestOnTop: false,
-    showDuration: '100',
-    hideDuration: '100',
-    timeOut: '1200',
-    extendedTimeOut: '150',
-  });
-
-  // update state of PageContainer
-  const newState = {
-    pageId: page._id,
-    revisionId: page.revision._id,
-    revisionCreatedAt: new Date(page.revision.createdAt).getTime() / 1000,
-    remoteRevisionId: page.revision._id,
-    revisionIdHackmdSynced: page.revisionHackmdSynced,
-    hasDraftOnHackmd: page.hasDraftOnHackmd,
-    markdown: page.revision.body,
-    tags,
-  };
-  pageContainer.setState(newState);
-
-  // update state of EditorContainer
-  editorContainer.setState({ tags });
-
-  // PageEditor component
-  const pageEditor = appContainer.getComponentInstance('PageEditor');
-  if (pageEditor != null) {
-    if (editorMode !== 'builtin') {
-      pageEditor.updateEditorValue(newState.markdown);
-    }
-  }
-  // PageEditorByHackmd component
-  const pageEditorByHackmd = appContainer.getComponentInstance('PageEditorByHackmd');
-  if (pageEditorByHackmd != null) {
-    // reset
-    if (editorMode !== 'hackmd') {
-      pageEditorByHackmd.reset();
-    }
-  }
-
-  // hidden input
-  $('input[name="revision_id"]').val(newState.revisionId);
-};
-
-const errorHandler = function(error) {
-  toastr.error(error.message, 'Error occured', {
-    closeButton: true,
-    progressBar: true,
-    newestOnTop: false,
-    showDuration: '100',
-    hideDuration: '100',
-    timeOut: '3000',
-  });
-};
-
-const saveWithShortcut = function(markdown) {
-  const { editorMode } = appContainer.state;
-
-  const { pageId, path } = pageContainer.state;
-  let { revisionId } = pageContainer.state;
-
-  // get options
-  const options = editorContainer.getCurrentOptionsToSave();
-  options.socketClientId = websocketContainer.getCocketClientId();
-  options.pageTags = editorContainer.state.tags;
-
-  if (editorMode === 'hackmd') {
-    // set option to sync
-    options.isSyncRevisionToHackmd = true;
-    revisionId = pageContainer.state.revisionIdHackmdSynced;
-  }
-
-  let promise;
-  if (pageId == null) {
-    promise = appContainer.createPage(path, markdown, options);
-  }
-  else {
-    promise = appContainer.updatePage(pageId, revisionId, markdown, options);
-  }
-
-  promise
-    .then(saveWithShortcutSuccessHandler)
-    .catch(errorHandler);
-};
-
 const saveWithSubmitButtonSuccessHandler = function() {
   const { path } = pageContainer.state;
   editorContainer.clearDraft(path);
@@ -241,7 +145,7 @@ let componentMappings = {
 
   'create-page-name-input': <PagePathAutoComplete crowi={appContainer} initializedPath={pageContainer.state.path} addTrailingSlash />,
 
-  'page-editor': <PageEditor onSaveWithShortcut={saveWithShortcut} />,
+  'page-editor': <PageEditor />,
   'page-editor-options-selector': <OptionsSelector crowi={appContainer} />,
   'page-status-alert': <PageStatusAlert />,
   'save-page-controls': <SavePageControls onSubmit={saveWithSubmitButton} />,
@@ -253,7 +157,7 @@ let componentMappings = {
 // additional definitions if data exists
 if (pageContainer.state.pageId != null) {
   componentMappings = Object.assign({
-    'page-editor-with-hackmd': <PageEditorByHackmd onSaveWithShortcut={saveWithShortcut} />,
+    'page-editor-with-hackmd': <PageEditorByHackmd />,
     'page-comments-list': <PageComments />,
     'page-attachment':  <PageAttachment />,
     'page-comment-write':  <CommentEditorLazyRenderer />,
@@ -269,7 +173,7 @@ if (pageContainer.state.pageId != null) {
 if (pageContainer.state.path != null) {
   componentMappings = Object.assign({
     // eslint-disable-next-line quote-props
-    'page': <Page onSaveWithShortcut={saveWithShortcut} />,
+    'page': <Page />,
     'revision-path':  <RevisionPath pageId={pageContainer.state.pageId} pagePath={pageContainer.state.path} crowi={appContainer} />,
     'tag-label':  <TagLabels />,
   }, componentMappings);

+ 4 - 3
src/client/js/components/Page.jsx

@@ -44,7 +44,10 @@ class Page extends React.Component {
       this.state.currentTargetTableArea.beginLineNumber,
       this.state.currentTargetTableArea.endLineNumber,
     );
-    this.props.onSaveWithShortcut(newMarkdown);
+
+    // TODO save with pageContainer.save(newMarkdown)
+    // this.props.onSaveWithShortcut(newMarkdown);
+
     this.setState({ currentTargetTableArea: null });
   }
 
@@ -73,8 +76,6 @@ const PageWrapper = (props) => {
 Page.propTypes = {
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
-
-  onSaveWithShortcut: PropTypes.func.isRequired,
 };
 
 export default PageWrapper;

+ 33 - 27
src/client/js/components/PageEditor.jsx

@@ -1,10 +1,9 @@
 import React from 'react';
 import PropTypes from 'prop-types';
+import loggerFactory from '@alias/logger';
 
 import { throttle, debounce } from 'throttle-debounce';
 
-import * as toastr from 'toastr';
-
 import AppContainer from '../services/AppContainer';
 import PageContainer from '../services/PageContainer';
 
@@ -14,6 +13,7 @@ import Preview from './PageEditor/Preview';
 import scrollSyncHelper from './PageEditor/ScrollSyncHelper';
 import EditorContainer from '../services/EditorContainer';
 
+const logger = loggerFactory('growi:PageEditor');
 
 class PageEditor extends React.Component {
 
@@ -35,14 +35,13 @@ class PageEditor extends React.Component {
     this.setCaretLine = this.setCaretLine.bind(this);
     this.focusToEditor = this.focusToEditor.bind(this);
     this.onMarkdownChanged = this.onMarkdownChanged.bind(this);
-    this.onSave = this.onSave.bind(this);
+    this.onSaveWithShortcut = this.onSaveWithShortcut.bind(this);
     this.onUpload = this.onUpload.bind(this);
     this.onEditorScroll = this.onEditorScroll.bind(this);
     this.onEditorScrollCursorIntoView = this.onEditorScrollCursorIntoView.bind(this);
     this.onPreviewScroll = this.onPreviewScroll.bind(this);
     this.saveDraft = this.saveDraft.bind(this);
     this.clearDraft = this.clearDraft.bind(this);
-    this.apiErrorHandler = this.apiErrorHandler.bind(this);
     this.showUnsavedWarning = this.showUnsavedWarning.bind(this);
 
     // get renderer
@@ -114,9 +113,27 @@ class PageEditor extends React.Component {
     this.props.appContainer.setIsDocSaved(false);
   }
 
-  onSave() {
-    this.props.onSaveWithShortcut(this.state.markdown);
-    this.props.appContainer.setIsDocSaved(true);
+  async onSaveWithShortcut() {
+    const { appContainer, pageContainer, editorContainer } = this.props;
+    const optionsToSave = editorContainer.getCurrentOptionsToSave();
+
+    try {
+      const { page, tags } = await pageContainer.save(this.state.markdown, optionsToSave);
+      logger.debug('success to save');
+
+      pageContainer.showSuccessToastr();
+
+      // update state of PageContainer
+      pageContainer.updateStateAfterSave(page);
+      // update state of EditorContainer
+      editorContainer.setState({ tags });
+
+      appContainer.setIsDocSaved(true);
+    }
+    catch (error) {
+      logger.error('failed to save', error);
+      pageContainer.showErrorToastr(error);
+    }
   }
 
   /**
@@ -124,9 +141,10 @@ class PageEditor extends React.Component {
    * @param {any} file
    */
   async onUpload(file) {
+    const { appContainer, pageContainer } = this.props;
+
     try {
-      let res = await this.props.appContainer.apiGet('/attachments.limit', {
-        _csrf: this.props.appContainer.csrfToken,
+      let res = await appContainer.apiGet('/attachments.limit', {
         fileSize: file.size,
       });
 
@@ -135,12 +153,12 @@ class PageEditor extends React.Component {
       }
 
       const formData = new FormData();
-      formData.append('_csrf', this.props.appContainer.csrfToken);
+      formData.append('_csrf', appContainer.csrfToken);
       formData.append('file', file);
-      formData.append('path', this.props.pageContainer.state.path);
+      formData.append('path', pageContainer.state.path);
       formData.append('page_id', this.state.pageId || 0);
 
-      res = await this.props.appContainer.apiPost('/attachments.add', formData);
+      res = await appContainer.apiPost('/attachments.add', formData);
       const attachment = res.attachment;
       const fileName = attachment.originalName;
 
@@ -158,7 +176,8 @@ class PageEditor extends React.Component {
       }
     }
     catch (e) {
-      this.apiErrorHandler(e);
+      logger.error('failed to upload', e);
+      pageContainer.showErrorToastr(e);
     }
     finally {
       this.editor.terminateUploadingState();
@@ -309,17 +328,6 @@ class PageEditor extends React.Component {
 
   }
 
-  apiErrorHandler(error) {
-    toastr.error(error.message, 'Error occured', {
-      closeButton: true,
-      progressBar: true,
-      newestOnTop: false,
-      showDuration: '100',
-      hideDuration: '100',
-      timeOut: '3000',
-    });
-  }
-
   render() {
     const config = this.props.appContainer.getConfig();
     const noCdn = !!config.env.NO_CDN;
@@ -340,7 +348,7 @@ class PageEditor extends React.Component {
             onScrollCursorIntoView={this.onEditorScrollCursorIntoView}
             onChange={this.onMarkdownChanged}
             onUpload={this.onUpload}
-            onSave={this.onSave}
+            onSave={this.onSaveWithShortcut}
           />
         </div>
         <div className="col-md-6 hidden-sm hidden-xs page-editor-preview-container">
@@ -370,8 +378,6 @@ PageEditor.propTypes = {
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
   editorContainer: PropTypes.instanceOf(EditorContainer).isRequired,
-
-  onSaveWithShortcut: PropTypes.func.isRequired,
 };
 
 export default PageEditorWrapper;

+ 4 - 17
src/client/js/components/PageEditorByHackmd.jsx

@@ -27,8 +27,6 @@ class PageEditorByHackmd extends React.Component {
     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() {
@@ -97,7 +95,9 @@ class PageEditorByHackmd extends React.Component {
           revisionIdHackmdSynced: res.revisionIdHackmdSynced,
         });
       })
-      .catch(this.apiErrorHandler)
+      .catch((err) => {
+        pageContainer.showErrorToastr(err);
+      })
       .then(() => {
         this.setState({ isInitializing: false });
       });
@@ -146,17 +146,6 @@ class PageEditorByHackmd extends React.Component {
       });
   }
 
-  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 { pageContainer } = this.props;
@@ -176,7 +165,7 @@ class PageEditorByHackmd extends React.Component {
           initializationMarkdown={isResume ? null : this.state.markdown}
           onChange={this.hackmdEditorChangeHandler}
           onSaveWithShortcut={(document) => {
-            this.props.onSaveWithShortcut(document);
+            this.onSaveWithShortcut(document);
           }}
         >
         </HackmdEditor>
@@ -298,8 +287,6 @@ const PageEditorByHackmdWrapper = (props) => {
 PageEditorByHackmd.propTypes = {
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
-
-  onSaveWithShortcut: PropTypes.func.isRequired,
 };
 
 export default PageEditorByHackmdWrapper;

+ 0 - 29
src/client/js/services/AppContainer.js

@@ -270,35 +270,6 @@ export default class AppContainer extends Container {
     return null;
   }
 
-  createPage(pagePath, markdown, additionalParams = {}) {
-    const params = Object.assign(additionalParams, {
-      path: pagePath,
-      body: markdown,
-    });
-    return this.apiPost('/pages.create', params)
-      .then((res) => {
-        if (!res.ok) {
-          throw new Error(res.error);
-        }
-        return { page: res.page, tags: res.tags };
-      });
-  }
-
-  updatePage(pageId, revisionId, markdown, additionalParams = {}) {
-    const params = Object.assign(additionalParams, {
-      page_id: pageId,
-      revision_id: revisionId,
-      body: markdown,
-    });
-    return this.apiPost('/pages.update', params)
-      .then((res) => {
-        if (!res.ok) {
-          throw new Error(res.error);
-        }
-        return { page: res.page, tags: res.tags };
-      });
-  }
-
   launchHandsontableModal(componentKind, beginLineNumber, endLineNumber) {
     let targetComponent;
     switch (componentKind) {

+ 1 - 0
src/client/js/services/EditorContainer.js

@@ -129,6 +129,7 @@ export default class EditorContainer extends Container {
       isSlackEnabled: this.state.isSlackEnabled,
       slackChannels: this.state.slackChannels,
       grant: this.state.grant,
+      pageTags: this.state.tags,
     };
 
     if (this.state.grantGroupId != null) {

+ 133 - 0
src/client/js/services/PageContainer.js

@@ -3,6 +3,7 @@ import { Container } from 'unstated';
 import loggerFactory from '@alias/logger';
 
 import * as entities from 'entities';
+import * as toastr from 'toastr';
 
 const logger = loggerFactory('growi:services:PageContainer');
 
@@ -54,6 +55,7 @@ export default class PageContainer extends Container {
     this.initStateMarkdown();
     this.initStateOthers();
 
+    this.save = this.save.bind(this);
     this.addWebSocketEventHandlers = this.addWebSocketEventHandlers.bind(this);
     this.addWebSocketEventHandlers();
   }
@@ -101,6 +103,137 @@ export default class PageContainer extends Container {
     });
   }
 
+
+  /**
+   * save success handler
+   * @param {object} page Page instance
+   * @param {Array[Tag]} tags Array of Tag
+   */
+  updateStateAfterSave(page, tags) {
+    const { editorMode } = this.appContainer.state;
+
+    // update state of PageContainer
+    const newState = {
+      pageId: page._id,
+      revisionId: page.revision._id,
+      revisionCreatedAt: new Date(page.revision.createdAt).getTime() / 1000,
+      remoteRevisionId: page.revision._id,
+      revisionIdHackmdSynced: page.revisionHackmdSynced,
+      hasDraftOnHackmd: page.hasDraftOnHackmd,
+      markdown: page.revision.body,
+      tags,
+    };
+    this.setState(newState);
+
+    // PageEditor component
+    const pageEditor = this.appContainer.getComponentInstance('PageEditor');
+    if (pageEditor != null) {
+      if (editorMode !== 'builtin') {
+        pageEditor.updateEditorValue(newState.markdown);
+      }
+    }
+    // PageEditorByHackmd component
+    const pageEditorByHackmd = this.appContainer.getComponentInstance('PageEditorByHackmd');
+    if (pageEditorByHackmd != null) {
+      // reset
+      if (editorMode !== 'hackmd') {
+        pageEditorByHackmd.reset();
+      }
+    }
+
+    // hidden input
+    $('input[name="revision_id"]').val(newState.revisionId);
+  }
+
+  /**
+   * Save page
+   * @param {string} markdown
+   * @param {object} optionsToSave
+   * @return {object} { page: Page, tags: Tag[] }
+   */
+  async save(markdown, optionsToSave) {
+    const { editorMode } = this.appContainer.state;
+
+    const { pageId, path } = this.state;
+    let { revisionId } = this.state;
+
+    const options = Object.assign({}, optionsToSave);
+
+    if (editorMode === 'hackmd') {
+      // set option to sync
+      options.isSyncRevisionToHackmd = true;
+      revisionId = this.pageContainer.state.revisionIdHackmdSynced;
+    }
+
+    let promise;
+    if (pageId == null) {
+      promise = this.createPage(path, markdown, options);
+    }
+    else {
+      promise = this.updatePage(pageId, revisionId, markdown, options);
+    }
+
+    return promise;
+  }
+
+  async createPage(pagePath, markdown, tmpParams) {
+    const websocketContainer = this.appContainer.getContainer('WebsocketContainer');
+
+    // clone
+    const params = Object.assign(tmpParams, {
+      socketClientId: websocketContainer.getSocketClientId(),
+      path: pagePath,
+      body: markdown,
+    });
+
+    const res = await this.appContainer.apiPost('/pages.create', params);
+    if (!res.ok) {
+      throw new Error(res.error);
+    }
+    return { page: res.page, tags: res.tags };
+  }
+
+  async updatePage(pageId, revisionId, markdown, tmpParams) {
+    const websocketContainer = this.appContainer.getContainer('WebsocketContainer');
+
+    // clone
+    const params = Object.assign(tmpParams, {
+      socketClientId: websocketContainer.getSocketClientId(),
+      page_id: pageId,
+      revision_id: revisionId,
+      body: markdown,
+    });
+
+    const res = await this.appContainer.apiPost('/pages.update', params);
+    if (!res.ok) {
+      throw new Error(res.error);
+    }
+    return { page: res.page, tags: res.tags };
+  }
+
+  showSuccessToastr() {
+    toastr.success(undefined, 'Saved successfully', {
+      closeButton: true,
+      progressBar: true,
+      newestOnTop: false,
+      showDuration: '100',
+      hideDuration: '100',
+      timeOut: '1200',
+      extendedTimeOut: '150',
+    });
+  }
+
+  showErrorToastr(error) {
+    toastr.error(error.message, 'Error occured', {
+      closeButton: true,
+      progressBar: true,
+      newestOnTop: false,
+      showDuration: '100',
+      hideDuration: '100',
+      timeOut: '3000',
+    });
+  }
+
   addWebSocketEventHandlers() {
     const pageContainer = this;
     const websocketContainer = this.appContainer.getContainer('WebsocketContainer');