hd-garden 0.13.3 → 0.14.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-compose/SKILL.md +24 -0
- package/.agents/skills/garden-compose/references/colors.md +11 -0
- package/.agents/skills/garden-compose/references/composition-patterns.md +162 -0
- package/.agents/skills/garden-compose/references/examples.md +55 -0
- package/.agents/skills/garden-compose/references/frames.md +7 -0
- package/.agents/skills/garden-compose/references/layout-recipes.md +84 -0
- package/.agents/skills/garden-compose/references/rendering-model.md +120 -0
- package/.agents/skills/garden-compose/references/static-play.md +19 -0
- package/.agents/skills/garden-compose/references/visual-selection.md +38 -0
- package/.agents/skills/garden-compose/references/workflow.md +27 -0
- package/.agents/skills/garden-compose/scripts/_cli.mjs +12 -0
- package/.agents/skills/garden-compose/scripts/apply.mjs +2 -0
- package/.agents/skills/garden-compose/scripts/check.mjs +2 -0
- package/.agents/skills/garden-compose/scripts/prompt.mjs +2 -0
- package/.agents/skills/garden-compose/scripts/scan.mjs +2 -0
- package/.agents/skills/garden-post-review/SKILL.md +5 -16
- package/Makefile +12 -3
- package/README.en.md +10 -5
- package/README.md +10 -5
- package/app/admin/preview/[...path]/page.tsx +4 -16
- package/app/api/admin/ui/blocks/route.ts +1 -1
- package/app/components/AdminUiPanels.tsx +6 -3
- package/app/components/GardenContentGuide.tsx +7 -8
- package/app/components/MarkdownArticle.tsx +2 -0
- package/app/components/UiStudio.tsx +29 -32
- package/app/components/garden/AuthorRich.tsx +5 -0
- package/app/components/garden/ComposePreview.tsx +9 -0
- package/app/components/garden/GardenBlock.tsx +12 -10
- package/app/components/garden/GardenFile.tsx +1 -1
- package/app/components/garden/blocks.tsx +9 -197
- package/app/components/garden/file-icons.tsx +1 -1
- package/app/components/garden/patterns.tsx +7 -65
- package/app/lib/rehype-garden-blocks.mjs +30 -0
- package/app/lib/remark-garden.mjs +0 -4
- package/app/lib/ui-model.ts +8 -9
- package/app/styles/a2.css +17 -1
- package/bin/garden.mjs +34 -35
- package/docs/handbook/ai-writing.md +19 -22
- package/docs/handbook/components/compose.md +16 -7
- package/docs/handbook/components/image.md +1 -1
- package/docs/handbook/components/link-card.md +2 -2
- package/docs/handbook/components.md +5 -213
- package/docs/handbook/deploy.md +11 -0
- package/docs/handbook/en/ai-writing.md +17 -20
- package/docs/handbook/en/components/compose.md +15 -6
- package/docs/handbook/en/components/image.md +1 -1
- package/docs/handbook/en/components/link-card.md +2 -2
- package/docs/handbook/en/components.md +7 -215
- package/docs/handbook/en/deploy.md +11 -0
- package/docs/handbook/en/index.md +29 -53
- package/docs/handbook/en/reference/compose.md +77 -148
- package/docs/handbook/en/reference.md +6 -22
- package/docs/handbook/en/start.md +1 -1
- package/docs/handbook/en/workflow.md +21 -17
- package/docs/handbook/en/writing.md +3 -3
- package/docs/handbook/index.md +26 -47
- package/docs/handbook/reference/compose.md +79 -148
- package/docs/handbook/reference.md +6 -22
- package/docs/handbook/start.md +1 -1
- package/docs/handbook/workflow.md +21 -17
- package/docs/handbook/writing.md +3 -3
- package/editors/dist/typora/main.js +324 -330
- package/editors/dist/typora/style.css +4299 -11
- package/editors/dist/vscode/garden-ui-0.14.2.vsix +0 -0
- package/eslint.config.mjs +3 -0
- package/package.json +5 -3
- package/public/images/compose-resources/resource-015b41eb0f32.svg +1 -0
- package/public/images/compose-resources/resource-ecee6b47f015.svg +1 -0
- package/public/images/compose-resources/resource-fd51121b5e8c.svg +1 -0
- package/scripts/build-content.mjs +40 -131
- package/scripts/build-ui-preview.mjs +7 -5
- package/scripts/check-post.mjs +10 -106
- package/scripts/check-site.mjs +80 -0
- package/scripts/compose-style-reference.mjs +22 -0
- package/scripts/editor-preview-css.mjs +14 -0
- package/scripts/file-preview.mjs +1 -1
- package/scripts/file-sizes.mjs +7 -7
- package/scripts/generated-cache.mjs +36 -0
- package/scripts/handbook-facts.mjs +10 -3
- package/scripts/image-sizes.mjs +6 -12
- package/scripts/link-assets.mjs +35 -10
- package/scripts/managed-skills.mjs +56 -0
- package/scripts/pack-editors.mjs +12 -1
- package/scripts/prepare-pages-output.mjs +1 -1
- package/scripts/site-guide.mjs +19 -0
- package/scripts/sync-editor-version.mjs +28 -0
- package/scripts/ui-cli.mjs +26 -33
- package/templates/garden-blocks.md +4 -180
- package/templates/garden-snippets.plist +2 -183
- package/templates/site/AGENTS.en.md +89 -0
- package/templates/site/AGENTS.md +72 -0
- package/templates/site/Makefile +12 -3
- package/templates/site/Makefile.en +12 -3
- package/templates/site/README.en.md +6 -1
- package/templates/site/README.md +6 -1
- package/templates/skills-en/garden-compose/SKILL.md +24 -0
- package/templates/skills-en/garden-post-review/SKILL.md +5 -16
- package/templates/workflows/pages.en.yml +11 -0
- package/templates/workflows/pages.yml +11 -0
- package/ui/compose/compose.css +54 -14
- package/ui/compose/contracts.mjs +16 -0
- package/ui/compose/dil.mjs +109 -48
- package/ui/compose/distribution.mjs +7 -0
- package/ui/compose/features.mjs +90 -0
- package/ui/compose/icons.mjs +8 -0
- package/ui/compose/index.mjs +2 -0
- package/ui/compose/inspect.mjs +11 -0
- package/ui/compose/markup.mjs +80 -23
- package/ui/compose/preview.mjs +13 -0
- package/ui/compose/prompt-catalog.mjs +15 -3
- package/ui/compose/render.mjs +46 -11
- package/ui/compose/scenes.mjs +10 -0
- package/ui/core/apply.mjs +12 -45
- package/ui/core/catalog.mjs +22 -32
- package/ui/core/cli-locale.mjs +8 -18
- package/ui/core/color.mjs +34 -0
- package/ui/core/compile.mjs +1 -8
- package/ui/core/demos.mjs +138 -108
- package/ui/core/fence.mjs +14 -70
- package/ui/core/fix-prompt.mjs +1 -3
- package/ui/core/garden-fence.mjs +3 -63
- package/ui/core/index.mjs +3 -5
- package/ui/core/parse.mjs +22 -8
- package/ui/core/scan.mjs +31 -43
- package/ui/core/starters.mjs +3 -3
- package/ui/core/static.mjs +36 -6
- package/ui/core/transform.mjs +64 -149
- package/ui/core/validate.mjs +20 -9
- package/ui/garden/article-content.mjs +98 -0
- package/ui/garden/catalog.mjs +21 -427
- package/ui/garden/check.mjs +18 -60
- package/ui/garden/file-block.mjs +1 -1
- package/ui/garden/file-paths.mjs +8 -6
- package/ui/garden/parse.mjs +19 -182
- package/ui/garden/relations.mjs +1 -1
- package/ui/garden/snippets.mjs +12 -153
- package/ui/garden/source-icon.mjs +17 -9
- package/ui/garden/word-count.mjs +16 -85
- package/ui/index.mjs +1 -5
- package/ui/preview/dist/garden-ui.css +4300 -11
- package/ui/preview/dist/garden-ui.esm.js +446 -418
- package/ui/preview/dist/garden-ui.iife.js +446 -418
- package/ui/preview/index.tsx +31 -98
- package/ui/preview/site-config.ts +4 -0
- package/ui/prompt/build.mjs +1 -4
- package/ui/prompt/recipes/code-walkthrough.md +17 -12
- package/ui/prompt/recipes/compare.md +14 -10
- package/ui/prompt/recipes/compose.md +3 -3
- package/ui/prompt/recipes/converter.md +17 -14
- package/ui/prompt/recipes/data-chart.md +9 -8
- package/ui/prompt/recipes/inspect-grid.md +9 -8
- package/ui/prompt/recipes/param-bench.md +17 -12
- package/ui/prompt/recipes/plan.md +4 -3
- package/ui/prompt/recipes/stepper.md +18 -16
- package/ui/prompt/recipes/trace.md +20 -18
- package/ui/prompt/rules.md +26 -133
- package/ui/prompt/studio.md +3 -18
- package/ui/react/CatalogPage.tsx +19 -21
- package/ui/react/recipes.ts +3 -3
- package/ui/react/runtime.tsx +23 -2
- package/ui/react/ui.css +17 -11
- package/vite.config.ts +16 -3
- package/.agents/skills/garden-interactive-chart/SKILL.md +0 -84
- package/.agents/skills/garden-interactive-chart/references/shell.html +0 -49
- package/.agents/skills/garden-interactive-chart/references/style.md +0 -61
- package/.agents/skills/garden-ui/SKILL.md +0 -125
- package/app/components/UiBlock.tsx +0 -23
- package/app/components/WidgetChart.tsx +0 -224
- package/app/lib/generated-content.ts +0 -385
- package/docs/handbook/components/card.md +0 -67
- package/docs/handbook/components/chart.md +0 -38
- package/docs/handbook/components/choice.md +0 -42
- package/docs/handbook/components/compare.md +0 -53
- package/docs/handbook/components/file.md +0 -29
- package/docs/handbook/components/html.md +0 -49
- package/docs/handbook/components/interactive.md +0 -48
- package/docs/handbook/components/list.md +0 -65
- package/docs/handbook/components/relations.md +0 -95
- package/docs/handbook/components/stat.md +0 -39
- package/docs/handbook/components/timeline.md +0 -37
- package/docs/handbook/en/components/card.md +0 -67
- package/docs/handbook/en/components/chart.md +0 -38
- package/docs/handbook/en/components/choice.md +0 -42
- package/docs/handbook/en/components/compare.md +0 -53
- package/docs/handbook/en/components/file.md +0 -29
- package/docs/handbook/en/components/html.md +0 -49
- package/docs/handbook/en/components/interactive.md +0 -44
- package/docs/handbook/en/components/list.md +0 -65
- package/docs/handbook/en/components/relations.md +0 -95
- package/docs/handbook/en/components/stat.md +0 -39
- package/docs/handbook/en/components/timeline.md +0 -37
- package/docs/handbook/en/reference/card.md +0 -85
- package/docs/handbook/en/reference/chart.md +0 -68
- package/docs/handbook/en/reference/choice.md +0 -69
- package/docs/handbook/en/reference/compare.md +0 -59
- package/docs/handbook/en/reference/file.md +0 -50
- package/docs/handbook/en/reference/html.md +0 -62
- package/docs/handbook/en/reference/interactive.md +0 -71
- package/docs/handbook/en/reference/list.md +0 -72
- package/docs/handbook/en/reference/relations.md +0 -113
- package/docs/handbook/en/reference/stat.md +0 -60
- package/docs/handbook/en/reference/timeline.md +0 -56
- package/docs/handbook/reference/card.md +0 -85
- package/docs/handbook/reference/chart.md +0 -68
- package/docs/handbook/reference/choice.md +0 -69
- package/docs/handbook/reference/compare.md +0 -59
- package/docs/handbook/reference/file.md +0 -50
- package/docs/handbook/reference/html.md +0 -62
- package/docs/handbook/reference/interactive.md +0 -71
- package/docs/handbook/reference/list.md +0 -72
- package/docs/handbook/reference/relations.md +0 -113
- package/docs/handbook/reference/stat.md +0 -60
- package/docs/handbook/reference/timeline.md +0 -56
- package/editors/dist/vscode/garden-ui-0.1.1.vsix +0 -0
- package/templates/skills-en/garden-interactive-chart/SKILL.md +0 -84
- package/templates/skills-en/garden-interactive-chart/references/shell.html +0 -49
- package/templates/skills-en/garden-interactive-chart/references/style.md +0 -61
- package/templates/skills-en/garden-ui/SKILL.md +0 -125
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: garden-post-review
|
|
3
|
-
description:
|
|
4
|
-
Reviews one specified Garden post in three steps: fix script-reported
|
|
5
|
-
structure errors, fix widgets with the chart skill, then suggest prose
|
|
6
|
-
improvements without editing. Use when the user asks to 审查 / 审稿 /
|
|
7
|
-
review a Garden blog post.
|
|
3
|
+
description: "Review one specified Garden post: fix script-reported structure errors, verify existing Compose blocks, then suggest prose improvements without editing."
|
|
8
4
|
---
|
|
9
|
-
|
|
10
5
|
# 审查一篇 Garden 博文
|
|
11
6
|
|
|
12
7
|
只审用户点名的稿。三步按顺序做,后一步看到的是前一步改完的稿。
|
|
13
8
|
|
|
14
|
-
开始前先运行 `npx garden ui scan content/posts/路径.md`。有 `status` 为 `uncompiled
|
|
9
|
+
开始前先运行 `npx garden ui scan content/posts/路径.md`。有 `status` 为 `uncompiled` 或 `error` 的块时,先按 [garden-compose](../garden-compose/SKILL.md) 处理完,再进入第 1 步。
|
|
15
10
|
|
|
16
11
|
硬性检查命令(在站点根目录):
|
|
17
12
|
|
|
@@ -29,17 +24,11 @@ stdout 是 JSON。`ok` 为 true 才算通过。不要自己再查一遍标题、
|
|
|
29
24
|
- 脚本没报的结构问题不要顺手改。
|
|
30
25
|
- `references` 是推荐额外阅读,不必和正文里的外链一一对应。不要因为正文有链接就补进表,也不要因为表里的材料正文没提到就删掉。
|
|
31
26
|
- `reference-heading`:把 `## 参考` / `## 参考资料` 收进 `references`,删掉这个标题。`## 参考文献` 留在正文里。
|
|
32
|
-
-
|
|
33
|
-
|
|
34
|
-
## 2. 图表按图表 skill 审并修
|
|
35
|
-
|
|
36
|
-
这一步只在稿里有 `widget` 时做。先读 [garden-interactive-chart](../garden-interactive-chart/SKILL.md)。
|
|
37
|
-
|
|
38
|
-
可以改同名目录里的 HTML,以及对应围栏的 `caption`。周围正文不动。`caption` 必须是一句判断。
|
|
27
|
+
- Compose 文件缺失时,只核对并修正写错的 `file src`;不凭空补附件或新增图表。
|
|
39
28
|
|
|
40
|
-
|
|
29
|
+
## 2. 检查既有 Compose
|
|
41
30
|
|
|
42
|
-
|
|
31
|
+
只处理已存在且获授权修复的 Compose 块,按 garden-compose 的 scan → 带 base hash 的 apply → check 流程。保留完整原文、资源与顺序;内容岛、布局、原生场景和必要交互都在同一入口,不改周围正文。对实际用到的布局/交互做完整主题、桌面/窄屏检查;没做视觉验收就明说,不把脚本通过当作外观通过。没有 Compose 则跳过,不主动把文章改成组件集合。
|
|
43
32
|
|
|
44
33
|
## 3. 通读整篇,只出建议
|
|
45
34
|
|
package/Makefile
CHANGED
|
@@ -2,17 +2,18 @@ MESSAGE ?= 更新博客
|
|
|
2
2
|
|
|
3
3
|
.DEFAULT_GOAL := help
|
|
4
4
|
|
|
5
|
-
.PHONY: help new embed skill prepare dev check build update
|
|
5
|
+
.PHONY: help new embed skill prepare dev check diff build update
|
|
6
6
|
|
|
7
7
|
# Pass literal user input via environment, never interpolate it into shell code.
|
|
8
|
-
unexport TITLE SLUG DIR URL
|
|
8
|
+
unexport TITLE SLUG DIR URL FILE
|
|
9
9
|
export GARDEN_NEW_TITLE := $(value TITLE)
|
|
10
10
|
export GARDEN_NEW_SLUG := $(value SLUG)
|
|
11
11
|
export GARDEN_NEW_DIR := $(value DIR)
|
|
12
12
|
export GARDEN_EMBED_URL := $(value URL)
|
|
13
|
+
export GARDEN_CHECK_FILE := $(value FILE)
|
|
13
14
|
|
|
14
15
|
help:
|
|
15
|
-
@printf "%s\n" "Garden 常用命令:" " make new 创建文章草稿(无需安装 npm 依赖)" ' make new TITLE="文章标题" SLUG=my-post DIR=writing' ' make embed URL="https://example.com" 拉取外部网页摘要,打印可粘贴的 embed 围栏' " make skill 安装可交互图表 skill" " make dev 首次准备并本地预览" " make check
|
|
16
|
+
@printf "%s\n" "Garden 常用命令:" " make new 创建文章草稿(无需安装 npm 依赖)" ' make new TITLE="文章标题" SLUG=my-post DIR=writing' ' make embed URL="https://example.com" 拉取外部网页摘要,打印可粘贴的 embed 围栏' " make skill 安装可交互图表 skill" " make dev 首次准备并本地预览" " make check 全量严格预检后 lint/测试;FILE=路径 只限制预检" " make check diff 只检查 Git 改动的 Markdown,不构建/跑全套测试" " make build 构建公开站点" " make update 检查、提交并触发自动发布" " make update MESSAGE=\"新增一篇文章\" 自定义提交说明"
|
|
16
17
|
|
|
17
18
|
new:
|
|
18
19
|
@node scripts/new-post.mjs
|
|
@@ -29,8 +30,16 @@ prepare:
|
|
|
29
30
|
dev: prepare
|
|
30
31
|
node bin/garden.mjs dev
|
|
31
32
|
|
|
33
|
+
# Parse goals once: even make -j check diff runs only one diff preflight.
|
|
34
|
+
ifneq ($(filter diff,$(MAKECMDGOALS)),)
|
|
32
35
|
check:
|
|
36
|
+
node bin/garden.mjs verify --diff
|
|
37
|
+
diff: check
|
|
38
|
+
else
|
|
39
|
+
check:
|
|
40
|
+
node bin/garden.mjs verify
|
|
33
41
|
npm run lint && npm test
|
|
42
|
+
endif
|
|
34
43
|
|
|
35
44
|
build:
|
|
36
45
|
node bin/garden.mjs build
|
package/README.en.md
CHANGED
|
@@ -21,7 +21,7 @@ The published npm package is `hd-garden`; once installed inside a site, you can
|
|
|
21
21
|
|
|
22
22
|
## 2. Write interactives in VS Code, Cursor, and Typora
|
|
23
23
|
|
|
24
|
-
The Intelligent UI plugins ship inside `hd-garden`. You do not compile them in the site. They insert a one-sentence idea, preview the rendered component in the editor, open the component catalog, and hand the post to the `garden-
|
|
24
|
+
The Intelligent UI plugins ship inside `hd-garden`. You do not compile them in the site. They insert a one-sentence idea, preview the rendered component in the editor, open the component catalog, and hand the post to the `garden-compose` skill to compile.
|
|
25
25
|
|
|
26
26
|
`npx hd-garden init` copies the files to `.garden/editors/` (a `.vsix` for VS Code / Cursor, and the Typora plugin). In an interactive terminal it asks about VS Code / Cursor first, then Typora. The first `npx garden sync` (`make dev` syncs first) asks again if that prompt has not happened yet. `y` installs; pressing Enter skips.
|
|
27
27
|
|
|
@@ -36,11 +36,11 @@ npx garden editors install --typora # Typora only
|
|
|
36
36
|
|
|
37
37
|
### VS Code / Cursor
|
|
38
38
|
|
|
39
|
-
- **Insert an idea**: `Cmd+Alt+I` on macOS, `Ctrl+Alt+I` on Windows and Linux. The Command Palette and the editor context menu have the same Garden command. It inserts a plain `garden` fence: a short brief for the `garden-
|
|
39
|
+
- **Insert an idea**: `Cmd+Alt+I` on macOS, `Ctrl+Alt+I` on Windows and Linux. The Command Palette and the editor context menu have the same Garden command. It inserts a plain `garden` fence: a short brief for the `garden-compose` skill, which compiles it later. In Typora, type `/` and choose `/garden`; the next list gives each component a small preview image.
|
|
40
40
|
- **Preview**: Open the Markdown preview. A `ui` fence renders the same component as the blog. An `intent` fence renders as an idea card. The extension reads sidecar data files next to the post.
|
|
41
41
|
- **Check**: Errors show up in the Problems panel and as squiggles in the source. A CodeLens above each block says whether it is ok, broken, stale, or not compiled yet.
|
|
42
42
|
- **Catalog**: The Garden catalog command inserts a recipe at the cursor when you choose to start from it. The same catalog from the terminal is `npx garden ui catalog` (it writes `.garden/ui-catalog.html`).
|
|
43
|
-
- **Compile**: Copy the compile prompt. On an English site (`locale: en_US`) it is `Compile this post:` plus the path. Paste that to an Agent that has the `garden-
|
|
43
|
+
- **Compile**: Copy the compile prompt. On an English site (`locale: en_US`) it is `Compile this post:` plus the path. Paste that to an Agent that has the `garden-compose` skill. The skill runs `npx garden ui scan`, reads `npx garden ui prompt`, and writes the fence back with `npx garden ui apply`. The plugin does not call a model and does not store a key.
|
|
44
44
|
|
|
45
45
|
When `code` or `cursor` is on `PATH`, `garden editors install` runs `--install-extension`. If both commands exist, it installs into both. Without a CLI, open the Extensions view, choose the `...` menu, then **Install from VSIX**, and select the file under `.garden/editors/vscode/`.
|
|
46
46
|
|
|
@@ -50,7 +50,7 @@ Typora has no official plugin API. Install [typora-community-plugin](https://git
|
|
|
50
50
|
|
|
51
51
|
The plugin is copied to `~/.typora/community-plugins/plugins/hedon954.garden-intelligent-ui/`. On macOS it is also copied to `~/Library/Application Support/abnerworks.Typora/plugins/plugins/hedon954.garden-intelligent-ui/`, which is the directory the Installed Plugins list reads. Open Typora and enable Garden Intelligent UI under Settings, Installed plugins.
|
|
52
52
|
|
|
53
|
-
Use it the same way as VS Code: leaving a fence previews `ui` / `intent` (as with Mermaid, click into the fence to edit the source; clicks on sliders and buttons stay in the preview). Type `/`, choose `/garden`, or use the insert command (`Ctrl+Alt+I` on Windows and Linux; on macOS, Option rewrites the key, so record a shortcut under Settings, Keyboard Shortcuts). Copy compile prompt hands the post to the `garden-
|
|
53
|
+
Use it the same way as VS Code: leaving a fence previews `ui` / `intent` (as with Mermaid, click into the fence to edit the source; clicks on sliders and buttons stay in the preview). Type `/`, choose `/garden`, or use the insert command (`Ctrl+Alt+I` on Windows and Linux; on macOS, Option rewrites the key, so record a shortcut under Settings, Keyboard Shortcuts). Copy compile prompt hands the post to the `garden-compose` skill.
|
|
54
54
|
|
|
55
55
|
A Typora upgrade overwrites the community framework inside the app. Reinstall typora-community-plugin, then enable this plugin again. The fences stay in the Markdown, and `make dev` still previews them.
|
|
56
56
|
|
|
@@ -66,7 +66,7 @@ Where you want a component or interactive calculation, write an unnamed `garden`
|
|
|
66
66
|
```
|
|
67
67
|
````
|
|
68
68
|
|
|
69
|
-
Ask your Agent to compile the post using the bundled `garden-
|
|
69
|
+
Ask your Agent to compile the post using the bundled `garden-compose` skill: it runs `npx garden ui scan` to locate uncompiled or broken blocks, reads the component catalog from `npx garden ui prompt`, generates a `garden compose` block (or `card`, `stat`, `compare`, `timeline`, `chart`, `figure`, `html`, `file`, `relations`, `list`, `choice`, or a `diff` block right below a code snippet), and writes only that fence back via `npx garden ui apply`. If you run `git commit` before compiling, the pre-commit hook runs `npx --no-install garden ui check --staged --quiet`, stops the commit, and prints a ready-to-paste fix prompt for your AI.
|
|
70
70
|
|
|
71
71
|
## 4. Common commands
|
|
72
72
|
|
|
@@ -103,3 +103,8 @@ Start `make dev` and open `/garden/en` (Chinese at `/garden`), or read the markd
|
|
|
103
103
|
## License
|
|
104
104
|
|
|
105
105
|
[MIT](LICENSE). Sites built with Garden should keep the Garden attribution link in the footer.
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
### AI writing entry point
|
|
109
|
+
|
|
110
|
+
New sites include a locale-appropriate root `AGENTS.md`: a short map of Garden content, source/generated files, project skills and verification commands, not a duplicated component catalog. Existing sites can run `npx --no-install garden agents` to add it only if missing. Sync/dev also fills in a missing guide. Existing files, directories and symlinks named `AGENTS.md` are never replaced; the site guide is not installed over engine-maintainer instructions.
|
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ make dev
|
|
|
23
23
|
|
|
24
24
|
## 2. 在 VS Code、Cursor 和 Typora 里写交互
|
|
25
25
|
|
|
26
|
-
Intelligent UI 插件随 `hd-garden` 一起发布,不用在站点里编译源码。它做四件事:插入一句想法、在编辑器里预览画好的组件、打开组件清单,以及把「编译这篇」交给 `garden-
|
|
26
|
+
Intelligent UI 插件随 `hd-garden` 一起发布,不用在站点里编译源码。它做四件事:插入一句想法、在编辑器里预览画好的组件、打开组件清单,以及把「编译这篇」交给 `garden-compose` skill。
|
|
27
27
|
|
|
28
28
|
`npx hd-garden init` 把产物复制到站点的 `.garden/editors/`(VS Code 的 `.vsix`,以及 Typora 插件目录)。终端可交互时,会先问要不要装进 VS Code / Cursor,再问要不要装进 Typora。第一次 `npx garden sync`(`make dev` 会先同步)如果还没问过,会再问一次。回答 `y` 才会安装;直接回车是不装。
|
|
29
29
|
|
|
@@ -38,11 +38,11 @@ npx garden editors install --typora # 只装 Typora
|
|
|
38
38
|
|
|
39
39
|
### VS Code / Cursor
|
|
40
40
|
|
|
41
|
-
- **插入想法**:macOS 用 `Cmd+Alt+I`,Windows / Linux 用 `Ctrl+Alt+I`,或命令「插入 Garden 块」。插入的是不带组件名的 `garden` 围栏,正文用中文告诉你要写什么,交给 `garden-
|
|
41
|
+
- **插入想法**:macOS 用 `Cmd+Alt+I`,Windows / Linux 用 `Ctrl+Alt+I`,或命令「插入 Garden 块」。插入的是不带组件名的 `garden` 围栏,正文用中文告诉你要写什么,交给 `garden-compose` skill 编译。Typora 里先输入 `/` 选 `/garden`,再从带小预览图的列表里选组件。
|
|
42
42
|
- **预览**:打开 Markdown 预览。`ui` 围栏画成和博客一样的组件,`intent` 围栏显示成想法卡片。同名目录里的数据文件由扩展读取。
|
|
43
43
|
- **检查**:错误进「问题」面板,并在源码上画波浪线。每个块上方的 CodeLens 标出正常、有错、过期或未编译。
|
|
44
44
|
- **组件清单**:命令「查看组件清单」。点「用它开始」把配方插进当前稿件。终端里的同一份清单是 `npx garden ui catalog`(生成 `.garden/ui-catalog.html`)。
|
|
45
|
-
- **编译**:命令「复制编译提示词」。中文站点复制 `编译这篇:<相对路径>`,英文站点(`locale: en_US`)复制 `Compile this post: <相对路径>`。把这句话发给装了 `garden-
|
|
45
|
+
- **编译**:命令「复制编译提示词」。中文站点复制 `编译这篇:<相对路径>`,英文站点(`locale: en_US`)复制 `Compile this post: <相对路径>`。把这句话发给装了 `garden-compose` skill 的 Agent,由它 `npx garden ui scan`、读 `npx garden ui prompt`、再 `npx garden ui apply`。插件不调用模型,也不保存密钥。
|
|
46
46
|
|
|
47
47
|
本机有 `code` 或 `cursor` 命令时,`garden editors install` 会执行 `--install-extension`。两个命令都在时,会各装一次。没有命令行时,打开扩展视图,点右上角「…」,选 **Install from VSIX**(从 VSIX 安装),文件在 `.garden/editors/vscode/`。
|
|
48
48
|
|
|
@@ -52,7 +52,7 @@ Typora 没有官方插件接口。先按 [typora-community-plugin 的安装说
|
|
|
52
52
|
|
|
53
53
|
插件文件复制到 `~/.typora/community-plugins/plugins/hedon954.garden-intelligent-ui/`。macOS 同时写到 `~/Library/Application Support/abnerworks.Typora/plugins/plugins/hedon954.garden-intelligent-ui/`。打开 Typora,在「设置 → 已安装插件」里勾选 Garden Intelligent UI。
|
|
54
54
|
|
|
55
|
-
用法和 VS Code 同一套:光标离开围栏时预览 `ui` / `intent`(和 Mermaid 一样,点进围栏编辑源码;点滑杆或按钮不会跳进源码);先输入 `/` 再选 `/garden`,或用命令「插入 Garden 块」,都会插入想法围栏(Windows / Linux 默认 `Ctrl+Alt+I`,macOS 的 Option 会改写按键,请到「设置 → 快捷键」里自己录);「Garden:复制编译提示词」交给 `garden-
|
|
55
|
+
用法和 VS Code 同一套:光标离开围栏时预览 `ui` / `intent`(和 Mermaid 一样,点进围栏编辑源码;点滑杆或按钮不会跳进源码);先输入 `/` 再选 `/garden`,或用命令「插入 Garden 块」,都会插入想法围栏(Windows / Linux 默认 `Ctrl+Alt+I`,macOS 的 Option 会改写按键,请到「设置 → 快捷键」里自己录);「Garden:复制编译提示词」交给 `garden-compose` skill。
|
|
56
56
|
|
|
57
57
|
升级 Typora 会覆盖社区框架改过的程序文件,插件跟着失效。需要重新安装 typora-community-plugin,再勾选本插件。稿件里的围栏原文还在,也可以用 `make dev` 预览。
|
|
58
58
|
|
|
@@ -68,7 +68,7 @@ Obsidian 插件这一轮不随包安装。
|
|
|
68
68
|
```
|
|
69
69
|
````
|
|
70
70
|
|
|
71
|
-
让 Agent 按站点里的 `garden-
|
|
71
|
+
让 Agent 按站点里的 `garden-compose` skill 处理这篇稿件:Agent 先用 `npx garden ui scan` 读取待编译块的哈希,再读 `npx garden ui prompt` 的组件规则,生成 `garden compose`(或 `card`、`stat`、`compare`、`timeline`、`chart`、`compose`、`html`、`file`、`relations`、`list`、`choice` 等内置组件,或在完整代码下方生成 `diff`),最后用 `npx garden ui apply` 只替换该围栏。如果你在编译前直接 `git commit`,pre-commit 钩子会运行同一条 `npx --no-install garden ui check --staged --quiet` 拦下提交,并打印一段可直接粘贴给 AI 的修复提示词。
|
|
72
72
|
|
|
73
73
|
## 4. 常用命令
|
|
74
74
|
|
|
@@ -105,3 +105,8 @@ npx garden editors install --yes # 把 Intelligent UI 插件装进
|
|
|
105
105
|
## 许可证
|
|
106
106
|
|
|
107
107
|
[MIT](LICENSE)。使用 Garden 构建的站点请保留页脚中的 Garden 署名链接。
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
### AI 写作入口
|
|
111
|
+
|
|
112
|
+
新建站点自带按语言选择的根目录 `AGENTS.md`:简要说明内容模型、源文件与生成物、项目技能和校验入口,不重复组件目录。已有站点可运行 `npx --no-install garden agents`,仅在缺失时补齐;sync/dev 也会补齐缺失指南。已有同名文件、目录或符号链接均不覆盖,不将站点指南安装到引擎维护仓库。
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import matter from "gray-matter";
|
|
2
2
|
import Link from "next/link";
|
|
3
|
-
import { parse as parseYaml
|
|
4
|
-
import { scanFences } from "../../../../ui/core/fence.mjs";
|
|
3
|
+
import { parse as parseYaml } from "yaml";
|
|
5
4
|
import { collectDataSources, transformUiContent } from "../../../../ui/core/transform.mjs";
|
|
6
5
|
import { AdminUiShell, isStaticExport, requireAdminPage, StaticAdminNotice } from "../../../components/AdminUiShell";
|
|
7
6
|
import { MarkdownArticle } from "../../../components/MarkdownArticle";
|
|
@@ -25,21 +24,10 @@ const isRelative = (value: string) => !/^(?:[a-z][a-z0-9+.-]*:|\/|#)/iu.test(val
|
|
|
25
24
|
/** Same-name-directory files go through the authenticated, sandboxed proxy. */
|
|
26
25
|
function proxyAssets(content: string, post: string) {
|
|
27
26
|
const proxy = (relative: string) => `/api/admin/preview/asset?post=${encodeURIComponent(post)}&path=${encodeURIComponent(relative)}`;
|
|
28
|
-
|
|
27
|
+
return replaceInlineLinks(content, (link) =>
|
|
29
28
|
link.image && isRelative(link.url) ? formatInlineLink(link, proxy(link.url)) : undefined,
|
|
30
29
|
);
|
|
31
|
-
|
|
32
|
-
if (fence.lang !== "widget") continue;
|
|
33
|
-
try {
|
|
34
|
-
const data = parseYaml(fence.body) as Record<string, unknown> | null;
|
|
35
|
-
if (!data || typeof data.src !== "string" || !isRelative(data.src)) continue;
|
|
36
|
-
const next = `${fence.marker}widget\n${stringifyYaml({ ...data, src: proxy(data.src) }).trimEnd()}\n${fence.marker}`;
|
|
37
|
-
output = `${output.slice(0, fence.start)}${next}${output.slice(fence.end)}`;
|
|
38
|
-
} catch {
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return output;
|
|
30
|
+
|
|
43
31
|
}
|
|
44
32
|
|
|
45
33
|
export default async function DraftPreviewPage({ params }: { params: Promise<{ path: string[] }> }) {
|
|
@@ -87,7 +75,7 @@ export default async function DraftPreviewPage({ params }: { params: Promise<{ p
|
|
|
87
75
|
草稿预览 · {post}{draft ? " · 草稿" : ""} · <Link href="/admin/ui/blocks">回到交互块</Link>
|
|
88
76
|
</p>
|
|
89
77
|
<h1 className="ui-admin-preview-title">{typeof data.title === "string" ? data.title : post}</h1>
|
|
90
|
-
{typeof data.description === "string" ? <p className="ui-studio-meta">{data.description}</p> : null}
|
|
78
|
+
{typeof data.description === "string" && data.description.trim() ? <p className="ui-studio-meta">{data.description}</p> : null}
|
|
91
79
|
<article className="article-body">
|
|
92
80
|
<MarkdownArticle content={proxyAssets(content, post)} numbering={data.numbering === false ? false : undefined} />
|
|
93
81
|
</article>
|
|
@@ -9,7 +9,7 @@ export async function GET() {
|
|
|
9
9
|
const counts = { uncompiled: 0, stale: 0, error: 0, ok: 0 };
|
|
10
10
|
for (const file of files) {
|
|
11
11
|
for (const block of file.blocks) {
|
|
12
|
-
|
|
12
|
+
counts[block.status as keyof typeof counts] += 1;
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
return Response.json({ files, counts });
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import {renderComposePreview} from "./garden/ComposePreview";
|
|
4
|
+
|
|
3
5
|
import Link from "next/link";
|
|
4
6
|
import { useEffect, useMemo, useState, useSyncExternalStore } from "react";
|
|
5
7
|
import { buildFixPrompt } from "../../ui/core/fix-prompt.mjs";
|
|
@@ -10,6 +12,7 @@ export function AdminCatalog({ recipes }: { recipes: RecipeSource[] }) {
|
|
|
10
12
|
return (
|
|
11
13
|
<section className="admin-panel ui-admin-catalog">
|
|
12
14
|
<CatalogPage
|
|
15
|
+
renderBlock={renderComposePreview}
|
|
13
16
|
recipes={recipes}
|
|
14
17
|
onStart={(fence) => {
|
|
15
18
|
window.localStorage.setItem(STUDIO_START_KEY, fence);
|
|
@@ -69,7 +72,7 @@ export function AdminBlocks({ siteUrl }: { siteUrl: string }) {
|
|
|
69
72
|
}, []);
|
|
70
73
|
|
|
71
74
|
const visible = (block: Block) =>
|
|
72
|
-
|
|
75
|
+
(filter === "all" || (filter === "todo" ? block.status !== "ok" : block.status === filter));
|
|
73
76
|
|
|
74
77
|
const copyPrompt = async (file: FileBlocks, block: Block) => {
|
|
75
78
|
const prompt = buildFixPrompt([{ file: file.path, block, diagnostics: block.diagnostics.filter((item) => item.severity === "error") }]);
|
|
@@ -78,7 +81,7 @@ export function AdminBlocks({ siteUrl }: { siteUrl: string }) {
|
|
|
78
81
|
};
|
|
79
82
|
|
|
80
83
|
const counts = { uncompiled: 0, stale: 0, error: 0, ok: 0 };
|
|
81
|
-
for (const file of files ?? []) for (const block of file.blocks)
|
|
84
|
+
for (const file of files ?? []) for (const block of file.blocks) counts[block.status] += 1;
|
|
82
85
|
|
|
83
86
|
return (
|
|
84
87
|
<section className="admin-panel ui-admin-blocks">
|
|
@@ -111,7 +114,7 @@ export function AdminBlocks({ siteUrl }: { siteUrl: string }) {
|
|
|
111
114
|
<tbody>
|
|
112
115
|
{blocks.map((block) => (
|
|
113
116
|
<tr key={block.startLine}>
|
|
114
|
-
<td>{
|
|
117
|
+
<td>{"Compose"} · {block.id ?? `第 ${block.startLine} 行`}</td>
|
|
115
118
|
<td><span className={`ui-admin-status ui-admin-status-${block.status}`}>{STATUS_LABELS[block.status]}</span></td>
|
|
116
119
|
<td>{block.caption ?? block.intent?.split("\n")[0] ?? ""}{block.diagnostics.length ? `(${block.diagnostics.length} 条诊断)` : ""}</td>
|
|
117
120
|
<td className="ui-admin-actions">
|
|
@@ -32,14 +32,13 @@ cover: writing/first-post.jpg
|
|
|
32
32
|
|
|
33
33
|
## 一个小标题
|
|
34
34
|
|
|
35
|
-
可以继续插入图片、代码、公式、Mermaid 图表,或用 \`garden\` 围栏写组件:
|
|
36
|
-
|
|
37
|
-
\`\`\`garden
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
text: 先把旧笔记整理成目录
|
|
35
|
+
可以继续插入图片、代码、公式、Mermaid 图表,或用 \`garden compose\` 围栏写组件:
|
|
36
|
+
|
|
37
|
+
\`\`\`garden compose
|
|
38
|
+
<icon-list>
|
|
39
|
+
<entry icon="idea"><text>记下为什么重新开始写</text></entry>
|
|
40
|
+
<entry icon="book"><text>先把旧笔记整理成目录</text></entry>
|
|
41
|
+
</icon-list>
|
|
43
42
|
\`\`\``;
|
|
44
43
|
|
|
45
44
|
const thoughtSource = `---
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {rehypeGardenBlocks} from "../lib/rehype-garden-blocks.mjs";
|
|
1
2
|
import { UiLangProvider } from "./UiLang";
|
|
2
3
|
import { siteLang, ui, type UiLang } from "../lib/i18n";
|
|
3
4
|
import { markdownPlugins } from "../lib/markdown-plugins";
|
|
@@ -252,6 +253,7 @@ export function MarkdownArticle({ content, lang, fieldTables, refDemos, handbook
|
|
|
252
253
|
remarkPlugins={[...(refDemos ? [remarkRefDemos] : []), ...markdownPlugins, remarkLinkCard, [remarkA2, { imageSize: (src: string) => lookupImageSize(imageSizes, src), lang: lang ?? siteLang }]]}
|
|
253
254
|
rehypePlugins={[
|
|
254
255
|
rehypeRaw,
|
|
256
|
+
rehypeGardenBlocks,
|
|
255
257
|
rehypeKatex,
|
|
256
258
|
rehypeSlug,
|
|
257
259
|
// Not rehype-autolink-headings "wrap": a heading with a link in it would nest <a> in <a> (React #418).
|
|
@@ -4,11 +4,15 @@ import { defaultKeymap, history, historyKeymap } from "@codemirror/commands";
|
|
|
4
4
|
import { RangeSetBuilder, StateEffect, StateField } from "@codemirror/state";
|
|
5
5
|
import { Decoration, EditorView, keymap, lineNumbers, type DecorationSet } from "@codemirror/view";
|
|
6
6
|
import { useEffect, useEffectEvent, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
7
|
+
import { parseGardenBlock } from "../../ui/garden/parse.mjs";
|
|
8
|
+
import { GardenBlock } from "./garden/GardenBlock";
|
|
9
|
+
import { parsePlayCompose, looksLikePlayCompose } from "../../ui/compose/dil.mjs";
|
|
10
|
+
import { gardenInfoId } from "../../ui/core/garden-fence.mjs";
|
|
7
11
|
import { compileUiBlock } from "../../ui/core/compile.mjs";
|
|
8
|
-
import { fenceHash, findBlocks, idOf, intentHash,
|
|
12
|
+
import { fenceHash, findBlocks, idOf, intentHash, intentOf, normalizeNewlines } from "../../ui/core/fence.mjs";
|
|
9
13
|
import { renderStatic } from "../../ui/core/static.mjs";
|
|
10
14
|
import { collectDataSources } from "../../ui/core/transform.mjs";
|
|
11
|
-
import {
|
|
15
|
+
import { type Diagnostic, type UiPayload } from "../../ui/react";
|
|
12
16
|
import { parseRecipe, type RecipeSource } from "../../ui/react/recipes";
|
|
13
17
|
|
|
14
18
|
import { STUDIO_START_KEY, STUDIO_STORAGE_KEY as STORAGE_KEY } from "./ui-studio-keys";
|
|
@@ -48,13 +52,7 @@ function useDebounced<T>(value: T, delay: number) {
|
|
|
48
52
|
return debounced;
|
|
49
53
|
}
|
|
50
54
|
|
|
51
|
-
function stamp(text: string) {
|
|
52
|
-
const fence = scanFences(text).find((item) => item.lang === "ui");
|
|
53
|
-
if (!fence) return text;
|
|
54
|
-
const { header } = splitUiBody(fence.body);
|
|
55
|
-
if (typeof header?.intent !== "string") return text;
|
|
56
|
-
return `${text.slice(0, fence.start)}${setCompiled(fence.raw, intentHash(header.intent))}${text.slice(fence.end)}`;
|
|
57
|
-
}
|
|
55
|
+
function stamp(text: string) { return text; }
|
|
58
56
|
|
|
59
57
|
function loadSaved() {
|
|
60
58
|
try {
|
|
@@ -133,27 +131,34 @@ function Studio({ configured, modelName, remaining: initialRemaining, recipes }:
|
|
|
133
131
|
|
|
134
132
|
const debouncedText = useDebounced(text, 150);
|
|
135
133
|
const compiled = useMemo(() => {
|
|
136
|
-
const fence =
|
|
134
|
+
const fence = findBlocks(debouncedText).find((item) => item.lang === "garden-compose");
|
|
137
135
|
if (!fence) {
|
|
138
136
|
return {
|
|
139
137
|
payload: null as UiPayload | null,
|
|
138
|
+
scene: null as string | null,
|
|
140
139
|
diagnostics: debouncedText.trim()
|
|
141
|
-
? [{ code: "ui-header", line: 1, message: "结构栏里要有一个
|
|
140
|
+
? [{ code: "ui-header", line: 1, message: "结构栏里要有一个 garden compose 围栏", severity: "error" } as Diagnostic]
|
|
142
141
|
: [],
|
|
143
142
|
};
|
|
144
143
|
}
|
|
145
|
-
const
|
|
144
|
+
const block=parseGardenBlock(`garden ${fence.info}`,fence.body);
|
|
145
|
+
const error=block&&"error" in block?block.error:null;
|
|
146
|
+
if(!block||error)return {payload:null,scene:null,diagnostics:[{code:error?.code??"compose-invalid",line:fence.startLine,message:error?.message??"无效的 Compose",severity:"error"} as Diagnostic]};
|
|
147
|
+
if(!("mode" in block)||block.mode!=="play")return {payload:null,scene:encodeURIComponent(JSON.stringify(block)),diagnostics:[] as Diagnostic[]};
|
|
148
|
+
const parsed=parsePlayCompose({id:gardenInfoId(fence.info)},fence.body);
|
|
149
|
+
const result = compileUiBlock(parsed.uiSource, {
|
|
146
150
|
lineOffset: fence.startLine,
|
|
147
151
|
markdownPath: origin?.post ?? null,
|
|
148
152
|
readData: (src: string) => dataFiles[src],
|
|
149
153
|
skipData: !origin,
|
|
150
154
|
});
|
|
151
|
-
return { payload: result.payload as UiPayload | null, diagnostics: result.check.diagnostics as Diagnostic[] };
|
|
155
|
+
return { payload: result.payload as UiPayload | null, scene:result.payload?encodeURIComponent(JSON.stringify({...block,payload:result.payload})):null, diagnostics: result.check.diagnostics as Diagnostic[] };
|
|
152
156
|
}, [debouncedText, origin, dataFiles]);
|
|
153
157
|
|
|
154
|
-
const [lastGood,
|
|
155
|
-
if
|
|
156
|
-
const shown
|
|
158
|
+
const [lastGood,setLastGood]=useState<{text:string;payload:UiPayload|null;scene:string}|null>(null);
|
|
159
|
+
if(compiled.scene&&lastGood?.text!==debouncedText)setLastGood({text:debouncedText,payload:compiled.payload,scene:compiled.scene});
|
|
160
|
+
const shown=compiled.payload??lastGood?.payload??null;
|
|
161
|
+
const shownScene=compiled.scene??lastGood?.scene??null;
|
|
157
162
|
|
|
158
163
|
useEffect(() => {
|
|
159
164
|
viewRef.current?.dispatch({ effects: setErrorLines.of(compiled.diagnostics.filter((item) => item.severity === "error").map((item) => item.line)) });
|
|
@@ -226,17 +231,9 @@ function Studio({ configured, modelName, remaining: initialRemaining, recipes }:
|
|
|
226
231
|
}
|
|
227
232
|
const id = idOf(block);
|
|
228
233
|
const base = fenceHash(block.raw);
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
setInstruction(intent);
|
|
233
|
-
replaceText("");
|
|
234
|
-
setMessage({ tone: "info", text: "已载入这段想法。按「生成」把它编译成 ui 块。" });
|
|
235
|
-
} else {
|
|
236
|
-
setOrigin({ post, target: id ? { id } : { intentHash: intentHash(splitUiBody(block.body).header?.intent ?? "") }, base });
|
|
237
|
-
replaceText(block.raw);
|
|
238
|
-
setMessage({ tone: "info", text: `已从 ${post} 打开 ${id ?? "这一块"}。改完用「复制写回命令」带回本地。` });
|
|
239
|
-
}
|
|
234
|
+
setOrigin({post,target:id?{id}:{intentHash:intentHash(intentOf(block)??"")},base});
|
|
235
|
+
replaceText(block.raw);
|
|
236
|
+
setMessage({tone:"info",text:`已从 ${post} 打开 ${id??"这一块"}。改完用「复制写回命令」带回本地。`});
|
|
240
237
|
const sources = collectDataSources(block.raw);
|
|
241
238
|
const loaded: Record<string, string> = {};
|
|
242
239
|
for (const src of sources) {
|
|
@@ -339,9 +336,9 @@ function Studio({ configured, modelName, remaining: initialRemaining, recipes }:
|
|
|
339
336
|
这一篇
|
|
340
337
|
<select value="" onChange={(event) => void openBlock(pickedFile, Number(event.target.value))}>
|
|
341
338
|
<option value="">选一块</option>
|
|
342
|
-
{pickedBlocks.
|
|
339
|
+
{pickedBlocks.map((block) => (
|
|
343
340
|
<option key={block.startLine} value={block.startLine}>
|
|
344
|
-
{`${
|
|
341
|
+
{`${"Compose"} · ${block.id ?? `第 ${block.startLine} 行`} · ${block.status}`}
|
|
345
342
|
</option>
|
|
346
343
|
))}
|
|
347
344
|
</select>
|
|
@@ -387,9 +384,9 @@ function Studio({ configured, modelName, remaining: initialRemaining, recipes }:
|
|
|
387
384
|
</div>
|
|
388
385
|
|
|
389
386
|
<div className="ui-studio-preview" data-theme={theme} data-width={width} hidden={stage !== "preview"}>
|
|
390
|
-
{
|
|
391
|
-
view === "interactive" ? (
|
|
392
|
-
<
|
|
387
|
+
{shownScene ? (
|
|
388
|
+
view === "interactive" || !shown ? (
|
|
389
|
+
<GardenBlock dataPayload={shownScene} />
|
|
393
390
|
) : (
|
|
394
391
|
<figure className="garden-ui" lang="en-US">
|
|
395
392
|
<div className="garden-ui-body garden-ui-static" dangerouslySetInnerHTML={{ __html: renderStatic(shown, "html") }} />
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { UiLangProvider } from "../UiLang";
|
|
4
|
+
import { GardenBlock } from "./GardenBlock";
|
|
5
|
+
|
|
6
|
+
/** Site/editor host adapter; the portable UI package only receives this callback. */
|
|
7
|
+
export function renderComposePreview(block: unknown, lang?: "zh" | "en") {
|
|
8
|
+
return <UiLangProvider lang={lang}><GardenBlock dataPayload={encodeURIComponent(JSON.stringify(block))} /></UiLangProvider>;
|
|
9
|
+
}
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import ReactMarkdown, {type Components} from "react-markdown";
|
|
4
|
+
import rehypeRaw from "rehype-raw";
|
|
3
5
|
import { useUiLang } from "../UiLang";
|
|
6
|
+
import { readPayload } from "../../../ui/core/compile.mjs";
|
|
7
|
+
import { parseCompose } from "../../../ui/compose/index.mjs";
|
|
4
8
|
import { renderCompose } from "../../../ui/compose/render.mjs";
|
|
5
9
|
import { readGardenPayload } from "../../lib/remark-garden.mjs";
|
|
6
10
|
import {
|
|
7
11
|
GardenCard,
|
|
8
12
|
GardenChart,
|
|
9
13
|
GardenCompare,
|
|
10
|
-
GardenHtml,
|
|
11
14
|
GardenIdea,
|
|
12
15
|
GardenInteractive,
|
|
13
|
-
GardenLegacy,
|
|
14
16
|
GardenStat,
|
|
15
17
|
GardenTimeline,
|
|
16
18
|
GardenUnknown,
|
|
@@ -18,11 +20,14 @@ import {
|
|
|
18
20
|
import { GardenFile } from "./GardenFile";
|
|
19
21
|
import { GardenChoice, GardenList, GardenRelations } from "./patterns";
|
|
20
22
|
|
|
23
|
+
const composeComponents = {
|
|
24
|
+
"garden-block": (nested: {dataPayload?:string;"data-payload"?:string}) => <GardenBlock dataPayload={nested.dataPayload??nested["data-payload"]}/>,
|
|
25
|
+
};
|
|
26
|
+
|
|
21
27
|
export function GardenBlock({ dataPayload }: { dataPayload?: string }) {
|
|
22
28
|
const lang = useUiLang();
|
|
23
29
|
const block = readGardenPayload(dataPayload);
|
|
24
30
|
if (!block) return null;
|
|
25
|
-
if (block.legacy && block.suggestion) return <GardenLegacy suggestion={String(block.suggestion)} />;
|
|
26
31
|
if (!block.name) return <GardenIdea text={String(block.idea || block.children || "")} />;
|
|
27
32
|
if (block.error) return <GardenUnknown name={String(block.name)} />;
|
|
28
33
|
const props = (block.props ?? {}) as Record<string, unknown>;
|
|
@@ -41,24 +46,21 @@ export function GardenBlock({ dataPayload }: { dataPayload?: string }) {
|
|
|
41
46
|
if (block.error?.code === "compose-uncompiled") return <GardenIdea text={String(block.idea || block.children || "")} />;
|
|
42
47
|
if (block.error) return <GardenUnknown name="compose" />;
|
|
43
48
|
if (block.mode === "play") {
|
|
44
|
-
//
|
|
49
|
+
// Play receives lowered restricted source or a validated build payload.
|
|
45
50
|
return (
|
|
46
51
|
<GardenInteractive
|
|
52
|
+
renderScene={source => { const parsed=parseCompose(source); return parsed.ok ? <GardenBlock dataPayload={encodeURIComponent(JSON.stringify({name:"compose",props:{...parsed.doc,frame:"none"}}))}/> : null; }}
|
|
47
53
|
block={{
|
|
48
54
|
id: block.id,
|
|
49
55
|
props,
|
|
56
|
+
payload: block.payload ? readPayload(JSON.stringify(block.payload)) : null,
|
|
50
57
|
children: String(block.children ?? ""),
|
|
51
58
|
uiSource: typeof block.uiSource === "string" ? block.uiSource : undefined,
|
|
52
59
|
}}
|
|
53
60
|
/>
|
|
54
61
|
);
|
|
55
62
|
}
|
|
56
|
-
return <div className="g-compose-host"
|
|
57
|
-
case "html":
|
|
58
|
-
return <GardenHtml props={props}>{String(block.children ?? "")}</GardenHtml>;
|
|
59
|
-
case "interactive":
|
|
60
|
-
// TEMP compat if a payload still names interactive; parse now rewrites to compose.
|
|
61
|
-
return <GardenInteractive block={{ id: block.id, props, children: String(block.children ?? "") }} />;
|
|
63
|
+
return <div className="g-compose-host"><ReactMarkdown rehypePlugins={[rehypeRaw]} components={composeComponents as Components}>{renderCompose(props)}</ReactMarkdown></div>;
|
|
62
64
|
case "file":
|
|
63
65
|
// Post bodies render file cards on the server (MarkdownArticle); this path has no Markdown preview.
|
|
64
66
|
return <GardenFile props={props} lang={lang} />;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Server component: the card for
|
|
1
|
+
// Server component: the card for Compose file. Only the 预览 control is a client island.
|
|
2
2
|
import { siteLang, ui, type UiLang } from "../../lib/i18n";
|
|
3
3
|
import { blockFiles, fileMeta } from "../../../ui/garden/file-block.mjs";
|
|
4
4
|
import { ActionGlyph, FileGlyph, safeFileHref } from "./file-icons";
|