1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-16 23:29:49 +00:00

support rename Wiki (#6142)

* 01 UI support rename Wiki

* rename wiki

* old wiki support rename

* fix wiki-v1 and wiki-v2 have same id

* fix format

---------

Co-authored-by: ‘JoinTyang’ <yangtong1009@163.com>
This commit is contained in:
Michael An
2024-05-31 10:59:59 +08:00
committed by GitHub
parent abdf2ebb0c
commit 7ea5517e40
24 changed files with 264 additions and 21 deletions

View File

@@ -75,7 +75,7 @@ class App extends Component {
}
navigateClientUrlToLib = () =>{
if(window.location.hash && window.location.hash.indexOf('common/lib') != -1){
if (window.location.hash && window.location.hash.indexOf('common/lib') != -1){
let splitUrlArray = window.location.hash.split('/');
let repoID = splitUrlArray[splitUrlArray.length - 2];
let url = siteRoot + 'library/' + repoID + '/';

View File

@@ -32,7 +32,7 @@ class ImportMembersDialog extends React.Component {
}
// check file extension
let fileName = this.fileInputRef.current.files[0].name;
if(fileName.substr(fileName.lastIndexOf('.') + 1) != 'xlsx') {
if (fileName.substr(fileName.lastIndexOf('.') + 1) != 'xlsx') {
this.setState({
errorMsg: gettext('Please choose a .xlsx file.')
});

View File

@@ -32,7 +32,7 @@ class ImportOrgUsersDialog extends React.Component {
}
// check file extension
let fileName = this.fileInputRef.current.files[0].name;
if(fileName.substr(fileName.lastIndexOf('.') + 1) != 'xlsx') {
if (fileName.substr(fileName.lastIndexOf('.') + 1) != 'xlsx') {
this.setState({
errorMsg: gettext('Please choose a .xlsx file.')
});

View File

@@ -0,0 +1,93 @@
import React from 'react';
import PropTypes from 'prop-types';
import { gettext } from '../../utils/constants';
import { Button, Modal, ModalHeader, Input, ModalBody, ModalFooter, Alert } from 'reactstrap';
const propTypes = {
wiki: PropTypes.object,
onRename: PropTypes.func.isRequired,
toggleCancel: PropTypes.func.isRequired,
};
class RenameWikiDialog extends React.Component {
constructor(props) {
super(props);
this.state = {
newName: props.wiki.name,
errMessage: '',
isSubmitBtnActive: false,
};
this.newInput = React.createRef();
}
handleChange = (e) => {
this.setState({
isSubmitBtnActive: !!e.target.value.trim(),
newName: e.target.value
});
};
handleSubmit = () => {
let { isValid, errMessage } = this.validateInput();
if (!isValid) {
this.setState({ errMessage : errMessage });
} else {
this.props.onRename(this.state.newName.trim());
}
};
handleKeyDown = (e) => {
if (e.key === 'Enter') {
this.handleSubmit();
}
};
toggle = () => {
this.props.toggleCancel();
};
validateInput = () => {
let newName = this.state.newName.trim();
let isValid = true;
let errMessage = '';
if (!newName) {
isValid = false;
errMessage = gettext('Name is required.');
return { isValid, errMessage };
}
if (newName.indexOf('/') > -1) {
isValid = false;
errMessage = gettext('Name should not include ' + '\'/\'' + '.');
return { isValid, errMessage };
}
return { isValid, errMessage };
};
render() {
return (
<Modal isOpen={true} toggle={this.toggle}>
<ModalHeader toggle={this.toggle}>{gettext('Rename Wiki')}</ModalHeader>
<ModalBody>
<p>{gettext('New Wiki name')}</p>
<Input
onKeyDown={this.handleKeyDown}
innerRef={this.newInput}
placeholder="newName"
value={this.state.newName}
onChange={this.handleChange}
/>
{this.state.errMessage && <Alert color="danger" className="mt-2">{this.state.errMessage}</Alert>}
</ModalBody>
<ModalFooter>
<Button color="secondary" onClick={this.toggle}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={this.handleSubmit} disabled={!this.state.isSubmitBtnActive}>{gettext('Submit')}</Button>
</ModalFooter>
</Modal>
);
}
}
RenameWikiDialog.propTypes = propTypes;
export default RenameWikiDialog;

View File

@@ -179,7 +179,7 @@ class ShareToGroup extends React.Component {
let path = this.props.itemPath;
let repoID = this.props.repoID;
seafileAPI.listSharedItems(repoID, path, 'group').then((res) => {
if(res.data.length !== 0) {
if (res.data.length !== 0) {
this.setState({
sharedItems: res.data
});

View File

@@ -188,7 +188,7 @@ class ShareToUser extends React.Component {
let path = this.props.itemPath;
let repoID = this.props.repoID;
seafileAPI.listSharedItems(repoID, path, 'user').then((res) => {
if(res.data.length !== 0) {
if (res.data.length !== 0) {
this.setState({sharedItems: res.data});
}
}).catch(error => {

View File

@@ -33,7 +33,7 @@ class SysAdminImportUserDialog extends React.Component {
}
// check file extension
let fileName = this.fileInputRef.current.files[0].name;
if(fileName.substr(fileName.lastIndexOf('.') + 1) != 'xlsx') {
if (fileName.substr(fileName.lastIndexOf('.') + 1) != 'xlsx') {
this.setState({
errorMsg: gettext('Please choose a .xlsx file.')
});

View File

@@ -154,7 +154,7 @@ class SysAdminShareToGroup extends React.Component {
listSharedGroups = () => {
let repoID = this.props.repoID;
seafileAPI.sysAdminListRepoSharedItems(repoID, 'group').then((res) => {
if(res.data.length !== 0) {
if (res.data.length !== 0) {
this.setState({
sharedItems: res.data
});

View File

@@ -133,7 +133,7 @@ class SysAdminShareToUser extends React.Component {
componentDidMount() {
let repoID = this.props.repoID;
seafileAPI.sysAdminListRepoSharedItems(repoID, 'user').then((res) => {
if(res.data.length !== 0) {
if (res.data.length !== 0) {
this.setState({sharedItems: res.data});
}
}).catch(error => {

View File

@@ -615,7 +615,7 @@ class FileUploader extends React.Component {
resumableFile.bootstrap();
var firedRetry = false;
resumableFile.resumableObj.on('chunkingComplete', () => {
if(!firedRetry) {
if (!firedRetry) {
seafileAPI.getFileUploadedBytes(repoID, path, fileName).then(res => {
let uploadedBytes = res.data.uploadedBytes;
let blockSize = parseInt(resumableUploadFileBlockSize) * 1024 * 1024 || 1024 * 1024;

View File

@@ -85,7 +85,7 @@ class Search extends Component {
getSearchResult(queryData) {
if(this.source){
if (this.source){
this.cancelRequest();
}
this.setState({

View File

@@ -615,7 +615,7 @@ class FileUploader extends React.Component {
resumableFile.bootstrap();
var firedRetry = false;
resumableFile.resumableObj.on('chunkingComplete', () => {
if(!firedRetry) {
if (!firedRetry) {
seafileAPI.getFileUploadedBytes(repoID, path, fileName).then(res => {
let uploadedBytes = res.data.uploadedBytes;
let blockSize = parseInt(resumableUploadFileBlockSize) * 1024 * 1024 || 1024 * 1024;

View File

@@ -7,6 +7,7 @@ const propTypes = {
deleteWiki: PropTypes.func.isRequired,
title: PropTypes.string.isRequired,
isDepartment: PropTypes.bool.isRequired,
renameWiki: PropTypes.func.isRequired,
};
class WikiCardGroup extends Component {
@@ -22,10 +23,11 @@ class WikiCardGroup extends Component {
{wikis.map((wiki, index) => {
return (
<WikiCardItem
key={index}
key={index + wiki.id}
wiki={wiki}
deleteWiki={this.props.deleteWiki}
isDepartment={isDepartment}
renameWiki={this.props.renameWiki}
/>
);
})}

View File

@@ -6,10 +6,12 @@ import { seafileAPI } from '../../utils/seafile-api';
import { siteRoot, gettext } from '../../utils/constants';
import ModalPortal from '../modal-portal';
import WikiDeleteDialog from '../dialog/wiki-delete-dialog';
import RenameWikiDialog from '../dialog/rename-wiki-dialog';
const propTypes = {
wiki: PropTypes.object.isRequired,
deleteWiki: PropTypes.func.isRequired,
renameWiki: PropTypes.func.isRequired,
isDepartment: PropTypes.bool.isRequired,
};
@@ -18,6 +20,7 @@ class WikiCardItem extends Component {
super(props);
this.state = {
isShowDeleteDialog: false,
isShowRenameDialog: false,
isItemMenuShow: false,
ownerAvatar: '',
};
@@ -32,6 +35,12 @@ class WikiCardItem extends Component {
}
}
onRenameToggle = (e) => {
this.setState({
isShowRenameDialog: !this.state.isShowRenameDialog,
});
};
onDeleteToggle = (e) => {
e.preventDefault();
this.setState({
@@ -53,6 +62,13 @@ class WikiCardItem extends Component {
});
};
renameWiki = (newName) => {
if (this.props.wiki.name !== newName) {
this.props.renameWiki(this.props.wiki, newName);
}
this.setState({ isShowRenameDialog: false });
};
clickWikiCard = (link) => {
window.open(link);
};
@@ -94,7 +110,10 @@ class WikiCardItem extends Component {
let wikiName = isOldVersion ? `${wiki.name} (old version)` : wiki.name;
return (
<>
<div className="wiki-card-item" onClick={this.clickWikiCard.bind(this, isOldVersion ? publishedUrl : editUrl )}>
<div
className={`wiki-card-item ${this.state.isItemMenuShow ? 'wiki-card-item-menu-open' : ''}`}
onClick={this.clickWikiCard.bind(this, isOldVersion ? publishedUrl : editUrl )}
>
<div className="wiki-card-item-top">
<div className="d-flex align-items-center" style={{width: 'calc(100% - 46px)'}}>
<span className="sf3-font-wiki sf3-font" aria-hidden="true"></span>
@@ -114,7 +133,7 @@ class WikiCardItem extends Component {
style={{'minWidth': '0'}}
/>
<DropdownMenu right={true} className="dtable-dropdown-menu">
{/* <DropdownItem onClick={}>{gettext('Rename')}</DropdownItem> */}
<DropdownItem onClick={this.onRenameToggle}>{gettext('Rename')}</DropdownItem>
<DropdownItem onClick={this.onDeleteToggle}>{gettext('Unpublish')}</DropdownItem>
</DropdownMenu>
</Dropdown>
@@ -132,6 +151,15 @@ class WikiCardItem extends Component {
/>
</ModalPortal>
}
{this.state.isShowRenameDialog &&
<ModalPortal>
<RenameWikiDialog
toggleCancel={this.onRenameToggle}
onRename={this.renameWiki}
wiki={wiki}
/>
</ModalPortal>
}
</>
);
}

View File

@@ -59,6 +59,7 @@
margin-left: 20px;
}
.wiki-card-item.wiki-card-item-menu-open .wiki-card-item-top .dropdown .sf-dropdown-toggle,
.wiki-card-item:hover .wiki-card-item-top .dropdown .sf-dropdown-toggle {
opacity: 1;
}

View File

@@ -7,6 +7,7 @@ import './wiki-card-view.css';
const propTypes = {
data: PropTypes.object.isRequired,
deleteWiki: PropTypes.func.isRequired,
renameWiki: PropTypes.func.isRequired,
};
class WikiCardView extends Component {
@@ -45,6 +46,7 @@ class WikiCardView extends Component {
<WikiCardGroup
key='my-Wikis'
deleteWiki={this.props.deleteWiki}
renameWiki={this.props.renameWiki}
wikis={myWikis}
title={gettext('My Wikis')}
isDepartment={false}
@@ -55,6 +57,7 @@ class WikiCardView extends Component {
<WikiCardGroup
key={'department-Wikis' + key}
deleteWiki={this.props.deleteWiki}
renameWiki={this.props.renameWiki}
wikis={department2WikisMap[key]}
title={department2WikisMap[key][0].owner_nickname}
isDepartment={true}
@@ -65,6 +68,7 @@ class WikiCardView extends Component {
<WikiCardGroup
key='old-Wikis'
deleteWiki={this.props.deleteWiki}
renameWiki={this.props.renameWiki}
wikis={v1Wikis}
title={gettext('Old Wikis')}
isDepartment={false}

View File

@@ -321,7 +321,7 @@ class OrgAllRepos extends Component {
return new OrgAdminRepo(item);
});
let page_info = {};
if(res.data.page_info === undefined){
if (res.data.page_info === undefined){
let page = res.data.page;
let has_next_page = res.data.page_next;
page_info = {

View File

@@ -90,7 +90,7 @@ class StatisticCommonTool extends React.Component {
onSubmit = () => {
let { startValue, endValue } = this.state;
if(!startValue || !endValue) {
if (!startValue || !endValue) {
return;
}
this.setState({

View File

@@ -90,7 +90,7 @@ class StatisticCommonTool extends React.Component {
onSubmit = () => {
let { startValue, endValue } = this.state;
if(!startValue || !endValue) {
if (!startValue || !endValue) {
return;
}
this.setState({

View File

@@ -578,7 +578,7 @@ class FileUploader extends React.Component {
resumableFile.bootstrap();
var firedRetry = false;
resumableFile.resumableObj.on('chunkingComplete', () => {
if(!firedRetry) {
if (!firedRetry) {
seafileAPI.getFileUploadedBytes(repoID, path, fileName).then(res => {
let uploadedBytes = res.data.uploadedBytes;
let blockSize = parseInt(resumableUploadFileBlockSize) * 1024 * 1024 || 1024 * 1024;

View File

@@ -103,7 +103,7 @@ class Wikis extends Component {
});
this.setState({wikis: wikis});
}).catch((error) => {
if(error.response) {
if (error.response) {
let errorMsg = error.response.data.error_msg;
toaster.danger(errorMsg);
}
@@ -115,7 +115,41 @@ class Wikis extends Component {
});
this.setState({wikis: wikis});
}).catch((error) => {
if(error.response) {
if (error.response) {
let errorMsg = error.response.data.error_msg;
toaster.danger(errorMsg);
}
});
}
};
renameWiki = (wiki, newName) => {
if (wiki.version === 'v1') {
wikiAPI.renameWiki(wiki.id, newName).then(() => {
let wikis = this.state.wikis.map(item => {
if (item.id === wiki.id && item.version === 'v1') {
item.name = newName;
}
return item;
});
this.setState({wikis: wikis});
}).catch((error) => {
if (error.response) {
let errorMsg = error.response.data.error_msg;
toaster.danger(errorMsg);
}
});
} else {
wikiAPI.renameWiki2(wiki.id, newName).then(() => {
let wikis = this.state.wikis.map(item => {
if (item.id === wiki.id && item.version === 'v2') {
item.name = newName;
}
return item;
});
this.setState({wikis: wikis});
}).catch((error) => {
if (error.response) {
let errorMsg = error.response.data.error_msg;
toaster.danger(errorMsg);
}
@@ -161,6 +195,7 @@ class Wikis extends Component {
<WikiCardView
data={this.state}
deleteWiki={this.deleteWiki}
renameWiki={this.renameWiki}
/>
</div>
}

View File

@@ -19,7 +19,7 @@ export const Utils = {
bytesToSize: function(bytes) {
if (typeof(bytes) == 'undefined') return ' ';
if(bytes < 0) return '--';
if (bytes < 0) return '--';
const sizes = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
if (bytes === 0) return bytes + ' ' + sizes[0];

View File

@@ -105,6 +105,13 @@ class WikiAPI {
return this._sendPostRequest(url, form);
}
renameWiki(wikiId, wikiName) {
const url = this.server + '/api/v2.1/wikis/' + wikiId + '/';
let form = new FormData();
form.append('wiki_name', wikiName);
return this._sendPostRequest(url, form);
}
deleteWiki(wikiId) {
const url = this.server + '/api/v2.1/wikis/' + wikiId + '/';
return this.req.delete(url);
@@ -191,6 +198,14 @@ class WikiAPI {
return this.req.get(url);
}
renameWiki2(wikiId, wikiName) {
const url = this.server + '/api/v2.1/wiki2/' + wikiId + '/';
let params = {
wiki_name: wikiName
};
return this.req.put(url, params);
}
}
let wikiAPI = new WikiAPI();

View File

@@ -202,6 +202,67 @@ class Wiki2View(APIView):
permission_classes = (IsAuthenticated, )
throttle_classes = (UserRateThrottle, )
def put(self, request, wiki_id):
wiki_name = request.data.get('wiki_name')
if not wiki_name:
return api_error(status.HTTP_400_BAD_REQUEST, 'wiki name is required.')
if not is_valid_dirent_name(wiki_name):
return api_error(status.HTTP_400_BAD_REQUEST, 'name invalid.')
username = request.user.username
try:
wiki = Wiki.objects.get(id=wiki_id)
except Wiki.DoesNotExist:
error_msg = 'Wiki not found.'
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
if not check_wiki_admin_permission(wiki, username):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
if wiki_name == wiki.name:
return Response({"success": True})
repo_id = wiki.repo_id
repo = seafile_api.get_repo(repo_id)
if not repo:
error_msg = "Wiki library not found."
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
if not check_wiki_admin_permission(wiki, username):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
if not is_group_wiki(wiki):
if is_org_context(request):
repo_owner = seafile_api.get_org_repo_owner(repo.id)
else:
repo_owner = seafile_api.get_repo_owner(repo.id)
is_owner = True if username == repo_owner else False
if not is_owner:
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
# check repo status
repo_status = repo.status
if repo_status != 0:
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
try:
# desc is ''
seafile_api.edit_repo(repo_id, wiki_name, '', username)
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
wiki.name = wiki_name
wiki.save()
return Response({"success": True})
def delete(self, request, wiki_id):
"""Delete a wiki.
"""
@@ -432,6 +493,10 @@ class Wiki2PageView(APIView):
path = page_info.get('path')
doc_uuid = page_info.get('docUuid')
if not page_info:
error_msg = 'page %s not found.' % page_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
permission = check_folder_permission(request, wiki.repo_id, '/')
if not permission:
error_msg = 'Permission denied.'