terry-staging #5

Merged
terry merged 13 commits from terry-staging into main 2026-05-28 08:56:30 +00:00
18 changed files with 136 additions and 164 deletions
Showing only changes of commit 28b0ef3f9a - Show all commits

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 357 KiB

After

Width:  |  Height:  |  Size: 357 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 343 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 646 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 646 KiB

After

Width:  |  Height:  |  Size: 646 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 445 KiB

After

Width:  |  Height:  |  Size: 445 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 291 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 381 KiB

After

Width:  |  Height:  |  Size: 381 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 489 KiB

After

Width:  |  Height:  |  Size: 489 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 406 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 407 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 414 KiB

After

Width:  |  Height:  |  Size: 414 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 379 KiB

After

Width:  |  Height:  |  Size: 379 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 504 KiB

After

Width:  |  Height:  |  Size: 504 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 405 KiB

After

Width:  |  Height:  |  Size: 405 KiB

View File

@@ -4,6 +4,7 @@ import type { Resource } from "../api";
import { assetUrl } from "../api"; import { assetUrl } from "../api";
import { useI18n } from "../i18n"; import { useI18n } from "../i18n";
import { useMemo, useState } from "react"; import { useMemo, useState } from "react";
import { cleanCategoryDisplayName } from "../utils/categoryDisplay";
import { formatDateYmd } from "../utils/format"; import { formatDateYmd } from "../utils/format";
import { officialRecommendationCoverFallbacks } from "./FigmaBanner"; import { officialRecommendationCoverFallbacks } from "./FigmaBanner";
import { import {
@@ -46,12 +47,10 @@ export function RecommendedCard({
} }
return assetUrl(original); return assetUrl(original);
}, [figmaCover, r.coverImage, r.previewUrl, useFigmaDesign]); }, [figmaCover, r.coverImage, r.previewUrl, useFigmaDesign]);
const displayTitle = useFigmaDesign const displayTitle = r.title;
? "ARK 2026「共识加速计划」 🚀 邀请王霸榜 · 重磅回归" const displayCategoryName = cleanCategoryDisplayName(r.categoryName);
: r.title; const dateStr = formatDateYmd(r.updatedAt);
const displayCategoryName = useFigmaDesign ? "项目资料" : r.categoryName; const dateTime = r.updatedAt;
const dateStr = useFigmaDesign ? "2026-04-10" : formatDateYmd(r.updatedAt);
const dateTime = useFigmaDesign ? "2026-04-10" : r.updatedAt;
const dl = const dl =
r.isDownloadable && (r.fileUrl || r.previewUrl) r.isDownloadable && (r.fileUrl || r.previewUrl)
@@ -68,7 +67,11 @@ export function RecommendedCard({
<img <img
src={cover} src={cover}
alt="" alt=""
className="h-full w-full object-cover transition duration-300 group-hover:scale-[1.02]" className={`h-full w-full object-cover transition duration-300 ${
useFigmaDesign
? "origin-top scale-[1.08] group-hover:scale-[1.1]"
: "group-hover:scale-[1.02]"
}`}
loading="lazy" loading="lazy"
/> />
) : ( ) : (

View File

@@ -13,36 +13,35 @@ import { SectionHeader } from "../../components/SectionHeader";
import { MessageBubble } from "../../components/messageStream/MessageBubble"; import { MessageBubble } from "../../components/messageStream/MessageBubble";
import { langQuery, useI18n } from "../../i18n"; import { langQuery, useI18n } from "../../i18n";
import { sourceLanguageQuery } from "../../i18nLanguages"; import { sourceLanguageQuery } from "../../i18nLanguages";
import { categoryCardLines } from "../../utils/categoryDisplay"; import { cleanCategoryDisplayName } from "../../utils/categoryDisplay";
import { import {
postToResource, postToResource,
type PostBackedResource, type PostBackedResource,
} from "../../utils/postResourceAdapter"; } from "../../utils/postResourceAdapter";
import type { Post } from "../../types/post"; import type { Post } from "../../types/post";
const FIGMA_CATEGORY_LABELS: Record<string, string> = { const FIGMA_CATEGORY_ORDER = [
"project-ppt": "项目资料", "project-ppt",
"daily-class": "每日课堂", "daily-class",
"official-announcement": "官方公告", "official-announcement",
"academy-materials": "学堂教育", "academy-materials",
"global-evangelism": "全球布道", "global-evangelism",
"daily-poster": "每日海报", "daily-poster",
"community-tweets": "社群动向", "community-tweets",
"video-hub": "视频汇总", "video-hub",
"subsidy-policy": "补贴政策", "subsidy-policy",
}; "how-to",
"official-assets",
const FIGMA_CATEGORY_ORDER = Object.keys(FIGMA_CATEGORY_LABELS); "media-coverage",
"academy-video",
"general",
];
function figmaCategoryRank(category: Category): number { function figmaCategoryRank(category: Category): number {
const index = FIGMA_CATEGORY_ORDER.indexOf(category.slug); const index = FIGMA_CATEGORY_ORDER.indexOf(category.slug);
return index === -1 ? FIGMA_CATEGORY_ORDER.length : index; return index === -1 ? FIGMA_CATEGORY_ORDER.length : index;
} }
function figmaCategoryName(category: Category): string {
return FIGMA_CATEGORY_LABELS[category.slug] ?? category.name;
}
export function Home() { export function Home() {
const { t, lang } = useI18n(); const { t, lang } = useI18n();
const [cats, setCats] = useState<Category[]>([]); const [cats, setCats] = useState<Category[]>([]);
@@ -199,7 +198,7 @@ export function Home() {
<Link <Link
key={c.id} key={c.id}
to={`/category/${c.slug}`} to={`/category/${c.slug}`}
className="group flex h-[88px] min-w-0 flex-col items-center justify-center gap-2 rounded-xl bg-[#1D1E23] px-4 py-3 text-center transition hover:bg-[#252630] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ark-gold/80 focus-visible:ring-offset-2 focus-visible:ring-offset-ark-bg" className="group flex h-[88px] min-w-0 flex-col items-center justify-center gap-2 rounded-xl border border-[#27292E] bg-[#1D1E23] px-4 py-3 text-center transition hover:bg-[#252630] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ark-gold/80 focus-visible:ring-offset-2 focus-visible:ring-offset-ark-bg"
> >
<CategoryIcon <CategoryIcon
iconKey={c.iconKey} iconKey={c.iconKey}
@@ -207,7 +206,7 @@ export function Home() {
className="h-9 w-9 shrink-0 text-ark-gold" className="h-9 w-9 shrink-0 text-ark-gold"
/> />
<div className="w-full truncate text-[13px] font-medium leading-[19.5px] text-white"> <div className="w-full truncate text-[13px] font-medium leading-[19.5px] text-white">
{figmaCategoryName(c)} {cleanCategoryDisplayName(c.name)}
</div> </div>
</Link> </Link>
))} ))}
@@ -247,32 +246,22 @@ export function Home() {
</div> </div>
<div className="mt-7 hidden grid-cols-3 gap-3 min-[440px]:gap-3.5 md:grid md:grid-cols-5 md:gap-3 lg:grid-cols-6 xl:grid-cols-7 xl:gap-4"> <div className="mt-7 hidden grid-cols-3 gap-3 min-[440px]:gap-3.5 md:grid md:grid-cols-5 md:gap-3 lg:grid-cols-6 xl:grid-cols-7 xl:gap-4">
{figmaOrderedCategories.map((c) => { {figmaOrderedCategories.map((c) => (
const { line1, line2 } = categoryCardLines(figmaCategoryName(c));
return (
<Link <Link
key={c.id} key={c.id}
to={`/category/${c.slug}`} to={`/category/${c.slug}`}
className="group flex min-h-[111px] min-w-0 flex-col items-center justify-center gap-3 rounded-xl border border-ark-line bg-ark-panel px-2.5 py-4 text-center transition hover:border-ark-gold/55 hover:shadow-[0_0_0_1px_rgba(238,183,38,0.12)] md:min-h-24 md:flex-row md:justify-start md:gap-4 md:px-5 md:text-left" className="group flex h-[88px] min-w-0 flex-col items-center justify-center gap-2 rounded-xl border border-[#27292E] bg-[#1D1E23] px-4 py-3 text-center transition hover:border-ark-gold/55 hover:bg-[#252630] hover:shadow-[0_0_0_1px_rgba(238,183,38,0.12)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ark-gold/80 focus-visible:ring-offset-2 focus-visible:ring-offset-ark-bg"
> >
<CategoryIcon <CategoryIcon
iconKey={c.iconKey} iconKey={c.iconKey}
categorySlug={c.slug} categorySlug={c.slug}
className="h-9 w-9 shrink-0 text-ark-gold md:h-9 md:w-9" className="h-9 w-9 shrink-0 text-ark-gold"
/> />
<div className="min-w-0"> <div className="w-full text-center text-[13px] font-medium leading-[19.5px] text-white line-clamp-2">
<div className="text-[15px] font-bold leading-snug text-white line-clamp-2 md:text-sm"> {cleanCategoryDisplayName(c.name)}
{line1}
</div>
{line2 ? (
<div className="mt-0.5 text-[15px] font-bold leading-snug text-white line-clamp-2 md:text-sm">
{line2}
</div>
) : null}
</div> </div>
</Link> </Link>
); ))}
})}
</div> </div>
</section> </section>

View File

@@ -1,5 +1,13 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { categoryCardLines } from "./categoryDisplay"; import { categoryCardLines, cleanCategoryDisplayName } from "./categoryDisplay";
describe("cleanCategoryDisplayName", () => {
it("removes parenthetical suffixes used as backend qualifiers", () => {
expect(cleanCategoryDisplayName("官方公告(繁中)")).toBe("官方公告");
expect(cleanCategoryDisplayName("Tutorials (EN)")).toBe("Tutorials");
expect(cleanCategoryDisplayName("补贴政策\r")).toBe("补贴政策");
});
});
describe("categoryCardLines", () => { describe("categoryCardLines", () => {
it("splits Chinese and ASCII parenthetical subtitles", () => { it("splits Chinese and ASCII parenthetical subtitles", () => {

View File

@@ -1,3 +1,10 @@
export function cleanCategoryDisplayName(name: string): string {
return name
.replace(/\s*[(][^()]*[)]\s*/g, " ")
.replace(/\s+/g, " ")
.trim();
}
/** Split display name into title + parenthetical subtitle (matches design cards). */ /** Split display name into title + parenthetical subtitle (matches design cards). */
export function categoryCardLines( export function categoryCardLines(
name: string, name: string,