1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-21 03:18:23 +00:00

repo share admin (#5596)

* repo share admin

* check share_to param

* [repo 'share admin' dialog] removed 'internal links'; fixup & improvements

---------

Co-authored-by: llj <lingjun.li1@gmail.com>
This commit is contained in:
lian
2023-08-24 10:18:40 +08:00
committed by GitHub
parent 191ab183f8
commit 3b6142e639
14 changed files with 1244 additions and 247 deletions

View File

@@ -0,0 +1,130 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { Modal, ModalHeader, ModalBody, TabContent, TabPane, Nav, NavItem, NavLink } from 'reactstrap';
import { gettext, canGenerateShareLink, canGenerateUploadLink } from '../../utils/constants';
import RepoShareAdminShareLinks from './repo-share-admin/share-links';
import RepoShareAdminUploadLinks from './repo-share-admin/upload-links';
import RepoShareAdminUserShares from './repo-share-admin/user-shares';
import RepoShareAdminGroupShares from './repo-share-admin/group-shares';
const propTypes = {
repo: PropTypes.object.isRequired,
toggleDialog: PropTypes.func.isRequired,
};
class RepoShareAdminDialog extends React.Component {
constructor(props) {
super(props);
this.enableShareLink = !this.props.repo.encrypted && canGenerateShareLink;
this.enableUploadLink = !this.props.repo.encrypted && canGenerateUploadLink;
this.state = {
activeTab: this.getInitialActiveTab()
};
}
getInitialActiveTab = () => {
if (this.enableShareLink) {
return 'shareLink';
} else if (this.enableUploadLink) {
return 'uploadLink';
} else {
return 'shareToUser';
}
}
toggle = (tab) => {
if (this.state.activeTab !== tab) {
this.setState({ activeTab: tab });
}
}
onTabKeyDown = (e) => {
if (e.key == 'Enter' || e.key == 'Space') {
e.target.click();
}
}
render() {
const { activeTab } = this.state;
const { repoName } = this.props.repo;
return (
<div>
<Modal isOpen={true} style={{maxWidth: '760px'}} className="share-dialog" toggle={this.props.toggleDialog}>
<ModalHeader toggle={this.props.toggleDialog}>
<span className="op-target" title={repoName}>{repoName}</span> {gettext('Share Admin')}
</ModalHeader>
<ModalBody className="dialog-list-container share-dialog-content" role="tablist">
<Fragment>
<div className="share-dialog-side">
<Nav pills>
{this.enableShareLink &&
<NavItem role="tab" aria-selected={activeTab === 'shareLink'} aria-controls="share-link-panel">
<NavLink className={activeTab === 'shareLink' ? 'active' : ''} onClick={(this.toggle.bind(this, 'shareLink'))} tabIndex="0" onKeyDown={this.onTabKeyDown}>
{gettext('Share Links')}
</NavLink>
</NavItem>
}
{this.enableUploadLink &&
<NavItem role="tab" aria-selected={activeTab === 'uploadLink'} aria-controls="upload-link-panel">
<NavLink className={activeTab === 'uploadLink' ? 'active' : ''} onClick={this.toggle.bind(this, 'uploadLink')} tabIndex="0" onKeyDown={this.onTabKeyDown}>
{gettext('Upload Links')}
</NavLink>
</NavItem>
}
<NavItem role="tab" aria-selected={activeTab === 'shareToUser'} aria-controls="share-to-user-panel">
<NavLink className={activeTab === 'shareToUser' ? 'active' : ''} onClick={this.toggle.bind(this, 'shareToUser')} tabIndex="0" onKeyDown={this.onTabKeyDown}>
{gettext('User Shares')}
</NavLink>
</NavItem>
<NavItem role="tab" aria-selected={activeTab === 'shareToGroup'} aria-controls="share-to-group-panel">
<NavLink className={activeTab === 'shareToGroup' ? 'active' : ''} onClick={this.toggle.bind(this, 'shareToGroup')} tabIndex="0" onKeyDown={this.onTabKeyDown}>
{gettext('Group Shares')}
</NavLink>
</NavItem>
</Nav>
</div>
<div className="share-dialog-main">
<TabContent activeTab={this.state.activeTab}>
{(this.enableShareLink && activeTab === 'shareLink') &&
<TabPane tabId="shareLink" role="tabpanel" id="share-link-panel">
<RepoShareAdminShareLinks
repo={this.props.repo}
/>
</TabPane>
}
{(this.enableUploadLink && activeTab === 'uploadLink') &&
<TabPane tabId="uploadLink" role="tabpanel" id="upload-link-panel">
<RepoShareAdminUploadLinks
repo={this.props.repo}
/>
</TabPane>
}
{activeTab === 'shareToUser' &&
<TabPane tabId="shareToUser" role="tabpanel" id="share-to-user-panel">
<RepoShareAdminUserShares
repo={this.props.repo}
/>
</TabPane>
}
{activeTab === 'shareToGroup' &&
<TabPane tabId="shareToGroup" role="tabpanel" id="share-to-group-panel">
<RepoShareAdminGroupShares
repo={this.props.repo}
/>
</TabPane>
}
</TabContent>
</div>
</Fragment>
</ModalBody>
</Modal>
</div>
);
}
}
RepoShareAdminDialog.propTypes = propTypes;
export default RepoShareAdminDialog;

View File

@@ -0,0 +1,213 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { Link } from '@gatsbyjs/reach-router';
import { Utils } from '../../../utils/utils';
import { seafileAPI } from '../../../utils/seafile-api';
import { gettext, siteRoot, isPro, username } from '../../../utils/constants';
import Loading from '../../../components/loading';
import toaster from '../../../components/toast';
import EmptyTip from '../../../components/empty-tip';
import SharePermissionEditor from '../../../components/select-editor/share-permission-editor';
const itemPropTypes = {
item: PropTypes.object.isRequired,
deleteItem: PropTypes.func.isRequired,
isRepoOwner: PropTypes.bool.isRequired
};
class Item extends Component {
constructor(props) {
super(props);
this.state = {
permission: this.props.item.permission,
isOperationShow: false,
isShowPermEditor: false,
};
this.permissions = ['rw', 'r'];
if (isPro) {
if (this.props.item.path === '/' && this.props.isRepoOwner) {
this.permissions.push('admin');
}
this.permissions.push('cloud-edit', 'preview');
}
}
onMouseEnter = () => {
this.setState({isOperationShow: true});
};
onMouseLeave = () => {
this.setState({isOperationShow: false});
};
onDeleteLink = (e) => {
e.preventDefault();
this.props.deleteItem(this.props.item);
};
changePerm = (permission) => {
const item = this.props.item;
seafileAPI.updateShareToGroupItemPermission(item.repo_id, item.path, 'group', item.share_to, permission).then(() => {
this.setState({
permission: permission,
});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
onEditPermission = (event) => {
event.nativeEvent.stopImmediatePropagation();
this.setState({isShowPermEditor: true});
}
render() {
let objUrl;
let item = this.props.item;
let path = item.path === '/' ? '/' : item.path.slice(0, item.path.length - 1);
objUrl = `${siteRoot}library/${item.repo_id}/${encodeURIComponent(item.repo_name)}${Utils.encodePath(path)}`;
return (
<tr onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} onFocus={this.onMouseEnter}>
<td>
<Link to={objUrl}>{Utils.getFolderName(item.path)}</Link>
</td>
<td className="name">{item.share_to_name}</td>
<td>
{!this.state.isShowPermEditor && (
<div>
<span>{item.permission_name || Utils.sharePerms(this.state.permission)}</span>
{this.state.isOperationShow && (
<a href="#"
role="button"
aria-label={gettext('Edit')}
title={gettext('Edit')}
className="fa fa-pencil-alt attr-action-icon"
onClick={this.onEditPermission}>
</a>
)}
</div>
)}
{this.state.isShowPermEditor && (
<SharePermissionEditor
repoID={item.repo_id}
isTextMode={true}
isEditIconShow={this.state.isOperationShow}
isEditing={true}
currentPermission={this.state.permission}
permissions={this.permissions}
onPermissionChanged={this.changePerm}
/>
)}
</td>
<td>
<span
tabIndex="0"
role="button"
className={`sf2-icon-x3 action-icon ${this.state.isOperationShow ? '' : 'invisible'}`}
onClick={this.onDeleteLink}
onKeyDown={Utils.onKeyDown}
title={gettext('Delete')}
aria-label={gettext('Delete')}
>
</span>
</td>
</tr>
);
}
}
Item.propTypes = itemPropTypes;
const propTypes = {
repo: PropTypes.object.isRequired,
};
class RepoShareAdminGroupShares extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
errorMsg: '',
items: [],
};
}
componentDidMount() {
seafileAPI.getAllRepoFolderShareInfo(this.props.repo.repo_id, 'group').then((res) => {
this.setState({
loading: false,
items: res.data.share_info_list,
});
}).catch((error) => {
this.setState({
loading: false,
errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
});
});
}
deleteItem = (item) => {
seafileAPI.deleteShareToGroupItem(item.repo_id, item.path, 'group', item.share_to).then(res => {
let items = this.state.items.filter(shareItem => {
return shareItem.path + shareItem.share_to !== item.path + item.share_to;
});
this.setState({items: items});
let message = gettext('Successfully deleted 1 item');
toaster.success(message);
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
render() {
const { loading, errorMsg, items } = this.state;
const { repo } = this.props;
const isRepoOwner = repo.owner_email === username;
return (
<Fragment>
{loading && <Loading />}
{!loading && errorMsg && <p className="error text-center mt-8">{errorMsg}</p>}
{!loading && !errorMsg && !items.length &&
<EmptyTip forDialog={true}>
<p className="text-secondary">{gettext('No group shares')}</p>
</EmptyTip>
}
{!loading && !errorMsg && items.length > 0 &&
<table className="table-hover">
<thead>
<tr>
<th width="30%">{gettext('Name')}</th>
<th width="30%">{gettext('Group')}</th>
<th width="30%">{gettext('Permission')}</th>
<th width="10%"></th>
</tr>
</thead>
<tbody>
{items.map((item, index) => {
return (
<Item
key={index}
item={item}
deleteItem={this.deleteItem}
isRepoOwner={isRepoOwner}
/>
);
})}
</tbody>
</table>
}
</Fragment>
);
}
}
RepoShareAdminGroupShares.propTypes = propTypes;
export default RepoShareAdminGroupShares;

View File

@@ -0,0 +1,162 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { Link } from '@gatsbyjs/reach-router';
import { Utils } from '../../../utils/utils';
import { seafileAPI } from '../../../utils/seafile-api';
import { gettext, siteRoot } from '../../../utils/constants';
import Loading from '../../../components/loading';
import toaster from '../../../components/toast';
import EmptyTip from '../../../components/empty-tip';
const itemPropTypes = {
item: PropTypes.object.isRequired,
deleteItem: PropTypes.func.isRequired
};
class Item extends Component {
constructor(props) {
super(props);
this.state = {
isOperationShow: false
};
}
onMouseEnter = () => {
this.setState({isOperationShow: true});
};
onMouseLeave = () => {
this.setState({isOperationShow: false});
};
onDeleteLink = (e) => {
e.preventDefault();
this.props.deleteItem(this.props.item);
};
render() {
let objUrl;
let item = this.props.item;
let path = item.path === '/' ? '/' : item.path.slice(0, item.path.length - 1);
if (item.is_dir) {
objUrl = `${siteRoot}library/${item.repo_id}/${encodeURIComponent(item.repo_name)}${Utils.encodePath(path)}`;
} else {
objUrl = `${siteRoot}lib/${item.repo_id}/file${Utils.encodePath(item.path)}`;
}
return (
<tr onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} onFocus={this.onMouseEnter}>
<td className="name">{item.creator_name}</td>
<td>
{item.is_dir ? <Link to={objUrl}>{item.obj_name}</Link> :
<a href={objUrl} target="_blank" rel="noreferrer">{item.obj_name}</a>}
</td>
<td>
<a href={item.link} target="_blank" rel="noreferrer">{item.link}</a>
</td>
<td>
<span
tabIndex="0"
role="button"
className={`sf2-icon-x3 action-icon ${this.state.isOperationShow ? '' : 'invisible'}`}
onClick={this.onDeleteLink}
onKeyDown={Utils.onKeyDown}
title={gettext('Delete')}
aria-label={gettext('Delete')}
>
</span>
</td>
</tr>
);
}
}
Item.propTypes = itemPropTypes;
const propTypes = {
repo: PropTypes.object.isRequired,
};
class RepoShareAdminShareLinks extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
errorMsg: '',
items: []
};
}
componentDidMount() {
seafileAPI.listRepoShareLinks(this.props.repo.repo_id).then((res) => {
this.setState({
loading: false,
items: res.data,
});
}).catch((error) => {
this.setState({
loading: false,
errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
});
});
}
deleteItem = (item) => {
seafileAPI.deleteRepoShareLink(this.props.repo.repo_id, item.token).then(() => {
let items = this.state.items.filter(linkItem => {
return linkItem.token !== item.token;
});
this.setState({items: items});
let message = gettext('Successfully deleted 1 item');
toaster.success(message);
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
render() {
const { loading, errorMsg, items } = this.state;
return (
<Fragment>
{loading && <Loading />}
{!loading && errorMsg && <p className="error text-center mt-8">{errorMsg}</p>}
{!loading && !errorMsg && !items.length &&
<EmptyTip forDialog={true}>
<p className="text-secondary">{gettext('No share links')}</p>
</EmptyTip>
}
{!loading && !errorMsg && items.length > 0 &&
<table className="table-hover">
<thead>
<tr>
<th width="22%">{gettext('Creator')}</th>
<th width="20%">{gettext('Name')}</th>
<th width="50%">{gettext('Link')}</th>
<th width="8%"></th>
</tr>
</thead>
<tbody>
{items.map((item, index) => {
return (
<Item
key={index}
item={item}
deleteItem={this.deleteItem}
/>
);
})}
</tbody>
</table>
}
</Fragment>
);
}
}
RepoShareAdminShareLinks.propTypes = propTypes;
export default RepoShareAdminShareLinks;

View File

@@ -0,0 +1,155 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { Link } from '@gatsbyjs/reach-router';
import { Utils } from '../../../utils/utils';
import { seafileAPI } from '../../../utils/seafile-api';
import { gettext, siteRoot } from '../../../utils/constants';
import Loading from '../../../components/loading';
import toaster from '../../../components/toast';
import EmptyTip from '../../../components/empty-tip';
const itemPropTypes = {
item: PropTypes.object.isRequired,
deleteItem: PropTypes.func.isRequired
};
class Item extends Component {
constructor(props) {
super(props);
this.state = {
isOperationShow: false
};
}
onMouseEnter = () => {
this.setState({isOperationShow: true});
};
onMouseLeave = () => {
this.setState({isOperationShow: false});
};
onDeleteLink = (e) => {
e.preventDefault();
this.props.deleteItem(this.props.item);
};
render() {
let item = this.props.item;
let path = item.path === '/' ? '/' : item.path.slice(0, item.path.length - 1);
let objUrl = `${siteRoot}library/${item.repo_id}/${encodeURIComponent(item.repo_name)}${Utils.encodePath(path)}`;
return (
<tr onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} onFocus={this.onMouseEnter}>
<td className="name">{item.creator_name}</td>
<td>
<Link to={objUrl}>{item.obj_name}</Link>
</td>
<td>
<a href={item.link} target="_blank" rel="noreferrer">{item.link}</a>
</td>
<td>
<span
tabIndex="0"
role="button"
className={`sf2-icon-x3 action-icon ${this.state.isOperationShow ? '' : 'invisible'}`}
onClick={this.onDeleteLink}
onKeyDown={Utils.onKeyDown}
title={gettext('Delete')}
aria-label={gettext('Delete')}
>
</span>
</td>
</tr>
);
}
}
Item.propTypes = itemPropTypes;
const propTypes = {
repo: PropTypes.object.isRequired,
};
class RepoShareAdminUploadLinks extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
errorMsg: '',
items: [],
};
}
componentDidMount() {
seafileAPI.listRepoUploadLinks(this.props.repo.repo_id).then((res) => {
this.setState({
loading: false,
items: res.data,
});
}).catch((error) => {
this.setState({
loading: false,
errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
});
});
}
deleteItem = (item) => {
seafileAPI.deleteRepoUploadLink(this.props.repo.repo_id, item.token).then(() => {
let items = this.state.items.filter(linkItem => {
return linkItem.token !== item.token;
});
this.setState({items: items});
let message = gettext('Successfully deleted 1 item');
toaster.success(message);
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
render() {
const { loading, errorMsg, items } = this.state;
return (
<Fragment>
{loading && <Loading />}
{!loading && errorMsg && <p className="error text-center mt-8">{errorMsg}</p>}
{!loading && !errorMsg && !items.length &&
<EmptyTip forDialog={true}>
<p className="text-secondary">{gettext('No upload links')}</p>
</EmptyTip>
}
{!loading && !errorMsg && items.length > 0 &&
<table className="table-hover">
<thead>
<tr>
<th width="22%">{gettext('Creator')}</th>
<th width="20%">{gettext('Name')}</th>
<th width="50%">{gettext('Link')}</th>
<th width="8%"></th>
</tr>
</thead>
<tbody>
{items.map((item, index) => {
return (
<Item
key={index}
item={item}
deleteItem={this.deleteItem}
/>
);
})}
</tbody>
</table>
}
</Fragment>
);
}
}
RepoShareAdminUploadLinks.propTypes = propTypes;
export default RepoShareAdminUploadLinks;

View File

@@ -0,0 +1,213 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { Link } from '@gatsbyjs/reach-router';
import { Utils } from '../../../utils/utils';
import { seafileAPI } from '../../../utils/seafile-api';
import { gettext, siteRoot, isPro, username } from '../../../utils/constants';
import Loading from '../../../components/loading';
import toaster from '../../../components/toast';
import EmptyTip from '../../../components/empty-tip';
import SharePermissionEditor from '../../../components/select-editor/share-permission-editor';
const itemPropTypes = {
item: PropTypes.object.isRequired,
deleteItem: PropTypes.func.isRequired,
isRepoOwner: PropTypes.bool.isRequired
};
class Item extends Component {
constructor(props) {
super(props);
this.state = {
permission: this.props.item.permission,
isOperationShow: false,
isShowPermEditor: false,
};
this.permissions = ['rw', 'r'];
if (isPro) {
if (this.props.item.path === '/' && this.props.isRepoOwner) {
this.permissions.push('admin');
}
this.permissions.push('cloud-edit', 'preview');
}
}
onMouseEnter = () => {
this.setState({isOperationShow: true});
};
onMouseLeave = () => {
this.setState({isOperationShow: false});
};
onDeleteLink = (e) => {
e.preventDefault();
this.props.deleteItem(this.props.item);
};
changePerm = (permission) => {
const item = this.props.item;
seafileAPI.updateShareToUserItemPermission(item.repo_id, item.path, 'user', item.share_to, permission).then(() => {
this.setState({
permission: permission,
});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
onEditPermission = (event) => {
event.nativeEvent.stopImmediatePropagation();
this.setState({isShowPermEditor: true});
}
render() {
let objUrl;
let item = this.props.item;
let path = item.path === '/' ? '/' : item.path.slice(0, item.path.length - 1);
objUrl = `${siteRoot}library/${item.repo_id}/${encodeURIComponent(item.repo_name)}${Utils.encodePath(path)}`;
return (
<tr onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} onFocus={this.onMouseEnter}>
<td>
<Link to={objUrl}>{Utils.getFolderName(item.path)}</Link>
</td>
<td className="name">{item.share_to_name}</td>
<td>
{!this.state.isShowPermEditor && (
<div>
<span>{item.permission_name || Utils.sharePerms(this.state.permission)}</span>
{this.state.isOperationShow && (
<a href="#"
role="button"
aria-label={gettext('Edit')}
title={gettext('Edit')}
className="fa fa-pencil-alt attr-action-icon"
onClick={this.onEditPermission}>
</a>
)}
</div>
)}
{this.state.isShowPermEditor && (
<SharePermissionEditor
repoID={item.repo_id}
isTextMode={true}
isEditIconShow={this.state.isOperationShow}
isEditing={true}
currentPermission={this.state.permission}
permissions={this.permissions}
onPermissionChanged={this.changePerm}
/>
)}
</td>
<td>
<span
tabIndex="0"
role="button"
className={`sf2-icon-x3 action-icon ${this.state.isOperationShow ? '' : 'invisible'}`}
onClick={this.onDeleteLink}
onKeyDown={Utils.onKeyDown}
title={gettext('Delete')}
aria-label={gettext('Delete')}
>
</span>
</td>
</tr>
);
}
}
Item.propTypes = itemPropTypes;
const propTypes = {
repo: PropTypes.object.isRequired,
};
class RepoShareAdminUserShares extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
errorMsg: '',
items: [],
};
}
componentDidMount() {
seafileAPI.getAllRepoFolderShareInfo(this.props.repo.repo_id, 'user').then((res) => {
this.setState({
loading: false,
items: res.data.share_info_list,
});
}).catch((error) => {
this.setState({
loading: false,
errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
});
});
}
deleteItem = (item) => {
seafileAPI.deleteShareToUserItem(item.repo_id, item.path, 'user', item.share_to).then(res => {
let items = this.state.items.filter(shareItem => {
return shareItem.path + shareItem.share_to !== item.path + item.share_to;
});
this.setState({items: items});
let message = gettext('Successfully deleted 1 item');
toaster.success(message);
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
render() {
const { loading, errorMsg, items } = this.state;
const { repo } = this.props;
const isRepoOwner = repo.owner_email === username;
return (
<Fragment>
{loading && <Loading />}
{!loading && errorMsg && <p className="error text-center mt-8">{errorMsg}</p>}
{!loading && !errorMsg && !items.length &&
<EmptyTip forDialog={true}>
<p className="text-secondary">{gettext('No user shares')}</p>
</EmptyTip>
}
{!loading && !errorMsg && items.length > 0 &&
<table className="table-hover">
<thead>
<tr>
<th width="30%">{gettext('Name')}</th>
<th width="30%">{gettext('User')}</th>
<th width="30%">{gettext('Permission')}</th>
<th width="10%"></th>
</tr>
</thead>
<tbody>
{items.map((item, index) => {
return (
<Item
key={index}
item={item}
deleteItem={this.deleteItem}
isRepoOwner={isRepoOwner}
/>
);
})}
</tbody>
</table>
}
</Fragment>
);
}
}
RepoShareAdminUserShares.propTypes = propTypes;
export default RepoShareAdminUserShares;

View File

@@ -1,220 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Modal, ModalHeader, ModalBody, NavLink } from 'reactstrap';
import { seafileAPI } from '../../utils/seafile-api';
import { siteRoot, gettext } from '../../utils/constants';
import { Utils } from '../../utils/utils';
import toaster from '../toast';
import Loading from '../loading';
import EmptyTip from '../empty-tip';
const repoShareUploadLinkItemPropTypes = {
item: PropTypes.object.isRequired,
activeTab: PropTypes.string.isRequired,
deleteItem: PropTypes.func.isRequired
};
class RepoShareUploadLinkItem extends React.Component {
constructor(props) {
super(props);
this.state = {
isOperationShow: false
};
}
onMouseEnter = () => {
this.setState({isOperationShow: true});
};
onMouseLeave = () => {
this.setState({isOperationShow: false});
};
onDeleteLink = (e) => {
e.preventDefault();
this.props.deleteItem(this.props.item.token);
};
render() {
let objUrl;
let item = this.props.item;
let path = item.path === '/' ? '/' : item.path.slice(0, item.path.length - 1);
if (this.props.activeTab === 'shareLinks') {
if (item.is_dir) {
objUrl = `${siteRoot}library/${item.repo_id}/${encodeURIComponent(item.repo_name)}${Utils.encodePath(path)}`;
} else {
objUrl = `${siteRoot}lib/${item.repo_id}/file${Utils.encodePath(item.path)}`;
}
}
if (this.props.activeTab === 'uploadLinks') {
objUrl = `${siteRoot}library/${item.repo_id}/${encodeURIComponent(item.repo_name)}${Utils.encodePath(path)}`;
}
return (
<tr onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} onFocus={this.onMouseEnter}>
<td className="name">{item.creator_name}</td>
<td>
<a href={objUrl} target="_blank" className="text-inherit" rel="noreferrer">{item.obj_name}</a>
</td>
<td>
<a href={item.link} target="_blank" className="text-inherit" rel="noreferrer">{item.link}</a>
</td>
<td>
<span
tabIndex="0"
role="button"
className={`sf2-icon-x3 action-icon ${this.state.isOperationShow ? '' : 'invisible'}`}
onClick={this.onDeleteLink}
onKeyDown={Utils.onKeyDown}
title={gettext('Delete')}
aria-label={gettext('Delete')}
/>
</td>
</tr>
);
}
}
RepoShareUploadLinkItem.propTypes = repoShareUploadLinkItemPropTypes;
const RepoShareUploadLinksDialogPropTypes = {
repo: PropTypes.object.isRequired,
toggleDialog: PropTypes.func.isRequired
};
class RepoShareUploadLinksDialog extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: true,
activeTab: 'shareLinks',
repoShareUploadLinkList: [],
errorMsg: ''
};
}
componentDidMount() {
this.getItems('share-link');
}
getItems = (itemType) => {
const repoID = this.props.repo.repo_id;
const request = itemType == 'share-link' ?
seafileAPI.listRepoShareLinks(repoID) :
seafileAPI.listRepoUploadLinks(repoID);
request.then((res) => {
this.setState({
loading: false,
repoShareUploadLinkList: res.data,
});
}).catch(error => {
this.setState({
isLoading: false,
errorMsg: Utils.getErrorMsg(error, true)
});
});
}
deleteItem = (token) => {
const repoID = this.props.repo.repo_id;
const request = this.state.activeTab == 'shareLinks' ?
seafileAPI.deleteRepoShareLink(repoID, token) :
seafileAPI.deleteRepoUploadLink(repoID, token);
request.then((res) => {
const repoShareUploadLinkList = this.state.repoShareUploadLinkList.filter(item => {
return item.token !== token;
});
this.setState({
repoShareUploadLinkList: repoShareUploadLinkList
});
}).catch(error => {
toaster.danger(Utils.getErrorMsg(error));
});
};
toggle = (tab) => {
if (this.state.activeTab !== tab) {
this.setState({activeTab: tab});
}
if (tab == 'shareLinks') {
this.getItems('share-link');
}
if (tab == 'uploadLinks') {
this.getItems('upload-link');
}
};
render() {
const { loading, errorMsg, activeTab, repoShareUploadLinkList } = this.state;
const itemName = '<span class="op-target">' + Utils.HTMLescape(this.props.repo.repo_name) + '</span>';
const title = gettext('{placeholder} Share/Upload Links').replace('{placeholder}', itemName);
return (
<Modal isOpen={true} style={{maxWidth: '800px'}}>
<ModalHeader toggle={this.props.toggleDialog}>
<span dangerouslySetInnerHTML={{__html: title}}></span>
</ModalHeader>
<ModalBody className="p-0 pb-4">
<div className="main-panel-center">
<div className="cur-view-container" role="tablist">
<div className="cur-view-path share-upload-nav">
<ul className="nav">
<li className="nav-item" role="tab" aria-selected={activeTab === 'shareLinks'} aria-controls="share-links-panel">
<NavLink className={activeTab === 'shareLinks' ? 'active' : ''} onClick={this.toggle.bind(this, 'shareLinks')} tabIndex="0" onKeyDown={Utils.onKeyDown}>{gettext('Share Links')}</NavLink>
</li>
<li className="nav-item" role="tab" aria-selected={activeTab === 'uploadLinks'} aria-controls="upload-links-panel">
<NavLink className={activeTab === 'uploadLinks' ? 'active' : ''} onClick={this.toggle.bind(this, 'uploadLinks')} tabIndex="0" onKeyDown={Utils.onKeyDown}>{gettext('Upload Links')}</NavLink>
</li>
</ul>
</div>
<div className="cur-view-content" style={{minHeight: '20rem', maxHeight: '20rem'}} role="tabpanel" id={activeTab === 'shareLinks' ? 'share-links-panel' : 'upload-links-panel'}>
{loading && <Loading />}
{!loading && errorMsg && <p className="error text-center mt-8">{errorMsg}</p>}
{!loading && !errorMsg && !repoShareUploadLinkList.length &&
<EmptyTip forDialog={true}>
<p className="text-secondary">{activeTab == 'shareLinks' ? gettext('No share links') : gettext('No upload links')}</p>
</EmptyTip>
}
{!loading && !errorMsg && repoShareUploadLinkList.length > 0 &&
<table>
<thead>
<tr>
<th width="22%">{gettext('Creator')}</th>
<th width="20%">{gettext('Name')}</th>
<th width="48%">{gettext('Link')}</th>
<th width="10%"></th>
</tr>
</thead>
<tbody>
{this.state.repoShareUploadLinkList.map((item, index) => {
return (
<RepoShareUploadLinkItem
key={index}
item={item}
activeTab={this.state.activeTab}
deleteItem={this.deleteItem}
/>
);
})}
</tbody>
</table>
}
</div>
</div>
</div>
</ModalBody>
</Modal>
);
}
}
RepoShareUploadLinksDialog.propTypes = RepoShareUploadLinksDialogPropTypes;
export default RepoShareUploadLinksDialog;

View File

@@ -16,7 +16,7 @@ import { seafileAPI } from '../../utils/seafile-api';
import LibHistorySettingDialog from '../dialog/lib-history-setting-dialog';
import toaster from '../toast';
import RepoAPITokenDialog from '../dialog/repo-api-token-dialog';
import RepoShareUploadLinksDialog from '../dialog/repo-share-upload-links-dialog';
import RepoShareAdminDialog from '../dialog/repo-share-admin-dialog';
import RepoMonitoredIcon from '../../components/repo-monitored-icon';
const propTypes = {
@@ -48,7 +48,7 @@ class SharedRepoListItem extends React.Component {
isHistorySettingDialogShow: false,
isDeleteDialogShow: false,
isAPITokenDialogShow: false,
isRepoShareUploadLinksDialogOpen: false,
isRepoShareAdminDialogOpen: false,
isRepoDeleted: false,
isChangePasswordDialogShow: false,
isResetPasswordDialogShow: false
@@ -156,8 +156,8 @@ class SharedRepoListItem extends React.Component {
case 'API Token':
this.onAPITokenToggle();
break;
case 'Share Links Admin':
this.toggleRepoShareUploadLinksDialog();
case 'Share Admin':
this.toggleRepoShareAdminDialog();
break;
case 'Change Password':
this.onChangePasswordToggle();
@@ -275,8 +275,8 @@ class SharedRepoListItem extends React.Component {
this.setState({isShowSharedDialog: false});
}
toggleRepoShareUploadLinksDialog = () => {
this.setState({isRepoShareUploadLinksDialogOpen: !this.state.isRepoShareUploadLinksDialogOpen});
toggleRepoShareAdminDialog = () => {
this.setState({isRepoShareAdminDialogOpen: !this.state.isRepoShareAdminDialogOpen});
}
onAPITokenToggle = () => {
@@ -312,8 +312,8 @@ class SharedRepoListItem extends React.Component {
case 'History Setting':
translateResult = gettext('History Setting');
break;
case 'Share Links Admin':
translateResult = gettext('Share Links Admin');
case 'Share Admin':
translateResult = gettext('Share Admin');
break;
case 'Change Password':
translateResult = gettext('Change Password');
@@ -352,7 +352,7 @@ class SharedRepoListItem extends React.Component {
if (folderPermEnabled) {
operations.push('Folder Permission');
}
operations.push('Share Links Admin');
operations.push('Share Admin');
if (repo.encrypted) {
operations.push('Change Password');
}
@@ -653,11 +653,11 @@ class SharedRepoListItem extends React.Component {
/>
</ModalPortal>
)}
{this.state.isRepoShareUploadLinksDialogOpen && (
{this.state.isRepoShareAdminDialogOpen && (
<ModalPortal>
<RepoShareUploadLinksDialog
<RepoShareAdminDialog
repo={repo}
toggleDialog={this.toggleRepoShareUploadLinksDialog}
toggleDialog={this.toggleRepoShareAdminDialog}
/>
</ModalPortal>
)}