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

+ 2 - 0
src/client/js/app.jsx

@@ -19,6 +19,7 @@ import PageEditorByHackmd from './components/PageEditorByHackmd';
 import Page from './components/Page';
 import PageHistory from './components/PageHistory';
 import PageComments from './components/PageComments';
+import PageTimeline from './components/PageTimeline';
 import CommentEditorLazyRenderer from './components/PageComment/CommentEditorLazyRenderer';
 import PageAttachment from './components/PageAttachment';
 import PageStatusAlert from './components/PageStatusAlert';
@@ -106,6 +107,7 @@ if (pageContainer.state.pageId != null) {
     'page-editor-with-hackmd': <PageEditorByHackmd />,
     'page-comments-list': <PageComments />,
     'page-attachment':  <PageAttachment />,
+    'page-timeline':  <PageTimeline />,
     'page-comment-write':  <CommentEditorLazyRenderer />,
     'revision-toc': <TableOfContents />,
     'like-button': <LikeButton pageId={pageContainer.state.pageId} isLiked={pageContainer.state.isLiked} />,

+ 127 - 0
src/client/js/components/PageTimeline.jsx

@@ -0,0 +1,127 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { withTranslation } from 'react-i18next';
+
+import AppContainer from '../services/AppContainer';
+import { createSubscribedElement } from './UnstatedUtils';
+
+import RevisionLoader from './Page/RevisionLoader';
+
+
+class PageTimeline extends React.Component {
+
+  constructor(props) {
+    super(props);
+
+    const { appContainer } = this.props;
+
+    this.state = {
+      isEnabled: appContainer.getConfig().isEnabledTimeline,
+      isInitialized: false,
+
+      // TODO: remove after when timeline is implemented with React and inject data with props
+      pages: this.props.pages,
+    };
+
+  }
+
+  componentWillMount() {
+    if (!this.state.isEnabled) {
+      return;
+    }
+
+    const { appContainer } = this.props;
+
+    // initialize GrowiRenderer
+    this.growiRenderer = appContainer.getRenderer('timeline');
+
+    this.initBsTab();
+  }
+
+  /**
+   * initialize Bootstrap Tab event for 'shown.bs.tab'
+   * TODO: remove this method after implement with React
+   */
+  initBsTab() {
+    $('a[data-toggle="tab"][href="#view-timeline"]').on('shown.bs.tab', () => {
+      if (this.state.isInitialized) {
+        return;
+      }
+
+      const pageIdsElm = document.getElementById('page-timeline-data');
+
+      if (pageIdsElm == null || pageIdsElm.text.length === 0) {
+        return;
+      }
+
+      const pages = this.extractDataFromDom();
+
+      this.setState({
+        isInitialized: true,
+        pages,
+      });
+    });
+  }
+
+  /**
+   * extract page data from DOM
+   * TODO: remove this method after implement with React
+   */
+  extractDataFromDom() {
+    const pageIdsElm = document.getElementById('page-timeline-data');
+
+    if (pageIdsElm == null || pageIdsElm.text.length === 0) {
+      return;
+    }
+
+    return JSON.parse(pageIdsElm.text);
+  }
+
+  render() {
+    if (!this.state.isEnabled) {
+      return <React.Fragment></React.Fragment>;
+    }
+
+    const { pages } = this.state;
+
+    if (pages == null) {
+      return <React.Fragment></React.Fragment>;
+    }
+
+    return pages.map((page) => {
+      return (
+        <div className="timeline-body" key={`key-${page.id}`}>
+          <div className="panel panel-timeline">
+            <div className="panel-heading"><a href={page.path}>{page.path}</a></div>
+            <div className="panel-body">
+              <RevisionLoader
+                lazy
+                growiRenderer={this.growiRenderer}
+                pageId={page.id}
+                revisionId={page.revision}
+              />
+            </div>
+          </div>
+        </div>
+      );
+    });
+
+  }
+
+}
+
+PageTimeline.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  pages: PropTypes.arrayOf(PropTypes.object),
+};
+
+/**
+ * Wrapper component for using unstated
+ */
+const PageTimelineWrapper = (props) => {
+  return createSubscribedElement(PageTimeline, props, [AppContainer]);
+};
+
+export default withTranslation()(PageTimelineWrapper);

+ 0 - 46
src/client/js/legacy/crowi.js

@@ -1,14 +1,5 @@
 /* eslint-disable react/jsx-filename-extension */
 
-import React from 'react';
-import ReactDOM from 'react-dom';
-
-import { Provider } from 'unstated';
-
-import { pathUtils } from 'growi-commons';
-
-import RevisionLoader from '../components/Page/RevisionLoader';
-
 const entities = require('entities');
 const escapeStringRegexp = require('escape-string-regexp');
 require('jquery.cookie');
@@ -486,43 +477,6 @@ $(() => {
     $link.html(path.replace(new RegExp(pattern), `<strong>${shortPath}$1</strong>`));
   });
 
-  // for list page
-  let growiRendererForTimeline = null;
-  $('a[data-toggle="tab"][href="#view-timeline"]').on('shown.bs.tab', () => {
-    const isShown = $('#view-timeline').data('shown');
-
-    if (growiRendererForTimeline == null) {
-      growiRendererForTimeline = appContainer.getRenderer('timeline');
-    }
-
-    if (isShown === 0) {
-      $('#view-timeline .timeline-body').each(function() {
-        const id = $(this).attr('id');
-        const revisionBody = `#${id} .revision-body`;
-        const revisionBodyElem = document.querySelector(revisionBody);
-        const timelineElm = document.getElementById(id);
-        const pageId = timelineElm.getAttribute('data-page-id');
-        const pagePath = timelineElm.getAttribute('data-page-path');
-        const revisionId = timelineElm.getAttribute('data-revision');
-
-        ReactDOM.render(
-          <Provider inject={[appContainer]}>
-            <RevisionLoader
-              lazy
-              growiRenderer={growiRendererForTimeline}
-              pageId={pageId}
-              pagePath={pagePath}
-              revisionId={revisionId}
-            />
-          </Provider>,
-          revisionBodyElem,
-        );
-      });
-
-      $('#view-timeline').data('shown', 1);
-    }
-  });
-
   if (pageId) {
     // for Crowi Template LangProcessor
     $('.template-create-button', $('#revision-body')).on('click', function() {

+ 9 - 0
src/server/util/swigFunctions.js

@@ -174,6 +174,15 @@ module.exports = function(crowi, app, req, locals) {
     return `/user/${user.username}`;
   };
 
+  locals.pagesDataForTimeline = function(pages) {
+    return pages.map((page) => {
+      return {
+        id: page.id,
+        revision: page.revision,
+      };
+    });
+  };
+
   locals.css = {
     grant(pageData) {
       if (!pageData) {

+ 4 - 13
src/server/views/widget/page_list_and_timeline.html

@@ -24,20 +24,11 @@
 
     {# timeline view #}
     {% if getConfig('crowi', 'customize:isEnabledTimeline') %}
-    <div class="tab-pane m-t-30" id="view-timeline" data-shown=0>
-      {% for page in pages %}
-      <div class="timeline-body" id="id-{{ page.id }}" data-page-id="{{ page.id }}" data-page-path="{{ page.path }}" data-revision="{{ page.revision.toString() }}">
-        <div class="panel panel-timeline">
-          <div class="panel-heading"><a href="{{ page.path }}">{{ decodeURIComponent(page.path) }}</a></div>
-          <div class="panel-body">
-            <div class="revision-body wiki"></div>
-          </div>
-        </div>
-        <script type="text/template">{{ page.revision.body.toString() | encodeHTML }}</script>
+      <div class="tab-pane m-t-30" id="view-timeline">
+        <script type="text/template" id="page-timeline-data">{{ JSON.stringify(pagesDataForTimeline(pages)) }}</script>
+        {# render React Component PageTimeline #}
+        <div id="page-timeline"></div>
       </div>
-      <hr>
-      {% endfor %}
-    </div>
     {% endif %}
   </div>
 </div>

+ 4 - 13
src/server/views/widget/page_list_and_timeline_kibela.html

@@ -24,20 +24,11 @@
 
     {# timeline view #}
     {% if getConfig('crowi', 'customize:isEnabledTimeline') %}
-    <div class="tab-pane m-t-30" id="view-timeline" data-shown=0>
-      {% for page in pages %}
-      <div class="timeline-body" id="id-{{ page.id }}" data-page-id="{{ page.id }}" data-page-path="{{ page.path }}" data-revision="{{ page.revision._id }}">
-        <div class="panel panel-timeline">
-          <div class="panel-heading"><a href="{{ page.path }}">{{ decodeURIComponent(page.path) }}</a></div>
-          <div class="panel-body">
-            <div class="revision-body wiki"></div>
-          </div>
-        </div>
-        <script type="text/template">{{ page.revision.body.toString() | encodeHTML }}</script>
+      <div class="tab-pane m-t-30" id="view-timeline">
+        <script type="text/template" id="page-timeline-data">{{ JSON.stringify(pagesDataForTimeline(pages)) }}</script>
+        {# render React Component PageTimeline #}
+        <div id="page-timeline"></div>
       </div>
-      <hr>
-      {% endfor %}
-    </div>
     {% endif %}
   </div>
 </div>