mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-13 22:01:06 +00:00
Merge pull request #3254 from haiwen/add-inviti-people
init invite people
This commit is contained in:
@@ -23,6 +23,7 @@ import PublicSharedView from './pages/shared-with-all/public-shared-view';
|
|||||||
import LibContentView from './pages/lib-content-view/lib-content-view';
|
import LibContentView from './pages/lib-content-view/lib-content-view';
|
||||||
import Group from './pages/groups/group-view';
|
import Group from './pages/groups/group-view';
|
||||||
import Groups from './pages/groups/groups-view';
|
import Groups from './pages/groups/groups-view';
|
||||||
|
import InvitationsView from './pages/invitations/invitations-view';
|
||||||
import Wikis from './pages/wikis/wikis';
|
import Wikis from './pages/wikis/wikis';
|
||||||
import MainContentWrapper from './components/main-content-wrapper';
|
import MainContentWrapper from './components/main-content-wrapper';
|
||||||
|
|
||||||
@@ -232,6 +233,7 @@ class App extends Component {
|
|||||||
/>
|
/>
|
||||||
<Wikis path={siteRoot + 'published'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick}/>
|
<Wikis path={siteRoot + 'published'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick}/>
|
||||||
<PublicSharedView path={siteRoot + 'org/'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} onTabNavClick={this.tabItemClick}/>
|
<PublicSharedView path={siteRoot + 'org/'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} onTabNavClick={this.tabItemClick}/>
|
||||||
|
<InvitationsView path={siteRoot + 'invitations/'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
||||||
</Router>
|
</Router>
|
||||||
</MainPanel>
|
</MainPanel>
|
||||||
</div>
|
</div>
|
||||||
|
124
frontend/src/components/dialog/invite-people-dialog.js
Normal file
124
frontend/src/components/dialog/invite-people-dialog.js
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import {gettext} from '../../utils/constants';
|
||||||
|
import {seafileAPI} from '../../utils/seafile-api';
|
||||||
|
import {Modal, ModalHeader, ModalBody, ModalFooter, Input, Button} from 'reactstrap';
|
||||||
|
import toaster from "../toast";
|
||||||
|
|
||||||
|
class InvitePeopleDialog extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
emails: '',
|
||||||
|
errorMsg: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleEmailsChange = (event) => {
|
||||||
|
let emails = event.target.value;
|
||||||
|
this.setState({
|
||||||
|
emails: emails
|
||||||
|
});
|
||||||
|
if (this.state.errorMsg) {
|
||||||
|
this.setState({
|
||||||
|
errorMsg: ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyDown = (e) => {
|
||||||
|
if (e.keyCode === 13) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.handleSubmitInvite();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmitInvite = () => {
|
||||||
|
let emails = this.state.emails.trim();
|
||||||
|
let emailsArray = [];
|
||||||
|
emails = emails.split(',');
|
||||||
|
for (let i = 0; i < emails.length; i++) {
|
||||||
|
let email = emails[i].trim();
|
||||||
|
if (email) {
|
||||||
|
emailsArray.push(email);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (emailsArray.length) {
|
||||||
|
seafileAPI.invitePeople(emailsArray).then((res) => {
|
||||||
|
this.setState({
|
||||||
|
emails: '',
|
||||||
|
});
|
||||||
|
this.props.toggleInvitePeopleDialog();
|
||||||
|
// success messages
|
||||||
|
let successMsg = '';
|
||||||
|
if (res.data.success.length === 1) {
|
||||||
|
successMsg = gettext('Successfully invited %(email).')
|
||||||
|
.replace('%(email)', res.data.success[0].accepter);
|
||||||
|
} else if(res.data.success.length > 1) {
|
||||||
|
successMsg = gettext('Successfully invited %(email) and %(num) other people.')
|
||||||
|
.replace('%(email)', res.data.success[0].accepter)
|
||||||
|
.replace('%(num)', res.data.success.length - 1);
|
||||||
|
}
|
||||||
|
if (successMsg) {
|
||||||
|
toaster.success(successMsg, {duration: 2});
|
||||||
|
this.props.onInvitePeople(res.data.success);
|
||||||
|
}
|
||||||
|
// failed messages
|
||||||
|
if (res.data.failed.length) {
|
||||||
|
for (let i = 0; i< res.data.failed.length; i++){
|
||||||
|
let failedMsg = res.data.failed[i].email + ': ' + res.data.failed[i].error_msg;
|
||||||
|
toaster.danger(failedMsg, {duration: 3});}
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
this.props.toggleInvitePeopleDialog();
|
||||||
|
if (error.response){
|
||||||
|
toaster.danger(error.response.data.detail || gettext('Error'), {duration: 3});
|
||||||
|
} else {
|
||||||
|
toaster.danger(gettext('Please check the network.'), {duration: 3});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (this.state.emails){
|
||||||
|
this.setState({
|
||||||
|
errorMsg: gettext('Email is invalid.')
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
errorMsg: gettext('It is required.')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Modal isOpen={this.props.isInvitePeopleDialogOpen} toggle={this.props.toggleInvitePeopleDialog}>
|
||||||
|
<ModalHeader toggle={this.props.toggleInvitePeopleDialog}>{gettext('Invite People')}</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<label htmlFor="emails">{gettext('Emails')}</label>
|
||||||
|
<Input type="text" id="emails" placeholder={gettext("Emails, separated by ','")}
|
||||||
|
value={this.state.emails}
|
||||||
|
onChange={this.handleEmailsChange}
|
||||||
|
onKeyDown={this.handleKeyDown}
|
||||||
|
/>
|
||||||
|
<span className="error">{this.state.errorMsg}</span>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button color="secondary" onClick={this.props.toggleInvitePeopleDialog}>{gettext('Cancel')}</Button>
|
||||||
|
<Button color="primary" onClick={this.handleSubmitInvite}>{gettext('Submit')}</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const InvitePeopleDialogPropTypes = {
|
||||||
|
toggleInvitePeopleDialog: PropTypes.func.isRequired,
|
||||||
|
isInvitePeopleDialogOpen: PropTypes.bool.isRequired,
|
||||||
|
onInvitePeople: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
InvitePeopleDialog.propTypes = InvitePeopleDialogPropTypes;
|
||||||
|
|
||||||
|
export default InvitePeopleDialog;
|
@@ -1,10 +1,11 @@
|
|||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { gettext, isPro } from '../../utils/constants';
|
import {gettext, isPro, canInvitePeople, siteRoot} from '../../utils/constants';
|
||||||
import { Button } from 'reactstrap';
|
import { Button } from 'reactstrap';
|
||||||
import { seafileAPI } from '../../utils/seafile-api.js';
|
import { seafileAPI } from '../../utils/seafile-api.js';
|
||||||
import UserSelect from '../user-select';
|
import UserSelect from '../user-select';
|
||||||
import SharePermissionEditor from '../select-editor/share-permission-editor';
|
import SharePermissionEditor from '../select-editor/share-permission-editor';
|
||||||
|
import "../../css/invitations.css";
|
||||||
|
|
||||||
class UserItem extends React.Component {
|
class UserItem extends React.Component {
|
||||||
|
|
||||||
@@ -318,6 +319,12 @@ class ShareToUser extends React.Component {
|
|||||||
deleteShareItem={this.deleteShareItem}
|
deleteShareItem={this.deleteShareItem}
|
||||||
onChangeUserPermission={this.onChangeUserPermission}
|
onChangeUserPermission={this.onChangeUserPermission}
|
||||||
/>
|
/>
|
||||||
|
{ canInvitePeople &&
|
||||||
|
<a href={siteRoot + 'invitations/'} className="invite-link-in-popup">
|
||||||
|
<i className="sf2-icon-invite invite-link-icon-in-popup"></i>
|
||||||
|
<span className="invite-link-icon-in-popup">{gettext('Invite People')}</span>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Link } from '@reach/router';
|
import { Link } from '@reach/router';
|
||||||
import Group from '../models/group';
|
import Group from '../models/group';
|
||||||
import { gettext, siteRoot, enableWiki, canAddRepo, canGenerateShareLink, canGenerateUploadLink } from '../utils/constants';
|
import { gettext, siteRoot, enableWiki, canAddRepo, canGenerateShareLink, canGenerateUploadLink, canInvitePeople } from '../utils/constants';
|
||||||
import { seafileAPI } from '../utils/seafile-api';
|
import { seafileAPI } from '../utils/seafile-api';
|
||||||
import { Badge } from 'reactstrap';
|
import { Badge } from 'reactstrap';
|
||||||
|
|
||||||
@@ -223,6 +223,14 @@ class MainSideNav extends React.Component {
|
|||||||
<span className="nav-text">{gettext('Linked Devices')}</span>
|
<span className="nav-text">{gettext('Linked Devices')}</span>
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
|
{canInvitePeople &&
|
||||||
|
<li className="nav-item">
|
||||||
|
<Link className={`nav-link ellipsis ${this.getActiveClass('invitations')}`} to={siteRoot + 'invitations/'} title={gettext('Invite People')} onClick={() => this.tabItemClick('invitations')}>
|
||||||
|
<span className="sf2-icon-invite" aria-hidden="true"></span>
|
||||||
|
<span className="nav-text">{gettext('Invite People')}</span>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
<li className="nav-item flex-column" id="share-admin-nav">
|
<li className="nav-item flex-column" id="share-admin-nav">
|
||||||
<a className="nav-link ellipsis" title={gettext('Share Admin')} onClick={this.shExtend}>
|
<a className="nav-link ellipsis" title={gettext('Share Admin')} onClick={this.shExtend}>
|
||||||
<span className={`toggle-icon float-right fas ${this.state.sharedExtended ? 'fa-caret-down':'fa-caret-left'}`} aria-hidden="true"></span>
|
<span className={`toggle-icon float-right fas ${this.state.sharedExtended ? 'fa-caret-down':'fa-caret-left'}`} aria-hidden="true"></span>
|
||||||
|
47
frontend/src/components/toolbar/invitations-toolbar.js
Normal file
47
frontend/src/components/toolbar/invitations-toolbar.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import MediaQuery from 'react-responsive';
|
||||||
|
import CommonToolbar from './common-toolbar';
|
||||||
|
import { Button } from 'reactstrap';
|
||||||
|
import { gettext } from '../../utils/constants';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
onShowSidePanel: PropTypes.func.isRequired,
|
||||||
|
onSearchedClick: PropTypes.func.isRequired,
|
||||||
|
toggleInvitePeopleDialog: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
class InvitationsToolbar extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let { onShowSidePanel, onSearchedClick, toggleInvitePeopleDialog } = this.props;
|
||||||
|
return (
|
||||||
|
<div className="main-panel-north border-left-show">
|
||||||
|
<div className="cur-view-toolbar">
|
||||||
|
<span title="Side Nav Menu" onClick={onShowSidePanel}
|
||||||
|
className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none">
|
||||||
|
</span>
|
||||||
|
<MediaQuery query="(min-width: 768px)">
|
||||||
|
<div className="operation">
|
||||||
|
<Button color="btn btn-secondary operation-item" onClick={toggleInvitePeopleDialog}>
|
||||||
|
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('Invite People')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</MediaQuery>
|
||||||
|
<MediaQuery query="(max-width: 768px)">
|
||||||
|
<span className="sf2-icon-plus mobile-toolbar-icon" title={gettext('Invite People')}></span>
|
||||||
|
</MediaQuery>
|
||||||
|
</div>
|
||||||
|
<CommonToolbar searchPlaceholder={this.props.searchPlaceholder} onSearchedClick={onSearchedClick}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InvitationsToolbar.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default InvitationsToolbar;
|
23
frontend/src/css/invitations.css
Normal file
23
frontend/src/css/invitations.css
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
.invite-accept-icon {
|
||||||
|
color: green;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-style: normal;
|
||||||
|
line-height: 1;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-link-in-popup,
|
||||||
|
.invite-link-in-popup:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-link-icon-in-popup {
|
||||||
|
color: #f89a68;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-style: normal;
|
||||||
|
line-height: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
243
frontend/src/pages/invitations/invitations-view.js
Normal file
243
frontend/src/pages/invitations/invitations-view.js
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
import React, {Fragment} from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import {gettext, siteRoot} from '../../utils/constants';
|
||||||
|
import InvitationsToolbar from '../../components/toolbar/invitations-toolbar';
|
||||||
|
import InvitePeopleDialog from "../../components/dialog/invite-people-dialog";
|
||||||
|
import {seafileAPI} from "../../utils/seafile-api";
|
||||||
|
import {Table} from 'reactstrap';
|
||||||
|
import Loading from "../../components/loading";
|
||||||
|
import moment from 'moment';
|
||||||
|
import toaster from '../../components/toast';
|
||||||
|
|
||||||
|
import "../../css/invitations.css";
|
||||||
|
|
||||||
|
class InvitationsListItem extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isOperationShow: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseEnter = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
this.setState({
|
||||||
|
isOperationShow: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseOver = () => {
|
||||||
|
this.setState({
|
||||||
|
isOperationShow: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseLeave = () => {
|
||||||
|
this.setState({
|
||||||
|
isOperationShow: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const invitationItem = this.props.invitation;
|
||||||
|
const acceptIcon = <i className="sf2-icon-tick invite-accept-icon"></i>;
|
||||||
|
const deleteOperation = <i className="action-icon sf2-icon-x3"
|
||||||
|
title={gettext('Delete')}
|
||||||
|
style={!this.state.isOperationShow ? {opacity: 0} : {}}
|
||||||
|
onClick={this.props.onItemDelete.bind(this, invitationItem.token, this.props.index)}></i>;
|
||||||
|
return (
|
||||||
|
<tr onMouseEnter={this.onMouseEnter}
|
||||||
|
onMouseOver={this.onMouseOver}
|
||||||
|
onMouseLeave={this.onMouseLeave}>
|
||||||
|
<td>{invitationItem.accepter}</td>
|
||||||
|
<td>{moment(invitationItem.invite_time).format("YYYY-MM-DD")}</td>
|
||||||
|
<td>{moment(invitationItem.expire_time).format("YYYY-MM-DD")}</td>
|
||||||
|
<td>{invitationItem.accept_time && acceptIcon}</td>
|
||||||
|
<td>{!invitationItem.accept_time && deleteOperation}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const InvitationsListItemPropTypes = {
|
||||||
|
invitation: PropTypes.object.isRequired,
|
||||||
|
onItemDelete: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
InvitationsListItem.propTypes = InvitationsListItemPropTypes;
|
||||||
|
|
||||||
|
class InvitationsListView extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
onItemDelete = (token, index) => {
|
||||||
|
seafileAPI.deleteInvitation(token).then((res) => {
|
||||||
|
this.props.onDeleteInvitation(index);
|
||||||
|
toaster.success(gettext('Successfully deleted 1 item.'), {duration: 1});
|
||||||
|
}).catch((error) => {
|
||||||
|
if (error.response){
|
||||||
|
toaster.danger(error.response.data.detail || gettext('Error'), {duration: 3});
|
||||||
|
} else {
|
||||||
|
toaster.danger(gettext('Please check the network.'), {duration: 3});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const invitationsListItems = this.props.invitationsList.map((invitation, index) => {
|
||||||
|
return (
|
||||||
|
<InvitationsListItem
|
||||||
|
key={index}
|
||||||
|
onItemDelete={this.onItemDelete}
|
||||||
|
invitation={invitation}
|
||||||
|
index={index}
|
||||||
|
/>)
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table hover>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="25%">{gettext('Email')}</th>
|
||||||
|
<th width="20%">{gettext('Invite Time')}</th>
|
||||||
|
<th width="20%">{gettext('Expiration')}</th>
|
||||||
|
<th width="18%">{gettext('Accepted')}</th>
|
||||||
|
<th width="7%"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{invitationsListItems}
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const InvitationsListViewPropTypes = {
|
||||||
|
invitationsList: PropTypes.array.isRequired,
|
||||||
|
onDeleteInvitation: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
InvitationsListView.propTypes = InvitationsListViewPropTypes;
|
||||||
|
|
||||||
|
class InvitationsView extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isInvitePeopleDialogOpen: false,
|
||||||
|
invitationsList: [],
|
||||||
|
isLoading: true,
|
||||||
|
permissionDeniedMsg: '',
|
||||||
|
showEmptyTip: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
listInvitations = () => {
|
||||||
|
seafileAPI.listInvitations().then((res) => {
|
||||||
|
this.setState({
|
||||||
|
invitationsList: res.data,
|
||||||
|
showEmptyTip: true,
|
||||||
|
isLoading: false,
|
||||||
|
});
|
||||||
|
}).catch((error) => {
|
||||||
|
this.setState({
|
||||||
|
isLoading: false,
|
||||||
|
});
|
||||||
|
if (error.response){
|
||||||
|
if (error.response.status === 403){
|
||||||
|
let permissionDeniedMsg = gettext('Permission error');
|
||||||
|
this.setState({
|
||||||
|
permissionDeniedMsg: permissionDeniedMsg,
|
||||||
|
});
|
||||||
|
} else{
|
||||||
|
toaster.danger(error.response.data.detail || gettext('Error'), {duration: 3});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toaster.danger(gettext('Please check the network.'), {duration: 3});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onInvitePeople = (invitationsArray) => {
|
||||||
|
invitationsArray.push.apply(invitationsArray,this.state.invitationsList);
|
||||||
|
this.setState({
|
||||||
|
invitationsList: invitationsArray,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onDeleteInvitation = (index) => {
|
||||||
|
this.state.invitationsList.splice(index, 1);
|
||||||
|
this.setState({
|
||||||
|
invitationsList: this.state.invitationsList,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.listInvitations();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleInvitePeopleDialog = () => {
|
||||||
|
this.setState({
|
||||||
|
isInvitePeopleDialogOpen: !this.state.isInvitePeopleDialogOpen
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
emptyTip = () => {
|
||||||
|
return (
|
||||||
|
<div className="message empty-tip">
|
||||||
|
<h2>{gettext('You have not invited any people.')}</h2>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePermissionDenied = () => {
|
||||||
|
window.location = siteRoot;
|
||||||
|
return(
|
||||||
|
<div className="error mt-6 text-center">
|
||||||
|
<span>{this.state.permissionDeniedMsg}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<InvitationsToolbar
|
||||||
|
onShowSidePanel={this.props.onShowSidePanel}
|
||||||
|
onSearchedClick={this.props.onSearchedClick}
|
||||||
|
toggleInvitePeopleDialog={this.toggleInvitePeopleDialog}
|
||||||
|
/>
|
||||||
|
<div className="main-panel-center flex-row">
|
||||||
|
<div className="cur-view-container">
|
||||||
|
<div className="cur-view-path">
|
||||||
|
<h3 className="sf-heading">{gettext('Invite People')}</h3>
|
||||||
|
</div>
|
||||||
|
<div className="cur-view-content">
|
||||||
|
{this.state.isLoading && <Loading/>}
|
||||||
|
{(!this.state.isLoading && this.state.permissionDeniedMsg !== '') && this.handlePermissionDenied() }
|
||||||
|
{(!this.state.isLoading && this.state.showEmptyTip && this.state.invitationsList.length === 0) && this.emptyTip()}
|
||||||
|
{(!this.state.isLoading && this.state.invitationsList.length !== 0) &&
|
||||||
|
< InvitationsListView
|
||||||
|
invitationsList={this.state.invitationsList}
|
||||||
|
onDeleteInvitation={this.onDeleteInvitation}
|
||||||
|
/>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{this.state.isInvitePeopleDialogOpen &&
|
||||||
|
<InvitePeopleDialog
|
||||||
|
toggleInvitePeopleDialog={this.toggleInvitePeopleDialog}
|
||||||
|
isInvitePeopleDialogOpen={this.state.isInvitePeopleDialogOpen}
|
||||||
|
onInvitePeople={this.onInvitePeople}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InvitationsView;
|
@@ -45,6 +45,7 @@ export const isSystemStaff = window.app.pageOptions.isSystemStaff;
|
|||||||
export const thumbnailSizeForOriginal = window.app.pageOptions.thumbnailSizeForOriginal;
|
export const thumbnailSizeForOriginal = window.app.pageOptions.thumbnailSizeForOriginal;
|
||||||
export const repoPasswordMinLength = window.app.pageOptions.repoPasswordMinLength;
|
export const repoPasswordMinLength = window.app.pageOptions.repoPasswordMinLength;
|
||||||
export const canAddPublicRepo = window.app.pageOptions.canAddPublicRepo;
|
export const canAddPublicRepo = window.app.pageOptions.canAddPublicRepo;
|
||||||
|
export const canInvitePeople = window.app.pageOptions.canInvitePeople;
|
||||||
|
|
||||||
export const curNoteMsg = window.app.pageOptions.curNoteMsg;
|
export const curNoteMsg = window.app.pageOptions.curNoteMsg;
|
||||||
export const curNoteID = window.app.pageOptions.curNoteID;
|
export const curNoteID = window.app.pageOptions.curNoteID;
|
||||||
|
@@ -100,6 +100,7 @@
|
|||||||
.sf2-icon-drafts:before {content:"\e03a"}
|
.sf2-icon-drafts:before {content:"\e03a"}
|
||||||
.sf2-icon-recycle:before {content:"\e03b"}
|
.sf2-icon-recycle:before {content:"\e03b"}
|
||||||
.sf2-icon-library:before { content:"\e00d"; }
|
.sf2-icon-library:before { content:"\e00d"; }
|
||||||
|
.sf2-icon-invite:before { content: "\e02b"; }
|
||||||
|
|
||||||
/* common class and element style*/
|
/* common class and element style*/
|
||||||
body { overflow-y: auto; }
|
body { overflow-y: auto; }
|
||||||
|
@@ -83,6 +83,7 @@
|
|||||||
thumbnailSizeForOriginal: {{ thumbnail_size_for_original }},
|
thumbnailSizeForOriginal: {{ thumbnail_size_for_original }},
|
||||||
repoPasswordMinLength: {{repo_password_min_length}},
|
repoPasswordMinLength: {{repo_password_min_length}},
|
||||||
canAddPublicRepo: {% if can_add_public_repo %} true {% else %} false {% endif %},
|
canAddPublicRepo: {% if can_add_public_repo %} true {% else %} false {% endif %},
|
||||||
|
canInvitePeople: {% if enable_guest_invitation and user.permissions.can_invite_guest %} true {% else %} false {% endif %},
|
||||||
|
|
||||||
{% if request.user.is_authenticated and request.cur_note %}
|
{% if request.user.is_authenticated and request.cur_note %}
|
||||||
curNoteMsg: '{{ request.cur_note.message|urlize }}',
|
curNoteMsg: '{{ request.cur_note.message|urlize }}',
|
||||||
|
@@ -215,6 +215,7 @@ urlpatterns = [
|
|||||||
url(r'^library/(?P<repo_id>[-0-9a-f]{36})/(?P<repo_name>[^/]+)/(?P<path>.*)$', react_fake_view, name="lib_view"),
|
url(r'^library/(?P<repo_id>[-0-9a-f]{36})/(?P<repo_name>[^/]+)/(?P<path>.*)$', react_fake_view, name="lib_view"),
|
||||||
url(r'^my-libs/deleted/$', react_fake_view, name="my_libs_deleted"),
|
url(r'^my-libs/deleted/$', react_fake_view, name="my_libs_deleted"),
|
||||||
url(r'^org/$', react_fake_view, name="org"),
|
url(r'^org/$', react_fake_view, name="org"),
|
||||||
|
url(r'^invitations/$', react_fake_view, name="invitations"),
|
||||||
|
|
||||||
### Ajax ###
|
### Ajax ###
|
||||||
url(r'^ajax/repo/(?P<repo_id>[-0-9a-f]{36})/dirents/$', get_dirents, name="get_dirents"),
|
url(r'^ajax/repo/(?P<repo_id>[-0-9a-f]{36})/dirents/$', get_dirents, name="get_dirents"),
|
||||||
|
Reference in New Issue
Block a user