From d95065ecf81f51c4b682960a9f5973a98be9f67c Mon Sep 17 00:00:00 2001 From: Hiroshi Nishito Date: Fri, 17 Jan 2025 00:44:59 +0900 Subject: [PATCH 1/3] =?UTF-8?q?#18=20README=E4=BF=AE=E6=AD=A3:=20=E3=82=A2?= =?UTF-8?q?=E3=83=97=E3=83=AA=E3=82=A4=E3=83=A1=E3=83=BC=E3=82=B8=E3=81=AE?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index e12c896..f0bf8e2 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,16 @@ - **フロントエンド**: Vite + React をベースに、Markdownエディタや認証機能、UI等を提供 - **バックエンド**: AWS Lambda (Node.js) + API Gateway + DynamoDB のサーバーレス構成 +## デモアプリケーションイメージ +### (認証不要)Markdownエディタ画面 +![image](https://github.com/user-attachments/assets/c41ac2b7-c24a-405d-9653-bf599715891b) + +### (要認証)ドキュメント一覧画面 +![image](https://github.com/user-attachments/assets/99ea0435-ed2c-40a2-abad-9ec9094ea55a) + +### (認証不要)公開ドキュメント閲覧画面 +![](https://github.com/user-attachments/assets/4d0d0eb4-7aae-45d1-97d7-93aed02a5491) + ## 主な特徴 - **Markdown ドキュメントの作成・編集・公開** From 9d4732fe2b240a8a48c09cd447d3954be4f538ec Mon Sep 17 00:00:00 2001 From: Hiroshi Nishito Date: Fri, 17 Jan 2025 20:25:56 +0900 Subject: [PATCH 2/3] =?UTF-8?q?#36=20=E3=83=9A=E3=83=BC=E3=82=B8=E3=83=B3?= =?UTF-8?q?=E3=82=B0=E3=83=BB=E6=A4=9C=E7=B4=A2=E3=81=AE=E3=83=99=E3=83=BC?= =?UTF-8?q?=E3=82=B9=E6=88=90=E5=8A=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/DocsListPage.tsx | 99 ++++++++++++++++++++++++++--- 1 file changed, 89 insertions(+), 10 deletions(-) diff --git a/frontend/src/pages/DocsListPage.tsx b/frontend/src/pages/DocsListPage.tsx index de3572b..171d2e8 100644 --- a/frontend/src/pages/DocsListPage.tsx +++ b/frontend/src/pages/DocsListPage.tsx @@ -1,10 +1,9 @@ // DocsListPage.tsx -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, useMemo } from "react"; import { Link } from "react-router-dom"; import { useApiClient } from "../services/apiClient"; -import { useAuthContextSwitch as useAuthContext} from "../context/useAuthContextSwitch"; +import { useAuthContextSwitch as useAuthContext } from "../context/useAuthContextSwitch"; import { AxiosError } from "axios"; - import styles from "../styles/DocsListPage.module.scss"; const extractTitle = (markdown: string): string => { @@ -20,6 +19,14 @@ const extractTitle = (markdown: string): string => { const DocsListPage: React.FC = () => { const [documents, setDocuments] = useState([]); //eslint-disable-line const [error, setError] = useState(null); + + // ★ 検索用ステートを追加 + const [searchTerm, setSearchTerm] = useState(""); + + // ★ ページング用ステート + const [currentPage, setCurrentPage] = useState(1); + const pageSize = 10; // 1ページあたり5件表示など + const { user, isSignedIn } = useAuthContext(); const api = useApiClient(); @@ -48,14 +55,34 @@ const DocsListPage: React.FC = () => { } } }; + if (isSignedIn) { fetchDocs(); } }, [api, user, isSignedIn]); - if (error) { - return
{error}
; - } + // ★ 1) 検索フィルタリング:searchTerm を含むドキュメントだけを抽出 + // ここではタイトル or 本文(content)内に searchTerm が含まれるかざっくり判定 + const filteredDocs = useMemo(() => { + const lowerSearchTerm = searchTerm.toLowerCase(); + if (!lowerSearchTerm) return documents; + + return documents.filter((doc) => { + const title = extractTitle(doc.content).toLowerCase(); + const content = doc.content?.toLowerCase() || ""; + return ( + title.includes(lowerSearchTerm) || content.includes(lowerSearchTerm) + ); + }); + }, [documents, searchTerm]); + + // ★ 2) ページング: filteredDocs を currentPage, pageSize に基づいて slice + const totalPages = Math.ceil(filteredDocs.length / pageSize); + const paginatedDocs = useMemo(() => { + const startIndex = (currentPage - 1) * pageSize; + const endIndex = startIndex + pageSize; + return filteredDocs.slice(startIndex, endIndex); + }, [filteredDocs, currentPage]); // 「共有」ボタン押下時の挙動例 const handleShare = (slug: string) => { @@ -64,12 +91,19 @@ const DocsListPage: React.FC = () => { alert("公開URLをクリップボードにコピーしました!\n" + publicUrl); }; + if (error) { + return
{error}
; + } + + // -------------------------- + // レンダリング + // -------------------------- return ( -
+
{isSignedIn ? ( <> + {/* 新規ドキュメントボタン */}
- {/* 新規ドキュメントは "/" に */} {

ドキュメント一覧

+ {/* ★ 検索入力フォーム */} +
+ { + setSearchTerm(e.target.value); + // ページを戻す(必要なら1ページ目に戻す) + setCurrentPage(1); + }} + style={{ + padding: "8px", + borderRadius: "4px", + border: "1px solid #ccc", + width: "240px", + }} + /> +
+ + {/* 一覧テーブル */} @@ -99,7 +154,7 @@ const DocsListPage: React.FC = () => { - {documents.map((doc) => { + {paginatedDocs.map((doc) => { const title = extractTitle(doc.content); const isPublic = doc.isPublic; return ( @@ -126,7 +181,10 @@ const DocsListPage: React.FC = () => { {/* 共有ボタン(公開時のみ) */}
{isPublic ? ( - ) : ( @@ -138,6 +196,27 @@ const DocsListPage: React.FC = () => { })}
+ + {/* ★ ページングナビゲーション */} + {filteredDocs.length > 0 && ( +
+ +
+ ページ {currentPage} / {totalPages} +
+ +
+ )} ) : (
ログインしてください
From 57ad148f28edcbef9463a433a7abad175a6aa54f Mon Sep 17 00:00:00 2001 From: Hiroshi Nishito Date: Fri, 17 Jan 2025 20:32:42 +0900 Subject: [PATCH 3/3] =?UTF-8?q?#36=20=E3=83=9A=E3=83=BC=E3=82=B8=E3=83=B3?= =?UTF-8?q?=E3=82=B0=E3=83=9C=E3=82=BF=E3=83=B3=E3=82=92=E5=8B=95=E7=9A=84?= =?UTF-8?q?=E3=81=AB=E8=A1=A8=E7=A4=BA=E3=83=BB=E9=9D=9E=E8=A1=A8=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 実際に前、次のページがあるときにしか、ボタンを表示しない。 --- frontend/src/pages/DocsListPage.tsx | 54 +++++++++++++---------------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/frontend/src/pages/DocsListPage.tsx b/frontend/src/pages/DocsListPage.tsx index 171d2e8..48b940b 100644 --- a/frontend/src/pages/DocsListPage.tsx +++ b/frontend/src/pages/DocsListPage.tsx @@ -1,4 +1,3 @@ -// DocsListPage.tsx import React, { useEffect, useState, useMemo } from "react"; import { Link } from "react-router-dom"; import { useApiClient } from "../services/apiClient"; @@ -25,7 +24,7 @@ const DocsListPage: React.FC = () => { // ★ ページング用ステート const [currentPage, setCurrentPage] = useState(1); - const pageSize = 10; // 1ページあたり5件表示など + const pageSize = 15; // 1ページあたりの表示件数 const { user, isSignedIn } = useAuthContext(); const api = useApiClient(); @@ -62,7 +61,6 @@ const DocsListPage: React.FC = () => { }, [api, user, isSignedIn]); // ★ 1) 検索フィルタリング:searchTerm を含むドキュメントだけを抽出 - // ここではタイトル or 本文(content)内に searchTerm が含まれるかざっくり判定 const filteredDocs = useMemo(() => { const lowerSearchTerm = searchTerm.toLowerCase(); if (!lowerSearchTerm) return documents; @@ -76,14 +74,23 @@ const DocsListPage: React.FC = () => { }); }, [documents, searchTerm]); - // ★ 2) ページング: filteredDocs を currentPage, pageSize に基づいて slice + // ★ 2) ページング const totalPages = Math.ceil(filteredDocs.length / pageSize); + const paginatedDocs = useMemo(() => { const startIndex = (currentPage - 1) * pageSize; const endIndex = startIndex + pageSize; return filteredDocs.slice(startIndex, endIndex); }, [filteredDocs, currentPage]); + // 前へ/次へボタンの挙動 + const goToPrevious = () => { + setCurrentPage((prev) => Math.max(prev - 1, 1)); + }; + const goToNext = () => { + setCurrentPage((prev) => Math.min(prev + 1, totalPages)); + }; + // 「共有」ボタン押下時の挙動例 const handleShare = (slug: string) => { const publicUrl = `${window.location.origin}/documents/${slug}`; @@ -95,14 +102,10 @@ const DocsListPage: React.FC = () => { return
{error}
; } - // -------------------------- - // レンダリング - // -------------------------- return (
{isSignedIn ? ( <> - {/* 新規ドキュメントボタン */}
{

ドキュメント一覧

- {/* ★ 検索入力フォーム */} + {/* ★ 検索入力欄 */}
{ value={searchTerm} onChange={(e) => { setSearchTerm(e.target.value); - // ページを戻す(必要なら1ページ目に戻す) - setCurrentPage(1); + setCurrentPage(1); // 検索語が変わったら1ページ目に戻す }} style={{ padding: "8px", @@ -140,7 +142,6 @@ const DocsListPage: React.FC = () => { />
- {/* 一覧テーブル */} @@ -159,7 +160,6 @@ const DocsListPage: React.FC = () => { const isPublic = doc.isPublic; return ( - {/* タイトル */} - - {/* 公開/非公開 */} - - {/* 共有ボタン(公開時のみ) */}
{ {title} {isPublic ? ( 公開 @@ -177,8 +175,6 @@ const DocsListPage: React.FC = () => { 非公開 )} {isPublic ? (
{/* ★ ページングナビゲーション */} - {filteredDocs.length > 0 && ( + {filteredDocs.length > 0 && totalPages > 1 && (
- + {/* 前へボタン: currentPage>1 のときだけ表示 */} + {currentPage > 1 && ( + + )} +
ページ {currentPage} / {totalPages}
- + + {/* 次へボタン: currentPage次へ + )}
)}