김민용 commited on
Commit
c5d0166
·
1 Parent(s): c1ac65c

feat: Next.js 기반 마크다운 포털로 전체 리뉴얼

Browse files
README.md CHANGED
@@ -1,36 +1,57 @@
1
- This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
2
 
3
- ## Getting Started
4
 
5
- First, run the development server:
6
 
7
- ```bash
8
- npm run dev
9
- # or
10
- yarn dev
11
- # or
12
- pnpm dev
13
- # or
14
- bun dev
15
- ```
16
 
17
- Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18
 
19
- You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
 
 
 
 
20
 
21
- This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22
 
23
- ## Learn More
 
 
24
 
25
- To learn more about Next.js, take a look at the following resources:
 
26
 
27
- - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28
- - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
- You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31
 
32
- ## Deploy on Vercel
33
 
34
- The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35
 
36
- Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
 
 
1
+ # AI Tree Portal
2
 
3
+ Next.js 기반의 마크다운 문서 관리 포털입니다.
4
 
5
+ ## 주요 기능
6
 
7
+ - ✍️ 마크다운 기반 문서 작성
8
+ - 🏷️ 태그 및 카테고리 관리
9
+ - 🔍 전체 텍스트 검색
10
+ - 📊 JSON-LD 구조화 데이터 지원
11
+ - 📝 Git 기반 버전 관리
12
+ - 변경 이력 추적
13
+ - 문서 버전 비교
14
+ - 롤백 기능
 
15
 
16
+ ## 기술 스택
17
 
18
+ - **Frontend**: Next.js 15, React, TypeScript, Tailwind CSS
19
+ - **Backend**: Next.js API Routes
20
+ - **데이터 저장**: 마크다운 파일, Git
21
+ - **검색**: 전체 텍스트 검색, 태그/카테고리 필터링
22
+ - **SEO**: JSON-LD, 동적 메타데이터
23
 
24
+ ## 설치 실행
25
 
26
+ ```bash
27
+ # 의존성 설치
28
+ npm install
29
 
30
+ # 개발 서버 실행
31
+ npm run dev
32
 
33
+ # 프로덕션 빌드
34
+ npm run build
35
+ npm start
36
+ ```
37
+
38
+ ## 프로젝트 구조
39
+
40
+ ```
41
+ src/
42
+ ├── app/ # Next.js 페이지 및 레이아웃
43
+ ├── components/ # React 컴포넌트
44
+ ├── lib/ # 유틸리티 함수
45
+ └── types/ # TypeScript 타입 정의
46
+
47
+ content/ # 마크다운 문서 저장소
48
+ ```
49
 
50
+ ## 라이선스
51
 
52
+ MIT License
53
 
54
+ ## 작성자
55
 
56
+ - richard-kim-79
57
+ - Email: [email protected]
content ADDED
@@ -0,0 +1 @@
 
 
1
+ Subproject commit 794f345c1724b059684761fc8d9f356b60eb69b5
package-lock.json CHANGED
@@ -8,9 +8,16 @@
8
  "name": "aitree_1",
9
  "version": "0.1.0",
10
  "dependencies": {
 
 
 
 
 
11
  "next": "15.2.4",
12
  "react": "^19.0.0",
13
- "react-dom": "^19.0.0"
 
 
14
  },
15
  "devDependencies": {
16
  "@eslint/eslintrc": "^3",
@@ -1119,6 +1126,21 @@
1119
  "tailwindcss": "4.1.3"
1120
  }
1121
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1122
  "node_modules/@tybys/wasm-util": {
1123
  "version": "0.9.0",
1124
  "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz",
@@ -1130,6 +1152,15 @@
1130
  "tslib": "^2.4.0"
1131
  }
1132
  },
 
 
 
 
 
 
 
 
 
1133
  "node_modules/@types/estree": {
1134
  "version": "1.0.7",
1135
  "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
@@ -1137,6 +1168,15 @@
1137
  "dev": true,
1138
  "license": "MIT"
1139
  },
 
 
 
 
 
 
 
 
 
1140
  "node_modules/@types/json-schema": {
1141
  "version": "7.0.15",
1142
  "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -1151,6 +1191,27 @@
1151
  "dev": true,
1152
  "license": "MIT"
1153
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1154
  "node_modules/@types/node": {
1155
  "version": "20.17.30",
1156
  "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.30.tgz",
@@ -1181,6 +1242,12 @@
1181
  "@types/react": "^19.0.0"
1182
  }
1183
  },
 
 
 
 
 
 
1184
  "node_modules/@typescript-eslint/eslint-plugin": {
1185
  "version": "8.29.0",
1186
  "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.0.tgz",
@@ -1417,6 +1484,12 @@
1417
  "url": "https://opencollective.com/typescript-eslint"
1418
  }
1419
  },
 
 
 
 
 
 
1420
  "node_modules/@unrs/resolver-binding-darwin-arm64": {
1421
  "version": "1.3.3",
1422
  "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.3.3.tgz",
@@ -1914,6 +1987,16 @@
1914
  "node": ">= 0.4"
1915
  }
1916
  },
 
 
 
 
 
 
 
 
 
 
1917
  "node_modules/balanced-match": {
1918
  "version": "1.0.2",
1919
  "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -2036,6 +2119,16 @@
2036
  ],
2037
  "license": "CC-BY-4.0"
2038
  },
 
 
 
 
 
 
 
 
 
 
2039
  "node_modules/chalk": {
2040
  "version": "4.1.2",
2041
  "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -2053,6 +2146,36 @@
2053
  "url": "https://github.com/chalk/chalk?sponsor=1"
2054
  }
2055
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2056
  "node_modules/client-only": {
2057
  "version": "0.0.1",
2058
  "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
@@ -2104,6 +2227,16 @@
2104
  "simple-swizzle": "^0.2.2"
2105
  }
2106
  },
 
 
 
 
 
 
 
 
 
 
2107
  "node_modules/concat-map": {
2108
  "version": "0.0.1",
2109
  "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -2126,6 +2259,18 @@
2126
  "node": ">= 8"
2127
  }
2128
  },
 
 
 
 
 
 
 
 
 
 
 
 
2129
  "node_modules/csstype": {
2130
  "version": "3.1.3",
2131
  "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
@@ -2194,11 +2339,20 @@
2194
  "url": "https://github.com/sponsors/ljharb"
2195
  }
2196
  },
 
 
 
 
 
 
 
 
 
 
2197
  "node_modules/debug": {
2198
  "version": "4.4.0",
2199
  "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
2200
  "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
2201
- "dev": true,
2202
  "license": "MIT",
2203
  "dependencies": {
2204
  "ms": "^2.1.3"
@@ -2212,6 +2366,19 @@
2212
  }
2213
  }
2214
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
2215
  "node_modules/deep-is": {
2216
  "version": "0.1.4",
2217
  "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -2255,6 +2422,15 @@
2255
  "url": "https://github.com/sponsors/ljharb"
2256
  }
2257
  },
 
 
 
 
 
 
 
 
 
2258
  "node_modules/detect-libc": {
2259
  "version": "2.0.3",
2260
  "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
@@ -2265,6 +2441,19 @@
2265
  "node": ">=8"
2266
  }
2267
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
2268
  "node_modules/doctrine": {
2269
  "version": "2.1.0",
2270
  "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
@@ -2881,6 +3070,19 @@
2881
  "url": "https://opencollective.com/eslint"
2882
  }
2883
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
2884
  "node_modules/esquery": {
2885
  "version": "1.6.0",
2886
  "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
@@ -2927,6 +3129,24 @@
2927
  "node": ">=0.10.0"
2928
  }
2929
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2930
  "node_modules/fast-deep-equal": {
2931
  "version": "3.1.3",
2932
  "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -3249,6 +3469,43 @@
3249
  "dev": true,
3250
  "license": "MIT"
3251
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3252
  "node_modules/has-bigints": {
3253
  "version": "1.1.0",
3254
  "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
@@ -3343,6 +3600,67 @@
3343
  "node": ">= 0.4"
3344
  }
3345
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3346
  "node_modules/ignore": {
3347
  "version": "5.3.2",
3348
  "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -3547,6 +3865,15 @@
3547
  "url": "https://github.com/sponsors/ljharb"
3548
  }
3549
  },
 
 
 
 
 
 
 
 
 
3550
  "node_modules/is-extglob": {
3551
  "version": "2.1.1",
3552
  "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -3645,6 +3972,18 @@
3645
  "url": "https://github.com/sponsors/ljharb"
3646
  }
3647
  },
 
 
 
 
 
 
 
 
 
 
 
 
3648
  "node_modules/is-regex": {
3649
  "version": "1.2.1",
3650
  "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
@@ -3912,6 +4251,15 @@
3912
  "json-buffer": "3.0.1"
3913
  }
3914
  },
 
 
 
 
 
 
 
 
 
3915
  "node_modules/language-subtag-registry": {
3916
  "version": "0.3.23",
3917
  "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz",
@@ -4201,13 +4549,40 @@
4201
  "url": "https://github.com/sponsors/sindresorhus"
4202
  }
4203
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4204
  "node_modules/lodash.merge": {
4205
  "version": "4.6.2",
4206
  "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
4207
  "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
4208
- "dev": true,
4209
  "license": "MIT"
4210
  },
 
 
 
 
 
 
 
 
 
 
4211
  "node_modules/loose-envify": {
4212
  "version": "1.4.0",
4213
  "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -4231,61 +4606,595 @@
4231
  "node": ">= 0.4"
4232
  }
4233
  },
4234
- "node_modules/merge2": {
4235
- "version": "1.4.1",
4236
- "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
4237
- "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
4238
- "dev": true,
4239
  "license": "MIT",
4240
- "engines": {
4241
- "node": ">= 8"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4242
  }
4243
  },
4244
- "node_modules/micromatch": {
4245
- "version": "4.0.8",
4246
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
4247
- "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
4248
- "dev": true,
4249
  "license": "MIT",
4250
  "dependencies": {
4251
- "braces": "^3.0.3",
4252
- "picomatch": "^2.3.1"
4253
  },
4254
- "engines": {
4255
- "node": ">=8.6"
 
4256
  }
4257
  },
4258
- "node_modules/minimatch": {
4259
- "version": "3.1.2",
4260
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
4261
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
4262
- "dev": true,
4263
- "license": "ISC",
4264
  "dependencies": {
4265
- "brace-expansion": "^1.1.7"
 
 
 
 
 
 
 
 
4266
  },
4267
- "engines": {
4268
- "node": "*"
 
4269
  }
4270
  },
4271
- "node_modules/minimist": {
4272
- "version": "1.2.8",
4273
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
4274
- "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
4275
- "dev": true,
4276
  "license": "MIT",
 
 
 
 
 
 
 
 
 
 
 
4277
  "funding": {
4278
- "url": "https://github.com/sponsors/ljharb"
 
4279
  }
4280
  },
4281
- "node_modules/ms": {
4282
- "version": "2.1.3",
4283
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
4284
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
4285
- "dev": true,
4286
- "license": "MIT"
4287
- },
4288
- "node_modules/nanoid": {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4289
  "version": "3.3.11",
4290
  "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
4291
  "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
@@ -4681,6 +5590,19 @@
4681
  "node": "^10 || ^12 || >=14"
4682
  }
4683
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
4684
  "node_modules/prelude-ls": {
4685
  "version": "1.2.1",
4686
  "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -4703,6 +5625,16 @@
4703
  "react-is": "^16.13.1"
4704
  }
4705
  },
 
 
 
 
 
 
 
 
 
 
4706
  "node_modules/punycode": {
4707
  "version": "2.3.1",
4708
  "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -4806,6 +5738,70 @@
4806
  "url": "https://github.com/sponsors/ljharb"
4807
  }
4808
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4809
  "node_modules/resolve": {
4810
  "version": "1.22.10",
4811
  "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
@@ -4943,6 +5939,19 @@
4943
  "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
4944
  "license": "MIT"
4945
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
4946
  "node_modules/semver": {
4947
  "version": "7.7.1",
4948
  "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
@@ -5163,6 +6172,22 @@
5163
  "node": ">=0.10.0"
5164
  }
5165
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5166
  "node_modules/stable-hash": {
5167
  "version": "0.0.5",
5168
  "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz",
@@ -5291,6 +6316,20 @@
5291
  "url": "https://github.com/sponsors/ljharb"
5292
  }
5293
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5294
  "node_modules/strip-bom": {
5295
  "version": "3.0.0",
5296
  "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
@@ -5301,6 +6340,15 @@
5301
  "node": ">=4"
5302
  }
5303
  },
 
 
 
 
 
 
 
 
 
5304
  "node_modules/strip-json-comments": {
5305
  "version": "3.1.1",
5306
  "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@@ -5367,7 +6415,6 @@
5367
  "version": "4.1.3",
5368
  "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.3.tgz",
5369
  "integrity": "sha512-2Q+rw9vy1WFXu5cIxlvsabCwhU2qUwodGq03ODhLJ0jW4ek5BUtoCsnLB0qG+m8AHgEsSJcJGDSDe06FXlP74g==",
5370
- "dev": true,
5371
  "license": "MIT"
5372
  },
5373
  "node_modules/tapable": {
@@ -5438,6 +6485,26 @@
5438
  "node": ">=8.0"
5439
  }
5440
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5441
  "node_modules/ts-api-utils": {
5442
  "version": "2.1.0",
5443
  "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
@@ -5601,6 +6668,93 @@
5601
  "dev": true,
5602
  "license": "MIT"
5603
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5604
  "node_modules/unrs-resolver": {
5605
  "version": "1.3.3",
5606
  "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.3.3.tgz",
@@ -5638,6 +6792,40 @@
5638
  "punycode": "^2.1.0"
5639
  }
5640
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5641
  "node_modules/which": {
5642
  "version": "2.0.2",
5643
  "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -5765,6 +6953,16 @@
5765
  "funding": {
5766
  "url": "https://github.com/sponsors/sindresorhus"
5767
  }
 
 
 
 
 
 
 
 
 
 
5768
  }
5769
  }
5770
  }
 
8
  "name": "aitree_1",
9
  "version": "0.1.0",
10
  "dependencies": {
11
+ "@tailwindcss/typography": "^0.5.16",
12
+ "@types/lodash": "^4.17.16",
13
+ "date-fns": "^4.1.0",
14
+ "gray-matter": "^4.0.3",
15
+ "lodash": "^4.17.21",
16
  "next": "15.2.4",
17
  "react": "^19.0.0",
18
+ "react-dom": "^19.0.0",
19
+ "remark": "^15.0.1",
20
+ "remark-html": "^16.0.1"
21
  },
22
  "devDependencies": {
23
  "@eslint/eslintrc": "^3",
 
1126
  "tailwindcss": "4.1.3"
1127
  }
1128
  },
1129
+ "node_modules/@tailwindcss/typography": {
1130
+ "version": "0.5.16",
1131
+ "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz",
1132
+ "integrity": "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==",
1133
+ "license": "MIT",
1134
+ "dependencies": {
1135
+ "lodash.castarray": "^4.4.0",
1136
+ "lodash.isplainobject": "^4.0.6",
1137
+ "lodash.merge": "^4.6.2",
1138
+ "postcss-selector-parser": "6.0.10"
1139
+ },
1140
+ "peerDependencies": {
1141
+ "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
1142
+ }
1143
+ },
1144
  "node_modules/@tybys/wasm-util": {
1145
  "version": "0.9.0",
1146
  "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz",
 
1152
  "tslib": "^2.4.0"
1153
  }
1154
  },
1155
+ "node_modules/@types/debug": {
1156
+ "version": "4.1.12",
1157
+ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
1158
+ "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
1159
+ "license": "MIT",
1160
+ "dependencies": {
1161
+ "@types/ms": "*"
1162
+ }
1163
+ },
1164
  "node_modules/@types/estree": {
1165
  "version": "1.0.7",
1166
  "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
 
1168
  "dev": true,
1169
  "license": "MIT"
1170
  },
1171
+ "node_modules/@types/hast": {
1172
+ "version": "3.0.4",
1173
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
1174
+ "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
1175
+ "license": "MIT",
1176
+ "dependencies": {
1177
+ "@types/unist": "*"
1178
+ }
1179
+ },
1180
  "node_modules/@types/json-schema": {
1181
  "version": "7.0.15",
1182
  "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
 
1191
  "dev": true,
1192
  "license": "MIT"
1193
  },
1194
+ "node_modules/@types/lodash": {
1195
+ "version": "4.17.16",
1196
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.16.tgz",
1197
+ "integrity": "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==",
1198
+ "license": "MIT"
1199
+ },
1200
+ "node_modules/@types/mdast": {
1201
+ "version": "4.0.4",
1202
+ "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
1203
+ "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
1204
+ "license": "MIT",
1205
+ "dependencies": {
1206
+ "@types/unist": "*"
1207
+ }
1208
+ },
1209
+ "node_modules/@types/ms": {
1210
+ "version": "2.1.0",
1211
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
1212
+ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
1213
+ "license": "MIT"
1214
+ },
1215
  "node_modules/@types/node": {
1216
  "version": "20.17.30",
1217
  "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.30.tgz",
 
1242
  "@types/react": "^19.0.0"
1243
  }
1244
  },
1245
+ "node_modules/@types/unist": {
1246
+ "version": "3.0.3",
1247
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
1248
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
1249
+ "license": "MIT"
1250
+ },
1251
  "node_modules/@typescript-eslint/eslint-plugin": {
1252
  "version": "8.29.0",
1253
  "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.0.tgz",
 
1484
  "url": "https://opencollective.com/typescript-eslint"
1485
  }
1486
  },
1487
+ "node_modules/@ungap/structured-clone": {
1488
+ "version": "1.3.0",
1489
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
1490
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
1491
+ "license": "ISC"
1492
+ },
1493
  "node_modules/@unrs/resolver-binding-darwin-arm64": {
1494
  "version": "1.3.3",
1495
  "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.3.3.tgz",
 
1987
  "node": ">= 0.4"
1988
  }
1989
  },
1990
+ "node_modules/bail": {
1991
+ "version": "2.0.2",
1992
+ "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
1993
+ "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==",
1994
+ "license": "MIT",
1995
+ "funding": {
1996
+ "type": "github",
1997
+ "url": "https://github.com/sponsors/wooorm"
1998
+ }
1999
+ },
2000
  "node_modules/balanced-match": {
2001
  "version": "1.0.2",
2002
  "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
 
2119
  ],
2120
  "license": "CC-BY-4.0"
2121
  },
2122
+ "node_modules/ccount": {
2123
+ "version": "2.0.1",
2124
+ "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
2125
+ "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
2126
+ "license": "MIT",
2127
+ "funding": {
2128
+ "type": "github",
2129
+ "url": "https://github.com/sponsors/wooorm"
2130
+ }
2131
+ },
2132
  "node_modules/chalk": {
2133
  "version": "4.1.2",
2134
  "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
 
2146
  "url": "https://github.com/chalk/chalk?sponsor=1"
2147
  }
2148
  },
2149
+ "node_modules/character-entities": {
2150
+ "version": "2.0.2",
2151
+ "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
2152
+ "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==",
2153
+ "license": "MIT",
2154
+ "funding": {
2155
+ "type": "github",
2156
+ "url": "https://github.com/sponsors/wooorm"
2157
+ }
2158
+ },
2159
+ "node_modules/character-entities-html4": {
2160
+ "version": "2.1.0",
2161
+ "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
2162
+ "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==",
2163
+ "license": "MIT",
2164
+ "funding": {
2165
+ "type": "github",
2166
+ "url": "https://github.com/sponsors/wooorm"
2167
+ }
2168
+ },
2169
+ "node_modules/character-entities-legacy": {
2170
+ "version": "3.0.0",
2171
+ "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
2172
+ "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
2173
+ "license": "MIT",
2174
+ "funding": {
2175
+ "type": "github",
2176
+ "url": "https://github.com/sponsors/wooorm"
2177
+ }
2178
+ },
2179
  "node_modules/client-only": {
2180
  "version": "0.0.1",
2181
  "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
 
2227
  "simple-swizzle": "^0.2.2"
2228
  }
2229
  },
2230
+ "node_modules/comma-separated-tokens": {
2231
+ "version": "2.0.3",
2232
+ "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
2233
+ "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
2234
+ "license": "MIT",
2235
+ "funding": {
2236
+ "type": "github",
2237
+ "url": "https://github.com/sponsors/wooorm"
2238
+ }
2239
+ },
2240
  "node_modules/concat-map": {
2241
  "version": "0.0.1",
2242
  "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
 
2259
  "node": ">= 8"
2260
  }
2261
  },
2262
+ "node_modules/cssesc": {
2263
+ "version": "3.0.0",
2264
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
2265
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
2266
+ "license": "MIT",
2267
+ "bin": {
2268
+ "cssesc": "bin/cssesc"
2269
+ },
2270
+ "engines": {
2271
+ "node": ">=4"
2272
+ }
2273
+ },
2274
  "node_modules/csstype": {
2275
  "version": "3.1.3",
2276
  "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
 
2339
  "url": "https://github.com/sponsors/ljharb"
2340
  }
2341
  },
2342
+ "node_modules/date-fns": {
2343
+ "version": "4.1.0",
2344
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
2345
+ "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
2346
+ "license": "MIT",
2347
+ "funding": {
2348
+ "type": "github",
2349
+ "url": "https://github.com/sponsors/kossnocorp"
2350
+ }
2351
+ },
2352
  "node_modules/debug": {
2353
  "version": "4.4.0",
2354
  "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
2355
  "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
 
2356
  "license": "MIT",
2357
  "dependencies": {
2358
  "ms": "^2.1.3"
 
2366
  }
2367
  }
2368
  },
2369
+ "node_modules/decode-named-character-reference": {
2370
+ "version": "1.1.0",
2371
+ "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.1.0.tgz",
2372
+ "integrity": "sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==",
2373
+ "license": "MIT",
2374
+ "dependencies": {
2375
+ "character-entities": "^2.0.0"
2376
+ },
2377
+ "funding": {
2378
+ "type": "github",
2379
+ "url": "https://github.com/sponsors/wooorm"
2380
+ }
2381
+ },
2382
  "node_modules/deep-is": {
2383
  "version": "0.1.4",
2384
  "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
 
2422
  "url": "https://github.com/sponsors/ljharb"
2423
  }
2424
  },
2425
+ "node_modules/dequal": {
2426
+ "version": "2.0.3",
2427
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
2428
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
2429
+ "license": "MIT",
2430
+ "engines": {
2431
+ "node": ">=6"
2432
+ }
2433
+ },
2434
  "node_modules/detect-libc": {
2435
  "version": "2.0.3",
2436
  "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
 
2441
  "node": ">=8"
2442
  }
2443
  },
2444
+ "node_modules/devlop": {
2445
+ "version": "1.1.0",
2446
+ "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
2447
+ "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
2448
+ "license": "MIT",
2449
+ "dependencies": {
2450
+ "dequal": "^2.0.0"
2451
+ },
2452
+ "funding": {
2453
+ "type": "github",
2454
+ "url": "https://github.com/sponsors/wooorm"
2455
+ }
2456
+ },
2457
  "node_modules/doctrine": {
2458
  "version": "2.1.0",
2459
  "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
 
3070
  "url": "https://opencollective.com/eslint"
3071
  }
3072
  },
3073
+ "node_modules/esprima": {
3074
+ "version": "4.0.1",
3075
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
3076
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
3077
+ "license": "BSD-2-Clause",
3078
+ "bin": {
3079
+ "esparse": "bin/esparse.js",
3080
+ "esvalidate": "bin/esvalidate.js"
3081
+ },
3082
+ "engines": {
3083
+ "node": ">=4"
3084
+ }
3085
+ },
3086
  "node_modules/esquery": {
3087
  "version": "1.6.0",
3088
  "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
 
3129
  "node": ">=0.10.0"
3130
  }
3131
  },
3132
+ "node_modules/extend": {
3133
+ "version": "3.0.2",
3134
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
3135
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
3136
+ "license": "MIT"
3137
+ },
3138
+ "node_modules/extend-shallow": {
3139
+ "version": "2.0.1",
3140
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
3141
+ "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
3142
+ "license": "MIT",
3143
+ "dependencies": {
3144
+ "is-extendable": "^0.1.0"
3145
+ },
3146
+ "engines": {
3147
+ "node": ">=0.10.0"
3148
+ }
3149
+ },
3150
  "node_modules/fast-deep-equal": {
3151
  "version": "3.1.3",
3152
  "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
 
3469
  "dev": true,
3470
  "license": "MIT"
3471
  },
3472
+ "node_modules/gray-matter": {
3473
+ "version": "4.0.3",
3474
+ "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
3475
+ "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
3476
+ "license": "MIT",
3477
+ "dependencies": {
3478
+ "js-yaml": "^3.13.1",
3479
+ "kind-of": "^6.0.2",
3480
+ "section-matter": "^1.0.0",
3481
+ "strip-bom-string": "^1.0.0"
3482
+ },
3483
+ "engines": {
3484
+ "node": ">=6.0"
3485
+ }
3486
+ },
3487
+ "node_modules/gray-matter/node_modules/argparse": {
3488
+ "version": "1.0.10",
3489
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
3490
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
3491
+ "license": "MIT",
3492
+ "dependencies": {
3493
+ "sprintf-js": "~1.0.2"
3494
+ }
3495
+ },
3496
+ "node_modules/gray-matter/node_modules/js-yaml": {
3497
+ "version": "3.14.1",
3498
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
3499
+ "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
3500
+ "license": "MIT",
3501
+ "dependencies": {
3502
+ "argparse": "^1.0.7",
3503
+ "esprima": "^4.0.0"
3504
+ },
3505
+ "bin": {
3506
+ "js-yaml": "bin/js-yaml.js"
3507
+ }
3508
+ },
3509
  "node_modules/has-bigints": {
3510
  "version": "1.1.0",
3511
  "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
 
3600
  "node": ">= 0.4"
3601
  }
3602
  },
3603
+ "node_modules/hast-util-sanitize": {
3604
+ "version": "5.0.2",
3605
+ "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-5.0.2.tgz",
3606
+ "integrity": "sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==",
3607
+ "license": "MIT",
3608
+ "dependencies": {
3609
+ "@types/hast": "^3.0.0",
3610
+ "@ungap/structured-clone": "^1.0.0",
3611
+ "unist-util-position": "^5.0.0"
3612
+ },
3613
+ "funding": {
3614
+ "type": "opencollective",
3615
+ "url": "https://opencollective.com/unified"
3616
+ }
3617
+ },
3618
+ "node_modules/hast-util-to-html": {
3619
+ "version": "9.0.5",
3620
+ "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz",
3621
+ "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==",
3622
+ "license": "MIT",
3623
+ "dependencies": {
3624
+ "@types/hast": "^3.0.0",
3625
+ "@types/unist": "^3.0.0",
3626
+ "ccount": "^2.0.0",
3627
+ "comma-separated-tokens": "^2.0.0",
3628
+ "hast-util-whitespace": "^3.0.0",
3629
+ "html-void-elements": "^3.0.0",
3630
+ "mdast-util-to-hast": "^13.0.0",
3631
+ "property-information": "^7.0.0",
3632
+ "space-separated-tokens": "^2.0.0",
3633
+ "stringify-entities": "^4.0.0",
3634
+ "zwitch": "^2.0.4"
3635
+ },
3636
+ "funding": {
3637
+ "type": "opencollective",
3638
+ "url": "https://opencollective.com/unified"
3639
+ }
3640
+ },
3641
+ "node_modules/hast-util-whitespace": {
3642
+ "version": "3.0.0",
3643
+ "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
3644
+ "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
3645
+ "license": "MIT",
3646
+ "dependencies": {
3647
+ "@types/hast": "^3.0.0"
3648
+ },
3649
+ "funding": {
3650
+ "type": "opencollective",
3651
+ "url": "https://opencollective.com/unified"
3652
+ }
3653
+ },
3654
+ "node_modules/html-void-elements": {
3655
+ "version": "3.0.0",
3656
+ "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz",
3657
+ "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==",
3658
+ "license": "MIT",
3659
+ "funding": {
3660
+ "type": "github",
3661
+ "url": "https://github.com/sponsors/wooorm"
3662
+ }
3663
+ },
3664
  "node_modules/ignore": {
3665
  "version": "5.3.2",
3666
  "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
 
3865
  "url": "https://github.com/sponsors/ljharb"
3866
  }
3867
  },
3868
+ "node_modules/is-extendable": {
3869
+ "version": "0.1.1",
3870
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
3871
+ "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
3872
+ "license": "MIT",
3873
+ "engines": {
3874
+ "node": ">=0.10.0"
3875
+ }
3876
+ },
3877
  "node_modules/is-extglob": {
3878
  "version": "2.1.1",
3879
  "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
 
3972
  "url": "https://github.com/sponsors/ljharb"
3973
  }
3974
  },
3975
+ "node_modules/is-plain-obj": {
3976
+ "version": "4.1.0",
3977
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
3978
+ "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
3979
+ "license": "MIT",
3980
+ "engines": {
3981
+ "node": ">=12"
3982
+ },
3983
+ "funding": {
3984
+ "url": "https://github.com/sponsors/sindresorhus"
3985
+ }
3986
+ },
3987
  "node_modules/is-regex": {
3988
  "version": "1.2.1",
3989
  "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
 
4251
  "json-buffer": "3.0.1"
4252
  }
4253
  },
4254
+ "node_modules/kind-of": {
4255
+ "version": "6.0.3",
4256
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
4257
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
4258
+ "license": "MIT",
4259
+ "engines": {
4260
+ "node": ">=0.10.0"
4261
+ }
4262
+ },
4263
  "node_modules/language-subtag-registry": {
4264
  "version": "0.3.23",
4265
  "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz",
 
4549
  "url": "https://github.com/sponsors/sindresorhus"
4550
  }
4551
  },
4552
+ "node_modules/lodash": {
4553
+ "version": "4.17.21",
4554
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
4555
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
4556
+ "license": "MIT"
4557
+ },
4558
+ "node_modules/lodash.castarray": {
4559
+ "version": "4.4.0",
4560
+ "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
4561
+ "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
4562
+ "license": "MIT"
4563
+ },
4564
+ "node_modules/lodash.isplainobject": {
4565
+ "version": "4.0.6",
4566
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
4567
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
4568
+ "license": "MIT"
4569
+ },
4570
  "node_modules/lodash.merge": {
4571
  "version": "4.6.2",
4572
  "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
4573
  "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
 
4574
  "license": "MIT"
4575
  },
4576
+ "node_modules/longest-streak": {
4577
+ "version": "3.1.0",
4578
+ "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
4579
+ "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==",
4580
+ "license": "MIT",
4581
+ "funding": {
4582
+ "type": "github",
4583
+ "url": "https://github.com/sponsors/wooorm"
4584
+ }
4585
+ },
4586
  "node_modules/loose-envify": {
4587
  "version": "1.4.0",
4588
  "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
 
4606
  "node": ">= 0.4"
4607
  }
4608
  },
4609
+ "node_modules/mdast-util-from-markdown": {
4610
+ "version": "2.0.2",
4611
+ "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz",
4612
+ "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==",
 
4613
  "license": "MIT",
4614
+ "dependencies": {
4615
+ "@types/mdast": "^4.0.0",
4616
+ "@types/unist": "^3.0.0",
4617
+ "decode-named-character-reference": "^1.0.0",
4618
+ "devlop": "^1.0.0",
4619
+ "mdast-util-to-string": "^4.0.0",
4620
+ "micromark": "^4.0.0",
4621
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
4622
+ "micromark-util-decode-string": "^2.0.0",
4623
+ "micromark-util-normalize-identifier": "^2.0.0",
4624
+ "micromark-util-symbol": "^2.0.0",
4625
+ "micromark-util-types": "^2.0.0",
4626
+ "unist-util-stringify-position": "^4.0.0"
4627
+ },
4628
+ "funding": {
4629
+ "type": "opencollective",
4630
+ "url": "https://opencollective.com/unified"
4631
  }
4632
  },
4633
+ "node_modules/mdast-util-phrasing": {
4634
+ "version": "4.1.0",
4635
+ "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
4636
+ "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==",
 
4637
  "license": "MIT",
4638
  "dependencies": {
4639
+ "@types/mdast": "^4.0.0",
4640
+ "unist-util-is": "^6.0.0"
4641
  },
4642
+ "funding": {
4643
+ "type": "opencollective",
4644
+ "url": "https://opencollective.com/unified"
4645
  }
4646
  },
4647
+ "node_modules/mdast-util-to-hast": {
4648
+ "version": "13.2.0",
4649
+ "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz",
4650
+ "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==",
4651
+ "license": "MIT",
 
4652
  "dependencies": {
4653
+ "@types/hast": "^3.0.0",
4654
+ "@types/mdast": "^4.0.0",
4655
+ "@ungap/structured-clone": "^1.0.0",
4656
+ "devlop": "^1.0.0",
4657
+ "micromark-util-sanitize-uri": "^2.0.0",
4658
+ "trim-lines": "^3.0.0",
4659
+ "unist-util-position": "^5.0.0",
4660
+ "unist-util-visit": "^5.0.0",
4661
+ "vfile": "^6.0.0"
4662
  },
4663
+ "funding": {
4664
+ "type": "opencollective",
4665
+ "url": "https://opencollective.com/unified"
4666
  }
4667
  },
4668
+ "node_modules/mdast-util-to-markdown": {
4669
+ "version": "2.1.2",
4670
+ "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz",
4671
+ "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==",
 
4672
  "license": "MIT",
4673
+ "dependencies": {
4674
+ "@types/mdast": "^4.0.0",
4675
+ "@types/unist": "^3.0.0",
4676
+ "longest-streak": "^3.0.0",
4677
+ "mdast-util-phrasing": "^4.0.0",
4678
+ "mdast-util-to-string": "^4.0.0",
4679
+ "micromark-util-classify-character": "^2.0.0",
4680
+ "micromark-util-decode-string": "^2.0.0",
4681
+ "unist-util-visit": "^5.0.0",
4682
+ "zwitch": "^2.0.0"
4683
+ },
4684
  "funding": {
4685
+ "type": "opencollective",
4686
+ "url": "https://opencollective.com/unified"
4687
  }
4688
  },
4689
+ "node_modules/mdast-util-to-string": {
4690
+ "version": "4.0.0",
4691
+ "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz",
4692
+ "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==",
4693
+ "license": "MIT",
4694
+ "dependencies": {
4695
+ "@types/mdast": "^4.0.0"
4696
+ },
4697
+ "funding": {
4698
+ "type": "opencollective",
4699
+ "url": "https://opencollective.com/unified"
4700
+ }
4701
+ },
4702
+ "node_modules/merge2": {
4703
+ "version": "1.4.1",
4704
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
4705
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
4706
+ "dev": true,
4707
+ "license": "MIT",
4708
+ "engines": {
4709
+ "node": ">= 8"
4710
+ }
4711
+ },
4712
+ "node_modules/micromark": {
4713
+ "version": "4.0.2",
4714
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz",
4715
+ "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==",
4716
+ "funding": [
4717
+ {
4718
+ "type": "GitHub Sponsors",
4719
+ "url": "https://github.com/sponsors/unifiedjs"
4720
+ },
4721
+ {
4722
+ "type": "OpenCollective",
4723
+ "url": "https://opencollective.com/unified"
4724
+ }
4725
+ ],
4726
+ "license": "MIT",
4727
+ "dependencies": {
4728
+ "@types/debug": "^4.0.0",
4729
+ "debug": "^4.0.0",
4730
+ "decode-named-character-reference": "^1.0.0",
4731
+ "devlop": "^1.0.0",
4732
+ "micromark-core-commonmark": "^2.0.0",
4733
+ "micromark-factory-space": "^2.0.0",
4734
+ "micromark-util-character": "^2.0.0",
4735
+ "micromark-util-chunked": "^2.0.0",
4736
+ "micromark-util-combine-extensions": "^2.0.0",
4737
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
4738
+ "micromark-util-encode": "^2.0.0",
4739
+ "micromark-util-normalize-identifier": "^2.0.0",
4740
+ "micromark-util-resolve-all": "^2.0.0",
4741
+ "micromark-util-sanitize-uri": "^2.0.0",
4742
+ "micromark-util-subtokenize": "^2.0.0",
4743
+ "micromark-util-symbol": "^2.0.0",
4744
+ "micromark-util-types": "^2.0.0"
4745
+ }
4746
+ },
4747
+ "node_modules/micromark-core-commonmark": {
4748
+ "version": "2.0.3",
4749
+ "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz",
4750
+ "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==",
4751
+ "funding": [
4752
+ {
4753
+ "type": "GitHub Sponsors",
4754
+ "url": "https://github.com/sponsors/unifiedjs"
4755
+ },
4756
+ {
4757
+ "type": "OpenCollective",
4758
+ "url": "https://opencollective.com/unified"
4759
+ }
4760
+ ],
4761
+ "license": "MIT",
4762
+ "dependencies": {
4763
+ "decode-named-character-reference": "^1.0.0",
4764
+ "devlop": "^1.0.0",
4765
+ "micromark-factory-destination": "^2.0.0",
4766
+ "micromark-factory-label": "^2.0.0",
4767
+ "micromark-factory-space": "^2.0.0",
4768
+ "micromark-factory-title": "^2.0.0",
4769
+ "micromark-factory-whitespace": "^2.0.0",
4770
+ "micromark-util-character": "^2.0.0",
4771
+ "micromark-util-chunked": "^2.0.0",
4772
+ "micromark-util-classify-character": "^2.0.0",
4773
+ "micromark-util-html-tag-name": "^2.0.0",
4774
+ "micromark-util-normalize-identifier": "^2.0.0",
4775
+ "micromark-util-resolve-all": "^2.0.0",
4776
+ "micromark-util-subtokenize": "^2.0.0",
4777
+ "micromark-util-symbol": "^2.0.0",
4778
+ "micromark-util-types": "^2.0.0"
4779
+ }
4780
+ },
4781
+ "node_modules/micromark-factory-destination": {
4782
+ "version": "2.0.1",
4783
+ "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz",
4784
+ "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==",
4785
+ "funding": [
4786
+ {
4787
+ "type": "GitHub Sponsors",
4788
+ "url": "https://github.com/sponsors/unifiedjs"
4789
+ },
4790
+ {
4791
+ "type": "OpenCollective",
4792
+ "url": "https://opencollective.com/unified"
4793
+ }
4794
+ ],
4795
+ "license": "MIT",
4796
+ "dependencies": {
4797
+ "micromark-util-character": "^2.0.0",
4798
+ "micromark-util-symbol": "^2.0.0",
4799
+ "micromark-util-types": "^2.0.0"
4800
+ }
4801
+ },
4802
+ "node_modules/micromark-factory-label": {
4803
+ "version": "2.0.1",
4804
+ "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz",
4805
+ "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==",
4806
+ "funding": [
4807
+ {
4808
+ "type": "GitHub Sponsors",
4809
+ "url": "https://github.com/sponsors/unifiedjs"
4810
+ },
4811
+ {
4812
+ "type": "OpenCollective",
4813
+ "url": "https://opencollective.com/unified"
4814
+ }
4815
+ ],
4816
+ "license": "MIT",
4817
+ "dependencies": {
4818
+ "devlop": "^1.0.0",
4819
+ "micromark-util-character": "^2.0.0",
4820
+ "micromark-util-symbol": "^2.0.0",
4821
+ "micromark-util-types": "^2.0.0"
4822
+ }
4823
+ },
4824
+ "node_modules/micromark-factory-space": {
4825
+ "version": "2.0.1",
4826
+ "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz",
4827
+ "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==",
4828
+ "funding": [
4829
+ {
4830
+ "type": "GitHub Sponsors",
4831
+ "url": "https://github.com/sponsors/unifiedjs"
4832
+ },
4833
+ {
4834
+ "type": "OpenCollective",
4835
+ "url": "https://opencollective.com/unified"
4836
+ }
4837
+ ],
4838
+ "license": "MIT",
4839
+ "dependencies": {
4840
+ "micromark-util-character": "^2.0.0",
4841
+ "micromark-util-types": "^2.0.0"
4842
+ }
4843
+ },
4844
+ "node_modules/micromark-factory-title": {
4845
+ "version": "2.0.1",
4846
+ "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz",
4847
+ "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==",
4848
+ "funding": [
4849
+ {
4850
+ "type": "GitHub Sponsors",
4851
+ "url": "https://github.com/sponsors/unifiedjs"
4852
+ },
4853
+ {
4854
+ "type": "OpenCollective",
4855
+ "url": "https://opencollective.com/unified"
4856
+ }
4857
+ ],
4858
+ "license": "MIT",
4859
+ "dependencies": {
4860
+ "micromark-factory-space": "^2.0.0",
4861
+ "micromark-util-character": "^2.0.0",
4862
+ "micromark-util-symbol": "^2.0.0",
4863
+ "micromark-util-types": "^2.0.0"
4864
+ }
4865
+ },
4866
+ "node_modules/micromark-factory-whitespace": {
4867
+ "version": "2.0.1",
4868
+ "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz",
4869
+ "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==",
4870
+ "funding": [
4871
+ {
4872
+ "type": "GitHub Sponsors",
4873
+ "url": "https://github.com/sponsors/unifiedjs"
4874
+ },
4875
+ {
4876
+ "type": "OpenCollective",
4877
+ "url": "https://opencollective.com/unified"
4878
+ }
4879
+ ],
4880
+ "license": "MIT",
4881
+ "dependencies": {
4882
+ "micromark-factory-space": "^2.0.0",
4883
+ "micromark-util-character": "^2.0.0",
4884
+ "micromark-util-symbol": "^2.0.0",
4885
+ "micromark-util-types": "^2.0.0"
4886
+ }
4887
+ },
4888
+ "node_modules/micromark-util-character": {
4889
+ "version": "2.1.1",
4890
+ "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz",
4891
+ "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
4892
+ "funding": [
4893
+ {
4894
+ "type": "GitHub Sponsors",
4895
+ "url": "https://github.com/sponsors/unifiedjs"
4896
+ },
4897
+ {
4898
+ "type": "OpenCollective",
4899
+ "url": "https://opencollective.com/unified"
4900
+ }
4901
+ ],
4902
+ "license": "MIT",
4903
+ "dependencies": {
4904
+ "micromark-util-symbol": "^2.0.0",
4905
+ "micromark-util-types": "^2.0.0"
4906
+ }
4907
+ },
4908
+ "node_modules/micromark-util-chunked": {
4909
+ "version": "2.0.1",
4910
+ "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz",
4911
+ "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==",
4912
+ "funding": [
4913
+ {
4914
+ "type": "GitHub Sponsors",
4915
+ "url": "https://github.com/sponsors/unifiedjs"
4916
+ },
4917
+ {
4918
+ "type": "OpenCollective",
4919
+ "url": "https://opencollective.com/unified"
4920
+ }
4921
+ ],
4922
+ "license": "MIT",
4923
+ "dependencies": {
4924
+ "micromark-util-symbol": "^2.0.0"
4925
+ }
4926
+ },
4927
+ "node_modules/micromark-util-classify-character": {
4928
+ "version": "2.0.1",
4929
+ "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz",
4930
+ "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==",
4931
+ "funding": [
4932
+ {
4933
+ "type": "GitHub Sponsors",
4934
+ "url": "https://github.com/sponsors/unifiedjs"
4935
+ },
4936
+ {
4937
+ "type": "OpenCollective",
4938
+ "url": "https://opencollective.com/unified"
4939
+ }
4940
+ ],
4941
+ "license": "MIT",
4942
+ "dependencies": {
4943
+ "micromark-util-character": "^2.0.0",
4944
+ "micromark-util-symbol": "^2.0.0",
4945
+ "micromark-util-types": "^2.0.0"
4946
+ }
4947
+ },
4948
+ "node_modules/micromark-util-combine-extensions": {
4949
+ "version": "2.0.1",
4950
+ "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz",
4951
+ "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==",
4952
+ "funding": [
4953
+ {
4954
+ "type": "GitHub Sponsors",
4955
+ "url": "https://github.com/sponsors/unifiedjs"
4956
+ },
4957
+ {
4958
+ "type": "OpenCollective",
4959
+ "url": "https://opencollective.com/unified"
4960
+ }
4961
+ ],
4962
+ "license": "MIT",
4963
+ "dependencies": {
4964
+ "micromark-util-chunked": "^2.0.0",
4965
+ "micromark-util-types": "^2.0.0"
4966
+ }
4967
+ },
4968
+ "node_modules/micromark-util-decode-numeric-character-reference": {
4969
+ "version": "2.0.2",
4970
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz",
4971
+ "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==",
4972
+ "funding": [
4973
+ {
4974
+ "type": "GitHub Sponsors",
4975
+ "url": "https://github.com/sponsors/unifiedjs"
4976
+ },
4977
+ {
4978
+ "type": "OpenCollective",
4979
+ "url": "https://opencollective.com/unified"
4980
+ }
4981
+ ],
4982
+ "license": "MIT",
4983
+ "dependencies": {
4984
+ "micromark-util-symbol": "^2.0.0"
4985
+ }
4986
+ },
4987
+ "node_modules/micromark-util-decode-string": {
4988
+ "version": "2.0.1",
4989
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz",
4990
+ "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==",
4991
+ "funding": [
4992
+ {
4993
+ "type": "GitHub Sponsors",
4994
+ "url": "https://github.com/sponsors/unifiedjs"
4995
+ },
4996
+ {
4997
+ "type": "OpenCollective",
4998
+ "url": "https://opencollective.com/unified"
4999
+ }
5000
+ ],
5001
+ "license": "MIT",
5002
+ "dependencies": {
5003
+ "decode-named-character-reference": "^1.0.0",
5004
+ "micromark-util-character": "^2.0.0",
5005
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
5006
+ "micromark-util-symbol": "^2.0.0"
5007
+ }
5008
+ },
5009
+ "node_modules/micromark-util-encode": {
5010
+ "version": "2.0.1",
5011
+ "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz",
5012
+ "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==",
5013
+ "funding": [
5014
+ {
5015
+ "type": "GitHub Sponsors",
5016
+ "url": "https://github.com/sponsors/unifiedjs"
5017
+ },
5018
+ {
5019
+ "type": "OpenCollective",
5020
+ "url": "https://opencollective.com/unified"
5021
+ }
5022
+ ],
5023
+ "license": "MIT"
5024
+ },
5025
+ "node_modules/micromark-util-html-tag-name": {
5026
+ "version": "2.0.1",
5027
+ "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz",
5028
+ "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==",
5029
+ "funding": [
5030
+ {
5031
+ "type": "GitHub Sponsors",
5032
+ "url": "https://github.com/sponsors/unifiedjs"
5033
+ },
5034
+ {
5035
+ "type": "OpenCollective",
5036
+ "url": "https://opencollective.com/unified"
5037
+ }
5038
+ ],
5039
+ "license": "MIT"
5040
+ },
5041
+ "node_modules/micromark-util-normalize-identifier": {
5042
+ "version": "2.0.1",
5043
+ "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz",
5044
+ "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==",
5045
+ "funding": [
5046
+ {
5047
+ "type": "GitHub Sponsors",
5048
+ "url": "https://github.com/sponsors/unifiedjs"
5049
+ },
5050
+ {
5051
+ "type": "OpenCollective",
5052
+ "url": "https://opencollective.com/unified"
5053
+ }
5054
+ ],
5055
+ "license": "MIT",
5056
+ "dependencies": {
5057
+ "micromark-util-symbol": "^2.0.0"
5058
+ }
5059
+ },
5060
+ "node_modules/micromark-util-resolve-all": {
5061
+ "version": "2.0.1",
5062
+ "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz",
5063
+ "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==",
5064
+ "funding": [
5065
+ {
5066
+ "type": "GitHub Sponsors",
5067
+ "url": "https://github.com/sponsors/unifiedjs"
5068
+ },
5069
+ {
5070
+ "type": "OpenCollective",
5071
+ "url": "https://opencollective.com/unified"
5072
+ }
5073
+ ],
5074
+ "license": "MIT",
5075
+ "dependencies": {
5076
+ "micromark-util-types": "^2.0.0"
5077
+ }
5078
+ },
5079
+ "node_modules/micromark-util-sanitize-uri": {
5080
+ "version": "2.0.1",
5081
+ "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz",
5082
+ "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
5083
+ "funding": [
5084
+ {
5085
+ "type": "GitHub Sponsors",
5086
+ "url": "https://github.com/sponsors/unifiedjs"
5087
+ },
5088
+ {
5089
+ "type": "OpenCollective",
5090
+ "url": "https://opencollective.com/unified"
5091
+ }
5092
+ ],
5093
+ "license": "MIT",
5094
+ "dependencies": {
5095
+ "micromark-util-character": "^2.0.0",
5096
+ "micromark-util-encode": "^2.0.0",
5097
+ "micromark-util-symbol": "^2.0.0"
5098
+ }
5099
+ },
5100
+ "node_modules/micromark-util-subtokenize": {
5101
+ "version": "2.1.0",
5102
+ "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz",
5103
+ "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==",
5104
+ "funding": [
5105
+ {
5106
+ "type": "GitHub Sponsors",
5107
+ "url": "https://github.com/sponsors/unifiedjs"
5108
+ },
5109
+ {
5110
+ "type": "OpenCollective",
5111
+ "url": "https://opencollective.com/unified"
5112
+ }
5113
+ ],
5114
+ "license": "MIT",
5115
+ "dependencies": {
5116
+ "devlop": "^1.0.0",
5117
+ "micromark-util-chunked": "^2.0.0",
5118
+ "micromark-util-symbol": "^2.0.0",
5119
+ "micromark-util-types": "^2.0.0"
5120
+ }
5121
+ },
5122
+ "node_modules/micromark-util-symbol": {
5123
+ "version": "2.0.1",
5124
+ "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz",
5125
+ "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==",
5126
+ "funding": [
5127
+ {
5128
+ "type": "GitHub Sponsors",
5129
+ "url": "https://github.com/sponsors/unifiedjs"
5130
+ },
5131
+ {
5132
+ "type": "OpenCollective",
5133
+ "url": "https://opencollective.com/unified"
5134
+ }
5135
+ ],
5136
+ "license": "MIT"
5137
+ },
5138
+ "node_modules/micromark-util-types": {
5139
+ "version": "2.0.2",
5140
+ "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz",
5141
+ "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==",
5142
+ "funding": [
5143
+ {
5144
+ "type": "GitHub Sponsors",
5145
+ "url": "https://github.com/sponsors/unifiedjs"
5146
+ },
5147
+ {
5148
+ "type": "OpenCollective",
5149
+ "url": "https://opencollective.com/unified"
5150
+ }
5151
+ ],
5152
+ "license": "MIT"
5153
+ },
5154
+ "node_modules/micromatch": {
5155
+ "version": "4.0.8",
5156
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
5157
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
5158
+ "dev": true,
5159
+ "license": "MIT",
5160
+ "dependencies": {
5161
+ "braces": "^3.0.3",
5162
+ "picomatch": "^2.3.1"
5163
+ },
5164
+ "engines": {
5165
+ "node": ">=8.6"
5166
+ }
5167
+ },
5168
+ "node_modules/minimatch": {
5169
+ "version": "3.1.2",
5170
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
5171
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
5172
+ "dev": true,
5173
+ "license": "ISC",
5174
+ "dependencies": {
5175
+ "brace-expansion": "^1.1.7"
5176
+ },
5177
+ "engines": {
5178
+ "node": "*"
5179
+ }
5180
+ },
5181
+ "node_modules/minimist": {
5182
+ "version": "1.2.8",
5183
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
5184
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
5185
+ "dev": true,
5186
+ "license": "MIT",
5187
+ "funding": {
5188
+ "url": "https://github.com/sponsors/ljharb"
5189
+ }
5190
+ },
5191
+ "node_modules/ms": {
5192
+ "version": "2.1.3",
5193
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
5194
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
5195
+ "license": "MIT"
5196
+ },
5197
+ "node_modules/nanoid": {
5198
  "version": "3.3.11",
5199
  "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
5200
  "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
 
5590
  "node": "^10 || ^12 || >=14"
5591
  }
5592
  },
5593
+ "node_modules/postcss-selector-parser": {
5594
+ "version": "6.0.10",
5595
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
5596
+ "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
5597
+ "license": "MIT",
5598
+ "dependencies": {
5599
+ "cssesc": "^3.0.0",
5600
+ "util-deprecate": "^1.0.2"
5601
+ },
5602
+ "engines": {
5603
+ "node": ">=4"
5604
+ }
5605
+ },
5606
  "node_modules/prelude-ls": {
5607
  "version": "1.2.1",
5608
  "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
 
5625
  "react-is": "^16.13.1"
5626
  }
5627
  },
5628
+ "node_modules/property-information": {
5629
+ "version": "7.0.0",
5630
+ "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.0.0.tgz",
5631
+ "integrity": "sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==",
5632
+ "license": "MIT",
5633
+ "funding": {
5634
+ "type": "github",
5635
+ "url": "https://github.com/sponsors/wooorm"
5636
+ }
5637
+ },
5638
  "node_modules/punycode": {
5639
  "version": "2.3.1",
5640
  "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
 
5738
  "url": "https://github.com/sponsors/ljharb"
5739
  }
5740
  },
5741
+ "node_modules/remark": {
5742
+ "version": "15.0.1",
5743
+ "resolved": "https://registry.npmjs.org/remark/-/remark-15.0.1.tgz",
5744
+ "integrity": "sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==",
5745
+ "license": "MIT",
5746
+ "dependencies": {
5747
+ "@types/mdast": "^4.0.0",
5748
+ "remark-parse": "^11.0.0",
5749
+ "remark-stringify": "^11.0.0",
5750
+ "unified": "^11.0.0"
5751
+ },
5752
+ "funding": {
5753
+ "type": "opencollective",
5754
+ "url": "https://opencollective.com/unified"
5755
+ }
5756
+ },
5757
+ "node_modules/remark-html": {
5758
+ "version": "16.0.1",
5759
+ "resolved": "https://registry.npmjs.org/remark-html/-/remark-html-16.0.1.tgz",
5760
+ "integrity": "sha512-B9JqA5i0qZe0Nsf49q3OXyGvyXuZFDzAP2iOFLEumymuYJITVpiH1IgsTEwTpdptDmZlMDMWeDmSawdaJIGCXQ==",
5761
+ "license": "MIT",
5762
+ "dependencies": {
5763
+ "@types/mdast": "^4.0.0",
5764
+ "hast-util-sanitize": "^5.0.0",
5765
+ "hast-util-to-html": "^9.0.0",
5766
+ "mdast-util-to-hast": "^13.0.0",
5767
+ "unified": "^11.0.0"
5768
+ },
5769
+ "funding": {
5770
+ "type": "opencollective",
5771
+ "url": "https://opencollective.com/unified"
5772
+ }
5773
+ },
5774
+ "node_modules/remark-parse": {
5775
+ "version": "11.0.0",
5776
+ "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
5777
+ "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==",
5778
+ "license": "MIT",
5779
+ "dependencies": {
5780
+ "@types/mdast": "^4.0.0",
5781
+ "mdast-util-from-markdown": "^2.0.0",
5782
+ "micromark-util-types": "^2.0.0",
5783
+ "unified": "^11.0.0"
5784
+ },
5785
+ "funding": {
5786
+ "type": "opencollective",
5787
+ "url": "https://opencollective.com/unified"
5788
+ }
5789
+ },
5790
+ "node_modules/remark-stringify": {
5791
+ "version": "11.0.0",
5792
+ "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz",
5793
+ "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==",
5794
+ "license": "MIT",
5795
+ "dependencies": {
5796
+ "@types/mdast": "^4.0.0",
5797
+ "mdast-util-to-markdown": "^2.0.0",
5798
+ "unified": "^11.0.0"
5799
+ },
5800
+ "funding": {
5801
+ "type": "opencollective",
5802
+ "url": "https://opencollective.com/unified"
5803
+ }
5804
+ },
5805
  "node_modules/resolve": {
5806
  "version": "1.22.10",
5807
  "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
 
5939
  "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
5940
  "license": "MIT"
5941
  },
5942
+ "node_modules/section-matter": {
5943
+ "version": "1.0.0",
5944
+ "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
5945
+ "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
5946
+ "license": "MIT",
5947
+ "dependencies": {
5948
+ "extend-shallow": "^2.0.1",
5949
+ "kind-of": "^6.0.0"
5950
+ },
5951
+ "engines": {
5952
+ "node": ">=4"
5953
+ }
5954
+ },
5955
  "node_modules/semver": {
5956
  "version": "7.7.1",
5957
  "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
 
6172
  "node": ">=0.10.0"
6173
  }
6174
  },
6175
+ "node_modules/space-separated-tokens": {
6176
+ "version": "2.0.2",
6177
+ "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
6178
+ "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
6179
+ "license": "MIT",
6180
+ "funding": {
6181
+ "type": "github",
6182
+ "url": "https://github.com/sponsors/wooorm"
6183
+ }
6184
+ },
6185
+ "node_modules/sprintf-js": {
6186
+ "version": "1.0.3",
6187
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
6188
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
6189
+ "license": "BSD-3-Clause"
6190
+ },
6191
  "node_modules/stable-hash": {
6192
  "version": "0.0.5",
6193
  "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz",
 
6316
  "url": "https://github.com/sponsors/ljharb"
6317
  }
6318
  },
6319
+ "node_modules/stringify-entities": {
6320
+ "version": "4.0.4",
6321
+ "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
6322
+ "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
6323
+ "license": "MIT",
6324
+ "dependencies": {
6325
+ "character-entities-html4": "^2.0.0",
6326
+ "character-entities-legacy": "^3.0.0"
6327
+ },
6328
+ "funding": {
6329
+ "type": "github",
6330
+ "url": "https://github.com/sponsors/wooorm"
6331
+ }
6332
+ },
6333
  "node_modules/strip-bom": {
6334
  "version": "3.0.0",
6335
  "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
 
6340
  "node": ">=4"
6341
  }
6342
  },
6343
+ "node_modules/strip-bom-string": {
6344
+ "version": "1.0.0",
6345
+ "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
6346
+ "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
6347
+ "license": "MIT",
6348
+ "engines": {
6349
+ "node": ">=0.10.0"
6350
+ }
6351
+ },
6352
  "node_modules/strip-json-comments": {
6353
  "version": "3.1.1",
6354
  "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
 
6415
  "version": "4.1.3",
6416
  "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.3.tgz",
6417
  "integrity": "sha512-2Q+rw9vy1WFXu5cIxlvsabCwhU2qUwodGq03ODhLJ0jW4ek5BUtoCsnLB0qG+m8AHgEsSJcJGDSDe06FXlP74g==",
 
6418
  "license": "MIT"
6419
  },
6420
  "node_modules/tapable": {
 
6485
  "node": ">=8.0"
6486
  }
6487
  },
6488
+ "node_modules/trim-lines": {
6489
+ "version": "3.0.1",
6490
+ "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
6491
+ "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==",
6492
+ "license": "MIT",
6493
+ "funding": {
6494
+ "type": "github",
6495
+ "url": "https://github.com/sponsors/wooorm"
6496
+ }
6497
+ },
6498
+ "node_modules/trough": {
6499
+ "version": "2.2.0",
6500
+ "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz",
6501
+ "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==",
6502
+ "license": "MIT",
6503
+ "funding": {
6504
+ "type": "github",
6505
+ "url": "https://github.com/sponsors/wooorm"
6506
+ }
6507
+ },
6508
  "node_modules/ts-api-utils": {
6509
  "version": "2.1.0",
6510
  "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
 
6668
  "dev": true,
6669
  "license": "MIT"
6670
  },
6671
+ "node_modules/unified": {
6672
+ "version": "11.0.5",
6673
+ "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
6674
+ "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==",
6675
+ "license": "MIT",
6676
+ "dependencies": {
6677
+ "@types/unist": "^3.0.0",
6678
+ "bail": "^2.0.0",
6679
+ "devlop": "^1.0.0",
6680
+ "extend": "^3.0.0",
6681
+ "is-plain-obj": "^4.0.0",
6682
+ "trough": "^2.0.0",
6683
+ "vfile": "^6.0.0"
6684
+ },
6685
+ "funding": {
6686
+ "type": "opencollective",
6687
+ "url": "https://opencollective.com/unified"
6688
+ }
6689
+ },
6690
+ "node_modules/unist-util-is": {
6691
+ "version": "6.0.0",
6692
+ "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz",
6693
+ "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==",
6694
+ "license": "MIT",
6695
+ "dependencies": {
6696
+ "@types/unist": "^3.0.0"
6697
+ },
6698
+ "funding": {
6699
+ "type": "opencollective",
6700
+ "url": "https://opencollective.com/unified"
6701
+ }
6702
+ },
6703
+ "node_modules/unist-util-position": {
6704
+ "version": "5.0.0",
6705
+ "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz",
6706
+ "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==",
6707
+ "license": "MIT",
6708
+ "dependencies": {
6709
+ "@types/unist": "^3.0.0"
6710
+ },
6711
+ "funding": {
6712
+ "type": "opencollective",
6713
+ "url": "https://opencollective.com/unified"
6714
+ }
6715
+ },
6716
+ "node_modules/unist-util-stringify-position": {
6717
+ "version": "4.0.0",
6718
+ "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
6719
+ "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
6720
+ "license": "MIT",
6721
+ "dependencies": {
6722
+ "@types/unist": "^3.0.0"
6723
+ },
6724
+ "funding": {
6725
+ "type": "opencollective",
6726
+ "url": "https://opencollective.com/unified"
6727
+ }
6728
+ },
6729
+ "node_modules/unist-util-visit": {
6730
+ "version": "5.0.0",
6731
+ "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz",
6732
+ "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==",
6733
+ "license": "MIT",
6734
+ "dependencies": {
6735
+ "@types/unist": "^3.0.0",
6736
+ "unist-util-is": "^6.0.0",
6737
+ "unist-util-visit-parents": "^6.0.0"
6738
+ },
6739
+ "funding": {
6740
+ "type": "opencollective",
6741
+ "url": "https://opencollective.com/unified"
6742
+ }
6743
+ },
6744
+ "node_modules/unist-util-visit-parents": {
6745
+ "version": "6.0.1",
6746
+ "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz",
6747
+ "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==",
6748
+ "license": "MIT",
6749
+ "dependencies": {
6750
+ "@types/unist": "^3.0.0",
6751
+ "unist-util-is": "^6.0.0"
6752
+ },
6753
+ "funding": {
6754
+ "type": "opencollective",
6755
+ "url": "https://opencollective.com/unified"
6756
+ }
6757
+ },
6758
  "node_modules/unrs-resolver": {
6759
  "version": "1.3.3",
6760
  "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.3.3.tgz",
 
6792
  "punycode": "^2.1.0"
6793
  }
6794
  },
6795
+ "node_modules/util-deprecate": {
6796
+ "version": "1.0.2",
6797
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
6798
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
6799
+ "license": "MIT"
6800
+ },
6801
+ "node_modules/vfile": {
6802
+ "version": "6.0.3",
6803
+ "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
6804
+ "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
6805
+ "license": "MIT",
6806
+ "dependencies": {
6807
+ "@types/unist": "^3.0.0",
6808
+ "vfile-message": "^4.0.0"
6809
+ },
6810
+ "funding": {
6811
+ "type": "opencollective",
6812
+ "url": "https://opencollective.com/unified"
6813
+ }
6814
+ },
6815
+ "node_modules/vfile-message": {
6816
+ "version": "4.0.2",
6817
+ "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz",
6818
+ "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==",
6819
+ "license": "MIT",
6820
+ "dependencies": {
6821
+ "@types/unist": "^3.0.0",
6822
+ "unist-util-stringify-position": "^4.0.0"
6823
+ },
6824
+ "funding": {
6825
+ "type": "opencollective",
6826
+ "url": "https://opencollective.com/unified"
6827
+ }
6828
+ },
6829
  "node_modules/which": {
6830
  "version": "2.0.2",
6831
  "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
 
6953
  "funding": {
6954
  "url": "https://github.com/sponsors/sindresorhus"
6955
  }
6956
+ },
6957
+ "node_modules/zwitch": {
6958
+ "version": "2.0.4",
6959
+ "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
6960
+ "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
6961
+ "license": "MIT",
6962
+ "funding": {
6963
+ "type": "github",
6964
+ "url": "https://github.com/sponsors/wooorm"
6965
+ }
6966
  }
6967
  }
6968
  }
package.json CHANGED
@@ -9,19 +9,26 @@
9
  "lint": "next lint"
10
  },
11
  "dependencies": {
 
 
 
 
 
 
12
  "react": "^19.0.0",
13
  "react-dom": "^19.0.0",
14
- "next": "15.2.4"
 
15
  },
16
  "devDependencies": {
17
- "typescript": "^5",
 
18
  "@types/node": "^20",
19
  "@types/react": "^19",
20
  "@types/react-dom": "^19",
21
- "@tailwindcss/postcss": "^4",
22
- "tailwindcss": "^4",
23
  "eslint": "^9",
24
  "eslint-config-next": "15.2.4",
25
- "@eslint/eslintrc": "^3"
 
26
  }
27
  }
 
9
  "lint": "next lint"
10
  },
11
  "dependencies": {
12
+ "@tailwindcss/typography": "^0.5.16",
13
+ "@types/lodash": "^4.17.16",
14
+ "date-fns": "^4.1.0",
15
+ "gray-matter": "^4.0.3",
16
+ "lodash": "^4.17.21",
17
+ "next": "15.2.4",
18
  "react": "^19.0.0",
19
  "react-dom": "^19.0.0",
20
+ "remark": "^15.0.1",
21
+ "remark-html": "^16.0.1"
22
  },
23
  "devDependencies": {
24
+ "@eslint/eslintrc": "^3",
25
+ "@tailwindcss/postcss": "^4",
26
  "@types/node": "^20",
27
  "@types/react": "^19",
28
  "@types/react-dom": "^19",
 
 
29
  "eslint": "^9",
30
  "eslint-config-next": "15.2.4",
31
+ "tailwindcss": "^4",
32
+ "typescript": "^5"
33
  }
34
  }
src/app/api/content/route.ts ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextResponse } from 'next/server';
2
+ import { getAllPosts, getPostById, getPostsByTag, getPostsByCategory } from '@/lib/markdown';
3
+
4
+ export async function GET(request: Request) {
5
+ const { searchParams } = new URL(request.url);
6
+ const id = searchParams.get('id');
7
+ const tag = searchParams.get('tag');
8
+ const category = searchParams.get('category');
9
+
10
+ try {
11
+ if (id) {
12
+ const post = await getPostById(id);
13
+ if (!post) {
14
+ return NextResponse.json({ error: 'Post not found' }, { status: 404 });
15
+ }
16
+ return NextResponse.json(post);
17
+ }
18
+
19
+ if (tag) {
20
+ const posts = await getPostsByTag(tag);
21
+ return NextResponse.json(posts);
22
+ }
23
+
24
+ if (category) {
25
+ const posts = await getPostsByCategory(category);
26
+ return NextResponse.json(posts);
27
+ }
28
+
29
+ const posts = await getAllPosts();
30
+ return NextResponse.json(posts);
31
+ } catch (error) {
32
+ console.error('API Error:', error);
33
+ return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
34
+ }
35
+ }
src/app/api/git/route.ts ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextResponse } from 'next/server';
2
+ import { exec } from 'node:child_process';
3
+ import { promisify } from 'node:util';
4
+ import path from 'node:path';
5
+ import fs from 'node:fs/promises';
6
+
7
+ const execAsync = promisify(exec);
8
+ const contentDir = path.join(process.cwd(), 'content');
9
+
10
+ export async function GET(request: Request) {
11
+ const { searchParams } = new URL(request.url);
12
+ const action = searchParams.get('action');
13
+
14
+ try {
15
+ switch (action) {
16
+ case 'history':
17
+ const { stdout: historyOutput } = await execAsync(
18
+ 'git log --pretty=format:"%H|%an|%ad|%s" --name-status',
19
+ { cwd: contentDir }
20
+ );
21
+
22
+ const commits = parseGitHistory(historyOutput);
23
+ return NextResponse.json({ commits });
24
+
25
+ case 'diff': {
26
+ const hash1 = searchParams.get('hash1');
27
+ const hash2 = searchParams.get('hash2');
28
+ const filePath = searchParams.get('file') || '.';
29
+
30
+ if (!hash1 || !hash2) {
31
+ return NextResponse.json(
32
+ { error: '커밋 해시가 필요합니다.' },
33
+ { status: 400 }
34
+ );
35
+ }
36
+
37
+ const { stdout: diffOutput } = await execAsync(
38
+ `git diff ${hash1} ${hash2} -- ${filePath}`,
39
+ { cwd: contentDir }
40
+ );
41
+
42
+ return NextResponse.json({ diff: diffOutput });
43
+ }
44
+
45
+ default:
46
+ return NextResponse.json(
47
+ { error: '잘못된 작업입니다.' },
48
+ { status: 400 }
49
+ );
50
+ }
51
+ } catch (error) {
52
+ console.error('Git API 에러:', error);
53
+ return NextResponse.json(
54
+ { error: 'Git 작업 중 오류가 발생했습니다.' },
55
+ { status: 500 }
56
+ );
57
+ }
58
+ }
59
+
60
+ export async function POST(request: Request) {
61
+ try {
62
+ const { action, commitHash, message } = await request.json();
63
+
64
+ switch (action) {
65
+ case 'rollback':
66
+ if (!commitHash) {
67
+ return NextResponse.json(
68
+ { error: '커밋 해시가 필요합니다.' },
69
+ { status: 400 }
70
+ );
71
+ }
72
+
73
+ // 백업 생성
74
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
75
+ const backupDir = path.join(process.cwd(), 'backups', `backup-${timestamp}`);
76
+ await fs.mkdir(backupDir, { recursive: true });
77
+ await execAsync(`git archive HEAD -o "${backupDir}/content.zip"`, { cwd: contentDir });
78
+
79
+ // 롤백 실행
80
+ await execAsync(`git checkout ${commitHash}`, { cwd: contentDir });
81
+ return NextResponse.json({ success: true, backupDir });
82
+
83
+ case 'commit':
84
+ if (!message) {
85
+ return NextResponse.json(
86
+ { error: '커밋 메시지가 필요합니다.' },
87
+ { status: 400 }
88
+ );
89
+ }
90
+
91
+ await execAsync('git add .', { cwd: contentDir });
92
+ const { stdout } = await execAsync(`git commit -m "${message}"`, { cwd: contentDir });
93
+ return NextResponse.json({ success: true, message: stdout });
94
+
95
+ default:
96
+ return NextResponse.json(
97
+ { error: '잘못된 작업입니다.' },
98
+ { status: 400 }
99
+ );
100
+ }
101
+ } catch (error) {
102
+ console.error('Git API 에러:', error);
103
+ return NextResponse.json(
104
+ { error: 'Git 작업 중 오류가 발생했습니다.' },
105
+ { status: 500 }
106
+ );
107
+ }
108
+ }
109
+
110
+ function parseGitHistory(output: string) {
111
+ const commits = [];
112
+ const commitChunks = output.split('\n\n');
113
+
114
+ for (const chunk of commitChunks) {
115
+ if (!chunk.trim()) continue;
116
+
117
+ const [headerLine, ...changes] = chunk.split('\n');
118
+ const [hash, author, date, message] = headerLine.split('|');
119
+
120
+ const changesList = {
121
+ added: [] as string[],
122
+ modified: [] as string[],
123
+ deleted: [] as string[],
124
+ };
125
+
126
+ changes.forEach(change => {
127
+ if (!change.trim()) return;
128
+ const [status, file] = change.split('\t');
129
+ if (status === 'A') changesList.added.push(file);
130
+ else if (status === 'M') changesList.modified.push(file);
131
+ else if (status === 'D') changesList.deleted.push(file);
132
+ });
133
+
134
+ commits.push({
135
+ hash,
136
+ author,
137
+ date,
138
+ message,
139
+ changes: changesList,
140
+ });
141
+ }
142
+
143
+ return commits;
144
+ }
src/app/api/search/route.ts ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextResponse } from 'next/server';
2
+ import { getAllPosts } from '@/lib/markdown';
3
+
4
+ export async function GET(request: Request) {
5
+ const { searchParams } = new URL(request.url);
6
+ const query = searchParams.get('q') || '';
7
+ const tags = searchParams.get('tags')?.split(',').filter(Boolean) || [];
8
+ const category = searchParams.get('category') || '';
9
+
10
+ const posts = await getAllPosts();
11
+
12
+ const filteredPosts = posts.filter(post => {
13
+ const matchesQuery = query
14
+ ? post.title.toLowerCase().includes(query.toLowerCase()) ||
15
+ post.content.toLowerCase().includes(query.toLowerCase())
16
+ : true;
17
+
18
+ const matchesTags = tags.length > 0
19
+ ? tags.every(tag => post.tags.includes(tag))
20
+ : true;
21
+
22
+ const matchesCategory = category
23
+ ? post.category === category
24
+ : true;
25
+
26
+ return matchesQuery && matchesTags && matchesCategory;
27
+ });
28
+
29
+ return NextResponse.json({
30
+ posts: filteredPosts.map(post => ({
31
+ id: post.id,
32
+ title: post.title,
33
+ date: post.date,
34
+ author: post.author,
35
+ tags: post.tags,
36
+ category: post.category,
37
+ excerpt: post.content.substring(0, 200) + '...'
38
+ }))
39
+ });
40
+ }
src/app/categories/[category]/page.tsx ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { getPostsByCategory } from '@/lib/markdown';
2
+ import Link from 'next/link';
3
+ import { notFound } from 'next/navigation';
4
+
5
+ interface CategoryPageProps {
6
+ params: Promise<{
7
+ category: string;
8
+ }>;
9
+ }
10
+
11
+ export default async function CategoryPage({ params }: CategoryPageProps) {
12
+ const resolvedParams = await params;
13
+ const { category } = resolvedParams;
14
+ const posts = await getPostsByCategory(category);
15
+
16
+ if (posts.length === 0) {
17
+ notFound();
18
+ }
19
+
20
+ return (
21
+ <main className="min-h-screen p-8">
22
+ <h1 className="text-4xl font-bold mb-8">
23
+ 카테고리: {category}
24
+ </h1>
25
+
26
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
27
+ {posts.map(post => (
28
+ <article key={post.id} className="border rounded-lg p-6 hover:shadow-lg transition-shadow">
29
+ <h2 className="text-2xl font-semibold mb-2">
30
+ <Link href={`/posts/${post.id}`} className="hover:text-blue-600">
31
+ {post.title}
32
+ </Link>
33
+ </h2>
34
+
35
+ <div className="text-gray-600 mb-4">
36
+ <time>{new Date(post.date).toLocaleDateString()}</time>
37
+ <span className="mx-2">•</span>
38
+ <span>{post.author}</span>
39
+ </div>
40
+
41
+ {post.tags.length > 0 && (
42
+ <div className="flex flex-wrap gap-2 mb-4">
43
+ {post.tags.map(tag => (
44
+ <Link
45
+ key={tag}
46
+ href={`/tags/${tag}`}
47
+ className="bg-gray-100 px-2 py-1 rounded text-sm hover:bg-gray-200"
48
+ >
49
+ #{tag}
50
+ </Link>
51
+ ))}
52
+ </div>
53
+ )}
54
+
55
+ {post.category && (
56
+ <Link
57
+ href={`/categories/${post.category}`}
58
+ className={`inline-block px-3 py-1 rounded-full text-sm ${
59
+ post.category === category
60
+ ? 'bg-blue-100 text-blue-800'
61
+ : 'bg-gray-100 hover:bg-gray-200'
62
+ }`}
63
+ >
64
+ {post.category}
65
+ </Link>
66
+ )}
67
+ </article>
68
+ ))}
69
+ </div>
70
+
71
+ <div className="mt-8">
72
+ <Link
73
+ href="/"
74
+ className="text-blue-600 hover:underline"
75
+ >
76
+ ← 목록으로 돌아가기
77
+ </Link>
78
+ </div>
79
+ </main>
80
+ );
81
+ }
src/app/page.tsx CHANGED
@@ -1,103 +1,79 @@
1
- import Image from "next/image";
 
 
2
 
3
- export default function Home() {
4
- return (
5
- <div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
6
- <main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
7
- <Image
8
- className="dark:invert"
9
- src="/next.svg"
10
- alt="Next.js logo"
11
- width={180}
12
- height={38}
13
- priority
14
- />
15
- <ol className="list-inside list-decimal text-sm/6 text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
16
- <li className="mb-2 tracking-[-.01em]">
17
- Get started by editing{" "}
18
- <code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-[family-name:var(--font-geist-mono)] font-semibold">
19
- src/app/page.tsx
20
- </code>
21
- .
22
- </li>
23
- <li className="tracking-[-.01em]">
24
- Save and see your changes instantly.
25
- </li>
26
- </ol>
27
 
28
- <div className="flex gap-4 items-center flex-col sm:flex-row">
29
- <a
30
- className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
31
- href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
32
- target="_blank"
33
- rel="noopener noreferrer"
34
- >
35
- <Image
36
- className="dark:invert"
37
- src="/vercel.svg"
38
- alt="Vercel logomark"
39
- width={20}
40
- height={20}
41
- />
42
- Deploy now
43
- </a>
44
- <a
45
- className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
46
- href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
47
- target="_blank"
48
- rel="noopener noreferrer"
49
- >
50
- Read our docs
51
- </a>
52
- </div>
53
- </main>
54
- <footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
55
- <a
56
- className="flex items-center gap-2 hover:underline hover:underline-offset-4"
57
- href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
58
- target="_blank"
59
- rel="noopener noreferrer"
60
- >
61
- <Image
62
- aria-hidden
63
- src="/file.svg"
64
- alt="File icon"
65
- width={16}
66
- height={16}
67
- />
68
- Learn
69
- </a>
70
- <a
71
- className="flex items-center gap-2 hover:underline hover:underline-offset-4"
72
- href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
73
- target="_blank"
74
- rel="noopener noreferrer"
75
- >
76
- <Image
77
- aria-hidden
78
- src="/window.svg"
79
- alt="Window icon"
80
- width={16}
81
- height={16}
82
- />
83
- Examples
84
- </a>
85
- <a
86
- className="flex items-center gap-2 hover:underline hover:underline-offset-4"
87
- href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
88
- target="_blank"
89
- rel="noopener noreferrer"
90
- >
91
- <Image
92
- aria-hidden
93
- src="/globe.svg"
94
- alt="Globe icon"
95
- width={16}
96
- height={16}
97
- />
98
- Go to nextjs.org →
99
- </a>
100
- </footer>
101
- </div>
102
  );
103
  }
 
1
+ import { getAllPosts, getAllTags, getAllCategories } from '@/lib/markdown';
2
+ import Search from '@/components/Search';
3
+ import Link from 'next/link';
4
 
5
+ export default async function HomePage() {
6
+ const [posts, tags, categories] = await Promise.all([
7
+ getAllPosts(),
8
+ getAllTags(),
9
+ getAllCategories()
10
+ ]);
11
+
12
+ const recentPosts = posts.sort((a, b) =>
13
+ new Date(b.date).getTime() - new Date(a.date).getTime()
14
+ ).slice(0, 6);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
+ return (
17
+ <main className="min-h-screen p-8">
18
+ <div className="max-w-6xl mx-auto">
19
+ <h1 className="text-4xl font-bold mb-8 text-center">AI Tree 문서 포털</h1>
20
+
21
+ <Search />
22
+
23
+ <section className="mt-12">
24
+ <h2 className="text-2xl font-semibold mb-6">최신 게시물</h2>
25
+
26
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
27
+ {recentPosts.map(post => (
28
+ <article key={post.id} className="border rounded-lg p-6 hover:shadow-lg transition-shadow">
29
+ <h3 className="text-xl font-semibold mb-2">
30
+ <Link href={`/posts/${post.id}`} className="hover:text-blue-600">
31
+ {post.title}
32
+ </Link>
33
+ </h3>
34
+
35
+ <div className="text-gray-600 mb-4">
36
+ <time>{new Date(post.date).toLocaleDateString()}</time>
37
+ <span className="mx-2">•</span>
38
+ <span>{post.author}</span>
39
+ </div>
40
+
41
+ {post.tags.length > 0 && (
42
+ <div className="flex flex-wrap gap-2 mb-4">
43
+ {post.tags.map(tag => (
44
+ <Link
45
+ key={tag}
46
+ href={`/tags/${tag}`}
47
+ className="bg-gray-100 px-2 py-1 rounded text-sm hover:bg-gray-200"
48
+ >
49
+ #{tag}
50
+ </Link>
51
+ ))}
52
+ </div>
53
+ )}
54
+
55
+ {post.category && (
56
+ <Link
57
+ href={`/categories/${post.category}`}
58
+ className="inline-block bg-blue-100 px-3 py-1 rounded-full text-sm text-blue-800 hover:bg-blue-200"
59
+ >
60
+ {post.category}
61
+ </Link>
62
+ )}
63
+ </article>
64
+ ))}
65
+ </div>
66
+
67
+ <div className="text-center mt-8">
68
+ <Link
69
+ href="/search"
70
+ className="inline-block bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700 transition-colors"
71
+ >
72
+ 모든 게시물 보기
73
+ </Link>
74
+ </div>
75
+ </section>
76
+ </div>
77
+ </main>
 
 
 
 
 
 
 
 
 
 
 
 
78
  );
79
  }
src/app/posts/[...slug]/page.tsx ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { getPostById } from '@/lib/markdown';
2
+ import Link from 'next/link';
3
+ import { notFound } from 'next/navigation';
4
+ import JsonLd from '@/components/JsonLd';
5
+ import { BlogPostingJsonLd } from '@/types/jsonld';
6
+
7
+ interface PostPageProps {
8
+ params: Promise<{
9
+ slug: string[];
10
+ }>;
11
+ }
12
+
13
+ export default async function PostPage({ params }: PostPageProps) {
14
+ const resolvedParams = await params;
15
+ const id = resolvedParams.slug.join('/');
16
+ const post = await getPostById(id);
17
+
18
+ if (!post) {
19
+ notFound();
20
+ }
21
+
22
+ const jsonLd: BlogPostingJsonLd = {
23
+ '@context': 'https://schema.org',
24
+ '@type': 'BlogPosting',
25
+ '@id': `https://example.com/posts/${id}`,
26
+ headline: post.title,
27
+ datePublished: post.date,
28
+ author: {
29
+ '@type': 'Person',
30
+ name: post.author
31
+ },
32
+ keywords: post.tags,
33
+ articleSection: post.category,
34
+ text: post.content,
35
+ url: `https://example.com/posts/${id}`
36
+ };
37
+
38
+ return (
39
+ <>
40
+ <JsonLd data={jsonLd} />
41
+ <article className="max-w-4xl mx-auto p-8">
42
+ <header className="mb-8">
43
+ <h1 className="text-4xl font-bold mb-4">{post.title}</h1>
44
+
45
+ <div className="flex items-center gap-4 text-gray-600 mb-4">
46
+ <time>{new Date(post.date).toLocaleDateString()}</time>
47
+ <span>•</span>
48
+ <span>{post.author}</span>
49
+ </div>
50
+
51
+ {post.tags.length > 0 && (
52
+ <div className="flex flex-wrap gap-2 mb-4">
53
+ {post.tags.map(tag => (
54
+ <Link
55
+ key={tag}
56
+ href={`/tags/${tag}`}
57
+ className="bg-gray-100 px-2 py-1 rounded text-sm hover:bg-gray-200"
58
+ >
59
+ #{tag}
60
+ </Link>
61
+ ))}
62
+ </div>
63
+ )}
64
+
65
+ {post.category && (
66
+ <Link
67
+ href={`/categories/${post.category}`}
68
+ className="inline-block bg-blue-100 px-3 py-1 rounded-full text-sm text-blue-800 hover:bg-blue-200"
69
+ >
70
+ {post.category}
71
+ </Link>
72
+ )}
73
+ </header>
74
+
75
+ <div
76
+ className="prose prose-lg max-w-none dark:prose-invert"
77
+ dangerouslySetInnerHTML={{ __html: post.content }}
78
+ />
79
+
80
+ <div className="mt-8 pt-8 border-t">
81
+ <Link
82
+ href="/"
83
+ className="text-blue-600 hover:underline"
84
+ >
85
+ ← 목록으로 돌아가기
86
+ </Link>
87
+ </div>
88
+ </article>
89
+ </>
90
+ );
91
+ }
src/app/search/page.tsx ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { getAllPosts } from '@/lib/markdown';
2
+ import Search from '@/components/Search';
3
+ import Link from 'next/link';
4
+
5
+ interface SearchPageProps {
6
+ searchParams: {
7
+ q?: string;
8
+ tags?: string;
9
+ category?: string;
10
+ };
11
+ }
12
+
13
+ export default async function SearchPage({ searchParams }: SearchPageProps) {
14
+ const { q = '', tags = '', category = '' } = searchParams;
15
+ const selectedTags = tags ? tags.split(',') : [];
16
+
17
+ const allPosts = await getAllPosts();
18
+
19
+ const filteredPosts = allPosts.filter(post => {
20
+ const matchesQuery = q
21
+ ? post.title.toLowerCase().includes(q.toLowerCase()) ||
22
+ post.content.toLowerCase().includes(q.toLowerCase())
23
+ : true;
24
+
25
+ const matchesTags = selectedTags.length > 0
26
+ ? selectedTags.every(tag => post.tags.includes(tag))
27
+ : true;
28
+
29
+ const matchesCategory = category
30
+ ? post.category === category
31
+ : true;
32
+
33
+ return matchesQuery && matchesTags && matchesCategory;
34
+ });
35
+
36
+ return (
37
+ <main className="min-h-screen p-8">
38
+ <h1 className="text-4xl font-bold mb-8">검색 결과</h1>
39
+
40
+ <Search
41
+ initialQuery={q}
42
+ initialTags={selectedTags}
43
+ initialCategory={category}
44
+ />
45
+
46
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
47
+ {filteredPosts.map(post => (
48
+ <article key={post.id} className="border rounded-lg p-6 hover:shadow-lg transition-shadow">
49
+ <h2 className="text-2xl font-semibold mb-2">
50
+ <Link href={`/posts/${post.id}`} className="hover:text-blue-600">
51
+ {post.title}
52
+ </Link>
53
+ </h2>
54
+
55
+ <div className="text-gray-600 mb-4">
56
+ <time>{new Date(post.date).toLocaleDateString()}</time>
57
+ <span className="mx-2">•</span>
58
+ <span>{post.author}</span>
59
+ </div>
60
+
61
+ {post.tags.length > 0 && (
62
+ <div className="flex flex-wrap gap-2 mb-4">
63
+ {post.tags.map(tag => (
64
+ <Link
65
+ key={tag}
66
+ href={`/tags/${tag}`}
67
+ className="bg-gray-100 px-2 py-1 rounded text-sm hover:bg-gray-200"
68
+ >
69
+ #{tag}
70
+ </Link>
71
+ ))}
72
+ </div>
73
+ )}
74
+
75
+ {post.category && (
76
+ <Link
77
+ href={`/categories/${post.category}`}
78
+ className="inline-block bg-blue-100 px-3 py-1 rounded-full text-sm text-blue-800 hover:bg-blue-200"
79
+ >
80
+ {post.category}
81
+ </Link>
82
+ )}
83
+ </article>
84
+ ))}
85
+ </div>
86
+
87
+ {filteredPosts.length === 0 && (
88
+ <div className="text-center text-gray-600 dark:text-gray-400 mt-8">
89
+ 검색 결과가 없습니다.
90
+ </div>
91
+ )}
92
+ </main>
93
+ );
94
+ }
src/app/tags/[tag]/page.tsx ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { getPostsByTag } from '@/lib/markdown';
2
+ import Link from 'next/link';
3
+ import { notFound } from 'next/navigation';
4
+
5
+ interface TagPageProps {
6
+ params: Promise<{
7
+ tag: string;
8
+ }>;
9
+ }
10
+
11
+ export default async function TagPage({ params }: TagPageProps) {
12
+ const resolvedParams = await params;
13
+ const { tag } = resolvedParams;
14
+ const posts = await getPostsByTag(tag);
15
+
16
+ if (posts.length === 0) {
17
+ notFound();
18
+ }
19
+
20
+ return (
21
+ <main className="min-h-screen p-8">
22
+ <h1 className="text-4xl font-bold mb-8">
23
+ 태그: #{tag}
24
+ </h1>
25
+
26
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
27
+ {posts.map(post => (
28
+ <article key={post.id} className="border rounded-lg p-6 hover:shadow-lg transition-shadow">
29
+ <h2 className="text-2xl font-semibold mb-2">
30
+ <Link href={`/posts/${post.id}`} className="hover:text-blue-600">
31
+ {post.title}
32
+ </Link>
33
+ </h2>
34
+
35
+ <div className="text-gray-600 mb-4">
36
+ <time>{new Date(post.date).toLocaleDateString()}</time>
37
+ <span className="mx-2">•</span>
38
+ <span>{post.author}</span>
39
+ </div>
40
+
41
+ {post.tags.length > 0 && (
42
+ <div className="flex flex-wrap gap-2 mb-4">
43
+ {post.tags.map(t => (
44
+ <Link
45
+ key={t}
46
+ href={`/tags/${t}`}
47
+ className={`px-2 py-1 rounded text-sm ${
48
+ t === tag
49
+ ? 'bg-blue-100 text-blue-800'
50
+ : 'bg-gray-100 hover:bg-gray-200'
51
+ }`}
52
+ >
53
+ #{t}
54
+ </Link>
55
+ ))}
56
+ </div>
57
+ )}
58
+
59
+ {post.category && (
60
+ <Link
61
+ href={`/categories/${post.category}`}
62
+ className="inline-block bg-blue-100 px-3 py-1 rounded-full text-sm text-blue-800 hover:bg-blue-200"
63
+ >
64
+ {post.category}
65
+ </Link>
66
+ )}
67
+ </article>
68
+ ))}
69
+ </div>
70
+
71
+ <div className="mt-8">
72
+ <Link
73
+ href="/"
74
+ className="text-blue-600 hover:underline"
75
+ >
76
+ ← 목록으로 돌아가기
77
+ </Link>
78
+ </div>
79
+ </main>
80
+ );
81
+ }
src/app/versions/page.tsx ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useState, useEffect } from 'react';
4
+ import { CommitInfo, getCommitHistory, rollbackToCommit, getFileDiff, createBackup } from '@/lib/git';
5
+ import { format } from 'date-fns';
6
+ import { ko } from 'date-fns/locale';
7
+
8
+ export default function VersionsPage() {
9
+ const [commits, setCommits] = useState<CommitInfo[]>([]);
10
+ const [selectedCommit, setSelectedCommit] = useState<string | null>(null);
11
+ const [diffContent, setDiffContent] = useState<string>('');
12
+ const [isLoading, setIsLoading] = useState(true);
13
+ const [error, setError] = useState<string | null>(null);
14
+
15
+ useEffect(() => {
16
+ loadCommitHistory();
17
+ }, []);
18
+
19
+ async function loadCommitHistory() {
20
+ try {
21
+ const history = await getCommitHistory();
22
+ setCommits(history);
23
+ setIsLoading(false);
24
+ } catch (err) {
25
+ setError('커밋 이력을 불러오는데 실패했습니다.');
26
+ setIsLoading(false);
27
+ }
28
+ }
29
+
30
+ async function handleRollback(commitHash: string) {
31
+ if (!confirm('이 버전으로 롤백하시겠습니까? 현재 작업중인 내용이 있다면 먼저 커밋하세요.')) {
32
+ return;
33
+ }
34
+
35
+ try {
36
+ await rollbackToCommit(commitHash);
37
+ alert('성공적으로 롤백되었습니다.');
38
+ loadCommitHistory();
39
+ } catch (err) {
40
+ setError('롤백 중 오류가 발생했습니다.');
41
+ }
42
+ }
43
+
44
+ async function handleViewDiff(commitHash: string) {
45
+ try {
46
+ if (commits.length > 1) {
47
+ const currentIndex = commits.findIndex(c => c.hash === commitHash);
48
+ if (currentIndex >= 0 && currentIndex < commits.length - 1) {
49
+ const diff = await getFileDiff(
50
+ '.',
51
+ commitHash,
52
+ commits[currentIndex + 1].hash
53
+ );
54
+ setDiffContent(diff);
55
+ setSelectedCommit(commitHash);
56
+ }
57
+ }
58
+ } catch (err) {
59
+ setError('변경사항을 불러오는데 실패했습니다.');
60
+ }
61
+ }
62
+
63
+ if (isLoading) {
64
+ return <div className="p-8">로딩 중...</div>;
65
+ }
66
+
67
+ if (error) {
68
+ return <div className="p-8 text-red-600">{error}</div>;
69
+ }
70
+
71
+ return (
72
+ <main className="min-h-screen p-8">
73
+ <h1 className="text-3xl font-bold mb-8">버전 관리</h1>
74
+
75
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
76
+ <div className="space-y-4">
77
+ <h2 className="text-xl font-semibold mb-4">커밋 이력</h2>
78
+ {commits.map((commit) => (
79
+ <div
80
+ key={commit.hash}
81
+ className={`p-4 border rounded-lg ${
82
+ selectedCommit === commit.hash ? 'border-blue-500 bg-blue-50' : ''
83
+ }`}
84
+ >
85
+ <div className="flex justify-between items-start mb-2">
86
+ <div className="font-medium">{commit.message}</div>
87
+ <div className="text-sm text-gray-500">
88
+ {format(new Date(commit.date), 'PPP p', { locale: ko })}
89
+ </div>
90
+ </div>
91
+
92
+ <div className="text-sm text-gray-600 mb-2">
93
+ 작성자: {commit.author}
94
+ </div>
95
+
96
+ <div className="space-y-1 text-sm">
97
+ {commit.changes.added.length > 0 && (
98
+ <div className="text-green-600">
99
+ 추가: {commit.changes.added.join(', ')}
100
+ </div>
101
+ )}
102
+ {commit.changes.modified.length > 0 && (
103
+ <div className="text-blue-600">
104
+ 수정: {commit.changes.modified.join(', ')}
105
+ </div>
106
+ )}
107
+ {commit.changes.deleted.length > 0 && (
108
+ <div className="text-red-600">
109
+ 삭제: {commit.changes.deleted.join(', ')}
110
+ </div>
111
+ )}
112
+ </div>
113
+
114
+ <div className="mt-4 space-x-2">
115
+ <button
116
+ onClick={() => handleViewDiff(commit.hash)}
117
+ className="px-3 py-1 text-sm bg-gray-100 hover:bg-gray-200 rounded"
118
+ >
119
+ 변경사항 보기
120
+ </button>
121
+ <button
122
+ onClick={() => handleRollback(commit.hash)}
123
+ className="px-3 py-1 text-sm bg-red-100 hover:bg-red-200 text-red-700 rounded"
124
+ >
125
+ 이 버전으로 롤백
126
+ </button>
127
+ </div>
128
+ </div>
129
+ ))}
130
+ </div>
131
+
132
+ {selectedCommit && (
133
+ <div className="space-y-4">
134
+ <h2 className="text-xl font-semibold mb-4">변경사항</h2>
135
+ <pre className="p-4 bg-gray-50 rounded-lg overflow-x-auto text-sm">
136
+ {diffContent || '변경사항이 없습니다.'}
137
+ </pre>
138
+ </div>
139
+ )}
140
+ </div>
141
+ </main>
142
+ );
143
+ }
src/components/JsonLd.tsx ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { JsonLdContext } from '@/types/jsonld';
2
+
3
+ interface JsonLdProps {
4
+ data: JsonLdContext;
5
+ }
6
+
7
+ export default function JsonLd({ data }: JsonLdProps) {
8
+ return (
9
+ <script
10
+ type="application/ld+json"
11
+ dangerouslySetInnerHTML={{
12
+ __html: JSON.stringify(data, null, 2)
13
+ }}
14
+ />
15
+ );
16
+ }
src/components/Search.tsx ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useState, useCallback } from 'react';
4
+ import { useRouter } from 'next/navigation';
5
+ import debounce from 'lodash/debounce';
6
+
7
+ interface SearchProps {
8
+ initialQuery?: string;
9
+ initialTags?: string[];
10
+ initialCategory?: string;
11
+ }
12
+
13
+ export default function Search({ initialQuery = '', initialTags = [], initialCategory = '' }: SearchProps) {
14
+ const [query, setQuery] = useState(initialQuery);
15
+ const [selectedTags, setSelectedTags] = useState<string[]>(initialTags);
16
+ const [selectedCategory, setSelectedCategory] = useState(initialCategory);
17
+ const router = useRouter();
18
+
19
+ const debouncedSearch = useCallback(
20
+ debounce((searchQuery: string, tags: string[], category: string) => {
21
+ const params = new URLSearchParams();
22
+
23
+ if (searchQuery) {
24
+ params.set('q', searchQuery);
25
+ }
26
+
27
+ if (tags.length > 0) {
28
+ params.set('tags', tags.join(','));
29
+ }
30
+
31
+ if (category) {
32
+ params.set('category', category);
33
+ }
34
+
35
+ const queryString = params.toString();
36
+ router.push(`/search${queryString ? `?${queryString}` : ''}`);
37
+ }, 300),
38
+ []
39
+ );
40
+
41
+ const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
42
+ const newQuery = e.target.value;
43
+ setQuery(newQuery);
44
+ debouncedSearch(newQuery, selectedTags, selectedCategory);
45
+ };
46
+
47
+ const toggleTag = (tag: string) => {
48
+ const newTags = selectedTags.includes(tag)
49
+ ? selectedTags.filter(t => t !== tag)
50
+ : [...selectedTags, tag];
51
+
52
+ setSelectedTags(newTags);
53
+ debouncedSearch(query, newTags, selectedCategory);
54
+ };
55
+
56
+ const handleCategoryChange = (category: string) => {
57
+ const newCategory = selectedCategory === category ? '' : category;
58
+ setSelectedCategory(newCategory);
59
+ debouncedSearch(query, selectedTags, newCategory);
60
+ };
61
+
62
+ return (
63
+ <div className="w-full max-w-4xl mx-auto space-y-4">
64
+ <div className="relative">
65
+ <input
66
+ type="text"
67
+ value={query}
68
+ onChange={handleSearch}
69
+ placeholder="검색어를 입력하세요..."
70
+ className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
71
+ />
72
+ </div>
73
+
74
+ <div className="flex flex-wrap gap-4">
75
+ <div className="flex-1">
76
+ <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
77
+ 태그 필터
78
+ </label>
79
+ <div className="flex flex-wrap gap-2">
80
+ {['시작하기', '가이드', '문법', 'API', '참조'].map(tag => (
81
+ <button
82
+ key={tag}
83
+ onClick={() => toggleTag(tag)}
84
+ className={`px-3 py-1 rounded-full text-sm ${
85
+ selectedTags.includes(tag)
86
+ ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200'
87
+ : 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300'
88
+ } hover:opacity-80 transition-opacity`}
89
+ >
90
+ #{tag}
91
+ </button>
92
+ ))}
93
+ </div>
94
+ </div>
95
+
96
+ <div className="flex-1">
97
+ <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
98
+ 카테고리 필터
99
+ </label>
100
+ <div className="flex flex-wrap gap-2">
101
+ {['가이드', '문서', '소개'].map(category => (
102
+ <button
103
+ key={category}
104
+ onClick={() => handleCategoryChange(category)}
105
+ className={`px-3 py-1 rounded-full text-sm ${
106
+ selectedCategory === category
107
+ ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'
108
+ : 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300'
109
+ } hover:opacity-80 transition-opacity`}
110
+ >
111
+ {category}
112
+ </button>
113
+ ))}
114
+ </div>
115
+ </div>
116
+ </div>
117
+ </div>
118
+ );
119
+ }
src/content/articles/getting-started.md ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: AI 친화적인 포털 시작하기
3
+ date: 2024-03-21
4
+ author: 관리자
5
+ tags: [시작하기, 가이드, 튜토리얼]
6
+ category: 가이드
7
+ ---
8
+
9
+ # AI 친화적인 포털 시작하기
10
+
11
+ 이 문서는 AI 친화적인 마크다운/JSON 기반 포털을 사용하는 방법을 안내합니다.
12
+
13
+ ## 주요 기능
14
+
15
+ - 마크다운 기반 콘텐츠 작성
16
+ - JSON 형식의 메타데이터 관리
17
+ - AI 친화적인 API 엔드포인트
18
+ - 모던한 웹 인터페이스
19
+
20
+ ## 콘텐츠 작성 방법
21
+
22
+ 1. 마크다운 파일 생성
23
+ ```bash
24
+ touch src/content/articles/새로운-문서.md
25
+ ```
26
+
27
+ 2. YAML Front Matter 추가
28
+ ```markdown
29
+ ---
30
+ title: 문서 제목
31
+ date: 2024-03-21
32
+ author: 작성자
33
+ tags: [태그1, 태그2]
34
+ category: 카테고리
35
+ ---
36
+ ```
37
+
38
+ 3. 콘텐츠 작성
39
+ - 마크다운 문법을 사용하여 콘텐츠를 작성합니다
40
+ - 코드 블록, 이미지, 링크 등을 활용할 수 있습니다
41
+
42
+ ## API 사용 방법
43
+
44
+ ```javascript
45
+ // 모든 콘텐츠 조회
46
+ fetch('/api/content')
47
+ .then(response => response.json())
48
+ .then(data => console.log(data));
49
+
50
+ // 특정 콘텐츠 조회
51
+ fetch('/api/content?id=articles/getting-started')
52
+ .then(response => response.json())
53
+ .then(data => console.log(data));
54
+
55
+ // 태그로 필터링
56
+ fetch('/api/content?tag=시작하기')
57
+ .then(response => response.json())
58
+ .then(data => console.log(data));
59
+ ```
60
+
61
+ ## 도움말
62
+
63
+ 더 자세한 정보는 [문서](/docs)를 참조하세요.
src/content/articles/markdown-guide.md ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: 마크다운 작성 가이드
3
+ date: 2024-03-21
4
+ author: 관리자
5
+ tags: [마크다운, 가이드, 문법]
6
+ category: 가이드
7
+ ---
8
+
9
+ # 마크다운 작성 가이드
10
+
11
+ 이 문서는 마크다운 문법을 사용하여 콘텐츠를 작성하는 방법을 설명합니다.
12
+
13
+ ## 기본 문법
14
+
15
+ ### 제목
16
+
17
+ ```markdown
18
+ # 제목 1
19
+ ## 제목 2
20
+ ### 제목 3
21
+ ```
22
+
23
+ ### 텍스트 서식
24
+
25
+ ```markdown
26
+ *이탤릭체*
27
+ **볼드체**
28
+ ~~취소선~~
29
+ ```
30
+
31
+ ### 목록
32
+
33
+ ```markdown
34
+ - 순서 없는 목록
35
+ - 들여쓰기 목록
36
+ 1. 순서 있는 목록
37
+ 2. 두 번째 항목
38
+ ```
39
+
40
+ ### 링크와 이미지
41
+
42
+ ```markdown
43
+ [링크 텍스트](https://example.com)
44
+ ![이미지 설명](이미지-경로.jpg)
45
+ ```
46
+
47
+ ### 코드
48
+
49
+ ```markdown
50
+ `인라인 코드`
51
+
52
+ ```javascript
53
+ // 코드 블록
54
+ function hello() {
55
+ console.log('Hello, world!');
56
+ }
57
+ ```
58
+ ```
59
+
60
+ ## 고급 기능
61
+
62
+ ### 테이블
63
+
64
+ ```markdown
65
+ | 헤더 1 | 헤더 2 |
66
+ |--------|--------|
67
+ | 셀 1 | 셀 2 |
68
+ | 셀 3 | 셀 4 |
69
+ ```
70
+
71
+ ### 인용문
72
+
73
+ ```markdown
74
+ > 인용문입니다.
75
+ > 여러 줄의 인용문도 가능합니다.
76
+ ```
77
+
78
+ ### 수평선
79
+
80
+ ```markdown
81
+ ---
82
+ ```
83
+
84
+ ## 팁과 트릭
85
+
86
+ 1. YAML Front Matter를 사용하여 메타데이터를 추가하세요
87
+ 2. 이미지는 `public` 디렉토리에 저장하세요
88
+ 3. 코드 블록에는 언어를 지정하여 문법 강조를 활성화하세요
src/content/articles/welcome.md ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: AI 친화적인 포털에 오신 것을 환영합니다
3
+ date: 2024-03-21
4
+ author: 관리자
5
+ tags: [시작하기, 소개]
6
+ category: 소개
7
+ ---
8
+
9
+ # AI 친화적인 포털에 오신 것을 환영합니다
10
+
11
+ 이 포털은 AI가 쉽게 이해하고 활용할 수 있는 마크다운과 JSON 기반의 콘텐츠 관리 시스템입니다.
12
+
13
+ ## 주요 특징
14
+
15
+ - 마크다운 기반 콘텐츠 작성
16
+ - JSON 형식의 메타데이터 관리
17
+ - AI 친화적인 API 엔드포인트
18
+ - 모던한 웹 인터페이스
19
+
20
+ ## 시작하기
21
+
22
+ 1. 콘텐츠 작성
23
+ - 마크다운 형식으로 문서를 작성합니다
24
+ - YAML Front Matter를 사용하여 메타데이터를 추가합니다
25
+
26
+ 2. API 활용
27
+ - `/api/content` 엔드포인트를 통해 콘텐츠에 접근
28
+ - `/api/search`를 사용하여 콘텐츠 검색
29
+
30
+ ## 예제 코드
31
+
32
+ ```javascript
33
+ // API 호출 예제
34
+ fetch('/api/content')
35
+ .then(response => response.json())
36
+ .then(data => console.log(data));
37
+ ```
38
+
39
+ ## 도움말
40
+
41
+ 더 자세한 정보는 [문서](/docs)를 참조하세요.
src/content/docs/api-reference.md ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: API 참조 문서
3
+ date: 2024-03-21
4
+ author: 관리자
5
+ tags: [API, 참조, 문서]
6
+ category: 문서
7
+ ---
8
+
9
+ # API 참조 문서
10
+
11
+ 이 문서는 AI 친화적인 포털의 API 엔드포인트를 설명합니다.
12
+
13
+ ## 콘텐츠 API
14
+
15
+ ### 모든 콘텐츠 조회
16
+
17
+ ```http
18
+ GET /api/content
19
+ ```
20
+
21
+ 응답 예시:
22
+ ```json
23
+ [
24
+ {
25
+ "id": "articles/getting-started",
26
+ "title": "AI 친화적인 포털 시작하기",
27
+ "date": "2024-03-21",
28
+ "author": "관리자",
29
+ "tags": ["시작하기", "가이드", "튜토리얼"],
30
+ "category": "가이드",
31
+ "content": "..."
32
+ }
33
+ ]
34
+ ```
35
+
36
+ ### 특정 콘텐츠 조회
37
+
38
+ ```http
39
+ GET /api/content?id=articles/getting-started
40
+ ```
41
+
42
+ ### 태그로 필터링
43
+
44
+ ```http
45
+ GET /api/content?tag=시작하기
46
+ ```
47
+
48
+ ### 카테고리로 필터링
49
+
50
+ ```http
51
+ GET /api/content?category=가이드
52
+ ```
53
+
54
+ ## 응답 형식
55
+
56
+ ### 성공 응답
57
+
58
+ ```json
59
+ {
60
+ "id": "string",
61
+ "title": "string",
62
+ "date": "string",
63
+ "author": "string",
64
+ "tags": ["string"],
65
+ "category": "string",
66
+ "content": "string"
67
+ }
68
+ ```
69
+
70
+ ### 에러 응답
71
+
72
+ ```json
73
+ {
74
+ "error": "string"
75
+ }
76
+ ```
77
+
78
+ ## 상태 코드
79
+
80
+ - 200: 성공
81
+ - 404: 콘텐츠를 찾을 수 없음
82
+ - 500: 서버 에러
83
+
84
+ ## 사용 예시
85
+
86
+ ```javascript
87
+ // 모든 콘텐츠 조회
88
+ const response = await fetch('/api/content');
89
+ const data = await response.json();
90
+
91
+ // 특정 콘텐츠 조회
92
+ const response = await fetch('/api/content?id=articles/getting-started');
93
+ const data = await response.json();
94
+
95
+ // 태그로 필터링
96
+ const response = await fetch('/api/content?tag=시작하기');
97
+ const data = await response.json();
98
+ ```
src/lib/git.ts ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ export interface CommitInfo {
4
+ hash: string;
5
+ author: string;
6
+ date: string;
7
+ message: string;
8
+ changes: {
9
+ added: string[];
10
+ modified: string[];
11
+ deleted: string[];
12
+ };
13
+ }
14
+
15
+ export async function getCommitHistory(): Promise<CommitInfo[]> {
16
+ try {
17
+ const response = await fetch('/api/git?action=history');
18
+ if (!response.ok) {
19
+ throw new Error('커밋 이력 조회 실패');
20
+ }
21
+ const data = await response.json();
22
+ return data.commits;
23
+ } catch (error) {
24
+ console.error('커밋 이력 조회 실패:', error);
25
+ throw error;
26
+ }
27
+ }
28
+
29
+ export async function rollbackToCommit(commitHash: string): Promise<void> {
30
+ try {
31
+ const response = await fetch('/api/git', {
32
+ method: 'POST',
33
+ headers: {
34
+ 'Content-Type': 'application/json',
35
+ },
36
+ body: JSON.stringify({
37
+ action: 'rollback',
38
+ commitHash,
39
+ }),
40
+ });
41
+
42
+ if (!response.ok) {
43
+ const data = await response.json();
44
+ throw new Error(data.error || '롤백 실패');
45
+ }
46
+ } catch (error) {
47
+ console.error('롤백 실패:', error);
48
+ throw error;
49
+ }
50
+ }
51
+
52
+ export async function getFileDiff(
53
+ filePath: string,
54
+ commitHash1: string,
55
+ commitHash2: string
56
+ ): Promise<string> {
57
+ try {
58
+ const response = await fetch(
59
+ `/api/git?action=diff&hash1=${commitHash1}&hash2=${commitHash2}&file=${encodeURIComponent(filePath)}`
60
+ );
61
+
62
+ if (!response.ok) {
63
+ const data = await response.json();
64
+ throw new Error(data.error || '파일 변경사항 비교 실패');
65
+ }
66
+
67
+ const data = await response.json();
68
+ return data.diff;
69
+ } catch (error) {
70
+ console.error('파일 변경사항 비교 실패:', error);
71
+ throw error;
72
+ }
73
+ }
74
+
75
+ export async function commitChanges(message: string): Promise<string> {
76
+ try {
77
+ const response = await fetch('/api/git', {
78
+ method: 'POST',
79
+ headers: {
80
+ 'Content-Type': 'application/json',
81
+ },
82
+ body: JSON.stringify({
83
+ action: 'commit',
84
+ message,
85
+ }),
86
+ });
87
+
88
+ if (!response.ok) {
89
+ const data = await response.json();
90
+ throw new Error(data.error || '커밋 실패');
91
+ }
92
+
93
+ const data = await response.json();
94
+ return data.message;
95
+ } catch (error) {
96
+ console.error('변경사항 커밋 실패:', error);
97
+ throw error;
98
+ }
99
+ }
src/lib/markdown.ts ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import matter from 'gray-matter';
4
+ import { remark } from 'remark';
5
+ import html from 'remark-html';
6
+
7
+ const contentDirectory = path.join(process.cwd(), 'src/content');
8
+
9
+ export interface PostMetadata {
10
+ title: string;
11
+ date: string;
12
+ author: string;
13
+ tags: string[];
14
+ category?: string;
15
+ }
16
+
17
+ export interface Post extends PostMetadata {
18
+ id: string;
19
+ content: string;
20
+ }
21
+
22
+ export async function getPostById(id: string): Promise<Post | null> {
23
+ try {
24
+ const fullPath = path.join(contentDirectory, `${id}.md`);
25
+ const fileContents = fs.readFileSync(fullPath, 'utf8');
26
+
27
+ const { data, content } = matter(fileContents);
28
+ const processedContent = await remark()
29
+ .use(html)
30
+ .process(content);
31
+
32
+ return {
33
+ id,
34
+ content: processedContent.toString(),
35
+ title: data.title,
36
+ date: data.date,
37
+ author: data.author,
38
+ tags: data.tags || [],
39
+ category: data.category
40
+ };
41
+ } catch (error) {
42
+ console.error(`Error getting post ${id}:`, error);
43
+ return null;
44
+ }
45
+ }
46
+
47
+ export async function getAllPosts(): Promise<Post[]> {
48
+ const posts: Post[] = [];
49
+
50
+ try {
51
+ const directories = ['articles', 'docs'];
52
+
53
+ for (const dir of directories) {
54
+ const dirPath = path.join(contentDirectory, dir);
55
+ if (!fs.existsSync(dirPath)) continue;
56
+
57
+ const files = fs.readdirSync(dirPath);
58
+
59
+ for (const file of files) {
60
+ if (!file.endsWith('.md')) continue;
61
+
62
+ const id = `${dir}/${file.replace(/\.md$/, '')}`;
63
+ const post = await getPostById(id);
64
+ if (post) posts.push(post);
65
+ }
66
+ }
67
+
68
+ return posts.sort((a, b) => (new Date(b.date)).getTime() - (new Date(a.date)).getTime());
69
+ } catch (error) {
70
+ console.error('Error getting all posts:', error);
71
+ return [];
72
+ }
73
+ }
74
+
75
+ export async function getPostsByTag(tag: string): Promise<Post[]> {
76
+ const allPosts = await getAllPosts();
77
+ return allPosts.filter(post => post.tags.includes(tag));
78
+ }
79
+
80
+ export async function getPostsByCategory(category: string): Promise<Post[]> {
81
+ const allPosts = await getAllPosts();
82
+ return allPosts.filter(post => post.category === category);
83
+ }
84
+
85
+ export async function getAllTags(): Promise<string[]> {
86
+ const allPosts = await getAllPosts();
87
+ const tagSet = new Set<string>();
88
+
89
+ allPosts.forEach(post => {
90
+ post.tags.forEach(tag => tagSet.add(tag));
91
+ });
92
+
93
+ return Array.from(tagSet).sort();
94
+ }
95
+
96
+ export async function getAllCategories(): Promise<string[]> {
97
+ const allPosts = await getAllPosts();
98
+ const categorySet = new Set<string>();
99
+
100
+ allPosts.forEach(post => {
101
+ if (post.category) categorySet.add(post.category);
102
+ });
103
+
104
+ return Array.from(categorySet).sort();
105
+ }
src/types/jsonld.ts ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export interface JsonLdContext {
2
+ '@context': string | Record<string, string>;
3
+ }
4
+
5
+ export interface BlogPostingJsonLd extends JsonLdContext {
6
+ '@type': 'BlogPosting';
7
+ '@id': string;
8
+ headline: string;
9
+ datePublished: string;
10
+ author: {
11
+ '@type': 'Person';
12
+ name: string;
13
+ };
14
+ keywords: string[];
15
+ articleSection?: string;
16
+ description?: string;
17
+ text: string;
18
+ url: string;
19
+ }
20
+
21
+ export interface WebPageJsonLd extends JsonLdContext {
22
+ '@type': 'WebPage';
23
+ '@id': string;
24
+ name: string;
25
+ description?: string;
26
+ url: string;
27
+ isPartOf: {
28
+ '@type': 'WebSite';
29
+ '@id': string;
30
+ name: string;
31
+ url: string;
32
+ };
33
+ }
34
+
35
+ export interface BreadcrumbListJsonLd extends JsonLdContext {
36
+ '@type': 'BreadcrumbList';
37
+ itemListElement: {
38
+ '@type': 'ListItem';
39
+ position: number;
40
+ item: {
41
+ '@id': string;
42
+ name: string;
43
+ };
44
+ }[];
45
+ }
tailwind.config.ts ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Config } from 'tailwindcss';
2
+
3
+ const config: Config = {
4
+ content: [
5
+ './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
6
+ './src/components/**/*.{js,ts,jsx,tsx,mdx}',
7
+ './src/app/**/*.{js,ts,jsx,tsx,mdx}',
8
+ ],
9
+ theme: {
10
+ extend: {
11
+ backgroundImage: {
12
+ 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
13
+ 'gradient-conic':
14
+ 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
15
+ },
16
+ },
17
+ },
18
+ plugins: [
19
+ require('@tailwindcss/typography'),
20
+ ],
21
+ };
22
+
23
+ export default config;