1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-09 02:42:47 +00:00

12.0 refactor wiki folder (#6290)

* 01 remove wiki folder

* remove folder

* 03 remove wiki folder css

* 04 optimise codes
This commit is contained in:
Michael An
2024-07-03 17:57:10 +08:00
committed by GitHub
parent 8b6abc7855
commit 17114eb2fc
20 changed files with 119 additions and 1515 deletions

View File

@@ -1,24 +0,0 @@
<?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:#FFFFFF;}
.st1{fill:#949494;}
</style>
<g>
<path id="path-1" class="st0" d="M28.8,12.8H3.2v-8c0-0.9,0.5-1.6,1.4-1.6H10c0.6,0,1.2,0.3,1.4,0.9l1.8,3.7
c0.5,1.1,1.7,1.8,2.9,1.8H27c0.9,0,1.8,0.7,1.8,1.6V12.8z M28.8,27.2c0,0.9-0.9,1.6-1.8,1.6H4.6c-0.9,0-1.4-0.7-1.4-1.6V16h25.6
V27.2z M28.6,6.4H17.8c-1.2,0-2.3-0.7-2.9-1.8l-1.4-2.9C13,0.7,11.9,0,10.6,0H3C1.3,0,0,1.4,0,3.2v25.6C0,30.6,1.3,32,3,32h25.6
c1.8,0,3.4-1.4,3.4-3.2V9.6C32,7.8,30.4,6.4,28.6,6.4L28.6,6.4z"/>
</g>
<title>folder</title>
<g id="folder">
<g>
<path id="path-1_1_" class="st1" d="M28.8,12.8H3.2v-8c0-0.9,0.5-1.6,1.4-1.6H10c0.6,0,1.2,0.3,1.4,0.9l1.8,3.7
c0.5,1.1,1.7,1.8,2.9,1.8H27c0.9,0,1.8,0.7,1.8,1.6V12.8z M28.8,27.2c0,0.9-0.9,1.6-1.8,1.6H4.6c-0.9,0-1.4-0.7-1.4-1.6V16h25.6
V27.2z M28.6,6.4H17.8c-1.2,0-2.3-0.7-2.9-1.8l-1.4-2.9C13,0.7,11.9,0,10.6,0H3C1.3,0,0,1.4,0,3.2v25.6C0,30.6,1.3,32,3,32h25.6
c1.8,0,3.4-1.4,3.4-3.2V9.6C32,7.8,30.4,6.4,28.6,6.4L28.6,6.4z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,4 +0,0 @@
const FOLDER = 'folder';
const PAGE = 'page';
export { FOLDER, PAGE };

View File

@@ -15,14 +15,11 @@
padding-bottom: 0.5rem;
overflow: auto;
user-select: none;
max-height: 100%;
}
.wiki-nav .page-folder {
position: relative;
}
.wiki-nav .page-folder.can-drop::after,
.wiki-nav .page-folder.can-drop-top::after {
.wiki-nav .can-drop::after,
.wiki-nav .can-drop-top::after {
content: '';
width: 100%;
height: 1px;
@@ -32,7 +29,16 @@
background-color: #666;
}
.wiki-nav .page-folder .page-folder-children {
.wiki-nav .can-drop::after {
top: unset;
bottom: 0;
}
.wiki-nav .can-drop-top::after {
top: 0;
}
.wiki-nav .page-children {
transition: height 0.25s ease-in-out 0s;
}
@@ -44,16 +50,6 @@
border-bottom: 1px solid #666;
}
.wiki-nav .page-folder.can-drop::after {
top: unset;
bottom: 0;
}
.wiki-nav .page-folder.can-drop-top::after {
top: 0;
}
.wiki-nav .page-folder-wrapper,
.wiki-nav .wiki-page-item {
position: relative;
display: flex;
@@ -72,7 +68,6 @@
background-color: #EDEDEA;
}
.wiki-nav .page-folder-wrapper:hover,
.wiki-nav .wiki-page-item:hover {
background-color: #EFEFED;
}
@@ -81,7 +76,6 @@
background-color: #E6E6E4;
}
.wiki-nav .folder-main,
.wiki-nav .wiki-page-item-main {
flex: 1;
display: flex;
@@ -90,15 +84,6 @@
overflow: hidden;
}
.wiki-nav .icon-expand-folder {
display: inline-block;
font-size: 12px;
transform: scale(0.8);
color: #b5b5b5;
cursor: pointer;
}
.wiki-nav .folder-content,
.wiki-nav .wiki-page-content {
height: 100%;
padding-right: 8px;
@@ -109,29 +94,25 @@
overflow: hidden;
}
.wiki-nav .in-folder .wiki-page-content {
.wiki-nav .wiki-page-content {
padding-left: calc(12 * 0.8px + 0.5rem);
}
.wiki-nav .folder-content:hover,
.wiki-nav .wiki-page-content:hover {
cursor: pointer;
}
.wiki-nav .folder-content .dtable-font,
.wiki-nav .wiki-page-content .dtable-font {
margin-right: 6px;
font-size: 14px;
}
.wiki-nav .folder-content .folder-name,
.wiki-nav .wiki-page-content .wiki-page-title {
height: 40px;
line-height: 40px;
flex: 1;
}
.wiki-nav .folder-operation-dropdownmenu,
.wiki-nav .more-wiki-page-operation {
width: 24px;
height: 24px;
@@ -141,37 +122,31 @@
}
.wiki-nav .wiki-add-page-btn:hover,
.wiki-nav .folder-operation:hover,
.wiki-nav .more-wiki-page-operation:hover {
border-radius: 3px;
background-color: #DFDFDD;
}
.wiki-nav .wiki-page-item .sf3-font.sf3-font-enlarge,
.wiki-nav .page-folder-wrapper .folder-operation-dropdownmenu .seafile-multicolor-icon-more-level,
.wiki-nav .wiki-page-item .more-wiki-page-operation .seafile-multicolor-icon-more-level {
cursor: pointer;
opacity: 0;
}
.wiki-nav .wiki-page-item:hover .sf3-font.sf3-font-enlarge,
.wiki-nav .page-folder-wrapper:hover .folder-operation-dropdownmenu .seafile-multicolor-icon-more-level,
.wiki-nav .wiki-page-item:hover .more-wiki-page-operation .seafile-multicolor-icon-more-level {
opacity: 1;
}
.wiki-nav .folder-operation-dropdownmenu:hover,
.wiki-nav .more-wiki-page-operation:hover {
cursor: pointer;
}
.wiki-nav .folder-operation-dropdownmenu .dtable-font,
.wiki-nav .more-wiki-page-operation .dtable-font {
font-size: 14px;
margin-right: 10px;
}
.wiki-nav .folder-list .page-folder.fold-freezed .btn-folder-operation,
.wiki-nav .wiki-page-item.wiki-page-freezed .sf3-font.sf3-font-enlarge,
.wiki-nav .wiki-page-item.wiki-page-freezed .seafile-multicolor-icon-more-level {
opacity: 1;
@@ -254,69 +229,6 @@
margin: 0.2rem 0;
}
/* folders dropdown */
.wiki-nav .more-wiki-page-operation .btn-move-to-folder {
display: flex;
align-items: center;
}
.wiki-nav .more-wiki-page-operation .move-to-folders-toggle {
opacity: 0;
width: 0;
min-width: 0;
margin-left: -12px;
padding: 0;
}
.wiki-nav .more-wiki-page-operation .folders-dropdown-menu {
margin-top: -16px;
margin-left: 10px;
}
.wiki-nav .folders-dropdown-menu .dropdown-item {
display: flex;
align-items: center;
}
.wiki-nav .more-wiki-page-operation .btn-move-to-folder:focus .dtable-icon-insert-right {
color: #fff;
}
.wiki-nav .folders-dropdown {
width: 100%;
}
.wiki-nav .folders-dropdown-toggle {
display: flex;
align-items: center;
}
.wiki-nav .folders-dropdown .item-text {
flex: 1;
}
.wiki-nav .folders-dropdown .dropdown-menu {
max-height: 300px;
overflow-y: auto;
}
.wiki-nav .folders-dropdown .dropdown-menu .folder-name {
display: inline-block;
width: 100%;
}
.wiki-nav .folders-dropdown .icon-dropdown-toggle {
display: inline-flex;
align-items: center;
width: 12px;
height: 12px;
margin-right: 12px;
}
.wiki-nav .folders-dropdown .icon-dropdown-toggle .item-icon {
transform: scale(0.8);
}
.wiki-nav .wiki-page-item.page-can-drop::after,
.wiki-nav .wiki-page-item.page-can-drop-top::after {
content: '';
@@ -351,40 +263,15 @@
.wiki-nav,
.wiki-nav .wiki-page-item .sf3-font.sf3-font-enlarge:hover,
.wiki-nav .wiki-page-item .seafile-multicolor-icon-more-level:hover,
.wiki-nav .page-folder .seafile-multicolor-icon-more-level:hover,
.wiki-nav .icon-expand-folder:hover {
.wiki-nav .wiki-page-item .seafile-multicolor-icon-more-level:hover {
color: #212529;
}
.wiki-nav .wiki-page-item .sf3-font.sf3-font-enlarge,
.wiki-nav .wiki-page-item .seafile-multicolor-icon-more-level,
.wiki-nav .page-folder .seafile-multicolor-icon-more-level,
.wiki-nav .icon-expand-folder {
.wiki-nav .wiki-page-item .seafile-multicolor-icon-more-level {
color: #666;
}
.wiki-nav .page-folder-wrapper.can-drop-top::before {
content: '';
width: 100%;
height: 1px;
position: absolute;
left: 0;
top: 0;
background-color: #666;
}
.wiki-nav .page-folder-wrapper.can-drop::after {
content: '';
width: 100%;
height: 1px;
position: absolute;
left: 0;
background-color: #666;
top: unset;
bottom: 0;
}
.svg-item {
width: 1em;
height: 1em;

View File

@@ -103,15 +103,8 @@ class Wiki extends Component {
getFirstPageId = (config) => {
if (!config || !Array.isArray(config.navigation)) return '';
for (let i = 0; i < config.navigation.length; i++) {
const item = config.navigation[i] || {};
if (item.type === 'page') {
return item.id;
}
if (item.type === 'folder' && item.children[0]) {
return item.children[0].id;
}
}
const firstPage = config.navigation[0] || {};
return firstPage.id;
};
getSdocFileContent = (docUuid, accessToken) => {

View File

@@ -1,8 +0,0 @@
export default class Folder {
constructor(object) {
this.type = 'folder';
this.id = object.id;
this.name = object.name;
this.children = Array.isArray(object.children) ? object.children : [];
}
}

View File

@@ -1,21 +1,37 @@
import Page from './page';
import Folder from './folder';
export default class WikiConfig {
constructor(object) {
this.version = object.version || 1;
this.navigation = (Array.isArray(object.navigation) ? object.navigation : []).map(item => {
if (item.type === 'folder') {
return new Folder(item);
} else if (item.type === 'page') {
return {
id: item.id,
type: item.type,
children: item.children || [],
};
const { version = 1, pages = [], navigation = [] } = object;
this.version = version;
this.pages = pages.map(page => new Page(page));
this.navigation = navigation.filter(item => {
return item.type === 'page';
});
// Render pages in folder to navigation root
const page_id_map = {};
this.pages.forEach(page => {
page_id_map[page.id] = false;
});
function traversePage(node) {
if (!node) return;
if (node.id) {
page_id_map[node.id] = true;
}
return null;
}).filter(item => !!item);
this.pages = Array.isArray(object.pages) ? object.pages.map(page => new Page(page)) : [];
if (Array.isArray(node.children)) {
node.children.forEach(child => traversePage(child));
}
}
traversePage({ children: this.navigation });
for (let key in page_id_map) {
if (page_id_map[key] === false) {
const page = this.pages.find(item => item.id === key);
this.navigation.push({
id: page.id,
type: 'page',
children: page.children || [],
});
}
}
}
}

View File

@@ -7,14 +7,9 @@ import toaster from '../../components/toast';
import Loading from '../../components/loading';
import WikiNav from './wiki-nav/index';
import PageUtils from './wiki-nav/page-utils';
import NewFolderDialog from './wiki-nav/new-folder-dialog';
import AddNewPageDialog from './wiki-nav/add-new-page-dialog';
import WikiNavFooter from './wiki-nav/wiki-nav-footer';
import { generateUniqueId, isObjectNotEmpty } from './utils';
import Folder from './models/folder';
import Page from './models/page';
import wikiAPI from '../../utils/wiki-api';
import { FOLDER } from './constant';
import { Utils } from '../../utils/utils';
import WikiExternalOperations from './wiki-external-operations';
@@ -37,10 +32,6 @@ class SidePanel extends Component {
constructor(props) {
super(props);
this.state = {
isShowNewFolderDialog: false,
isShowAddNewPageDialog: false,
};
}
confirmDeletePage = (pageId) => {
@@ -50,7 +41,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)
// TODO: delete a page, then delete all subpages
wikiAPI.deleteWiki2Page(wikiId, pageId);
if (config.pages.length > 0) {
this.props.setCurrentPage(config.pages[0].id);
@@ -72,7 +63,7 @@ class SidePanel extends Component {
const navigation = config.navigation;
const pageId = generateUniqueId(navigation);
const newPage = new Page({ id: pageId, name, icon, path, docUuid });
this.addPage(newPage, this.current_folder_id, successCallback, errorCallback, jumpToNewPage);
this.addPage(newPage, '', successCallback, errorCallback, jumpToNewPage);
};
duplicatePage = async (fromPageConfig, successCallback, errorCallback) => {
@@ -102,240 +93,32 @@ class SidePanel extends Component {
this.props.saveWikiConfig(config, onSuccess, errorCallback);
};
movePage = ({ moved_page_id, target_page_id, source_page_folder_id, target_page_folder_id, move_position }) => {
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, source_page_folder_id, target_page_folder_id, move_position);
PageUtils.movePage(navigation, moved_page_id, target_page_id, move_position);
config.navigation = navigation;
this.props.saveWikiConfig(config);
};
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_id, target_id, move_position);
config.navigation = navigation;
this.props.saveWikiConfig(config);
};
// Create a new folder in the root directory (not supported to create a new subfolder in the folder)
addPageFolder = (folder_data, parent_folder_id) => {
const { config } = this.props;
const { navigation } = config;
let folder_id = generateUniqueId(navigation);
let newFolder = new Folder({ id: folder_id, ...folder_data });
// No parent folder, directly add to the root directory
if (!parent_folder_id) {
config.navigation.push(newFolder);
} else { // Recursively find the parent folder and add
navigation.forEach(item => {
if (item.type === FOLDER) {
this._addFolder(item, newFolder, parent_folder_id);
}
});
}
this.props.saveWikiConfig(config);
};
_addFolder(folder, newFolder, parent_folder_id) {
if (folder.id === parent_folder_id) {
folder.children.push(newFolder);
return;
}
folder.children.forEach(item => {
if (item.type === FOLDER) {
this._addFolder(item, newFolder, parent_folder_id);
}
});
}
onModifyFolder = (folder_id, folder_data) => {
const { config } = this.props;
const { navigation } = config;
PageUtils.modifyFolder(navigation, folder_id, folder_data);
config.navigation = navigation;
this.props.saveWikiConfig(config);
};
onDeleteFolder = (page_folder_id) => {
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_id, move_position) => {
const { config } = this.props;
const { navigation } = config;
let updatedNavigation = deepCopy(navigation);
// Get the moved folder first and delete the original location
let moved_folder;
let moved_folder_index = PageUtils.getFolderIndexById(updatedNavigation, moved_folder_id);
if (moved_folder_index === -1) {
updatedNavigation.forEach(item => {
if (item.type === FOLDER) {
moved_folder_index = PageUtils.getFolderIndexById(item.children, moved_folder_id);
if (moved_folder_index > -1) {
moved_folder = item.children[moved_folder_index];
item.children.splice(moved_folder_index, 1);
}
}
});
} else {
moved_folder = updatedNavigation[moved_folder_index];
updatedNavigation.splice(moved_folder_index, 1);
}
let indexOffset = 0;
if (move_position === 'move_below') {
indexOffset++;
}
// Get the location of the release
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_id);
if (target_folder_index > -1) {
item.children.splice(target_folder_index + indexOffset, 0, moved_folder);
}
} else {
// not changed
updatedNavigation = navigation;
}
});
} else {
updatedNavigation.splice(target_folder_index + indexOffset, 0, moved_folder);
}
config.navigation = updatedNavigation;
this.props.saveWikiConfig(config);
};
// Not support yet: Move a folder into another folder
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_id);
if (!target_folder) {
toaster.danger('Only_support_two_level_folders');
return;
}
let moved_folder;
let moved_folder_index = PageUtils.getFolderIndexById(navigation, moved_folder_id);
// The original directory is in the root directory
if (moved_folder_index > -1) {
moved_folder = PageUtils.getFolderById(navigation, moved_folder_id);
// If moved folder There are other directories under the ID, and dragging is not supported
if (moved_folder.children.some(item => item.type === FOLDER)) {
toaster.danger('Only_support_two_level_folders');
return;
}
target_folder.children.push(moved_folder);
navigation.splice(moved_folder_index, 1);
} else { // The original directory is not in the root directory
navigation.forEach(item => {
if (item.type === FOLDER) {
let moved_folder_index = PageUtils.getFolderIndexById(item.children, moved_folder_id);
if (moved_folder_index > -1) {
moved_folder = item.children[moved_folder_index];
target_folder.children.push(moved_folder);
item.children.splice(moved_folder_index, 1);
}
}
});
}
config.navigation = navigation;
this.props.saveWikiConfig(config);
};
onToggleAddFolder = () => {
this.setState({ isShowNewFolderDialog: !this.state.isShowNewFolderDialog });
};
openAddPageDialog = (folder_id) => {
this.current_folder_id = folder_id;
this.setState({ isShowAddNewPageDialog: true });
};
closeAddNewPageDialog = () => {
this.current_folder_id = null;
this.setState({ isShowAddNewPageDialog: false });
};
onSetFolderId = (folder_id) => {
this.current_folder_id = folder_id;
};
renderFolderView = () => {
renderWikiNav = () => {
const { config, onUpdatePage } = this.props;
const { pages, navigation } = config;
return (
<div className="wiki2-pages-container">
<WikiNav
isEditMode={isWiki2}
navigation={navigation}
pages={pages}
onToggleAddPage={this.openAddPageDialog}
onDeletePage={this.confirmDeletePage}
onUpdatePage={onUpdatePage}
setCurrentPage={this.props.setCurrentPage}
onMovePage={this.movePage}
movePageOut={this.movePageOut}
onToggleAddFolder={this.onToggleAddFolder}
onModifyFolder={this.onModifyFolder}
onDeleteFolder={this.onDeleteFolder}
onMoveFolder={this.onMoveFolder}
moveFolderToFolder={this.moveFolderToFolder}
onAddNewPage={this.onAddNewPage}
duplicatePage={this.duplicatePage}
onSetFolderId={this.onSetFolderId}
currentPageId={this.props.currentPageId}
addPageInside={this.addPageInside}
/>
{this.state.isShowNewFolderDialog &&
<NewFolderDialog
onAddFolder={this.addPageFolder}
onToggleAddFolderDialog={this.onToggleAddFolder}
/>
}
{this.state.isShowAddNewPageDialog &&
<AddNewPageDialog
toggle={this.closeAddNewPageDialog}
{isObjectNotEmpty(config) &&
<WikiNav
isEditMode={isWiki2}
navigation={navigation}
pages={pages}
onDeletePage={this.confirmDeletePage}
onUpdatePage={onUpdatePage}
setCurrentPage={this.props.setCurrentPage}
onMovePage={this.movePage}
onAddNewPage={this.onAddNewPage}
title={gettext('Add page')}
/>
}
</div>
);
};
renderNoFolder = () => {
return (
<div className="wiki2-pages-container">
{isWiki2 &&
<WikiNavFooter
onToggleAddPage={this.openAddPageDialog}
onToggleAddFolder={this.onToggleAddFolder}
/>
}
{this.state.isShowNewFolderDialog &&
<NewFolderDialog
onAddFolder={this.addPageFolder}
onToggleAddFolderDialog={this.onToggleAddFolder}
/>
}
{this.state.isShowAddNewPageDialog &&
<AddNewPageDialog
toggle={this.closeAddNewPageDialog}
onAddNewPage={this.onAddNewPage}
title={gettext('Add page')}
duplicatePage={this.duplicatePage}
currentPageId={this.props.currentPageId}
addPageInside={this.addPageInside}
/>
}
</div>
@@ -364,7 +147,7 @@ class SidePanel extends Component {
};
render() {
const { isLoading, config } = this.props;
const { isLoading } = this.props;
return (
<div className={`wiki2-side-panel${this.props.closeSideBar ? '' : ' left-zero'}`}>
<div className="wiki2-side-panel-top">
@@ -377,7 +160,7 @@ class SidePanel extends Component {
</UncontrolledTooltip>
</div>
<div className="wiki2-side-nav">
{isLoading ? <Loading /> : (isObjectNotEmpty(config) ? this.renderFolderView() : this.renderNoFolder())}
{isLoading ? <Loading /> : this.renderWikiNav()}
</div>
<WikiExternalOperations onAddWikiPage={this.handleAddNewPage.bind(false)} />
</div>

View File

@@ -46,7 +46,7 @@ function WikiTopNav({ config, currentPageId }) {
return (
<Fragment key={item.id}>
<div className='wiki2-top-nav-item d-flex'>
<NavItemIcon symbol={item.type === 'folder' ? 'wiki-folder' : 'file'} disable={true} />
<NavItemIcon symbol={'file'} disable={true} />
{item.name}
</div>
{index !== paths.length - 1 && <div>/</div>}

View File

@@ -1,52 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Dropdown, DropdownMenu, DropdownItem, DropdownToggle } from 'reactstrap';
import { gettext } from '../../../utils/constants';
class AddPageDropdownMenu extends Component {
toggle = event => {
this.onStopPropagation(event);
this.props.toggleDropdown();
};
addPage = event => {
this.onStopPropagation(event);
this.props.onToggleAddPage(null);
};
onToggleAddFolder = event => {
this.onStopPropagation(event);
this.props.onToggleAddFolder();
};
onStopPropagation = event => {
event && event.nativeEvent && event.nativeEvent.stopImmediatePropagation();
};
render() {
return (
<Dropdown isOpen toggle={this.toggle}>
<DropdownToggle caret></DropdownToggle>
<DropdownMenu container="body" className='dtable-dropdown-menu large mt-0'>
<DropdownItem onClick={this.addPage}>
<i className="sf3-font sf3-font-file" />
<span className='item-text'>{gettext('Add page')}</span>
</DropdownItem>
<DropdownItem onClick={this.onToggleAddFolder}>
<i className="sf3-font sf3-font-folder" />
<span className='item-text'>{gettext('Add folder')}</span>
</DropdownItem>
</DropdownMenu>
</Dropdown>
);
}
}
AddPageDropdownMenu.propTypes = {
toggleDropdown: PropTypes.func,
onToggleAddPage: PropTypes.func,
onToggleAddFolder: PropTypes.func,
};
export default AddPageDropdownMenu;

View File

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

View File

@@ -1,103 +0,0 @@
import { DragSource, DropTarget } from 'react-dnd';
import { DRAGGED_FOLDER_MODE, DRAGGED_PAGE_MODE } from '../constant';
import FolderItem from './folder-item';
const dragSource = {
beginDrag(props, monitor) {
const { folderIndex, folder } = props;
return {
idx: folderIndex,
data: folder,
mode: DRAGGED_FOLDER_MODE,
};
},
endDrag(props, monitor) {
const sourceRow = monitor.getItem();
const didDrop = monitor.didDrop();
let targetRow = {};
if (!didDrop) {
return { sourceRow, targetRow };
}
},
isDragging(props, monitor) {
const { folderIndex: currentIndex, draggedPage } = props;
const { idx } = draggedPage;
return idx > currentIndex;
},
};
const dropTarget = {
drop(props, monitor) {
const sourceRow = monitor.getItem();
const { folder: targetFolder } = props;
const targetFolderId = targetFolder.id;
const className = props.getClassName();
if (!className) return;
let move_position;
if (className.includes('can-drop')) {
move_position = 'move_below';
}
if (className.includes('can-drop-top')) {
move_position = 'move_above';
}
let moveInto = className.includes('dragged-page-over');
// 1. drag source is page
if (sourceRow.mode === DRAGGED_PAGE_MODE) {
const sourceFolderId = sourceRow.folderId;
const draggedPageId = sourceRow.data.id;
// 1.1 move page into folder
if (moveInto) {
props.onMovePage({
moved_page_id: draggedPageId,
target_page_id: null,
source_page_folder_id: sourceFolderId,
target_page_folder_id: targetFolderId,
move_position,
});
return;
} else { // 1.2 Drag the page above or below the folder
props.movePageOut(draggedPageId, sourceFolderId, targetFolderId, move_position);
return;
}
}
// 2. drag source is folder
if (sourceRow.mode === DRAGGED_FOLDER_MODE) {
const draggedFolderId = sourceRow.data.id;
// 2.0 If dragged folder and target folder are the same folder, return
if (targetFolderId === draggedFolderId) {
return;
}
// 2.1 Do not support drag folder into another folder
if (moveInto) {
// props.moveFolderToFolder(draggedFolderId, targetFolderId);
return;
} else {
// 2.2 Drag folder above or below another folder
props.onMoveFolder(draggedFolderId, targetFolderId, move_position);
}
return;
}
// 3. Drag other dom
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(),
connect,
monitor,
});
export default DropTarget('WikiNav', dropTarget, dropCollect)(
DragSource('WikiNav', dragSource, dragCollect)(FolderItem)
);

View File

@@ -1,284 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import FolderOperationDropdownMenu from './folder-operation-dropdownmenu';
import DraggedPageItem from '../pages/dragged-page-item';
import DraggedFolderItem from './dragged-folder-item';
import NameEditPopover from '../../common/name-edit-popover';
import NavItemIcon from '../../common/nav-item-icon';
class FolderItem extends Component {
constructor(props) {
super(props);
const { name, icon } = props.folder;
this.state = {
isEditing: false,
icon: icon || '',
name: name || '',
isMouseEnter: false,
};
}
toggleExpand = (e) => {
e.nativeEvent.stopImmediatePropagation();
this.props.toggleExpand(this.props.folder.id);
this.forceUpdate();
};
onClickFolderChildren = (e) => {
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();
};
openFolderEditor = () => {
this.setState({ isEditing: true });
};
closeFolderEditor = () => {
if (this.state.isEditing) {
const { name, icon } = this.state;
this.props.onModifyFolder(this.props.folder.id, { name, icon });
this.setState({ isEditing: false });
}
};
onChangeName = (name) => {
this.setState({ name });
};
onChangeIcon = (icon) => {
this.setState({ icon });
};
changeItemFreeze = (isFreeze) => {
this.isFreeze = true;
};
renderFolder = (folder, index, pagesLength, isOnlyOnePage, id_page_map) => {
const { isEditMode, pages, pathStr } = this.props;
const { id: folderId } = folder;
return (
<DraggedFolderItem
key={`page-folder-${folderId}`}
isEditMode={isEditMode}
folder={folder}
folderIndex={index}
pagesLength={pagesLength}
isOnlyOnePage={isOnlyOnePage}
id_page_map={id_page_map}
renderFolderMenuItems={this.props.renderFolderMenuItems}
toggleExpand={this.props.toggleExpand}
onToggleAddPage={this.props.onToggleAddPage}
onModifyFolder={this.props.onModifyFolder}
onDeleteFolder={this.props.onDeleteFolder}
onMoveFolder={this.props.onMoveFolder}
setCurrentPage={this.props.setCurrentPage}
onUpdatePage={this.props.onUpdatePage}
duplicatePage={this.props.duplicatePage}
onSetFolderId={this.props.onSetFolderId}
onDeletePage={this.props.onDeletePage}
onMovePageToFolder={this.props.onMovePageToFolder}
onMovePage={this.props.onMovePage}
moveFolderToFolder={this.props.moveFolderToFolder}
pages={pages}
pathStr={pathStr + '-' + folderId}
setClassName={this.props.setClassName}
getClassName={this.props.getClassName}
movePageOut={this.props.movePageOut}
layerDragProps={this.props.layerDragProps}
getFoldState={this.props.getFoldState}
currentPageId={this.props.currentPageId}
addPageInside={this.props.addPageInside}
/>
);
};
renderPage = (page, index, pagesLength, isOnlyOnePage) => {
const { isEditMode, pages, folder, pathStr } = this.props;
const id = page.id;
if (!pages.find(item => item.id === id)) return;
return (
<DraggedPageItem
key={id}
pagesLength={pagesLength}
isOnlyOnePage={isOnlyOnePage}
infolder={false}
page={Object.assign({}, pages.find(item => item.id === id), page)}
pageIndex={index}
folderId={folder.id}
isEditMode={isEditMode}
renderFolderMenuItems={this.props.renderFolderMenuItems}
duplicatePage={this.props.duplicatePage}
onSetFolderId={this.props.onSetFolderId}
setCurrentPage={this.props.setCurrentPage}
onUpdatePage={this.props.onUpdatePage}
onDeletePage={this.props.onDeletePage}
onMovePageToFolder={(targetFolderId) => {
this.props.onMovePageToFolder(folder.id, page.id, targetFolderId);
}}
onMovePage={this.props.onMovePage}
onMoveFolder={this.props.onMoveFolder}
pages={pages}
pathStr={pathStr + '-' + page.id}
currentPageId={this.props.currentPageId}
addPageInside={this.props.addPageInside}
getFoldState={this.props.getFoldState}
toggleExpand={this.props.toggleExpand}
/>
);
};
getFolderClassName = (layerDragProps, state) => {
if (!state || ! layerDragProps || !layerDragProps.clientOffset) {
return 'page-folder-wrapper';
}
let y = layerDragProps.clientOffset.y;
let top = this.foldWrapprRef.getBoundingClientRect().y;
let className = '';
// middle
if (top + 10 < y && y < top + 30) {
className += ' dragged-page-over ';
}
// top
if (top + 10 > y) {
className += ' can-drop-top ';
}
// bottom
if (top + 30 < y) {
className += ' can-drop ';
}
this.props.setClassName(className);
return className + 'page-folder-wrapper';
};
getFolderChildrenHeight = () => {
const folded = this.props.getFoldState(this.props.folder.id);
if (folded) return 0;
return 'auto';
};
onMouseEnter = () => {
this.setState({ isMouseEnter: true });
};
onMouseLeave = () => {
this.setState({ isMouseEnter: false });
};
render() {
const {
connectDropTarget, connectDragPreview, connectDragSource, isOver, canDrop,
isEditMode, folder, pagesLength, id_page_map, isOnlyOnePage, layerDragProps,
} = this.props;
const { isEditing } = this.state;
const { id: folderId, name, children } = folder;
const folded = this.props.getFoldState(folderId);
let navItemId = `folder-item-${folderId}`;
let fn = isEditMode ? connectDragSource : (argu) => {argu;};
return (
<div
className={classnames('page-folder', { 'readonly': !isEditMode })}
ref={ref => this.foldRef = ref}
onClick={this.toggleExpand}
>
{fn(connectDropTarget(
connectDragPreview(
<div
className={this.getFolderClassName(layerDragProps, isOver && canDrop)}
ref={ref => this.foldWrapprRef = ref}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
>
<div className='folder-main'>
<div
className='folder-content'
style={{ marginLeft: `${(this.props.pathStr.split('-').length - 1) * 16}px` }}
id={navItemId}
>
{this.state.isMouseEnter ?
<div className='nav-item-icon'>
<i className={`sf3-font-down sf3-font ${folded ? 'rotate-270' : ''}`}></i>
</div>
:
<NavItemIcon symbol={'wiki-folder'} disable={true} />
}
<span className='folder-name text-truncate' title={name}>{name}</span>
{isEditing &&
<NameEditPopover
oldName={this.state.name}
targetId={navItemId}
onChangeName={this.onChangeName}
toggleEditor={this.closeFolderEditor}
/>
}
</div>
</div>
{isEditMode &&
<FolderOperationDropdownMenu
changeItemFreeze={this.changeItemFreeze}
openFolderEditor={this.openFolderEditor}
onDeleteFolder={this.props.onDeleteFolder}
onToggleAddPage={this.props.onToggleAddPage}
folderId={folderId}
/>
}
</div>
)
))
}
<div
className="page-folder-children"
style={{ height: this.getFolderChildrenHeight() }}
onClick={this.onClickFolderChildren}
>
{!folded && children &&
children.map((item, index) => {
return item.type === 'folder' ? this.renderFolder(item, index, pagesLength, isOnlyOnePage, id_page_map) : this.renderPage(item, index, pagesLength, isOnlyOnePage);
})
}
</div>
</div>
);
}
}
FolderItem.propTypes = {
isEditMode: PropTypes.bool,
folder: PropTypes.object,
folderIndex: PropTypes.number,
pagesLength: PropTypes.number,
id_page_map: PropTypes.object,
isOver: PropTypes.bool,
canDrop: PropTypes.bool,
isDragging: PropTypes.bool,
connectDropTarget: PropTypes.func,
connectDragPreview: PropTypes.func,
connectDragSource: PropTypes.func,
renderFolderMenuItems: PropTypes.func,
duplicatePage: PropTypes.func,
onSetFolderId: PropTypes.func,
toggleExpand: PropTypes.func,
onToggleAddPage: PropTypes.func,
onModifyFolder: PropTypes.func,
onDeleteFolder: PropTypes.func,
setCurrentPage: PropTypes.func,
onUpdatePage: PropTypes.func,
onDeletePage: PropTypes.func,
onMovePageToFolder: PropTypes.func,
onMovePage: PropTypes.func,
isOnlyOnePage: PropTypes.bool,
pages: PropTypes.array,
onMoveFolder: PropTypes.func,
moveFolderToFolder: PropTypes.func,
pathStr: PropTypes.string,
setClassName: PropTypes.func,
getClassName: PropTypes.func,
movePageOut: PropTypes.func,
layerDragProps: PropTypes.object,
getFoldState: PropTypes.func,
currentPageId: PropTypes.string,
addPageInside: PropTypes.func,
};
export default FolderItem;

View File

@@ -1,75 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
import { gettext } from '../../../../utils/constants';
import Icon from '../../../../components/icon';
export default class FolderOperationDropdownMenu extends Component {
static propTypes = {
changeItemFreeze: PropTypes.func,
openFolderEditor: PropTypes.func,
onDeleteFolder: PropTypes.func,
onToggleAddPage: PropTypes.func,
folderId: PropTypes.string,
};
constructor(props) {
super(props);
this.state = {
isMenuShow: false,
};
}
onDropdownToggle = (e) => {
e.stopPropagation();
const isMenuShow = !this.state.isMenuShow;
this.props.changeItemFreeze(isMenuShow);
this.setState({ isMenuShow });
};
openFolderEditor = (evt) => {
evt.nativeEvent.stopImmediatePropagation();
this.props.openFolderEditor();
};
onDeleteFolder = (evt) => {
evt.nativeEvent.stopImmediatePropagation();
this.props.onDeleteFolder(this.props.folderId);
};
render() {
return (
<>
<Dropdown
isOpen={this.state.isMenuShow}
toggle={this.onDropdownToggle}
className="folder-operation-dropdownmenu"
>
<DropdownToggle tag="span" data-toggle="dropdown" aria-expanded={this.state.isMenuShow}>
<Icon symbol={'more-level'}/>
</DropdownToggle>
<DropdownMenu
className="dtable-dropdown-menu large"
flip={false}
modifiers={{ preventOverflow: { boundariesElement: document.body } }}
positionFixed={true}
>
<DropdownItem onClick={this.props.onToggleAddPage.bind(this, this.props.folderId)}>
<i className="sf3-font sf3-font-file" />
<span className="item-text">{gettext('Add page')}</span>
</DropdownItem>
<DropdownItem onClick={this.openFolderEditor}>
<i className="sf3-font sf3-font-rename" />
<span className="item-text">{gettext('Modify name')}</span>
</DropdownItem>
<DropdownItem onMouseDown={this.onDeleteFolder}>
<i className="sf3-font sf3-font-delete1" />
<span className="item-text">{gettext('Delete folder')}</span>
</DropdownItem>
</DropdownMenu>
</Dropdown>
</>
);
}
}

View File

@@ -1,98 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Modal, ModalHeader, ModalBody, ModalFooter, Form, FormGroup, Label, Input, Button, Alert } from 'reactstrap';
import { gettext } from '../../../utils/constants';
export default class NewFolderDialog extends Component {
static propTypes = {
onAddFolder: PropTypes.func,
onToggleAddFolderDialog: PropTypes.func,
};
constructor(props) {
super(props);
this.state = {
folderName: '',
errMessage: '',
iconClassName: '',
};
}
componentDidMount() {
document.addEventListener('keydown', this.onHotKey);
}
componentWillUnmount() {
document.removeEventListener('keydown', this.onHotKey);
}
onHotKey = (e) => {
if (e.keyCode === 13) {
e.preventDefault();
this.handleSubmit();
}
};
handleChange = (event) => {
let { folderName } = this.state;
let value = event.target.value;
if (value === folderName) {
return;
}
this.setState({ folderName: value });
};
handleSubmit = () => {
let { folderName, iconClassName } = this.state;
if (!folderName) {
this.setState({ errMessage: gettext('Name_is_required') });
return;
}
this.props.onAddFolder({ name: folderName, icon: iconClassName });
this.props.onToggleAddFolderDialog();
};
toggle = () => {
this.props.onToggleAddFolderDialog();
};
onIconChange = (className) => {
this.setState({ iconClassName: className });
};
render() {
const { folderName, errMessage } = this.state;
return (
<Modal
isOpen={true}
toggle={this.toggle}
autoFocus={false}
className="new-folder-dialog"
>
<ModalHeader toggle={this.toggle}>{gettext('New folder')}</ModalHeader>
<ModalBody>
<Form>
<FormGroup>
<Label for="folderName">{gettext('Name')}</Label>
<Input
id="folderName"
value={folderName}
innerRef={input => {
this.newInput = input;
}}
onChange={this.handleChange}
autoFocus={true}
/>
</FormGroup>
</Form>
{errMessage && <Alert color="danger" className="mt-2">{errMessage}</Alert>}
</ModalBody>
<ModalFooter>
<Button color="secondary" onClick={this.toggle}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={this.handleSubmit}>{gettext('Submit')}</Button>
</ModalFooter>
</Modal>
);
}
}

View File

@@ -1,10 +1,16 @@
import { FOLDER, PAGE } from '../constant';
class NewPage {
constructor(id) {
this.id = id;
this.type = 'page';
this.children = [];
}
}
export default class PageUtils {
static addPage(navigation, page_id, parentId) {
if (!parentId) {
navigation.push({ id: page_id, type: PAGE });
navigation.push(new NewPage(page_id));
} else {
navigation.forEach(item => {
this._addPageRecursion(page_id, item, parentId);
@@ -17,10 +23,10 @@ export default class PageUtils {
item.children = [];
}
if (item.id === parentId) {
item.children.push({ id: page_id, type: PAGE });
item.children.push(new NewPage(page_id));
return true;
}
item.children.forEach(item => {
item.children && item.children.forEach(item => {
this._addPageRecursion(page_id, item, parentId);
});
}
@@ -43,7 +49,7 @@ export default class PageUtils {
item.children.splice(pageIndex, 1);
return true;
}
item.children.forEach(item => {
item.children && item.children.forEach(item => {
this._deletePageRecursion(item, page_id);
});
}
@@ -57,76 +63,7 @@ export default class PageUtils {
return pages.findIndex(page => page.id === pageId);
};
static getFolderById = (list, folder_id) => {
if (!folder_id || !Array.isArray(list)) return null;
return list.find(item => item.type === FOLDER && item.id === folder_id);
};
static getFolderIndexById = (list, folder_id) => {
if (!folder_id || !Array.isArray(list)) return -1;
return list.findIndex(folder => folder.id === folder_id);
};
static modifyFolder(navigation, folder_id, folder_data) {
navigation.forEach(item => {
if (item.type === FOLDER) {
this._modifyFolder(item, folder_id, folder_data);
}
});
}
static _modifyFolder(folder, folder_id, folder_data) {
if (folder.id === folder_id) {
for (let key in folder_data) {
folder[key] = folder_data[key];
}
return;
}
folder.children.forEach(item => {
if (item.type === FOLDER) {
this._modifyFolder(item, folder_id, folder_data);
}
});
}
static deleteFolder(navigation, pages, folder_id) {
// delete folder and pages within it
const folderIndex = this.getFolderIndexById(navigation, folder_id);
if (folderIndex > -1) {
this._deletePagesInFolder(pages, navigation[folderIndex]);
navigation.splice(folderIndex, 1);
return true;
}
// delete subfolder and pages within it
navigation.forEach(item => {
if (item.type === FOLDER) {
const folderIndex = this.getFolderIndexById(item.children, folder_id);
if (folderIndex > -1) {
const subfolder = item.children[folderIndex];
this._deletePagesInFolder(pages, subfolder);
item.children.splice(folderIndex, 1);
return true;
}
}
});
return false;
}
static _deletePagesInFolder(pages, folder) {
folder.children.forEach(item => {
if (item.type === FOLDER) {
this._deletePagesInFolder(pages, item);
}
if (item.type === PAGE) {
let index = this.getPageIndexById(item.id, pages);
pages.splice(index, 1);
}
});
}
static insertPage(navigation, page_id, target_page_id, target_id, move_position) {
// 1. No folder, insert page in root directory
if (!target_id) {
let insertIndex = target_page_id ? navigation.findIndex(item => item.id === target_page_id) : -1;
if (insertIndex < 0) {
@@ -136,34 +73,12 @@ export default class PageUtils {
if (move_position === 'move_below') {
insertIndex++;
}
navigation.splice(insertIndex, 0, { id: page_id, type: PAGE });
navigation.splice(insertIndex, 0, new NewPage(page_id));
return;
}
// 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, target_id, move_position);
}
});
}
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++;
}
folder.children.splice(insertIndex, 0, { id: page_id, type: PAGE });
return;
}
folder.children.forEach(item => {
if (item.type === FOLDER) {
this._insertPageIntoFolder(item, page_id, target_page_id, target_id, move_position);
}
});
}
static movePage(navigation, moved_page_id, target_page_id, source_id, target_id, move_position) {
static movePage(navigation, moved_page_id, target_page_id, move_position) {
let movedPage = null;
function _cutPageRecursion(item, page_id) {
if (!item || !Array.isArray(item.children) || movedPage) return;
@@ -171,7 +86,7 @@ export default class PageUtils {
if (pageIndex > -1) {
movedPage = item.children.splice(pageIndex, 1)[0];
} else {
item.children.forEach(item => {
item.children && item.children.forEach(item => {
_cutPageRecursion(item, page_id);
});
}
@@ -195,7 +110,7 @@ export default class PageUtils {
item.children.splice(insertIndex, 0, movedPage);
return;
}
item.children.forEach(item => {
item.children && item.children.forEach(item => {
_insertPageRecursion(item, page_id, target_page_id, target_id, move_position);
});
}
@@ -217,58 +132,6 @@ export default class PageUtils {
});
}
_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++;
}
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);
_insertPage(navigation, moved_page_id, target_page_id, target_page_id, move_position);
}
}

View File

@@ -1,5 +1,4 @@
import { DragSource, DropTarget } from 'react-dnd';
import { DRAGGED_FOLDER_MODE, DRAGGED_PAGE_MODE } from '../constant';
import PageItem from './page-item';
const dragSource = {
@@ -7,8 +6,7 @@ const dragSource = {
return {
idx: props.pageIndex,
data: { ...props.page, index: props.pageIndex },
folderId: props.folderId,
mode: DRAGGED_PAGE_MODE,
mode: 'wiki-page',
};
},
endDrag(props, monitor) {
@@ -28,48 +26,22 @@ const dragSource = {
const dropTarget = {
drop(props, monitor) {
const sourceRow = monitor.getItem();
// 1 drag page
if (sourceRow.mode === DRAGGED_PAGE_MODE) {
const { infolder, pageIndex: targetIndex, page: targetPage, folderId: targetFolderId } = props;
const sourceFolderId = sourceRow.folderId;
const draggedPageId = sourceRow.data.id;
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 = sourceRow.idx;
let move_position;
if (infolder) {
move_position = 'move_below';
} else {
move_position = sourceIndex > targetIndex ? 'move_above' : 'move_below';
}
const sourceIndex = dragSource.idx;
const move_position = sourceIndex > targetIndex ? 'move_above' : 'move_below';
props.onMovePage({
moved_page_id: draggedPageId,
target_page_id: targetPageId,
source_page_folder_id: sourceFolderId,
target_page_folder_id: targetFolderId,
move_position,
});
}
return;
}
// 1 drag folder
if (sourceRow.mode === DRAGGED_FOLDER_MODE) {
const { pageIndex: targetIndex, page: targetPage } = props;
const draggedFolderId = sourceRow.data.id;
const targetPageId = targetPage.id;
const sourceIndex = sourceRow.idx;
// Drag the parent folder to the child page, return
if (props.pathStr.split('-').includes(draggedFolderId)) return;
props.onMoveFolder(
draggedFolderId,
targetPageId,
sourceIndex > targetIndex ? 'move_above' : 'move_below',
);
return;
}
}
};

View File

@@ -11,24 +11,15 @@ export default class PageDropdownMenu extends Component {
page: PropTypes.object.isRequired,
pages: PropTypes.array,
pagesLength: PropTypes.number,
folderId: PropTypes.string,
canDelete: PropTypes.bool,
canDuplicate: PropTypes.bool,
renderFolderMenuItems: PropTypes.func,
toggle: PropTypes.func,
toggleNameEditor: PropTypes.func,
duplicatePage: PropTypes.func,
onSetFolderId: PropTypes.func,
onDeletePage: PropTypes.func,
onMovePageToFolder: PropTypes.func,
isOnlyOnePage: PropTypes.bool,
};
constructor(props) {
super(props);
this.state = {
isShowMenu: false,
};
this.pageNameMap = this.calculateNameMap();
}
@@ -41,9 +32,6 @@ export default class PageDropdownMenu extends Component {
};
onDropdownToggle = (evt) => {
if (evt.target && this.foldersDropdownToggle && this.foldersDropdownToggle.contains(evt.target)) {
return;
}
evt.stopPropagation();
this.props.toggle();
};
@@ -58,22 +46,8 @@ export default class PageDropdownMenu extends Component {
this.props.onDeletePage();
};
onMovePageToFolder = (targetFolderId) => {
this.props.onMovePageToFolder(targetFolderId);
};
onRemoveFromFolder = (evt) => {
evt.nativeEvent.stopImmediatePropagation();
this.props.onMovePageToFolder(null);
};
onToggleFoldersMenu = () => {
this.setState({ isShowMenu: !this.state.isShowMenu });
};
duplicatePage = () => {
const { page, folderId } = this.props;
this.props.onSetFolderId(folderId);
const { page } = this.props;
this.props.duplicatePage({ from_page_id: page.id }, () => {}, this.duplicatePageFailure);
};
@@ -81,14 +55,6 @@ export default class PageDropdownMenu extends Component {
toaster.danger(gettext('Failed_to_duplicate_page'));
};
showMenu = () => {
this.setState({ isShowMenu: true });
};
hideMenu = () => {
this.setState({ isShowMenu: false });
};
handleCopyLink = () => {
const { page } = this.props;
const wikiLink = getWikPageLink(page.id);
@@ -109,10 +75,7 @@ export default class PageDropdownMenu extends Component {
};
render() {
const {
folderId, canDelete, canDuplicate, renderFolderMenuItems, pagesLength, isOnlyOnePage,
} = this.props;
const folderMenuItems = renderFolderMenuItems && renderFolderMenuItems({ currentFolderId: folderId, onMovePageToFolder: this.onMovePageToFolder });
const { pagesLength, isOnlyOnePage } = this.props;
return (
<Dropdown
@@ -135,63 +98,16 @@ export default class PageDropdownMenu extends Component {
<i className="sf3-font sf3-font-rename" />
<span className="item-text">{gettext('Modify name')}</span>
</DropdownItem>
{canDuplicate &&
<DropdownItem onClick={this.duplicatePage}>
<i className="sf3-font sf3-font-copy1" />
<span className="item-text">{gettext('Duplicate page')}</span>
</DropdownItem>
}
{(isOnlyOnePage || pagesLength === 1 || !canDelete) ? '' : (
<DropdownItem onClick={this.duplicatePage}>
<i className="sf3-font sf3-font-copy1" />
<span className="item-text">{gettext('Duplicate page')}</span>
</DropdownItem>
{(isOnlyOnePage || pagesLength === 1) ? '' : (
<DropdownItem onClick={this.onDeletePage}>
<i className="sf3-font sf3-font-delete1" />
<span className="item-text">{gettext('Delete page')}</span>
</DropdownItem>
)}
{folderId &&
<DropdownItem onClick={this.onRemoveFromFolder}>
<i className="sf3-font sf3-font-move" />
<span className="item-text">{gettext('Remove from folder')}</span>
</DropdownItem>
}
{renderFolderMenuItems && folderMenuItems.length > 0 &&
<DropdownItem
className="pr-2 btn-move-to-folder"
tag="div"
onClick={(evt) => {
evt.stopPropagation();
evt.nativeEvent.stopImmediatePropagation();
this.showMenu();
}}
onMouseEnter={this.showMenu}
onMouseLeave={this.hideMenu}
>
<Dropdown
className="folders-dropdown"
direction="right"
isOpen={this.state.isShowMenu}
toggle={this.onToggleFoldersMenu}
>
<div className="folders-dropdown-toggle" ref={ref => this.foldersDropdownToggle = ref}>
<i className="sf3-font sf3-font-move" />
<span className="item-text">{gettext('Move to')}</span>
<span className="icon-dropdown-toggle">
<i className="sf3-font-down sf3-font rotate-270"></i>
</span>
<DropdownToggle className="move-to-folders-toggle"></DropdownToggle>
</div>
{this.state.isShowMenu &&
<DropdownMenu
className="folders-dropdown-menu"
flip={false}
modifiers={{ preventOverflow: { boundariesElement: document.body } }}
positionFixed={true}
>
{folderMenuItems}
</DropdownMenu>
}
</Dropdown>
</DropdownItem>
}
< hr className='divider' />
<DropdownItem onClick={this.handleOpenInNewTab}>
<i className='sf3-font sf3-font-open-in-new-tab' />

View File

@@ -113,20 +113,20 @@ class PageItem extends Component {
window.seafile['docUuid'] = docUuid;
};
getFolderChildrenHeight = () => {
getPageChildrenHeight = () => {
const folded = this.props.getFoldState(this.props.page.id);
if (folded) return 0;
return 'auto';
};
onClickFolderChildren = (e) => {
onClickPageChildren = (e) => {
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();
};
renderPage = (page, index, pagesLength, isOnlyOnePage) => {
if (!page) return;
const { isEditMode, pages, folderId, pathStr } = this.props;
const { isEditMode, pages, pathStr } = this.props;
const id = page.id;
if (!pages.find(item => item.id === id)) return;
return (
@@ -134,22 +134,14 @@ class PageItem extends Component {
key={id}
pagesLength={pagesLength}
isOnlyOnePage={isOnlyOnePage}
infolder={false}
page={Object.assign({}, pages.find(item => item.id === id), page)}
pageIndex={index}
folderId={folderId}
isEditMode={isEditMode}
renderFolderMenuItems={this.props.renderFolderMenuItems}
duplicatePage={this.props.duplicatePage}
onSetFolderId={this.props.onSetFolderId}
setCurrentPage={this.props.setCurrentPage}
onUpdatePage={this.props.onUpdatePage}
onDeletePage={this.props.onDeletePage}
onMovePageToFolder={(targetFolderId) => {
this.props.onMovePageToFolder(folderId, page.id, targetFolderId);
}}
onMovePage={this.props.onMovePage}
onMoveFolder={this.props.onMoveFolder}
pages={pages}
pathStr={pathStr + '-' + page.id}
currentPageId={this.props.currentPageId}
@@ -173,21 +165,14 @@ class PageItem extends Component {
render() {
const {
connectDragSource, connectDragPreview, connectDropTarget, isOver, canDrop, isDragging,
infolder, page, pagesLength, isEditMode, folderId, isOnlyOnePage, pathStr,
page, pagesLength, isEditMode, isOnlyOnePage, pathStr,
} = this.props;
const { isShowNameEditor, pageName, isSelected } = this.state;
const isOverPage = isOver && canDrop;
if (isSelected) this.setDocUuid(page.docUuid);
let pageCanDropTop;
let pageCanDrop;
if (infolder) {
pageCanDropTop = false;
pageCanDrop = isOverPage;
} else {
pageCanDropTop = isOverPage && isDragging;
pageCanDrop = isOverPage && !isDragging;
}
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;
@@ -246,16 +231,10 @@ class PageItem extends Component {
pages={this.props.pages}
pagesLength={pagesLength}
isOnlyOnePage={isOnlyOnePage}
folderId={folderId}
canDelete={true}
canDuplicate={true}
toggle={this.toggleDropdown}
renderFolderMenuItems={this.props.renderFolderMenuItems}
toggleNameEditor={this.toggleNameEditor}
duplicatePage={this.props.duplicatePage}
onSetFolderId={this.props.onSetFolderId}
onDeletePage={this.openDeleteDialog}
onMovePageToFolder={this.props.onMovePageToFolder}
/>
}
</div>
@@ -283,9 +262,9 @@ class PageItem extends Component {
))
}
<div
className="page-folder-children"
style={{ height: this.getFolderChildrenHeight() }}
onClick={this.onClickFolderChildren}
className="page-children"
style={{ height: this.getPageChildrenHeight() }}
onClick={this.onClickPageChildren}
>
{page.children &&
page.children.map((item, index) => {
@@ -304,26 +283,19 @@ PageItem.propTypes = {
isDragging: PropTypes.bool,
draggedPage: PropTypes.object,
isEditMode: PropTypes.bool,
infolder: PropTypes.bool,
page: PropTypes.object,
folder: PropTypes.object,
pages: PropTypes.array,
pageIndex: PropTypes.number,
folderId: PropTypes.string,
pagesLength: PropTypes.number,
connectDragSource: PropTypes.func,
connectDragPreview: PropTypes.func,
connectDropTarget: PropTypes.func,
renderFolderMenuItems: PropTypes.func,
duplicatePage: PropTypes.func,
onSetFolderId: PropTypes.func,
setCurrentPage: PropTypes.func,
onUpdatePage: PropTypes.func,
onDeletePage: PropTypes.func,
onMovePageToFolder: PropTypes.func,
onMovePage: PropTypes.func,
isOnlyOnePage: PropTypes.bool,
onMoveFolder: PropTypes.func,
pathStr: PropTypes.string,
currentPageId: PropTypes.string,
addPageInside: PropTypes.func,

View File

@@ -1,48 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import CommonAddTool from '../../../components/common/common-add-tool';
import AddPageDropdownMenu from './add-page-dropdownmenu';
import { gettext } from '../../../utils/constants';
class WikiNavFooter extends Component {
constructor(props) {
super(props);
this.state = {
isShowDropdownMenu: false,
};
}
toggleDropdown = (event) => {
event && event.stopPropagation();
this.setState({ isShowDropdownMenu: !this.state.isShowDropdownMenu });
};
render() {
return (
<div className='wiki-nav-footer'>
<div className='add-wiki-page-wrapper'>
<CommonAddTool
className='add-wiki-page-btn'
callBack={this.toggleDropdown}
footerName={gettext('Add page or folder')}
/>
{this.state.isShowDropdownMenu &&
<AddPageDropdownMenu
toggleDropdown={this.toggleDropdown}
onToggleAddPage={this.props.onToggleAddPage}
onToggleAddFolder={this.props.onToggleAddFolder}
/>
}
</div>
</div>
);
}
}
WikiNavFooter.propTypes = {
onToggleAddPage: PropTypes.func,
onToggleAddFolder: PropTypes.func,
};
export default WikiNavFooter;

View File

@@ -1,11 +1,8 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { DropdownItem } from 'reactstrap';
import { DropTarget, DragLayer } from 'react-dnd';
import html5DragDropContext from './html5DragDropContext';
import DraggedFolderItem from './folders/dragged-folder-item';
import DraggedPageItem from './pages/dragged-page-item';
import WikiNavFooter from './wiki-nav-footer';
import { repoID } from '../../../utils/constants';
import '../css/wiki-nav.css';
@@ -17,26 +14,17 @@ class WikiNav extends Component {
navigation: PropTypes.array,
pages: PropTypes.array,
onTogglePinViewList: PropTypes.func,
onToggleAddPage: PropTypes.func,
onToggleAddFolder: PropTypes.func,
onModifyFolder: PropTypes.func,
onDeleteFolder: PropTypes.func,
onMoveFolder: PropTypes.func,
setCurrentPage: PropTypes.func,
onUpdatePage: PropTypes.func,
onDeletePage: PropTypes.func,
onMovePage: PropTypes.func,
moveFolderToFolder: PropTypes.func,
movePageOut: PropTypes.func,
duplicatePage: PropTypes.func,
onSetFolderId: PropTypes.func,
currentPageId: PropTypes.string,
addPageInside: PropTypes.func,
};
constructor(props) {
super(props);
this.folderClassNameCache = '';
this.idFoldedStatusMap = this.getFoldedFromLocal();
}
@@ -49,145 +37,63 @@ class WikiNav extends Component {
window.localStorage.setItem(`wiki-folded-${repoID}`, JSON.stringify(items));
};
getFoldState = (folderId) => {
return this.idFoldedStatusMap[folderId];
getFoldState = (pageId) => {
return this.idFoldedStatusMap[pageId];
};
toggleExpand = (folderId) => {
toggleExpand = (pageId) => {
const idFoldedStatusMap = this.getFoldedFromLocal();
if (idFoldedStatusMap[folderId]) {
delete idFoldedStatusMap[folderId];
if (idFoldedStatusMap[pageId]) {
delete idFoldedStatusMap[pageId];
} else {
idFoldedStatusMap[folderId] = true;
idFoldedStatusMap[pageId] = true;
}
this.saveFoldedToLocal(idFoldedStatusMap);
this.idFoldedStatusMap = idFoldedStatusMap;
};
onMovePageToFolder = (source_page_folder_id, moved_page_id, target_page_folder_id) => {
this.props.onMovePage({
moved_page_id,
source_page_folder_id,
target_page_folder_id,
target_page_id: null,
move_position: 'move_below'
});
};
renderFolderMenuItems = ({ currentFolderId, onMovePageToFolder }) => {
// folder lists (in the root directory)
const { navigation } = this.props;
let renderFolders = navigation.filter(item => item.type === 'folder' && item.id !== currentFolderId);
return renderFolders.map(folder => {
const { id, name } = folder;
return (
<DropdownItem key={`move-to-folder-${id}`} onClick={onMovePageToFolder.bind(this, id)}>
<span className="folder-name text-truncate" title={name}>{name}</span>
</DropdownItem>
);
});
};
setClassName = (name) => {
this.folderClassNameCache = name;
};
getClassName = () => {
return this.folderClassNameCache;
};
renderFolder = (folder, index, pagesLength, isOnlyOnePage, id_page_map, layerDragProps) => {
const { isEditMode, pages } = this.props;
const folderId = folder.id;
return (
<DraggedFolderItem
key={`page-folder-${folderId}`}
isEditMode={isEditMode}
folder={folder}
folderIndex={index}
pagesLength={pagesLength}
isOnlyOnePage={isOnlyOnePage}
id_page_map={id_page_map}
renderFolderMenuItems={this.renderFolderMenuItems}
toggleExpand={this.toggleExpand}
onToggleAddPage={this.props.onToggleAddPage}
onDeleteFolder={this.props.onDeleteFolder}
onMoveFolder={this.props.onMoveFolder}
setCurrentPage={this.props.setCurrentPage}
onUpdatePage={this.props.onUpdatePage}
duplicatePage={this.props.duplicatePage}
onSetFolderId={this.props.onSetFolderId}
onDeletePage={this.props.onDeletePage}
onMovePageToFolder={this.onMovePageToFolder}
onMovePage={this.props.onMovePage}
pages={pages}
moveFolderToFolder={this.props.moveFolderToFolder}
pathStr={folderId}
layerDragProps={layerDragProps}
setClassName={this.setClassName}
getClassName={this.getClassName}
movePageOut={this.props.movePageOut}
onModifyFolder={this.props.onModifyFolder}
getFoldState={this.getFoldState}
currentPageId={this.props.currentPageId}
addPageInside={this.props.addPageInside}
/>
);
};
renderPage = (page, index, pagesLength, isOnlyOnePage, id_page_map) => {
renderPage = (page, index, pagesLength, isOnlyOnePage, id_page_map, layerDragProps) => {
const { isEditMode, pages } = this.props;
const id = page.id;
if (!pages.find(item => item.id === id)) return;
const folderId = null; // Pages in the root directory, no folders, use null
return (
<DraggedPageItem
key={id}
pagesLength={pagesLength}
isOnlyOnePage={isOnlyOnePage}
infolder={false}
page={Object.assign({}, pages.find(item => item.id === id), page)}
pages={pages}
pageIndex={index}
folderId={folderId}
isEditMode={isEditMode}
renderFolderMenuItems={this.renderFolderMenuItems}
duplicatePage={this.props.duplicatePage}
onSetFolderId={this.props.onSetFolderId}
setCurrentPage={this.props.setCurrentPage}
onUpdatePage={this.props.onUpdatePage}
onDeletePage={this.props.onDeletePage}
onMovePageToFolder={(targetFolderId) => {
this.onMovePageToFolder(folderId, page.id, targetFolderId);
}}
onMovePage={this.props.onMovePage}
onMoveFolder={this.props.onMoveFolder}
pathStr={page.id}
currentPageId={this.props.currentPageId}
addPageInside={this.props.addPageInside}
getFoldState={this.getFoldState}
toggleExpand={this.toggleExpand}
id_page_map={id_page_map}
layerDragProps={layerDragProps}
/>
);
};
// eslint-disable-next-line
renderStructureBody = React.forwardRef((layerDragProps, ref) => {
const { navigation, pages, isEditMode } = this.props;
const { navigation, pages } = this.props;
let isOnlyOnePage = false;
if (pages.length === 1) {
isOnlyOnePage = true;
}
const pagesLength = pages.length;
let id_page_map = {};
pages.forEach(page => id_page_map[page.id] = page);
const style = { maxHeight: isEditMode ? 'calc(100% - 40px)' : '100%' };
return (
<div className='wiki-nav-body' style={style}>
<div className='wiki-nav-body'>
{navigation.map((item, index) => {
return item.type === 'folder' ?
this.renderFolder(item, index, pagesLength, isOnlyOnePage, id_page_map, layerDragProps) :
this.renderPage(item, index, pagesLength, isOnlyOnePage, id_page_map);
return this.renderPage(item, index, pages.length, isOnlyOnePage, id_page_map, layerDragProps);
})}
</div>
);
@@ -211,12 +117,6 @@ class WikiNav extends Component {
return (
<div className='wiki-nav'>
<StructureBody />
{(this.props.isEditMode) &&
<WikiNavFooter
onToggleAddPage={this.props.onToggleAddPage}
onToggleAddFolder={this.props.onToggleAddFolder}
/>
}
</div>
);
}