mirror of
https://github.com/haiwen/seahub.git
synced 2025-08-15 05:33:38 +00:00
migrate old wiki to new wiki (#6987)
* migrate old wiki to new wiki * change style --------- Co-authored-by: Michael An <2331806369@qq.com>
This commit is contained in:
parent
2cb758e302
commit
cc625f7815
@ -1,5 +1,5 @@
|
|||||||
import SeahubSelect from './seahub-select';
|
import SeahubSelect from './seahub-select';
|
||||||
import { NoGroupMessage } from './no-group-message';
|
import { NoGroupMessage } from './no-group-message';
|
||||||
import { MenuSelectStyle, UserSelectStyle } from './seahub-select-style';
|
import { MenuSelectStyle, UserSelectStyle, NoOptionsStyle } from './seahub-select-style';
|
||||||
|
|
||||||
export { SeahubSelect, NoGroupMessage, MenuSelectStyle, UserSelectStyle };
|
export { SeahubSelect, NoGroupMessage, MenuSelectStyle, UserSelectStyle, NoOptionsStyle };
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { gettext } from '../../../utils/constants';
|
import { gettext } from '../../../utils/constants';
|
||||||
|
import { NoOptionsStyle } from './seahub-select-style';
|
||||||
|
|
||||||
const NoGroupMessage = (props) => {
|
const NoGroupMessage = (props) => {
|
||||||
return (
|
return (
|
||||||
<div {...props.innerProps} style={{ margin: '6px 10px', textAlign: 'center', color: 'hsl(0,0%,50%)' }}>{gettext('Group not found')}</div>
|
<div {...props.innerProps} style={NoOptionsStyle}>{gettext('Group not found')}</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -87,4 +87,10 @@ const UserSelectStyle = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export { MenuSelectStyle, UserSelectStyle };
|
const NoOptionsStyle = {
|
||||||
|
margin: '6px 10px',
|
||||||
|
textAlign: 'center',
|
||||||
|
color: 'hsl(0, 0%, 50%)',
|
||||||
|
};
|
||||||
|
|
||||||
|
export { MenuSelectStyle, UserSelectStyle, NoOptionsStyle };
|
||||||
|
@ -5,7 +5,7 @@ import { gettext, isPro } from '../../utils/constants';
|
|||||||
import wikiAPI from '../../utils/wiki-api';
|
import wikiAPI from '../../utils/wiki-api';
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
import toaster from '../toast';
|
import toaster from '../toast';
|
||||||
import { SeahubSelect } from '../common/select';
|
import { SeahubSelect, NoOptionsStyle } from '../common/select';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
toggleCancel: PropTypes.func.isRequired,
|
toggleCancel: PropTypes.func.isRequired,
|
||||||
@ -97,7 +97,7 @@ class AddWikiDialog extends React.Component {
|
|||||||
maxMenuHeight={200}
|
maxMenuHeight={200}
|
||||||
value={this.state.selectedOption}
|
value={this.state.selectedOption}
|
||||||
components={{ NoOptionsMessage: (
|
components={{ NoOptionsMessage: (
|
||||||
<div style={{ margin: '6px 10px', textAlign: 'center', color: 'hsl(0,0%,50%)' }}>{gettext('No department')}</div>
|
<div style={NoOptionsStyle}>{gettext('No department')}</div>
|
||||||
) }}
|
) }}
|
||||||
noOptionsMessage={() => {return gettext('No options available');}}
|
noOptionsMessage={() => {return gettext('No options available');}}
|
||||||
/>
|
/>
|
||||||
|
114
frontend/src/components/dialog/convert-wiki-dialog.js
Normal file
114
frontend/src/components/dialog/convert-wiki-dialog.js
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Input, Label } from 'reactstrap';
|
||||||
|
import { gettext, isPro } from '../../utils/constants';
|
||||||
|
import wikiAPI from '../../utils/wiki-api';
|
||||||
|
import { Utils } from '../../utils/utils';
|
||||||
|
import toaster from '../toast';
|
||||||
|
import { SeahubSelect, NoOptionsStyle } from '../common/select';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
toggleCancel: PropTypes.func.isRequired,
|
||||||
|
convertWiki: PropTypes.func.isRequired,
|
||||||
|
wiki: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
class ConvertWikiDialog extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
name: '',
|
||||||
|
isSubmitBtnActive: false,
|
||||||
|
selectedOption: null,
|
||||||
|
options: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
if (!isPro) return;
|
||||||
|
wikiAPI.listWikiDepartments().then(res => {
|
||||||
|
const departments = res.data.sort((a, b) => {
|
||||||
|
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
|
||||||
|
});
|
||||||
|
let options = [];
|
||||||
|
for (let i = 0 ; i < departments.length; i++) {
|
||||||
|
let obj = {};
|
||||||
|
obj.value = departments[i].name;
|
||||||
|
obj.id = departments[i].id;
|
||||||
|
obj.email = departments[i].email;
|
||||||
|
obj.label = departments[i].name;
|
||||||
|
options.push(obj);
|
||||||
|
}
|
||||||
|
this.setState({ options });
|
||||||
|
}).catch(error => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
inputNewName = (e) => {
|
||||||
|
this.setState({
|
||||||
|
name: e.target.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleKeyDown = (e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
this.handleSubmit(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSubmit = (e) => {
|
||||||
|
const wikiName = this.state.name.trim();
|
||||||
|
const departmentID = this.state.selectedOption ? this.state.selectedOption.id : null;
|
||||||
|
if (!wikiName) return;
|
||||||
|
this.props.convertWiki(this.props.wiki, wikiName, departmentID);
|
||||||
|
this.props.toggleCancel(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
toggle = () => {
|
||||||
|
this.props.toggleCancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSelectChange = (option) => {
|
||||||
|
this.setState({ selectedOption: option });
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Modal isOpen={true} autoFocus={false} toggle={this.toggle}>
|
||||||
|
<ModalHeader toggle={this.toggle}>{gettext('Convert Wiki')}</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<Label>{gettext('Name')}</Label>
|
||||||
|
<Input onKeyDown={this.handleKeyDown} autoFocus={true} value={this.state.name} onChange={this.inputNewName}/>
|
||||||
|
{isPro &&
|
||||||
|
<>
|
||||||
|
<Label className='mt-4'>{gettext('Wiki owner')} ({gettext('Optional')})</Label>
|
||||||
|
<SeahubSelect
|
||||||
|
onChange={this.handleSelectChange}
|
||||||
|
options={this.state.options}
|
||||||
|
hideSelectedOptions={true}
|
||||||
|
placeholder={gettext('Select a department')}
|
||||||
|
maxMenuHeight={200}
|
||||||
|
value={this.state.selectedOption}
|
||||||
|
components={{ NoOptionsMessage: (
|
||||||
|
<div style={NoOptionsStyle}>{gettext('No department')}</div>
|
||||||
|
) }}
|
||||||
|
noOptionsMessage={() => {return gettext('No options available');}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button color="secondary" onClick={this.toggle}>{gettext('Cancel')}</Button>
|
||||||
|
<Button color="primary" onClick={this.handleSubmit} disabled={!this.state.name.trim()}>{gettext('Submit')}</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConvertWikiDialog.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default ConvertWikiDialog;
|
@ -3,7 +3,7 @@ import ReactDOM from 'react-dom';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
children: PropTypes.object.isRequired,
|
children: PropTypes.any.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
const modalRoot = document.getElementById('modal-wrapper');
|
const modalRoot = document.getElementById('modal-wrapper');
|
||||||
|
@ -5,7 +5,7 @@ import { seafileAPI } from '../utils/seafile-api';
|
|||||||
import { gettext, enableShowContactEmailWhenSearchUser, enableShowLoginIDWhenSearchUser } from '../utils/constants';
|
import { gettext, enableShowContactEmailWhenSearchUser, enableShowLoginIDWhenSearchUser } from '../utils/constants';
|
||||||
import { Utils } from '../utils/utils';
|
import { Utils } from '../utils/utils';
|
||||||
import toaster from './toast';
|
import toaster from './toast';
|
||||||
import { UserSelectStyle } from './common/select';
|
import { UserSelectStyle, NoOptionsStyle } from './common/select';
|
||||||
|
|
||||||
import '../css/user-select.css';
|
import '../css/user-select.css';
|
||||||
|
|
||||||
@ -86,7 +86,6 @@ class UserSelect extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const searchValue = this.state.searchValue;
|
const searchValue = this.state.searchValue;
|
||||||
const style = { margin: '6px 10px', textAlign: 'center', color: 'hsl(0,0%,50%)' };
|
|
||||||
return (
|
return (
|
||||||
<AsyncSelect
|
<AsyncSelect
|
||||||
isClearable
|
isClearable
|
||||||
@ -94,7 +93,9 @@ class UserSelect extends React.Component {
|
|||||||
components={{
|
components={{
|
||||||
NoOptionsMessage: (props) => {
|
NoOptionsMessage: (props) => {
|
||||||
return (
|
return (
|
||||||
<div {...props.innerProps} style={style}>{searchValue ? gettext('User not found') : gettext('Enter characters to start searching')}</div>
|
<div {...props.innerProps} style={NoOptionsStyle}>
|
||||||
|
{searchValue ? gettext('User not found') : gettext('Enter characters to start searching')}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
@ -14,6 +14,7 @@ const propTypes = {
|
|||||||
isDepartment: PropTypes.bool.isRequired,
|
isDepartment: PropTypes.bool.isRequired,
|
||||||
isShowAvatar: PropTypes.bool.isRequired,
|
isShowAvatar: PropTypes.bool.isRequired,
|
||||||
renameWiki: PropTypes.func.isRequired,
|
renameWiki: PropTypes.func.isRequired,
|
||||||
|
convertWiki: PropTypes.func,
|
||||||
toggelAddWikiDialog: PropTypes.func,
|
toggelAddWikiDialog: PropTypes.func,
|
||||||
sidePanelRate: PropTypes.number,
|
sidePanelRate: PropTypes.number,
|
||||||
isSidePanelFolded: PropTypes.bool,
|
isSidePanelFolded: PropTypes.bool,
|
||||||
@ -78,7 +79,10 @@ class WikiCardGroup extends Component {
|
|||||||
isDepartment={isDepartment}
|
isDepartment={isDepartment}
|
||||||
isShowAvatar={this.props.isShowAvatar}
|
isShowAvatar={this.props.isShowAvatar}
|
||||||
renameWiki={this.props.renameWiki}
|
renameWiki={this.props.renameWiki}
|
||||||
/> : <WikiCardItem
|
convertWiki={this.props.convertWiki}
|
||||||
|
/>
|
||||||
|
:
|
||||||
|
<WikiCardItem
|
||||||
key={index + wiki.id + wiki.name}
|
key={index + wiki.id + wiki.name}
|
||||||
wiki={wiki}
|
wiki={wiki}
|
||||||
deleteWiki={this.props.deleteWiki}
|
deleteWiki={this.props.deleteWiki}
|
||||||
@ -86,6 +90,7 @@ class WikiCardGroup extends Component {
|
|||||||
isDepartment={isDepartment}
|
isDepartment={isDepartment}
|
||||||
isShowAvatar={this.props.isShowAvatar}
|
isShowAvatar={this.props.isShowAvatar}
|
||||||
renameWiki={this.props.renameWiki}
|
renameWiki={this.props.renameWiki}
|
||||||
|
convertWiki={this.props.convertWiki}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -11,6 +11,7 @@ import ShareWikiDialog from '../dialog/share-wiki-dialog';
|
|||||||
import PublishWikiDialog from '../dialog/publish-wiki-dialog';
|
import PublishWikiDialog from '../dialog/publish-wiki-dialog';
|
||||||
import wikiAPI from '../../utils/wiki-api';
|
import wikiAPI from '../../utils/wiki-api';
|
||||||
import toaster from '../toast';
|
import toaster from '../toast';
|
||||||
|
import ConvertWikiDialog from '../dialog/convert-wiki-dialog';
|
||||||
|
|
||||||
dayjs.extend(relativeTime);
|
dayjs.extend(relativeTime);
|
||||||
|
|
||||||
@ -20,6 +21,7 @@ const propTypes = {
|
|||||||
deleteWiki: PropTypes.func.isRequired,
|
deleteWiki: PropTypes.func.isRequired,
|
||||||
unshareGroupWiki: PropTypes.func.isRequired,
|
unshareGroupWiki: PropTypes.func.isRequired,
|
||||||
renameWiki: PropTypes.func.isRequired,
|
renameWiki: PropTypes.func.isRequired,
|
||||||
|
convertWiki: PropTypes.func,
|
||||||
isDepartment: PropTypes.bool.isRequired,
|
isDepartment: PropTypes.bool.isRequired,
|
||||||
isShowAvatar: PropTypes.bool.isRequired,
|
isShowAvatar: PropTypes.bool.isRequired,
|
||||||
};
|
};
|
||||||
@ -33,6 +35,7 @@ class WikiCardItem extends Component {
|
|||||||
isItemMenuShow: false,
|
isItemMenuShow: false,
|
||||||
isShowShareDialog: false,
|
isShowShareDialog: false,
|
||||||
isShowPublishDialog: false,
|
isShowPublishDialog: false,
|
||||||
|
isShowConvertDialog: false,
|
||||||
customUrl: '',
|
customUrl: '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -56,6 +59,13 @@ class WikiCardItem extends Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onConvertToggle = (e) => {
|
||||||
|
e && e.preventDefault();
|
||||||
|
this.setState({
|
||||||
|
isShowConvertDialog: !this.state.isShowConvertDialog,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
onPublishToggle = (e) => {
|
onPublishToggle = (e) => {
|
||||||
this.getPublishWikiLink();
|
this.getPublishWikiLink();
|
||||||
};
|
};
|
||||||
@ -176,6 +186,7 @@ class WikiCardItem extends Component {
|
|||||||
let showLeaveShare = false;
|
let showLeaveShare = false;
|
||||||
let showDropdownMenu = false;
|
let showDropdownMenu = false;
|
||||||
let showPublish = false;
|
let showPublish = false;
|
||||||
|
let showWikiConvert = false;
|
||||||
|
|
||||||
if (isDepartment) {
|
if (isDepartment) {
|
||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
@ -184,6 +195,9 @@ class WikiCardItem extends Component {
|
|||||||
showShare = true;
|
showShare = true;
|
||||||
showRename = true;
|
showRename = true;
|
||||||
showPublish = true;
|
showPublish = true;
|
||||||
|
if (isOldVersion) {
|
||||||
|
showWikiConvert = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
showLeaveShare = true;
|
showLeaveShare = true;
|
||||||
}
|
}
|
||||||
@ -194,6 +208,9 @@ class WikiCardItem extends Component {
|
|||||||
showDelete = true;
|
showDelete = true;
|
||||||
showRename = true;
|
showRename = true;
|
||||||
showPublish = true;
|
showPublish = true;
|
||||||
|
if (isOldVersion) {
|
||||||
|
showWikiConvert = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
showLeaveShare = true;
|
showLeaveShare = true;
|
||||||
}
|
}
|
||||||
@ -239,6 +256,9 @@ class WikiCardItem extends Component {
|
|||||||
{showDelete &&
|
{showDelete &&
|
||||||
<DropdownItem onClick={this.onDeleteToggle}>{gettext('Delete')}</DropdownItem>
|
<DropdownItem onClick={this.onDeleteToggle}>{gettext('Delete')}</DropdownItem>
|
||||||
}
|
}
|
||||||
|
{showWikiConvert &&
|
||||||
|
<DropdownItem onClick={this.onConvertToggle}>{gettext('Convert to new Wiki')}</DropdownItem>
|
||||||
|
}
|
||||||
{showLeaveShare &&
|
{showLeaveShare &&
|
||||||
<DropdownItem onClick={this.onDeleteToggle}>{gettext('Leave')}</DropdownItem>
|
<DropdownItem onClick={this.onDeleteToggle}>{gettext('Leave')}</DropdownItem>
|
||||||
}
|
}
|
||||||
@ -327,6 +347,15 @@ class WikiCardItem extends Component {
|
|||||||
/>
|
/>
|
||||||
</ModalPortal>
|
</ModalPortal>
|
||||||
}
|
}
|
||||||
|
{this.state.isShowConvertDialog &&
|
||||||
|
<ModalPortal>
|
||||||
|
<ConvertWikiDialog
|
||||||
|
toggleCancel={this.onConvertToggle}
|
||||||
|
convertWiki={this.props.convertWiki}
|
||||||
|
wiki={this.props.wiki}
|
||||||
|
/>
|
||||||
|
</ModalPortal>
|
||||||
|
}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ const propTypes = {
|
|||||||
renameWiki: PropTypes.func.isRequired,
|
renameWiki: PropTypes.func.isRequired,
|
||||||
leaveSharedWiki: PropTypes.func.isRequired,
|
leaveSharedWiki: PropTypes.func.isRequired,
|
||||||
unshareGroupWiki: PropTypes.func.isRequired,
|
unshareGroupWiki: PropTypes.func.isRequired,
|
||||||
|
convertWiki: PropTypes.func.isRequired,
|
||||||
toggelAddWikiDialog: PropTypes.func,
|
toggelAddWikiDialog: PropTypes.func,
|
||||||
sidePanelRate: PropTypes.number,
|
sidePanelRate: PropTypes.number,
|
||||||
isSidePanelFolded: PropTypes.bool,
|
isSidePanelFolded: PropTypes.bool,
|
||||||
@ -132,6 +133,7 @@ class WikiCardView extends Component {
|
|||||||
deleteWiki={this.props.deleteWiki}
|
deleteWiki={this.props.deleteWiki}
|
||||||
renameWiki={this.props.renameWiki}
|
renameWiki={this.props.renameWiki}
|
||||||
unshareGroupWiki={this.props.unshareGroupWiki}
|
unshareGroupWiki={this.props.unshareGroupWiki}
|
||||||
|
convertWiki={this.props.convertWiki}
|
||||||
isSidePanelFolded={isSidePanelFolded}
|
isSidePanelFolded={isSidePanelFolded}
|
||||||
sidePanelRate={sidePanelRate}
|
sidePanelRate={sidePanelRate}
|
||||||
wikis={v1Wikis}
|
wikis={v1Wikis}
|
||||||
|
@ -249,6 +249,17 @@ class Wikis extends Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
convertWiki = (wiki, wikiName, departmentID) => {
|
||||||
|
wikiAPI.convertWiki(wiki.id, wikiName, departmentID).then((res) => {
|
||||||
|
this.getWikis();
|
||||||
|
}).catch((error) => {
|
||||||
|
if (error.response) {
|
||||||
|
let errorMsg = error.response.data.error_msg;
|
||||||
|
toaster.danger(errorMsg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
toggleDropdownMenu = (e) => {
|
toggleDropdownMenu = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -299,6 +310,7 @@ class Wikis extends Component {
|
|||||||
leaveSharedWiki={this.leaveSharedWiki}
|
leaveSharedWiki={this.leaveSharedWiki}
|
||||||
unshareGroupWiki={this.unshareGroupWiki}
|
unshareGroupWiki={this.unshareGroupWiki}
|
||||||
renameWiki={this.renameWiki}
|
renameWiki={this.renameWiki}
|
||||||
|
convertWiki={this.convertWiki}
|
||||||
toggelAddWikiDialog={this.toggelAddWikiDialog}
|
toggelAddWikiDialog={this.toggelAddWikiDialog}
|
||||||
sidePanelRate={this.props.sidePanelRate}
|
sidePanelRate={this.props.sidePanelRate}
|
||||||
isSidePanelFolded={this.props.isSidePanelFolded}
|
isSidePanelFolded={this.props.isSidePanelFolded}
|
||||||
|
@ -280,6 +280,16 @@ class WikiAPI {
|
|||||||
return this.req.delete(url);
|
return this.req.delete(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
convertWiki(oldWikiId, wikiName, owner) {
|
||||||
|
const url = this.server + '/api/v2.1/convert-wiki/';
|
||||||
|
let form = new FormData();
|
||||||
|
form.append('old_wiki_id', oldWikiId);
|
||||||
|
form.append('name', wikiName);
|
||||||
|
if (owner) {
|
||||||
|
form.append('owner', owner);
|
||||||
|
}
|
||||||
|
return this._sendPostRequest(url, form);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let wikiAPI = new WikiAPI();
|
let wikiAPI = new WikiAPI();
|
||||||
|
@ -25,11 +25,12 @@ from seahub.api2.utils import api_error, is_wiki_repo
|
|||||||
from seahub.api2.endpoints.utils import wiki_search
|
from seahub.api2.endpoints.utils import wiki_search
|
||||||
from seahub.utils.db_api import SeafileDB
|
from seahub.utils.db_api import SeafileDB
|
||||||
from seahub.wiki2.models import Wiki2 as Wiki
|
from seahub.wiki2.models import Wiki2 as Wiki
|
||||||
|
from seahub.wiki.models import Wiki as OldWiki
|
||||||
from seahub.wiki2.models import WikiPageTrash, Wiki2Publish
|
from seahub.wiki2.models import WikiPageTrash, Wiki2Publish
|
||||||
from seahub.wiki2.utils import is_valid_wiki_name, get_wiki_config, WIKI_PAGES_DIR, is_group_wiki, \
|
from seahub.wiki2.utils import is_valid_wiki_name, get_wiki_config, WIKI_PAGES_DIR, is_group_wiki, \
|
||||||
check_wiki_admin_permission, check_wiki_permission, get_all_wiki_ids, get_and_gen_page_nav_by_id, \
|
check_wiki_admin_permission, check_wiki_permission, get_all_wiki_ids, get_and_gen_page_nav_by_id, \
|
||||||
get_current_level_page_ids, save_wiki_config, gen_unique_id, gen_new_page_nav_by_id, pop_nav, \
|
get_current_level_page_ids, save_wiki_config, gen_unique_id, gen_new_page_nav_by_id, pop_nav, \
|
||||||
delete_page, move_nav, revert_nav, get_sub_ids_by_page_id, get_parent_id_stack
|
delete_page, move_nav, revert_nav, get_sub_ids_by_page_id, get_parent_id_stack, add_convert_wiki_task
|
||||||
|
|
||||||
from seahub.utils import is_org_context, get_user_repos, is_pro_version, is_valid_dirent_name, \
|
from seahub.utils import is_org_context, get_user_repos, is_pro_version, is_valid_dirent_name, \
|
||||||
get_no_duplicate_obj_name
|
get_no_duplicate_obj_name
|
||||||
@ -37,7 +38,7 @@ from seahub.utils import is_org_context, get_user_repos, is_pro_version, is_vali
|
|||||||
from seahub.views import check_folder_permission
|
from seahub.views import check_folder_permission
|
||||||
from seahub.base.templatetags.seahub_tags import email2nickname
|
from seahub.base.templatetags.seahub_tags import email2nickname
|
||||||
from seahub.utils.file_op import check_file_lock
|
from seahub.utils.file_op import check_file_lock
|
||||||
from seahub.utils.repo import get_repo_owner, is_valid_repo_id_format
|
from seahub.utils.repo import get_repo_owner, is_valid_repo_id_format, is_group_repo_staff, is_repo_owner
|
||||||
from seahub.seadoc.utils import get_seadoc_file_uuid, gen_seadoc_access_token, copy_sdoc_images_with_sdoc_uuid
|
from seahub.seadoc.utils import get_seadoc_file_uuid, gen_seadoc_access_token, copy_sdoc_images_with_sdoc_uuid
|
||||||
from seahub.settings import ENABLE_STORAGE_CLASSES, STORAGE_CLASS_MAPPING_POLICY, \
|
from seahub.settings import ENABLE_STORAGE_CLASSES, STORAGE_CLASS_MAPPING_POLICY, \
|
||||||
ENCRYPTED_LIBRARY_VERSION
|
ENCRYPTED_LIBRARY_VERSION
|
||||||
@ -51,6 +52,7 @@ from seahub.group.utils import group_id_to_name, is_group_admin
|
|||||||
from seahub.utils.rpc import SeafileAPI
|
from seahub.utils.rpc import SeafileAPI
|
||||||
from seahub.constants import PERMISSION_READ_WRITE
|
from seahub.constants import PERMISSION_READ_WRITE
|
||||||
from seaserv import ccnet_api
|
from seaserv import ccnet_api
|
||||||
|
from seahub.share.utils import is_repo_admin
|
||||||
|
|
||||||
HTTP_520_OPERATION_FAILED = 520
|
HTTP_520_OPERATION_FAILED = 520
|
||||||
|
|
||||||
@ -1311,3 +1313,136 @@ class WikiSearch(APIView):
|
|||||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||||
|
|
||||||
return Response(resp_json, resp.status_code)
|
return Response(resp_json, resp.status_code)
|
||||||
|
|
||||||
|
|
||||||
|
class WikiConvertView(APIView):
|
||||||
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
|
permission_classes = (IsAuthenticated,)
|
||||||
|
throttle_classes = (UserRateThrottle,)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
old_wiki_id = request.data.get('old_wiki_id', None)
|
||||||
|
if not old_wiki_id:
|
||||||
|
error_msg = 'old_wiki_id invalid.'
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
|
||||||
|
try:
|
||||||
|
wiki = OldWiki.objects.get(id=old_wiki_id)
|
||||||
|
except OldWiki.DoesNotExist:
|
||||||
|
error_msg = 'Old Wiki not found.'
|
||||||
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
|
|
||||||
|
username = request.user.username
|
||||||
|
old_repo_id = wiki.repo_id
|
||||||
|
|
||||||
|
# check old wiki permission
|
||||||
|
is_owner = is_repo_owner(request, old_repo_id, username)
|
||||||
|
if not is_owner:
|
||||||
|
repo_admin = is_repo_admin(username, old_repo_id)
|
||||||
|
if not repo_admin:
|
||||||
|
is_group_repo_admin = is_group_repo_staff(request, old_repo_id, username)
|
||||||
|
|
||||||
|
if not is_group_repo_admin:
|
||||||
|
error_msg = _('Permission denied.')
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||||
|
|
||||||
|
if wiki.username != username:
|
||||||
|
error_msg = 'Permission denied.'
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||||
|
|
||||||
|
if not request.user.permissions.can_add_repo():
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, 'You do not have permission to create library.')
|
||||||
|
|
||||||
|
wiki_name = request.data.get("name", None)
|
||||||
|
if not wiki_name:
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, 'wiki name is required.')
|
||||||
|
|
||||||
|
if not is_valid_wiki_name(wiki_name):
|
||||||
|
msg = _('Name can only contain letters, numbers, blank, hyphen or underscore.')
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, msg)
|
||||||
|
|
||||||
|
old_repo_id = wiki.repo_id
|
||||||
|
repo = seafile_api.get_repo(old_repo_id)
|
||||||
|
if not repo:
|
||||||
|
error_msg = 'Library %s not found.' % old_repo_id
|
||||||
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
|
|
||||||
|
wiki_owner = request.data.get('owner', 'me')
|
||||||
|
is_group_owner = False
|
||||||
|
group_id = ''
|
||||||
|
if wiki_owner == 'me':
|
||||||
|
wiki_owner = request.user.username
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
group_id = int(wiki_owner)
|
||||||
|
wiki_owner = "%s@seafile_group" % group_id
|
||||||
|
except:
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, 'wiki_owner invalid')
|
||||||
|
is_group_owner = True
|
||||||
|
|
||||||
|
org_id = -1
|
||||||
|
if is_org_context(request):
|
||||||
|
org_id = request.user.org.org_id
|
||||||
|
|
||||||
|
permission = PERMISSION_READ_WRITE
|
||||||
|
if is_group_owner:
|
||||||
|
group_id = int(group_id)
|
||||||
|
# only group admin can create wiki
|
||||||
|
if not is_group_admin(group_id, request.user.username):
|
||||||
|
error_msg = 'Permission denied.'
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||||
|
|
||||||
|
group_quota = seafile_api.get_group_quota(group_id)
|
||||||
|
group_quota = int(group_quota)
|
||||||
|
if group_quota <= 0 and group_quota != -2:
|
||||||
|
error_msg = 'No group quota.'
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||||
|
|
||||||
|
# create group owned repo
|
||||||
|
group_id = int(group_id)
|
||||||
|
password = None
|
||||||
|
if is_pro_version() and ENABLE_STORAGE_CLASSES:
|
||||||
|
|
||||||
|
if STORAGE_CLASS_MAPPING_POLICY in ('USER_SELECT', 'ROLE_BASED'):
|
||||||
|
storage_id = None
|
||||||
|
repo_id = seafile_api.add_group_owned_repo(group_id,
|
||||||
|
wiki_name,
|
||||||
|
permission,
|
||||||
|
password,
|
||||||
|
enc_version=ENCRYPTED_LIBRARY_VERSION,
|
||||||
|
storage_id=storage_id)
|
||||||
|
else:
|
||||||
|
# STORAGE_CLASS_MAPPING_POLICY == 'REPO_ID_MAPPING'
|
||||||
|
repo_id = SeafileAPI.add_group_owned_repo(
|
||||||
|
group_id, wiki_name, password, permission, org_id=org_id)
|
||||||
|
else:
|
||||||
|
repo_id = SeafileAPI.add_group_owned_repo(
|
||||||
|
group_id, wiki_name, password, permission, org_id=org_id)
|
||||||
|
else:
|
||||||
|
if org_id and org_id > 0:
|
||||||
|
repo_id = seafile_api.create_org_repo(wiki_name, '', wiki_owner, org_id)
|
||||||
|
else:
|
||||||
|
repo_id = seafile_api.create_repo(wiki_name, '', wiki_owner)
|
||||||
|
|
||||||
|
try:
|
||||||
|
seafile_db_api = SeafileDB()
|
||||||
|
seafile_db_api.set_repo_type(repo_id, 'wiki')
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
msg = 'Internal Server Error'
|
||||||
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, msg)
|
||||||
|
|
||||||
|
params = {
|
||||||
|
'old_repo_id': old_repo_id,
|
||||||
|
'new_repo_id': repo_id,
|
||||||
|
'username': request.user.username,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
task_id = add_convert_wiki_task(params=params)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||||
|
|
||||||
|
return Response({"task_id": task_id})
|
||||||
|
|
||||||
|
@ -210,7 +210,7 @@ from seahub.ocm.settings import OCM_ENDPOINT
|
|||||||
from seahub.wiki2.views import wiki_view, wiki_publish_view
|
from seahub.wiki2.views import wiki_view, wiki_publish_view
|
||||||
from seahub.api2.endpoints.wiki2 import Wikis2View, Wiki2View, Wiki2ConfigView, Wiki2PagesView, Wiki2PageView, \
|
from seahub.api2.endpoints.wiki2 import Wikis2View, Wiki2View, Wiki2ConfigView, Wiki2PagesView, Wiki2PageView, \
|
||||||
Wiki2DuplicatePageView, WikiPageTrashView, Wiki2PublishView, Wiki2PublishConfigView, Wiki2PublishPageView, \
|
Wiki2DuplicatePageView, WikiPageTrashView, Wiki2PublishView, Wiki2PublishConfigView, Wiki2PublishPageView, \
|
||||||
WikiSearch
|
WikiSearch, WikiConvertView
|
||||||
from seahub.api2.endpoints.subscription import SubscriptionView, SubscriptionPlansView, SubscriptionLogsView
|
from seahub.api2.endpoints.subscription import SubscriptionView, SubscriptionPlansView, SubscriptionLogsView
|
||||||
from seahub.api2.endpoints.metadata_manage import MetadataRecords, MetadataManage, MetadataColumns, MetadataRecordInfo, \
|
from seahub.api2.endpoints.metadata_manage import MetadataRecords, MetadataManage, MetadataColumns, MetadataRecordInfo, \
|
||||||
MetadataViews, MetadataViewsMoveView, MetadataViewsDetailView, MetadataViewsDuplicateView, FacesRecords, \
|
MetadataViews, MetadataViewsMoveView, MetadataViewsDetailView, MetadataViewsDuplicateView, FacesRecords, \
|
||||||
@ -558,7 +558,7 @@ urlpatterns = [
|
|||||||
re_path(r'^api/v2.1/wiki2/(?P<wiki_id>[-0-9a-f]{36})/trash/', WikiPageTrashView.as_view(), name='api-v2.1-wiki2-trash'),
|
re_path(r'^api/v2.1/wiki2/(?P<wiki_id>[-0-9a-f]{36})/trash/', WikiPageTrashView.as_view(), name='api-v2.1-wiki2-trash'),
|
||||||
re_path(r'^api/v2.1/wiki2/(?P<wiki_id>[-0-9a-f]{36})/publish/$', Wiki2PublishView.as_view(), name='api-v2.1-wiki2-publish'),
|
re_path(r'^api/v2.1/wiki2/(?P<wiki_id>[-0-9a-f]{36})/publish/$', Wiki2PublishView.as_view(), name='api-v2.1-wiki2-publish'),
|
||||||
re_path(r'^api/v2.1/wiki2/search/$', WikiSearch.as_view(), name='api-v2.1-wiki2-search'),
|
re_path(r'^api/v2.1/wiki2/search/$', WikiSearch.as_view(), name='api-v2.1-wiki2-search'),
|
||||||
|
re_path(r'^api/v2.1/convert-wiki/$', WikiConvertView.as_view(), name='api-v2.1-wiki-convert'),
|
||||||
## user::drafts
|
## user::drafts
|
||||||
re_path(r'^api/v2.1/drafts/$', DraftsView.as_view(), name='api-v2.1-drafts'),
|
re_path(r'^api/v2.1/drafts/$', DraftsView.as_view(), name='api-v2.1-drafts'),
|
||||||
re_path(r'^api/v2.1/drafts/(?P<pk>\d+)/$', DraftView.as_view(), name='api-v2.1-draft'),
|
re_path(r'^api/v2.1/drafts/(?P<pk>\d+)/$', DraftView.as_view(), name='api-v2.1-draft'),
|
||||||
|
@ -8,12 +8,16 @@ import json
|
|||||||
import requests
|
import requests
|
||||||
import posixpath
|
import posixpath
|
||||||
import random
|
import random
|
||||||
|
import jwt
|
||||||
|
import time
|
||||||
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
from seaserv import seafile_api
|
from seaserv import seafile_api
|
||||||
from seahub.constants import PERMISSION_READ_WRITE
|
from seahub.constants import PERMISSION_READ_WRITE
|
||||||
from seahub.utils import gen_inner_file_get_url, gen_file_upload_url
|
from seahub.utils import gen_inner_file_get_url, gen_file_upload_url
|
||||||
from seahub.group.utils import is_group_admin, is_group_member
|
from seahub.group.utils import is_group_admin, is_group_member
|
||||||
from seahub.wiki2.models import WikiPageTrash
|
from seahub.wiki2.models import WikiPageTrash
|
||||||
|
from seahub.settings import SECRET_KEY, SEAFEVENTS_SERVER_URL
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -318,3 +322,12 @@ def get_parent_id_stack(navigation, page_id):
|
|||||||
return_parent_page_id(navigation, page_id, id_list)
|
return_parent_page_id(navigation, page_id, id_list)
|
||||||
|
|
||||||
return id_list
|
return id_list
|
||||||
|
|
||||||
|
|
||||||
|
def add_convert_wiki_task(params):
|
||||||
|
payload = {'exp': int(time.time()) + 300, }
|
||||||
|
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
|
||||||
|
headers = {"Authorization": "Token %s" % token}
|
||||||
|
url = urljoin(SEAFEVENTS_SERVER_URL, '/add-convert-wiki-task')
|
||||||
|
resp = requests.get(url, params=params, headers=headers)
|
||||||
|
return json.loads(resp.content)['task_id']
|
||||||
|
Loading…
Reference in New Issue
Block a user