From a7b0fb17f487d919e65d0a4a1df71dbcd3c2d191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E9=A1=BA=E5=BC=BA?= Date: Wed, 20 Feb 2019 11:54:25 +0800 Subject: [PATCH] Combine view mode (#2959) --- frontend/src/app.js | 4 +- .../src/components/cur-dir-path/dir-path.js | 2 +- frontend/src/components/cur-dir-path/index.js | 2 +- frontend/src/components/dir-view/dir-panel.js | 287 ---- frontend/src/components/dir-view/dir-view.js | 756 ---------- frontend/src/components/file-content-view.js | 145 ++ .../toolbar/dir-operation-toolbar.js | 53 +- .../src/components/toolbar/groups-toolbar.js | 4 +- .../components/toolbar/repo-view-toobar.js | 2 +- .../components/toolbar/view-mode-toolbar.js | 2 +- .../components/tree-view/tree-node-view.js | 4 +- .../src/components/tree-view/tree-node.js | 21 +- .../src/components/tree-view/tree-view.js | 2 + frontend/src/css/file-uploader.css | 2 +- frontend/src/css/layout.css | 5 +- frontend/src/css/lib-content-view.css | 168 +++ frontend/src/css/side-panel.css | 3 +- frontend/src/css/toolbar.css | 9 +- frontend/src/pages/groups/group-view.js | 4 +- .../lib-content-view/lib-content-main.js | 285 ++++ .../pages/lib-content-view/lib-content-nav.js | 235 +++ .../lib-content-view/lib-content-view.js | 1279 +++++++++++++++++ frontend/src/pages/my-libs/my-libs.js | 2 +- .../src/pages/repo-wiki-mode/main-panel.js | 4 +- .../shared-with-all/public-shared-view.js | 4 +- frontend/src/pages/wiki/main-panel.js | 6 +- frontend/src/pages/wikis/wikis.js | 4 +- frontend/src/repo-wiki-mode.js | 4 +- frontend/src/utils/utils.js | 2 +- media/css/seahub_react.css | 2 +- 30 files changed, 2178 insertions(+), 1124 deletions(-) delete mode 100644 frontend/src/components/dir-view/dir-panel.js delete mode 100644 frontend/src/components/dir-view/dir-view.js create mode 100644 frontend/src/components/file-content-view.js create mode 100644 frontend/src/css/lib-content-view.css create mode 100644 frontend/src/pages/lib-content-view/lib-content-main.js create mode 100644 frontend/src/pages/lib-content-view/lib-content-nav.js create mode 100644 frontend/src/pages/lib-content-view/lib-content-view.js diff --git a/frontend/src/app.js b/frontend/src/app.js index ba995f7090..fc8cc53e5f 100644 --- a/frontend/src/app.js +++ b/frontend/src/app.js @@ -20,7 +20,7 @@ import SharedLibraries from './pages/shared-libs/shared-libs'; import MyLibraries from './pages/my-libs/my-libs'; import MyLibDeleted from './pages/my-libs/my-libs-deleted'; import PublicSharedView from './pages/shared-with-all/public-shared-view'; -import DirView from './components/dir-view/dir-view'; +import LibContentView from './pages/lib-content-view/lib-content-view'; import Group from './pages/groups/group-view'; import Groups from './pages/groups/groups-view'; import Wikis from './pages/wikis/wikis'; @@ -212,7 +212,7 @@ class App extends Component { - + { - this.setState({isDirentDetailShow: false}); - this.props.onPathClick(path); - } - - onItemClick = (dirent) => { - this.setState({isDirentDetailShow: false}); - this.props.onItemClick(dirent); - } - - // on '' - onDirentClick = (dirent) => { - if (this.state.isDirentDetailShow) { - this.onItemDetails(dirent); - } - } - - onItemDetails = (dirent) => { - this.setState({ - currentDirent: dirent, - isDirentDetailShow: true, - }); - } - - onItemDetailsClose = () => { - // todo there is bug when change item - this.setState({isDirentDetailShow: false}); - } - - onUploadFile = (e) => { - e.nativeEvent.stopImmediatePropagation(); - this.uploader.onFileUpload(); - } - - onUploadFolder = (e) => { - e.nativeEvent.stopImmediatePropagation(); - this.uploader.onFolderUpload(); - } - - switchViewMode = (mode) => { - let { path, repoID } = this.props; - if (mode === this.state.currentMode) { - return; - } - if (mode === 'wiki') { - var url = siteRoot + 'wiki/lib/' + repoID + path; - window.location = url; - } - cookie.save('view_mode', mode, { path: '/' }); - - this.setState({currentMode: mode}); - } - - render() { - const errMessage = (
{gettext('Folder does not exist.')}
); - const showRepoInfoBar = this.props.path === '/' && ( - this.props.usedRepoTags.length != 0 || this.props.readmeMarkdown != null || - this.props.draftCounts != 0 || this.props.reviewCounts != 0); - return ( -
-
- {!this.props.libNeedDecrypt && -
- -
- {this.props.isDirentSelected ? - : - - } -
- -
- } - -
-
- {!this.props.libNeedDecrypt && -
-
- -
-
- {this.props.errorMsg ? -

{this.props.errorMsg}

: - - {!this.props.pathExist ? - errMessage : - - {showRepoInfoBar && ( - - )} - - this.uploader = uploader} - path={this.props.path} - repoID={this.props.repoID} - direntList={this.props.direntList} - onFileUploadSuccess={this.props.onFileUploadSuccess} - /> - - } - - } -
-
- } - {this.state.isDirentDetailShow && ( -
- -
- )} - {this.props.libNeedDecrypt && ( - - - - )} -
-
- ); - } -} - -DirPanel.propTypes = propTypes; - -export default DirPanel; diff --git a/frontend/src/components/dir-view/dir-view.js b/frontend/src/components/dir-view/dir-view.js deleted file mode 100644 index c573c0e2e4..0000000000 --- a/frontend/src/components/dir-view/dir-view.js +++ /dev/null @@ -1,756 +0,0 @@ -import React, { Fragment } from 'react'; -import PropTypes from 'prop-types'; -import moment from 'moment'; -import { isPro, siteRoot, canGenerateShareLink, canGenerateUploadLink, username } from '../../utils/constants'; -import { seafileAPI } from '../../utils/seafile-api'; -import { Utils } from '../../utils/utils'; -import { gettext } from '../../utils/constants'; -import collabServer from '../../utils/collab-server'; -import toaster from '../toast'; -import DirPanel from './dir-panel'; -import Dirent from '../../models/dirent'; -import FileTag from '../../models/file-tag'; -import RepoTag from '../../models/repo-tag'; -import RepoInfo from '../../models/repo-info'; - -const propTypes = { - pathPrefix: PropTypes.array.isRequired, - onTabNavClick: PropTypes.func.isRequired, - onMenuClick: PropTypes.func.isRequired, -}; - -class DirView extends React.Component { - - constructor(props) { - super(props); - - this.state = { - path: '/', - pathExist: true, - - repoName: '', - repoID: '', - repoEncrypted: false, - isGroupOwnedRepo: false, - isAdmin: false, - ownerEmail: '', - userPerm: '', - - permission: true, - libNeedDecrypt: false, - isDirentSelected: false, - isAllDirentSelected: false, - isDirentListLoading: true, - currentRepoInfo: null, - sortBy: 'name', // 'name' or 'time' - sortOrder: 'asc', // 'asc' or 'desc' - direntList: [], - selectedDirentList: [], - dirID: '', - errorMsg: '', - usedRepoTags: [], - readmeMarkdown: null, - draftCounts: 0, - reviewCounts: 0, - }; - window.onpopstate = this.onpopstate; - this.lastModifyTime = new Date(); - } - - onpopstate = (event) => { - if (event.state && event.state.path) { - this.updateDirentList(event.state.path); - this.setState({path: event.state.path}); - } - } - - componentDidMount() { - // eg: http://127.0.0.1:8000/library/repo_id/repo_name/**/**/\ - let location = decodeURIComponent(window.location.href); - let repoID = this.props.repoID; - collabServer.watchRepo(repoID, this.onRepoUpdateEvent); - seafileAPI.getRepoDraftReviewCounts(repoID).then(res => { - this.setState({ - draftCounts: res.data.draft_counts, - reviewCounts: res.data.review_counts, - }); - }); - this.updateUsedRepoTags(); - seafileAPI.getRepoInfo(repoID).then(res => { - let repoInfo = new RepoInfo(res.data); - this.setState({ - currentRepoInfo: repoInfo, - repoID: repoInfo.repo_id, - repoName: repoInfo.repo_name, - repoEncrypted: repoInfo.encrypted, - isVirtual: repoInfo.is_virtual, - isAdmin: repoInfo.is_admin, - ownerEmail: repoInfo.owner_email, - permission: repoInfo.permission === 'rw', - libNeedDecrypt: res.data.lib_need_decrypt, - }); - - const ownerEmail = repoInfo.owner_email; - if (repoInfo.owner_email.indexOf('@seafile_group') != -1) { - const groupID = ownerEmail.substring(0, ownerEmail.indexOf('@')); - this.getGroupInfo(groupID); - this.setState({ - isGroupOwnedRepo: true - }); - } - - let repoID = repoInfo.repo_id; - let path = location.slice(location.indexOf(repoID) + repoID.length + 1); // get the string after repoID - path = path.slice(path.indexOf('/')); // get current path - this.setState({path: path}); - if (!res.data.lib_need_decrypt) { - this.updateDirentList(path); - - let fileUrl = siteRoot + 'library/' + repoInfo.repo_id + '/' + encodeURIComponent(repoInfo.repo_name) + Utils.encodePath(path); - window.history.pushState({url: fileUrl, path: path}, path, fileUrl); - } - }).catch(error => { - if (error.response) { - if (error.response.status == 403) { - this.setState({ - isDirentListLoading: false, - errorMsg: gettext('Permission denied') - }); - } else { - this.setState({ - isDirentListLoading: false, - errorMsg: gettext('Error') - }); - } - } else { - this.setState({ - isDirentListLoading: false, - errorMsg: gettext('Please check the network.') - }); - } - }); - } - - componentWillUnmount() { - collabServer.unwatchRepo(this.props.repoID, this.onRepoUpdateEvent); - } - - componentDidUpdate() { - this.lastModifyTime = new Date(); - } - - getGroupInfo = (groupID) => { - seafileAPI.getGroup(groupID).then(res => { - if (res.data.admins.indexOf(username) != -1) { - this.setState({ - isDepartmentAdmin: true - }); - } - }); - } - - onRepoUpdateEvent = () => { - let currentTime = new Date(); - if ((parseFloat(currentTime - this.lastModifyTime)/1000) <= 5) { - return; - } - let repoID = this.props.repoID; - let { path, dirID } = this.state; - seafileAPI.dirMetaData(repoID, path).then((res) => { - if (res.data.id !== dirID) { - toaster.notify( - - {gettext('This folder has been updated. ')} - {gettext('Refresh')} - , - {id: 'repo_updated', duration: 3600} - ); - } - }); - } - - updateUsedRepoTags = () => { - seafileAPI.listRepoTags(this.props.repoID).then(res => { - let usedRepoTags = []; - res.data.repo_tags.forEach(item => { - let usedRepoTag = new RepoTag(item); - if (usedRepoTag.fileCount > 0) { - usedRepoTags.push(usedRepoTag); - } - }); - this.setState({usedRepoTags: usedRepoTags}); - }); - } - - updateReadmeMarkdown = (direntList) => { - this.setState({readmeMarkdown: null}); - direntList.some(item => { - let fileName = item.name.toLowerCase(); - if (fileName === 'readme.md' || fileName === 'readme.markdown') { - this.setState({readmeMarkdown: item}); - return true; - } - }); - } - - updateDirentList = (path) => { - let repoID = this.state.repoID; - this.setState({isDirentListLoading: true}); - seafileAPI.listDir(repoID, path, {'with_thumbnail': true}).then(res => { - let direntList = res.data.dirent_list.map(item => { - let fileName = item.name.toLowerCase(); - if (fileName === 'readme.md' || fileName === 'readme.markdown') { - this.setState({readmeMarkdown: item}); - } - return new Dirent(item); - }); - - this.setState({ - isDirentListLoading: false, - pathExist: true, - userPerm: res.data.user_perm, - direntList: Utils.sortDirents(direntList, this.state.sortBy, this.state.sortOrder), - dirID: res.headers.oid, - }); - - if (!this.state.repoEncrypted && direntList.length) { - this.getThumbnails(repoID, path, this.state.direntList); - } - }).catch(() => { - this.setState({ - isDirentListLoading: false, - pathExist: false - }); - }); - } - - getThumbnails = (repoID, path, direntList) => { - let items = direntList.filter((item) => { - return Utils.imageCheck(item.name) && !item.encoded_thumbnail_src; - }); - if (items.length == 0) { - return ; - } - - const _this = this; - const len = items.length; - const thumbnailSize = 48; - let getThumbnail = (i) => { - const curItem = items[i]; - const curItemPath = [path, curItem.name].join('/'); - seafileAPI.createThumbnail(repoID, curItemPath, thumbnailSize).then((res) => { - curItem.encoded_thumbnail_src = res.data.encoded_thumbnail_src; - }).catch((error) => { - // do nothing - }).then(() => { - if (i < len - 1) { - getThumbnail(++i); - } else { - _this.setState({ - direntList: direntList - }); - } - }); - }; - getThumbnail(0); - } - - onItemClick = (dirent) => { - this.resetSelected(); - let direntPath = Utils.joinPath(this.state.path, dirent.name); - if (dirent.isDir()) { - this.updateDirentList(direntPath); - this.setState({path: direntPath}); - - let fileUrl = siteRoot + 'library/' + this.state.repoID + '/' + encodeURIComponent(this.state.repoName) + Utils.encodePath(direntPath); - window.history.pushState({url: fileUrl, path: direntPath}, direntPath, fileUrl); - } else { - const w=window.open('about:blank'); - const url = siteRoot + 'lib/' + this.state.repoID + '/file' + Utils.encodePath(direntPath); - w.location.href = url; - } - } - - onAddFolder = (dirPath) => { - let repoID = this.state.repoID; - seafileAPI.createDir(repoID, dirPath).then(() => { - let name = Utils.getFileName(dirPath); - let dirent = this.createDirent(name, 'dir'); - let direntList = this.addItem(dirent, 'dir'); - this.setState({direntList: direntList}); - }); - } - - onAddFile = (filePath, isDraft) => { - let repoID = this.state.repoID; - seafileAPI.createFile(repoID, filePath, isDraft).then(res => { - let name = Utils.getFileName(filePath); - let dirent = this.createDirent(name, 'file', res.data); - let direntList = this.addItem(dirent, 'file'); - this.setState({direntList: direntList}); - this.updateReadmeMarkdown(direntList); - }); - } - - onItemDelete = (dirent) => { - let repoID = this.state.repoID; - let direntPath = Utils.joinPath(this.state.path, dirent.name); - if (dirent.isDir()) { - seafileAPI.deleteDir(repoID, direntPath).then(() => { - let direntList = this.deleteItem(dirent); - this.setState({direntList: direntList}); - }).catch(() => { - // todo - }); - } else { - seafileAPI.deleteFile(repoID, direntPath).then(() => { - let direntList = this.deleteItem(dirent); - this.setState({direntList: direntList}); - this.updateReadmeMarkdown(direntList); - }).catch(() => { - // todo - }); - } - } - - onItemRename = (dirent, newName) => { - let repoID = this.state.repoID; - let direntPath = Utils.joinPath(this.state.path, dirent.name); - if (dirent.isDir()) { - seafileAPI.renameDir(repoID, direntPath, newName).then(() => { - let direntList = this.renameItem(dirent, newName); - this.setState({direntList: direntList}); - }).catch(() => { - //todo - }); - } else { - seafileAPI.renameFile(repoID, direntPath, newName).then(() => { - let direntList = this.renameItem(dirent, newName); - this.setState({direntList: direntList}); - this.updateReadmeMarkdown(direntList); - }).catch(() => { - //todo - }); - } - } - - onItemMove = (destRepo, dirent, moveToDirentPath) => { - let dirName = dirent.name; - let repoID = this.state.repoID; - seafileAPI.moveDir(repoID, destRepo.repo_id, moveToDirentPath, this.state.path, dirName).then(() => { - - let direntList = this.deleteItem(dirent); - this.setState({direntList: direntList}); - this.updateReadmeMarkdown(direntList); - - let message = gettext('Successfully moved %(name)s.'); - message = message.replace('%(name)s', dirName); - toaster.success(message); - }).catch(() => { - let message = gettext('Failed to move %(name)s'); - message = message.replace('%(name)s', dirName); - toaster.danger(message); - }); - } - - onItemCopy = (destRepo, dirent, copyToDirentPath) => { - let dirName = dirent.name; - let repoID = this.state.repoID; - seafileAPI.copyDir(repoID, destRepo.repo_id, copyToDirentPath, this.state.path, dirName).then(() => { - let message = gettext('Successfully copied %(name)s.'); - message = message.replace('%(name)s', dirName); - toaster.success(message); - }).catch(() => { - let message = gettext('Failed to copy %(name)s'); - message = message.replace('%(name)s', dirName); - toaster.danger(message); - }); - } - - onItemSelected = (dirent) => { - let direntList = this.state.direntList.map(item => { - if (item.name === dirent.name) { - item.isSelected = !item.isSelected; - } - return item; - }); - let selectedDirentList = direntList.filter(item => { - return item.isSelected; - }); - - if (selectedDirentList.length) { - this.setState({isDirentSelected: true}); - if (selectedDirentList.length === direntList.length) { - this.setState({ - isAllDirentSelected: true, - direntList: direntList, - selectedDirentList: selectedDirentList, - }); - } else { - this.setState({ - isAllDirentSelected: false, - direntList: direntList, - selectedDirentList: selectedDirentList - }); - } - } else { - this.setState({ - isDirentSelected: false, - isAllDirentSelected: false, - direntList: direntList, - selectedDirentList: [] - }); - } - } - - onItemsMove = (destRepo, destDirentPath) => { - let dirNames = this.getSelectedDirentNames(); - let repoID = this.state.repoID; - seafileAPI.moveDir(repoID, destRepo.repo_id, destDirentPath, this.state.path, dirNames).then(() => { - let direntList = this.deleteItems(dirNames); - this.setState({ - direntList: direntList, - isDirentSelected: false, - selectedDirentList: [], - }); - this.updateReadmeMarkdown(direntList); - let message = gettext('Successfully moved %(name)s.'); - message = message.replace('%(name)s', dirNames); - toaster.success(message); - }).catch(() => { - let message = gettext('Failed to move %(name)s'); - message = message.replace('%(name)s', dirNames); - toaster.danger(message); - }); - } - - onItemsCopy = (destRepo, destDirentPath) => { - let dirNames = this.getSelectedDirentNames(); - let repoID = this.state.repoID; - seafileAPI.copyDir(repoID, destRepo.repo_id, destDirentPath, this.state.path, dirNames).then(() => { - let message = gettext('Successfully copied %(name)s.'); - message = message.replace('%(name)s', dirNames); - toaster.success(message); - }).catch(() => { - let message = gettext('Failed to copy %(name)s'); - message = message.replace('%(name)s', dirNames); - toaster.danger(message); - }); - } - - onItemsDelete = () => { - let dirNames = this.getSelectedDirentNames(); - let repoID = this.state.repoID; - seafileAPI.deleteMutipleDirents(repoID, this.state.path, dirNames).then(res => { - let direntList = this.deleteItems(dirNames); - this.updateReadmeMarkdown(direntList); - this.setState({ - direntList: direntList, - isDirentSelected: false, - selectedDirentList: [], - }); - }); - } - - onAllItemSelected = () => { - if (this.state.isAllDirentSelected) { - let direntList = this.state.direntList.map(item => { - item.isSelected = false; - return item; - }); - this.setState({ - isDirentSelected: false, - isAllDirentSelected: false, - direntList: direntList, - selectedDirentList: [], - }); - } else { - let direntList = this.state.direntList.map(item => { - item.isSelected = true; - return item; - }); - this.setState({ - isDirentSelected: true, - isAllDirentSelected: true, - direntList: direntList, - selectedDirentList: direntList, - }); - } - } - - onFileTagChanged = (dirent, direntPath) => { - let repoID = this.state.repoID; - seafileAPI.listFileTags(repoID, direntPath).then(res => { - let fileTags = res.data.file_tags.map(item => { - return new FileTag(item); - }); - this.updateDirent(dirent, 'file_tags', fileTags); - }); - - this.updateUsedRepoTags(); - } - - onMenuClick = () => { - this.props.onMenuClick(); - } - - onPathClick = (path) => { - this.updateDirentList(path); - this.setState({path: path}); - - let fileUrl = siteRoot + 'library/' + this.state.repoID + '/' + encodeURIComponent(this.state.repoName) + Utils.encodePath(path); - window.history.pushState({url: fileUrl, path: path}, path, fileUrl); - } - - updateDirent = (dirent, paramKey, paramValue) => { - let newDirentList = this.state.direntList.map(item => { - if (item.name === dirent.name) { - item[paramKey] = paramValue; - } - return item; - }); - this.setState({direntList: newDirentList}); - } - - onFileUploadSuccess = (direntObject) => { - let isExist = this.state.direntList.some(item => { - return item.name === direntObject.name && item.type === direntObject.type; - }); - if (isExist) { - let direntList = this.state.direntList; - for (let i = 0; i < direntList.length; i++) { - let dirent = direntList[i]; - if (dirent.name === direntObject.name && dirent.type === direntObject.type) { - let mtime = moment.unix(direntObject.mtime).fromNow(); - this.updateDirent(dirent, 'mtime', mtime); // todo file size is need update too, api is not return; - break; - } - } - } else { - direntObject.permission = 'rw'; - let dirent = new Dirent(direntObject); - if (direntObject.type === 'dir') { - this.setState({direntList: [dirent, ...this.state.direntList]}); - } else { - this.setState({direntList: [...this.state.direntList, dirent]}); - this.updateReadmeMarkdown(this.state.direntList); - } - } - } - - onSearchedClick = (selectedItem) => { - if (selectedItem.is_dir === true) { - this.updateDirentList(selectedItem.path); - this.setState({path: selectedItem.path}); - } else { - let url = siteRoot + 'lib/' + selectedItem.repo_id + '/file' + selectedItem.path; - let newWindow = window.open('about:blank'); - newWindow.location.href = url; - } - } - - resetSelected = () => { - this.setState({isDirentSelected: false, isAllDirentSelected: false}); - } - - addItem = (dirent, type) => { - let direntList = this.state.direntList.map(item => {return item;}); //clone - if (type === 'dir') { - direntList.unshift(dirent); - return direntList; - } else { // type === 'file' - // there will be there conditions; - // first: direntList.length === 0; - // second: all the direntList's items are dir; - // third: direntList has dir and file; - let length = direntList.length; - if (length === 0 || direntList[length - 1].type === 'dir') { - direntList.push(dirent); - } else { - let index = 0; - for (let i = 0; i <= length; i++) { - if (direntList[i].type === 'file') { - index = i; - break; - } - } - direntList.splice(index, 0, dirent); - } - return direntList; - } - } - - deleteItem = (dirent) => { - return this.state.direntList.filter(item => { - return item.name !== dirent.name; - }); - } - - renameItem = (dirent, newName) => { - return this.state.direntList.map(item => { - if (item.name === dirent.name) { - item.name = newName; - } - return item; - }); - } - - deleteItems = (dirNames) => { - let direntList = this.state.direntList.map(item => {return item;}); //clone - while (dirNames.length) { - for (let i = 0; i < direntList.length; i++) { - if (direntList[i].name === dirNames[0]) { - direntList.splice(i, 1); - break; - } - } - dirNames.shift(); - } - return direntList; - } - - createDirent(name, type, direntInfo) { - let data = new Date().getTime()/1000; - let dirent = null; - if (type === 'dir') { - dirent = new Dirent({ - id: '000000000000000000', - name: name, - type: type, - mtime: data, - permission: 'rw', - }); - } else { - dirent = new Dirent({ - id: '000000000000000000', - name: name, - type: type, - mtime: data, - permission: 'rw', - size: direntInfo.size, - starred: false, - is_locked: false, - lock_time: '', - lock_owner: null, - locked_by_me: false, - modifier_name: '', - modifier_email: '', - modifier_contact_email: '', - file_tags: [] - }); - } - return dirent; - } - - getSelectedDirentNames = () => { - let names = []; - this.state.selectedDirentList.forEach(selectedDirent => { - names.push(selectedDirent.name); - }); - return names; - } - - isMarkdownFile(filePath) { - let index = filePath.lastIndexOf('.'); - if (index === -1) { - return false; - } else { - let type = filePath.substring(index).toLowerCase(); - if (type === '.md' || type === '.markdown') { - return true; - } else { - return false; - } - } - } - - onLibDecryptDialog = () => { - this.setState({ - libNeedDecrypt: !this.state.libNeedDecrypt - }); - this.updateDirentList(this.state.path); - } - - sortItems = (sortBy, sortOrder) => { - this.setState({ - sortBy: sortBy, - sortOrder: sortOrder, - items: Utils.sortDirents(this.state.direntList, sortBy, sortOrder) - }); - } - - render() { - let showShareBtn = false, - enableDirPrivateShare = false; - const { repoEncrypted, isAdmin, ownerEmail, userPerm, isVirtual, isDepartmentAdmin } = this.state; - const isRepoOwner = ownerEmail == username; - if (!repoEncrypted && ( - canGenerateShareLink || canGenerateUploadLink || - isRepoOwner || isAdmin) && ( - userPerm == 'rw' || userPerm == 'r')) { - showShareBtn = true; - if (!isVirtual && (isRepoOwner || isAdmin || isDepartmentAdmin)) { - enableDirPrivateShare = true; - } - } - - return ( - - ); - } -} - -DirView.propTypes = propTypes; - -export default DirView; diff --git a/frontend/src/components/file-content-view.js b/frontend/src/components/file-content-view.js new file mode 100644 index 0000000000..268cac0a67 --- /dev/null +++ b/frontend/src/components/file-content-view.js @@ -0,0 +1,145 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { Utils } from '../utils/utils'; +import { gettext, siteRoot } from '../utils/constants'; +import { seafileAPI } from '../utils/seafile-api'; +import CommonToolbar from './toolbar/common-toolbar'; +import CurDirPath from './cur-dir-path'; +import WikiMarkdownViewer from './wiki-markdown-viewer'; + +const propTypes = { + pathPrefix: PropTypes.array.isRequired, + currentMode: PropTypes.string.isRequired, + path: PropTypes.string.isRequired, + hash: PropTypes.string, + onTabNavClick: PropTypes.func.isRequired, + onSideNavMenuClick: PropTypes.func.isRequired, + onSearchedClick: PropTypes.func.isRequired, + onMainNavBarClick: PropTypes.func.isRequired, + repoID: PropTypes.string.isRequired, + currentRepoInfo: PropTypes.object, + repoPermission: PropTypes.bool, + isDraft: PropTypes.bool, + hasDraft: PropTypes.bool, + goDraftPage: PropTypes.func.isRequired, + reviewStatus: PropTypes.any, + goReviewPage: PropTypes.func.isRequired, + isFileLoading: PropTypes.bool.isRequired, + filePermission: PropTypes.bool, + content: PropTypes.string, + lastModified: PropTypes.string, + latestContributor: PropTypes.string, + onLinkClick: PropTypes.func.isRequired, +}; + +class FileContentView extends React.Component { + + componentDidMount() { + if (this.props.hash) { + let hash = this.props.hash; + setTimeout(function() { + window.location.hash = hash; + }, 500); + } + } + + onEditClick = (e) => { + e.preventDefault(); + let { path, repoID } = this.props; + let url = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(path) + '?mode=edit'; + window.open(url); + } + + onNewDraft = (e) => { + e.preventDefault(); + let { path, repoID } = this.props; + seafileAPI.createDraft(repoID, path).then(res => { + window.location.href = siteRoot + 'lib/' + res.data.origin_repo_id + '/file' + res.data.draft_file_path + '?mode=edit'; + }); + } + + goDraftPage = (e) => { + e.preventDefault(); + this.props.goDraftPage(); + } + + goReviewPage = (e) => { + e.preventDefault(); + this.props.goReviewPage(); + } + + render() { + let repoID = this.props.repoID; + return ( +
+
+
+ +
+ {(this.props.filePermission && !this.props.hasDraft ) && ( + + + + )} + {/* default have read priv */} + {(!this.props.isDraft && !this.props.hasDraft) && ( + + )} +
+
+ +
+
+
+
+ {this.props.currentRepoInfo && ( + + )} +
+
+ + + {this.props.reviewStatus === 'open' && +
+
+ {gettext('This file is in review stage')} + {gettext('View Review')} +
+
+ } + {(this.props.reviewStatus !== 'open' && !this.props.isDraft && this.props.hasDraft) && +
+
+ {gettext('This file is in draft stage.')} + {gettext('Edit Draft')} +
+
+ } +
+
+
+
+
+
+ ) + } +} + +FileContentView.propTypes = propTypes; + +export default FileContentView; diff --git a/frontend/src/components/toolbar/dir-operation-toolbar.js b/frontend/src/components/toolbar/dir-operation-toolbar.js index 72759642d1..f61136ef0f 100644 --- a/frontend/src/components/toolbar/dir-operation-toolbar.js +++ b/frontend/src/components/toolbar/dir-operation-toolbar.js @@ -1,16 +1,13 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import { Utils } from '../../utils/utils'; -import { gettext, siteRoot } from '../../utils/constants'; -import { seafileAPI } from '../../utils/seafile-api'; +import { gettext } from '../../utils/constants'; import ModalPortal from '../modal-portal'; import CreateFolder from '../../components/dialog/create-folder-dialog'; import CreateFile from '../../components/dialog/create-file-dialog'; import ShareDialog from '../../components/dialog/share-dialog'; const propTypes = { - isViewFile: PropTypes.bool, // just for view file, - permission: PropTypes.string, //just for view file, and premission is file permission path: PropTypes.string.isRequired, repoID: PropTypes.string.isRequired, showShareBtn: PropTypes.bool.isRequired, @@ -18,8 +15,6 @@ const propTypes = { onAddFolder: PropTypes.func.isRequired, onUploadFile: PropTypes.func.isRequired, onUploadFolder: PropTypes.func.isRequired, - isDraft: PropTypes.bool, - hasDraft: PropTypes.bool, direntList: PropTypes.array.isRequired, }; @@ -62,21 +57,6 @@ class DirOperationToolbar extends React.Component { this.setState({operationMenuStyle: style}); } - onEditClick = (e) => { - e.preventDefault(); - let { path, repoID } = this.props; - let url = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(path) + '?mode=edit'; - window.open(url); - } - - onNewDraft = (e) => { - e.preventDefault(); - let { path, repoID } = this.props; - seafileAPI.createDraft(repoID, path).then(res => { - window.location.href = siteRoot + 'lib/' + res.data.origin_repo_id + '/file' + res.data.draft_file_path + '?mode=edit'; - }); - } - onUploadClick = (e) => { this.toggleOperationMenu(e); this.setState({ @@ -108,7 +88,6 @@ class DirOperationToolbar extends React.Component { this.setState({ isShareDialogShow: !this.state.isShareDialogShow }); - } onCreateFolderToggle = () => { @@ -177,31 +156,17 @@ class DirOperationToolbar extends React.Component { } render() { - let { path, isViewFile, repoName } = this.props; - let itemType = isViewFile ? 'file' : 'dir'; - let itemName = isViewFile ? Utils.getFileName(path) : (path == '/' ? repoName : Utils.getFolderName(path)); + let { path, repoName } = this.props; + let itemType = 'dir'; + let itemName = path == '/' ? repoName : Utils.getFolderName(path); return (
- {(this.props.isViewFile && this.props.permission === 'rw' && !this.props.hasDraft ) && ( - - - - )} - - {(this.props.isViewFile && !this.props.isDraft && !this.props.hasDraft) && ( - - )} - - {!this.props.isViewFile && ( - - {Utils.isSupportUploadFolder() ? - : - - } - - - )} + {Utils.isSupportUploadFolder() ? + : + + } + {this.props.showShareBtn && } diff --git a/frontend/src/components/toolbar/groups-toolbar.js b/frontend/src/components/toolbar/groups-toolbar.js index 57700987f9..6f17c8f82f 100644 --- a/frontend/src/components/toolbar/groups-toolbar.js +++ b/frontend/src/components/toolbar/groups-toolbar.js @@ -20,8 +20,8 @@ class GroupsToolbar extends React.Component { render() { let { onShowSidePanel, onSearchedClick } = this.props; return ( -
-
+
+
{/* */} - +
); } diff --git a/frontend/src/components/tree-view/tree-node-view.js b/frontend/src/components/tree-view/tree-node-view.js index d2f47904c3..620c171f9c 100644 --- a/frontend/src/components/tree-view/tree-node-view.js +++ b/frontend/src/components/tree-view/tree-node-view.js @@ -4,6 +4,7 @@ import TreeNodeMenu from './tree-node-menu'; import { permission } from '../../utils/constants'; const propTypes = { + repoPermission: PropTypes.bool, node: PropTypes.object.isRequired, currentPath: PropTypes.string.isRequired, paddingLeft: PropTypes.number.isRequired, @@ -111,6 +112,7 @@ class TreeNodeView extends React.Component { key={item.path} node={item} paddingLeft={paddingLeft} + repoPermission={this.props.repoPermission} currentPath={this.props.currentPath} isNodeMenuShow={this.props.isNodeMenuShow} isItemFreezed={this.props.isItemFreezed} @@ -150,7 +152,7 @@ class TreeNodeView extends React.Component {
{isNodeMenuShow && (
- {(permission && this.state.isShowOperationMenu) && ( + {((this.props.repoPermission || permission) && this.state.isShowOperationMenu) && ( { - let newChild = child.clone(); + let newChild = child.clone(treeNode); return newChild; }); @@ -77,9 +77,24 @@ class TreeNode { rename(newName) { this.object.name = newName; this.path = this.generatePath(this.parentNode); + if (this.isExpanded) { + this.updateChildrenPath(this); + } // this.isLoaded = false; } + updateChildrenPath(node) { + let children = node.children; + children.forEach(child => { + child.path = child.generatePath(child.parentNode); + if (child.isExpanded) { + child.updateChildrenPath(child); + } else { + child.isLoaded = false; + } + }); + } + updateObjectProperties(keys, newValues) { if (Array.isArray(keys) && Array.isArray(newValues)) { keys.forEach((key, index) => { diff --git a/frontend/src/components/tree-view/tree-view.js b/frontend/src/components/tree-view/tree-view.js index fd986c098a..f2243e1a2e 100644 --- a/frontend/src/components/tree-view/tree-view.js +++ b/frontend/src/components/tree-view/tree-view.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import TreeNodeView from './tree-node-view'; const propTypes = { + repoPermission: PropTypes.bool, isNodeMenuShow: PropTypes.bool.isRequired, treeData: PropTypes.object.isRequired, currentPath: PropTypes.string.isRequired, @@ -39,6 +40,7 @@ class TreeView extends React.Component { return (
-
-
+
+
{canAddRepo && ( diff --git a/frontend/src/pages/lib-content-view/lib-content-main.js b/frontend/src/pages/lib-content-view/lib-content-main.js new file mode 100644 index 0000000000..4a4791a439 --- /dev/null +++ b/frontend/src/pages/lib-content-view/lib-content-main.js @@ -0,0 +1,285 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { gettext } from '../../utils/constants'; +import CommonToolbar from '../../components/toolbar/common-toolbar'; +import ViewModeToolbar from '../../components/toolbar/view-mode-toolbar'; +import DirOperationToolBar from '../../components/toolbar/dir-operation-toolbar'; +import MutipleDirOperationToolbar from '../../components/toolbar/mutilple-dir-operation-toolbar'; +import CurDirPath from '../../components/cur-dir-path'; +import DirentListView from '../../components/dirent-list-view/dirent-list-view'; +import DirentDetail from '../../components/dirent-detail/dirent-details'; +import FileUploader from '../../components/file-uploader/file-uploader'; +import RepoInfoBar from '../../components/repo-info-bar'; + +const propTypes = { + pathPrefix: PropTypes.array.isRequired, + currentMode: PropTypes.string.isRequired, + path: PropTypes.string.isRequired, + pathExist: PropTypes.bool.isRequired, + // repo + currentRepoInfo: PropTypes.object, // Initially not loaded + repoID: PropTypes.string.isRequired, + repoName: PropTypes.string.isRequired, + repoPermission: PropTypes.bool.isRequired, + repoEncrypted: PropTypes.bool.isRequired, + isAdmin: PropTypes.bool.isRequired, + userPerm: PropTypes.string.isRequired, + showShareBtn: PropTypes.bool.isRequired, + enableDirPrivateShare: PropTypes.bool.isRequired, + isRepoOwner: PropTypes.bool.isRequired, + isGroupOwnedRepo: PropTypes.bool.isRequired, + // toolbar + onTabNavClick: PropTypes.func.isRequired, + onSideNavMenuClick: PropTypes.func.isRequired, + selectedDirentList: PropTypes.array.isRequired, + onItemsMove: PropTypes.func.isRequired, + onItemsCopy: PropTypes.func.isRequired, + onItemsDelete: PropTypes.func.isRequired, + switchViewMode: PropTypes.func.isRequired, + onSearchedClick: PropTypes.func.isRequired, + onMainNavBarClick: PropTypes.func.isRequired, + // repo content + draftCounts: PropTypes.number, + reviewCounts: PropTypes.number, + usedRepoTags: PropTypes.array.isRequired, + readmeMarkdown: PropTypes.object, + updateUsedRepoTags: PropTypes.func.isRequired, + // dirent list + isDirentListLoading: PropTypes.bool.isRequired, + direntList: PropTypes.array.isRequired, + sortBy: PropTypes.string.isRequired, + sortOrder: PropTypes.string.isRequired, + sortItems: PropTypes.func.isRequired, + updateDirent: PropTypes.func.isRequired, + onItemClick: PropTypes.func.isRequired, + onItemSelected: PropTypes.func.isRequired, + onItemDelete: PropTypes.func.isRequired, + onItemRename: PropTypes.func.isRequired, + onItemMove: PropTypes.func.isRequired, + onItemCopy: PropTypes.func.isRequired, + onAddFolder: PropTypes.func.isRequired, + onAddFile: PropTypes.func.isRequired, + onFileTagChanged: PropTypes.func.isRequired, + isDirentSelected: PropTypes.bool.isRequired, + isAllDirentSelected: PropTypes.bool.isRequired, + onAllDirentSelected: PropTypes.func.isRequired, + onFileUploadSuccess: PropTypes.func.isRequired, +}; + +class LibContentMain extends React.Component { + + constructor(props) { + super(props); + this.state = { + currentDirent: null, + isDirentDetailShow: false, + }; + } + + onSideNavMenuClick = () => { + this.props.onSideNavMenuClick(); + } + + onPathClick = (path) => { + this.setState({isDirentDetailShow: false}); + this.props.onPathClick(path); + } + + onItemClick = (dirent) => { + this.setState({isDirentDetailShow: false}); + this.props.onItemClick(dirent); + } + + // on '' + onDirentClick = (dirent) => { + if (this.state.isDirentDetailShow) { + this.onItemDetails(dirent); + } + } + + onItemDetails = (dirent) => { + this.setState({ + currentDirent: dirent, + isDirentDetailShow: true, + }); + } + + onItemDetailsClose = () => { + this.setState({isDirentDetailShow: false}); + } + + onUploadFile = (e) => { + e.nativeEvent.stopImmediatePropagation(); + this.uploader.onFileUpload(); + } + + onUploadFolder = (e) => { + e.nativeEvent.stopImmediatePropagation(); + this.uploader.onFolderUpload(); + } + + switchViewMode = (mode) => { + this.props.switchViewMode(mode); + } + + onFileUploadSuccess = (direntObject) => { + this.props.onFileUploadSuccess(direntObject); + } + + renderListView = () => { + let repoID = this.props.repoID; + const showRepoInfoBar = this.props.path === '/' && ( + this.props.usedRepoTags.length != 0 || this.props.readmeMarkdown != null || + this.props.draftCounts != 0 || this.props.reviewCounts != 0); + return ( + + {showRepoInfoBar && ( + + )} + + + ); + } + + renderGridView = () => { + // todo + return ( +
grid-mode
+ ) + } + + renderColumnView = () => { + return ( + this.renderListView() + ) + } + + render() { + let repoID = this.props.repoID; + const errMessage = (
{gettext('Folder does not exist.')}
); + return ( +
+
+
+ +
+ {this.props.isDirentSelected ? + : + + } +
+ +
+ +
+
+
+
+ {this.props.currentRepoInfo && ( + + )} +
+
+ {!this.props.pathExist && errMessage } + {(this.props.pathExist && ( + + {this.props.currentMode === 'list' && this.renderListView()} + {this.props.currentMode === 'grid' && this.renderGridView()} + {this.props.currentMode === 'column' && this.renderColumnView()} + this.uploader = uploader} + dragAndDrop={true} + path={this.props.path} + repoID={repoID} + direntList={this.props.direntList} + onFileUploadSuccess={this.onFileUploadSuccess} + /> + + ))} +
+
+ {this.state.isDirentDetailShow && ( +
+ +
+ )} +
+
+ ); + } +} + +LibContentMain.propTypes = propTypes; + +export default LibContentMain; diff --git a/frontend/src/pages/lib-content-view/lib-content-nav.js b/frontend/src/pages/lib-content-view/lib-content-nav.js new file mode 100644 index 0000000000..97ec60ca6c --- /dev/null +++ b/frontend/src/pages/lib-content-view/lib-content-nav.js @@ -0,0 +1,235 @@ +import React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { gettext } from '../../utils/constants'; +import { Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap'; +import TreeView from '../../components/tree-view/tree-view'; +import Loading from '../../components/loading'; +import ModalPortal from '../../components/modal-portal'; +import Delete from '../../components/dialog/delete-dialog'; +import Rename from '../../components/dialog/rename-dialog'; +import CreateFolder from '../../components/dialog/create-folder-dialog'; +import CreateFile from '../../components/dialog/create-file-dialog'; + +const propTypes = { + repoPermission: PropTypes.bool.isRequired, + currentPath: PropTypes.string, + currentRepoInfo: PropTypes.object, // Initially not loaded + isTreeDataLoading: PropTypes.bool.isRequired, + treeData: PropTypes.object.isRequired, + onNodeClick: PropTypes.func.isRequired, + onNodeCollapse: PropTypes.func.isRequired, + onNodeExpanded: PropTypes.func.isRequired, + onRenameNode: PropTypes.func.isRequired, + onDeleteNode: PropTypes.func.isRequired, + onAddFileNode: PropTypes.func.isRequired, + onAddFolderNode: PropTypes.func.isRequired, +}; + +class LibContentNav extends React.Component { + + constructor(props) { + super(props); + this.state = { + opNode: null, + isLoadFailed: false, + isMenuIconShow: false, + isHeaderMenuShow: false, + isHeaderMenuFreezed: false, + isDeleteDialogShow: false, + isAddFileDialogShow: false, + isAddFolderDialogShow: false, + isRenameDialogShow: false, + }; + this.isNodeMenuShow = true; + } + + onDropdownToggleClick = (e) => { + e.preventDefault(); + this.toggleOperationMenu(); + } + + toggleOperationMenu = () => { + this.setState({isHeaderMenuShow: !this.state.isHeaderMenuShow}); + } + + onNodeClick = (node) => { + this.setState({opNode: node}); + this.props.onNodeClick(node); + } + + onMenuItemClick = (operation, node) => { + this.setState({opNode: node}); + switch (operation) { + case 'New Folder': + this.onAddFolderToggle(); + break; + case 'New File': + this.onAddFileToggle(); + break; + case 'Rename': + this.onRenameToggle(); + break; + case 'Delete': + this.onDeleteToggle(); + break; + } + } + + onAddFileToggle = (type) => { + if (type === 'root') { + let root = this.props.treeData.root; + this.setState({ + isAddFileDialogShow: !this.state.isAddFileDialogShow, + opNode: root, + }); + } else { + this.setState({isAddFileDialogShow: !this.state.isAddFileDialogShow}); + } + } + + onAddFolderToggle = (type) => { + if (type === 'root') { + let root = this.props.treeData.root; + this.setState({ + isAddFolderDialogShow: !this.state.isAddFolderDialogShow, + opNode: root, + }); + } else { + this.setState({isAddFolderDialogShow: !this.state.isAddFolderDialogShow}); + } + } + + onRenameToggle = () => { + this.setState({isRenameDialogShow: !this.state.isRenameDialogShow}); + } + + onDeleteToggle = () => { + this.setState({isDeleteDialogShow: !this.state.isDeleteDialogShow}); + } + + onAddFolderNode = (dirPath) => { + this.setState({isAddFolderDialogShow: !this.state.isAddFolderDialogShow}); + this.props.onAddFolderNode(dirPath); + } + + onAddFileNode = (filePath, isDraft) => { + this.setState({isAddFileDialogShow: !this.state.isAddFileDialogShow}); + this.props.onAddFileNode(filePath, isDraft); + } + + onRenameNode = (newName) => { + this.setState({isRenameDialogShow: !this.state.isRenameDialogShow}); + let node = this.state.opNode; + this.props.onRenameNode(node, newName); + } + + onDeleteNode = () => { + this.setState({isDeleteDialogShow: !this.state.isDeleteDialogShow}); + let node = this.state.opNode; + this.props.onDeleteNode(node); + } + + checkDuplicatedName = (newName) => { + let node = this.state.opNode; + // root node to new node conditions: parentNode is null, + let parentNode = node.parentNode ? node.parentNode : node; + let childrenObject = parentNode.children.map(item => { + return item.object; + }); + let isDuplicated = childrenObject.some(object => { + return object.name === newName; + }); + return isDuplicated; + } + + render() { + return ( + + + {this.state.isAddFolderDialogShow && ( + + + + )} + {this.state.isAddFileDialogShow && ( + + + + )} + {this.state.isRenameDialogShow && ( + + + + )} + {this.state.isDeleteDialogShow && ( + + + + )} + + ); + } +} + +LibContentNav.propTypes = propTypes; + +export default LibContentNav; \ No newline at end of file diff --git a/frontend/src/pages/lib-content-view/lib-content-view.js b/frontend/src/pages/lib-content-view/lib-content-view.js new file mode 100644 index 0000000000..3aff7764c0 --- /dev/null +++ b/frontend/src/pages/lib-content-view/lib-content-view.js @@ -0,0 +1,1279 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import cookie from 'react-cookies'; +import moment from 'moment'; +import { gettext, siteRoot, username, canGenerateShareLink, canGenerateUploadLink } from '../../utils/constants'; +import { seafileAPI } from '../../utils/seafile-api'; +import { Utils } from '../../utils/utils'; +import collabServer from '../../utils/collab-server'; +import Dirent from '../../models/dirent'; +import FileTag from '../../models/file-tag'; +import RepoTag from '../../models/repo-tag'; +import RepoInfo from '../../models/repo-info'; +import TreeNode from '../../components/tree-view/tree-node'; +import treeHelper from '../../components/tree-view/tree-helper'; +import toaster from '../../components/toast'; +import ModalPortal from '../../components/modal-portal'; +import LibDecryptDialog from '../../components/dialog/lib-decrypt-dialog'; +import FileContentView from '../../components/file-content-view'; +import LibContentNav from './lib-content-nav'; +import LibContentMain from './lib-content-main'; + +import '../../css/lib-content-view.css'; + +const propTypes = { + pathPrefix: PropTypes.array.isRequired, + onTabNavClick: PropTypes.func.isRequired, + onMenuClick: PropTypes.func.isRequired, +}; + +class LibContentView extends React.Component { + + constructor(props) { + super(props); + this.state = { + currentMode: cookie.load('seafile-view-mode'), + path: '', + pathExist: true, + isViewFile: false, + hash: '', + currentRepoInfo: null, + repoName: '', + repoPermission: true, + repoEncrypted: false, + libNeedDecrypt: false, + isGroupOwnedRepo: false, + isDepartmentAdmin: false, + isAdmin: false, + ownerEmail: '', + userPerm: '', + isVirtual: false, + selectedDirentList: [], + isDraft: false, + hasDraft: false, + draftFilePath: '', + draftCounts: 0, + reviewID: '', + reviewStatus: '', + reviewCounts: 0, + usedRepoTags: [], + readmeMarkdown: null, + isTreeDataLoading: true, + treeData: treeHelper.buildTree(), + currentNode: null, + isFileLoading: true, + filePermission: true, + content: '', + lastModified: '', + latestContributor: '', + isDirentListLoading: true, + direntList: [], + isDirentSelected: false, + sortBy: 'name', // 'name' or 'time' + sortOrder: 'asc', // 'asc' or 'desc' + isAllDirentSelected: false, + dirID: '', + errorMsg: '', + }; + + window.onpopstate = this.onpopstate; + this.lastModifyTime = new Date(); + } + + componentWillMount() { + const hash = window.location.hash; + if (hash.slice(0, 1) === '#') { + this.state.hash = hash; + } + } + + componentDidMount() { + // eg: http://127.0.0.1:8000/library/repo_id/repo_name/**/**/\ + let repoID = this.props.repoID; + let location = window.location.href.split('#')[0]; + location = decodeURIComponent(location); + seafileAPI.getRepoInfo(repoID).then(res => { + let repoInfo = new RepoInfo(res.data); + this.setState({ + currentRepoInfo: repoInfo, + repoName: repoInfo.repo_name, + libNeedDecrypt: res.data.lib_need_decrypt, + repoEncrypted: repoInfo.encrypted, + isVirtual: repoInfo.is_virtual, + isAdmin: repoInfo.is_admin, + ownerEmail: repoInfo.owner_email, + repoPermission: repoInfo.permission === 'rw' + }); + + const ownerEmail = repoInfo.owner_email; + if (repoInfo.owner_email.indexOf('@seafile_group') != -1) { + + const groupID = ownerEmail.substring(0, ownerEmail.indexOf('@')); + this.setState({isGroupOwnedRepo: true}); + + seafileAPI.getGroup(groupID).then(res => { + if (res.data.admins.indexOf(username) != -1) { + this.setState({isDepartmentAdmin: true}); + } + }); + } + + let repoID = repoInfo.repo_id; + let path = location.slice(location.indexOf(repoID) + repoID.length + 1); // get the string after repoID + path = path.slice(path.indexOf('/')); // get current path + + this.setState({path: path}); + + if (!res.data.lib_need_decrypt) { + this.loadDirData(path); + } + }).catch(error => { + if (error.response) { + if (error.response.status == 403) { + this.setState({ + isDirentListLoading: false, + errorMsg: gettext('Permission denied') + }); + } else { + this.setState({ + isDirentListLoading: false, + errorMsg: gettext('Error') + }); + } + } else { + this.setState({ + isDirentListLoading: false, + errorMsg: gettext('Please check the network.') + }); + } + }); + } + + componentWillUnmount() { + collabServer.unwatchRepo(this.props.repoID, this.onRepoUpdateEvent); + } + + componentDidUpdate() { + this.lastModifyTime = new Date(); + } + + onpopstate = (event) => { + if (event.state && event.state.path) { + let path = event.state.path; + if (this.state.currentMode === 'column') { + if (Utils.isMarkdownFile(path)) { // Judging not strict + this.showFile(path); + return; + } + } + this.loadDirentList(path); + this.setState({ + path: path, + isViewFile: false + }); + } + } + + onRepoUpdateEvent = () => { + let currentTime = new Date(); + if ((parseFloat(currentTime - this.lastModifyTime)/1000) <= 5) { + return; + } + let repoID = this.props.repoID; + let { path, dirID } = this.state; + seafileAPI.dirMetaData(repoID, path).then((res) => { + if (res.data.id !== dirID) { + toaster.notify( + + {gettext('This folder has been updated. ')} + {gettext('Refresh')} + , + {id: 'repo_updated', duration: 3600} + ); + } + }); + } + + updateUsedRepoTags = () => { + let repoID = this.props.repoID; + seafileAPI.listRepoTags(repoID).then(res => { + let usedRepoTags = []; + res.data.repo_tags.forEach(item => { + let usedRepoTag = new RepoTag(item); + if (usedRepoTag.fileCount > 0) { + usedRepoTags.push(usedRepoTag); + } + }); + this.setState({usedRepoTags: usedRepoTags}); + }); + } + + updateReadmeMarkdown = (direntList) => { + this.setState({readmeMarkdown: null}); + direntList.some(item => { + let fileName = item.name.toLowerCase(); + if (fileName === 'readme.md' || fileName === 'readme.markdown') { + this.setState({readmeMarkdown: item}); + return true; + } + }); + } + + // load data + loadDirData = (path) => { + let repoID = this.props.repoID; + + // listen current repo + collabServer.watchRepo(repoID, this.onRepoUpdateEvent); + + // list used FileTags + this.updateUsedRepoTags(); + + // list draft counts and revierw counts + seafileAPI.getRepoDraftReviewCounts(repoID).then(res => { + this.setState({ + draftCounts: res.data.draft_counts, + reviewCounts: res.data.review_counts + }); + }); + + if (this.state.currentMode === 'column') { + // there will be only two constence. path is file or path is dir. + if (Utils.isMarkdownFile(path)) { + seafileAPI.getFileInfo(this.props.repoID, path).then(() => { + this.showFile(path); + }).catch(() => { + this.showDir(path); // After an error occurs, follow dir + }); + } else { + this.showDir(path); + } + // load side-panel data + this.loadSidePanel(path); + + } else { + this.showDir(path); + } + } + + loadSidePanel = (path) => { + let repoID = this.props.repoID; + if (path === '/') { + seafileAPI.listDir(repoID, '/').then(res => { + let tree = this.state.treeData; + this.addResponseListToNode(res.data.dirent_list, tree.root); + this.setState({ + isTreeDataLoading: false, + treeData: tree + }); + }).catch(() => { + this.setState({isTreeDataLoading: false}); + // todo show error message + }); + } else { + this.loadNodeAndParentsByPath(path); + } + } + + showDir = (path) => { + let repoID = this.props.repoID; + + // update stste + this.setState({ + isDirentListLoading: true, + path: path, + isViewFile: false + }); + + // update data + this.loadDirentList(repoID, path); + + // update location + let repoInfo = this.state.currentRepoInfo; + let url = siteRoot + 'library/' + repoID + '/' + encodeURIComponent(repoInfo.repo_name) + Utils.encodePath(path); + window.history.pushState({url: url, path: path}, path, url); + } + + showFile = (filePath) => { + let repoID = this.props.repoID; + + // update state + this.setState({ + isFileLoading: true, + path: filePath, + isViewFile: true + }); + + // update data + seafileAPI.getFileInfo(repoID, filePath).then((res) => { + let { mtime, permission, last_modifier_name, is_draft, has_draft, + review_status, review_id, draft_file_path } = res.data; + seafileAPI.getFileDownloadLink(repoID, filePath).then((res) => { + seafileAPI.getFileContent(res.data).then((res) => { + this.setState({ + content: res.data, + filePermission: permission === 'rw', + latestContributor: last_modifier_name, + lastModified: moment.unix(mtime).fromNow(), + isFileLoading: false, + isDraft: is_draft, + hasDraft: has_draft, + reviewStatus: review_status, + reviewID: review_id, + draftFilePath: draft_file_path + }); + }); + }); + }); + + // update location + let repoInfo = this.state.currentRepoInfo; + let url = siteRoot + 'library/' + repoID + '/' + encodeURIComponent(repoInfo.repo_name) + Utils.encodePath(filePath); + window.history.pushState({url: url, path: filePath}, filePath, url); + } + + loadDirentList = (repoID, path) => { + seafileAPI.listDir(repoID, path, {'with_thumbnail': true}).then(res => { + let direntList = []; + let markdownItem = null; + res.data.dirent_list.forEach(item => { + let fileName = item.name.toLowerCase(); + if (fileName === 'readme.md' || fileName === 'readme.markdown') { + markdownItem = item; + } + let dirent = new Dirent(item); + direntList.push(dirent); + }); + + this.setState({ + pathExist: true, + userPerm: res.data.user_perm, + isDirentListLoading: false, + direntList: Utils.sortDirents(direntList, this.state.sortBy, this.state.sortOrder), + dirID: res.headers.oid, + readmeMarkdown: markdownItem, + }); + + if (!this.state.repoEncrypted && direntList.length) { + this.getThumbnails(repoID, path, this.state.direntList); + } + }).catch(() => { + this.setState({ + isDirentListLoading: false, + pathExist: false, + }); + }); + } + + getThumbnails = (repoID, path, direntList) => { + let items = direntList.filter((item) => { + return Utils.imageCheck(item.name) && !item.encoded_thumbnail_src; + }); + if (items.length == 0) { + return ; + } + + const _this = this; + const len = items.length; + const thumbnailSize = 48; + let getThumbnail = (i) => { + const curItem = items[i]; + const curItemPath = [path, curItem.name].join('/'); + seafileAPI.createThumbnail(repoID, curItemPath, thumbnailSize).then((res) => { + curItem.encoded_thumbnail_src = res.data.encoded_thumbnail_src; + }).catch((error) => { + // do nothing + }).then(() => { + if (i < len - 1) { + getThumbnail(++i); + } else { + _this.setState({ + direntList: direntList + }); + } + }); + }; + getThumbnail(0); + } + + // toolbar operations + onMoveItems = (destRepo, destDirentPath) => { + let repoID = this.props.repoID; + let direntPaths = this.getSelectedDirentPaths(); + let dirNames = this.getSelectedDirentNames(); + + seafileAPI.moveDir(repoID, destRepo.repo_id, destDirentPath, this.state.path, dirNames).then(res => { + let names = res.data.map(item => { + return item.obj_name; + }); + direntPaths.forEach((direntPath, index) => { + if (this.state.currentMode === 'column') { + this.moveTreeNode(direntPath, destDirentPath, destRepo, names[index]); + } + this.moveDirent(direntPath); + }); + let message = gettext('Successfully moved %(name)s.'); + message = message.replace('%(name)s', dirNames); + toaster.success(message); + }).catch(() => { + let message = gettext('Failed to move %(name)s'); + message = message.replace('%(name)s', dirNames); + toaster.danger(message); + }); + } + + onCopyItems = (destRepo, destDirentPath) => { + let repoID = this.props.repoID; + let direntPaths = this.getSelectedDirentPaths(); + let dirNames = this.getSelectedDirentNames(); + + seafileAPI.copyDir(repoID, destRepo.repo_id, destDirentPath, this.state.path, dirNames).then(res => { + let names = res.data.map(item => { + return item.obj_name; + }); + if (this.state.currentMode === 'column') { + direntPaths.forEach((direntPath, index) => { + this.copyTreeNode(direntPath, destDirentPath, destRepo, names[index]); + }); + } + let message = gettext('Successfully copied %(name)s.'); + message = message.replace('%(name)s', dirNames); + toaster.success(message); + }).catch(() => { + let message = gettext('Failed to copy %(name)s'); + message = message.replace('%(name)s', dirNames); + toaster.danger(message); + }); + } + + onDeleteItems = () => { + let repoID = this.props.repoID; + let direntPaths = this.getSelectedDirentPaths(); + let dirNames = this.getSelectedDirentNames(); + + seafileAPI.deleteMutipleDirents(repoID, this.state.path, dirNames).then(res => { + direntPaths.forEach(direntPath => { + if (this.state.currentMode === 'column') { + this.deleteTreeNode(direntPath); + } + this.deleteDirent(direntPath); + }); + }); + } + + onAddFolder = (dirPath) => { + let repoID = this.props.repoID; + seafileAPI.createDir(repoID, dirPath).then(() => { + let name = Utils.getFileName(dirPath); + let parentPath = Utils.getDirName(dirPath); + + if (this.state.currentMode === 'column') { + this.addNodeToTree(name, parentPath, 'dir'); + } + + if (parentPath === this.state.path && !this.state.isViewFile) { + this.addDirent(name, 'dir'); + } + }).catch(() => { + // return error message + }); + } + + onAddFile = (filePath, isDraft) => { + let repoID = this.props.repoID; + seafileAPI.createFile(repoID, filePath, isDraft).then(res => { + let name = Utils.getFileName(filePath); + let parentPath = Utils.getDirName(filePath); + if (this.state.currentMode === 'column') { + this.addNodeToTree(name, parentPath, 'file'); + } + if (parentPath === this.state.path && !this.state.isViewFile) { + this.addDirent(name, 'file', res.data.size); + } + }).catch(() => { + // todo + }); + } + + switchViewMode = (mode) => { + if (mode === this.state.currentMode) { + return; + } + cookie.save('seafile-view-mode', mode); + let path = this.state.path; + if (this.state.currentMode === 'column' && this.state.isViewFile) { + path = Utils.getDirName(path); + this.setState({ + path: path, + isViewFile: false, + }); + let repoInfo = this.state.currentRepoInfo; + + let url = siteRoot + 'library/' + repoInfo.repo_id + '/' + encodeURIComponent(repoInfo.repo_name) + Utils.encodePath(path); + window.history.pushState({url: url, path: path}, path, url); + } + + if (mode === 'column') { + this.loadSidePanel(this.state.path); + } + + this.setState({currentMode: mode}); + } + + onSearchedClick = (item) => { + let path = item.is_dir ? item.path.slice(0, item.path.length - 1) : item.path; + if (this.state.currentPath === path) { + return; + } + if (this.state.currentMode === 'column') { + // load sidePanel + let index = -1; + let paths = Utils.getPaths(path); + for (let i = 0; i < paths.length; i++) { + let node = this.state.treeData.getNodeByPath(node); + if (!node) { + index = i; + break; + } + } + if (index === -1) { // all the data has been loaded already. + let node = this.state.treeData.getNodeByPath(path); + this.setState({currentNode: node}); + } else { + this.loadNodeAndParentsByPath(path); + } + } + + // load mainPanel + if (item.is_dir) { + this.showDir(path); + } else { + if (Utils.isMarkdownFile(path)) { + this.showFile(path); + } else { + let url = siteRoot + 'lib/' + item.repo_id + '/file' + Utils.encodePath(path); + let newWindow = window.open('about:blank'); + newWindow.location.href = url; + } + } + } + + onMainNavBarClick = (nodePath) => { + //just for dir + this.resetSelected(); + if (this.state.currentMode === 'column') { + let tree = this.state.treeData.clone(); + let node = tree.getNodeByPath(nodePath); + tree.expandNode(node); + this.setState({treeData: tree, currentNode: node}); + } + + this.showDir(nodePath); + } + + onLinkClick = (link) => { + const url = link; + let repoID = this.props.repoID; + if (Utils.isInternalMarkdownLink(url, repoID)) { + let path = Utils.getPathFromInternalMarkdownLink(url, repoID); + this.showFile(path); + } else if (Utils.isInternalDirLink(url, repoID)) { + let path = Utils.getPathFromInternalDirLink(url, repoID); + this.showDir(path); + } else { + window.open(url); + } + } + + // list&tree operations + onMainPanelItemRename = (dirent, newName) => { + let path = Utils.joinPath(this.state.path, dirent.name); + this.renameItem(path, dirent.isDir(), newName); + } + + onMainPanelItemDelete = (dirent) => { + let path = Utils.joinPath(this.state.path, dirent.name); + this.deleteItem(path, dirent.isDir()); + } + + onRenameTreeNode = (node, newName) => { + this.renameItem(node.path, node.object.isDir(), newName); + } + + onDeleteTreeNode = (node) => { + this.deleteItem(node.path, node.object.isDir()); + } + + renameItem = (path, isDir, newName) => { + let repoID = this.props.repoID; + if (isDir) { + seafileAPI.renameDir(repoID, path, newName).then(() => { + this.renameItemAjaxCallback(path, newName); + }).catch(() => { + // todo + }); + } else { + seafileAPI.renameFile(repoID, path, newName).then(() => { + this.renameItemAjaxCallback(path, newName); + }).catch(() => { + // todo + }); + } + } + + renameItemAjaxCallback(path, newName) { + if (this.state.currentMode === 'column') { + this.renameTreeNode(path, newName); + } + this.renameDirent(path, newName); + } + + deleteItem(path, isDir) { + let repoID = this.props.repoID; + if (isDir) { + seafileAPI.deleteDir(repoID, path).then(() => { + this.deleteItemAjaxCallback(path, isDir); + }).catch(() => { + // todo + }); + } else { + seafileAPI.deleteFile(repoID, path).then(() => { + this.deleteItemAjaxCallback(path, isDir); + }).catch(() => { + // todo + }); + } + } + + deleteItemAjaxCallback(path) { + if (this.state.currentMode === 'column') { + this.deleteTreeNode(path); + } + this.deleteDirent(path); + } + + // list operations + onMoveItem = (destRepo, dirent, moveToDirentPath) => { + let repoID = this.props.repoID; + //just for view list state + let dirName = dirent.name; + let direntPath = Utils.joinPath(this.state.path, dirName); + seafileAPI.moveDir(repoID, destRepo.repo_id,moveToDirentPath, this.state.path, dirName).then(res => { + let nodeName = res.data[0].obj_name; + if (this.state.currentMode === 'column') { + this.moveTreeNode(direntPath, moveToDirentPath, destRepo, nodeName); + } + this.moveDirent(direntPath); + + let message = gettext('Successfully moved %(name)s.'); + message = message.replace('%(name)s', dirName); + toaster.success(message); + }).catch(() => { + let message = gettext('Failed to move %(name)s'); + message = message.replace('%(name)s', dirName); + toaster.danger(message); + }); + } + + onCopyItem = (destRepo, dirent, copyToDirentPath) => { + let repoID = this.props.repoID; + //just for view list state + let dirName = dirent.name; + let direntPath = Utils.joinPath(this.state.path, dirName); + seafileAPI.copyDir(repoID, destRepo.repo_id, copyToDirentPath, this.state.path, dirName).then(res => { + let nodeName = res.data[0].obj_name; + if (this.state.currentMode === 'column') { + this.copyTreeNode(direntPath, copyToDirentPath, destRepo, nodeName); + } + let message = gettext('Successfully copied %(name)s.'); + message = message.replace('%(name)s', dirName); + toaster.success(message); + }).catch(() => { + let message = gettext('Failed to copy %(name)s'); + message = message.replace('%(name)s', dirName); + toaster.danger(message); + }); + } + + onDirentClick = (dirent) => { + this.resetSelected(); + let repoID = this.props.repoID; + let direntPath = Utils.joinPath(this.state.path, dirent.name); + if (dirent.isDir()) { // is dir + if (this.state.currentMode === 'column') { + this.loadTreeNodeByPath(direntPath); + } + this.showDir(direntPath); + } else { // is file + if (this.state.currentMode === 'column' && Utils.isMarkdownFile(direntPath)) { + this.showFile(direntPath); + } else { + const w=window.open('about:blank'); + const url = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(direntPath); + w.location.href = url; + } + } + } + + onDirentSelected = (dirent) => { + let direntList = this.state.direntList.map(item => { + if (item.name === dirent.name) { + item.isSelected = !item.isSelected; + } + return item; + }); + let selectedDirentList = direntList.filter(item => { + return item.isSelected; + }); + + if (selectedDirentList.length) { + this.setState({isDirentSelected: true}); + if (selectedDirentList.length === direntList.length) { + this.setState({ + isAllDirentSelected: true, + direntList: direntList, + selectedDirentList: selectedDirentList, + }); + } else { + this.setState({ + isAllDirentSelected: false, + direntList: direntList, + selectedDirentList: selectedDirentList + }); + } + } else { + this.setState({ + isDirentSelected: false, + isAllDirentSelected: false, + direntList: direntList, + selectedDirentList: [] + }); + } + } + + onAllDirentSelected = () => { + if (this.state.isAllDirentSelected) { + let direntList = this.state.direntList.map(item => { + item.isSelected = false; + return item; + }); + this.setState({ + isDirentSelected: false, + isAllDirentSelected: false, + direntList: direntList, + selectedDirentList: [], + }); + } else { + let direntList = this.state.direntList.map(item => { + item.isSelected = true; + return item; + }); + this.setState({ + isDirentSelected: true, + isAllDirentSelected: true, + direntList: direntList, + selectedDirentList: direntList, + }); + } + } + + onFileTagChanged = (dirent, direntPath) => { + let repoID = this.props.repoID; + seafileAPI.listFileTags(repoID, direntPath).then(res => { + let fileTags = res.data.file_tags.map(item => { + return new FileTag(item); + }); + this.updateDirent(dirent, 'file_tags', fileTags); + }); + + this.updateUsedRepoTags(); + } + + onFileUploadSuccess = (direntObject) => { + let isExist = this.state.direntList.some(item => { + return item.name === direntObject.name && item.type === direntObject.type; + }); + if (isExist) { + let direntList = this.state.direntList; + for (let i = 0; i < direntList.length; i++) { + let dirent = direntList[i]; + if (dirent.name === direntObject.name && dirent.type === direntObject.type) { + let mtime = moment.unix(direntObject.mtime).fromNow(); + this.updateDirent(dirent, 'mtime', mtime); // todo file size is need update too, api is not return; + break; + } + } + } else { + direntObject.permission = 'rw'; + let dirent = new Dirent(direntObject); + if (this.state.currentMode === 'column') { + this.addNodeToTree(dirent.name, this.state.path, dirent.type); + } + if (direntObject.type === 'dir') { + this.setState({direntList: [dirent, ...this.state.direntList]}); + } else { + this.setState({direntList: [...this.state.direntList, dirent]}); + this.updateReadmeMarkdown(this.state.direntList); + } + } + } + + addDirent = (name, type, size) => { + let item = this.createDirent(name, type, size); + let direntList = this.state.direntList; + if (type === 'dir') { + direntList.unshift(item); + } else { + // there will be there conditions; + // first: direntList.length === 0; + // second: all the direntList's items are dir; + // third: direntList has dir and file; + let length = direntList.length; + if (length === 0 || direntList[length - 1].type === 'dir') { + direntList.push(item); + } else { + let index = 0; + for (let i = 0; i <= length; i++) { + if (direntList[i].type === 'file') { + index = i; + break; + } + } + direntList.splice(index, 0, item); + } + } + this.setState({direntList: direntList}); + this.updateReadmeMarkdown(direntList); + } + + renameDirent = (direntPath, newName) => { + let repoID = this.props.repoID; + let parentPath = Utils.getDirName(direntPath); + let newDirentPath = Utils.joinPath(parentPath, newName); + if (direntPath === this.state.path) { + // the renamed item is current viewed item + // example: direntPath = /A/B/C, state.path = /A/B/C + + this.setState({ path: newDirentPath }); + let url = siteRoot + 'wiki/lib/' + repoID + newDirentPath; + window.history.replaceState({ url: url, path: newDirentPath}, newDirentPath, url); + } else if (Utils.isChildPath(direntPath, this.state.path)) { + // example: direntPath = /A/B/C/D, state.path = /A/B/C + let oldName = Utils.getFileName(direntPath); + let direntList = this.state.direntList.map(item => { + if (item.name === oldName) { + item.name = newName; + } + return item; + }); + this.setState({ direntList: direntList }); + this.updateReadmeMarkdown(direntList); + } else if (Utils.isAncestorPath(direntPath, this.state.path)) { + // example: direntPath = /A/B, state.path = /A/B/C + let newPath = Utils.renameAncestorPath(this.state.path, direntPath, newDirentPath); + this.setState({ path: newPath }); + + let url = siteRoot + 'wiki/lib/' + repoID + newPath; + window.history.replaceState({ url: url, path: newPath}, newPath, url); + } + } + + deleteDirent(direntPath) { + if (direntPath === this.state.path) { + // The deleted item is current item + let parentPath = Utils.getDirName(direntPath); + this.showDir(parentPath); + } else if (Utils.isChildPath(direntPath, this.state.path)) { + // The deleted item is inside current path + let name = Utils.getFileName(direntPath); + let direntList = this.state.direntList.filter(item => { + return item.name !== name; + }); + this.setState({ direntList: direntList }); + this.updateReadmeMarkdown(direntList); + } else if (Utils.isAncestorPath(direntPath, this.state.path)) { + // the deleted item is ancester of the current item + let parentPath = Utils.getDirName(direntPath); + this.showDir(parentPath); + } + // else do nothing + } + + moveDirent = (direntPath) => { + let name = direntPath.slice(direntPath.lastIndexOf('/') + 1); + let direntList = this.state.direntList.filter(item => { + return item.name !== name; + }); + this.setState({direntList: direntList}); + this.updateReadmeMarkdown(direntList); + } + + updateDirent = (dirent, paramKey, paramValue) => { + let newDirentList = this.state.direntList.map(item => { + if (item.name === dirent.name) { + item[paramKey] = paramValue; + } + return item; + }); + this.setState({direntList: newDirentList}); + } + + // tree operations + loadTreeNodeByPath = (path) => { + let repoID = this.props.repoID; + let tree = this.state.treeData.clone(); + let node = tree.getNodeByPath(path); + if (!node.isLoaded) { + seafileAPI.listDir(repoID, node.path).then(res => { + this.addResponseListToNode(res.data.dirent_list, node); + let parentNode = tree.getNodeByPath(node.parentNode.path); + parentNode.isExpanded = true; + this.setState({ + treeData: tree, + currentNode: node + }); + }); + } else { + let parentNode = tree.getNodeByPath(node.parentNode.path); + parentNode.isExpanded = true; + this.setState({treeData: tree, currentNode: node}); //tree + } + } + + loadNodeAndParentsByPath = (path) => { + let repoID = this.props.repoID; + let tree = this.state.treeData.clone(); + if (Utils.isMarkdownFile(path)) { + path = Utils.getDirName(path); + } + seafileAPI.listDir(repoID, path, {with_parents: true}).then(res => { + let direntList = res.data.dirent_list; + let results = {}; + for (let i = 0; i < direntList.length; i++) { + let object = direntList[i]; + let key = object.parent_dir; + if (!results[key]) { + results[key] = []; + } + results[key].push(object); + } + for (let key in results) { + let node = tree.getNodeByPath(key); + if (!node.isLoaded) { + this.addResponseListToNode(results[key], node); + } + } + this.setState({ + isTreeDataLoading: false, + treeData: tree + }); + }).catch(() => { + this.setState({isLoadFailed: true}); + }); + } + + onTreeNodeClick = (node) => { + this.resetSelected(); + let repoID = this.props.repoID; + if (!this.state.pathExist) { + this.setState({pathExist: true}); + } + + if (node.object.isDir()) { + if (!node.isLoaded) { + let tree = this.state.treeData.clone(); + node = tree.getNodeByPath(node.path); + seafileAPI.listDir(repoID, node.path).then(res => { + this.addResponseListToNode(res.data.dirent_list, node); + tree.collapseNode(node); + this.setState({treeData: tree}); + }); + } + if (node.path === this.state.path) { + if (node.isExpanded) { + let tree = treeHelper.collapseNode(this.state.treeData, node); + this.setState({treeData: tree}); + } else { + let tree = this.state.treeData.clone(); + node = tree.getNodeByPath(node.path); + tree.expandNode(node); + this.setState({treeData: tree}); + } + } + } + + if (node.path === this.state.path ) { + return; + } + + if (node.object.isDir()) { // isDir + this.showDir(node.path); + } else { + if (Utils.isMarkdownFile(node.path)) { + if (node.path !== this.state.path) { + this.showFile(node.path); + } + } else { + const w = window.open('about:blank'); + const url = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(node.path); + w.location.href = url; + } + } + } + + onTreeNodeCollapse = (node) => { + let tree = treeHelper.collapseNode(this.state.treeData, node); + this.setState({treeData: tree}); + } + + onTreeNodeExpanded = (node) => { + let repoID = this.props.repoID; + let tree = this.state.treeData.clone(); + node = tree.getNodeByPath(node.path); + if (!node.isLoaded) { + seafileAPI.listDir(repoID, node.path).then(res => { + this.addResponseListToNode(res.data.dirent_list, node); + this.setState({treeData: tree}); + }); + } else { + tree.expandNode(node); + this.setState({treeData: tree}); + } + } + + addNodeToTree = (name, parentPath, type) => { + let node = this.createTreeNode(name, type); + let tree = treeHelper.addNodeToParentByPath(this.state.treeData, node, parentPath); + this.setState({treeData: tree}); + } + + renameTreeNode = (path, newName) => { + let tree = treeHelper.renameNodeByPath(this.state.treeData, path, newName); + this.setState({treeData: tree}); + } + + deleteTreeNode = (path) => { + let tree = treeHelper.deleteNodeByPath(this.state.treeData, path); + this.setState({treeData: tree}); + } + + moveTreeNode = (nodePath, moveToPath, moveToRepo, nodeName) => { + let repoID = this.props.repoID; + if (repoID !== moveToRepo.repo_id) { + let tree = treeHelper.deleteNodeByPath(this.state.treeData, nodePath); + this.setState({treeData: tree}); + return; + } + let tree = treeHelper.moveNodeByPath(this.state.treeData, nodePath, moveToPath, nodeName); + this.setState({treeData: tree}); + } + + copyTreeNode = (nodePath, copyToPath, destRepo, nodeName) => { + let repoID = this.props.repoID; + if (repoID !== destRepo.repo_id) { + return; + } + let tree = treeHelper.copyNodeByPath(this.state.treeData, nodePath, copyToPath, nodeName); + this.setState({treeData: tree}); + } + + createTreeNode(name, type) { + let object = this.createDirent(name, type); + return new TreeNode({object}); + } + + createDirent(name, type, size) { + let mtime = new Date().getTime()/1000; + let dirent = new Dirent({name, type, mtime, size}); + return dirent; + } + + addResponseListToNode = (list, node) => { + node.isLoaded = true; + node.isExpanded = true; + let direntList = list.map(item => { + return new Dirent(item); + }); + direntList = Utils.sortDirents(direntList, 'name', 'asc'); + + let nodeList = direntList.map(object => { + return new TreeNode({object}); + }); + node.addChildren(nodeList); + } + + getSelectedDirentPaths = () => { + let paths = []; + this.state.selectedDirentList.forEach(selectedDirent => { + paths.push(Utils.joinPath(this.state.path, selectedDirent.name)); + }); + return paths; + } + + getSelectedDirentNames = () => { + let names = []; + this.state.selectedDirentList.forEach(selectedDirent => { + names.push(selectedDirent.name); + }); + return names; + } + + resetSelected = () => { + this.setState({ + isDirentSelected: false, + isAllDirentSelected: false, + }); + } + + onLibDecryptDialog = () => { + this.setState({libNeedDecrypt: false}); + this.loadDirData(this.state.path); + } + + goReviewPage = () => { + window.location.href = siteRoot + 'drafts/review/' + this.state.reviewID; + } + + goDraftPage = () => { + let repoID = this.props.repoID; + window.location.href = siteRoot + 'lib/' + repoID + '/file' + this.state.draftFilePath + '?mode=edit'; + } + + sortItems = (sortBy, sortOrder) => { + this.setState({ + sortBy: sortBy, + sortOrder: sortOrder, + items: Utils.sortDirents(this.state.direntList, sortBy, sortOrder) + }); + } + + render() { + if (this.state.libNeedDecrypt) { + return ( + + + + ); + } + + let showShareBtn = false; + let enableDirPrivateShare = false; + const { repoEncrypted, isAdmin, ownerEmail, userPerm, isVirtual, isDepartmentAdmin } = this.state; + let isRepoOwner = ownerEmail === username; + if (!repoEncrypted) { + if ((canGenerateShareLink || canGenerateUploadLink || isRepoOwner || isAdmin) && (userPerm == 'rw' || userPerm == 'r')) { + showShareBtn = true; + if (!isVirtual && (isRepoOwner || isAdmin || isDepartmentAdmin)) { + enableDirPrivateShare = true; + } + } + } + + return ( +
+ {this.state.currentMode === 'column' && + + } + {this.state.isViewFile && + + } + {!this.state.isViewFile && ( + + )} +
+ ); + } +} + +LibContentView.propTypes = propTypes; + +export default LibContentView; \ No newline at end of file diff --git a/frontend/src/pages/my-libs/my-libs.js b/frontend/src/pages/my-libs/my-libs.js index b5cd270e8e..9d18792dc5 100644 --- a/frontend/src/pages/my-libs/my-libs.js +++ b/frontend/src/pages/my-libs/my-libs.js @@ -131,7 +131,7 @@ class MyLibraries extends Component { render() { return ( -
+
diff --git a/frontend/src/pages/repo-wiki-mode/main-panel.js b/frontend/src/pages/repo-wiki-mode/main-panel.js index e31a9633ec..4ddd48fdae 100644 --- a/frontend/src/pages/repo-wiki-mode/main-panel.js +++ b/frontend/src/pages/repo-wiki-mode/main-panel.js @@ -169,8 +169,8 @@ class MainPanel extends Component { return (
-
-
+
+
{this.props.isDirentSelected ? diff --git a/frontend/src/pages/shared-with-all/public-shared-view.js b/frontend/src/pages/shared-with-all/public-shared-view.js index 1c5a98f0c5..3e73caf598 100644 --- a/frontend/src/pages/shared-with-all/public-shared-view.js +++ b/frontend/src/pages/shared-with-all/public-shared-view.js @@ -165,8 +165,8 @@ class PublicSharedView extends React.Component { ); return ( -
-
+
+
diff --git a/frontend/src/pages/wiki/main-panel.js b/frontend/src/pages/wiki/main-panel.js index b7ca4bdee0..b8f9509303 100644 --- a/frontend/src/pages/wiki/main-panel.js +++ b/frontend/src/pages/wiki/main-panel.js @@ -73,10 +73,10 @@ class MainPanel extends Component { const errMessage = (
{gettext('Folder does not exist.')}
); return (
-
+
{username && ( -
+
{this.props.permission === 'rw' && ( @@ -90,7 +90,7 @@ class MainPanel extends Component { )}
-
+
{username && diff --git a/frontend/src/pages/wikis/wikis.js b/frontend/src/pages/wikis/wikis.js index 79ca114f9b..424aa67c1f 100644 --- a/frontend/src/pages/wikis/wikis.js +++ b/frontend/src/pages/wikis/wikis.js @@ -119,8 +119,8 @@ class Wikis extends Component { render() { return ( -
-
+
+
diff --git a/frontend/src/repo-wiki-mode.js b/frontend/src/repo-wiki-mode.js index d5af063331..fc46ba8abd 100644 --- a/frontend/src/repo-wiki-mode.js +++ b/frontend/src/repo-wiki-mode.js @@ -118,9 +118,7 @@ class Wiki extends Component { getGroupInfo = (groupID) => { seafileAPI.getGroup(groupID).then(res => { if (res.data.admins.indexOf(username) != -1) { - this.setState({ - isDepartmentAdmin: true - }); + this.setState({isDepartmentAdmin: true}); } }); } diff --git a/frontend/src/utils/utils.js b/frontend/src/utils/utils.js index e5ad680fcb..e1d8422c5d 100644 --- a/frontend/src/utils/utils.js +++ b/frontend/src/utils/utils.js @@ -213,7 +213,7 @@ export const Utils = { }, renameAncestorPath: function(path, ancestor, newAncestor) { - return newAncestor + '/' + path.replace(ancestor, ''); + return path.replace(ancestor, newAncestor); }, joinPath: function(pathA, pathB) { diff --git a/media/css/seahub_react.css b/media/css/seahub_react.css index c506bd98f1..58be50ca85 100644 --- a/media/css/seahub_react.css +++ b/media/css/seahub_react.css @@ -508,7 +508,7 @@ ul,ol,li { .side-nav-con { overflow:hidden; - padding:20px; + padding:12px; } .side-nav-con:hover {