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 { } - - {/*
    - - {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 (
    @@ -140,25 +142,35 @@ class Notification extends React.Component { onNotificationDialogToggle={this.onNotificationDialogToggle} onMarkAllNotifications={this.onMarkAllNotifications} tabItemClick={this.tabItemClick} + generalNoticeListUnseen={generalNoticeListUnseen} + discussionNoticeListUnseen={discussionNoticeListUnseen} > - {this.state.currentTab === 'general' && + {currentTab === 'general' &&
      - {this.state.generalNoticeList.map(item => { - return (); + {generalNoticeList.map(item => { + return ( + + ); })}
    } - {this.state.currentTab === 'discussion' && + {currentTab === 'discussion' &&
      - {this.state.discussionNoticeList.map(item => { - return (); + {discussionNoticeList.map(item => { + return ( + + ); })}
    } } {this.state.isShowNotificationDialog && - + }
    ); 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 ( - + <>
    -