|
@@ -1,464 +0,0 @@
|
|
|
-import React, { useCallback } from 'react';
|
|
|
|
|
-
|
|
|
|
|
-import { UserPicture } from '@growi/ui';
|
|
|
|
|
-import PropTypes from 'prop-types';
|
|
|
|
|
-import {
|
|
|
|
|
- Button,
|
|
|
|
|
- TabContent, TabPane,
|
|
|
|
|
-} from 'reactstrap';
|
|
|
|
|
-import * as toastr from 'toastr';
|
|
|
|
|
-
|
|
|
|
|
-import AppContainer from '~/client/services/AppContainer';
|
|
|
|
|
-import CommentContainer from '~/client/services/CommentContainer';
|
|
|
|
|
-import EditorContainer from '~/client/services/EditorContainer';
|
|
|
|
|
-import PageContainer from '~/client/services/PageContainer';
|
|
|
|
|
-import GrowiRenderer from '~/client/util/GrowiRenderer';
|
|
|
|
|
-import { useCurrentPagePath, useCurrentUser } from '~/stores/context';
|
|
|
|
|
-import { useSWRxSlackChannels, useIsSlackEnabled } from '~/stores/editor';
|
|
|
|
|
-import { useIsMobile } from '~/stores/ui';
|
|
|
|
|
-
|
|
|
|
|
-import { CustomNavTab } from '../CustomNavigation/CustomNav';
|
|
|
|
|
-import NotAvailableForGuest from '../NotAvailableForGuest';
|
|
|
|
|
-import Editor from '../PageEditor/Editor';
|
|
|
|
|
-import { SlackNotification } from '../SlackNotification';
|
|
|
|
|
-import { withUnstatedContainers } from '../UnstatedUtils';
|
|
|
|
|
-
|
|
|
|
|
-import CommentPreview from './CommentPreview';
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-const navTabMapping = {
|
|
|
|
|
- comment_editor: {
|
|
|
|
|
- Icon: () => <i className="icon-settings" />,
|
|
|
|
|
- i18n: 'Write',
|
|
|
|
|
- index: 0,
|
|
|
|
|
- },
|
|
|
|
|
- comment_preview: {
|
|
|
|
|
- Icon: () => <i className="icon-settings" />,
|
|
|
|
|
- i18n: 'Preview',
|
|
|
|
|
- index: 1,
|
|
|
|
|
- },
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- *
|
|
|
|
|
- * @author Yuki Takei <yuki@weseek.co.jp>
|
|
|
|
|
- *
|
|
|
|
|
- * @extends {React.Component}
|
|
|
|
|
- */
|
|
|
|
|
-
|
|
|
|
|
-class CommentEditor extends React.Component {
|
|
|
|
|
-
|
|
|
|
|
- constructor(props) {
|
|
|
|
|
- super(props);
|
|
|
|
|
-
|
|
|
|
|
- const config = this.props.appContainer.getConfig();
|
|
|
|
|
- const isUploadable = config.upload.image || config.upload.file;
|
|
|
|
|
- const isUploadableFile = config.upload.file;
|
|
|
|
|
-
|
|
|
|
|
- this.state = {
|
|
|
|
|
- isReadyToUse: !this.props.isForNewComment,
|
|
|
|
|
- comment: this.props.commentBody || '',
|
|
|
|
|
- isMarkdown: true,
|
|
|
|
|
- html: '',
|
|
|
|
|
- activeTab: 'comment_editor',
|
|
|
|
|
- isUploadable,
|
|
|
|
|
- isUploadableFile,
|
|
|
|
|
- errorMessage: undefined,
|
|
|
|
|
- isSlackConfigured: config.isSlackConfigured,
|
|
|
|
|
- slackChannels: this.props.slackChannels,
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- this.updateState = this.updateState.bind(this);
|
|
|
|
|
- this.updateStateCheckbox = this.updateStateCheckbox.bind(this);
|
|
|
|
|
-
|
|
|
|
|
- this.cancelButtonClickedHandler = this.cancelButtonClickedHandler.bind(this);
|
|
|
|
|
- this.commentButtonClickedHandler = this.commentButtonClickedHandler.bind(this);
|
|
|
|
|
- this.ctrlEnterHandler = this.ctrlEnterHandler.bind(this);
|
|
|
|
|
- this.postComment = this.postComment.bind(this);
|
|
|
|
|
- this.uploadHandler = this.uploadHandler.bind(this);
|
|
|
|
|
-
|
|
|
|
|
- this.renderHtml = this.renderHtml.bind(this);
|
|
|
|
|
- this.handleSelect = this.handleSelect.bind(this);
|
|
|
|
|
- this.onSlackChannelsChange = this.onSlackChannelsChange.bind(this);
|
|
|
|
|
- this.fetchSlackChannels = this.fetchSlackChannels.bind(this);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- updateState(value) {
|
|
|
|
|
- this.setState({ comment: value });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- updateStateCheckbox(event) {
|
|
|
|
|
- const value = event.target.checked;
|
|
|
|
|
- this.setState({ isMarkdown: value });
|
|
|
|
|
- // changeMode
|
|
|
|
|
- this.editor.setGfmMode(value);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- handleSelect(activeTab) {
|
|
|
|
|
- this.setState({ activeTab });
|
|
|
|
|
- this.renderHtml(this.state.comment);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- fetchSlackChannels(slackChannels) {
|
|
|
|
|
- this.setState({ slackChannels });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- componentDidUpdate(prevProps) {
|
|
|
|
|
- if (this.props.slackChannels !== prevProps.slackChannels) {
|
|
|
|
|
- this.fetchSlackChannels(this.props.slackChannels);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- onSlackChannelsChange(slackChannels) {
|
|
|
|
|
- this.setState({ slackChannels });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- initializeEditor() {
|
|
|
|
|
- this.setState({
|
|
|
|
|
- comment: '',
|
|
|
|
|
- isMarkdown: true,
|
|
|
|
|
- html: '',
|
|
|
|
|
- activeTab: 'comment_editor',
|
|
|
|
|
- errorMessage: undefined,
|
|
|
|
|
- });
|
|
|
|
|
- // reset value
|
|
|
|
|
- this.editor.setValue('');
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- cancelButtonClickedHandler() {
|
|
|
|
|
- const { isForNewComment, onCancelButtonClicked } = this.props;
|
|
|
|
|
-
|
|
|
|
|
- // change state to not ready
|
|
|
|
|
- // when this editor is for the new comment mode
|
|
|
|
|
- if (isForNewComment) {
|
|
|
|
|
- this.setState({ isReadyToUse: false });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (onCancelButtonClicked != null) {
|
|
|
|
|
- const { replyTo, currentCommentId } = this.props;
|
|
|
|
|
- onCancelButtonClicked(replyTo || currentCommentId);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- commentButtonClickedHandler() {
|
|
|
|
|
- this.postComment();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- ctrlEnterHandler(event) {
|
|
|
|
|
- if (event != null) {
|
|
|
|
|
- event.preventDefault();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- this.postComment();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Post comment with CommentContainer and update state
|
|
|
|
|
- */
|
|
|
|
|
- async postComment() {
|
|
|
|
|
- const {
|
|
|
|
|
- commentContainer, replyTo, currentCommentId, commentCreator, onCommentButtonClicked,
|
|
|
|
|
- } = this.props;
|
|
|
|
|
- try {
|
|
|
|
|
- if (currentCommentId != null) {
|
|
|
|
|
- await commentContainer.putComment(
|
|
|
|
|
- this.state.comment,
|
|
|
|
|
- this.state.isMarkdown,
|
|
|
|
|
- currentCommentId,
|
|
|
|
|
- commentCreator,
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
- else {
|
|
|
|
|
- await this.props.commentContainer.postComment(
|
|
|
|
|
- this.state.comment,
|
|
|
|
|
- this.state.isMarkdown,
|
|
|
|
|
- replyTo,
|
|
|
|
|
- this.props.isSlackEnabled,
|
|
|
|
|
- this.state.slackChannels,
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
- this.initializeEditor();
|
|
|
|
|
-
|
|
|
|
|
- if (onCommentButtonClicked != null) {
|
|
|
|
|
- onCommentButtonClicked();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- catch (err) {
|
|
|
|
|
- const errorMessage = err.message || 'An unknown error occured when posting comment';
|
|
|
|
|
- this.setState({ errorMessage });
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- uploadHandler(file) {
|
|
|
|
|
- this.props.commentContainer.uploadAttachment(file)
|
|
|
|
|
- .then((res) => {
|
|
|
|
|
- const attachment = res.attachment;
|
|
|
|
|
- const fileName = attachment.originalName;
|
|
|
|
|
-
|
|
|
|
|
- let insertText = `[${fileName}](${attachment.filePathProxied})`;
|
|
|
|
|
- // when image
|
|
|
|
|
- if (attachment.fileFormat.startsWith('image/')) {
|
|
|
|
|
- // modify to "" syntax
|
|
|
|
|
- insertText = `!${insertText}`;
|
|
|
|
|
- }
|
|
|
|
|
- this.editor.insertText(insertText);
|
|
|
|
|
- })
|
|
|
|
|
- .catch(this.apiErrorHandler)
|
|
|
|
|
- // finally
|
|
|
|
|
- .then(() => {
|
|
|
|
|
- this.editor.terminateUploadingState();
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- apiErrorHandler(error) {
|
|
|
|
|
- toastr.error(error.message, 'Error occured', {
|
|
|
|
|
- closeButton: true,
|
|
|
|
|
- progressBar: true,
|
|
|
|
|
- newestOnTop: false,
|
|
|
|
|
- showDuration: '100',
|
|
|
|
|
- hideDuration: '100',
|
|
|
|
|
- timeOut: '3000',
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- getCommentHtml() {
|
|
|
|
|
- return (
|
|
|
|
|
- <CommentPreview
|
|
|
|
|
- html={this.state.html}
|
|
|
|
|
- />
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- renderHtml(markdown) {
|
|
|
|
|
- const context = {
|
|
|
|
|
- markdown,
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const { growiRenderer } = this.props;
|
|
|
|
|
- const { interceptorManager } = window;
|
|
|
|
|
- interceptorManager.process('preRenderCommnetPreview', context)
|
|
|
|
|
- .then(() => { return interceptorManager.process('prePreProcess', context) })
|
|
|
|
|
- .then(() => {
|
|
|
|
|
- context.markdown = growiRenderer.preProcess(context.markdown, context);
|
|
|
|
|
- })
|
|
|
|
|
- .then(() => { return interceptorManager.process('postPreProcess', context) })
|
|
|
|
|
- .then(() => {
|
|
|
|
|
- const parsedHTML = growiRenderer.process(context.markdown, context);
|
|
|
|
|
- context.parsedHTML = parsedHTML;
|
|
|
|
|
- })
|
|
|
|
|
- .then(() => { return interceptorManager.process('prePostProcess', context) })
|
|
|
|
|
- .then(() => {
|
|
|
|
|
- context.parsedHTML = growiRenderer.postProcess(context.parsedHTML, context);
|
|
|
|
|
- })
|
|
|
|
|
- .then(() => { return interceptorManager.process('postPostProcess', context) })
|
|
|
|
|
- .then(() => { return interceptorManager.process('preRenderCommentPreviewHtml', context) })
|
|
|
|
|
- .then(() => {
|
|
|
|
|
- this.setState({ html: context.parsedHTML });
|
|
|
|
|
- })
|
|
|
|
|
- // process interceptors for post rendering
|
|
|
|
|
- .then(() => { return interceptorManager.process('postRenderCommentPreviewHtml', context) });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- generateInnerHtml(html) {
|
|
|
|
|
- return { __html: html };
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- renderBeforeReady() {
|
|
|
|
|
- return (
|
|
|
|
|
- <div className="text-center">
|
|
|
|
|
- <NotAvailableForGuest>
|
|
|
|
|
- <button
|
|
|
|
|
- type="button"
|
|
|
|
|
- className="btn btn-lg btn-link"
|
|
|
|
|
- onClick={() => this.setState({ isReadyToUse: true })}
|
|
|
|
|
- >
|
|
|
|
|
- <i className="icon-bubble"></i> Add Comment
|
|
|
|
|
- </button>
|
|
|
|
|
- </NotAvailableForGuest>
|
|
|
|
|
- </div>
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- renderReady() {
|
|
|
|
|
- const { isMobile } = this.props;
|
|
|
|
|
- const { activeTab } = this.state;
|
|
|
|
|
-
|
|
|
|
|
- const commentPreview = this.state.isMarkdown ? this.getCommentHtml() : null;
|
|
|
|
|
-
|
|
|
|
|
- const errorMessage = <span className="text-danger text-right mr-2">{this.state.errorMessage}</span>;
|
|
|
|
|
- const cancelButton = (
|
|
|
|
|
- <Button outline color="danger" size="xs" className="btn btn-outline-danger rounded-pill" onClick={this.cancelButtonClickedHandler}>
|
|
|
|
|
- Cancel
|
|
|
|
|
- </Button>
|
|
|
|
|
- );
|
|
|
|
|
- const submitButton = (
|
|
|
|
|
- <Button
|
|
|
|
|
- outline
|
|
|
|
|
- color="primary"
|
|
|
|
|
- className="btn btn-outline-primary rounded-pill"
|
|
|
|
|
- onClick={this.commentButtonClickedHandler}
|
|
|
|
|
- >
|
|
|
|
|
- Comment
|
|
|
|
|
- </Button>
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- return (
|
|
|
|
|
- <>
|
|
|
|
|
- <div className="comment-write">
|
|
|
|
|
- <CustomNavTab activeTab={activeTab} navTabMapping={navTabMapping} onNavSelected={this.handleSelect} hideBorderBottom />
|
|
|
|
|
- <TabContent activeTab={activeTab}>
|
|
|
|
|
- <TabPane tabId="comment_editor">
|
|
|
|
|
- <Editor
|
|
|
|
|
- ref={(c) => { this.editor = c }}
|
|
|
|
|
- value={this.state.comment}
|
|
|
|
|
- isGfmMode={this.state.isMarkdown}
|
|
|
|
|
- lineNumbers={false}
|
|
|
|
|
- isMobile={isMobile}
|
|
|
|
|
- isUploadable={this.state.isUploadable}
|
|
|
|
|
- isUploadableFile={this.state.isUploadableFile}
|
|
|
|
|
- onChange={this.updateState}
|
|
|
|
|
- onUpload={this.uploadHandler}
|
|
|
|
|
- onCtrlEnter={this.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
|
|
|
|
|
- */}
|
|
|
|
|
- </TabPane>
|
|
|
|
|
- <TabPane tabId="comment_preview">
|
|
|
|
|
- <div className="comment-form-preview">
|
|
|
|
|
- {commentPreview}
|
|
|
|
|
- </div>
|
|
|
|
|
- </TabPane>
|
|
|
|
|
- </TabContent>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <div className="comment-submit">
|
|
|
|
|
- <div className="d-flex">
|
|
|
|
|
- <label className="mr-2">
|
|
|
|
|
- {activeTab === 'comment_editor' && (
|
|
|
|
|
- <span className="custom-control custom-checkbox">
|
|
|
|
|
- <input
|
|
|
|
|
- type="checkbox"
|
|
|
|
|
- className="custom-control-input"
|
|
|
|
|
- id="comment-form-is-markdown"
|
|
|
|
|
- name="isMarkdown"
|
|
|
|
|
- checked={this.state.isMarkdown}
|
|
|
|
|
- value="1"
|
|
|
|
|
- onChange={this.updateStateCheckbox}
|
|
|
|
|
- />
|
|
|
|
|
- <label
|
|
|
|
|
- className="ml-2 custom-control-label"
|
|
|
|
|
- htmlFor="comment-form-is-markdown"
|
|
|
|
|
- >
|
|
|
|
|
- Markdown
|
|
|
|
|
- </label>
|
|
|
|
|
- </span>
|
|
|
|
|
- ) }
|
|
|
|
|
- </label>
|
|
|
|
|
- <span className="flex-grow-1" />
|
|
|
|
|
- <span className="d-none d-sm-inline">{ this.state.errorMessage && errorMessage }</span>
|
|
|
|
|
-
|
|
|
|
|
- { this.state.isSlackConfigured
|
|
|
|
|
- && (
|
|
|
|
|
- <div className="form-inline align-self-center mr-md-2">
|
|
|
|
|
- <SlackNotification
|
|
|
|
|
- isSlackEnabled={this.props.isSlackEnabled}
|
|
|
|
|
- slackChannels={this.state.slackChannels}
|
|
|
|
|
- onEnabledFlagChange={this.props.onSlackEnabledFlagChange}
|
|
|
|
|
- onChannelChange={this.onSlackChannelsChange}
|
|
|
|
|
- id="idForComment"
|
|
|
|
|
- />
|
|
|
|
|
- </div>
|
|
|
|
|
- )
|
|
|
|
|
- }
|
|
|
|
|
- <div className="d-none d-sm-block">
|
|
|
|
|
- <span className="mr-2">{cancelButton}</span><span>{submitButton}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div className="d-block d-sm-none mt-2">
|
|
|
|
|
- <div className="d-flex justify-content-end">
|
|
|
|
|
- { this.state.errorMessage && errorMessage }
|
|
|
|
|
- <span className="mr-2">{cancelButton}</span><span>{submitButton}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </>
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- render() {
|
|
|
|
|
- const { currentUser } = this.props;
|
|
|
|
|
- const { isReadyToUse } = this.state;
|
|
|
|
|
-
|
|
|
|
|
- return (
|
|
|
|
|
- <div className="form page-comment-form">
|
|
|
|
|
- <div className="comment-form">
|
|
|
|
|
- <div className="comment-form-user">
|
|
|
|
|
- <UserPicture user={currentUser} noLink noTooltip />
|
|
|
|
|
- </div>
|
|
|
|
|
- <div className="comment-form-main">
|
|
|
|
|
- { !isReadyToUse
|
|
|
|
|
- ? this.renderBeforeReady()
|
|
|
|
|
- : this.renderReady()
|
|
|
|
|
- }
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * Wrapper component for using unstated
|
|
|
|
|
- */
|
|
|
|
|
-const CommentEditorHOCWrapper = withUnstatedContainers(CommentEditor, [AppContainer, PageContainer, EditorContainer, CommentContainer]);
|
|
|
|
|
-
|
|
|
|
|
-CommentEditor.propTypes = {
|
|
|
|
|
- appContainer: PropTypes.instanceOf(AppContainer).isRequired,
|
|
|
|
|
- pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
|
|
|
|
|
- editorContainer: PropTypes.instanceOf(EditorContainer).isRequired,
|
|
|
|
|
- commentContainer: PropTypes.instanceOf(CommentContainer).isRequired,
|
|
|
|
|
-
|
|
|
|
|
- slackChannels: PropTypes.string.isRequired,
|
|
|
|
|
- isSlackEnabled: PropTypes.bool.isRequired,
|
|
|
|
|
- growiRenderer: PropTypes.instanceOf(GrowiRenderer).isRequired,
|
|
|
|
|
- currentUser: PropTypes.instanceOf(Object),
|
|
|
|
|
- isMobile: PropTypes.bool,
|
|
|
|
|
- isForNewComment: PropTypes.bool,
|
|
|
|
|
- replyTo: PropTypes.string,
|
|
|
|
|
- currentCommentId: PropTypes.string,
|
|
|
|
|
- commentBody: PropTypes.string,
|
|
|
|
|
- commentCreator: PropTypes.string,
|
|
|
|
|
- onCancelButtonClicked: PropTypes.func,
|
|
|
|
|
- onCommentButtonClicked: PropTypes.func,
|
|
|
|
|
- onSlackEnabledFlagChange: PropTypes.func,
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-const CommentEditorWrapper = (props) => {
|
|
|
|
|
- const { data: isMobile } = useIsMobile();
|
|
|
|
|
- const { data: currentUser } = useCurrentUser();
|
|
|
|
|
- const { data: isSlackEnabled, mutate: mutateIsSlackEnabled } = useIsSlackEnabled();
|
|
|
|
|
- const { data: currentPagePath } = useCurrentPagePath();
|
|
|
|
|
- const { data: slackChannelsData } = useSWRxSlackChannels(currentPagePath);
|
|
|
|
|
-
|
|
|
|
|
- const onSlackEnabledFlagChange = useCallback((isSlackEnabled) => {
|
|
|
|
|
- mutateIsSlackEnabled(isSlackEnabled, false);
|
|
|
|
|
- }, [mutateIsSlackEnabled]);
|
|
|
|
|
-
|
|
|
|
|
- return (
|
|
|
|
|
- <CommentEditorHOCWrapper
|
|
|
|
|
- {...props}
|
|
|
|
|
- onSlackEnabledFlagChange={onSlackEnabledFlagChange}
|
|
|
|
|
- slackChannels={slackChannelsData.toString()}
|
|
|
|
|
- isSlackEnabled={isSlackEnabled}
|
|
|
|
|
- currentUser={currentUser}
|
|
|
|
|
- isMobile={isMobile}
|
|
|
|
|
- />
|
|
|
|
|
- );
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-export default CommentEditorWrapper;
|
|
|