1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-22 11:57:34 +00:00

Merge branch '7.0'

This commit is contained in:
plt
2019-07-08 18:27:48 +08:00
31 changed files with 477 additions and 143 deletions

View File

@@ -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')}

View File

@@ -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>

View 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;

View File

@@ -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';
});
}

View File

@@ -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() {

View File

@@ -10,6 +10,8 @@ class SharedUploadInfo {
this.ctime = object.ctime;
this.token = object.token;
this.view_cnt = object.view_cnt;
this.expire_date = object.expire_date;
this.is_expired = object.is_expired;
}
}

View File

@@ -20,6 +20,7 @@ import RenameGroupDialog from '../../components/dialog/rename-group-dialog';
import TransferGroupDialog from '../../components/dialog/transfer-group-dialog';
// import ImportMembersDialog from '../../components/dialog/import-members-dialog';
import ManageMembersDialog from '../../components/dialog/manage-members-dialog';
import LeaveGroupDialog from '../../components/dialog/leave-group-dialog';
import SharedRepoListView from '../../components/shared-repo-list-view/shared-repo-list-view';
import LibDetail from '../../components/dirent-detail/lib-details';
@@ -60,6 +61,7 @@ class GroupView extends React.Component {
showManageMembersDialog: false,
groupMembers: [],
isShowDetails: false,
isLeaveGroupDialogOpen: false,
};
}
@@ -307,6 +309,13 @@ class GroupView extends React.Component {
});
}
toggleLeaveGroupDialog = () => {
this.setState({
isLeaveGroupDialogOpen: !this.state.isLeaveGroupDialogOpen,
showGroupDropdown: false,
});
}
listGroupMembers = () => {
seafileAPI.listGroupMembers(this.props.groupID).then((res) => {
this.setState({
@@ -407,7 +416,7 @@ class GroupView extends React.Component {
)}
</div>
<div className="path-tool">
{ (isShowSettingIcon && this.state.isStaff) &&
{ isShowSettingIcon &&
<React.Fragment>
<a href="#" className="sf2-icon-cog1 action-icon group-top-action-icon" title="Settings" id="settings"
onClick={this.toggleGroupDropdown}></a>
@@ -419,6 +428,7 @@ class GroupView extends React.Component {
onClick={this.toggleGroupDropdown}></a>
</div>
<div className="sf-popover-con">
{(this.state.isStaff || this.state.isOwner) &&
<ul className="sf-popover-list">
<li><a href="#" className="sf-popover-item" onClick={this.toggleRenameGroupDialog} >{gettext('Rename')}</a></li>
{
@@ -426,16 +436,25 @@ class GroupView extends React.Component {
<li><a href="#" className="sf-popover-item" onClick={this.toggleTransferGroupDialog} >{gettext('Transfer')}</a></li>
}
</ul>
}
{(this.state.isStaff || this.state.isOwner) &&
<ul className="sf-popover-list">
{/* <li><a href="#" className="sf-popover-item" onClick={this.toggleImportMembersDialog} >{gettext('Import Members')}</a></li> */}
<li><a href="#" className="sf-popover-item" onClick={this.toggleManageMembersDialog} >{gettext('Manage Members')}</a></li>
</ul>
}
{
this.state.isOwner &&
<ul className="sf-popover-list">
<li><a href="#" className="sf-popover-item" onClick={this.toggleDismissGroupDialog}>{gettext('Delete Group')}</a></li>
</ul>
}
{/* gourp owner only can dissmiss group, admin could not quit, department member could not quit */}
{(!this.state.isOwner && !this.state.isStaff && !isDepartmentGroup) &&
<ul className="sf-popover-list">
<li><a href="#" className="sf-popover-item" onClick={this.toggleLeaveGroupDialog}>{gettext('Leave Group')}</a></li>
</ul>
}
</div>
</Popover>
</React.Fragment>
@@ -557,6 +576,13 @@ class GroupView extends React.Component {
isOwner={this.state.isOwner}
/>
}
{this.state.isLeaveGroupDialogOpen &&
<LeaveGroupDialog
toggleLeaveGroupDialog={this.toggleLeaveGroupDialog}
groupID={this.props.groupID}
onGroupChanged={this.props.onGroupChanged}
/>
}
</Fragment>
);
}

View File

@@ -1,5 +1,6 @@
import React, { Component } from 'react';
import React, { Component, Fragment } from 'react';
import { Link } from '@reach/router';
import moment from 'moment';
import { Modal, ModalHeader, ModalBody } from 'reactstrap';
import { gettext, siteRoot, loginUrl, canGenerateShareLink } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
@@ -52,9 +53,10 @@ class Content extends Component {
<thead>
<tr>
<th width="4%">{/*icon*/}</th>
<th width="42%">{gettext('Name')}</th>
<th width="30%">{gettext('Library')}</th>
<th width="14%">{gettext('Visits')}</th>
<th width="30%">{gettext('Name')}</th>
<th width="24%">{gettext('Library')}</th>
<th width="16%">{gettext('Visits')}</th>
<th width="16%">{gettext('Expiration')}</th>
<th width="10%">{/*Operations*/}</th>
</tr>
</thead>
@@ -114,6 +116,24 @@ class Item extends Component {
return { iconUrl, uploadUrl };
}
renderExpriedData = () => {
let item = this.props.item;
if (!item.expire_date) {
return (
<Fragment>--</Fragment>
);
}
let expire_date = moment(item.expire_date).format('YYYY-MM-DD');
return (
<Fragment>
{item.is_expired ?
<span className="error">{expire_date}</span> :
expire_date
}
</Fragment>
);
}
render() {
let item = this.props.item;
let { iconUrl, uploadUrl } = this.getUploadParams();
@@ -128,6 +148,7 @@ class Item extends Component {
<td><Link to={uploadUrl}>{item.obj_name}</Link></td>
<td><Link to={`${siteRoot}library/${item.repo_id}/${item.repo_name}`}>{item.repo_name}</Link></td>
<td>{item.view_cnt}</td>
<td>{this.renderExpriedData()}</td>
<td>
<a href="#" className={linkIconClassName} title={gettext('View')} onClick={this.viewLink}></a>
<a href="#" className={deleteIconClassName} title={gettext('Remove')} onClick={this.removeLink}></a>

View File

@@ -5,7 +5,7 @@ import Logo from '../../components/logo';
import { gettext, siteRoot, isPro, isDefaultAdmin, canViewSystemInfo, canViewStatistic,
canConfigSystem, canManageLibrary, canManageUser, canManageGroup, canViewUserLog,
canViewAdminLog, constanceEnabled, multiTenancy, multiInstitution, sysadminExtraEnabled,
enableGuestInvitation, enableTermsAndConditions, enableFileScan, enableWorkWeixinDepartments } from '../../utils/constants';
enableGuestInvitation, enableTermsAndConditions, enableFileScan, enableWorkWeixin } from '../../utils/constants';
const propTypes = {
isSidePanelClosed: PropTypes.bool.isRequired,
@@ -174,7 +174,7 @@ class SidePanel extends React.Component {
</a>
</li>
}
{isDefaultAdmin && enableWorkWeixinDepartments &&
{isDefaultAdmin && enableWorkWeixin &&
<li className="nav-item">
<Link className={`nav-link ellipsis ${this.getActiveClass('departments')}`} to={siteRoot + 'sys/work-weixin/departments/'}>
<span className="sf3-font-enterprise-wechat sf3-font" aria-hidden="true"></span>

View File

@@ -7,6 +7,7 @@ import WikiMarkdownViewer from '../../components/wiki-markdown-viewer';
import WikiDirListView from '../../components/wiki-dir-list-view/wiki-dir-list-view';
import Loading from '../../components/loading';
import { Utils } from '../../utils/utils';
import Search from '../../components/search/search';
const propTypes = {
path: PropTypes.string.isRequired,
@@ -77,6 +78,21 @@ class MainPanel extends Component {
return (
<div className="main-panel wiki-main-panel o-hidden">
<div className="main-panel-north panel-top border-left-show">
{!username &&
<Fragment>
<div className="cur-view-toolbar">
<span className="sf2-icon-menu hidden-md-up d-md-none side-nav-toggle" title="Side Nav Menu" onClick={this.onMenuClick}></span>
</div>
<div className="common-toolbar">
<Search
isPublic={true}
repoID={repoID}
onSearchedClick={this.props.onSearchedClick}
placeholder={gettext('Search files in this library')}
/>
</div>
</Fragment>
}
{username && (
<Fragment>
<div className="cur-view-toolbar">

View File

@@ -119,5 +119,5 @@ export const canManageUser = window.sysadmin ? window.sysadmin.pageOptions.admin
export const canManageGroup = window.sysadmin ? window.sysadmin.pageOptions.admin_permissions.can_manage_group : '';
export const canViewUserLog = window.sysadmin ? window.sysadmin.pageOptions.admin_permissions.can_view_user_log : '';
export const canViewAdminLog = window.sysadmin ? window.sysadmin.pageOptions.admin_permissions.can_view_admin_log : '';
export const enableWorkWeixinDepartments = window.sysadmin ? window.sysadmin.pageOptions.enable_work_weixin_departments : '';
export const enableWorkWeixin = window.sysadmin ? window.sysadmin.pageOptions.enable_work_weixin : '';

View File

@@ -240,7 +240,8 @@ export const Utils = {
isSupportUploadFolder: function() {
return navigator.userAgent.indexOf('Firefox')!=-1 ||
navigator.userAgent.indexOf('Chrome') > -1;
navigator.userAgent.indexOf('Chrome') > -1 ||
navigator.userAgent.indexOf("Safari") > -1;
},
isIEBrower: function() { // is ie <= ie11 not include Edge