|
1 | 1 | /** |
2 | | - * Affiliated PyTorchKR channels and external resources. |
3 | | - * Individual maintainer roster is not duplicated here — refer to the GitHub |
4 | | - * organization for an authoritative, up-to-date list. |
| 2 | + * Loads member and organization cards from `_members/**` markdown files. |
| 3 | + * Schema mirrors PyTorchKR's `_members/` Jekyll collection so future |
| 4 | + * synchronization stays trivial. |
5 | 5 | */ |
6 | 6 |
|
7 | | -export interface PartnerOrg { |
| 7 | +export interface MemberLinks { |
| 8 | + github?: string |
| 9 | + linkedin?: string |
| 10 | + twitter?: string |
| 11 | + facebook?: string |
| 12 | + instagram?: string |
| 13 | + youtube?: string |
| 14 | + homepage?: string |
| 15 | +} |
| 16 | + |
| 17 | +export interface Member { |
| 18 | + id: string |
| 19 | + name: string |
| 20 | + title: string |
| 21 | + team: string |
| 22 | + joined: string |
| 23 | + role: 'maintainer' | 'alumni' | 'advisory' |
| 24 | + links: MemberLinks |
| 25 | +} |
| 26 | + |
| 27 | +export interface Organization { |
| 28 | + id: string |
8 | 29 | name: string |
9 | | - href: string |
| 30 | + title: string |
| 31 | + joined: string |
10 | 32 | description: string |
| 33 | + links: MemberLinks |
11 | 34 | } |
12 | 35 |
|
13 | | -export const PARTNER_ORGS: PartnerOrg[] = [ |
14 | | - { |
15 | | - name: '파이토치 한국 사용자 모임', |
16 | | - href: 'https://pytorch.kr', |
17 | | - description: '본 프로젝트를 운영하는 한국 PyTorch 커뮤니티의 공식 사이트', |
18 | | - }, |
19 | | - { |
20 | | - name: 'PyTorch 한국어 튜토리얼', |
21 | | - href: 'https://tutorials.pytorch.kr', |
22 | | - description: 'PyTorch 공식 튜토리얼의 한국어 번역 — 본 용어집의 주된 활용처', |
23 | | - }, |
24 | | - { |
25 | | - name: 'PyTorch 한국 사용자 모임 (Discuss)', |
26 | | - href: 'https://discuss.pytorch.kr', |
27 | | - description: '질문과 토론이 오가는 커뮤니티 포럼', |
28 | | - }, |
29 | | - { |
30 | | - name: 'PyTorchKorea GitHub Organization', |
31 | | - href: 'https://github.com/PyTorchKorea', |
32 | | - description: '오픈소스 저장소 모음 — 관리자 목록과 활동 이력은 여기에서 확인', |
33 | | - }, |
34 | | -] |
| 36 | +function parseFrontmatter(raw: string): Record<string, string> { |
| 37 | + const m = raw.match(/^---\s*\n([\s\S]*?)\n---/) |
| 38 | + if (!m) return {} |
| 39 | + const out: Record<string, string> = {} |
| 40 | + for (const line of m[1].split(/\r?\n/)) { |
| 41 | + const i = line.indexOf(':') |
| 42 | + if (i < 0) continue |
| 43 | + const key = line.slice(0, i).trim() |
| 44 | + const value = line.slice(i + 1).trim() |
| 45 | + if (key) out[key] = value |
| 46 | + } |
| 47 | + return out |
| 48 | +} |
| 49 | + |
| 50 | +function toLinks(fm: Record<string, string>): MemberLinks { |
| 51 | + const pick = (k: string) => (fm[k] && fm[k].length > 0 ? fm[k] : undefined) |
| 52 | + return { |
| 53 | + github: pick('link_github'), |
| 54 | + linkedin: pick('link_linkedin'), |
| 55 | + twitter: pick('link_twitter'), |
| 56 | + facebook: pick('link_facebook'), |
| 57 | + instagram: pick('link_instagram'), |
| 58 | + youtube: pick('link_youtube'), |
| 59 | + homepage: pick('link_homepage'), |
| 60 | + } |
| 61 | +} |
| 62 | + |
| 63 | +const RAW = import.meta.glob('../../_members/**/*.md', { |
| 64 | + eager: true, |
| 65 | + query: '?raw', |
| 66 | + import: 'default', |
| 67 | +}) as Record<string, string> |
| 68 | + |
| 69 | +const allMembers: Member[] = [] |
| 70 | +const allOrgs: Organization[] = [] |
| 71 | + |
| 72 | +for (const [path, raw] of Object.entries(RAW)) { |
| 73 | + const fm = parseFrontmatter(raw) |
| 74 | + if (!fm._id) continue |
| 75 | + |
| 76 | + if (fm.kind === 'organization' || path.includes('/organizations/')) { |
| 77 | + allOrgs.push({ |
| 78 | + id: fm._id, |
| 79 | + name: fm.name || fm._id, |
| 80 | + title: fm.title || '', |
| 81 | + joined: fm.joined || '', |
| 82 | + description: fm.description || '', |
| 83 | + links: toLinks(fm), |
| 84 | + }) |
| 85 | + continue |
| 86 | + } |
| 87 | + |
| 88 | + const role = (fm.role as Member['role']) || 'maintainer' |
| 89 | + allMembers.push({ |
| 90 | + id: fm._id, |
| 91 | + name: fm.name || fm._id, |
| 92 | + title: fm.title || '', |
| 93 | + team: fm.team || '', |
| 94 | + joined: fm.joined || '', |
| 95 | + role, |
| 96 | + links: toLinks(fm), |
| 97 | + }) |
| 98 | +} |
| 99 | + |
| 100 | +const byJoined = (a: { joined: string }, b: { joined: string }) => |
| 101 | + a.joined.localeCompare(b.joined) |
| 102 | + |
| 103 | +export const MAINTAINERS: Member[] = allMembers |
| 104 | + .filter((m) => m.role === 'maintainer') |
| 105 | + .sort(byJoined) |
| 106 | + |
| 107 | +export const ALUMNI: Member[] = allMembers |
| 108 | + .filter((m) => m.role === 'alumni') |
| 109 | + .sort(byJoined) |
| 110 | + |
| 111 | +export const ADVISORY: Member[] = allMembers |
| 112 | + .filter((m) => m.role === 'advisory') |
| 113 | + .sort(byJoined) |
| 114 | + |
| 115 | +export const ORGANIZATIONS: Organization[] = allOrgs.sort(byJoined) |
0 commit comments