1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-04 16:31:13 +00:00

12.0 wiki page can add sub page (#6165)

* 00 add page.children

* 01 add delete inner page

* 02 change page indent and path

* 03 change top nav style

* 04 change svg and var name

* 05 move pages into or out folder

* 06 change codes
This commit is contained in:
Michael An
2024-06-07 09:45:05 +08:00
committed by GitHub
parent e5b696b098
commit 440fde0553
19 changed files with 632 additions and 416 deletions

View File

@@ -3,17 +3,16 @@
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve">
<style type="text/css">
.st0{fill:#949494;}
.st0{fill:#999999;}
</style>
<title>file1</title>
<desc>Created with Sketch.</desc>
<g id="file1">
<g id="编组-5">
<g id="文件" transform="translate(3.000000, 0.000000)">
<path id="形状" class="st0" d="M25.6,6.9l-6.7-6.6C18.6,0,18.3,0,18,0H4C2.1,0,1,2.3,1,3.9C1,4.9,1,13.3,1,29c0,1,1.3,3,3,3
s17.4,0,19.1,0s2.9-1.4,2.9-3c0-1,0-8.2,0-21.3C26,7.4,25.7,7,25.6,6.9z M17.9,3.7L22.3,8H18V3.7H17.9z M22.8,29H4V3h11v5.3
c0,1.8,0.8,2.7,2.9,2.7H23v18H22.8z"/>
</g>
<title>file</title>
<g id="file">
<g transform="translate(4.000000, 1.000000)">
<path id="形状" class="st0" d="M20,30H4c-2.3,0-4-1.8-4-4.1V4.1C0,1.8,1.7,0,4,0h9.3c0.4,0,0.7,0.1,0.9,0.4l9.3,9.5
c0.3,0.3,0.4,0.5,0.4,1v15C24,28.2,22.3,30,20,30z M4.3,3C3.5,3,3,3.5,3,4.3v21.3C3,26.5,3.5,27,4.3,27h15.4
c0.8,0,1.3-0.5,1.3-1.3V11.5L12.8,3H4.3z"/>
<path id="路径" class="st0" d="M21.7,13h-9.3c-0.8,0-1.3-0.5-1.3-1.3V2.3C11,1.5,11.5,1,12.3,1s1.3,0.5,1.3,1.3v8h8
c0.8,0,1.3,0.5,1.3,1.3S22.5,13,21.7,13L21.7,13z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 866 B

After

Width:  |  Height:  |  Size: 941 B

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve">
<style type="text/css">
.st0{fill:#999999;}
</style>
<title>files</title>
<g id="files">
<g id="file" transform="translate(4.000000, 1.000000)">
<path id="形状" class="st0" d="M20,30H4c-2.3,0-4-1.8-4-4.1V4.1C0,1.8,1.7,0,4,0h9.3c0.4,0,0.7,0.1,0.9,0.4l9.3,9.5
c0.3,0.3,0.4,0.5,0.4,1v15C24,28.2,22.3,30,20,30z M4.3,3C3.5,3,3,3.5,3,4.3v21.3C3,26.5,3.5,27,4.3,27h15.4
c0.8,0,1.3-0.5,1.3-1.3V11.5L12.8,3H4.3z"/>
<path id="路径" class="st0" d="M21.7,13h-9.3c-0.8,0-1.3-0.5-1.3-1.3V2.3C11,1.5,11.5,1,12.3,1s1.3,0.5,1.3,1.3v8h8
c0.8,0,1.3,0.5,1.3,1.3S22.5,13,21.7,13L21.7,13z"/>
<path id="矩形" class="st0" d="M7.5,16h9c0.8,0,1.5,0.7,1.5,1.5l0,0c0,0.8-0.7,1.5-1.5,1.5h-9C6.7,19,6,18.3,6,17.5l0,0
C6,16.7,6.7,16,7.5,16z"/>
<path id="矩形备份" class="st0" d="M7.5,21h9c0.8,0,1.5,0.7,1.5,1.5l0,0c0,0.8-0.7,1.5-1.5,1.5h-9C6.7,24,6,23.3,6,22.5l0,0
C6,21.7,6.7,21,7.5,21z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -94,7 +94,7 @@
padding-left: 20px;
}
.view-structure .view-folder-wrapper .icon-expand-folder {
.view-structure .icon-expand-folder {
display: inline-block;
font-size: 12px;
transform: scale(0.8);
@@ -145,10 +145,13 @@
}
.view-structure .view-folder-wrapper .more-view-folder-operation .seafile-multicolor-icon-more-level,
.view-structure .view-item .fas.fa-plus,
.view-structure .view-item .more-view-operation .seafile-multicolor-icon-more-level {
cursor: pointer;
opacity: 0;
}
.view-structure .view-item:hover .fas.fa-plus,
.view-structure .view-folder-wrapper:hover .more-view-folder-operation .seafile-multicolor-icon-more-level,
.view-structure .view-item:hover .more-view-operation .seafile-multicolor-icon-more-level {
opacity: 1;
@@ -166,6 +169,7 @@
}
.view-structure .folder-list .view-folder.fold-freezed .btn-folder-operation,
.view-structure .view-item.view-freezed .fas.fa-plus,
.view-structure .view-item.view-freezed .seafile-multicolor-icon-more-level {
opacity: 1;
}
@@ -358,21 +362,24 @@
/* dark mode */
.view-structure-dark.view-structure,
.view-structure-dark.view-structure .view-folder .icon-expand-folder {
.view-structure-dark.view-structure .icon-expand-folder {
color: #fff;
}
/* light mode */
.view-structure-light.view-structure,
.view-structure-light.view-structure .view-item .fas.fa-plus:hover,
.view-structure-light.view-structure .view-item .seafile-multicolor-icon-more-level:hover,
.view-structure-light.view-structure .view-folder .seafile-multicolor-icon-more-level:hover,
.view-structure-light.view-structure .view-folder .icon-expand-folder:hover {
.view-structure-light.view-structure .icon-expand-folder:hover {
color: #212529;
}
.view-structure-light.view-structure .view-item .fas.fa-plus,
.view-structure-light.view-structure .view-item .seafile-multicolor-icon-more-level,
.view-structure-light.view-structure .view-folder .seafile-multicolor-icon-more-level,
.view-structure-light.view-structure .view-folder .icon-expand-folder {
.view-structure-light.view-structure .icon-expand-folder {
color: #666;
}

View File

@@ -1,9 +1,12 @@
export default class Page {
class Page {
constructor(object) {
this.id = object.id;
this.name = object.name;
this.path = object.path;
this.icon = object.icon;
this.docUuid = object.docUuid;
this.children = Array.isArray(object.children) ? object.children.map(item => new Page(item)) : [];
}
}
export default Page;

View File

@@ -11,6 +11,7 @@ export default class WikiConfig {
return {
id: item.id,
type: item.type,
children: item.children || [],
};
}
return null;

View File

@@ -47,6 +47,7 @@ class SidePanel extends Component {
config.pages.splice(index, 1);
PageUtils.deletePage(navigation, pageId);
this.props.saveWikiConfig(config);
// TODO: To delete a page, do you need to delete all subpages at once (requires a new API)
wikiAPI.deleteWiki2Page(wikiId, pageId);
if (config.pages.length > 0) {
this.props.setCurrentPage(config.pages[0].id);
@@ -55,12 +56,20 @@ 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 });
this.addPage(newPage, parentPageId, successCallback, errorCallback);
};
onAddNewPage = async ({ 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 });
this.addPage(newPage, successCallback, errorCallback);
this.addPage(newPage, this.current_folder_id, successCallback, errorCallback);
};
duplicatePage = async (fromPageConfig, successCallback, errorCallback) => {
@@ -75,15 +84,15 @@ class SidePanel extends Component {
name,
};
const newPage = new Page({ ...newPageConfig });
this.addPage(newPage, successCallback, errorCallback);
this.addPage(newPage, this.current_folder_id, successCallback, errorCallback);
};
addPage = (page, successCallback, errorCallback) => {
addPage = (page, parentId, successCallback, errorCallback) => {
const { config } = this.props;
const navigation = config.navigation;
const pageId = page.id;
config.pages.push(page);
PageUtils.addPage(navigation, pageId, this.current_folder_id);
PageUtils.addPage(navigation, pageId, parentId);
config.navigation = navigation;
const onSuccess = () => {
this.props.setCurrentPage(pageId, successCallback);
@@ -113,10 +122,10 @@ class SidePanel extends Component {
this.props.saveWikiConfig(config);
};
movePageOut = (moved_page_id, source_folder_id, target_folder_id, move_position) => {
movePageOut = (moved_page_id, source_id, target_id, move_position) => {
let config = deepCopy(this.props.config);
let { navigation } = config;
PageUtils.movePageOut(navigation, moved_page_id, source_folder_id, target_folder_id, move_position);
PageUtils.movePageOut(navigation, moved_page_id, source_id, target_id, move_position);
config.navigation = navigation;
this.props.saveWikiConfig(config);
};
@@ -164,12 +173,13 @@ class SidePanel extends Component {
const { config } = this.props;
const { navigation, pages } = config;
PageUtils.deleteFolder(navigation, pages, page_folder_id);
// TODO: delete all pages inside the folder, A new API is required
config.navigation = navigation;
this.props.saveWikiConfig(config);
};
// Drag a folder to the front and back of another folder
onMoveFolder = (moved_folder_id, target_folder_id, move_position) => {
onMoveFolder = (moved_folder_id, target_id, move_position) => {
const { config } = this.props;
const { navigation } = config;
let updatedNavigation = deepCopy(navigation);
@@ -196,11 +206,11 @@ class SidePanel extends Component {
indexOffset++;
}
// Get the location of the release
let target_folder_index = PageUtils.getFolderIndexById(updatedNavigation, target_folder_id);
let target_folder_index = PageUtils.getFolderIndexById(updatedNavigation, target_id);
if (target_folder_index === -1) {
updatedNavigation.forEach(item => {
if (item.type === FOLDER) {
target_folder_index = PageUtils.getFolderIndexById(item.children, target_folder_id);
target_folder_index = PageUtils.getFolderIndexById(item.children, target_id);
if (target_folder_index > -1) {
item.children.splice(target_folder_index + indexOffset, 0, moved_folder);
}
@@ -217,12 +227,12 @@ class SidePanel extends Component {
};
// Not support yet: Move a folder into another folder
moveFolderToFolder = (moved_folder_id, target_folder_id) => {
moveFolderToFolder = (moved_folder_id, target_id) => {
let { config } = this.props;
let { navigation } = config;
// Find the folder and move it to this new folder
let target_folder = PageUtils.getFolderById(navigation, target_folder_id);
let target_folder = PageUtils.getFolderById(navigation, target_id);
if (!target_folder) {
toaster.danger('Only_support_two_level_folders');
return;
@@ -299,6 +309,7 @@ class SidePanel extends Component {
duplicatePage={this.duplicatePage}
onSetFolderId={this.onSetFolderId}
currentPageId={this.props.currentPageId}
addPageInside={this.addPageInside}
/>
{this.state.isShowNewFolderDialog &&
<NewFolderDialog
@@ -310,6 +321,7 @@ class SidePanel extends Component {
<AddNewPageDialog
toggle={this.closeAddNewPageDialog}
onAddNewPage={this.onAddNewPage}
title={gettext('Add page')}
/>
}
</div>
@@ -335,6 +347,7 @@ class SidePanel extends Component {
<AddNewPageDialog
toggle={this.closeAddNewPageDialog}
onAddNewPage={this.onAddNewPage}
title={gettext('Add page')}
/>
}
</div>

View File

@@ -1,31 +1,56 @@
import React from 'react';
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import NavItemIcon from '../view-structure/nav-item-icon';
import './index.css';
// Find the path array from the root to the leaf based on the currentPageId (leaf)
function getPaths(navigation, currentPageId, pages) {
let idPageMap = {};
pages.forEach(page => idPageMap[page.id] = page);
navigation.forEach(item => {
if (!idPageMap[item.id]) {
idPageMap[item.id] = item;
}
});
let pathStr = null;
function runNode(node) {
const newPath = node._path ? (node._path + '-' + node.id) : node.id;
if (node.id === currentPageId) {
pathStr = newPath;
return;
}
if (node.children) {
node.children.forEach(child => {
child._path = newPath;
runNode(child);
});
}
}
let root = {};
root.id = '';
root._path = '';
root.children = navigation;
runNode(root);
if (!pathStr) return [];
return pathStr.split('-').map(id => idPageMap[id]);
}
function WikiTopNav({ config, currentPageId }) {
const { navigation, pages } = config;
const folder = navigation.find(item => {
return item.type === 'folder' && item.children && item.children.find(item => item.id === currentPageId);
});
const page = pages.find(page => page.id === currentPageId);
const paths = getPaths(navigation, currentPageId, pages);
return (
<div className="wiki2-top-nav d-flex">
{folder &&
<>
<div className='wiki2-top-nav-item d-flex'>
<NavItemIcon symbol={'wiki-folder'} disable={true} />
{folder.name}
</div>
<div>/</div>
</>
}
{page &&
<div className='wiki2-top-nav-item d-flex'>
<NavItemIcon symbol={'file'} disable={true} />
{page.name}
</div>
}
{paths.map((item, index) => {
return (
<Fragment key={item.id}>
<div className='wiki2-top-nav-item d-flex'>
<NavItemIcon symbol={item.type === 'folder' ? 'wiki-folder' : 'file'} disable={true} />
{item.name}
</div>
{index !== paths.length - 1 && <div>/</div>}
</Fragment>
);
})}
</div>
);
}

View File

@@ -10,6 +10,7 @@ import wikiAPI from '../../../utils/wiki-api';
import '../css/add-new-page-dialog.css';
const propTypes = {
title: PropTypes.node,
toggle: PropTypes.func.isRequired,
onAddNewPage: PropTypes.func,
};
@@ -95,9 +96,10 @@ class AddNewPageDialog extends React.Component {
};
render() {
const { title } = this.props;
return (
<Modal isOpen={true} toggle={this.toggle} autoFocus={false} className='add-new-page-dialog'>
<ModalHeader toggle={this.toggle}>{gettext('Add page')}</ModalHeader>
<ModalHeader toggle={this.toggle}>{title}</ModalHeader>
<ModalBody className='pr-4'>
<Label>{gettext('Page name')}</Label>
<Input

View File

@@ -1,2 +1,2 @@
export const DRAGGED_FOLDER_MODE = 'view-folder';
export const DRAGGED_VIEW_MODE = 'view';
export const DRAGGED_FOLDER_MODE = 'wiki-folder';
export const DRAGGED_PAGE_MODE = 'wiki-page';

View File

@@ -1,5 +1,5 @@
import { DragSource, DropTarget } from 'react-dnd';
import { DRAGGED_FOLDER_MODE, DRAGGED_VIEW_MODE } from '../constant';
import { DRAGGED_FOLDER_MODE, DRAGGED_PAGE_MODE } from '../constant';
import FolderItem from './folder-item';
const dragSource = {
@@ -20,8 +20,8 @@ const dragSource = {
}
},
isDragging(props, monitor) {
const { folderIndex: currentIndex, draggedRow } = props;
const { idx } = draggedRow;
const { folderIndex: currentIndex, draggedPage } = props;
const { idx } = draggedPage;
return idx > currentIndex;
},
};
@@ -43,7 +43,7 @@ const dropTarget = {
let moveInto = className.includes('dragged-view-over');
// 1. drag source is page
if (sourceRow.mode === DRAGGED_VIEW_MODE) {
if (sourceRow.mode === DRAGGED_PAGE_MODE) {
const sourceFolderId = sourceRow.folderId;
const draggedViewId = sourceRow.data.id;
// 1.1 move page into folder
@@ -93,7 +93,7 @@ const dropCollect = (connect, monitor) => ({
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
draggedRow: monitor.getItem(),
draggedPage: monitor.getItem(),
connect,
monitor,
});

View File

@@ -2,10 +2,9 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import FolderOperationDropdownMenu from './folder-operation-dropdownmenu';
import ViewItem from '../views/view-item';
import DraggedViewItem from '../views/dragged-view-item';
import DraggedFolderItem from './dragged-folder-item';
import ViewEditPopover from '../../view-structure/views/view-edit-popover';
import { FOLDER } from '../../constant';
import NavItemIcon from '../nav-item-icon';
class FolderItem extends Component {
@@ -21,14 +20,9 @@ class FolderItem extends Component {
};
}
onToggleExpandFolder = (e) => {
toggleExpand = (e) => {
e.nativeEvent.stopImmediatePropagation();
this.props.onToggleExpandFolder(this.props.folder.id);
this.forceUpdate();
};
onToggleExpandSubfolder = (subfolderId) => {
this.props.onToggleExpandFolder(subfolderId);
this.props.toggleExpand(this.props.folder.id);
this.forceUpdate();
};
@@ -66,8 +60,8 @@ class FolderItem extends Component {
// }
};
renderFolder = (folder, index, tableGridsLength, isOnlyOneView, id_view_map) => {
const { isEditMode, views, foldersStr } = this.props;
renderFolder = (folder, index, pagesLength, isOnlyOneView, id_view_map) => {
const { isEditMode, views, pathStr } = this.props;
const { id: folderId } = folder;
return (
<DraggedFolderItem
@@ -75,11 +69,11 @@ class FolderItem extends Component {
isEditMode={isEditMode}
folder={folder}
folderIndex={index}
tableGridsLength={tableGridsLength}
pagesLength={pagesLength}
isOnlyOneView={isOnlyOneView}
id_view_map={id_view_map}
renderFolderMenuItems={this.props.renderFolderMenuItems}
onToggleExpandFolder={this.onToggleExpandSubfolder}
toggleExpand={this.props.toggleExpand}
onToggleAddView={this.props.onToggleAddView}
onModifyFolder={this.props.onModifyFolder}
onDeleteFolder={this.props.onDeleteFolder}
@@ -93,45 +87,49 @@ class FolderItem extends Component {
onMoveView={this.props.onMoveView}
moveFolderToFolder={this.props.moveFolderToFolder}
views={views}
foldersStr={foldersStr + '-' + folderId}
pathStr={pathStr + '-' + folderId}
setClassName={this.props.setClassName}
getClassName={this.props.getClassName}
movePageOut={this.props.movePageOut}
layerDragProps={this.props.layerDragProps}
getFolderState={this.props.getFolderState}
getFoldState={this.props.getFoldState}
currentPageId={this.props.currentPageId}
addPageInside={this.props.addPageInside}
/>
);
};
renderView = (view, index, tableGridsLength, isOnlyOneView) => {
const { isEditMode, views, folder, foldersStr } = this.props;
renderView = (view, index, pagesLength, isOnlyOneView) => {
const { isEditMode, views, folder, pathStr } = this.props;
const id = view.id;
if (!views.find(item => item.id === id)) return;
return (
<ViewItem
<DraggedViewItem
key={id}
tableGridsLength={tableGridsLength}
pagesLength={pagesLength}
isOnlyOneView={isOnlyOneView}
infolder={false}
view={views.find(item => item.id === id)}
view={Object.assign({}, views.find(item => item.id === id), view)}
viewIndex={index}
folderId={folder.id}
isEditMode={isEditMode}
renderFolderMenuItems={this.props.renderFolderMenuItems}
duplicatePage={this.props.duplicatePage}
onSetFolderId={this.props.onSetFolderId}
onSelectView={() => this.props.onSelectView(id)}
onSelectView={this.props.onSelectView}
onUpdatePage={this.props.onUpdatePage}
onDeleteView={this.props.onDeleteView.bind(this, id)}
onDeleteView={this.props.onDeleteView}
onMoveViewToFolder={(targetFolderId) => {
this.props.onMoveViewToFolder(folder.id, view.id, targetFolderId);
}}
onMoveView={this.props.onMoveView}
onMoveFolder={this.props.onMoveFolder}
views={views}
foldersStr={foldersStr}
pathStr={pathStr + '-' + view.id}
currentPageId={this.props.currentPageId}
addPageInside={this.props.addPageInside}
getFoldState={this.props.getFoldState}
toggleExpand={this.props.toggleExpand}
/>
);
};
@@ -160,22 +158,9 @@ class FolderItem extends Component {
};
getFolderChildrenHeight = () => {
const { id: folderId, children } = this.props.folder;
const folded = this.props.getFolderState(folderId);
const folded = this.props.getFoldState(this.props.folder.id);
if (folded) return 0;
let height = 0;
children.forEach((child) => {
// just support two levels
const { type, id: childId, children } = child;
if (type === FOLDER) {
height += (this.props.getFolderState(childId) || !Array.isArray(children))
? 40 : (children.length + 1) * 40;
} else {
height += 40;
}
});
return height;
return 'auto';
};
onMouseEnter = () => {
@@ -189,18 +174,18 @@ class FolderItem extends Component {
render() {
const {
connectDropTarget, connectDragPreview, connectDragSource, isOver, canDrop,
isEditMode, folder, tableGridsLength, id_view_map, isOnlyOneView, layerDragProps,
isEditMode, folder, pagesLength, id_view_map, isOnlyOneView, layerDragProps,
} = this.props;
const { isEditing } = this.state;
const { id: folderId, name, children } = folder;
const folded = this.props.getFolderState(folderId);
const folded = this.props.getFoldState(folderId);
let viewEditorId = `folder-item-${folderId}`;
let fn = isEditMode ? connectDragSource : (argu) => {argu;};
return (
<div
className={classnames('view-folder', { 'readonly': !isEditMode })}
ref={ref => this.foldRef = ref}
onClick={this.onToggleExpandFolder}
onClick={this.toggleExpand}
>
{fn(connectDropTarget(
connectDragPreview(
@@ -213,7 +198,7 @@ class FolderItem extends Component {
<div className='folder-main'>
<div
className='folder-content'
style={{ marginLeft: `${(this.props.foldersStr.split('-').length - 1) * 16}px` }}
style={{ marginLeft: `${(this.props.pathStr.split('-').length - 1) * 16}px` }}
id={viewEditorId}
>
{this.state.isMouseEnter ?
@@ -254,7 +239,7 @@ class FolderItem extends Component {
>
{!folded && children &&
children.map((item, index) => {
return item.type === 'folder' ? this.renderFolder(item, index, tableGridsLength, isOnlyOneView, id_view_map) : this.renderView(item, index, tableGridsLength, isOnlyOneView);
return item.type === 'folder' ? this.renderFolder(item, index, pagesLength, isOnlyOneView, id_view_map) : this.renderView(item, index, pagesLength, isOnlyOneView);
})
}
</div>
@@ -267,19 +252,18 @@ FolderItem.propTypes = {
isEditMode: PropTypes.bool,
folder: PropTypes.object,
folderIndex: PropTypes.number,
tableGridsLength: PropTypes.number,
pagesLength: PropTypes.number,
id_view_map: PropTypes.object,
isOver: PropTypes.bool,
canDrop: PropTypes.bool,
isDragging: PropTypes.bool,
draggedRow: PropTypes.object,
connectDropTarget: PropTypes.func,
connectDragPreview: PropTypes.func,
connectDragSource: PropTypes.func,
renderFolderMenuItems: PropTypes.func,
duplicatePage: PropTypes.func,
onSetFolderId: PropTypes.func,
onToggleExpandFolder: PropTypes.func,
toggleExpand: PropTypes.func,
onToggleAddView: PropTypes.func,
onModifyFolder: PropTypes.func,
onDeleteFolder: PropTypes.func,
@@ -292,13 +276,14 @@ FolderItem.propTypes = {
views: PropTypes.array,
onMoveFolder: PropTypes.func,
moveFolderToFolder: PropTypes.func,
foldersStr: PropTypes.string,
pathStr: PropTypes.string,
setClassName: PropTypes.func,
getClassName: PropTypes.func,
movePageOut: PropTypes.func,
layerDragProps: PropTypes.object,
getFolderState: PropTypes.func,
getFoldState: PropTypes.func,
currentPageId: PropTypes.string,
addPageInside: PropTypes.func,
};
export default FolderItem;

View File

@@ -4,9 +4,9 @@ import Icon from '../../../components/icon';
import classNames from 'classnames';
import './nav-item-icon.css';
function NavItemIcon({ symbol, className, disable }) {
function NavItemIcon({ symbol, className, disable, onClick }) {
return (
<div className={classNames('nav-item-icon', {'nav-item-icon-disable': disable})}>
<div onClick={onClick} className={classNames('nav-item-icon', {'nav-item-icon-disable': disable})}>
<Icon symbol={symbol} className={className} />
</div>
);
@@ -16,6 +16,7 @@ NavItemIcon.propTypes = {
symbol: PropTypes.string.isRequired,
className: PropTypes.string,
disable: PropTypes.bool,
onClick: PropTypes.func,
};
export default NavItemIcon;

View File

@@ -2,34 +2,57 @@ import { FOLDER, PAGE } from '../constant';
export default class PageUtils {
static addPage(navigation, page_id, parentId) {
if (!parentId) {
navigation.push({ id: page_id, type: PAGE });
} else {
navigation.forEach(item => {
this._addPageRecursion(page_id, item, parentId);
});
}
}
static _addPageRecursion(page_id, item, parentId) {
if (!Array.isArray(item.children)) {
item.children = [];
}
if (item.id === parentId) {
item.children.push({ id: page_id, type: PAGE });
return true;
}
item.children.forEach(item => {
this._addPageRecursion(page_id, item, parentId);
});
}
static deletePage(navigation, page_id) {
const pageIndex = navigation.findIndex(item => item.id === page_id);
if (pageIndex > -1) {
navigation.splice(pageIndex, 1);
return true;
}
navigation.forEach(item => {
this._deletePageRecursion(item, page_id);
});
}
static _deletePageRecursion(item, page_id) {
if (!item || !Array.isArray(item.children)) return;
let pageIndex = item.children.findIndex(item => item.id === page_id);
if (pageIndex > -1) {
item.children.splice(pageIndex, 1);
return true;
}
item.children.forEach(item => {
this._deletePageRecursion(item, page_id);
});
}
static getPageById = (pages, page_id) => {
if (!page_id || !Array.isArray(pages)) return null;
return pages.find((page) => page.id === page_id) || null;
};
static getPageFromNavigationById = (navigation, page_id) => {
if (!page_id || !Array.isArray(navigation)) return null;
let page_index = navigation.indexOf(item => item.id === page_id);
if (page_index > -1) {
return navigation[page_index];
}
for (let i = 0; i < navigation.length; i++) {
const currNavigation = navigation[i];
if (currNavigation.id === page_id) {
return currNavigation;
}
if (Array.isArray(currNavigation.children)) {
for (let j = 0; j < currNavigation.children.length; j++) {
if (currNavigation.children[j].id === page_id) {
return currNavigation.children[j];
}
}
}
}
return null;
};
static getPageIndexById = (pageId, pages) => {
return pages.findIndex(page => page.id === pageId);
};
@@ -102,39 +125,12 @@ export default class PageUtils {
});
}
static addPage(navigation, page_id, target_folder_id) {
// 1. Add pages directly under the root directory
if (!target_folder_id) {
navigation.push({ id: page_id, type: PAGE });
return;
} else {
// 2. Add pages to the folder
navigation.forEach(item => {
if (item.type === FOLDER) {
this._addPageInFolder(page_id, item, target_folder_id);
}
});
}
}
static _addPageInFolder(page_id, folder, target_folder_id) {
if (folder.id === target_folder_id) {
folder.children.push({ id: page_id, type: PAGE });
return true;
}
folder.children.forEach(item => {
if (item.type === FOLDER) {
this._addPageInFolder(page_id, item, target_folder_id);
}
});
}
static insertPage(navigation, page_id, target_page_id, folder_id, move_position) {
static insertPage(navigation, page_id, target_page_id, target_id, move_position) {
// 1. No folder, insert page in root directory
if (!folder_id) {
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, folder_id);
this.addPage(navigation, page_id, target_id);
return true;
}
if (move_position === 'move_below') {
@@ -146,13 +142,13 @@ export default class PageUtils {
// 2. If there is a folder, find it and insert it
navigation.forEach(item => {
if (item.type === FOLDER) {
this._insertPageIntoFolder(item, page_id, target_page_id, folder_id, move_position);
this._insertPageIntoFolder(item, page_id, target_page_id, target_id, move_position);
}
});
}
static _insertPageIntoFolder(folder, page_id, target_page_id, folder_id, move_position) {
if (folder.id === folder_id) {
static _insertPageIntoFolder(folder, page_id, target_page_id, target_id, move_position) {
if (folder.id === target_id) {
let insertIndex = target_page_id ? folder.children.findIndex(item => item.id === target_page_id) : -1;
if (move_position === 'move_below') {
insertIndex++;
@@ -162,70 +158,118 @@ export default class PageUtils {
}
folder.children.forEach(item => {
if (item.type === FOLDER) {
this._insertPageIntoFolder(item, page_id, target_page_id, folder_id, move_position);
this._insertPageIntoFolder(item, page_id, target_page_id, target_id, move_position);
}
});
}
// Move the page to the top or bottom of the folder
static insertPageOut(navigation, page_id, folder_id, move_position) {
let indexOffset = 0;
if (move_position === 'move_below') {
indexOffset++;
// move page into folder or page(已解决)
static movePage(navigation, moved_page_id, target_page_id, source_id, target_id, move_position) {
let movedPage = null;
function _cutPageRecursion(item, page_id) {
if (!item || !Array.isArray(item.children) || movedPage) return;
let pageIndex = item.children.findIndex(item => item.id === page_id);
if (pageIndex > -1) {
movedPage = item.children.splice(pageIndex, 1)[0];
} else {
item.children.forEach(item => {
_cutPageRecursion(item, page_id);
});
}
}
let page = { id: page_id, type: PAGE };
let folder_index = this.getFolderIndexById(navigation, folder_id);
if (folder_index > -1) {
navigation.splice(folder_index + indexOffset, 0, page);
} else {
navigation.forEach((item) => {
if (item.type === FOLDER) {
let folder_index = this.getFolderIndexById(item.children, folder_id);
if (folder_index > -1) {
item.children.splice(folder_index + indexOffset, 0, page);
}
function _cutPage(navigation, page_id) {
const pageIndex = navigation.findIndex(item => item.id === page_id);
if (pageIndex > -1) {
movedPage = navigation.splice(pageIndex, 1)[0];
} else {
navigation.forEach(item => {
_cutPageRecursion(item, page_id);
});
}
}
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++;
}
item.children.splice(insertIndex, 0, movedPage);
return;
}
item.children.forEach(item => {
_insertPageRecursion(item, page_id, target_page_id, target_id, move_position);
});
}
}
static deletePage(navigation, page_id) {
// 1. Delete pages directly under the root directory
const pageIndex = navigation.findIndex(item => item.id === page_id);
if (pageIndex > -1) {
navigation.splice(pageIndex, 1);
return true;
}
// 2. Delete Page in Folder
navigation.forEach(item => {
if (item.type === FOLDER) {
this._deletePageInFolder(item, page_id);
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;
}
});
}
static _deletePageInFolder(folder, page_id) {
let pageIndex = folder.children.findIndex(item => item.id === page_id);
if (pageIndex > -1) {
folder.children.splice(pageIndex, 1);
return true;
navigation.forEach(item => {
_insertPageRecursion(item, page_id, target_page_id, target_id, move_position);
});
}
folder.children.forEach(item => {
if (item.type === FOLDER) {
this._deletePageInFolder(item, page_id);
_cutPage(navigation, moved_page_id);
_insertPage(navigation, moved_page_id, target_page_id, target_id, move_position);
}
// move Page Outside Folder
static movePageOut(navigation, moved_page_id, source_id, target_id, move_position) {
let movedPage = null;
function getFolderIndexById(list, folder_id) {
if (!folder_id || !Array.isArray(list)) return -1;
return list.findIndex(folder => folder.id === folder_id);
}
// Move the page to the top or bottom of the folder
function _insertPage(navigation, page_id, target_id, move_position) {
let indexOffset = 0;
if (move_position === 'move_below') {
indexOffset++;
}
});
}
// movePageintoFolder
static movePage(navigation, moved_page_id, target_page_id, source_folder_id, target_folder_id, move_position) {
this.deletePage(navigation, moved_page_id, source_folder_id);
this.insertPage(navigation, moved_page_id, target_page_id, target_folder_id, move_position);
}
// movePageOutsideFolder
static movePageOut(navigation, moved_page_id, source_folder_id, target_folder_id, move_position) {
this.deletePage(navigation, moved_page_id, source_folder_id);
this.insertPageOut(navigation, moved_page_id, target_folder_id, move_position);
let folder_index = getFolderIndexById(navigation, target_id);
if (folder_index > -1) {
navigation.splice(folder_index + indexOffset, 0, movedPage);
} else {
navigation.forEach((item) => {
if (item.type === FOLDER) {
let folder_index = getFolderIndexById(item.children, target_id);
if (folder_index > -1) {
item.children.splice(folder_index + indexOffset, 0, movedPage);
}
}
});
}
}
function _cutPageRecursion(item, page_id) {
if (!item || !Array.isArray(item.children) || movedPage) return;
let pageIndex = item.children.findIndex(item => item.id === page_id);
if (pageIndex > -1) {
movedPage = item.children.splice(pageIndex, 1)[0];
} else {
item.children.forEach(item => {
_cutPageRecursion(item, page_id);
});
}
}
function _cutPage(navigation, page_id) {
const pageIndex = navigation.findIndex(item => item.id === page_id);
if (pageIndex > -1) {
movedPage = navigation.splice(pageIndex, 1)[0];
} else {
navigation.forEach(item => {
_cutPageRecursion(item, page_id);
});
}
}
_cutPage(navigation, moved_page_id, source_id);
_insertPage(navigation, moved_page_id, target_id, move_position);
}
}

View File

@@ -1,11 +1,10 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { DropdownItem } from 'reactstrap';
import { DropTarget, DragLayer } from 'react-dnd';
import html5DragDropContext from './html5DragDropContext';
import DraggedFolderItem from './folders/dragged-folder-item';
import ViewItem from './views/view-item';
import DraggedViewItem from './views/dragged-view-item';
import ViewStructureFooter from './view-structure-footer';
import { repoID } from '../../../utils/constants';
@@ -32,35 +31,36 @@ class ViewStructure extends Component {
duplicatePage: PropTypes.func,
onSetFolderId: PropTypes.func,
currentPageId: PropTypes.string,
addPageInside: PropTypes.func,
};
constructor(props) {
super(props);
this.folderClassNameCache = '';
this.idFoldedStatusMap = this.getFoldedFoldersFromBase();
this.idFoldedStatusMap = this.getFoldedFromLocal();
}
getFoldedFoldersFromBase = () => {
const foldedFolders = window.localStorage.getItem(`wiki-folded-folders-${repoID}`);
return foldedFolders ? JSON.parse(foldedFolders) : {};
getFoldedFromLocal = () => {
const items = window.localStorage.getItem(`wiki-folded-${repoID}`);
return items ? JSON.parse(items) : {};
};
setFoldedFolders = (foldedFolders) => {
window.localStorage.setItem(`wiki-folded-folders-${repoID}`, JSON.stringify(foldedFolders));
saveFoldedToLocal = (items) => {
window.localStorage.setItem(`wiki-folded-${repoID}`, JSON.stringify(items));
};
getFolderState = (folderId) => {
getFoldState = (folderId) => {
return this.idFoldedStatusMap[folderId];
};
onToggleExpandFolder = (folderId) => {
const idFoldedStatusMap = this.getFoldedFoldersFromBase();
toggleExpand = (folderId) => {
const idFoldedStatusMap = this.getFoldedFromLocal();
if (idFoldedStatusMap[folderId]) {
delete idFoldedStatusMap[folderId];
} else {
idFoldedStatusMap[folderId] = true;
}
this.setFoldedFolders(idFoldedStatusMap);
this.saveFoldedToLocal(idFoldedStatusMap);
this.idFoldedStatusMap = idFoldedStatusMap;
};
@@ -96,7 +96,7 @@ class ViewStructure extends Component {
return this.folderClassNameCache;
};
renderFolder = (folder, index, tableGridsLength, isOnlyOneView, id_view_map, layerDragProps) => {
renderFolder = (folder, index, pagesLength, isOnlyOneView, id_view_map, layerDragProps) => {
const { isEditMode, views } = this.props;
const folderId = folder.id;
return (
@@ -105,11 +105,11 @@ class ViewStructure extends Component {
isEditMode={isEditMode}
folder={folder}
folderIndex={index}
tableGridsLength={tableGridsLength}
pagesLength={pagesLength}
isOnlyOneView={isOnlyOneView}
id_view_map={id_view_map}
renderFolderMenuItems={this.renderFolderMenuItems}
onToggleExpandFolder={this.onToggleExpandFolder}
toggleExpand={this.toggleExpand}
onToggleAddView={this.props.onToggleAddView}
onDeleteFolder={this.props.onDeleteFolder}
onMoveFolder={this.props.onMoveFolder}
@@ -122,30 +122,31 @@ class ViewStructure extends Component {
onMoveView={this.props.onMoveView}
views={views}
moveFolderToFolder={this.props.moveFolderToFolder}
foldersStr={folderId}
pathStr={folderId}
layerDragProps={layerDragProps}
setClassName={this.setClassName}
getClassName={this.getClassName}
movePageOut={this.props.movePageOut}
onModifyFolder={this.props.onModifyFolder}
getFolderState={this.getFolderState}
getFoldState={this.getFoldState}
currentPageId={this.props.currentPageId}
addPageInside={this.props.addPageInside}
/>
);
};
renderView = (view, index, tableGridsLength, isOnlyOneView, id_view_map) => {
renderView = (view, index, pagesLength, isOnlyOneView, id_view_map) => {
const { isEditMode, views } = this.props;
const id = view.id;
if (!views.find(item => item.id === id)) return;
const folderId = null; // Pages in the root directory, no folders, use null
return (
<ViewItem
<DraggedViewItem
key={id}
tableGridsLength={tableGridsLength}
pagesLength={pagesLength}
isOnlyOneView={isOnlyOneView}
infolder={false}
view={views.find(item => item.id === id)}
view={Object.assign({}, views.find(item => item.id === id), view)}
views={views}
viewIndex={index}
folderId={folderId}
@@ -153,16 +154,19 @@ class ViewStructure extends Component {
renderFolderMenuItems={this.renderFolderMenuItems}
duplicatePage={this.props.duplicatePage}
onSetFolderId={this.props.onSetFolderId}
onSelectView={() => this.props.onSelectView(id)}
onSelectView={this.props.onSelectView}
onUpdatePage={this.props.onUpdatePage}
onDeleteView={this.props.onDeleteView.bind(this, id)}
onDeleteView={this.props.onDeleteView}
onMoveViewToFolder={(targetFolderId) => {
this.onMoveViewToFolder(folderId, view.id, targetFolderId);
}}
onMoveView={this.props.onMoveView}
onMoveFolder={this.props.onMoveFolder}
foldersStr={''}
pathStr={view.id}
currentPageId={this.props.currentPageId}
addPageInside={this.props.addPageInside}
getFoldState={this.getFoldState}
toggleExpand={this.toggleExpand}
/>
);
};
@@ -174,7 +178,7 @@ class ViewStructure extends Component {
if (views.length === 1) {
isOnlyOneView = true;
}
const tableGridsLength = views.length;
const pagesLength = views.length;
let id_view_map = {};
views.forEach(view => id_view_map[view.id] = view);
const style = { maxHeight: isEditMode ? 'calc(100% - 40px)' : '100%' };
@@ -182,8 +186,8 @@ class ViewStructure extends Component {
<div className='view-structure-body' style={style}>
{navigation.map((item, index) => {
return item.type === 'folder' ?
this.renderFolder(item, index, tableGridsLength, isOnlyOneView, id_view_map, layerDragProps) :
this.renderView(item, index, tableGridsLength, isOnlyOneView, id_view_map);
this.renderFolder(item, index, pagesLength, isOnlyOneView, id_view_map, layerDragProps) :
this.renderView(item, index, pagesLength, isOnlyOneView, id_view_map);
})}
</div>
);
@@ -204,15 +208,10 @@ class ViewStructure extends Component {
connectDropTarget: connect.dropTarget()
}))(DragLayer(this.collect)(this.renderStructureBody))
);
const isSpecialInstance = false;
const isDarkMode = false;
return (
<div className={classnames('view-structure',
{ 'view-structure-dark': isDarkMode },
{ 'view-structure-light': !isDarkMode },
)}>
<div className='view-structure view-structure-light'>
<StructureBody />
{(this.props.isEditMode && !isSpecialInstance) &&
{(this.props.isEditMode) &&
<ViewStructureFooter
onToggleAddView={this.props.onToggleAddView}
onToggleAddFolder={this.props.onToggleAddFolder}

View File

@@ -0,0 +1,91 @@
import { DragSource, DropTarget } from 'react-dnd';
import { DRAGGED_FOLDER_MODE, DRAGGED_PAGE_MODE } from '../constant';
import ViewItem from './view-item';
const dragSource = {
beginDrag: props => {
return {
idx: props.viewIndex,
data: { ...props.view, index: props.viewIndex },
folderId: props.folderId,
mode: DRAGGED_PAGE_MODE,
};
},
endDrag(props, monitor) {
const viewSource = monitor.getItem();
const didDrop = monitor.didDrop();
let viewTarget = {};
if (!didDrop) {
return { viewSource, viewTarget };
}
},
isDragging(props) {
const { draggedPage, viewIndex: targetIndex } = props;
const { idx } = draggedPage;
return idx > targetIndex;
}
};
const dropTarget = {
drop(props, monitor) {
const sourceRow = monitor.getItem();
// 1 drag page
if (sourceRow.mode === DRAGGED_PAGE_MODE) {
const { infolder, viewIndex: targetIndex, view: targetView, folderId: targetFolderId } = props;
const sourceFolderId = sourceRow.folderId;
const draggedViewId = sourceRow.data.id;
const targetViewId = targetView.id;
if (draggedViewId !== targetViewId) {
const sourceIndex = sourceRow.idx;
let move_position;
if (infolder) {
move_position = 'move_below';
} else {
move_position = sourceIndex > targetIndex ? 'move_above' : 'move_below';
}
props.onMoveView({
moved_view_id: draggedViewId,
target_view_id: targetViewId,
source_view_folder_id: sourceFolderId,
target_view_folder_id: targetFolderId,
move_position,
});
}
return;
}
// 1 drag folder
if (sourceRow.mode === DRAGGED_FOLDER_MODE) {
const { viewIndex: targetIndex, view: targetView } = props;
const draggedFolderId = sourceRow.data.id;
const targetViewId = targetView.id;
const sourceIndex = sourceRow.idx;
// Drag the parent folder to the child page, return
if (props.pathStr.split('-').includes(draggedFolderId)) return;
props.onMoveFolder(
draggedFolderId,
targetViewId,
sourceIndex > targetIndex ? 'move_above' : 'move_below',
);
return;
}
}
};
const dragCollect = (connect, monitor) => ({
connectDragSource: connect.dragSource(),
connectDragPreview: connect.dragPreview(),
isDragging: monitor.isDragging()
});
const dropCollect = (connect, monitor) => ({
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
draggedPage: monitor.getItem()
});
export default DropTarget('ViewStructure', dropTarget, dropCollect)(
DragSource('ViewStructure', dragSource, dragCollect)(ViewItem)
);

View File

@@ -8,15 +8,15 @@ const DropTargetTopView = (Placeholder) => class extends React.Component {
connectDropTarget: PropTypes.func.isRequired,
isOver: PropTypes.bool,
canDrop: PropTypes.bool,
draggedRow: PropTypes.object,
draggedPage: PropTypes.object,
targetFolderId: PropTypes.string,
targetViewId: PropTypes.string,
onMoveView: PropTypes.func,
};
render() {
const { connectDropTarget, isOver, canDrop, draggedRow } = this.props;
const { mode } = draggedRow || {};
const { connectDropTarget, isOver, canDrop, draggedPage } = this.props;
const { mode } = draggedPage || {};
if (mode !== 'view') {
return null;
}
@@ -61,7 +61,7 @@ function collect(connect, monitor) {
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
draggedRow: monitor.getItem(),
draggedPage: monitor.getItem(),
};
}

View File

@@ -10,7 +10,7 @@ export default class PageDropdownMenu extends Component {
static propTypes = {
view: PropTypes.object.isRequired,
views: PropTypes.array,
tableGridsLength: PropTypes.number,
pagesLength: PropTypes.number,
folderId: PropTypes.string,
canDelete: PropTypes.bool,
canDuplicate: PropTypes.bool,
@@ -105,7 +105,7 @@ export default class PageDropdownMenu extends Component {
render() {
const {
folderId, canDelete, canDuplicate, renderFolderMenuItems, tableGridsLength, isOnlyOneView,
folderId, canDelete, canDuplicate, renderFolderMenuItems, pagesLength, isOnlyOneView,
} = this.props;
const folderMenuItems = renderFolderMenuItems && renderFolderMenuItems({ currentFolderId: folderId, onMoveViewToFolder: this.onMoveViewToFolder });
return (
@@ -132,7 +132,7 @@ export default class PageDropdownMenu extends Component {
<span className="item-text">{gettext('Duplicate page')}</span>
</DropdownItem>
}
{(isOnlyOneView || tableGridsLength === 1 || !canDelete) ? '' : (
{(isOnlyOneView || pagesLength === 1 || !canDelete) ? '' : (
<DropdownItem onClick={this.onDeleteView}>
<Icon symbol={'delete'}/>
<span className="item-text">{gettext('Delete page')}</span>

View File

@@ -1,100 +1,14 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { DragSource, DropTarget } from 'react-dnd';
import ViewEditPopover from './view-edit-popover';
import PageDropdownMenu from './page-dropdownmenu';
import DeleteDialog from './delete-dialog';
import { DRAGGED_FOLDER_MODE, DRAGGED_VIEW_MODE } from '../constant';
import { gettext } from '../../../../utils/constants';
import AddNewPageDialog from '../add-new-page-dialog';
import Icon from '../../../../components/icon';
import NavItemIcon from '../nav-item-icon';
const dragSource = {
beginDrag: props => {
return {
idx: props.viewIndex,
data: { ...props.view, index: props.viewIndex },
folderId: props.folderId,
mode: DRAGGED_VIEW_MODE,
};
},
endDrag(props, monitor) {
const viewSource = monitor.getItem();
const didDrop = monitor.didDrop();
let viewTarget = {};
if (!didDrop) {
return { viewSource, viewTarget };
}
},
isDragging(props) {
const { draggedRow, infolder, viewIndex: targetIndex } = props;
if (infolder) {
return false;
}
const { idx } = draggedRow;
return idx > targetIndex;
}
};
const dropTarget = {
drop(props, monitor) {
const sourceRow = monitor.getItem();
// 1 drag page
if (sourceRow.mode === DRAGGED_VIEW_MODE) {
const { infolder, viewIndex: targetIndex, view: targetView, folderId: targetFolderId } = props;
const sourceFolderId = sourceRow.folderId;
const draggedViewId = sourceRow.data.id;
const targetViewId = targetView.id;
if (draggedViewId !== targetViewId) {
const sourceIndex = sourceRow.idx;
let move_position;
if (infolder) {
move_position = 'move_below';
} else {
move_position = sourceIndex > targetIndex ? 'move_above' : 'move_below';
}
props.onMoveView({
moved_view_id: draggedViewId,
target_view_id: targetViewId,
source_view_folder_id: sourceFolderId,
target_view_folder_id: targetFolderId,
move_position,
});
}
return;
}
// 1 drag folder
if (sourceRow.mode === DRAGGED_FOLDER_MODE) {
const { viewIndex: targetIndex, view: targetView } = props;
const draggedFolderId = sourceRow.data.id;
const targetViewId = targetView.id;
const sourceIndex = sourceRow.idx;
// Drag the parent folder to the child page, return
if (props.foldersStr.split('-').includes(draggedFolderId)) return;
props.onMoveFolder(
draggedFolderId,
targetViewId,
sourceIndex > targetIndex ? 'move_above' : 'move_below',
);
return;
}
}
};
const dragCollect = (connect, monitor) => ({
connectDragSource: connect.dragSource(),
connectDragPreview: connect.dragPreview(),
isDragging: monitor.isDragging()
});
const dropCollect = (connect, monitor) => ({
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
draggedRow: monitor.getItem()
});
import DraggedViewItem from '../views/dragged-view-item';
class ViewItem extends Component {
@@ -104,18 +18,26 @@ class ViewItem extends Component {
isShowViewEditor: false,
isShowViewOperationDropdown: false,
isShowDeleteDialog: false,
isShowInsertPage: false,
viewName: props.view.name || '',
viewIcon: props.view.icon,
isSelected: props.currentPageId === props.view.id,
isMouseEnter: false,
};
this.viewItemRef = React.createRef();
}
onMouseEnter = () => {
this.setState({ isMouseEnter: true });
if (this.state.isSelected) return;
};
onMouseMove = () => {
if (!this.state.isMouseEnter) this.setState({ isMouseEnter: true });
};
onMouseLeave = () => {
this.setState({ isMouseEnter: false });
if (this.state.isSelected) return;
};
@@ -137,6 +59,10 @@ class ViewItem extends Component {
});
};
toggleInsertPage = () => {
this.setState({ isShowInsertPage: !this.state.isShowInsertPage });
};
saveViewProperties = () => {
const { name, icon, id } = this.props.view;
const { viewIcon } = this.state;
@@ -198,15 +124,71 @@ class ViewItem extends Component {
window.seafile['docUuid'] = docUuid;
};
getFolderChildrenHeight = () => {
const folded = this.props.getFoldState(this.props.view.id);
if (folded) return 0;
return 'auto';
};
onClickFolderChildren = (e) => {
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();
};
renderView = (view, index, pagesLength, isOnlyOneView) => {
const { isEditMode, views, folderId, pathStr } = this.props;
const id = view.id;
if (!views.find(item => item.id === id)) return;
return (
<DraggedViewItem
key={id}
pagesLength={pagesLength}
isOnlyOneView={isOnlyOneView}
infolder={false}
view={Object.assign({}, views.find(item => item.id === id), view)}
viewIndex={index}
folderId={folderId}
isEditMode={isEditMode}
renderFolderMenuItems={this.props.renderFolderMenuItems}
duplicatePage={this.props.duplicatePage}
onSetFolderId={this.props.onSetFolderId}
onSelectView={this.props.onSelectView}
onUpdatePage={this.props.onUpdatePage}
onDeleteView={this.props.onDeleteView}
onMoveViewToFolder={(targetFolderId) => {
this.props.onMoveViewToFolder(folderId, view.id, targetFolderId);
}}
onMoveView={this.props.onMoveView}
onMoveFolder={this.props.onMoveFolder}
views={views}
pathStr={pathStr + '-' + view.id}
currentPageId={this.props.currentPageId}
addPageInside={this.props.addPageInside}
getFoldState={this.props.getFoldState}
toggleExpand={this.props.toggleExpand}
/>
);
};
toggleExpand = (e) => {
e.nativeEvent.stopImmediatePropagation();
this.props.toggleExpand(this.props.view.id);
this.forceUpdate();
};
onAddNewPage = (newPage) => {
const { view } = this.props;
this.props.addPageInside(Object.assign({ parentPageId: view.id }, newPage));
};
render() {
const {
connectDragSource, connectDragPreview, connectDropTarget, isOver, canDrop, isDragging,
infolder, view, tableGridsLength, isEditMode, folderId, isOnlyOneView, foldersStr,
infolder, view, pagesLength, isEditMode, folderId, isOnlyOneView, pathStr,
} = this.props;
const { isShowViewEditor, viewName, viewIcon, isSelected } = this.state;
const isOverView = isOver && canDrop;
if (isSelected) this.setDocUuid(view.docUuid);
const isSpecialInstance = false;
let viewCanDropTop;
let viewCanDrop;
@@ -219,72 +201,116 @@ class ViewItem extends Component {
}
let viewEditorId = `view-editor-${view.id}`;
let fn = isEditMode ? connectDragSource : (argu) => {argu;};
let childNumber = Array.isArray(view.children) ? view.children.length : 0;
return fn(connectDropTarget(
connectDragPreview(
<div
className={classnames('view-item', 'view',
{ 'selected-view': isSelected },
{ 'view-can-drop-top': viewCanDropTop },
{ 'view-can-drop': viewCanDrop },
{ 'readonly': !isEditMode },
)}
ref={ref => this.viewItemRef = ref}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
id={viewEditorId}
>
<div className="view-item-main" onClick={isShowViewEditor ? () => {} : this.props.onSelectView}>
<div className='view-content' style={foldersStr ? { marginLeft: `${(foldersStr.split('-').length) * 24}px` } : {}}>
<NavItemIcon symbol={'file'} disable={true} />
{/* {this.renderIcon(view.icon)} */}
<span className="view-title text-truncate" title={view.name}>{view.name}</span>
{isShowViewEditor && (
<ViewEditPopover
viewName={viewName}
viewIcon={viewIcon}
viewEditorId={viewEditorId}
onChangeName={this.onChangeName}
onChangeIcon={this.onChangeIcon}
toggleViewEditor={this.toggleViewEditor}
/>
)}
</div>
</div>
<div className="d-flex">
{isEditMode &&
<div className="more-view-operation" onClick={this.onViewOperationDropdownToggle}>
<Icon symbol={'more-level'}/>
{this.state.isShowViewOperationDropdown &&
<PageDropdownMenu
view={view}
views={this.props.views}
tableGridsLength={tableGridsLength}
isOnlyOneView={isOnlyOneView}
folderId={folderId}
canDelete={!isSpecialInstance}
canDuplicate={!isSpecialInstance}
toggle={this.onViewOperationDropdownToggle}
renderFolderMenuItems={this.props.renderFolderMenuItems}
toggleViewEditor={this.toggleViewEditor}
duplicatePage={this.props.duplicatePage}
onSetFolderId={this.props.onSetFolderId}
onDeleteView={this.openDeleteDialog}
onMoveViewToFolder={this.props.onMoveViewToFolder}
const folded = this.props.getFoldState(view.id);
return (
<div>
{
fn(connectDropTarget(
connectDragPreview(
<div
className={classnames('view-item', 'view',
{ 'selected-view': isSelected },
{ 'view-can-drop-top': viewCanDropTop },
{ 'view-can-drop': viewCanDrop },
{ 'readonly': !isEditMode },
)}
ref={ref => this.viewItemRef = ref}
onMouseEnter={this.onMouseEnter}
onMouseMove={this.onMouseMove}
onMouseLeave={this.onMouseLeave}
id={viewEditorId}
>
<div className="view-item-main" onClick={isShowViewEditor ? () => {} : (e) => this.props.onSelectView(view.id)}>
<div className='view-content' style={pathStr ? { marginLeft: `${(pathStr.split('-').length - 1) * 24}px` } : {}}>
{childNumber === 0 &&
<NavItemIcon symbol={'file'} disable={true} />
}
{(!this.state.isMouseEnter && childNumber > 0) &&
<NavItemIcon symbol={'files'} disable={true} />
}
{(this.state.isMouseEnter && childNumber > 0) &&
<NavItemIcon
className="icon-expand-folder"
symbol={folded ? 'right-slide' : 'drop-down'}
onClick={this.toggleExpand}
/>
}
{/* {this.renderIcon(view.icon)} */}
<span className="view-title text-truncate" title={view.name}>{view.name}</span>
{isShowViewEditor && (
<ViewEditPopover
viewName={viewName}
viewIcon={viewIcon}
viewEditorId={viewEditorId}
onChangeName={this.onChangeName}
onChangeIcon={this.onChangeIcon}
toggleViewEditor={this.toggleViewEditor}
/>
)}
</div>
</div>
<div className="d-flex">
{isEditMode &&
<>
<div className="more-view-operation" onClick={this.onViewOperationDropdownToggle}>
<Icon symbol={'more-level'}/>
{this.state.isShowViewOperationDropdown &&
<PageDropdownMenu
view={view}
views={this.props.views}
pagesLength={pagesLength}
isOnlyOneView={isOnlyOneView}
folderId={folderId}
canDelete={true}
canDuplicate={true}
toggle={this.onViewOperationDropdownToggle}
renderFolderMenuItems={this.props.renderFolderMenuItems}
toggleViewEditor={this.toggleViewEditor}
duplicatePage={this.props.duplicatePage}
onSetFolderId={this.props.onSetFolderId}
onDeleteView={this.openDeleteDialog}
onMoveViewToFolder={this.props.onMoveViewToFolder}
/>
}
</div>
<div className="ml-2" onClick={this.toggleInsertPage}>
<span className='fas fa-plus'></span>
</div>
</>
}
</div>
{this.state.isShowDeleteDialog &&
<DeleteDialog
closeDeleteDialog={this.closeDeleteDialog}
handleSubmit={this.props.onDeleteView.bind(this, view.id)}
/>
}
{this.state.isShowInsertPage &&
<AddNewPageDialog
toggle={this.toggleInsertPage}
onAddNewPage={this.onAddNewPage}
title={gettext('Add page inside')}
/>
}
</div>
}
</div>
{this.state.isShowDeleteDialog &&
<DeleteDialog
closeDeleteDialog={this.closeDeleteDialog}
handleSubmit={this.props.onDeleteView}
/>
)
))
}
<div
className="view-folder-children"
style={{ height: this.getFolderChildrenHeight() }}
onClick={this.onClickFolderChildren}
>
{view.children &&
view.children.map((item, index) => {
return this.renderView(item, index, pagesLength, isOnlyOneView);
})
}
</div>
)
));
</div>
);
}
}
@@ -292,14 +318,15 @@ ViewItem.propTypes = {
isOver: PropTypes.bool,
canDrop: PropTypes.bool,
isDragging: PropTypes.bool,
draggedRow: PropTypes.object,
draggedPage: PropTypes.object,
isEditMode: PropTypes.bool,
infolder: PropTypes.bool,
view: PropTypes.object,
folder: PropTypes.object,
views: PropTypes.array,
viewIndex: PropTypes.number,
folderId: PropTypes.string,
tableGridsLength: PropTypes.number,
pagesLength: PropTypes.number,
connectDragSource: PropTypes.func,
connectDragPreview: PropTypes.func,
connectDropTarget: PropTypes.func,
@@ -313,10 +340,11 @@ ViewItem.propTypes = {
onMoveView: PropTypes.func,
isOnlyOneView: PropTypes.bool,
onMoveFolder: PropTypes.func,
foldersStr: PropTypes.string,
pathStr: PropTypes.string,
currentPageId: PropTypes.string,
addPageInside: PropTypes.func,
getFoldState: PropTypes.func,
toggleExpand: PropTypes.func,
};
export default DropTarget('ViewStructure', dropTarget, dropCollect)(
DragSource('ViewStructure', dragSource, dragCollect)(ViewItem)
);
export default ViewItem;

View File

@@ -55,10 +55,6 @@ class Wikis extends Component {
errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
});
});
this.setState({
loading: false,
wikis: wikis
});
}).catch((error) => {
this.setState({
loading: false,