1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-17 15:53:28 +00:00
This commit is contained in:
孙永强
2024-11-21 17:11:02 +08:00
parent 27dcce694b
commit 0f77313761
8 changed files with 163 additions and 54 deletions

View File

@@ -4,6 +4,7 @@ import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime'; import relativeTime from 'dayjs/plugin/relativeTime';
import { gettext, siteRoot } from '../../utils/constants'; import { gettext, siteRoot } from '../../utils/constants';
import { Utils } from '../../utils/utils'; import { Utils } from '../../utils/utils';
import { processor } from '@seafile/seafile-editor';
const propTypes = { const propTypes = {
noticeItem: PropTypes.object.isRequired, noticeItem: PropTypes.object.isRequired,
@@ -35,7 +36,7 @@ class NoticeItem extends React.Component {
let noticeItem = this.props.noticeItem; let noticeItem = this.props.noticeItem;
let noticeType = noticeItem.type; let noticeType = noticeItem.type;
let detail = noticeItem.detail; let detail = noticeItem.detail;
console.log(detail, noticeItem, noticeType)
if (noticeType === MSG_TYPE_ADD_USER_TO_GROUP) { if (noticeType === MSG_TYPE_ADD_USER_TO_GROUP) {
let avatar_url = detail.group_staff_avatar_url; let avatar_url = detail.group_staff_avatar_url;
@@ -378,19 +379,23 @@ class NoticeItem extends React.Component {
} }
if (noticeType === MSG_TYPE_SEADOC_COMMENT) { if (noticeType === MSG_TYPE_SEADOC_COMMENT) {
let avatar_url = detail.share_from_user_avatar_url; let avatar_url = detail.avatar_url;
let notice = ''; let notice = detail.comment;
console.log(111) let username = detail.user_name;
notice = Utils.HTMLescape(notice); processor.process(notice, (error, vfile) => {
return { avatar_url, notice }; notice = String(vfile);
});
return { avatar_url, username, notice };
} }
if (noticeType === MSG_TYPE_SEADOC_REPLY) { if (noticeType === MSG_TYPE_SEADOC_REPLY) {
let avatar_url = detail.share_from_user_avatar_url; let avatar_url = detail.avatar_url;
let notice = detail.reply; let notice = detail.reply;
notice = Utils.HTMLescape(notice); let username = detail.user_name;
console.log(notice) processor.process(notice, (error, vfile) => {
return { avatar_url, notice }; notice = String(vfile);
});
return { avatar_url, username, notice };
} }
// if (noticeType === MSG_TYPE_GUEST_INVITATION_ACCEPTED) { // if (noticeType === MSG_TYPE_GUEST_INVITATION_ACCEPTED) {
@@ -410,8 +415,7 @@ class NoticeItem extends React.Component {
render() { render() {
let noticeItem = this.props.noticeItem; let noticeItem = this.props.noticeItem;
let { avatar_url, notice } = this.generatorNoticeInfo(); let { avatar_url, username, notice } = this.generatorNoticeInfo();
if (!avatar_url && !notice) { if (!avatar_url && !notice) {
return ''; return '';
} }
@@ -432,8 +436,13 @@ class NoticeItem extends React.Component {
<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">
<img src={avatar_url} width="32" height="32" className="avatar" alt=""/> <div className="auther-info">
<p className="brief" dangerouslySetInnerHTML={{ __html: notice }}></p> <img src={avatar_url} width="32" height="32" className="avatar" alt=""/>
<p>{username}</p>
</div>
<div>
<p className="brief" dangerouslySetInnerHTML={{ __html: notice }}></p>
</div>
</div> </div>
<p className="time">{dayjs(noticeItem.time).fromNow()}</p> <p className="time">{dayjs(noticeItem.time).fromNow()}</p>
</div> </div>

View File

@@ -72,39 +72,38 @@ export default class NotificationPopover extends React.Component {
<span className="sf3-font sf3-font-x-01 notification-close-icon" onClick={this.props.onNotificationListToggle}></span> <span className="sf3-font sf3-font-x-01 notification-close-icon" onClick={this.props.onNotificationListToggle}></span>
</div> </div>
<div className="notification-body"> <div className="notification-body">
<div className="mark-notifications"> <div className="mark-notifications">
<ul className="nav"> <ul className="nav">
<li className="nav-item" onClick={() => this.tabItemClick('general')}> <li className="nav-item" onClick={() => this.tabItemClick('general')}>
<span className={`nav-link ${currentTab === 'general' ? 'active' : ''}`}> <span className={`nav-link ${currentTab === 'general' ? 'active' : ''}`}>
{gettext('General')} {gettext('General')}
</span> </span>
</li> </li>
<li className="nav-item" onClick={() => this.tabItemClick('discussion')}> <li className="nav-item" onClick={() => this.tabItemClick('discussion')}>
<span className={`nav-link ${currentTab === 'discussion' ? 'active' : ''}`}> <span className={`nav-link ${currentTab === 'discussion' ? 'active' : ''}`}>
{gettext('Discussion')} {gettext('Discussion')}
</span>
</span> </li>
</li> </ul>
</ul> <span className="mark-all-read" onClick={this.props.onMarkAllNotifications}>
<span className="mark-all-read" onClick={this.onMarkAllNotifications}> {bodyText}
{gettext('Mark all as read')} </span>
</span> </div>
</div> {currentTab === 'general' &&
{currentTab === 'general' &&
<div className="notification-list-container" onScroll={this.onHandleScroll} ref={ref => this.notificationListRef = ref}> <div className="notification-list-container" onScroll={this.onHandleScroll} ref={ref => this.notificationListRef = ref}>
<div ref={ref => this.notificationsWrapperRef = ref}> <div ref={ref => this.notificationsWrapperRef = ref}>
{this.props.children} {this.props.children}
</div> </div>
</div> </div>
} }
{currentTab === 'discussion' && {currentTab === 'discussion' &&
<div className="notification-list-container" onScroll={this.onHandleScroll} ref={ref => this.notificationListRef = ref}> <div className="notification-list-container" onScroll={this.onHandleScroll} ref={ref => this.notificationListRef = ref}>
<div ref={ref => this.notificationsWrapperRef = ref}> <div ref={ref => this.notificationsWrapperRef = ref}>
{this.props.children} {this.props.children}
</div> </div>
</div> </div>
} }
{/* <div className="mark-notifications" onClick={this.props.onMarkAllNotifications}> {/* <div className="mark-notifications" onClick={this.props.onMarkAllNotifications}>
<ul className="nav dtable-external-links-tab"> <ul className="nav dtable-external-links-tab">
<li className="nav-item"> <li className="nav-item">

View File

@@ -38,16 +38,16 @@ class Notification extends React.Component {
this.setState({ showNotice: true }); this.setState({ showNotice: true });
} }
}; };
tabItemClick = (tab) => { tabItemClick = (tab) => {
const { currentTab } = this.state; const { currentTab } = this.state;
if (currentTab === tab) return; if (currentTab === tab) return;
this.setState({ this.setState({
showNotice: true, showNotice: true,
currentTab: tab currentTab: tab
}, () => { }, () => {
this.loadNotices(); this.loadNotices();
}); });
}; };
loadNotices = () => { loadNotices = () => {
@@ -62,7 +62,6 @@ class Notification extends React.Component {
if (this.state.currentTab === 'discussion') { if (this.state.currentTab === 'discussion') {
seafileAPI.listSdocNotifications(page, perPage).then(res => { seafileAPI.listSdocNotifications(page, perPage).then(res => {
let noticeList = res.data.notification_list; let noticeList = res.data.notification_list;
console.log(noticeList)
this.setState({ noticeList: noticeList }); this.setState({ noticeList: noticeList });
}); });
} }
@@ -76,7 +75,14 @@ class Notification extends React.Component {
} }
return item; return item;
}); });
seafileAPI.markNoticeAsRead(noticeItem.id);
if (this.state.currentTab === 'general') {
seafileAPI.markNoticeAsRead(noticeItem.id);
}
if (this.state.currentTab === 'discussion') {
seafileAPI.markSdocNoticeAsRead(noticeItem.id);
}
let unseenCount = this.state.unseenCount === 0 ? 0 : this.state.unseenCount - 1; let unseenCount = this.state.unseenCount === 0 ? 0 : this.state.unseenCount - 1;
this.setState({ this.setState({
noticeList: noticeList, noticeList: noticeList,

View File

@@ -1475,6 +1475,11 @@ class SeafileAPI {
return this.req.put(url); return this.req.put(url);
} }
updateSdocNotifications() {
const url = this.server + '/api/v2.1/sdoc-notifications/';
return this.req.put(url);
}
deleteNotifications() { deleteNotifications() {
const url = this.server + '/api/v2.1/notifications/'; const url = this.server + '/api/v2.1/notifications/';
return this.req.delete(url); return this.req.delete(url);
@@ -1492,6 +1497,13 @@ class SeafileAPI {
return this.req.put(url, from); return this.req.put(url, from);
} }
markSdocNoticeAsRead(noticeId) {
const url = this.server + '/api/v2.1/sdoc-notification/';
let from = new FormData();
from.append('notice_id', noticeId);
return this.req.put(url, from);
}
// ---- Linked Devices API // ---- Linked Devices API
listLinkedDevices() { listLinkedDevices() {
const url = this.server + '/api2/devices/'; const url = this.server + '/api2/devices/';

View File

@@ -13,6 +13,7 @@ from seahub.api2.authentication import TokenAuthentication
from seahub.api2.throttling import UserRateThrottle from seahub.api2.throttling import UserRateThrottle
from seahub.notifications.models import UserNotification from seahub.notifications.models import UserNotification
from seahub.seadoc.models import get_cache_key_of_unseen_sdoc_notifications
from seahub.notifications.models import get_cache_key_of_unseen_notifications from seahub.notifications.models import get_cache_key_of_unseen_notifications
from seahub.notifications.utils import update_notice_detail, update_sdoc_notice_detail from seahub.notifications.utils import update_notice_detail, update_sdoc_notice_detail
from seahub.api2.utils import api_error from seahub.api2.utils import api_error
@@ -166,18 +167,13 @@ class NotificationView(APIView):
return Response({'success': True}) return Response({'success': True})
class SdocNotificationView(APIView): class SdocNotificationsView(APIView):
def get(self, request): def get(self, request):
""" used for get sdoc notifications """ used for get sdoc notifications
Permission checking: Permission checking:
1. login user. 1. login user.
""" """
notice_type = request.GET.get('type', 'general')
if notice_type not in NOTIF_TYPE:
error_msg = 'notice_type invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
result = {} result = {}
username = request.user.username username = request.user.username
@@ -205,7 +201,7 @@ class SdocNotificationView(APIView):
notice['seen'] = i.seen notice['seen'] = i.seen
notification_list.append(notice) notification_list.append(notice)
cache_key = get_cache_key_of_unseen_notifications(username) cache_key = get_cache_key_of_unseen_sdoc_notifications(username)
unseen_count_from_cache = cache.get(cache_key, None) unseen_count_from_cache = cache.get(cache_key, None)
# for case of count value is `0` # for case of count value is `0`
@@ -222,3 +218,69 @@ class SdocNotificationView(APIView):
result['count'] = total_count result['count'] = total_count
return Response(result) return Response(result)
def put(self, request):
""" currently only used for mark all notifications seen
Permission checking:
1. login user.
"""
username = request.user.username
unseen_notices = SeadocNotification.objects.filter(username, seen=False)
for notice in unseen_notices:
notice.seen = True
notice.save()
cache_key = get_cache_key_of_unseen_sdoc_notifications(username)
cache.delete(cache_key)
return Response({'success': True})
class SdocNotificationView(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle,)
def put(self, request):
""" currently only used for mark a sdoc notification seen
Permission checking:
1. login user.
"""
notice_id = request.data.get('notice_id')
# argument check
try:
int(notice_id)
except Exception as e:
error_msg = 'notice_id invalid.'
logger.error(e)
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
# resource check
try:
notice = SeadocNotification.objects.get(id=notice_id)
except SeadocNotification.DoesNotExist as e:
logger.error(e)
error_msg = 'Notification %s not found.' % notice_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
# permission check
username = request.user.username
if notice.username != username:
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
if not notice.seen:
notice.seen = True
notice.save()
cache_key = get_cache_key_of_unseen_sdoc_notifications(username)
cache.delete(cache_key)
return Response({'success': True})

View File

@@ -2,6 +2,7 @@
import os import os
import json import json
import logging import logging
import posixpath
from django.core.cache import cache from django.core.cache import cache
from django.utils.html import escape from django.utils.html import escape
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
@@ -10,6 +11,7 @@ from seaserv import ccnet_api, seafile_api
from seahub.constants import CUSTOM_PERMISSION_PREFIX from seahub.constants import CUSTOM_PERMISSION_PREFIX
from seahub.notifications.models import Notification from seahub.notifications.models import Notification
from seahub.tags.models import FileUUIDMap
from seahub.notifications.settings import NOTIFICATION_CACHE_TIMEOUT from seahub.notifications.settings import NOTIFICATION_CACHE_TIMEOUT
from seahub.avatar.templatetags.avatar_tags import api_avatar_url from seahub.avatar.templatetags.avatar_tags import api_avatar_url
from seahub.base.templatetags.seahub_tags import email2nickname, email2contact_email from seahub.base.templatetags.seahub_tags import email2nickname, email2contact_email
@@ -403,17 +405,30 @@ def update_notice_detail(request, notices):
def update_sdoc_notice_detail(request, notices): def update_sdoc_notice_detail(request, notices):
repo_dict = {}
for notice in notices: for notice in notices:
if notice.is_comment(): if notice.is_comment():
try: try:
d = json.loads(notice.detail) d = json.loads(notice.detail)
uuid = FileUUIDMap.objects.get_fileuuidmap_by_uuid(notice.doc_uuid)
origin_file_path = posixpath.join(uuid.parent_path, uuid.filename)
url, _, _ = api_avatar_url(d['author'])
d['avatar_url'] = url
d['sdoc_path'] = origin_file_path
d['sdoc_name'] = uuid.filename
d['repo_id'] = uuid.repo_id
notice.detail = d notice.detail = d
except Exception as e: except Exception as e:
logger.error(e) logger.error(e)
elif notice.is_reply(): elif notice.is_reply():
try: try:
d = json.loads(notice.detail) d = json.loads(notice.detail)
uuid = FileUUIDMap.objects.get_fileuuidmap_by_uuid(notice.doc_uuid)
origin_file_path = posixpath.join(uuid.parent_path, uuid.filename)
url, _, _ = api_avatar_url(d['author'])
d['avatar_url'] = url
d['sdoc_path'] = origin_file_path
d['sdoc_name'] = uuid.filename
d['repo_id'] = uuid.repo_id
notice.detail = d notice.detail = d
except Exception as e: except Exception as e:
logger.error(e) logger.error(e)

View File

@@ -7,7 +7,7 @@ from django.db import models
from seahub.utils.timeutils import datetime_to_isoformat_timestr from seahub.utils.timeutils import datetime_to_isoformat_timestr
from seahub.base.templatetags.seahub_tags import email2nickname from seahub.base.templatetags.seahub_tags import email2nickname
from seahub.seadoc.settings import SDOC_REVISIONS_DIR from seahub.seadoc.settings import SDOC_REVISIONS_DIR
from seahub.utils import normalize_cache_key
class SeadocHistoryNameManager(models.Manager): class SeadocHistoryNameManager(models.Manager):
def update_name(self, doc_uuid, obj_id, name): def update_name(self, doc_uuid, obj_id, name):
@@ -251,6 +251,11 @@ class SeadocCommentReply(models.Model):
### sdoc notification ### sdoc notification
MSG_TYPE_REPLY = 'reply' MSG_TYPE_REPLY = 'reply'
MSG_TYPE_COMMENT = 'comment' MSG_TYPE_COMMENT = 'comment'
SDOC_NOTIFICATION_COUNT_CACHE_PREFIX = 'SDOC_NOTIFICATION_COUNT_'
def get_cache_key_of_unseen_sdoc_notifications(username):
return normalize_cache_key(username,
SDOC_NOTIFICATION_COUNT_CACHE_PREFIX)
class SeadocNotificationManager(models.Manager): class SeadocNotificationManager(models.Manager):
def total_count(self, doc_uuid, username): def total_count(self, doc_uuid, username):

View File

@@ -91,7 +91,7 @@ from seahub.api2.endpoints.invitations import InvitationsView, InvitationsBatchV
from seahub.api2.endpoints.invitation import InvitationView, InvitationRevokeView from seahub.api2.endpoints.invitation import InvitationView, InvitationRevokeView
from seahub.api2.endpoints.repo_share_invitations import RepoShareInvitationsView, RepoShareInvitationsBatchView from seahub.api2.endpoints.repo_share_invitations import RepoShareInvitationsView, RepoShareInvitationsBatchView
from seahub.api2.endpoints.repo_share_invitation import RepoShareInvitationView from seahub.api2.endpoints.repo_share_invitation import RepoShareInvitationView
from seahub.api2.endpoints.notifications import NotificationsView, NotificationView, SdocNotificationView from seahub.api2.endpoints.notifications import NotificationsView, NotificationView, SdocNotificationView, SdocNotificationsView
from seahub.api2.endpoints.repo_file_uploaded_bytes import RepoFileUploadedBytesView from seahub.api2.endpoints.repo_file_uploaded_bytes import RepoFileUploadedBytesView
from seahub.api2.endpoints.user_avatar import UserAvatarView from seahub.api2.endpoints.user_avatar import UserAvatarView
from seahub.api2.endpoints.wikis import WikisView, WikiView from seahub.api2.endpoints.wikis import WikisView, WikiView
@@ -522,7 +522,8 @@ urlpatterns = [
re_path(r'^api/v2.1/notifications/$', NotificationsView.as_view(), name='api-v2.1-notifications'), re_path(r'^api/v2.1/notifications/$', NotificationsView.as_view(), name='api-v2.1-notifications'),
re_path(r'^api/v2.1/notification/$', NotificationView.as_view(), name='api-v2.1-notification'), re_path(r'^api/v2.1/notification/$', NotificationView.as_view(), name='api-v2.1-notification'),
re_path(r'^api/v2.1/sdoc-notifications/$', SdocNotificationView.as_view(), name='api-v2.1-sdoc-notifications'), 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'),
## user::invitations ## user::invitations
re_path(r'^api/v2.1/invitations/$', InvitationsView.as_view()), re_path(r'^api/v2.1/invitations/$', InvitationsView.as_view()),