1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-02 23:48:47 +00:00

Merge branch '7.1' into master

This commit is contained in:
lian
2020-08-19 14:01:17 +08:00
107 changed files with 13230 additions and 14827 deletions

View File

@@ -6,6 +6,7 @@ import { Utils } from '../../utils/utils';
const propTypes = {
repo: PropTypes.object.isRequired,
isRepoDeleted: PropTypes.bool.isRequired,
toggle: PropTypes.func.isRequired,
onDeleteRepo: PropTypes.func.isRequired,
};
@@ -19,6 +20,12 @@ class DeleteRepoDialog extends Component {
};
}
componentWillReceiveProps(nextProps) {
if (!nextProps.isRepoDeleted) {
this.setState({isRequestSended: false});
}
}
toggle = () => {
this.props.toggle();
}

View File

@@ -1,9 +1,9 @@
import React from 'react';
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import copy from 'copy-to-clipboard';
import moment from 'moment';
import { Button, Form, FormGroup, Label, Input, InputGroup, InputGroupAddon, InputGroupText, Alert } from 'reactstrap';
import { gettext, shareLinkPasswordMinLength, canSendShareLinkEmail } from '../../utils/constants';
import { Button, Form, FormGroup, Label, Input, InputGroup, InputGroupAddon, InputGroupText, Alert, FormText } from 'reactstrap';
import { gettext, shareLinkPasswordMinLength, canSendShareLinkEmail, uploadLinkExpireDaysMin, uploadLinkExpireDaysMax, uploadLinkExpireDaysDefault } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
import UploadLink from '../../models/upload-link';
@@ -22,6 +22,24 @@ const inputWidth = Utils.isDesktop() ? 250 : 210;
class GenerateUploadLink extends React.Component {
constructor(props) {
super(props);
this.isExpireDaysNoLimit = (uploadLinkExpireDaysMin === 0 && uploadLinkExpireDaysMax === 0 && uploadLinkExpireDaysDefault == 0);
this.defaultExpireDays = this.isExpireDaysNoLimit ? '' : uploadLinkExpireDaysDefault;
let expirationLimitTip = '';
if (uploadLinkExpireDaysMin !== 0 && uploadLinkExpireDaysMax !== 0) {
expirationLimitTip = gettext('{minDays_placeholder} - {maxDays_placeholder} days')
.replace('{minDays_placeholder}', uploadLinkExpireDaysMin)
.replace('{maxDays_placeholder}', uploadLinkExpireDaysMax);
} else if (uploadLinkExpireDaysMin !== 0 && uploadLinkExpireDaysMax === 0) {
expirationLimitTip = gettext('Greater than or equal to {minDays_placeholder} days')
.replace('{minDays_placeholder}', uploadLinkExpireDaysMin);
} else if (uploadLinkExpireDaysMin === 0 && uploadLinkExpireDaysMax !== 0) {
expirationLimitTip = gettext('Less than or equal to {maxDays_placeholder} days')
.replace('{maxDays_placeholder}', uploadLinkExpireDaysMax);
}
this.expirationLimitTip = expirationLimitTip;
this.state = {
showPasswordInput: false,
passwordVisible: false,
@@ -29,9 +47,9 @@ class GenerateUploadLink extends React.Component {
passwdnew: '',
sharedUploadInfo: null,
isSendLinkShown: false,
isExpireChecked: false,
isExpireChecked: !this.isExpireDaysNoLimit,
setExp: 'by-days',
expireDays: '',
expireDays: this.defaultExpireDays,
expDate: null
};
}
@@ -177,7 +195,19 @@ class GenerateUploadLink extends React.Component {
return false;
}
return current.isBefore(moment(), 'day');
if (this.isExpireDaysNoLimit) {
return current.isBefore(moment(), 'day');
}
const startDay = moment().add(uploadLinkExpireDaysMin, 'days');
const endDay = moment().add(uploadLinkExpireDaysMax, 'days');
if (uploadLinkExpireDaysMin !== 0 && uploadLinkExpireDaysMax !== 0) {
return current.isBefore(startDay, 'day') || current.isAfter(endDay, 'day');
} else if (uploadLinkExpireDaysMin !== 0 && uploadLinkExpireDaysMax === 0) {
return current.isBefore(startDay, 'day');
} else if (uploadLinkExpireDaysMin === 0 && uploadLinkExpireDaysMax !== 0) {
return current.isBefore(moment(), 'day') || current.isAfter(endDay, 'day');
}
}
onExpDateChanged = (value) => {
@@ -203,6 +233,8 @@ class GenerateUploadLink extends React.Component {
seafileAPI.deleteUploadLink(sharedUploadInfo.token).then(() => {
this.setState({
showPasswordInput: false,
expireDays: this.defaultExpireDays,
isExpireChecked: !this.isExpireDaysNoLimit,
password: '',
passwordnew: '',
sharedUploadInfo: null,
@@ -286,7 +318,11 @@ class GenerateUploadLink extends React.Component {
</FormGroup>
<FormGroup check>
<Label check>
<Input type="checkbox" onChange={this.onExpireChecked} />
{this.isExpireDaysNoLimit ? (
<Input type="checkbox" onChange={this.onExpireChecked} />
) : (
<Input type="checkbox" checked readOnly disabled />
)}
<span>{gettext('Add auto expiration')}</span>
</Label>
{this.state.isExpireChecked &&
@@ -297,12 +333,17 @@ class GenerateUploadLink extends React.Component {
<span>{gettext('Expiration days')}</span>
</Label>
{this.state.setExp == 'by-days' && (
<InputGroup style={{width: inputWidth}}>
<Input type="text" value={this.state.expireDays} onChange={this.onExpireDaysChanged} />
<InputGroupAddon addonType="append">
<InputGroupText>{gettext('days')}</InputGroupText>
</InputGroupAddon>
</InputGroup>
<Fragment>
<InputGroup style={{width: inputWidth}}>
<Input type="text" value={this.state.expireDays} onChange={this.onExpireDaysChanged} />
<InputGroupAddon addonType="append">
<InputGroupText>{gettext('days')}</InputGroupText>
</InputGroupAddon>
</InputGroup>
{!this.state.isExpireDaysNoLimit && (
<FormText color="muted">{this.expirationLimitTip}</FormText>
)}
</Fragment>
)}
</FormGroup>
<FormGroup check>

View File

@@ -8,6 +8,7 @@ import { Utils } from '../../utils/utils';
const propTypes = {
sharedToken: PropTypes.string.isRequired,
filePath: PropTypes.string,
toggleCancel: PropTypes.func.isRequired,
handleSaveSharedFile: PropTypes.func.isRequired,
};
@@ -24,7 +25,8 @@ class SaveSharedFileDialog extends React.Component {
}
onSaveSharedFile = () => {
seafileAPI.saveSharedFile(this.state.repo.repo_id, this.state.selectedPath, this.props.sharedToken).then((res) => {
const { sharedToken, filePath } = this.props;
seafileAPI.saveSharedFile(this.state.repo.repo_id, this.state.selectedPath, sharedToken, filePath).then((res) => {
this.props.toggleCancel();
this.props.handleSaveSharedFile();
}).catch((error) => {

View File

@@ -9,12 +9,15 @@ import UserSelect from '../user-select';
import SharePermissionEditor from '../select-editor/share-permission-editor';
import '../../css/invitations.css';
import '../../css/share-to-user.css';
class UserItem extends React.Component {
constructor(props) {
super(props);
this.state = {
isOperationShow: false
isOperationShow: false,
isUserDetailsPopoverOpen: false
};
}
@@ -26,11 +29,19 @@ class UserItem extends React.Component {
this.setState({isOperationShow: false});
}
userAvatarOnMouseEnter = () => {
this.setState({isUserDetailsPopoverOpen: true});
}
userAvatarOnMouseLeave = () => {
this.setState({isUserDetailsPopoverOpen: false});
}
deleteShareItem = () => {
let item = this.props.item;
this.props.deleteShareItem(item.user_info.name);
}
onChangeUserPermission = (permission) => {
let item = this.props.item;
this.props.onChangeUserPermission(item, permission);
@@ -39,9 +50,33 @@ class UserItem extends React.Component {
render() {
let item = this.props.item;
let currentPermission = item.is_admin ? 'admin' : item.permission;
const { isUserDetailsPopoverOpen } = this.state;
return (
<tr onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
<td className="name">{item.user_info.nickname}</td>
<td className="name">
<div className="position-relative">
<img src={item.user_info.avatar_url}
width="24" alt={item.user_info.nickname}
className="rounded-circle mr-2 cursor-pointer"
onMouseEnter={this.userAvatarOnMouseEnter}
onMouseLeave={this.userAvatarOnMouseLeave} />
<span>{item.user_info.nickname}</span>
{isUserDetailsPopoverOpen && (
<div className="user-details-popover p-4 position-absolute w-100 mt-1">
<div className="user-details-main pb-3">
<img src={item.user_info.avatar_url} width="40"
alt={item.user_info.nickname}
className="rounded-circle mr-2" />
<span className="user-details-name">{item.user_info.nickname}</span>
</div>
<dl className="m-0 mt-3 d-flex">
<dt className="m-0 mr-3">{gettext('Email')}</dt>
<dd className="m-0">{item.user_info.contact_email}</dd>
</dl>
</div>
)}
</div>
</td>
<td>
<SharePermissionEditor
isTextMode={true}

View File

@@ -65,7 +65,7 @@ class FileToolbar extends React.Component {
let showShareBtn = false;
if (repoEncrypted) {
showShareBtn = true; // for internal link
} else if ((filePerm == 'rw' || filePerm == 'r') && canGenerateShareLink) {
} else if (filePerm == 'rw' || filePerm == 'r') {
showShareBtn = true;
}

View File

@@ -135,8 +135,8 @@ class SharedFileView extends React.Component {
</div>
{this.state.showSaveSharedFileDialog &&
<SaveSharedFileDialog
repoID={repoID}
sharedToken={sharedToken}
filePath={zipped ? filePath : ''}
toggleCancel={this.toggleCancel}
handleSaveSharedFile={this.handleSaveSharedFile}
/>

View File

@@ -44,7 +44,8 @@ class SharedRepoListItem extends React.Component {
isHistorySettingDialogShow: false,
isDeleteDialogShow: false,
isAPITokenDialogShow: false,
isRepoShareUploadLinksDialogOpen: false
isRepoShareUploadLinksDialogOpen: false,
isRepoDeleted: false,
};
this.isDeparementOnwerGroupMember = false;
}
@@ -187,6 +188,37 @@ class SharedRepoListItem extends React.Component {
this.setState({isDeleteDialogShow: !this.state.isDeleteDialogShow});
}
onItemDelete = () => {
const { currentGroup, repo } = this.props;
if (!currentGroup) { // repo can not be deleted in share all module
return;
}
const groupID = currentGroup.id;
seafileAPI.deleteGroupOwnedLibrary(groupID, repo.repo_id).then(() => {
this.setState({
isRepoDeleted: true,
isDeleteDialogShow: false,
});
this.props.onItemDelete(repo);
let name = repo.repo_name;
var msg = gettext('Successfully deleted {name}.').replace('{name}', name);
toaster.success(msg);
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
if (errMessage === gettext('Error')) {
let name = repo.repo_name;
errMessage = gettext('Failed to delete {name}.').replace('{name}', name);
}
toaster.danger(errMessage);
this.setState({isRepoDeleted: false});
});
}
toggleShareDialog = () => {
this.setState({isShowSharedDialog: false});
}
@@ -475,7 +507,8 @@ class SharedRepoListItem extends React.Component {
<ModalPortal>
<DeleteRepoDialog
repo={this.props.repo}
onDeleteRepo={this.props.onItemDelete}
isRepoDeleted={this.state.isRepoDeleted}
onDeleteRepo={this.onItemDelete}
toggle={this.onItemDeleteToggle}
/>
</ModalPortal>

View File

@@ -1,10 +1,11 @@
import React, {Fragment} from 'react';
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { gettext } from '../../utils/constants';
import { Utils } from '../../utils/utils';
import SharedRepoListItem from './shared-repo-list-item';
import toaster from '../toast';
import LibsMobileThead from '../libs-mobile-thead';
import Loading from '../loading';
const propTypes = {
libraryType: PropTypes.string,
@@ -15,9 +16,10 @@ const propTypes = {
sortItems: PropTypes.func,
repoList: PropTypes.array.isRequired,
onItemUnshare: PropTypes.func.isRequired,
onItemDelete: PropTypes.func.isRequired,
onItemDelete: PropTypes.func,
onItemDetails: PropTypes.func,
onItemRename: PropTypes.func,
hasNextPage: PropTypes.bool
};
class SharedRepoListView extends React.Component {
@@ -141,10 +143,16 @@ class SharedRepoListView extends React.Component {
}
render() {
if (Utils.isDesktop()) {
return this.renderPCUI();
const table = Utils.isDesktop() ? this.renderPCUI() : this.renderMobileUI();
if (this.props.hasNextPage) {
return (
<Fragment>
{table}
<Loading />
</Fragment>
);
} else {
return this.renderMobileUI();
return table;
}
}
}

View File

@@ -2,10 +2,12 @@ import React from 'react';
import PropTypes from 'prop-types';
import AsyncSelect from 'react-select/lib/Async';
import { seafileAPI } from '../utils/seafile-api.js';
import { gettext } from '../utils/constants';
import { gettext, enableShowContactEmailWhenSearchUser } from '../utils/constants';
import { Utils } from '../utils/utils.js';
import toaster from './toast';
import '../css/user-select.css';
const propTypes = {
placeholder: PropTypes.string.isRequired,
onSelectChange: PropTypes.func.isRequired,
@@ -44,10 +46,19 @@ class UserSelect extends React.Component {
obj.value = item.name;
obj.email = item.email;
obj.label =
<React.Fragment>
<img src={item.avatar_url} className="select-module select-module-icon avatar" alt=""/>
<span className='select-module select-module-name'>{item.name}</span>
</React.Fragment>;
enableShowContactEmailWhenSearchUser ? (
<div className="d-flex">
<img src={item.avatar_url} className="avatar" width="24" alt="" />
<div className="ml-2">
<span className="user-option-name">{item.name}</span><br />
<span className="user-option-email">{item.contact_email}</span>
</div>
</div>
) : (
<React.Fragment>
<img src={item.avatar_url} className="select-module select-module-icon avatar" alt=""/>
<span className='select-module select-module-name'>{item.name}</span>
</React.Fragment>);
this.options.push(obj);
}
callback(this.options);