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

Merge pull request #6279 from weseek/support/apply-nextjs-to-PageComments

support: Apply nextjs to PageComments
Yuki Takei 3 лет назад
Родитель
Сommit
6d13c9303f

+ 1 - 1
packages/app/_obsolete/src/client/app.jsx

@@ -30,7 +30,7 @@ import { Page } from '../components/Page';
 import DisplaySwitcher from '../components/Page/DisplaySwitcher';
 import RedirectedAlert from '../components/Page/RedirectedAlert';
 import ShareLinkAlert from '../components/Page/ShareLinkAlert';
-import PageComment from '../components/PageComment';
+import { PageComment } from '../components/PageComment';
 import CommentEditorLazyRenderer from '../components/PageComment/CommentEditorLazyRenderer';
 import PageContentFooter from '../components/PageContentFooter';
 import BookmarkList from '../components/PageList/BookmarkList';

+ 26 - 23
packages/app/src/components/PageComment.tsx

@@ -2,41 +2,41 @@ import React, {
   FC, useEffect, useState, useMemo, memo, useCallback,
 } from 'react';
 
+import { Nullable } from '@growi/core';
 import { Button } from 'reactstrap';
 
-
-import AppContainer from '~/client/services/AppContainer';
 import { toastError } from '~/client/util/apiNotification';
 import { apiPost } from '~/client/util/apiv1-client';
+import { useCurrentPagePath } from '~/stores/context';
+import { useSWRxCurrentPage } from '~/stores/page';
 import { useCommentPreviewOptions } from '~/stores/renderer';
 
 import { ICommentHasId, ICommentHasIdList } from '../interfaces/comment';
 import { useSWRxPageComment } from '../stores/comment';
 
-
-import Comment from './PageComment/Comment';
+import { Comment } from './PageComment/Comment';
 import CommentEditor from './PageComment/CommentEditor';
 import DeleteCommentModal from './PageComment/DeleteCommentModal';
 import ReplayComments from './PageComment/ReplayComments';
 
 type Props = {
-  appContainer: AppContainer,
-  pageId: string,
+  pageId?: Nullable<string>, // TODO: check pageId type
   isReadOnly : boolean,
   titleAlign?: 'center' | 'left' | 'right',
   highlightKeywords?:string[],
   hideIfEmpty?: boolean,
 }
 
-
-const PageComment:FC<Props> = memo((props:Props):JSX.Element => {
+export const PageComment:FC<Props> = memo((props:Props):JSX.Element => {
 
   const {
-    appContainer, pageId, highlightKeywords, isReadOnly, titleAlign, hideIfEmpty,
+    pageId, highlightKeywords, isReadOnly, titleAlign, hideIfEmpty,
   } = props;
 
   const { data: comments, mutate } = useSWRxPageComment(pageId);
   const { data: rendererOptions } = useCommentPreviewOptions();
+  const { data: currentPage } = useSWRxCurrentPage();
+  const { data: currentPagePath } = useCurrentPagePath();
 
   const [commentToBeDeleted, setCommentToBeDeleted] = useState<ICommentHasId | null>(null);
   const [isDeleteConfirmModalShown, setIsDeleteConfirmModalShown] = useState<boolean>(false);
@@ -110,17 +110,8 @@ const PageComment:FC<Props> = memo((props:Props):JSX.Element => {
     }
   }, [commentToBeDeleted, onDeleteCommentAfterOperation]);
 
-  const generateCommentInnerElement = (comment: ICommentHasId) => (
-    <Comment
-      rendererOptions={rendererOptions}
-      deleteBtnClicked={onClickDeleteButton}
-      comment={comment}
-      onComment={mutate}
-      isReadOnly={isReadOnly}
-    />
-  );
-
   const generateAllRepliesElement = (replyComments: ICommentHasIdList) => (
+    // TODO: need page props path
     <ReplayComments
       replyList={replyComments}
       deleteBtnClicked={onClickDeleteButton}
@@ -143,11 +134,25 @@ const PageComment:FC<Props> = memo((props:Props):JSX.Element => {
   if (hideIfEmpty && comments?.length === 0) {
     return <></>;
   }
-
-  if (rendererOptions == null) {
+  if (rendererOptions == null || currentPagePath == null) {
     return <></>;
   }
 
+  const generateCommentInnerElement = (comment: ICommentHasId) => (
+    currentPage != null && (
+      <Comment
+        rendererOptions={rendererOptions}
+        deleteBtnClicked={onClickDeleteButton}
+        comment={comment}
+        onComment={mutate}
+        isReadOnly={isReadOnly}
+        currentPagePath={currentPagePath}
+        currentRevisionId={currentPage.revision._id}
+        currentRevisionCreatedAt={currentPage.revision.createdAt}
+      />
+    )
+  );
+
   let commentTitleClasses = 'border-bottom py-3 mb-3';
   commentTitleClasses = titleAlign != null ? `${commentTitleClasses} text-${titleAlign}` : `${commentTitleClasses} text-center`;
 
@@ -224,5 +229,3 @@ const PageComment:FC<Props> = memo((props:Props):JSX.Element => {
 });
 
 PageComment.displayName = 'PageComment';
-
-export default PageComment;

+ 0 - 259
packages/app/src/components/PageComment/Comment.jsx

@@ -1,259 +0,0 @@
-import React from 'react';
-
-import { UserPicture } from '@growi/ui';
-import { format } from 'date-fns';
-import PropTypes from 'prop-types';
-import { useTranslation } from 'next-i18next';
-import { UncontrolledTooltip } from 'reactstrap';
-
-import AppContainer from '~/client/services/AppContainer';
-import PageContainer from '~/client/services/PageContainer';
-import { useCurrentUser } from '~/stores/context';
-
-import FormattedDistanceDate from '../FormattedDistanceDate';
-import HistoryIcon from '../Icons/HistoryIcon';
-import RevisionBody from '../Page/RevisionBody';
-import { withUnstatedContainers } from '../UnstatedUtils';
-import Username from '../User/Username';
-
-import CommentControl from './CommentControl';
-import CommentEditor from './CommentEditor';
-
-import { RendererOptions } from '~/services/renderer/renderer';
-
-/**
- *
- * @author Yuki Takei <yuki@weseek.co.jp>
- *
- * @export
- * @class Comment
- * @extends {React.Component}
- */
-class Comment extends React.PureComponent {
-
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      html: '',
-      isReEdit: false,
-    };
-
-    this.isCurrentUserIsAuthor = this.isCurrentUserEqualsToAuthor.bind(this);
-    this.isCurrentRevision = this.isCurrentRevision.bind(this);
-    this.getRootClassName = this.getRootClassName.bind(this);
-    this.deleteBtnClickedHandler = this.deleteBtnClickedHandler.bind(this);
-    this.renderText = this.renderText.bind(this);
-    this.renderHtml = this.renderHtml.bind(this);
-  }
-
-
-  initCurrentRenderingContext() {
-    this.currentRenderingContext = {
-      markdown: this.props.comment.comment,
-    };
-  }
-
-  componentDidMount() {
-    this.initCurrentRenderingContext();
-    this.renderHtml();
-  }
-
-  componentDidUpdate(prevProps) {
-    const { comment: prevComment } = prevProps;
-    const { comment } = this.props;
-
-    // render only when props.markdown is updated
-    if (comment !== prevComment) {
-      this.initCurrentRenderingContext();
-      this.renderHtml();
-      return;
-    }
-
-    const { interceptorManager } = window;
-
-    interceptorManager.process('postRenderCommentHtml', this.currentRenderingContext);
-  }
-
-  isCurrentUserEqualsToAuthor() {
-    const { comment, currentUser } = this.props;
-    const { creator } = comment;
-
-    if (creator == null || currentUser == null) {
-      return false;
-    }
-    return creator.username === currentUser.username;
-  }
-
-  isCurrentRevision() {
-    return this.props.comment.revision === this.props.pageContainer.state.revisionId;
-  }
-
-  getRootClassName(comment) {
-    let className = 'page-comment flex-column';
-
-    const { revisionId, revisionCreatedAt } = this.props.pageContainer.state;
-    if (comment.revision === revisionId) {
-      className += ' page-comment-current';
-    }
-    else if (Date.parse(comment.createdAt) / 1000 > revisionCreatedAt) {
-      className += ' page-comment-newer';
-    }
-    else {
-      className += ' page-comment-older';
-    }
-
-    if (this.isCurrentUserEqualsToAuthor()) {
-      className += ' page-comment-me';
-    }
-
-    return className;
-  }
-
-  deleteBtnClickedHandler() {
-    this.props.deleteBtnClicked(this.props.comment);
-  }
-
-  renderText(comment) {
-    return <span style={{ whiteSpace: 'pre-wrap' }}>{comment}</span>;
-  }
-
-  renderRevisionBody() {
-    const config = this.props.appContainer.getConfig();
-    const isMathJaxEnabled = !!config.env.MATHJAX;
-    return (
-      <RevisionBody
-        html={this.state.html}
-        isMathJaxEnabled={isMathJaxEnabled}
-        renderMathJaxOnInit
-        additionalClassName="comment"
-      />
-    );
-  }
-
-  async renderHtml() {
-
-    const { rendererOptions, appContainer } = this.props;
-    const { interceptorManager } = window;
-    const context = this.currentRenderingContext;
-
-    await interceptorManager.process('preRenderComment', context);
-    await interceptorManager.process('prePreProcess', context);
-    context.markdown = await rendererOptions.preProcess(context.markdown, context);
-    await interceptorManager.process('postPreProcess', context);
-    context.parsedHTML = await rendererOptions.process(context.markdown, context);
-    await interceptorManager.process('prePostProcess', context);
-    context.parsedHTML = await rendererOptions.postProcess(context.parsedHTML, context);
-    await interceptorManager.process('postPostProcess', context);
-    await interceptorManager.process('preRenderCommentHtml', context);
-    this.setState({ html: context.parsedHTML });
-    await interceptorManager.process('postRenderCommentHtml', context);
-  }
-
-  render() {
-    const {
-      t, comment, isReadOnly, onComment,
-    } = this.props;
-    const commentId = comment._id;
-    const creator = comment.creator;
-    const isMarkdown = comment.isMarkdown;
-    const createdAt = new Date(comment.createdAt);
-    const updatedAt = new Date(comment.updatedAt);
-    const isEdited = createdAt < updatedAt;
-
-    const rootClassName = this.getRootClassName(comment);
-    const commentBody = isMarkdown ? this.renderRevisionBody() : this.renderText(comment.comment);
-    const revHref = `?revision=${comment.revision}`;
-
-    const editedDateId = `editedDate-${comment._id}`;
-    const editedDateFormatted = isEdited
-      ? format(updatedAt, 'yyyy/MM/dd HH:mm')
-      : null;
-
-    return (
-      <React.Fragment>
-        {(this.state.isReEdit && !isReadOnly) ? (
-          <CommentEditor
-            rendererOptions={this.props.rendererOptions}
-            currentCommentId={commentId}
-            commentBody={comment.comment}
-            replyTo={undefined}
-            commentCreator={creator?.username}
-            onCancelButtonClicked={() => this.setState({ isReEdit: false })}
-            onCommentButtonClicked={() => {
-              this.setState({ isReEdit: false });
-              if (onComment != null) onComment();
-            }}
-          />
-        ) : (
-          <div id={commentId} className={rootClassName}>
-            <div className="page-comment-writer">
-              <UserPicture user={creator} />
-            </div>
-            <div className="page-comment-main">
-              <div className="page-comment-creator">
-                <Username user={creator} />
-              </div>
-              <div className="page-comment-body">{commentBody}</div>
-              <div className="page-comment-meta">
-                <a href={`#${commentId}`}>
-                  <FormattedDistanceDate id={commentId} date={comment.createdAt} />
-                </a>
-                { isEdited && (
-                  <>
-                    <span id={editedDateId}>&nbsp;(edited)</span>
-                    <UncontrolledTooltip placement="bottom" fade={false} target={editedDateId}>{editedDateFormatted}</UncontrolledTooltip>
-                  </>
-                )}
-                <span className="ml-2">
-                  <a id={`page-comment-revision-${commentId}`} className="page-comment-revision" href={revHref}>
-                    <HistoryIcon />
-                  </a>
-                  <UncontrolledTooltip placement="bottom" fade={false} target={`page-comment-revision-${commentId}`}>
-                    {t('page_comment.display_the_page_when_posting_this_comment')}
-                  </UncontrolledTooltip>
-                </span>
-              </div>
-              {(this.isCurrentUserEqualsToAuthor() && !isReadOnly) && (
-                <CommentControl
-                  onClickDeleteBtn={this.deleteBtnClickedHandler}
-                  onClickEditBtn={() => this.setState({ isReEdit: true })}
-                />
-              ) }
-            </div>
-          </div>
-        )
-        }
-      </React.Fragment>
-    );
-  }
-
-}
-
-Comment.propTypes = {
-  t: PropTypes.func.isRequired, // i18next
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-  pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
-
-  comment: PropTypes.object.isRequired,
-  isReadOnly: PropTypes.bool.isRequired,
-  rendererOptions: PropTypes.instanceOf(RendererOptions).isRequired,
-  deleteBtnClicked: PropTypes.func.isRequired,
-  currentUser: PropTypes.object,
-  onComment: PropTypes.func,
-};
-
-const CommentWrapperFC = (props) => {
-  const { t } = useTranslation();
-
-  const { data: currentUser } = useCurrentUser();
-
-  return <Comment t={t} currentUser={currentUser} {...props} />;
-};
-
-/**
- * Wrapper component for using unstated
- */
-const CommentWrapper = withUnstatedContainers(CommentWrapperFC, [AppContainer, PageContainer]);
-
-export default CommentWrapper;

+ 178 - 0
packages/app/src/components/PageComment/Comment.tsx

@@ -0,0 +1,178 @@
+import React, { useEffect, useState } from 'react';
+
+
+import { UserPicture } from '@growi/ui';
+import { ConsoleFormattedStream } from 'browser-bunyan';
+import { format } from 'date-fns';
+import { useTranslation } from 'next-i18next';
+import { UncontrolledTooltip } from 'reactstrap';
+
+import { RendererOptions } from '~/services/renderer/renderer';
+import { useCurrentUser } from '~/stores/context';
+
+import { ICommentHasId } from '../../interfaces/comment';
+import FormattedDistanceDate from '../FormattedDistanceDate';
+import HistoryIcon from '../Icons/HistoryIcon';
+import RevisionRenderer from '../Page/RevisionRenderer';
+import Username from '../User/Username';
+
+import CommentControl from './CommentControl';
+import CommentEditor from './CommentEditor';
+
+type CommentProps = {
+  comment: ICommentHasId,
+  isReadOnly: boolean,
+  deleteBtnClicked: (comment: ICommentHasId) => void,
+  onComment: () => void,
+  rendererOptions: RendererOptions,
+  currentPagePath: string,
+  currentRevisionId: string,
+  currentRevisionCreatedAt: Date,
+}
+
+export const Comment = (props: CommentProps): JSX.Element => {
+  const {
+    comment, isReadOnly, deleteBtnClicked, onComment, rendererOptions, currentPagePath, currentRevisionId, currentRevisionCreatedAt,
+  } = props;
+  const { t } = useTranslation();
+  const { data: currentUser } = useCurrentUser();
+
+  const [markdown, setMarkdown] = useState('');
+  const [isReEdit, setIsReEdit] = useState(false);
+
+  const commentId = comment._id;
+  const creator = comment.creator;
+  const isMarkdown = comment.isMarkdown;
+  const createdAt = new Date(comment.createdAt);
+  const updatedAt = new Date(comment.updatedAt);
+  const isEdited = createdAt < updatedAt;
+
+  useEffect(() => {
+    setMarkdown(comment.comment);
+
+    const isCurrentRevision = () => {
+      return comment.revision === currentRevisionId;
+    };
+    isCurrentRevision();
+
+  }, [comment, currentRevisionId]);
+
+  const isCurrentUserEqualsToAuthor = () => {
+    const { creator }: any = comment;
+
+    if (creator == null || currentUser == null) {
+      return false;
+    }
+    return creator.username === currentUser.username;
+  };
+
+  const getRootClassName = (comment) => {
+    let className = 'page-comment flex-column';
+
+    if (comment.revision === currentRevisionId) {
+      className += ' page-comment-current';
+    }
+    else if (comment.createdAt.getTime() > currentRevisionCreatedAt.getTime()) {
+      className += ' page-comment-newer';
+    }
+    else {
+      className += ' page-comment-older';
+    }
+
+    if (isCurrentUserEqualsToAuthor()) {
+      className += ' page-comment-me';
+    }
+
+    return className;
+  };
+
+  const deleteBtnClickedHandler = (comment) => {
+    deleteBtnClicked(comment);
+  };
+
+  const renderText = (comment) => {
+    return <span style={{ whiteSpace: 'pre-wrap' }}>{comment}</span>;
+  };
+
+  // TODO: Remove when update ReplayComments.jsx
+  if (currentPagePath == null) {
+    return <></>;
+  }
+
+  const renderRevisionBody = () => {
+    return (
+      <RevisionRenderer
+        rendererOptions={rendererOptions}
+        markdown={markdown}
+        additionalClassName="comment"
+        pagePath={currentPagePath}
+      />
+    );
+  };
+
+  const rootClassName = getRootClassName(comment);
+  const commentBody = isMarkdown ? renderRevisionBody() : renderText(comment.comment);
+  const revHref = `?revision=${comment.revision}`;
+
+  const editedDateId = `editedDate-${comment._id}`;
+  const editedDateFormatted = isEdited
+    ? format(updatedAt, 'yyyy/MM/dd HH:mm')
+    : null;
+
+  return (
+    <>
+      {(isReEdit && !isReadOnly) ? (
+        <CommentEditor
+          rendererOptions={rendererOptions}
+          currentCommentId={commentId}
+          commentBody={comment.comment}
+          replyTo={undefined}
+          commentCreator={creator?.username}
+          onCancelButtonClicked={() => setIsReEdit(false)}
+          onCommentButtonClicked={() => {
+            setIsReEdit(false);
+            if (onComment != null) onComment();
+          }}
+        />
+      ) : (
+        <div id={commentId} className={rootClassName}>
+          <div className="page-comment-writer">
+            <UserPicture user={creator} />
+          </div>
+          <div className="page-comment-main">
+            <div className="page-comment-creator">
+              <Username user={creator} />
+            </div>
+            <div className="page-comment-body">{commentBody}</div>
+            <div className="page-comment-meta">
+              <a href={`#${commentId}`}>
+                <FormattedDistanceDate id={commentId} date={comment.createdAt} />
+              </a>
+              { isEdited && (
+                <>
+                  <span id={editedDateId}>&nbsp;(edited)</span>
+                  <UncontrolledTooltip placement="bottom" fade={false} target={editedDateId}>{editedDateFormatted}</UncontrolledTooltip>
+                </>
+              )}
+              <span className="ml-2">
+                <a id={`page-comment-revision-${commentId}`} className="page-comment-revision" href={revHref}>
+                  <HistoryIcon />
+                </a>
+                <UncontrolledTooltip placement="bottom" fade={false} target={`page-comment-revision-${commentId}`}>
+                  {t('page_comment.display_the_page_when_posting_this_comment')}
+                </UncontrolledTooltip>
+              </span>
+            </div>
+            {(isCurrentUserEqualsToAuthor() && !isReadOnly) && (
+              <CommentControl
+                onClickDeleteBtn={deleteBtnClickedHandler}
+                onClickEditBtn={() => setIsReEdit(true)}
+              />
+            ) }
+          </div>
+        </div>
+      )
+      }
+    </>
+  );
+};

+ 16 - 29
packages/app/src/components/PageComment/CommentEditor.tsx

@@ -9,16 +9,13 @@ import {
 } from 'reactstrap';
 import * as toastr from 'toastr';
 
-import AppContainer from '~/client/services/AppContainer';
-import EditorContainer from '~/client/services/EditorContainer';
-import PageContainer from '~/client/services/PageContainer';
 import { apiPostForm } from '~/client/util/apiv1-client';
 import { CustomWindow } from '~/interfaces/global';
 import { IInterceptorManager } from '~/interfaces/interceptor-manager';
 import { RendererOptions } from '~/services/renderer/renderer';
 import { useSWRxPageComment } from '~/stores/comment';
 import {
-  useCurrentPagePath, useCurrentPageId, useCurrentUser, useRevisionId,
+  useCurrentPagePath, useCurrentPageId, useCurrentUser, useRevisionId, useRendererConfig,
 } from '~/stores/context';
 import { useSWRxSlackChannels, useIsSlackEnabled } from '~/stores/editor';
 import { useIsMobile } from '~/stores/ui';
@@ -26,9 +23,8 @@ import { useIsMobile } from '~/stores/ui';
 
 import { CustomNavTab } from '../CustomNavigation/CustomNav';
 import NotAvailableForGuest from '../NotAvailableForGuest';
-import Editor from '../PageEditor/Editor';
+// import Editor from '../PageEditor/Editor';
 import { SlackNotification } from '../SlackNotification';
-import { withUnstatedContainers } from '../UnstatedUtils';
 
 import CommentPreview from './CommentPreview';
 
@@ -47,8 +43,6 @@ const navTabMapping = {
 };
 
 type PropsType = {
-  appContainer: AppContainer,
-
   rendererOptions: RendererOptions,
   isForNewComment?: boolean,
   replyTo?: string,
@@ -68,7 +62,7 @@ type EditorRef = {
 const CommentEditor = (props: PropsType): JSX.Element => {
 
   const {
-    appContainer, rendererOptions, isForNewComment, replyTo,
+    rendererOptions, isForNewComment, replyTo,
     currentCommentId, commentBody, commentCreator, onCancelButtonClicked, onCommentButtonClicked,
   } = props;
   const { data: currentUser } = useCurrentUser();
@@ -79,11 +73,11 @@ const CommentEditor = (props: PropsType): JSX.Element => {
   const { data: isMobile } = useIsMobile();
   const { data: isSlackEnabled, mutate: mutateIsSlackEnabled } = useIsSlackEnabled();
   const { data: slackChannelsData } = useSWRxSlackChannels(currentPagePath);
+  const { data: config } = useRendererConfig();
 
-  const config = appContainer.getConfig();
-  const isUploadable = config.upload.image || config.upload.file;
-  const isUploadableFile = config.upload.file;
-  const isSlackConfigured = config.isSlackConfigured;
+  // const isUploadable = config.upload.image || config.upload.file;
+  // const isUploadableFile = config.upload.file;
+  // const isSlackConfigured = config.isSlackConfigured;
 
   const [isReadyToUse, setIsReadyToUse] = useState(!isForNewComment);
   const [comment, setComment] = useState(commentBody ?? '');
@@ -292,8 +286,8 @@ const CommentEditor = (props: PropsType): JSX.Element => {
       </Button>
     );
 
-    // TODO: typescriptize Editor
-    const AnyEditor = Editor as any;
+    // // TODO: typescriptize Editor
+    // const AnyEditor = Editor as any;
 
     return (
       <>
@@ -301,18 +295,18 @@ const CommentEditor = (props: PropsType): JSX.Element => {
           <CustomNavTab activeTab={activeTab} navTabMapping={navTabMapping} onNavSelected={handleSelect} hideBorderBottom />
           <TabContent activeTab={activeTab}>
             <TabPane tabId="comment_editor">
-              <AnyEditor
+              {/* <AnyEditor
                 ref={editorRef}
                 value={comment}
                 lineNumbers={false}
                 isMobile={isMobile}
-                isUploadable={isUploadable}
-                isUploadableFile={isUploadableFile}
+                // isUploadable={isUploadable}
+                // isUploadableFile={isUploadableFile}
                 onChange={setComment}
                 onUpload={uploadHandler}
                 onCtrlEnter={ctrlEnterHandler}
                 isComment
-              />
+              /> */}
               {/*
                 Note: <OptionsSelector /> is not optimized for ComentEditor in terms of responsive design.
                 See a review comment in https://github.com/weseek/growi/pull/3473
@@ -331,7 +325,7 @@ const CommentEditor = (props: PropsType): JSX.Element => {
             <span className="flex-grow-1" />
             <span className="d-none d-sm-inline">{ errorMessage && errorMessage }</span>
 
-            { isSlackConfigured
+            {/* { isSlackConfigured
               && (
                 <div className="form-inline align-self-center mr-md-2">
                   <SlackNotification
@@ -343,7 +337,7 @@ const CommentEditor = (props: PropsType): JSX.Element => {
                   />
                 </div>
               )
-            }
+            } */}
             <div className="d-none d-sm-block">
               <span className="mr-2">{cancelButton}</span><span>{submitButton}</span>
             </div>
@@ -377,11 +371,4 @@ const CommentEditor = (props: PropsType): JSX.Element => {
 
 };
 
-/**
- * Wrapper component for using unstated
- */
-const CommentEditorWrapper = withUnstatedContainers<unknown, Partial<PropsType>>(
-  CommentEditor, [AppContainer, PageContainer, EditorContainer],
-);
-
-export default CommentEditorWrapper;
+export default CommentEditor;

+ 2 - 7
packages/app/src/components/PageComment/CommentEditorLazyRenderer.tsx

@@ -1,14 +1,12 @@
 import React, { FC } from 'react';
 
-import { useSWRxPageComment } from '../../stores/comment';
+import { useCommentPreviewOptions } from '~/stores/renderer';
 
-import AppContainer from '~/client/services/AppContainer';
+import { useSWRxPageComment } from '../../stores/comment';
 
 import CommentEditor from './CommentEditor';
-import { useCommentPreviewOptions } from '~/stores/renderer';
 
 type Props = {
-  appContainer: AppContainer,
   pageId: string,
 }
 
@@ -22,11 +20,8 @@ const CommentEditorLazyRenderer:FC<Props> = (props:Props):JSX.Element => {
     return <></>;
   }
 
-  const { appContainer } = props;
-
   return (
     <CommentEditor
-      appContainer={appContainer}
       rendererOptions={rendererOptions}
       replyTo={undefined}
       onCommentButtonClicked={mutate}

+ 13 - 17
packages/app/src/components/PageComment/ReplayComments.jsx

@@ -1,16 +1,13 @@
 import React from 'react';
-import PropTypes from 'prop-types';
 
+import PropTypes from 'prop-types';
 import { Collapse } from 'reactstrap';
 
-import AppContainer from '~/client/services/AppContainer';
-import PageContainer from '~/client/services/PageContainer';
-
-import Comment from './Comment';
+import { RendererOptions } from '~/services/renderer/renderer';
+import { useRendererConfig } from '~/stores/context';
 
-import { withUnstatedContainers } from '../UnstatedUtils';
+import { Comment } from './Comment';
 
-import { RendererOptions } from '~/services/renderer/renderer';
 
 class ReplayComments extends React.PureComponent {
 
@@ -42,8 +39,9 @@ class ReplayComments extends React.PureComponent {
   }
 
   render() {
+    const { config } = this.props;
 
-    const isAllReplyShown = this.props.appContainer.getConfig().isAllReplyShown || false;
+    const isAllReplyShown = config.isAllReplyShown || false;
     const replyList = this.props.replyList;
 
     if (isAllReplyShown) {
@@ -100,19 +98,17 @@ class ReplayComments extends React.PureComponent {
 
 }
 
-/**
- * Wrapper component for using unstated
- */
-const ReplayCommentsWrapper = withUnstatedContainers(ReplayComments, [AppContainer, PageContainer]);
-
 ReplayComments.propTypes = {
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-  pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
-
   rendererOptions: PropTypes.instanceOf(RendererOptions).isRequired,
   deleteBtnClicked: PropTypes.func.isRequired,
   isReadOnly: PropTypes.bool.isRequired,
   replyList: PropTypes.array,
 };
 
-export default ReplayCommentsWrapper;
+const ReplayCommentsWrapperFC = (props) => {
+  const { data: config } = useRendererConfig();
+
+  return <ReplayComments config={config} {...props} />;
+};
+
+export default ReplayCommentsWrapperFC;

+ 3 - 9
packages/app/src/components/PageEditor/DrawioModal.jsx

@@ -1,15 +1,12 @@
 import React from 'react';
-import PropTypes from 'prop-types';
-import i18next from 'i18next';
 
+import i18next from 'i18next';
+import PropTypes from 'prop-types';
 import {
   Modal,
   ModalBody,
 } from 'reactstrap';
 
-import { withUnstatedContainers } from '../UnstatedUtils';
-import AppContainer from '~/client/services/AppContainer';
-import EditorContainer from '~/client/services/EditorContainer';
 import { getDiagramsNetLangCode } from '~/client/util/locale-utils';
 
 class DrawioModal extends React.PureComponent {
@@ -168,11 +165,8 @@ class DrawioModal extends React.PureComponent {
 }
 
 DrawioModal.propTypes = {
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-  editorContainer: PropTypes.instanceOf(EditorContainer).isRequired,
-
   onSave: PropTypes.func,
 };
 
 
-export default withUnstatedContainers(DrawioModal, [AppContainer, EditorContainer]);
+export default DrawioModal;

+ 5 - 15
packages/app/src/components/PageEditor/Preview.tsx

@@ -2,14 +2,11 @@ import React, {
   useCallback, useEffect, useMemo, useState, SyntheticEvent, RefObject,
 } from 'react';
 
-
-import AppContainer from '~/client/services/AppContainer';
 import InterceptorManager from '~/services/interceptor-manager';
 import { RendererOptions } from '~/services/renderer/renderer';
 import { useEditorSettings } from '~/stores/editor';
 
 import RevisionBody from '../Page/RevisionBody';
-import { withUnstatedContainers } from '../UnstatedUtils';
 
 
 declare const interceptorManager: InterceptorManager;
@@ -23,9 +20,7 @@ type Props = {
   onScroll?: (scrollTop: number) => void,
 }
 
-type UnstatedProps = Props & { appContainer: AppContainer };
-
-const Preview = React.forwardRef((props: UnstatedProps, ref: RefObject<HTMLDivElement>): JSX.Element => {
+const Preview = React.forwardRef((props: Props, ref: RefObject<HTMLDivElement>): JSX.Element => {
 
   const {
     rendererOptions,
@@ -108,16 +103,11 @@ const Preview = React.forwardRef((props: UnstatedProps, ref: RefObject<HTMLDivEl
 
 Preview.displayName = 'Preview';
 
-/**
- * Wrapper component for using unstated
- */
-const PreviewWrapper = withUnstatedContainers(Preview, [AppContainer]);
-
 // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
-const PreviewWrapper2 = React.forwardRef((props: Props, ref: RefObject<HTMLDivElement>): JSX.Element => {
-  return <PreviewWrapper ref={ref} {...props} />;
+const PreviewWrapper = React.forwardRef((props: Props, ref: RefObject<HTMLDivElement>): JSX.Element => {
+  return <Preview ref={ref} {...props} />;
 });
 
-PreviewWrapper2.displayName = 'PreviewWrapper2';
+PreviewWrapper.displayName = 'PreviewWrapper';
 
-export default PreviewWrapper2;
+export default PreviewWrapper;

+ 2 - 2
packages/app/src/components/SearchPage/SearchResultContent.tsx

@@ -24,7 +24,7 @@ import { AdditionalMenuItemsRendererProps, ForceHideMenuItems } from '../Common/
 import { GrowiSubNavigation } from '../Navbar/GrowiSubNavigation';
 import { SubNavButtons } from '../Navbar/SubNavButtons';
 import RevisionLoader from '../Page/RevisionLoader';
-import PageComment from '../PageComment';
+import { PageComment } from '../PageComment';
 import PageContentFooter from '../PageContentFooter';
 
 
@@ -214,7 +214,7 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
           revisionId={page.revision}
           highlightKeywords={highlightKeywords}
         />
-        <PageComment appContainer={appContainer} pageId={page._id} highlightKeywords={highlightKeywords} isReadOnly hideIfEmpty />
+        <PageComment pageId={page._id} highlightKeywords={highlightKeywords} isReadOnly hideIfEmpty />
         <PageContentFooter
           createdAt={new Date(pageWithMeta.data.createdAt)}
           updatedAt={new Date(pageWithMeta.data.updatedAt)}

+ 4 - 4
packages/app/src/pages/[[...path]].page.tsx

@@ -18,8 +18,9 @@ import { useRouter } from 'next/router';
 import superjson from 'superjson';
 
 import { PageAlerts } from '~/components/PageAlert/PageAlerts';
-// import { PageComments } from '~/components/PageComment/PageComments';
+import { PageComment } from '~/components/PageComment';
 // import { useTranslation } from '~/i18n';
+import CommentEditorLazyRenderer from '~/components/PageComment/CommentEditorLazyRenderer';
 import { CrowiRequest } from '~/interfaces/crowi-request';
 // import { renderScriptTagByName, renderHighlightJsStyleTag } from '~/service/cdn-resources-loader';
 // import { useIndentSize } from '~/stores/editor';
@@ -226,7 +227,6 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
   // useRendererSettings(props.rendererSettingsStr != null ? JSON.parse(props.rendererSettingsStr) : undefined);
   // useGrowiRendererConfig(props.growiRendererConfigStr != null ? JSON.parse(props.growiRendererConfigStr) : undefined);
 
-
   // const { data: editorMode } = useEditorMode();
 
   const { pageWithMeta, userUISettings } = props;
@@ -322,8 +322,8 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
           </div>
         </div>
         <footer>
-          {/* <PageComments /> */}
-          PageComments
+          <PageComment pageId={useCurrentPageId().data} isReadOnly={false} titleAlign="left" />
+          {/* <CommentEditorLazyRenderer pageId={useCurrentPageId().data} /> */}
         </footer>
 
         <UnsavedAlertDialog />

+ 9 - 0
packages/app/src/services/renderer/renderer.tsx

@@ -340,6 +340,15 @@ export const generatePreviewOptions: ReactMarkdownOptionsGenerator = (config: Re
 
 export const generateCommentPreviewOptions: ReactMarkdownOptionsGenerator = (config: RendererConfig): RendererOptions => {
   const options = generateCommonOptions(config);
+  const { remarkPlugins } = options;
+
+  // add remark plugins
+  if (remarkPlugins != null) {
+    remarkPlugins.push(emoji);
+    if (config.isEnabledLinebreaksInComments) {
+      remarkPlugins.push(breaks);
+    }
+  }
 
   // renderer.addConfigurers([
   //   new TableConfigurer(),