[mobile] Made receiving and displaying comments into collaboration

This commit is contained in:
JuliaSvinareva 2021-02-25 19:26:42 +03:00
parent 060e51b47a
commit c3dd451d7a
10 changed files with 480 additions and 18 deletions

View file

@ -10,13 +10,7 @@ class CollaborationController extends Component {
// this.api = api;
api.asc_registerCallback('asc_onAuthParticipantsChanged', this.onChangeEditUsers.bind(this));
api.asc_registerCallback('asc_onParticipantsChanged', this.onChangeEditUsers.bind(this));
// this.api.asc_registerCallback('asc_onAddComment', _.bind(this.onApiAddComment, this));
// this.api.asc_registerCallback('asc_onAddComments', _.bind(this.onApiAddComments, this));
// this.api.asc_registerCallback('asc_onChangeCommentData', _.bind(this.onApiChangeCommentData, this));
// this.api.asc_registerCallback('asc_onRemoveComment', _.bind(this.onApiRemoveComment, this));
// this.api.asc_registerCallback('asc_onRemoveComments', _.bind(this.onApiRemoveComments, this));
// this.api.asc_registerCallback('asc_onShowComment', _.bind(this.apiShowComments, this));
// this.api.asc_registerCallback('asc_onHideComment', _.bind(this.apiHideComments, this));
api.asc_registerCallback('asc_onConnectionStateChanged', this.onUserConnection.bind(this));
});
}
@ -24,7 +18,11 @@ class CollaborationController extends Component {
const storeUsers = this.props.users;
storeUsers.reset(users);
storeUsers.setCurrentUser(this.props.storeAppOptions.user.id);
};
}
onUserConnection(change) {
this.props.users.connection(change);
}
render() {
return null

View file

@ -4,7 +4,7 @@ import { f7 } from 'framework7-react';
import {Device} from '../../../../../common/mobile/utils/device';
import { withTranslation} from 'react-i18next';
import {AddComment} from '../../view/collaboration/Comments';
import {AddComment, ViewComments} from '../../view/collaboration/Comments';
// utils
const timeZoneOffsetInMs = (new Date()).getTimezoneOffset() * 60000;
@ -18,10 +18,191 @@ const ooDateToString = (date) => {
return (date.getTime()).toString();
return '';
};
const stringOOToLocalDate = (date) => {
if (typeof date === 'string')
return parseInt(date);
return 0;
};
const stringUtcToLocalDate = (date) => {
if (typeof date === 'string')
return parseInt(date) + timeZoneOffsetInMs;
return 0;
};
const dateToLocaleTimeString = (date) => {
const format = (date) => {
let hours = date.getHours();
let minutes = date.getMinutes();
let ampm = hours >= 12 ? 'pm' : 'am';
hours = hours % 12;
hours = hours ? hours : 12; // the hour '0' should be '12'
minutes = minutes < 10 ? '0' + minutes : minutes;
return hours + ':' + minutes + ' ' + ampm;
};
// MM/dd/yyyy hh:mm AM
return (date.getMonth() + 1) + '/' + (date.getDate()) + '/' + date.getFullYear() + ' ' + format(date);
};
//end utils
@inject('storeComments', 'users')
@observer
class CommentsController extends Component {
constructor(props) {
super(props);
this.usersStore = this.props.users;
this.appOptions = this.props.storeAppOptions;
this.storeComments = this.props.storeComments;
Common.Notifications.on('engineCreated', api => {
api.asc_registerCallback('asc_onAddComment', this.addComment.bind(this));
api.asc_registerCallback('asc_onAddComments', this.addComments.bind(this));
api.asc_registerCallback('asc_onRemoveComment', this.removeComment.bind(this));
api.asc_registerCallback('asc_onRemoveComments', this.removeComments.bind(this));
api.asc_registerCallback('asc_onChangeCommentData', this.changeCommentData.bind(this));
});
Common.Notifications.on('comments:filterchange', this.onFilterChange.bind(this)); // for sse
Common.Notifications.on('configOptionsFill', () => {
this.curUserId = this.appOptions.user.id;
});
}
addComment (id, data) {
const comment = this.readSDKComment(id, data);
if (comment) {
this.storeComments.addComment(comment);
}
}
addComments (data) {
for (let i = 0; i < data.length; ++i) {
const comment = this.readSDKComment(data[i].asc_getId(), data[i]);
this.storeComments.addComment(comment);
}
}
removeComment (id) {
this.storeComments.removeComment(id);
}
removeComments (data) {
for (let i = 0; i < data.length; i++) {
this.removeComment(data[i]);
}
}
changeCommentData (id, data) {
let date = null;
let replies = null;
let repliesCount = 0;
let dateReply = null;
const comment = this.storeComments.findComment(id);
if (comment) {
date = (data.asc_getOnlyOfficeTime()) ? new Date(stringOOToLocalDate(data.asc_getOnlyOfficeTime())) :
((data.asc_getTime() === '') ? new Date() : new Date(stringUtcToLocalDate(data.asc_getTime())));
let user = this.usersStore.searchUserById(data.asc_getUserId());
comment.comment = data.asc_getText();
comment.userid = data.asc_getUserId();
comment.userName = data.asc_getUserName();
comment.usercolor = (user) ? user.asc_getColor() : null;
comment.resolved = data.asc_getSolved();
comment.quote = data.asc_getQuoteText();
comment.time = date.getTime();
comment.date = dateToLocaleTimeString(date);
comment.editable = this.appOptions.canEditComments || (data.asc_getUserId() === this.curUserId);
comment.removable = this.appOptions.canDeleteComments || (data.asc_getUserId() === this.curUserId);
replies = [];
repliesCount = data.asc_getRepliesCount();
for (let i = 0; i < repliesCount; ++i) {
dateReply = (data.asc_getReply(i).asc_getOnlyOfficeTime()) ? new Date(stringOOToLocalDate(data.asc_getReply(i).asc_getOnlyOfficeTime())) :
((data.asc_getReply(i).asc_getTime() === '') ? new Date() : new Date(stringUtcToLocalDate(data.asc_getReply(i).asc_getTime())));
user = this.usersStore.searchUserById(data.asc_getReply(i).asc_getUserId());
const userName = data.asc_getReply(i).asc_getUserName();
replies.push({
ind: i,
userId: data.asc_getReply(i).asc_getUserId(),
userName: userName,
userColor: (user) ? user.asc_getColor() : null,
date: dateToLocaleTimeString(dateReply),
reply: data.asc_getReply(i).asc_getText(),
time: dateReply.getTime(),
userInitials: this.usersStore.getInitials(userName),
editable: this.appOptions.canEditComments || (data.asc_getReply(i).asc_getUserId() === this.curUserId),
removable: this.appOptions.canDeleteComments || (data.asc_getReply(i).asc_getUserId() === this.curUserId)
});
}
comment.replies = replies;
}
}
onFilterChange (filter) {
this.storeComments.changeFilter(filter);
}
readSDKComment (id, data) {
const date = (data.asc_getOnlyOfficeTime()) ? new Date(stringOOToLocalDate(data.asc_getOnlyOfficeTime())) :
((data.asc_getTime() === '') ? new Date() : new Date(stringUtcToLocalDate(data.asc_getTime())));
const user = this.usersStore.searchUserById(data.asc_getUserId());
const groupName = id.substr(0, id.lastIndexOf('_')+1).match(/^(doc|sheet[0-9_]+)_/);
const userName = data.asc_getUserName();
const comment = {
uid : id,
userId : data.asc_getUserId(),
userName : userName,
userColor : (user) ? user.asc_getColor() : null,
date : dateToLocaleTimeString(date),
quote : data.asc_getQuoteText(),
comment : data.asc_getText(),
resolved : data.asc_getSolved(),
unattached : !!data.asc_getDocumentFlag ? data.asc_getDocumentFlag() : false,
time : date.getTime(),
replies : [],
groupName : (groupName && groupName.length>1) ? groupName[1] : null,
userInitials : this.usersStore.getInitials(userName),
editable : this.appOptions.canEditComments || (data.asc_getUserId() === this.curUserId),
removable : this.appOptions.canDeleteComments || (data.asc_getUserId() === this.curUserId)
};
if (comment) {
const replies = this.readSDKReplies(data);
if (replies.length > 0) {
comment.replies = replies;
}
}
return comment;
}
readSDKReplies (data) {
const replies = [];
const repliesCount = data.asc_getRepliesCount();
let i = 0;
let date = null;
if (repliesCount) {
for (i = 0; i < repliesCount; ++i) {
date = (data.asc_getReply(i).asc_getOnlyOfficeTime()) ? new Date(stringOOToLocalDate(data.asc_getReply(i).asc_getOnlyOfficeTime())) :
((data.asc_getReply(i).asc_getTime() === '') ? new Date() : new Date(stringUtcToLocalDate(data.asc_getReply(i).asc_getTime())));
const user = this.usersStore.searchUserById(data.asc_getReply(i).asc_getUserId());
const userName = data.asc_getReply(i).asc_getUserName();
replies.push({
ind : i,
userId : data.asc_getReply(i).asc_getUserId(),
userName : userName,
userColor : (user) ? user.asc_getColor() : null,
date : dateToLocaleTimeString(date),
reply : data.asc_getReply(i).asc_getText(),
time : date.getTime(),
userInitials : this.usersStore.getInitials(userName),
editable : this.appOptions.canEditComments || (data.asc_getReply(i).asc_getUserId() === this.curUserId),
removable : this.appOptions.canDeleteComments || (data.asc_getReply(i).asc_getUserId() === this.curUserId)
});
}
}
return replies;
}
render() {
return null;
}
}
class AddCommentController extends Component {
constructor(props) {
super(props);
@ -73,4 +254,22 @@ class AddCommentController extends Component {
}
}
export {AddCommentController};
class ViewCommentsController extends Component {
constructor(props) {
super(props);
}
render() {
return(
<ViewComments />
)
}
}
const _CommentsController = inject('storeAppOptions', 'storeComments', 'users')(observer(CommentsController));
const _AddCommentController = inject('storeAppOptions', 'storeComments', 'users')(observer(AddCommentController));
export {
_CommentsController as CommentsController,
_AddCommentController as AddCommentController,
ViewCommentsController
};

View file

@ -1,7 +1,108 @@
import {observable, action} from 'mobx';
import {observable, action, computed} from 'mobx';
export class storeComments {
@observable collectionComments = [];
@observable groupCollectionComments = [];
@action addComment (comment) {
comment.groupName ? this.addCommentToGroupCollection(comment) : this.addCommentToCollection(comment);
}
addCommentToCollection (comment) {
this.collectionComments.push(comment);
}
addCommentToGroupCollection (comment) {
const groupName = comment.groupName;
if (!this.groupCollectionComments[groupName]) {
this.groupCollectionComments[groupName] = [];
}
this.groupCollectionComments[groupname].push(comment);
if (this.filter.indexOf(groupname) !== -1) {
this.groupCollectionFilter.push(comment);
}
}
@action removeComment (id) {
if (this.collectionComments.length > 0) {
this.removeCommentFromCollection(id);
} else {
this.removeCommentFromGroups(id);
}
}
removeCommentFromCollection (id) {
const index = this.collectionComments.findIndex((comment) => {
return comment.uid === id;
});
if (index !== -1) {
this.collectionComments.splice(index, 1);
}
}
removeCommentFromGroups (id) {
for (let name in this.groupCollectionComments) {
const store = this.groupCollectionComments[name];
const comment = store.find((item) => {
return item.uid === id;
});
const index = store.indexOf(comment);
if (index !== -1) {
this.groupCollectionComments[name].splice(index, 1);
if (this.filter.indexOf(name) !== -1) {
this.groupCollectionFilter.splice(this.groupCollectionFilter.indexOf(comment), 1);
}
}
}
}
@observable filter; // for sse
@observable groupCollectionFilter = []; // for sse
@action changeFilter (filter) {
let comments = [];
this.filter = filter;
filter.forEach((item) => {
if (!this.groupCollectionComments[item])
this.groupCollectionComments[item] = [];
comments = comments.concat(this.groupCollectionComments[item]);
});
this.groupCollectionFilter = comments;
}
findComment (id) {
let comment = this.collectionComments.find((item) => {
return item.uid === id;
});
if (!comment) {
comment = this.findCommentInGroup(id);
}
return comment;
}
findCommentInGroup (id) {
let model;
for (let name in this.groupCollectionComments) {
const store = this.groupCollectionComments[name];
const id = id.isArray() ? id[0] : id;
model = store.find((item) => {
return item.uid === id;
});
if (model) return model;
}
return model;
}
@computed get sortComments () {
const comments = (this.groupCollectionFilter.length !== 0) ? this.groupCollectionFilter : (this.collectionComments.length !== 0 ? this.collectionComments : false);
if (comments.length > 0) {
return [...comments].sort((a, b) => a.time > b.time ? 1 : -1);
}
return false;
}
// Add comment modal window
@observable isOpenAddComment = false;
@action openAddComment (open) {

View file

@ -18,6 +18,20 @@ export class storeUsers {
});
}
@action connection (change) {
let changed = false;
for (let uid in this.users) {
if (undefined !== uid) {
const user = this.users[uid];
if (user && user.asc_getId() === change.asc_getId()) {
this.users[uid] = change;
changed = true;
}
}
}
!changed && change && (this.users[change.asc_getId()] = change);
}
getInitials (name) {
const fio = Common.Utils.UserInfoParser.getParsedName(name).split(' ');
let initials = fio[0].substring(0, 1).toUpperCase();
@ -29,4 +43,12 @@ export class storeUsers {
}
return initials;
}
searchUserById (id) {
this.users.forEach((item) => {
if (item.asc_getIdOriginal() === id) {
return item;
}
});
}
}

View file

@ -8,6 +8,8 @@ import {Device} from "../../../utils/device";
import {ReviewController, ReviewChangeController} from "../../controller/collaboration/Review";
import {PageDisplayMode} from "./Review";
import {ViewCommentsController} from "../../controller/collaboration/Comments";
const PageUsers = inject("users")(observer(props => {
const { t } = useTranslation();
const _t = t('Common.Collaboration', {returnObjects: true});
@ -44,6 +46,10 @@ const routes = [
{
path: '/review-change/',
component: ReviewChangeController
},
{
path: '/comments/',
component: ViewCommentsController
}
];
@ -66,7 +72,7 @@ const PageCollaboration = props => {
<ListItem link={'/users/'} title={_t.textUsers}>
<Icon slot="media" icon="icon-users"></Icon>
</ListItem>
<ListItem link="#" title={_t.textComments}>
<ListItem link='/comments/' title={_t.textComments}>
<Icon slot="media" icon="icon-insert-comment"></Icon>
</ListItem>
{window.editorType === 'de' &&

View file

@ -1,9 +1,11 @@
import React, {useState, useEffect} from 'react';
import {observer, inject} from "mobx-react";
import { f7, Popup, Navbar, NavLeft, NavRight, NavTitle, Link, Input, Icon } from 'framework7-react';
import { f7, Popup, Page, Navbar, NavLeft, NavRight, NavTitle, Link, Input, Icon, List, ListItem } from 'framework7-react';
import { useTranslation } from 'react-i18next';
import {Device} from '../../../utils/device';
// Add comment
const AddCommentPopup = inject("storeComments")(observer(props => {
const { t } = useTranslation();
const _t = t('Common.Collaboration', {returnObjects: true});
@ -121,4 +123,67 @@ const AddComment = props => {
)
};
export {AddComment}
// View comments
const ViewComments = ({storeComments}) => {
const { t } = useTranslation();
const _t = t('Common.Collaboration', {returnObjects: true});
const comments = storeComments.sortComments;
return (
<Page>
<Navbar title={_t.textComments} backLink={_t.textBack}/>
{!comments ?
<div className='no-comments'>{_t.textNoComments}</div> :
<List className='comment-list'>
{comments.map((comment, indexComment) => {
return (
<ListItem key={`comment-${indexComment}`}>
<div slot='header'>
<div className='user-name'>{comment.userName}</div>
<div className='comment-date'>{comment.date}</div>
</div>
<div slot='footer'>
<div className='comment-quote'>{comment.quote}</div>
<div className='comment-text'><pre>{comment.comment}</pre></div>
{comment.replies.length > 0 &&
<ul className='list-reply'>
{comment.replies.map((reply, indexReply) => {
return (
<li key={`reply-${indexComment}-${indexReply}`}
className='reply-item'
>
<div className='item-content'>
<div className='item-inner'>
<div className='item-title'>
<div slot='header'>
<div className='user-name'>{reply.userName}</div>
<div className='reply-date'>{reply.date}</div>
</div>
<div slot='footer'>
<div className='reply-text'><pre>{reply.reply}</pre></div>
</div>
</div>
</div>
</div>
</li>
)
})}
</ul>
}
</div>
</ListItem>
)
})}
</List>
}
</Page>
)
};
const _ViewComments = inject('storeComments')(observer(ViewComments));
export {
AddComment,
_ViewComments as ViewComments
}

View file

@ -37,3 +37,69 @@
}
}
}
.comment-list {
.item-inner {
padding-right: 0;
padding-bottom: 0;
}
.item-title {
width: 100%;
}
.user-name {
font-size: 16px;
line-height: 22px;
color: @black;
margin: 0;
font-weight: 700;
}
.comment-date, .reply-date {
font-size: 12px;
line-height: 18px;
color: @comment-date;
margin: 0;
margin-top: 0;
}
.comment-quote {
color: @themeColor;
border-left: 1px solid @themeColor;
padding-left: 10px;
padding-right: 16px;
margin: 5px 0;
font-size: 14px;
}
.comment-text, .reply-text {
color: @black;
font-size: 14px;
line-height: 25px;
margin: 0;
max-width: 100%;
padding-right: 15px;
pre {
white-space: pre-wrap;
}
}
.list-reply {
padding-left: 26px;
}
.reply-item {
&:after {
content: none;
}
&:before {
content: '';
position: absolute;
left: auto;
bottom: 0;
right: auto;
top: 0;
height: 1px;
width: 100%;
background-color: @separator-color;
display: block;
z-index: 15;
-webkit-transform-origin: 50% 100%;
transform-origin: 50% 100%;
}
}
}

View file

@ -8,6 +8,9 @@
@background-normal: @white;
@autoColor: @black;
@comment-date: #6d6d72;
@separator-color: #c8c7cc;
.popup, .popover, .sheet-modal {
.list {
&:first-child {

View file

@ -115,7 +115,8 @@
"textParaMoveFromDown": "Moved Down:",
"textAddComment": "Add Comment",
"textCancel": "Cancel",
"textDone": "Done"
"textDone": "Done",
"textNoComments": "This document doesn't contain comments"
}
},
"Settings": {

View file

@ -6,7 +6,7 @@ import { withTranslation } from 'react-i18next';
import CollaborationController from '../../../../common/mobile/lib/controller/collaboration/Collaboration.jsx';
import {InitReviewController as ReviewController} from '../../../../common/mobile/lib/controller/collaboration/Review.jsx';
import { onAdvancedOptions } from './settings/Download.jsx';
import {AddCommentController} from "../../../../common/mobile/lib/controller/collaboration/Comments";
import {CommentsController, AddCommentController} from "../../../../common/mobile/lib/controller/collaboration/Comments";
@inject(
"storeAppOptions",
@ -333,6 +333,7 @@ class MainController extends Component {
<Fragment>
<CollaborationController />
<ReviewController />
<CommentsController />
<AddCommentController />
</Fragment>
)