@bettercms-ai/richtext 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/html.ts +11 -2
- package/src/read.ts +24 -1
- package/src/types.ts +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bettercms-ai/richtext",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "The one Portable Text contract for BetterCMS: schema, converters, and rendering. Shared by the backend, the dashboard and every delivery SDK.",
|
package/src/html.ts
CHANGED
|
@@ -235,6 +235,11 @@ export interface RenderOptions {
|
|
|
235
235
|
* sits on each `<li>`, because a wrapper per item would split the list into one-item lists.
|
|
236
236
|
*/
|
|
237
237
|
bindingBase?: string;
|
|
238
|
+
/**
|
|
239
|
+
* Renders an embedded block this serialiser cannot resolve itself (a component, say). A
|
|
240
|
+
* string replaces the marker; `undefined` keeps it.
|
|
241
|
+
*/
|
|
242
|
+
renderBlock?: (value: Record<string, unknown>) => string | undefined;
|
|
238
243
|
}
|
|
239
244
|
|
|
240
245
|
/**
|
|
@@ -269,6 +274,7 @@ const bindingAttrs = (base: string, key: unknown): string =>
|
|
|
269
274
|
const buildComponents = (
|
|
270
275
|
classes: HtmlClasses = {},
|
|
271
276
|
binding?: { base: string; keys: ReadonlySet<unknown> },
|
|
277
|
+
renderBlock?: RenderOptions["renderBlock"],
|
|
272
278
|
): Partial<PortableTextHtmlComponents> => ({
|
|
273
279
|
block: tagOverrides(classes, STYLE_TAGS),
|
|
274
280
|
list: tagOverrides(classes, { bullet: "ul", number: "ol", check: "ul" }),
|
|
@@ -315,6 +321,8 @@ const buildComponents = (
|
|
|
315
321
|
*/
|
|
316
322
|
const legacy = builtInTableToTable(value);
|
|
317
323
|
if (legacy) return tableHtml(legacy);
|
|
324
|
+
const rendered = renderBlock?.(value as Record<string, unknown>);
|
|
325
|
+
if (rendered !== undefined) return rendered;
|
|
318
326
|
const key = typeof v.schemaKey === "string" && TOKEN.test(v.schemaKey.replace(/^builtin:/, ""))
|
|
319
327
|
? v.schemaKey
|
|
320
328
|
: "block";
|
|
@@ -392,11 +400,12 @@ export function portableTextToHtml(
|
|
|
392
400
|
opts: RenderOptions = {},
|
|
393
401
|
): string {
|
|
394
402
|
if (!Array.isArray(value) || value.length === 0) return "";
|
|
395
|
-
const components =
|
|
403
|
+
const components =
|
|
404
|
+
opts.classes || opts.renderBlock ? buildComponents(opts.classes, undefined, opts.renderBlock) : defaultComponents;
|
|
396
405
|
|
|
397
406
|
if (opts.bindingBase) {
|
|
398
407
|
const base = opts.bindingBase;
|
|
399
|
-
const bound = buildComponents(opts.classes, { base, keys: new Set(value.map((n) => n._key)) });
|
|
408
|
+
const bound = buildComponents(opts.classes, { base, keys: new Set(value.map((n) => n._key)) }, opts.renderBlock);
|
|
400
409
|
const out: string[] = [];
|
|
401
410
|
for (let i = 0; i < value.length; ) {
|
|
402
411
|
const node = value[i];
|
package/src/read.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PORTABLE_TEXT_FORMAT, PT_TYPES, type PortableText } from "./types";
|
|
1
|
+
import { EMBEDDED_COMPONENT_KEY, PORTABLE_TEXT_FORMAT, PT_TYPES, type PortableText } from "./types";
|
|
2
2
|
import { lexicalToPortableText } from "./from-lexical";
|
|
3
3
|
import { migrateTable } from "./table";
|
|
4
4
|
import { randomKey, type KeyGenerator } from "./key";
|
|
@@ -231,3 +231,26 @@ export function readStructuredContent(props: unknown): PortableText | null {
|
|
|
231
231
|
: null;
|
|
232
232
|
return arr && arr.length > 0 ? (arr as PortableText) : null;
|
|
233
233
|
}
|
|
234
|
+
|
|
235
|
+
/** The `component` block an embed in rich text stands for. */
|
|
236
|
+
export interface EmbeddedComponentBlock {
|
|
237
|
+
type: "component";
|
|
238
|
+
/** The node's `_key` — unique within its rich-text field only. */
|
|
239
|
+
id: string;
|
|
240
|
+
props: { componentId: string; overrides: Record<string, unknown> };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* A component embedded in rich text, as the ordinary `component` block every renderer already
|
|
245
|
+
* draws — so an embed gets the same resolver, cycle guard and identity attributes as a placed
|
|
246
|
+
* instance. Null for any other node.
|
|
247
|
+
*/
|
|
248
|
+
export function embeddedComponentBlock(value: unknown): EmbeddedComponentBlock | null {
|
|
249
|
+
if (!isRecord(value) || value.schemaKey !== EMBEDDED_COMPONENT_KEY || !isRecord(value.fields)) return null;
|
|
250
|
+
const { componentId, overrides } = value.fields;
|
|
251
|
+
return {
|
|
252
|
+
type: "component",
|
|
253
|
+
id: typeof value._key === "string" ? value._key : "",
|
|
254
|
+
props: { componentId: String(componentId ?? ""), overrides: isRecord(overrides) ? overrides : {} },
|
|
255
|
+
};
|
|
256
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -276,6 +276,12 @@ export const STORABLE_BLOCK_TYPES: ReadonlySet<string> = new Set<string>([
|
|
|
276
276
|
export const BUILTIN_IMAGE_KEY = "builtin:image" as const;
|
|
277
277
|
export const BUILTIN_EMBED_KEY = "builtin:video" as const;
|
|
278
278
|
|
|
279
|
+
/**
|
|
280
|
+
* The `schemaKey` of a component embedded in rich text. A wire value shared with the dashboard
|
|
281
|
+
* (`COMPONENT_SCHEMA_KEY`) and the backend (`RICHTEXT_COMPONENT_KEY`).
|
|
282
|
+
*/
|
|
283
|
+
export const EMBEDDED_COMPONENT_KEY = "component" as const;
|
|
284
|
+
|
|
279
285
|
/**
|
|
280
286
|
* Top-level nodes whose `_type` this platform cannot store or render, in document order.
|
|
281
287
|
*
|