页面区块:15 个无障碍版块(首屏、价格、带 JSON-LD 的 FAQ 等)、后台编辑器和可扩展注册表
代码25 个文件上下文约 681 个 token扫描通过
安装
$
genpm add @core/blocks你将获得
- 源代码位于 src/lib/blocks/,共 25 个文件。 (52.7 kB)
- AI 规则位于 src/lib/blocks/AGENTS.md,另附 IDE 规则文件。
- 自动为你解析 @core/admin, @core/contracts, @core/db, @core/media, @core/rich-text, @core/ui。
README
此包没有 README。
约 681 个 token→ src/lib/blocks/AGENTS.md→ .cursor/rules/genpm-core-blocks.mdc
这正是你的 AI 在 src/lib/blocks 中工作时读取的内容。不会向其上下文添加其他任何内容。
@core/blocks — rules for AI agents
Purpose
Page sections that editors combine from the admin: hero, features, benefits, testimonials, logos, pricing, FAQ (with FAQPage JSON-LD), call to action, gallery, video, stats, comparison, guarantee, rich text and form. Each block is a zod schema + editor fields + a React component. Other packages and kits add their own blocks (product grid, latest posts…) to the same registry. Blocks are stored as JSON inside @core/content entries; no tables of its own.
Map
index.ts—defineBlock,createBlockRegistry,coreBlocks(and each block),RenderBlocks,collectMediaIds.core/*.ts— one file per block.parts.ts—Section,Heading,Img,CtaLink,JsonLdfor new blocks.media.ts—loadBlockMedia(blocks, registry)(server: one query to @core/media).editor.ts—'use client':blocksRenderer(blockMeta(registry))forAdminApp.blocks.css— styles on @core/ui tokens.
Integration
- Create
src/genpm/blocks.ts:export const blocks = createBlockRegistry([...coreBlocks /*, kit blocks */]); - Add a field to a content collection: schema
blocks: blocks.schema, admin fieldblocks.field('blocks', 'Sections'). - Admin: compute
blockMeta(blocks)on the server and pass it to the client component that renders<AdminApp renderers={{ blocks: blocksRenderer(meta), /* richText, media… */ }} />(never import the registry in client code). - Page (server component):
const media = await loadBlockMedia(page.data.blocks, blocks); return <RenderBlocks blocks={page.data.blocks} registry={blocks} media={media} renderForm={(key) => <Form formKey={key} />} embeds={hasConsent('marketing') ? 'iframe' : 'link'} />; - Styles: import
src/lib/ui/ui.cssthensrc/lib/blocks/blocks.cssin the root layout. - Verify: a page with every block passes axe and has one
h1; a block with an unknown type renders nothing and warns in dev.
Conventions
- A new block = new file with
defineBlock+ entry in the registry. Never addif (type === …)toRenderBlocks. - Use
SectionandHeadingwithctx.level(items one level below); only blocks withpageTitlemay become theh1. - Images are media ids resolved with
loadBlockMedia; links go through thehrefschema (nojavascript:). - Text in blocks is plain text; use the
rich-textblock for formatting.
Don't
- Don't render user HTML (
dangerouslySetInnerHTML) except viaJsonLd, which escapes<,>and&. - Don't load video iframes without marketing consent (
embeds: 'link'is the default). - Don't invent testimonials, ratings or statistics; the editor help texts remind users these must be real and verifiable.
# @core/blocks — rules for AI agents
## Purpose
Page sections that editors combine from the admin: hero, features, benefits, testimonials, logos, pricing, FAQ (with
FAQPage JSON-LD), call to action, gallery, video, stats, comparison, guarantee, rich text and form. Each block is a
zod schema + editor fields + a React component. Other packages and kits add their own blocks (product grid, latest
posts…) to the same registry. Blocks are stored as JSON inside @core/content entries; no tables of its own.
## Map
- `index.ts` — `defineBlock`, `createBlockRegistry`, `coreBlocks` (and each block), `RenderBlocks`, `collectMediaIds`.
- `core/*.ts` — one file per block. `parts.ts` — `Section`, `Heading`, `Img`, `CtaLink`, `JsonLd` for new blocks.
- `media.ts` — `loadBlockMedia(blocks, registry)` (server: one query to @core/media).
- `editor.ts` — `'use client'`: `blocksRenderer(blockMeta(registry))` for `AdminApp`. `blocks.css` — styles on @core/ui tokens.
## Integration
1. Create `src/genpm/blocks.ts`: `export const blocks = createBlockRegistry([...coreBlocks /*, kit blocks */]);`
2. Add a field to a content collection: schema `blocks: blocks.schema`, admin field `blocks.field('blocks', 'Sections')`.
3. Admin: compute `blockMeta(blocks)` on the server and pass it to the client component that renders
`<AdminApp renderers={{ blocks: blocksRenderer(meta), /* richText, media… */ }} />` (never import the registry in client code).
4. Page (server component):
```tsx
const media = await loadBlockMedia(page.data.blocks, blocks);
return <RenderBlocks blocks={page.data.blocks} registry={blocks} media={media}
renderForm={(key) => <Form formKey={key} />} embeds={hasConsent('marketing') ? 'iframe' : 'link'} />;
```
5. Styles: import `src/lib/ui/ui.css` then `src/lib/blocks/blocks.css` in the root layout.
6. Verify: a page with every block passes axe and has one `h1`; a block with an unknown type renders nothing and warns in dev.
## Conventions
- A new block = new file with `defineBlock` + entry in the registry. Never add `if (type === …)` to `RenderBlocks`.
- Use `Section` and `Heading` with `ctx.level` (items one level below); only blocks with `pageTitle` may become the `h1`.
- Images are media ids resolved with `loadBlockMedia`; links go through the `href` schema (no `javascript:`).
- Text in blocks is plain text; use the `rich-text` block for formatting.
## Don't
- Don't render user HTML (`dangerouslySetInnerHTML`) except via `JsonLd`, which escapes `<`, `>` and `&`.
- Don't load video iframes without marketing consent (`embeds: 'link'` is the default).
- Don't invent testimonials, ratings or statistics; the editor help texts remind users these must be real and verifiable.
应用 .genpmignore 后将被注入的确切目录树。固定于
'use client';
// Campo `blocks` para @core/admin: lista ordenable de bloques con añadir, duplicar, ocultar, mover y quitar.
// Uso: en el servidor `const meta = blockMeta(blocks)` (registro de `src/genpm/blocks.ts`) y en el cliente
// `<AdminApp renderers={{ blocks: blocksRenderer(meta) }} />`: al navegador solo viajan tipos, campos y valores iniciales.
import { createElement as h, type ReactNode, useId, useState } from 'react';
import { type FieldRenderer, type FieldRendererProps, FieldControl, getPath, setPath } from '../admin/client.ts';
import { Badge, Button, SelectField } from '../ui/index.ts';
import { type BlockInstance, type BlockMeta, newBlockId } from './define.ts';
export const editorLabels = {
add: 'Add block',
type: 'Block type',
moveUp: 'Move up',
moveDown: 'Move down',
duplicate: 'Duplicate',
remove: 'Remove',
hide: 'Hide',
show: 'Show',
hidden: 'Hidden',
edit: 'Edit',
collapse: 'Collapse',
unknown: 'Unknown block',
empty: 'No blocks yet.',
};
function BlocksEditor(p: FieldRendererProps & { meta: BlockMeta[]; text: typeof editorLabels }): ReactNode {
const blocks = Array.isArray(p.value) ? (p.value as BlockInstance[]) : [];
const defs = p.meta;
const get = (type: string) => defs.find((d) => d.type === type);
const [type, setType] = useState(defs[0]?.type ?? '');
const [open, setOpen] = useState<string | null>(null);
const legendId = useId();
const set = (next: BlockInstance[]) => p.onChange(next);
const update = (i: number, patch: Partial<BlockInstance>) => set(blocks.map((b, j) => (j === i ? { ...b, ...patch } : b)));
const move = (i: number, d: -1 | 1) => {
const next = [...blocks];
const [b] = next.splice(i, 1);
next.splice(i + d, 0, b!);
set(next);
};
const L = p.text;
return h(
'fieldset',
{ className: 'ui-field admin-list', 'aria-labelledby': legendId },
h('legend', { id: legendId, className: 'ui-field__label' }, p.field.label),
!blocks.length && h('p', { className: 'ui-field__help' }, L.empty),
h(
'ol',
{ className: 'blk-editor' },
blocks.map((b, i) => {
const def = get(b.type);
const name = `${i + 1}. ${def?.label ?? L.unknown}`;
const isOpen = open === b.id;
return h(
'li',
{ key: b.id, className: 'admin-list__item' },
h(
'div',
{ className: 'admin-list__tools' },
h(Button, { size: 'sm', variant: 'ghost', 'aria-expanded': isOpen, onClick: () => setOpen(isOpen ? null : b.id) }, h('strong', null, name)),
b.hidden && h(Badge, { tone: 'warning' }, L.hidden),
h(Button, { size: 'sm', disabled: p.disabled || i === 0, onClick: () => move(i, -1), 'aria-label': `${L.moveUp}: ${name}` }, '↑'),
h(Button, { size: 'sm', disabled: p.disabled || i === blocks.length - 1, onClick: () => move(i, 1), 'aria-label': `${L.moveDown}: ${name}` }, '↓'),
h(Button, { size: 'sm', disabled: p.disabled, onClick: () => update(i, { hidden: !b.hidden }), 'aria-label': `${b.hidden ? L.show : L.hide}: ${name}` }, b.hidden ? L.show : L.hide),
h(
Button,
{
size: 'sm',
disabled: p.disabled,
'aria-label': `${L.duplicate}: ${name}`,
onClick: () => set([...blocks.slice(0, i + 1), { ...structuredClone(b), id: newBlockId() }, ...blocks.slice(i + 1)]),
},
L.duplicate,
),
h(Button, { size: 'sm', variant: 'danger', disabled: p.disabled, onClick: () => set(blocks.filter((_, j) => j !== i)), 'aria-label': `${L.remove}: ${name}` }, L.remove),
),
isOpen &&
def &&
def.fields.map((f) =>
h(FieldControl, {
key: f.name,
field: f,
value: getPath(b.data, f.name),
onChange: (v) => update(i, { data: setPath(b.data, f.name, v) }),
disabled: p.disabled,
api: p.api,
labels: p.labels,
renderers: p.renderers,
}),
),
);
}),
),
defs.length > 0 &&
h(
'div',
{ className: 'admin-list__tools' },
h(SelectField, { label: L.type, options: defs.map((d) => ({ value: d.type, label: d.label })), value: type, disabled: p.disabled, onChange: (e) => setType(e.currentTarget.value) }),
h(
Button,
{
disabled: p.disabled || !type,
onClick: () => {
const def = get(type);
if (!def) return;
const b: BlockInstance = { id: newBlockId(), type, data: structuredClone(def.defaults) };
set([...blocks, b]);
setOpen(b.id);
},
},
L.add,
),
),
p.error && h('span', { className: 'ui-field__error', role: 'alert' }, p.error),
);
}
/** Renderizador del tipo de campo `blocks` para `AdminApp`; los campos internos usan los demás `renderers` de la app. */
export function blocksRenderer(meta: BlockMeta[], opts: { labels?: Partial<typeof editorLabels> } = {}): FieldRenderer {
const text = { ...editorLabels, ...opts.labels };
return (props) => h(BlocksEditor, { ...props, meta, text });
}
此包未声明 MCP 服务器。
| 版本 | 提交 | 发布时间 | 扫描 |
|---|---|---|---|
| 1.0.0 | a255d6d | 4小时前 | 扫描通过 |
- genpm
- @core/admin ^1.0.0@core/contracts ^1.0.0@core/db ^1.0.0@core/media ^1.0.0@core/rich-text ^1.0.0@core/ui ^1.0.0
- 建议
- GenPM 会给出 npm 命令建议,只有你同意时才会运行。
- 被以下包使用(1)
- @core/kit-cms ^1.0.0
- 扫描
- 扫描通过 · 0 个问题
- 提交
- v1.0.0 → a255d6d08201d6342d07ad41d96123ea1407a5f1 · 获取后已校验
- 脚本
- 无。GenPM 从不运行包中的代码。
- 许可证
- MIT
- 质量
- 100/100
- 可识别的许可证已满足
- AGENTS.md 说明了用途已满足
- AGENTS.md 包含集成步骤已满足
- AGENTS.md 列出约定或禁止事项已满足
- 包含测试已满足
- 通过安全扫描已满足
- 最近 6 个月内发布已满足
- 已验证的发布者已满足
- 摘要和关键词已满足
- 举报
- 发现问题了吗?