1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-17 15:53:28 +00:00

[user notifications] rewrote it with react (#4486)

* [user notifications] rewrote it with react

* rewrote 'user notifications' page with react
* cleaned up the related files & code
* fixed 'popup notices'

* [seafile-js] updated the version
This commit is contained in:
llj
2020-03-24 15:24:47 +08:00
committed by GitHub
parent ac9e9b9ea4
commit 24b3b516bd
14 changed files with 287 additions and 253 deletions

View File

@@ -7,7 +7,7 @@ import { Utils } from '../../utils/utils';
const propTypes = {
noticeItem: PropTypes.object.isRequired,
onNoticeItemClick: PropTypes.func.isRequired,
onNoticeItemClick: PropTypes.func
};
const MSG_TYPE_ADD_USER_TO_GROUP = 'add_user_to_group';
@@ -206,7 +206,19 @@ class NoticeItem extends React.Component {
return '';
}
return (
return this.props.tr ? (
<tr className={noticeItem.seen ? 'read' : 'unread font-weight-bold'}>
<td className="text-center">
<img src={avatar_url} width="32" height="32" className="avatar" alt="" />
</td>
<td className="pr-8">
<p className="m-0" dangerouslySetInnerHTML={{__html: notice}}></p>
</td>
<td>
{moment(noticeItem.time).fromNow()}
</td>
</tr>
) : (
<li onClick={this.onNoticeItemClick} className={noticeItem.seen ? 'read' : 'unread'}>
<div className="notice-item">
<div className="main-info">

View File

@@ -19,7 +19,8 @@ class Notification extends React.Component {
});
}
onClick = () => {
onClick = (e) => {
e.preventDefault();
if (this.state.showNotice) {
seafileAPI.updateNotifications();
this.setState({
@@ -35,7 +36,7 @@ class Notification extends React.Component {
loadNotices = () => {
let page = 1;
let perPage = 5;
seafileAPI.listPopupNotices(page, perPage).then(res => {
seafileAPI.listNotifications(page, perPage).then(res => {
let noticeList = res.data.notification_list;
this.setState({noticeList: noticeList});
});
@@ -61,7 +62,7 @@ class Notification extends React.Component {
return (
<div id="notifications">
<a href="#" onClick={this.onClick} className="no-deco" id="notice-icon" title="Notifications" aria-label={gettext('Notifications')}>
<a href="#" onClick={this.onClick} className="no-deco" id="notice-icon" title={gettext('Notifications')} aria-label={gettext('Notifications')}>
<span className="sf2-icon-bell"></span>
<span className={`num ${this.state.unseenCount ? '' : 'hide'}`}>{this.state.unseenCount}</span>
</a>

View File

@@ -0,0 +1,17 @@
body {
overflow: hidden;
}
#wrapper {
height: 100%;
}
.top-header {
background: #f4f4f7;
border-bottom: 1px solid #e8e8e8;
padding: .5rem 1rem;
flex-shrink: 0;
}
.op-bar {
padding: 9px 10px;
background: #f2f2f2;
border-radius: 2px;
}

View File

@@ -0,0 +1,215 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { navigate } from '@reach/router';
import { Utils } from './utils/utils';
import { gettext, siteRoot, mediaUrl, logoPath, logoWidth, logoHeight, siteTitle } from './utils/constants';
import { seafileAPI } from './utils/seafile-api';
import Loading from './components/loading';
import Paginator from './components/paginator';
import CommonToolbar from './components/toolbar/common-toolbar';
import NoticeItem from './components/common/notice-item';
import './css/toolbar.css';
import './css/search.css';
import './css/user-notifications.css';
class UserNotifications extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
errorMsg: '',
currentPage: 1,
perPage: 25,
hasNextPage: false,
items: []
};
}
componentDidMount() {
let urlParams = (new URL(window.location)).searchParams;
const {
currentPage, perPage
} = this.state;
this.setState({
perPage: parseInt(urlParams.get('per_page') || perPage),
currentPage: parseInt(urlParams.get('page') || currentPage)
}, () => {
this.getItems(this.state.currentPage);
});
}
getItems = (page) => {
const { perPage } = this.state;
seafileAPI.listNotifications(page, perPage).then((res) => {
this.setState({
isLoading: false,
items: res.data.notification_list,
currentPage: page,
hasNextPage: Utils.hasNextPage(page, perPage, res.data.count)
});
}).catch((error) => {
this.setState({
isLoading: false,
errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
});
});
}
resetPerPage = (perPage) => {
this.setState({
perPage: perPage
}, () => {
this.getItems(1);
});
}
onSearchedClick = (selectedItem) => {
if (selectedItem.is_dir === true) {
let url = siteRoot + 'library/' + selectedItem.repo_id + '/' + selectedItem.repo_name + selectedItem.path;
navigate(url, {repalce: true});
} else {
let url = siteRoot + 'lib/' + selectedItem.repo_id + '/file' + Utils.encodePath(selectedItem.path);
let newWindow = window.open('about:blank');
newWindow.location.href = url;
}
}
markAllRead = () => {
seafileAPI.updateNotifications().then((res) => {
this.setState({
items: this.state.items.map(item => {
item.seen = true;
return item;
})
});
}).catch((error) => {
this.setState({
isLoading: false,
errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
});
});
}
clearAll = () => {
seafileAPI.deleteNotifications().then((res) => {
this.setState({
items: []
});
}).catch((error) => {
this.setState({
isLoading: false,
errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
});
});
}
render() {
return (
<React.Fragment>
<div className="h-100 d-flex flex-column">
<div className="top-header d-flex justify-content-between">
<a href={siteRoot}>
<img src={mediaUrl + logoPath} height={logoHeight} width={logoWidth} title={siteTitle} alt="logo" />
</a>
<CommonToolbar onSearchedClick={this.onSearchedClick} />
</div>
<div className="flex-auto container-fluid pt-4 pb-6 o-auto">
<div className="row">
<div className="col-md-10 offset-md-1">
<div className="d-flex justify-content-between align-items-center op-bar">
<h2 className="h4 m-0">{gettext('Notifications')}</h2>
<div>
<button className="btn btn-secondary op-bar-btn" onClick={this.markAllRead}>{gettext('Mark all read')}</button>
<button className="btn btn-secondary op-bar-btn ml-2" onClick={this.clearAll}>{gettext('Clear')}</button>
</div>
</div>
<Content
isLoading={this.state.isLoading}
errorMsg={this.state.errorMsg}
items={this.state.items}
currentPage={this.state.currentPage}
hasNextPage={this.state.hasNextPage}
curPerPage={this.state.perPage}
resetPerPage={this.resetPerPage}
getListByPage={this.getItems}
/>
</div>
</div>
</div>
</div>
</React.Fragment>
);
}
}
class Content extends React.Component {
constructor(props) {
super(props);
this.theadData = [
{width: '7%', text: ''},
{width: '73%', text: gettext('Message')},
{width: '20%', text: gettext('Time')}
];
}
getPreviousPage = () => {
this.props.getListByPage(this.props.currentPage - 1);
}
getNextPage = () => {
this.props.getListByPage(this.props.currentPage + 1);
}
render() {
const {
isLoading, errorMsg, items,
curPerPage, currentPage, hasNextPage
} = this.props;
if (isLoading) {
return <Loading />;
}
if (errorMsg) {
return <p className="error mt-6 text-center">{errorMsg}</p>;
}
return (
<React.Fragment>
<table className="table-hover">
<thead>
<tr>
{this.theadData.map((item, index) => {
return <th key={index} width={item.width}>{item.text}</th>;
})}
</tr>
</thead>
<tbody>
{items.map((item, index) => {
return (<NoticeItem key={index} noticeItem={item} tr={true} />);
})}
</tbody>
</table>
{items.length > 0 &&
<Paginator
gotoPreviousPage={this.getPreviousPage}
gotoNextPage={this.getNextPage}
currentPage={currentPage}
hasNextPage={hasNextPage}
curPerPage={curPerPage}
resetPerPage={this.props.resetPerPage}
/>
}
</React.Fragment>
);
}
}
ReactDOM.render(
<UserNotifications />,
document.getElementById('wrapper')
);