1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-05 17:02:47 +00:00

change UI 1

This commit is contained in:
Michael An
2024-12-02 10:55:29 +08:00
committed by 孙永强
parent 4f87a9e477
commit 3f17045e47
8 changed files with 188 additions and 155 deletions

View File

@@ -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 = '<a href=' + userHref + '>' + groupStaff + '</a>';
let groupLink = '<a href=' + groupHref + '>' + groupName + '</a>';
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}', `<a href='${Utils.encodePath(repoUrl)}'>`);
notice = notice.replace('{/tagA}', '</a>');
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}', `<a href='${Utils.encodePath(repoUrl)}'>`);
notice = notice.replace('{/tagA}', '</a>');
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}', `<a href='${Utils.encodePath(repoUrl)}'>`);
notice = notice.replace('{/tagA}', '</a>');
notice = notice.replace('{tagB}', `<a href='${Utils.encodePath(groupUrl)}'>`);
notice = notice.replace('{/tagB}', '</a>');
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}', `<a href=${Utils.encodePath(repoUrl)}>`);
notice = notice.replace('{/tagA}', '</a>');
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}', `<a href=${Utils.encodePath(fileLink)}>`);
notice = notice.replace('{/tagA}', '</a>');
@@ -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 = `<a href=${repoURL} target="_blank">${Utils.HTMLescape(repo_name)}</a>`;
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 {
</td>
</tr>
) : (
<li onClick={this.onNoticeItemClick} className={noticeItem.seen ? 'read' : 'unread'}>
<div className="notice-item">
<div className="main-info">
<div className="auther-info">
<img src={avatar_url} width="32" height="32" className="avatar" alt=""/>
<p>{username}</p>
</div>
<div>
<p className="brief" dangerouslySetInnerHTML={{ __html: notice }}></p>
<li className='notification-item' onClick={this.onNoticeItemClick}>
<div className="notification-item-header">
{!noticeItem.seen &&
<span className="notification-point" onClick={this.onMarkNotificationRead}></span>
}
<div className="notification-header-info">
<div className="notification-user-detail">
<img className="notification-user-avatar" src={avatar_url} alt="" />
<span className="ml-2 notification-user-name">{username || gettext('System')}</span>
</div>
<span className="notification-time">{dayjs(noticeItem.time).fromNow()}</span>
</div>
<p className="time">{dayjs(noticeItem.time).fromNow()}</p>
</div>
<div className="notification-content-wrapper">
<div dangerouslySetInnerHTML={{ __html: notice }}></div>
</div>
</li>
);

View File

@@ -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;
}
color: #ED7109;
}

View File

@@ -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 (
<Popover
className="notification-wrapper"
@@ -77,11 +79,13 @@ export default class NotificationPopover extends React.Component {
<li className="nav-item" onClick={() => this.tabItemClick('general')}>
<span className={`nav-link ${currentTab === 'general' ? 'active' : ''}`}>
{gettext('General')}
{generalNoticeListUnseen > 0 && <span>({generalNoticeListUnseen})</span>}
</span>
</li>
<li className="nav-item" onClick={() => this.tabItemClick('discussion')}>
<span className={`nav-link ${currentTab === 'discussion' ? 'active' : ''}`}>
{gettext('Discussion')}
{discussionNoticeListUnseen > 0 && <span>({discussionNoticeListUnseen})</span>}
</span>
</li>
</ul>
@@ -103,23 +107,6 @@ export default class NotificationPopover extends React.Component {
</div>
</div>
}
{/* <div className="mark-notifications" onClick={this.props.onMarkAllNotifications}>
<ul className="nav dtable-external-links-tab">
<li className="nav-item">
<span className="nav-link">General</span>
</li>
<li className="nav-item">
<span className="nav-link">Discussion</span>
</li>
</ul>
<span className="mark-all-read">{bodyText}</span>
</div>
<div className="notification-list-container" onScroll={this.onHandleScroll} ref={ref => this.notificationListRef = ref}>
<div ref={ref => this.notificationsWrapperRef = ref}>
{this.props.children}
</div>
</div> */}
<div className="notification-footer" onClick={this.onNotificationDialogToggle}>{footerText}</div>
</div>
</div>

View File

@@ -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 (
<div id="notifications">
<a href="#" onClick={this.onClick} className="no-deco" id="notice-icon" title={gettext('Notifications')} aria-label={gettext('Notifications')}>
@@ -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' &&
<ul className="notice-list list-unstyled" id="notice-popover">
{this.state.generalNoticeList.map(item => {
return (<NoticeItem key={item.id} noticeItem={item} onNoticeItemClick={this.onNoticeItemClick}/>);
{generalNoticeList.map(item => {
return (
<NoticeItem key={item.id} noticeItem={item} onNoticeItemClick={this.onNoticeItemClick}/>
);
})}
</ul>
}
{this.state.currentTab === 'discussion' &&
{currentTab === 'discussion' &&
<ul className="notice-list list-unstyled" id="notice-popover">
{this.state.discussionNoticeList.map(item => {
return (<NoticeItem key={item.id} noticeItem={item} onNoticeItemClick={this.onNoticeItemClick}/>);
{discussionNoticeList.map(item => {
return (
<NoticeItem key={item.id} noticeItem={item} onNoticeItemClick={this.onNoticeItemClick}/>
);
})}
</ul>
}
</NotificationPopover>
}
{this.state.isShowNotificationDialog &&
<UserNotificationsDialog onNotificationDialogToggle={this.onNotificationDialogToggle} />
<UserNotificationsDialog
onNotificationDialogToggle={this.onNotificationDialogToggle}
generalNoticeListUnseen={generalNoticeListUnseen}
discussionNoticeListUnseen={discussionNoticeListUnseen}
/>
}
</div>
);

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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 (
<Fragment>
<>
<div className="notice-dialog-side">
<Nav pills>
<Nav pills className="flex-column">
<NavItem role="tab" aria-selected={activeTab === 'general'} aria-controls="general-notice-panel">
<NavLink className={activeTab === 'general' ? 'active' : ''} onClick={this.tabItemClick} tabIndex="0" value="general">
{gettext('General')}
{generalNoticeListUnseen > 0 && <span>({generalNoticeListUnseen})</span>}
</NavLink>
</NavItem>
<NavItem role="tab" aria-selected={activeTab === 'discussion'} aria-controls="discussion-notice-panel">
<NavLink className={activeTab === 'discussion' ? 'active' : ''} onClick={this.tabItemClick} tabIndex="1" value="discussion">
{gettext('Discussion')}
{discussionNoticeListUnseen > 0 && <span>({discussionNoticeListUnseen})</span>}
</NavLink>
</NavItem>
</Nav>
@@ -229,24 +232,22 @@ class UserNotificationsDialog extends React.Component {
<div className="notice-dialog-main">
<TabContent activeTab={this.state.activeTab}>
{activeTab === 'general' &&
<TabPane tabId="general" role="tabpanel" id="general-notice-panel">
<div className="notification-dialog-body" ref={ref => this.notificationTableRef = ref}
onScroll={this.onHandleScroll}>
<TabPane tabId="general" role="tabpanel" id="general-notice-panel" className="h-100">
<div className="notification-dialog-body" ref={ref => this.notificationTableRef = ref} onScroll={this.onHandleScroll}>
{content}
</div>
</TabPane>
}
{activeTab === 'discussion' &&
<TabPane tabId="discussion" role="tabpanel" id="discussion-notice-panel">
<div className="notification-dialog-body" ref={ref => this.notificationTableRef = ref}
onScroll={this.onHandleScroll}>
<TabPane tabId="discussion" role="tabpanel" id="discussion-notice-panel" className="h-100">
<div className="notification-dialog-body" ref={ref => this.notificationTableRef = ref} onScroll={this.onHandleScroll}>
{content}
</div>
</TabPane>
}
</TabContent>
</div>
</Fragment>
</>
);
};
@@ -278,7 +279,9 @@ class UserNotificationsDialog extends React.Component {
</thead>
<tbody>
{items.map((item, index) => {
return (<NoticeItem key={index} noticeItem={item} tr={true} />);
return (
<NoticeItem key={index} noticeItem={item} tr={true} />
);
})}
</tbody>
</table>
@@ -289,8 +292,14 @@ class UserNotificationsDialog extends React.Component {
}
return (
<Modal isOpen={true} toggle={this.toggle} className="notification-list-dialog" contentClassName="notification-list-content"
zIndex={1046}>
<Modal
isOpen={true}
size={'lg'}
toggle={this.toggle}
className="notification-list-dialog"
contentClassName="notification-list-content"
zIndex={1046}
>
<ModalHeader close={this.renderHeaderRowBtn()} toggle={this.toggle}>{gettext('Notifications')}</ModalHeader>
<ModalBody className="notification-modal-body">
{this.renderNoticeContent(content)}