terry-staging #5
|
Before Width: | Height: | Size: 357 KiB After Width: | Height: | Size: 357 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 343 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 646 KiB |
|
Before Width: | Height: | Size: 646 KiB After Width: | Height: | Size: 646 KiB |
|
Before Width: | Height: | Size: 445 KiB After Width: | Height: | Size: 445 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 291 KiB |
|
Before Width: | Height: | Size: 381 KiB After Width: | Height: | Size: 381 KiB |
|
Before Width: | Height: | Size: 489 KiB After Width: | Height: | Size: 489 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 406 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 407 KiB |
|
Before Width: | Height: | Size: 414 KiB After Width: | Height: | Size: 414 KiB |
|
Before Width: | Height: | Size: 379 KiB After Width: | Height: | Size: 379 KiB |
|
Before Width: | Height: | Size: 504 KiB After Width: | Height: | Size: 504 KiB |
|
Before Width: | Height: | Size: 405 KiB After Width: | Height: | Size: 405 KiB |
@@ -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"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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", () => {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||