mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-01 15:09:14 +00:00
Merge branch '7.1' into master
This commit is contained in:
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { siteRoot, gettext, appAvatarURL } from '../../utils/constants';
|
||||
import { siteRoot, gettext, appAvatarURL, logoutUrl } from '../../utils/constants';
|
||||
import toaster from '../toast';
|
||||
|
||||
const propTypes = {
|
||||
@@ -164,7 +164,7 @@ class Account extends Component {
|
||||
</div>
|
||||
<a href={siteRoot + 'profile/'} className="item">{gettext('Settings')}</a>
|
||||
{this.renderMenu()}
|
||||
<a href={siteRoot + 'accounts/logout/'} className="item">{gettext('Log out')}</a>
|
||||
<a href={logoutUrl} className="item">{gettext('Log out')}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import { siteRoot, gettext } from '../../utils/constants';
|
||||
import { gettext, logoutUrl } from '../../utils/constants';
|
||||
|
||||
export default function Logout() {
|
||||
return (
|
||||
<a className="logout-icon" href={`${siteRoot}accounts/logout/`} title={gettext('Log out')}>
|
||||
<a className="logout-icon" href={logoutUrl} title={gettext('Log out')}>
|
||||
<i className="sf3-font sf3-font-logout" style={{fontSize: '24px'}}></i>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -43,7 +43,7 @@ class AboutDialog extends React.Component {
|
||||
<button type="button" className="close" onClick={this.toggle}><span aria-hidden="true">×</span></button>
|
||||
<div className="about-content">
|
||||
<p><img src={mediaUrl + logoPath} height={logoHeight} width={logoWidth} title={siteTitle} alt="logo" /></p>
|
||||
<p>{gettext('Server Version: ')}{seafileVersion}<br />© 2020 {gettext('Seafile')}</p>
|
||||
<p>{gettext('Server Version: ')}{seafileVersion}<br />© {(new Date()).getFullYear()} {gettext('Seafile')}</p>
|
||||
<p>{this.renderExternalAboutLinks()}</p>
|
||||
<p><a href={href} target="_blank">{gettext('About Us')}</a></p>
|
||||
</div>
|
||||
|
@@ -1,12 +1,14 @@
|
||||
import React from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Table } from 'reactstrap';
|
||||
import { seafileAPI } from '../../utils/seafile-api.js';
|
||||
import RoleEditor from '../select-editor/role-editor';
|
||||
import UserSelect from '../user-select.js';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import RoleEditor from '../select-editor/role-editor';
|
||||
import UserSelect from '../user-select';
|
||||
import toaster from '../toast';
|
||||
import Loading from '../loading';
|
||||
|
||||
import '../../css/manage-members-dialog.css';
|
||||
|
||||
const propTypes = {
|
||||
@@ -21,13 +23,45 @@ class ManageMembersDialog extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isLoading: true, // first loading
|
||||
isLoadingMore: false,
|
||||
groupMembers: [],
|
||||
page: 1,
|
||||
perPage: 100,
|
||||
hasNextPage: false,
|
||||
selectedOption: null,
|
||||
errMessage: [],
|
||||
isItemFreezed: false,
|
||||
isItemFreezed: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.listGroupMembers(this.state.page);
|
||||
}
|
||||
|
||||
listGroupMembers = (page) => {
|
||||
const { groupID } = this.props;
|
||||
const { perPage, groupMembers } = this.state;
|
||||
seafileAPI.listGroupMembers(groupID, page, perPage).then((res) => {
|
||||
const members = res.data;
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
isLoadingMore: false,
|
||||
page: page,
|
||||
hasNextPage: members.length < perPage ? false : true,
|
||||
groupMembers: groupMembers.concat(members)
|
||||
});
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
isLoadingMore: false,
|
||||
hasNextPage: false
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onSelectChange = (option) => {
|
||||
this.setState({
|
||||
selectedOption: option,
|
||||
@@ -41,8 +75,9 @@ class ManageMembersDialog extends React.Component {
|
||||
emails.push(this.state.selectedOption[i].email);
|
||||
}
|
||||
seafileAPI.addGroupMembers(this.props.groupID, emails).then((res) => {
|
||||
this.onGroupMembersChange();
|
||||
const newMembers = res.data.success;
|
||||
this.setState({
|
||||
groupMembers: [].concat(newMembers, this.state.groupMembers),
|
||||
selectedOption: null,
|
||||
});
|
||||
this.refs.userSelect.clearSelect();
|
||||
@@ -57,21 +92,6 @@ class ManageMembersDialog extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
listGroupMembers = () => {
|
||||
seafileAPI.listGroupMembers(this.props.groupID).then((res) => {
|
||||
this.setState({
|
||||
groupMembers: res.data
|
||||
});
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
onGroupMembersChange = () => {
|
||||
this.listGroupMembers();
|
||||
}
|
||||
|
||||
toggleItemFreezed = (isFreezed) => {
|
||||
this.setState({
|
||||
isItemFreezed: isFreezed
|
||||
@@ -82,11 +102,43 @@ class ManageMembersDialog extends React.Component {
|
||||
this.props.toggleManageMembersDialog();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.listGroupMembers();
|
||||
handleScroll = (event) => {
|
||||
// isLoadingMore: to avoid repeated request
|
||||
const { page, hasNextPage, isLoadingMore } = this.state;
|
||||
if (hasNextPage && !isLoadingMore) {
|
||||
const clientHeight = event.target.clientHeight;
|
||||
const scrollHeight = event.target.scrollHeight;
|
||||
const scrollTop = event.target.scrollTop;
|
||||
const isBottom = (clientHeight + scrollTop + 1 >= scrollHeight);
|
||||
if (isBottom) { // scroll to the bottom
|
||||
this.setState({isLoadingMore: true}, () => {
|
||||
this.listGroupMembers(page + 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changeMember = (targetMember) => {
|
||||
this.setState({
|
||||
groupMembers: this.state.groupMembers.map((item) => {
|
||||
if (item.email == targetMember.email) {
|
||||
item = targetMember;
|
||||
}
|
||||
return item;
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
deleteMember = (targetMember) => {
|
||||
const groupMembers = this.state.groupMembers;
|
||||
groupMembers.splice(groupMembers.indexOf(targetMember), 1);
|
||||
this.setState({
|
||||
groupMembers: groupMembers
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isLoading, hasNextPage } = this.state;
|
||||
return (
|
||||
<Modal isOpen={true} toggle={this.toggle}>
|
||||
<ModalHeader toggle={this.toggle}>{gettext('Manage group members')}</ModalHeader>
|
||||
@@ -113,36 +165,41 @@ class ManageMembersDialog extends React.Component {
|
||||
);
|
||||
})
|
||||
}
|
||||
<div className="manage-members">
|
||||
<Table size="sm" className="manage-members-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="15%"></th>
|
||||
<th width="45%">{gettext('Name')}</th>
|
||||
<th width="30%">{gettext('Role')}</th>
|
||||
<th width="10%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
this.state.groupMembers.length > 0 &&
|
||||
this.state.groupMembers.map((item, index = 0) => {
|
||||
<div className="manage-members" onScroll={this.handleScroll}>
|
||||
{isLoading ? <Loading /> : (
|
||||
<Fragment>
|
||||
<Table size="sm" className="manage-members-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="15%"></th>
|
||||
<th width="45%">{gettext('Name')}</th>
|
||||
<th width="30%">{gettext('Role')}</th>
|
||||
<th width="10%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
this.state.groupMembers.length > 0 &&
|
||||
this.state.groupMembers.map((item, index) => {
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
<Member
|
||||
key={index}
|
||||
item={item}
|
||||
onGroupMembersChange={this.onGroupMembersChange}
|
||||
changeMember={this.changeMember}
|
||||
deleteMember={this.deleteMember}
|
||||
groupID={this.props.groupID}
|
||||
isOwner={this.props.isOwner}
|
||||
isItemFreezed={this.state.isItemFreezed}
|
||||
toggleItemFreezed={this.toggleItemFreezed}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</Table>
|
||||
}
|
||||
</tbody>
|
||||
</Table>
|
||||
{hasNextPage && <Loading />}
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
@@ -157,7 +214,8 @@ ManageMembersDialog.propTypes = propTypes;
|
||||
|
||||
const MemberPropTypes = {
|
||||
item: PropTypes.object.isRequired,
|
||||
onGroupMembersChange: PropTypes.func.isRequired,
|
||||
changeMember: PropTypes.func.isRequired,
|
||||
deleteMember: PropTypes.func.isRequired,
|
||||
groupID: PropTypes.string.isRequired,
|
||||
isOwner: PropTypes.bool.isRequired,
|
||||
};
|
||||
@@ -175,16 +233,18 @@ class Member extends React.PureComponent {
|
||||
onChangeUserRole = (role) => {
|
||||
let isAdmin = role === 'Admin' ? 'True' : 'False';
|
||||
seafileAPI.setGroupAdmin(this.props.groupID, this.props.item.email, isAdmin).then((res) => {
|
||||
this.props.onGroupMembersChange();
|
||||
});
|
||||
this.setState({
|
||||
highlight: false,
|
||||
this.props.changeMember(res.data);
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
deleteMember = (name) => {
|
||||
seafileAPI.deleteGroupMember(this.props.groupID, name).then((res) => {
|
||||
this.props.onGroupMembersChange();
|
||||
deleteMember = () => {
|
||||
const { item } = this.props;
|
||||
seafileAPI.deleteGroupMember(this.props.groupID, item.email).then((res) => {
|
||||
this.props.deleteMember(item);
|
||||
toaster.success(gettext('Successfully deleted {name}.').replace('{name}', item.name));
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
@@ -243,8 +303,7 @@ class Member extends React.PureComponent {
|
||||
{(deleteAuthority && !this.props.isItemFreezed) &&
|
||||
<i
|
||||
className="fa fa-times delete-group-member-icon"
|
||||
name={item.email}
|
||||
onClick={this.deleteMember.bind(this, item.email)}>
|
||||
onClick={this.deleteMember}>
|
||||
</i>
|
||||
}
|
||||
</td>
|
||||
|
@@ -171,8 +171,10 @@ class DirentListItem extends React.Component {
|
||||
|
||||
onItemClick = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const dirent = this.props.dirent;
|
||||
if (this.state.isRenameing) {
|
||||
return;
|
||||
}
|
||||
if (Utils.imageCheck(dirent.name)) {
|
||||
this.props.showImagePopup(dirent);
|
||||
} else {
|
||||
@@ -664,19 +666,19 @@ class DirentListItem extends React.Component {
|
||||
);
|
||||
const mobileItem = (
|
||||
<tr>
|
||||
<td>
|
||||
<td onClick={this.onItemClick}>
|
||||
<div className="dir-icon">
|
||||
{dirent.encoded_thumbnail_src ?
|
||||
<img src={`${siteRoot}${dirent.encoded_thumbnail_src}`} className="thumbnail cursor-pointer" onClick={this.onItemClick} alt="" /> :
|
||||
<img src={`${siteRoot}${dirent.encoded_thumbnail_src}`} className="thumbnail cursor-pointer" alt="" /> :
|
||||
<img src={iconUrl} width="24" alt="" />
|
||||
}
|
||||
{dirent.is_locked && <img className="locked" src={mediaUrl + 'img/file-locked-32.png'} alt={gettext('locked')} title={lockedInfo}/>}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<td onClick={this.onItemClick}>
|
||||
{this.state.isRenameing ?
|
||||
<Rename hasSuffix={dirent.type !== 'dir'} name={dirent.name} onRenameConfirm={this.onRenameConfirm} onRenameCancel={this.onRenameCancel} /> :
|
||||
<a href={dirent.type === 'dir' ? dirHref : fileHref} onClick={this.onItemClick}>{dirent.name}</a>
|
||||
<a href={dirent.type === 'dir' ? dirHref : fileHref}>{dirent.name}</a>
|
||||
}
|
||||
<br />
|
||||
{dirent.size && <span className="item-meta-info">{dirent.size}</span>}
|
||||
|
@@ -19,6 +19,11 @@ class SearchResultItem extends React.Component {
|
||||
let className = item.link_content ? 'item-img' : 'lib-item-img';
|
||||
let folderIconUrl = item.link_content ? Utils.getFolderIconUrl(false, 192) : Utils.getDefaultLibIconUrl(true);
|
||||
let fileIconUrl = item.is_dir ? folderIconUrl : Utils.getFileIconUrl(item.name, 192);
|
||||
|
||||
if (item.thumbnail_url !== '') {
|
||||
fileIconUrl = item.thumbnail_url;
|
||||
}
|
||||
|
||||
return (
|
||||
<li className="search-result-item" onClick={this.onClickHandler}>
|
||||
<img className={className} src={fileIconUrl} alt="" />
|
||||
|
@@ -178,6 +178,7 @@ class Search extends Component {
|
||||
items[i]['is_dir'] = data[i].is_dir;
|
||||
items[i]['link_content'] = decodeURI(data[i].fullpath).substring(1);
|
||||
items[i]['content'] = data[i].content_highlight;
|
||||
items[i]['thumbnail_url'] = data[i].thumbnail_url;
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@ import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import moment from 'moment';
|
||||
import { Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap';
|
||||
import { Link } from '@reach/router';
|
||||
import { Link, navigate } from '@reach/router';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import { gettext, siteRoot, isPro, username, folderPermEnabled, isSystemStaff, enableResetEncryptedRepoPassword, isEmailConfigured } from '../../utils/constants';
|
||||
import ModalPortal from '../../components/modal-portal';
|
||||
@@ -479,14 +479,21 @@ class SharedRepoListItem extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
visitRepo = () => {
|
||||
if (!this.state.isRenaming) {
|
||||
navigate(this.repoURL);
|
||||
}
|
||||
}
|
||||
|
||||
renderMobileUI = () => {
|
||||
let { iconUrl, iconTitle, libPath } = this.getRepoComputeParams();
|
||||
let { repo } = this.props;
|
||||
this.repoURL = libPath;
|
||||
return (
|
||||
<Fragment>
|
||||
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseOver={this.onMouseOver} onMouseLeave={this.onMouseLeave}>
|
||||
<td><img src={iconUrl} title={iconTitle} width="24" alt={iconTitle}/></td>
|
||||
<td>
|
||||
<td onClick={this.visitRepo}><img src={iconUrl} title={iconTitle} width="24" alt={iconTitle}/></td>
|
||||
<td onClick={this.visitRepo}>
|
||||
{this.state.isRenaming ?
|
||||
<Rename name={repo.repo_name} onRenameConfirm={this.onRenameConfirm} onRenameCancel={this.onRenameCancel} /> :
|
||||
<Link to={libPath}>{repo.repo_name}</Link>
|
||||
|
Reference in New Issue
Block a user