|
@@ -1,7 +1,8 @@
|
|
|
-import React, { useCallback } from 'react';
|
|
|
|
|
|
|
+import React, {
|
|
|
|
|
+ useCallback, useState, useRef, useEffect,
|
|
|
|
|
+} from 'react';
|
|
|
|
|
|
|
|
import { UserPicture } from '@growi/ui';
|
|
import { UserPicture } from '@growi/ui';
|
|
|
-import PropTypes from 'prop-types';
|
|
|
|
|
import {
|
|
import {
|
|
|
Button,
|
|
Button,
|
|
|
TabContent, TabPane,
|
|
TabContent, TabPane,
|
|
@@ -14,6 +15,8 @@ import EditorContainer from '~/client/services/EditorContainer';
|
|
|
import PageContainer from '~/client/services/PageContainer';
|
|
import PageContainer from '~/client/services/PageContainer';
|
|
|
import GrowiRenderer from '~/client/util/GrowiRenderer';
|
|
import GrowiRenderer from '~/client/util/GrowiRenderer';
|
|
|
import { apiPostForm } from '~/client/util/apiv1-client';
|
|
import { apiPostForm } from '~/client/util/apiv1-client';
|
|
|
|
|
+import { CustomWindow } from '~/interfaces/global';
|
|
|
|
|
+import InterceptorManager from '~/services/interceptor-manager';
|
|
|
import { useCurrentPagePath, useCurrentPageId, useCurrentUser } from '~/stores/context';
|
|
import { useCurrentPagePath, useCurrentPageId, useCurrentUser } from '~/stores/context';
|
|
|
import { useSWRxSlackChannels, useIsSlackEnabled } from '~/stores/editor';
|
|
import { useSWRxSlackChannels, useIsSlackEnabled } from '~/stores/editor';
|
|
|
import { useIsMobile } from '~/stores/ui';
|
|
import { useIsMobile } from '~/stores/ui';
|
|
@@ -40,145 +43,174 @@ const navTabMapping = {
|
|
|
},
|
|
},
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-/**
|
|
|
|
|
- *
|
|
|
|
|
- * @author Yuki Takei <yuki@weseek.co.jp>
|
|
|
|
|
- *
|
|
|
|
|
- * @extends {React.Component}
|
|
|
|
|
- */
|
|
|
|
|
|
|
+type PropsType = {
|
|
|
|
|
+ appContainer: AppContainer,
|
|
|
|
|
+ commentContainer: CommentContainer,
|
|
|
|
|
|
|
|
-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,
|
|
|
|
|
- };
|
|
|
|
|
|
|
+ growiRenderer: GrowiRenderer,
|
|
|
|
|
+ isForNewComment: boolean,
|
|
|
|
|
+ replyTo: string,
|
|
|
|
|
+ currrentCommentId: string,
|
|
|
|
|
+ commentBody: string,
|
|
|
|
|
+ commentCreator: string,
|
|
|
|
|
+ onCancelButtonClicked: (id: string) => void,
|
|
|
|
|
+ onCommentButtonClicked: () => void,
|
|
|
|
|
|
|
|
- this.updateState = this.updateState.bind(this);
|
|
|
|
|
- this.updateStateCheckbox = this.updateStateCheckbox.bind(this);
|
|
|
|
|
|
|
+ currentCommentId: string
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- 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);
|
|
|
|
|
|
|
+interface ICommentEditorOperation {
|
|
|
|
|
+ setGfmMode: (value: boolean) => void,
|
|
|
|
|
+ setValue: (value: string) => void,
|
|
|
|
|
+ insertText: (text: string) => void,
|
|
|
|
|
+ terminateUploadingState: () => void,
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- this.renderHtml = this.renderHtml.bind(this);
|
|
|
|
|
- this.handleSelect = this.handleSelect.bind(this);
|
|
|
|
|
- this.onSlackChannelsChange = this.onSlackChannelsChange.bind(this);
|
|
|
|
|
- this.fetchSlackChannels = this.fetchSlackChannels.bind(this);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+const CommentEditor = (props: PropsType): JSX.Element => {
|
|
|
|
|
|
|
|
- updateState(value) {
|
|
|
|
|
- this.setState({ comment: value });
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ const {
|
|
|
|
|
+ appContainer, commentContainer, growiRenderer, isForNewComment,
|
|
|
|
|
+ replyTo, currentCommentId, commentBody, commentCreator,
|
|
|
|
|
+ } = props;
|
|
|
|
|
+ const { data: currentUser } = useCurrentUser();
|
|
|
|
|
+ const { data: currentPagePath } = useCurrentPagePath();
|
|
|
|
|
+ const { data: currentPageId } = useCurrentPageId();
|
|
|
|
|
+ const { data: isMobile } = useIsMobile();
|
|
|
|
|
+ const { data: isSlackEnabled, mutate: mutateIsSlackEnabled } = useIsSlackEnabled();
|
|
|
|
|
+ const { data: slackChannelsData } = useSWRxSlackChannels(currentPagePath);
|
|
|
|
|
+
|
|
|
|
|
+ const config = appContainer.getConfig();
|
|
|
|
|
+ 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 ?? '');
|
|
|
|
|
+ const [isMarkdown, setIsMarkdown] = useState(false);
|
|
|
|
|
+ const [html, setHtml] = useState('');
|
|
|
|
|
+ const [activeTab, setActiveTab] = useState('comment_editor');
|
|
|
|
|
+ const [error, setError] = useState();
|
|
|
|
|
+ const [slackChannels, setSlackChannels] = useState(slackChannelsData?.toString());
|
|
|
|
|
|
|
|
- updateStateCheckbox(event) {
|
|
|
|
|
|
|
+ const editorRef = useRef<ICommentEditorOperation>(null);
|
|
|
|
|
+
|
|
|
|
|
+ // TODO: typescriptize Editor
|
|
|
|
|
+ const AnyEditor = Editor as any;
|
|
|
|
|
+
|
|
|
|
|
+ const updateState = (value:string) => {
|
|
|
|
|
+ setComment(value);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const updateStateCheckbox = (event) => {
|
|
|
|
|
+ if (editorRef.current == null) { return }
|
|
|
const value = event.target.checked;
|
|
const value = event.target.checked;
|
|
|
- this.setState({ isMarkdown: value });
|
|
|
|
|
|
|
+ setIsMarkdown(value);
|
|
|
// changeMode
|
|
// changeMode
|
|
|
- this.editor.setGfmMode(value);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ editorRef.current.setGfmMode(value);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const renderHtml = (markdown: string) => {
|
|
|
|
|
+ const context = {
|
|
|
|
|
+ markdown,
|
|
|
|
|
+ parsedHTML: '',
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const interceptorManager: InterceptorManager = (window as CustomWindow).interceptorManager;
|
|
|
|
|
+ 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(() => {
|
|
|
|
|
+ setHtml(context.parsedHTML);
|
|
|
|
|
+ })
|
|
|
|
|
+ // process interceptors for post rendering
|
|
|
|
|
+ .then(() => { return interceptorManager.process('postRenderCommentPreviewHtml', context) });
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
- handleSelect(activeTab) {
|
|
|
|
|
- this.setState({ activeTab });
|
|
|
|
|
- this.renderHtml(this.state.comment);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ const handleSelect = (activeTab: string) => {
|
|
|
|
|
+ setActiveTab(activeTab);
|
|
|
|
|
+ renderHtml(comment);
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
- fetchSlackChannels(slackChannels) {
|
|
|
|
|
- this.setState({ slackChannels });
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ const fetchSlackChannels = (slackChannels: string|undefined) => {
|
|
|
|
|
+ if (slackChannels === undefined) { return }
|
|
|
|
|
+ setSlackChannels(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,
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ const onSlackEnabledFlagChange = useCallback((isSlackEnabled) => {
|
|
|
|
|
+ mutateIsSlackEnabled(isSlackEnabled, false);
|
|
|
|
|
+ }, [mutateIsSlackEnabled]);
|
|
|
|
|
+
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ // if (this.props.slackChannels !== prevProps.slackChannels) {
|
|
|
|
|
+ // this.fetchSlackChannels(this.props.slackChannels);
|
|
|
|
|
+ // }
|
|
|
|
|
+ // 実装を考える必要あり
|
|
|
|
|
+ fetchSlackChannels(slackChannelsData?.toString());
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const onSlackChannelsChange = (slackChannels: string) => {
|
|
|
|
|
+ setSlackChannels(slackChannels);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const initializeEditor = () => {
|
|
|
|
|
+ setComment('');
|
|
|
|
|
+ setIsMarkdown(true);
|
|
|
|
|
+ setHtml('');
|
|
|
|
|
+ setActiveTab('comment_editor');
|
|
|
|
|
+ setError(undefined);
|
|
|
// reset value
|
|
// reset value
|
|
|
- this.editor.setValue('');
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (editorRef.current == null) { return }
|
|
|
|
|
+ editorRef.current.setValue('');
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
- cancelButtonClickedHandler() {
|
|
|
|
|
- const { isForNewComment, onCancelButtonClicked } = this.props;
|
|
|
|
|
|
|
+ const cancelButtonClickedHandler = () => {
|
|
|
|
|
+ const { onCancelButtonClicked } = props;
|
|
|
|
|
|
|
|
// change state to not ready
|
|
// change state to not ready
|
|
|
// when this editor is for the new comment mode
|
|
// when this editor is for the new comment mode
|
|
|
if (isForNewComment) {
|
|
if (isForNewComment) {
|
|
|
- this.setState({ isReadyToUse: false });
|
|
|
|
|
|
|
+ setIsReadyToUse(false);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (onCancelButtonClicked != null) {
|
|
if (onCancelButtonClicked != null) {
|
|
|
- const { replyTo, currentCommentId } = this.props;
|
|
|
|
|
onCancelButtonClicked(replyTo || currentCommentId);
|
|
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;
|
|
|
|
|
|
|
+ const postComment = async() => {
|
|
|
|
|
+ const { onCommentButtonClicked } = props;
|
|
|
try {
|
|
try {
|
|
|
if (currentCommentId != null) {
|
|
if (currentCommentId != null) {
|
|
|
await commentContainer.putComment(
|
|
await commentContainer.putComment(
|
|
|
- this.state.comment,
|
|
|
|
|
- this.state.isMarkdown,
|
|
|
|
|
|
|
+ comment,
|
|
|
|
|
+ isMarkdown,
|
|
|
currentCommentId,
|
|
currentCommentId,
|
|
|
commentCreator,
|
|
commentCreator,
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
else {
|
|
else {
|
|
|
- await this.props.commentContainer.postComment(
|
|
|
|
|
- this.state.comment,
|
|
|
|
|
- this.state.isMarkdown,
|
|
|
|
|
|
|
+ await commentContainer.postComment(
|
|
|
|
|
+ comment,
|
|
|
|
|
+ isMarkdown,
|
|
|
replyTo,
|
|
replyTo,
|
|
|
- this.props.isSlackEnabled,
|
|
|
|
|
- this.state.slackChannels,
|
|
|
|
|
|
|
+ isSlackEnabled,
|
|
|
|
|
+ slackChannels,
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
- this.initializeEditor();
|
|
|
|
|
|
|
+ initializeEditor();
|
|
|
|
|
|
|
|
if (onCommentButtonClicked != null) {
|
|
if (onCommentButtonClicked != null) {
|
|
|
onCommentButtonClicked();
|
|
onCommentButtonClicked();
|
|
@@ -186,20 +218,47 @@ class CommentEditor extends React.Component {
|
|
|
}
|
|
}
|
|
|
catch (err) {
|
|
catch (err) {
|
|
|
const errorMessage = err.message || 'An unknown error occured when posting comment';
|
|
const errorMessage = err.message || 'An unknown error occured when posting comment';
|
|
|
- this.setState({ errorMessage });
|
|
|
|
|
|
|
+ setError(errorMessage);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const commentButtonClickedHandler = () => {
|
|
|
|
|
+ postComment();
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const ctrlEnterHandler = (event) => {
|
|
|
|
|
+ if (event != null) {
|
|
|
|
|
+ event.preventDefault();
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- async uploadHandler(file) {
|
|
|
|
|
- const pagePath = this.props.currentPagePath;
|
|
|
|
|
- const pageId = this.props.currentPageId;
|
|
|
|
|
|
|
+ postComment();
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const apiErrorHandler = (error) => {
|
|
|
|
|
+ toastr.error(error.message, 'Error occured', {
|
|
|
|
|
+ closeButton: true,
|
|
|
|
|
+ progressBar: true,
|
|
|
|
|
+ newestOnTop: false,
|
|
|
|
|
+ showDuration: '100',
|
|
|
|
|
+ hideDuration: '100',
|
|
|
|
|
+ timeOut: '3000',
|
|
|
|
|
+ });
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const uploadHandler = async(file) => {
|
|
|
|
|
+
|
|
|
|
|
+ if (editorRef.current == null) { return }
|
|
|
|
|
+
|
|
|
|
|
+ const pagePath = currentPagePath;
|
|
|
|
|
+ const pageId = currentPageId;
|
|
|
const endpoint = '/attachments.add';
|
|
const endpoint = '/attachments.add';
|
|
|
const formData = new FormData();
|
|
const formData = new FormData();
|
|
|
formData.append('file', file);
|
|
formData.append('file', file);
|
|
|
- formData.append('path', pagePath);
|
|
|
|
|
- formData.append('page_id', pageId);
|
|
|
|
|
|
|
+ formData.append('path', pagePath ?? '');
|
|
|
|
|
+ formData.append('page_id', pageId ?? '');
|
|
|
try {
|
|
try {
|
|
|
- const res = await apiPostForm(endpoint, formData);
|
|
|
|
|
|
|
+ // TODO: typescriptize res
|
|
|
|
|
+ const res = await apiPostForm(endpoint, formData) as any;
|
|
|
const attachment = res.attachment;
|
|
const attachment = res.attachment;
|
|
|
const fileName = attachment.originalName;
|
|
const fileName = attachment.originalName;
|
|
|
let insertText = `[${fileName}](${attachment.filePathProxied})`;
|
|
let insertText = `[${fileName}](${attachment.filePathProxied})`;
|
|
@@ -208,93 +267,46 @@ class CommentEditor extends React.Component {
|
|
|
// modify to "" syntax
|
|
// modify to "" syntax
|
|
|
insertText = `!${insertText}`;
|
|
insertText = `!${insertText}`;
|
|
|
}
|
|
}
|
|
|
- this.editor.insertText(insertText);
|
|
|
|
|
|
|
+ editorRef.current.insertText(insertText);
|
|
|
}
|
|
}
|
|
|
catch (err) {
|
|
catch (err) {
|
|
|
- this.apiErrorHandler(err);
|
|
|
|
|
|
|
+ apiErrorHandler(err);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- this.editor.terminateUploadingState();
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ editorRef.current.terminateUploadingState();
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
- apiErrorHandler(error) {
|
|
|
|
|
- toastr.error(error.message, 'Error occured', {
|
|
|
|
|
- closeButton: true,
|
|
|
|
|
- progressBar: true,
|
|
|
|
|
- newestOnTop: false,
|
|
|
|
|
- showDuration: '100',
|
|
|
|
|
- hideDuration: '100',
|
|
|
|
|
- timeOut: '3000',
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- getCommentHtml() {
|
|
|
|
|
|
|
+ const getCommentHtml = () => {
|
|
|
return (
|
|
return (
|
|
|
<CommentPreview
|
|
<CommentPreview
|
|
|
- html={this.state.html}
|
|
|
|
|
|
|
+ html={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() {
|
|
|
|
|
|
|
+ const renderBeforeReady = (): JSX.Element => {
|
|
|
return (
|
|
return (
|
|
|
<div className="text-center">
|
|
<div className="text-center">
|
|
|
<NotAvailableForGuest>
|
|
<NotAvailableForGuest>
|
|
|
<button
|
|
<button
|
|
|
type="button"
|
|
type="button"
|
|
|
className="btn btn-lg btn-link"
|
|
className="btn btn-lg btn-link"
|
|
|
- onClick={() => this.setState({ isReadyToUse: true })}
|
|
|
|
|
|
|
+ onClick={() => setIsReadyToUse(true)}
|
|
|
>
|
|
>
|
|
|
<i className="icon-bubble"></i> Add Comment
|
|
<i className="icon-bubble"></i> Add Comment
|
|
|
</button>
|
|
</button>
|
|
|
</NotAvailableForGuest>
|
|
</NotAvailableForGuest>
|
|
|
</div>
|
|
</div>
|
|
|
);
|
|
);
|
|
|
- }
|
|
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
- renderReady() {
|
|
|
|
|
- const { isMobile } = this.props;
|
|
|
|
|
- const { activeTab } = this.state;
|
|
|
|
|
|
|
+ const renderReady = () => {
|
|
|
|
|
|
|
|
- const commentPreview = this.state.isMarkdown ? this.getCommentHtml() : null;
|
|
|
|
|
|
|
+ const commentPreview = isMarkdown ? getCommentHtml() : null;
|
|
|
|
|
|
|
|
- const errorMessage = <span className="text-danger text-right mr-2">{this.state.errorMessage}</span>;
|
|
|
|
|
|
|
+ const errorMessage = <span className="text-danger text-right mr-2">{error}</span>;
|
|
|
const cancelButton = (
|
|
const cancelButton = (
|
|
|
- <Button outline color="danger" size="xs" className="btn btn-outline-danger rounded-pill" onClick={this.cancelButtonClickedHandler}>
|
|
|
|
|
|
|
+ <Button outline color="danger" size="xs" className="btn btn-outline-danger rounded-pill" onClick={cancelButtonClickedHandler}>
|
|
|
Cancel
|
|
Cancel
|
|
|
</Button>
|
|
</Button>
|
|
|
);
|
|
);
|
|
@@ -303,7 +315,7 @@ class CommentEditor extends React.Component {
|
|
|
outline
|
|
outline
|
|
|
color="primary"
|
|
color="primary"
|
|
|
className="btn btn-outline-primary rounded-pill"
|
|
className="btn btn-outline-primary rounded-pill"
|
|
|
- onClick={this.commentButtonClickedHandler}
|
|
|
|
|
|
|
+ onClick={commentButtonClickedHandler}
|
|
|
>
|
|
>
|
|
|
Comment
|
|
Comment
|
|
|
</Button>
|
|
</Button>
|
|
@@ -313,20 +325,20 @@ class CommentEditor extends React.Component {
|
|
|
return (
|
|
return (
|
|
|
<>
|
|
<>
|
|
|
<div className="comment-write">
|
|
<div className="comment-write">
|
|
|
- <CustomNavTab activeTab={activeTab} navTabMapping={navTabMapping} onNavSelected={this.handleSelect} hideBorderBottom />
|
|
|
|
|
|
|
+ <CustomNavTab activeTab={activeTab} navTabMapping={navTabMapping} onNavSelected={handleSelect} hideBorderBottom />
|
|
|
<TabContent activeTab={activeTab}>
|
|
<TabContent activeTab={activeTab}>
|
|
|
<TabPane tabId="comment_editor">
|
|
<TabPane tabId="comment_editor">
|
|
|
- <Editor
|
|
|
|
|
- ref={(c) => { this.editor = c }}
|
|
|
|
|
- value={this.state.comment}
|
|
|
|
|
- isGfmMode={this.state.isMarkdown}
|
|
|
|
|
|
|
+ <AnyEditor
|
|
|
|
|
+ ref={editorRef}
|
|
|
|
|
+ value={comment}
|
|
|
|
|
+ isGfmMode={isMarkdown}
|
|
|
lineNumbers={false}
|
|
lineNumbers={false}
|
|
|
isMobile={isMobile}
|
|
isMobile={isMobile}
|
|
|
- isUploadable={this.state.isUploadable}
|
|
|
|
|
- isUploadableFile={this.state.isUploadableFile}
|
|
|
|
|
- onChange={this.updateState}
|
|
|
|
|
- onUpload={this.uploadHandler}
|
|
|
|
|
- onCtrlEnter={this.ctrlEnterHandler}
|
|
|
|
|
|
|
+ isUploadable={isUploadable}
|
|
|
|
|
+ isUploadableFile={isUploadableFile}
|
|
|
|
|
+ onChange={updateState}
|
|
|
|
|
+ onUpload={uploadHandler}
|
|
|
|
|
+ onCtrlEnter={ctrlEnterHandler}
|
|
|
isComment
|
|
isComment
|
|
|
/>
|
|
/>
|
|
|
{/*
|
|
{/*
|
|
@@ -352,9 +364,9 @@ class CommentEditor extends React.Component {
|
|
|
className="custom-control-input"
|
|
className="custom-control-input"
|
|
|
id="comment-form-is-markdown"
|
|
id="comment-form-is-markdown"
|
|
|
name="isMarkdown"
|
|
name="isMarkdown"
|
|
|
- checked={this.state.isMarkdown}
|
|
|
|
|
|
|
+ checked={isMarkdown}
|
|
|
value="1"
|
|
value="1"
|
|
|
- onChange={this.updateStateCheckbox}
|
|
|
|
|
|
|
+ onChange={updateStateCheckbox}
|
|
|
/>
|
|
/>
|
|
|
<label
|
|
<label
|
|
|
className="ml-2 custom-control-label"
|
|
className="ml-2 custom-control-label"
|
|
@@ -366,16 +378,16 @@ class CommentEditor extends React.Component {
|
|
|
) }
|
|
) }
|
|
|
</label>
|
|
</label>
|
|
|
<span className="flex-grow-1" />
|
|
<span className="flex-grow-1" />
|
|
|
- <span className="d-none d-sm-inline">{ this.state.errorMessage && errorMessage }</span>
|
|
|
|
|
|
|
+ <span className="d-none d-sm-inline">{ errorMessage && errorMessage }</span>
|
|
|
|
|
|
|
|
- { this.state.isSlackConfigured
|
|
|
|
|
|
|
+ { isSlackConfigured
|
|
|
&& (
|
|
&& (
|
|
|
<div className="form-inline align-self-center mr-md-2">
|
|
<div className="form-inline align-self-center mr-md-2">
|
|
|
<SlackNotification
|
|
<SlackNotification
|
|
|
- isSlackEnabled={this.props.isSlackEnabled}
|
|
|
|
|
- slackChannels={this.state.slackChannels}
|
|
|
|
|
- onEnabledFlagChange={this.props.onSlackEnabledFlagChange}
|
|
|
|
|
- onChannelChange={this.onSlackChannelsChange}
|
|
|
|
|
|
|
+ isSlackEnabled
|
|
|
|
|
+ slackChannels={slackChannelsData?.toString() ?? ''}
|
|
|
|
|
+ onEnabledFlagChange={onSlackEnabledFlagChange}
|
|
|
|
|
+ onChannelChange={onSlackChannelsChange}
|
|
|
id="idForComment"
|
|
id="idForComment"
|
|
|
/>
|
|
/>
|
|
|
</div>
|
|
</div>
|
|
@@ -387,90 +399,36 @@ class CommentEditor extends React.Component {
|
|
|
</div>
|
|
</div>
|
|
|
<div className="d-block d-sm-none mt-2">
|
|
<div className="d-block d-sm-none mt-2">
|
|
|
<div className="d-flex justify-content-end">
|
|
<div className="d-flex justify-content-end">
|
|
|
- { this.state.errorMessage && errorMessage }
|
|
|
|
|
|
|
+ { error && errorMessage }
|
|
|
<span className="mr-2">{cancelButton}</span><span>{submitButton}</span>
|
|
<span className="mr-2">{cancelButton}</span><span>{submitButton}</span>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</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>
|
|
|
|
|
|
|
+ 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
|
|
|
|
|
+ ? renderBeforeReady()
|
|
|
|
|
+ : renderReady()
|
|
|
|
|
+ }
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ </div>
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
-}
|
|
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Wrapper component for using unstated
|
|
* 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,
|
|
|
|
|
-
|
|
|
|
|
- currentPagePath: PropTypes.string.isRequired,
|
|
|
|
|
- currentPageId: PropTypes.string.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: currentPageId } = useCurrentPageId();
|
|
|
|
|
- const { data: slackChannelsData } = useSWRxSlackChannels(currentPagePath);
|
|
|
|
|
-
|
|
|
|
|
- const onSlackEnabledFlagChange = useCallback((isSlackEnabled) => {
|
|
|
|
|
- mutateIsSlackEnabled(isSlackEnabled, false);
|
|
|
|
|
- }, [mutateIsSlackEnabled]);
|
|
|
|
|
-
|
|
|
|
|
- return (
|
|
|
|
|
- <CommentEditorHOCWrapper
|
|
|
|
|
- {...props}
|
|
|
|
|
- currentPagePath={currentPagePath}
|
|
|
|
|
- currentPageId={currentPageId}
|
|
|
|
|
- onSlackEnabledFlagChange={onSlackEnabledFlagChange}
|
|
|
|
|
- slackChannels={slackChannelsData.toString()}
|
|
|
|
|
- isSlackEnabled={isSlackEnabled}
|
|
|
|
|
- currentUser={currentUser}
|
|
|
|
|
- isMobile={isMobile}
|
|
|
|
|
- />
|
|
|
|
|
- );
|
|
|
|
|
-};
|
|
|
|
|
|
|
+const CommentEditorWrapper = withUnstatedContainers(CommentEditor, [AppContainer, PageContainer, EditorContainer, CommentContainer]);
|
|
|
|
|
|
|
|
export default CommentEditorWrapper;
|
|
export default CommentEditorWrapper;
|