1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-20 02:48:51 +00:00

change notification popover (#5629)

* change notification popover

delete useless test

* Update iconfont and user notification ui

* change notification dialog detail

* format css

* update url of all notifications when send notice email

---------

Co-authored-by: lian <imwhatiam123@gmail.com>
This commit is contained in:
Michael An
2023-09-08 13:54:05 +08:00
committed by GitHub
parent cd3ede438f
commit b46a60268f
24 changed files with 710 additions and 768 deletions

View File

@@ -1,7 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import { gettext, siteRoot } from '../../utils/constants';
import { Utils } from '../../utils/utils';

View File

@@ -0,0 +1,186 @@
.notification-wrapper .popover {
max-width: 300px;
}
.notification-container {
position: absolute;
background: #fff;
width: 320px;
right: -10px;
top: -1px;
border-radius: 3px;
box-shadow: 0 0 5px #ccc;
}
.notification-container .notification-header {
display: flex;
align-items: center;
justify-content: center;
height: 50px;
border-bottom: 1px solid #ededed;
font-size: 16px;
font-weight: 600;
position: relative;
}
.notification-container .notification-header .notification-close-icon {
position: absolute;
right: 14px;
height: 24px;
width: 24px;
text-align: center;
cursor: pointer;
color: #000;
opacity: 0.5;
font-weight: 700;
}
.notification-container .notification-header .notification-close-icon:hover {
opacity: 0.75;
}
.notification-container .notification-body {
padding: 0;
}
.notification-container .notification-body .show-weixin-qrcode {
cursor: pointer;
border-bottom: 1px solid #ededed;
height: 40px;
display: flex;
align-items: center;
justify-content: flex-start;
padding-left: 10px;
}
.show-weixin-qrcode .weixin-icon {
color: #999;
font-size: 20px;
margin-left: 20px;
}
.notification-container .notification-body .mark-notifications {
color: #b4b4b4;
cursor: pointer;
border-bottom: 1px solid #ededed;
height: 36px;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 1rem;
}
.notification-container .notification-body .mark-notifications:hover {
text-decoration: underline;
}
.notification-body .notification-list-container {
max-height: 260px;
overflow: auto;
}
.notification-list-container .notification-item {
padding: 14px 16px 14px 10px;
border-bottom: 1px solid #ededed;
position: relative;
cursor: pointer;
}
.notification-list-container .notification-item:last-child {
border-bottom: none;
}
.notification-list-container .notification-item:hover {
background: #f5f5f5;
}
.notification-list-container .notification-item .notification-item-header {
display: flex;
align-items: center
}
.notification-list-container .notification-item .notification-point {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
background: red;
margin-right: 12px;
position: absolute;
}
.notification-list-container .notification-item .notification-header-info {
display: flex;
justify-content: space-between;
flex: 1;
margin-left: 20px;
width: calc(100% - 20px);
}
.notification-user-detail {
display: flex;
width: 65%;
}
.notification-user-detail img {
margin-top: 3px;
}
.notification-user-name {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
font-weight: 500;
}
.notification-item .notification-header-info .notification-time {
color: #b4b4b4;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
font-size: 13px;
}
.notification-list-container .notification-item .notification-content-wrapper {
font-size: 13px;
}
.notification-item .notification-content-quotes {
width: 8px;
}
.notification-list-container .notification-item .notification-comment-content {
max-width: calc(100% - 16px);
}
.notification-list-container .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-list-container .notification-item .notification-comment-content p img {
max-width: 70%;
height: auto;
max-height: 60px;
}
.notification-body .notification-footer {
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: #f9f9f9;
cursor: pointer;
border-bottom-right-radius: 3px;
border-bottom-left-radius: 3px;
border-top: 1px solid #ededed;
}
.notification-body .notification-footer:hover {
text-decoration: underline;
}

View File

@@ -0,0 +1,83 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Popover } from 'reactstrap';
import './index.css';
export default class NotificationPopover extends React.Component {
static propTypes = {
headerText: PropTypes.string.isRequired,
bodyText: PropTypes.string.isRequired,
footerText: PropTypes.string.isRequired,
onNotificationListToggle: PropTypes.func,
onNotificationDialogToggle: PropTypes.func,
listNotifications: PropTypes.func,
onMarkAllNotifications: PropTypes.func,
children: PropTypes.any,
};
static defaultProps = {
headerText: '',
bodyText: '',
footerText: '',
};
componentDidMount() {
document.addEventListener('mousedown', this.handleOutsideClick);
}
componentWillUnmount() {
document.removeEventListener('mousedown', this.handleOutsideClick);
}
handleOutsideClick = (e) => {
if (!this.notificationContainerRef.contains(e.target)) {
document.removeEventListener('mousedown', this.handleOutsideClick);
if (e.target.className === 'tool notification' || e.target.parentNode.className === 'tool notification') {
return;
}
this.props.onNotificationListToggle();
}
}
onNotificationDialogToggle = () => {
this.props.onNotificationDialogToggle();
this.props.onNotificationListToggle();
}
onHandleScroll = () => {
if (this.notificationListRef.offsetHeight + this.notificationListRef.scrollTop + 1 >= this.notificationsWrapperRef.offsetHeight) {
this.props.listNotifications && this.props.listNotifications();
}
}
render() {
const { headerText, bodyText, footerText } = this.props;
return (
<Popover
className="notification-wrapper"
target="notification-popover"
isOpen={true}
fade={false}
hideArrow={true}
placement="bottom"
>
<div className="notification-container" ref={ref => this.notificationContainerRef = ref}>
<div className="notification-header">
{headerText}
<span className="sf3-font sf3-font-x-01 notification-close-icon" onClick={this.props.onNotificationListToggle}></span>
</div>
<div className="notification-body">
<div className="mark-notifications" onClick={this.props.onMarkAllNotifications}>{bodyText}</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>
</Popover>
);
}
}

View File

@@ -0,0 +1,126 @@
#notifications {
position: relative;
width: 32px;
}
#notice-icon {
position: relative;
display: block;
}
@media (max-width: 390px) {
#notifications {
margin-left: 8px;
}
}
#notifications .title {
line-height: 1.5;
font-size: 1rem;
color: #322;
font-weight: normal;
}
#notifications .sf2-icon-bell {
font-size: 24px;
line-height: 1;
color: #999;
vertical-align: middle;
}
#notifications .num {
position: absolute;
top: -3px;
left: 12px;
padding: 0 2px;
min-width: 16px;
height: 16px;
color: #fff;
font-size: 9px;
line-height: 16px;
text-align: center;
background: #fc6440;
border-radius: 100%;
}
#notice-popover {
top: 38px;
right: -12px;
}
#notice-popover .outer-caret {
right: 18px;
}
#notice-popover a {
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;
text-align: right;
font-size: 0.8125rem;
line-height: 1.5rem;
clear: both;
}
#notice-popover .view-all {
display: block;
padding: 7px 0;
text-align: center;
color: #a4a4a4;
}
#notice-popover .sf-popover-close {
position: absolute;
right: 10px;
top: 17px;
}
#notice-popover .sf-popover-hd {
border-bottom: 1px solid #dfdfe1;
margin: 0 10px;
}
#notice-popover .sf-popover-con {
max-height: 25rem;
}

View File

@@ -1,7 +1,11 @@
import React from 'react';
import NotificationPopover from './notification-popover';
import { seafileAPI } from '../../utils/seafile-api';
import { gettext, siteRoot } from '../../utils/constants';
import { gettext } from '../../utils/constants';
import NoticeItem from './notice-item';
import UserNotificationsDialog from '../../user-notifications';
import { Utils } from '../../utils/utils';
import './notification.css';
class Notification extends React.Component {
constructor(props) {
@@ -10,6 +14,7 @@ class Notification extends React.Component {
showNotice: false,
unseenCount: 0,
noticeList: [],
isShowNotificationDialog: this.getInitDialogState(),
};
}
@@ -58,29 +63,60 @@ class Notification extends React.Component {
}
render() {
getInitDialogState = () => {
const searchParams = Utils.getUrlSearches();
return searchParams.notifications === 'all';
}
onNotificationDialogToggle = () => {
let newSearch = this.state.isShowNotificationDialog ? null : 'all';
Utils.updateSearchParameter('notifications', newSearch);
this.setState({isShowNotificationDialog: !this.state.isShowNotificationDialog});
}
onNotificationListToggle = () => {
this.setState({showNotice: false});
}
onMarkAllNotifications = () => {
seafileAPI.updateNotifications().then(() => {
this.setState({
unseenCount: 0,
});
}).catch((error) => {
this.setState({
errorMsg: Utils.getErrorMsg(error, true)
});
});
}
render() {
const { unseenCount } = this.state;
return (
<div id="notifications">
<a href="#" onClick={this.onClick} className="no-deco" id="notice-icon" title={gettext('Notifications')} aria-label={gettext('Notifications')}>
<span className="sf2-icon-bell"></span>
<span className={`num ${this.state.unseenCount ? '' : 'hide'}`}>{this.state.unseenCount}</span>
<span className="sf2-icon-bell" id="notification-popover"></span>
<span className={`num ${unseenCount ? '' : 'hide'}`}>{unseenCount}</span>
</a>
<div id="notice-popover" className={`sf-popover ${this.state.showNotice ? '': 'hide'}`}>
<div className="outer-caret up-outer-caret"><div className="inner-caret"></div></div>
<div className="sf-popover-hd h-7 d-flex align-items-center justify-content-center">
<h3 className="sf-popover-title title m-0">{gettext('Notifications')}</h3>
<a href="#" onClick={this.onClick} title={gettext('Close')} aria-label={gettext('Close')} className="sf-popover-close js-close sf2-icon-x1 action-icon m-0"></a>
</div>
<div className="sf-popover-con">
<ul className="notice-list list-unstyled">
{this.state.showNotice &&
<NotificationPopover
headerText={gettext('Notification')}
bodyText={gettext('Mark all as read')}
footerText={gettext('View all notifications')}
onNotificationListToggle={this.onNotificationListToggle}
onNotificationDialogToggle={this.onNotificationDialogToggle}
onMarkAllNotifications={this.onMarkAllNotifications}
>
<ul className="notice-list list-unstyled" id="notice-popover">
{this.state.noticeList.map(item => {
return (<NoticeItem key={item.id} noticeItem={item} onNoticeItemClick={this.onNoticeItemClick}/>);
})}
</ul>
<a href={siteRoot + 'notice/list/'} className="view-all">{gettext('See All Notifications')}</a>
</div>
</div>
</NotificationPopover>
}
{this.state.isShowNotificationDialog &&
<UserNotificationsDialog onNotificationDialogToggle={this.onNotificationDialogToggle} />
}
</div>
);
}

View File

@@ -1,17 +1,142 @@
body {
overflow: hidden;
.notification-list-dialog {
width: 720px;
max-width: 720px;
height: calc(100% - 56px);
}
#wrapper {
.notification-list-dialog .notification-list-content {
height: 100%;
}
.top-header {
background: #f4f4f7;
border-bottom: 1px solid #e8e8e8;
padding: .5rem 1rem;
flex-shrink: 0;
.notification-header-close {
display: flex;
}
.op-bar {
padding: 9px 10px;
background: #f2f2f2;
border-radius: 2px;
.notification-header-close .notification-dropdown-toggle {
display: flex;
justify-content: center;
align-items: center;
height: 24px;
width: 24px
}
.notification-header-close .item-dropdown-icon,
.notification-header-close .notification-close-icon {
height: 24px;
width: 24px;
cursor: pointer;
color: #000;
opacity: 0.5;
}
.notification-header-close .notification-close-icon:hover,
.notification-header-close .item-dropdown-icon:hover {
color: #000;
opacity: 0.75;
}
/* The icon "..." do not need to be bold */
.notification-header-close .item-dropdown-icon {
font-weight: 400;
}
/* The icon 'x' needs to be bold */
.notification-header-close .notification-close-icon {
font-weight: 700;
}
.notification-header-close .dropdown-menu {
min-width: 8rem;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
}
.notification-header-close .dtable-dropdown-menu.large.dropdown-menu .dropdown-item {
padding: 0.25rem 1.5rem;
min-height: unset;
}
.notification-list-content .notification-modal-body {
height: 100%;
overflow: hidden;
padding: 0;
}
.notification-modal-body .notification-dialog-body {
overflow: auto;
padding: 2rem 1rem;
height: 100%;
}
.notification-dialog-body table {
width: 100%;
table-layout: fixed;
overflow-y: auto;
}
.notification-modal-body .notification-dialog-body .paginator {
height: 38px;
}
.notification-dialog-body table thead tr {
height: 2.1875rem;
}
.notification-dialog-body table th {
padding: 0.3125rem 0.1875rem;
border-bottom: 1px solid #eee;
text-align: left;
font-weight: normal;
line-height: 1.6;
color: #9c9c9c;
}
.notification-dialog-body table tbody tr:hover {
background: #f5f5f5;
cursor: pointer;
}
.notification-dialog-body table td {
padding: 0.5rem 0.1875rem;
border-bottom: 1px solid #eee;
color: #333;
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;
}
.notification-dialog-body .empty-tip {
margin: 5.5em 1em;
border-radius: 3px;
padding: 30px;
background-color: #fff;
text-align: center;
}
.notification-dialog-body .empty-tip .no-items-img-tip {
width: 100px;
height: 100px;
}
@media (min-width: 768px) {
.notification-dialog-body .empty-tip {
padding: 30px 80px;
}
}

View File

@@ -1,20 +1,19 @@
import React from 'react';
import ReactDom from 'react-dom';
import { navigate } from '@gatsbyjs/reach-router';
import PropTypes from 'prop-types';
import { Modal, ModalHeader, ModalBody, Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
import { Utils } from './utils/utils';
import { gettext, siteRoot, mediaUrl, logoPath, logoWidth, logoHeight, siteTitle } from './utils/constants';
import { gettext } 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 {
const PER_PAGE = 20;
class UserNotificationsDialog extends React.Component {
constructor(props) {
super(props);
@@ -22,19 +21,16 @@ class UserNotifications extends React.Component {
isLoading: true,
errorMsg: '',
currentPage: 1,
perPage: 25,
hasNextPage: false,
items: []
items: [],
isItemMenuShow: false,
};
}
componentDidMount() {
let urlParams = (new URL(window.location)).searchParams;
const {
currentPage, perPage
} = this.state;
const { currentPage } = this.state;
this.setState({
perPage: parseInt(urlParams.get('per_page') || perPage),
currentPage: parseInt(urlParams.get('page') || currentPage)
}, () => {
this.getItems(this.state.currentPage);
@@ -42,13 +38,13 @@ class UserNotifications extends React.Component {
}
getItems = (page) => {
const { perPage } = this.state;
seafileAPI.listNotifications(page, perPage).then((res) => {
this.setState({ isLoading: true })
seafileAPI.listNotifications(page, PER_PAGE).then((res) => {
this.setState({
isLoading: false,
items: res.data.notification_list,
items: [...this.state.items, ...res.data.notification_list],
currentPage: page,
hasNextPage: Utils.hasNextPage(page, perPage, res.data.count)
hasNextPage: Utils.hasNextPage(page, PER_PAGE, res.data.count)
});
}).catch((error) => {
this.setState({
@@ -56,26 +52,7 @@ class UserNotifications extends React.Component {
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) => {
@@ -91,7 +68,7 @@ class UserNotifications extends React.Component {
errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
});
});
}
};
clearAll = () => {
seafileAPI.deleteNotifications().then((res) => {
@@ -104,89 +81,61 @@ class UserNotifications extends React.Component {
errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
});
});
};
toggle = () => {
this.props.onNotificationDialogToggle();
};
toggleDropDownMenu = () => {
this.setState({isItemMenuShow: !this.state.isItemMenuShow});
};
onHandleScroll = () => {
if (!this.state.hasNextPage || this.state.isLoading ||!this.tableRef) {
return;
}
if (this.notificationTableRef.offsetHeight + this.notificationTableRef.scrollTop + 1 >= this.tableRef.offsetHeight) {
this.getItems(this.state.currentPage + 1);
}
}
render() {
renderHeaderRowBtn = () => {
return (
<React.Fragment>
<div className="h-100 d-flex flex-column">
<div className="top-header d-flex justify-content-between">
<a href={siteRoot}>
<img src={mediaUrl + logoPath} height={logoHeight} width={logoWidth} title={siteTitle} alt="logo" />
</a>
<CommonToolbar onSearchedClick={this.onSearchedClick} />
</div>
<div className="flex-auto container-fluid pt-4 pb-6 o-auto">
<div className="row">
<div className="col-md-10 offset-md-1">
<div className="d-flex justify-content-between align-items-center flex-wrap op-bar">
<h2 className="h4 m-0 my-1">{gettext('Notifications')}</h2>
<div>
<button className="btn btn-secondary op-bar-btn" onClick={this.markAllRead}>{gettext('Mark all read')}</button>
<button className="btn btn-secondary op-bar-btn ml-2" onClick={this.clearAll}>{gettext('Clear')}</button>
</div>
</div>
<Content
isLoading={this.state.isLoading}
errorMsg={this.state.errorMsg}
items={this.state.items}
currentPage={this.state.currentPage}
hasNextPage={this.state.hasNextPage}
curPerPage={this.state.perPage}
resetPerPage={this.resetPerPage}
getListByPage={this.getItems}
/>
</div>
</div>
</div>
</div>
</React.Fragment>
<div className="notification-header-close">
<Dropdown isOpen={this.state.isItemMenuShow} toggle={this.toggleDropDownMenu}>
<DropdownToggle tag="span" data-toggle="dropdown" aria-expanded={this.state.isItemMenuShow} className="notification-dropdown-toggle">
<span className="sf3-font sf3-font-more-level item-dropdown-icon"></span>
</DropdownToggle>
<DropdownMenu right={true} className="dtable-dropdown-menu large">
<DropdownItem onClick={this.markAllRead}>{gettext('Mark all read')}</DropdownItem>
<DropdownItem onClick={this.clearAll}>{gettext('Clear')}</DropdownItem>
</DropdownMenu>
</Dropdown>
<span className="sf3-font sf3-font-x-01 notification-close-icon" onClick={this.toggle}></span>
</div>
);
}
}
class Content extends React.Component {
constructor(props) {
super(props);
}
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 <Loading />;
}
const { isLoading, errorMsg, items } = this.state;
let content;
if (errorMsg) {
return <p className="error mt-6 text-center">{errorMsg}</p>;
content = <p className="error mt-6 text-center">{errorMsg}</p>;
}
const isDesktop = Utils.isDesktop();
const theadData = isDesktop ? [
{width: '7%', text: ''},
{width: '73%', text: gettext('Message')},
{width: '20%', text: gettext('Time')}
] : [
{width: '15%', text: ''},
{width: '52%', text: gettext('Message')},
{width: '33%', text: gettext('Time')}
];
return (
<React.Fragment>
<table className="table-hover">
else {
const isDesktop = Utils.isDesktop();
const theadData = isDesktop ? [
{width: '7%', text: ''},
{width: '73%', text: gettext('Message')},
{width: '20%', text: gettext('Time')}
] : [
{width: '15%', text: ''},
{width: '52%', text: gettext('Message')},
{width: '33%', text: gettext('Time')}
];
content = (
<table className="table-hover" ref={ref => this.tableRef = ref}>
<thead>
<tr>
{theadData.map((item, index) => {
@@ -200,19 +149,28 @@ class Content extends React.Component {
})}
</tbody>
</table>
{items.length > 0 &&
<Paginator
gotoPreviousPage={this.getPreviousPage}
gotoNextPage={this.getNextPage}
currentPage={currentPage}
hasNextPage={hasNextPage}
curPerPage={curPerPage}
resetPerPage={this.props.resetPerPage}
/>
}
</React.Fragment>
);
if (isLoading) {
content = <>{content}<Loading /></>;
}
}
return (
<Modal isOpen={true} 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">
<div className="notification-dialog-body" ref={ref => this.notificationTableRef = ref} onScroll={this.onHandleScroll}>
{content}
</div>
</ModalBody>
</Modal>
);
}
}
ReactDom.render(<UserNotifications />, document.getElementById('wrapper'));
UserNotificationsDialog.propTypes = {
onNotificationDialogToggle: PropTypes.func.isRequired,
};
export default UserNotificationsDialog;

View File

@@ -1571,4 +1571,34 @@ export const Utils = {
return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
},
getUrlSearches() {
const search = location.search;
let searchParams = {};
if (search.length === 0) {
return searchParams;
}
let allSearches = search.split('?')[1];
let allSearchesArr = allSearches.split('&');
allSearchesArr.forEach(item => {
let itemArr = item.split('=');
searchParams[itemArr[0]] = decodeURI(itemArr[1]);
});
return searchParams;
},
// If value is null, delete the search parameter; else, add or update the search parameter.
updateSearchParameter(key, value) {
const { origin, pathname } = location;
const searchParams = this.getUrlSearches();
searchParams[key] = value;
let newSearch = '?';
for (let key in searchParams) {
let value = searchParams[key];
if (value) {
newSearch = newSearch === '?' ? `?${key}=${value}` : `${newSearch}&${key}=${value}`;
}
}
history.replaceState(null, '', origin + pathname + newSearch);
},
};