2024-05-15 03:57:30 +00:00
|
|
|
import React, { Component } from 'react';
|
|
|
|
import PropTypes from 'prop-types';
|
|
|
|
import deepCopy from 'deep-copy';
|
2024-06-05 05:52:33 +00:00
|
|
|
import { UncontrolledTooltip } from 'reactstrap';
|
2024-05-27 09:19:58 +00:00
|
|
|
import { gettext, isWiki2, wikiId } from '../../utils/constants';
|
2024-05-15 03:57:30 +00:00
|
|
|
import toaster from '../../components/toast';
|
|
|
|
import Loading from '../../components/loading';
|
|
|
|
import ViewStructure from './view-structure';
|
|
|
|
import PageUtils from './view-structure/page-utils';
|
|
|
|
import NewFolderDialog from './view-structure/new-folder-dialog';
|
|
|
|
import AddNewPageDialog from './view-structure/add-new-page-dialog';
|
|
|
|
import ViewStructureFooter from './view-structure/view-structure-footer';
|
2024-05-15 09:37:27 +00:00
|
|
|
import { generateUniqueId, isObjectNotEmpty } from './utils';
|
2024-05-15 03:57:30 +00:00
|
|
|
import Folder from './models/folder';
|
|
|
|
import Page from './models/page';
|
2024-05-27 09:19:58 +00:00
|
|
|
import wikiAPI from '../../utils/wiki-api';
|
2024-05-24 03:06:54 +00:00
|
|
|
import { FOLDER } from './constant';
|
2024-06-05 05:52:33 +00:00
|
|
|
import { Utils } from '../../utils/utils';
|
2024-05-15 03:57:30 +00:00
|
|
|
|
2024-05-24 03:06:54 +00:00
|
|
|
import './side-panel.css';
|
2024-05-15 03:57:30 +00:00
|
|
|
|
2024-05-15 09:37:27 +00:00
|
|
|
const { repoName } = window.wiki.config;
|
|
|
|
|
2024-05-15 03:57:30 +00:00
|
|
|
const propTypes = {
|
|
|
|
closeSideBar: PropTypes.bool.isRequired,
|
|
|
|
isLoading: PropTypes.bool.isRequired,
|
|
|
|
config: PropTypes.object.isRequired,
|
|
|
|
saveWikiConfig: PropTypes.func.isRequired,
|
|
|
|
setCurrentPage: PropTypes.func.isRequired,
|
|
|
|
currentPageId: PropTypes.string,
|
|
|
|
};
|
|
|
|
|
|
|
|
class SidePanel extends Component {
|
|
|
|
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
this.state = {
|
|
|
|
isShowNewFolderDialog: false,
|
|
|
|
isShowAddNewPageDialog: false,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
confirmDeletePage = (pageId) => {
|
|
|
|
const config = deepCopy(this.props.config);
|
|
|
|
const { pages, navigation } = config;
|
|
|
|
const index = PageUtils.getPageIndexById(pageId, pages);
|
|
|
|
config.pages.splice(index, 1);
|
|
|
|
PageUtils.deletePage(navigation, pageId);
|
|
|
|
this.props.saveWikiConfig(config);
|
2024-06-07 01:45:05 +00:00
|
|
|
// TODO: To delete a page, do you need to delete all subpages at once (requires a new API)
|
2024-05-27 09:19:58 +00:00
|
|
|
wikiAPI.deleteWiki2Page(wikiId, pageId);
|
2024-05-15 03:57:30 +00:00
|
|
|
if (config.pages.length > 0) {
|
|
|
|
this.props.setCurrentPage(config.pages[0].id);
|
|
|
|
} else {
|
|
|
|
this.props.setCurrentPage('');
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2024-06-07 01:45:05 +00:00
|
|
|
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);
|
|
|
|
};
|
|
|
|
|
2024-05-21 07:39:58 +00:00
|
|
|
onAddNewPage = async ({ name, icon, path, docUuid, successCallback, errorCallback }) => {
|
2024-05-15 03:57:30 +00:00
|
|
|
const { config } = this.props;
|
|
|
|
const navigation = config.navigation;
|
|
|
|
const pageId = generateUniqueId(navigation);
|
2024-05-21 07:39:58 +00:00
|
|
|
const newPage = new Page({ id: pageId, name, icon, path, docUuid });
|
2024-06-07 01:45:05 +00:00
|
|
|
this.addPage(newPage, this.current_folder_id, successCallback, errorCallback);
|
2024-05-15 03:57:30 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
duplicatePage = async (fromPageConfig, successCallback, errorCallback) => {
|
|
|
|
const { config } = this.props;
|
|
|
|
const { name, from_page_id } = fromPageConfig;
|
|
|
|
const { navigation, pages } = config;
|
|
|
|
const fromPage = PageUtils.getPageById(pages, from_page_id);
|
|
|
|
const newPageId = generateUniqueId(navigation);
|
|
|
|
let newPageConfig = {
|
|
|
|
...fromPage,
|
|
|
|
id: newPageId,
|
|
|
|
name,
|
|
|
|
};
|
|
|
|
const newPage = new Page({ ...newPageConfig });
|
2024-06-07 01:45:05 +00:00
|
|
|
this.addPage(newPage, this.current_folder_id, successCallback, errorCallback);
|
2024-05-15 03:57:30 +00:00
|
|
|
};
|
|
|
|
|
2024-06-07 01:45:05 +00:00
|
|
|
addPage = (page, parentId, successCallback, errorCallback) => {
|
2024-05-15 03:57:30 +00:00
|
|
|
const { config } = this.props;
|
|
|
|
const navigation = config.navigation;
|
|
|
|
const pageId = page.id;
|
|
|
|
config.pages.push(page);
|
2024-06-07 01:45:05 +00:00
|
|
|
PageUtils.addPage(navigation, pageId, parentId);
|
2024-05-15 03:57:30 +00:00
|
|
|
config.navigation = navigation;
|
|
|
|
const onSuccess = () => {
|
|
|
|
this.props.setCurrentPage(pageId, successCallback);
|
|
|
|
successCallback();
|
|
|
|
};
|
|
|
|
this.props.saveWikiConfig(config, onSuccess, errorCallback);
|
|
|
|
};
|
|
|
|
|
|
|
|
onUpdatePage = (pageId, newPage) => {
|
|
|
|
if (newPage.name === '') {
|
|
|
|
toaster.danger(gettext('Page name cannot be empty'));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const { config } = this.props;
|
|
|
|
let pages = config.pages;
|
|
|
|
let currentPage = pages.find(page => page.id === pageId);
|
|
|
|
Object.assign(currentPage, newPage);
|
|
|
|
config.pages = pages;
|
|
|
|
this.props.saveWikiConfig(config);
|
|
|
|
};
|
|
|
|
|
|
|
|
movePage = ({ moved_view_id, target_view_id, source_view_folder_id, target_view_folder_id, move_position }) => {
|
|
|
|
let config = deepCopy(this.props.config);
|
|
|
|
let { navigation } = config;
|
|
|
|
PageUtils.movePage(navigation, moved_view_id, target_view_id, source_view_folder_id, target_view_folder_id, move_position);
|
|
|
|
config.navigation = navigation;
|
|
|
|
this.props.saveWikiConfig(config);
|
|
|
|
};
|
|
|
|
|
2024-06-07 01:45:05 +00:00
|
|
|
movePageOut = (moved_page_id, source_id, target_id, move_position) => {
|
2024-05-15 03:57:30 +00:00
|
|
|
let config = deepCopy(this.props.config);
|
|
|
|
let { navigation } = config;
|
2024-06-07 01:45:05 +00:00
|
|
|
PageUtils.movePageOut(navigation, moved_page_id, source_id, target_id, move_position);
|
2024-05-15 03:57:30 +00:00
|
|
|
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);
|
2024-06-07 01:45:05 +00:00
|
|
|
// TODO: delete all pages inside the folder, A new API is required
|
2024-05-15 03:57:30 +00:00
|
|
|
config.navigation = navigation;
|
|
|
|
this.props.saveWikiConfig(config);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Drag a folder to the front and back of another folder
|
2024-06-07 01:45:05 +00:00
|
|
|
onMoveFolder = (moved_folder_id, target_id, move_position) => {
|
2024-05-15 03:57:30 +00:00
|
|
|
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
|
2024-06-07 01:45:05 +00:00
|
|
|
let target_folder_index = PageUtils.getFolderIndexById(updatedNavigation, target_id);
|
2024-05-15 03:57:30 +00:00
|
|
|
if (target_folder_index === -1) {
|
|
|
|
updatedNavigation.forEach(item => {
|
|
|
|
if (item.type === FOLDER) {
|
2024-06-07 01:45:05 +00:00
|
|
|
target_folder_index = PageUtils.getFolderIndexById(item.children, target_id);
|
2024-05-15 03:57:30 +00:00
|
|
|
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
|
2024-06-07 01:45:05 +00:00
|
|
|
moveFolderToFolder = (moved_folder_id, target_id) => {
|
2024-05-15 03:57:30 +00:00
|
|
|
let { config } = this.props;
|
|
|
|
let { navigation } = config;
|
|
|
|
|
|
|
|
// Find the folder and move it to this new folder
|
2024-06-07 01:45:05 +00:00
|
|
|
let target_folder = PageUtils.getFolderById(navigation, target_id);
|
2024-05-15 03:57:30 +00:00
|
|
|
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 = () => {
|
|
|
|
const { config } = this.props;
|
|
|
|
const { pages, navigation } = config;
|
|
|
|
return (
|
2024-05-24 03:06:54 +00:00
|
|
|
<div className="wiki2-pages-container">
|
2024-05-15 03:57:30 +00:00
|
|
|
<ViewStructure
|
2024-05-23 09:16:44 +00:00
|
|
|
isEditMode={isWiki2}
|
2024-05-15 03:57:30 +00:00
|
|
|
navigation={navigation}
|
|
|
|
views={pages}
|
|
|
|
onToggleAddView={this.openAddPageDialog}
|
|
|
|
onDeleteView={this.confirmDeletePage}
|
|
|
|
onUpdatePage={this.onUpdatePage}
|
|
|
|
onSelectView={this.props.setCurrentPage}
|
|
|
|
onMoveView={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}
|
2024-06-07 01:45:05 +00:00
|
|
|
addPageInside={this.addPageInside}
|
2024-05-15 03:57:30 +00:00
|
|
|
/>
|
|
|
|
{this.state.isShowNewFolderDialog &&
|
|
|
|
<NewFolderDialog
|
|
|
|
onAddFolder={this.addPageFolder}
|
|
|
|
onToggleAddFolderDialog={this.onToggleAddFolder}
|
|
|
|
/>
|
|
|
|
}
|
|
|
|
{this.state.isShowAddNewPageDialog &&
|
|
|
|
<AddNewPageDialog
|
|
|
|
toggle={this.closeAddNewPageDialog}
|
|
|
|
onAddNewPage={this.onAddNewPage}
|
2024-06-07 01:45:05 +00:00
|
|
|
title={gettext('Add page')}
|
2024-05-15 03:57:30 +00:00
|
|
|
/>
|
|
|
|
}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2024-05-29 07:19:42 +00:00
|
|
|
renderNoFolder = () => {
|
|
|
|
return (
|
|
|
|
<div className="wiki2-pages-container">
|
|
|
|
{isWiki2 &&
|
|
|
|
<ViewStructureFooter
|
|
|
|
onToggleAddView={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}
|
2024-06-07 01:45:05 +00:00
|
|
|
title={gettext('Add page')}
|
2024-05-29 07:19:42 +00:00
|
|
|
/>
|
|
|
|
}
|
|
|
|
</div>
|
|
|
|
);
|
2024-05-15 03:57:30 +00:00
|
|
|
};
|
|
|
|
|
2024-06-05 05:52:33 +00:00
|
|
|
handleAddNewPage = () => {
|
|
|
|
const pageName = 'Untitled'; // default page name
|
|
|
|
const voidFn = () => void 0;
|
|
|
|
wikiAPI.createWiki2Page(wikiId, pageName).then(res => {
|
|
|
|
const { obj_name, parent_dir, doc_uuid,page_name } = res.data;
|
|
|
|
this.onAddNewPage({
|
|
|
|
name: page_name,
|
|
|
|
icon: '',
|
|
|
|
path: parent_dir === '/' ? `/${obj_name}` : `${parent_dir}/${obj_name}`,
|
|
|
|
docUuid: doc_uuid,
|
|
|
|
successCallback: voidFn,
|
|
|
|
errorCallback: voidFn,
|
|
|
|
});
|
|
|
|
}).catch((error) => {
|
|
|
|
let errMessage = Utils.getErrorMsg(error);
|
|
|
|
toaster.danger(errMessage);
|
|
|
|
this.onError();
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2024-05-15 03:57:30 +00:00
|
|
|
render() {
|
2024-05-29 07:19:42 +00:00
|
|
|
const { isLoading, config } = this.props;
|
2024-05-15 03:57:30 +00:00
|
|
|
return (
|
2024-05-24 03:06:54 +00:00
|
|
|
<div className={`wiki2-side-panel${this.props.closeSideBar ? '' : ' left-zero'}`}>
|
|
|
|
<div className="wiki2-side-panel-top">
|
|
|
|
<h4 className="text-truncate ml-0 mb-0" title={repoName}>{repoName}</h4>
|
2024-06-05 05:52:33 +00:00
|
|
|
<div id='wiki-add-new-page' className='add-new-page' onClick={this.handleAddNewPage}>
|
|
|
|
<i className='sf3-font sf3-font-new-page'></i>
|
|
|
|
</div>
|
|
|
|
<UncontrolledTooltip target="wiki-add-new-page">
|
|
|
|
{gettext('New page')}
|
|
|
|
</UncontrolledTooltip>
|
2024-05-15 03:57:30 +00:00
|
|
|
</div>
|
2024-05-24 03:06:54 +00:00
|
|
|
<div className="wiki2-side-nav">
|
2024-05-29 07:19:42 +00:00
|
|
|
{isLoading ? <Loading /> : (isObjectNotEmpty(config) ? this.renderFolderView() : this.renderNoFolder())}
|
2024-05-15 03:57:30 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
SidePanel.propTypes = propTypes;
|
|
|
|
|
|
|
|
export default SidePanel;
|