diff --git a/frontend/config/webpack.config.dev.js b/frontend/config/webpack.config.dev.js index 71a6d27c11..31f796dd1a 100644 --- a/frontend/config/webpack.config.dev.js +++ b/frontend/config/webpack.config.dev.js @@ -59,6 +59,11 @@ module.exports = { require.resolve('react-dev-utils/webpackHotDevClient'), paths.appSrc + "/tc-accept.js", ], + userNotifications: [ + require.resolve('./polyfills'), + require.resolve('react-dev-utils/webpackHotDevClient'), + paths.appSrc + "/user-notifications.js", + ], wiki: [ require.resolve('./polyfills'), require.resolve('react-dev-utils/webpackHotDevClient'), diff --git a/frontend/config/webpack.config.prod.js b/frontend/config/webpack.config.prod.js index 116ca73066..35ae90a4c1 100644 --- a/frontend/config/webpack.config.prod.js +++ b/frontend/config/webpack.config.prod.js @@ -60,6 +60,7 @@ module.exports = { entry: { markdownEditor: [require.resolve('./polyfills'), paths.appIndexJs], 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"], fileHistory: [require.resolve('./polyfills'), paths.appSrc + "/file-history.js"], fileHistoryOld: [require.resolve('./polyfills'), paths.appSrc + "/file-history-old.js"], diff --git a/frontend/package.json b/frontend/package.json index 5f05bdb1e3..03c3a1ac00 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -44,7 +44,7 @@ "react-responsive": "^6.1.2", "react-select": "^2.4.1", "reactstrap": "^6.4.0", - "seafile-js": "^0.2.144", + "seafile-js": "^0.2.145", "socket.io-client": "^2.2.0", "sw-precache-webpack-plugin": "0.11.4", "unified": "^7.0.0", diff --git a/frontend/src/components/common/notice-item.js b/frontend/src/components/common/notice-item.js index 0722840990..babe1b16a1 100644 --- a/frontend/src/components/common/notice-item.js +++ b/frontend/src/components/common/notice-item.js @@ -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 ? ( + + + + + +

+ + + {moment(noticeItem.time).fromNow()} + + + ) : (
  • diff --git a/frontend/src/components/common/notification.js b/frontend/src/components/common/notification.js index 09104e36ab..67b1b86786 100644 --- a/frontend/src/components/common/notification.js +++ b/frontend/src/components/common/notification.js @@ -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 (
    - + {this.state.unseenCount} diff --git a/frontend/src/css/user-notifications.css b/frontend/src/css/user-notifications.css new file mode 100644 index 0000000000..540240785f --- /dev/null +++ b/frontend/src/css/user-notifications.css @@ -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; +} diff --git a/frontend/src/user-notifications.js b/frontend/src/user-notifications.js new file mode 100644 index 0000000000..d6d1f154b3 --- /dev/null +++ b/frontend/src/user-notifications.js @@ -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 ( + +
    +
    + + logo + + +
    +
    +
    +
    +
    +

    {gettext('Notifications')}

    +
    + + +
    +
    + +
    +
    +
    +
    +
    + ); + } +} + +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 ; + } + + if (errorMsg) { + return

    {errorMsg}

    ; + } + + return ( + + + + + {this.theadData.map((item, index) => { + return ; + })} + + + + {items.map((item, index) => { + return (); + })} + +
    {item.text}
    + {items.length > 0 && + + } +
    + ); + } +} + +ReactDOM.render( + , + document.getElementById('wrapper') +); diff --git a/media/css/seahub.css b/media/css/seahub.css index c399e818e5..d16e3ef73c 100644 --- a/media/css/seahub.css +++ b/media/css/seahub.css @@ -2006,37 +2006,6 @@ a.sf-popover-item { 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 { margin:-15px 0 20px; diff --git a/media/css/seahub_react.css b/media/css/seahub_react.css index b19589c2bd..c3b6a7bc83 100644 --- a/media/css/seahub_react.css +++ b/media/css/seahub_react.css @@ -218,6 +218,16 @@ a:hover { color:#eb8205; } 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 */ /**** caret ****/ diff --git a/seahub/notifications/templates/notifications/user_notification_list.html b/seahub/notifications/templates/notifications/user_notification_list.html deleted file mode 100644 index 09e3339ce8..0000000000 --- a/seahub/notifications/templates/notifications/user_notification_list.html +++ /dev/null @@ -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 %} -
    - -
    - - -
    -
    - -{% if notices %} - - - - - - - - - - {% include "notifications/user_notification_tr.html" %} - -
    {% trans "Message"%}{% trans "Time"%}
    -{% if notices_more %} -
    -
    - -

    -
    -{% endif %} -{% endif %} -{% endblock %} - -{% block extra_script %} - -{% endblock %} diff --git a/seahub/notifications/templates/notifications/user_notification_list_react.html b/seahub/notifications/templates/notifications/user_notification_list_react.html new file mode 100644 index 0000000000..999855561e --- /dev/null +++ b/seahub/notifications/templates/notifications/user_notification_list_react.html @@ -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 %} + +{% render_bundle 'userNotifications' 'js' %} +{% endblock %} diff --git a/seahub/notifications/templates/notifications/user_notification_tr.html b/seahub/notifications/templates/notifications/user_notification_tr.html deleted file mode 100644 index 5e64b388db..0000000000 --- a/seahub/notifications/templates/notifications/user_notification_tr.html +++ /dev/null @@ -1,48 +0,0 @@ -{% load i18n seahub_tags avatar_tags %} - {% for notice in notices %} - {% if notice.seen %} - - {% else %} - - {% endif %} - - - {% if notice.msg_from %} - {% avatar notice.msg_from 32 %} - {% else %} - - {% endif %} - - - {% if notice.is_file_uploaded_msg %} -

    {{ notice.format_file_uploaded_msg|safe }}

    - - {% elif notice.is_repo_share_msg %} -

    {{ notice.format_repo_share_msg|safe }}

    - - {% elif notice.is_repo_share_to_group_msg %} -

    {{ notice.format_repo_share_to_group_msg|safe }}

    - - {% elif notice.is_group_join_request %} -

    {{ notice.format_group_join_request|safe }}

    - - {% elif notice.is_file_comment_msg %} -

    {{ notice.format_file_comment_msg|safe }}

    - - {% elif notice.is_draft_comment_msg %} -

    {{ notice.format_draft_comment_msg|safe }}

    - - {% elif notice.is_draft_reviewer_msg %} -

    {{ notice.format_draft_reviewer_msg|safe }}

    - - {% elif notice.is_guest_invitation_accepted_msg %} -

    {{ notice.format_guest_invitation_accepted_msg|safe }}

    - - {% elif notice.is_add_user_to_group %} -

    {{ notice.format_add_user_to_group|safe }}

    - - {% endif %} - - {{ notice.timestamp|translate_seahub_time }} - - {% endfor %} diff --git a/seahub/notifications/urls.py b/seahub/notifications/urls.py index da2bd72984..a120def7d5 100644 --- a/seahub/notifications/urls.py +++ b/seahub/notifications/urls.py @@ -5,6 +5,4 @@ from .views import * urlpatterns = [ ########## user notifications 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'), ] diff --git a/seahub/notifications/views.py b/seahub/notifications/views.py index 82baf0d25a..f9e3471464 100644 --- a/seahub/notifications/views.py +++ b/seahub/notifications/views.py @@ -22,74 +22,8 @@ logger = logging.getLogger(__name__) ########## user notifications @login_required def user_notification_list(request): - """ - - 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) + return render(request, "notifications/user_notification_list_react.html", { + }) def add_notice_from_info(notices): '''Add 'msg_from' or 'default_avatar_url' to notice.