diff --git a/frontend/src/pages/wiki2/css/wiki-nav.css b/frontend/src/pages/wiki2/css/wiki-nav.css index b5764a7687..7ec79fe0c1 100644 --- a/frontend/src/pages/wiki2/css/wiki-nav.css +++ b/frontend/src/pages/wiki2/css/wiki-nav.css @@ -12,7 +12,7 @@ } .wiki-nav-body { - padding-bottom: 0.5rem; + padding-bottom: 1rem; overflow: auto; user-select: none; max-height: 100%; @@ -234,21 +234,29 @@ margin: 0.2rem 0; } -.wiki-nav .wiki-page-item.page-can-drop::after, +.wiki-nav .wiki-page-item.page-can-drop-bottom::after, .wiki-nav .wiki-page-item.page-can-drop-top::after { content: ''; width: 100%; - height: 1px; + height: 5px; position: absolute; left: 0; - top: 39px; - background-color: #666 !important; + background-color: rgb(200, 220, 240) !important; +} + +.wiki-nav .wiki-page-item.page-can-drop-bottom::after { + top: 32px; } .wiki-nav .wiki-page-item.page-can-drop-top::after { top: 0; } +.wiki-nav .wiki-page-item.dragged-page-over { + border-radius: 3px; + background-color: rgb(200, 220, 240) !important; +} + .dtable-dropdown-menu .dropdown-item .sf3-font { font-size: 14px; margin-right: 10px; diff --git a/frontend/src/pages/wiki2/side-panel.js b/frontend/src/pages/wiki2/side-panel.js index 2c3c926019..59cd2dd910 100644 --- a/frontend/src/pages/wiki2/side-panel.js +++ b/frontend/src/pages/wiki2/side-panel.js @@ -89,8 +89,7 @@ class SidePanel extends Component { movePage = ({ moved_page_id, target_page_id, move_position }) => { let config = deepCopy(this.props.config); let { navigation } = config; - PageUtils.movePage(navigation, moved_page_id, target_page_id, move_position); - config.navigation = navigation; + config.navigation = PageUtils.movePage(navigation, moved_page_id, target_page_id, move_position); JSON.stringify(config); this.props.updateWikiConfig(config); }; diff --git a/frontend/src/pages/wiki2/wiki-nav/page-utils.js b/frontend/src/pages/wiki2/wiki-nav/page-utils.js index 52d7596b96..4244f20e62 100644 --- a/frontend/src/pages/wiki2/wiki-nav/page-utils.js +++ b/frontend/src/pages/wiki2/wiki-nav/page-utils.js @@ -63,21 +63,29 @@ export default class PageUtils { return pages.findIndex(page => page.id === pageId); }; - static insertPage(navigation, page_id, target_page_id, target_id, move_position) { - if (!target_id) { - let insertIndex = target_page_id ? navigation.findIndex(item => item.id === target_page_id) : -1; - if (insertIndex < 0) { - this.addPage(navigation, page_id, target_id); - return true; + static generatePaths = (tree) => { + tree._path = ''; + function runNode(node) { + const newPath = node._path ? (node._path + '-' + node.id) : (node.id || ''); + if (node.children) { + node.children.forEach(child => { + if (child) { + child._path = newPath; + runNode(child); + } + }); } - if (move_position === 'move_below') { - insertIndex++; - } - navigation.splice(insertIndex, 0, new NewPage(page_id)); - return; } - } + runNode(tree); + }; + /** + * move page to another page + * @param {object} navigation + * @param {string} moved_page_id + * @param {string} target_page_id + * @param {string} move_position, one of'move_into', 'move_below', 'move_into' + */ static movePage(navigation, moved_page_id, target_page_id, move_position) { let movedPage = null; function _cutPageRecursion(item, page_id) { @@ -101,42 +109,47 @@ export default class PageUtils { }); } } - function _insertPageRecursion(item, page_id, target_page_id, target_id, move_position) { - if (item.id === target_id) { - 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); - } - return; - } - item.children && item.children.forEach(item => { - _insertPageRecursion(item, page_id, target_page_id, target_id, move_position); - }); - } - function _insertPage(navigation, page_id, target_page_id, target_id, move_position) { - if (!target_id) { - let insertIndex = target_page_id ? navigation.findIndex(item => item.id === target_page_id) : -1; - if (insertIndex < 0) { - navigation.splice(0, 0, movedPage); - return; - } - if (move_position === 'move_below') { - insertIndex++; - } - navigation.splice(insertIndex, 0, movedPage); - return; - } - navigation.forEach(item => { - _insertPageRecursion(item, page_id, target_page_id, target_id, move_position); - }); - } _cutPage(navigation, moved_page_id); - _insertPage(navigation, moved_page_id, target_page_id, target_page_id, move_position); + if (!movedPage) return; + + function _insertPage(tree, target_page_id, move_position) { + if (!tree) return; + if (!Array.isArray(tree.children)) { + tree.children = []; + } + const target_page = tree.children.find(item => item.id === target_page_id); + const target_index = tree.children.findIndex(item => item.id === target_page_id); + if (target_page) { + switch (move_position) { + case 'move_into': { + if (!Array.isArray(target_page.children)) { + target_page.children = []; + } + target_page.children.push(movedPage); + break; + } + case 'move_above': { + tree.children.splice(target_index, 0, movedPage); + break; + } + case 'move_below': { + tree.children.splice(target_index + 1, 0, movedPage); + break; + } + default: + break; + } + } else { + tree.children.forEach(child => { + _insertPage(child, target_page_id, move_position); + }); + } + } + + let tree = {}; + tree.children = navigation; + _insertPage(tree, target_page_id, move_position); + this.generatePaths(tree); + return tree.children; } } 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 c3e3d7214d..7410a37358 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 @@ -31,30 +31,36 @@ const dragSource = { const dropTarget = { drop(props, monitor) { const dragSource = monitor.getItem(); - if (dragSource.mode === 'wiki-page') { - const { pageIndex: targetIndex, page: targetPage } = props; - const draggedPageId = dragSource.data.id; - const targetPageId = targetPage.id; - if (draggedPageId !== targetPageId) { - const sourceIndex = dragSource.idx; - const move_position = sourceIndex > targetIndex ? 'move_above' : 'move_below'; - 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; + const className = props.getClassName(); + let move_position; + if (className.includes('page-can-drop-bottom')) { + move_position = 'move_below'; + } else if (className.includes('page-can-drop-top')) { + move_position = 'move_above'; + } else if (className.includes('dragged-page-over')) { + move_position = 'move_into'; } + if (!move_position) return; + if (dragSource.mode === 'wiki-page') { + const targetPage = props.page; + const draggedPage = dragSource.data; + const moved_page_id = draggedPage.id; + const target_page_id = targetPage.id; + if (moved_page_id === target_page_id) { + return; + } + if (targetPage._path && targetPage._path.includes(moved_page_id)) { + toaster.danger(gettext('Cannot move parent page to child page')); + return; + } + wikiAPI.moveWiki2Page(wikiId, moved_page_id, target_page_id, move_position).then(res => { + props.onMovePage({ moved_page_id, target_page_id, move_position }); + }).catch((error) => { + 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 caaa9da9eb..38c657cfef 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,9 @@ class PageItem extends Component { addPageInside={this.props.addPageInside} getFoldState={this.props.getFoldState} toggleExpand={this.props.toggleExpand} + setClassName={this.props.setClassName} + getClassName={this.props.getClassName} + layerDragProps={this.props.layerDragProps} /> ); }; @@ -157,17 +160,32 @@ class PageItem extends Component { this.props.addPageInside(Object.assign({ parentPageId: this.props.page.id }, newPage)); }; - render() { - const { - connectDragSource, connectDragPreview, connectDropTarget, isOver, canDrop, isDragging, - page, pagesLength, isEditMode, isOnlyOnePage, pathStr, - } = this.props; - const { isShowNameEditor, pageName, isSelected } = this.state; + getPageClassName = () => { + const { isOver, canDrop, isEditMode, layerDragProps } = this.props; const isOverPage = isOver && canDrop; + if (!isOverPage || ! layerDragProps || !layerDragProps.clientOffset) { + return classnames('wiki-page-item', { 'selected-page': this.state.isSelected }, { 'readonly': !isEditMode }); + } + let y = layerDragProps.clientOffset.y; + let top = this.pageItemRef.getBoundingClientRect().y; + const className = classnames( + 'wiki-page-item', + { 'dragged-page-over': (top + 10 < y && y < top + 30) }, + { 'page-can-drop-top': (top + 10 > y) }, + { 'page-can-drop-bottom': (top + 30 < y) }, + { 'selected-page': this.state.isSelected }, + { 'readonly': !isEditMode }, + ); + this.props.setClassName(className); + return className; + }; + + render() { + const { connectDragSource, connectDragPreview, connectDropTarget, + page, pagesLength, isEditMode, isOnlyOnePage, pathStr } = this.props; + const { isShowNameEditor, pageName, isSelected } = this.state; if (isSelected) this.setDocUuid(page.docUuid); - let pageCanDropTop = isOverPage && isDragging; - let pageCanDrop = isOverPage && !isDragging; let navItemId = `page-editor-${page.id}`; let fn = isEditMode ? connectDragSource : (argu) => { argu; }; let childNumber = Array.isArray(page.children) ? page.children.length : 0; @@ -180,12 +198,7 @@ class PageItem extends Component { fn(connectDropTarget( connectDragPreview(
this.pageItemRef = ref} onMouseEnter={this.onMouseEnter} onMouseMove={this.onMouseMove} @@ -296,6 +309,8 @@ PageItem.propTypes = { getFoldState: PropTypes.func, toggleExpand: PropTypes.func, updateWikiConfig: PropTypes.func, + getClassName: PropTypes.func, + setClassName: 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 6928acc907..5f2424a123 100644 --- a/frontend/src/pages/wiki2/wiki-nav/wiki-nav.js +++ b/frontend/src/pages/wiki2/wiki-nav/wiki-nav.js @@ -25,6 +25,7 @@ class WikiNav extends Component { constructor(props) { super(props); + this.folderClassNameCache = ''; this.idFoldedStatusMap = this.getFoldedFromLocal(); } @@ -52,6 +53,14 @@ class WikiNav extends Component { this.idFoldedStatusMap = idFoldedStatusMap; }; + setClassName = (name) => { + this.folderClassNameCache = name; + }; + + getClassName = () => { + return this.folderClassNameCache; + }; + renderPage = (page, index, pagesLength, isOnlyOnePage, id_page_map, layerDragProps) => { const { isEditMode, pages } = this.props; const id = page.id; @@ -78,6 +87,8 @@ class WikiNav extends Component { toggleExpand={this.toggleExpand} id_page_map={id_page_map} layerDragProps={layerDragProps} + setClassName={this.setClassName} + getClassName={this.getClassName} /> ); }; diff --git a/seahub/api2/endpoints/wiki2.py b/seahub/api2/endpoints/wiki2.py index 381b43a90d..e533d19da2 100644 --- a/seahub/api2/endpoints/wiki2.py +++ b/seahub/api2/endpoints/wiki2.py @@ -571,6 +571,13 @@ class Wiki2PagesView(APIView): id_set = get_all_wiki_ids(navigation) target_page_id = request.data.get('target_id', '') moved_page_id = request.data.get('moved_id', '') + move_position = request.data.get('move_position', '') + # check arguments + valid_move_positions = ['move_below', 'move_above', 'move_into'] + if move_position not in valid_move_positions: + error_msg = 'Invalid move_position value: ' + move_position + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + 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) @@ -583,7 +590,7 @@ class Wiki2PagesView(APIView): logger.error(error_msg) return api_error(status.HTTP_400_BAD_REQUEST, error_msg) - move_nav(navigation, target_page_id, moved_nav) + move_nav(navigation, target_page_id, moved_nav, move_position) wiki_config['navigation'] = navigation wiki_config = json.dumps(wiki_config) diff --git a/seahub/wiki2/utils.py b/seahub/wiki2/utils.py index b0f734ded2..d76da35d3d 100644 --- a/seahub/wiki2/utils.py +++ b/seahub/wiki2/utils.py @@ -244,14 +244,23 @@ def pop_nav(navigation, page_id): return None -def move_nav(navigation, target_id, moved_nav): - for nav in navigation: +def move_nav(navigation, target_id, moved_nav, move_position): + def move_item(nav_list, nav_index, moved_nav, move_position): + if move_position == 'move_below': + nav_list.insert(nav_index + 1, moved_nav) + elif move_position == 'move_above': + nav_list.insert(nav_index, moved_nav) + + for nav_index, nav in enumerate(navigation): if nav['id'] == target_id: - if 'children' in nav: - nav['children'].insert(0, moved_nav) - else: - nav['children'] = [moved_nav] + if move_position == 'move_below' or move_position == 'move_above': + move_item(navigation, nav_index, moved_nav, move_position) + if move_position == 'move_into': + if 'children' in nav: + nav['children'].append(moved_nav) + else: + nav['children'] = [moved_nav] return if 'children' in nav: - move_nav(nav['children'], target_id, moved_nav) + move_nav(nav['children'], target_id, moved_nav, move_position)