Merge branch 'feature/mobile-apps-on-reactjs' of https://github.com/ONLYOFFICE/web-apps into feature/mobile-apps-on-reactjs

This commit is contained in:
SergeyEzhin 2021-03-02 14:39:50 +03:00
commit 42d8461f1c
11 changed files with 413 additions and 56 deletions

View file

@ -24,7 +24,7 @@ class ContextMenuController extends Component {
this.$targetEl = $$(idCntextMenuTargetElement);
if ( !this.$targetEl.length ) {
// this.$targetEl = $$('<div id="idx-context-menu-target" style="position:absolute;width:15px;height:15px;background-color:green;z-index:1;"></div>');
this.$targetEl = $$(`<div id="${idCntextMenuTargetElement} style="position:absolute;"></div>`);
this.$targetEl = $$(`<div id="${idCntextMenuTargetElement.substr(1)}" style="position:absolute;"></div>`);
this.$targetEl.css({left: '-10000px', top: '-10000px'});
$$('#editor_sdk').append(this.$targetEl);

View file

@ -1,10 +1,11 @@
import React, {Component} from 'react';
import React, {Component, Fragment} from 'react';
import { inject, observer } from "mobx-react";
import { f7 } from 'framework7-react';
import {Device} from '../../../../../common/mobile/utils/device';
import { withTranslation} from 'react-i18next';
import { LocalStorage } from '../../../utils/LocalStorage';
import {AddComment, ViewComments} from '../../view/collaboration/Comments';
import {AddComment, EditComment, ViewComments} from '../../view/collaboration/Comments';
// utils
const timeZoneOffsetInMs = (new Date()).getTimezoneOffset() * 60000;
@ -64,6 +65,17 @@ class CommentsController extends Component {
Common.Notifications.on('configOptionsFill', () => {
this.curUserId = this.appOptions.user.id;
});
Common.Notifications.on('document:ready', () => {
if (window.editorType === 'de' || window.editorType === 'sse') {
const api = Common.EditorApi.get();
/** coauthoring begin **/
const isLiveCommenting = LocalStorage.getBool(`${window.editorType}-mobile-settings-livecomment`, true);
const resolved = LocalStorage.getBool(`${window.editorType}-settings-resolvedcomment`, true);
isLiveCommenting ? api.asc_showComments(resolved) : api.asc_hideComments();
/** coauthoring end **/
}
});
}
addComment (id, data) {
const comment = this.readSDKComment(id, data);
@ -255,21 +267,210 @@ class AddCommentController extends Component {
}
class ViewCommentsController extends Component {
constructor(props) {
constructor (props) {
super(props);
this.onCommentMenuClick = this.onCommentMenuClick.bind(this);
this.onResolveComment = this.onResolveComment.bind(this);
this.onEditComment = this.onEditComment.bind(this);
this.closeEditComment = this.closeEditComment.bind(this);
this.currentUser = this.props.users.currentUser;
this.state = {
showEditComment: false,
showEditReply: false
};
}
onChangeComment (comment) {
const ascComment = !!Asc.asc_CCommentDataWord ? new Asc.asc_CCommentDataWord(null) : new Asc.asc_CCommentData(null);
if (ascComment && comment) {
ascComment.asc_putText(comment.comment);
ascComment.asc_putQuoteText(comment.quote);
ascComment.asc_putTime(utcDateToString(new Date(comment.time)));
ascComment.asc_putOnlyOfficeTime(ooDateToString(new Date(comment.time)));
ascComment.asc_putUserId(comment.userId);
ascComment.asc_putUserName(comment.userName);
ascComment.asc_putSolved(comment.resolved);
ascComment.asc_putGuid(comment.guid);
if (!!ascComment.asc_putDocumentFlag) {
ascComment.asc_putDocumentFlag(comment.unattached);
}
var reply = comment.replies;
if (reply && reply.length > 0) {
reply.forEach((reply) => {
var addReply = (!!Asc.asc_CCommentDataWord ? new Asc.asc_CCommentDataWord(null) : new Asc.asc_CCommentData(null));
if (addReply) {
addReply.asc_putText(reply.reply);
addReply.asc_putTime(utcDateToString(new Date(reply.time)));
addReply.asc_putOnlyOfficeTime(ooDateToString(new Date(reply.time)));
addReply.asc_putUserId(reply.userId);
addReply.asc_putUserName(reply.userName);
ascComment.asc_addReply(addReply);
}
});
}
const api = Common.EditorApi.get();
api.asc_changeComment(comment.uid, ascComment);
}
}
onResolveComment (comment) {
let reply = null,
addReply = null,
ascComment = (!!Asc.asc_CCommentDataWord ? new Asc.asc_CCommentDataWord(null) : new Asc.asc_CCommentData(null));
if (ascComment && comment) {
ascComment.asc_putText(comment.comment);
ascComment.asc_putQuoteText(comment.quote);
ascComment.asc_putTime(utcDateToString(new Date(comment.time)));
ascComment.asc_putOnlyOfficeTime(ooDateToString(new Date(comment.time)));
ascComment.asc_putUserId(comment.userId);
ascComment.asc_putUserName(comment.userName);
ascComment.asc_putSolved(!comment.resolved);
ascComment.asc_putGuid(comment.guid);
if (!!ascComment.asc_putDocumentFlag) {
ascComment.asc_putDocumentFlag(comment.unattached);
}
reply = comment.replies;
if (reply && reply.length > 0) {
reply.forEach((reply) => {
addReply = (!!Asc.asc_CCommentDataWord ? new Asc.asc_CCommentDataWord(null) : new Asc.asc_CCommentData(null));
if (addReply) {
addReply.asc_putText(reply.reply);
addReply.asc_putTime(utcDateToString(new Date(reply.time)));
addReply.asc_putOnlyOfficeTime(ooDateToString(new Date(reply.time)));
addReply.asc_putUserId(reply.userId);
addReply.asc_putUserName(reply.userName);
ascComment.asc_addReply(addReply);
}
});
}
const api = Common.EditorApi.get();
api.asc_changeComment(comment.uid, ascComment);
}
}
deleteComment (comment) {
const api = Common.EditorApi.get();
comment && api.asc_removeComment(comment.uid);
}
onEditComment (comment, text) {
comment.comment = text.trim();
comment.userid = this.currentUser.asc_getIdOriginal();
comment.username = this.currentUser.asc_getUserName();
this.onChangeComment(comment);
}
deleteReply (comment, indReply) {
let replies = null,
addReply = null,
ascComment = (!!Asc.asc_CCommentDataWord ? new Asc.asc_CCommentDataWord(null) : new Asc.asc_CCommentData(null));
if (ascComment && comment) {
ascComment.asc_putText(comment.comment);
ascComment.asc_putQuoteText(comment.quote);
ascComment.asc_putTime(utcDateToString(new Date(comment.time)));
ascComment.asc_putOnlyOfficeTime(ooDateToString(new Date(comment.time)));
ascComment.asc_putUserId(comment.userId);
ascComment.asc_putUserName(comment.userName);
ascComment.asc_putSolved(comment.resolved);
ascComment.asc_putGuid(comment.guid);
if (!!ascComment.asc_putDocumentFlag) {
ascComment.asc_putDocumentFlag(comment.unattached);
}
replies = comment.replies;
if (replies && replies.length) {
replies.forEach((reply) => {
if (reply.ind !== indReply) {
addReply = (!!Asc.asc_CCommentDataWord ? new Asc.asc_CCommentDataWord(null) : new Asc.asc_CCommentData(null));
if (addReply) {
addReply.asc_putText(reply.reply);
addReply.asc_putTime(utcDateToString(new Date(reply.time)));
addReply.asc_putOnlyOfficeTime(ooDateToString(new Date(reply.time)));
addReply.asc_putUserId(reply.userId);
addReply.asc_putUserName(reply.userName);
ascComment.asc_addReply(addReply);
}
}
});
}
const api = Common.EditorApi.get();
api.asc_changeComment(comment.uid, ascComment);
}
}
onCommentMenuClick (action, comment) {
const { t } = this.props;
const _t = t("Common.Collaboration", { returnObjects: true });
switch (action) {
case 'editComment':
this.setState({
showEditComment: true,
editProps: {
comment: comment,
onEditComment: this.onEditComment
}
});
console.log('editComment');
break;
case 'resolve':
this.onResolveComment(comment);
break;
case 'deleteComment':
f7.dialog.confirm(
_t.textMessageDeleteComment,
_t.textDeleteComment,
() => {
this.deleteComment(comment);
}
);
break;
case 'editReply':
this.setState({showEditReply: true});
console.log('editReply');
break;
case 'deleteReply':
f7.dialog.confirm(
_t.textMessageDeleteReply,
_t.textDeleteReply,
() => {
this.deleteReply(comment, indReply);
}
);
break;
case 'addReply':
console.log('addReply');
break;
}
}
closeEditComment () {
this.setState({showEditComment: false});
}
render() {
return(
<ViewComments />
<Fragment>
<ViewComments showEditComment={this.showEditComment}
onCommentMenuClick={this.onCommentMenuClick}
onResolveComment={this.onResolveComment}
/>
{this.state.showEditComment && <EditComment editProps={this.state.editProps} opened={this.state.showEditComment} close={this.closeEditComment}/>}
</Fragment>
)
}
}
const _CommentsController = inject('storeAppOptions', 'storeComments', 'users')(observer(CommentsController));
const _AddCommentController = inject('storeAppOptions', 'storeComments', 'users')(observer(AddCommentController));
const _ViewCommentsController = inject('storeComments', 'users')(observer(withTranslation()(ViewCommentsController)));
export {
_CommentsController as CommentsController,
_AddCommentController as AddCommentController,
ViewCommentsController
_ViewCommentsController as ViewCommentsController
};

View file

@ -38,4 +38,5 @@ class ContextMenuView extends Component {
}
}
export {ContextMenuView as default, idContextMenuElement};
const exportedIdMenuElemen = `#${idContextMenuElement}`;
export {ContextMenuView as default, exportedIdMenuElemen as idContextMenuElement};

View file

@ -53,7 +53,7 @@ const routes = [
}
];
const PageCollaboration = props => {
const PageCollaboration = inject('storeAppOptions')(observer(props => {
const { t } = useTranslation();
const _t = t('Common.Collaboration', {returnObjects: true});
return (
@ -72,9 +72,11 @@ const PageCollaboration = props => {
<ListItem link={'/users/'} title={_t.textUsers}>
<Icon slot="media" icon="icon-users"></Icon>
</ListItem>
{props.storeAppOptions.canViewComments &&
<ListItem link='/comments/' title={_t.textComments}>
<Icon slot="media" icon="icon-insert-comment"></Icon>
</ListItem>
}
{window.editorType === 'de' &&
<ListItem link={'/review/'} title={_t.textReview}>
<Icon slot="media" icon="icon-review"></Icon>
@ -85,7 +87,7 @@ const PageCollaboration = props => {
</View>
)
};
}));
class CollaborationView extends Component {
constructor(props) {
@ -132,6 +134,5 @@ const Collaboration = props => {
)
};
// export withTranslation()(CollaborationPopover);
export {PageCollaboration}
export default Collaboration;

View file

@ -1,6 +1,6 @@
import React, {useState, useEffect} from 'react';
import React, {useState, useEffect, Fragment} from 'react';
import {observer, inject} from "mobx-react";
import { f7, Popup, Page, Navbar, NavLeft, NavRight, NavTitle, Link, Input, Icon, List, ListItem } from 'framework7-react';
import { f7, Popup, Page, Navbar, NavLeft, NavRight, NavTitle, Link, Input, Icon, List, ListItem, Actions, ActionsGroup, ActionsButton } from 'framework7-react';
import { useTranslation } from 'react-i18next';
import {Device} from '../../../utils/device';
@ -123,12 +123,107 @@ const AddComment = props => {
)
};
// View comments
const ViewComments = ({storeComments}) => {
// Actions
const CommentActions = ({comment, onCommentMenuClick, opened, openActionComment}) => {
const { t } = useTranslation();
const _t = t('Common.Collaboration', {returnObjects: true});
return (
<Actions id='comment-menu' opened={opened} onActionsClosed={() => openActionComment(false)}>
<ActionsGroup>
{comment && <Fragment>
{comment.editable && <ActionsButton onClick={() => {onCommentMenuClick('editComment', comment);}}>{_t.textEdit}</ActionsButton>}
{!comment.resolved ?
<ActionsButton onClick={() => {onCommentMenuClick('resolve', comment);}}>{_t.textResolve}</ActionsButton> :
<ActionsButton onClick={() => {onCommentMenuClick('resolve', comment);}}>{_t.textReopen}</ActionsButton>
}
<ActionsButton onClick={() => {onCommentMenuClick('addReply', comment);}}>{_t.textAddReply}</ActionsButton>
{comment.removable && <ActionsButton color='red' onClick={() => {onCommentMenuClick('deleteComment', comment);}}>{_t.textDeleteComment}</ActionsButton>}
</Fragment>
}
</ActionsGroup>
<ActionsGroup>
<ActionsButton>{_t.textCancel}</ActionsButton>
</ActionsGroup>
</Actions>
)
};
// Edit comment
const EditCommentPopup = ({comment, onEditComment, opened, close}) => {
const { t } = useTranslation();
const _t = t('Common.Collaboration', {returnObjects: true});
const [stateText, setText] = useState(comment.comment);
console.log(comment);
return (
<Popup className="edit-comment-popup" opened={opened} animate={true}>
<Navbar>
<NavLeft>
<Link onClick={() => {
close();
//f7.popup.close('.edit-comment-popup');
}}>{_t.textCancel}</Link>
</NavLeft>
<NavTitle>{_t.textEditComment}</NavTitle>
<NavRight>
<Link className={stateText.length === 0 && 'disabled'}
onClick={() => {
onEditComment(comment, stateText);
close();
//f7.popup.close('.edit-comment-popup');
}}
>
{Device.android ? <Icon icon='icon-done-comment-white'/> : _t.textDone}
</Link>
</NavRight>
</Navbar>
<div className='wrap-comment'>
<div className="comment-header">
{Device.android &&
<div className='initials' style={{backgroundColor: `${comment.userColor}`}}>{comment.userInitials}</div>
}
<div>
<div className='name'>{comment.userName}</div>
<div className='comment-date'>{comment.date}</div>
</div>
</div>
<div className='wrap-textarea'>
<Input type='textarea' placeholder={_t.textEditComment} autofocus value={stateText} onChange={(event) => {setText(event.target.value);}}></Input>
</div>
</div>
</Popup>
)
};
const EditComment = ({editProps, opened, close}) => {
return (
Device.phone ?
<EditCommentPopup {...editProps} opened={opened} close={close}/> :
<EditCommentDialog />
)
};
// View comments
const ViewComments = ({storeComments, storeAppOptions, onCommentMenuClick, onResolveComment}) => {
const { t } = useTranslation();
const _t = t('Common.Collaboration', {returnObjects: true});
const isAndroid = Device.android;
const viewMode = !storeAppOptions.canComments;
const comments = storeComments.sortComments;
const sliceQuote = (text) => {
if (text) {
let sliced = text.slice(0, 100);
if (sliced.length < text.length) {
sliced += '...';
return sliced;
}
return text;
}
};
const [clickComment, setClickComment] = useState();
const [commentActionsOpened, openActionComment] = useState(false);
return (
<Page>
<Navbar title={_t.textComments} backLink={_t.textBack}/>
@ -138,12 +233,25 @@ const ViewComments = ({storeComments}) => {
{comments.map((comment, indexComment) => {
return (
<ListItem key={`comment-${indexComment}`}>
<div slot='header'>
<div slot='header' className='comment-header'>
<div className='left'>
{isAndroid && <div className='initials' style={{backgroundColor: `${comment.userColor ? comment.userColor : '#cfcfcf'}`}}>{comment.userInitials}</div>}
<div>
<div className='user-name'>{comment.userName}</div>
<div className='comment-date'>{comment.date}</div>
</div>
</div>
{!viewMode &&
<div className='right'>
<div className='comment-resolve' onClick={() => {onResolveComment(comment);}}><Icon icon={comment.resolved ? 'icon-resolve-comment check' : 'icon-resolve-comment'} /></div>
<div className='comment-menu'
onClick={() => {setClickComment(comment); openActionComment(true);}}
><Icon icon='icon-menu-comment'/></div>
</div>
}
</div>
<div slot='footer'>
<div className='comment-quote'>{comment.quote}</div>
{comment.quote && <div className='comment-quote'>{sliceQuote(comment.quote)}</div>}
<div className='comment-text'><pre>{comment.comment}</pre></div>
{comment.replies.length > 0 &&
<ul className='list-reply'>
@ -155,10 +263,20 @@ const ViewComments = ({storeComments}) => {
<div className='item-content'>
<div className='item-inner'>
<div className='item-title'>
<div slot='header'>
<div slot='header' className='reply-header'>
<div className='left'>
{isAndroid && <div className='initials' style={{backgroundColor: `${reply.userColor ? reply.userColor : '#cfcfcf'}`}}>{reply.userInitials}</div>}
<div>
<div className='user-name'>{reply.userName}</div>
<div className='reply-date'>{reply.date}</div>
</div>
</div>
{!viewMode &&
<div className='right'>
<div className='reply-menu'><Icon icon='icon-menu-comment'/></div>
</div>
}
</div>
<div slot='footer'>
<div className='reply-text'><pre>{reply.reply}</pre></div>
</div>
@ -177,13 +295,15 @@ const ViewComments = ({storeComments}) => {
</List>
}
<CommentActions comment={clickComment} onCommentMenuClick={onCommentMenuClick} opened={commentActionsOpened} openActionComment={openActionComment}/>
</Page>
)
};
const _ViewComments = inject('storeComments')(observer(ViewComments));
const _ViewComments = inject('storeComments', 'storeAppOptions')(observer(ViewComments));
export {
AddComment,
EditComment,
_ViewComments as ViewComments
}

View file

@ -39,9 +39,25 @@
}
.comment-list {
.item-inner {
.item-content .item-inner {
padding-right: 0;
padding-bottom: 0;
padding-top: 16px;
.comment-header {
display: flex;
justify-content: space-between;
padding-right: 16px;
.right {
display: flex;
justify-content: space-between;
width: 70px;
}
}
.reply-header {
display: flex;
justify-content: space-between;
padding-right: 16px;
}
}
.item-title {
width: 100%;
@ -77,29 +93,14 @@
padding-right: 15px;
pre {
white-space: pre-wrap;
overflow-wrap: break-word;
}
}
.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%;
}
}
}
.edit-comment-popup {
z-index: 20000;
}

View file

@ -1,6 +1,9 @@
.device-ios {
@blockTitleColor: #6d6d72;
@item-border-color: #c8c7cc;
--f7-list-item-border-color: @item-border-color;
--f7-navbar-link-color: @themeColor;
--f7-navbar-text-color: @black;

View file

@ -9,7 +9,6 @@
@autoColor: @black;
@comment-date: #6d6d72;
@separator-color: #c8c7cc;
.popup, .popover, .sheet-modal {
.list {

View file

@ -1,2 +1,24 @@
.device-ios {
.comment-list {
.reply-item {
.item-inner:after {
content: none !important;
}
&:before {
content: '';
position: absolute;
left: auto;
bottom: 0;
right: auto;
top: 0;
height: 1px;
width: 100%;
background-color: var(--f7-list-item-border-color);
display: block;
z-index: 15;
-webkit-transform-origin: 50% 100%;
transform-origin: 50% 100%;
}
}
}
}

View file

@ -116,7 +116,16 @@
"textAddComment": "Add Comment",
"textCancel": "Cancel",
"textDone": "Done",
"textNoComments": "This document doesn't contain comments"
"textNoComments": "This document doesn't contain comments",
"textEdit": "Edit",
"textResolve": "Resolve",
"textReopen": "Reopen",
"textAddReply": "Add Reply",
"textDeleteComment": "Delete Comment",
"textMessageDeleteComment": "Do you really want to delete this comment?",
"textMessageDeleteReply": "Do you really want to delete this reply?",
"textDeleteReply": "Delete Reply",
"textEditComment": "Edit Comment"
}
},
"Settings": {

View file

@ -42,21 +42,21 @@ class ApplicationSettingsController extends Component {
if (!value) {
api.asc_hideComments();
this.switchDisplayResolved(value);
// Common.localStorage.setBool("de-settings-resolvedcomment", false);
LocalStorage.setBool("de-settings-resolvedcomment", false);
} else {
// let resolved = Common.localStorage.getBool("de-settings-resolvedcomment");
api.asc_showComments(value);
const resolved = LocalStorage.getBool("de-settings-resolvedcomment");
api.asc_showComments(resolved);
}
// Common.localStorage.setBool("de-mobile-settings-livecomment", value);
LocalStorage.setBool("de-mobile-settings-livecomment", value);
}
switchDisplayResolved(value) {
const api = Common.EditorApi.get();
// let displayComments = Common.localStorage.getBool("de-mobile-settings-livecomment");
if (value) {
const displayComments = LocalStorage.getBool("de-mobile-settings-livecomment");
if (displayComments) {
api.asc_showComments(value);
LocalStorage.setBool("de-settings-resolvedcomment", value);
}
// Common.localStorage.setBool("de-settings-resolvedcomment", value);
}
setMacrosSettings(value) {