diff --git a/frontend/src/components/comments-list.js b/frontend/src/components/comments-list.js new file mode 100644 index 0000000000..d774c8adad --- /dev/null +++ b/frontend/src/components/comments-list.js @@ -0,0 +1,215 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import moment from 'moment'; +import { processor } from '@seafile/seafile-editor/dist/utils/seafile-markdown2html'; +import { Button, Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap'; +import { gettext } from '../utils/constants'; +import { seafileAPI } from '../utils/seafile-api'; +import '../css/comments-list.css'; + +const { repoID, filePath } = window.app.pageOptions; +const { username } = window.app.userInfo; + +const CommentsListPropTypes = { + toggleCommentsList: PropTypes.func.isRequired +}; + +class CommentsList extends React.Component { + + constructor(props) { + super(props); + this.state = { + commentsList: [], + showResolvedComment: true, + }; + } + + toggleResolvedComment = () => { + this.setState({ + showResolvedComment: !this.state.showResolvedComment + }); + } + + listComments = () => { + seafileAPI.listComments(repoID, filePath).then((res) => { + this.setState({ + commentsList: res.data.comments + }); + }); + } + + handleCommentChange = (event) => { + this.setState({ + comment: event.target.value, + }); + } + + submitComment = () => { + let comment = this.refs.commentTextarea.value; + if (comment.trim()) { + seafileAPI.postComment(repoID, filePath, comment).then(() => { + this.listComments(); + }); + } + this.refs.commentTextarea.value = ''; + } + + resolveComment = (event) => { + seafileAPI.updateComment(repoID, event.target.id, 'true').then(() => { + this.listComments(); + }); + } + + deleteComment = (event) => { + seafileAPI.deleteComment(repoID, event.target.id).then(() => { + this.listComments(); + }); + } + + componentDidMount() { + this.listComments(); + } + + render() { + return ( +
+
+
+ +
+
{gettext('comments')}
+
+
+
{gettext('Show resolved comments')}
+
+ +
+
+ +
+ + +
+
+ ); + } +} + +CommentsList.propTypes = CommentsListPropTypes; + + +const commentItemPropTypes = { + time: PropTypes.string.isRequired, + item: PropTypes.object.isRequired, + deleteComment: PropTypes.func.isRequired, + resolveComment: PropTypes.func.isRequired, + showResolvedComment: PropTypes.bool.isRequired, +}; + +class CommentItem extends React.Component { + + constructor(props) { + super(props); + this.state = { + dropdownOpen: false, + html: '', + }; + } + + toggleDropDownMenu = () => { + this.setState({ + dropdownOpen: !this.state.dropdownOpen, + }); + } + + convertComment = (mdFile) => { + processor.process(mdFile).then( + (result) => { + let html = String(result); + this.setState({ + html: html + }); + } + ); + } + + componentWillMount() { + this.convertComment(this.props.item.comment); + } + + componentWillReceiveProps(nextProps) { + this.convertComment(nextProps.item.comment); + } + + render() { + const item = this.props.item; + if (item.resolved && !this.props.showResolvedComment) { + return null; + } + return ( +
  • +
    + +
    +
    {item.user_name}
    +
    {this.props.time}
    +
    + + + + + + { + (item.user_email === username) && + {gettext('Delete')}} + { + !item.resolved && + {gettext('Mark as resolved')} + } + + +
    +
    +
  • + ); + } +} + +CommentItem.propTypes = commentItemPropTypes; + +export default CommentsList; diff --git a/frontend/src/css/comments-list.css b/frontend/src/css/comments-list.css new file mode 100644 index 0000000000..6c9ba562c5 --- /dev/null +++ b/frontend/src/css/comments-list.css @@ -0,0 +1,118 @@ +.seafile-comment { + border-left: 1px solid #e6e6dd; + display: flex; + flex-direction: column; +} +.seafile-comment-title { + border-bottom: 1px solid #e5e5e5; + min-height: 3em; + line-height: 3em; + padding: 0 1em; + display: flex; + background-color: #fafaf9; +} +.seafile-comment-title .seafile-comment-title-text { + width: 100%; + text-align: center; + font-weight: 700; +} +.seafile-comment-title .seafile-comment-title-close { + color: #b9b9b9; +} +.seafile-comment-title .seafile-comment-title-close:hover { + color: #888; +} +.seafile-comment-toggle-resolved { + margin-top: 45px; + border-bottom: 1px solid #e5e5e5; + padding: 5px 10px; + display: flex; + position: absolute; + background-color: #fff; + justify-content: space-between; + width: 29%; +} +.seafile-comment-list { + height: calc(100% - 40px); + margin-top: 30px; + overflow-y: auto; + margin-bottom: 0; +} +.seafile-comment-list .comment-vacant { + padding: 1em; + text-align: center; +} +.seafile-comment-item { + overflow-y: hidden; + padding: 15px 10px; + margin-bottom: 0; +} +.seafile-comment-item .seafile-comment-info { + padding-bottom: 0.5em; + height: 3em; + display: flex; + justify-content: flex-start; +} +.seafile-comment-item .seafile-comment-info .reviewer-info { + padding-left: 10px; +} +.seafile-comment-item .seafile-comment-info .review-time { + font-size: 10px; + color: #777; +} +.seafile-comment-item .seafile-comment-info .seafile-comment-dropdown { + margin-left: auto; +} +.seafile-comment-item .seafile-comment-info .seafile-comment-dropdown button { + border: none; + box-shadow: none; + background-color: #fff; +} +.seafile-comment-item .seafile-comment-info .seafile-comment-dropdown .seafile-comment-dropdown-btn { + color: #999; + background-color: transparent; +} +.seafile-comment-item .seafile-comment-info .seafile-comment-dropdown:hover .seafile-comment-dropdown-btn { + color: #555; +} +.seafile-comment-item .seafile-comment-info .seafile-comment-dropdown button:hover, +.seafile-comment-item .seafile-comment-info .seafile-comment-dropdown button:focus { + border: none; + box-shadow: none; + background-color: #eee; +} +.seafile-comment-item .seafile-comment-content { + margin-left: 42px; +} +.seafile-comment-item .seafile-comment-content ol, +.seafile-comment-item .seafile-comment-content ul, +.seafile-comment-item .seafile-comment-content li { + margin-left: 10px; +} +.seafile-comment-item .seafile-comment-content table, +.seafile-comment-item .seafile-comment-content th, +.seafile-comment-item .seafile-comment-content td { + border: 1px solid #333; +} +.seafile-comment-item-resolved { + background-color: #e6ffed; +} +.seafile-comment-footer { + background-color: #fafaf9; + padding: 10px 10px 0; + border-top: 1px solid #e5e5e5; + display: flex; + flex-direction: column; + min-height: 150px; +} +.seafile-comment-footer .add-comment-input { + border: 1px solid #e6e6dd; + padding: 5px; + width: 23em; + min-height: 90px; +} +.seafile-comment-footer .submit-comment { + margin-top: 5px; + width: 60px; + height: 28px; +} diff --git a/frontend/src/css/view-file-text.css b/frontend/src/css/view-file-text.css index fc860dee6d..000c7a4992 100644 --- a/frontend/src/css/view-file-text.css +++ b/frontend/src/css/view-file-text.css @@ -80,4 +80,20 @@ .file-internal-link { color: #585858; -} \ No newline at end of file +} + +.txt-file-view-body .txt-view-comment { + background: #fff; + display: flex; + height: 100%; +} + +.txt-file-view-body .txt-view-comment .ReactCodeMirror { + width: 70%; + margin: 5px 20px; + overflow-y: auto; +} + +.txt-file-view-body .txt-view-comment .seafile-comment { + width: 30%; +} diff --git a/frontend/src/view-file-text.js b/frontend/src/view-file-text.js index e0de470eed..97f5e44b1d 100644 --- a/frontend/src/view-file-text.js +++ b/frontend/src/view-file-text.js @@ -9,6 +9,7 @@ import { seafileAPI } from './utils/seafile-api'; import { Utils } from './utils/utils'; import { serviceURL, gettext, mediaUrl } from './utils/constants'; import InternalLinkDialog from './components/dialog/internal-link-dialog'; +import CommentsList from './components/comments-list'; import 'codemirror/lib/codemirror.css'; import 'codemirror/mode/javascript/javascript'; import 'codemirror/mode/css/css'; @@ -87,6 +88,9 @@ class ViewFileText extends React.Component { case 'download': window.location.href = serviceURL + '/lib/' + repoID + '/file/' + filePath +'?dl=1'; break; + case 'comment': + this.toggleCommentsList(); + break; } } @@ -128,19 +132,23 @@ class ViewFileText extends React.Component { onMouseDown={() => this.handleMouseDown('download')} icon={'fa fa-download'} /> - {/* - this.handleMouseDown('comment')} - icon={'sf-btn-group-btn op-icon sf2-icon-msgs sf-btn-group-btn-last'} - /> - */} + this.handleMouseDown('comment')} + icon={'fa fa-comment'} + /> ); } + toggleCommentsList = () => { + this.setState({ + showCommentsList: !this.state.showCommentsList + }); + } + toggleStar = () => { if (this.state.star) { seafileAPI.unStarFile(repoID, filePath).then((res) => { @@ -190,13 +198,16 @@ class ViewFileText extends React.Component { {this.renderToolbar()}
    - {this.fileEncode()} -
    + {!this.state.showCommentsList && this.fileEncode()} +
    + { this.state.showCommentsList && + + }