Skip to content

Commit fc6c00e

Browse files
committed
feat: optimize
Signed-off-by: Innei <[email protected]>
1 parent c1c7372 commit fc6c00e

File tree

12 files changed

+177
-82
lines changed

12 files changed

+177
-82
lines changed

src/app/thinking/constants.ts

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const FETCH_SIZE = 10
2+
export const QUERY_KEY = ['recent']

src/app/thinking/page.tsx

+2-4
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ import { urlBuilder } from '~/lib/url-builder'
3535
import { useAggregationSelector } from '~/providers/root/aggregation-data-provider'
3636
import { useModalStack } from '~/providers/root/modal-stack-provider'
3737

38-
const FETCH_SIZE = 10
39-
const QUERY_KEY = ['recent']
38+
import { FETCH_SIZE, QUERY_KEY } from './constants'
39+
4040
export default function Page() {
4141
return (
4242
<div>
@@ -168,8 +168,6 @@ const List = () => {
168168
return acc + cur.length
169169
}, 0)
170170

171-
console.log(count)
172-
173171
animate(
174172
'li',
175173
{

src/components/layout/header/internal/HeaderContent.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,10 @@ const ForDesktop: Component<{
118118
<div className="flex px-4 font-medium text-zinc-800 dark:text-zinc-200">
119119
{headerMenuConfig.map((section) => {
120120
const subItemActive =
121-
section.subMenu?.findIndex((item) => item.path === pathname) || -1
121+
section.subMenu?.findIndex((item) => {
122+
return item.path === pathname || pathname.slice(1) === item.path
123+
}) ?? -1
124+
122125
return (
123126
<HeaderMenuItem
124127
iconLayout={animatedIcon}

src/components/ui/link-card/LinkCard.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import type { FC, ReactNode, SyntheticEvent } from 'react'
99
import { simpleCamelcaseKeys as camelcaseKeys } from '@mx-space/api-client'
1010

1111
import { LazyLoad } from '~/components/common/Lazyload'
12-
import { usePeek } from '~/components/widgets/peek/PeekLink'
12+
import { usePeek } from '~/components/widgets/peek/usePeek'
1313
import { useIsClientTransition } from '~/hooks/common/use-is-client'
1414
import { preventDefault } from '~/lib/dom'
1515
import { apiClient } from '~/lib/request'

src/components/widgets/peek/PeekLink.tsx

+1-60
Original file line numberDiff line numberDiff line change
@@ -3,68 +3,9 @@ import Link from 'next/link'
33
import type { LinkProps } from 'next/link'
44
import type { FC, PropsWithChildren, SyntheticEvent } from 'react'
55

6-
import { useIsMobile } from '~/atoms'
76
import { preventDefault } from '~/lib/dom'
8-
import { useModalStack } from '~/providers/root/modal-stack-provider'
97

10-
import { PeekModal } from './PeekModal'
11-
12-
export const usePeek = () => {
13-
const isMobile = useIsMobile()
14-
const { present } = useModalStack()
15-
return useCallback(
16-
(href: string) => {
17-
if (isMobile) return
18-
const basePresentProps = {
19-
clickOutsideToDismiss: true,
20-
title: 'Preview',
21-
modalClassName:
22-
'relative mx-auto mt-[10vh] scrollbar-none max-w-full overflow-auto px-2 lg:max-w-[65rem] lg:p-0',
23-
}
24-
25-
if (href.startsWith('/notes/')) {
26-
requestAnimationFrame(async () => {
27-
const NotePreview = await import('./NotePreview').then(
28-
(module) => module.NotePreview,
29-
)
30-
present({
31-
...basePresentProps,
32-
CustomModalComponent: () => (
33-
<PeekModal to={href}>
34-
<NotePreview noteId={parseInt(href.split('/').pop()!)} />
35-
</PeekModal>
36-
),
37-
content: () => null,
38-
})
39-
})
40-
41-
return true
42-
} else if (href.startsWith('/posts/')) {
43-
requestAnimationFrame(async () => {
44-
const PostPreview = await import('./PostPreview').then(
45-
(module) => module.PostPreview,
46-
)
47-
const splitpath = href.split('/')
48-
const slug = splitpath.pop()!
49-
const category = splitpath.pop()!
50-
present({
51-
...basePresentProps,
52-
CustomModalComponent: () => (
53-
<PeekModal to={href}>
54-
<PostPreview category={category} slug={slug} />
55-
</PeekModal>
56-
),
57-
content: () => null,
58-
})
59-
})
60-
return true
61-
}
62-
63-
return false
64-
},
65-
[isMobile, present],
66-
)
67-
}
8+
import { usePeek } from './usePeek'
689

6910
export const PeekLink: FC<
7011
{
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { useLayoutEffect } from 'react'
2+
3+
import { usePeek } from './usePeek'
4+
5+
declare global {
6+
interface Window {
7+
peek: ReturnType<typeof usePeek>
8+
}
9+
}
10+
11+
export const PeekPortal = () => {
12+
const peek = usePeek()
13+
14+
useLayoutEffect(() => {
15+
peek && (window.peek = peek)
16+
}, [peek])
17+
return null
18+
}
+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { useCallback } from 'react'
2+
3+
import { useIsMobile } from '~/atoms'
4+
import { useModalStack } from '~/providers/root/modal-stack-provider'
5+
6+
import { PeekModal } from './PeekModal'
7+
8+
export const usePeek = () => {
9+
const isMobile = useIsMobile()
10+
const { present } = useModalStack()
11+
return useCallback(
12+
(href: string) => {
13+
if (isMobile) return
14+
const basePresentProps = {
15+
clickOutsideToDismiss: true,
16+
title: 'Preview',
17+
modalClassName:
18+
'relative mx-auto mt-[10vh] scrollbar-none max-w-full overflow-auto px-2 lg:max-w-[65rem] lg:p-0',
19+
}
20+
21+
if (href.startsWith('/notes/')) {
22+
requestAnimationFrame(async () => {
23+
const NotePreview = await import('./NotePreview').then(
24+
(module) => module.NotePreview,
25+
)
26+
present({
27+
...basePresentProps,
28+
CustomModalComponent: () => (
29+
<PeekModal to={href}>
30+
<NotePreview noteId={parseInt(href.split('/').pop()!)} />
31+
</PeekModal>
32+
),
33+
content: () => null,
34+
})
35+
})
36+
37+
return true
38+
} else if (href.startsWith('/posts/')) {
39+
requestAnimationFrame(async () => {
40+
const PostPreview = await import('./PostPreview').then(
41+
(module) => module.PostPreview,
42+
)
43+
const splitpath = href.split('/')
44+
const slug = splitpath.pop()!
45+
const category = splitpath.pop()!
46+
present({
47+
...basePresentProps,
48+
CustomModalComponent: () => (
49+
<PeekModal to={href}>
50+
<PostPreview category={category} slug={slug} />
51+
</PeekModal>
52+
),
53+
content: () => null,
54+
})
55+
})
56+
return true
57+
}
58+
59+
return false
60+
},
61+
[isMobile, present],
62+
)
63+
}

src/components/widgets/shared/ToastCard.tsx

+12-4
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,15 @@ export const ToastCard: FC<{
2323
toastProps?: ToastProps
2424
iconElement?: JSX.Element
2525
closeToast?: () => void
26+
onClick?: () => void
2627
}> = (props) => {
27-
const { iconElement, message, closeToast } = props
28+
const { iconElement, message, closeToast, onClick } = props
2829
const id = useId()
30+
31+
const MotionTag = onClick ? m.button : m.div
32+
2933
return (
30-
<m.div
34+
<MotionTag
3135
layoutId={id}
3236
layout="position"
3337
className={clsx(
@@ -39,17 +43,21 @@ export const ToastCard: FC<{
3943
'flex items-center',
4044
'select-none',
4145
)}
46+
onClick={onClick}
4247
>
4348
{iconElement ?? typeMap[props.toastProps?.type ?? 'default']}
4449
<span>{message}</span>
4550

4651
<MotionButtonBase
4752
aria-label="Close toast"
4853
className="absolute bottom-0 right-3 top-0 flex items-center text-sm text-base-content/40 duration-200 hover:text-base-content/80"
49-
onClick={closeToast}
54+
onClick={(e) => {
55+
e.stopPropagation()
56+
closeToast?.()
57+
}}
5058
>
5159
<i className="icon-[mingcute--close-fill] p-2" />
5260
</MotionButtonBase>
53-
</m.div>
61+
</MotionTag>
5462
)
5563
}

src/lib/route-builder.ts

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export const enum Routes {
2323

2424
Says = '/says',
2525
Friends = '/friends',
26+
Thinking = '/thinking',
2627

2728
PageDeletd = '/common/deleted',
2829
}

src/lib/toast.ts

+15-11
Original file line numberDiff line numberDiff line change
@@ -24,30 +24,34 @@ interface ToastCustom {
2424
): Id
2525
}
2626

27+
interface CustomToastOptions {
28+
iconElement?: JSX.Element
29+
onClick?: () => void
30+
}
2731
interface ToastCustom {
28-
success(message: string, options?: ToastOptions): Id
29-
info(message: string, options?: ToastOptions): Id
30-
warn(message: string, options?: ToastOptions): Id
31-
error(message: string, options?: ToastOptions): Id
32+
success(message: string, options?: ToastOptions & CustomToastOptions): Id
33+
info(message: string, options?: ToastOptions & CustomToastOptions): Id
34+
warn(message: string, options?: ToastOptions & CustomToastOptions): Id
35+
error(message: string, options?: ToastOptions & CustomToastOptions): Id
3236
}
3337

3438
// @ts-ignore
3539
export const toast: ToastCustom = (
3640
message: string,
3741
type?: TypeOptions,
38-
options?: ToastOptions & {
39-
iconElement?: JSX.Element
40-
},
42+
options?: ToastOptions & CustomToastOptions,
4143
) => {
42-
const { iconElement, ...rest } = options || {}
43-
return Toast(createElement(ToastCard, { message, iconElement }), {
44+
const { iconElement, onClick, ...rest } = options || {}
45+
return Toast(createElement(ToastCard, { message, iconElement, onClick }), {
4446
type,
4547
...baseConfig,
4648
...rest,
4749
})
4850
}
4951
;['success', 'info', 'warn', 'error'].forEach((type) => {
5052
// @ts-ignore
51-
toast[type] = (message: string, options?: ToastOptions) =>
52-
toast(message, type as TypeOptions, options)
53+
toast[type] = (
54+
message: string,
55+
options?: ToastOptions & CustomToastOptions,
56+
) => toast(message, type as TypeOptions, options)
5357
})

src/providers/root/index.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { LazyMotion } from 'framer-motion'
66
import { ThemeProvider } from 'next-themes'
77
import type { PropsWithChildren } from 'react'
88

9+
import { PeekPortal } from '~/components/widgets/peek/PeekPortal'
10+
911
import { ProviderComposer } from '../../components/common/ProviderComposer'
1012
import { AccentColorProvider } from './accent-color-provider'
1113
import { DebugProvider } from './debug-provider'
@@ -31,6 +33,7 @@ export function Providers({ children }: PropsWithChildren) {
3133
<>
3234
<ProviderComposer contexts={contexts}>
3335
{children}
36+
<PeekPortal />
3437
<SocketContainer />
3538
<ModalStackProvider key="modalStackProvider" />
3639
<EventProvider key="viewportProvider" />

src/socket/handler.ts

+55-1
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,25 @@
11
import { queryClient } from '~/providers/root/react-query-provider'
2+
import React from 'react'
23
import { produce } from 'immer'
34
import type {
45
NoteModel,
56
PaginateResult,
67
PostModel,
8+
RecentlyModel,
79
SayModel,
810
} from '@mx-space/api-client'
911
import type { InfiniteData } from '@tanstack/react-query'
1012
import type { AppRouterInstance } from 'next/dist/shared/lib/app-router-context'
1113

1214
import { sayQueryKey } from '~/app/says/query'
15+
import { QUERY_KEY as ThinkingQueryKey } from '~/app/thinking/constants'
1316
import { setOnlineCount } from '~/atoms'
1417
import { setActivityMediaInfo, setActivityProcessName } from '~/atoms/activity'
18+
import {
19+
FaSolidFeatherAlt,
20+
IcTwotoneSignpost,
21+
MdiLightbulbOn20,
22+
} from '~/components/icons/menu-collection'
1523
import { isDev } from '~/lib/env'
1624
import { routeBuilder, Routes } from '~/lib/route-builder'
1725
import { toast } from '~/lib/toast'
@@ -95,7 +103,53 @@ export const eventHandler = (
95103
break
96104
}
97105

98-
// TODO create event
106+
case EventTypes.NOTE_CREATE: {
107+
const { title, nid } = data as NoteModel
108+
109+
toast.success('有新的内容发布了:' + `「${title}」`, {
110+
onClick: () => {
111+
window.peek(`/notes/${nid}`)
112+
},
113+
iconElement: React.createElement(FaSolidFeatherAlt),
114+
autoClose: false,
115+
})
116+
117+
break
118+
}
119+
120+
case EventTypes.POST_CREATE: {
121+
const { title, category, slug } = data as PostModel
122+
toast.success('有新的内容发布了:' + `「${title}」`, {
123+
onClick: () => {
124+
window.peek(`/posts/${category.slug}/${slug}`)
125+
},
126+
iconElement: React.createElement(IcTwotoneSignpost),
127+
})
128+
129+
break
130+
}
131+
132+
case EventTypes.RECENTLY_CREATE: {
133+
if (location.pathname === routeBuilder(Routes.Thinking, {})) {
134+
queryClient.setQueryData<InfiniteData<RecentlyModel[]>>(
135+
ThinkingQueryKey,
136+
(prev) => {
137+
return produce(prev, (draft) => {
138+
draft?.pages[0].unshift(data as RecentlyModel)
139+
})
140+
},
141+
)
142+
} else {
143+
toast.success(`写下一点小思考:\n${(data as RecentlyModel).content}`, {
144+
autoClose: 10000,
145+
iconElement: React.createElement(MdiLightbulbOn20),
146+
onClick: () => {
147+
router.push(routeBuilder(Routes.Thinking, {}))
148+
},
149+
})
150+
}
151+
break
152+
}
99153

100154
case EventTypes.SAY_CREATE: {
101155
if (location.pathname === routeBuilder(Routes.Says, {})) {

0 commit comments

Comments
 (0)