mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-13 22:01:06 +00:00
Merge branch '7.0'
This commit is contained in:
@@ -41,6 +41,7 @@ class GenerateShareLink extends React.Component {
|
||||
'can_download': true
|
||||
};
|
||||
this.isExpireDaysNoLimit = (parseInt(shareLinkExpireDaysMin) === 0 && parseInt(shareLinkExpireDaysMax) === 0);
|
||||
this.isOfficeFile = Utils.isOfficeFile(this.props.itemPath);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -57,12 +58,13 @@ class GenerateShareLink extends React.Component {
|
||||
this.setState({isLoading: false});
|
||||
}
|
||||
});
|
||||
|
||||
seafileAPI.getFileInfo(repoID, path).then((res) => {
|
||||
if (res.data) {
|
||||
this.setState({fileInfo: res.data});
|
||||
}
|
||||
});
|
||||
if (this.isOfficeFile) {
|
||||
seafileAPI.getFileInfo(repoID, path).then((res) => {
|
||||
if (res.data) {
|
||||
this.setState({fileInfo: res.data});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onPasswordInputChecked = () => {
|
||||
@@ -402,7 +404,7 @@ class GenerateShareLink extends React.Component {
|
||||
<Input type="radio" name="radio1" onChange={() => this.setPermission('preview')} />{' '}{gettext('Preview only')}
|
||||
</Label>
|
||||
</FormGroup>
|
||||
{(Utils.isOfficeFile(this.props.itemPath) && fileInfo && fileInfo.can_edit) &&
|
||||
{(this.isOfficeFile && fileInfo && fileInfo.can_edit) &&
|
||||
<FormGroup check className="permission">
|
||||
<Label className="form-check-label">
|
||||
<Input type="radio" name="radio1" onChange={() => this.setPermission('editOnCloudAndDownload')} />{' '}{gettext('Edit on cloud and download')}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import React 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, Alert } from 'reactstrap';
|
||||
import { gettext, shareLinkPasswordMinLength, canSendShareLinkEmail } from '../../utils/constants';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
@@ -25,7 +26,9 @@ class GenerateUploadLink extends React.Component {
|
||||
password: '',
|
||||
passwdnew: '',
|
||||
sharedUploadInfo: null,
|
||||
isSendLinkShown: false
|
||||
isSendLinkShown: false,
|
||||
isExpireChecked: false,
|
||||
expireDays: 0,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -90,29 +93,61 @@ class GenerateUploadLink extends React.Component {
|
||||
generateUploadLink = () => {
|
||||
let path = this.props.itemPath;
|
||||
let repoID = this.props.repoID;
|
||||
let { password, expireDays } = this.state;
|
||||
|
||||
if (this.state.showPasswordInput && (this.state.password == '')) {
|
||||
this.setState({
|
||||
errorInfo: gettext('Please enter password')
|
||||
});
|
||||
}
|
||||
else if (this.state.showPasswordInput && (this.state.showPasswordInput && this.state.password.length < shareLinkPasswordMinLength)) {
|
||||
this.setState({
|
||||
errorInfo: gettext('Password is too short')
|
||||
});
|
||||
}
|
||||
else if (this.state.showPasswordInput && (this.state.password !== this.state.passwordnew)) {
|
||||
this.setState({
|
||||
errorInfo: gettext('Passwords don\'t match')
|
||||
});
|
||||
} else {
|
||||
seafileAPI.createUploadLink(repoID, path, this.state.password).then((res) => {
|
||||
let isValid = this.validateParamsInput();
|
||||
if (isValid) {
|
||||
seafileAPI.createUploadLink(repoID, path, password, expireDays).then((res) => {
|
||||
let sharedUploadInfo = new SharedUploadInfo(res.data);
|
||||
this.setState({sharedUploadInfo: sharedUploadInfo});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
validateParamsInput = () => {
|
||||
let { showPasswordInput , password, passwordnew, isExpireChecked, expireDays } = this.state;
|
||||
|
||||
// check password params
|
||||
if (showPasswordInput) {
|
||||
if (password.length === 0) {
|
||||
this.setState({errorInfo: gettext('Please enter password')});
|
||||
return false;
|
||||
}
|
||||
if (password.length < shareLinkPasswordMinLength) {
|
||||
this.setState({errorInfo: gettext('Password is too short')});
|
||||
return false;
|
||||
}
|
||||
if (password !== passwordnew) {
|
||||
this.setState({errorInfo: gettext('Passwords don\'t match')});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// check expire day params
|
||||
let reg = /^\d+$/;
|
||||
if (isExpireChecked) {
|
||||
if (!expireDays) {
|
||||
this.setState({errorInfo: gettext('Please enter days')});
|
||||
return false;
|
||||
}
|
||||
if (!reg.test(expireDays)) {
|
||||
this.setState({errorInfo: gettext('Please enter a non-negative integer')});
|
||||
return false;
|
||||
}
|
||||
this.setState({expireDays: parseInt(expireDays)});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
onExpireChecked = (e) => {
|
||||
this.setState({isExpireChecked: e.target.checked});
|
||||
}
|
||||
|
||||
onExpireDaysChanged = (e) => {
|
||||
let day = e.target.value.trim();
|
||||
this.setState({expireDays: day});
|
||||
}
|
||||
|
||||
onCopyUploadLink = () => {
|
||||
let uploadLink = this.state.sharedUploadInfo.link;
|
||||
copy(uploadLink);
|
||||
@@ -156,6 +191,12 @@ class GenerateUploadLink extends React.Component {
|
||||
<span className="far fa-copy action-icon" onClick={this.onCopyUploadLink}></span>
|
||||
</dd>
|
||||
</FormGroup>
|
||||
{sharedUploadInfo.expire_date && (
|
||||
<FormGroup className="mb-0">
|
||||
<dt className="text-secondary font-weight-normal">{gettext('Expiration Date:')}</dt>
|
||||
<dd>{moment(sharedUploadInfo.expire_date).format('YYYY-MM-DD hh:mm:ss')}</dd>
|
||||
</FormGroup>
|
||||
)}
|
||||
</Form>
|
||||
{canSendShareLinkEmail && !isSendLinkShown && <Button onClick={this.toggleSendLink} className="mr-2">{gettext('Send')}</Button>}
|
||||
{!isSendLinkShown && <Button onClick={this.deleteUploadLink}>{gettext('Delete')}</Button>}
|
||||
@@ -192,6 +233,18 @@ class GenerateUploadLink extends React.Component {
|
||||
<Input className="passwd" type={this.state.passwordVisible ? 'text' : 'password'} value={this.state.passwordnew || ''} onChange={this.inputPasswordNew} />
|
||||
</FormGroup>
|
||||
}
|
||||
<FormGroup check>
|
||||
<Label check>
|
||||
<Input className="expire-checkbox" type="checkbox" onChange={this.onExpireChecked}/>{' '}{gettext('Add auto expiration')}
|
||||
</Label>
|
||||
</FormGroup>
|
||||
{this.state.isExpireChecked &&
|
||||
<FormGroup check>
|
||||
<Label check>
|
||||
<Input className="expire-input expire-input-border" type="text" value={this.state.expireDays} onChange={this.onExpireDaysChanged} readOnly={!this.state.isExpireChecked}/><span className="expir-span">{gettext('days')}</span>
|
||||
</Label>
|
||||
</FormGroup>
|
||||
}
|
||||
{this.state.errorInfo && <Alert color="danger" className="mt-2">{this.state.errorInfo}</Alert>}
|
||||
<Button className="generate-link-btn" onClick={this.generateUploadLink}>{gettext('Generate')}</Button>
|
||||
</Form>
|
||||
|
43
frontend/src/components/dialog/leave-group-dialog.js
Normal file
43
frontend/src/components/dialog/leave-group-dialog.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { gettext, username } from '../../utils/constants';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { Modal, ModalHeader, ModalBody, ModalFooter, Button } from 'reactstrap';
|
||||
|
||||
class LeaveGroupDialog extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
leaveGroup = () => {
|
||||
seafileAPI.quitGroup(this.props.groupID, username).then((res)=> {
|
||||
this.props.onGroupChanged();
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return(
|
||||
<Modal isOpen={true} toggle={this.props.toggleLeaveGroupDialog}>
|
||||
<ModalHeader toggle={this.props.toggleLeaveGroupDialog}>{gettext('Leave Group')}</ModalHeader>
|
||||
<ModalBody>
|
||||
<p>{gettext('Really want to leave this group?')}</p>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="secondary" onClick={this.props.toggleLeaveGroupDialog}>{gettext('Cancel')}</Button>
|
||||
<Button color="primary" onClick={this.leaveGroup}>{gettext('Leave')}</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const LeaveGroupDialogPropTypes = {
|
||||
toggleLeaveGroupDialog: PropTypes.func.isRequired,
|
||||
groupID: PropTypes.string.isRequired,
|
||||
onGroupChanged: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
LeaveGroupDialog.propTypes = LeaveGroupDialogPropTypes;
|
||||
|
||||
export default LeaveGroupDialog;
|
@@ -128,43 +128,31 @@ class FileUploader extends React.Component {
|
||||
this.resumable.on('dragstart', this.onDragStart.bind(this));
|
||||
}
|
||||
|
||||
onChunkingComplete = (file) => {
|
||||
if (file.relativePath !== file.fileName) {
|
||||
return; // is upload a folder;
|
||||
}
|
||||
if (enableResumableFileUpload) {
|
||||
let repoID = this.props.repoID;
|
||||
seafileAPI.getFileUploadedBytes(repoID, this.props.path, file.fileName).then(res => {
|
||||
let uploadedBytes = res.data.uploadedBytes;
|
||||
let offset = Math.floor(uploadedBytes / (1024 * 1024));
|
||||
file.markChunksCompleted(offset);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onFileAdded = (resumableFile, files) => {
|
||||
//get parent_dir、relative_path;
|
||||
onChunkingComplete = (resumableFile) => {
|
||||
//get parent_dir relative_path
|
||||
let path = this.props.path === '/' ? '/' : this.props.path + '/';
|
||||
let fileName = resumableFile.fileName;
|
||||
let relativePath = resumableFile.relativePath;
|
||||
let isFile = fileName === relativePath;
|
||||
|
||||
//update formdata;
|
||||
//update formdata
|
||||
resumableFile.formData = {};
|
||||
if (isFile) {
|
||||
if (isFile) { // upload file
|
||||
resumableFile.formData = {
|
||||
parent_dir: path,
|
||||
};
|
||||
} else {
|
||||
} else { // upload folder
|
||||
let relative_path = relativePath.slice(0, relativePath.lastIndexOf('/') + 1);
|
||||
resumableFile.formData = {
|
||||
parent_dir: path,
|
||||
relative_path: relative_path
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
//check repetition
|
||||
//uploading is file and only upload one file
|
||||
onFileAdded = (resumableFile, files) => {
|
||||
let isFile = resumableFile.fileName === resumableFile.relativePath;
|
||||
// uploading is file and only upload one file
|
||||
if (isFile && files.length === 1) {
|
||||
let hasRepetition = false;
|
||||
let direntList = this.props.direntList;
|
||||
@@ -181,14 +169,28 @@ class FileUploader extends React.Component {
|
||||
});
|
||||
} else {
|
||||
this.setUploadFileList(this.resumable.files);
|
||||
this.resumableUpload(resumableFile);
|
||||
}
|
||||
} else {
|
||||
this.setUploadFileList(this.resumable.files);
|
||||
if (isFile) {
|
||||
this.resumableUpload(resumableFile);
|
||||
} else {
|
||||
this.resumable.upload();
|
||||
}
|
||||
} else {
|
||||
this.setUploadFileList(this.resumable.files);
|
||||
this.resumable.upload();
|
||||
}
|
||||
}
|
||||
|
||||
resumableUpload = (resumableFile) => {
|
||||
let { repoID, path } = this.props;
|
||||
seafileAPI.getFileUploadedBytes(repoID, path, resumableFile.fileName).then(res => {
|
||||
let uploadedBytes = res.data.uploadedBytes;
|
||||
let offset = Math.floor(uploadedBytes / (1024 * 1024));
|
||||
resumableFile.markChunksCompleted(offset);
|
||||
this.resumable.upload();
|
||||
});
|
||||
}
|
||||
|
||||
filesAddedComplete = (resumable, files) => {
|
||||
// single file uploading can check repetition, because custom dialog conn't prevent program execution;
|
||||
}
|
||||
@@ -416,7 +418,7 @@ class FileUploader extends React.Component {
|
||||
this.uploadInput.current.removeAttribute('webkitdirectory');
|
||||
let repoID = this.props.repoID;
|
||||
seafileAPI.getUploadLink(repoID, this.props.path).then(res => {
|
||||
this.resumable.opts.target = res.data;
|
||||
this.resumable.opts.target = res.data + '?ret-json=1';
|
||||
if (Utils.isIEBrower()) {
|
||||
this.uploadInput.current.click();
|
||||
}
|
||||
@@ -430,7 +432,7 @@ class FileUploader extends React.Component {
|
||||
this.uploadInput.current.setAttribute('webkitdirectory', 'webkitdirectory');
|
||||
let repoID = this.props.repoID;
|
||||
seafileAPI.getUploadLink(repoID, this.props.path).then(res => {
|
||||
this.resumable.opts.target = res.data;
|
||||
this.resumable.opts.target = res.data + '?ret-json=1';
|
||||
if (Utils.isIEBrower()) {
|
||||
this.uploadInput.current.click();
|
||||
}
|
||||
@@ -444,7 +446,7 @@ class FileUploader extends React.Component {
|
||||
let repoID = this.props.repoID;
|
||||
this.uploadInput.current.setAttribute('webkitdirectory', 'webkitdirectory');
|
||||
seafileAPI.getUploadLink(repoID, this.props.path).then(res => {
|
||||
this.resumable.opts.target = res.data;
|
||||
this.resumable.opts.target = res.data + '?ret-json=1';
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -1,12 +1,14 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import MediaQuery from 'react-responsive';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { gettext, siteRoot } from '../../utils/constants';
|
||||
import SearchResultItem from './search-result-item';
|
||||
import editorUtilities from '../../utils/editor-utilties';
|
||||
import More from '../more';
|
||||
|
||||
const propTypes = {
|
||||
isPublic: PropTypes.bool,
|
||||
repoID: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
onSearchedClick: PropTypes.func.isRequired,
|
||||
@@ -93,27 +95,53 @@ class Search extends Component {
|
||||
|
||||
sendRequest(queryData, cancelToken) {
|
||||
var _this = this;
|
||||
editorUtilities.searchFiles(queryData,cancelToken).then(res => {
|
||||
if (!res.data.total) {
|
||||
let isPublic = this.props.isPublic;
|
||||
|
||||
if (isPublic) {
|
||||
seafileAPI.searchFilesInPublishedRepo(queryData.q, queryData.search_repo).then(res => {
|
||||
if (!res.data.total) {
|
||||
_this.setState({
|
||||
resultItems: [],
|
||||
isResultGetted: true
|
||||
});
|
||||
_this.source = null;
|
||||
return;
|
||||
}
|
||||
|
||||
let items = _this.formatResultItems(res.data.results);
|
||||
_this.setState({
|
||||
resultItems: [],
|
||||
resultItems: items,
|
||||
isResultGetted: true
|
||||
});
|
||||
_this.source = null;
|
||||
return;
|
||||
}
|
||||
|
||||
let items = _this.formatResultItems(res.data.results);
|
||||
_this.setState({
|
||||
resultItems: items,
|
||||
isResultGetted: true
|
||||
}).catch(res => {
|
||||
/* eslint-disable */
|
||||
console.log(res);
|
||||
/* eslint-enable */
|
||||
});
|
||||
_this.source = null;
|
||||
}).catch(res => {
|
||||
/* eslint-disable */
|
||||
console.log(res);
|
||||
/* eslint-enable */
|
||||
});
|
||||
} else {
|
||||
editorUtilities.searchFiles(queryData,cancelToken).then(res => {
|
||||
if (!res.data.total) {
|
||||
_this.setState({
|
||||
resultItems: [],
|
||||
isResultGetted: true
|
||||
});
|
||||
_this.source = null;
|
||||
return;
|
||||
}
|
||||
|
||||
let items = _this.formatResultItems(res.data.results);
|
||||
_this.setState({
|
||||
resultItems: items,
|
||||
isResultGetted: true
|
||||
});
|
||||
_this.source = null;
|
||||
}).catch(res => {
|
||||
/* eslint-disable */
|
||||
console.log(res);
|
||||
/* eslint-enable */
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
cancelRequest() {
|
||||
|
Reference in New Issue
Block a user