Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: new notification #473

Merged
merged 68 commits into from
Apr 5, 2023
Merged
Show file tree
Hide file tree
Changes from 61 commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
947cab0
ws watch notify
trim21 Dec 16, 2022
c91a331
remove unused state
trim21 Dec 16, 2022
163ad1d
aviod create too mant ws client
trim21 Dec 16, 2022
dd7433d
Merge branch 'master' into notify
trim21 Dec 17, 2022
200fd58
wrap notify ws client
trim21 Dec 17, 2022
c6cace9
Merge branch 'master' into notify
trim21 Dec 17, 2022
9044f01
wip
trim21 Dec 17, 2022
9b3a14a
use websocket
trim21 Dec 17, 2022
3736561
enable ws
trim21 Dec 17, 2022
7b2cc5a
fix socket.io connection leak
trim21 Dec 18, 2022
60eeb54
increast backoff
trim21 Dec 18, 2022
226687d
no need lodash
trim21 Dec 18, 2022
d2fcc76
basic notify
trim21 Dec 18, 2022
b78c6a4
update
trim21 Dec 18, 2022
01b3081
fix tucao
trim21 Dec 18, 2022
529ac50
add style
trim21 Dec 18, 2022
01d4d80
format
trim21 Dec 18, 2022
b9cb488
on close
trim21 Dec 18, 2022
fde797d
on close
trim21 Dec 18, 2022
fc67ffe
mark as read
trim21 Dec 18, 2022
e587976
Delete old.html
trim21 Dec 20, 2022
0e03ec5
lock
trim21 Dec 20, 2022
41a3408
fix lint
trim21 Dec 20, 2022
a3cbd4b
Merge remote-tracking branch 'upstream/master' into notify
trim21 Jan 11, 2023
4ba272a
use sse
trim21 Jan 11, 2023
4e2663d
fix import
trim21 Jan 11, 2023
ef1c8ad
fallback to polling
trim21 Jan 11, 2023
f1a713e
Merge branch 'notify' of https:/trim21/frontend into noti…
FoundTheWOUT Mar 17, 2023
5b4ef5f
Merge branch 'master' of https:/bangumi/frontend into not…
FoundTheWOUT Mar 17, 2023
0540a9c
Icon Enter, Less preprocessor inject style/index.less
FoundTheWOUT Mar 17, 2023
18d5d09
WIP
FoundTheWOUT Mar 17, 2023
4f3af59
Merge branch 'master' of https:/bangumi/frontend into not…
FoundTheWOUT Mar 18, 2023
3f3bbbb
Move noticeItem to website internal
FoundTheWOUT Mar 18, 2023
01e4ac2
style
FoundTheWOUT Mar 18, 2023
5d3078f
Add pagination
FoundTheWOUT Mar 18, 2023
0fba78a
use polling
FoundTheWOUT Mar 18, 2023
71374d8
update
FoundTheWOUT Mar 19, 2023
1b357ad
Merge remote-tracking branch 'upstream/master' into notification
trim21 Mar 24, 2023
a85b294
Merge remote-tracking branch 'upstream/master' into notification
trim21 Mar 25, 2023
7dda146
短信收发 TODO
FoundTheWOUT Mar 27, 2023
9ba4b9f
Merge branch 'master' into notification
trim21 Mar 27, 2023
11db551
Merge branch 'master' into notification
trim21 Mar 27, 2023
d1cc6c0
clearNotice
FoundTheWOUT Mar 27, 2023
9fc5340
Merge branch 'master' into notification
trim21 Mar 28, 2023
d5027f2
Merge branch 'master' of https:/bangumi/frontend into not…
FoundTheWOUT Mar 31, 2023
ac9b778
Update document title when notified
FoundTheWOUT Mar 31, 2023
a38fd46
Merge branch 'notification' of github.com:FoundTheWOUT/bangumi-fronte…
FoundTheWOUT Mar 31, 2023
6a02f68
Watch title change in useNotify
FoundTheWOUT Mar 31, 2023
ae8dea6
Fix style
FoundTheWOUT Mar 31, 2023
fe20ba4
Merge branch 'master' of https:/bangumi/frontend into not…
FoundTheWOUT Apr 1, 2023
81923d5
Dispose socket
FoundTheWOUT Apr 1, 2023
c08b3e5
Merge branch 'master' into notification
trim21 Apr 3, 2023
fcbb571
fallback to polling
FoundTheWOUT Apr 3, 2023
3bf944e
Merge branch 'notification' of github.com:FoundTheWOUT/bangumi-fronte…
FoundTheWOUT Apr 3, 2023
a43a568
fix
FoundTheWOUT Apr 3, 2023
6d07f7e
use notice provider
trim21 Apr 3, 2023
b85e928
fix build
trim21 Apr 3, 2023
d76f1c5
fix test
trim21 Apr 3, 2023
1f3a3b9
fix test
trim21 Apr 3, 2023
7bb1f2a
fix context type
trim21 Apr 3, 2023
e1fd859
fallback inhook
trim21 Apr 3, 2023
4cdf3e9
Merge branch 'master' into notification
trim21 Apr 3, 2023
9e0d265
fix page title
trim21 Apr 3, 2023
67d84ff
code review
trim21 Apr 4, 2023
feadddd
code review
trim21 Apr 4, 2023
eee8913
fix error page
trim21 Apr 4, 2023
526d3cb
Merge remote-tracking branch 'upstream/master' into notification
trim21 Apr 4, 2023
94dc756
Merge branch 'master' into notification
trim21 Apr 4, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/client/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ export enum UserGroup {
User = 10,
WikiContributor = 11,
}

export type { Notice as INotice } from './client';
3 changes: 3 additions & 0 deletions packages/design/theme/variables.less
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
@blue-doing: #8fcaec;
@blue-collect: #3db3f5;

/* State */
@error: #f97f77;

/* Response */
@sm: 640px;
@md: 768px;
Expand Down
3 changes: 3 additions & 0 deletions packages/icons/assets/arrow-path.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions packages/icons/assets/enter.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions packages/icons/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export { ReactComponent as ArrowDown } from './assets/arrow-down.svg';
export { ReactComponent as ArrowDownCircle } from './assets/arrow-down-circle.svg';
export { ReactComponent as ArrowUpCircle } from './assets/arrow-up-circle.svg';
export { ReactComponent as ArrowRightCircle } from './assets/arrow-right-circle.svg';
export { ReactComponent as ArrowPath } from './assets/arrow-path.svg';
export { ReactComponent as Plus } from './assets/plus.svg';
export { ReactComponent as Minus } from './assets/minus.svg';
export { ReactComponent as Cursor } from './assets/cursor.svg';
Expand All @@ -37,3 +38,4 @@ export { ReactComponent as TopicClosed } from './assets/topic-closed.svg';
export { ReactComponent as TopicReopen } from './assets/topic-reopen.svg';
export { ReactComponent as OpenQuote } from './assets/open-quote.svg';
export { ReactComponent as CloseQuote } from './assets/close-quote.svg';
export { ReactComponent as Enter } from './assets/enter.svg';
1 change: 1 addition & 0 deletions packages/website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"react-router-dom": "^6.10.0",
"reset-css": "^5.0.1",
"rooks": "^7.10.0",
"socket.io-client": "^4.5.4",
"swr": "^2.1.1"
},
"devDependencies": {
Expand Down
6 changes: 5 additions & 1 deletion packages/website/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ import React, { Suspense } from 'react';
import { HelmetProvider } from 'react-helmet-async';
import { useRoutes } from 'react-router-dom';

import { NoticeProvider } from '@bangumi/website/hooks/use-notify';

import { UserProvider } from './hooks/use-user';

const App = () => {
return (
<HelmetProvider>
<UserProvider>
<Suspense fallback={null}>{useRoutes(pageRoutes)}</Suspense>
<NoticeProvider>
<Suspense fallback={null}>{useRoutes(pageRoutes)}</Suspense>
</NoticeProvider>
</UserProvider>
</HelmetProvider>
);
Expand Down
21 changes: 18 additions & 3 deletions packages/website/src/components/Header/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import cn from 'classnames';
import type { FC } from 'react';
import React, { useState } from 'react';
import { Link, useLocation } from 'react-router-dom';

import { Avatar, Button, Divider, Input, Menu } from '@bangumi/design';
import { Notification, Search as SearchIcon, Setting } from '@bangumi/icons';
import { UnreadableCodeError } from '@bangumi/utils';
import { useNotify } from '@bangumi/website/hooks/use-notify';

import { ReactComponent as Logo } from '../../assets/logo.svg';
import { ReactComponent as Musume1 } from '../../assets/musume_1.svg';
Expand Down Expand Up @@ -90,12 +92,15 @@ if (Musume === undefined) {

const Header: FC = () => {
const { user } = useUser();
const { noticeCount } = useNotify();
const location = useLocation();

const [showMobileMenu, setShowMobileMenu] = useState(false);

return (
<header className={style.container}>
<div className={style.main}>
{/* left */}
<div className='flex items-center'>
{/* Logo */}
<a className={style.logo} href='/'>
Expand All @@ -119,7 +124,9 @@ const Header: FC = () => {
<Menu items={navRight} wrapperClass={style.navRight} />
</div>
</div>
<div className='flex items-center'>

{/* right */}
<div className={style.headerRight}>
<div className={style.infoBox}>
{/* Search Todo */}
<Input
Expand All @@ -143,8 +150,16 @@ const Header: FC = () => {
{/* Avatar */}
{user ? (
<>
<Notification className={style.iconNotification} />
<Setting className={style.iconSetting} />
<Link
to='/notifications'
className={cn(style.icon, style.iconNotification, {
[style.iconNotificationNotice!]: noticeCount > 0,
trim21 marked this conversation as resolved.
Show resolved Hide resolved
})}
>
<Notification />
</Link>

<Setting className={style.icon} />
<Avatar src={user.avatar.large} wrapperClass={style.avatar} />
</>
) : (
Expand Down
39 changes: 32 additions & 7 deletions packages/website/src/components/Header/style.module.less
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,17 @@
}
}

.header-right {
.flex();
.items-center();

gap: 12px;
}

.infoBox {
display: flex;
align-items: center;
padding: 0 7px 0 9px;
margin-right: 16px;

.search {
height: 32px;
Expand All @@ -109,16 +115,35 @@
}
}

.iconNotification {
margin-right: 25px;
width: 16px;
height: 18px;
.red-dot {
content: '';
position: absolute;
background: #f00;
border-radius: 50%;
width: 8px;
height: 8px;
top: -3px;
right: 0;
}

.iconSetting {
margin-right: 25px;
.icon {
width: 18px;
height: 18px;

&-notification {
position: relative;

&-notice::before {
.red-dot();
}

// ping animation
&-notice::after {
.red-dot();

animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
}
}
}

.link {
Expand Down
12 changes: 11 additions & 1 deletion packages/website/src/components/Helmet.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import React from 'react';
import { Helmet as BaseHelmet, type HelmetProps } from 'react-helmet-async';

import { useNotify } from '@bangumi/website/hooks/use-notify.tsx';
trim21 marked this conversation as resolved.
Show resolved Hide resolved

const Helmet = (props: React.PropsWithChildren<HelmetProps>) => {
const { noticeCount } = useNotify();

return (
<BaseHelmet defaultTitle='Bangumi 番组计划' titleTemplate='%s | Bangumi 番组计划' {...props} />
<BaseHelmet
defaultTitle='Bangumi 番组计划'
titleTemplate={
noticeCount ? `(${noticeCount}) %s | Bangumi 番组计划` : `%s | Bangumi 番组计划`
}
{...props}
/>
);
};

Expand Down
61 changes: 61 additions & 0 deletions packages/website/src/hooks/use-notify.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import type { PropsWithChildren } from 'react';
import React, { useEffect, useState } from 'react';
import type { Socket } from 'socket.io-client';
import { io } from 'socket.io-client';

import { useUser } from '@bangumi/website/hooks/use-user';

let socket: Socket | null;

interface NoticeContextType {
noticeCount: number;
}

const NoticeContext = React.createContext<NoticeContextType>(null!);

export const NoticeProvider: React.FC<PropsWithChildren> = ({ children }) => {
const { user } = useUser();
const [noticeCount, setNoticeCount] = useState(0);

useEffect(() => {
if (!user) {
return;
}
if (!socket) {
socket = io(location.host, {
path: '/p1/socket-io/',
reconnection: true,
reconnectionDelay: 5000,
reconnectionDelayMax: 10000,
transports: ['websocket'],
});

socket.on('notify', (event: string) => {
const { count } = JSON.parse(event) as { count: number };
setNoticeCount(count);
});

socket.on('connect_error', () => {
// fallback to polling in netlify env
if (socket) {
socket.io.opts.transports = ['polling', 'websocket'];
}
});
}

return () => {
socket?.disconnect();
socket = null;
};
}, [user, setNoticeCount]);

const value: NoticeContextType = {
noticeCount,
};

return <NoticeContext.Provider value={value}> {children} </NoticeContext.Provider>;
};

export function useNotify(): NoticeContextType {
return React.useContext(NoticeContext) ?? { noticeCount: 0 };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

React.useContext(NoticeContext) 在没有provider的情况下是 null,所以会导致一些测试出错。

不过我感觉这里也不用具体给每个失败的测试都加上provider,就直接加了个fallback。

}
92 changes: 92 additions & 0 deletions packages/website/src/pages/index/notifications/index.module.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
@import '@bangumi/design/theme/base';

.title {
font-weight: 600;
font-size: 24px;
line-height: 34px;
color: @gray-100;
}

.subtitle {
display: flex;
gap: 10px;
align-items: center;
height: 22px;
color: @gray-60;

> svg {
cursor: pointer;
}
}

.tab {
margin-top: 24px;
margin-bottom: 20px;
}

.read-all-btn {
height: 34px;
}

.filter {
.flex();

justify-content: space-between;
margin-bottom: 10px;

&-input {
height: 34px;
}
}

.notice-item {
// padding: 5px 0 5px 5px;
display: flex;
align-items: center;
height: 40px;
gap: 10px;
border-bottom: 1px dashed @gray-10;
color: @gray-80;

a,
span {
white-space: nowrap;
}

a {
font-weight: 600;
}

&-avatar {
height: 30px;
width: 30px;
border-radius: 6px;
}

// TODO: 移动端换行
&-body {
display: flex;
gap: 4px;
max-width: 60%;

&-content {
.truncate();
}
}

&-date {
color: @gray-10;
}

&-red-dot {
width: 12px;
height: 12px;
border-radius: 9999px;
margin-left: auto;
margin-right: 4px;

/* Alert/Error */

background: @error;
}
}
Loading