@pramen/cms 0.0.14 → 0.0.15
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/dist/index.d.ts +1078 -0
- package/dist/index.js +1342 -0
- package/dist/react.d.ts +41 -0
- package/dist/react.js +30 -0
- package/package.json +6 -3
- package/src/index.ts +14 -0
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ComponentType, ReactElement } from "react";
|
|
2
|
+
import type { RenderedBlock, BlockTypeDef, BlockFieldsOf } from "./index";
|
|
3
|
+
/** Props a component for a specific typed block type receives — `fields` is inferred from
|
|
4
|
+
* the block type's schema via `BlockFieldsOf` (see `defineBlockType`). */
|
|
5
|
+
export interface TypedBlockProps<D extends BlockTypeDef> {
|
|
6
|
+
fields: BlockFieldsOf<D>;
|
|
7
|
+
block: RenderedBlock;
|
|
8
|
+
region?: string;
|
|
9
|
+
position: number;
|
|
10
|
+
}
|
|
11
|
+
/** A React component for a typed block type: `const Hero: TypedBlockComponent<typeof heroDef>`. */
|
|
12
|
+
export type TypedBlockComponent<D extends BlockTypeDef> = ComponentType<TypedBlockProps<D>>;
|
|
13
|
+
/** Props a block component receives. `fields` is the block's (override-merged) content. */
|
|
14
|
+
export interface BlockComponentProps {
|
|
15
|
+
fields: Record<string, unknown>;
|
|
16
|
+
block: RenderedBlock;
|
|
17
|
+
region?: string;
|
|
18
|
+
position: number;
|
|
19
|
+
}
|
|
20
|
+
export type BlockComponents = Record<string, ComponentType<BlockComponentProps>>;
|
|
21
|
+
export interface BlockRendererProps {
|
|
22
|
+
blocks: RenderedBlock[];
|
|
23
|
+
region?: string;
|
|
24
|
+
components: BlockComponents;
|
|
25
|
+
/** Rendered when no component is registered for a block's type. Defaults to a text node. */
|
|
26
|
+
fallback?: ComponentType<{
|
|
27
|
+
block: RenderedBlock;
|
|
28
|
+
}>;
|
|
29
|
+
}
|
|
30
|
+
/** Render an ordered list of blocks, each via its registered component. */
|
|
31
|
+
export declare function BlockRenderer({ blocks, region, components, fallback }: BlockRendererProps): ReactElement;
|
|
32
|
+
export interface RegionRendererProps {
|
|
33
|
+
regions: Record<string, RenderedBlock[]>;
|
|
34
|
+
name: string;
|
|
35
|
+
components: BlockComponents;
|
|
36
|
+
fallback?: ComponentType<{
|
|
37
|
+
block: RenderedBlock;
|
|
38
|
+
}>;
|
|
39
|
+
}
|
|
40
|
+
/** Render a single named region from an AssembledPage's `regions` map. */
|
|
41
|
+
export declare function RegionRenderer({ regions, name, components, fallback }: RegionRendererProps): ReactElement;
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// @pramen/cms/react — the headless renderer. Like WollyCMS's BlockRenderer, it maps a
|
|
2
|
+
// block's `block_type` slug to a React component from a registry you provide; the CMS
|
|
3
|
+
// stays headless and never dictates markup. Pair with useLiveQuery from @pramen/react
|
|
4
|
+
// for a live-updating page:
|
|
5
|
+
//
|
|
6
|
+
// import { BlockRenderer, RegionRenderer } from "@pramen/cms/react";
|
|
7
|
+
// const components = { hero: Hero, rich_text: RichText, cta: Cta };
|
|
8
|
+
// const { data } = useLiveQuery(client, "getPage", { slug }); // AssembledPage
|
|
9
|
+
// return RegionRenderer({ regions: data.regions, name: "content", components });
|
|
10
|
+
//
|
|
11
|
+
// Written with `createElement` (not JSX) so the package needs no DOM lib / JSX build
|
|
12
|
+
// config and stays on the same tsconfig as the rest of pramen. In a JSX codebase you'd
|
|
13
|
+
// normally write `<BlockRenderer .../>` — it's an ordinary component either way.
|
|
14
|
+
import { createElement, Fragment } from "react";
|
|
15
|
+
/** Render an ordered list of blocks, each via its registered component. */
|
|
16
|
+
export function BlockRenderer({ blocks, region, components, fallback }) {
|
|
17
|
+
return createElement(Fragment, null, blocks.map((block, index) => {
|
|
18
|
+
const Component = components[block.block_type];
|
|
19
|
+
if (!Component) {
|
|
20
|
+
return fallback
|
|
21
|
+
? createElement(fallback, { key: block.id, block })
|
|
22
|
+
: createElement(Fragment, { key: block.id }, `Unknown block type: ${block.block_type}`);
|
|
23
|
+
}
|
|
24
|
+
return createElement(Component, { key: block.id, fields: block.fields, block, region, position: index });
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
/** Render a single named region from an AssembledPage's `regions` map. */
|
|
28
|
+
export function RegionRenderer({ regions, name, components, fallback }) {
|
|
29
|
+
return BlockRenderer({ blocks: regions[name] ?? [], region: name, components, fallback });
|
|
30
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pramen/cms",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
4
4
|
"description": "Optional block/page builder for pramen — Drupal-Paragraphs-style typed blocks in named regions, reusable blocks, scheduled publishing, built entirely from pramen primitives.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -30,7 +30,10 @@
|
|
|
30
30
|
},
|
|
31
31
|
"main": "./dist/index.js",
|
|
32
32
|
"types": "./dist/index.d.ts",
|
|
33
|
-
"files": [
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"src"
|
|
36
|
+
],
|
|
34
37
|
"scripts": {
|
|
35
38
|
"build": "rm -rf dist && tsc -p tsconfig.build.json"
|
|
36
39
|
},
|
|
@@ -38,7 +41,7 @@
|
|
|
38
41
|
"access": "public"
|
|
39
42
|
},
|
|
40
43
|
"dependencies": {
|
|
41
|
-
"@pramen/server": "
|
|
44
|
+
"@pramen/server": "0.0.15"
|
|
42
45
|
},
|
|
43
46
|
"peerDependencies": {
|
|
44
47
|
"react": ">=18"
|
package/src/index.ts
CHANGED
|
@@ -872,6 +872,20 @@ export function createCmsHandlers(opts: CmsHandlerOpts = {}) {
|
|
|
872
872
|
},
|
|
873
873
|
}),
|
|
874
874
|
|
|
875
|
+
/** Edit a media asset's metadata (currently just `alt` text). Editor-gated. */
|
|
876
|
+
updateMedia: mutation(async (ctx, input: { id: string; alt: string | null }) => {
|
|
877
|
+
const updated = await cdb(ctx).update("cms_media", input.id, { alt: input.alt ?? null });
|
|
878
|
+
if (!updated) throw notFound("media");
|
|
879
|
+
return updated;
|
|
880
|
+
}, {
|
|
881
|
+
...editor,
|
|
882
|
+
input: (raw): { id: string; alt: string | null } => {
|
|
883
|
+
const o = asObj(raw);
|
|
884
|
+
if (typeof o.id !== "string") throw new BadRequest("id is required");
|
|
885
|
+
return { id: o.id, alt: typeof o.alt === "string" ? o.alt : null };
|
|
886
|
+
},
|
|
887
|
+
}),
|
|
888
|
+
|
|
875
889
|
/** Delete a media row AND its R2 blob. (Automatic orphan sweeping — media no longer
|
|
876
890
|
* referenced by any block — is future work; refs live inside opaque block JSON.) */
|
|
877
891
|
deleteMedia: mutation(async (ctx, input: { id: string }) => {
|