Skip to content

Commit

Permalink
feat: add onMissingComponent prop
Browse files Browse the repository at this point in the history
  • Loading branch information
theisel committed Jul 23, 2022
1 parent 4f362ed commit 02e36c6
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 28 deletions.
5 changes: 5 additions & 0 deletions .changeset/pretty-chairs-remain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"astro-portabletext": minor
---

Adds `onMissingComponent` prop to `PortableText` component
125 changes: 97 additions & 28 deletions component/lib/PortableText.astro
Original file line number Diff line number Diff line change
@@ -1,13 +1,4 @@
---
import {
ComponentOrRecord,
PortableTextComponents,
Component,
Props,
PortableTextProps,
TypedObject,
} from "./types";
import {
isPortableTextBlock,
isPortableTextListItemBlock,
Expand All @@ -19,8 +10,29 @@ import {
LIST_NEST_MODE_HTML,
} from "@portabletext/toolkit";
import mitt from "mitt";
import {
ComponentOrRecord,
PortableTextComponents,
Component,
Props,
PortableTextProps,
TypedObject,
NodeType,
} from "./types";
import { isComponent, mergeComponents } from "./internal";
import {
printWarning,
unknownTypeWarning,
unknownMarkWarning,
unknownBlockStyleWarning,
unknownListStyleWarning,
unknownListItemStyleWarning,
} from "./warnings";
import NodeRenderer from "./NodeRenderer.astro";
import Block from "./components/Block.astro";
Expand All @@ -39,6 +51,7 @@ const {
value,
components: componentOverrides = {},
listNestingMode = LIST_NEST_MODE_HTML,
onMissingComponent = printWarning,
class: astroClass,
} = Astro.props as PortableTextProps & { class?: string };
Expand Down Expand Up @@ -81,24 +94,75 @@ const components = mergeComponents(
componentOverrides
) as PortableTextComponents;
const getComponent = (
componentHandler: [
prop: "type" | "block" | "list" | "listItem" | "mark" | "hardBreak",
recordKey: string
],
unknownComponentHandler:
const noop = () => {};
const missingComponentHandler = onMissingComponent || noop;
const emitter = mitt<{
unknownType: string;
unknownBlockStyle: string;
unknownList: string;
unknownListItem: string;
unknownMark: string;
}>();
emitter.on("unknownType", (type: string) => {
missingComponentHandler(unknownTypeWarning(type), {
nodeType: NodeType.BLOCK,
type: type,
});
});
emitter.on("unknownBlockStyle", (style: string) => {
missingComponentHandler(unknownBlockStyleWarning(style), {
nodeType: NodeType.BLOCK_STYLE,
type: style,
});
});
emitter.on("unknownList", (listItem: string) => {
missingComponentHandler(unknownListStyleWarning(listItem), {
nodeType: NodeType.LIST_STYLE,
type: listItem,
});
});
emitter.on("unknownListItem", (listItem: string) => {
missingComponentHandler(unknownListStyleWarning(listItem), {
nodeType: NodeType.LIST_ITEM_STYLE,
type: listItem,
});
});
emitter.on("unknownMark", (markType: string) => {
missingComponentHandler(unknownMarkWarning(markType), {
nodeType: NodeType.MARK,
type: markType,
});
});
const provideComponent = (
type: "type" | "block" | "list" | "listItem" | "mark",
fallback:
| "unknownType"
| "unknownBlockStyle"
| "unknownList"
| "unknownListItem"
| "unknownMark"
| "unknownMark",
key: string
): Component => {
const [prop, recordKey] = componentHandler;
const componentOrRecord = components[prop] as ComponentOrRecord;
const componentOrRecord = components[type] as ComponentOrRecord;
return isComponent(componentOrRecord)
? componentOrRecord
: componentOrRecord[recordKey] ?? components[unknownComponentHandler];
if (isComponent(componentOrRecord)) {
return componentOrRecord;
}
if (key in componentOrRecord) {
return componentOrRecord[key];
}
emitter.emit(fallback, key);
return components[fallback] as Component;
};
const blocks = Array.isArray(value) ? value : [value];
Expand Down Expand Up @@ -128,15 +192,19 @@ const asComponentProps = (

return isPortableTextToolkitList(node) ? (
<NodeRenderer
component={getComponent(["list", node.listItem], "unknownList")}
component={provideComponent("list", "unknownList", node.listItem)}
node={node}
{...props}
>
<Raw value={node.children.map(serializeNode(false)).map(renderNode)} />
</NodeRenderer>
) : isPortableTextListItemBlock(node) ? (
<NodeRenderer
component={getComponent(["listItem", node.listItem], "unknownListItem")}
component={provideComponent(
"listItem",
"unknownListItem",
node.listItem
)}
node={node}
{...props}
>
Expand All @@ -152,17 +220,18 @@ const asComponentProps = (
</NodeRenderer>
) : isPortableTextToolkitSpan(node) ? (
<NodeRenderer
component={getComponent(["mark", node.markType], "unknownMark")}
component={provideComponent("mark", "unknownMark", node.markType)}
node={node}
{...props}
>
<Raw value={node.children.map(serializeNode(true)).map(renderNode)} />
</NodeRenderer>
) : isPortableTextBlock(node) ? (
<NodeRenderer
component={getComponent(
["block", node.style ?? "normal"],
"unknownBlockStyle"
component={provideComponent(
"block",
"unknownBlockStyle",
node.style ?? "normal"
)}
node={{ style: "normal", ...node }}
{...props}
Expand All @@ -179,7 +248,7 @@ const asComponentProps = (
)
) : (
<NodeRenderer
component={getComponent(["type", node._type], "unknownType")}
component={provideComponent("type", "unknownType", node._type)}
node={node}
{...props}
/>
Expand Down
21 changes: 21 additions & 0 deletions component/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ export interface PortableTextProps<
*/
components: PortableTextComponents;

/**
* This handler is called when faced with unknown types.
*
* Prints a warning message to the console by default.
* Use `false` to disable.
*/
onMissingComponent?: MissingComponentHandler | false;

/**
* A 'nice to have', default is 'html'
* Refer to {@link https://portabletext.github.io/toolkit/modules.html#ToolkitListNestMode}
Expand Down Expand Up @@ -201,6 +209,19 @@ export interface Mark<
markKey: string;
}

export type MissingComponentHandler = (
message: string,
context: { type: string; nodeType: NodeType }
) => void;

export enum NodeType {
BLOCK = "block",
BLOCK_STYLE = "blockStyle",
LIST_STYLE = "listStyle",
LIST_ITEM_STYLE = "listItemStyle",
MARK = "mark",
}

/**
* @deprecated Use {@link Props} instead
*/
Expand Down

0 comments on commit 02e36c6

Please sign in to comment.