Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/api/article.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,29 @@ export const uploadImage = async (imageUrl: string) => {
throw new Error('api Error');
}
};

export const getArticleList = async (
type: string,
category: string,
page: number,
) => {
try {
const token = localStorage.getItem('token');
if (!token) {
throw new Error('로그인이 필요합니다.');
}

const url = `/contents/list?type=${type}&category=${category}&page=${page}`;

const { data } = await apiClient.get(url, {
headers: {
Authorization: `${token}`,
},
});

return data;
} catch (error) {
console.error(error);
throw new Error('API Error');
}
};
12 changes: 9 additions & 3 deletions src/components/editor/common/link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ export const createLinkNodeHTML = (attrs: LinkAttributes): HTMLElement => {
) {
formattedUrl = 'https://' + formattedUrl;
}
try {
const url = new URL(formattedUrl);
formattedUrl = url.hostname;
} catch (err) {
console.error("Invalid URL:", formattedUrl, err);
}

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

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

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

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

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

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

textWrapper.appendChild(titleElement);
Expand Down
2 changes: 1 addition & 1 deletion src/components/editor/customComponent/CustomBlockLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ const CustomBlockLink = Node.create({
[
'p',
{
class: 'text-[12px] text-[#a1885f] underline',
class: 'text-[12px] text-[#a1885f] no-underline truncate',
},
HTMLAttributes.url,
],
Expand Down
2 changes: 1 addition & 1 deletion src/components/editor/customComponent/CustomLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ const CustomLink = Node.create({
'p',
{
class:
'whitespace-nowrap overflow-hidden text-ellipsis break-all mt-[9px] text-[#a1885f] text-[13px] no-underline',
'mt-[9px] text-[#a1885f] text-[13px] no-underline truncate',
},
HTMLAttributes.url,
],
Expand Down
2 changes: 1 addition & 1 deletion src/components/editor/customComponent/CustomOgLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ const CustomOgLink = Node.create({
'p',
{
class:
'mt-[9px] text-[#a1885f] text-[13px] break-all whitespace-nowrap overflow-hidden text-ellipsis no-underline',
'mt-[9px] text-[#a1885f] text-[13px] no-underline truncate',
},
HTMLAttributes.url,
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ const CustomVerticalLink = Node.create({
[
'p',
{
class: 'text-[12px] text-[#a1885f] no-underline',
class: 'text-[12px] text-[#a1885f] no-underline truncate',
},
HTMLAttributes.url,
],
Expand Down
85 changes: 62 additions & 23 deletions src/pages/HomPage.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,83 @@
import React from "react";
import React, { useEffect, useState } from 'react';
import { ArticleContent } from '../types/article';
import { getArticleList } from '../api/article';
import ArticlePagination from './pagenation';

// 날짜 변환 함수 (YYYY-MM-DD)
const formatDate = (dateString: string) => {
const date = new Date(dateString);
return `${date.getFullYear()}년 ${date.getMonth() + 1}월 ${date.getDate()}일`;
};

// type (article -> 아티클, video -> 영상, all -> 전체)
const typeToName = (type: string) => {
if (type === 'article') return '아티클';
if (type === 'video') return '영상';
return '전체';
};

const HomePage: React.FC = () => {
const bookmarks = [
{
id: 1,
title: "오래된 아이템...",
date: "2024-06-06",
category: "가구 장식",
},
{
id: 2,
title: "또다른 오래된...",
date: "2024-06-05",
category: "가구 장식",
},
];
const [articles, setArticles] = useState<ArticleContent[]>([]);
const [page, setPage] = useState(1);
const [size] = useState(5);
const [totalPages, setTotalPages] = useState(1);

useEffect(() => {
const fetchArticles = async () => {
const type = 'all';
const category = 'all';
const response = await getArticleList(type, category, page);
setArticles(response.content);
setTotalPages(Math.ceil(response.total / size));
};

fetchArticles();
}, [page, size]);

const handlePageChange = (newPage: number) => {
if (newPage >= 1 && newPage <= totalPages) {
setPage(newPage);
}
};

return (
<div className="m-8 ml-14">
<div className="text-2xl font-bold mb-4">북마크</div>
<div className="flex justify-between items-center mb-6">
<div className="text-gray-500">찜한 콘텐츠 {bookmarks.length}</div>
<div className="text-gray-500">찜한 콘텐츠 {articles.length}</div>
</div>
<div className="space-y-4">
{bookmarks.map((bookmark) => (
{articles.map((items) => (
<div
key={bookmark.id}
key={items.article.articleId}
className="flex items-center bg-white p-4 shadow rounded-lg border border-gray-200"
>
<div className="w-24 h-24 bg-gray-100 rounded-lg flex-shrink-0"></div>
<div className="w-24 h-24 bg-gray-100 rounded-lg flex-shrink-0">
<img
src={items.article.img_link}
alt={items.article.title}
className="w-24 h-24 object-cover rounded-lg"
/>
</div>
<div className="ml-4 flex-1">
<div className="text-lg font-bold text-gray-800">
{bookmark.title}
<div className="text-lg font-bold text-gray-800 whitespace-nowrap overflow-hidden overflow-ellipsis max-xl:max-w-[200px]">
{items.article.title}
</div>
<div className="text-gray-500">
{typeToName(items.article.type)}
</div>
<div className="text-sm text-gray-400">
{formatDate(items.article.date)}
</div>
<div className="text-gray-500">{bookmark.category}</div>
<div className="text-sm text-gray-400">{bookmark.date}</div>
</div>
</div>
))}
</div>

<ArticlePagination
totalPages={totalPages}
onPageChange={handlePageChange}
/>
</div>
);
};
Expand Down
89 changes: 89 additions & 0 deletions src/pages/pagenation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import React, { useState, useEffect } from 'react';

interface PaginationProps {
totalPages: number;
onPageChange: (page: number) => void; // 페이지 변경 시 부모 컴포넌트에서 처리 가능
}

const ArticlePagination: React.FC<PaginationProps> = ({
totalPages,
onPageChange,
}) => {
const [currentPage, setCurrentPage] = useState<number>(1);
const [pageNumbers, setPageNumbers] = useState<number[]>([]);
const [currentRange, setCurrentRange] = useState<number>(0);

useEffect(() => {
// 현재 페이지가 속한 그룹(5개 단위) 계산
const range = Math.floor((currentPage - 1) / 5);
setCurrentRange(range);
}, [currentPage]);

useEffect(() => {
// 현재 그룹의 페이지 목록 생성 (5개씩)
const start = currentRange * 5 + 1;
const end = Math.min(start + 4, totalPages);
const pages: number[] = Array.from(
{ length: end - start + 1 },
(_, i) => start + i,
);
setPageNumbers(pages);
}, [currentRange, totalPages]);

const handlePageClick = (page: number) => {
setCurrentPage(page);
onPageChange(page); // 부모 컴포넌트에서 데이터 요청 가능
};

const handleNextRange = () => {
if ((currentRange + 1) * 5 < totalPages) {
const newRange = currentRange + 1;
setCurrentRange(newRange);
handlePageClick(newRange * 5 + 1); // 다음 그룹의 첫 번째 페이지로 이동
}
};

const handlePrevRange = () => {
if (currentRange > 0) {
const newRange = currentRange - 1;
setCurrentRange(newRange);
handlePageClick(newRange * 5 + 1); // 이전 그룹의 첫 번째 페이지로 이동
}
};

return (
<div className="flex justify-center items-center space-x-2 mt-10 mb-10">
<button
onClick={handlePrevRange}
disabled={currentRange === 0}
className="px-2 py-1 border rounded disabled:opacity-50 sm:px-3 sm:py-1"
>
&lt;
</button>

{pageNumbers.map((page) => (
<button
key={page}
onClick={() => handlePageClick(page)}
className={`px-2 py-1 border rounded sm:px-3 sm:py-1 ${
page === currentPage
? 'bg-blue-500 text-white'
: 'bg-white text-black'
}`}
>
{page}
</button>
))}

<button
onClick={handleNextRange}
disabled={(currentRange + 1) * 5 >= totalPages}
className="px-2 py-1 border rounded disabled:opacity-50 sm:px-3 sm:py-1"
>
&gt;
</button>
</div>
);
};

export default ArticlePagination;
22 changes: 21 additions & 1 deletion src/types/article.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,24 @@ export interface AddArticleData {
imageLink: string;
paywallUp: string;
paywallDown: string;
}
}

export interface Article {
articleId: number;
title: string;
date: string;
type: string;
img_link?: string;
}

export interface ArticleContent {
likedByMe: boolean;
article: Article;
}

export interface ListArticleData {
total: number;
size: number;
page: number;
content: ArticleContent[];
}