1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-07-16 16:21:48 +00:00

Merge pull request #2648 from haiwen/repo-share

Repo share
This commit is contained in:
Daniel Pan 2018-12-17 15:22:44 +08:00 committed by GitHub
commit bd13a10a50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 490 additions and 239 deletions

View File

@ -10793,9 +10793,9 @@
} }
}, },
"seafile-js": { "seafile-js": {
"version": "0.2.45", "version": "0.2.46",
"resolved": "https://registry.npmjs.org/seafile-js/-/seafile-js-0.2.45.tgz", "resolved": "https://registry.npmjs.org/seafile-js/-/seafile-js-0.2.46.tgz",
"integrity": "sha512-qkKuqUMRKJxyXYYFI2Q4PPCbbxuA7wc6RWs4WTF67cHvh1VwAMNlE4xsRZhKQP0ZdUS+cHf2W8enZvb+dvucJQ==", "integrity": "sha512-dD2Vd4o6z40bjLy6RsUKZMiWPzlruc4pApVsGzMGorKxrRVbNt+YSTqOE+7Q4wb6WaiiHj8PdY7hTMcP5UdmQA==",
"requires": { "requires": {
"axios": "^0.18.0", "axios": "^0.18.0",
"form-data": "^2.3.2", "form-data": "^2.3.2",

View File

@ -30,7 +30,7 @@
"react-moment": "^0.7.9", "react-moment": "^0.7.9",
"react-select": "^2.1.1", "react-select": "^2.1.1",
"reactstrap": "^6.4.0", "reactstrap": "^6.4.0",
"seafile-js": "^0.2.45", "seafile-js": "^0.2.46",
"seafile-ui": "^0.1.10", "seafile-ui": "^0.1.10",
"sw-precache-webpack-plugin": "0.11.4", "sw-precache-webpack-plugin": "0.11.4",
"unified": "^7.0.0", "unified": "^7.0.0",

View File

@ -107,7 +107,7 @@ class App extends Component {
let newWindow = window.open('markdown-editor'); let newWindow = window.open('markdown-editor');
newWindow.location.href = url; newWindow.location.href = url;
} else { } 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'); let newWindow = window.open('about:blank');
newWindow.location.href = url; newWindow.location.href = url;
} }

View File

@ -28,9 +28,8 @@ class CreateDepartmentRepoDialog extends React.Component {
let isValid = this.validateRepoName(); let isValid = this.validateRepoName();
if (isValid) { if (isValid) {
let repo = this.createRepo(this.state.repoName); let repo = this.createRepo(this.state.repoName);
this.props.onCreateRepo(repo); this.props.onCreateRepo(repo, 'department');
} }
} }
handleKeyPress = (e) => { handleKeyPress = (e) => {

View File

@ -14,9 +14,10 @@ class GenerateShareLink extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
passwordVisible: false,
showPasswordInput: false,
isValidate: false, isValidate: false,
isShowPasswordInput: false,
isPasswordVisible: false,
isExpireChecked: false,
password: '', password: '',
passwdnew: '', passwdnew: '',
expireDays: '', expireDays: '',
@ -28,6 +29,7 @@ class GenerateShareLink extends React.Component {
'can_edit': false, 'can_edit': false,
'can_download': true 'can_download': true
}; };
this.isExpireDaysNoLimit = (parseInt(shareLinkExpireDaysMin) === 0 && parseInt(shareLinkExpireDaysMax) === 0);
} }
componentDidMount() { componentDidMount() {
@ -47,9 +49,9 @@ class GenerateShareLink extends React.Component {
}); });
} }
addPassword = () => { onPasswordInputChecked = () => {
this.setState({ this.setState({
showPasswordInput: !this.state.showPasswordInput, isShowPasswordInput: !this.state.isShowPasswordInput,
password: '', password: '',
passwdnew: '', passwdnew: '',
errorInfo: '' errorInfo: ''
@ -58,7 +60,7 @@ class GenerateShareLink extends React.Component {
togglePasswordVisible = () => { togglePasswordVisible = () => {
this.setState({ 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); let val = Math.random().toString(36).substr(5);
this.setState({ this.setState({
password: val, password: val,
passwordnew: val passwdnew: val
}); });
} }
inputPassword = (e) => { inputPassword = (e) => {
this.setState({ let passwd = e.target.value.trim();
password: e.target.value this.setState({password: passwd});
});
} }
inputPasswordNew = (e) => { inputPasswordNew = (e) => {
this.setState({ let passwd = e.target.value.trim();
passwordnew: e.target.value this.setState({passwdnew: passwd});
});
} }
setPermission = (permission) => { setPermission = (permission) => {
@ -97,35 +97,14 @@ class GenerateShareLink extends React.Component {
} }
generateShareLink = () => { generateShareLink = () => {
let path = this.props.itemPath; let isValid = this.validateParamsInput();
let repoID = this.props.repoID; if (isValid) {
if (this.state.showPasswordInput && (this.state.password == '')) { this.setState({errorInfo: ''});
this.setState({ let { itemPath, repoID } = this.props;
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 { password, expireDays } = this.state; let { password, expireDays } = this.state;
let permissions = this.permissions; let permissions = this.permissions;
permissions = JSON.stringify(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({ this.setState({
link: res.data.link, link: res.data.link,
token: res.data.token token: res.data.token
@ -139,49 +118,102 @@ class GenerateShareLink extends React.Component {
this.setState({ this.setState({
link: '', link: '',
token: '', token: '',
showPasswordInput: false,
password: '', password: '',
passwordnew: '', passwordnew: '',
isShowPasswordInput: false,
expireDays: '',
isExpireChecked: false,
errorInfo: '',
}); });
this.permissions = { this.permissions = {
'can_edit': false, 'can_edit': false,
'can_download': true 'can_download': true
}; };
}); });
} }
onExpireHandler = (e) => { onExpireChecked = (e) => {
let day = e.target.value; 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 reg = /^\d+$/;
let flag = reg.test(day); if (this.isExpireDaysNoLimit) {
if (!flag) { if (isExpireChecked) {
this.setState({ if (!expireDays) {
isValidate: false, this.setState({errorInfo: 'Please enter days'});
errorInfo: gettext('Please enter a non-negative integer'), return false;
expireDays: day, }
}); let flag = reg.test(expireDays);
return; if (!flag) {
} this.setState({errorInfo: 'Please enter a non-negative integer'});
return false;
day = parseInt(day); }
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) { expireDays = parseInt(expireDays);
let errorMessage = gettext('Please enter a value between day1 and day2'); let minDays = parseInt(shareLinkExpireDaysMin);
errorMessage = errorMessage.replace('day1', shareLinkExpireDaysMin); let maxDays = parseInt(shareLinkExpireDaysMax);
errorMessage = errorMessage.replace('day2', shareLinkExpireDaysMax);
this.setState({ if (minDays !== 0 && maxDays !== maxDays) {
isValidate: false, if (expireDays < minDays) {
errorInfo: errorMessage, this.setState({errorInfo: 'Please enter valid days'});
expireDays: day return false;
}); }
return; }
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({ return true;
isValidate: true,
errorInfo: '',
expireDays: day
});
} }
render() { render() {
@ -197,35 +229,48 @@ class GenerateShareLink extends React.Component {
<Form className="generate-share-link"> <Form className="generate-share-link">
<FormGroup check> <FormGroup check>
<Label check> <Label check>
<Input type="checkbox" onChange={this.addPassword}/>{' '}{gettext('Add password protection')} <Input type="checkbox" onChange={this.onPasswordInputChecked}/>{' '}{gettext('Add password protection')}
</Label> </Label>
</FormGroup> </FormGroup>
{this.state.showPasswordInput && {this.state.isShowPasswordInput &&
<FormGroup className="link-operation-content"> <FormGroup className="link-operation-content">
<Label>{gettext('Password')}</Label><span className="tip"> ({gettext('at least 8 characters')}) </span> <Label>{gettext('Password')}</Label><span className="tip"> ({gettext('at least 8 characters')}) </span>
<InputGroup className="passwd"> <InputGroup className="passwd">
<Input type={this.state.passwordVisible ? 'text' : 'password'} value={this.state.password || ''} onChange={this.inputPassword}/> <Input type={this.state.isPasswordVisible ? 'text' : 'password'} value={this.state.password || ''} onChange={this.inputPassword}/>
<InputGroupAddon addonType="append"> <InputGroupAddon addonType="append">
<Button onClick={this.togglePasswordVisible}><i className={`link-operation-icon fas ${this.state.passwordVisible ? 'fa-eye': 'fa-eye-slash'}`}></i></Button> <Button onClick={this.togglePasswordVisible}><i className={`link-operation-icon fas ${this.state.isPasswordVisible ? 'fa-eye': 'fa-eye-slash'}`}></i></Button>
<Button onClick={this.generatePassword}><i className="link-operation-icon fas fa-magic"></i></Button> <Button onClick={this.generatePassword}><i className="link-operation-icon fas fa-magic"></i></Button>
</InputGroupAddon> </InputGroupAddon>
</InputGroup> </InputGroup>
<Label>{gettext('Password again')}</Label> <Label>{gettext('Password again')}</Label>
<Input className="passwd" type={this.state.passwordVisible ? 'text' : 'password'} value={this.state.passwordnew || ''} onChange={this.inputPasswordNew} /> <Input className="passwd" type={this.state.isPasswordVisible ? 'text' : 'password'} value={this.state.passwdnew || ''} onChange={this.inputPasswordNew} />
</FormGroup> </FormGroup>
} }
<FormGroup check> {this.isExpireDaysNoLimit && (
<Label check> <FormGroup check>
<Input className="expire-checkbox" type="checkbox" checked readOnly/>{' '}{gettext('Add auto expiration')} <Label check>
<Input className="expire-input" type="text" value={this.state.expireDays} onChange={this.onExpireHandler}/> <span>{gettext('days')}</span> <Input className="expire-checkbox" type="checkbox" onChange={this.onExpireChecked}/>{' '}{gettext('Add auto expiration')}
{parseInt(shareLinkExpireDaysMin) === 0 && parseInt(shareLinkExpireDaysMax) === 0 && ( <Input className="expire-input" type="text" value={this.state.expireDays} onChange={this.onExpireDaysChanged} readOnly={!this.state.isExpireChecked}/><span>{gettext('days')}</span>
<span> ({gettext('no limit')})</span> </Label>
)} </FormGroup>
{parseInt(shareLinkExpireDaysMax) !== 0 && ( )}
<span> ({shareLinkExpireDaysMin} - {shareLinkExpireDaysMax}{gettext('days')})</span> {!this.isExpireDaysNoLimit && (
)} <FormGroup check>
</Label> <Label check>
</FormGroup> <Input className="expire-checkbox" type="checkbox" onChange={this.onExpireChecked} checked readOnly/>{' '}{gettext('Add auto expiration')}
<Input className="expire-input" type="text" value={this.state.expireDays} onChange={this.onExpireDaysChanged} /> <span>{gettext('days')}</span>
{(parseInt(shareLinkExpireDaysMin) !== 0 && parseInt(shareLinkExpireDaysMax) !== 0) && (
<span> ({shareLinkExpireDaysMin} - {shareLinkExpireDaysMax}{' '}{gettext('days')})</span>
)}
{(parseInt(shareLinkExpireDaysMin) !== 0 && parseInt(shareLinkExpireDaysMax) === 0) && (
<span> ({gettext('Greater than or equal to')} {shareLinkExpireDaysMin}{' '}{gettext('days')})</span>
)}
{(parseInt(shareLinkExpireDaysMin) === 0 && parseInt(shareLinkExpireDaysMax) !== 0) && (
<span> ({gettext('Less than or equal to')} {shareLinkExpireDaysMax}{' '}{gettext('days')})</span>
)}
</Label>
</FormGroup>
)}
<FormGroup check> <FormGroup check>
<Label check> <Label check>
<Input type="checkbox" checked readOnly/>{' '}{gettext('Set permission')} <Input type="checkbox" checked readOnly/>{' '}{gettext('Set permission')}
@ -241,7 +286,7 @@ class GenerateShareLink extends React.Component {
<Input type="radio" name="radio1" onChange={this.setPermission('preview')} />{' '}{gettext('Preview only')} <Input type="radio" name="radio1" onChange={this.setPermission('preview')} />{' '}{gettext('Preview only')}
</Label> </Label>
</FormGroup> </FormGroup>
<Label className="err-message">{this.state.errorInfo}</Label><br /> <Label className="err-message">{gettext(this.state.errorInfo)}</Label><br />
<Button onClick={this.generateShareLink}>{gettext('Generate')}</Button> <Button onClick={this.generateShareLink}>{gettext('Generate')}</Button>
</Form> </Form>
); );

View File

@ -9,10 +9,11 @@ import GenerateUploadLink from './generate-upload-link';
import '../../css/share-link-dialog.css'; import '../../css/share-link-dialog.css';
const propTypes = { 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, itemName: PropTypes.string.isRequired,
itemPath: PropTypes.string.isRequired,
toggleDialog: PropTypes.func.isRequired, toggleDialog: PropTypes.func.isRequired,
isDir: PropTypes.bool.isRequired,
repoID: PropTypes.string.isRequired repoID: PropTypes.string.isRequired
}; };
@ -67,10 +68,10 @@ class ShareDialog extends React.Component {
<GenerateUploadLink itemPath={this.props.itemPath} repoID={this.props.repoID} /> <GenerateUploadLink itemPath={this.props.itemPath} repoID={this.props.repoID} />
</TabPane> </TabPane>
<TabPane tabId="shareToUser"> <TabPane tabId="shareToUser">
<ShareToUser itemPath={this.props.itemPath} repoID={this.props.repoID} /> <ShareToUser isGroupOwnedRepo={this.props.isGroupOwnedRepo} itemPath={this.props.itemPath} repoID={this.props.repoID} />
</TabPane> </TabPane>
<TabPane tabId="shareToGroup"> <TabPane tabId="shareToGroup">
<ShareToGroup itemPath={this.props.itemPath} repoID={this.props.repoID} /> <ShareToGroup isGroupOwnedRepo={this.props.isGroupOwnedRepo} itemPath={this.props.itemPath} repoID={this.props.repoID} />
</TabPane> </TabPane>
</TabContent> </TabContent>
</div> </div>
@ -79,14 +80,13 @@ class ShareDialog extends React.Component {
} }
renderFileContent = () => { renderFileContent = () => {
let activeTab = this.state.activeTab;
return ( return (
<Fragment> <Fragment>
<div className="share-dialog-side"> <div className="share-dialog-side">
<Nav pills vertical> <Nav pills vertical>
<NavItem> <NavItem>
<NavLink <NavLink
className={activeTab === 'shareLink' ? 'active' : ''} onClick={() => {this.toggle.bind(this, 'shareLink');}}> className="active" onClick={() => {this.toggle.bind(this, 'shareLink');}}>
{gettext('Share Link')} {gettext('Share Link')}
</NavLink> </NavLink>
</NavItem> </NavItem>
@ -104,15 +104,14 @@ class ShareDialog extends React.Component {
} }
render() { render() {
let itemName = this.props.itemName; let { itemType, itemName } = this.props;
return ( return (
<div> <div>
<Modal isOpen={true} style={{maxWidth: '720px'}} className="share-dialog"> <Modal isOpen={true} style={{maxWidth: '720px'}} className="share-dialog">
<ModalHeader toggle={this.props.toggleDialog}>Share <span className="sf-font" title={itemName}>{itemName}</span></ModalHeader> <ModalHeader toggle={this.props.toggleDialog}>{gettext('Share')} <span className="sf-font" title={itemName}>{itemName}</span></ModalHeader>
<ModalBody className="share-dialog-content"> <ModalBody className="share-dialog-content">
{this.props.isDir && this.renderDirContent()} {(itemType === 'library' || itemType === 'dir') && this.renderDirContent()}
{!this.props.isDir && this.renderFileContent()} {itemType === 'file' && this.renderFileContent()}
</ModalBody> </ModalBody>
</Modal> </Modal>
</div> </div>

View File

@ -8,6 +8,7 @@ import { Utils } from '../../utils/utils';
import { seafileAPI } from '../../utils/seafile-api.js'; import { seafileAPI } from '../../utils/seafile-api.js';
const propTypes = { const propTypes = {
isGroupOwnedRepo: PropTypes.bool,
itemPath: PropTypes.string.isRequired, itemPath: PropTypes.string.isRequired,
repoID: PropTypes.string.isRequired repoID: PropTypes.string.isRequired
}; };
@ -26,9 +27,7 @@ class ShareToGroup extends React.Component {
} }
handleSelectChange = (option) => { handleSelectChange = (option) => {
this.setState({ this.setState({selectedOption: option});
selectedOption: option,
});
} }
componentDidMount() { componentDidMount() {
@ -85,37 +84,76 @@ class ShareToGroup extends React.Component {
let groups = []; let groups = [];
let path = this.props.itemPath; let path = this.props.itemPath;
let repoID = this.props.repoID; let repoID = this.props.repoID;
let isGroupOwnedRepo = this.props.isGroupOwnedRepo;
if (this.state.selectedOption.length > 0 ) { if (this.state.selectedOption.length > 0 ) {
for (let i = 0; i < this.state.selectedOption.length; i ++) { for (let i = 0; i < this.state.selectedOption.length; i ++) {
groups[i] = this.state.selectedOption[i].id; groups[i] = this.state.selectedOption[i].id;
} }
} }
seafileAPI.shareFolder(repoID, path, 'group', this.state.permission, groups).then(res => { if (isGroupOwnedRepo) {
if (res.data.failed.length > 0) { seafileAPI.shareGroupOwnedRepoToGroup(repoID, this.state.permission, groups).then(res => {
let errorMsg = []; if (res.data.failed.length > 0) {
for (let i = 0 ; i < res.data.failed.length ; i++) { let errorMsg = [];
errorMsg[i] = res.data.failed[i]; for (let i = 0 ; i < res.data.failed.length ; i++) {
errorMsg[i] = res.data.failed[i];
}
this.setState({
errorMsg: errorMsg
});
} }
this.setState({
errorMsg: errorMsg
});
}
this.setState({ // todo modify api
sharedItems: this.state.sharedItems.concat(res.data.success) let items = res.data.success.map(item => {
let sharedItem = {
'group_info': { 'id': item.group_id, 'name': item.group_name},
'permission': item.permission,
'share_type': 'group',
};
return sharedItem;
});
this.setState({
sharedItems: this.state.sharedItems.concat(items),
selectedOption: null,
});
}); });
}); } else {
seafileAPI.shareFolder(repoID, path, 'group', this.state.permission, groups).then(res => {
if (res.data.failed.length > 0) {
let errorMsg = [];
for (let i = 0 ; i < res.data.failed.length ; i++) {
errorMsg[i] = res.data.failed[i];
}
this.setState({
errorMsg: errorMsg
});
}
this.setState({
sharedItems: this.state.sharedItems.concat(res.data.success),
selectedOption: null,
});
});
}
} }
deleteShareItem = (e, groupID) => { deleteShareItem = (e, groupID) => {
e.preventDefault(); e.preventDefault();
let path = this.props.itemPath; let path = this.props.itemPath;
let repoID = this.props.repoID; let repoID = this.props.repoID;
seafileAPI.deleteShareToGroupItem(repoID, path, 'group', groupID).then(() => { if (this.props.isGroupOwnedRepo) {
this.setState({ seafileAPI.deleteGroupOwnedRepoSharedGroupItem(repoID, groupID).then(() => {
sharedItems: this.state.sharedItems.filter(item => { return item.group_info.id !== groupID; }) this.setState({
sharedItems: this.state.sharedItems.filter(item => { return item.group_info.id !== groupID; })
});
}); });
}); } else {
seafileAPI.deleteShareToGroupItem(repoID, path, 'group', groupID).then(() => {
this.setState({
sharedItems: this.state.sharedItems.filter(item => { return item.group_info.id !== groupID; })
});
});
}
} }
render() { render() {
@ -135,6 +173,7 @@ class ShareToGroup extends React.Component {
options={this.options} options={this.options}
components={makeAnimated()} components={makeAnimated()}
inputId={'react-select-2-input'} inputId={'react-select-2-input'}
value={this.state.selectedOption}
/> />
</td> </td>
<td> <td>
@ -150,14 +189,16 @@ class ShareToGroup extends React.Component {
</td> </td>
</tr> </tr>
<tr> <tr>
{this.state.errorMsg.length > 0 && <td colSpan={3}>
this.state.errorMsg.map((item, index = 0, arr) => { {this.state.errorMsg.length > 0 &&
return ( this.state.errorMsg.map((item, index = 0, arr) => {
<p className="error" key={index}>{this.state.errorMsg[index].group_name} return (
{': '}{this.state.errorMsg[index].error_msg}</p> <p className="error" key={index}>{this.state.errorMsg[index].group_name}
); {': '}{this.state.errorMsg[index].error_msg}</p>
}) );
} })
}
</td>
</tr> </tr>
</thead> </thead>
<GroupList items={this.state.sharedItems} deleteShareItem={this.deleteShareItem} /> <GroupList items={this.state.sharedItems} deleteShareItem={this.deleteShareItem} />
@ -173,7 +214,7 @@ function GroupList(props) {
<tr key={index}> <tr key={index}>
<td>{item.group_info.name}</td> <td>{item.group_info.name}</td>
<td>{Utils.sharePerms[item.permission]}</td> <td>{Utils.sharePerms[item.permission]}</td>
<td><i onClick={(e) => {props.deleteShareItem(e, item.group_info.id);}} className="sf2-icon-delete" title="Delete"></i></td> <td><a href="#" onClick={(e) => {props.deleteShareItem(e, item.group_info.id);}} className="sf2-icon-x3 sf2-x op-icon" title={gettext('Delete')}></a></td>
</tr> </tr>
))} ))}
</tbody> </tbody>

View File

@ -7,6 +7,7 @@ import { Button, Input } from 'reactstrap';
import { seafileAPI } from '../../utils/seafile-api.js'; import { seafileAPI } from '../../utils/seafile-api.js';
const propTypes = { const propTypes = {
isGroupOwnedRepo: PropTypes.bool,
itemPath: PropTypes.string.isRequired, itemPath: PropTypes.string.isRequired,
repoID: PropTypes.string.isRequired repoID: PropTypes.string.isRequired
}; };
@ -92,29 +93,64 @@ class ShareToUser extends React.Component {
users[i] = this.state.selectedOption[i].email; users[i] = this.state.selectedOption[i].email;
} }
} }
seafileAPI.shareFolder(repoID, path, 'user', this.state.permission, users).then(res => { if (this.props.isGroupOwnedRepo) {
if (res.data.failed.length > 0) { seafileAPI.shareGroupOwnedRepoToUser(repoID, this.state.permission, users).then(res => {
let errorMsg = []; if (res.data.failed.length > 0) {
for (let i = 0 ; i < res.data.failed.length ; i++) { let errorMsg = [];
errorMsg[i] = res.data.failed[i]; for (let i = 0 ; i < res.data.failed.length ; i++) {
errorMsg[i] = res.data.failed[i];
}
this.setState({errorMsg: errorMsg});
} }
this.setState({errorMsg: errorMsg}); // todo modify api
}
this.setState({ let items = res.data.success.map(item => {
sharedItems: this.state.sharedItems.concat(res.data.success) let sharedItem = {
'user_info': { 'nickname': item.user_name, 'name': item.user_email},
'permission': item.permission,
'share_type': 'user',
};
return sharedItem;
});
this.setState({
sharedItems: this.state.sharedItems.concat(items),
selectedOption: null,
});
}); });
}); } else {
seafileAPI.shareFolder(repoID, path, 'user', this.state.permission, users).then(res => {
if (res.data.failed.length > 0) {
let errorMsg = [];
for (let i = 0 ; i < res.data.failed.length ; i++) {
errorMsg[i] = res.data.failed[i];
}
this.setState({errorMsg: errorMsg});
}
this.setState({
sharedItems: this.state.sharedItems.concat(res.data.success),
selectedOption: null,
});
});
}
} }
deleteShareItem = (e, username) => { deleteShareItem = (e, username) => {
e.preventDefault(); e.preventDefault();
let path = this.props.itemPath; let path = this.props.itemPath;
let repoID = this.props.repoID; let repoID = this.props.repoID;
seafileAPI.deleteShareToUserItem(repoID, path, 'user', username).then(res => { if (this.props.isGroupOwnedRepo) {
this.setState({ seafileAPI.deleteGroupOwnedRepoSharedUserItem(repoID, username).then(res => {
sharedItems: this.state.sharedItems.filter( item => { return item.user_info.name !== username; }) this.setState({
sharedItems: this.state.sharedItems.filter( item => { return item.user_info.name !== username; })
});
}); });
}); } else {
seafileAPI.deleteShareToUserItem(repoID, path, 'user', username).then(res => {
this.setState({
sharedItems: this.state.sharedItems.filter( item => { return item.user_info.name !== username; })
});
});
}
} }
render() { render() {
@ -130,12 +166,16 @@ class ShareToUser extends React.Component {
<tr> <tr>
<td> <td>
<AsyncSelect <AsyncSelect
className='reviewer-select' isMulti isFocused
loadOptions={this.loadOptions}
placeholder={gettext('Please enter 1 or more character')}
onChange={this.handleSelectChange}
isClearable classNamePrefix
inputId={'react-select-1-input'} inputId={'react-select-1-input'}
className='reviewer-select'
placeholder={gettext('Please enter 1 or more character')}
loadOptions={this.loadOptions}
onChange={this.handleSelectChange}
value={this.state.selectedOption}
isMulti
isFocused
isClearable
classNamePrefix
/> />
</td> </td>
<td> <td>
@ -152,14 +192,16 @@ class ShareToUser extends React.Component {
</td> </td>
</tr> </tr>
<tr> <tr>
{this.state.errorMsg.length > 0 && <td colSpan={3}>
this.state.errorMsg.map((item, index = 0, arr) => { {this.state.errorMsg.length > 0 &&
return ( this.state.errorMsg.map((item, index = 0, arr) => {
<p className="error" key={index}>{this.state.errorMsg[index].email} return (
{': '}{this.state.errorMsg[index].error_msg}</p> <p className="error" key={index}>{this.state.errorMsg[index].email}
); {': '}{this.state.errorMsg[index].error_msg}</p>
}) );
} })
}
</td>
</tr> </tr>
</thead> </thead>
<UserList items={sharedItems} deleteShareItem={this.deleteShareItem} /> <UserList items={sharedItems} deleteShareItem={this.deleteShareItem} />
@ -175,7 +217,7 @@ function UserList(props) {
<tr key={index}> <tr key={index}>
<td>{item.user_info.nickname}</td> <td>{item.user_info.nickname}</td>
<td>{Utils.sharePerms[item.permission]}</td> <td>{Utils.sharePerms[item.permission]}</td>
<td><i onClick={(e) => {props.deleteShareItem(e, item.user_info.name);}} className="sf2-icon-delete" title="Delete"></i></td> <td><a href="#" onClick={(e) => {props.deleteShareItem(e, item.user_info.name);}} className="sf2-icon-x3 sf2-x op-icon" title={gettext('Delete')}></a></td>
</tr> </tr>
))} ))}
</tbody> </tbody>

View File

@ -96,7 +96,7 @@ class DirView extends React.Component {
window.history.pushState({url: fileUrl, path: direntPath}, direntPath, fileUrl); window.history.pushState({url: fileUrl, path: direntPath}, direntPath, fileUrl);
} else { } else {
const w=window.open('about:blank'); const w=window.open('about:blank');
const url = siteRoot + 'lib/' + this.state.repoID + '/file' + direntPath; const url = siteRoot + 'lib/' + this.state.repoID + '/file' + Utils.encodePath(direntPath);
w.location.href = url; w.location.href = url;
} }
} }

View File

@ -477,9 +477,9 @@ class DirentListItem extends React.Component {
{this.state.isShareDialogShow && {this.state.isShareDialogShow &&
<ModalPortal> <ModalPortal>
<ShareDialog <ShareDialog
isDir={dirent.isDir()} itemType={dirent.type}
itemPath={direntPath}
itemName={dirent.name} itemName={dirent.name}
itemPath={direntPath}
repoID={this.props.repoID} repoID={this.props.repoID}
toggleDialog={this.onItemShare} toggleDialog={this.onItemShare}
/> />

View File

@ -5,6 +5,8 @@ import { Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap
import { Link } from '@reach/router'; import { Link } from '@reach/router';
import { Utils } from '../../utils/utils'; import { Utils } from '../../utils/utils';
import { gettext, siteRoot, isPro, username, folderPermEnabled } from '../../utils/constants'; import { gettext, siteRoot, isPro, username, folderPermEnabled } from '../../utils/constants';
import ModalPotal from '../../components/modal-portal';
import ShareDialog from '../../components/dialog/share-dialog';
const propTypes = { const propTypes = {
currentGroup: PropTypes.object, currentGroup: PropTypes.object,
@ -22,6 +24,7 @@ class SharedRepoListItem extends React.Component {
highlight: false, highlight: false,
isOperationShow: false, isOperationShow: false,
isItemMenuShow: false, isItemMenuShow: false,
isShowSharedDialog: false,
}; };
this.isDeparementOnwerGroupMember = false; this.isDeparementOnwerGroupMember = false;
} }
@ -90,7 +93,6 @@ class SharedRepoListItem extends React.Component {
'permission': repo.permission 'permission': repo.permission
}); });
//todo change to library; div-view is not compatibility
let libPath = `${siteRoot}library/${repo.repo_id}/${Utils.encodePath(repo.repo_name)}/`; let libPath = `${siteRoot}library/${repo.repo_id}/${Utils.encodePath(repo.repo_name)}/`;
return { iconUrl, iconTitle, libPath }; return { iconUrl, iconTitle, libPath };
@ -109,7 +111,7 @@ class SharedRepoListItem extends React.Component {
this.onItemDetails(); this.onItemDetails();
break; break;
case 'Share': case 'Share':
this.onItemShared(); this.onItemShare();
break; break;
case 'Unshare': case 'Unshare':
this.onItemUnshare(); this.onItemUnshare();
@ -132,16 +134,19 @@ class SharedRepoListItem extends React.Component {
} }
onItemShare = () => { onItemShare = () => {
// todo this.setState({isShowSharedDialog: true});
} }
onItemUnshare = () => { onItemUnshare = () => {
// todo
this.props.onItemUnshare(this.props.repo); this.props.onItemUnshare(this.props.repo);
} }
onItemDelete = () => { onItemDelete = () => {
// todo this.props.onItemDelete(this.props.repo);
}
toggleShareDialog = () => {
this.setState({isShowSharedDialog: false});
} }
generatorOperations = () => { generatorOperations = () => {
@ -217,7 +222,7 @@ class SharedRepoListItem extends React.Component {
// scene one: (share, delete, itemToggle and other operations); // scene one: (share, delete, itemToggle and other operations);
// scene two: (share, unshare), (share), (unshare) // scene two: (share, unshare), (share), (unshare)
let operations = this.generatorOperations(); let operations = this.generatorOperations();
const shareOperation = <a href="#" className="sf2-icon-share sf2-x op-icon" title={gettext("Share")} onClick={this.onItemShared}></a>; const shareOperation = <a href="#" className="sf2-icon-share sf2-x op-icon" title={gettext("Share")} onClick={this.onItemShare}></a>;
const unshareOperation = <a href="#" className="sf2-icon-x3 sf2-x op-icon" title={gettext("Unshare")} onClick={this.onItemUnshare}></a> const unshareOperation = <a href="#" className="sf2-icon-x3 sf2-x op-icon" title={gettext("Unshare")} onClick={this.onItemUnshare}></a>
const deleteOperation = <a href="#" className="sf2-icon-delete sf2-x op-icon" title={gettext('Delete')} onClick={this.onItemDelete}></a>; const deleteOperation = <a href="#" className="sf2-icon-delete sf2-x op-icon" title={gettext('Delete')} onClick={this.onItemDelete}></a>;
@ -266,32 +271,62 @@ class SharedRepoListItem extends React.Component {
renderPCUI = () => { renderPCUI = () => {
let { iconUrl, iconTitle, libPath } = this.getRepoComputeParams(); let { iconUrl, iconTitle, libPath } = this.getRepoComputeParams();
let { repo } = this.props; let { repo } = this.props;
let isGroupOwnedRepo = repo.owner_email.indexOf('@seafile_group') > -1;
return ( return (
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseOver={this.onMouseOver} onMouseLeave={this.onMouseLeave}> <Fragment>
<td><img src={iconUrl} title={repo.iconTitle} alt={iconTitle} width="24" /></td> <tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseOver={this.onMouseOver} onMouseLeave={this.onMouseLeave}>
<td><Link to={libPath}>{repo.repo_name}</Link></td> <td><img src={iconUrl} title={repo.iconTitle} alt={iconTitle} width="24" /></td>
<td>{this.state.isOperationShow && this.generatorPCMenu()}</td> <td><Link to={libPath}>{repo.repo_name}</Link></td>
<td>{repo.size}</td> <td>{this.state.isOperationShow && this.generatorPCMenu()}</td>
<td title={moment(repo.last_modified).format('llll')}>{moment(repo.last_modified).fromNow()}</td> <td>{repo.size}</td>
<td title={repo.owner_contact_email}>{repo.owner_name}</td> <td title={moment(repo.last_modified).format('llll')}>{moment(repo.last_modified).fromNow()}</td>
</tr> <td title={repo.owner_contact_email}>{repo.owner_name}</td>
</tr>
{this.state.isShowSharedDialog && (
<ModalPotal>
<ShareDialog
itemType={'library'}
itemName={repo.repo_name}
itemPath={'/'}
repoID={repo.repo_id}
isGroupOwnedRepo={isGroupOwnedRepo}
toggleDialog={this.toggleShareDialog}
/>
</ModalPotal>
)}
</Fragment>
); );
} }
renderMobileUI = () => { renderMobileUI = () => {
let { iconUrl, iconTitle, libPath } = this.getRepoComputeParams(); let { iconUrl, iconTitle, libPath } = this.getRepoComputeParams();
let { repo } = this.props; let { repo } = this.props;
let isGroupOwnedRepo = repo.owner_email.indexOf('@seafile_group') > -1;
return ( return (
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseOver={this.onMouseOver} onMouseLeave={this.onMouseLeave}> <Fragment>
<td><img src={iconUrl} title={iconTitle} alt={iconTitle}/></td> <tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseOver={this.onMouseOver} onMouseLeave={this.onMouseLeave}>
<td> <td><img src={iconUrl} title={iconTitle} alt={iconTitle}/></td>
<Link to={libPath}>{repo.repo_name}</Link><br /> <td>
<span className="item-meta-info" title={repo.owner_contact_email}>{repo.owner_name}</span> <Link to={libPath}>{repo.repo_name}</Link><br />
<span className="item-meta-info">{repo.size}</span> <span className="item-meta-info" title={repo.owner_contact_email}>{repo.owner_name}</span>
<span className="item-meta-info" title={moment(repo.last_modified).format('llll')}>{moment(repo.last_modified).fromNow()}</span> <span className="item-meta-info">{repo.size}</span>
</td> <span className="item-meta-info" title={moment(repo.last_modified).format('llll')}>{moment(repo.last_modified).fromNow()}</span>
<td>{this.generatorMobileMenu()}</td> </td>
</tr> <td>{this.generatorMobileMenu()}</td>
</tr>
{this.state.isShowSharedDialog && (
<ModalPotal>
<ShareDialog
itemType={'library'}
itemName={repo.repo_name}
itemPath={'/'}
repoID={repo.repo_id}
isGroupOwnedRepo={isGroupOwnedRepo}
toggleDialog={this.toggleShareDialog}
/>
</ModalPotal>
)}
</Fragment>
); );
} }

View File

@ -8,6 +8,7 @@ const propTypes = {
repoList: PropTypes.array.isRequired, repoList: PropTypes.array.isRequired,
isShowTableThread: PropTypes.bool, isShowTableThread: PropTypes.bool,
onItemUnshare: PropTypes.func.isRequired, onItemUnshare: PropTypes.func.isRequired,
onItemDelete: PropTypes.func.isRequired,
}; };
class SharedRepoListView extends React.Component { class SharedRepoListView extends React.Component {
@ -37,6 +38,7 @@ class SharedRepoListView extends React.Component {
isItemFreezed={this.state.isItemFreezed} isItemFreezed={this.state.isItemFreezed}
onFreezedItem={this.onFreezedItem} onFreezedItem={this.onFreezedItem}
onItemUnshare={this.props.onItemUnshare} onItemUnshare={this.props.onItemUnshare}
onItemDelete={this.props.onItemDelete}
/> />
); );
})} })}

View File

@ -1,4 +1,4 @@
.department-group-icon { /* for cur-view-path*/ .department-group-icon {
margin-left: 0.25rem; margin-left: 0.25rem;
color:#888; color:#888;
} }

View File

@ -22,12 +22,10 @@
} }
.share-dialog-content label { .share-dialog-content label {
font-weight: bold;
padding: 0.5rem 0 0.25rem; padding: 0.5rem 0 0.25rem;
} }
.share-dialog-content label.form-check-label { .share-dialog-content label.form-check-label {
font-weight: normal;
padding: 0.25rem 0; padding: 0.25rem 0;
} }

View File

@ -152,16 +152,49 @@ class GroupView extends React.Component {
this.setState({isCreateRepoDialogShow: !this.state.isCreateRepoDialogShow}); this.setState({isCreateRepoDialogShow: !this.state.isCreateRepoDialogShow});
} }
onCreateRepo = (repo) => { onCreateRepo = (repo, groupOwnerType) => {
let groupId = this.props.groupID; let groupId = this.props.groupID;
seafileAPI.createGroupRepo(groupId, repo).then(res => { if (groupOwnerType && groupOwnerType === 'department') {
let repo = new RepoInfo(res.data); seafileAPI.createGroupOwnedLibrary(groupId, repo).then(res => { //need modify endpoint api
let repoList = this.addRepoItem(repo); let object = {
repo_id: res.data.id,
repo_name: res.data.name,
owner_name: res.data.group_name,
owner_email: res.data.owner,
permission: res.data.permission,
mtime: res.data.mtime,
size: res.data.size,
encrypted: res.data.encrypted,
};
let repo = new RepoInfo(object);
let repoList = this.addRepoItem(repo);
this.setState({repoList: repoList});
}).then(() => {
//todo
});
} else {
seafileAPI.createGroupRepo(groupId, repo).then(res => {
let repo = new RepoInfo(res.data);
let repoList = this.addRepoItem(repo);
this.setState({repoList: repoList});
}).catch(() => {
//todo
});
}
this.onCreateRepoToggle();
}
onItemDelete = (repo) => {
let groupID = this.props.groupID;
seafileAPI.deleteGroupOwnedLibrary(groupID, repo.repo_id).then(() => {
let repoList = this.state.repoList.filter(item => {
return item.repo_id !== repo.repo_id;
});
this.setState({repoList: repoList}); this.setState({repoList: repoList});
}).catch(() => { }).catch(() => {
//todo // todo;
}); });
this.onCreateRepoToggle();
} }
addRepoItem = (repo) => { addRepoItem = (repo) => {
@ -232,6 +265,7 @@ class GroupView extends React.Component {
repoList={this.state.repoList} repoList={this.state.repoList}
currentGroup={this.state.currentGroup} currentGroup={this.state.currentGroup}
onItemUnshare={this.onItemUnshare} onItemUnshare={this.onItemUnshare}
onItemDelete={this.onItemDelete}
/> />
} }
</div> </div>

View File

@ -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() { render() {
let group = this.props.group; let group = this.props.group;
const emptyTip = <p className="group-item-empty-tip">{gettext('No libraries')}</p>; const emptyTip = <p className="group-item-empty-tip">{gettext('No libraries')}</p>;
@ -60,6 +72,7 @@ class RepoListViewPanel extends React.Component {
currentGroup={this.props.group} currentGroup={this.props.group}
repoList={this.state.repoList} repoList={this.state.repoList}
onItemUnshare={this.onItemUnshare} onItemUnshare={this.onItemUnshare}
onItemDelete={this.onItemDelete}
/> />
} }
</div> </div>
@ -178,6 +191,5 @@ const GroupsViewPropTypes = {
}; };
GroupsView.propTypes = GroupsViewPropTypes; GroupsView.propTypes = GroupsViewPropTypes;
// Groups.propTypes = propTypes;
export default GroupsView; export default GroupsView;

View File

@ -1,5 +1,5 @@
import React, { Component } from 'react'; import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import moment from 'moment'; import moment from 'moment';
import { Link } from '@reach/router'; import { Link } from '@reach/router';
@ -8,6 +8,8 @@ import { gettext, siteRoot, storages, canGenerateShareLink, canGenerateUploadLin
import { Utils } from '../../utils/utils'; import { Utils } from '../../utils/utils';
import { seafileAPI } from '../../utils/seafile-api'; import { seafileAPI } from '../../utils/seafile-api';
import RenameInput from '../../components/rename-input'; import RenameInput from '../../components/rename-input';
import ModalPotal from '../../components/modal-portal';
import ShareDialog from '../../components/dialog/share-dialog';
const propTypes = { const propTypes = {
data: PropTypes.object.isRequired, data: PropTypes.object.isRequired,
@ -29,6 +31,7 @@ class Item extends Component {
showOpIcon: false, showOpIcon: false,
operationMenuOpen: false, operationMenuOpen: false,
showChangeLibName: false, showChangeLibName: false,
isShowSharedDialog: false,
highlight: false, highlight: false,
}; };
} }
@ -73,7 +76,11 @@ class Item extends Component {
share = (e) => { share = (e) => {
e.preventDefault(); e.preventDefault();
// TODO this.setState({isShowSharedDialog: true});
}
toggleShareDialog = () => {
this.setState({isShowSharedDialog: false});
} }
showDeleteItemPopup = (e) => { showDeleteItemPopup = (e) => {
@ -232,43 +239,69 @@ class Item extends Component {
); );
const desktopItem = ( const desktopItem = (
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}> <Fragment>
<td><img src={data.icon_url} title={data.icon_title} alt={data.icon_title} width="24" /></td> <tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
<td> <td><img src={data.icon_url} title={data.icon_title} alt={data.icon_title} width="24" /></td>
{this.state.showChangeLibName && ( <td>
<RenameInput {this.state.showChangeLibName && (
name={data.repo_name} <RenameInput
onRenameConfirm={this.onRenameConfirm} name={data.repo_name}
onRenameCancel={this.onRenameCancel} onRenameConfirm={this.onRenameConfirm}
onRenameCancel={this.onRenameCancel}
/>
)}
{!this.state.showChangeLibName && data.repo_name && (
<Link to={data.url}>{data.repo_name}</Link>
)}
{!this.state.showChangeLibName && !data.repo_name &&
(gettext('Broken (please contact your administrator to fix this library)'))
}
</td>
<td>{data.repo_name ? desktopOperations : ''}</td>
<td>{Utils.formatSize({bytes: data.size})}</td>
{storages.length ? <td>{data.storage_name}</td> : null}
<td title={moment(data.last_modified).format('llll')}>{moment(data.last_modified).fromNow()}</td>
</tr>
{this.state.isShowSharedDialog && (
<ModalPotal>
<ShareDialog
itemType={'library'}
itemName={data.repo_name}
itemPath={'/'}
repoID={data.repo_id}
toggleDialog={this.toggleShareDialog}
/> />
)} </ModalPotal>
{!this.state.showChangeLibName && data.repo_name && ( )}
<Link to={data.url}>{data.repo_name}</Link> </Fragment>
)}
{!this.state.showChangeLibName && !data.repo_name &&
(gettext('Broken (please contact your administrator to fix this library)'))
}
</td>
<td>{data.repo_name ? desktopOperations : ''}</td>
<td>{Utils.formatSize({bytes: data.size})}</td>
{storages.length ? <td>{data.storage_name}</td> : null}
<td title={moment(data.last_modified).format('llll')}>{moment(data.last_modified).fromNow()}</td>
</tr>
); );
const mobileItem = ( const mobileItem = (
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}> <Fragment>
<td><img src={data.icon_url} title={data.icon_title} alt={data.icon_title} width="24" /></td> <tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
<td> <td><img src={data.icon_url} title={data.icon_title} alt={data.icon_title} width="24" /></td>
{data.repo_name ? <td>
<Link to={data.url}>{data.repo_name}</Link> : {data.repo_name ?
gettext('Broken (please contact your administrator to fix this library)')} <Link to={data.url}>{data.repo_name}</Link> :
<br /> gettext('Broken (please contact your administrator to fix this library)')}
<span className="item-meta-info">{Utils.formatSize({bytes: data.size})}</span> <br />
<span className="item-meta-info" title={moment(data.last_modified).format('llll')}>{moment(data.last_modified).fromNow()}</span> <span className="item-meta-info">{Utils.formatSize({bytes: data.size})}</span>
</td> <span className="item-meta-info" title={moment(data.last_modified).format('llll')}>{moment(data.last_modified).fromNow()}</span>
<td>{data.repo_name ? mobileOperations : ''}</td> </td>
</tr> <td>{data.repo_name ? mobileOperations : ''}</td>
</tr>
{this.state.isShowSharedDialog && (
<ModalPotal>
<ShareDialog
itemType={'library'}
itemName={data.repo_name}
itemPath={'/'}
repoID={data.repo_id}
toggleDialog={this.toggleShareDialog}
/>
</ModalPotal>
)}
</Fragment>
); );
return window.innerWidth >= 768 ? desktopItem : mobileItem; return window.innerWidth >= 768 ? desktopItem : mobileItem;

View File

@ -48,8 +48,17 @@ class MyLibraries extends Component {
} }
onCreateRepo = (repo) => { onCreateRepo = (repo) => {
let permission = repo.permission;
seafileAPI.createMineRepo(repo).then((res) => { 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.state.items.push(repo);
this.setState({items: this.state.items}); this.setState({items: this.state.items});
}); });

View File

@ -334,7 +334,7 @@ class Wiki extends Component {
this.showFile(node.path); this.showFile(node.path);
} }
} else { } 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'); let newWindow = window.open('about:blank');
newWindow.location.href = url; newWindow.location.href = url;
} }
@ -367,7 +367,7 @@ class Wiki extends Component {
this.showDir(node.path); this.showDir(node.path);
} else { } else {
const w=window.open('about:blank'); 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; w.location.href = url;
} }
} }

View File

@ -147,7 +147,9 @@ export const Utils = {
return encodeURIComponent(e); return encodeURIComponent(e);
}).join('/'); }).join('/');
*/ */
if (!path) {
return '';
}
var path_arr = path.split('/'); var path_arr = path.split('/');
var path_arr_ = []; var path_arr_ = [];
for (var i = 0, len = path_arr.length; i < len; i++) { for (var i = 0, len = path_arr.length; i < len; i++) {

View File

@ -133,7 +133,7 @@ class Wiki extends Component {
this.enterViewFileState(tree, node, node.path); this.enterViewFileState(tree, node, node.path);
} }
} else { } 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'); let newWindow = window.open('about:blank');
newWindow.location.href = url; newWindow.location.href = url;
} }
@ -161,7 +161,7 @@ class Wiki extends Component {
this.exitViewFileState(tree, node); this.exitViewFileState(tree, node);
} else { } else {
const w=window.open('about:blank'); 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; w.location.href = url;
} }
} }

View File

@ -204,7 +204,7 @@ urlpatterns = [
url(r'^shared-libs/$', react_fake_view, name="shared_libs"), url(r'^shared-libs/$', react_fake_view, name="shared_libs"),
url(r'^my-libs/$', react_fake_view, name="my_libs"), url(r'^my-libs/$', react_fake_view, name="my_libs"),
url(r'^groups/$', react_fake_view, name="groups"), url(r'^groups/$', react_fake_view, name="groups"),
url(r'^group/(?P<group_id>\d+)/$', react_group, name="group"), url(r'^group/.*$', react_fake_view, name="group"),
url(r'^library/.*$', react_fake_view, name="lib_view"), url(r'^library/.*$', react_fake_view, name="lib_view"),
url(r'^my-libs/deleted/$', react_fake_view, name="my_libs_deleted"), url(r'^my-libs/deleted/$', react_fake_view, name="my_libs_deleted"),