1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-13 22:01:06 +00:00
* ocm share local and inter-server api, ocm share page (#4335)

* ocm share

* optimize code

* ocm repo (#4341)

* ocm repo

* hide share if not repo owner

* optimize code

* fix All refresh page, hide upload if is r perm

* update permission check

* update return status code

* update code

* add receive user drop share
This commit is contained in:
Leo
2020-09-24 10:57:45 +08:00
committed by GitHub
parent f6efead60b
commit 267e9f525c
24 changed files with 1767 additions and 9 deletions

View File

@@ -19,6 +19,8 @@ import ShareAdminFolders from './pages/share-admin/folders';
import ShareAdminShareLinks from './pages/share-admin/share-links';
import ShareAdminUploadLinks from './pages/share-admin/upload-links';
import SharedLibraries from './pages/shared-libs/shared-libs';
import ShareWithOCM from './pages/share-with-ocm/shared-with-ocm';
import OCMRepoDir from './pages/share-with-ocm/remote-dir-view';
import MyLibraries from './pages/my-libs/my-libs';
import MyLibDeleted from './pages/my-libs/my-libs-deleted';
import PublicSharedView from './pages/shared-with-all/public-shared-view';
@@ -38,6 +40,7 @@ const DraftsViewWrapper = MainContentWrapper(DraftsView);
const StarredWrapper = MainContentWrapper(Starred);
const LinkedDevicesWrapper = MainContentWrapper(LinkedDevices);
const SharedLibrariesWrapper = MainContentWrapper(SharedLibraries);
const SharedWithOCMWrapper = MainContentWrapper(ShareWithOCM);
const ShareAdminLibrariesWrapper = MainContentWrapper(ShareAdminLibraries);
const ShareAdminFoldersWrapper = MainContentWrapper(ShareAdminFolders);
const ShareAdminShareLinksWrapper = MainContentWrapper(ShareAdminShareLinks);
@@ -257,9 +260,11 @@ class App extends Component {
<ShareAdminShareLinksWrapper path={siteRoot + 'share-admin-share-links'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
<ShareAdminUploadLinksWrapper path={siteRoot + 'share-admin-upload-links'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
<SharedLibrariesWrapper path={siteRoot + 'shared-libs'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
<SharedWithOCMWrapper path={siteRoot + 'shared-with-ocm'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
<MyLibraries path={siteRoot + 'my-libs'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
<MyLibDeleted path={siteRoot + 'my-libs/deleted/'} onSearchedClick={this.onSearchedClick} />
<LibContentView path={siteRoot + 'library/:repoID/*'} pathPrefix={this.state.pathPrefix} onMenuClick={this.onShowSidePanel} onTabNavClick={this.tabItemClick}/>
<OCMRepoDir path={siteRoot + 'remote-library/:providerID/:repoID/*'} pathPrefix={this.state.pathPrefix} onMenuClick={this.onShowSidePanel} onTabNavClick={this.tabItemClick}/>
<Groups path={siteRoot + 'groups'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick}/>
<Group
path={siteRoot + 'group/:groupID'}

View File

@@ -1,12 +1,13 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { Modal, ModalHeader, ModalBody, TabContent, TabPane, Nav, NavItem, NavLink } from 'reactstrap';
import { gettext, username, canGenerateShareLink, canGenerateUploadLink, canInvitePeople, additionalShareDialogNote } from '../../utils/constants';
import { Modal, ModalHeader, ModalBody, TabContent, TabPane, Nav, NavItem, NavLink } from 'reactstrap';
import { gettext, username, canGenerateShareLink, canGenerateUploadLink, canInvitePeople, additionalShareDialogNote, enableOCM } from '../../utils/constants';
import ShareToUser from './share-to-user';
import ShareToGroup from './share-to-group';
import ShareToInvitePeople from './share-to-invite-people';
import GenerateShareLink from './generate-share-link';
import GenerateUploadLink from './generate-upload-link';
import ShareToOtherServer from './share-to-other-server';
import InternalLink from './internal-link';
import { seafileAPI } from '../../utils/seafile-api';
import Loading from '../loading';
@@ -141,6 +142,13 @@ class ShareDialog extends React.Component {
}
</Fragment>
}
{enableOCM && itemType === 'library' && this.state.isRepoOwner &&
<NavItem>
<NavLink className={activeTab === 'shareToOtherServer' ? 'active' : ''} onClick={this.toggle.bind(this, 'shareToOtherServer')}>
{gettext('Share to other server')}
</NavLink>
</NavItem>
}
</Nav>
</div>
<div className="share-dialog-main">
@@ -190,6 +198,11 @@ class ShareDialog extends React.Component {
}
</Fragment>
}
{enableOCM && itemType === 'library' && activeTab === 'shareToOtherServer' &&
<TabPane tabId="shareToOtherServer">
<ShareToOtherServer itemType={this.props.itemType} isGroupOwnedRepo={this.props.isGroupOwnedRepo} itemPath={this.props.itemPath} repoID={this.props.repoID} isRepoOwner={this.state.isRepoOwner} />
</TabPane>
}
</TabContent>
</div>
</Fragment>

View File

@@ -0,0 +1,234 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { gettext } from '../../utils/constants';
import { Input } from 'reactstrap';
import { Button } from 'reactstrap';
import { seafileAPI } from '../../utils/seafile-api.js';
import { Utils } from '../../utils/utils';
import toaster from '../toast';
import SharePermissionEditor from '../select-editor/share-permission-editor';
class ShareItem extends React.Component {
constructor(props) {
super(props);
this.state = {
isOperationShow: false
};
}
onMouseEnter = () => {
this.setState({isOperationShow: true});
}
onMouseLeave = () => {
this.setState({isOperationShow: false});
}
deleteShareItem = () => {
let item = this.props.item;
this.props.deleteShareItem(item);
}
render() {
let item = this.props.item;
return (
<tr onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
<td>{item.to_sever_url}</td>
<td className="name">{item.to_user}</td>
<td>{Utils.sharePerms(item.permission)}</td>
{/* <td>
<SharePermissionEditor
isTextMode={true}
isEditIconShow={this.state.isOperationShow}
currentPermission={currentPermission}
permissions={this.props.permissions}
onPermissionChanged={this.onChangeUserPermission}
/>
</td> */}
<td>
<span
className={`sf2-icon-x3 action-icon ${this.state.isOperationShow ? '' : 'hide'}`}
onClick={this.deleteShareItem}
title={gettext('Delete')}
>
</span>
</td>
</tr>
);
}
}
class ShareList extends React.Component {
render() {
return (
<div className="share-list-container">
<table className="table-thead-hidden">
<thead>
<tr>
<th width="40%">{gettext('Server URL')}</th>
<th width="25%">{gettext('User Email')}</th>
<th width="20%">{gettext('Permission')}</th>
<th width="15%"></th>
</tr>
</thead>
<tbody>
{this.props.items.map((item, index) => {
return (
<ShareItem
key={index}
item={item}
deleteShareItem={this.props.deleteShareItem}
/>
);
})}
</tbody>
</table>
</div>
);
}
}
const propTypes = {
isGroupOwnedRepo: PropTypes.bool,
itemPath: PropTypes.string.isRequired,
itemType: PropTypes.string.isRequired,
repoID: PropTypes.string.isRequired,
isRepoOwner: PropTypes.bool.isRequired,
};
class ShareToOtherServer extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedOption: null,
errorMsg: [],
permission: 'rw',
ocmShares: [],
toUser: '',
toServerURL: '',
};
this.options = [];
this.permissions = ['rw', 'r'];
this.UnshareMessage = 'File was unshared';
}
handleSelectChange = (option) => {
this.setState({selectedOption: option});
this.options = [];
}
componentDidMount() {
seafileAPI.listOCMSharesPrepare(this.props.repoID).then((res) => {
this.setState({ocmShares: res.data.ocm_share_list});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
startOCMShare = () => {
let { repoID, itemPath } = this.props;
let { toServerURL, toUser, permission } = this.state;
if (!toServerURL.endsWith('/')) {
toServerURL += '/';
}
seafileAPI.addOCMSharePrepare(toUser, toServerURL, repoID, itemPath, permission).then((res) => {
toaster.success(gettext('share success.'));
let ocmShares = this.state.ocmShares;
ocmShares.push(res.data);
this.setState({ocmShares: ocmShares});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
handleToUserChange = (e) => {
this.setState({
toUser: e.target.value,
});
}
handleURLChange = (e) => {
this.setState({
toServerURL: e.target.value,
});
}
deleteShareItem = (deletedItem) => {
let { id } = deletedItem;
seafileAPI.deleteOCMSharePrepare(id).then((res) => {
toaster.success(gettext('delete success.'));
let ocmShares = this.state.ocmShares.filter(item => {
return item.id != id;
});
this.setState({ocmShares: ocmShares});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
setPermission = (permission) => {
this.setState({permission: permission});
}
render() {
let { ocmShares, toUser, toServerURL, permission } = this.state;
return (
<Fragment>
<table>
<thead>
<tr>
<th width="40%">{gettext('Server URL')}</th>
<th width="25%">{gettext('User Email')}</th>
<th width="20%">{gettext('Permission')}</th>
<th width="15%"></th>
</tr>
</thead>
<tbody>
<tr>
<td>
<Input
value={toServerURL}
onChange={this.handleURLChange}
/>
</td>
<td>
<Input
value={toUser}
onChange={this.handleToUserChange}
/>
</td>
<td>
<SharePermissionEditor
isTextMode={false}
isEditIconShow={false}
currentPermission={permission}
permissions={this.permissions}
onPermissionChanged={this.setPermission}
/>
</td>
<td>
<Button onClick={this.startOCMShare}>{gettext('Submit')}</Button>
</td>
</tr>
</tbody>
</table>
<ShareList
items={ocmShares}
deleteShareItem={this.deleteShareItem}
/>
</Fragment>
);
}
}
ShareToOtherServer.propTypes = propTypes;
export default ShareToOtherServer;

View File

@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Link } from '@reach/router';
import { Badge } from 'reactstrap';
import { gettext, siteRoot, canPublishRepo, canAddRepo, canGenerateShareLink, canGenerateUploadLink, canInvitePeople, dtableWebServer } from '../utils/constants';
import { gettext, siteRoot, canPublishRepo, canAddRepo, canGenerateShareLink, canGenerateUploadLink, canInvitePeople, dtableWebServer, enableOCM } from '../utils/constants';
import { seafileAPI } from '../utils/seafile-api';
import { Utils } from '../utils/utils';
import toaster from './toast';
@@ -216,6 +216,14 @@ class MainSideNav extends React.Component {
</a>
{this.renderSharedGroups()}
</li>
{enableOCM &&
<li className="nav-item">
<Link to={siteRoot + 'shared-with-ocm/'} className={`nav-link ellipsis ${this.getActiveClass('shared-with-ocm')}`} title={gettext('Shared from other servers')} onClick={(e) => this.tabItemClick(e, 'shared-with-ocm')}>
<span className="sf2-icon-share" aria-hidden="true"></span>
<span className="nav-text">{gettext('Shared from other servers')}</span>
</Link>
</li>
}
</ul>

View File

@@ -0,0 +1,121 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { Link } from '@reach/router';
import moment from 'moment';
import { gettext } from '../../utils/constants';
import { Utils } from '../../utils/utils';
import Loading from '../../components/loading';
class DirentItem extends React.Component {
constructor(props) {
super(props);
this.state = {
isOpIconShown: false
};
}
handleMouseOver = () => {
this.setState({
isOpIconShown: true
});
}
handleMouseOut = () => {
this.setState({
isOpIconShown: false
});
}
openFolder = () => {
this.props.openFolder(this.props.dirent);
}
downloadDirent = (e) => {
e.preventDefault();
this.props.downloadDirent(this.props.dirent);
}
render () {
let { isOpIconShown } = this.state;
let { dirent } = this.props;
let iconUrl = Utils.getDirentIcon(dirent);
return (
<Fragment>
<tr onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
<td className="text-center"><img src={iconUrl} width="24" alt='' /></td>
<td>
{dirent.is_file ?
dirent.name :
<Link to="#" onClick={this.openFolder}>{dirent.name}</Link>
}
</td>
<td>
{isOpIconShown && dirent.is_file &&
<a href="#" className="op-icon sf2-icon-download" title={gettext('Download')} onClick={this.downloadDirent}></a>
}
</td>
<td>{Utils.bytesToSize(dirent.size)}</td>
<td>{moment(dirent.mtime).fromNow()}</td>
</tr>
</Fragment>
);
}
}
const propTypes = {
direntList: PropTypes.array.isRequired
};
class DirContent extends React.Component {
constructor(props) {
super(props);
}
render() {
let { loading, errorMsg, direntList } = this.props;
if (loading) {
return <Loading />;
}
if (errorMsg) {
return <p className="error text-center mt-4">{errorMsg}</p>;
}
return (
<Fragment>
<table className="table-hover">
<thead>
<tr>
<th width="5%">{/*icon*/}</th>
<th width="55%">{gettext('Name')}</th>
<th width="10%">{/*operation*/}</th>
<th width="15%">{gettext('Size')}</th>
<th width="15%">{gettext('Last Update')}</th>
</tr>
</thead>
<tbody>
{direntList.map((dirent, index) => {
return <DirentItem
key={index}
dirent={dirent}
openFolder={this.props.openFolder}
deleteDirent={this.props.deleteDirent}
downloadDirent={this.props.downloadDirent}
fromSystemRepo={this.props.fromSystemRepo}
/>;
})}
</tbody>
</table>
</Fragment>
);
}
}
DirContent.propTypes = propTypes;
export default DirContent;

View File

@@ -0,0 +1,70 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { Link } from '@reach/router';
import { siteRoot, gettext } from '../../utils/constants';
import { Utils } from '../../utils/utils';
const propTypes = {
repoName: PropTypes.string.isRequired,
currentPath: PropTypes.string.isRequired,
onPathClick: PropTypes.func.isRequired,
onTabNavClick: PropTypes.func.isRequired,
repoID: PropTypes.string.isRequired,
};
class DirPath extends React.Component {
onPathClick = (e) => {
let path = Utils.getEventData(e, 'path');
this.props.onPathClick(path);
}
turnPathToLink = (path) => {
path = path[path.length - 1] === '/' ? path.slice(0, path.length - 1) : path;
let pathList = path.split('/');
let nodePath = '';
let pathElem = pathList.map((item, index) => {
if (item === '') {
return;
}
if (index === (pathList.length - 1)) {
return (
<Fragment key={index}>
<span className="path-split">/</span>
<span className="path-file-name">{item}</span>
</Fragment>
);
} else {
nodePath += '/' + item;
return (
<Fragment key={index} >
<span className="path-split">/</span>
<a className="path-link" data-path={nodePath} onClick={this.onPathClick}>{item}</a>
</Fragment>
);
}
});
return pathElem;
}
render() {
let { currentPath, repoName } = this.props;
let pathElem = this.turnPathToLink(currentPath);
return (
<div className="path-container">
<Link to={siteRoot + 'shared-with-ocm/'} className="normal" onClick={(e) => this.props.onTabNavClick('shared-with-ocm')}>{gettext('All')}</Link>
<span className="path-split">/</span>
{(currentPath === '/' || currentPath === '') ?
<span className="path-repo-name">{repoName}</span>:
<a className="path-link" data-path="/" onClick={this.onPathClick}>{repoName}</a>
}
{pathElem}
</div>
);
}
}
DirPath.propTypes = propTypes;
export default DirPath;

View File

@@ -0,0 +1,30 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Account from '../../components/common/account';
const propTypes = {
children: PropTypes.object
};
class MainPanelTopbar extends Component {
render() {
return (
<div className={`main-panel-north ${this.props.children ? 'border-left-show' : ''}`}>
<div className="cur-view-toolbar">
<span className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none" title="Side Nav Menu"></span>
<div className="operation">
{this.props.children}
</div>
</div>
<div className="common-toolbar">
<Account isAdminPanel={false} />
</div>
</div>
);
}
}
MainPanelTopbar.propTypes = propTypes;
export default MainPanelTopbar;

View File

@@ -0,0 +1,188 @@
import React, { Component, Fragment } from 'react';
import { Button } from 'reactstrap';
import { post } from 'axios';
import { Utils } from '../../utils/utils';
import { seafileAPI } from '../../utils/seafile-api';
import { loginUrl, siteRoot, gettext } from '../../utils/constants';
import toaster from '../../components/toast';
import MainPanelTopbar from './remote-dir-topbar';
import DirPathBar from './remote-dir-path';
import DirContent from './remote-dir-content';
class Dirent {
constructor(obj) {
this.name = obj.name;
this.mtime = obj.mtime;
this.size = obj.size;
this.is_file = obj.type === 'file';
}
isDir() {
return !this.is_file;
}
}
class DirView extends Component {
constructor(props) {
super(props);
this.fileInput = React.createRef();
this.state = {
loading: true,
errorMsg: '',
repoName: '',
path: '',
direntList: [],
isNewFolderDialogOpen: false,
userPerm: '',
};
}
componentDidMount () {
this.loadDirentList('/');
}
onPathClick = (path) => {
this.loadDirentList(path);
}
openFolder = (dirent) => {
let direntPath = Utils.joinPath(this.state.path, dirent.name);
if (!dirent.is_file) {
this.loadDirentList(direntPath);
}
}
loadDirentList = (path) => {
const { providerID, repoID } = this.props;
seafileAPI.listOCMRepoDir(providerID, repoID, path).then(res => {
const { repo_name: repoName, dirent_list, user_perm } = res.data;
let direntList = [];
dirent_list.forEach(item => {
let dirent = new Dirent(item);
direntList.push(dirent);
});
this.setState({
loading: false,
repoName: repoName,
direntList: direntList,
path: path,
userPerm: user_perm,
}, () => {
let url =`${siteRoot}remote-library/${providerID}/${repoID}/${repoName}${Utils.encodePath(path)}`;
window.history.replaceState({url: url, path: path}, path, url);
});
}).catch((error) => {
if (error.response) {
if (error.response.status == 403) {
this.setState({
loading: false,
errorMsg: gettext('Permission denied')
});
location.href = `${loginUrl}?next=${encodeURIComponent(location.href)}`;
} else {
this.setState({
loading: false,
errorMsg: gettext('Error')
});
}
} else {
this.setState({
loading: false,
errorMsg: gettext('Please check the network.')
});
}
});
}
downloadDirent = (dirent) => {
let path = Utils.joinPath(this.state.path, dirent.name);
seafileAPI.getOCMRepoDownloadURL(this.props.providerID, this.props.repoID, path).then(res => {
location.href = res.data;
}).catch((err) => {
let errMessage = Utils.getErrorMsg(err);
toaster.danger(errMessage);
});
}
openFileInput = () => {
this.fileInput.current.click();
}
onFileInputChange = () => {
if (!this.fileInput.current.files.length) {
return;
}
const file = this.fileInput.current.files[0];
let { path } = this.state;
let { providerID, repoID } = this.props;
seafileAPI.getOCMRepoUploadURL(providerID, repoID, path).then(res => {
let formData = new FormData();
formData.append('parent_dir', path);
formData.append('file', file);
post(res.data, formData).then(res => {
const fileObj = res.data[0];
let newDirent = new Dirent({
'type': 'file',
'name': fileObj.name,
'size': fileObj.size,
'mtime': (new Date()).getTime()
});
let direntList = this.state.direntList;
const dirs = direntList.filter(item => { return !item.is_file; });
direntList.splice(dirs.length, 0, newDirent);
this.setState({
direntList: direntList
});
});
}).catch((err) => {
let errMessage = Utils.getErrorMsg(err);
toaster.danger(errMessage);
});
}
render() {
const { loading, errorMsg,
repoName, direntList, path, userPerm } = this.state;
const { repoID } = this.props;
return (
<Fragment>
<MainPanelTopbar>
<Fragment>
<input className="d-none" type="file" onChange={this.onFileInputChange} ref={this.fileInput} />
{userPerm === 'rw' &&
<Button className="operation-item" onClick={this.openFileInput}>{gettext('Upload')}</Button>
}
</Fragment>
</MainPanelTopbar>
<div className="main-panel-center flex-row">
<div className="cur-view-container">
<div className="cur-view-path align-items-center">
<DirPathBar
repoID={repoID}
repoName={repoName}
currentPath={path}
onPathClick={this.onPathClick}
onTabNavClick={this.props.onTabNavClick}
/>
</div>
<div className="cur-view-content">
<DirContent
loading={loading}
errorMsg={errorMsg}
direntList={direntList}
openFolder={this.openFolder}
deleteDirent={this.deleteDirent}
downloadDirent={this.downloadDirent}
/>
</div>
</div>
</div>
</Fragment>
);
}
}
export default DirView;

View File

@@ -0,0 +1,203 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import { Link } from '@reach/router';
import { gettext, siteRoot } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
import toaster from '../../components/toast';
import Loading from '../../components/loading';
import EmptyTip from '../../components/empty-tip';
class Content extends Component {
render() {
const { loading, errorMsg, items } = this.props;
const emptyTip = (
<EmptyTip>
<h2>{gettext('No libraries have been shared with you')}</h2>
<p>{gettext('No libraries have been shared directly with you. You can find more shared libraries at "Shared with groups".')}</p>
</EmptyTip>
);
if (loading) {
return <Loading />;
} else if (errorMsg) {
return <p className="error text-center">{errorMsg}</p>;
} else {
const table = (
<table>
<thead>
<tr>
<th width="4%"></th>
<th width="20%">{gettext('Name')}</th>
<th width="20%">{gettext('Shared from')}</th>
<th width="26%">{gettext('At site')}</th>
<th width="20%">{gettext('Time')}</th>
<th width="10%">{/* operations */}</th>
</tr>
</thead>
<tbody>
{items.map((item, index) => {
return <Item
key={index}
item={item}
deleteShare={this.props.deleteShare}
/>;
})}
</tbody>
</table>
);
return items.length ? table : emptyTip;
}
}
}
Content.propTypes = {
loading: PropTypes.bool.isRequired,
errorMsg: PropTypes.string.isRequired,
items: PropTypes.array.isRequired,
};
class Item extends Component {
constructor(props) {
super(props);
this.state = {
showOpIcon: false,
isOpMenuOpen: false // for mobile
};
}
toggleOpMenu = () => {
this.setState({
isOpMenuOpen: !this.state.isOpMenuOpen
});
}
handleMouseOver = () => {
this.setState({
showOpIcon: true
});
}
handleMouseOut = () => {
this.setState({
showOpIcon: false
});
}
deleteShare = () => {
this.props.deleteShare(this.props.item);
}
render() {
const item = this.props.item;
item.icon_url = Utils.getLibIconUrl(item);
item.icon_title = Utils.getLibIconTitle(item);
item.url = `${siteRoot}#shared-libs/lib/${item.repo_id}/`;
let shareRepoUrl =`${siteRoot}remote-library/${this.props.item.provider_id}/${this.props.item.repo_id}/${Utils.encodePath(this.props.item.repo_name)}/`;
let iconVisibility = this.state.showOpIcon ? '' : ' invisible';
let deleteIcon = `action-icon sf2-icon-x3 ${iconVisibility ? 'invisible' : ''}`;
return (
<Fragment>
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
<td><img src={item.icon_url} title={item.icon_title} alt={item.icon_title} width="24" /></td>
<td><Link to={shareRepoUrl}>{item.repo_name}</Link></td>
<td>{item.from_user}</td>
<td>{item.from_server_url}</td>
<td title={moment(item.last_modified).format('llll')}>{moment(item.ctime).fromNow()}</td>
<td>
<a href="#" className={deleteIcon} title={gettext('Remove')} onClick={this.deleteShare}></a>
</td>
</tr>
</Fragment>
);
}
}
Item.propTypes = {
item: PropTypes.object.isRequired
};
class SharedWithOCM extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
errorMsg: '',
items: [],
};
}
componentDidMount() {
seafileAPI.listOCMSharesReceived().then((res) => {
this.setState({
loading: false,
items: res.data.ocm_share_received_list
});
}).catch((error) => {
if (error.response) {
if (error.response.status == 403) {
this.setState({
loading: false,
errorMsg: gettext('Permission denied')
});
} else {
this.setState({
loading: false,
errorMsg: gettext('Error')
});
}
} else {
this.setState({
loading: false,
errorMsg: gettext('Please check the network.')
});
}
});
}
deleteShare = (item) => {
let { id } = item;
seafileAPI.deleteOCMShareReceived(id).then((res) => {
toaster.success(gettext('delete success.'));
let items = this.state.items.filter(item => {
return item.id != id;
});
this.setState({items: items});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
render() {
return (
<Fragment>
<div className="main-panel-center">
<div className="cur-view-container">
<div className="cur-view-path">
<h3 className="sf-heading m-0">{gettext('Shared from other servers')}</h3>
</div>
<div className="cur-view-content">
<Content
loading={this.state.loading}
errorMsg={this.state.errorMsg}
items={this.state.items}
deleteShare={this.deleteShare}
/>
</div>
</div>
</div>
</Fragment>
);
}
}
export default SharedWithOCM;

View File

@@ -65,6 +65,7 @@ export const customNavItems = window.app.pageOptions.customNavItems;
export const enableShowContactEmailWhenSearchUser = window.app.pageOptions.enableShowContactEmailWhenSearchUser;
export const maxUploadFileSize = window.app.pageOptions.maxUploadFileSize;
export const maxNumberOfFilesForFileupload = window.app.pageOptions.maxNumberOfFilesForFileupload;
export const enableOCM = window.app.pageOptions.enableOCM;
export const curNoteMsg = window.app.pageOptions.curNoteMsg;
export const curNoteID = window.app.pageOptions.curNoteID;