Skip to content

Commit c6508f4

Browse files
weibohan07wxh06
authored andcommitted
feat(web): improve UI
1 parent 12cb4a7 commit c6508f4

File tree

88 files changed

+7084
-1697
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

88 files changed

+7084
-1697
lines changed

apps/web/app/(article)/a/[id]/@metaCard/[snapshot]/page.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import ArticleMetaRow from "../../article-meta-row";
21
import { getArticleData } from "../../data-cache";
2+
import ArticleMetaRow from "../../meta-row";
33

44
export default async function Page({
55
params,
@@ -11,7 +11,6 @@ export default async function Page({
1111
}) {
1212
const { id, snapshot: snapshotStr } = await params;
1313
const snapshot = new Date(parseInt(snapshotStr, 36));
14-
console.log("snapshot", snapshot);
1514

1615
const article = await getArticleData(id, snapshot);
1716

apps/web/app/(article)/a/[id]/@metaCard/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import ArticleMetaRow from "../article-meta-row";
21
import { getArticleData } from "../data-cache";
2+
import ArticleMetaRow from "../meta-row";
33

44
export default async function Page({
55
params,

apps/web/app/(article)/a/[id]/@metaRow/[snapshot]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import ArticleMetaRow from "../../article-meta-row";
21
import { getArticleData } from "../../data-cache";
2+
import ArticleMetaRow from "../../meta-row";
33

44
export default async function Page({
55
params,

apps/web/app/(article)/a/[id]/@metaRow/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import ArticleMetaRow from "../article-meta-row";
21
import { getArticleData } from "../data-cache";
2+
import ArticleMetaRow from "../meta-row";
33

44
export default async function Page({
55
params,

apps/web/app/(article)/a/[id]/@operationPanel/[snapshot]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import ArticleOperationPanel from "../../article-operation-panel";
21
import { getArticleData } from "../../data-cache";
2+
import ArticleOperationPanel from "../../operation-panel";
33

44
export default async function Page({
55
params,

apps/web/app/(article)/a/[id]/@operationPanel/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import ArticleOperationPanel from "../article-operation-panel";
21
import { getArticleData } from "../data-cache";
2+
import ArticleOperationPanel from "../operation-panel";
33

44
export default async function Page({
55
params,

apps/web/app/(article)/a/[id]/@replies/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { getArticleBasicInfo } from "@luogu-discussion-archive/query";
22

3-
import ArticleComments from "../article-comments";
3+
import ArticleComments from "../comments";
44

55
export default async function Layout({
66
params,

apps/web/app/(article)/a/[id]/data-cache.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,13 @@ export const getArticleData = cache(async (lid: string, snapshot?: Date) => {
1717
lid,
1818
time: articleWithSnapshot.time,
1919
replyCount: articleWithSnapshot.replyCount,
20-
solutionFor: articleWithSnapshot.snapshots[0].solutionFor,
20+
solutionFor: articleWithSnapshot.snapshots[0].solutionFor
21+
? {
22+
...articleWithSnapshot.snapshots[0].solutionFor,
23+
difficulty:
24+
articleWithSnapshot.snapshots[0].solutionFor.difficulty ?? null,
25+
}
26+
: null,
2127
title: articleWithSnapshot.snapshots[0].title,
2228
content: articleWithSnapshot.snapshots[0].content,
2329
capturedAt: articleWithSnapshot.snapshots[0].capturedAt,

apps/web/app/(article)/a/[id]/layout.tsx

Lines changed: 57 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,26 @@ import { useParams } from "next/navigation";
66

77
import { cn } from "@/lib/utils";
88

9-
import { ArticleWaybackModal } from "./article-wayback-modal";
9+
import { ArticleWaybackModal } from "./wayback-modal";
1010

1111
type TocItem = {
1212
id: string;
1313
text: string;
1414
level: number;
1515
};
1616

17+
const DESKTOP_HEADING_OFFSET = 96;
18+
const MOBILE_HEADING_OFFSET = 72;
19+
20+
function getHeadingViewportOffset() {
21+
if (typeof window === "undefined") {
22+
return MOBILE_HEADING_OFFSET;
23+
}
24+
return window.matchMedia("(min-width: 1024px)").matches
25+
? DESKTOP_HEADING_OFFSET
26+
: MOBILE_HEADING_OFFSET;
27+
}
28+
1729
export default function Layout({
1830
titleRow,
1931
metaRow,
@@ -110,13 +122,37 @@ export default function Layout({
110122
const target = document.getElementById(headingId);
111123
if (!target) return;
112124

113-
const prefersDesktop = window.matchMedia("(min-width: 1024px)").matches;
114-
const offset = prefersDesktop ? 96 : 72;
125+
const offset = getHeadingViewportOffset();
115126
const top = target.getBoundingClientRect().top + window.scrollY - offset;
116127
window.scrollTo({ top, behavior: "smooth" });
117128
setIsMobileTocOpen(false);
118129
}, []);
119130

131+
React.useEffect(() => {
132+
if (typeof window === "undefined") return;
133+
const container = contentRef.current;
134+
if (!container) return;
135+
136+
const handleClick = (event: MouseEvent) => {
137+
const anchor = (event.target as HTMLElement | null)?.closest(
138+
".markdown-heading-anchor",
139+
) as HTMLAnchorElement | null;
140+
if (!anchor) return;
141+
const href = anchor.getAttribute("href") ?? "";
142+
if (!href.startsWith("#")) return;
143+
const headingId = href.slice(1);
144+
if (!headingId) return;
145+
event.preventDefault();
146+
scrollToHeading(headingId);
147+
const url = new URL(window.location.href);
148+
url.hash = headingId;
149+
window.history.replaceState(null, "", url);
150+
};
151+
152+
container.addEventListener("click", handleClick);
153+
return () => container.removeEventListener("click", handleClick);
154+
}, [scrollToHeading]);
155+
120156
React.useEffect(() => {
121157
if (typeof window === "undefined") return;
122158

@@ -254,6 +290,7 @@ export default function Layout({
254290

255291
const updateActiveHeading = () => {
256292
frameId = 0;
293+
const offset = getHeadingViewportOffset();
257294
if (!headingElements.length) {
258295
headingElements = resolveHeadingElements();
259296
if (!headingElements.length) {
@@ -264,7 +301,8 @@ export default function Layout({
264301

265302
let candidateId: string | null = headingElements[0]?.id ?? null;
266303
for (const element of headingElements) {
267-
if (element.getBoundingClientRect().top <= VIEWPORT_TOP_EPSILON) {
304+
const topDistance = element.getBoundingClientRect().top - offset;
305+
if (topDistance <= VIEWPORT_TOP_EPSILON) {
268306
candidateId = element.id;
269307
} else {
270308
break;
@@ -429,16 +467,11 @@ export default function Layout({
429467
)}
430468
>
431469
<div className="article-floating-toc-hitbox">
432-
<button
433-
type="button"
434-
aria-label="查看文章目录"
435-
className="article-floating-toc-button"
436-
>
437-
<ListTree className="h-5 w-5" />
438-
</button>
470+
<span className="article-floating-toc-button">
471+
<ListTree className="size-4" />
472+
</span>
439473
<div className="article-floating-toc-panel">
440474
<div className="article-toc-card">
441-
<div className="article-toc-card-header">内容目录</div>
442475
<TocNavigation
443476
items={tocItems}
444477
activeId={activeHeadingId}
@@ -456,9 +489,11 @@ export default function Layout({
456489
"article-grid grid gap-8",
457490
"lg:grid-cols-[minmax(0,8fr)_minmax(0,3.2fr)]",
458491
"xl:grid-cols-[minmax(0,8fr)_minmax(0,2.7fr)]",
459-
"2xl:grid-cols-[minmax(0,3fr)_minmax(0,8fr)_minmax(0,3fr)]",
492+
hasToc
493+
? "2xl:grid-cols-[minmax(0,2fr)_minmax(0,8fr)_minmax(0,3fr)]"
494+
: "2xl:grid-cols-[minmax(0,3fr)_minmax(0,8fr)_minmax(0,3fr)]",
460495
hasToc &&
461-
"3xl:grid-cols-[minmax(0,2.5fr)_minmax(0,2.4fr)_minmax(0,9fr)_minmax(0,3fr)]",
496+
"3xl:grid-cols-[minmax(0,2.5fr)_minmax(0,2fr)_minmax(0,9fr)_minmax(0,3fr)]",
462497
)}
463498
>
464499
<aside className="hidden 2xl:order-1 2xl:flex 2xl:flex-col 2xl:gap-4">
@@ -470,7 +505,6 @@ export default function Layout({
470505
className="article-toc-card sticky"
471506
style={tocStickyStyle}
472507
>
473-
<div className="article-toc-card-header">内容目录</div>
474508
<TocNavigation
475509
items={tocItems}
476510
activeId={activeHeadingId}
@@ -496,7 +530,6 @@ export default function Layout({
496530
className="article-toc-card sticky"
497531
style={tocStickyStyle}
498532
>
499-
<div className="article-toc-card-header">内容目录</div>
500533
<TocNavigation
501534
items={tocItems}
502535
activeId={activeHeadingId}
@@ -563,10 +596,11 @@ export default function Layout({
563596
isMetaPinned ? "opacity-100" : "opacity-0",
564597
)}
565598
>
566-
<div className="pointer-events-none absolute inset-0 rounded-2xl shadow-sm" />
567-
<div className="h-full overflow-hidden rounded-2xl border border-border bg-background/95">
568-
<div ref={floatingMetaRef} className="px-5 py-4">
599+
<div className="pointer-events-none absolute inset-0" />
600+
<div className="h-full overflow-hidden">
601+
<div ref={floatingMetaRef} className="pb-2.5">
569602
{metaCard}
603+
<hr className="mt-7" />
570604
</div>
571605
</div>
572606
</div>
@@ -597,9 +631,6 @@ export default function Layout({
597631
>
598632
<div className="mx-auto w-full max-w-sm rounded-3xl border bg-background p-5 shadow-xl">
599633
<div className="mb-4 flex items-center justify-between">
600-
<div className="text-sm font-semibold tracking-wide text-muted-foreground uppercase">
601-
内容目录
602-
</div>
603634
<button
604635
type="button"
605636
className="rounded-full border p-1 text-muted-foreground transition hover:text-foreground"
@@ -608,7 +639,7 @@ export default function Layout({
608639
<X className="h-4 w-4" />
609640
</button>
610641
</div>
611-
<div className="max-h-[60vh] overflow-y-auto pr-1">
642+
<div className="max-h-[80dvh] overflow-y-auto pr-1">
612643
<TocNavigation
613644
items={tocItems}
614645
activeId={activeHeadingId}
@@ -649,7 +680,9 @@ function TocNavigation({ items, activeId, onNavigate }: TocNavigationProps) {
649680
)}
650681
onClick={() => onNavigate(item.id)}
651682
>
652-
<span className="line-clamp-2 text-left">{item.text}</span>
683+
<span className="line-clamp-1 truncate text-left">
684+
{item.text}
685+
</span>
653686
</button>
654687
</li>
655688
);

0 commit comments

Comments
 (0)