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(