mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-17 15:53:28 +00:00
['share dialog' - 'Share Link', 'Share Admin' - 'Links'] share links: get limited links per request, scroll down to get more (#5840)
This commit is contained in:
@@ -18,6 +18,8 @@ const propTypes = {
|
|||||||
itemType: PropTypes.string
|
itemType: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const PER_PAGE = 25;
|
||||||
|
|
||||||
class ShareLinkPanel extends React.Component {
|
class ShareLinkPanel extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -28,6 +30,9 @@ class ShareLinkPanel extends React.Component {
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
|
hasMore: false,
|
||||||
|
isLoadingMore: false,
|
||||||
|
page: 1,
|
||||||
mode: 'listLinks',
|
mode: 'listLinks',
|
||||||
sharedLinkInfo: null,
|
sharedLinkInfo: null,
|
||||||
shareLinks: [],
|
shareLinks: [],
|
||||||
@@ -37,11 +42,12 @@ class ShareLinkPanel extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
let path = this.props.itemPath;
|
const { page } = this.state;
|
||||||
let repoID = this.props.repoID;
|
const { repoID, itemPath: path } = this.props;
|
||||||
seafileAPI.getShareLink(repoID, path).then((res) => {
|
seafileAPI.listShareLinks({repoID, path, page}).then((res) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
hasMore: res.data.length == PER_PAGE,
|
||||||
shareLinks: res.data.map(item => new ShareLink(item))
|
shareLinks: res.data.map(item => new ShareLink(item))
|
||||||
});
|
});
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
@@ -184,13 +190,46 @@ class ShareLinkPanel extends React.Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleScroll = (event) => {
|
||||||
|
if (!this.state.isLoadingMore && this.state.hasMore) {
|
||||||
|
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.getMore();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getMore = () => {
|
||||||
|
const { page, shareLinks } = this.state;
|
||||||
|
const { repoID, itemPath: path } = this.props;
|
||||||
|
seafileAPI.listShareLinks({repoID, path, page: page + 1}).then((res) => {
|
||||||
|
this.setState({
|
||||||
|
isLoadingMore: false,
|
||||||
|
hasMore: res.data.length == PER_PAGE,
|
||||||
|
page: page + 1,
|
||||||
|
shareLinks: shareLinks.concat(res.data.map(item => new ShareLink(item)))
|
||||||
|
});
|
||||||
|
}).catch(error => {
|
||||||
|
this.setState({
|
||||||
|
isLoadingMore: false
|
||||||
|
});
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.isLoading) {
|
if (this.state.isLoading) {
|
||||||
return <Loading />;
|
return <Loading />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { repoID, itemPath, userPerm } = this.props;
|
const { repoID, itemPath, userPerm } = this.props;
|
||||||
const { mode, shareLinks, sharedLinkInfo, permissionOptions, currentPermission } = this.state;
|
const { mode, shareLinks, sharedLinkInfo, permissionOptions, currentPermission, isLoadingMore } = this.state;
|
||||||
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case 'displayLinkDetails':
|
case 'displayLinkDetails':
|
||||||
@@ -242,6 +281,8 @@ class ShareLinkPanel extends React.Component {
|
|||||||
toggleSelectLink={this.toggleSelectLink}
|
toggleSelectLink={this.toggleSelectLink}
|
||||||
deleteShareLinks={this.deleteShareLinks}
|
deleteShareLinks={this.deleteShareLinks}
|
||||||
deleteLink={this.deleteLink}
|
deleteLink={this.deleteLink}
|
||||||
|
handleScroll={this.handleScroll}
|
||||||
|
isLoadingMore={isLoadingMore}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ import { gettext, siteRoot } from '../../utils/constants';
|
|||||||
import EmptyTip from '../empty-tip';
|
import EmptyTip from '../empty-tip';
|
||||||
import LinkItem from './link-item';
|
import LinkItem from './link-item';
|
||||||
import CommonOperationConfirmationDialog from '../../components/dialog/common-operation-confirmation-dialog';
|
import CommonOperationConfirmationDialog from '../../components/dialog/common-operation-confirmation-dialog';
|
||||||
|
import Loading from '../../components/loading';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
shareLinks: PropTypes.array.isRequired,
|
shareLinks: PropTypes.array.isRequired,
|
||||||
@@ -13,7 +14,9 @@ const propTypes = {
|
|||||||
toggleSelectAllLinks: PropTypes.func.isRequired,
|
toggleSelectAllLinks: PropTypes.func.isRequired,
|
||||||
toggleSelectLink: PropTypes.func.isRequired,
|
toggleSelectLink: PropTypes.func.isRequired,
|
||||||
deleteLink: PropTypes.func.isRequired,
|
deleteLink: PropTypes.func.isRequired,
|
||||||
deleteShareLinks: PropTypes.func.isRequired
|
deleteShareLinks: PropTypes.func.isRequired,
|
||||||
|
isLoadingMore: PropTypes.bool.isRequired,
|
||||||
|
handleScroll: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
class LinkList extends React.Component {
|
class LinkList extends React.Component {
|
||||||
@@ -46,7 +49,7 @@ class LinkList extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { shareLinks, permissionOptions } = this.props;
|
const { shareLinks, permissionOptions, isLoadingMore, handleScroll } = this.props;
|
||||||
const selectedLinks = shareLinks.filter(item => item.isSelected);
|
const selectedLinks = shareLinks.filter(item => item.isSelected);
|
||||||
const isAllLinksSelected = shareLinks.length == selectedLinks.length;
|
const isAllLinksSelected = shareLinks.length == selectedLinks.length;
|
||||||
|
|
||||||
@@ -88,7 +91,7 @@ class LinkList extends React.Component {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
</table>
|
</table>
|
||||||
<div className='table-real-container'>
|
<div className='table-real-container' onScroll={handleScroll}>
|
||||||
<table className="table-real-content table-thead-hidden">
|
<table className="table-real-content table-thead-hidden">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -114,6 +117,7 @@ class LinkList extends React.Component {
|
|||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
{isLoadingMore && <Loading />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@@ -129,10 +129,6 @@ class EditorApi {
|
|||||||
return seafileAPI.getInternalLink(repoID, filePath);
|
return seafileAPI.getInternalLink(repoID, filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
getShareLink() {
|
|
||||||
return seafileAPI.getShareLink(repoID, filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
createShareLink (repoID, filePath, userPassword, userValidDays, permissions) {
|
createShareLink (repoID, filePath, userPassword, userValidDays, permissions) {
|
||||||
return seafileAPI.createShareLink(repoID, filePath, userPassword, userValidDays, permissions);
|
return seafileAPI.createShareLink(repoID, filePath, userPassword, userValidDays, permissions);
|
||||||
}
|
}
|
||||||
|
@@ -19,6 +19,7 @@ import Selector from '../../components/single-selector';
|
|||||||
|
|
||||||
const contentPropTypes = {
|
const contentPropTypes = {
|
||||||
loading: PropTypes.bool.isRequired,
|
loading: PropTypes.bool.isRequired,
|
||||||
|
isLoadingMore: PropTypes.bool.isRequired,
|
||||||
errorMsg: PropTypes.string.isRequired,
|
errorMsg: PropTypes.string.isRequired,
|
||||||
items: PropTypes.array.isRequired,
|
items: PropTypes.array.isRequired,
|
||||||
sortBy: PropTypes.string.isRequired,
|
sortBy: PropTypes.string.isRequired,
|
||||||
@@ -114,7 +115,12 @@ class Content extends Component {
|
|||||||
</table>
|
</table>
|
||||||
);
|
);
|
||||||
|
|
||||||
return items.length ? table : emptyTip;
|
return items.length ? (
|
||||||
|
<>
|
||||||
|
{table}
|
||||||
|
{this.props.isLoadingMore && <div className="flex-shrink-0"><Loading /></div>}
|
||||||
|
</>
|
||||||
|
) : emptyTip;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -360,6 +366,8 @@ const propTypes = {
|
|||||||
onSearchedClick: PropTypes.func.isRequired
|
onSearchedClick: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const PER_PAGE = 25;
|
||||||
|
|
||||||
class ShareAdminShareLinks extends Component {
|
class ShareAdminShareLinks extends Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -367,6 +375,9 @@ class ShareAdminShareLinks extends Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
isCleanInvalidShareLinksDialogOpen: false,
|
isCleanInvalidShareLinksDialogOpen: false,
|
||||||
loading: true,
|
loading: true,
|
||||||
|
hasMore: false,
|
||||||
|
isLoadingMore: false,
|
||||||
|
page: 1,
|
||||||
errorMsg: '',
|
errorMsg: '',
|
||||||
items: [],
|
items: [],
|
||||||
sortBy: 'name', // 'name' or 'time'
|
sortBy: 'name', // 'name' or 'time'
|
||||||
@@ -437,12 +448,14 @@ class ShareAdminShareLinks extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
listUserShareLinks() {
|
listUserShareLinks() {
|
||||||
seafileAPI.listUserShareLinks().then((res) => {
|
const { page } = this.state;
|
||||||
|
seafileAPI.listShareLinks({ page }).then((res) => {
|
||||||
let items = res.data.map(item => {
|
let items = res.data.map(item => {
|
||||||
return new ShareLink(item);
|
return new ShareLink(item);
|
||||||
});
|
});
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
|
hasMore: res.data.length == PER_PAGE,
|
||||||
items: this._sortItems(items, this.state.sortBy, this.state.sortOrder)
|
items: this._sortItems(items, this.state.sortBy, this.state.sortOrder)
|
||||||
});
|
});
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
@@ -453,6 +466,40 @@ class ShareAdminShareLinks extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleScroll = (event) => {
|
||||||
|
if (!this.state.isLoadingMore && this.state.hasMore) {
|
||||||
|
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.getMore();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getMore = () => {
|
||||||
|
const { page } = this.state;
|
||||||
|
seafileAPI.listShareLinks({ page: page + 1 }).then((res) => {
|
||||||
|
let moreItems = res.data.map(item => {
|
||||||
|
return new ShareLink(item);
|
||||||
|
});
|
||||||
|
this.setState({
|
||||||
|
isLoadingMore: false,
|
||||||
|
hasMore: res.data.length == PER_PAGE,
|
||||||
|
page: page + 1,
|
||||||
|
items: this._sortItems(this.state.items.concat(moreItems), this.state.sortBy, this.state.sortOrder)
|
||||||
|
});
|
||||||
|
}).catch((error) => {
|
||||||
|
this.setState({
|
||||||
|
isLoadingMore: false,
|
||||||
|
errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
onRemoveLink = (item) => {
|
onRemoveLink = (item) => {
|
||||||
seafileAPI.deleteShareLink(item.token).then(() => {
|
seafileAPI.deleteShareLink(item.token).then(() => {
|
||||||
let items = this.state.items.filter(uploadItem => {
|
let items = this.state.items.filter(uploadItem => {
|
||||||
@@ -511,9 +558,10 @@ class ShareAdminShareLinks extends Component {
|
|||||||
</ul>
|
</ul>
|
||||||
{(!Utils.isDesktop() && this.state.items.length > 0) && <span className="sf3-font sf3-font-sort action-icon" onClick={this.toggleSortOptionsDialog}></span>}
|
{(!Utils.isDesktop() && this.state.items.length > 0) && <span className="sf3-font sf3-font-sort action-icon" onClick={this.toggleSortOptionsDialog}></span>}
|
||||||
</div>
|
</div>
|
||||||
<div className="cur-view-content">
|
<div className="cur-view-content" onScroll={this.handleScroll}>
|
||||||
<Content
|
<Content
|
||||||
loading={this.state.loading}
|
loading={this.state.loading}
|
||||||
|
isLoadingMore={this.state.isLoadingMore}
|
||||||
errorMsg={this.state.errorMsg}
|
errorMsg={this.state.errorMsg}
|
||||||
items={this.state.items}
|
items={this.state.items}
|
||||||
sortBy={this.state.sortBy}
|
sortBy={this.state.sortBy}
|
||||||
|
Reference in New Issue
Block a user