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

+ 19 - 78
src/client/js/app.js

@@ -8,7 +8,6 @@ import * as toastr from 'toastr';
 
 import loggerFactory from '@alias/logger';
 import Xss from '@commons/service/xss';
-import * as entities from 'entities';
 import i18nFactory from './i18n';
 
 
@@ -64,35 +63,7 @@ const i18n = i18nFactory(userlang);
 const xss = new Xss();
 window.xss = xss;
 
-const mainContent = document.querySelector('#content-main');
-let pageId = null;
-let pageRevisionId = null;
-let pageRevisionCreatedAt = null;
-let pageRevisionIdHackmdSynced = null;
-let hasDraftOnHackmd = false;
-let pageIdOnHackmd = null;
-let pagePath;
-let pageContent = '';
-let markdown = '';
-let slackChannels;
 let pageTags = [];
-let templateTagData = '';
-if (mainContent !== null) {
-  pageId = mainContent.getAttribute('data-page-id') || null;
-  pageRevisionId = mainContent.getAttribute('data-page-revision-id');
-  pageRevisionCreatedAt = +mainContent.getAttribute('data-page-revision-created');
-  pageRevisionIdHackmdSynced = mainContent.getAttribute('data-page-revision-id-hackmd-synced') || null;
-  pageIdOnHackmd = mainContent.getAttribute('data-page-id-on-hackmd') || null;
-  hasDraftOnHackmd = !!mainContent.getAttribute('data-page-has-draft-on-hackmd');
-  pagePath = mainContent.attributes['data-path'].value;
-  slackChannels = mainContent.getAttribute('data-slack-channels') || '';
-  templateTagData = mainContent.getAttribute('data-template-tags') || '';
-  const rawText = document.getElementById('raw-text-original');
-  if (rawText) {
-    pageContent = rawText.innerHTML;
-  }
-  markdown = entities.decodeHTML(pageContent);
-}
 const isLoggedin = document.querySelector('.main-container.nologin') == null;
 
 // create unstated container instance
@@ -185,7 +156,7 @@ const saveWithShortcutSuccessHandler = function(page) {
   }
 
   // hidden input
-  $('input[name="revision_id"]').val(pageRevisionId);
+  $('input[name="revision_id"]').val(newState.revisionId);
 };
 
 const errorHandler = function(error) {
@@ -230,8 +201,9 @@ const saveWithShortcut = function(markdown) {
 };
 
 const saveWithSubmitButtonSuccessHandler = function() {
-  appContainer.clearDraft(pagePath);
-  window.location.href = pagePath;
+  const { path } = pageContainer.state;
+  appContainer.clearDraft(path);
+  window.location.href = path;
 };
 
 const saveWithSubmitButton = function(submitOpts) {
@@ -286,12 +258,6 @@ const saveWithSubmitButton = function(submitOpts) {
 // setup renderer after plugins are installed
 crowiRenderer.setup();
 
-// restore draft when the first time to edit
-const draft = appContainer.findDraft(pagePath);
-if (!pageRevisionId && draft != null) {
-  markdown = draft;
-}
-
 /**
  * define components
  *  key: id of element
@@ -305,30 +271,37 @@ let componentMappings = {
   // 'revision-history': <PageHistory pageId={pageId} />,
   'tags-page': <TagsList crowi={crowi} />,
 
-  'create-page-name-input': <PagePathAutoComplete crowi={crowi} initializedPath={pagePath} addTrailingSlash />,
+  'create-page-name-input': <PagePathAutoComplete crowi={crowi} initializedPath={pageContainer.state.path} addTrailingSlash />,
 
   'page-editor': <PageEditor crowiRenderer={crowiRenderer} onSaveWithShortcut={saveWithShortcut} />,
   'page-editor-options-selector': <OptionsSelector crowi={crowi} />,
   'page-status-alert': <PageStatusAlert />,
   'save-page-controls': <SavePageControls onSubmit={saveWithSubmitButton} />,
+
+  'user-created-list': <RecentCreated />,
+  'user-draft-list': <MyDraftList crowiOriginRenderer={crowiRenderer} />,
 };
 
 // additional definitions if data exists
-if (pageId) {
+if (pageContainer.state.pageId != null) {
   componentMappings = Object.assign({
     'page-editor-with-hackmd': <PageEditorByHackmd onSaveWithShortcut={saveWithShortcut} />,
     'page-comments-list': <PageComments crowiOriginRenderer={crowiRenderer} />,
+    'page-attachment':  <PageAttachment />,
     'page-comment-write':  <CommentEditorLazyRenderer crowiOriginRenderer={crowiRenderer} />,
+    'bookmark-button':  <BookmarkButton pageId={pageContainer.state.pageId} crowi={crowi} />,
+    'bookmark-button-lg':  <BookmarkButton pageId={pageContainer.state.pageId} crowi={crowi} size="lg" />,
+    'rename-page-name-input':  <PagePathAutoComplete crowi={crowi} initializedPath={pageContainer.state.path} />,
+    'duplicate-page-name-input':  <PagePathAutoComplete crowi={crowi} initializedPath={pageContainer.state.path} />,
   }, componentMappings);
 }
-if (pagePath) {
+if (pageContainer.state.path != null) {
   componentMappings = Object.assign({
     // eslint-disable-next-line quote-props
     'page': <Page crowiRenderer={crowiRenderer} onSaveWithShortcut={saveWithShortcut} />,
-    'revision-path':  <RevisionPath pageId={pageId} pagePath={pagePath} crowi={crowi} />,
-    'tag-label':  <TagLabels crowi={crowi} pageId={pageId} sendTagData={setTagData} templateTagData={templateTagData} />,
+    'revision-path':  <RevisionPath pageId={pageContainer.state.pageId} pagePath={pageContainer.state.path} crowi={crowi} />,
+    'tag-label':  <TagLabels crowi={crowi} pageId={pageContainer.state.pageId} sendTagData={setTagData} templateTagData={pageContainer.state.templateTagData} />,
   }, componentMappings);
-
 }
 
 Object.keys(componentMappings).forEach((key) => {
@@ -350,7 +323,7 @@ const likeButtonElem = document.getElementById('like-button');
 if (likeButtonElem) {
   const isLiked = likeButtonElem.dataset.liked === 'true';
   ReactDOM.render(
-    <LikeButton crowi={crowi} pageId={pageId} isLiked={isLiked} />,
+    <LikeButton crowi={crowi} pageId={pageContainer.state.pageId} isLiked={isLiked} />,
     likeButtonElem,
   );
 }
@@ -376,38 +349,6 @@ if (likerListElem) {
   );
 }
 
-const recentCreatedControlsElem = document.getElementById('user-created-list');
-if (recentCreatedControlsElem) {
-  let limit = appContainer.getConfig().recentCreatedLimit;
-  if (limit == null) {
-    limit = 10;
-  }
-  ReactDOM.render(
-    <RecentCreated crowi={crowi} pageId={pageId} limit={limit}>
-
-    </RecentCreated>, document.getElementById('user-created-list'),
-  );
-}
-
-const myDraftControlsElem = document.getElementById('user-draft-list');
-if (myDraftControlsElem) {
-  let limit = appContainer.getConfig().recentCreatedLimit;
-  if (limit == null) {
-    limit = 10;
-  }
-
-  ReactDOM.render(
-    <I18nextProvider i18n={i18n}>
-      <MyDraftList
-        limit={limit}
-        crowi={crowi}
-        crowiOriginRenderer={crowiRenderer}
-      />
-    </I18nextProvider>,
-    myDraftControlsElem,
-  );
-}
-
 // render for admin
 const customCssEditorElem = document.getElementById('custom-css-editor');
 if (customCssEditorElem != null) {
@@ -462,7 +403,7 @@ if (adminGrantSelectorElem != null) {
 $('a[data-toggle="tab"][href="#revision-history"]').on('show.bs.tab', () => {
   ReactDOM.render(
     <I18nextProvider i18n={i18n}>
-      <PageHistory pageId={pageId} crowi={crowi} />
+      <PageHistory pageId={pageContainer.state.pageId} crowi={crowi} />
     </I18nextProvider>, document.getElementById('revision-history'),
   );
 });

+ 26 - 9
src/client/js/components/MyDraftList/MyDraftList.jsx

@@ -1,10 +1,15 @@
 import React from 'react';
-
 import PropTypes from 'prop-types';
+
 import Pagination from 'react-bootstrap/lib/Pagination';
+
+import { createSubscribedElement } from '../UnstatedUtils';
+import AppContainer from '../../services/AppContainer';
+import PageContainer from '../../services/PageContainer';
+
 import Draft from '../PageList/Draft';
 
-export default class MyDraftList extends React.Component {
+class MyDraftList extends React.Component {
 
   constructor(props) {
     super(props);
@@ -31,7 +36,7 @@ export default class MyDraftList extends React.Component {
   async getDraftsFromLocalStorage() {
     const draftsAsObj = JSON.parse(window.localStorage.getItem('draft') || '{}');
 
-    const res = await this.props.crowi.apiGet('/pages.exist', {
+    const res = await this.props.appContainer.apiGet('/pages.exist', {
       pages: draftsAsObj,
     });
 
@@ -49,7 +54,10 @@ export default class MyDraftList extends React.Component {
   }
 
   getCurrentDrafts(selectPageNumber) {
-    const limit = this.props.limit;
+    const { appContainer } = this.props;
+
+    const limit = appContainer.getConfig().recentCreatedLimit;
+
     const totalCount = this.state.drafts.length;
     const activePage = selectPageNumber;
     const paginationNumbers = this.calculatePagination(limit, totalCount, activePage);
@@ -74,7 +82,6 @@ export default class MyDraftList extends React.Component {
       return (
         <Draft
           key={draft.path}
-          crowi={this.props.crowi}
           crowiOriginRenderer={this.props.crowiOriginRenderer}
           path={draft.path}
           markdown={draft.markdown}
@@ -86,7 +93,7 @@ export default class MyDraftList extends React.Component {
   }
 
   clearDraft(path) {
-    this.props.crowi.clearDraft(path);
+    this.props.appContainer.clearDraft(path);
 
     this.setState((prevState) => {
       return {
@@ -97,7 +104,7 @@ export default class MyDraftList extends React.Component {
   }
 
   clearAllDrafts() {
-    this.props.crowi.clearAllDrafts();
+    this.props.appContainer.clearAllDrafts();
 
     this.setState({
       drafts: [],
@@ -244,9 +251,19 @@ export default class MyDraftList extends React.Component {
 
 }
 
+/**
+ * Wrapper component for using unstated
+ */
+const MyDraftListWrapper = (props) => {
+  return createSubscribedElement(MyDraftList, props, [AppContainer, PageContainer]);
+};
+
 
 MyDraftList.propTypes = {
-  limit: PropTypes.number,
-  crowi: PropTypes.object.isRequired,
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
+
   crowiOriginRenderer: PropTypes.object.isRequired,
 };
+
+export default MyDraftListWrapper;

+ 23 - 9
src/client/js/components/PageAttachment.js

@@ -4,8 +4,11 @@ import PropTypes from 'prop-types';
 
 import PageAttachmentList from './PageAttachment/PageAttachmentList';
 import DeleteAttachmentModal from './PageAttachment/DeleteAttachmentModal';
+import { createSubscribedElement } from './UnstatedUtils';
+import AppContainer from '../services/AppContainer';
+import PageContainer from '../services/PageContainer';
 
-export default class PageAttachment extends React.Component {
+class PageAttachment extends React.Component {
 
   constructor(props) {
     super(props);
@@ -23,13 +26,13 @@ export default class PageAttachment extends React.Component {
   }
 
   componentDidMount() {
-    const pageId = this.props.pageId;
+    const { pageId } = this.props.pageContainer.state;
 
     if (!pageId) {
       return;
     }
 
-    this.props.crowi.apiGet('/attachments.list', { page_id: pageId })
+    this.props.appContainer.apiGet('/attachments.list', { page_id: pageId })
       .then((res) => {
         const attachments = res.attachments;
         const inUse = {};
@@ -46,7 +49,9 @@ export default class PageAttachment extends React.Component {
   }
 
   checkIfFileInUse(attachment) {
-    if (this.props.markdown.match(attachment.filePathProxied)) {
+    const { markdown } = this.pageContainer.state;
+
+    if (markdown.match(attachment.filePathProxied)) {
       return true;
     }
     return false;
@@ -64,7 +69,7 @@ export default class PageAttachment extends React.Component {
       deleting: true,
     });
 
-    this.props.crowi.apiPost('/attachments.remove', { attachment_id: attachmentId })
+    this.props.appContainer.apiPost('/attachments.remove', { attachment_id: attachmentId })
       .then((res) => {
         this.setState({
           attachments: this.state.attachments.filter((at) => {
@@ -84,7 +89,7 @@ export default class PageAttachment extends React.Component {
   }
 
   isUserLoggedIn() {
-    return this.props.crowi.me !== '';
+    return this.props.appContainer.me !== '';
   }
 
   render() {
@@ -133,8 +138,17 @@ export default class PageAttachment extends React.Component {
 
 }
 
+/**
+ * Wrapper component for using unstated
+ */
+const PageAttachmentWrapper = (props) => {
+  return createSubscribedElement(PageAttachment, props, [AppContainer, PageContainer]);
+};
+
+
 PageAttachment.propTypes = {
-  crowi: PropTypes.object.isRequired,
-  markdown: PropTypes.string.isRequired,
-  pageId: PropTypes.string.isRequired,
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
 };
+
+export default PageAttachmentWrapper;

+ 15 - 4
src/client/js/components/PageList/Draft.jsx

@@ -3,7 +3,9 @@ import PropTypes from 'prop-types';
 
 import { withTranslation } from 'react-i18next';
 
+import { createSubscribedElement } from '../UnstatedUtils';
 import GrowiRenderer from '../../util/GrowiRenderer';
+import AppContainer from '../../services/AppContainer';
 
 import RevisionBody from '../Page/RevisionBody';
 
@@ -17,7 +19,7 @@ class Draft extends React.Component {
       isOpen: false,
     };
 
-    this.growiRenderer = new GrowiRenderer(this.props.crowi, this.props.crowiOriginRenderer, { mode: 'draft' });
+    this.growiRenderer = new GrowiRenderer(window.crowi, this.props.crowiOriginRenderer, { mode: 'draft' });
 
     this.renderHtml = this.renderHtml.bind(this);
     this.toggleContent = this.toggleContent.bind(this);
@@ -52,7 +54,7 @@ class Draft extends React.Component {
     };
 
     const growiRenderer = this.growiRenderer;
-    const interceptorManager = this.props.crowi.interceptorManager;
+    const interceptorManager = this.props.appContainer.interceptorManager;
     await interceptorManager.process('prePreProcess', context)
       .then(() => {
         context.markdown = growiRenderer.preProcess(context.markdown);
@@ -154,9 +156,18 @@ class Draft extends React.Component {
 
 }
 
+/**
+ * Wrapper component for using unstated
+ */
+const DraftWrapper = (props) => {
+  return createSubscribedElement(Draft, props, [AppContainer]);
+};
+
+
 Draft.propTypes = {
   t: PropTypes.func.isRequired,
-  crowi: PropTypes.object.isRequired,
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+
   crowiOriginRenderer: PropTypes.object.isRequired,
   path: PropTypes.string.isRequired,
   markdown: PropTypes.string.isRequired,
@@ -164,4 +175,4 @@ Draft.propTypes = {
   clearDraft: PropTypes.func.isRequired,
 };
 
-export default withTranslation()(Draft);
+export default withTranslation()(DraftWrapper);

+ 22 - 11
src/client/js/components/RecentCreated/RecentCreated.jsx

@@ -1,10 +1,15 @@
 import React from 'react';
-
 import PropTypes from 'prop-types';
+
 import Pagination from 'react-bootstrap/lib/Pagination';
+
+import { createSubscribedElement } from '../UnstatedUtils';
+import AppContainer from '../../services/AppContainer';
+import PageContainer from '../../services/PageContainer';
+
 import Page from '../PageList/Page';
 
-export default class RecentCreated extends React.Component {
+class RecentCreated extends React.Component {
 
   constructor(props) {
     super(props);
@@ -23,13 +28,15 @@ export default class RecentCreated extends React.Component {
   }
 
   getRecentCreatedList(selectPageNumber) {
-    const pageId = this.props.pageId;
-    const userId = this.props.crowi.me;
-    const limit = this.props.limit;
+    const { appContainer, pageContainer } = this.props;
+    const { pageId } = pageContainer.state;
+
+    const userId = appContainer.me;
+    const limit = appContainer.getConfig().recentCreatedLimit;
     const offset = (selectPageNumber - 1) * limit;
 
     // pagesList get and pagination calculate
-    this.props.crowi.apiGet('/pages.recentCreated', {
+    this.props.appContainer.apiGet('/pages.recentCreated', {
       page_id: pageId, user: userId, limit, offset,
     })
       .then((res) => {
@@ -183,12 +190,16 @@ export default class RecentCreated extends React.Component {
 
 }
 
+/**
+ * Wrapper component for using unstated
+ */
+const RecentCreatedWrapper = (props) => {
+  return createSubscribedElement(RecentCreated, props, [AppContainer, PageContainer]);
+};
 
 RecentCreated.propTypes = {
-  pageId: PropTypes.string.isRequired,
-  crowi: PropTypes.object.isRequired,
-  limit: PropTypes.number,
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
 };
 
-RecentCreated.defaultProps = {
-};
+export default RecentCreatedWrapper;

+ 10 - 1
src/client/js/services/PageContainer.js

@@ -55,6 +55,7 @@ export default class PageContainer extends Container {
 
     this.initStateMarkdown();
     this.initStateGrant();
+    this.initDraft();
 
     this.addWebSocketEventHandlers = this.addWebSocketEventHandlers.bind(this);
     this.addWebSocketEventHandlers();
@@ -86,6 +87,15 @@ export default class PageContainer extends Container {
     }
   }
 
+  initDraft() {
+    if (this.state.pageId == null) {
+      const draft = this.appContainer.findDraft(this.state.path);
+      if (draft != null) {
+        this.state.markdown = draft;
+      }
+    }
+  }
+
   getCurrentOptionsToSave() {
     const opt = {
       isSlackEnabled: this.state.isSlackEnabled,
@@ -109,7 +119,6 @@ export default class PageContainer extends Container {
   }
 
   addWebSocketEventHandlers() {
-    const appContainer = this.appContainer;
     const pageContainer = this;
     const websocketContainer = this.appContainer.getContainer('WebsocketContainer');
     const socket = websocketContainer.getWebSocket();