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:
@@ -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();
|
||||
}
|
||||
|
@@ -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>
|
||||
|
@@ -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) => {
|
||||
|
@@ -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}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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}
|
||||
/>
|
||||
|
@@ -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>
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
|
Reference in New Issue
Block a user