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

Merge branch '10.0'

This commit is contained in:
lian
2023-08-07 13:40:14 +08:00
19 changed files with 402 additions and 92 deletions

View File

@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Link } from '@gatsbyjs/reach-router';
import { Badge } from 'reactstrap';
import { gettext, siteRoot, canPublishRepo, canAddRepo, canGenerateShareLink, canGenerateUploadLink, canInvitePeople, dtableWebServer, enableOCM, enableOCMViaWebdav } from '../utils/constants';
import { gettext, siteRoot, canAddRepo, canGenerateShareLink, canGenerateUploadLink, canInvitePeople, dtableWebServer, enableOCM, enableOCMViaWebdav } from '../utils/constants';
import { seafileAPI } from '../utils/seafile-api';
import { Utils } from '../utils/utils';
import toaster from './toast';
@@ -251,14 +251,12 @@ class MainSideNav extends React.Component {
</Link>
</li>
}
{canPublishRepo &&
<li className="nav-item">
<Link className={`nav-link ellipsis ${this.getActiveClass('published')}`} to={siteRoot + 'published/'} title={gettext('Published Libraries')} onClick={(e) => this.tabItemClick(e, 'published')}>
<span className="sf2-icon-wiki-view" aria-hidden="true"></span>
<span className="nav-text">{gettext('Published Libraries')}</span>
</Link>
</li>
}
{isDocs &&
<li className="nav-item" onClick={(e) => this.tabItemClick(e, 'drafts')}>
<Link className={`nav-link ellipsis ${this.getActiveClass('drafts')}`} to={siteRoot + 'drafts/'} title={gettext('Drafts')}>

View File

@@ -95,15 +95,46 @@ class ShareLinkPanel extends React.Component {
});
}
deleteLink = () => {
const { sharedLinkInfo, shareLinks } = this.state;
seafileAPI.deleteShareLink(sharedLinkInfo.token).then(() => {
deleteLink = (token) => {
const { shareLinks } = this.state;
seafileAPI.deleteShareLink(token).then(() => {
this.setState({
mode: '',
sharedLinkInfo: null,
shareLinks: shareLinks.filter(item => item.token !== sharedLinkInfo.token)
shareLinks: shareLinks.filter(item => item.token !== token)
});
toaster.success(gettext('Successfully deleted 1 share link'));
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
}
deleteShareLinks = () => {
const { shareLinks } = this.state;
const tokens = shareLinks.filter(item => item.isSelected).map(link => link.token);
seafileAPI.deleteShareLinks(tokens).then(res => {
const { success, failed } = res.data;
if (success.length) {
let newShareLinkList = shareLinks.filter(shareLink => {
return !success.some(deletedShareLink => {
return deletedShareLink.token == shareLink.token;
});
});
this.setState({
shareLinks: newShareLinkList
});
const length = success.length;
const msg = length == 1 ?
gettext('Successfully deleted 1 share link') :
gettext('Successfully deleted {number_placeholder} share links')
.replace('{number_placeholder}', length);
toaster.success(msg);
}
failed.forEach(item => {
const msg = `${item.token}: ${item.error_msg}`;
toaster.danger(msg);
});
toaster.success(gettext('Link deleted'));
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
@@ -209,6 +240,8 @@ class ShareLinkPanel extends React.Component {
showLinkDetails={this.showLinkDetails}
toggleSelectAllLinks={this.toggleSelectAllLinks}
toggleSelectLink={this.toggleSelectLink}
deleteShareLinks={this.deleteShareLinks}
deleteLink={this.deleteLink}
/>
);
}

View File

@@ -5,6 +5,7 @@ import copy from 'copy-to-clipboard';
import { Button } from 'reactstrap';
import { isPro, gettext, shareLinkExpireDaysMin, shareLinkExpireDaysMax, shareLinkExpireDaysDefault, canSendShareLinkEmail } from '../../utils/constants';
import ShareLinkPermissionEditor from '../../components/select-editor/share-link-permission-editor';
import CommonOperationConfirmationDialog from '../../components/dialog/common-operation-confirmation-dialog';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
import ShareLink from '../../models/share-link';
@@ -38,7 +39,7 @@ class LinkDetails extends React.Component {
expireDays: this.props.defaultExpireDays,
expDate: null,
isOpIconShown: false,
isNoticeMessageShow: false,
isLinkDeleteDialogOpen: false,
isSendLinkShown: false
};
}
@@ -129,14 +130,20 @@ class LinkDetails extends React.Component {
});
}
onNoticeMessageToggle = () => {
this.setState({isNoticeMessageShow: !this.state.isNoticeMessageShow});
toggleLinkDeleteDialog = () => {
this.setState({isLinkDeleteDialogOpen: !this.state.isLinkDeleteDialogOpen});
}
toggleSendLink = () => {
this.setState({ isSendLinkShown: !this.state.isSendLinkShown });
}
deleteLink = () => {
const { sharedLinkInfo } = this.props;
const { token } = sharedLinkInfo;
this.props.deleteLink(token);
}
goBack = () => {
this.props.showLinkDetails(null);
}
@@ -235,7 +242,7 @@ class LinkDetails extends React.Component {
</>
)}
</dl>
{(canSendShareLinkEmail && !this.state.isSendLinkShown && !this.state.isNoticeMessageShow) &&
{(canSendShareLinkEmail && !this.state.isSendLinkShown) &&
<Button onClick={this.toggleSendLink} className='mr-2'>{gettext('Send')}</Button>
}
{this.state.isSendLinkShown &&
@@ -246,16 +253,17 @@ class LinkDetails extends React.Component {
closeShareDialog={this.props.closeShareDialog}
/>
}
{(!this.state.isSendLinkShown && !this.state.isNoticeMessageShow) &&
<Button onClick={this.onNoticeMessageToggle}>{gettext('Delete')}</Button>
{(!this.state.isSendLinkShown) &&
<Button onClick={this.toggleLinkDeleteDialog}>{gettext('Delete')}</Button>
}
{this.state.isNoticeMessageShow &&
<div className="alert alert-warning">
<h4 className="alert-heading">{gettext('Are you sure you want to delete the share link?')}</h4>
<p className="mb-4">{gettext('If the share link is deleted, no one will be able to access it any more.')}</p>
<button className="btn btn-primary" onClick={this.props.deleteLink}>{gettext('Delete')}</button>{' '}
<button className="btn btn-secondary" onClick={this.onNoticeMessageToggle}>{gettext('Cancel')}</button>
</div>
{this.state.isLinkDeleteDialogOpen &&
<CommonOperationConfirmationDialog
title={gettext('Delete share link')}
message={gettext('Are you sure you want to delete the share link?')}
executeOperation={this.deleteLink}
confirmBtnText={gettext('Delete')}
toggleDialog={this.toggleLinkDeleteDialog}
/>
}
</div>
);

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import copy from 'copy-to-clipboard';
@@ -6,12 +6,14 @@ import toaster from '../toast';
import { isPro, gettext } from '../../utils/constants';
import ShareLinkPermissionEditor from '../../components/select-editor/share-link-permission-editor';
import { Utils } from '../../utils/utils';
import CommonOperationConfirmationDialog from '../../components/dialog/common-operation-confirmation-dialog';
const propTypes = {
item: PropTypes.object.isRequired,
permissionOptions: PropTypes.array,
showLinkDetails : PropTypes.func.isRequired,
toggleSelectLink: PropTypes.func.isRequired
toggleSelectLink: PropTypes.func.isRequired,
deleteLink: PropTypes.func.isRequired
};
class LinkItem extends React.Component {
@@ -19,7 +21,8 @@ class LinkItem extends React.Component {
constructor(props) {
super(props);
this.state = {
isItemOpVisible: false
isItemOpVisible: false,
isDeleteShareLinkDialogOpen: false
};
}
@@ -40,6 +43,10 @@ class LinkItem extends React.Component {
return link.slice(0, 9) + '...' + link.slice(length-5);
}
toggleDeleteShareLinkDialog = () => {
this.setState({isDeleteShareLinkDialogOpen: !this.state.isDeleteShareLinkDialogOpen});
}
copyLink = (e) => {
e.preventDefault();
const { item } = this.props;
@@ -57,36 +64,55 @@ class LinkItem extends React.Component {
this.props.toggleSelectLink(item, e.target.checked);
}
deleteLink = () => {
const { item } = this.props;
this.props.deleteLink(item.token);
}
render() {
const { isItemOpVisible } = this.state;
const { item, permissionOptions } = this.props;
const { isSelected = false, permissions, link, expire_date } = item;
const currentPermission = Utils.getShareLinkPermissionStr(permissions);
return (
<tr onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut} className={isSelected ? 'tr-highlight' : ''}>
<td className="text-center">
<input type="checkbox" checked={isSelected} onChange={this.toggleSelectLink} className="vam" />
</td>
<td>{this.cutLink(link)}</td>
<td>
{(isPro && permissions) && (
<ShareLinkPermissionEditor
isTextMode={true}
isEditIconShow={false}
currentPermission={currentPermission}
permissionOptions={permissionOptions}
onPermissionChanged={() => {}}
/>
)}
</td>
<td>
{expire_date ? moment(expire_date).format('YYYY-MM-DD HH:mm') : '--'}
</td>
<td>
<a href="#" role="button" onClick={this.copyLink} className={`sf2-icon-copy action-icon ${isItemOpVisible ? '' : 'invisible'}`} title={gettext('Copy')} aria-label={gettext('Copy')}></a>
<a href="#" role="button" onClick={this.viewDetails} className={`fas fa-info-circle font-weight-bold action-icon ${isItemOpVisible ? '' : 'invisible'}`} title={gettext('Details')} aria-label={gettext('Details')}></a>
</td>
</tr>
<Fragment>
<tr onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut} className={isSelected ? 'tr-highlight' : ''}>
<td className="text-center">
<input type="checkbox" checked={isSelected} onChange={this.toggleSelectLink} className="vam" />
</td>
<td>
<a href="#" onClick={this.viewDetails} className="text-inherit">{this.cutLink(link)}</a>
</td>
<td>
{(isPro && permissions) && (
<ShareLinkPermissionEditor
isTextMode={true}
isEditIconShow={false}
currentPermission={currentPermission}
permissionOptions={permissionOptions}
onPermissionChanged={() => {}}
/>
)}
</td>
<td>
{expire_date ? moment(expire_date).format('YYYY-MM-DD HH:mm') : '--'}
</td>
<td>
<a href="#" role="button" onClick={this.copyLink} className={`sf2-icon-copy action-icon ${isItemOpVisible ? '' : 'invisible'}`} title={gettext('Copy')} aria-label={gettext('Copy')}></a>
<a href="#" role="button" onClick={this.viewDetails} className={`fa fa-pencil-alt attr-action-icon ${isItemOpVisible ? '' : 'invisible'}`} title={gettext('Edit')} aria-label={gettext('Edit')}></a>
<a href="#" role="button" onClick={this.toggleDeleteShareLinkDialog} className={`sf2-icon-delete action-icon ${isItemOpVisible ? '' : 'invisible'}`} title={gettext('Delete')} aria-label={gettext('Delete')}></a>
</td>
</tr>
{this.state.isDeleteShareLinkDialogOpen && (
<CommonOperationConfirmationDialog
title={gettext('Delete share link')}
message={gettext('Are you sure you want to delete the share link?')}
executeOperation={this.deleteLink}
confirmBtnText={gettext('Delete')}
toggleDialog={this.toggleDeleteShareLinkDialog}
/>
)}
</Fragment>
);
}
}

View File

@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { gettext, siteRoot } from '../../utils/constants';
import EmptyTip from '../empty-tip';
import LinkItem from './link-item';
import CommonOperationConfirmationDialog from '../../components/dialog/common-operation-confirmation-dialog';
const propTypes = {
shareLinks: PropTypes.array.isRequired,
@@ -10,11 +11,24 @@ const propTypes = {
setMode: PropTypes.func.isRequired,
showLinkDetails: PropTypes.func.isRequired,
toggleSelectAllLinks: PropTypes.func.isRequired,
toggleSelectLink: PropTypes.func.isRequired
toggleSelectLink: PropTypes.func.isRequired,
deleteLink: PropTypes.func.isRequired,
deleteShareLinks: PropTypes.func.isRequired
};
class LinkList extends React.Component {
constructor(props) {
super(props);
this.state = {
isDeleteShareLinksDialogOpen: false
};
}
toggleDeleteShareLinksDialog = () => {
this.setState({isDeleteShareLinksDialogOpen: !this.state.isDeleteShareLinksDialogOpen});
}
toggleSelectAllLinks = (e) => {
this.props.toggleSelectAllLinks(e.target.checked);
}
@@ -40,7 +54,7 @@ class LinkList extends React.Component {
<Fragment>
<div className="d-flex justify-content-between align-items-center pb-2 border-bottom">
<h6 className="font-weight-normal m-0">{gettext('Share Link')}</h6>
<div>
<div className="d-flex">
{selectedLinks.length == 0 ? (
<>
<button className="btn btn-sm btn-outline-primary mr-2" onClick={this.props.setMode.bind(this, 'singleLinkCreation')}>{gettext('Generate Link')}</button>
@@ -49,7 +63,8 @@ class LinkList extends React.Component {
) : (
<>
<button className="btn btn-sm btn-secondary mr-2" onClick={this.cancelSelectAllLinks}>{gettext('Cancel')}</button>
<button className="btn btn-sm btn-primary" onClick={this.exportSelectedLinks}>{gettext('Export')}</button>
<button className="btn btn-sm btn-secondary mr-2" onClick={this.toggleDeleteShareLinksDialog}>{gettext('Delete')}</button>
<button className="btn btn-sm btn-secondary" onClick={this.exportSelectedLinks}>{gettext('Export')}</button>
</>
)}
</div>
@@ -67,8 +82,8 @@ class LinkList extends React.Component {
</th>
<th width="23%">{gettext('Link')}</th>
<th width="30%">{gettext('Permission')}</th>
<th width="28%">{gettext('Expiration')}</th>
<th width="14%"></th>
<th width="24%">{gettext('Expiration')}</th>
<th width="18%"></th>
</tr>
</thead>
<tbody>
@@ -80,12 +95,22 @@ class LinkList extends React.Component {
permissionOptions={permissionOptions}
showLinkDetails={this.props.showLinkDetails}
toggleSelectLink={this.props.toggleSelectLink}
deleteLink={this.props.deleteLink}
/>
);
})}
</tbody>
</table>
)}
{this.state.isDeleteShareLinksDialogOpen && (
<CommonOperationConfirmationDialog
title={gettext('Delete share links')}
message={gettext('Are you sure you want to delete the selected share link(s) ?')}
executeOperation={this.props.deleteShareLinks}
confirmBtnText={gettext('Delete')}
toggleDialog={this.toggleDeleteShareLinksDialog}
/>
)}
</Fragment>
);
}