Skip to content

Commit 8eea111

Browse files
authored
Merge pull request #25 from Moaguide-develop:feat/editor
Feat/editor
2 parents 81d3a88 + c9f0119 commit 8eea111

File tree

9 files changed

+211
-31
lines changed

9 files changed

+211
-31
lines changed

src/api/article.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,29 @@ export const uploadImage = async (imageUrl: string) => {
3838
throw new Error('api Error');
3939
}
4040
};
41+
42+
export const getArticleList = async (
43+
type: string,
44+
category: string,
45+
page: number,
46+
) => {
47+
try {
48+
const token = localStorage.getItem('token');
49+
if (!token) {
50+
throw new Error('로그인이 필요합니다.');
51+
}
52+
53+
const url = `/contents/list?type=${type}&category=${category}&page=${page}`;
54+
55+
const { data } = await apiClient.get(url, {
56+
headers: {
57+
Authorization: `${token}`,
58+
},
59+
});
60+
61+
return data;
62+
} catch (error) {
63+
console.error(error);
64+
throw new Error('API Error');
65+
}
66+
};

src/components/editor/common/link.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ export const createLinkNodeHTML = (attrs: LinkAttributes): HTMLElement => {
2020
) {
2121
formattedUrl = 'https://' + formattedUrl;
2222
}
23+
try {
24+
const url = new URL(formattedUrl);
25+
formattedUrl = url.hostname;
26+
} catch (err) {
27+
console.error("Invalid URL:", formattedUrl, err);
28+
}
2329

2430
if (attrs.type === 'imageLink') {
2531
if (attrs.thumbnail) {
@@ -64,7 +70,7 @@ export const createLinkNodeHTML = (attrs: LinkAttributes): HTMLElement => {
6470
summaryElement.textContent = attrs.summary;
6571

6672
const urlElement = document.createElement('p');
67-
urlElement.className = 'mt-[9px] text-[12px] text-sky-600 no-underline';
73+
urlElement.className = 'mt-[9px] text-[12px] text-sky-600 no-underline truncate';
6874
urlElement.textContent = formattedUrl;
6975

7076
textWrapper.appendChild(titleElement);
@@ -122,7 +128,7 @@ export const createLinkNodeHTML = (attrs: LinkAttributes): HTMLElement => {
122128

123129
const urlElement = document.createElement('p');
124130
urlElement.className =
125-
'mt-[9px] text-sky-600 text-[13px] break-all whitespace-nowrap overflow-hidden text-ellipsis no-underline';
131+
'mt-[9px] text-sky-600 text-[13px] no-underline truncate';
126132
urlElement.textContent = formattedUrl;
127133

128134
textWrapper.appendChild(titleElement);
@@ -155,7 +161,7 @@ export const createLinkNodeHTML = (attrs: LinkAttributes): HTMLElement => {
155161
summaryElement.textContent = attrs.summary;
156162

157163
const urlElement = document.createElement('p');
158-
urlElement.className = 'text-[12px] text-sky-600 mt-[9px] no-underline';
164+
urlElement.className = 'text-[12px] text-sky-600 mt-[9px] no-underline truncate';
159165
urlElement.textContent = formattedUrl;
160166

161167
textWrapper.appendChild(titleElement);

src/components/editor/customComponent/CustomBlockLink.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ const CustomBlockLink = Node.create({
8383
[
8484
'p',
8585
{
86-
class: 'text-[12px] text-[#a1885f] underline',
86+
class: 'text-[12px] text-[#a1885f] no-underline truncate',
8787
},
8888
HTMLAttributes.url,
8989
],

src/components/editor/customComponent/CustomLink.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ const CustomLink = Node.create({
139139
'p',
140140
{
141141
class:
142-
'whitespace-nowrap overflow-hidden text-ellipsis break-all mt-[9px] text-[#a1885f] text-[13px] no-underline',
142+
'mt-[9px] text-[#a1885f] text-[13px] no-underline truncate',
143143
},
144144
HTMLAttributes.url,
145145
],

src/components/editor/customComponent/CustomOgLink.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ const CustomOgLink = Node.create({
140140
'p',
141141
{
142142
class:
143-
'mt-[9px] text-[#a1885f] text-[13px] break-all whitespace-nowrap overflow-hidden text-ellipsis no-underline',
143+
'mt-[9px] text-[#a1885f] text-[13px] no-underline truncate',
144144
},
145145
HTMLAttributes.url,
146146
],

src/components/editor/customComponent/CustomVerticalLink.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ const CustomVerticalLink = Node.create({
8181
[
8282
'p',
8383
{
84-
class: 'text-[12px] text-[#a1885f] no-underline',
84+
class: 'text-[12px] text-[#a1885f] no-underline truncate',
8585
},
8686
HTMLAttributes.url,
8787
],

src/pages/HomPage.tsx

Lines changed: 62 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,83 @@
1-
import React from "react";
1+
import React, { useEffect, useState } from 'react';
2+
import { ArticleContent } from '../types/article';
3+
import { getArticleList } from '../api/article';
4+
import ArticlePagination from './pagenation';
5+
6+
// 날짜 변환 함수 (YYYY-MM-DD)
7+
const formatDate = (dateString: string) => {
8+
const date = new Date(dateString);
9+
return `${date.getFullYear()}${date.getMonth() + 1}${date.getDate()}일`;
10+
};
11+
12+
// type (article -> 아티클, video -> 영상, all -> 전체)
13+
const typeToName = (type: string) => {
14+
if (type === 'article') return '아티클';
15+
if (type === 'video') return '영상';
16+
return '전체';
17+
};
218

319
const HomePage: React.FC = () => {
4-
const bookmarks = [
5-
{
6-
id: 1,
7-
title: "오래된 아이템...",
8-
date: "2024-06-06",
9-
category: "가구 장식",
10-
},
11-
{
12-
id: 2,
13-
title: "또다른 오래된...",
14-
date: "2024-06-05",
15-
category: "가구 장식",
16-
},
17-
];
20+
const [articles, setArticles] = useState<ArticleContent[]>([]);
21+
const [page, setPage] = useState(1);
22+
const [size] = useState(5);
23+
const [totalPages, setTotalPages] = useState(1);
24+
25+
useEffect(() => {
26+
const fetchArticles = async () => {
27+
const type = 'all';
28+
const category = 'all';
29+
const response = await getArticleList(type, category, page);
30+
setArticles(response.content);
31+
setTotalPages(Math.ceil(response.total / size));
32+
};
33+
34+
fetchArticles();
35+
}, [page, size]);
36+
37+
const handlePageChange = (newPage: number) => {
38+
if (newPage >= 1 && newPage <= totalPages) {
39+
setPage(newPage);
40+
}
41+
};
1842

1943
return (
2044
<div className="m-8 ml-14">
2145
<div className="text-2xl font-bold mb-4">북마크</div>
2246
<div className="flex justify-between items-center mb-6">
23-
<div className="text-gray-500">찜한 콘텐츠 {bookmarks.length}</div>
47+
<div className="text-gray-500">찜한 콘텐츠 {articles.length}</div>
2448
</div>
2549
<div className="space-y-4">
26-
{bookmarks.map((bookmark) => (
50+
{articles.map((items) => (
2751
<div
28-
key={bookmark.id}
52+
key={items.article.articleId}
2953
className="flex items-center bg-white p-4 shadow rounded-lg border border-gray-200"
3054
>
31-
<div className="w-24 h-24 bg-gray-100 rounded-lg flex-shrink-0"></div>
55+
<div className="w-24 h-24 bg-gray-100 rounded-lg flex-shrink-0">
56+
<img
57+
src={items.article.img_link}
58+
alt={items.article.title}
59+
className="w-24 h-24 object-cover rounded-lg"
60+
/>
61+
</div>
3262
<div className="ml-4 flex-1">
33-
<div className="text-lg font-bold text-gray-800">
34-
{bookmark.title}
63+
<div className="text-lg font-bold text-gray-800 whitespace-nowrap overflow-hidden overflow-ellipsis max-xl:max-w-[200px]">
64+
{items.article.title}
65+
</div>
66+
<div className="text-gray-500">
67+
{typeToName(items.article.type)}
68+
</div>
69+
<div className="text-sm text-gray-400">
70+
{formatDate(items.article.date)}
3571
</div>
36-
<div className="text-gray-500">{bookmark.category}</div>
37-
<div className="text-sm text-gray-400">{bookmark.date}</div>
3872
</div>
3973
</div>
4074
))}
4175
</div>
76+
77+
<ArticlePagination
78+
totalPages={totalPages}
79+
onPageChange={handlePageChange}
80+
/>
4281
</div>
4382
);
4483
};

src/pages/pagenation.tsx

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import React, { useState, useEffect } from 'react';
2+
3+
interface PaginationProps {
4+
totalPages: number;
5+
onPageChange: (page: number) => void; // 페이지 변경 시 부모 컴포넌트에서 처리 가능
6+
}
7+
8+
const ArticlePagination: React.FC<PaginationProps> = ({
9+
totalPages,
10+
onPageChange,
11+
}) => {
12+
const [currentPage, setCurrentPage] = useState<number>(1);
13+
const [pageNumbers, setPageNumbers] = useState<number[]>([]);
14+
const [currentRange, setCurrentRange] = useState<number>(0);
15+
16+
useEffect(() => {
17+
// 현재 페이지가 속한 그룹(5개 단위) 계산
18+
const range = Math.floor((currentPage - 1) / 5);
19+
setCurrentRange(range);
20+
}, [currentPage]);
21+
22+
useEffect(() => {
23+
// 현재 그룹의 페이지 목록 생성 (5개씩)
24+
const start = currentRange * 5 + 1;
25+
const end = Math.min(start + 4, totalPages);
26+
const pages: number[] = Array.from(
27+
{ length: end - start + 1 },
28+
(_, i) => start + i,
29+
);
30+
setPageNumbers(pages);
31+
}, [currentRange, totalPages]);
32+
33+
const handlePageClick = (page: number) => {
34+
setCurrentPage(page);
35+
onPageChange(page); // 부모 컴포넌트에서 데이터 요청 가능
36+
};
37+
38+
const handleNextRange = () => {
39+
if ((currentRange + 1) * 5 < totalPages) {
40+
const newRange = currentRange + 1;
41+
setCurrentRange(newRange);
42+
handlePageClick(newRange * 5 + 1); // 다음 그룹의 첫 번째 페이지로 이동
43+
}
44+
};
45+
46+
const handlePrevRange = () => {
47+
if (currentRange > 0) {
48+
const newRange = currentRange - 1;
49+
setCurrentRange(newRange);
50+
handlePageClick(newRange * 5 + 1); // 이전 그룹의 첫 번째 페이지로 이동
51+
}
52+
};
53+
54+
return (
55+
<div className="flex justify-center items-center space-x-2 mt-10 mb-10">
56+
<button
57+
onClick={handlePrevRange}
58+
disabled={currentRange === 0}
59+
className="px-2 py-1 border rounded disabled:opacity-50 sm:px-3 sm:py-1"
60+
>
61+
&lt;
62+
</button>
63+
64+
{pageNumbers.map((page) => (
65+
<button
66+
key={page}
67+
onClick={() => handlePageClick(page)}
68+
className={`px-2 py-1 border rounded sm:px-3 sm:py-1 ${
69+
page === currentPage
70+
? 'bg-blue-500 text-white'
71+
: 'bg-white text-black'
72+
}`}
73+
>
74+
{page}
75+
</button>
76+
))}
77+
78+
<button
79+
onClick={handleNextRange}
80+
disabled={(currentRange + 1) * 5 >= totalPages}
81+
className="px-2 py-1 border rounded disabled:opacity-50 sm:px-3 sm:py-1"
82+
>
83+
&gt;
84+
</button>
85+
</div>
86+
);
87+
};
88+
89+
export default ArticlePagination;

src/types/article.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,24 @@ export interface AddArticleData {
77
imageLink: string;
88
paywallUp: string;
99
paywallDown: string;
10-
}
10+
}
11+
12+
export interface Article {
13+
articleId: number;
14+
title: string;
15+
date: string;
16+
type: string;
17+
img_link?: string;
18+
}
19+
20+
export interface ArticleContent {
21+
likedByMe: boolean;
22+
article: Article;
23+
}
24+
25+
export interface ListArticleData {
26+
total: number;
27+
size: number;
28+
page: number;
29+
content: ArticleContent[];
30+
}

0 commit comments

Comments
 (0)