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

Wiki page trash (#6423)

* add wiki page trash

* update css

* update ui

* redesign

* update ui

* optimize code

* Update views.py

* Update models.py

* update notes

* Update mysql.sql

* change wiki trash UI

* redesign2

* update

* update

---------

Co-authored-by: 孙永强 <11704063+s-yongqiang@user.noreply.gitee.com>
Co-authored-by: r350178982 <32759763+r350178982@users.noreply.github.com>
Co-authored-by: Michael An <2331806369@qq.com>
This commit is contained in:
awu0403
2024-08-16 10:17:54 +08:00
committed by GitHub
parent 2b1c9cc8df
commit 6d0680f4b4
16 changed files with 800 additions and 73 deletions

View File

@@ -279,6 +279,7 @@
color: #212529;
}
.wiki-nav .wiki2-trash .sf3-font,
.wiki-nav .wiki-page-item .sf3-font.sf3-font-enlarge,
.wiki-nav .wiki-page-item .seafile-multicolor-icon-more-level {
color: #666;
@@ -289,3 +290,16 @@
height: 1em;
font-size: 16px;
}
.wiki-nav .wiki2-trash {
height: 32px;
display: flex;
align-items: center;
padding-left: 10px;
margin-top: 16px;
cursor: pointer;
}
.wiki-nav .wiki2-trash:hover {
background-color: #EFEFED;
}

View File

@@ -239,6 +239,7 @@ class Wiki extends Component {
onCloseSide={this.onCloseSide}
config={this.state.config}
updateWikiConfig={this.updateWikiConfig}
getWikiConfig={this.getWikiConfig}
setCurrentPage={this.setCurrentPage}
currentPageId={this.state.currentPageId}
onUpdatePage={this.onUpdatePage}

View File

@@ -23,15 +23,5 @@ export default class WikiConfig {
}
}
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

@@ -12,6 +12,7 @@ import { isObjectNotEmpty } from './utils';
import wikiAPI from '../../utils/wiki-api';
import { Utils } from '../../utils/utils';
import WikiExternalOperations from './wiki-external-operations';
import WikiTrashDialog from './wiki-trash-dialog';
import './side-panel.css';
@@ -22,13 +23,19 @@ const propTypes = {
isLoading: PropTypes.bool.isRequired,
config: PropTypes.object.isRequired,
updateWikiConfig: PropTypes.func.isRequired,
getWikiConfig: PropTypes.func.isRequired,
setCurrentPage: PropTypes.func.isRequired,
currentPageId: PropTypes.string,
onUpdatePage: PropTypes.func.isRequired,
};
class SidePanel extends Component {
constructor(props) {
super(props);
this.state = {
isShowTrashDialog: false,
};
}
confirmDeletePage = (pageId) => {
const config = deepCopy(this.props.config);
const { pages } = config;
@@ -93,6 +100,10 @@ class SidePanel extends Component {
this.props.updateWikiConfig(config);
};
toggelTrashDialog = () => {
this.setState({ 'isShowTrashDialog': !this.state.isShowTrashDialog });
};
renderWikiNav = () => {
const { config, onUpdatePage } = this.props;
const { pages, navigation } = config;
@@ -112,6 +123,7 @@ class SidePanel extends Component {
duplicatePage={this.duplicatePage}
currentPageId={this.props.currentPageId}
addPageInside={this.addPageInside}
toggelTrashDialog={this.toggelTrashDialog}
/>
}
</div>
@@ -156,9 +168,16 @@ class SidePanel extends Component {
</UncontrolledTooltip>
</div>
<div className="wiki2-side-nav">
{isLoading ? <Loading /> : this.renderWikiNav()}
{isLoading ? <Loading/> : this.renderWikiNav()}
</div>
<WikiExternalOperations onAddWikiPage={this.handleAddNewPage.bind(false)} />
<WikiExternalOperations onAddWikiPage={this.handleAddNewPage.bind(false)}/>
{this.state.isShowTrashDialog && (
<WikiTrashDialog
showTrashDialog={this.state.isShowTrashDialog}
toggleTrashDialog={this.toggelTrashDialog}
getWikiConfig={this.props.getWikiConfig}
/>
)}
</div>
);
}

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { DropTarget, DragLayer } from 'react-dnd';
import html5DragDropContext from './html5DragDropContext';
import DraggedPageItem from './pages/dragged-page-item';
import { repoID } from '../../../utils/constants';
import { repoID, gettext } from '../../../utils/constants';
import '../css/wiki-nav.css';
@@ -21,6 +21,7 @@ class WikiNav extends Component {
currentPageId: PropTypes.string,
addPageInside: PropTypes.func,
updateWikiConfig: PropTypes.func.isRequired,
toggelTrashDialog: PropTypes.func.isRequired,
};
constructor(props) {
@@ -107,6 +108,10 @@ class WikiNav extends Component {
{navigation.map((item, index) => {
return this.renderPage(item, index, pages.length, isOnlyOnePage, id_page_map, layerDragProps);
})}
<div className='wiki2-trash' onClick={this.props.toggelTrashDialog}>
<span className="sf3-font-recycle1 sf3-font mr-2"></span>
<span>{gettext('Trash')}</span>
</div>
</div>
);
});

View File

@@ -0,0 +1,271 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Modal, ModalHeader, ModalBody } from 'reactstrap';
import moment from 'moment';
import { Utils } from '../../utils/utils';
import { gettext, wikiId } from '../../utils/constants';
import wikiAPI from '../../utils/wiki-api';
import ModalPortal from '../../components/modal-portal';
import toaster from '../../components/toast';
import Paginator from '../../components/paginator';
import WikiCleanTrash from '../../components/dialog/wiki-clean-trash';
import NavItemIcon from './common/nav-item-icon';
import '../../css/toolbar.css';
import '../../css/search.css';
import '../../css/wiki-trash-dialog.css';
const propTypes = {
showTrashDialog: PropTypes.bool.isRequired,
toggleTrashDialog: PropTypes.func.isRequired,
getWikiConfig: PropTypes.func.isRequired
};
class WikiTrashDialog extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
errorMsg: '',
items: [],
isCleanTrashDialogOpen: false,
currentPage: 1,
perPage: 100,
hasNextPage: false
};
}
componentDidMount() {
this.getItems();
}
getItems = (page) => {
wikiAPI.getWikiTrash(wikiId, page, this.state.perPage).then((res) => {
const { items, total_count } = res.data;
if (!page) {
page = 1;
}
this.setState({
currentPage: page,
hasNextPage: total_count - page * this.state.perPage > 0,
isLoading: false,
items: items,
});
});
};
resetPerPage = (perPage) => {
this.setState({
perPage: perPage
}, () => {
this.getItems(1);
});
};
cleanTrash = () => {
this.toggleCleanTrashDialog();
};
toggleCleanTrashDialog = () => {
this.setState({
isCleanTrashDialogOpen: !this.state.isCleanTrashDialogOpen
});
};
refreshTrash = () => {
this.setState({
isLoading: true,
errorMsg: '',
items: []
});
this.getItems();
};
render() {
const { showTrashDialog, toggleTrashDialog } = this.props;
const { isCleanTrashDialogOpen } = this.state;
const { isAdmin, enableUserCleanTrash, repoName } = window.wiki.config;
let title = gettext('{placeholder} Wiki Trash');
title = title.replace('{placeholder}', '<span class="op-target text-truncate mx-1">' + Utils.HTMLescape(repoName) + '</span>');
return (
<Modal className="trash-dialog" isOpen={showTrashDialog} toggle={toggleTrashDialog}>
<ModalHeader
close={
<>
<div className="but-contral">
{(isAdmin && enableUserCleanTrash) &&
<button className="btn btn-secondary clean flex-shrink-0 ml-4" onClick={this.cleanTrash}>{gettext('Clean')}</button>
}
<span aria-hidden="true" className="trash-dialog-close-icon sf3-font sf3-font-x-01 ml-4" onClick={toggleTrashDialog}></span>
</div>
</>
}
>
<div dangerouslySetInnerHTML={{ __html: title }}></div>
</ModalHeader>
<ModalBody>
<Content
data={this.state}
currentPage={this.state.currentPage}
curPerPage={this.state.perPage}
hasNextPage={this.state.hasNextPage}
getListByPage={this.getItems}
resetPerPage={this.resetPerPage}
getWikiConfig={this.props.getWikiConfig}
/>
{isCleanTrashDialogOpen &&
<ModalPortal>
<WikiCleanTrash
wikiId={wikiId}
refreshTrash={this.refreshTrash}
toggleDialog={this.toggleCleanTrashDialog}
/>
</ModalPortal>
}
</ModalBody>
</Modal>
);
}
}
class Content extends React.Component {
constructor(props) {
super(props);
this.theadData = [
{ width: '3%', text: gettext('Name') },
{ width: '20%', text: '' },
{ width: '30%', text: gettext('Size') },
{ width: '37%', text: gettext('Delete Time') },
{ width: '10%', text: '' }
];
}
getPreviousPage = () => {
this.props.getListByPage(this.props.currentPage - 1);
};
getNextPage = () => {
this.props.getListByPage(this.props.currentPage + 1);
};
render() {
const { items } = this.props.data;
const { curPerPage, currentPage, hasNextPage } = this.props;
return (
<React.Fragment>
<table className="table-hover">
<thead>
<tr>
{this.theadData.map((item, index) => {
return <th key={index} className={index === 0 ? 'pl-3' : ''} width={item.width}>{item.text}</th>;
})}
</tr>
</thead>
<tbody>
{items.map((item, index) => {
return (
<Item
key={index}
item={item}
getWikiConfig={this.props.getWikiConfig}
/>
);
})}
</tbody>
</table>
<Paginator
gotoPreviousPage={this.getPreviousPage}
gotoNextPage={this.getNextPage}
currentPage={currentPage}
hasNextPage={hasNextPage}
curPerPage={curPerPage}
resetPerPage={this.props.resetPerPage}
/>
</React.Fragment>
);
}
}
Content.propTypes = {
data: PropTypes.object.isRequired,
getListByPage: PropTypes.func.isRequired,
resetPerPage: PropTypes.func.isRequired,
currentPage: PropTypes.number.isRequired,
curPerPage: PropTypes.number.isRequired,
hasNextPage: PropTypes.bool.isRequired,
getWikiConfig: PropTypes.func.isRequired
};
class Item extends React.Component {
constructor(props) {
super(props);
this.state = {
restored: false,
isIconShown: false,
getWikiConfig: PropTypes.func.isRequired
};
}
handleMouseOver = () => {
this.setState({ isIconShown: true });
};
handleMouseOut = () => {
this.setState({ isIconShown: false });
};
restoreItem = (e) => {
e.preventDefault();
const item = this.props.item;
wikiAPI.revertTrashPage(wikiId, item.page_id).then(res => {
this.setState({
restored: true
});
this.props.getWikiConfig();
toaster.success(gettext('Successfully restored 1 item.'));
}).catch((error) => {
let errorMsg = '';
if (error.response) {
errorMsg = error.response.data.error_msg || gettext('Error');
} else {
errorMsg = gettext('Please check the network.');
}
toaster.danger(errorMsg);
});
};
render() {
const item = this.props.item;
const { restored, isIconShown } = this.state;
if (restored) {
return null;
}
const { isAdmin } = window.wiki.config;
return (
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut} onFocus={this.handleMouseOver}>
<td><NavItemIcon symbol={'file'} disable={true} /></td>
<td>{item.name}</td>
<td>{Utils.bytesToSize(item.size)}</td>
<td title={moment(item.deleted_time).format('LLLL')}>{moment(item.deleted_time).format('YYYY-MM-DD')}</td>
<td>
{isAdmin &&
<a href="#" className={isIconShown ? '' : 'invisible'} onClick={this.restoreItem} role="button">{gettext('Restore')}</a>
}
</td>
</tr>
);
}
}
Item.propTypes = {
item: PropTypes.object.isRequired
};
WikiTrashDialog.propTypes = propTypes;
export default WikiTrashDialog;