Skip to content

Commit 456b9d5

Browse files
committed
feat: post detail page
Signed-off-by: Innei <[email protected]>
1 parent 90877db commit 456b9d5

30 files changed

+376
-95
lines changed

src/app.config.ts

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ export const appConfig = {
44
process.env.NODE_ENV === 'production'
55
? 'https://innei.ren'
66
: 'http://localhost:2323',
7+
8+
favicon: 'https://cdn.innei.ren/github_innei.svg',
79
},
810

911
module: {

src/app/layout.tsx

+8-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { headers } from 'next/dist/client/components/headers'
77

88
import { ClerkProvider } from '@clerk/nextjs'
99

10+
import { appConfig } from '~/app.config'
1011
import { Root } from '~/components/layout/root/Root'
1112
import { REQUEST_GEO, REQUEST_PATHNAME } from '~/constants/system'
1213
import { defineMetadata } from '~/lib/define-metadata'
@@ -30,6 +31,8 @@ export const generateMetadata = defineMetadata(async (_, getData) => {
3031
},
3132
description: seo.description,
3233
keywords: seo.keywords?.join(',') || '',
34+
icons: [appConfig.site.favicon],
35+
3336
themeColor: [
3437
{ media: '(prefers-color-scheme: dark)', color: '#000212' },
3538
{ media: '(prefers-color-scheme: light)', color: '#fafafa' },
@@ -56,6 +59,10 @@ export const generateMetadata = defineMetadata(async (_, getData) => {
5659
locale: 'zh_CN',
5760
type: 'website',
5861
url: url.webUrl,
62+
images: {
63+
url: user.avatar,
64+
username: user.name,
65+
},
5966
},
6067
twitter: {
6168
creator: `@${user.username}`,
@@ -108,7 +115,7 @@ export default async function RootLayout(props: Props) {
108115
<ClerkProvider>
109116
<html lang="zh-CN" className="noise" suppressHydrationWarning>
110117
<body
111-
className={`${sansFont.variable} ${serifFont.variable} m-0 h-full p-0 font-sans antialiased`}
118+
className={`${sansFont.variable} ${serifFont.variable} m-0 h-full p-0 font-sans`}
112119
>
113120
<Providers>
114121
<Hydrate state={dehydratedState}>

src/app/notes/[id]/page.tsx

+8-7
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import type { MarkdownToJSX } from '~/components/ui/markdown'
1313
import { ClientOnly } from '~/components/common/ClientOnly'
1414
import { PageDataHolder } from '~/components/common/PageHolder'
1515
import { MdiClockOutline } from '~/components/icons/clock'
16-
import { useSetHeaderMetaInfo } from '~/components/layout/header/internal/hooks'
16+
import { useSetHeaderMetaInfo } from '~/components/layout/header/hooks'
1717
import { FloatPopover } from '~/components/ui/float-popover'
1818
import { Loading } from '~/components/ui/loading'
1919
import { Markdown } from '~/components/ui/markdown'
@@ -22,7 +22,8 @@ import { NoteTopic } from '~/components/widgets/note/NoteTopic'
2222
import { SubscribeBell } from '~/components/widgets/subscribe/SubscribeBell'
2323
import { TocAside, TocAutoScroll } from '~/components/widgets/toc'
2424
import { XLogInfoForNote, XLogSummaryForNote } from '~/components/widgets/xlog'
25-
import { useNoteByNidQuery, useNoteData } from '~/hooks/data/use-note'
25+
import { useCurrentNoteData, useNoteByNidQuery } from '~/hooks/data/use-note'
26+
import { noopArr } from '~/lib/noop'
2627
import { MarkdownImageRecordProvider } from '~/providers/article/MarkdownImageRecordProvider'
2728
import {
2829
CurrentNoteIdProvider,
@@ -39,8 +40,6 @@ import { NoteHideIfSecret } from '../../../components/widgets/note/NoteHideIfSec
3940
import { NoteMetaBar } from '../../../components/widgets/note/NoteMetaBar'
4041
import styles from './page.module.css'
4142

42-
const noopArr = [] as Image[]
43-
4443
const PageImpl = () => {
4544
const { id } = useParams() as { id: string }
4645
const { data } = useNoteByNidQuery(id)
@@ -108,7 +107,9 @@ const NotePage = memo(({ note }: { note: NoteModel }) => {
108107
<NoteHideIfSecret>
109108
<XLogSummaryForNote />
110109
<WrappedElementProvider>
111-
<MarkdownImageRecordProvider images={note.images || noopArr}>
110+
<MarkdownImageRecordProvider
111+
images={note.images || (noopArr as Image[])}
112+
>
112113
<Markdown
113114
as="main"
114115
renderers={MarkdownRenderers}
@@ -139,7 +140,7 @@ const NotePage = memo(({ note }: { note: NoteModel }) => {
139140
})
140141

141142
const NoteTitle = () => {
142-
const note = useNoteData()
143+
const note = useCurrentNoteData()
143144
if (!note) return null
144145
const title = note.title
145146
return (
@@ -150,7 +151,7 @@ const NoteTitle = () => {
150151
}
151152

152153
const NoteDateMeta = () => {
153-
const note = useNoteData()
154+
const note = useCurrentNoteData()
154155
if (!note) return null
155156

156157
const dateFormat = dayjs(note.created)

src/app/posts/(post-detail)/Container.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export const Container: Component = ({ children }) => {
22
return (
3-
<div className="container m-auto mt-12 max-w-7xl px-2 md:p-0">
3+
<div className="container m-auto mt-[120px] max-w-7xl px-2 md:p-0">
44
{children}
55
</div>
66
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { Loading } from '~/components/ui/loading'
2+
3+
export default () => <Loading useDefaultLoadingText />
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,88 @@
11
'use client'
2+
3+
import { useEffect } from 'react'
4+
import { Balancer } from 'react-wrap-balancer'
5+
import type { Image, PostModel } from '@mx-space/api-client'
6+
7+
import { ReadIndicator } from '~/components/common/ReadIndicator'
8+
import { useSetHeaderMetaInfo } from '~/components/layout/header/hooks'
9+
import { Loading } from '~/components/ui/loading'
10+
import { Markdown } from '~/components/ui/markdown'
11+
import { PostActionAside } from '~/components/widgets/post/PostActionAside'
12+
import { PostMetaBar } from '~/components/widgets/post/PostMetaBar'
13+
import { SubscribeBell } from '~/components/widgets/subscribe/SubscribeBell'
14+
import { TocAside, TocAutoScroll } from '~/components/widgets/toc'
15+
import { XLogInfoForPost, XLogSummaryForPost } from '~/components/widgets/xlog'
16+
import { useCurrentPostData } from '~/hooks/data/use-post'
17+
import { noopArr } from '~/lib/noop'
18+
import { MarkdownImageRecordProvider } from '~/providers/article/MarkdownImageRecordProvider'
19+
import { LayoutRightSidePortal } from '~/providers/shared/LayoutRightSideProvider'
20+
import { WrappedElementProvider } from '~/providers/shared/WrappedElementProvider'
21+
22+
const useHeaderMeta = (data?: PostModel | null) => {
23+
const setHeader = useSetHeaderMetaInfo()
24+
useEffect(() => {
25+
if (!data) return
26+
setHeader({
27+
title: data.title,
28+
description:
29+
data.category.name +
30+
(data.tags.length > 0 ? ` / ${data.tags.join(', ')}` : ''),
31+
slug: `${data.category.slug}/${data.slug}`,
32+
})
33+
}, [
34+
data?.title,
35+
data?.category.name,
36+
data?.tags.length,
37+
data?.category.slug,
38+
data?.slug,
39+
])
40+
}
241
export default () => {
3-
return <div>Test</div>
42+
const data = useCurrentPostData()
43+
44+
useHeaderMeta(data)
45+
if (!data) {
46+
return <Loading useDefaultLoadingText />
47+
}
48+
49+
return (
50+
<div>
51+
<article className="prose">
52+
<header className="mb-8">
53+
<h1>
54+
<Balancer>{data.title}</Balancer>
55+
</h1>
56+
<div>
57+
<PostMetaBar data={data} />
58+
</div>
59+
60+
<XLogSummaryForPost />
61+
</header>
62+
<WrappedElementProvider>
63+
<MarkdownImageRecordProvider
64+
images={data.images || (noopArr as Image[])}
65+
>
66+
<main className="relative">
67+
<Markdown value={data.text} />
68+
</main>
69+
</MarkdownImageRecordProvider>
70+
71+
<LayoutRightSidePortal>
72+
<TocAside
73+
className="sticky top-[120px] ml-4 mt-[120px]"
74+
treeClassName="max-h-[calc(100vh-6rem-4.5rem-300px)] h-[calc(100vh-6rem-4.5rem-300px)] min-h-[120px] relative"
75+
accessory={ReadIndicator}
76+
>
77+
<PostActionAside className="translate-y-full" />
78+
</TocAside>
79+
<TocAutoScroll />
80+
</LayoutRightSidePortal>
81+
</WrappedElementProvider>
82+
</article>
83+
84+
<SubscribeBell defaultType="post_c" />
85+
<XLogInfoForPost />
86+
</div>
87+
)
488
}

src/components/layout/header/hooks.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
import { useEffect } from 'react'
44

5-
import { useSetHeaderShouldShowBg } from '~/components/layout/header/internal/hooks'
5+
import {
6+
useSetHeaderMetaInfo,
7+
useSetHeaderShouldShowBg,
8+
} from '~/components/layout/header/internal/hooks'
69

710
export const useHideHeaderBgInRoute = () => {
811
const setter = useSetHeaderShouldShowBg()
@@ -18,3 +21,5 @@ export const HeaderHideBg = () => {
1821
useHideHeaderBgInRoute()
1922
return null
2023
}
24+
25+
export { useSetHeaderMetaInfo }

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

+6-3
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,12 @@ export const HeaderMeta = () => {
4848
</h2>
4949
</div>
5050

51-
<div className="hidden min-w-0 flex-shrink-0 flex-col text-right leading-5 lg:flex">
52-
<span className="whitespace-pre text-base-content"> {slug}</span>
53-
<span className="font-medium text-gray-400 dark:text-gray-600">
51+
<div className="hidden min-w-0 flex-shrink flex-col text-right leading-5 lg:flex">
52+
<span className="min-w-0 truncate whitespace-pre text-gray-600/60 dark:text-gray-300/60">
53+
{' '}
54+
{slug}
55+
</span>
56+
<span className="font-medium text-gray-600 dark:text-gray-300">
5457
{seoTitle}
5558
</span>
5659
</div>

src/components/ui/code-highlighter/CodeHighlighter.module.css

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
position: relative;
33

44
pre > code {
5-
background-color: var(--light-bg) !important;
5+
@apply block bg-base-100 font-mono text-[14px] font-medium;
66
}
77
}
88

src/components/ui/image/ZoomedImage.tsx

+8-5
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,7 @@ export const ImageLazy: Component<TImageProps & BaseImageProps> = ({
5959
const [zoomer_] = useState(() => {
6060
if (isServer) return null
6161
if (zoomer) return zoomer
62-
const zoom = mediumZoom(undefined, {
63-
background: 'var(--sbg)',
64-
})
62+
const zoom = mediumZoom(undefined)
6563
zoomer = zoom
6664
return zoom
6765
}) as [Zoom]
@@ -180,8 +178,8 @@ const Placeholder: FC<
180178

181179
if (containerWidth <= 0) return
182180
const { height: scaleHeight, width: scaleWidth } = calculateDimensions({
183-
width: nextHeight,
184-
height: nextWidth,
181+
width: nextWidth,
182+
height: nextHeight,
185183
max: {
186184
width: containerWidth,
187185
height: Infinity,
@@ -199,6 +197,11 @@ const Placeholder: FC<
199197
return (
200198
<span
201199
className={styles.base}
200+
data-width={scaledSize.scaleWidth}
201+
data-height={scaledSize.scaleHeight}
202+
data-from-record-height={imageMeta?.height}
203+
data-from-record-width={imageMeta?.width}
204+
data-src={src}
202205
style={{
203206
height: scaledSize.scaleHeight,
204207
width: scaledSize.scaleWidth,

src/components/ui/markdown/markdown.module.css

+4
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,8 @@
8585
input[type='checkbox']:read-only {
8686
@apply cursor-not-allowed;
8787
}
88+
89+
code {
90+
@apply font-mono;
91+
}
8892
}

src/components/ui/markdown/renderers/link.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ const ExtendIcon = () => (
2121
className="inline align-middle leading-normal"
2222
>
2323
<path
24-
fill="var(--shizuku-text-color)"
24+
fill="currentColor"
2525
d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"
2626
/>
2727
<polygon
28-
fill="var(--shizuku-text-color)"
28+
fill="currentColor"
2929
points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"
3030
/>
3131
</svg>
@@ -84,7 +84,7 @@ export const MLink: FC<{
8484
{props.children}
8585
</a>
8686

87-
{ExtendIcon}
87+
<ExtendIcon />
8888
</>
8989
),
9090
[handleRedirect, props.children, props.href, props.title],

src/components/widgets/note/NoteActionAside.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type { NoteWrappedPayload } from '@mx-space/api-client'
77

88
import { MotionButtonBase } from '~/components/ui/button'
99
import { useIsClient } from '~/hooks/common/use-is-client'
10-
import { useNoteData } from '~/hooks/data/use-note'
10+
import { useCurrentNoteData } from '~/hooks/data/use-note'
1111
import { routeBuilder, Routes } from '~/lib/route-builder'
1212
import { toast } from '~/lib/toast'
1313
import { urlBuilder } from '~/lib/url-builder'
@@ -35,7 +35,7 @@ export const NoteActionAside: Component = ({ className }) => {
3535
}
3636

3737
const LikeButton = () => {
38-
const note = useNoteData()
38+
const note = useCurrentNoteData()
3939

4040
const queryClient = useQueryClient()
4141
const control = useAnimationControls()
@@ -112,7 +112,7 @@ const LikeButton = () => {
112112
const ShareButton = () => {
113113
const hasShare = 'share' in navigator
114114
const isClient = useIsClient()
115-
const note = useNoteData()
115+
const note = useCurrentNoteData()
116116
const aggregation = useAggregationData()
117117
if (!isClient) return null
118118
if (!note) return null

src/components/widgets/note/NoteHideIfSecret.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import { useEffect, useMemo } from 'react'
66
import dayjs from 'dayjs'
77

88
import { useIsLogged } from '~/atoms/owner'
9-
import { useNoteData } from '~/hooks/data/use-note'
9+
import { useCurrentNoteData } from '~/hooks/data/use-note'
1010
import { toast } from '~/lib/toast'
1111

1212
export const NoteHideIfSecret: Component = ({ children }) => {
13-
const note = useNoteData()
13+
const note = useCurrentNoteData()
1414
const secretDate = useMemo(() => new Date(note?.secret!), [note?.secret])
1515
const isSecret = note?.secret
1616
? dayjs(note?.secret).isAfter(new Date())

src/components/widgets/note/NoteMetaBar.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ import type { ReactNode } from 'react'
77

88
import { CreativeCommonsIcon } from '~/components/icons/cc'
99
import { DividerVertical } from '~/components/ui/divider'
10-
import { useNoteData } from '~/hooks/data/use-note'
10+
import { useCurrentNoteData } from '~/hooks/data/use-note'
1111
import { mood2icon, weather2icon } from '~/lib/meta-icon'
1212

1313
const dividerVertical = <DividerVertical className="!mx-2 scale-y-50" />
1414
const dividerVerticalWithKey = () =>
1515
cloneElement(dividerVertical, { key: `divider-${Math.random()}` })
1616
const sectionBlockClassName = 'flex items-center space-x-1 flex-shrink-0'
1717
export const NoteMetaBar = () => {
18-
const note = useNoteData()
18+
const note = useCurrentNoteData()
1919
if (!note) return null
2020

2121
const children = [] as ReactNode[]

src/components/widgets/note/NoteTimeline.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ import Link from 'next/link'
77
import { tv } from 'tailwind-variants'
88

99
import { LeftToRightTransitionView } from '~/components/ui/transition/LeftToRightTransitionView'
10-
import { useNoteData } from '~/hooks/data/use-note'
10+
import { useCurrentNoteData } from '~/hooks/data/use-note'
1111
import { routeBuilder, Routes } from '~/lib/route-builder'
1212
import { clsxm } from '~/utils/helper'
1313
import { apiClient } from '~/utils/request'
1414

1515
export const NoteTimeline = () => {
16-
const note = useNoteData()
16+
const note = useCurrentNoteData()
1717
const noteId = note?.id
1818

1919
const { data: timelineData } = useQuery(

0 commit comments

Comments
 (0)