hd-garden 0.8.0 → 0.8.2
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/.agents/skills/garden-ui/SKILL.md +2 -2
- package/.env.en.example +67 -0
- package/README.en.md +2 -2
- package/README.md +2 -2
- package/app/a2/[slug]/page.tsx +4 -3
- package/app/a2/page.tsx +4 -3
- package/app/about/page.tsx +8 -9
- package/app/admin/page.tsx +7 -6
- package/app/admin/preview/[...path]/page.tsx +2 -1
- package/app/admin/ui/blocks/page.tsx +2 -1
- package/app/admin/ui/catalog/page.tsx +2 -1
- package/app/admin/ui/evals/page.tsx +2 -1
- package/app/admin/ui/page.tsx +2 -1
- package/app/blog/[...path]/page.tsx +7 -6
- package/app/blog/page.tsx +4 -2
- package/app/columns/[column]/[...path]/page.tsx +8 -7
- package/app/columns/page.tsx +12 -8
- package/app/components/AdminUiShell.tsx +4 -3
- package/app/components/ArticleMeasure.tsx +6 -3
- package/app/components/ArticleReferences.tsx +4 -1
- package/app/components/ArticleTags.tsx +3 -2
- package/app/components/BlogArchive.tsx +20 -16
- package/app/components/Comments.tsx +16 -10
- package/app/components/ContentEmptyState.tsx +29 -28
- package/app/components/ExternalEmbed.tsx +15 -14
- package/app/components/GardenContentGuide.tsx +10 -2
- package/app/components/GardenDocsNav.tsx +4 -2
- package/app/components/GiscusComments.tsx +2 -1
- package/app/components/GithubLiveOverview.tsx +10 -8
- package/app/components/GithubProfile.tsx +31 -21
- package/app/components/HandbookLangSwitch.tsx +8 -4
- package/app/components/HandbookView.tsx +92 -9
- package/app/components/MarkdownArticle.tsx +31 -7
- package/app/components/MermaidDiagram.tsx +4 -3
- package/app/components/SiteChrome.tsx +3 -3
- package/app/components/SiteFooter.tsx +1 -1
- package/app/components/SiteHeader.tsx +13 -4
- package/app/components/ThoughtCard.tsx +16 -14
- package/app/components/UiBlock.tsx +3 -2
- package/app/components/Webmentions.tsx +11 -11
- package/app/components/WidgetChart.tsx +8 -6
- package/app/components/garden/FilePreview.tsx +2 -2
- package/app/components/garden/GardenBlock.tsx +7 -0
- package/app/components/garden/GardenFile.tsx +2 -2
- package/app/components/garden/RefDemo.tsx +62 -0
- package/app/components/garden/blocks.tsx +56 -8
- package/app/components/garden/patterns.tsx +497 -0
- package/app/garden/[...slug]/page.tsx +3 -2
- package/app/garden/en/page.tsx +7 -7
- package/app/garden/page.tsx +3 -2
- package/app/globals.css +49 -1
- package/app/lib/a2-meta.ts +26 -3
- package/app/lib/content.ts +2 -2
- package/app/lib/garden-docs-meta.ts +24 -0
- package/app/lib/github-id.mjs +8 -0
- package/app/lib/handbook-switch.mjs +12 -0
- package/app/lib/i18n.ts +401 -2
- package/app/lib/rehype-field-tables.mjs +47 -0
- package/app/lib/remark-a2.mjs +18 -8
- package/app/lib/remark-link-card.mjs +22 -0
- package/app/lib/remark-ref-demos.mjs +42 -0
- package/app/lib/tag-filter.ts +9 -2
- package/app/not-found.tsx +32 -4
- package/app/page.tsx +17 -14
- package/app/rss.xml/route.ts +2 -1
- package/app/styles/a2.css +338 -0
- package/app/styles/shell.css +15 -2
- package/app/tags/page.tsx +7 -5
- package/app/thoughts/[slug]/page.tsx +2 -1
- package/app/thoughts/page/[n]/page.tsx +1 -1
- package/app/thoughts/page.tsx +2 -1
- package/app/ui-parity/page.tsx +38 -22
- package/bin/garden.mjs +129 -19
- package/docs/handbook/ai-writing.md +2 -2
- package/docs/handbook/assets/reading-layout-en.svg +26 -0
- package/docs/handbook/components/card.md +43 -22
- package/docs/handbook/components/chart.md +10 -26
- package/docs/handbook/components/choice.md +42 -0
- package/docs/handbook/components/compare.md +32 -17
- package/docs/handbook/components/figure.md +33 -46
- package/docs/handbook/components/file.md +10 -15
- package/docs/handbook/components/html.md +10 -30
- package/docs/handbook/components/interactive.md +9 -35
- package/docs/handbook/components/list.md +65 -0
- package/docs/handbook/components/relations.md +95 -0
- package/docs/handbook/components/stat.md +10 -28
- package/docs/handbook/components/timeline.md +13 -28
- package/docs/handbook/components.md +210 -19
- package/docs/handbook/config.md +5 -3
- package/docs/handbook/deploy.md +1 -1
- package/docs/handbook/design.md +20 -12
- package/docs/handbook/en/ai-writing.md +2 -2
- package/docs/handbook/en/components/card.md +42 -21
- package/docs/handbook/en/components/chart.md +10 -26
- package/docs/handbook/en/components/choice.md +42 -0
- package/docs/handbook/en/components/compare.md +30 -15
- package/docs/handbook/en/components/figure.md +29 -42
- package/docs/handbook/en/components/file.md +9 -14
- package/docs/handbook/en/components/html.md +9 -29
- package/docs/handbook/en/components/interactive.md +8 -34
- package/docs/handbook/en/components/list.md +65 -0
- package/docs/handbook/en/components/relations.md +95 -0
- package/docs/handbook/en/components/stat.md +9 -27
- package/docs/handbook/en/components/timeline.md +9 -24
- package/docs/handbook/en/components.md +211 -19
- package/docs/handbook/en/config.md +32 -30
- package/docs/handbook/en/deploy.md +12 -12
- package/docs/handbook/en/design.md +19 -12
- package/docs/handbook/en/index.md +2 -2
- package/docs/handbook/en/reference/card.md +85 -0
- package/docs/handbook/en/reference/chart.md +68 -0
- package/docs/handbook/en/reference/choice.md +69 -0
- package/docs/handbook/en/reference/compare.md +59 -0
- package/docs/handbook/en/reference/figure.md +84 -0
- package/docs/handbook/en/reference/file/checklist.md +17 -0
- package/docs/handbook/en/reference/file.md +50 -0
- package/docs/handbook/en/reference/html.md +62 -0
- package/docs/handbook/en/reference/interactive.md +71 -0
- package/docs/handbook/en/reference/list.md +72 -0
- package/docs/handbook/en/reference/relations.md +113 -0
- package/docs/handbook/en/reference/stat.md +60 -0
- package/docs/handbook/en/reference/timeline.md +56 -0
- package/docs/handbook/en/reference.md +26 -0
- package/docs/handbook/en/start.md +2 -2
- package/docs/handbook/en/workflow.md +6 -5
- package/docs/handbook/en/writing.md +3 -2
- package/docs/handbook/index.md +1 -1
- package/docs/handbook/reference/card.md +85 -0
- package/docs/handbook/reference/chart.md +68 -0
- package/docs/handbook/reference/choice.md +69 -0
- package/docs/handbook/reference/compare.md +59 -0
- package/docs/handbook/reference/figure.md +84 -0
- package/docs/handbook/reference/file/checklist.md +17 -0
- package/docs/handbook/reference/file.md +50 -0
- package/docs/handbook/reference/html.md +62 -0
- package/docs/handbook/reference/interactive.md +71 -0
- package/docs/handbook/reference/list.md +72 -0
- package/docs/handbook/reference/relations.md +113 -0
- package/docs/handbook/reference/stat.md +60 -0
- package/docs/handbook/reference/timeline.md +56 -0
- package/docs/handbook/reference.md +26 -0
- package/docs/handbook/start.md +1 -1
- package/docs/handbook/workflow.md +6 -5
- package/docs/handbook/writing.md +3 -2
- package/next.config.ts +6 -0
- package/package.json +2 -1
- package/public/images/garden/ref-card-book.svg +1 -0
- package/public/images/garden/ref-card-lamp.svg +1 -0
- package/scripts/build-content.mjs +37 -16
- package/scripts/favicon-ico.mjs +9 -6
- package/scripts/file-preview.mjs +10 -7
- package/scripts/handbook-allowlist.mjs +18 -0
- package/scripts/handbook-facts.mjs +305 -11
- package/scripts/link-assets.mjs +7 -0
- package/scripts/new-post.mjs +18 -9
- package/scripts/prepare-pages-output.mjs +30 -18
- package/scripts/redirects.mjs +17 -6
- package/templates/garden-blocks.md +67 -0
- package/templates/garden-snippets.plist +61 -0
- package/templates/site/Makefile.en +45 -0
- package/templates/site/README.en.md +15 -0
- package/templates/site/site.config.en.yaml +55 -0
- package/templates/skills-en/garden-interactive-chart/SKILL.md +84 -0
- package/templates/skills-en/garden-interactive-chart/references/shell.html +49 -0
- package/templates/skills-en/garden-interactive-chart/references/style.md +61 -0
- package/templates/skills-en/garden-post-review/SKILL.md +63 -0
- package/templates/skills-en/garden-ui/SKILL.md +118 -0
- package/templates/workflows/pages.en.yml +195 -0
- package/templates/workflows/pages.yml +195 -0
- package/ui/garden/catalog.mjs +205 -4
- package/ui/garden/check.mjs +6 -0
- package/ui/garden/file-block.mjs +8 -7
- package/ui/garden/html-lang.mjs +73 -1
- package/ui/garden/markdown-links.mjs +7 -4
- package/ui/garden/parse.mjs +45 -0
- package/ui/garden/relations.mjs +252 -0
- package/ui/garden/search-index.mjs +6 -4
- package/ui/garden/source-icon.mjs +54 -0
- package/ui/parity-fixtures.mjs +77 -0
- package/ui/preview/dist/garden-ui.esm.js +41 -4
- package/ui/preview/dist/garden-ui.iife.js +41 -4
- package/ui/prompt/rules.md +28 -1
- package/ui/react/components/chart.tsx +1 -1
- package/ui/react/components/display.tsx +1 -1
- package/ui/react/lang.tsx +4 -0
- package/ui/react/parity.tsx +8 -6
- package/worker/index.ts +8 -2
package/bin/garden.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import fs from "node:fs";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
7
|
import { gardenPackageRoot, gardenSiteRoot } from "../scripts/garden-paths.mjs";
|
|
8
|
+
import { langOf, siteCliLang } from "../ui/core/cli-locale.mjs";
|
|
8
9
|
|
|
9
10
|
const siteRoot = gardenSiteRoot();
|
|
10
11
|
const args = process.argv.slice(2);
|
|
@@ -90,7 +91,17 @@ function ensurePublic() {
|
|
|
90
91
|
}
|
|
91
92
|
}
|
|
92
93
|
|
|
93
|
-
function printGiscusGuide() {
|
|
94
|
+
function printGiscusGuide(lang = siteCliLang(siteRoot)) {
|
|
95
|
+
if (lang === "en") {
|
|
96
|
+
process.stdout.write(`Comments are disabled by default. Configure Giscus before publishing, or GitHub Actions builds will fail:
|
|
97
|
+
1. Prepare a public GitHub repository and enable Discussions. A public source repo works directly; a private source repo needs a separate public repo.
|
|
98
|
+
2. Open https://giscus.app, install the Giscus App for that repository, and pick a category.
|
|
99
|
+
3. Set GISCUS_REPO, GISCUS_REPO_ID, GISCUS_CATEGORY, and GISCUS_CATEGORY_ID in .env.local.
|
|
100
|
+
4. Add the same four variables under Settings -> Secrets and variables -> Actions -> Variables in your source repository.
|
|
101
|
+
Placeholder values do not count as configured. To publish without comments, set the Actions Variable GISCUS_DISABLED to 1.
|
|
102
|
+
`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
94
105
|
process.stdout.write(`评论默认关闭。发布前要接上自己的 Giscus,留空会让 GitHub Actions 构建失败:
|
|
95
106
|
1. 准备一个公开 GitHub 仓库,并开启 Discussions。公开源码仓库可以直接用;私有源码要另建一个公开仓库。
|
|
96
107
|
2. 打开 https://giscus.app/zh-CN ,安装 Giscus App,只授权这个仓库,并选一个分类。
|
|
@@ -104,11 +115,12 @@ function ensureEnv() {
|
|
|
104
115
|
// CI injects the real values. Copying .env.example there would fill any
|
|
105
116
|
// unset key with a sample, which used to turn comments on for owner/repository.
|
|
106
117
|
if (process.env.GITHUB_ACTIONS === "true") return;
|
|
107
|
-
const
|
|
118
|
+
const lang = siteCliLang(siteRoot);
|
|
119
|
+
const example = path.join(gardenPackageRoot, lang === "en" ? ".env.en.example" : ".env.example");
|
|
108
120
|
const local = path.join(siteRoot, ".env.local");
|
|
109
121
|
if (!fs.existsSync(local) && fs.existsSync(example)) {
|
|
110
122
|
fs.copyFileSync(example, local);
|
|
111
|
-
printGiscusGuide();
|
|
123
|
+
printGiscusGuide(lang);
|
|
112
124
|
}
|
|
113
125
|
}
|
|
114
126
|
|
|
@@ -156,6 +168,16 @@ function vinextRoot() {
|
|
|
156
168
|
}
|
|
157
169
|
}
|
|
158
170
|
|
|
171
|
+
// English sites: the runtime manifest is a copy with the English description, not a link.
|
|
172
|
+
// Remove the link first so the write never goes through it into the engine's own package.json.
|
|
173
|
+
if (siteCliLang(siteRoot) === "en") {
|
|
174
|
+
const target = path.join(runtime, "package.json");
|
|
175
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(gardenPackageRoot, "package.json"), "utf8"));
|
|
176
|
+
manifest.description = "A personal blog engine: write in Markdown, publish with GitHub Pages.";
|
|
177
|
+
fs.rmSync(target, { force: true });
|
|
178
|
+
fs.writeFileSync(target, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
179
|
+
}
|
|
180
|
+
|
|
159
181
|
return runtime;
|
|
160
182
|
}
|
|
161
183
|
|
|
@@ -176,22 +198,27 @@ function copyIfMissing(from, to) {
|
|
|
176
198
|
|
|
177
199
|
function ensureSiteKit(destinationRoot = siteRoot) {
|
|
178
200
|
if (samePath(destinationRoot, gardenPackageRoot)) return;
|
|
201
|
+
const lang = siteCliLang(destinationRoot);
|
|
179
202
|
|
|
180
203
|
const added = [];
|
|
181
204
|
if (
|
|
182
205
|
copyIfMissing(
|
|
183
|
-
path.join(gardenPackageRoot, "templates", "site", "Makefile"),
|
|
206
|
+
path.join(gardenPackageRoot, "templates", "site", lang === "en" ? "Makefile.en" : "Makefile"),
|
|
184
207
|
path.join(destinationRoot, "Makefile"),
|
|
185
208
|
)
|
|
186
209
|
) {
|
|
187
210
|
added.push("Makefile");
|
|
188
211
|
}
|
|
189
212
|
if (added.length) {
|
|
190
|
-
process.stdout.write(
|
|
213
|
+
process.stdout.write(
|
|
214
|
+
lang === "en"
|
|
215
|
+
? `Added missing site files: ${added.join(", ")}\n`
|
|
216
|
+
: `已补齐站点文件:${added.join("、")}\n`,
|
|
217
|
+
);
|
|
191
218
|
}
|
|
192
219
|
|
|
193
|
-
installSkill(destinationRoot, { onlyMissing: true });
|
|
194
|
-
if (process.env.GARDEN_SKIP_HOOKS !== "1") installHook(destinationRoot, { quiet: true });
|
|
220
|
+
installSkill(destinationRoot, { onlyMissing: true, lang });
|
|
221
|
+
if (process.env.GARDEN_SKIP_HOOKS !== "1") installHook(destinationRoot, { quiet: true, lang });
|
|
195
222
|
}
|
|
196
223
|
|
|
197
224
|
const HOOK_MARKER = "# garden: ui check";
|
|
@@ -203,30 +230,46 @@ if [ ! -d node_modules/hd-garden ]; then
|
|
|
203
230
|
fi
|
|
204
231
|
exec npx --no-install garden ui check --staged --quiet
|
|
205
232
|
`;
|
|
233
|
+
const HOOK_SCRIPT_EN = `#!/bin/sh
|
|
234
|
+
${HOOK_MARKER}
|
|
235
|
+
if [ ! -d node_modules/hd-garden ]; then
|
|
236
|
+
echo "garden is not installed yet (run npm install first); skipping ui check." >&2
|
|
237
|
+
exit 0
|
|
238
|
+
fi
|
|
239
|
+
exec npx --no-install garden ui check --staged --quiet
|
|
240
|
+
`;
|
|
206
241
|
|
|
207
242
|
/** Installs the pre-commit hook unless the repository already has one. */
|
|
208
|
-
function installHook(destinationRoot = siteRoot, { quiet = false, force = false } = {}) {
|
|
243
|
+
function installHook(destinationRoot = siteRoot, { quiet = false, force = false, lang = siteCliLang(destinationRoot) } = {}) {
|
|
209
244
|
const result = spawnSync("git", ["rev-parse", "--git-path", "hooks/pre-commit"], { cwd: destinationRoot, encoding: "utf8" });
|
|
210
245
|
if (result.status !== 0) {
|
|
211
|
-
if (!quiet) process.stdout.write("这里不是 git 仓库,没有安装提交前检查。\n");
|
|
246
|
+
if (!quiet) process.stdout.write(lang === "en" ? "Not a git repository; skipping pre-commit hook installation.\n" : "这里不是 git 仓库,没有安装提交前检查。\n");
|
|
212
247
|
return;
|
|
213
248
|
}
|
|
214
249
|
const hookPath = path.resolve(destinationRoot, result.stdout.trim());
|
|
215
250
|
if (fs.existsSync(hookPath)) {
|
|
216
251
|
const current = fs.readFileSync(hookPath, "utf8");
|
|
217
252
|
if (current.includes(HOOK_MARKER) && !force) {
|
|
218
|
-
if (!quiet) process.stdout.write("提交前检查已经装好了。\n");
|
|
253
|
+
if (!quiet) process.stdout.write(lang === "en" ? "Pre-commit hook is already installed.\n" : "提交前检查已经装好了。\n");
|
|
219
254
|
return;
|
|
220
255
|
}
|
|
221
256
|
if (!current.includes(HOOK_MARKER)) {
|
|
222
|
-
process.stdout.write(
|
|
257
|
+
process.stdout.write(
|
|
258
|
+
lang === "en"
|
|
259
|
+
? `Existing pre-commit hook kept. Add this line to ${path.relative(destinationRoot, hookPath)}:\n npx --no-install garden ui check --staged --quiet\n`
|
|
260
|
+
: `已有 pre-commit 钩子,没有覆盖。请把这一行加进 ${path.relative(destinationRoot, hookPath)}:\n npx --no-install garden ui check --staged --quiet\n`,
|
|
261
|
+
);
|
|
223
262
|
return;
|
|
224
263
|
}
|
|
225
264
|
}
|
|
226
265
|
fs.mkdirSync(path.dirname(hookPath), { recursive: true });
|
|
227
|
-
fs.writeFileSync(hookPath, HOOK_SCRIPT, { mode: 0o755 });
|
|
266
|
+
fs.writeFileSync(hookPath, lang === "en" ? HOOK_SCRIPT_EN : HOOK_SCRIPT, { mode: 0o755 });
|
|
228
267
|
fs.chmodSync(hookPath, 0o755);
|
|
229
|
-
process.stdout.write(
|
|
268
|
+
process.stdout.write(
|
|
269
|
+
lang === "en"
|
|
270
|
+
? "Installed pre-commit hook: staged posts will be checked for valid ui blocks before commit.\n"
|
|
271
|
+
: "已安装提交前检查:提交时会检查暂存稿件里的 ui 块。\n",
|
|
272
|
+
);
|
|
230
273
|
}
|
|
231
274
|
|
|
232
275
|
function prepare() {
|
|
@@ -283,10 +326,12 @@ function runVinext(vinextArgs, { copyDist = false } = {}) {
|
|
|
283
326
|
if (result.status !== 0) process.exit(result.status || 1);
|
|
284
327
|
}
|
|
285
328
|
|
|
329
|
+
const EN_TEMPLATE_VARIANTS = new Set(["site.config.en.yaml", "Makefile.en", "README.en.md"]);
|
|
330
|
+
|
|
286
331
|
function copyTemplate(from, to) {
|
|
287
332
|
fs.mkdirSync(to, { recursive: true });
|
|
288
333
|
for (const entry of fs.readdirSync(from, { withFileTypes: true })) {
|
|
289
|
-
if (entry.name === ".git") continue;
|
|
334
|
+
if (entry.name === ".git" || EN_TEMPLATE_VARIANTS.has(entry.name)) continue;
|
|
290
335
|
const source = path.join(from, entry.name);
|
|
291
336
|
const destination = path.join(to, entry.name);
|
|
292
337
|
if (entry.isDirectory()) copyTemplate(source, destination);
|
|
@@ -294,6 +339,34 @@ function copyTemplate(from, to) {
|
|
|
294
339
|
}
|
|
295
340
|
}
|
|
296
341
|
|
|
342
|
+
function parseInitArgs(argv) {
|
|
343
|
+
let locale = process.env.GARDEN_LOCALE ?? "";
|
|
344
|
+
const positional = [];
|
|
345
|
+
const hasLocaleFlag = argv.includes("--locale");
|
|
346
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
347
|
+
const arg = argv[i];
|
|
348
|
+
if (arg === "--locale") {
|
|
349
|
+
locale = argv[i + 1] ?? "";
|
|
350
|
+
i += 1;
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
if (arg.startsWith("--locale=")) {
|
|
354
|
+
locale = arg.slice("--locale=".length);
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
if (/^locale\s*[:=]/iu.test(arg)) {
|
|
358
|
+
locale = arg.replace(/^locale\s*[:=]\s*/iu, "");
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
positional.push(arg);
|
|
362
|
+
}
|
|
363
|
+
void hasLocaleFlag;
|
|
364
|
+
return {
|
|
365
|
+
destination: path.resolve(positional[0] ?? "."),
|
|
366
|
+
lang: langOf(locale),
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
297
370
|
function writeEngineDependency(destination) {
|
|
298
371
|
const engine = JSON.parse(fs.readFileSync(path.join(gardenPackageRoot, "package.json"), "utf8"));
|
|
299
372
|
const sitePkgPath = path.join(destination, "package.json");
|
|
@@ -306,14 +379,44 @@ function writeEngineDependency(destination) {
|
|
|
306
379
|
}
|
|
307
380
|
|
|
308
381
|
function initSite() {
|
|
309
|
-
const destination =
|
|
382
|
+
const { destination, lang } = parseInitArgs(rest);
|
|
310
383
|
const template = path.join(gardenPackageRoot, "templates", "site");
|
|
311
384
|
if (!fs.existsSync(template)) fail("Garden 包里缺少 templates/site。");
|
|
312
385
|
if (fs.existsSync(path.join(destination, "site.config.yaml"))) {
|
|
313
|
-
fail(`目录已是 Garden 站点:${destination}`);
|
|
386
|
+
fail(lang === "en" ? `Directory is already a Garden site: ${destination}` : `目录已是 Garden 站点:${destination}`);
|
|
314
387
|
}
|
|
315
388
|
copyTemplate(template, destination);
|
|
389
|
+
if (lang === "en") {
|
|
390
|
+
for (const [srcName, destName] of [
|
|
391
|
+
["site.config.en.yaml", "site.config.yaml"],
|
|
392
|
+
["Makefile.en", "Makefile"],
|
|
393
|
+
["README.en.md", "README.md"],
|
|
394
|
+
]) {
|
|
395
|
+
const srcFile = path.join(template, srcName);
|
|
396
|
+
if (fs.existsSync(srcFile)) {
|
|
397
|
+
fs.copyFileSync(srcFile, path.join(destination, destName));
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
const enWorkflow = path.join(gardenPackageRoot, "templates", "workflows", "pages.en.yml");
|
|
401
|
+
if (fs.existsSync(enWorkflow)) {
|
|
402
|
+
const workflowDest = path.join(destination, ".github", "workflows", "pages.yml");
|
|
403
|
+
fs.mkdirSync(path.dirname(workflowDest), { recursive: true });
|
|
404
|
+
fs.copyFileSync(enWorkflow, workflowDest);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
316
407
|
writeEngineDependency(destination);
|
|
408
|
+
if (lang === "en") {
|
|
409
|
+
process.stdout.write(`Created site: ${destination}\n`);
|
|
410
|
+
installSkill(destination, { lang });
|
|
411
|
+
process.stdout.write("Next steps:\n");
|
|
412
|
+
process.stdout.write(` cd ${destination}\n`);
|
|
413
|
+
process.stdout.write(" npm install\n");
|
|
414
|
+
process.stdout.write(" Configure comments in four steps below, then start writing\n");
|
|
415
|
+
process.stdout.write(" make new TITLE=\"Hello, World\" SLUG=hello\n");
|
|
416
|
+
process.stdout.write(" make dev\n");
|
|
417
|
+
printGiscusGuide("en");
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
317
420
|
process.stdout.write(`已创建站点:${destination}\n`);
|
|
318
421
|
installSkill(destination);
|
|
319
422
|
process.stdout.write("下一步:\n");
|
|
@@ -322,7 +425,7 @@ function initSite() {
|
|
|
322
425
|
process.stdout.write(" 按下面四步接上评论,再写文章\n");
|
|
323
426
|
process.stdout.write(" make new TITLE=\"你好,世界\" SLUG=hello\n");
|
|
324
427
|
process.stdout.write(" make dev\n");
|
|
325
|
-
printGiscusGuide();
|
|
428
|
+
printGiscusGuide("zh");
|
|
326
429
|
}
|
|
327
430
|
|
|
328
431
|
function packagedSkills() {
|
|
@@ -333,7 +436,7 @@ function packagedSkills() {
|
|
|
333
436
|
.map((entry) => entry.name);
|
|
334
437
|
}
|
|
335
438
|
|
|
336
|
-
function installSkill(destinationRoot = siteRoot, { onlyMissing = false } = {}) {
|
|
439
|
+
function installSkill(destinationRoot = siteRoot, { onlyMissing = false, lang = siteCliLang(destinationRoot) } = {}) {
|
|
337
440
|
const names = packagedSkills();
|
|
338
441
|
if (!names.length) fail("Garden 包里没有 skill。");
|
|
339
442
|
const installed = [];
|
|
@@ -341,10 +444,17 @@ function installSkill(destinationRoot = siteRoot, { onlyMissing = false } = {})
|
|
|
341
444
|
const to = path.join(destinationRoot, ".agents", "skills", name);
|
|
342
445
|
if (onlyMissing && fs.existsSync(path.join(to, "SKILL.md"))) continue;
|
|
343
446
|
fs.cpSync(path.join(gardenPackageRoot, ".agents", "skills", name), to, { recursive: true });
|
|
447
|
+
// English sites get the English skill text; files without a translation keep the packaged copy.
|
|
448
|
+
const english = path.join(gardenPackageRoot, "templates", "skills-en", name);
|
|
449
|
+
if (lang === "en" && fs.existsSync(english)) fs.cpSync(english, to, { recursive: true, force: true });
|
|
344
450
|
installed.push(name);
|
|
345
451
|
}
|
|
346
452
|
if (installed.length) {
|
|
347
|
-
process.stdout.write(
|
|
453
|
+
process.stdout.write(
|
|
454
|
+
lang === "en"
|
|
455
|
+
? `Installed skills: ${installed.join(", ")}\n`
|
|
456
|
+
: `已安装 skill:${installed.join("、")}\n`,
|
|
457
|
+
);
|
|
348
458
|
}
|
|
349
459
|
}
|
|
350
460
|
|
|
@@ -86,8 +86,8 @@ Agent 按 `garden-ui` skill 会把这个想法块替换成一段紧挨着完整
|
|
|
86
86
|
|
|
87
87
|
不借助 AI 手写组件时,可以直接使用包内自带的模板与代码片段:
|
|
88
88
|
|
|
89
|
-
- `make new` 创建的草稿底部自带一段 `<!-- 组件速查 ... -->` 注释,包含想法块和
|
|
90
|
-
- `templates/garden-blocks.md` 汇总了想法块与
|
|
89
|
+
- `make new` 创建的草稿底部自带一段 `<!-- 组件速查 ... -->` 注释,包含想法块和 12 个组件的示例,写完可直接删掉。
|
|
90
|
+
- `templates/garden-blocks.md` 汇总了想法块与 12 个组件的适用场景(`when` / `whenNot`)和可复制示例;macOS 与 Typora / Alfred 用户可导入 `templates/garden-snippets.plist`,输入 `;gidea`(或 `;garden想法`)、`;gcard`、`;gstat`、`;gcompare`、`;gtimeline`、`;gchart`、`;gfigure`、`;ghtml`、`;ginteractive`、`;gfile`、`;grel`、`;glist`、`;gchoice` 即可展开对应围栏。
|
|
91
91
|
- 引擎仓库的 `editors/vscode`、`editors/typora` 与 `editors/obsidian` 目录提供了对应的编辑器片段与插件支持,运行 `npx garden ui catalog` 则可在本地生成 `.garden/ui-catalog.html` 离线组件画廊。
|
|
92
92
|
|
|
93
93
|
## 7. 可选的独立管理后台
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="2080" height="560" viewBox="0 0 1040 280" fill="none" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif">
|
|
2
|
+
<!-- Outer 1340px wide-table boundary -->
|
|
3
|
+
<rect x="20" y="20" width="1000" height="240" rx="14" stroke="#6e6b64" stroke-width="1.5" stroke-dasharray="6 5" />
|
|
4
|
+
<text x="44" y="48" fill="#4a4844" font-size="13" font-weight="600">Wide-table zone · up to 1340px on article pages (extends up to 300px per side)</text>
|
|
5
|
+
<text x="996" y="48" text-anchor="end" fill="#6e6b64" font-size="12" font-family="ui-monospace, SFMono-Regular, Menlo, monospace">max 1340px</text>
|
|
6
|
+
|
|
7
|
+
<!-- Middle 1040px wide-figure boundary -->
|
|
8
|
+
<rect x="110" y="66" width="820" height="174" rx="12" stroke="#2d2c29" stroke-width="1.5" stroke-dasharray="4 4" />
|
|
9
|
+
<text x="132" y="92" fill="#2d2c29" font-size="13" font-weight="600">Wide-figure zone · up to 1040px on article pages (retina screenshots render at half pixel width, expanding past 740px)</text>
|
|
10
|
+
<text x="908" y="92" text-anchor="end" fill="#6e6b64" font-size="12" font-family="ui-monospace, SFMono-Regular, Menlo, monospace">max 1040px</text>
|
|
11
|
+
|
|
12
|
+
<!-- Inner 740px reading column -->
|
|
13
|
+
<rect x="215" y="110" width="610" height="110" rx="10" fill="#f0eee6" fill-opacity="0.72" stroke="#1b1a18" stroke-width="2" />
|
|
14
|
+
<text x="520" y="146" text-anchor="middle" fill="#1b1a18" font-size="16" font-weight="700">Reading column · 740px (var(--col))</text>
|
|
15
|
+
<text x="520" y="172" text-anchor="middle" fill="#2d2c29" font-size="13">Paragraphs, lists, blockquotes, code blocks, and formulas stay inside this column</text>
|
|
16
|
+
<text x="520" y="196" text-anchor="middle" fill="#6e6b64" font-size="12" font-family="ui-monospace, SFMono-Regular, Menlo, monospace">Transparent-background figures get var(--image-backing) #faf9f5 warm backing in dark mode</text>
|
|
17
|
+
|
|
18
|
+
<!-- Left & right expansion arrows -->
|
|
19
|
+
<path d="M210 165 H118" stroke="#2d2c29" stroke-width="1.5" />
|
|
20
|
+
<path d="M124 160 L116 165 L124 170" stroke="#2d2c29" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
|
21
|
+
<text x="164" y="156" text-anchor="middle" fill="#4a4844" font-size="11" font-family="ui-monospace, SFMono-Regular, Menlo, monospace">≤ 150px</text>
|
|
22
|
+
|
|
23
|
+
<path d="M830 165 H922" stroke="#2d2c29" stroke-width="1.5" />
|
|
24
|
+
<path d="M916 160 L924 165 L916 170" stroke="#2d2c29" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
|
25
|
+
<text x="876" y="156" text-anchor="middle" fill="#4a4844" font-size="11" font-family="ui-monospace, SFMono-Regular, Menlo, monospace">≤ 150px</text>
|
|
26
|
+
</svg>
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: 知识卡片(card)
|
|
3
3
|
navTitle: 知识卡片
|
|
4
|
-
description:
|
|
4
|
+
description: 介绍一篇论文、一个项目或一组值得参考的公开工具。
|
|
5
5
|
section: components
|
|
6
6
|
order: 31
|
|
7
7
|
badge: card
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
-
在 `garden` 想法块里写一句「放一张 `card` 介绍 SQLite 的 WAL 文档,带标题、链接和三条要点」,Agent 会按 `garden-ui` skill 生成下面的 `garden card`(手写格式附后)。
|
|
11
|
-
|
|
12
10
|
```garden card
|
|
13
11
|
title: SQLite WAL 写入模式
|
|
14
12
|
url: https://www.sqlite.org/wal.html
|
|
@@ -20,27 +18,50 @@ points:
|
|
|
20
18
|
- 后台 Checkpoint 按阈值将日志页分批写回主库文件。
|
|
21
19
|
```
|
|
22
20
|
|
|
21
|
+
- **什么时候用**:介绍一篇论文、一个仓库或一本书,需要标题、链接和几条说明;或者把几项参考资源放在一起,各附一句可借鉴的结论。
|
|
22
|
+
- **什么时候不用**:正文里一两句话已经说清,或者只需要普通外链卡片时,直接写正文段落。
|
|
23
|
+
|
|
24
|
+
可以这样对 AI 说:
|
|
25
|
+
|
|
23
26
|
````markdown
|
|
27
|
+
```garden
|
|
28
|
+
整理一张卡片介绍 SQLite 的 WAL 文档,带链接和三条核心要点。
|
|
29
|
+
```
|
|
30
|
+
````
|
|
31
|
+
|
|
32
|
+
## 多张资源行卡(`cards`)
|
|
33
|
+
|
|
34
|
+
需要连续列出几个项目、公开工具或参考资料,并在每张卡片下方附上一句启发时,可以用 `cards` 列表:
|
|
35
|
+
|
|
24
36
|
```garden card
|
|
25
|
-
|
|
26
|
-
url: https://
|
|
27
|
-
|
|
28
|
-
description:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
cards:
|
|
38
|
+
- url: https://openlibrary.org/
|
|
39
|
+
title: Open Library:每本书一个页面
|
|
40
|
+
description: Internet Archive 维护的开放图书目录,可以查版本、借阅电子书。
|
|
41
|
+
image: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 132 132'%3E%3Crect width='132' height='132' fill='%23F0EEE6'/%3E%3Crect x='34' y='26' width='64' height='84' rx='6' fill='%23C9B79C'/%3E%3Crect x='34' y='26' width='10' height='84' fill='%238B6A45'/%3E%3Crect x='52' y='44' width='34' height='5' rx='2' fill='%23FAF9F5'/%3E%3Crect x='52' y='54' width='24' height='5' rx='2' fill='%23FAF9F5'/%3E%3C/svg%3E"
|
|
42
|
+
alt: 一本合上的书
|
|
43
|
+
source:
|
|
44
|
+
- https://openlibrary.org/
|
|
45
|
+
takeaway: "**可借鉴:** 选书时直接贴书的页面链接,大家看到的是同一个版本。"
|
|
46
|
+
- url: https://www.gutenberg.org/
|
|
47
|
+
title: Project Gutenberg:公版电子书
|
|
48
|
+
description: 收录已进入公有领域的电子书,可以免费下载。
|
|
49
|
+
image: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 132 132'%3E%3Crect width='132' height='132' fill='%23E8EEF0'/%3E%3Ccircle cx='66' cy='60' r='30' fill='%239DB7A8'/%3E%3Crect x='30' y='98' width='72' height='8' rx='4' fill='%233F6B55'/%3E%3Crect x='44' y='112' width='44' height='6' rx='3' fill='%239DB7A8'/%3E%3C/svg%3E"
|
|
50
|
+
alt: 一盏台灯
|
|
51
|
+
source:
|
|
52
|
+
- https://www.gutenberg.org/
|
|
53
|
+
- https://en.wikipedia.org/wiki/Project_Gutenberg
|
|
54
|
+
takeaway: "**可借鉴:** 偶尔选一本公版书,没买书的人也能马上加入。"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
可以这样对 AI 说:
|
|
58
|
+
|
|
59
|
+
````markdown
|
|
60
|
+
```garden
|
|
61
|
+
把 Open Library 和 Project Gutenberg 做成两张资源卡片,附上来源链接和各自可借鉴的地方。
|
|
33
62
|
```
|
|
34
63
|
````
|
|
35
64
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
| 字段 | 必填 | 类型 | 说明 |
|
|
40
|
-
| :--- | :--- | :--- | :--- |
|
|
41
|
-
| `title` | 建议 | 字符串 | 卡片标题 |
|
|
42
|
-
| `url` | 否 | 完整链接 | 标题旁显示的外部链接(渲染为 `来源 ↗`) |
|
|
43
|
-
| `meta` | 否 | 字符串 | 标题下方的副标题或元信息说明 |
|
|
44
|
-
| `description` | 与 `points` 二选一 | 字符串 | 卡片主说明段落(支持行内加粗与代码) |
|
|
45
|
-
| `points` | 与 `description` 二选一 | 字符串数组 | 下方带编号的要点列表 |
|
|
46
|
-
| `source` | 否 | 字符串或 `{ title, url }` | 右下角出处角标 |
|
|
65
|
+
## 字段说明
|
|
66
|
+
|
|
67
|
+
全部字段、默认值、取值限制和报错代码见 [组件参考 · card](/garden/reference/card)。
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: 条形图表(chart)
|
|
3
3
|
navTitle: 条形图表
|
|
4
|
-
description:
|
|
4
|
+
description: 把一组算好的数字画成比例条形图,下方自带可展开的数据表。
|
|
5
5
|
section: components
|
|
6
6
|
order: 35
|
|
7
7
|
badge: chart
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
-
在 `garden` 想法块里写一句「用 `chart` 画条形图比较正文列、宽图区和宽表区的宽度上限」,Agent 会按 `garden-ui` skill 生成下面的 `garden chart`(手写格式附后)。
|
|
11
|
-
|
|
12
10
|
```garden chart
|
|
13
11
|
title: 三种版面区域的宽度上限
|
|
14
12
|
xLabel: 版面区域
|
|
@@ -24,31 +22,17 @@ data:
|
|
|
24
22
|
caption: 点击「查看数据表」可展开完整数值表格。
|
|
25
23
|
```
|
|
26
24
|
|
|
25
|
+
- **什么时候用**:一组已经算好的同单位数字,想让读者一眼看出长短比例。
|
|
26
|
+
- **什么时候不用**:数字会随读者调整参数而变化时用[可调公式与参数](/garden/components/interactive);图表下方已经自带可展开的数据表,同一小节里不需要再重复放一张相同数字的表格。
|
|
27
|
+
|
|
28
|
+
可以这样对 AI 说:
|
|
29
|
+
|
|
27
30
|
````markdown
|
|
28
|
-
```garden
|
|
29
|
-
|
|
30
|
-
xLabel: 版面区域
|
|
31
|
-
yLabel: 最大宽度
|
|
32
|
-
unit: px
|
|
33
|
-
data:
|
|
34
|
-
- x: 普通正文列
|
|
35
|
-
y: 740
|
|
36
|
-
- x: 宽幅配图区
|
|
37
|
-
y: 1040
|
|
38
|
-
- x: 多列宽表格
|
|
39
|
-
y: 1340
|
|
40
|
-
caption: 点击「查看数据表」可展开完整数值表格。
|
|
31
|
+
```garden
|
|
32
|
+
画一张条形图比较普通正文列(740px)、宽幅配图区(1040px)和多列宽表格(1340px)的宽度上限。
|
|
41
33
|
```
|
|
42
34
|
````
|
|
43
35
|
|
|
44
|
-
|
|
45
|
-
- **别用**:已经在同一个小节里放了同组数字的 Markdown 表格时不要再画一张 `chart`(图表底部已自带可展开的数据表)。
|
|
36
|
+
## 字段说明
|
|
46
37
|
|
|
47
|
-
|
|
48
|
-
| :--- | :--- | :--- | :--- |
|
|
49
|
-
| `title` | 建议 | 字符串 | 图表标题 |
|
|
50
|
-
| `data` | 是 | `{ x, y, label? }[]`(至少 2 项) | 数据行列表,`x` 为名称,`y` 为数值 |
|
|
51
|
-
| `unit` | 否 | 字符串 | 数值后缀单位(当数据项未单独指定 `label` 时自动拼接) |
|
|
52
|
-
| `xLabel` | 否 | 字符串 | 展开数据表第一列的表头名称(默认 `项目`) |
|
|
53
|
-
| `yLabel` | 否 | 字符串 | 展开数据表第二列的表头名称(默认 `数值`) |
|
|
54
|
-
| `caption` | 否 | 字符串 | 图表底部的图注说明 |
|
|
38
|
+
全部字段、默认值、取值限制和报错代码见 [组件参考 · chart](/garden/reference/chart)。
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 选择卡(choice)
|
|
3
|
+
navTitle: 选择卡
|
|
4
|
+
description: 给出两三个具体选项卡片,读者点选后看到对应的回应。
|
|
5
|
+
section: components
|
|
6
|
+
order: 42
|
|
7
|
+
badge: choice
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
```garden choice
|
|
11
|
+
title: 下个月在哪里见面?
|
|
12
|
+
items:
|
|
13
|
+
- icon: idea
|
|
14
|
+
value: ¥40 / 人
|
|
15
|
+
text: 街角咖啡馆,含一杯饮料
|
|
16
|
+
label: 更看重氛围
|
|
17
|
+
reply: 选了咖啡馆。我们会提前订好靠窗的长桌。
|
|
18
|
+
- icon: book
|
|
19
|
+
value: 免费
|
|
20
|
+
text: 社区图书馆活动室,需提前预约
|
|
21
|
+
label: 更看重安静
|
|
22
|
+
reply: 选了图书馆。我们会在月初提交预约。
|
|
23
|
+
none:
|
|
24
|
+
label: 都不合适 / 说不清
|
|
25
|
+
reply: 没关系,我们会在群里再列几个场地。
|
|
26
|
+
caption: 示意交互,场地和价格均为虚构。
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- **什么时候用**:给读者两三个具体选项让他选一个,每个选项带图标、一个醒目的数字或价格和一句说明。
|
|
30
|
+
- **什么时候不用**:选项会改变后面的公式或图表计算结果时,用[可调公式与参数](/garden/components/interactive)。
|
|
31
|
+
|
|
32
|
+
可以这样对 AI 说:
|
|
33
|
+
|
|
34
|
+
````markdown
|
|
35
|
+
```garden
|
|
36
|
+
放一组选择卡让读者在街角咖啡馆(40 元含饮料,重氛围)和社区图书馆(免费,重安静)之间选一个,底部加一个「都不合适」按钮。
|
|
37
|
+
```
|
|
38
|
+
````
|
|
39
|
+
|
|
40
|
+
## 字段说明
|
|
41
|
+
|
|
42
|
+
全部字段、默认值、取值限制和报错代码见 [组件参考 · choice](/garden/reference/choice)。
|
|
@@ -1,38 +1,53 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: 并排对比(compare)
|
|
3
3
|
navTitle: 并排对比
|
|
4
|
-
description:
|
|
4
|
+
description: 把两三种方案左右并列,或把不好的做法与更好的做法上下对照。
|
|
5
5
|
section: components
|
|
6
6
|
order: 33
|
|
7
7
|
badge: compare
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
-
在 `garden` 想法块里写一句「用 `compare` 并排比较用一句话描述代码改动和手写 diff 两种做法」,Agent 会按 `garden-ui` skill 生成下面的 `garden compare`(手写格式附后)。
|
|
11
|
-
|
|
12
10
|
```garden compare
|
|
13
11
|
items:
|
|
14
12
|
- title: 写一句话说明改了什么
|
|
15
|
-
text: 想法块紧跟在完整代码下面,
|
|
13
|
+
text: 想法块紧跟在完整代码下面,AI 把它换成 diff,页面合成「代码 / 对比上一版」。
|
|
16
14
|
- title: 手写 diff
|
|
17
|
-
text: 完整代码下面紧跟一段 diff
|
|
15
|
+
text: 完整代码下面紧跟一段 diff。
|
|
18
16
|
```
|
|
19
17
|
|
|
18
|
+
- **什么时候用**:两三件东西并排看差异(并排模式下每格文字不超过约 60 字),或者把一正一反两种做法放在一起对照。
|
|
19
|
+
- **什么时候不用**:每段说明都很长,或者要横向对齐多个参数维度时,直接用列表或表格。
|
|
20
|
+
|
|
21
|
+
可以这样对 AI 说:
|
|
22
|
+
|
|
20
23
|
````markdown
|
|
24
|
+
```garden
|
|
25
|
+
把「用一句话说明改了什么」和「手写 diff」这两种做法并排对比一下。
|
|
26
|
+
```
|
|
27
|
+
````
|
|
28
|
+
|
|
29
|
+
## 正反做法对照(`verdict: good | bad`)
|
|
30
|
+
|
|
31
|
+
如果要对比「不好的做法」和「更好的做法」,条目带上 `verdict: bad` 或 `verdict: good` 后会改为上下堆叠,并在标题旁显示红叉或绿勾:
|
|
32
|
+
|
|
21
33
|
```garden compare
|
|
22
34
|
items:
|
|
23
|
-
- title:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
35
|
+
- title: 不好的提问
|
|
36
|
+
verdict: bad
|
|
37
|
+
text: 「你读过这位作者的其他作品吗?能分享一段书评吗?」
|
|
38
|
+
- title: 更好的提问
|
|
39
|
+
verdict: good
|
|
40
|
+
text: 「你平时喜欢在什么时间读书?想先旁听一次吗?」
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
可以这样对 AI 说:
|
|
44
|
+
|
|
45
|
+
````markdown
|
|
46
|
+
```garden
|
|
47
|
+
对比一下问新成员时「不好的提问」和「更好的提问」。
|
|
27
48
|
```
|
|
28
49
|
````
|
|
29
50
|
|
|
30
|
-
|
|
31
|
-
- **别用**:对比项超过 3 个或需要横向对齐多个参数维度时,直接用 Markdown 表格。
|
|
51
|
+
## 字段说明
|
|
32
52
|
|
|
33
|
-
|
|
34
|
-
| :--- | :--- | :--- | :--- |
|
|
35
|
-
| `items` | 是 | 数组(至少 2 项) | 对比项列表;每项也可简写为 `"标题:说明"` 单行字符串 |
|
|
36
|
-
| `items[].title` | 建议 | 字符串 | 该对比列的标题 |
|
|
37
|
-
| `items[].text` | 建议 | 字符串或字符串数组 | 该对比列的正文说明 |
|
|
38
|
-
| `items[].<自定义键>` | 否 | 字符串或数字 | 除 `title` / `text` 外的附加键值会渲染在卡片底部的属性标签行 |
|
|
53
|
+
全部字段、默认值、取值限制和报错代码见 [组件参考 · compare](/garden/reference/compare)。
|