본문 바로가기
Javascript 프로젝트/GitHub 웹 페이지

[GitHub 웹페이지] Blog 페이지

by cogito21_js 2024. 6. 10.
반응형

Markdown 파일에서 제목, 날짜, 내용의 일부만 읽어와서 표시하도록 JavaScript 코드를 업데이트하겠습니다. 이를 위해 각 Markdown 파일에서 첫 번째 제목(#), 날짜(*yyyy-mm-dd*), 그리고 내용의 첫 번째 단락만 가져오도록 하겠습니다.

디렉터리 구조

/blog
    index.html
    blog.css
    blog.js
/assets/markdown/posts
    post1.md
    post2.md
    ...

HTML (index.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Blog Page</title>
    <link rel="stylesheet" href="../common/css/common.css">
    <link rel="stylesheet" href="blog.css">
</head>
<body>
    <!-- 공통 헤더 -->
    <header>
        <nav>
            <ul>
                <li><a href="../home/index.html">Home</a></li>
                <li><a href="../profile/index.html">Profile</a></li>
                <li><a href="../blog/index.html">Blog</a></li>
            </ul>
        </nav>
    </header>

    <!-- 메인 콘텐츠 -->
    <main>
        <section class="blog-posts">
            <h2>Blog Posts</h2>
            <div class="title-divider"></div>
            <div id="posts-container"></div>
            <div id="pagination"></div>
        </section>
    </main>

    <!-- 공통 푸터 -->
    <footer>
        <p>&copy; 2024 Your Name. All rights reserved.</p>
    </footer>

    <script src="blog.js"></script>
</body>
</html>

CSS (blog.css)

/* 공통 스타일을 사용하기 위해 common.css 포함 */
@import url('../common/css/common.css');

/* 블로그 페이지 스타일 */
body {
    font-family: Arial, sans-serif;
    margin: 0;
    padding: 0;
    background: #f4f4f9;
}

header nav ul {
    display: flex;
    justify-content: center;
    background: #333;
    padding: 0;
}

header nav ul li {
    list-style: none;
    margin: 0;
}

header nav ul li a {
    display: block;
    padding: 15px 20px;
    color: white;
    text-decoration: none;
}

header nav ul li a:hover {
    background: #575757;
}

.blog-posts {
    max-width: 800px;
    margin: 40px auto;
    padding: 20px;
    background: white;
    border-radius: 8px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    text-align: center; /* 섹션 글자 가운데 정렬 */
}

.blog-posts h2 {
    margin-bottom: 10px;
}

.title-divider {
    width: 80px;
    height: 4px;
    background-color: #007BFF;
    margin: 10px auto 20px auto;
    border-radius: 2px;
}

#posts-container {
    margin-top: 20px; /* 목록과 제목 구분 */
}

.post {
    border-bottom: 1px solid #ddd;
    padding: 20px 0;
    text-align: left; /* 포스트 내용은 왼쪽 정렬 */
}

.post:last-child {
    border-bottom: none;
}

.post-title {
    font-size: 1.5em;
    margin: 0;
}

.post-date {
    color: #999;
    font-size: 0.9em;
}

.post-summary {
    margin: 10px 0;
}

.read-more {
    color: #007BFF;
    text-decoration: none;
}

.read-more:hover {
    text-decoration: underline;
}

/* 페이징 스타일 */
.pagination {
    display: flex;
    justify-content: center;
    margin-top: 20px;
    gap: 5px;
}

.pagination button {
    background: #007BFF;
    color: white;
    border: none;
    padding: 10px 15px;
    cursor: pointer;
    border-radius: 5px;
}

.pagination button:hover {
    background: #0056b3;
}

.pagination button.disabled {
    background: #ccc;
    cursor: not-allowed;
}

.pagination .arrow {
    font-size: 1.2em;
}

JavaScript (blog.js)

// blog.js

// Markdown을 HTML로 변환하는 함수
function markdownToHtml(markdown) {
  const titleMatch = markdown.match(/^# (.*$)/m);
  const dateMatch = markdown.match(/^\*([0-9-]+)\*/m);
  const contentMatch = markdown.match(/(?:#.*?\n|\*.*?\n)([^#\*]*)/s);

  const title = titleMatch ? titleMatch[1] : 'No title';
  const date = dateMatch ? dateMatch[1] : 'No date';
  const content = contentMatch ? contentMatch[1].trim().split('\n').slice(0, 2).join(' ') : 'No content';

  return `
    <h3 class="post-title">${title}</h3>
    <p class="post-date">${date}</p>
    <p class="post-summary">${content}</p>
    <a class="read-more" href="#">Read more</a>
  `;
}

// Markdown 파일을 비동기적으로 읽어오는 함수
async function fetchMarkdown(file) {
  const response = await fetch(file);
  const markdown = await response.text();
  return markdownToHtml(markdown);
}

// 디렉토리 목록을 비동기적으로 가져오는 함수
async function fetchMarkdownFiles() {
  // 실제로는 서버에서 파일 목록을 받아와야 합니다.
  // 이 예제에서는 하드코딩된 파일 목록을 사용합니다.
  return [
    'assets/markdown/posts/post1.md',
    'assets/markdown/posts/post2.md',
    // 다른 게시물 파일들을 추가할 수 있습니다
  ];
}

// 블로그 게시물 목록을 생성하는 함수
async function createBlogPosts(page = 1) {
  const postsContainer = document.getElementById('posts-container');
  postsContainer.innerHTML = ''; // 이전 게시물 초기화

  const postFiles = await fetchMarkdownFiles();

  const postsPerPage = 5;
  const startIndex = (page - 1) * postsPerPage;
  const endIndex = startIndex + postsPerPage;
  const paginatedPosts = postFiles.slice(startIndex, endIndex);

  for (const file of paginatedPosts) {
    const postHtml = await fetchMarkdown(file);
    const postElement = document.createElement('div');
    postElement.classList.add('post');
    postElement.innerHTML = postHtml;
    postsContainer.appendChild(postElement);
  }

  // 페이징 생성
  createPagination(postFiles.length, postsPerPage, page);
}

// 페이징 생성 함수
function createPagination(totalPosts, postsPerPage, currentPage) {
  const paginationContainer = document.getElementById('pagination');
  paginationContainer.innerHTML = ''; // 이전 페이징 초기화

  const totalPages = Math.ceil(totalPosts / postsPerPage);
  const maxVisiblePages = 5; // 한번에 보여줄 최대 페이지 수
  let startPage = Math.max(currentPage - Math.floor(maxVisiblePages / 2), 1);
  let endPage = Math.min(startPage + maxVisiblePages - 1, totalPages);

  if (endPage - startPage < maxVisiblePages - 1) {
    startPage = Math.max(endPage - maxVisiblePages + 1, 1);
  }

  if (currentPage > 1) {
    const prevButton = document.createElement('button');
    prevButton.innerHTML = '&laquo;';
    prevButton.classList.add('arrow');
    prevButton.addEventListener('click', () => createBlogPosts(currentPage - 1));
    paginationContainer.appendChild(prevButton);
  }

  for (let page = startPage; page <= endPage; page++) {
    const button = document.createElement('button');
    button.innerText = page;
    button.classList.add(page === currentPage ? 'disabled' : '');
    button.disabled = page === currentPage;
    button.addEventListener('click', () => createBlogPosts(page));
    paginationContainer.appendChild(button);
  }

  if (currentPage < totalPages) {
    const nextButton = document.createElement('button');
    nextButton.innerHTML = '&raquo;';
    nextButton.classList.add('arrow');
    nextButton.addEventListener('click', () => createBlogPosts(currentPage + 1));
    paginationContainer.appendChild(nextButton);
  }
}

// DOMContentLoaded 이벤트가 발생하면 createBlogPosts 함수 호출
document.addEventListener('DOMContentLoaded', () => {
  createBlogPosts();
});

Markdown 파일 예시 (post1.md)

# First Blog Post
*2024-06-01*

This is the summary of the first blog post. It contains an introduction and a few details about the post.

[Read more](post1.html)

주요 변경 사항 설명

  1. markdownToHtml 함수:
    • Markdown 파일의 첫 번째 제목(#), 날짜(*yyyy-mm-dd*), 그리고 첫 번째 단락의 일부만 추출하여 HTML로 변환합니다.
  • 제목, 날짜, 내용 일부를 HTML 구조로 반환합니다.
  1. fetchMarkdownFiles 함수:

    • 실제 서버에서 파일 목록을 받아와야 하지만, 예제에서는 하드코딩된 파일 목록을 사용합니다.
    • 서버에서 파일 목록을 가져오는 부분은 필요에 따라 구현합니다.
  2. createBlogPosts 함수:

    • 지정된 페이지의 Markdown 파일 목록을 읽어와서 각 게시물의 일부만 표시합니다.
    • postsPerPage로 페이지당 표시할 게시물 수를 설정합니다.
  3. createPagination 함수:

    • 전체 게시물 수에 따라 페이징 버튼을 생성합니다.
    • 각 버튼을 클릭하면 해당 페이지의 게시물이 표시됩니다.
    • 좌우 버튼을 통해 페이지 이동이 가능하도록 구현하였습니다.

이제 Blog 페이지는 assets/markdown/posts 디렉토리의 Markdown 파일에서 제목, 날짜, 내용 일부만 읽어와서 목록으로 표시하며, 페이지당 5개의 게시물을 표시하고, 페이징 기능을 제공합니다. 제목과 목록 부분이 분리되어 명확하게 보이도록 스타일을 설정했습니다.

반응형