1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-12 21:30:39 +00:00

Change participants UI and API (#3889)

* change tooltip ID

* change dialog API

* change mention style

* change comment panel API

change detail panel API

* change Tooltip

* change mention color

update seafile-api

change comment content click
This commit is contained in:
Michael An
2019-07-26 16:51:47 +08:00
committed by Daniel Pan
parent f7d226bf95
commit ec87c48002
7 changed files with 133 additions and 97 deletions

View File

@@ -16272,9 +16272,9 @@
}
},
"seafile-js": {
"version": "0.2.108",
"resolved": "https://registry.npmjs.org/seafile-js/-/seafile-js-0.2.108.tgz",
"integrity": "sha512-uGBqFfGxywsf4z+I4UX8Prz4xJe46Gvy/aCRed3751B0fCUG3+OSiKtJXnZH9TN3lOVp4sJpm7MkER1Bf1ln9A==",
"version": "0.2.109",
"resolved": "https://registry.npmjs.org/seafile-js/-/seafile-js-0.2.109.tgz",
"integrity": "sha512-qcFKdC8hA1G3Mbe4PpJ/ircBD9Miaduur2qIPkjyYIzJ8MvownGxku+r13UsNh6fLp3oCs2GqzuY4UDO5y1gcw==",
"requires": {
"axios": "^0.18.0",
"form-data": "^2.3.2",

View File

@@ -38,7 +38,7 @@
"react-responsive": "^6.1.2",
"react-select": "^2.4.1",
"reactstrap": "^6.4.0",
"seafile-js": "^0.2.108",
"seafile-js": "^0.2.109",
"socket.io-client": "^2.2.0",
"sw-precache-webpack-plugin": "0.11.4",
"unified": "^7.0.0",

View File

@@ -89,14 +89,12 @@ class FileParticipantDialog extends Component {
if (!selectedOption || selectedOption.length === 0) {
return;
}
for (let i = 0; i < selectedOption.length; i++) {
seafileAPI.addFileParticipant(repoID, filePath, selectedOption[i].email).then((res) => {
let emails = selectedOption.map((option) => {return option.email;});
seafileAPI.addFileParticipants(repoID, filePath, emails).then((res) => {
this.props.onParticipantsChange(repoID, filePath);
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
toaster.danger(Utils.getErrorMsg(error));
});
}
this.setState({ selectedOption: null });
this.refs.userSelect.clearSelect();
};

View File

@@ -28,6 +28,7 @@ class DetailCommentList extends React.Component {
relatedUsers: null,
comment: '',
};
this.toBeAddedParticipant = [];
}
componentDidMount() {
@@ -55,21 +56,33 @@ class DetailCommentList extends React.Component {
});
}
submitComment = () => {
let comment = this.state.comment;
addComment = () => {
const { repoID, filePath } = this.props;
if (comment.trim()) {
seafileAPI.postComment(repoID, filePath, comment.trim()).then(() => {
if (!this.state.comment.trim()) return;
seafileAPI.postComment(repoID, filePath, this.state.comment.trim()).then(() => {
this.listComments();
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
}).catch(err => {
toaster.danger(Utils.getErrorMsg(err));
});
this.addParticipant(username);
}
this.setState({ comment: '' });
}
onSubmit = () => {
this.addParticipant(username);
if (this.toBeAddedParticipant.length === 0) {
this.addComment();
} else {
const { repoID, filePath } = this.props;
seafileAPI.addFileParticipants(repoID, filePath, this.toBeAddedParticipant).then((res) => {
this.props.onParticipantsChange(repoID, filePath);
this.toBeAddedParticipant = [];
this.addComment();
}).catch((err) => {
toaster.danger(Utils.getErrorMsg(err));
});
}
}
resolveComment = (event) => {
const { repoID } = this.props;
seafileAPI.updateComment(repoID, event.target.id, 'true').then(() => {
@@ -104,7 +117,7 @@ class DetailCommentList extends React.Component {
const { repoID } = this.props;
seafileAPI.listRepoRelatedUsers(repoID).then((res) => {
let users = res.data.user_list.map((item) => {
return { id: (siteRoot + 'profile/' + item.email), display: item.name};
return { id: item.email, display: item.name};
});
this.setState({ relatedUsers: users });
});
@@ -116,23 +129,13 @@ class DetailCommentList extends React.Component {
addParticipant = (email) => {
if (this.checkParticipant(email)) return;
const { repoID, filePath } = this.props;
seafileAPI.addFileParticipant(repoID, filePath, email).then((res) => {
this.props.onParticipantsChange(repoID, filePath);
}).catch((err) => {
toaster.danger(Utils.getErrorMsg(err));
});
this.toBeAddedParticipant.push(email);
}
renderUserSuggestion = (entry, search, highlightedDisplay, index, focused) => {
return <div className={`user ${focused ? 'focused' : ''}`}>{highlightedDisplay}</div>;
}
handleMention = (id, display) => {
const email = id.slice(id.lastIndexOf('/') + 1);
this.addParticipant(email);
}
render() {
const { repoID, filePath, fileParticipantList } = this.props;
return (
@@ -173,13 +176,14 @@ class DetailCommentList extends React.Component {
>
<Mention
trigger="@"
displayTransform={(username, display) => `@${display}`}
data={this.state.relatedUsers}
renderSuggestion={this.renderUserSuggestion}
style={defaultMentionStyle}
onAdd={this.handleMention}
onAdd={(id, display) => {this.addParticipant(id);}}
/>
</MentionsInput>
<Button className="submit-comment" color="primary" size="sm" onClick={this.submitComment}>{gettext('Submit')}</Button>
<Button className="submit-comment" color="primary" size="sm" onClick={this.onSubmit}>{gettext('Submit')}</Button>
</div>
</div>
);
@@ -243,6 +247,15 @@ class CommentItem extends React.Component {
this.setState({ newComment: event.target.value });
}
onCommentClick = (e) => {
// click participant link, page shouldn't jump
if (e.target.nodeName !== 'A') return;
const preNode = e.target.previousSibling;
if (preNode && preNode.nodeType === 3 && preNode.nodeValue.slice(-1) === '@') {
e.preventDefault();
}
}
renderInfo = (item) => {
return (
<Fragment>
@@ -293,7 +306,11 @@ class CommentItem extends React.Component {
</DropdownMenu>
</Dropdown>
</div>
<div className="seafile-comment-content" dangerouslySetInnerHTML={{ __html: this.state.html }}></div>
<div
className="seafile-comment-content"
dangerouslySetInnerHTML={{ __html: this.state.html }}
onClick={e => this.onCommentClick(e)}
></div>
</li>
);
}

View File

@@ -8,7 +8,6 @@ import { seafileAPI } from '../../utils/seafile-api';
import ParticipantsList from './participants-list';
import { Utils } from '../../utils/utils';
import toaster from '../toast';
import { siteRoot } from '../../utils/constants';
import { MentionsInput, Mention } from 'react-mentions';
import { defaultStyle, defaultMentionStyle } from '../../css/react-mentions-default-style';
import '../../css/comments-list.css';
@@ -33,6 +32,7 @@ class CommentPanel extends React.Component {
relatedUsers: null,
comment: '',
};
this.toBeAddedParticipant = [];
}
toggleResolvedComment = () => {
@@ -53,7 +53,7 @@ class CommentPanel extends React.Component {
listRepoRelatedUsers = () => {
seafileAPI.listRepoRelatedUsers(repoID).then((res) => {
let users = res.data.user_list.map((item) => {
return { id: (siteRoot + 'profile/' + item.email), display: item.name};
return { id: item.email, display: item.name};
});
this.setState({ relatedUsers: users });
});
@@ -63,20 +63,31 @@ class CommentPanel extends React.Component {
this.setState({ comment: event.target.value });
}
submitComment = () => {
let comment = this.state.comment;
if (comment.trim()) {
seafileAPI.postComment(repoID, filePath, comment).then(() => {
addComment = () => {
if (!this.state.comment.trim()) return;
seafileAPI.postComment(repoID, filePath, this.state.comment.trim()).then(() => {
this.listComments();
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
}).catch(err => {
toaster.danger(Utils.getErrorMsg(err));
});
this.addParticipant(username);
}
this.setState({ comment: '' });
}
onSubmit = () => {
this.addParticipant(username);
if (this.toBeAddedParticipant.length === 0) {
this.addComment();
} else {
seafileAPI.addFileParticipants(repoID, filePath, this.toBeAddedParticipant).then((res) => {
this.onParticipantsChange(repoID, filePath);
this.toBeAddedParticipant = [];
this.addComment();
}).catch((err) => {
toaster.danger(Utils.getErrorMsg(err));
});
}
}
resolveComment = (event) => {
seafileAPI.updateComment(repoID, event.target.id, 'true').then(() => {
this.listComments();
@@ -128,23 +139,13 @@ class CommentPanel extends React.Component {
addParticipant = (email) => {
if (this.checkParticipant(email)) return;
seafileAPI.addFileParticipant(repoID, filePath, email).then((res) => {
this.onParticipantsChange(repoID, filePath);
}).catch((err) => {
let errMessage = Utils.getErrorMsg(err);
toaster.danger(errMessage);
});
this.toBeAddedParticipant.push(email);
}
renderUserSuggestion = (entry, search, highlightedDisplay, index, focused) => {
return <div className={`user ${focused ? 'focused' : ''}`}>{highlightedDisplay}</div>;
}
handleMention = (id, display) => {
const email = id.slice(id.lastIndexOf('/') + 1);
this.addParticipant(email);
}
componentDidMount() {
this.listComments();
this.getParticipants();
@@ -221,15 +222,14 @@ class CommentPanel extends React.Component {
>
<Mention
trigger="@"
displayTransform={(username, display) => `@${display}`}
data={this.state.relatedUsers}
renderSuggestion={this.renderUserSuggestion}
style={defaultMentionStyle}
onAdd={this.handleMention}
onAdd={(id, display) => {this.addParticipant(id);}}
/>
</MentionsInput>
<Button
className="submit-comment" color="primary"
size="sm" onClick={this.submitComment} >
<Button className="submit-comment" color="primary" size="sm" onClick={this.onSubmit}>
{gettext('Submit')}</Button>
</div>
</div>
@@ -268,15 +268,11 @@ class CommentItem extends React.Component {
}
convertComment = (mdFile) => {
processor.process(mdFile).then(
(result) => {
processor.process(mdFile).then((result) => {
let html = String(result);
this.setState({
html: html
this.setState({ html: html });
});
}
);
}
toggleEditComment = () => {
this.setState({
@@ -298,6 +294,15 @@ class CommentItem extends React.Component {
});
}
onCommentClick = (e) => {
// click participant link, page shouldn't jump
if (e.target.nodeName !== 'A') return;
const preNode = e.target.previousSibling;
if (preNode && preNode.nodeType === 3 && preNode.nodeValue.slice(-1) === '@') {
e.preventDefault();
}
}
componentWillMount() {
this.convertComment(this.props.item.comment);
}
@@ -324,7 +329,7 @@ class CommentItem extends React.Component {
<div className="seafile-edit-comment">
<textarea className="edit-comment-input" value={this.state.newComment} onChange={this.handleCommentChange} clos="100" rows="3" warp="virtual"></textarea>
<Button className="comment-btn" color="success" size="sm" onClick={this.updateComment} id={item.id}>{gettext('Update')}</Button>{' '}
<Button className="comment-btn" color="secondary" size="sm" onClick={this.toggleEditComment}> {gettext('Cancel')}</Button>
<Button className="comment-btn" color="secondary" size="sm" onClick={this.toggleEditComment}>{gettext('Cancel')}</Button>
</div>
</li>
);
@@ -344,24 +349,25 @@ class CommentItem extends React.Component {
<i className="fas fa-ellipsis-v"></i>
</DropdownToggle>
<DropdownMenu>
{
(item.user_email === username) &&
{(item.user_email === username) &&
<DropdownItem onClick={this.props.deleteComment} className="delete-comment"
id={item.id}>{gettext('Delete')}</DropdownItem>}
{
(item.user_email === username) &&
{(item.user_email === username) &&
<DropdownItem onClick={this.toggleEditComment}
className="edit-comment" id={item.id}>{gettext('Edit')}</DropdownItem>
}
{
!item.resolved &&
{!item.resolved &&
<DropdownItem onClick={this.props.resolveComment} className="seafile-comment-resolved"
id={item.id}>{gettext('Mark as resolved')}</DropdownItem>
}
</DropdownMenu>
</Dropdown>
</div>
<div className="seafile-comment-content" dangerouslySetInnerHTML={{ __html: this.state.html }}></div>
<div
className="seafile-comment-content"
dangerouslySetInnerHTML={{ __html: this.state.html }}
onClick={e => this.onCommentClick(e)}
></div>
</li>
);
}

View File

@@ -22,6 +22,7 @@ class ParticipantsList extends React.Component {
showDialog : false,
tooltipOpen: false,
};
this.targetID = 'add-participant-icon';
}
toggleDialog = () => {
@@ -32,6 +33,10 @@ class ParticipantsList extends React.Component {
this.setState({ tooltipOpen: !this.state.tooltipOpen });
}
componentWillMount() {
this.targetID = this.targetID + Math.floor(Math.random() * 1000);
}
render() {
const { participants, repoID, filePath, showIconTip } = this.props;
return (
@@ -39,11 +44,11 @@ class ParticipantsList extends React.Component {
{participants.map((item, index) => {
return <Participant item={item} index={index} key={index}/>;
})}
<span className="add-participants" onClick={this.toggleDialog} id="add-participant-icon">
<span className="add-participants" onClick={this.toggleDialog} id={this.targetID}>
<i className="fas fa-plus-circle"></i>
</span>
{showIconTip &&
<Tooltip toggle={this.tooltipToggle} delay={{show: 0, hide: 0}} target="add-participant-icon" placement='bottom' isOpen={this.state.tooltipOpen}>
<Tooltip toggle={this.tooltipToggle} delay={{show: 0, hide: 0}} target={this.targetID} placement='bottom' isOpen={this.state.tooltipOpen}>
{gettext('Add participants')}
</Tooltip>
}
@@ -77,19 +82,23 @@ class Participant extends React.Component {
this.state = {
showAvatarTooltip: false,
};
this.targetID = 'participant-avatar-';
}
toggleAvatarTooltip = () => {
this.setState({ showAvatarTooltip: !this.state.showAvatarTooltip });
}
componentWillMount() {
this.targetID = this.targetID + this.props.index + Math.floor(Math.random() * 1000);
}
render() {
const { item, index } = this.props;
const target = 'participant-avatar-' + index;
return (
<span className="participant-avatar">
<img src={item.avatar_url} className="avatar" id={target} alt="avatar" key={index}/>
<Tooltip toggle={this.toggleAvatarTooltip} delay={{show: 0, hide: 0}} target={target} placement='bottom' isOpen={this.state.showAvatarTooltip}>
<img src={item.avatar_url} className="avatar" id={this.targetID} alt="avatar" key={index}/>
<Tooltip toggle={this.toggleAvatarTooltip} delay={{show: 0, hide: 0}} target={this.targetID} placement='bottom' isOpen={this.state.showAvatarTooltip}>
{item.name}
</Tooltip>
</span>

View File

@@ -26,20 +26,24 @@ const defaultStyle = {
},
'&multiLine': {
control: {
fontFamily: 'monospace',
border: '1px solid silver',
},
highlighter: {
padding: 9,
},
input: {
padding: 5,
padding: '8px 6px',
minHeight: 90,
height: 90,
outline: 0,
border: '1px solid #e6e6dd',
backgroundColor: '#fff',
overfflowY: 'auto'
borderRadius: '5px',
overfflowY: 'auto',
'&focused': {
backgroundColor: '#cee4e5',
outlineOffset: '-2px',
outlineColor: '-webkit-focus-ring-color',
outlineStyle: 'auto',
outlineWidth: '5px',
},
},
},
suggestions: {
@@ -54,16 +58,18 @@ const defaultStyle = {
},
item: {
padding: '5px 15px',
minWidth: '50px',
borderBottom: '1px solid rgba(0,0,0,0.15)',
'&focused': {
backgroundColor: '#cee4e5',
backgroundColor: '#f19654',
color: '#fff',
fontWeight: '700',
},
},
},
};
const defaultMentionStyle = {
backgroundColor: '#cee4e5'
};
export { defaultStyle, defaultMentionStyle };