From 6831d9e519b4315db4e0ea35e37a8d0ffadfef74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=B1=E6=B0=B4=E4=BA=BA=E5=AE=B6?= Date: Sat, 13 Oct 2018 17:07:54 +0800 Subject: [PATCH] Wiki mode optimized (#2442) --- frontend/package-lock.json | 26 +-- frontend/package.json | 4 +- .../components/dialog/zip-download-dialog.js | 8 + .../dirent-list-view/dirent-list-item.js | 125 ++++++++++++++ .../dirent-list-view/dirent-list-view.js | 158 ++++++++++++++++++ .../dirent-operation/operation-group.js | 45 +++-- .../dirent-operation/operation-menu.js | 55 ++++-- frontend/src/models/dirent.js | 26 +++ frontend/src/models/repo.js | 21 +++ .../src/pages/repo-wiki-mode/main-panel.js | 82 ++++++--- frontend/src/repo-wiki-mode.js | 79 ++++----- media/css/seahub_react.css | 7 + seahub/api2/endpoints/repos.py | 19 ++- seahub/api2/views.py | 11 +- seahub/utils/repo.py | 26 +++ seahub/utils/star.py | 7 +- seahub/views/ajax.py | 23 +-- 17 files changed, 585 insertions(+), 137 deletions(-) create mode 100644 frontend/src/components/dirent-list-view/dirent-list-item.js create mode 100644 frontend/src/components/dirent-list-view/dirent-list-view.js create mode 100644 frontend/src/models/dirent.js create mode 100644 frontend/src/models/repo.js diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 13953cf7c1..ae3bf8672c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -2896,9 +2896,9 @@ "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" }, "deep-extend": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", - "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true }, "deep-is": { @@ -4186,9 +4186,9 @@ "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=" }, "follow-redirects": { - "version": "1.5.8", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.8.tgz", - "integrity": "sha512-sy1mXPmv7kLAMKW/8XofG7o9T+6gAjzdZK4AJF6ryqQYUa/hnzgiypoeUecZ53x7XiqKNEpNqLtS97MshW2nxg==", + "version": "1.5.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.9.tgz", + "integrity": "sha512-Bh65EZI/RU8nx0wbYF9shkFZlqLP+6WT/5FnA3cE/djNSuKNHJEinGGZgu/cQEkeeb2GdFOgenAmn8qaqYke2w==", "requires": { "debug": "=3.1.0" }, @@ -10168,9 +10168,9 @@ } }, "seafile-js": { - "version": "0.2.21", - "resolved": "https://registry.npmjs.org/seafile-js/-/seafile-js-0.2.21.tgz", - "integrity": "sha512-zUY1X8YqoLHg2G+fmFLbNOK5w96EW9glmEc/pkNFlXi2SzHd5SasPt5J2scpnD+jyLmYWLfwqJbjqp+xLEO+Yg==", + "version": "0.2.25", + "resolved": "https://registry.npmjs.org/seafile-js/-/seafile-js-0.2.25.tgz", + "integrity": "sha512-XVJ6qvFeSv6tfihBvNscBS+rmnb+TkhyXvEYKBa13PipZbJdxbzVeWRhCXiR82fQwWLzYdAAv2rclSeURyCe5Q==", "requires": { "axios": "^0.18.0", "form-data": "^2.3.2" @@ -11773,12 +11773,12 @@ } }, "webpack-bundle-tracker": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/webpack-bundle-tracker/-/webpack-bundle-tracker-0.3.0.tgz", - "integrity": "sha512-I0Gwkug8QX8xZS14SvmfWin1AmZDoZp/0AGvlgKqNxyw20DgkFkq1jTQ/Ml73YgjFTmQ5bATyQM7TjtYMP1nFA==", + "version": "0.4.2-beta", + "resolved": "https://registry.npmjs.org/webpack-bundle-tracker/-/webpack-bundle-tracker-0.4.2-beta.tgz", + "integrity": "sha512-CCyJbCQnRtjR1sk97u/H5DtJibrIcJ79MnntMyjOpc9HCmfIQYgt7ze7i/Z+DStBZ4NC4HxqGDsB///2Na1DTA==", "dev": true, "requires": { - "deep-extend": "^0.4.1", + "deep-extend": "^0.6.0", "mkdirp": "^0.5.1", "strip-ansi": "^2.0.1" }, diff --git a/frontend/package.json b/frontend/package.json index 8a61158b00..a669e49fe4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -24,7 +24,7 @@ "react-dom": "^16.5.2", "react-moment": "^0.7.9", "reactstrap": "^6.4.0", - "seafile-js": "^0.2.21", + "seafile-js": "^0.2.25", "seafile-ui": "^0.1.10", "sw-precache-webpack-plugin": "0.11.4", "url-loader": "0.6.2", @@ -106,7 +106,7 @@ "react-dev-utils": "^5.0.0", "react-i18next": "^7.6.1", "webpack": "3.8.1", - "webpack-bundle-tracker": "^0.3.0", + "webpack-bundle-tracker": "^0.4.2-beta", "webpack-dev-server": "2.9.4", "webpack-manifest-plugin": "1.3.2" } diff --git a/frontend/src/components/dialog/zip-download-dialog.js b/frontend/src/components/dialog/zip-download-dialog.js index 21cdd6ccce..a64d1d1878 100644 --- a/frontend/src/components/dialog/zip-download-dialog.js +++ b/frontend/src/components/dialog/zip-download-dialog.js @@ -1,6 +1,12 @@ import React from 'react'; +import PropTypes from 'prop-types'; import { Modal, ModalHeader, ModalBody } from 'reactstrap'; +const propTypes = { + onCancelDownload: PropTypes.func.isRequired, + progress: PropTypes.string.isRequired, +}; + class ZipDownloadDialog extends React.Component { toggle = () => { @@ -19,4 +25,6 @@ class ZipDownloadDialog extends React.Component { } } +ZipDownloadDialog.propTypes = propTypes; + export default ZipDownloadDialog; diff --git a/frontend/src/components/dirent-list-view/dirent-list-item.js b/frontend/src/components/dirent-list-view/dirent-list-item.js new file mode 100644 index 0000000000..7bc7b75f5b --- /dev/null +++ b/frontend/src/components/dirent-list-view/dirent-list-item.js @@ -0,0 +1,125 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { serviceUrl, gettext } from '../../utils/constants'; +import OperationGroup from '../dirent-operation/operation-group'; + +const propTypes = { + isItemFreezed: PropTypes.bool.isRequired, + dirent: PropTypes.object.isRequired, + onItemClick: PropTypes.func.isRequired, + onItemMenuShow: PropTypes.func.isRequired, + onItemMenuHide: PropTypes.func.isRequired, + onItemDelete: PropTypes.func.isRequired, + onItemStarred: PropTypes.func.isRequired, + onItemDownload: PropTypes.func.isRequired, +}; + +class DirentListItem extends React.Component { + + constructor(props) { + super(props); + this.state = { + isOperationShow: false, + highlight: false + }; + } + + //UI Interactive + onMouseEnter = () => { + if (!this.props.isItemFreezed) { + this.setState({ + highlight: true, + isOperationShow: true, + }); + } + } + + onMouseOver = () => { + if (!this.props.isItemFreezed) { + this.setState({ + highlight: true, + isOperationShow: true, + }); + } + } + + onMouseLeave = () => { + if (!this.props.isItemFreezed) { + this.setState({ + highlight: false, + isOperationShow: false, + }); + } + } + + onItemMenuShow = () => { + this.props.onItemMenuShow(); + } + + onItemMenuHide = () => { + this.setState({ + isOperationShow: false, + highlight: '' + }); + this.props.onItemMenuHide(); + } + + //buiness handler + onItemSelected = () => { + //todos; + } + + onItemStarred = () => { + this.props.onItemStarred(this.props.dirent); + } + + onItemClick = () => { + this.props.onItemClick(this.props.dirent); + } + + + onItemDownload = () => { + this.props.onItemDownload(this.props.dirent); + } + + onItemDelete = () => { + this.props.onItemDelete(this.props.dirent); + } + + render() { + let { dirent } = this.props; + return ( + + + + + + {dirent.starred !== undefined && !dirent.starred && } + {dirent.starred !== undefined && dirent.starred && } + + + {gettext('file + + {dirent.name} + + { + this.state.isOperationShow && + + } + + {dirent.size && dirent.size} + + + ); + } +} + +DirentListItem.propTypes = propTypes; + +export default DirentListItem; diff --git a/frontend/src/components/dirent-list-view/dirent-list-view.js b/frontend/src/components/dirent-list-view/dirent-list-view.js new file mode 100644 index 0000000000..6520db99ca --- /dev/null +++ b/frontend/src/components/dirent-list-view/dirent-list-view.js @@ -0,0 +1,158 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { gettext, repoID } from '../../utils/constants'; +import URLDecorator from '../../utils/url-decorator'; +import editorUtilities from '../../utils/editor-utilties'; +import { seafileAPI } from '../../utils/seafile-api'; +import DirentListItem from './dirent-list-item'; +import ZipDownloadDialog from '../dialog/zip-download-dialog'; + +const propTypes = { + filePath: PropTypes.string.isRequired, + direntList: PropTypes.array.isRequired, + onItemDelete: PropTypes.func.isRequired, + onItemClick: PropTypes.func.isRequired, + updateViewList: PropTypes.func.isRequired, +}; + +class DirentListView extends React.Component { + + constructor(props) { + super(props); + this.state = { + isItemFreezed: false, + isProgressDialogShow: false, + progress: '0%', + }; + } + + onItemMenuShow = () => { + this.setState({isItemFreezed: true}); + } + + onItemMenuHide = () => { + this.setState({isItemFreezed: false}); + } + + onItemClick = (dirent) => { + let direntPath = this.getDirentPath(dirent); + this.props.onItemClick(direntPath); + } + + onItemDelete = (dirent) => { + let direntPath = this.getDirentPath(dirent); + this.props.onItemDelete(direntPath); + } + + onItemStarred = (dirent) => { + let filePath = this.getDirentPath(dirent); + if (dirent.starred) { + seafileAPI.unStarFile(repoID, filePath).then(() => { + this.props.updateViewList(this.props.filePath); + }); + } else { + seafileAPI.starFile(repoID, filePath).then(() => { + this.props.updateViewList(this.props.filePath); + }); + } + } + + onItemDownload = (dirent) => { + if (dirent.type === 'dir') { + this.setState({isProgressDialogShow: true, progress: '0%'}); + editorUtilities.zipDownload(this.props.filePath, dirent.name).then(res => { + this.zip_token = res.data['zip_token']; + this.addDownloadAnimation(); + this.interval = setInterval(this.addDownloadAnimation, 1000); + }); + } else { + let path = this.getDirentPath(dirent); + let url = URLDecorator.getUrl({type: 'download_file_url', repoID: repoID, filePath: path}); + location.href = url; + } + } + + addDownloadAnimation = () => { + let _this = this; + let token = this.zip_token; + editorUtilities.queryZipProgress(token).then(res => { + let data = res.data; + let progress = data.total === 0 ? '100%' : (data.zipped / data.total * 100).toFixed(0) + '%'; + this.setState({progress: progress}); + + if (data['total'] === data['zipped']) { + this.setState({ + progress: '100%' + }); + clearInterval(this.interval); + location.href = URLDecorator.getUrl({type: 'download_dir_zip_url', token: token}); + setTimeout(function() { + _this.setState({isProgressDialogShow: false}); + }, 500); + } + + }); + } + + onCancelDownload = () => { + let zip_token = this.zip_token; + editorUtilities.cancelZipTask(zip_token).then(res => { + this.setState({ + isProgressDialogShow: false, + }); + }); + } + + getDirentPath = (dirent) => { + let path = this.props.filePath; + return path === '/' ? path + dirent.name : path + '/' + dirent.name; + } + + render() { + const { direntList } = this.props; + return ( +
+ + + + + + + + + + + + + + { + direntList.length !== 0 && direntList.map((dirent, index) => { + return ( + + ); + }) + } + +
{gettext('Name')}{gettext('Size')}{gettext('Last Update')}
+ { + this.state.isProgressDialogShow && + + } +
+ ); + } +} + +DirentListView.propTypes = propTypes; + +export default DirentListView; diff --git a/frontend/src/components/dirent-operation/operation-group.js b/frontend/src/components/dirent-operation/operation-group.js index 8335fbe689..eee0d41477 100644 --- a/frontend/src/components/dirent-operation/operation-group.js +++ b/frontend/src/components/dirent-operation/operation-group.js @@ -1,7 +1,16 @@ import React from 'react'; +import PropTypes from 'prop-types'; import { gettext } from '../../utils/constants'; import OperationMenu from './operation-menu'; +const propTypes = { + dirent: PropTypes.object.isRequired, + onItemMenuShow: PropTypes.func.isRequired, + onItemMenuHide: PropTypes.func.isRequired, + onDelete: PropTypes.func.isRequired, + onDownload: PropTypes.func.isRequired, +}; + class OperationGroup extends React.Component { constructor(props) { @@ -9,7 +18,7 @@ class OperationGroup extends React.Component { this.state = { isItemMenuShow: false, menuPosition: {top: 0, left: 0 }, - } + }; } componentDidMount() { @@ -34,24 +43,28 @@ class OperationGroup extends React.Component { this.props.onDelete(); } - onItemMenuShow = (e) => { + onItemMenuToggle = (e) => { + e.stopPropagation(); + e.nativeEvent.stopImmediatePropagation(); + if (!this.state.isItemMenuShow) { - e.stopPropagation(); - e.nativeEvent.stopImmediatePropagation(); - - let left = e.clientX - 8*16; - let top = e.clientY + 15; - let position = Object.assign({},this.state.menuPosition, {left: left, top: top}); - this.setState({ - menuPosition: position, - isItemMenuShow: true, - }); - this.props.onItemMenuShow(); + this.onItemMenuShow(e); } else { this.onItemMenuHide(); } } + onItemMenuShow = (e) => { + let left = e.clientX - 8*16; + let top = e.clientY + 15; + let position = Object.assign({},this.state.menuPosition, {left: left, top: top}); + this.setState({ + menuPosition: position, + isItemMenuShow: true, + }); + this.props.onItemMenuShow(); + } + onItemMenuHide = () => { this.setState({ isItemMenuShow: false, @@ -81,13 +94,13 @@ class OperationGroup extends React.Component {
  • - +
  • { this.state.isItemMenuShow && { + let repo = new Repo(res.data); + seafileAPI.getAccountInfo().then(res => { + let user_email = res.data.email; + let is_repo_owner = repo.owner_email === user_email; + this.setState({ + repo: repo, + is_repo_owner: is_repo_owner + }); + }) + }); + } + getItemType() { - return this.props.currentItem.type; + let type = this.props.dirent.is_dir ? 'dir' : 'file'; + return type; } renderDirentDirMenu() { let position = this.props.menuPosition; let style = {position: 'fixed', left: position.left, top: position.top, display: 'block'}; - if (this.props.currentItem.permission === 'rw') { + if (this.props.dirent.permission === 'rw') { return (
    • @@ -44,7 +69,7 @@ class OperationMenu extends React.Component { ); } - if (this.props.currentItem.permission === 'r') { + if (this.props.dirent.permission === 'r') { return (
      • @@ -62,7 +87,7 @@ class OperationMenu extends React.Component { renderDirentFileMenu() { let position = this.props.menuPosition; let style = {position: 'fixed', left: position.left, top: position.top, display: 'block'}; - if (this.props.currentItem.permission === 'rw') { + if (this.props.dirent.permission === 'rw') { return (
        • @@ -105,7 +130,7 @@ class OperationMenu extends React.Component { ); } - if (this.props.currentItem.permission === "r") { + if (this.props.dirent.permission === 'r') { return (
          • @@ -130,14 +155,14 @@ class OperationMenu extends React.Component { let type = this.getItemType(); let menu = null; switch(type) { - case 'file': - menu = this.renderDirentFileMenu(); - break; - case 'dir': - menu = this.renderDirentDirMenu(); - break; - default: - break; + case 'file': + menu = this.renderDirentFileMenu(); + break; + case 'dir': + menu = this.renderDirentDirMenu(); + break; + default: + break; } return menu; } diff --git a/frontend/src/models/dirent.js b/frontend/src/models/dirent.js new file mode 100644 index 0000000000..126b67dcb6 --- /dev/null +++ b/frontend/src/models/dirent.js @@ -0,0 +1,26 @@ +import moment from 'moment'; +import { Utils } from '../utils/utils'; + +class Dirent { + constructor(json) { + this.id = json.id; + this.name = json.name; + this.type = json.type; + this.mtime = moment.unix(json.mtime).fromNow(); + this.permission = json.permission; + if (json.type === 'file') { + this.size = Utils.bytesToSize(json.size); + this.starred = json.starred; + this.is_locked = json.is_locked; + this.lock_time = moment.unix(json.lock_time).fromNow(); + this.lock_owner= json.lock_owner; + this.locked_by_me = json.locked_by_me; + this.modifier_name = json.modifier_name; + this.modifier_email = json.modifier_email; + this.modifier_contact_email = json.modifier_contact_email; + } + } + +} + +export default Dirent; diff --git a/frontend/src/models/repo.js b/frontend/src/models/repo.js new file mode 100644 index 0000000000..8506608efe --- /dev/null +++ b/frontend/src/models/repo.js @@ -0,0 +1,21 @@ +import { Utils } from '../utils/utils'; + +class Repo { + constructor(object) { + this.repo_id = object.repo_id; + this.repo_name = object.name; + this.permission = object.permission; + this.size = Utils.bytesToSize(object.size); + this.file_count = object.file_count; + this.owner_name = object.owner_name; + this.owner_email = object.owner_email; + this.owner_contact_email = object.owner_contact_email; + this.is_admin = object.is_admin; + this.is_virtual = object.is_virtual; + this.no_quota = object.no_quota; + this.has_been_shared_out = object.has_been_shared_out; + this.encrypted = object.encrypted; + } +} + +export default Repo; diff --git a/frontend/src/pages/repo-wiki-mode/main-panel.js b/frontend/src/pages/repo-wiki-mode/main-panel.js index 2f67644b0c..355c5b42f5 100644 --- a/frontend/src/pages/repo-wiki-mode/main-panel.js +++ b/frontend/src/pages/repo-wiki-mode/main-panel.js @@ -1,9 +1,27 @@ import React, { Component } from 'react'; +import PropTypes from 'prop-types'; import { gettext, repoID, serviceUrl, slug, siteRoot } from '../../utils/constants'; +import { seafileAPI } from '../../utils/seafile-api'; import CommonToolbar from '../../components/toolbar/common-toolbar'; import PathToolbar from '../../components/toolbar/path-toolbar'; import MarkdownViewer from '../../components/markdown-viewer'; -import TreeDirView from '../../components/tree-dir-view/tree-dir-view'; +import DirentListView from '../../components/dirent-list-view/dirent-list-view'; +import Dirent from '../../models/dirent'; + +const propTypes = { + content: PropTypes.string, + lastModified: PropTypes.string, + latestContributor: PropTypes.string, + permission: PropTypes.string, + filePath: PropTypes.string.isRequired, + isFileLoading: PropTypes.bool.isRequired, + isViewFileState: PropTypes.bool.isRequired, + onMenuClick: PropTypes.func.isRequired, + onSearchedClick: PropTypes.func.isRequired, + onMainNavBarClick: PropTypes.func.isRequired, + onMainItemClick: PropTypes.func.isRequired, + onMainItemDelete: PropTypes.func.isRequired, +} class MainPanel extends Component { @@ -11,20 +29,35 @@ class MainPanel extends Component { super(props); this.state = { isWikiMode: true, - needOperationGroup: true, + direntList: [] }; } + componentWillReceiveProps(nextProps) { + let node = nextProps.changedNode; + if (node && node.isDir()) { + let path = node.path; + this.updateViewList(path); + } + } + + updateViewList = (filePath) => { + seafileAPI.listDir(repoID, filePath, 48).then(res => { + let direntList = []; + res.data.forEach(item => { + let dirent = new Dirent(item); + direntList.push(dirent); + }); + this.setState({ + direntList: direntList, + }); + }); + } + onMenuClick = () => { this.props.onMenuClick(); } - onEditClick = (e) => { - // const w=window.open('about:blank') - e.preventDefault(); - window.location.href= serviceUrl + '/lib/' + repoID + '/file' + this.props.filePath + '?mode=edit'; - } - onMainNavBarClick = (e) => { this.props.onMainNavBarClick(e.target.dataset.path); } @@ -38,8 +71,12 @@ class MainPanel extends Component { this.props.switchViewMode(e.target.id); } - render() { + onEditClick = (e) => { + e.preventDefault(); + window.location.href= serviceUrl + '/lib/' + repoID + '/file' + this.props.filePath + '?mode=edit'; + } + render() { let filePathList = this.props.filePath.split('/'); let nodePath = ''; let pathElem = filePathList.map((item, index) => { @@ -88,31 +125,32 @@ class MainPanel extends Component {
            {gettext('Libraries')} / - {slug} + { + this.props.filePath === '/' ? + {slug} : + {slug} + } {pathElem}
            - { this.props.isViewFileState && + { this.props.isViewFileState ? : + } - { !this.props.isViewFileState && - - - }
            @@ -120,4 +158,6 @@ class MainPanel extends Component { } } +MainPanel.propTypes = propTypes; + export default MainPanel; diff --git a/frontend/src/repo-wiki-mode.js b/frontend/src/repo-wiki-mode.js index 920f60fc0c..0e9e59db38 100644 --- a/frontend/src/repo-wiki-mode.js +++ b/frontend/src/repo-wiki-mode.js @@ -54,31 +54,8 @@ class Wiki extends Component { this.exitViewFileState(treeData, node); this.setState({isFileLoading: false}); } else { - seafileAPI.getFileInfo(repoID, filePath).then((res) => { - let { mtime, permission, last_modifier_name } = res.data; - - this.setState({ - tree_data: treeData, - latestContributor: last_modifier_name, - lastModified: moment.unix(mtime).fromNow(), - permission: permission, - filePath: filePath, - isFileLoading: false - }); - - seafileAPI.getFileDownloadLink(repoID, filePath).then((res) => { - const downLoadUrl = res.data; - seafileAPI.getFileContent(downLoadUrl).then((res) => { - this.setState({ - content: res.data, - isFileLoading: false - }); - }); - }); - }); - - let fileUrl = serviceUrl + '/wiki/lib/' + repoID + filePath; - window.history.pushState({urlPath: fileUrl, filePath: filePath}, filePath, fileUrl); + this.setState({tree_data: treeData}); + this.initMainPanelData(filePath); } }, () => { /* eslint-disable */ @@ -149,7 +126,14 @@ class Wiki extends Component { onpopstate = (event) => { if (event.state && event.state.filePath) { - this.initMainPanelData(event.state.filePath); + let path = event.state.filePath; + if (this.isMarkdownFile(path)) { + this.initMainPanelData(path); + } else { + let changedNode = this.state.tree_data.getNodeByPath(path); + this.exitViewFileState(this.state.tree_data, changedNode); + } + } } @@ -177,9 +161,11 @@ class Wiki extends Component { window.history.pushState({urlPath: fileUrl, filePath: node.path},node.path, fileUrl); } - onMainNodeClick = (node) => { + onMainItemClick = (direntPath) => { let tree = this.state.tree_data.clone(); - tree.expandNode(node); + let node = tree.getNodeByPath(direntPath); + let parentNode = tree.findNodeParentFromTree(node); + tree.expandNode(parentNode); if (node.isMarkdown()) { this.initMainPanelData(node.path); this.enterViewFileState(tree, node, node.path); @@ -192,6 +178,15 @@ class Wiki extends Component { } } + onMainItemDelete = (direntPath) => { + let node = this.state.tree_data.getNodeByPath(direntPath); + this.onDeleteNode(node); + } + + onMainItemRename = () => { + //todos: + } + onNodeClick = (e, node) => { if (node instanceof Node && node.isMarkdown()){ let tree = this.state.tree_data.clone(); @@ -315,7 +310,6 @@ class Wiki extends Component { }); } else if (node.isDir()) { editorUtilities.renameDir(filePath, newName).then(res => { - let currentFilePath = this.state.filePath; let currentFileNode = tree.getNodeByPath(currentFilePath); let nodePath = node.path; @@ -355,11 +349,18 @@ class Wiki extends Component { onDeleteNode = (node) => { let filePath = node.path; if (node.isDir()) { - editorUtilities.deleteDir(filePath); + editorUtilities.deleteDir(filePath).then(() => { + this.deleteNode(node); + }); } else { - editorUtilities.deleteFile(filePath); + editorUtilities.deleteFile(filePath).then(() => { + this.deleteNode(node); + }); } - + } + + deleteNode = (node) => { + let tree = this.state.tree_data.clone(); let isCurrentFile = false; if (node.isDir()) { @@ -368,9 +369,9 @@ class Wiki extends Component { isCurrentFile = this.isModifyCurrentFile(node); } - let tree = this.state.tree_data.clone(); - if (this.state.isViewFileState) { + tree.deleteNode(node); + if (isCurrentFile) { let homeNode = this.getHomeNode(tree); tree.expandNode(homeNode); @@ -385,13 +386,14 @@ class Wiki extends Component { } else { let parentNode = tree.getNodeByPath(this.state.filePath); let isChild = tree.isNodeChild(parentNode, node); + + tree.deleteNode(node); if (isChild) { this.exitViewFileState(tree, parentNode); } else { this.setState({tree_data: tree}); } } - tree.deleteNode(node); } @@ -528,14 +530,13 @@ class Wiki extends Component { isViewFileState={this.state.isViewFileState} changedNode={this.state.changedNode} isFileLoading={this.state.isFileLoading} + switchViewMode={this.switchViewMode} onLinkClick={this.onLinkClick} onMenuClick={this.onMenuClick} onSearchedClick={this.onSearchedClick} onMainNavBarClick={this.onMainNavBarClick} - onMainNodeClick={this.onMainNodeClick} - switchViewMode={this.switchViewMode} - onDeleteNode={this.onDeleteNode} - onRenameNode={this.onRenameNode} + onMainItemClick={this.onMainItemClick} + onMainItemDelete={this.onMainItemDelete} /> ); diff --git a/media/css/seahub_react.css b/media/css/seahub_react.css index dc119819b3..264ae04a3c 100644 --- a/media/css/seahub_react.css +++ b/media/css/seahub_react.css @@ -777,9 +777,16 @@ a.op-icon:focus { .table-container table th { padding-top: 1rem; } +.table-container table .select, +.table-container table .star, .table-container table .icon { text-align: center; } + +.table-container table .star .empty { + color: #d0d0d0; +} + .table-container table .icon img { width: 1.5rem; height: 1.5rem; diff --git a/seahub/api2/endpoints/repos.py b/seahub/api2/endpoints/repos.py index 75ea8bef70..5085edf631 100644 --- a/seahub/api2/endpoints/repos.py +++ b/seahub/api2/endpoints/repos.py @@ -13,7 +13,8 @@ from seahub.api2.utils import api_error from seahub.base.templatetags.seahub_tags import email2nickname, \ email2contact_email -from seahub.utils import is_org_context +from seahub.utils.repo import get_repo_owner, is_repo_admin, \ + repo_has_been_shared_out from seahub.views import check_folder_permission from seaserv import seafile_api @@ -45,10 +46,14 @@ class RepoView(APIView): error_msg = 'Permission denied.' return api_error(status.HTTP_403_FORBIDDEN, error_msg) - if is_org_context(request): - repo_owner = seafile_api.get_org_repo_owner(repo_id) - else: - repo_owner = seafile_api.get_repo_owner(repo_id) + username = request.user.username + repo_owner = get_repo_owner(request, repo_id) + + try: + has_been_shared_out = repo_has_been_shared_out(request, repo_id) + except Exception as e: + has_been_shared_out = False + logger.error(e) result = { "repo_id": repo.id, @@ -62,6 +67,10 @@ class RepoView(APIView): "encrypted": repo.encrypted, "file_count": repo.file_count, "permission": permission, + "no_quota": True if seafile_api.check_quota(repo_id) < 0 else False, + "is_admin": is_repo_admin(username, repo_id), + "is_virtual": repo.is_virtual, + "has_been_shared_out": has_been_shared_out, } return Response(result) diff --git a/seahub/api2/views.py b/seahub/api2/views.py index 3f98d480ce..13fe0d8959 100644 --- a/seahub/api2/views.py +++ b/seahub/api2/views.py @@ -75,7 +75,8 @@ from seahub.utils.repo import get_repo_owner, get_library_storages, \ get_locked_files_by_dir, get_related_users_by_repo, \ is_valid_repo_id_format, can_set_folder_perm_by_user, \ add_encrypted_repo_secret_key_to_database -from seahub.utils.star import star_file, unstar_file +from seahub.utils.star import star_file, unstar_file, \ + get_dir_starred_files from seahub.utils.file_types import DOCUMENT from seahub.utils.file_size import get_file_size_unit from seahub.utils.file_op import check_file_lock @@ -1893,7 +1894,7 @@ def get_dir_entrys_by_id(request, repo, path, dir_id, request_type=None): """ username = request.user.username try: - dirs = seafserv_threaded_rpc.list_dir_with_perm(repo.id, path, dir_id, + dirs = seafile_api.list_dir_with_perm(repo.id, path, dir_id, username, -1, -1) dirs = dirs if dirs else [] except SearpcError, e: @@ -1945,10 +1946,16 @@ def get_dir_entrys_by_id(request, repo, path, dir_id, request_type=None): if e not in nickname_dict: nickname_dict[e] = email2nickname(e) + starred_files = get_dir_starred_files(username, repo.id, path) for e in file_list: e['modifier_contact_email'] = contact_email_dict.get(e['modifier_email'], '') e['modifier_name'] = nickname_dict.get(e['modifier_email'], '') + file_path = posixpath.join(path, e['name']) + e['starred'] = False + if normalize_file_path(file_path) in starred_files: + e['starred'] = True + dir_list.sort(lambda x, y: cmp(x['name'].lower(), y['name'].lower())) file_list.sort(lambda x, y: cmp(x['name'].lower(), y['name'].lower())) diff --git a/seahub/utils/repo.py b/seahub/utils/repo.py index a7dbca8810..27d6c5146c 100644 --- a/seahub/utils/repo.py +++ b/seahub/utils/repo.py @@ -240,5 +240,31 @@ def is_group_repo_staff(repo_id, username): return is_staff +def repo_has_been_shared_out(request, repo_id): + + has_been_shared_out = False + username = request.user.username + + if is_org_context(request): + org_id = request.user.org.org_id + + is_inner_org_pub_repo = False + # check if current repo is pub-repo + org_pub_repos = seafile_api.list_org_inner_pub_repos_by_owner( + org_id, username) + for org_pub_repo in org_pub_repos: + if repo_id == org_pub_repo.id: + is_inner_org_pub_repo = True + break + + if seafile_api.org_repo_has_been_shared(repo_id, including_groups=True) or is_inner_org_pub_repo: + has_been_shared_out = True + else: + if seafile_api.repo_has_been_shared(repo_id, including_groups=True) or \ + (not request.cloud_mode and seafile_api.is_inner_pub_repo(repo_id)): + has_been_shared_out = True + + return has_been_shared_out + # TODO from seahub.share.utils import is_repo_admin diff --git a/seahub/utils/star.py b/seahub/utils/star.py index 3a03e57402..683188fb9e 100644 --- a/seahub/utils/star.py +++ b/seahub/utils/star.py @@ -1,14 +1,11 @@ # Copyright (c) 2012-2016 Seafile Ltd. # -*- coding: utf-8 -*- import logging -import urllib2 from django.db import IntegrityError -from pysearpc import SearpcError -from seaserv import seafile_api - from seahub.base.models import UserStarredFiles +from seahub.utils import normalize_file_path # Get an instance of a logger logger = logging.getLogger(__name__) @@ -61,5 +58,5 @@ def get_dir_starred_files(email, repo_id, parent_dir, org_id=-1): repo_id=repo_id, path__startswith=parent_dir, org_id=org_id) - return [ f.path for f in starred_files ] + return [ normalize_file_path(f.path) for f in starred_files ] diff --git a/seahub/views/ajax.py b/seahub/views/ajax.py index b835209dd8..477b22b541 100644 --- a/seahub/views/ajax.py +++ b/seahub/views/ajax.py @@ -46,7 +46,8 @@ from seahub.utils import check_filename_with_rename, EMPTY_SHA1, \ from seahub.utils.star import get_dir_starred_files from seahub.utils.file_types import IMAGE, VIDEO from seahub.utils.file_op import check_file_lock, ONLINE_OFFICE_LOCK_OWNER -from seahub.utils.repo import get_locked_files_by_dir, get_repo_owner +from seahub.utils.repo import get_locked_files_by_dir, get_repo_owner, \ + repo_has_been_shared_out from seahub.utils.error_msg import file_type_error_msg, file_size_error_msg from seahub.base.accounts import User from seahub.thumbnail.utils import get_thumbnail_src @@ -332,26 +333,8 @@ def list_lib_dir(request, repo_id): result["is_admin"] = is_repo_admin(username, repo_id) if repo_owner == username: result["is_repo_owner"] = True - try: - if is_org_context(request): - org_id = request.user.org.org_id - - is_inner_org_pub_repo = False - # check if current repo is pub-repo - org_pub_repos = seafile_api.list_org_inner_pub_repos_by_owner( - org_id, username) - for org_pub_repo in org_pub_repos: - if repo_id == org_pub_repo.id: - is_inner_org_pub_repo = True - break - - if seafile_api.org_repo_has_been_shared(repo_id, including_groups=True) or is_inner_org_pub_repo: - result["has_been_shared_out"] = True - else: - if seafile_api.repo_has_been_shared(repo_id, including_groups=True) or \ - (not request.cloud_mode and seafile_api.is_inner_pub_repo(repo_id)): - result["has_been_shared_out"] = True + result["has_been_shared_out"] = repo_has_been_shared_out(request, repo_id) except Exception as e: logger.error(e)