diff --git a/frontend/src/components/share-link-panel/index.js b/frontend/src/components/share-link-panel/index.js index a2313a717ba..ed8b739b392 100644 --- a/frontend/src/components/share-link-panel/index.js +++ b/frontend/src/components/share-link-panel/index.js @@ -18,6 +18,8 @@ const propTypes = { itemType: PropTypes.string }; +const PER_PAGE = 25; + class ShareLinkPanel extends React.Component { constructor(props) { @@ -28,6 +30,9 @@ class ShareLinkPanel extends React.Component { this.state = { isLoading: true, + hasMore: false, + isLoadingMore: false, + page: 1, mode: 'listLinks', sharedLinkInfo: null, shareLinks: [], @@ -37,11 +42,12 @@ class ShareLinkPanel extends React.Component { } componentDidMount() { - let path = this.props.itemPath; - let repoID = this.props.repoID; - seafileAPI.getShareLink(repoID, path).then((res) => { + const { page } = this.state; + const { repoID, itemPath: path } = this.props; + seafileAPI.listShareLinks({repoID, path, page}).then((res) => { this.setState({ isLoading: false, + hasMore: res.data.length == PER_PAGE, shareLinks: res.data.map(item => new ShareLink(item)) }); }).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() { if (this.state.isLoading) { return ; } 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) { case 'displayLinkDetails': @@ -242,6 +281,8 @@ class ShareLinkPanel extends React.Component { toggleSelectLink={this.toggleSelectLink} deleteShareLinks={this.deleteShareLinks} deleteLink={this.deleteLink} + handleScroll={this.handleScroll} + isLoadingMore={isLoadingMore} /> ); } diff --git a/frontend/src/components/share-link-panel/link-list.js b/frontend/src/components/share-link-panel/link-list.js index 5736a42e1f9..8c04d7eba5e 100644 --- a/frontend/src/components/share-link-panel/link-list.js +++ b/frontend/src/components/share-link-panel/link-list.js @@ -4,6 +4,7 @@ import { gettext, siteRoot } from '../../utils/constants'; import EmptyTip from '../empty-tip'; import LinkItem from './link-item'; import CommonOperationConfirmationDialog from '../../components/dialog/common-operation-confirmation-dialog'; +import Loading from '../../components/loading'; const propTypes = { shareLinks: PropTypes.array.isRequired, @@ -13,7 +14,9 @@ const propTypes = { toggleSelectAllLinks: PropTypes.func.isRequired, toggleSelectLink: 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 { @@ -46,7 +49,7 @@ class LinkList extends React.Component { }; render() { - const { shareLinks, permissionOptions } = this.props; + const { shareLinks, permissionOptions, isLoadingMore, handleScroll } = this.props; const selectedLinks = shareLinks.filter(item => item.isSelected); const isAllLinksSelected = shareLinks.length == selectedLinks.length; @@ -88,7 +91,7 @@ class LinkList extends React.Component { -
+
@@ -114,6 +117,7 @@ class LinkList extends React.Component { })}
+ {isLoadingMore && }
)} diff --git a/frontend/src/pages/markdown-editor/editor-api.js b/frontend/src/pages/markdown-editor/editor-api.js index 7b9b9c44b87..99d4bfa90b0 100644 --- a/frontend/src/pages/markdown-editor/editor-api.js +++ b/frontend/src/pages/markdown-editor/editor-api.js @@ -129,10 +129,6 @@ class EditorApi { return seafileAPI.getInternalLink(repoID, filePath); } - getShareLink() { - return seafileAPI.getShareLink(repoID, filePath); - } - createShareLink (repoID, filePath, userPassword, userValidDays, permissions) { return seafileAPI.createShareLink(repoID, filePath, userPassword, userValidDays, permissions); } diff --git a/frontend/src/pages/share-admin/share-links.js b/frontend/src/pages/share-admin/share-links.js index 488fb367f36..0d5050a4a7c 100644 --- a/frontend/src/pages/share-admin/share-links.js +++ b/frontend/src/pages/share-admin/share-links.js @@ -19,6 +19,7 @@ import Selector from '../../components/single-selector'; const contentPropTypes = { loading: PropTypes.bool.isRequired, + isLoadingMore: PropTypes.bool.isRequired, errorMsg: PropTypes.string.isRequired, items: PropTypes.array.isRequired, sortBy: PropTypes.string.isRequired, @@ -114,7 +115,12 @@ class Content extends Component { ); - return items.length ? table : emptyTip; + return items.length ? ( + <> + {table} + {this.props.isLoadingMore &&
} + + ) : emptyTip; } } } @@ -360,6 +366,8 @@ const propTypes = { onSearchedClick: PropTypes.func.isRequired }; +const PER_PAGE = 25; + class ShareAdminShareLinks extends Component { constructor(props) { @@ -367,6 +375,9 @@ class ShareAdminShareLinks extends Component { this.state = { isCleanInvalidShareLinksDialogOpen: false, loading: true, + hasMore: false, + isLoadingMore: false, + page: 1, errorMsg: '', items: [], sortBy: 'name', // 'name' or 'time' @@ -437,12 +448,14 @@ class ShareAdminShareLinks extends Component { } listUserShareLinks() { - seafileAPI.listUserShareLinks().then((res) => { + const { page } = this.state; + seafileAPI.listShareLinks({ page }).then((res) => { let items = res.data.map(item => { return new ShareLink(item); }); this.setState({ loading: false, + hasMore: res.data.length == PER_PAGE, items: this._sortItems(items, this.state.sortBy, this.state.sortOrder) }); }).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) => { seafileAPI.deleteShareLink(item.token).then(() => { let items = this.state.items.filter(uploadItem => { @@ -511,9 +558,10 @@ class ShareAdminShareLinks extends Component { {(!Utils.isDesktop() && this.state.items.length > 0) && } -
+