Skip to content

Commit 53da26f

Browse files
committed
refactor(vitepress): modularize benchmark UI and migrate site config to TypeScript
- split VitePress site configuration into dedicated config and sidebar modules with locale-aware navigation entries - refactor benchmark pages by extracting reusable chart/card components and adding stronger TypeScript typing - improve contributor loading with safer data guards plus localStorage caching to reduce repeated GitHub API requests - simplify rainbow theme animation CSS and remove runtime style injection logic from theme entry - add Vue module declarations and update tsconfig include/path settings for better type coverage - align toolchain dependencies and lockfile updates (vite/vue/sass/types) for the new TS-oriented setup
1 parent 4c63954 commit 53da26f

13 files changed

Lines changed: 1882 additions & 1847 deletions
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { defineConfig } from "vitepress";
2-
import sidebar from "./sidebar.mjs";
2+
import sidebar from "./sidebar";
33

44
// https://vitepress.dev/reference/site-config
55
export default defineConfig({
Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
// import {DefaultTheme} from "vitepress";
2-
// import Sidebar = DefaultTheme.Sidebar;
3-
41
export default {
52
"/docs/": [
63
{

.vitepress/theme/components/Contributors.vue

Lines changed: 172 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,16 @@
22
import { VPTeamMembers } from "vitepress/theme";
33
import { ref, computed } from "vue";
44
5-
const props = defineProps({
6-
lang: {
7-
default: "en",
8-
type: String
5+
type LangString = "en" | "tr" | "de" | "pt" | "ru" | "zh";
6+
7+
const props = withDefaults(
8+
defineProps<{
9+
lang?: LangString;
10+
}>(),
11+
{
12+
lang: "en"
913
}
10-
});
11-
interface LangString {
12-
en: string;
13-
tr: string;
14-
de: string;
15-
pt: string;
16-
ru: string;
17-
zh: string;
18-
}
14+
);
1915
2016
interface Contributor {
2117
avatar: string;
@@ -24,64 +20,145 @@ interface Contributor {
2420
links: Array<{ icon: string; link: string }>;
2521
}
2622
27-
const members = ref<Contributor[]>([]);
28-
const websiteMembers = ref<(Contributor | null)[]>([]);
29-
const loaded = ref(false);
30-
const websiteLoaded = ref(false);
31-
32-
const titleCreator = (<LangString>{
33-
en: "Creator of Leaf",
34-
tr: "Leaf'in Yaratıcısı",
35-
de: "Schöpfer von Leaf",
36-
pt: "Criador do Leaf",
37-
ru: "Создатель Leaf",
38-
zh: "Leaf 的创建者"
39-
})[props.lang];
40-
const titleCoreTeam = (<LangString>{
41-
en: "Core team",
42-
tr: "Ana ekip",
43-
de: "Kernteam",
44-
pt: "Equipe Principal",
45-
ru: "Основная команда",
46-
zh: "核心团队"
47-
})[props.lang];
48-
const titleWebDev = (<LangString>{
49-
en: "Designer & Web Dev",
50-
tr: "Tasarımcı & Web Geliştirici",
51-
de: "Designer & Webentwickler",
52-
pt: "Designer & Desenvolvedor Web",
53-
ru: "Дизайнер и Веб-разработчик",
54-
zh: "设计师兼网页开发"
55-
})[props.lang];
56-
const titleSpecial = (<LangString>{
57-
en: "Special Supporter",
58-
tr: "Özel Destekçi",
59-
de: "Besonderer Unterstützer",
60-
pt: "Apoiante Especial",
61-
ru: "Особый поддерживающий",
62-
zh: "特别支持者"
63-
})[props.lang];
64-
65-
const rewrites = {
66-
"Dreeam-qwq": { title: titleCreator },
67-
HaHaWTH: { name: "Creeam (HaHaWTH)", title: titleCoreTeam },
68-
//"Taiyou06": { title: titleCoreTeam },
69-
hayanesuru: { title: titleCoreTeam },
70-
MartijnMuijsers: { title: titleCoreTeam },
71-
Pascalpex: { title: titleSpecial },
23+
interface GitHubContributor {
24+
login: string;
25+
avatar_url: string;
26+
html_url: string;
27+
type: string;
28+
}
29+
30+
type LocalizedText = Record<LangString, string>;
31+
type Rewrite = Partial<Omit<Contributor, "links">> & {
32+
links?: Contributor["links"];
33+
};
34+
type ContributorCache = {
35+
timestamp: number;
36+
data: GitHubContributor[];
37+
};
38+
39+
const CACHE_TTL_MS = 30 * 60 * 1000;
40+
const CACHE_KEY_PREFIX = "leaf-website:contributors:";
41+
42+
const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null;
43+
44+
const isGitHubContributor = (value: unknown): value is GitHubContributor => {
45+
if (!isRecord(value)) return false;
46+
47+
return (
48+
typeof value.login === "string" &&
49+
typeof value.avatar_url === "string" &&
50+
typeof value.html_url === "string" &&
51+
typeof value.type === "string"
52+
);
53+
};
54+
55+
const getUserContributors = (data: unknown): GitHubContributor[] => {
56+
if (!Array.isArray(data)) return [];
57+
return data.filter(isGitHubContributor).filter((m) => m.type === "User");
58+
};
59+
60+
const isContributorCache = (value: unknown): value is ContributorCache => {
61+
if (!isRecord(value)) return false;
62+
63+
return typeof value.timestamp === "number" && Array.isArray(value.data);
64+
};
65+
66+
const getCacheKey = (repo: string) => `${CACHE_KEY_PREFIX}${repo}`;
67+
68+
const readCachedContributors = (repo: string): GitHubContributor[] | null => {
69+
if (typeof window === "undefined") return null;
70+
71+
try {
72+
const raw = window.localStorage.getItem(getCacheKey(repo));
73+
if (!raw) return null;
74+
75+
const parsed: unknown = JSON.parse(raw);
76+
if (!isContributorCache(parsed)) return null;
77+
if (Date.now() - parsed.timestamp > CACHE_TTL_MS) {
78+
window.localStorage.removeItem(getCacheKey(repo));
79+
return null;
80+
}
81+
82+
return getUserContributors(parsed.data);
83+
} catch {
84+
return null;
85+
}
86+
};
87+
88+
const writeCachedContributors = (repo: string, data: GitHubContributor[]) => {
89+
if (typeof window === "undefined") return;
90+
91+
try {
92+
const payload: ContributorCache = {
93+
timestamp: Date.now(),
94+
data
95+
};
96+
window.localStorage.setItem(getCacheKey(repo), JSON.stringify(payload));
97+
} catch {}
98+
};
99+
100+
const localizedTitles = {
101+
creator: {
102+
en: "Creator of Leaf",
103+
tr: "Leaf'in Yaratıcısı",
104+
de: "Schöpfer von Leaf",
105+
pt: "Criador do Leaf",
106+
ru: "Создатель Leaf",
107+
zh: "Leaf 的创建者"
108+
},
109+
coreTeam: {
110+
en: "Core team",
111+
tr: "Ana ekip",
112+
de: "Kernteam",
113+
pt: "Equipe Principal",
114+
ru: "Основная команда",
115+
zh: "核心团队"
116+
},
117+
webDev: {
118+
en: "Designer & Web Dev",
119+
tr: "Tasarımcı & Web Geliştirici",
120+
de: "Designer & Webentwickler",
121+
pt: "Designer & Desenvolvedor Web",
122+
ru: "Дизайнер и Веб-разработчик",
123+
zh: "设计师兼网页开发"
124+
},
125+
special: {
126+
en: "Special Supporter",
127+
tr: "Özel Destekçi",
128+
de: "Besonderer Unterstützer",
129+
pt: "Apoiante Especial",
130+
ru: "Особый поддерживающий",
131+
zh: "特别支持者"
132+
}
133+
} satisfies Record<string, LocalizedText>;
134+
135+
type TitleKey = keyof typeof localizedTitles;
136+
137+
const title = (key: TitleKey) => localizedTitles[key][props.lang];
138+
139+
const rewrites = computed<Record<string, Rewrite>>(() => ({
140+
"Dreeam-qwq": { title: title("creator") },
141+
HaHaWTH: { name: "Creeam (HaHaWTH)", title: title("coreTeam") },
142+
//"Taiyou06": { title: title("coreTeam") },
143+
hayanesuru: { title: title("coreTeam") },
144+
MartijnMuijsers: { title: title("coreTeam") },
145+
Pascalpex: { title: title("special") },
72146
envizar: {
73-
title: titleWebDev,
147+
title: title("webDev"),
74148
links: [{ icon: "telegram", link: "https://t.me/envizar" }]
75149
}
76-
};
150+
}));
151+
152+
const repoMembers = ref<GitHubContributor[]>([]);
153+
const websiteRepoMembers = ref<GitHubContributor[]>([]);
77154
78-
const transform = ({ login, avatar_url, html_url }: any) => {
155+
const transform = ({ login, avatar_url, html_url }: GitHubContributor, rewrite?: Rewrite): Contributor => {
79156
const base = {
80157
avatar: avatar_url,
81158
name: login,
82159
links: [{ icon: "github", link: html_url }]
83160
};
84-
const rewrite = rewrites[login];
161+
85162
return rewrite
86163
? {
87164
...base,
@@ -91,56 +168,45 @@ const transform = ({ login, avatar_url, html_url }: any) => {
91168
: base;
92169
};
93170
94-
const transformWebsite = ({ login, avatar_url, html_url }: any) => {
95-
if (login in rewrites) {
96-
return null; // Skip this contributor if already in main list with custom title
97-
}
171+
const allMembers = computed<Contributor[]>(() => {
172+
const rewriteMap = rewrites.value;
173+
const mainMembers = repoMembers.value.map((m) => transform(m, rewriteMap[m.login]));
174+
const mainNames = new Set(mainMembers.map((m) => m.name));
98175
99-
const base = {
100-
avatar: avatar_url,
101-
name: login,
102-
links: [{ icon: "github", link: html_url }]
103-
};
104-
return base;
105-
};
176+
const websiteMembers = websiteRepoMembers.value
177+
.filter((m) => !(m.login in rewriteMap))
178+
.map((m) => transform(m))
179+
.filter((m) => !mainNames.has(m.name));
106180
107-
// Combine both contributor lists, filtering out nulls and duplicates
108-
const allMembers = computed(() => {
109-
if (!loaded.value || !websiteLoaded.value) return members.value;
181+
return [...mainMembers, ...websiteMembers];
182+
});
110183
111-
const mainContributors = new Set(members.value.map((m) => m.name));
112-
const filteredWebsiteMembers = websiteMembers.value.filter((m) => m !== null && !mainContributors.has(m.name));
184+
const fetchRepoContributors = async (repo: string): Promise<GitHubContributor[]> => {
185+
const cached = readCachedContributors(repo);
186+
if (cached) return cached;
113187
114-
return [...members.value, ...filteredWebsiteMembers];
115-
});
188+
try {
189+
const resp = await fetch(`https://api.github.com/repos/Winds-Studio/${repo}/contributors`);
190+
const data: unknown = await resp.json();
116191
117-
// Fetch main repo contributors
118-
fetch("https://api.github.com/repos/Winds-Studio/Leaf/contributors")
119-
.then((resp) => resp.json())
120-
.then((data) => {
121-
// TODO: find a solution to avoid rate limit
122-
if (Array.isArray(data)) {
123-
members.value = data.filter((m) => m.type == "User").map(transform);
124-
} else {
125-
console.warn(`Unexpected response: ${JSON.stringify(data)}`);
126-
members.value = [];
192+
if (!Array.isArray(data)) {
193+
console.warn(`Unexpected response from ${repo}: ${JSON.stringify(data)}`);
194+
return [];
127195
}
128-
})
129-
.finally(() => (loaded.value = true));
130-
131-
// Fetch website repo contributors
132-
fetch("https://api.github.com/repos/Winds-Studio/Leaf-website/contributors")
133-
.then((resp) => resp.json())
134-
.then((data) => {
135-
// TODO: find a solution to avoid rate limit
136-
if (Array.isArray(data)) {
137-
websiteMembers.value = data.filter((m) => m.type == "User").map(transform);
138-
} else {
139-
console.warn(`Unexpected response: ${JSON.stringify(data)}`);
140-
websiteMembers.value = [];
141-
}
142-
})
143-
.finally(() => (websiteLoaded.value = true));
196+
197+
const contributors = getUserContributors(data);
198+
writeCachedContributors(repo, contributors);
199+
return contributors;
200+
} catch (error) {
201+
console.warn(`Failed to fetch contributors from ${repo}:`, error);
202+
return [];
203+
}
204+
};
205+
206+
void Promise.all([fetchRepoContributors("Leaf"), fetchRepoContributors("Leaf-website")]).then(([main, website]) => {
207+
repoMembers.value = main;
208+
websiteRepoMembers.value = website;
209+
});
144210
</script>
145211

146212
<template>

0 commit comments

Comments
 (0)