diff --git a/astro-portabletext/README.md b/astro-portabletext/README.md
index 8baabd2..f958aaa 100644
--- a/astro-portabletext/README.md
+++ b/astro-portabletext/README.md
@@ -92,6 +92,7 @@ import { PortableText } from "astro-portabletext";
strong: /* */,
underline: /* */
},
+ text: /* renders string; use custom handler to change output */
hardBreak: /*
*/,
}
```
diff --git a/astro-portabletext/components/PortableText.astro b/astro-portabletext/components/PortableText.astro
index ae03c3d..edadd56 100644
--- a/astro-portabletext/components/PortableText.astro
+++ b/astro-portabletext/components/PortableText.astro
@@ -20,19 +20,22 @@ import type {
TypedObject,
} from "../lib/types";
-import type { Component, NodeType } from "../lib/internal";
-import { isComponent, mergeComponents } from "../lib/internal";
+import {
+ isComponent,
+ mergeComponents,
+ type Component,
+ type NodeType,
+} from "../lib/internal";
import { getWarningMessage, printWarning } from "../lib/warnings";
-
-import type { Context } from "../lib/context";
-import { key as contextRef } from "../lib/context";
+import { key as contextRef, type Context } from "../lib/context";
import Block from "./Block.astro";
import HardBreak from "./HardBreak.astro";
import List from "./List.astro";
import ListItem from "./ListItem.astro";
import Mark from "./Mark.astro";
+import Text from "./Text.astro";
import UnknownBlock from "./UnknownBlock.astro";
import UnknownList from "./UnknownList.astro";
import UnknownListItem from "./UnknownListItem.astro";
@@ -84,6 +87,7 @@ const components = mergeComponents(
underline: Mark,
},
unknownMark: UnknownMark,
+ text: Text,
hardBreak: HardBreak,
},
componentOverrides
@@ -125,66 +129,97 @@ const asComponentProps = (
const provideComponent = (
nodeType: NodeType,
- type: string
-): Component | undefined => {
- const component = components[nodeType];
-
- return isComponent(component)
- ? component
- : (component[type as keyof typeof component] ??
- (missingComponentHandler(getWarningMessage(nodeType, type), {
- nodeType,
- type,
- }) as undefined));
+ type: string,
+ fallbackComponent: Component
+): Component => {
+ const component: Component | undefined = ((component) => {
+ return component[type as keyof typeof component] || component;
+ })(components[nodeType]);
+
+ if (isComponent(component)) {
+ return component;
+ }
+
+ missingComponentHandler(getWarningMessage(nodeType, type), {
+ nodeType,
+ type,
+ });
+
+ return fallbackComponent;
};
const prepareForRender = (
props: ComponentProps
-): [Component | string, ComponentProps[]] => {
+):
+ | [Component | string, ComponentProps[]]
+ | [Component | string] => {
const { node } = props;
- return isPortableTextToolkitList(node)
- ? [
- provideComponent("list", node.listItem) ?? components.unknownList,
- serializeChildren(node, false),
- ]
- : isPortableTextListItemBlock(node)
- ? [
- provideComponent("listItem", node.listItem) ??
- components.unknownListItem,
- serializeMarksTree(node).map((children) => {
- if (node.style !== "normal") {
- const { listItem, ...blockNode } = node;
- children = serializeNode(false)(blockNode, 0);
- }
- return children;
- }),
- ]
- : isPortableTextToolkitSpan(node)
- ? [
- provideComponent("mark", node.markType) ?? components.unknownMark,
- serializeChildren(node, true),
- ]
- : isPortableTextBlock(node)
- ? [
- provideComponent(
- "block",
- node.style ??
- (node.style = "normal") /* Make sure style has been set */
- ) ?? components.unknownBlock,
- serializeMarksTree(node),
- ]
- : isPortableTextToolkitTextNode(node)
- ? [
- "\n" === node.text && isComponent(components.hardBreak)
- ? components.hardBreak
- : node.text,
- [],
- ]
- : [
- provideComponent("type", node._type) ?? components.unknownType,
- [],
- ];
+ if (isPortableTextToolkitList(node)) {
+ return [
+ provideComponent(
+ "list",
+ node.listItem,
+ components.unknownList ?? UnknownList
+ ),
+ serializeChildren(node, false),
+ ];
+ }
+
+ if (isPortableTextListItemBlock(node)) {
+ return [
+ provideComponent(
+ "listItem",
+ node.listItem,
+ components.unknownListItem ?? UnknownListItem
+ ),
+ serializeMarksTree(node).map((children) => {
+ if (node.style !== "normal") {
+ const { listItem, ...blockNode } = node;
+ children = serializeNode(false)(blockNode, 0);
+ }
+ return children;
+ }),
+ ];
+ }
+
+ if (isPortableTextToolkitSpan(node)) {
+ return [
+ provideComponent(
+ "mark",
+ node.markType,
+ components.unknownMark ?? UnknownMark
+ ),
+ serializeChildren(node, true),
+ ];
+ }
+
+ if (isPortableTextBlock(node)) {
+ return [
+ provideComponent(
+ "block",
+ (node.style ??= "normal") /* Make sure style has been set */,
+ components.unknownBlock ?? UnknownBlock
+ ),
+ serializeMarksTree(node),
+ ];
+ }
+
+ if (isPortableTextToolkitTextNode(node)) {
+ return [
+ "\n" === node.text
+ ? isComponent(components.hardBreak)
+ ? components.hardBreak
+ : HardBreak
+ : isComponent(components.text)
+ ? components.text
+ : Text,
+ ];
+ }
+
+ return [
+ provideComponent("type", node._type, components.unknownType ?? UnknownType),
+ ];
};
(globalThis as any)[contextRef] = (node: TypedObject): Context => {
@@ -196,36 +231,43 @@ const prepareForRender = (
// Returns the `default` component related to the passed in node
const provideDefaultComponent = (node: TypedObject) => {
- return isPortableTextToolkitList(node)
- ? List
- : isPortableTextListItemBlock(node)
- ? ListItem
- : isPortableTextToolkitSpan(node)
- ? Mark
- : isPortableTextBlock(node)
- ? Block
- : isPortableTextToolkitTextNode(node)
- ? HardBreak
- : UnknownType;
+ if (isPortableTextToolkitList(node)) return List;
+ if (isPortableTextListItemBlock(node)) return ListItem;
+ if (isPortableTextToolkitSpan(node)) return Mark;
+ if (isPortableTextBlock(node)) return Block;
+
+ if (isPortableTextToolkitTextNode(node)) {
+ return "\n" === node.text ? HardBreak : Text;
+ }
+
+ return UnknownType;
};
// Returns the `unknown` component related to the passed in node
const provideUnknownComponent = (node: TypedObject) => {
- return isPortableTextToolkitList(node)
- ? components.unknownList
- : isPortableTextListItemBlock(node)
- ? components.unknownListItem
- : isPortableTextToolkitSpan(node)
- ? components.unknownMark
- : isPortableTextBlock(node)
- ? components.unknownBlock
- : !isPortableTextToolkitTextNode(node)
- ? components.unknownType
- : (() => {
- throw new Error(
- `[PortableText getUnknownComponent] Unable to provide component with node type ${node._type}`
- );
- })();
+ if (isPortableTextToolkitList(node)) {
+ return components.unknownList ?? UnknownList;
+ }
+
+ if (isPortableTextListItemBlock(node)) {
+ return components.unknownListItem ?? UnknownListItem;
+ }
+
+ if (isPortableTextToolkitSpan(node)) {
+ return components.unknownMark ?? UnknownMark;
+ }
+
+ if (isPortableTextBlock(node)) {
+ return components.unknownBlock ?? UnknownBlock;
+ }
+
+ if (!isPortableTextToolkitTextNode(node)) {
+ return components.unknownType ?? UnknownType;
+ }
+
+ throw new Error(
+ `[PortableText getUnknownComponent] Unable to provide component with node type ${node._type}`
+ );
};
// Make sure we have an array of blocks
@@ -243,7 +285,7 @@ function* renderBlocks() {
{
[...renderBlocks()].map(function render(props) {
- const [Cmp, children] = prepareForRender(props);
+ const [Cmp, children = []] = prepareForRender(props);
return !isComponent(Cmp) ? (
diff --git a/astro-portabletext/components/Text.astro b/astro-portabletext/components/Text.astro
new file mode 100644
index 0000000..f7c5357
--- /dev/null
+++ b/astro-portabletext/components/Text.astro
@@ -0,0 +1,9 @@
+---
+import type { TextNode, Props as $ } from "../lib/types";
+
+export type Props = $;
+
+const { node } = Astro.props;
+---
+
+{node.text}
diff --git a/astro-portabletext/lib/types.ts b/astro-portabletext/lib/types.ts
index a7228c0..da6d236 100644
--- a/astro-portabletext/lib/types.ts
+++ b/astro-portabletext/lib/types.ts
@@ -97,6 +97,10 @@ export interface PortableTextComponents {
* Used when a {@link PortableTextComponents.mark mark} component isn't found
*/
unknownMark: Component>;
+ /**
+ * How text should be rendered
+ */
+ text: Component;
/**
* How line breaks should be rendered
*/
diff --git a/lab/src/components/TextReplace.astro b/lab/src/components/TextReplace.astro
new file mode 100644
index 0000000..573d5c4
--- /dev/null
+++ b/lab/src/components/TextReplace.astro
@@ -0,0 +1,10 @@
+---
+import type { TextNode, Props as $ } from "astro-portabletext/types";
+
+export type Props = $;
+
+const { node } = Astro.props;
+const replacedText = node.text.replace('programmer', 'JavaScript developer').replace('arrays', 'callbacks');
+---
+
+{replacedText}
diff --git a/lab/src/components/TextStyleBySplit.astro b/lab/src/components/TextStyleBySplit.astro
new file mode 100644
index 0000000..0d45f5f
--- /dev/null
+++ b/lab/src/components/TextStyleBySplit.astro
@@ -0,0 +1,17 @@
+---
+import type { TextNode, Props as $ } from "astro-portabletext/types";
+
+export type Props = $;
+
+const { node } = Astro.props;
+---
+
+{node.text.split(' ').map((it, idx) => idx === 0 ? (
+ <> {it}>
+) : it)}
+
+
\ No newline at end of file
diff --git a/lab/src/components/TextStylebyIndex.astro b/lab/src/components/TextStylebyIndex.astro
new file mode 100644
index 0000000..9745911
--- /dev/null
+++ b/lab/src/components/TextStylebyIndex.astro
@@ -0,0 +1,19 @@
+---
+import type { TextNode, Props as $ } from "astro-portabletext/types";
+
+export type Props = $;
+
+const { node, index } = Astro.props;
+---
+{index === 1 ? (
+ <>
+
+ {node.text.trim()}
+ >
+) : node.text}
+
+
\ No newline at end of file
diff --git a/lab/src/pages/text/default.astro b/lab/src/pages/text/default.astro
new file mode 100644
index 0000000..e4a178a
--- /dev/null
+++ b/lab/src/pages/text/default.astro
@@ -0,0 +1,20 @@
+---
+import { PortableText } from "astro-portabletext";
+import Layout from "../../layouts/Default.astro";
+
+const blocks = [
+ {
+ _type: "block",
+ children: [
+ {
+ _type: "span",
+ text: "hello world",
+ },
+ ],
+ },
+];
+---
+
+
+
+
diff --git a/lab/src/pages/text/replace.astro b/lab/src/pages/text/replace.astro
new file mode 100644
index 0000000..0ea040a
--- /dev/null
+++ b/lab/src/pages/text/replace.astro
@@ -0,0 +1,21 @@
+---
+import { PortableText } from "astro-portabletext";
+import Layout from "../../layouts/Default.astro";
+import TextReplace from '../../components/TextReplace.astro';
+
+const blocks = [
+ {
+ _type: "block",
+ children: [
+ {
+ _type: "span",
+ text: "Why did the programmer quit his job? Because he didn't get arrays.",
+ },
+ ],
+ },
+];
+---
+
+
+
+
diff --git a/lab/src/pages/text/style-by-index.astro b/lab/src/pages/text/style-by-index.astro
new file mode 100644
index 0000000..81d2b72
--- /dev/null
+++ b/lab/src/pages/text/style-by-index.astro
@@ -0,0 +1,29 @@
+---
+import { PortableText } from "astro-portabletext";
+import Layout from "../../layouts/Default.astro";
+import TextStylebyIndex from "../../components/TextStylebyIndex.astro";
+
+const blocks = [
+ {
+ _type: "block",
+ children: [
+ {
+ _type: "span",
+ text: "Red",
+ },
+ {
+ _type: "span",
+ text: " Green",
+ },
+ {
+ _type: "span",
+ text: " Blue",
+ },
+ ],
+ },
+];
+---
+
+
+
+
diff --git a/lab/src/pages/text/style-by-split.astro b/lab/src/pages/text/style-by-split.astro
new file mode 100644
index 0000000..a67f664
--- /dev/null
+++ b/lab/src/pages/text/style-by-split.astro
@@ -0,0 +1,21 @@
+---
+import { PortableText } from "astro-portabletext";
+import Layout from "../../layouts/Default.astro";
+import TextStyleBySplit from "../../components/TextStyleBySplit.astro";
+
+const blocks = [
+ {
+ _type: "block",
+ children: [
+ {
+ _type: "span",
+ text: "Yellow Orange",
+ },
+ ],
+ },
+];
+---
+
+
+
+
diff --git a/lab/src/pages/text/undefined.astro b/lab/src/pages/text/undefined.astro
new file mode 100644
index 0000000..c4bec3e
--- /dev/null
+++ b/lab/src/pages/text/undefined.astro
@@ -0,0 +1,20 @@
+---
+import { PortableText } from "astro-portabletext";
+import Layout from "../../layouts/Default.astro";
+
+const blocks = [
+ {
+ _type: "block",
+ children: [
+ {
+ _type: "span",
+ text: "hello world",
+ },
+ ],
+ },
+];
+---
+
+
+
+
diff --git a/lab/src/test/text.test.js b/lab/src/test/text.test.js
new file mode 100644
index 0000000..97700b4
--- /dev/null
+++ b/lab/src/test/text.test.js
@@ -0,0 +1,56 @@
+import { suite } from "uvu";
+import * as assert from "uvu/assert";
+import { fetchContent } from "../utils.mjs";
+
+const text = suite("text");
+
+text("should have `hello world`", async () => {
+ const $ = await fetchContent("text/default");
+ const $el = $("p");
+
+ assert.is($el.length, 1);
+ assert.is($el.text(), "hello world");
+});
+
+text("should have `hello world` with undefined component", async () => {
+ const $ = await fetchContent("text/undefined");
+ const $el = $("p");
+
+ assert.is($el.length, 1);
+ assert.is($el.text(), "hello world");
+});
+
+text("should change joke", async () => {
+ const $ = await fetchContent("text/replace");
+ const $el = $("p");
+
+ assert.is($el.length, 1);
+ assert.is(
+ $el.text(),
+ "Why did the JavaScript developer quit his job? Because he didn't get callbacks."
+ );
+});
+
+text("should style first word by string split", async () => {
+ const $ = await fetchContent("text/style-by-split");
+ const $head = $("head");
+ const $p = $("p");
+
+ assert.is($head.children("style").length, 1);
+ assert.is($p.length, 1);
+ assert.is($p.children("span").length, 1);
+ assert.is($p.children("span").text(), "Yellow");
+});
+
+text("should style first word by index position", async () => {
+ const $ = await fetchContent("text/style-by-index");
+ const $head = $("head");
+ const $p = $("p");
+
+ assert.is($head.children("style").length, 1);
+ assert.is($p.length, 1);
+ assert.is($p.children("span").length, 1);
+ assert.is($p.children("span").text(), "Green");
+});
+
+text.run();