Skip to content
Closed
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
25 changes: 25 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

- uses: actions/setup-node@v7
with:
node-version: 20
cache: npm

- run: npm ci

- run: npm run lint

- run: npm run format:check

- run: npm run typecheck
4 changes: 4 additions & 0 deletions .oxfmtrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"ignorePatterns": []
}
12 changes: 12 additions & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript", "unicorn", "oxc", "react", "jsx-a11y", "nextjs", "import"],
"categories": {
"correctness": "error"
},
"rules": {},
"env": {
"builtin": true
},
"ignorePatterns": [".next/**", "out/**", "build/**", "next-env.d.ts", "app/generated/**"]
}
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<!-- BEGIN:nextjs-agent-rules -->

# This is NOT the Next.js you know

This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
Expand Down
74 changes: 48 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,57 @@
```bash
npm run dev # 開発モードでプロジェクトを実行(これで動作等を確認する)
```

基本的に、localhost:3000 が開くはず

```bash
npm ci # ciとはclean installの略, package-lock.jsonの内容を基にパッケージ(再利用可能なプログラムのまとまりのこと)をインストール
```

(なお、インストール内容について、もし手動編集をする場合、package.jsonの方を編集し、package-lock.jsonは編集しないこと)

```bash
cp .env.example .env # .env.exampleの内容をコピーして自分用の.envを作成する
```

(`.env`ファイルは秘密情報を含みうるため`.gitignore`でGit管理から除外されている。`.env.example`はGit管理されているのでcloneすれば手元にあるはずだが、無ければ手動で作成すること)

```bash
npx prisma migrate dev # マイグレーション履歴(prisma/migrations)を基に、自分のローカル環境にDB(prisma/dev.db)を作成・更新する
```

(`prisma/dev.db`はSQLiteのDB実体ファイルで、複数人が同じファイルをGit管理すると変更が衝突して壊れるため`.gitignore`で除外されている。そのため各自が上記コマンドでローカルに作成する。他の人がスキーマ(`prisma/schema.prisma`)を変更してマイグレーションファイルを追加した場合、`git pull`した後に再度このコマンドを実行して自分のDBにも変更を反映すること)

```bash
git clone git@github.com:アカウント名/リポジトリ名.git # リモートリポジトリの内容を、自身のローカルリポジトリにクローンする
```

```bash
git pull origin main
# 自分のリポジトリにリモートリポジトリのmainブランチの変更(他の人が施したものなど)を取り込む
# git fetch(最新情報の取得)とgit merge(手元のデータへの統合)の2つの処理を同時に実行している
```

```bash
git branch # 現在いるブランチを確認
git branch # 現在いるブランチを確認
```

```bash
git switch ブランチ名 # ブランチを切り替える
```

```bash
git switch -c ブランチ名 # ブランチを新たに作成する
```

```bash
rm -rf リポジトリ名
# リポジトリを強制削除する
# どうしてもうまくいかなかったときは、一旦ローカルリポジトリを削除して再びクローンしてもよい(多分正攻法ではない)
```

## プロジェクト構成の大枠

- layout.tsx
- 共通するレイアウトのレンダリングを担っている
- {children}の中に各コンポーネントのレンダリングが集約されているイメージ
Expand All @@ -66,12 +81,16 @@ rm -rf リポジトリ名
- prismaディレクトリ
- Node.jsおよびTypeScript向けORMツールであるprismaを管理するディレクトリ
- ORM(オブジェクト関係マッピング): SQLを書かずにデータを操作する技術

## Next.jsについて

Next.jsとは、reactベースのWebアプリケーションフレームワークで、主に以下の2つの特徴がある。

- App Router: ディレクトリを使って画面の切り替えを行い、アプリを設計する機能
- Server Side Rendering(SSR): サーバー側でHTMLを作ってから表示する機能

### App Routerルーティング規則

App Routerのルーティングは、次の2つの軸で成り立っている。

- **フォルダ** … 階層がそのまま URL のパスになる
Expand All @@ -98,26 +117,28 @@ App Routerのルーティングは、次の2つの軸で成り立っている。

フォルダの階層がそのままURLになる。ただし中に`page`ファイルが必要

| フォルダ構造 | 対応するURL |
|---|---|
| `app/page.tsx` | `/` |
| `app/learn/page.tsx` | `/learn` |
| `app/learn/[language]/page.tsx` | `/learn/english` など |
| フォルダ構造 | 対応するURL |
| ----------------------------------------- | ----------------------------- |
| `app/page.tsx` | `/` |
| `app/learn/page.tsx` | `/learn` |
| `app/learn/[language]/page.tsx` | `/learn/english` など |
| `app/learn/[language]/[section]/page.tsx` | `/learn/english/grammar` など |

#### 2. 特殊ファイル(予約名)

ファイル名によってApp Routerにおける役割が変わる
拡張子`.tsx`/`.jsx`/`.js`(`page`は`.mdx`も可)は選べる
| ファイル名 | 役割 |
|---|---|
| `page` | そのルートの UI 本体。**これがないとアクセス不可** |
| `layout` | その階層以下で共有される枠。上位から入れ子に継承される |
| `loading` | ロード中の UI(内部でReact Suspense利用) |
| `error` | エラー時の UI(Error Boundary) |
| `not-found` | 404 の UI |
| `route` | API エンドポイント。`page`とは同じ階層に共存できない |
| `template` | 再マウントされる点以外は`layout`に近い |
それ以外のファイル(.tsなど)は「非公開ファイル(Colocation)」になり、そのフォルダ専用のコンポーネントやデータ処理関数を配置するのが一般的

| ファイル名 | 役割 |
| ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
| `page` | そのルートの UI 本体。**これがないとアクセス不可** |
| `layout` | その階層以下で共有される枠。上位から入れ子に継承される |
| `loading` | ロード中の UI(内部でReact Suspense利用) |
| `error` | エラー時の UI(Error Boundary) |
| `not-found` | 404 の UI |
| `route` | API エンドポイント。`page`とは同じ階層に共存できない |
| `template` | 再マウントされる点以外は`layout`に近い |
| それ以外のファイル(.tsなど)は「非公開ファイル(Colocation)」になり、そのフォルダ専用のコンポーネントやデータ処理関数を配置するのが一般的 |

##### layoutについて

Expand All @@ -128,22 +149,23 @@ App Routerのルーティングは、次の2つの軸で成り立っている。
#### 3. 動的ルート(角括弧の記法)

URL の一部が可変になる部分は角括弧
| 記法 | 名称 | マッチ例 | params の中身 |
|---|---|---|---|
| `[folder]` | 動的セグメント | `/learn/english` | `{ language: "english" }` |
| `[...folder]` | catch-all | `/shop/a/b` | `{ slug: ["a", "b"] }`(配列) |
| `[[...folder]]` | optional catch-all | `/shop` も含む | セグメント無しにもマッチ |

| 記法 | 名称 | マッチ例 | params の中身 |
| --------------- | ------------------ | ---------------- | ------------------------------ |
| `[folder]` | 動的セグメント | `/learn/english` | `{ language: "english" }` |
| `[...folder]` | catch-all | `/shop/a/b` | `{ slug: ["a", "b"] }`(配列) |
| `[[...folder]]` | optional catch-all | `/shop` も含む | セグメント無しにもマッチ |

#### 4. page.tsxのコンポーネントが受け取る引数

`page.tsx`のデフォルトエクスポート関数は Next.js から**`params`と`searchParams`を自動的に受け取る。

| 引数 | 中身 | 例 |
|---|---|---|
| `params` | 動的セグメント `[...]` | `/learn/english` → `{ language: "english" }` |
| `searchParams` | クエリ文字列 `?...` | `?level=beginner` → `{ level: "beginner" }` |
| 引数 | 中身 | 例 |
| -------------- | ---------------------- | -------------------------------------------- |
| `params` | 動的セグメント `[...]` | `/learn/english` → `{ language: "english" }` |
| `searchParams` | クエリ文字列 `?...` | `?level=beginner` → `{ level: "beginner" }` |

### サーバーサイドレンダリング

- 原則、ルーティング規則に含まれるものはデフォルトでSSRが適用される
- 'use client'は、「クライアントコンポーネント」を宣言するディレクティブであり、ファイルの一番上にこれを記述すると、useStateやuseEffectなどのReactフックが使えるようになる(この場合でもSSR自体は適用されるので、誤解しないよう注意)

3 changes: 1 addition & 2 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,6 @@ body {
}
}


@keyframes vb-scroll-left {
from {
transform: translateX(0);
Expand Down Expand Up @@ -255,4 +254,4 @@ body {
.vb-flow-row {
animation: none;
}
}
}
8 changes: 2 additions & 6 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ const geistMono = Geist_Mono({

export const metadata: Metadata = {
title: "Vocabook | 多次元単語帳アプリ",
description:
"自分だけの多次元単語帳を作って共有できる、多言語対応の暗記学習アプリ。",
description: "自分だけの多次元単語帳を作って共有できる、多言語対応の暗記学習アプリ。",
};

// ルートレイアウト
Expand All @@ -27,10 +26,7 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html
lang="ja"
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
>
<html lang="ja" className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}>
<body className="min-h-full flex flex-col">
<Header />
<div className="flex flex-1 flex-col">{children}</div>
Expand Down
8 changes: 4 additions & 4 deletions app/learn/chinese/01/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ export const title = "名詞の性の語尾による判別";

# 名詞の性の語尾による判別

*第1章 品詞と文型より*
_第1章 品詞と文型より_

ものの名詞は男性・女性を直観的に判断できないため、語尾によってある程度判別する。(例外もある)

| 語尾 | 性 |
|---|---|
| -age、-ment、-eau、-teur | 男性名詞 |
| 語尾 | 性 |
| ------------------------------- | -------- |
| -age、-ment、-eau、-teur | 男性名詞 |
| -tion、-sion、-té、-esse、-ette | 女性名詞 |
10 changes: 2 additions & 8 deletions app/learn/chinese/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,10 @@ import { getLanguage } from "@/app/learn/languages";
const language = getLanguage("chinese");

// 中国語セクション専用レイアウト(各言語ディレクトリの layout.tsx はファイル自体が必須)
export default function ChineseSectionLayout({
children,
}: {
children: React.ReactNode;
}) {
export default function ChineseSectionLayout({ children }: { children: React.ReactNode }) {
return (
<main className="flex flex-1 flex-col items-center px-6 py-16">
<p className="text-sm font-medium text-zinc-500 dark:text-zinc-400">
{language?.label}
</p>
<p className="text-sm font-medium text-zinc-500 dark:text-zinc-400">{language?.label}</p>
<div className="chinese-content mt-8 w-full max-w-3xl text-left">{children}</div>
</main>
);
Expand Down
18 changes: 10 additions & 8 deletions app/learn/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,21 @@ async function listSlugs(languageSlug: string): Promise<string[]> {
return [];
}

return entries
// filterによって、entries配列の中から、ディレクトリであり、かつ名前が数字のみで構成されているものを抽出する(layout.tsxを省くため)
.filter((entry) => entry.isDirectory() && /^\d+$/.test(entry.name))
// mapによって、抽出されたディレクトリの名前を配列として返す
.map((entry) => entry.name)
// sortによって、数字順に並び替える
.sort();
return (
entries
// filterによって、entries配列の中から、ディレクトリであり、かつ名前が数字のみで構成されているものを抽出する(layout.tsxを省くため)
.filter((entry) => entry.isDirectory() && /^\d+$/.test(entry.name))
// mapによって、抽出されたディレクトリの名前を配列として返す
.map((entry) => entry.name)
// sortによって、数字順に並び替える
.sort()
);
}

// 任意の言語について、セクションの番号配列を返す関数
export async function getSections(languageSlug: string): Promise<SectionSummary[]> {
const sectionSlugs = await listSlugs(languageSlug);

// Promise.all()は、複数のPromiseをまとめて"並列"で待ち、すべて完了したら結果を1つの配列で返す
return Promise.all(
sectionSlugs.map(async (sectionSlug) => {
Expand Down
18 changes: 9 additions & 9 deletions app/learn/french/01/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@ export const title = "冠詞の一覧と縮約形";

# 冠詞の一覧と縮約形

*第1章 品詞と文型より*
_第1章 品詞と文型より_

## 冠詞の種類

| | | | 男性形 | 女性形 |
|---|---|---|---|---|
| 可算 | 不定冠詞 | 単数形 | un | une |
| 可算 | 不定冠詞 | 複数形 | des | des |
| 可算 | 定冠詞 | 単数形 | le (l') | la (l') |
| 可算 | 定冠詞 | 複数形 | les | les |
| 不可算 | 部分冠詞 | - | du (de l') | de la (de l') |
| 不可算 | 定冠詞 | - | le (l') | la (l') |
| | | | 男性形 | 女性形 |
| ------ | -------- | ------ | ---------- | ------------- |
| 可算 | 不定冠詞 | 単数形 | un | une |
| 可算 | 不定冠詞 | 複数形 | des | des |
| 可算 | 定冠詞 | 単数形 | le (l') | la (l') |
| 可算 | 定冠詞 | 複数形 | les | les |
| 不可算 | 部分冠詞 | - | du (de l') | de la (de l') |
| 不可算 | 定冠詞 | - | le (l') | la (l') |

- 括弧内は直後の名詞が母音始まりの時に用いる。
- 「du」は「de le」の短縮版であり、部分冠詞は「de+定冠詞」で作られると考えてよい。
Expand Down
Loading
Loading