Skip to content

Commit d5799ec

Browse files
committed
feat: LinkPreview figma type add
1 parent e21841f commit d5799ec

File tree

2 files changed

+171
-40
lines changed

2 files changed

+171
-40
lines changed

packages/notion-to-jsx/src/components/Renderer/components/LinkPreview/LinkPreview.tsx

Lines changed: 150 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
11
import React, { useState, useEffect } from 'react';
2-
import {
3-
link,
4-
card,
5-
content,
6-
iconContainer,
7-
icon,
8-
title,
9-
updatedText,
10-
} from './styles.css';
2+
import * as styles from './styles.css';
113

124
export interface LinkPreviewProps {
135
url: string;
@@ -22,6 +14,12 @@ interface RepoData {
2214
updated_at: string;
2315
}
2416

17+
interface FigmaData {
18+
name: string;
19+
url: string;
20+
thumbnailUrl?: string;
21+
}
22+
2523
// GitHub 레포지토리 데이터를 가져오는 함수
2624
const fetchGitHubRepoData = async (
2725
repoPath: string
@@ -42,6 +40,67 @@ const fetchGitHubRepoData = async (
4240
}
4341
};
4442

43+
// Figma 파일 정보 추출 함수
44+
const extractFigmaData = (url: string): FigmaData | null => {
45+
try {
46+
const parsedUrl = new URL(url);
47+
if (parsedUrl.hostname.includes('figma.com')) {
48+
// URL에서 파일 이름 추출
49+
const pathSegments = parsedUrl.pathname.split('/');
50+
const fileSegment = pathSegments.find((segment) =>
51+
segment.includes('file')
52+
);
53+
54+
if (!fileSegment) return null;
55+
56+
// 파일 ID와 이름 파싱
57+
const fileIdMatch = fileSegment.match(/file\/([^/]+)/);
58+
const fileId = fileIdMatch ? fileIdMatch[1] : '';
59+
60+
// URL에서 파일 이름 추출 (URL 파라미터에서)
61+
let fileName = '';
62+
let mode = '';
63+
64+
// URL 경로에서 파일 이름 추출 시도
65+
if (pathSegments.length > 3) {
66+
// URL 경로에서 이름 부분 추출 (/file/ID/NAME 형식)
67+
const encodedName = pathSegments[3];
68+
if (encodedName) {
69+
// URL 디코딩 및 하이픈을 공백으로 변환
70+
fileName = decodeURIComponent(encodedName).replace(/-/g, ' ');
71+
}
72+
}
73+
74+
// 파일 이름이 추출되지 않았으면 URL에서 직접 찾기
75+
if (!fileName && parsedUrl.pathname.includes('-')) {
76+
const nameMatch = parsedUrl.pathname.match(/\/([^/]+)(?:\?|$)/);
77+
if (nameMatch && nameMatch[1]) {
78+
fileName = decodeURIComponent(nameMatch[1].replace(/-/g, ' '));
79+
}
80+
}
81+
82+
// 파라미터에서 모드 추출 (dev, design 등)
83+
if (parsedUrl.search) {
84+
const searchParams = new URLSearchParams(parsedUrl.search);
85+
mode = searchParams.get('mode') || '';
86+
}
87+
88+
// 이름이 추출되지 않았으면 기본값 사용
89+
fileName = fileName || 'Figma Design';
90+
91+
return {
92+
name: fileName,
93+
url: url,
94+
thumbnailUrl: 'https://static.figma.com/app/icon/1/favicon.svg',
95+
};
96+
}
97+
return null;
98+
} catch (error) {
99+
console.error('Error parsing Figma URL:', error);
100+
return null;
101+
}
102+
};
103+
45104
// GitHub URL에서 레포지토리 경로 추출
46105
const extractRepoPathFromUrl = (url: string): string | null => {
47106
try {
@@ -62,6 +121,21 @@ const extractRepoPathFromUrl = (url: string): string | null => {
62121
}
63122
};
64123

124+
// URL이 어떤 타입의 링크인지 확인
125+
const getLinkType = (url: string): 'github' | 'figma' | 'unknown' => {
126+
try {
127+
const parsedUrl = new URL(url);
128+
if (parsedUrl.hostname === 'github.com') {
129+
return 'github';
130+
} else if (parsedUrl.hostname.includes('figma.com')) {
131+
return 'figma';
132+
}
133+
return 'unknown';
134+
} catch {
135+
return 'unknown';
136+
}
137+
};
138+
65139
// 날짜 포맷팅 함수
66140
const formatUpdatedTime = (dateString: string): string => {
67141
const date = new Date(dateString);
@@ -100,22 +174,33 @@ const formatUpdatedTime = (dateString: string): string => {
100174

101175
const LinkPreview: React.FC<LinkPreviewProps> = ({ url }) => {
102176
const [repoData, setRepoData] = useState<RepoData | null>(null);
177+
const [figmaData, setFigmaData] = useState<FigmaData | null>(null);
103178
const [loading, setLoading] = useState(true);
179+
const [linkType, setLinkType] = useState<'github' | 'figma' | 'unknown'>(
180+
'unknown'
181+
);
104182

105183
useEffect(() => {
106-
const loadRepoData = async () => {
184+
const loadLinkData = async () => {
107185
setLoading(true);
108-
const repoPath = extractRepoPathFromUrl(url);
186+
const type = getLinkType(url);
187+
setLinkType(type);
109188

110-
if (repoPath) {
111-
const data = await fetchGitHubRepoData(repoPath);
112-
setRepoData(data);
189+
if (type === 'github') {
190+
const repoPath = extractRepoPathFromUrl(url);
191+
if (repoPath) {
192+
const data = await fetchGitHubRepoData(repoPath);
193+
setRepoData(data);
194+
}
195+
} else if (type === 'figma') {
196+
const data = extractFigmaData(url);
197+
setFigmaData(data);
113198
}
114199

115200
setLoading(false);
116201
};
117202

118-
loadRepoData();
203+
loadLinkData();
119204
}, [url]);
120205

121206
// 레포지토리 이름 추출 (full_name에서 organization/repo 형식)
@@ -129,26 +214,60 @@ const LinkPreview: React.FC<LinkPreviewProps> = ({ url }) => {
129214
? formatUpdatedTime(repoData.updated_at)
130215
: '';
131216

217+
// 모든 링크 타입을 조건부 렌더링으로 통합
132218
return (
133-
<a href={url} target="_blank" rel="noopener noreferrer" className={link}>
134-
<div className={card}>
135-
<div className={iconContainer}>
136-
<img
137-
src={
138-
repoData?.owner?.avatar_url ||
139-
'https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png'
140-
}
141-
alt="Repository icon"
142-
className={icon}
143-
/>
219+
<a
220+
href={url}
221+
target="_blank"
222+
rel="noopener noreferrer"
223+
className={styles.link}
224+
>
225+
{linkType === 'figma' && figmaData ? (
226+
// Figma 프리뷰 렌더링
227+
<div className={styles.preview}>
228+
<div className={styles.iconContainer}>
229+
<img
230+
src={
231+
figmaData.thumbnailUrl ||
232+
'https://static.figma.com/app/icon/1/favicon.svg'
233+
}
234+
alt="Figma icon"
235+
className={styles.icon}
236+
/>
237+
</div>
238+
<div className={styles.content}>
239+
<div className={styles.title}>{figmaData.name}</div>
240+
<div className={styles.description}>www.figma.com</div>
241+
</div>
242+
</div>
243+
) : linkType === 'github' ? (
244+
// GitHub 프리뷰 렌더링
245+
<div className={`${styles.preview} ${styles.githubPreview}`}>
246+
<div className={styles.iconContainer}>
247+
<img
248+
src={
249+
repoData?.owner?.avatar_url ||
250+
'https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png'
251+
}
252+
alt="Repository icon"
253+
className={styles.icon}
254+
/>
255+
</div>
256+
<div className={`${styles.content} ${styles.githubContent}`}>
257+
<div className={styles.title}>{repoName}</div>
258+
<div className={styles.description}>
259+
{loading ? 'Loading...' : `${repoName}${updatedTimeText}`}
260+
</div>
261+
</div>
144262
</div>
145-
<div className={content}>
146-
<div className={title}>{repoName}</div>
147-
<div className={updatedText}>
148-
{loading ? 'Loading...' : `${repoName}${updatedTimeText}`}
263+
) : (
264+
// 기본 링크 프리뷰 렌더링
265+
<div className={styles.preview}>
266+
<div className={styles.content}>
267+
<div className={styles.title}>{url}</div>
149268
</div>
150269
</div>
151-
</div>
270+
)}
152271
</a>
153272
);
154273
};

packages/notion-to-jsx/src/components/Renderer/components/LinkPreview/styles.css.ts

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,14 @@ export const link = style({
88
paddingBottom: vars.spacing.xxs,
99
});
1010

11-
export const card = style({
11+
export const preview = style({
1212
display: 'flex',
1313
border: `1px solid ${vars.colors.border}`,
1414
borderRadius: vars.borderRadius.md,
1515
overflow: 'hidden',
1616
transition: 'box-shadow 0.2s ease',
1717
alignItems: 'center',
18-
maxHeight: '4rem',
1918
padding: vars.spacing.base,
20-
paddingLeft: vars.spacing.md,
2119
gap: vars.spacing.md,
2220
':hover': {
2321
boxShadow: vars.shadows.md,
@@ -28,22 +26,21 @@ export const content = style({
2826
display: 'flex',
2927
flex: '1 1 auto',
3028
flexDirection: 'column',
31-
justifyContent: 'space-between',
3229
overflow: 'hidden',
3330
});
3431

3532
export const iconContainer = style({
3633
display: 'flex',
3734
alignItems: 'center',
3835
justifyContent: 'center',
39-
maxWidth: '2.5rem',
40-
height: '100%',
36+
width: '2.5rem',
37+
height: '2.5rem',
4138
flexShrink: 0,
4239
});
4340

4441
export const icon = style({
45-
width: '2.5rem',
46-
height: '2.5rem',
42+
width: '100%',
43+
height: '100%',
4744
objectFit: 'contain',
4845
borderRadius: vars.borderRadius.sm,
4946
});
@@ -57,10 +54,25 @@ export const title = style({
5754
whiteSpace: 'nowrap',
5855
});
5956

60-
export const updatedText = style({
57+
export const description = style({
6158
fontSize: vars.typography.fontSize.xs,
6259
color: vars.colors.secondary,
6360
overflow: 'hidden',
6461
textOverflow: 'ellipsis',
6562
whiteSpace: 'nowrap',
6663
});
64+
65+
/**
66+
* 타입별 특수 스타일: 각 링크 타입에만 필요한 추가 스타일
67+
*/
68+
69+
// GitHub 프리뷰에만 필요한 스타일
70+
export const githubPreview = style({
71+
maxHeight: '4rem',
72+
paddingLeft: vars.spacing.md,
73+
});
74+
75+
// GitHub 컨텐츠에만 필요한 스타일
76+
export const githubContent = style({
77+
justifyContent: 'space-between',
78+
});

0 commit comments

Comments
 (0)