coding-friend-cli 1.1.1 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -6
- package/dist/{chunk-AQXTNLQD.js → chunk-6OI37OZX.js} +9 -1
- package/dist/chunk-CSF4FAHL.js +129 -0
- package/dist/{chunk-KZT4AFDW.js → chunk-Q4DKU5IG.js} +4 -6
- package/dist/dev-MAAWPWML.js +290 -0
- package/dist/{host-JBTJCWM2.js → host-2WINWEW7.js} +2 -2
- package/dist/index.js +44 -6
- package/dist/{init-E6CL3UZQ.js → init-CTCDQKIQ.js} +24 -13
- package/dist/{mcp-MWESK6UX.js → mcp-43HCE2KD.js} +2 -2
- package/dist/postinstall.js +1 -1
- package/dist/{statusline-7D6YU5YM.js → statusline-ARI7I5YM.js} +1 -1
- package/dist/{update-IH3G4SN5.js → update-GGCBM7U4.js} +91 -40
- package/lib/learn-host/.prettierignore +4 -0
- package/lib/learn-host/.prettierrc +8 -0
- package/lib/learn-host/CHANGELOG.md +14 -0
- package/lib/learn-host/README.md +114 -0
- package/lib/learn-host/eslint.config.mjs +6 -0
- package/lib/learn-host/next-env.d.ts +1 -1
- package/lib/learn-host/next.config.ts +4 -0
- package/lib/learn-host/package-lock.json +6039 -391
- package/lib/learn-host/package.json +30 -15
- package/lib/learn-host/public/logo.svg +1 -0
- package/lib/learn-host/src/app/[category]/[slug]/page.tsx +36 -32
- package/lib/learn-host/src/app/[category]/page.tsx +2 -3
- package/lib/learn-host/src/app/apple-icon.svg +1 -0
- package/lib/learn-host/src/app/globals.css +74 -14
- package/lib/learn-host/src/app/icon.svg +1 -0
- package/lib/learn-host/src/app/layout.tsx +29 -9
- package/lib/learn-host/src/app/page.tsx +9 -11
- package/lib/learn-host/src/components/Breadcrumbs.tsx +12 -4
- package/lib/learn-host/src/components/DocCard.tsx +28 -10
- package/lib/learn-host/src/components/MarkdownRenderer.tsx +6 -2
- package/lib/learn-host/src/components/MobileNav.tsx +43 -35
- package/lib/learn-host/src/components/PagefindSearch.tsx +177 -54
- package/lib/learn-host/src/components/Sidebar.tsx +27 -29
- package/lib/learn-host/src/components/TableOfContents.tsx +62 -0
- package/lib/learn-host/src/components/TagBadge.tsx +1 -1
- package/lib/learn-host/src/components/ThemeToggle.tsx +36 -9
- package/lib/learn-host/src/components/layout/Footer.tsx +41 -0
- package/lib/learn-host/src/components/layout/Header.tsx +117 -0
- package/lib/learn-host/src/lib/docs.ts +98 -8
- package/lib/learn-host/src/lib/types.ts +7 -1
- package/lib/learn-host/tsconfig.json +8 -2
- package/lib/learn-host/tsconfig.tsbuildinfo +1 -0
- package/lib/learn-mcp/CHANGELOG.md +12 -0
- package/lib/learn-mcp/README.md +169 -0
- package/lib/learn-mcp/package.json +2 -1
- package/lib/learn-mcp/src/index.ts +1 -1
- package/lib/learn-mcp/src/lib/docs.ts +1 -3
- package/lib/learn-mcp/src/lib/knowledge.ts +2 -1
- package/lib/learn-mcp/src/tools/get-review-list.ts +1 -4
- package/lib/learn-mcp/src/tools/search-docs.ts +1 -4
- package/package.json +14 -6
- package/dist/chunk-VHZQ6KEU.js +0 -73
- package/lib/learn-host/src/app/search/page.tsx +0 -19
- package/lib/learn-host/src/components/SearchBar.tsx +0 -36
|
@@ -1,31 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coding-friend-learn-host",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"private": true,
|
|
5
5
|
"scripts": {
|
|
6
|
-
"dev": "next dev",
|
|
6
|
+
"dev": "next dev -p 3333",
|
|
7
7
|
"build": "next build",
|
|
8
8
|
"postbuild": "pagefind --site .next/server/app --output-path public/_pagefind",
|
|
9
|
-
"start": "next start"
|
|
9
|
+
"start": "next start",
|
|
10
|
+
"lint": "eslint src/",
|
|
11
|
+
"lint:fix": "eslint src/ --fix",
|
|
12
|
+
"format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json,css,md}\"",
|
|
13
|
+
"format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json,css,md}\""
|
|
10
14
|
},
|
|
11
15
|
"dependencies": {
|
|
12
|
-
"
|
|
13
|
-
"react": "^
|
|
14
|
-
"react-
|
|
16
|
+
"@radix-ui/react-dialog": "^1.1.15",
|
|
17
|
+
"@radix-ui/react-visually-hidden": "^1.2.4",
|
|
18
|
+
"babel-plugin-react-compiler": "^1.0.0",
|
|
19
|
+
"cmdk": "^1.1.1",
|
|
20
|
+
"gray-matter": "^4.0.3",
|
|
21
|
+
"next": "^16.1.6",
|
|
22
|
+
"next-themes": "^0.4.0",
|
|
23
|
+
"react": "^19.2.4",
|
|
24
|
+
"react-dom": "^19.2.4",
|
|
15
25
|
"react-markdown": "^9.0.0",
|
|
16
|
-
"remark-gfm": "^4.0.0",
|
|
17
26
|
"rehype-highlight": "^7.0.0",
|
|
18
|
-
"
|
|
19
|
-
"
|
|
27
|
+
"rehype-slug": "^6.0.0",
|
|
28
|
+
"remark-gfm": "^4.0.0"
|
|
20
29
|
},
|
|
21
30
|
"devDependencies": {
|
|
22
|
-
"
|
|
23
|
-
"@types/node": "^22.0.0",
|
|
24
|
-
"@types/react": "^19.0.0",
|
|
25
|
-
"@types/react-dom": "^19.0.0",
|
|
26
|
-
"tailwindcss": "^4.0.0",
|
|
31
|
+
"@eslint/js": "^9.39.3",
|
|
27
32
|
"@tailwindcss/postcss": "^4.0.0",
|
|
28
33
|
"@tailwindcss/typography": "^0.5.0",
|
|
29
|
-
"
|
|
34
|
+
"@types/node": "^22.0.0",
|
|
35
|
+
"@types/react": "^19.2.0",
|
|
36
|
+
"@types/react-dom": "^19.2.0",
|
|
37
|
+
"eslint": "^9.39.3",
|
|
38
|
+
"eslint-config-next": "^16.1.6",
|
|
39
|
+
"eslint-config-prettier": "^10.1.8",
|
|
40
|
+
"pagefind": "^1.3.0",
|
|
41
|
+
"prettier": "^3.8.1",
|
|
42
|
+
"prettier-plugin-tailwindcss": "^0.7.2",
|
|
43
|
+
"tailwindcss": "^4.0.0",
|
|
44
|
+
"typescript": "^5.7.0"
|
|
30
45
|
}
|
|
31
46
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512" x="0" y="0" viewBox="0 0 64 64" style="enable-background:new 0 0 512 512" xml:space="preserve" class=""><g><linearGradient id="a" x1="78.644" x2="25.425" y1="74.123" y2="11.863" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b45309"></stop><stop offset="1" stop-color="#f59e0b"></stop></linearGradient><path fill="url(#a)" fill-rule="evenodd" d="M2 14C2 7.373 7.373 2 14 2h36c6.627 0 12 5.373 12 12v28c0 6.627-5.373 12-12 12H39.908l-6.403 7.317a2 2 0 0 1-3.01 0L24.093 54H14C7.373 54 2 48.627 2 42z" clip-rule="evenodd" opacity="1" data-original="url(#a)" class=""></path><path fill="#ffffff" d="M32 14a2 2 0 0 1 1.95 1.556l1.107 4.867a6 6 0 0 0 4.52 4.52l4.867 1.107a2 2 0 0 1 0 3.9l-4.867 1.107a6 6 0 0 0-4.52 4.52l-1.107 4.866a2 2 0 0 1-3.9 0l-1.107-4.866a6 6 0 0 0-4.52-4.52l-4.866-1.107a2 2 0 0 1 0-3.9l4.866-1.107a6 6 0 0 0 4.52-4.52l1.107-4.866A2 2 0 0 1 32 14z"></path></g></svg>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { getAllDocs, getDocBySlug } from "@/lib/docs";
|
|
1
|
+
import { getAllDocs, getDocBySlug, extractHeadings } from "@/lib/docs";
|
|
2
2
|
import Breadcrumbs from "@/components/Breadcrumbs";
|
|
3
3
|
import MarkdownRenderer from "@/components/MarkdownRenderer";
|
|
4
|
+
import TableOfContents from "@/components/TableOfContents";
|
|
4
5
|
import TagBadge from "@/components/TagBadge";
|
|
5
6
|
import { notFound } from "next/navigation";
|
|
6
7
|
|
|
@@ -12,7 +13,6 @@ export async function generateStaticParams() {
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export const dynamicParams = true;
|
|
15
|
-
export const revalidate = 10;
|
|
16
16
|
|
|
17
17
|
export default async function DocPage({
|
|
18
18
|
params,
|
|
@@ -24,39 +24,43 @@ export default async function DocPage({
|
|
|
24
24
|
if (!doc) notFound();
|
|
25
25
|
|
|
26
26
|
const categoryDisplay = category.replace(/[_-]/g, " ");
|
|
27
|
+
const headings = extractHeadings(doc.content);
|
|
27
28
|
|
|
28
29
|
return (
|
|
29
|
-
<
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
{
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
30
|
+
<div className="flex w-full">
|
|
31
|
+
<article className="min-w-0 flex-1" data-pagefind-body>
|
|
32
|
+
<Breadcrumbs
|
|
33
|
+
crumbs={[
|
|
34
|
+
{ label: categoryDisplay, href: `/${category}/` },
|
|
35
|
+
{ label: doc.frontmatter.title },
|
|
36
|
+
]}
|
|
37
|
+
/>
|
|
38
|
+
|
|
39
|
+
<header className="mb-8">
|
|
40
|
+
<h1 className="mb-2 text-3xl font-bold">{doc.frontmatter.title}</h1>
|
|
41
|
+
{(doc.frontmatter.created || doc.frontmatter.updated) && (
|
|
42
|
+
<div className="flex flex-wrap items-center gap-3 text-sm text-slate-500 dark:text-slate-400">
|
|
43
|
+
{doc.frontmatter.created && (
|
|
44
|
+
<span>Created: {doc.frontmatter.created}</span>
|
|
45
|
+
)}
|
|
46
|
+
{doc.frontmatter.updated && (
|
|
47
|
+
<span>Updated: {doc.frontmatter.updated}</span>
|
|
48
|
+
)}
|
|
49
|
+
</div>
|
|
45
50
|
)}
|
|
46
|
-
{doc.frontmatter.
|
|
47
|
-
<
|
|
51
|
+
{doc.frontmatter.tags.length > 0 && (
|
|
52
|
+
<div className="mt-3 flex flex-wrap gap-1.5">
|
|
53
|
+
{doc.frontmatter.tags.map((tag) => (
|
|
54
|
+
<TagBadge key={tag} tag={tag} />
|
|
55
|
+
))}
|
|
56
|
+
</div>
|
|
48
57
|
)}
|
|
49
|
-
</
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
)}
|
|
57
|
-
</header>
|
|
58
|
-
|
|
59
|
-
<MarkdownRenderer content={doc.content} />
|
|
60
|
-
</article>
|
|
58
|
+
</header>
|
|
59
|
+
|
|
60
|
+
<MarkdownRenderer content={doc.content} />
|
|
61
|
+
</article>
|
|
62
|
+
|
|
63
|
+
<TableOfContents headings={headings} />
|
|
64
|
+
</div>
|
|
61
65
|
);
|
|
62
66
|
}
|
|
@@ -7,7 +7,6 @@ export async function generateStaticParams() {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export const dynamicParams = true;
|
|
10
|
-
export const revalidate = 10;
|
|
11
10
|
|
|
12
11
|
export default async function CategoryPage({
|
|
13
12
|
params,
|
|
@@ -21,8 +20,8 @@ export default async function CategoryPage({
|
|
|
21
20
|
return (
|
|
22
21
|
<div>
|
|
23
22
|
<Breadcrumbs crumbs={[{ label: displayName }]} />
|
|
24
|
-
<h1 className="text-2xl font-bold capitalize
|
|
25
|
-
<p className="text-
|
|
23
|
+
<h1 className="mb-1 text-2xl font-bold capitalize">{displayName}</h1>
|
|
24
|
+
<p className="mb-6 text-slate-500 dark:text-slate-400">
|
|
26
25
|
{docs.length} {docs.length === 1 ? "doc" : "docs"}
|
|
27
26
|
</p>
|
|
28
27
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512" x="0" y="0" viewBox="0 0 64 64" style="enable-background:new 0 0 512 512" xml:space="preserve" class=""><g><linearGradient id="a" x1="78.644" x2="25.425" y1="74.123" y2="11.863" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b45309"></stop><stop offset="1" stop-color="#f59e0b"></stop></linearGradient><path fill="url(#a)" fill-rule="evenodd" d="M2 14C2 7.373 7.373 2 14 2h36c6.627 0 12 5.373 12 12v28c0 6.627-5.373 12-12 12H39.908l-6.403 7.317a2 2 0 0 1-3.01 0L24.093 54H14C7.373 54 2 48.627 2 42z" clip-rule="evenodd" opacity="1" data-original="url(#a)" class=""></path><path fill="#ffffff" d="M32 14a2 2 0 0 1 1.95 1.556l1.107 4.867a6 6 0 0 0 4.52 4.52l4.867 1.107a2 2 0 0 1 0 3.9l-4.867 1.107a6 6 0 0 0-4.52 4.52l-1.107 4.866a2 2 0 0 1-3.9 0l-1.107-4.866a6 6 0 0 0-4.52-4.52l-4.866-1.107a2 2 0 0 1 0-3.9l4.866-1.107a6 6 0 0 0 4.52-4.52l1.107-4.866A2 2 0 0 1 32 14z"></path></g></svg>
|
|
@@ -4,28 +4,88 @@
|
|
|
4
4
|
@custom-variant dark (&:where(.dark, .dark *));
|
|
5
5
|
|
|
6
6
|
@theme {
|
|
7
|
-
--
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
--color-
|
|
11
|
-
--color-
|
|
12
|
-
--color-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
--color-
|
|
16
|
-
--color-
|
|
17
|
-
--color-
|
|
18
|
-
|
|
7
|
+
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
|
|
8
|
+
|
|
9
|
+
/* Warm slate scale (dark palette — from website) */
|
|
10
|
+
--color-navy-800: #31353f;
|
|
11
|
+
--color-navy-900: #2a2d38;
|
|
12
|
+
--color-navy-950: #23262e;
|
|
13
|
+
|
|
14
|
+
/* Violet accent (from website) */
|
|
15
|
+
--color-accent: #7c3aed;
|
|
16
|
+
--color-accent-light: #a78bfa;
|
|
17
|
+
--color-accent-dark: #6d28d9;
|
|
18
|
+
|
|
19
|
+
--color-surface: #2a2d38;
|
|
20
|
+
--color-surface-secondary: #31353f;
|
|
21
|
+
--color-border: rgba(160, 160, 160, 0.11);
|
|
22
|
+
--color-text: #f8fafc;
|
|
23
|
+
--color-text-muted: #94a3b8;
|
|
24
|
+
--color-link: #58adfb;
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
html {
|
|
22
28
|
scroll-behavior: smooth;
|
|
23
29
|
}
|
|
24
30
|
|
|
31
|
+
@media (prefers-reduced-motion: reduce) {
|
|
32
|
+
html {
|
|
33
|
+
scroll-behavior: auto;
|
|
34
|
+
}
|
|
35
|
+
*,
|
|
36
|
+
*::before,
|
|
37
|
+
*::after {
|
|
38
|
+
animation-duration: 0.01ms !important;
|
|
39
|
+
animation-iteration-count: 1 !important;
|
|
40
|
+
transition-duration: 0.01ms !important;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* Scrollbar */
|
|
45
|
+
*::-webkit-scrollbar {
|
|
46
|
+
width: 8px;
|
|
47
|
+
height: 8px;
|
|
48
|
+
}
|
|
49
|
+
*::-webkit-scrollbar-thumb {
|
|
50
|
+
background: #d1d5db4c;
|
|
51
|
+
border-radius: 10px;
|
|
52
|
+
}
|
|
53
|
+
*::-webkit-scrollbar-thumb:hover {
|
|
54
|
+
background: #d1d5dbce;
|
|
55
|
+
}
|
|
56
|
+
*::-webkit-scrollbar-track {
|
|
57
|
+
background: transparent;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Hide scrollbar utility */
|
|
61
|
+
.scrollbar-none::-webkit-scrollbar {
|
|
62
|
+
display: none;
|
|
63
|
+
}
|
|
64
|
+
|
|
25
65
|
/* Syntax highlighting */
|
|
26
|
-
pre code
|
|
27
|
-
padding: 1rem;
|
|
66
|
+
pre code {
|
|
28
67
|
border-radius: 0.5rem;
|
|
29
68
|
font-size: 0.875rem;
|
|
30
69
|
line-height: 1.7;
|
|
70
|
+
|
|
71
|
+
&.hljs {
|
|
72
|
+
padding: 0 !important;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
pre {
|
|
77
|
+
padding: 1rem;
|
|
78
|
+
background: #0d1117;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
:where(code):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
|
|
82
|
+
font-weight: 500;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
:not(pre) > code {
|
|
86
|
+
background: none !important;
|
|
87
|
+
border: 1px solid #a0a0a06b;
|
|
88
|
+
font-size: 85%;
|
|
89
|
+
padding: 1px 5px;
|
|
90
|
+
border-radius: 4px;
|
|
31
91
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512" x="0" y="0" viewBox="0 0 64 64" style="enable-background:new 0 0 512 512" xml:space="preserve" class=""><g><linearGradient id="a" x1="78.644" x2="25.425" y1="74.123" y2="11.863" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b45309"></stop><stop offset="1" stop-color="#f59e0b"></stop></linearGradient><path fill="url(#a)" fill-rule="evenodd" d="M2 14C2 7.373 7.373 2 14 2h36c6.627 0 12 5.373 12 12v28c0 6.627-5.373 12-12 12H39.908l-6.403 7.317a2 2 0 0 1-3.01 0L24.093 54H14C7.373 54 2 48.627 2 42z" clip-rule="evenodd" opacity="1" data-original="url(#a)" class=""></path><path fill="#ffffff" d="M32 14a2 2 0 0 1 1.95 1.556l1.107 4.867a6 6 0 0 0 4.52 4.52l4.867 1.107a2 2 0 0 1 0 3.9l-4.867 1.107a6 6 0 0 0-4.52 4.52l-1.107 4.866a2 2 0 0 1-3.9 0l-1.107-4.866a6 6 0 0 0-4.52-4.52l-4.866-1.107a2 2 0 0 1 0-3.9l4.866-1.107a6 6 0 0 0 4.52-4.52l1.107-4.866A2 2 0 0 1 32 14z"></path></g></svg>
|
|
@@ -1,36 +1,56 @@
|
|
|
1
1
|
import type { Metadata } from "next";
|
|
2
2
|
import { ThemeProvider } from "next-themes";
|
|
3
3
|
import { getAllCategories } from "@/lib/docs";
|
|
4
|
+
import Header from "@/components/layout/Header";
|
|
5
|
+
import Footer from "@/components/layout/Footer";
|
|
4
6
|
import Sidebar from "@/components/Sidebar";
|
|
5
7
|
import MobileNav from "@/components/MobileNav";
|
|
6
8
|
import "./globals.css";
|
|
7
9
|
import "highlight.js/styles/github-dark.css";
|
|
8
10
|
|
|
9
|
-
export const revalidate = 10;
|
|
10
|
-
|
|
11
11
|
export const metadata: Metadata = {
|
|
12
12
|
title: "Learning Notes",
|
|
13
13
|
description: "Personal learning knowledge base",
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
export default function RootLayout({
|
|
16
|
+
export default function RootLayout({
|
|
17
|
+
children,
|
|
18
|
+
}: {
|
|
19
|
+
children: React.ReactNode;
|
|
20
|
+
}) {
|
|
17
21
|
const categories = getAllCategories();
|
|
18
22
|
|
|
19
23
|
return (
|
|
20
|
-
<html lang="en" suppressHydrationWarning>
|
|
21
|
-
<
|
|
24
|
+
<html lang="en" data-scroll-behavior="smooth" suppressHydrationWarning>
|
|
25
|
+
<head>
|
|
26
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
27
|
+
<link
|
|
28
|
+
rel="preconnect"
|
|
29
|
+
href="https://fonts.gstatic.com"
|
|
30
|
+
crossOrigin="anonymous"
|
|
31
|
+
/>
|
|
32
|
+
<link
|
|
33
|
+
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap"
|
|
34
|
+
rel="stylesheet"
|
|
35
|
+
/>
|
|
36
|
+
</head>
|
|
37
|
+
<body className="dark:bg-navy-900 bg-white text-slate-900 antialiased dark:text-slate-50">
|
|
22
38
|
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
|
|
23
|
-
<
|
|
39
|
+
<Header categories={categories} />
|
|
40
|
+
<div data-pagefind-ignore className="md:hidden">
|
|
24
41
|
<MobileNav categories={categories} />
|
|
25
42
|
</div>
|
|
26
43
|
<div className="flex">
|
|
27
44
|
<div data-pagefind-ignore>
|
|
28
45
|
<Sidebar categories={categories} />
|
|
29
46
|
</div>
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
47
|
+
<div className="flex min-w-0 flex-1 justify-center md:pl-64 lg:pl-[300px]">
|
|
48
|
+
<main className="min-h-screen w-full max-w-5xl p-6 pb-24 md:p-8 md:pb-24">
|
|
49
|
+
{children}
|
|
50
|
+
</main>
|
|
51
|
+
</div>
|
|
33
52
|
</div>
|
|
53
|
+
<Footer />
|
|
34
54
|
</ThemeProvider>
|
|
35
55
|
</body>
|
|
36
56
|
</html>
|
|
@@ -3,8 +3,6 @@ import DocCard from "@/components/DocCard";
|
|
|
3
3
|
import TagBadge from "@/components/TagBadge";
|
|
4
4
|
import Link from "next/link";
|
|
5
5
|
|
|
6
|
-
export const revalidate = 10;
|
|
7
|
-
|
|
8
6
|
export default function HomePage() {
|
|
9
7
|
const categories = getAllCategories();
|
|
10
8
|
const docs = getAllDocs();
|
|
@@ -13,25 +11,25 @@ export default function HomePage() {
|
|
|
13
11
|
|
|
14
12
|
return (
|
|
15
13
|
<div>
|
|
16
|
-
<h1 className="text-3xl font-bold
|
|
17
|
-
<p className="text-
|
|
14
|
+
<h1 className="mb-2 text-3xl font-bold">Learning Notes</h1>
|
|
15
|
+
<p className="mb-8 text-slate-500 dark:text-slate-400">
|
|
18
16
|
{docs.length} docs across {categories.length} categories
|
|
19
17
|
</p>
|
|
20
18
|
|
|
21
19
|
{/* Categories */}
|
|
22
20
|
<section className="mb-8">
|
|
23
|
-
<h2 className="text-lg font-semibold
|
|
24
|
-
<div className="grid grid-cols-2 sm:grid-cols-3
|
|
21
|
+
<h2 className="mb-3 text-lg font-semibold">Categories</h2>
|
|
22
|
+
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3">
|
|
25
23
|
{categories.map((cat) => (
|
|
26
24
|
<Link
|
|
27
25
|
key={cat.name}
|
|
28
26
|
href={`/${cat.name}/`}
|
|
29
|
-
className="
|
|
27
|
+
className="rounded-lg border border-slate-200 p-3 transition-colors hover:border-violet-400/50 dark:border-[#a0a0a01c] dark:hover:border-violet-400/50"
|
|
30
28
|
>
|
|
31
|
-
<div className="font-medium
|
|
29
|
+
<div className="font-medium text-slate-900 capitalize dark:text-slate-100">
|
|
32
30
|
{cat.name.replace(/[_-]/g, " ")}
|
|
33
31
|
</div>
|
|
34
|
-
<div className="text-sm text-
|
|
32
|
+
<div className="text-sm text-slate-500 dark:text-slate-400">
|
|
35
33
|
{cat.docCount} {cat.docCount === 1 ? "doc" : "docs"}
|
|
36
34
|
</div>
|
|
37
35
|
</Link>
|
|
@@ -42,7 +40,7 @@ export default function HomePage() {
|
|
|
42
40
|
{/* Tags */}
|
|
43
41
|
{tags.length > 0 && (
|
|
44
42
|
<section className="mb-8">
|
|
45
|
-
<h2 className="text-lg font-semibold
|
|
43
|
+
<h2 className="mb-3 text-lg font-semibold">Tags</h2>
|
|
46
44
|
<div className="flex flex-wrap gap-2">
|
|
47
45
|
{tags.slice(0, 20).map(({ tag }) => (
|
|
48
46
|
<TagBadge key={tag} tag={tag} />
|
|
@@ -53,7 +51,7 @@ export default function HomePage() {
|
|
|
53
51
|
|
|
54
52
|
{/* Recent Docs */}
|
|
55
53
|
<section>
|
|
56
|
-
<h2 className="text-lg font-semibold
|
|
54
|
+
<h2 className="mb-3 text-lg font-semibold">Recently Updated</h2>
|
|
57
55
|
<div className="grid gap-3">
|
|
58
56
|
{recentDocs.map((doc) => (
|
|
59
57
|
<DocCard key={`${doc.category}/${doc.slug}`} doc={doc} />
|
|
@@ -7,19 +7,27 @@ interface Crumb {
|
|
|
7
7
|
|
|
8
8
|
export default function Breadcrumbs({ crumbs }: { crumbs: Crumb[] }) {
|
|
9
9
|
return (
|
|
10
|
-
<nav className="flex items-center gap-2 text-sm text-
|
|
11
|
-
<Link
|
|
10
|
+
<nav className="mb-4 flex items-center gap-2 text-sm text-slate-500 dark:text-slate-400">
|
|
11
|
+
<Link
|
|
12
|
+
href="/"
|
|
13
|
+
className="hover:text-violet-600 dark:hover:text-violet-400"
|
|
14
|
+
>
|
|
12
15
|
Home
|
|
13
16
|
</Link>
|
|
14
17
|
{crumbs.map((crumb, i) => (
|
|
15
18
|
<span key={i} className="flex items-center gap-2">
|
|
16
19
|
<span>/</span>
|
|
17
20
|
{crumb.href ? (
|
|
18
|
-
<Link
|
|
21
|
+
<Link
|
|
22
|
+
href={crumb.href}
|
|
23
|
+
className="capitalize hover:text-violet-600 dark:hover:text-violet-400"
|
|
24
|
+
>
|
|
19
25
|
{crumb.label}
|
|
20
26
|
</Link>
|
|
21
27
|
) : (
|
|
22
|
-
<span className="text-
|
|
28
|
+
<span className="text-slate-700 dark:text-slate-300">
|
|
29
|
+
{crumb.label}
|
|
30
|
+
</span>
|
|
23
31
|
)}
|
|
24
32
|
</span>
|
|
25
33
|
))}
|
|
@@ -1,32 +1,50 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
1
3
|
import Link from "next/link";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
2
5
|
import TagBadge from "./TagBadge";
|
|
3
6
|
import type { DocMeta } from "@/lib/types";
|
|
4
7
|
|
|
5
8
|
export default function DocCard({ doc }: { doc: DocMeta }) {
|
|
9
|
+
const router = useRouter();
|
|
10
|
+
const href = `/${doc.category}/${doc.slug}/`;
|
|
11
|
+
|
|
6
12
|
return (
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
|
|
13
|
+
<div
|
|
14
|
+
role="link"
|
|
15
|
+
tabIndex={0}
|
|
16
|
+
onClick={() => router.push(href)}
|
|
17
|
+
onKeyDown={(e) => {
|
|
18
|
+
if (e.key === "Enter") router.push(href);
|
|
19
|
+
}}
|
|
20
|
+
className="dark:bg-navy-800/50 block cursor-pointer rounded-lg border border-slate-200 bg-white p-4 transition-all hover:border-violet-400/50 dark:border-[#a0a0a01c] dark:hover:border-violet-400/50"
|
|
10
21
|
>
|
|
11
|
-
<h3 className="font-semibold text-
|
|
12
|
-
{
|
|
22
|
+
<h3 className="mb-1 font-semibold text-slate-900 dark:text-slate-100">
|
|
23
|
+
<Link href={href} className="hover:underline">
|
|
24
|
+
{doc.frontmatter.title}
|
|
25
|
+
</Link>
|
|
13
26
|
</h3>
|
|
14
|
-
<p className="text-sm text-
|
|
27
|
+
<p className="mb-2 line-clamp-2 text-sm text-slate-500 dark:text-slate-400">
|
|
15
28
|
{doc.excerpt}
|
|
16
29
|
</p>
|
|
17
30
|
<div className="flex items-center justify-between">
|
|
18
|
-
<div
|
|
31
|
+
<div
|
|
32
|
+
className="flex flex-wrap items-center gap-2"
|
|
33
|
+
onClick={(e) => e.stopPropagation()}
|
|
34
|
+
>
|
|
19
35
|
{doc.frontmatter.tags.slice(0, 3).map((tag) => (
|
|
20
36
|
<TagBadge key={tag} tag={tag} />
|
|
21
37
|
))}
|
|
22
38
|
{doc.frontmatter.tags.length > 3 && (
|
|
23
|
-
<span className="text-xs text-
|
|
39
|
+
<span className="text-xs text-slate-400">
|
|
40
|
+
+{doc.frontmatter.tags.length - 3}
|
|
41
|
+
</span>
|
|
24
42
|
)}
|
|
25
43
|
</div>
|
|
26
|
-
<span className="text-xs text-
|
|
44
|
+
<span className="text-xs text-slate-400">
|
|
27
45
|
{doc.frontmatter.updated || doc.frontmatter.created}
|
|
28
46
|
</span>
|
|
29
47
|
</div>
|
|
30
|
-
</
|
|
48
|
+
</div>
|
|
31
49
|
);
|
|
32
50
|
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import ReactMarkdown from "react-markdown";
|
|
2
2
|
import remarkGfm from "remark-gfm";
|
|
3
3
|
import rehypeHighlight from "rehype-highlight";
|
|
4
|
+
import rehypeSlug from "rehype-slug";
|
|
4
5
|
|
|
5
6
|
export default function MarkdownRenderer({ content }: { content: string }) {
|
|
6
7
|
return (
|
|
7
|
-
<div className="prose prose-
|
|
8
|
-
<ReactMarkdown
|
|
8
|
+
<div className="prose prose-slate prose-headings:scroll-mt-20 prose-a:text-link prose-code:before:content-none prose-code:after:content-none dark:prose-invert dark:prose-a:text-link max-w-none">
|
|
9
|
+
<ReactMarkdown
|
|
10
|
+
remarkPlugins={[remarkGfm]}
|
|
11
|
+
rehypePlugins={[rehypeHighlight, rehypeSlug]}
|
|
12
|
+
>
|
|
9
13
|
{content}
|
|
10
14
|
</ReactMarkdown>
|
|
11
15
|
</div>
|
|
@@ -1,55 +1,63 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import Link from "next/link";
|
|
4
|
+
import { usePathname } from "next/navigation";
|
|
4
5
|
import { useState } from "react";
|
|
5
6
|
import type { CategoryInfo } from "@/lib/types";
|
|
6
|
-
import SearchBar from "./SearchBar";
|
|
7
|
-
import ThemeToggle from "./ThemeToggle";
|
|
8
7
|
|
|
9
|
-
export default function MobileNav({
|
|
8
|
+
export default function MobileNav({
|
|
9
|
+
categories,
|
|
10
|
+
}: {
|
|
11
|
+
categories: CategoryInfo[];
|
|
12
|
+
}) {
|
|
13
|
+
const pathname = usePathname();
|
|
10
14
|
const [open, setOpen] = useState(false);
|
|
11
15
|
|
|
12
16
|
return (
|
|
13
|
-
<div className="
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
</div>
|
|
34
|
-
</div>
|
|
17
|
+
<div className="dark:bg-navy-950 border-b border-slate-200 bg-slate-50 dark:border-[#a0a0a01c]">
|
|
18
|
+
<button
|
|
19
|
+
onClick={() => setOpen(!open)}
|
|
20
|
+
className="flex w-full cursor-pointer items-center justify-between px-4 py-3 text-sm font-medium text-slate-700 dark:text-slate-300"
|
|
21
|
+
>
|
|
22
|
+
<span>Navigation</span>
|
|
23
|
+
<svg
|
|
24
|
+
className={`h-4 w-4 transition-transform ${open ? "rotate-180" : ""}`}
|
|
25
|
+
fill="none"
|
|
26
|
+
viewBox="0 0 24 24"
|
|
27
|
+
stroke="currentColor"
|
|
28
|
+
>
|
|
29
|
+
<path
|
|
30
|
+
strokeLinecap="round"
|
|
31
|
+
strokeLinejoin="round"
|
|
32
|
+
strokeWidth={2}
|
|
33
|
+
d="M19 9l-7 7-7-7"
|
|
34
|
+
/>
|
|
35
|
+
</svg>
|
|
36
|
+
</button>
|
|
35
37
|
|
|
36
38
|
{open && (
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
<nav className="max-h-[60vh] space-y-0.5 overflow-y-auto px-4 pb-4">
|
|
40
|
+
{categories.map((cat) => {
|
|
41
|
+
const isActive = pathname === `/${cat.name}/`;
|
|
42
|
+
return (
|
|
41
43
|
<Link
|
|
42
44
|
key={cat.name}
|
|
43
45
|
href={`/${cat.name}/`}
|
|
44
46
|
onClick={() => setOpen(false)}
|
|
45
|
-
className=
|
|
47
|
+
className={`flex items-center justify-between rounded-md px-3 py-1.5 text-sm capitalize ${
|
|
48
|
+
isActive
|
|
49
|
+
? "font-medium text-violet-600 dark:text-violet-400"
|
|
50
|
+
: "text-slate-600 dark:text-slate-400"
|
|
51
|
+
}`}
|
|
46
52
|
>
|
|
47
|
-
<span
|
|
48
|
-
<span className="text-xs text-
|
|
53
|
+
<span>{cat.name.replace(/[_-]/g, " ")}</span>
|
|
54
|
+
<span className="text-xs text-slate-400 dark:text-slate-500">
|
|
55
|
+
{cat.docCount}
|
|
56
|
+
</span>
|
|
49
57
|
</Link>
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
</
|
|
58
|
+
);
|
|
59
|
+
})}
|
|
60
|
+
</nav>
|
|
53
61
|
)}
|
|
54
62
|
</div>
|
|
55
63
|
);
|