diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0f80be5ae3..c1355e767f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10793,9 +10793,9 @@ } }, "seafile-js": { - "version": "0.2.45", - "resolved": "https://registry.npmjs.org/seafile-js/-/seafile-js-0.2.45.tgz", - "integrity": "sha512-qkKuqUMRKJxyXYYFI2Q4PPCbbxuA7wc6RWs4WTF67cHvh1VwAMNlE4xsRZhKQP0ZdUS+cHf2W8enZvb+dvucJQ==", + "version": "0.2.46", + "resolved": "https://registry.npmjs.org/seafile-js/-/seafile-js-0.2.46.tgz", + "integrity": "sha512-dD2Vd4o6z40bjLy6RsUKZMiWPzlruc4pApVsGzMGorKxrRVbNt+YSTqOE+7Q4wb6WaiiHj8PdY7hTMcP5UdmQA==", "requires": { "axios": "^0.18.0", "form-data": "^2.3.2", diff --git a/frontend/package.json b/frontend/package.json index 2d6d50c10e..2b7f6c30ba 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -30,7 +30,7 @@ "react-moment": "^0.7.9", "react-select": "^2.1.1", "reactstrap": "^6.4.0", - "seafile-js": "^0.2.45", + "seafile-js": "^0.2.46", "seafile-ui": "^0.1.10", "sw-precache-webpack-plugin": "0.11.4", "unified": "^7.0.0", diff --git a/frontend/src/app.js b/frontend/src/app.js index 8861d4a008..7439201870 100644 --- a/frontend/src/app.js +++ b/frontend/src/app.js @@ -107,7 +107,7 @@ class App extends Component { let newWindow = window.open('markdown-editor'); newWindow.location.href = url; } else { - let url = siteRoot + 'lib/' + selectedItem.repo_id + '/file' + selectedItem.path; + let url = siteRoot + 'lib/' + selectedItem.repo_id + '/file' + Utils.encodePath(selectedItem.path); let newWindow = window.open('about:blank'); newWindow.location.href = url; } diff --git a/frontend/src/components/dialog/create-department-repo-dialog.js b/frontend/src/components/dialog/create-department-repo-dialog.js index b320cdae46..c5a31687bb 100644 --- a/frontend/src/components/dialog/create-department-repo-dialog.js +++ b/frontend/src/components/dialog/create-department-repo-dialog.js @@ -28,9 +28,8 @@ class CreateDepartmentRepoDialog extends React.Component { let isValid = this.validateRepoName(); if (isValid) { let repo = this.createRepo(this.state.repoName); - this.props.onCreateRepo(repo); + this.props.onCreateRepo(repo, 'department'); } - } handleKeyPress = (e) => { diff --git a/frontend/src/components/dialog/generate-share-link.js b/frontend/src/components/dialog/generate-share-link.js index 30338de2da..20388da451 100644 --- a/frontend/src/components/dialog/generate-share-link.js +++ b/frontend/src/components/dialog/generate-share-link.js @@ -14,9 +14,10 @@ class GenerateShareLink extends React.Component { constructor(props) { super(props); this.state = { - passwordVisible: false, - showPasswordInput: false, isValidate: false, + isShowPasswordInput: false, + isPasswordVisible: false, + isExpireChecked: false, password: '', passwdnew: '', expireDays: '', @@ -28,6 +29,7 @@ class GenerateShareLink extends React.Component { 'can_edit': false, 'can_download': true }; + this.isExpireDaysNoLimit = (parseInt(shareLinkExpireDaysMin) === 0 && parseInt(shareLinkExpireDaysMax) === 0); } componentDidMount() { @@ -47,9 +49,9 @@ class GenerateShareLink extends React.Component { }); } - addPassword = () => { + onPasswordInputChecked = () => { this.setState({ - showPasswordInput: !this.state.showPasswordInput, + isShowPasswordInput: !this.state.isShowPasswordInput, password: '', passwdnew: '', errorInfo: '' @@ -58,7 +60,7 @@ class GenerateShareLink extends React.Component { togglePasswordVisible = () => { this.setState({ - passwordVisible: !this.state.passwordVisible + isPasswordVisible: !this.state.isPasswordVisible }); } @@ -66,20 +68,18 @@ class GenerateShareLink extends React.Component { let val = Math.random().toString(36).substr(5); this.setState({ password: val, - passwordnew: val + passwdnew: val }); } inputPassword = (e) => { - this.setState({ - password: e.target.value - }); + let passwd = e.target.value.trim(); + this.setState({password: passwd}); } inputPasswordNew = (e) => { - this.setState({ - passwordnew: e.target.value - }); + let passwd = e.target.value.trim(); + this.setState({passwdnew: passwd}); } setPermission = (permission) => { @@ -97,35 +97,14 @@ class GenerateShareLink extends React.Component { } generateShareLink = () => { - let path = this.props.itemPath; - let repoID = this.props.repoID; - if (this.state.showPasswordInput && (this.state.password == '')) { - this.setState({ - errorInfo: gettext('Please enter password') - }); - } - else if (this.state.showPasswordInput && (this.state.showPasswordInput && this.state.password.length < 8)) { - this.setState({ - errorInfo: gettext('Password is too short') - }); - } - else if (this.state.showPasswordInput && (this.state.password !== this.state.passwordnew)) { - this.setState({ - errorInfo: gettext('Passwords don\'t match') - }); - } - else if (this.state.expireDays === '') { - this.setState({ - errorInfo: gettext('Please enter days') - }); - } else if (!this.state.isValidate) { - // errMessage had been setted - return; - } else { + let isValid = this.validateParamsInput(); + if (isValid) { + this.setState({errorInfo: ''}); + let { itemPath, repoID } = this.props; let { password, expireDays } = this.state; let permissions = this.permissions; permissions = JSON.stringify(permissions); - seafileAPI.createShareLink(repoID, path, password, expireDays, permissions).then((res) => { + seafileAPI.createShareLink(repoID, itemPath, password, expireDays, permissions).then((res) => { this.setState({ link: res.data.link, token: res.data.token @@ -139,49 +118,102 @@ class GenerateShareLink extends React.Component { this.setState({ link: '', token: '', - showPasswordInput: false, password: '', passwordnew: '', + isShowPasswordInput: false, + expireDays: '', + isExpireChecked: false, + errorInfo: '', }); this.permissions = { 'can_edit': false, 'can_download': true }; }); - } + } - onExpireHandler = (e) => { - let day = e.target.value; + onExpireChecked = (e) => { + this.setState({isExpireChecked: e.target.checked}); + } + + onExpireDaysChanged = (e) => { + let day = e.target.value.trim(); + this.setState({expireDays: day}); + } + + validateParamsInput = () => { + let { isShowPasswordInput , password, passwdnew, isExpireChecked, expireDays } = this.state; + // validate password + if (isShowPasswordInput) { + if (password.length === 0) { + this.setState({errorInfo: 'Please enter password'}); + return false; + } + if (password.length < 8) { + this.setState({errorInfo: 'Password is too short'}); + return false; + } + if (password !== passwdnew) { + this.setState({errorInfo: 'Passwords don\'t match'}); + return false; + } + } + + // validate days + // no limit let reg = /^\d+$/; - let flag = reg.test(day); - if (!flag) { - this.setState({ - isValidate: false, - errorInfo: gettext('Please enter a non-negative integer'), - expireDays: day, - }); - return; - } - - day = parseInt(day); + if (this.isExpireDaysNoLimit) { + if (isExpireChecked) { + if (!expireDays) { + this.setState({errorInfo: 'Please enter days'}); + return false; + } + let flag = reg.test(expireDays); + if (!flag) { + this.setState({errorInfo: 'Please enter a non-negative integer'}); + return false; + } + this.setState({expireDays: parseInt(expireDays)}); + } + } else { + if (!expireDays) { + this.setState({errorInfo: 'Please enter days'}); + return false; + } + let flag = reg.test(expireDays); + if (!flag) { + this.setState({errorInfo: 'Please enter a non-negative integer'}); + return false; + } - if (day < shareLinkExpireDaysMin || day > shareLinkExpireDaysMax) { - let errorMessage = gettext('Please enter a value between day1 and day2'); - errorMessage = errorMessage.replace('day1', shareLinkExpireDaysMin); - errorMessage = errorMessage.replace('day2', shareLinkExpireDaysMax); - this.setState({ - isValidate: false, - errorInfo: errorMessage, - expireDays: day - }); - return; + expireDays = parseInt(expireDays); + let minDays = parseInt(shareLinkExpireDaysMin); + let maxDays = parseInt(shareLinkExpireDaysMax); + + if (minDays !== 0 && maxDays !== maxDays) { + if (expireDays < minDays) { + this.setState({errorInfo: 'Please enter valid days'}); + return false; + } + } + + if (minDays === 0 && maxDays !== 0 ) { + if (expireDays > maxDays) { + this.setState({errorInfo: 'Please enter valid days'}); + return false; + } + } + + if (minDays !== 0 && maxDays !== 0) { + if (expireDays < minDays || expireDays < maxDays) { + this.setState({errorInfo: 'Please enter valid days'}); + return false; + } + } + this.setState({expireDays: expireDays}); } - this.setState({ - isValidate: true, - errorInfo: '', - expireDays: day - }); + return true; } render() { @@ -197,35 +229,48 @@ class GenerateShareLink extends React.Component {
- {this.state.showPasswordInput && + {this.state.isShowPasswordInput && ({gettext('at least 8 characters')}) - + - + - + } - - - + {this.isExpireDaysNoLimit && ( + + + + )} + {!this.isExpireDaysNoLimit && ( + + + + )} -
+
); diff --git a/frontend/src/components/dialog/share-dialog.js b/frontend/src/components/dialog/share-dialog.js index d02768f826..c8fe662241 100644 --- a/frontend/src/components/dialog/share-dialog.js +++ b/frontend/src/components/dialog/share-dialog.js @@ -9,10 +9,11 @@ import GenerateUploadLink from './generate-upload-link'; import '../../css/share-link-dialog.css'; const propTypes = { - itemPath: PropTypes.string.isRequired, + isGroupOwnedRepo: PropTypes.bool, + itemType: PropTypes.string.isRequired, // there will be three choose: ['library', 'dir', 'file'] itemName: PropTypes.string.isRequired, + itemPath: PropTypes.string.isRequired, toggleDialog: PropTypes.func.isRequired, - isDir: PropTypes.bool.isRequired, repoID: PropTypes.string.isRequired }; @@ -67,10 +68,10 @@ class ShareDialog extends React.Component { - + - + @@ -79,14 +80,13 @@ class ShareDialog extends React.Component { } renderFileContent = () => { - let activeTab = this.state.activeTab; return (
diff --git a/frontend/src/pages/groups/groups-view.js b/frontend/src/pages/groups/groups-view.js index f828168f6f..b5f1eb4706 100644 --- a/frontend/src/pages/groups/groups-view.js +++ b/frontend/src/pages/groups/groups-view.js @@ -44,6 +44,18 @@ class RepoListViewPanel extends React.Component { }); } + onItemDelete = (repo) => { + let group = this.props.group; + seafileAPI.deleteGroupOwnedLibrary(group.id, repo.repo_id).then(() => { + let repoList = this.state.repoList.filter(item => { + return item.repo_id !== repo.repo_id; + }); + this.setState({repoList: repoList}); + }).catch(() => { + // todo; + }); + } + render() { let group = this.props.group; const emptyTip =

{gettext('No libraries')}

; @@ -60,6 +72,7 @@ class RepoListViewPanel extends React.Component { currentGroup={this.props.group} repoList={this.state.repoList} onItemUnshare={this.onItemUnshare} + onItemDelete={this.onItemDelete} /> } @@ -178,6 +191,5 @@ const GroupsViewPropTypes = { }; GroupsView.propTypes = GroupsViewPropTypes; -// Groups.propTypes = propTypes; export default GroupsView; diff --git a/frontend/src/pages/my-libs/item.js b/frontend/src/pages/my-libs/item.js index 0d9e2b9a76..f9ced61e6b 100644 --- a/frontend/src/pages/my-libs/item.js +++ b/frontend/src/pages/my-libs/item.js @@ -1,5 +1,5 @@ -import React, { Component } from 'react'; +import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; import moment from 'moment'; import { Link } from '@reach/router'; @@ -8,6 +8,8 @@ import { gettext, siteRoot, storages, canGenerateShareLink, canGenerateUploadLin import { Utils } from '../../utils/utils'; import { seafileAPI } from '../../utils/seafile-api'; import RenameInput from '../../components/rename-input'; +import ModalPotal from '../../components/modal-portal'; +import ShareDialog from '../../components/dialog/share-dialog'; const propTypes = { data: PropTypes.object.isRequired, @@ -29,6 +31,7 @@ class Item extends Component { showOpIcon: false, operationMenuOpen: false, showChangeLibName: false, + isShowSharedDialog: false, highlight: false, }; } @@ -73,7 +76,11 @@ class Item extends Component { share = (e) => { e.preventDefault(); - // TODO + this.setState({isShowSharedDialog: true}); + } + + toggleShareDialog = () => { + this.setState({isShowSharedDialog: false}); } showDeleteItemPopup = (e) => { @@ -232,43 +239,69 @@ class Item extends Component { ); const desktopItem = ( - - {data.icon_title} - - {this.state.showChangeLibName && ( - + + {data.icon_title} + + {this.state.showChangeLibName && ( + + )} + {!this.state.showChangeLibName && data.repo_name && ( + {data.repo_name} + )} + {!this.state.showChangeLibName && !data.repo_name && + (gettext('Broken (please contact your administrator to fix this library)')) + } + + {data.repo_name ? desktopOperations : ''} + {Utils.formatSize({bytes: data.size})} + {storages.length ? {data.storage_name} : null} + {moment(data.last_modified).fromNow()} + + {this.state.isShowSharedDialog && ( + + - )} - {!this.state.showChangeLibName && data.repo_name && ( - {data.repo_name} - )} - {!this.state.showChangeLibName && !data.repo_name && - (gettext('Broken (please contact your administrator to fix this library)')) - } - - {data.repo_name ? desktopOperations : ''} - {Utils.formatSize({bytes: data.size})} - {storages.length ? {data.storage_name} : null} - {moment(data.last_modified).fromNow()} - + + )} +
); const mobileItem = ( - - {data.icon_title} - - {data.repo_name ? - {data.repo_name} : - gettext('Broken (please contact your administrator to fix this library)')} -
- {Utils.formatSize({bytes: data.size})} - {moment(data.last_modified).fromNow()} - - {data.repo_name ? mobileOperations : ''} - + + + {data.icon_title} + + {data.repo_name ? + {data.repo_name} : + gettext('Broken (please contact your administrator to fix this library)')} +
+ {Utils.formatSize({bytes: data.size})} + {moment(data.last_modified).fromNow()} + + {data.repo_name ? mobileOperations : ''} + + {this.state.isShowSharedDialog && ( + + + + )} +
); return window.innerWidth >= 768 ? desktopItem : mobileItem; diff --git a/frontend/src/pages/my-libs/my-libs.js b/frontend/src/pages/my-libs/my-libs.js index 9cbb7863a2..d66b043cac 100644 --- a/frontend/src/pages/my-libs/my-libs.js +++ b/frontend/src/pages/my-libs/my-libs.js @@ -48,8 +48,17 @@ class MyLibraries extends Component { } onCreateRepo = (repo) => { + let permission = repo.permission; seafileAPI.createMineRepo(repo).then((res) => { - let repo = res.data; + let repo = { + repo_id: res.data.repo_id, + repo_name: res.data.repo_name, + size: res.data.repo_size, + mtime: res.data.mtime, + owner_email: res.data.email, + encrypted: res.data.encrypted, + permission: permission, + }; this.state.items.push(repo); this.setState({items: this.state.items}); }); diff --git a/frontend/src/repo-wiki-mode.js b/frontend/src/repo-wiki-mode.js index a2f509dd53..e6e028260b 100644 --- a/frontend/src/repo-wiki-mode.js +++ b/frontend/src/repo-wiki-mode.js @@ -334,7 +334,7 @@ class Wiki extends Component { this.showFile(node.path); } } else { - let url = siteRoot + 'lib/' + item.repo_id + '/file' + item.path; + let url = siteRoot + 'lib/' + item.repo_id + '/file' + Utils.encodePath(item.path); let newWindow = window.open('about:blank'); newWindow.location.href = url; } @@ -367,7 +367,7 @@ class Wiki extends Component { this.showDir(node.path); } else { const w=window.open('about:blank'); - const url = siteRoot + 'lib/' + repoID + '/file' + node.path; + const url = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(node.path); w.location.href = url; } } diff --git a/frontend/src/utils/utils.js b/frontend/src/utils/utils.js index 3beb944c52..39e68cecb3 100644 --- a/frontend/src/utils/utils.js +++ b/frontend/src/utils/utils.js @@ -147,7 +147,9 @@ export const Utils = { return encodeURIComponent(e); }).join('/'); */ - + if (!path) { + return ''; + } var path_arr = path.split('/'); var path_arr_ = []; for (var i = 0, len = path_arr.length; i < len; i++) { diff --git a/frontend/src/wiki.js b/frontend/src/wiki.js index a29c208c82..4773cfc3d0 100644 --- a/frontend/src/wiki.js +++ b/frontend/src/wiki.js @@ -133,7 +133,7 @@ class Wiki extends Component { this.enterViewFileState(tree, node, node.path); } } else { - let url = siteRoot + 'lib/' + item.repo_id + '/file' + item.path; + let url = siteRoot + 'lib/' + item.repo_id + '/file' + Utils.encodePath(item.path); let newWindow = window.open('about:blank'); newWindow.location.href = url; } @@ -161,7 +161,7 @@ class Wiki extends Component { this.exitViewFileState(tree, node); } else { const w=window.open('about:blank'); - const url = siteRoot + 'lib/' + repoID + '/file' + node.path; + const url = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(node.path); w.location.href = url; } } diff --git a/seahub/urls.py b/seahub/urls.py index 66824debbc..8a4ef1739a 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -204,7 +204,7 @@ urlpatterns = [ url(r'^shared-libs/$', react_fake_view, name="shared_libs"), url(r'^my-libs/$', react_fake_view, name="my_libs"), url(r'^groups/$', react_fake_view, name="groups"), - url(r'^group/(?P\d+)/$', react_group, name="group"), + url(r'^group/.*$', react_fake_view, name="group"), url(r'^library/.*$', react_fake_view, name="lib_view"), url(r'^my-libs/deleted/$', react_fake_view, name="my_libs_deleted"),