diff --git a/frontend/src/components/common/notice-item.js b/frontend/src/components/common/notice-item.js
index 958a91d575..33dbc50d95 100644
--- a/frontend/src/components/common/notice-item.js
+++ b/frontend/src/components/common/notice-item.js
@@ -5,6 +5,7 @@ import relativeTime from 'dayjs/plugin/relativeTime';
import { gettext, siteRoot } from '../../utils/constants';
import { Utils } from '../../utils/utils';
import { processor } from '@seafile/seafile-editor';
+import '../../css/notice-item.css';
const propTypes = {
noticeItem: PropTypes.object.isRequired,
@@ -38,35 +39,26 @@ class NoticeItem extends React.Component {
let detail = noticeItem.detail;
if (noticeType === MSG_TYPE_ADD_USER_TO_GROUP) {
-
let avatar_url = detail.group_staff_avatar_url;
-
let groupStaff = detail.group_staff_name;
-
// group name does not support special characters
let userHref = siteRoot + 'profile/' + detail.group_staff_email + '/';
let groupHref = siteRoot + 'group/' + detail.group_id + '/';
let groupName = detail.group_name;
-
+ let username = detail.group_staff_name;
let notice = gettext('User {user_link} has added you to {group_link}');
let userLink = '' + groupStaff + '';
let groupLink = '' + groupName + '';
-
notice = notice.replace('{user_link}', userLink);
notice = notice.replace('{group_link}', groupLink);
-
- return { avatar_url, notice };
+ return { avatar_url, notice, username };
}
if (noticeType === MSG_TYPE_REPO_SHARE) {
-
let avatar_url = detail.share_from_user_avatar_url;
-
let shareFrom = detail.share_from_user_name;
-
let repoName = detail.repo_name;
let repoUrl = siteRoot + 'library/' + detail.repo_id + '/' + repoName + '/';
-
let path = detail.path;
let notice = '';
// 1. handle translate
@@ -75,21 +67,17 @@ class NoticeItem extends React.Component {
} else { // share folder
notice = gettext('{share_from} has shared a folder named {repo_link} to you.');
}
-
// 2. handle xss(cross-site scripting)
notice = notice.replace('{share_from}', shareFrom);
notice = notice.replace('{repo_link}', `{tagA}${repoName}{/tagA}`);
notice = Utils.HTMLescape(notice);
-
// 3. add jump link
notice = notice.replace('{tagA}', ``);
notice = notice.replace('{/tagA}', '');
-
- return { avatar_url, notice };
+ return { avatar_url, notice, username: shareFrom };
}
if (noticeType === MSG_TYPE_REPO_SHARE_PERM_CHANGE) {
-
let avatar_url = detail.share_from_user_avatar_url;
let shareFrom = detail.share_from_user_name;
let permission = detail.permission;
@@ -103,22 +91,18 @@ class NoticeItem extends React.Component {
} else { // share folder
notice = gettext('{share_from} has changed the permission of folder {repo_link} to {permission}.');
}
-
// 2. handle xss(cross-site scripting)
notice = notice.replace('{share_from}', shareFrom);
notice = notice.replace('{repo_link}', `{tagA}${repoName}{/tagA}`);
notice = notice.replace('{permission}', permission);
notice = Utils.HTMLescape(notice);
-
// 3. add jump link
notice = notice.replace('{tagA}', ``);
notice = notice.replace('{/tagA}', '');
-
- return { avatar_url, notice };
+ return { avatar_url, notice, username: shareFrom };
}
if (noticeType === MSG_TYPE_REPO_SHARE_PERM_DELETE) {
-
let avatar_url = detail.share_from_user_avatar_url;
let shareFrom = detail.share_from_user_name;
let repoName = detail.repo_name;
@@ -130,26 +114,20 @@ class NoticeItem extends React.Component {
} else { // share folder
notice = gettext('{share_from} has cancelled the sharing of folder {repo_name}.');
}
-
// 2. handle xss(cross-site scripting)
notice = notice.replace('{share_from}', shareFrom);
notice = notice.replace('{repo_name}', repoName);
notice = Utils.HTMLescape(notice);
- return { avatar_url, notice };
+ return { avatar_url, notice, username: shareFrom };
}
if (noticeType === MSG_TYPE_REPO_SHARE_TO_GROUP) {
-
let avatar_url = detail.share_from_user_avatar_url;
-
let shareFrom = detail.share_from_user_name;
-
let repoName = detail.repo_name;
let repoUrl = siteRoot + 'library/' + detail.repo_id + '/' + repoName + '/';
-
let groupUrl = siteRoot + 'group/' + detail.group_id + '/';
let groupName = detail.group_name;
-
let path = detail.path;
let notice = '';
// 1. handle translate
@@ -158,60 +136,50 @@ class NoticeItem extends React.Component {
} else {
notice = gettext('{share_from} has shared a folder named {repo_link} to group {group_link}.');
}
-
// 2. handle xss(cross-site scripting)
notice = notice.replace('{share_from}', shareFrom);
notice = notice.replace('{repo_link}', `{tagA}${repoName}{/tagA}`);
notice = notice.replace('{group_link}', `{tagB}${groupName}{/tagB}`);
notice = Utils.HTMLescape(notice);
-
// 3. add jump link
notice = notice.replace('{tagA}', ``);
notice = notice.replace('{/tagA}', '');
notice = notice.replace('{tagB}', ``);
notice = notice.replace('{/tagB}', '');
- return { avatar_url, notice };
+ return { avatar_url, notice, username: shareFrom };
}
if (noticeType === MSG_TYPE_REPO_TRANSFER) {
-
let avatar_url = detail.transfer_from_user_avatar_url;
-
let repoOwner = detail.transfer_from_user_name;
-
let repoName = detail.repo_name;
let repoUrl = siteRoot + 'library/' + detail.repo_id + '/' + repoName + '/';
// 1. handle translate
let notice = gettext('{user} has transfered a library named {repo_link} to you.');
-
// 2. handle xss(cross-site scripting)
notice = notice.replace('{user}', repoOwner);
notice = notice.replace('{repo_link}', `{tagA}${repoName}{/tagA}`);
notice = Utils.HTMLescape(notice);
-
// 3. add jump link
notice = notice.replace('{tagA}', ``);
notice = notice.replace('{/tagA}', '');
- return { avatar_url, notice };
+ return { avatar_url, notice, username: repoOwner };
}
if (noticeType === MSG_TYPE_FILE_UPLOADED) {
let avatar_url = detail.uploaded_user_avatar_url;
let fileName = detail.file_name;
let fileLink = siteRoot + 'lib/' + detail.repo_id + '/' + 'file' + detail.file_path;
-
let folderName = detail.folder_name;
let folderLink = siteRoot + 'library/' + detail.repo_id + '/' + detail.repo_name + detail.folder_path;
let notice = '';
if (detail.repo_id) { // todo is repo exist ?
// 1. handle translate
notice = gettext('A file named {upload_file_link} is uploaded to {uploaded_link}.');
-
// 2. handle xss(cross-site scripting)
notice = notice.replace('{upload_file_link}', `{tagA}${fileName}{/tagA}`);
notice = notice.replace('{uploaded_link}', `{tagB}${folderName}{/tagB}`);
notice = Utils.HTMLescape(notice);
-
// 3. add jump link
notice = notice.replace('{tagA}', ``);
notice = notice.replace('{/tagA}', '');
@@ -220,7 +188,6 @@ class NoticeItem extends React.Component {
} else {
// 1. handle translate
notice = gettext('A file named {upload_file_link} is uploaded.');
-
// 2. handle xss(cross-site scripting)
notice = notice.replace('{upload_file_link}', `${fileName}`);
notice = Utils.HTMLescape(notice);
@@ -344,17 +311,11 @@ class NoticeItem extends React.Component {
}
if (noticeType === MSG_TYPE_DELETED_FILES) {
- const {
- repo_id,
- repo_name,
- } = detail;
-
+ const { repo_id, repo_name } = detail;
const repoURL = `${siteRoot}library/${repo_id}/${encodeURIComponent(repo_name)}/`;
const repoLink = `${Utils.HTMLescape(repo_name)}`;
-
let notice = gettext('Your library {libraryName} has recently deleted a large number of files.');
notice = notice.replace('{libraryName}', repoLink);
-
return { avatar_url: null, notice };
}
@@ -374,7 +335,6 @@ class NoticeItem extends React.Component {
if (noticeType === MSG_TYPE_SAML_SSO_FAILED) {
const { error_msg } = detail;
let notice = gettext(error_msg);
-
return { avatar_url: null, notice };
}
@@ -424,7 +384,7 @@ class NoticeItem extends React.Component {
// }
- return { avatar_url: null, notice: null };
+ return { avatar_url: null, notice: null, username: null };
}
onNoticeItemClick = () => {
@@ -455,18 +415,21 @@ class NoticeItem extends React.Component {
) : (
-
-
-
-
-

-
{username}
-
-
-
+
+
+ {!noticeItem.seen &&
+
+ }
+
+
+

+
{username || gettext('System')}
+
{dayjs(noticeItem.time).fromNow()}
-
{dayjs(noticeItem.time).fromNow()}
+
+
);
diff --git a/frontend/src/components/common/notification-popover/index.css b/frontend/src/components/common/notification-popover/index.css
index 50b91b7307..f41e431809 100644
--- a/frontend/src/components/common/notification-popover/index.css
+++ b/frontend/src/components/common/notification-popover/index.css
@@ -66,7 +66,7 @@
}
.notification-container .mark-all-read {
- color: #b4b4b4;
+ color: #666;
cursor: pointer;
display: flex;
align-items: center;
@@ -187,6 +187,9 @@
.notification-container .notification-body .mark-notifications {
display: flex;
+ justify-content: space-between;
+ border-bottom: 1px solid #ededed;
+ padding-left: 15px;
}
.notification-container .notification-body .mark-notifications .mark-all-read:hover {
@@ -198,8 +201,9 @@
margin-right: 15px;
margin-left: 15px;
font-size: 14px;
+ color: #212529;
}
.notification-container .notification-body .nav .nav-item .nav-link.active {
- color: #ED7109 !important;
-}
\ No newline at end of file
+ color: #ED7109;
+}
diff --git a/frontend/src/components/common/notification-popover/index.js b/frontend/src/components/common/notification-popover/index.js
index bf30cebb2f..1c24289a2b 100644
--- a/frontend/src/components/common/notification-popover/index.js
+++ b/frontend/src/components/common/notification-popover/index.js
@@ -18,6 +18,8 @@ export default class NotificationPopover extends React.Component {
tabItemClick: PropTypes.func,
children: PropTypes.any,
currentTab: PropTypes.string,
+ generalNoticeListUnseen: PropTypes.number,
+ discussionNoticeListUnseen: PropTypes.number,
};
static defaultProps = {
@@ -56,7 +58,7 @@ export default class NotificationPopover extends React.Component {
};
render() {
- const { headerText, bodyText, footerText, currentTab } = this.props;
+ const { headerText, bodyText, footerText, currentTab, generalNoticeListUnseen, discussionNoticeListUnseen } = this.props;
return (
this.tabItemClick('general')}>
{gettext('General')}
+ {generalNoticeListUnseen > 0 && ({generalNoticeListUnseen})}
this.tabItemClick('discussion')}>
{gettext('Discussion')}
+ {discussionNoticeListUnseen > 0 && ({discussionNoticeListUnseen})}
@@ -103,23 +107,6 @@ export default class NotificationPopover extends React.Component {
}
-
- {/*
-
- -
- General
-
- -
- Discussion
-
-
-
{bodyText}
-
-
this.notificationListRef = ref}>
-
this.notificationsWrapperRef = ref}>
- {this.props.children}
-
-
*/}
{footerText}
diff --git a/frontend/src/components/common/notification.js b/frontend/src/components/common/notification.js
index 1806b1221b..b2dd80b1ec 100644
--- a/frontend/src/components/common/notification.js
+++ b/frontend/src/components/common/notification.js
@@ -123,7 +123,9 @@ class Notification extends React.Component {
};
render() {
- const { unseenCount, currentTab } = this.state;
+ const { unseenCount, currentTab, generalNoticeList, discussionNoticeList } = this.state;
+ const generalNoticeListUnseen = generalNoticeList.filter(item => !item.seen).length;
+ const discussionNoticeListUnseen = discussionNoticeList.filter(item => !item.seen).length;
return (
);
diff --git a/frontend/src/css/notice-item.css b/frontend/src/css/notice-item.css
new file mode 100644
index 0000000000..38b8c5ac2a
--- /dev/null
+++ b/frontend/src/css/notice-item.css
@@ -0,0 +1,94 @@
+.notification-item {
+ padding: 14px 16px 14px 10px;
+ border-bottom: 1px solid #ededed;
+ position: relative;
+ cursor: pointer;
+}
+
+.notification-item:last-child {
+ border-bottom: none;
+}
+
+.notification-item:hover {
+ background: #f5f5f5;
+}
+
+.notification-item .notification-item-header {
+ display: flex;
+ align-items: center
+}
+
+.notification-item .notification-point {
+ display: inline-block;
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: red;
+ margin-right: 12px;
+ position: absolute;
+}
+
+.notification-item .notification-header-info {
+ display: flex;
+ justify-content: space-between;
+ flex: 1;
+ margin-left: 20px;
+ width: calc(100% - 20px);
+}
+
+.notification-item .notification-user-detail {
+ display: flex;
+ width: 65%;
+}
+
+.notification-item .notification-user-detail .notification-user-avatar {
+ width: 24px;
+ height: 24px;
+ margin-top: 3px;
+ border-radius: 50%;
+}
+
+.notification-item .notification-user-detail .notification-user-name {
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ font-weight: 500;
+ margin-left: 8px;
+}
+
+.notification-item .notification-header-info .notification-time {
+ color: #666;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ font-size: 13px;
+}
+
+.notification-item .notification-content-wrapper {
+ font-size: 13px;
+ margin-left: 52px;
+}
+
+.notification-item .notification-content-quotes {
+ width: 8px;
+}
+
+.notification-item .notification-comment-content {
+ max-width: calc(100% - 16px);
+}
+
+.notification-item .notification-comment-content p {
+ display: inline-block;
+ letter-spacing: 1px;
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ margin-bottom: 0;
+}
+
+.notification-item .notification-comment-content p img {
+ max-width: 70%;
+ height: auto;
+ max-height: 60px;
+}
diff --git a/frontend/src/css/notification.css b/frontend/src/css/notification.css
index 6dd6024ec5..6dea937935 100644
--- a/frontend/src/css/notification.css
+++ b/frontend/src/css/notification.css
@@ -56,44 +56,11 @@
font-weight: normal;
}
-#notice-popover li {
- padding: 9px 0 3px;
- border-bottom: 1px solid #dfdfe1;
-}
-
-#notice-popover li.unread {
- padding-right: 10px;
- padding-left: 10px;
- border-left: 2px solid #feac74;
-}
-
-#notice-popover li.read {
- padding-right: 10px;
- padding-left: 10px;
- border-left: 2px solid transparent;
-}
-
-#notice-popover li:hover {
- background: #f5f5f7;
-}
-
-#notice-popover li.read:hover {
- background: #f5f5f7;
- border-left: 2px solid #dfdfe1;
-}
-
#notice-popover .avatar {
border-radius: 1000px;
float: left;
}
-#notice-popover .brief {
- margin-left: 40px;
- margin-bottom: 1rem;
- font-size: 0.8125rem;
- line-height: 1.5rem;
-}
-
#notice-popover .time {
margin: 0;
color: #999;
diff --git a/frontend/src/css/user-notifications.css b/frontend/src/css/user-notifications.css
index 35a9261b47..724b5ceed7 100644
--- a/frontend/src/css/user-notifications.css
+++ b/frontend/src/css/user-notifications.css
@@ -1,5 +1,5 @@
.notification-list-dialog {
- width: 720px;
+ width: calc(100% - 20rem);
max-width: calc(100% - 1rem);
height: calc(100% - 56px);
}
@@ -35,10 +35,26 @@
.notification-list-content .notification-modal-body {
height: 100%;
+ display: flex;
+ flex-direction: row;
+ min-height: 27rem;
overflow: hidden;
padding: 0;
}
+.notification-list-content .notification-modal-body .notice-dialog-side {
+ border-right: 1px solid #eee;
+ display: flex;
+ flex: 0 0 20%;
+ padding: 12px 8px;
+}
+
+.notification-list-content .notification-modal-body .notice-dialog-main {
+ display: flex;
+ flex: 0 0 80%;
+ overflow: inherit;
+}
+
.notification-modal-body .notification-dialog-body {
overflow: auto;
padding: 2rem 1rem;
@@ -80,22 +96,3 @@
font-size: 14px;
word-break: break-all;
}
-
-.wechat-dialog-body {
- display: flex;
- justify-content: center;
- padding: 3rem;
- flex-direction: column;
- align-items: center;
-}
-
-.wechat-dialog-message {
- width: 100%;
- display: flex;
- justify-content: center;
- flex-direction: column;
- align-items: center;
- margin-top: 1rem;
- color: #666;
- font-size: 14px;
-}
diff --git a/frontend/src/user-notifications.js b/frontend/src/user-notifications.js
index 1b05e8197b..07da49f047 100644
--- a/frontend/src/user-notifications.js
+++ b/frontend/src/user-notifications.js
@@ -1,4 +1,4 @@
-import React, { Fragment } from 'react';
+import React from 'react';
import PropTypes from 'prop-types';
import { Modal, ModalHeader, ModalBody, Dropdown, DropdownToggle, DropdownMenu, DropdownItem, TabPane, Nav, NavItem, NavLink, TabContent } from 'reactstrap';
import { Utils } from './utils/utils';
@@ -209,19 +209,22 @@ class UserNotificationsDialog extends React.Component {
renderNoticeContent = (content) => {
+ const { generalNoticeListUnseen, discussionNoticeListUnseen } = this.props;
let activeTab = this.state.activeTab;
return (
-
+ <>
-