diff --git a/frontend/src/components/common/notification.js b/frontend/src/components/common/notification.js
index 26beef83f0..0551a11042 100644
--- a/frontend/src/components/common/notification.js
+++ b/frontend/src/components/common/notification.js
@@ -13,15 +13,17 @@ class Notification extends React.Component {
this.state = {
showNotice: false,
unseenCount: 0,
- noticeList: [],
+ generalNoticeList: [],
+ discussionNoticeList: [],
currentTab: 'general',
isShowNotificationDialog: this.getInitDialogState(),
};
}
componentDidMount() {
- seafileAPI.getUnseenNotificationCount().then(res => {
- this.setState({ unseenCount: res.data.unseen_count });
+ seafileAPI.listAllNotifications().then(res => {
+ let unseen_count = res.data.general_notification.unseen_count + res.data.discussion_notification.unseen_count;
+ this.setState({ unseenCount: unseen_count });
});
}
@@ -45,50 +47,52 @@ class Notification extends React.Component {
this.setState({
showNotice: true,
currentTab: tab
- }, () => {
- this.loadNotices();
});
};
loadNotices = () => {
let page = 1;
let perPage = 5;
- if (this.state.currentTab === 'general') {
- seafileAPI.listNotifications(page, perPage).then(res => {
- let noticeList = res.data.notification_list;
- this.setState({ noticeList: noticeList });
+ seafileAPI.listAllNotifications(page, perPage).then(res => {
+ let generalNoticeList = res.data.general_notification.notification_list;
+ let discussionNoticeList = res.data.discussion_notification.notification_list;
+ this.setState({
+ generalNoticeList: generalNoticeList,
+ discussionNoticeList: discussionNoticeList
});
- }
- if (this.state.currentTab === 'discussion') {
- seafileAPI.listSdocNotifications(page, perPage).then(res => {
- let noticeList = res.data.notification_list;
- this.setState({ noticeList: noticeList });
- });
- }
-
+ });
};
onNoticeItemClick = (noticeItem) => {
- let noticeList = this.state.noticeList.map(item => {
- if (item.id === noticeItem.id) {
- item.seen = true;
- }
- return item;
- });
-
if (this.state.currentTab === 'general') {
+ let noticeList = this.state.generalNoticeList.map(item => {
+ if (item.id === noticeItem.id) {
+ item.seen = true;
+ }
+ return item;
+ });
+ let unseenCount = this.state.unseenCount === 0 ? 0 : this.state.unseenCount - 1;
+ this.setState({
+ generalNoticeList: noticeList,
+ unseenCount: unseenCount,
+ });
seafileAPI.markNoticeAsRead(noticeItem.id);
}
if (this.state.currentTab === 'discussion') {
+ let noticeList = this.state.discussionNoticeList.map(item => {
+ if (item.id === noticeItem.id) {
+ item.seen = true;
+ }
+ return item;
+ });
+ let unseenCount = this.state.unseenCount === 0 ? 0 : this.state.unseenCount - 1;
+ this.setState({
+ discussionNoticeList: noticeList,
+ unseenCount: unseenCount,
+ });
seafileAPI.markSdocNoticeAsRead(noticeItem.id);
}
- let unseenCount = this.state.unseenCount === 0 ? 0 : this.state.unseenCount - 1;
- this.setState({
- noticeList: noticeList,
- unseenCount: unseenCount,
- });
-
};
getInitDialogState = () => {
@@ -137,15 +141,27 @@ class Notification extends React.Component {
onMarkAllNotifications={this.onMarkAllNotifications}
tabItemClick={this.tabItemClick}
>
-
- {this.state.noticeList.map(item => {
- return ();
- })}
-
+ {this.state.currentTab === 'general' &&
+
+ {this.state.generalNoticeList.map(item => {
+ return ();
+ })}
+
+ }
+ {this.state.currentTab === 'discussion' &&
+
+ {this.state.discussionNoticeList.map(item => {
+ return ();
+ })}
+
+ }
}
{this.state.isShowNotificationDialog &&
-
+
}
);
diff --git a/frontend/src/user-notifications.js b/frontend/src/user-notifications.js
index 6d264400be..c2d669e967 100644
--- a/frontend/src/user-notifications.js
+++ b/frontend/src/user-notifications.js
@@ -1,6 +1,16 @@
-import React from 'react';
+import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
-import { Modal, ModalHeader, ModalBody, Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
+import {
+ Modal,
+ ModalHeader,
+ ModalBody,
+ Dropdown,
+ DropdownToggle,
+ DropdownMenu,
+ DropdownItem,
+ TabPane,
+ Nav, NavItem, NavLink, TabContent
+} from 'reactstrap';
import { Utils } from './utils/utils';
import { gettext } from './utils/constants';
import { seafileAPI } from './utils/seafile-api';
@@ -24,6 +34,7 @@ class UserNotificationsDialog extends React.Component {
hasNextPage: false,
items: [],
isItemMenuShow: false,
+ activeTab: 'general',
};
}
@@ -37,56 +48,132 @@ class UserNotificationsDialog extends React.Component {
});
}
- getItems = (page) => {
+ getItems = (page, is_scroll = false) => {
this.setState({ isLoading: true });
- seafileAPI.listNotifications(page, PER_PAGE).then((res) => {
- this.setState({
- isLoading: false,
- items: [...this.state.items, ...res.data.notification_list],
- currentPage: page,
- hasNextPage: Utils.hasNextPage(page, PER_PAGE, res.data.count)
+ if (this.state.activeTab === 'general') {
+ seafileAPI.listNotifications(page, PER_PAGE).then((res) => {
+ if (is_scroll) {
+ this.setState({
+ isLoading: false,
+ items: [...this.state.items, ...res.data.notification_list],
+ currentPage: page,
+ hasNextPage: Utils.hasNextPage(page, PER_PAGE, res.data.count)
+ });
+ } else {
+ this.setState({
+ isLoading: false,
+ items: [...res.data.notification_list],
+ currentPage: page,
+ hasNextPage: Utils.hasNextPage(page, PER_PAGE, res.data.count)
+ });
+ }
+ }).catch((error) => {
+ this.setState({
+ isLoading: false,
+ errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
+ });
});
- }).catch((error) => {
- this.setState({
- isLoading: false,
- errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
+ } else if (this.state.activeTab === 'discussion') {
+ seafileAPI.listSdocNotifications(page, PER_PAGE).then((res) => {
+ if (is_scroll) {
+ this.setState({
+ isLoading: false,
+ items: [...this.state.items, ...res.data.notification_list],
+ currentPage: page,
+ hasNextPage: Utils.hasNextPage(page, PER_PAGE, res.data.count)
+ });
+ } else {
+ this.setState({
+ isLoading: false,
+ items: [...res.data.notification_list],
+ currentPage: page,
+ hasNextPage: Utils.hasNextPage(page, PER_PAGE, res.data.count)
+ });
+ }
+
+ }).catch((error) => {
+ this.setState({
+ isLoading: false,
+ errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
+ });
});
- });
+ }
+
};
markAllRead = () => {
- seafileAPI.updateNotifications().then((res) => {
- this.setState({
- items: this.state.items.map(item => {
- item.seen = true;
- return item;
- })
+ if (this.state.activeTab === 'general') {
+ 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
+ });
});
- }).catch((error) => {
- this.setState({
- isLoading: false,
- errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
+ } else if (this.state.activeTab === 'discussion') {
+ seafileAPI.updateSdocNotifications().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: []
+ if (this.state.activeTab === 'general') {
+ seafileAPI.deleteNotifications().then((res) => {
+ this.setState({
+ items: []
+ });
+ }).catch((error) => {
+ this.setState({
+ isLoading: false,
+ errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
+ });
});
- }).catch((error) => {
- this.setState({
- isLoading: false,
- errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
+ } else if (this.state.activeTab === 'discussion') {
+ seafileAPI.deleteSdocNotifications().then((res) => {
+ this.setState({
+ items: []
+ });
+ }).catch((error) => {
+ this.setState({
+ isLoading: false,
+ errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
+ });
});
- });
+ }
};
toggle = () => {
this.props.onNotificationDialogToggle();
};
+ tabItemClick = (e) => {
+ let tab = e.target.getAttribute('value');
+ this.setState({
+ activeTab: tab,
+ currentPage: 1
+ }, () => {
+ this.getItems(this.state.currentPage);
+ });
+ };
+
toggleDropDownMenu = () => {
this.setState({ isItemMenuShow: !this.state.isItemMenuShow });
};
@@ -96,7 +183,7 @@ class UserNotificationsDialog extends React.Component {
return;
}
if (this.notificationTableRef.offsetHeight + this.notificationTableRef.scrollTop + 1 >= this.tableRef.offsetHeight) {
- this.getItems(this.state.currentPage + 1);
+ this.getItems(this.state.currentPage + 1, true);
}
};
@@ -130,6 +217,49 @@ class UserNotificationsDialog extends React.Component {
);
};
+
+ renderNoticeContent = (content) => {
+ let activeTab = this.state.activeTab;
+ return (
+
+
+
+
+
+
+ {activeTab === 'general' &&
+
+ this.notificationTableRef = ref}
+ onScroll={this.onHandleScroll}>
+ {content}
+
+
+ }
+ {activeTab === 'discussion' &&
+
+ this.notificationTableRef = ref}
+ onScroll={this.onHandleScroll}>
+ {content}
+
+
+ }
+
+
+
+ );
+ };
+
render() {
const { isLoading, errorMsg, items } = this.state;
let content;
@@ -173,9 +303,7 @@ class UserNotificationsDialog extends React.Component {
zIndex={1046}>
{gettext('Notifications')}
- this.notificationTableRef = ref} onScroll={this.onHandleScroll}>
- {content}
-
+ {this.renderNoticeContent(content)}
);
@@ -184,6 +312,7 @@ class UserNotificationsDialog extends React.Component {
UserNotificationsDialog.propTypes = {
onNotificationDialogToggle: PropTypes.func.isRequired,
+ tabItemClick: PropTypes.func.isRequired,
};
export default UserNotificationsDialog;
diff --git a/frontend/src/utils/seafile-api.js b/frontend/src/utils/seafile-api.js
index 4d0f794c98..0b819d171c 100644
--- a/frontend/src/utils/seafile-api.js
+++ b/frontend/src/utils/seafile-api.js
@@ -1451,6 +1451,15 @@ class SeafileAPI {
}
// ---- Notification API
+ listAllNotifications(page, perPage) {
+ const url = this.server + '/api/v2.1/all-notifications/';
+ let params = {
+ page: page,
+ per_page: perPage
+ };
+ return this.req.get(url, { params: params });
+ }
+
listNotifications(page, perPage) {
const url = this.server + '/api/v2.1/notifications/';
let params = {
@@ -1485,6 +1494,11 @@ class SeafileAPI {
return this.req.delete(url);
}
+ deleteSdocNotifications() {
+ const url = this.server + '/api/v2.1/sdoc-notifications/';
+ return this.req.delete(url);
+ }
+
getUnseenNotificationCount() {
const url = this.server + '/api/v2.1/notifications/';
return this.req.get(url);
diff --git a/seahub/api2/endpoints/notifications.py b/seahub/api2/endpoints/notifications.py
index ec01b2be7a..dcabbabfea 100644
--- a/seahub/api2/endpoints/notifications.py
+++ b/seahub/api2/endpoints/notifications.py
@@ -227,7 +227,7 @@ class SdocNotificationsView(APIView):
"""
username = request.user.username
- unseen_notices = SeadocNotification.objects.filter(username, seen=False)
+ unseen_notices = SeadocNotification.objects.filter(username=username, seen=False)
for notice in unseen_notices:
notice.seen = True
notice.save()
@@ -236,6 +236,21 @@ class SdocNotificationsView(APIView):
cache.delete(cache_key)
return Response({'success': True})
+
+ def delete(self, request):
+ """ delete a sdoc notification by username
+
+ Permission checking:
+ 1. login user.
+ """
+ username = request.user.username
+
+ SeadocNotification.objects.remove_user_notifications(username)
+
+ cache_key = get_cache_key_of_unseen_sdoc_notifications(username)
+ cache.delete(cache_key)
+
+ return Response({'success': True})
@@ -283,4 +298,97 @@ class SdocNotificationView(APIView):
cache_key = get_cache_key_of_unseen_sdoc_notifications(username)
cache.delete(cache_key)
- return Response({'success': True})
\ No newline at end of file
+ return Response({'success': True})
+
+
+class AllNotificationsView(APIView):
+
+ authentication_classes = (TokenAuthentication, SessionAuthentication)
+ permission_classes = (IsAuthenticated,)
+ throttle_classes = (UserRateThrottle,)
+
+ def get(self, request):
+ """ used for get all notifications
+ general and discussion
+
+ Permission checking:
+ 1. login user.
+ """
+ result = {
+ 'general_notification': {},
+ 'discussion_notification': {}
+ }
+
+ username = request.user.username
+
+ try:
+ per_page = int(request.GET.get('per_page', ''))
+ page = int(request.GET.get('page', ''))
+ except ValueError:
+ per_page = 25
+ page = 1
+
+ start = (page - 1) * per_page
+ end = page * per_page
+
+ notice_list = UserNotification.objects.get_user_notifications(username)[start:end]
+ sdoc_notice_list = SeadocNotification.objects.list_all_by_user(username, start, end)
+
+ result_notices = update_notice_detail(request, notice_list)
+ sdoc_result_notices = update_sdoc_notice_detail(request, sdoc_notice_list)
+
+ notification_list = []
+ sdoc_notification_list = []
+
+ for i in result_notices:
+ if i.detail is not None:
+ notice = {}
+ notice['id'] = i.id
+ notice['type'] = i.msg_type
+ notice['detail'] = i.detail
+ notice['time'] = datetime_to_isoformat_timestr(i.timestamp)
+ notice['seen'] = i.seen
+
+ notification_list.append(notice)
+
+ for i in sdoc_result_notices:
+ if i.detail is not None:
+ notice = {}
+ notice['id'] = i.id
+ notice['type'] = i.msg_type
+ notice['detail'] = i.detail
+ notice['time'] = datetime_to_isoformat_timestr(i.created_at)
+ notice['seen'] = i.seen
+
+ sdoc_notification_list.append(notice)
+
+ cache_key = get_cache_key_of_unseen_notifications(username)
+ unseen_count_from_cache = cache.get(cache_key, None)
+
+ sdoc_cache_key = get_cache_key_of_unseen_sdoc_notifications(username)
+ sdoc_unseen_count_from_cache = cache.get(sdoc_cache_key, None)
+
+ # for case of count value is `0`
+ if unseen_count_from_cache is not None:
+ result['general_notification']['unseen_count'] = unseen_count_from_cache
+ else:
+ unseen_count = UserNotification.objects.filter(to_user=username, seen=False).count()
+ result['general_notification']['unseen_count'] = unseen_count
+ cache.set(cache_key, unseen_count)
+
+ if sdoc_unseen_count_from_cache is not None:
+ result['discussion_notification']['unseen_count'] = sdoc_unseen_count_from_cache
+ else:
+ sdoc_unseen_count = SeadocNotification.objects.filter(username=username, seen=False).count()
+ result['discussion_notification']['unseen_count'] = sdoc_unseen_count
+ cache.set(sdoc_cache_key, sdoc_unseen_count)
+
+ total_count = UserNotification.objects.filter(to_user=username).count()
+ sdoc_total_count = SeadocNotification.objects.filter(username=username).count()
+
+ result['general_notification']['notification_list'] = notification_list
+ result['discussion_notification']['notification_list'] = sdoc_notification_list
+ result['general_notification']['count'] = total_count
+ result['discussion_notification']['count'] = sdoc_total_count
+
+ return Response(result)
\ No newline at end of file
diff --git a/seahub/seadoc/models.py b/seahub/seadoc/models.py
index 6afae79629..7e0f96bf14 100644
--- a/seahub/seadoc/models.py
+++ b/seahub/seadoc/models.py
@@ -272,6 +272,10 @@ class SeadocNotificationManager(models.Manager):
def list_all_by_user(self, username, start, end):
return self.filter(username=username).order_by('-created_at')[start: end]
+
+ def remove_user_notifications(self, username):
+ """"Remove all user notifications."""
+ self.filter(username=username).delete()
class SeadocNotification(models.Model):
diff --git a/seahub/urls.py b/seahub/urls.py
index e3a09781cd..ad1d7690b4 100644
--- a/seahub/urls.py
+++ b/seahub/urls.py
@@ -91,7 +91,7 @@ from seahub.api2.endpoints.invitations import InvitationsView, InvitationsBatchV
from seahub.api2.endpoints.invitation import InvitationView, InvitationRevokeView
from seahub.api2.endpoints.repo_share_invitations import RepoShareInvitationsView, RepoShareInvitationsBatchView
from seahub.api2.endpoints.repo_share_invitation import RepoShareInvitationView
-from seahub.api2.endpoints.notifications import NotificationsView, NotificationView, SdocNotificationView, SdocNotificationsView
+from seahub.api2.endpoints.notifications import NotificationsView, NotificationView, SdocNotificationView, SdocNotificationsView, AllNotificationsView
from seahub.api2.endpoints.repo_file_uploaded_bytes import RepoFileUploadedBytesView
from seahub.api2.endpoints.user_avatar import UserAvatarView
from seahub.api2.endpoints.wikis import WikisView, WikiView
@@ -524,6 +524,7 @@ urlpatterns = [
re_path(r'^api/v2.1/notification/$', NotificationView.as_view(), name='api-v2.1-notification'),
re_path(r'^api/v2.1/sdoc-notifications/$', SdocNotificationsView.as_view(), name='api-v2.1-sdoc-notifications'),
re_path(r'^api/v2.1/sdoc-notification/$', SdocNotificationView.as_view(), name='api-v2.1-notification'),
+ re_path(r'^api/v2.1/all-notifications/$', AllNotificationsView.as_view(), name='api-v2.1-all-notification'),
## user::invitations
re_path(r'^api/v2.1/invitations/$', InvitationsView.as_view()),