diff --git a/frontend/src/pages/wiki2/css/wiki-nav.css b/frontend/src/pages/wiki2/css/wiki-nav.css index 35e7025a0e..7c7e2dedb4 100644 --- a/frontend/src/pages/wiki2/css/wiki-nav.css +++ b/frontend/src/pages/wiki2/css/wiki-nav.css @@ -132,7 +132,7 @@ background-color: #DFDFDD; } -.wiki-nav .wiki-page-item .sf3-font.sf3-font-enlarge, +.wiki-nav .wiki-page-item .wiki-add-page-btn .sf3-font.sf3-font-enlarge, .wiki-nav .wiki-page-item .more-wiki-page-operation .seafile-multicolor-icon-more-level { font-size: 14px; cursor: pointer; @@ -270,13 +270,13 @@ } .wiki-nav, -.wiki-nav .wiki-page-item .sf3-font.sf3-font-enlarge:hover, +.wiki-nav .wiki-page-item .wiki-add-page-btn .sf3-font.sf3-font-enlarge:hover, .wiki-nav .wiki-page-item .seafile-multicolor-icon-more-level:hover { color: #212529; } .wiki-nav .wiki2-trash .sf3-font, -.wiki-nav .wiki-page-item .sf3-font.sf3-font-enlarge, +.wiki-nav .wiki-page-item .wiki-add-page-btn .sf3-font.sf3-font-enlarge, .wiki-nav .wiki-page-item .seafile-multicolor-icon-more-level { color: #666; } diff --git a/frontend/src/pages/wiki2/side-panel.js b/frontend/src/pages/wiki2/side-panel.js index bbb0c46286..0932ceb63a 100644 --- a/frontend/src/pages/wiki2/side-panel.js +++ b/frontend/src/pages/wiki2/side-panel.js @@ -83,16 +83,16 @@ class SidePanel extends PureComponent { }); }; - addPage = (page, parentId, successCallback, errorCallback, jumpToNewPage = true) => { + addPage = (page, parent_id, successCallback, errorCallback, jumpToNewPage = true) => { const { config } = this.props; const navigation = config.navigation; - const pageId = page.id; + const page_id = page.id; config.pages.push(page); - PageUtils.addPage(navigation, pageId, parentId); + PageUtils.addPage({ navigation, page_id, parent_id }); config.navigation = navigation; JSON.stringify(config); this.props.updateWikiConfig(config); - jumpToNewPage && this.props.setCurrentPage(pageId, successCallback); + jumpToNewPage && this.props.setCurrentPage(page_id, successCallback); successCallback && successCallback(); }; @@ -104,6 +104,19 @@ class SidePanel extends PureComponent { this.props.updateWikiConfig(config); }; + addSiblingPage = (page, parent_id, insert_position, sibling_page_id, successCallback) => { + const { config } = this.props; + const navigation = config.navigation; + const page_id = page.page_id; + config.pages.push(page); + PageUtils.addPage({ navigation, page_id, parent_id, insert_position, sibling_page_id }); + config.navigation = navigation; + JSON.stringify(config); + this.props.updateWikiConfig(config); + this.props.setCurrentPage(page_id, successCallback); + successCallback && successCallback(); + }; + toggleTrashDialog = () => { this.setState({ isShowTrashDialog: !this.state.isShowTrashDialog }); }; @@ -128,6 +141,7 @@ class SidePanel extends PureComponent { getCurrentPageId={this.props.getCurrentPageId} addPageInside={this.addPageInside} toggleTrashDialog={this.toggleTrashDialog} + addSiblingPage={this.addSiblingPage} /> } 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 c22d62a7e2..fd2d09d9d1 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 @@ -6,14 +6,16 @@ import { Utils } from '../../../utils/utils'; import toaster from '../../../components/toast'; import Loading from '../../../components/loading'; import wikiAPI from '../../../utils/wiki-api'; +import { INSERT_POSITION } from './constants'; import '../css/add-new-page-dialog.css'; const propTypes = { + page: PropTypes.object, title: PropTypes.node, toggle: PropTypes.func.isRequired, onAddNewPage: PropTypes.func, - getCurrentPageId: PropTypes.func.isRequired, + insertPosition: PropTypes.string, }; @@ -71,9 +73,11 @@ class AddNewPageDialog extends React.Component { }; createPage = (pageName) => { - wikiAPI.createWiki2Page(wikiId, pageName, this.props.getCurrentPageId()).then(res => { + const { insertPosition, page } = this.props; + wikiAPI.createWiki2Page(wikiId, pageName, page.id, insertPosition).then(res => { const { page_id, obj_name, doc_uuid, parent_dir } = res.data.file_info; this.props.onAddNewPage({ + id: page_id, page_id: page_id, name: pageName, icon: '', @@ -126,4 +130,8 @@ class AddNewPageDialog extends React.Component { AddNewPageDialog.propTypes = propTypes; +AddNewPageDialog.defaultProps = { + insertPosition: INSERT_POSITION.INNER, +}; + export default AddNewPageDialog; diff --git a/frontend/src/pages/wiki2/wiki-nav/constants.js b/frontend/src/pages/wiki2/wiki-nav/constants.js new file mode 100644 index 0000000000..7ad3caedb4 --- /dev/null +++ b/frontend/src/pages/wiki2/wiki-nav/constants.js @@ -0,0 +1,5 @@ +export const INSERT_POSITION = { + ABOVE: 'above', + BELOW: 'below', + INNER: 'inner', +}; diff --git a/frontend/src/pages/wiki2/wiki-nav/page-utils.js b/frontend/src/pages/wiki2/wiki-nav/page-utils.js index 4244f20e62..829b613620 100644 --- a/frontend/src/pages/wiki2/wiki-nav/page-utils.js +++ b/frontend/src/pages/wiki2/wiki-nav/page-utils.js @@ -1,3 +1,5 @@ +import { INSERT_POSITION } from './constants'; + class NewPage { constructor(id) { this.id = id; @@ -8,26 +10,52 @@ class NewPage { export default class PageUtils { - static addPage(navigation, page_id, parentId) { - if (!parentId) { - navigation.push(new NewPage(page_id)); + static addPage({ navigation, page_id, parent_id, insert_position, sibling_page_id }) { + if (!parent_id) { + const newPage = new NewPage(page_id); + if (sibling_page_id) { + let insertIndex = navigation.findIndex(item => item.id === sibling_page_id); + if (insertIndex > -1) { + if (insert_position === INSERT_POSITION.ABOVE) { + insertIndex -= 1; + } + navigation.splice(insertIndex + 1, 0, newPage); + } else { + navigation.push(newPage); + } + } else { + navigation.push(newPage); + } } else { navigation.forEach(item => { - this._addPageRecursion(page_id, item, parentId); + this._addPageRecursion({ page_id, item, parent_id, insert_position, sibling_page_id }); }); } } - static _addPageRecursion(page_id, item, parentId) { + static _addPageRecursion({ page_id, item, parent_id, insert_position, sibling_page_id }) { if (!Array.isArray(item.children)) { item.children = []; } - if (item.id === parentId) { - item.children.push(new NewPage(page_id)); + if (item.id === parent_id) { + const newPage = new NewPage(page_id); + if (sibling_page_id) { + let insertIndex = item.children.findIndex(item => item.id === sibling_page_id); + if (insertIndex > -1) { + if (insert_position === INSERT_POSITION.ABOVE) { + insertIndex -= 1; + } + item.children.splice(insertIndex + 1, 0, newPage); + } else { + item.children.push(newPage); + } + } else { + item.children.push(newPage); + } return true; } item.children && item.children.forEach(item => { - this._addPageRecursion(page_id, item, parentId); + this._addPageRecursion({ page_id, item, parent_id, insert_position, sibling_page_id }); }); } diff --git a/frontend/src/pages/wiki2/wiki-nav/pages/page-dropdownmenu.js b/frontend/src/pages/wiki2/wiki-nav/pages/page-dropdownmenu.js index 21c6e07372..da9d82d35f 100644 --- a/frontend/src/pages/wiki2/wiki-nav/pages/page-dropdownmenu.js +++ b/frontend/src/pages/wiki2/wiki-nav/pages/page-dropdownmenu.js @@ -4,6 +4,7 @@ import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap import toaster from '../../../../components/toast'; import { gettext } from '../../../../utils/constants'; import { getWikPageLink } from '../../utils'; +import { INSERT_POSITION } from '../constants'; export default class PageDropdownMenu extends Component { @@ -13,6 +14,7 @@ export default class PageDropdownMenu extends Component { pagesLength: PropTypes.number, toggle: PropTypes.func, toggleNameEditor: PropTypes.func, + toggleInsertSiblingPage: PropTypes.func, duplicatePage: PropTypes.func, onDeletePage: PropTypes.func, isOnlyOnePage: PropTypes.bool, @@ -46,6 +48,14 @@ export default class PageDropdownMenu extends Component { this.props.onDeletePage(); }; + addPageAbove = () => { + this.props.toggleInsertSiblingPage(INSERT_POSITION.ABOVE); + }; + + addPageBelow = () => { + this.props.toggleInsertSiblingPage(INSERT_POSITION.BELOW); + }; + duplicatePage = () => { const { page } = this.props; this.props.duplicatePage({ from_page_id: page.id }, () => {}, this.duplicatePageFailure); @@ -98,6 +108,14 @@ export default class PageDropdownMenu extends Component { {gettext('Modify name')} + + + {gettext('Add page above')} + + + + {gettext('Add page below')} + {gettext('Duplicate page')} 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 d255135faf..6f3d9067a3 100644 --- a/frontend/src/pages/wiki2/wiki-nav/pages/page-item.js +++ b/frontend/src/pages/wiki2/wiki-nav/pages/page-item.js @@ -11,6 +11,7 @@ import Icon from '../../../../components/icon'; import DraggedPageItem from './dragged-page-item'; import CustomIcon from '../../custom-icon'; import { eventBus } from '../../../../components/common/event-bus'; +import { INSERT_POSITION } from '../constants'; class PageItem extends Component { @@ -21,6 +22,8 @@ class PageItem extends Component { isShowOperationDropdown: false, isShowDeleteDialog: false, isShowInsertPage: false, + isShowAddSiblingPage: false, + insertPosition: '', pageName: props.page.name || '', isSelected: props.getCurrentPageId() === props.page.id, isMouseEnter: false, @@ -73,6 +76,17 @@ class PageItem extends Component { this.setState({ isShowInsertPage: !this.state.isShowInsertPage }); }; + toggleInsertSiblingPage = (position) => { + let insertPosition = null; + if (position === INSERT_POSITION.BELOW || position === INSERT_POSITION.ABOVE) { + insertPosition = position; + } + this.setState({ + insertPosition, + isShowAddSiblingPage: !this.state.isShowAddSiblingPage, + }); + }; + savePageProperties = () => { const { name, id } = this.props.page; const pageName = this.state.pageName.trim(); @@ -134,6 +148,7 @@ class PageItem extends Component { isOnlyOnePage={isOnlyOnePage} page={Object.assign({}, pages.find(item => item.id === id), page)} pageIndex={index} + parentPageId={this.props.page.id} isEditMode={isEditMode} duplicatePage={this.props.duplicatePage} setCurrentPage={this.props.setCurrentPage} @@ -150,6 +165,7 @@ class PageItem extends Component { setClassName={this.props.setClassName} getClassName={this.props.getClassName} layerDragProps={this.props.layerDragProps} + addSiblingPage={this.props.addSiblingPage} /> ); }; @@ -176,6 +192,11 @@ class PageItem extends Component { this.props.addPageInside(Object.assign({ parentPageId: this.props.page.id }, newPage)); }; + onAddSiblingPage = (newPage) => { + const { page } = this.props; + this.props.addSiblingPage(newPage, this.props.parentPageId, this.state.insertPosition, page.id, this.toggleInsertSiblingPage); + }; + getPageClassName = () => { const { isOver, canDrop, isEditMode, layerDragProps } = this.props; const isOverPage = isOver && canDrop; @@ -257,6 +278,7 @@ class PageItem extends Component { toggleNameEditor={this.toggleNameEditor} duplicatePage={this.props.duplicatePage} onDeletePage={this.openDeleteDialog} + toggleInsertSiblingPage={this.toggleInsertSiblingPage} /> } @@ -275,9 +297,18 @@ class PageItem extends Component { {this.state.isShowInsertPage && + } + {this.state.isShowAddSiblingPage && + } diff --git a/frontend/src/pages/wiki2/wiki-nav/wiki-nav.js b/frontend/src/pages/wiki2/wiki-nav/wiki-nav.js index 18d8894a2c..b4ba9b7645 100644 --- a/frontend/src/pages/wiki2/wiki-nav/wiki-nav.js +++ b/frontend/src/pages/wiki2/wiki-nav/wiki-nav.js @@ -19,6 +19,7 @@ class WikiNav extends Component { onDeletePage: PropTypes.func, onMovePage: PropTypes.func, duplicatePage: PropTypes.func, + addSiblingPage: PropTypes.func, getCurrentPageId: PropTypes.func, addPageInside: PropTypes.func, updateWikiConfig: PropTypes.func.isRequired, @@ -91,6 +92,7 @@ class WikiNav extends Component { pathStr={page.id} getCurrentPageId={this.props.getCurrentPageId} addPageInside={this.props.addPageInside} + addSiblingPage={this.props.addSiblingPage} getFoldState={this.getFoldState} toggleExpand={this.toggleExpand} id_page_map={id_page_map} diff --git a/frontend/src/utils/wiki-api.js b/frontend/src/utils/wiki-api.js index 34b6eefbfb..7a635e1fa6 100644 --- a/frontend/src/utils/wiki-api.js +++ b/frontend/src/utils/wiki-api.js @@ -181,13 +181,16 @@ class WikiAPI { return this.req.get(url); } - createWiki2Page(wikiId, pageName, currentId) { + createWiki2Page(wikiId, pageName, currentId, insertPosition) { const url = this.server + '/api/v2.1/wiki2/' + wikiId + '/pages/'; let form = new FormData(); form.append('page_name', pageName); if (currentId) { form.append('current_id', currentId); } + if (insertPosition) { + form.append('insert_position', insertPosition); + } return this._sendPostRequest(url, form); } diff --git a/seahub/api2/endpoints/wiki2.py b/seahub/api2/endpoints/wiki2.py index cbee2dac25..56ecd9d73e 100644 --- a/seahub/api2/endpoints/wiki2.py +++ b/seahub/api2/endpoints/wiki2.py @@ -500,6 +500,12 @@ class Wiki2PagesView(APIView): error_msg = 'page_name invalid.' return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + current_id = request.data.get('current_id', None) + insert_position = request.data.get('insert_position', None) + positions = ['above', 'below', 'inner'] + if insert_position and insert_position not in positions: + error_msg = 'insert_position invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) wiki = Wiki.objects.get(wiki_id=wiki_id) if not wiki: @@ -514,16 +520,15 @@ class Wiki2PagesView(APIView): return api_error(status.HTTP_403_FORBIDDEN, error_msg) repo_id = wiki.repo_id - # resource check 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) - - current_id = request.data.get('current_id', None) + wiki_config = get_wiki_config(repo_id, request.user.username) navigation = wiki_config.get('navigation', []) + # side panel create Untitled page if not current_id: page_ids = {element.get('id') for element in navigation if element.get('type') != 'folder'} else: @@ -537,22 +542,6 @@ class Wiki2PagesView(APIView): new_file_name = page_name + '.sdoc' 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.') - - try: - seafile_api.post_empty_file(repo_id, parent_dir, new_file_name, request.user.username) - except Exception as e: - if str(e) == 'Too many files in library.': - error_msg = _("The number of files in library exceeds the limit") - return api_error(HTTP_447_TOO_MANY_FILES_IN_LIBRARY, error_msg) - else: - logger.error(e) - error_msg = 'Internal Server Error' - return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) - new_file_path = posixpath.join(parent_dir, new_file_name) file_info = self.get_file_info(repo_id, new_file_path) file_info['doc_uuid'] = sdoc_uuid @@ -565,7 +554,27 @@ class Wiki2PagesView(APIView): 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) + is_find = [False] + gen_new_page_nav_by_id(navigation, new_page_id, current_id, insert_position, is_find) + if not is_find[0]: + error_msg = 'Current page does not exist' + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + # create new empty file + seafile_api.mkdir_with_parents(repo_id, '/', parent_dir.strip('/'), request.user.username) + if not is_valid_dirent_name(new_file_name): + return api_error(status.HTTP_400_BAD_REQUEST, 'name invalid.') + + try: + seafile_api.post_empty_file(repo_id, parent_dir, new_file_name, request.user.username) + except Exception as e: + if str(e) == 'Too many files in library.': + error_msg = _("The number of files in library exceeds the limit") + return api_error(HTTP_447_TOO_MANY_FILES_IN_LIBRARY, error_msg) + else: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + new_page = { 'id': new_page_id, 'name': page_name, diff --git a/seahub/wiki2/utils.py b/seahub/wiki2/utils.py index c39314567b..8e50befb88 100644 --- a/seahub/wiki2/utils.py +++ b/seahub/wiki2/utils.py @@ -173,22 +173,41 @@ 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): +def gen_new_page_nav_by_id(navigation, page_id, current_id, insert_position, is_find): 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) + if insert_position == 'inner': + 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 + is_find[0] = True + return True + else: + gen_new_page_nav_by_id(nav.get('children', []), page_id, current_id, insert_position, is_find) + elif insert_position == 'above': + for index, nav in enumerate(navigation): + if nav.get('type') == 'page' and nav.get('id') == current_id: + navigation.insert(index, new_nav) + is_find[0] = True + return True + else: + gen_new_page_nav_by_id(nav.get('children', []), page_id, current_id, insert_position, is_find) + elif insert_position == 'below': + for index, nav in enumerate(navigation): + if nav.get('type') == 'page' and nav.get('id') == current_id: + navigation.insert(index+1, new_nav) + is_find[0] = True + return True + else: + gen_new_page_nav_by_id(nav.get('children', []), page_id, current_id, insert_position, is_find) else: navigation.append(new_nav) + return True def get_current_level_page_ids(navigation, page_id, ids=[]):