diff --git a/frontend/src/pages/wiki2/side-panel.js b/frontend/src/pages/wiki2/side-panel.js index b724139c8a..db1fdad5ae 100644 --- a/frontend/src/pages/wiki2/side-panel.js +++ b/frontend/src/pages/wiki2/side-panel.js @@ -7,8 +7,8 @@ import toaster from '../../components/toast'; import Loading from '../../components/loading'; import WikiNav from './wiki-nav/index'; import PageUtils from './wiki-nav/page-utils'; -import { generateUniqueId, isObjectNotEmpty } from './utils'; import Page from './models/page'; +import { isObjectNotEmpty } from './utils'; import wikiAPI from '../../utils/wiki-api'; import { Utils } from '../../utils/utils'; import WikiExternalOperations from './wiki-external-operations'; @@ -36,13 +36,16 @@ class SidePanel extends Component { confirmDeletePage = (pageId) => { const config = deepCopy(this.props.config); - const { pages, navigation } = config; + const { pages } = config; const index = PageUtils.getPageIndexById(pageId, pages); config.pages.splice(index, 1); - PageUtils.deletePage(navigation, pageId); - this.props.saveWikiConfig(config); - // TODO: delete a page, then delete all subpages - wikiAPI.deleteWiki2Page(wikiId, pageId); + // TODO: To delete a page, do you need to delete all subpages at once(update PageUtils delete a page) + wikiAPI.deleteWiki2Page(wikiId, pageId).then((res) => { + this.props.updateWikiConfig(config); + }).catch((error) => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); if (config.pages.length > 0) { this.props.setCurrentPage(config.pages[0].id); } else { @@ -50,19 +53,13 @@ class SidePanel extends Component { } }; - addPageInside = async ({ parentPageId, name, icon, path, docUuid, successCallback, errorCallback }) => { - const { config } = this.props; - const navigation = config.navigation; - const pageId = generateUniqueId(navigation); - const newPage = new Page({ id: pageId, name, icon, path, docUuid }); + addPageInside = async ({ parentPageId, page_id, name, icon, path, docUuid, successCallback, errorCallback}) => { + const newPage = new Page({ id: page_id, name, icon, path, docUuid }); this.addPage(newPage, parentPageId, successCallback, errorCallback); }; - onAddNewPage = async ({ name, icon, path, docUuid, successCallback, errorCallback, jumpToNewPage = true }) => { - const { config } = this.props; - const navigation = config.navigation; - const pageId = generateUniqueId(navigation); - const newPage = new Page({ id: pageId, name, icon, path, docUuid }); + onAddNewPage = async ({ name, icon, path, page_id, docUuid, successCallback, errorCallback, jumpToNewPage = true }) => { + const newPage = new Page({ id: page_id, name, icon, path, docUuid }); this.addPage(newPage, '', successCallback, errorCallback, jumpToNewPage); }; @@ -86,11 +83,10 @@ class SidePanel extends Component { config.pages.push(page); PageUtils.addPage(navigation, pageId, parentId); config.navigation = navigation; - const onSuccess = () => { - jumpToNewPage && this.props.setCurrentPage(pageId, successCallback); - successCallback(); - }; - this.props.saveWikiConfig(config, onSuccess, errorCallback); + JSON.stringify(config); + this.props.updateWikiConfig(config); + jumpToNewPage && this.props.setCurrentPage(pageId, successCallback); + successCallback && successCallback(); }; movePage = ({ moved_page_id, target_page_id, move_position }) => { @@ -98,7 +94,8 @@ class SidePanel extends Component { let { navigation } = config; PageUtils.movePage(navigation, moved_page_id, target_page_id, move_position); config.navigation = navigation; - this.props.saveWikiConfig(config); + JSON.stringify(config); + this.props.updateWikiConfig(config); }; renderWikiNav = () => { @@ -115,6 +112,7 @@ class SidePanel extends Component { onUpdatePage={onUpdatePage} setCurrentPage={this.props.setCurrentPage} onMovePage={this.movePage} + updateWikiConfig={this.props.updateWikiConfig} onAddNewPage={this.onAddNewPage} duplicatePage={this.duplicatePage} currentPageId={this.props.currentPageId} @@ -129,8 +127,9 @@ class SidePanel extends Component { handleAddNewPage = (jumpToNewPage = true, pageName = 'Untitled') => { const voidFn = () => void 0; wikiAPI.createWiki2Page(wikiId, pageName).then(res => { - const { obj_name, parent_dir, doc_uuid, page_name } = res.data; + const { page_id, obj_name, doc_uuid, parent_dir, page_name } = res.data.file_info; this.onAddNewPage({ + page_id: page_id, name: page_name, icon: '', path: parent_dir === '/' ? `/${obj_name}` : `${parent_dir}/${obj_name}`, @@ -142,7 +141,6 @@ class SidePanel extends Component { }).catch((error) => { let errMessage = Utils.getErrorMsg(error); toaster.danger(errMessage); - this.onError(); }); }; diff --git a/frontend/src/pages/wiki2/wiki-nav/add-new-page-dialog.js b/frontend/src/pages/wiki2/wiki-nav/add-new-page-dialog.js index 130942ddd0..c2d71fcc89 100644 --- a/frontend/src/pages/wiki2/wiki-nav/add-new-page-dialog.js +++ b/frontend/src/pages/wiki2/wiki-nav/add-new-page-dialog.js @@ -13,6 +13,7 @@ const propTypes = { title: PropTypes.node, toggle: PropTypes.func.isRequired, onAddNewPage: PropTypes.func, + currentPageId: PropTypes.string, }; @@ -70,9 +71,10 @@ class AddNewPageDialog extends React.Component { }; createPage = (pageName) => { - wikiAPI.createWiki2Page(wikiId, pageName).then(res => { - const { obj_name, parent_dir, doc_uuid } = res.data; + wikiAPI.createWiki2Page(wikiId, pageName, this.props.currentPageId).then(res => { + const { page_id, obj_name, doc_uuid, parent_dir } = res.data.file_info; this.props.onAddNewPage({ + page_id: page_id, name: pageName, icon: '', path: parent_dir === '/' ? `/${obj_name}` : `${parent_dir}/${obj_name}`, diff --git a/frontend/src/pages/wiki2/wiki-nav/page-utils.js b/frontend/src/pages/wiki2/wiki-nav/page-utils.js index 898af9237f..52d7596b96 100644 --- a/frontend/src/pages/wiki2/wiki-nav/page-utils.js +++ b/frontend/src/pages/wiki2/wiki-nav/page-utils.js @@ -103,11 +103,16 @@ export default class PageUtils { } function _insertPageRecursion(item, page_id, target_page_id, target_id, move_position) { if (item.id === target_id) { - let insertIndex = target_page_id ? item.children.findIndex(item => item.id === target_page_id) : -1; - if (move_position === 'move_below') { - insertIndex++; + if (item.children) { + let insertIndex = target_page_id ? item.children.findIndex(item => item.id === target_page_id) : -1; + if (move_position === 'move_below') { + insertIndex++; + } + item.children.splice(insertIndex, 0, movedPage); + } else { + item.children = []; + item.children.push(movedPage); } - item.children.splice(insertIndex, 0, movedPage); return; } item.children && item.children.forEach(item => { diff --git a/frontend/src/pages/wiki2/wiki-nav/pages/dragged-page-item.js b/frontend/src/pages/wiki2/wiki-nav/pages/dragged-page-item.js index 799c4576b6..c3e3d7214d 100644 --- a/frontend/src/pages/wiki2/wiki-nav/pages/dragged-page-item.js +++ b/frontend/src/pages/wiki2/wiki-nav/pages/dragged-page-item.js @@ -1,5 +1,9 @@ import { DragSource, DropTarget } from 'react-dnd'; import PageItem from './page-item'; +import wikiAPI from '../../../../utils/wiki-api'; +import { wikiId, gettext } from '../../../../utils/constants'; +import { Utils } from '../../../../utils/utils'; +import toaster from '../../../../components/toast'; const dragSource = { beginDrag: props => { @@ -34,10 +38,19 @@ const dropTarget = { if (draggedPageId !== targetPageId) { const sourceIndex = dragSource.idx; const move_position = sourceIndex > targetIndex ? 'move_above' : 'move_below'; - props.onMovePage({ - moved_page_id: draggedPageId, - target_page_id: targetPageId, - move_position, + wikiAPI.moveWiki2Page(wikiId, draggedPageId, targetPageId, move_position).then(res => { + props.onMovePage({ + moved_page_id: draggedPageId, + target_page_id: targetPageId, + move_position, + }); + }).catch((error) => { + if (error.response && error.response.status === 400 && error.response.data.error_msg === 'Internal Server Error') { + toaster.danger(gettext('Cannot move parent page to child page')); + } else { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + } }); } return; diff --git a/frontend/src/pages/wiki2/wiki-nav/pages/page-item.js b/frontend/src/pages/wiki2/wiki-nav/pages/page-item.js index 8f7d4cf3a5..3abe8b4ee6 100644 --- a/frontend/src/pages/wiki2/wiki-nav/pages/page-item.js +++ b/frontend/src/pages/wiki2/wiki-nav/pages/page-item.js @@ -143,6 +143,7 @@ class PageItem extends Component { onUpdatePage={this.props.onUpdatePage} onDeletePage={this.props.onDeletePage} onMovePage={this.props.onMovePage} + updateWikiConfig={this.props.updateWikiConfig} pages={pages} pathStr={pathStr + '-' + page.id} currentPageId={this.props.currentPageId} @@ -252,6 +253,7 @@ class PageItem extends Component { {this.state.isShowInsertPage && @@ -300,6 +302,7 @@ PageItem.propTypes = { addPageInside: PropTypes.func, getFoldState: PropTypes.func, toggleExpand: PropTypes.func, + updateWikiConfig: PropTypes.func, }; export default PageItem; diff --git a/frontend/src/pages/wiki2/wiki-nav/wiki-nav.js b/frontend/src/pages/wiki2/wiki-nav/wiki-nav.js index c8d7ea7211..fc2e6db24f 100644 --- a/frontend/src/pages/wiki2/wiki-nav/wiki-nav.js +++ b/frontend/src/pages/wiki2/wiki-nav/wiki-nav.js @@ -21,6 +21,7 @@ class WikiNav extends Component { duplicatePage: PropTypes.func, currentPageId: PropTypes.string, addPageInside: PropTypes.func, + updateWikiConfig: PropTypes.func.isRequired, }; constructor(props) { @@ -70,6 +71,7 @@ class WikiNav extends Component { onUpdatePage={this.props.onUpdatePage} onDeletePage={this.props.onDeletePage} onMovePage={this.props.onMovePage} + updateWikiConfig={this.props.updateWikiConfig} pathStr={page.id} currentPageId={this.props.currentPageId} addPageInside={this.props.addPageInside} diff --git a/frontend/src/utils/wiki-api.js b/frontend/src/utils/wiki-api.js index 3ed1222d92..b2210c996f 100644 --- a/frontend/src/utils/wiki-api.js +++ b/frontend/src/utils/wiki-api.js @@ -191,6 +191,16 @@ class WikiAPI { return this.req.delete(url); } + moveWiki2Page(wikiId, moved_id, target_id, move_position) { + const url = this.server + '/api/v2.1/wiki2/' + wikiId + '/pages/'; + let params = { + 'moved_id': moved_id, + 'target_id': target_id, + 'move_position': move_position + }; + return this.req.put(url, params); + } + deleteWiki2Folder(wikiId, folderId) { const url = this.server + '/api/v2.1/wiki2/' + wikiId + '/folder/' + folderId + '/'; return this.req.delete(url); diff --git a/seahub/api2/endpoints/wiki2.py b/seahub/api2/endpoints/wiki2.py index d3d6ba2021..9e8a67108f 100644 --- a/seahub/api2/endpoints/wiki2.py +++ b/seahub/api2/endpoints/wiki2.py @@ -26,8 +26,10 @@ from seahub.utils.db_api import SeafileDB from seahub.wiki2.models import Wiki2 as Wiki from seahub.wiki2.utils import is_valid_wiki_name, can_edit_wiki, get_wiki_dirs_by_path, \ get_wiki_config, WIKI_PAGES_DIR, WIKI_CONFIG_PATH, WIKI_CONFIG_FILE_NAME, is_group_wiki, \ - check_wiki_admin_permission, check_wiki_permission, get_page_ids_in_folder, get_all_wiki_ids, \ - get_and_gen_page_nav_by_id, get_current_level_page_ids, save_wiki_config + 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, \ + delete_page, move_nav + from seahub.utils import is_org_context, get_user_repos, gen_inner_file_get_url, gen_file_upload_url, \ normalize_dir_path, is_pro_version, check_filename_with_rename, is_valid_dirent_name, get_no_duplicate_obj_name from seahub.views import check_folder_permission @@ -415,14 +417,12 @@ class Wiki2PagesView(APIView): current_id = request.data.get('current_id', None) wiki_config = get_wiki_config(repo_id, request.user.username) - navigation = wiki_config.get('navigation', []) if not current_id: page_ids = {element.get('id') for element in navigation if element.get('type') != 'folder'} else: page_ids = [] get_current_level_page_ids(navigation, current_id, page_ids) - pages = wiki_config.get('pages', []) exist_page_names = [page.get('name') for page in pages if page.get('id') in page_ids] page_name = get_no_duplicate_obj_name(page_name, exist_page_names) @@ -432,7 +432,6 @@ class Wiki2PagesView(APIView): parent_dir = os.path.join(WIKI_PAGES_DIR, str(sdoc_uuid)) path = os.path.join(parent_dir, new_file_name) seafile_api.mkdir_with_parents(repo_id, '/', parent_dir.strip('/'), request.user.username) - # create new empty file if not is_valid_dirent_name(new_file_name): return api_error(status.HTTP_400_BAD_REQUEST, 'name invalid.') @@ -456,12 +455,85 @@ class Wiki2PagesView(APIView): try: FileUUIDMap.objects.create_fileuuidmap_by_uuid(sdoc_uuid, repo_id, parent_dir, filename, is_dir=False) + # update wiki_config + id_set = get_all_wiki_ids(navigation) + new_page_id = gen_unique_id(id_set) + file_info['page_id'] = new_page_id + gen_new_page_nav_by_id(navigation, new_page_id, current_id) + new_page = { + 'id': new_page_id, + 'name': page_name, + 'path': path, + 'icon': '', + 'docUuid': str(sdoc_uuid) + } + pages.append(new_page) + + if len(wiki_config) == 0: + wiki_config['version'] = 1 + + wiki_config['navigation'] = navigation + wiki_config['pages'] = pages + wiki_config = json.dumps(wiki_config) + save_wiki_config(wiki, request.user.username, wiki_config) except Exception as e: logger.error(e) error_msg = 'Internal Server Error' return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) - return Response(file_info) + wiki = wiki.to_dict() + wiki['wiki_config'] = wiki_config + + return Response({'file_info': file_info}) + + def put(self, request, wiki_id): + 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) + + username = request.user.username + if not check_wiki_permission(wiki, username): + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + repo_id = wiki.repo_id + repo = seafile_api.get_repo(repo_id) + if not repo: + error_msg = 'Library %s not found.' % repo_id + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + wiki_config = get_wiki_config(repo_id, username) + navigation = wiki_config.get('navigation', []) + + id_set = get_all_wiki_ids(navigation) + target_page_id = request.data.get('target_id', '') + moved_page_id = request.data.get('moved_id', '') + if (target_page_id not in id_set) or (moved_page_id not in id_set): + error_msg = 'Page not found' + logger.error(error_msg) + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + moved_nav = pop_nav(navigation, moved_page_id) + # Move one into one's own subset + judge_navs = get_all_wiki_ids([moved_nav]) + if target_page_id in judge_navs: + error_msg = 'Internal Server Error' + logger.error(error_msg) + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + move_nav(navigation, target_page_id, moved_nav) + wiki_config['navigation'] = navigation + wiki_config = json.dumps(wiki_config) + + try: + save_wiki_config(wiki, username, wiki_config) + except Exception as e: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + return Response({'success': True}) class Wiki2PageView(APIView): @@ -576,29 +648,63 @@ class Wiki2PageView(APIView): error_msg = _("File is locked") return api_error(status.HTTP_403_FORBIDDEN, error_msg) - sdoc_dir_path = os.path.dirname(path) - parent_dir = os.path.dirname(sdoc_dir_path) - dir_name = os.path.basename(sdoc_dir_path) + # check page + navigation = wiki_config.get('navigation', []) + id_set = get_all_wiki_ids(navigation) + if page_id not in id_set: + error_msg = 'Page not found' + logger.error(error_msg) + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + # update navigation and page + pop_nav(navigation, page_id) + id_set = get_all_wiki_ids(navigation) + new_pages, old_pages = delete_page(pages, id_set) + for old_page in old_pages: + sdoc_dir_path = os.path.dirname(old_page['path']) + parent_dir = os.path.dirname(sdoc_dir_path) + dir_name = os.path.basename(sdoc_dir_path) + old_page['sdoc_dir_path'] = sdoc_dir_path + old_page['parent_dir'] = parent_dir + old_page['dir_name'] = dir_name # delete the folder where the sdoc is located try: - seafile_api.del_file(repo_id, parent_dir, json.dumps([dir_name]), username) + for old_page in old_pages: + seafile_api.del_file(repo_id, old_page['parent_dir'], json.dumps([old_page['dir_name']]), username) except SearpcError as e: logger.error(e) error_msg = 'Internal Server Error' return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) try: # rm sdoc fileuuid - file_name = os.path.basename(path) - file_uuid = get_seadoc_file_uuid(repo, path) - FileComment.objects.filter(uuid=file_uuid).delete() - FileUUIDMap.objects.delete_fileuuidmap_by_path(repo_id, sdoc_dir_path, file_name, is_dir=False) - SeadocHistoryName.objects.filter(doc_uuid=file_uuid).delete() - SeadocDraft.objects.filter(doc_uuid=file_uuid).delete() - SeadocCommentReply.objects.filter(doc_uuid=file_uuid).delete() + for old_page in old_pages: + file_name = os.path.basename(old_page['path']) + file_uuid = get_seadoc_file_uuid(repo, old_page['path']) + FileComment.objects.filter(uuid=file_uuid).delete() + FileUUIDMap.objects.delete_fileuuidmap_by_path(repo_id, old_page['sdoc_dir_path'], file_name, is_dir=False) + SeadocHistoryName.objects.filter(doc_uuid=file_uuid).delete() + SeadocDraft.objects.filter(doc_uuid=file_uuid).delete() + SeadocCommentReply.objects.filter(doc_uuid=file_uuid).delete() except Exception as e: logger.error(e) + # update wiki_config + try: + wiki_config['navigation'] = navigation + wiki_config['pages'] = new_pages + # TODO: add trash. + if 'trash_pages' in wiki_config: + wiki_config['trash_pages'].extend(old_pages) + else: + wiki_config['trash_pages'] = old_pages + wiki_config = json.dumps(wiki_config) + save_wiki_config(wiki, request.user.username, wiki_config) + except Exception as e: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + return Response({'success': True}) @@ -646,7 +752,6 @@ class Wiki2DuplicatePageView(APIView): # old page to new page old_to_new = {} get_and_gen_page_nav_by_id(id_set, navigation, page_id, old_to_new) - # page_id not exist in wiki if not old_to_new: error_msg = 'page %s not found.' % page_id diff --git a/seahub/wiki2/utils.py b/seahub/wiki2/utils.py index 017a1aa9bb..fb76b3f35a 100644 --- a/seahub/wiki2/utils.py +++ b/seahub/wiki2/utils.py @@ -16,7 +16,6 @@ from seahub.group.utils import is_group_admin, is_group_member logger = logging.getLogger(__name__) - WIKI_PAGES_DIR = '/wiki-pages' WIKI_CONFIG_PATH = '_Internal/Wiki' WIKI_CONFIG_FILE_NAME = 'index.json' @@ -173,6 +172,24 @@ def get_and_gen_page_nav_by_id(id_set, navigation, page_id, old_to_new): get_and_gen_page_nav_by_id(id_set, new_navigation, page_id, old_to_new) +def gen_new_page_nav_by_id(navigation, page_id, current_id): + new_nav = { + 'id': page_id, + 'type': 'page', + } + if current_id: + for nav in navigation: + if nav.get('type') == 'page' and nav.get('id') == current_id: + sub_nav = nav.get('children', []) + sub_nav.append(new_nav) + nav['children'] = sub_nav + return + else: + gen_new_page_nav_by_id(nav.get('children', []), page_id, current_id) + else: + navigation.append(new_nav) + + def get_current_level_page_ids(navigation, page_id, ids=[]): for item in navigation: if item.get('id') == page_id: @@ -207,3 +224,39 @@ def save_wiki_config(wiki, username, wiki_config): resp = requests.post(upload_link, files=files, data=data) if not resp.ok: raise Exception(resp.text) + + +def delete_page(pages, id_set): + new_pages = [] + old_pages = [] + for page in pages: + if page['id'] in id_set: + new_pages.append(page) + else: + old_pages.append(page) + return new_pages, old_pages + + +def pop_nav(navigation, page_id): + for nav in navigation: + if nav['id'] == page_id: + navigation.remove(nav) + return nav + if 'children' in nav and nav['children']: + result = pop_nav(nav['children'], page_id) + if result: + return result + return None + + +def move_nav(navigation, target_id, moved_nav): + for nav in navigation: + if nav['id'] == target_id: + if 'children' in nav: + nav['children'].insert(0, moved_nav) + else: + nav['children'] = [moved_nav] + return + if 'children' in nav: + move_nav(nav['children'], target_id, moved_nav) +