hd-garden 0.3.12 → 0.4.0

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.
Files changed (72) hide show
  1. package/app/a2/[slug]/page.tsx +61 -0
  2. package/app/a2/page.tsx +27 -0
  3. package/app/admin/page.tsx +11 -15
  4. package/app/admin/ui/page.tsx +1 -1
  5. package/app/blog/[...path]/page.tsx +33 -22
  6. package/app/blog/page.tsx +6 -7
  7. package/app/columns/[column]/[...path]/page.tsx +33 -25
  8. package/app/columns/page.tsx +38 -52
  9. package/app/components/AdminDashboard.tsx +24 -34
  10. package/app/components/AdminUiPanels.tsx +5 -5
  11. package/app/components/AdminUiShell.tsx +7 -7
  12. package/app/components/ArticleMeasure.tsx +95 -0
  13. package/app/components/ArticleTitle.tsx +20 -0
  14. package/app/components/BlogArchive.tsx +106 -102
  15. package/app/components/GardenAdminGuide.tsx +38 -45
  16. package/app/components/GardenContentGuide.tsx +22 -106
  17. package/app/components/GardenMediaGuide.tsx +24 -45
  18. package/app/components/MarkdownArticle.tsx +145 -66
  19. package/app/components/MermaidDiagram.tsx +44 -71
  20. package/app/components/PageIntro.tsx +2 -2
  21. package/app/components/SiteFooter.tsx +3 -7
  22. package/app/components/SiteHeader.tsx +55 -41
  23. package/app/components/TableOfContents.tsx +3 -2
  24. package/app/components/TagIndex.tsx +97 -0
  25. package/app/components/UiStudio.tsx +2 -2
  26. package/app/components/garden/GardenBlock.tsx +45 -0
  27. package/app/components/garden/blocks.tsx +530 -0
  28. package/app/components/garden/chrome.tsx +350 -0
  29. package/app/components/garden/sandbox-message.ts +16 -0
  30. package/app/garden/page.tsx +85 -223
  31. package/app/garden-modules.d.ts +2 -0
  32. package/app/globals.css +947 -88
  33. package/app/layout.tsx +9 -0
  34. package/app/lib/a2-meta.ts +22 -0
  35. package/app/lib/a2-preview.ts +50 -0
  36. package/app/lib/content.ts +48 -5
  37. package/app/lib/fallback-site-config.ts +1 -1
  38. package/app/lib/markdown-plugins.ts +2 -0
  39. package/app/lib/remark-a2.mjs +208 -0
  40. package/app/lib/remark-garden.mjs +88 -0
  41. package/app/page.tsx +122 -93
  42. package/app/styles/a2.css +1383 -0
  43. package/app/styles/shell.css +438 -0
  44. package/app/styles/tokens.css +111 -0
  45. package/app/tags/page.tsx +28 -0
  46. package/app/ui-parity/page.tsx +3 -2
  47. package/package.json +2 -2
  48. package/public/images/garden/admin-studio.png +0 -0
  49. package/scripts/build-content.mjs +11 -1
  50. package/scripts/check-post.mjs +18 -4
  51. package/scripts/new-post.mjs +3 -0
  52. package/site.config.yaml +2 -2
  53. package/templates/garden-blocks.md +128 -0
  54. package/templates/garden-snippets.plist +121 -0
  55. package/templates/site/site.config.yaml +1 -1
  56. package/ui/garden/catalog.mjs +196 -0
  57. package/ui/garden/check.mjs +103 -0
  58. package/ui/garden/enhance.mjs +89 -0
  59. package/ui/garden/parse.mjs +198 -0
  60. package/ui/garden/snippets.mjs +109 -0
  61. package/ui/garden/title.mjs +37 -0
  62. package/ui/garden/word-count.mjs +266 -0
  63. package/ui/host.mjs +45 -28
  64. package/ui/parity-fixtures.mjs +1 -1
  65. package/ui/preview/dist/garden-ui.css +81 -77
  66. package/ui/preview/dist/garden-ui.esm.js +124 -89
  67. package/ui/preview/dist/garden-ui.iife.js +124 -89
  68. package/ui/prompt/rules.md +17 -1
  69. package/ui/react/CatalogPage.tsx +12 -0
  70. package/ui/react/components/display.tsx +1 -1
  71. package/ui/react/components/media.tsx +2 -2
  72. package/ui/react/ui.css +81 -77
package/app/page.tsx CHANGED
@@ -1,119 +1,148 @@
1
1
  import Link from "next/link";
2
- import {
3
- ArrowRight,
4
- ArrowUpRight,
5
- GithubLogo,
6
- PushPin,
7
- } from "@phosphor-icons/react/ssr";
8
- import { columnHref, columns, formatDate, postHref, posts } from "./lib/content";
2
+ import { columns, postHref, posts } from "./lib/content";
9
3
  import { ThoughtCard } from "./components/ThoughtCard";
10
- import { PageIntro } from "./components/PageIntro";
11
4
  import { getPublishedThoughts } from "./lib/public-thoughts";
12
- import { githubUrl, siteConfig, siteDocumentTitle } from "./site.config";
5
+ import { githubUrl, siteConfig } from "./site.config";
13
6
 
14
7
  export const metadata = {
15
- title: siteDocumentTitle,
8
+ title: { absolute: siteConfig.name },
16
9
  description: siteConfig.description,
17
10
  };
18
11
 
12
+ function day(value: string) {
13
+ const date = new Date(value);
14
+ if (Number.isNaN(date.getTime())) return value.slice(0, 10);
15
+ return new Intl.DateTimeFormat("en-CA", {
16
+ timeZone: "Asia/Shanghai",
17
+ year: "numeric",
18
+ month: "2-digit",
19
+ day: "2-digit",
20
+ }).format(date);
21
+ }
22
+
19
23
  export default function Home() {
24
+ const recent = [...posts].sort((left, right) => Date.parse(right.date) - Date.parse(left.date));
25
+ const feature = recent[0];
20
26
  const pinned = posts.filter((post) => post.pinned);
21
27
  const thoughts = getPublishedThoughts();
28
+ const series = Array.from(
29
+ columns.reduce((result, entry) => {
30
+ const existing = result.get(entry.column ?? "");
31
+ if (existing) existing.push(entry);
32
+ else result.set(entry.column ?? "", [entry]);
33
+ return result;
34
+ }, new Map<string, typeof columns>()),
35
+ );
22
36
 
23
37
  return (
24
- <main className="page-shell home-page">
25
- <PageIntro
26
- eyebrow="HELLO, WORLD · 你好,世界"
27
- title={siteConfig.pages.home.title}
28
- subtitle={siteConfig.pages.home.subtitle}
29
- >
30
- <a
31
- className="inline-cta"
32
- href={githubUrl}
33
- target="_blank"
34
- rel="me noopener noreferrer"
35
- >
36
- <GithubLogo size={21} weight="fill" />
37
- 在 GitHub 找到我
38
- <ArrowUpRight size={16} />
39
- </a>
40
- </PageIntro>
38
+ <main className="sh-main" id="content">
39
+ <section className="sh-intro">
40
+ <h1>{siteConfig.pages.home.title}</h1>
41
+ <p>{siteConfig.pages.home.subtitle || siteConfig.description}</p>
42
+ <div className="links">
43
+ <Link href="/about">关于我</Link>
44
+ <a href={githubUrl} rel="me">GitHub</a>
45
+ <a href="/rss.xml">RSS 订阅</a>
46
+ </div>
47
+ </section>
41
48
 
42
- {pinned.length > 0 && <section className="home-section">
43
- <div className="section-heading accent-heading">
49
+ {posts.length > 0 && feature ? (
50
+ <article className="sh-feature" aria-labelledby="ft">
51
+ {feature.cover ? (
52
+ <div className="cover" style={{ background: "var(--cover)" }}>
53
+ {/* eslint-disable-next-line @next/next/no-img-element */}
54
+ <img src={feature.cover} alt="" />
55
+ </div>
56
+ ) : null}
44
57
  <div>
45
- <h2>置顶博文</h2>
58
+ <p className="kicker">
59
+ <span>最新文章</span>
60
+ <time className="num" dateTime={day(feature.date)}>{day(feature.date)}</time>
61
+ {feature.topic ? <span>{feature.topic}</span> : null}
62
+ </p>
63
+ <h2 id="ft"><Link href={postHref(feature)}>{feature.title}</Link></h2>
64
+ {feature.description ? <p>{feature.description}</p> : null}
65
+ {feature.tags?.length ? (
66
+ <p className="tags">{feature.tags.map((tag) => <span key={tag}>#{tag}</span>)}</p>
67
+ ) : null}
46
68
  </div>
47
- <PushPin size={20} weight="light" aria-hidden="true" />
48
- </div>
49
- <div className="post-list">
50
- {pinned.map((post) => (
51
- <Link href={postHref(post)} key={post.path} className="post-row">
52
- <time>{formatDate(post.date)}</time>
53
- <span>
54
- <strong>{post.title}</strong>
55
- <small>{post.topic}</small>
56
- </span>
57
- <ArrowRight size={18} />
58
- </Link>
59
- ))}
60
- </div>
61
- </section>}
69
+ </article>
70
+ ) : null}
62
71
 
63
- {posts.length > 0 && <section className="home-section">
64
- <div className="section-heading accent-heading">
65
- <div>
66
- <h2>最近博文</h2>
72
+ <div className="sh-home-grid">
73
+ <section className="sh-section" aria-labelledby="h-recent">
74
+ <div className="sh-sec-head">
75
+ <h2 id="h-recent">最近更新</h2>
76
+ <Link className="sh-more" href="/blog">全部 {posts.length} 篇</Link>
67
77
  </div>
68
- <Link href="/blog">全部文章 →</Link>
69
- </div>
70
- <div className="post-list">
71
- {posts.slice(0, 5).map((post) => (
72
- <Link href={postHref(post)} key={post.path} className="post-row">
73
- <time>{formatDate(post.date)}</time>
74
- <span>
75
- <strong>{post.title}</strong>
76
- <small>{post.topic}</small>
77
- </span>
78
- <ArrowRight size={18} />
79
- </Link>
80
- ))}
81
- </div>
82
- </section>}
78
+ <ol className="sh-rows">
79
+ {recent.slice(0, 7).map((post) => (
80
+ <li className="sh-row" key={post.path}>
81
+ <time className="num" dateTime={day(post.date)}>{day(post.date)}</time>
82
+ <h3><Link href={postHref(post)}>{post.title}</Link></h3>
83
+ {post.description ? <p>{post.description}</p> : null}
84
+ {post.topic ? <Link className="cat" href={`/blog?topic=${encodeURIComponent(post.topic)}`}>{post.topic}</Link> : null}
85
+ </li>
86
+ ))}
87
+ </ol>
88
+ </section>
89
+ {pinned.length > 0 ? (
90
+ <section className="sh-section" aria-labelledby="h-pin">
91
+ <div className="sh-sec-head section-heading accent-heading"><h2 id="h-pin">置顶</h2></div>
92
+ <ul className="sh-side-list">
93
+ {pinned.map((post) => (
94
+ <li key={post.path}>
95
+ <Link href={postHref(post)}>{post.title}</Link>
96
+ <span><time className="num" dateTime={day(post.date)}>{day(post.date)}</time>{post.topic ? ` ${post.topic}` : ""}</span>
97
+ </li>
98
+ ))}
99
+ </ul>
100
+ </section>
101
+ ) : null}
102
+ </div>
83
103
 
84
- {thoughts.length > 0 && <section className="home-section">
85
- <div className="section-heading accent-heading">
86
- <div>
87
- <h2>最近随想</h2>
104
+ {columns[0] && series.length ? (
105
+ <section className="sh-section" aria-labelledby="h-cols">
106
+ <div className="sh-sec-head">
107
+ <h2 id="h-cols">专栏</h2>
108
+ <Link className="sh-more" href="/columns">全部专栏</Link>
88
109
  </div>
89
- <Link href="/thoughts">进入随想 →</Link>
90
- </div>
91
- <div className="home-thought-list">
92
- {thoughts.slice(0, 5).map((thought) => (
93
- <ThoughtCard key={thought.slug} thought={thought} />
94
- ))}
95
- </div>
96
- </section>}
110
+ <div className="sh-cols">
111
+ {series.map(([slug, entries]) => {
112
+ const first = entries[0];
113
+ return (
114
+ <article className="sh-col" key={slug}>
115
+ <div className="cover" style={{ background: "var(--well)" }}>
116
+ {first.columnCover ? (
117
+ // eslint-disable-next-line @next/next/no-img-element
118
+ <img src={first.columnCover} alt={first.columnCoverAlt ?? ""} />
119
+ ) : null}
120
+ </div>
121
+ <div className="body">
122
+ <h3><Link href={`/columns#${slug}`}>{first.columnTitle}</Link></h3>
123
+ <p className="count">{entries.length} 篇,{first.columnStatus ?? "持续更新"}</p>
124
+ <p>{first.columnDescription ?? first.description}</p>
125
+ </div>
126
+ </article>
127
+ );
128
+ })}
129
+ </div>
130
+ </section>
131
+ ) : null}
97
132
 
98
- {columns[0] && <section className="home-section column-preview">
99
- <div className="section-heading accent-heading">
100
- <div>
101
- <h2>主题专栏</h2>
133
+ {thoughts.length > 0 ? (
134
+ <section className="sh-section" aria-labelledby="h-thoughts">
135
+ <div className="sh-sec-head">
136
+ <h2 id="h-thoughts">最近随想</h2>
137
+ <Link className="sh-more" href="/thoughts">全部随想</Link>
102
138
  </div>
103
- <Link href="/columns">全部专栏 →</Link>
104
- </div>
105
- <Link
106
- href={columnHref(columns[0])}
107
- className="column-card"
108
- >
109
- <div>
110
- <span>连载中 · {columns.length} 篇</span>
111
- <h3>{columns[0].columnTitle}</h3>
112
- <p>记录一个人从想法、最小闭环到稳定发布的完整构建过程。</p>
139
+ <div className="home-thought-list">
140
+ {thoughts.slice(0, 5).map((thought) => (
141
+ <ThoughtCard key={thought.slug} thought={thought} />
142
+ ))}
113
143
  </div>
114
- <span className="column-number">#01</span>
115
- </Link>
116
- </section>}
144
+ </section>
145
+ ) : null}
117
146
  </main>
118
147
  );
119
148
  }