mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-04 00:20:07 +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:
@@ -59,6 +59,11 @@ module.exports = {
|
|||||||
require.resolve('react-dev-utils/webpackHotDevClient'),
|
require.resolve('react-dev-utils/webpackHotDevClient'),
|
||||||
paths.appSrc + "/tc-accept.js",
|
paths.appSrc + "/tc-accept.js",
|
||||||
],
|
],
|
||||||
|
userNotifications: [
|
||||||
|
require.resolve('./polyfills'),
|
||||||
|
require.resolve('react-dev-utils/webpackHotDevClient'),
|
||||||
|
paths.appSrc + "/user-notifications.js",
|
||||||
|
],
|
||||||
wiki: [
|
wiki: [
|
||||||
require.resolve('./polyfills'),
|
require.resolve('./polyfills'),
|
||||||
require.resolve('react-dev-utils/webpackHotDevClient'),
|
require.resolve('react-dev-utils/webpackHotDevClient'),
|
||||||
|
@@ -60,6 +60,7 @@ module.exports = {
|
|||||||
entry: {
|
entry: {
|
||||||
markdownEditor: [require.resolve('./polyfills'), paths.appIndexJs],
|
markdownEditor: [require.resolve('./polyfills'), paths.appIndexJs],
|
||||||
TCAccept: [require.resolve('./polyfills'), paths.appSrc + "/tc-accept.js"],
|
TCAccept: [require.resolve('./polyfills'), paths.appSrc + "/tc-accept.js"],
|
||||||
|
userNotifications: [require.resolve('./polyfills'), paths.appSrc + "/user-notifications.js"],
|
||||||
wiki: [require.resolve('./polyfills'), paths.appSrc + "/wiki.js"],
|
wiki: [require.resolve('./polyfills'), paths.appSrc + "/wiki.js"],
|
||||||
fileHistory: [require.resolve('./polyfills'), paths.appSrc + "/file-history.js"],
|
fileHistory: [require.resolve('./polyfills'), paths.appSrc + "/file-history.js"],
|
||||||
fileHistoryOld: [require.resolve('./polyfills'), paths.appSrc + "/file-history-old.js"],
|
fileHistoryOld: [require.resolve('./polyfills'), paths.appSrc + "/file-history-old.js"],
|
||||||
|
@@ -44,7 +44,7 @@
|
|||||||
"react-responsive": "^6.1.2",
|
"react-responsive": "^6.1.2",
|
||||||
"react-select": "^2.4.1",
|
"react-select": "^2.4.1",
|
||||||
"reactstrap": "^6.4.0",
|
"reactstrap": "^6.4.0",
|
||||||
"seafile-js": "^0.2.144",
|
"seafile-js": "^0.2.145",
|
||||||
"socket.io-client": "^2.2.0",
|
"socket.io-client": "^2.2.0",
|
||||||
"sw-precache-webpack-plugin": "0.11.4",
|
"sw-precache-webpack-plugin": "0.11.4",
|
||||||
"unified": "^7.0.0",
|
"unified": "^7.0.0",
|
||||||
|
@@ -7,7 +7,7 @@ import { Utils } from '../../utils/utils';
|
|||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
noticeItem: PropTypes.object.isRequired,
|
noticeItem: PropTypes.object.isRequired,
|
||||||
onNoticeItemClick: PropTypes.func.isRequired,
|
onNoticeItemClick: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
const MSG_TYPE_ADD_USER_TO_GROUP = 'add_user_to_group';
|
const MSG_TYPE_ADD_USER_TO_GROUP = 'add_user_to_group';
|
||||||
@@ -206,7 +206,19 @@ class NoticeItem extends React.Component {
|
|||||||
return '';
|
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'}>
|
<li onClick={this.onNoticeItemClick} className={noticeItem.seen ? 'read' : 'unread'}>
|
||||||
<div className="notice-item">
|
<div className="notice-item">
|
||||||
<div className="main-info">
|
<div className="main-info">
|
||||||
|
@@ -19,7 +19,8 @@ class Notification extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onClick = () => {
|
onClick = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
if (this.state.showNotice) {
|
if (this.state.showNotice) {
|
||||||
seafileAPI.updateNotifications();
|
seafileAPI.updateNotifications();
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -35,7 +36,7 @@ class Notification extends React.Component {
|
|||||||
loadNotices = () => {
|
loadNotices = () => {
|
||||||
let page = 1;
|
let page = 1;
|
||||||
let perPage = 5;
|
let perPage = 5;
|
||||||
seafileAPI.listPopupNotices(page, perPage).then(res => {
|
seafileAPI.listNotifications(page, perPage).then(res => {
|
||||||
let noticeList = res.data.notification_list;
|
let noticeList = res.data.notification_list;
|
||||||
this.setState({noticeList: noticeList});
|
this.setState({noticeList: noticeList});
|
||||||
});
|
});
|
||||||
@@ -61,7 +62,7 @@ class Notification extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="notifications">
|
<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="sf2-icon-bell"></span>
|
||||||
<span className={`num ${this.state.unseenCount ? '' : 'hide'}`}>{this.state.unseenCount}</span>
|
<span className={`num ${this.state.unseenCount ? '' : 'hide'}`}>{this.state.unseenCount}</span>
|
||||||
</a>
|
</a>
|
||||||
|
17
frontend/src/css/user-notifications.css
Normal file
17
frontend/src/css/user-notifications.css
Normal 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;
|
||||||
|
}
|
215
frontend/src/user-notifications.js
Normal file
215
frontend/src/user-notifications.js
Normal 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')
|
||||||
|
);
|
@@ -2006,37 +2006,6 @@ a.sf-popover-item {
|
|||||||
width:180px;
|
width:180px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* notice page */
|
|
||||||
#notices-table .unread {
|
|
||||||
font-weight:bold;
|
|
||||||
}
|
|
||||||
#notices-table .avatar-cell {
|
|
||||||
vertical-align:top;
|
|
||||||
text-align:center;
|
|
||||||
padding-top:8px;
|
|
||||||
}
|
|
||||||
#notices-table .avatar {
|
|
||||||
border-radius:1000px;
|
|
||||||
}
|
|
||||||
#notices-table .brief,
|
|
||||||
#notices-table .detail {
|
|
||||||
margin:0.2em 100px .2em 0;
|
|
||||||
}
|
|
||||||
#notices-table .detail {
|
|
||||||
color:#9d9b9c;
|
|
||||||
cursor:pointer;
|
|
||||||
font-weight:normal;
|
|
||||||
}
|
|
||||||
#notice-list .topic,
|
|
||||||
#notices-table .topic {
|
|
||||||
padding:4px 13px;
|
|
||||||
border-left:3px solid #e0e0e0;
|
|
||||||
margin-left:7px;
|
|
||||||
}
|
|
||||||
#notices-table a {
|
|
||||||
font-weight:normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* pwd strength */
|
/* pwd strength */
|
||||||
#pwd_strength {
|
#pwd_strength {
|
||||||
margin:-15px 0 20px;
|
margin:-15px 0 20px;
|
||||||
|
@@ -218,6 +218,16 @@ a:hover { color:#eb8205; }
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.op-bar-btn {
|
||||||
|
border-color: #ccc;
|
||||||
|
border-radius: 2px;
|
||||||
|
height: 30px;
|
||||||
|
line-height: 28px;
|
||||||
|
font-weight: normal;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
min-width: 55px;
|
||||||
|
}
|
||||||
|
|
||||||
/* UI Widget */
|
/* UI Widget */
|
||||||
|
|
||||||
/**** caret ****/
|
/**** caret ****/
|
||||||
|
@@ -1,98 +0,0 @@
|
|||||||
{% extends "base_wide_page.html" %}
|
|
||||||
{% load avatar_tags i18n seahub_tags %}
|
|
||||||
|
|
||||||
{% block sub_title %}{% trans "Notices" %} - {% endblock %}
|
|
||||||
|
|
||||||
{% block wide_page_content %}
|
|
||||||
<div class="tabnav">
|
|
||||||
<ul class="tabnav-tabs">
|
|
||||||
<li class="tabnav-tab tabnav-tab-cur"><a href="{% url 'user_notification_list' %}">{% trans "Notices" %}</a></li>
|
|
||||||
</ul>
|
|
||||||
<div class="fright">
|
|
||||||
<button id="mark-all-read">{% trans "Mark all read" %}</button>
|
|
||||||
<button id="clear">{% trans "Clear" %}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if notices %}
|
|
||||||
<table id="notices-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th width="7%"></th>
|
|
||||||
<th width="73%">{% trans "Message"%}</th>
|
|
||||||
<th width="20%">{% trans "Time"%}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% include "notifications/user_notification_tr.html" %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{% if notices_more %}
|
|
||||||
<div id="notices-more">
|
|
||||||
<div id="notices-loading" class="hide"><span class="loading-icon"></span></div>
|
|
||||||
<button id="notices-more-btn" class="full-width-btn">{% trans 'More' %}</button>
|
|
||||||
<p id="notices-error" class="error hide"></p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block extra_script %}
|
|
||||||
<script type="text/javascript">
|
|
||||||
$('#clear').on('click', function() {
|
|
||||||
location.href = "{% url 'user_notification_remove' %}";
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#mark-all-read').on('click', function() {
|
|
||||||
var unread_items = $('#notices-table .unread');
|
|
||||||
if (unread_items.length > 0) {
|
|
||||||
$.ajax({
|
|
||||||
url: "{% url 'api-v2.1-notifications' %}",
|
|
||||||
type: 'PUT',
|
|
||||||
dataType: 'json',
|
|
||||||
beforeSend: prepareCSRFToken,
|
|
||||||
success: function() {
|
|
||||||
unread_items.removeClass('unread').addClass('read');
|
|
||||||
},
|
|
||||||
error: ajaxErrorHandler
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#notices-table .detail').on('click', function() {
|
|
||||||
location.href = $('.brief a', $(this).parent()).attr('href');
|
|
||||||
});
|
|
||||||
|
|
||||||
var start = {{start}}, limit = {{limit}};
|
|
||||||
$('#notices-more-btn').on('click', function() {
|
|
||||||
$(this).addClass('hide');
|
|
||||||
$('#notices-loading').removeClass('hide');
|
|
||||||
$.ajax({
|
|
||||||
url:'{% url "user_notification_more" %}' + '?start=' + start + '&limit=' + limit,
|
|
||||||
dataType: 'json',
|
|
||||||
cache: false,
|
|
||||||
success: function(data) {
|
|
||||||
$('#notices-loading').addClass('hide');
|
|
||||||
$('#notices-table').append(data['html']);
|
|
||||||
|
|
||||||
$('#notices-table .detail').off().on('click', function() {
|
|
||||||
location.href = $('.brief a', $(this).parent()).attr('href');
|
|
||||||
});
|
|
||||||
|
|
||||||
start = data['new_start'];
|
|
||||||
|
|
||||||
if (data['notices_more']) {
|
|
||||||
$('#notices-more-btn').removeClass('hide');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: function(jqXHR, textStatus, errorThrown) {
|
|
||||||
$('#notices-loading').addClass('hide');
|
|
||||||
if (!jqXHR.responseText && textStatus != 'abort') {
|
|
||||||
$('#notices-error').html("{% trans "Failed. Please check the network." %}").removeClass('hide');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
@@ -0,0 +1,18 @@
|
|||||||
|
{% extends 'base_for_react.html' %}
|
||||||
|
{% load seahub_tags i18n %}
|
||||||
|
{% load render_bundle from webpack_loader %}
|
||||||
|
|
||||||
|
{% block sub_title %}{% trans "Notifications" %} - {% endblock %}
|
||||||
|
|
||||||
|
{% block extra_style %}
|
||||||
|
{% render_bundle 'userNotifications' 'css' %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_script %}
|
||||||
|
<script type="text/javascript">
|
||||||
|
// overwrite the one in base_for_react.html
|
||||||
|
window.app.pageOptions = {
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
{% render_bundle 'userNotifications' 'js' %}
|
||||||
|
{% endblock %}
|
@@ -1,48 +0,0 @@
|
|||||||
{% load i18n seahub_tags avatar_tags %}
|
|
||||||
{% for notice in notices %}
|
|
||||||
{% if notice.seen %}
|
|
||||||
<tr class="read">
|
|
||||||
{% else %}
|
|
||||||
<tr class="unread">
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<td class="avatar-cell">
|
|
||||||
{% if notice.msg_from %}
|
|
||||||
<a href="{% url 'user_profile' notice.msg_from %}">{% avatar notice.msg_from 32 %}</a>
|
|
||||||
{% else %}
|
|
||||||
<img src={{notice.default_avatar_url}} width="32" height="32" class="avatar" alt="" />
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{% if notice.is_file_uploaded_msg %}
|
|
||||||
<p class="brief">{{ notice.format_file_uploaded_msg|safe }}</p>
|
|
||||||
|
|
||||||
{% elif notice.is_repo_share_msg %}
|
|
||||||
<p class="brief">{{ notice.format_repo_share_msg|safe }}</p>
|
|
||||||
|
|
||||||
{% elif notice.is_repo_share_to_group_msg %}
|
|
||||||
<p class="brief">{{ notice.format_repo_share_to_group_msg|safe }}</p>
|
|
||||||
|
|
||||||
{% elif notice.is_group_join_request %}
|
|
||||||
<p class="brief">{{ notice.format_group_join_request|safe }}</p>
|
|
||||||
|
|
||||||
{% elif notice.is_file_comment_msg %}
|
|
||||||
<p class="brief">{{ notice.format_file_comment_msg|safe }}</p>
|
|
||||||
|
|
||||||
{% elif notice.is_draft_comment_msg %}
|
|
||||||
<p class="brief">{{ notice.format_draft_comment_msg|safe }}</p>
|
|
||||||
|
|
||||||
{% elif notice.is_draft_reviewer_msg %}
|
|
||||||
<p class="brief">{{ notice.format_draft_reviewer_msg|safe }}</p>
|
|
||||||
|
|
||||||
{% elif notice.is_guest_invitation_accepted_msg %}
|
|
||||||
<p class="brief">{{ notice.format_guest_invitation_accepted_msg|safe }}</p>
|
|
||||||
|
|
||||||
{% elif notice.is_add_user_to_group %}
|
|
||||||
<p class="brief">{{ notice.format_add_user_to_group|safe }}</p>
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td>{{ notice.timestamp|translate_seahub_time }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
@@ -5,6 +5,4 @@ from .views import *
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
########## user notifications
|
########## user notifications
|
||||||
url(r'^list/$', user_notification_list, name='user_notification_list'),
|
url(r'^list/$', user_notification_list, name='user_notification_list'),
|
||||||
url(r'^more/$', user_notification_more, name='user_notification_more'),
|
|
||||||
url(r'^remove/$', user_notification_remove, name='user_notification_remove'),
|
|
||||||
]
|
]
|
||||||
|
@@ -22,74 +22,8 @@ logger = logging.getLogger(__name__)
|
|||||||
########## user notifications
|
########## user notifications
|
||||||
@login_required
|
@login_required
|
||||||
def user_notification_list(request):
|
def user_notification_list(request):
|
||||||
"""
|
return render(request, "notifications/user_notification_list_react.html", {
|
||||||
|
})
|
||||||
Arguments:
|
|
||||||
- `request`:
|
|
||||||
"""
|
|
||||||
username = request.user.username
|
|
||||||
count = 25 # initial notification count
|
|
||||||
limit = 25 # next a mount of notifications fetched by AJAX
|
|
||||||
|
|
||||||
notices = UserNotification.objects.get_user_notifications(username)[:count]
|
|
||||||
|
|
||||||
# Add 'msg_from' or 'default_avatar_url' to notice.
|
|
||||||
notices = add_notice_from_info(notices)
|
|
||||||
|
|
||||||
notices_more = True if len(notices) == count else False
|
|
||||||
|
|
||||||
return render(request, "notifications/user_notification_list.html", {
|
|
||||||
'notices': notices,
|
|
||||||
'start': count,
|
|
||||||
'limit': limit,
|
|
||||||
'notices_more': notices_more,
|
|
||||||
})
|
|
||||||
|
|
||||||
@login_required_ajax
|
|
||||||
def user_notification_more(request):
|
|
||||||
"""Fetch next ``limit`` notifications starts from ``start``.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
- `request`:
|
|
||||||
- `start`:
|
|
||||||
- `limit`:
|
|
||||||
"""
|
|
||||||
username = request.user.username
|
|
||||||
start = int(request.GET.get('start', 0))
|
|
||||||
limit = int(request.GET.get('limit', 0))
|
|
||||||
|
|
||||||
notices = UserNotification.objects.get_user_notifications(username)[
|
|
||||||
start: start+limit]
|
|
||||||
|
|
||||||
# Add 'msg_from' or 'default_avatar_url' to notice.
|
|
||||||
notices = add_notice_from_info(notices)
|
|
||||||
|
|
||||||
notices_more = True if len(notices) == limit else False
|
|
||||||
new_start = start+limit
|
|
||||||
|
|
||||||
ctx = {'notices': notices}
|
|
||||||
html = render_to_string("notifications/user_notification_tr.html", ctx)
|
|
||||||
|
|
||||||
ct = 'application/json; charset=utf-8'
|
|
||||||
return HttpResponse(json.dumps({
|
|
||||||
'html':html,
|
|
||||||
'notices_more':notices_more,
|
|
||||||
'new_start': new_start}), content_type=ct)
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def user_notification_remove(request):
|
|
||||||
"""
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
- `request`:
|
|
||||||
"""
|
|
||||||
UserNotification.objects.remove_user_notifications(request.user.username)
|
|
||||||
|
|
||||||
messages.success(request, _("Successfully cleared all notices."))
|
|
||||||
next_page = request.META.get('HTTP_REFERER', None)
|
|
||||||
if not next_page:
|
|
||||||
next_page = settings.SITE_ROOT
|
|
||||||
return HttpResponseRedirect(next_page)
|
|
||||||
|
|
||||||
def add_notice_from_info(notices):
|
def add_notice_from_info(notices):
|
||||||
'''Add 'msg_from' or 'default_avatar_url' to notice.
|
'''Add 'msg_from' or 'default_avatar_url' to notice.
|
||||||
|
Reference in New Issue
Block a user