├── .gitignore
├── astro-portabletext
├── .gitignore
├── tsconfig.json
├── lib
│ ├── index.ts
│ ├── utils.ts
│ ├── astro.d.ts
│ ├── components.ts
│ ├── context.ts
│ ├── utils.d.ts
│ ├── warnings.ts
│ └── internal.ts
├── components
│ ├── HardBreak.astro
│ ├── UnknownBlock.astro
│ ├── UnknownList.astro
│ ├── UnknownMark.astro
│ ├── Text.astro
│ ├── UnknownListItem.astro
│ ├── ListItem.astro
│ ├── List.astro
│ ├── UnknownType.astro
│ ├── Block.astro
│ └── Mark.astro
├── LICENSE
├── package.json
└── README.md
├── demo
├── src
│ ├── env.d.ts
│ ├── components
│ │ ├── counter
│ │ │ ├── index.ts
│ │ │ ├── Counter.svelte
│ │ │ ├── Counter.tsx
│ │ │ ├── JsxCounter.astro
│ │ │ ├── SvelteCounter.astro
│ │ │ └── Counter.astro
│ │ ├── portabletext
│ │ │ ├── index.ts
│ │ │ ├── List.astro
│ │ │ ├── ListItem.astro
│ │ │ ├── Type.astro
│ │ │ ├── Mark.astro
│ │ │ └── Block.astro
│ │ ├── Unicorn.astro
│ │ ├── ListStyleStar.astro
│ │ ├── ListItemStyleStar.astro
│ │ └── FancyBorder.astro
│ ├── astro.d.ts
│ ├── layouts
│ │ └── Default.astro
│ ├── pages
│ │ └── index.astro
│ └── data
│ │ └── portabletext.json
├── public
│ └── favicon.ico
├── .stackblitzrc
├── .vscode
│ ├── extensions.json
│ └── launch.json
├── tsconfig.json
├── astro.config.mjs
├── .gitignore
├── .devcontainer
│ └── devcontainer.json
├── package.json
├── README.md
└── CHANGELOG.md
├── lab
├── src
│ ├── env.d.ts
│ ├── components
│ │ ├── Grid.astro
│ │ ├── MyH1.astro
│ │ ├── HelloWorld.astro
│ │ ├── issues
│ │ │ └── BlockStandFirst.astro
│ │ ├── TextReplace.astro
│ │ ├── Mark.astro
│ │ ├── ListItem.astro
│ │ ├── MyBlock.astro
│ │ ├── TextStylebyIndex.astro
│ │ ├── BlockIndex.astro
│ │ ├── TextStyleBySplit.astro
│ │ ├── BlockWithRender.astro
│ │ ├── BlockWithBanner.astro
│ │ ├── MarkWithRender.astro
│ │ └── ListWithRender.astro
│ ├── layouts
│ │ └── Default.astro
│ ├── lib
│ │ ├── internal.test.ts
│ │ ├── warnings.test.ts
│ │ └── utils.test.ts
│ ├── pages
│ │ ├── type
│ │ │ ├── unknown-block.astro
│ │ │ ├── unknown-inline.astro
│ │ │ ├── block.astro
│ │ │ └── inline.astro
│ │ ├── hardbreak.astro
│ │ ├── text
│ │ │ ├── default.astro
│ │ │ ├── undefined.astro
│ │ │ ├── style-by-split.astro
│ │ │ ├── replace.astro
│ │ │ └── style-by-index.astro
│ │ ├── block
│ │ │ ├── missing-style.astro
│ │ │ ├── h1.astro
│ │ │ ├── h2.astro
│ │ │ ├── h3.astro
│ │ │ ├── h4.astro
│ │ │ ├── h5.astro
│ │ │ ├── h6.astro
│ │ │ ├── unknown.astro
│ │ │ ├── normal.astro
│ │ │ ├── with-style.astro
│ │ │ ├── blockquote.astro
│ │ │ ├── custom-handler.astro
│ │ │ ├── default-handler.astro
│ │ │ ├── override.astro
│ │ │ ├── merge.astro
│ │ │ └── block-index.astro
│ │ ├── list
│ │ │ ├── menu.astro
│ │ │ ├── ordered.astro
│ │ │ ├── unknown.astro
│ │ │ ├── unordered.astro
│ │ │ ├── styled.astro
│ │ │ └── nested.astro
│ │ ├── mark
│ │ │ ├── em.astro
│ │ │ ├── strong.astro
│ │ │ ├── code.astro
│ │ │ ├── unknown.astro
│ │ │ ├── underline.astro
│ │ │ ├── strike-through.astro
│ │ │ ├── link-missing-href.astro
│ │ │ └── link.astro
│ │ ├── render
│ │ │ ├── list.astro
│ │ │ ├── mark.astro
│ │ │ └── block.astro
│ │ ├── slot
│ │ │ ├── type.astro
│ │ │ ├── mark.astro
│ │ │ ├── list.astro
│ │ │ ├── mark-custom.astro
│ │ │ ├── block.astro
│ │ │ ├── block-custom.astro
│ │ │ ├── listitem-custom.astro
│ │ │ ├── listitem.astro
│ │ │ └── list-custom.astro
│ │ └── issues
│ │ │ └── issue-175.astro
│ ├── test
│ │ ├── extras.test.js
│ │ ├── issue.test.js
│ │ ├── render.test.js
│ │ ├── type.test.js
│ │ ├── list.test.js
│ │ ├── text.test.js
│ │ ├── slot.test.js
│ │ ├── mark.test.js
│ │ └── block.test.js
│ └── utils.mjs
├── tsconfig.json
├── astro.config.mjs
├── README.md
├── scripts
│ └── ci.sh
└── package.json
├── .release-please-manifest.json
├── .commitlintrc
├── .vscode
├── settings.json
└── extensions.json
├── .huskyrc
├── .husky
├── post-merge
├── pre-commit
└── commit-msg
├── .lintstagedrc
├── pnpm-workspace.yaml
├── .npmrc
├── .prettierignore
├── docs
├── types
│ ├── type-aliases
│ │ ├── NodeType.md
│ │ ├── SomePortableTextComponents.md
│ │ ├── ListProps.md
│ │ ├── BlockProps.md
│ │ ├── ListItemProps.md
│ │ ├── TextNodeProps.md
│ │ ├── ComponentOrRecord.md
│ │ ├── List.md
│ │ ├── Component.md
│ │ ├── TextNode.md
│ │ ├── ListItem.md
│ │ ├── MissingComponentHandler.md
│ │ ├── MarkProps.md
│ │ ├── RenderHandler.md
│ │ ├── RenderOptions.md
│ │ └── RenderHandlerProps.md
│ ├── interfaces
│ │ ├── TypedObject.md
│ │ ├── Props.md
│ │ ├── PortableTextProps.md
│ │ ├── PortableTextComponents.md
│ │ ├── Mark.md
│ │ ├── Context.md
│ │ └── Block.md
│ └── README.md
├── README.md
├── getting-started.md
├── utility-functions.md
└── portabletext-component.md
├── .prettierrc
├── examples
├── portabletext-basic.astro
├── Text.astro
├── portabletext-mapped-type.astro
├── Type.astro
├── List.astro
├── Mark.astro
├── ListItem.astro
├── Block.astro
├── BlockWithRenderFunction.astro
├── portabletext-mapped-type-property.astro
├── portabletext-slots.astro
└── README.md
├── typedoc.json
├── LICENSE
├── .github
└── workflows
│ ├── fix-lockfile.yml
│ ├── release.yml
│ ├── format.yml
│ ├── docs.yml
│ ├── test.yml
│ └── npm.yml
├── .devcontainer
└── devcontainer.json
├── eslint.config.mjs
├── release-please-config.json
├── package.json
├── README.md
└── logo.svg
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | .astro/
4 | *.log
--------------------------------------------------------------------------------
/astro-portabletext/.gitignore:
--------------------------------------------------------------------------------
1 | env.d.ts
2 | generated-docs
--------------------------------------------------------------------------------
/demo/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/lab/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/.release-please-manifest.json:
--------------------------------------------------------------------------------
1 | {"astro-portabletext":"0.12.0"}
2 |
--------------------------------------------------------------------------------
/lab/src/components/Grid.astro:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/lab/src/components/MyH1.astro:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/lab/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "astro/tsconfigs/base"
3 | }
4 |
--------------------------------------------------------------------------------
/.commitlintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["@commitlint/config-conventional"]
3 | }
4 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | { "prettier.prettierPath": "./node_modules/prettier" }
2 |
--------------------------------------------------------------------------------
/.huskyrc:
--------------------------------------------------------------------------------
1 | export NVM_DIR="$HOME/.nvm"
2 | [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
--------------------------------------------------------------------------------
/demo/src/components/counter/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Counter } from "./Counter.astro";
2 |
--------------------------------------------------------------------------------
/.husky/post-merge:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | pnpm install
5 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | pnpm exec lint-staged
5 |
--------------------------------------------------------------------------------
/demo/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/theisel/astro-portabletext/HEAD/demo/public/favicon.ico
--------------------------------------------------------------------------------
/astro-portabletext/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "astro/tsconfigs/strict",
3 | "include": ["lib"]
4 | }
5 |
--------------------------------------------------------------------------------
/lab/astro.config.mjs:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "astro/config";
2 |
3 | export default defineConfig();
4 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | pnpm exec commitlint --edit
5 |
--------------------------------------------------------------------------------
/demo/.stackblitzrc:
--------------------------------------------------------------------------------
1 | {
2 | "installDependencies": false,
3 | "startCommand": "pnpm install && pnpm start"
4 | }
5 |
--------------------------------------------------------------------------------
/lab/README.md:
--------------------------------------------------------------------------------
1 | # `astro-portabletext` laboratory
2 |
3 | This directory handles testing of the Astro component
4 |
--------------------------------------------------------------------------------
/.lintstagedrc:
--------------------------------------------------------------------------------
1 | {
2 | "*.{js,jsx,mjs,ts,tsx}": ["pnpm lint"],
3 | "*.json": "prettier --list-different --write"
4 | }
5 |
--------------------------------------------------------------------------------
/demo/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["astro-build.astro-vscode"],
3 | "unwantedRecommendations": []
4 | }
5 |
--------------------------------------------------------------------------------
/astro-portabletext/lib/index.ts:
--------------------------------------------------------------------------------
1 | export { default as PortableText } from "../components/PortableText.astro";
2 | export * from "./utils";
3 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - astro-portabletext
3 | - demo
4 | - lab
5 |
6 | onlyBuiltDependencies:
7 | - esbuild
8 | - sharp
9 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | # https://pnpm.io/npmrc#save-workspace-protocol
2 | save-workspace-protocol=false
3 | # https://pnpm.io/npmrc#public-hoist-pattern
4 | public-hoist-pattern[]=astro
--------------------------------------------------------------------------------
/demo/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "astro/tsconfigs/base",
3 | "compilerOptions": {
4 | "jsx": "preserve",
5 | "jsxImportSource": "solid-js"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/astro-portabletext/components/HardBreak.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { TextNode, Props as $ } from "../lib/types";
3 |
4 | export type Props = $;
5 | ---
6 |
7 |
8 |
--------------------------------------------------------------------------------
/demo/src/astro.d.ts:
--------------------------------------------------------------------------------
1 | declare module "*.astro" {
2 | type Props = any;
3 | const Component: (props: Props) => any;
4 |
5 | export default Component;
6 | export type { Props };
7 | }
8 |
--------------------------------------------------------------------------------
/lab/src/layouts/Default.astro:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Test
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/lab/src/components/HelloWorld.astro:
--------------------------------------------------------------------------------
1 | ---
2 | const { node, isInline, index, ...attrs } = Astro.props;
3 | ---
4 |
5 | {isInline ? Hello World : Hello World
}
6 |
--------------------------------------------------------------------------------
/astro-portabletext/lib/utils.ts:
--------------------------------------------------------------------------------
1 | export { toPlainText, spanToPlainText } from "@portabletext/toolkit";
2 | export { mergeComponents } from "./internal";
3 | export { usePortableText } from "./context";
4 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "astro-build.astro-vscode",
4 | "editorconfig.editorconfig",
5 | "dbaeumer.vscode-eslint",
6 | "esbenp.prettier-vscode"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/astro-portabletext/components/UnknownBlock.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { Block, Props as $ } from "../lib/types";
3 |
4 | export type Props = $;
5 | ---
6 |
7 |
8 |
--------------------------------------------------------------------------------
/astro-portabletext/components/UnknownList.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { Props as $, List } from "../lib/types";
3 |
4 | export type Props = $;
5 | ---
6 |
7 |
8 |
--------------------------------------------------------------------------------
/astro-portabletext/components/UnknownMark.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { Props as $, Mark } from "../lib/types";
3 |
4 | export type Props = $;
5 | ---
6 |
7 |
8 |
--------------------------------------------------------------------------------
/astro-portabletext/components/Text.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { TextNode, Props as $ } from "../lib/types";
3 |
4 | export type Props = $;
5 |
6 | const { node } = Astro.props;
7 | ---
8 |
9 | {node.text}
10 |
--------------------------------------------------------------------------------
/astro-portabletext/components/UnknownListItem.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { Props as $, ListItem } from "../lib/types";
3 |
4 | export type Props = $;
5 | ---
6 |
7 |
8 |
--------------------------------------------------------------------------------
/lab/scripts/ci.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | pnpm --filter astro-portabletext check || exit $? # Check for `astro-portabletext` component errors
3 | pnpm check || exit $? # Check for Astro errors
4 | pnpm test:lib || exit $?
5 | pnpm test:component || exit $?
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # Directories
2 | dist
3 | .astro/
4 | .changeset
5 | .husky
6 | .github
7 |
8 | # Files
9 | pnpm-lock.yaml
10 |
11 | # Components
12 | astro-portabletext/components/Block.astro
13 | astro-portabletext/components/Mark.astro
14 |
--------------------------------------------------------------------------------
/astro-portabletext/lib/astro.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare module "*.astro" {
4 | type Props = any;
5 | const Component: (props: Props) => any;
6 |
7 | export default Component;
8 | export type { Props };
9 | }
10 |
--------------------------------------------------------------------------------
/astro-portabletext/components/ListItem.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { ListItem, Props as $ } from "../lib/types";
3 |
4 | export type Props = $;
5 |
6 | const { node, index, isInline, ...attrs } = Astro.props;
7 | ---
8 |
9 |
10 |
--------------------------------------------------------------------------------
/demo/astro.config.mjs:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "astro/config";
2 | import solid from "@astrojs/solid-js";
3 | import svelte from "@astrojs/svelte";
4 |
5 | // https://astro.build/config
6 | export default defineConfig({
7 | integrations: [solid(), svelte()],
8 | });
9 |
--------------------------------------------------------------------------------
/demo/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "command": "./node_modules/.bin/astro dev",
6 | "name": "Development server",
7 | "request": "launch",
8 | "type": "node-terminal"
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/astro-portabletext/lib/components.ts:
--------------------------------------------------------------------------------
1 | export { default as Block } from "../components/Block.astro";
2 | export { default as List } from "../components/List.astro";
3 | export { default as ListItem } from "../components/ListItem.astro";
4 | export { default as Mark } from "../components/Mark.astro";
5 |
--------------------------------------------------------------------------------
/lab/src/lib/internal.test.ts:
--------------------------------------------------------------------------------
1 | import { test } from "uvu";
2 | import * as assert from "uvu/assert";
3 | import { throwError } from "../../../astro-portabletext/lib/internal";
4 |
5 | test("throwError", () => {
6 | assert.throws(() => throwError("test"), "test");
7 | });
8 |
9 | test.run();
10 |
--------------------------------------------------------------------------------
/docs/types/type-aliases/NodeType.md:
--------------------------------------------------------------------------------
1 | [**`astro-portabletext` • Type Definitions**](../README.md)
2 |
3 | ***
4 |
5 | # Type Alias: NodeType
6 |
7 | ```ts
8 | type NodeType = "type" | "block" | "list" | "listItem" | "mark";
9 | ```
10 |
11 | **`Internal`**
12 |
13 | Defines the type of Portable Text node
14 |
--------------------------------------------------------------------------------
/demo/.gitignore:
--------------------------------------------------------------------------------
1 | # build output
2 | dist/
3 | .output/
4 |
5 | # dependencies
6 | node_modules/
7 |
8 | # logs
9 | npm-debug.log*
10 | yarn-debug.log*
11 | yarn-error.log*
12 | pnpm-debug.log*
13 |
14 |
15 | # environment variables
16 | .env
17 | .env.production
18 |
19 | # macOS-specific files
20 | .DS_Store
21 |
--------------------------------------------------------------------------------
/lab/src/pages/type/unknown-block.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { PortableText } from "astro-portabletext";
3 | import Layout from "../../layouts/Default.astro";
4 |
5 | const blocks = [
6 | {
7 | _type: "helloWorld",
8 | },
9 | ];
10 | ---
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/lab/src/components/issues/BlockStandFirst.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { BlockProps } from "astro-portabletext/types";
3 |
4 | export type Props = BlockProps;
5 |
6 | const props = Astro.props;
7 | const { node, isInline, index, ...attrs } = props;
8 | ---
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/docs/types/type-aliases/SomePortableTextComponents.md:
--------------------------------------------------------------------------------
1 | [**`astro-portabletext` • Type Definitions**](../README.md)
2 |
3 | ***
4 |
5 | # Type Alias: SomePortableTextComponents
6 |
7 | ```ts
8 | type SomePortableTextComponents = Partial;
9 | ```
10 |
11 | Defines how some Portable Text types should be rendered.
12 |
--------------------------------------------------------------------------------
/demo/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "astro-portabletext demo",
3 | "image": "mcr.microsoft.com/devcontainers/typescript-node:24",
4 | "features": {
5 | "ghcr.io/devcontainers-extra/features/pnpm:2": {
6 | "version": "10.21.0"
7 | }
8 | },
9 | "forwardPorts": [4321],
10 | "postCreateCommand": "pnpm install"
11 | }
12 |
--------------------------------------------------------------------------------
/lab/src/components/TextReplace.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { TextNode, Props as $ } from "astro-portabletext/types";
3 |
4 | export type Props = $;
5 |
6 | const { node } = Astro.props;
7 | const replacedText = node.text
8 | .replace("programmer", "JavaScript developer")
9 | .replace("arrays", "callbacks");
10 | ---
11 |
12 | {replacedText}
13 |
--------------------------------------------------------------------------------
/lab/src/components/Mark.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { Props as $, Mark } from "astro-portabletext/types";
3 | import { usePortableText } from "astro-portabletext";
4 |
5 | type Props = $;
6 |
7 | const { getDefaultComponent } = usePortableText(Astro.props.node);
8 | const Mrk = getDefaultComponent();
9 | ---
10 |
11 |
12 |
--------------------------------------------------------------------------------
/lab/src/components/ListItem.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { Props as $, ListItem } from "astro-portabletext/types";
3 | import { usePortableText } from "astro-portabletext";
4 |
5 | type Props = $;
6 |
7 | const { getDefaultComponent } = usePortableText(Astro.props.node);
8 | const Li = getDefaultComponent();
9 | ---
10 |
11 |
12 |
--------------------------------------------------------------------------------
/demo/src/components/portabletext/index.ts:
--------------------------------------------------------------------------------
1 | import Block from "./Block.astro";
2 | import List from "./List.astro";
3 | import ListItem from "./ListItem.astro";
4 | import Mark from "./Mark.astro";
5 | import Type from "./Type.astro";
6 |
7 | export const components = {
8 | block: Block,
9 | list: List,
10 | listItem: ListItem,
11 | mark: Mark,
12 | type: Type,
13 | };
14 |
--------------------------------------------------------------------------------
/lab/src/test/extras.test.js:
--------------------------------------------------------------------------------
1 | import { suite } from "uvu";
2 | import * as assert from "uvu/assert";
3 | import { fetchContent } from "../utils.mjs";
4 |
5 | const extras = suite("extras");
6 |
7 | extras("hardbreak", async () => {
8 | const $ = await fetchContent("hardbreak");
9 | const $el = $("br");
10 |
11 | assert.is($el.length, 1);
12 | });
13 |
14 | extras.run();
15 |
--------------------------------------------------------------------------------
/lab/src/pages/type/unknown-inline.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { PortableText } from "astro-portabletext";
3 | import Layout from "../../layouts/Default.astro";
4 |
5 | const blocks = [
6 | {
7 | _type: "block",
8 | children: [
9 | {
10 | _type: "helloWorld",
11 | },
12 | ],
13 | },
14 | ];
15 | ---
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/lab/src/pages/hardbreak.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 |
5 | const blocks = [
6 | {
7 | _type: "block",
8 | children: [
9 | {
10 | _type: "span",
11 | text: "\n",
12 | },
13 | ],
14 | },
15 | ];
16 | ---
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/lab/src/components/MyBlock.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { Block, Props as $ } from "astro-portabletext/types";
3 | import { usePortableText } from "astro-portabletext";
4 |
5 | export type Props = $;
6 |
7 | const props = Astro.props;
8 | const { getDefaultComponent } = usePortableText(props.node);
9 | const Default = getDefaultComponent();
10 | ---
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/lab/src/pages/text/default.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { PortableText } from "astro-portabletext";
3 | import Layout from "../../layouts/Default.astro";
4 |
5 | const blocks = [
6 | {
7 | _type: "block",
8 | children: [
9 | {
10 | _type: "span",
11 | text: "hello world",
12 | },
13 | ],
14 | },
15 | ];
16 | ---
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/lab/src/pages/block/missing-style.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 |
5 | const blocks = [
6 | {
7 | _type: "block",
8 | children: [
9 | {
10 | _type: "span",
11 | text: "I'm a paragraph",
12 | },
13 | ],
14 | },
15 | ];
16 | ---
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/lab/src/pages/block/h1.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 |
5 | const blocks = [
6 | {
7 | _type: "block",
8 | style: "h1",
9 | children: [
10 | {
11 | _type: "span",
12 | text: "Heading L1",
13 | },
14 | ],
15 | },
16 | ];
17 | ---
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/lab/src/pages/block/h2.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 |
5 | const blocks = [
6 | {
7 | _type: "block",
8 | style: "h2",
9 | children: [
10 | {
11 | _type: "span",
12 | text: "Heading L2",
13 | },
14 | ],
15 | },
16 | ];
17 | ---
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/lab/src/pages/block/h3.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 |
5 | const blocks = [
6 | {
7 | _type: "block",
8 | style: "h3",
9 | children: [
10 | {
11 | _type: "span",
12 | text: "Heading L3",
13 | },
14 | ],
15 | },
16 | ];
17 | ---
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/lab/src/pages/block/h4.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 |
5 | const blocks = [
6 | {
7 | _type: "block",
8 | style: "h4",
9 | children: [
10 | {
11 | _type: "span",
12 | text: "Heading L4",
13 | },
14 | ],
15 | },
16 | ];
17 | ---
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/lab/src/pages/block/h5.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 |
5 | const blocks = [
6 | {
7 | _type: "block",
8 | style: "h5",
9 | children: [
10 | {
11 | _type: "span",
12 | text: "Heading L5",
13 | },
14 | ],
15 | },
16 | ];
17 | ---
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/lab/src/pages/block/h6.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 |
5 | const blocks = [
6 | {
7 | _type: "block",
8 | style: "h6",
9 | children: [
10 | {
11 | _type: "span",
12 | text: "Heading L6",
13 | },
14 | ],
15 | },
16 | ];
17 | ---
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/lab/src/pages/block/unknown.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 |
5 | const blocks = [
6 | {
7 | _type: "block",
8 | style: "extrabold",
9 | children: [
10 | {
11 | _type: "span",
12 | text: "Waffle",
13 | },
14 | ],
15 | },
16 | ];
17 | ---
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/lab/src/pages/list/menu.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 |
5 | const blocks = [
6 | {
7 | _type: "block",
8 | listItem: "menu",
9 | children: [
10 | {
11 | _type: "span",
12 | text: "Menu Item 1",
13 | },
14 | ],
15 | },
16 | ];
17 | ---
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/lab/src/pages/mark/em.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 |
5 | const blocks = [
6 | {
7 | _type: "block",
8 | children: [
9 | {
10 | _type: "span",
11 | text: "emphasize",
12 | marks: ["em"],
13 | },
14 | ],
15 | },
16 | ];
17 | ---
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/lab/src/pages/mark/strong.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 |
5 | const blocks = [
6 | {
7 | _type: "block",
8 | children: [
9 | {
10 | _type: "span",
11 | text: "bold",
12 | marks: ["strong"],
13 | },
14 | ],
15 | },
16 | ];
17 | ---
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/lab/src/pages/block/normal.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 |
5 | const blocks = [
6 | {
7 | _type: "block",
8 | style: "normal",
9 | children: [
10 | {
11 | _type: "span",
12 | text: "I'm a paragraph",
13 | },
14 | ],
15 | },
16 | ];
17 | ---
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/lab/src/pages/list/ordered.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 |
5 | const blocks = [
6 | {
7 | _type: "block",
8 | listItem: "number",
9 | children: [
10 | {
11 | _type: "span",
12 | text: "List Item 1",
13 | },
14 | ],
15 | },
16 | ];
17 | ---
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/lab/src/pages/list/unknown.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 |
5 | const blocks = [
6 | {
7 | _type: "block",
8 | listItem: "sparkle",
9 | children: [
10 | {
11 | _type: "span",
12 | text: "List Item 1",
13 | },
14 | ],
15 | },
16 | ];
17 | ---
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/lab/src/pages/type/block.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { PortableText } from "astro-portabletext";
3 | import Layout from "../../layouts/Default.astro";
4 | import HelloWorld from "../../components/HelloWorld.astro";
5 |
6 | const blocks = [
7 | {
8 | _type: "helloWorld",
9 | },
10 | ];
11 | ---
12 |
13 |
14 |
20 |
21 |
--------------------------------------------------------------------------------
/lab/src/pages/list/unordered.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 |
5 | const blocks = [
6 | {
7 | _type: "block",
8 | listItem: "bullet",
9 | children: [
10 | {
11 | _type: "span",
12 | text: "List Item 1",
13 | },
14 | ],
15 | },
16 | ];
17 | ---
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/lab/src/pages/mark/code.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 |
5 | const blocks = [
6 | {
7 | _type: "block",
8 | children: [
9 | {
10 | _type: "span",
11 | text: "function test() {}",
12 | marks: ["code"],
13 | },
14 | ],
15 | },
16 | ];
17 | ---
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/lab/src/pages/mark/unknown.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 |
5 | const blocks = [
6 | {
7 | _type: "block",
8 | children: [
9 | {
10 | _type: "span",
11 | text: "highlighted",
12 | marks: ["highlight"],
13 | },
14 | ],
15 | },
16 | ];
17 | ---
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/lab/src/pages/text/undefined.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { PortableText } from "astro-portabletext";
3 | import Layout from "../../layouts/Default.astro";
4 |
5 | const blocks = [
6 | {
7 | _type: "block",
8 | children: [
9 | {
10 | _type: "span",
11 | text: "hello world",
12 | },
13 | ],
14 | },
15 | ];
16 | ---
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/demo/src/components/Unicorn.astro:
--------------------------------------------------------------------------------
1 | ---
2 | export type { Props } from "astro-portabletext/types";
3 |
4 | const props = Astro.props;
5 | ---
6 |
7 | {
8 | props.isInline ? (
9 |
10 |
11 |
12 | ) : (
13 |
14 |
15 |
16 | )
17 | }
18 |
19 |
25 |
--------------------------------------------------------------------------------
/lab/src/components/TextStylebyIndex.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { TextNode, Props as $ } from "astro-portabletext/types";
3 |
4 | export type Props = $;
5 |
6 | const { node, index } = Astro.props;
7 | ---
8 |
9 | {
10 | index === 1 ? (
11 | <>
12 |
13 | {node.text.trim()}
14 | >
15 | ) : (
16 | node.text
17 | )
18 | }
19 |
20 |
25 |
--------------------------------------------------------------------------------
/lab/src/pages/mark/underline.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 |
5 | const blocks = [
6 | {
7 | _type: "block",
8 | children: [
9 | {
10 | _type: "span",
11 | text: "underscore",
12 | marks: ["underline"],
13 | },
14 | ],
15 | },
16 | ];
17 | ---
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/lab/src/pages/mark/strike-through.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 |
5 | const blocks = [
6 | {
7 | _type: "block",
8 | children: [
9 | {
10 | _type: "span",
11 | text: "deleted",
12 | marks: ["strike-through"],
13 | },
14 | ],
15 | },
16 | ];
17 | ---
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/demo/src/components/ListStyleStar.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { Props as $, List } from "astro-portabletext/types";
3 |
4 | export type Props = $;
5 |
6 | const { node, index, isInline, ...attrs } = Astro.props;
7 | ---
8 |
9 |
10 |
11 |
20 |
--------------------------------------------------------------------------------
/docs/types/type-aliases/ListProps.md:
--------------------------------------------------------------------------------
1 | [**`astro-portabletext` • Type Definitions**](../README.md)
2 |
3 | ***
4 |
5 | # Type Alias: ListProps
6 |
7 | ```ts
8 | type ListProps = Props;
9 | ```
10 |
11 | Convenience type for [List](List.md) component props
12 |
13 | ## Remarks
14 |
15 | Added in: `v0.11.0`
16 |
17 | ## Example
18 |
19 | ```ts
20 | ---
21 | import type { ListProps } from "astro-portabletext/types";
22 |
23 | type Props = ListProps;
24 | ---
25 | ```
26 |
--------------------------------------------------------------------------------
/lab/src/components/BlockIndex.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { Block, Props as $ } from "astro-portabletext/types";
3 | import { usePortableText } from "astro-portabletext";
4 |
5 | export type Props = $;
6 |
7 | const props = Astro.props;
8 | const { getDefaultComponent } = usePortableText(props.node);
9 | const Default = getDefaultComponent();
10 | ---
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/lab/src/utils.mjs:
--------------------------------------------------------------------------------
1 | import fs from "node:fs";
2 | import { fileURLToPath } from "node:url";
3 | import { load } from "cheerio";
4 |
5 | /**
6 | * @param {string} path
7 | *
8 | * @returns {Promise}
9 | */
10 | export async function fetchContent(path) {
11 | const url = new URL(`../dist/${path}/index.html`, import.meta.url);
12 | const content = await fs.promises.readFile(fileURLToPath(url), "utf8");
13 |
14 | return load(content);
15 | }
16 |
--------------------------------------------------------------------------------
/lab/src/components/TextStyleBySplit.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { TextNode, Props as $ } from "astro-portabletext/types";
3 |
4 | export type Props = $;
5 |
6 | const { node } = Astro.props;
7 | ---
8 |
9 | {
10 | node.text.split(" ").map((it, idx) =>
11 | idx === 0 ? (
12 | <>
13 | {it}
14 | >
15 | ) : (
16 | it
17 | )
18 | )
19 | }
20 |
21 |
26 |
--------------------------------------------------------------------------------
/docs/types/type-aliases/BlockProps.md:
--------------------------------------------------------------------------------
1 | [**`astro-portabletext` • Type Definitions**](../README.md)
2 |
3 | ***
4 |
5 | # Type Alias: BlockProps
6 |
7 | ```ts
8 | type BlockProps = Props;
9 | ```
10 |
11 | Convenience type for [Block](../interfaces/Block.md) component props
12 |
13 | ## Remarks
14 |
15 | Added in: `v0.11.0`
16 |
17 | ## Example
18 |
19 | ```ts
20 | ---
21 | import type { BlockProps } from "astro-portabletext/types";
22 |
23 | type Props = BlockProps;
24 | ---
25 | ```
26 |
--------------------------------------------------------------------------------
/docs/types/type-aliases/ListItemProps.md:
--------------------------------------------------------------------------------
1 | [**`astro-portabletext` • Type Definitions**](../README.md)
2 |
3 | ***
4 |
5 | # Type Alias: ListItemProps
6 |
7 | ```ts
8 | type ListItemProps = Props;
9 | ```
10 |
11 | Convenience type for [ListItem](ListItem.md) component props
12 |
13 | ## Remarks
14 |
15 | Added in: `v0.11.0`
16 |
17 | ## Example
18 |
19 | ```ts
20 | ---
21 | import type { ListItemProps } from "astro-portabletext/types";
22 |
23 | type Props = ListItemProps;
24 | ---
25 | ```
26 |
--------------------------------------------------------------------------------
/docs/types/type-aliases/TextNodeProps.md:
--------------------------------------------------------------------------------
1 | [**`astro-portabletext` • Type Definitions**](../README.md)
2 |
3 | ***
4 |
5 | # Type Alias: TextNodeProps
6 |
7 | ```ts
8 | type TextNodeProps = Props;
9 | ```
10 |
11 | Convenience type for [TextNode](TextNode.md) component props
12 |
13 | ## Remarks
14 |
15 | Added in: `v0.11.0`
16 |
17 | ## Example
18 |
19 | ```ts
20 | ---
21 | import type { TextNodeProps } from "astro-portabletext/types";
22 |
23 | type Props = TextNodeProps;
24 | ---
25 | ```
26 |
--------------------------------------------------------------------------------
/demo/src/components/ListItemStyleStar.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { Props as $, ListItem } from "astro-portabletext/types";
3 |
4 | export type Props = $;
5 |
6 | const { node, index, isInline, ...attrs } = Astro.props;
7 | ---
8 |
9 |
10 |
11 |
23 |
--------------------------------------------------------------------------------
/demo/src/components/portabletext/List.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { Props as $, List } from "astro-portabletext/types";
3 | import { List as DefaultList } from "astro-portabletext/components";
4 | import ListStyleStar from "../ListStyleStar.astro";
5 |
6 | export type Props = $;
7 |
8 | const props = Astro.props;
9 | const listItemIs = (listItem: string) => listItem === props.node.listItem;
10 |
11 | const Cmp = listItemIs("star") ? ListStyleStar : DefaultList;
12 | ---
13 |
14 |
15 |
--------------------------------------------------------------------------------
/lab/src/components/BlockWithRender.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { Block, Props as $ } from "astro-portabletext/types";
3 | import { usePortableText } from "astro-portabletext";
4 |
5 | export type Props = $;
6 |
7 | const props = Astro.props;
8 | const { node } = props;
9 |
10 | const { render } = usePortableText(node);
11 | ---
12 |
13 |
14 | {
15 | render({
16 | text: ({ props }) => {props.node.text} 🚀 ,
17 | })
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/docs/types/type-aliases/ComponentOrRecord.md:
--------------------------------------------------------------------------------
1 | [**`astro-portabletext` • Type Definitions**](../README.md)
2 |
3 | ***
4 |
5 | # Type Alias: ComponentOrRecord\
6 |
7 | ```ts
8 | type ComponentOrRecord =
9 | | Component
10 | | Record>;
11 | ```
12 |
13 | **`Internal`**
14 |
15 | Defines a component or a mapping of components
16 |
17 | ## Type Parameters
18 |
19 | | Type Parameter | Default type |
20 | | ------ | ------ |
21 | | `T` *extends* [`TypedObject`](../interfaces/TypedObject.md) | `any` |
22 |
--------------------------------------------------------------------------------
/lab/src/pages/block/with-style.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 |
5 | const blocks = [
6 | {
7 | _type: "block",
8 | style: "normal",
9 | children: [
10 | {
11 | _type: "span",
12 | text: "I'm a paragraph",
13 | },
14 | ],
15 | },
16 | ];
17 | ---
18 |
19 |
20 |
21 |
22 |
23 |
28 |
--------------------------------------------------------------------------------
/lab/src/pages/type/inline.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { PortableText } from "astro-portabletext";
3 | import Layout from "../../layouts/Default.astro";
4 | import HelloWorld from "../../components/HelloWorld.astro";
5 |
6 | const blocks = [
7 | {
8 | _type: "block",
9 | children: [
10 | {
11 | _type: "helloWorld",
12 | },
13 | ],
14 | },
15 | ];
16 | ---
17 |
18 |
19 |
25 |
26 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "tabWidth": 2,
4 | "semi": true,
5 | "singleQuote": false,
6 | "trailingComma": "es5",
7 | "arrowParens": "always",
8 | "plugins": [
9 | "./node_modules/prettier-plugin-astro/dist/index.js",
10 | "./node_modules/prettier-plugin-svelte/plugin.js"
11 | ],
12 | "overrides": [
13 | {
14 | "files": "*.astro",
15 | "options": { "parser": "astro" }
16 | },
17 | {
18 | "files": "*.svelte",
19 | "options": { "parser": "svelte" }
20 | }
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/lab/src/pages/text/style-by-split.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { PortableText } from "astro-portabletext";
3 | import Layout from "../../layouts/Default.astro";
4 | import TextStyleBySplit from "../../components/TextStyleBySplit.astro";
5 |
6 | const blocks = [
7 | {
8 | _type: "block",
9 | children: [
10 | {
11 | _type: "span",
12 | text: "Yellow Orange",
13 | },
14 | ],
15 | },
16 | ];
17 | ---
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/lab/src/test/issue.test.js:
--------------------------------------------------------------------------------
1 | import { suite } from "uvu";
2 | import * as assert from "uvu/assert";
3 | import { fetchContent } from "../utils.mjs";
4 |
5 | const issue = suite("issue");
6 |
7 | issue("issue-175", async () => {
8 | const $ = await fetchContent("issues/issue-175");
9 |
10 | const $ul = $("ul");
11 | const $li = $ul.find("li");
12 | const $el = $li.find("div[data-block='standfirst']");
13 |
14 | assert.is($ul.length, 1);
15 | assert.is($li.length, 1);
16 | assert.is($el.length, 1);
17 | });
18 |
19 | issue.run();
20 |
--------------------------------------------------------------------------------
/demo/src/components/portabletext/ListItem.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { Props as $, ListItem } from "astro-portabletext/types";
3 | import { ListItem as DefaultListItem } from "astro-portabletext/components";
4 | import ListItemStyleStar from "../ListItemStyleStar.astro";
5 |
6 | export type Props = $;
7 |
8 | const props = Astro.props;
9 | const listItemIs = (listItem: string) => listItem === props.node.listItem;
10 |
11 | const Cmp = listItemIs("star") ? ListItemStyleStar : DefaultListItem;
12 | ---
13 |
14 |
15 |
--------------------------------------------------------------------------------
/lab/src/pages/render/list.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 | import ListWithRender from "../../components/ListWithRender.astro";
5 |
6 | const blocks = [
7 | {
8 | _type: "block",
9 | listItem: "bullet",
10 | children: [
11 | {
12 | _type: "span",
13 | text: "List Item 1",
14 | },
15 | ],
16 | },
17 | ];
18 | ---
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/lab/src/pages/render/mark.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 | import MarkWithRender from "../../components/MarkWithRender.astro";
5 |
6 | const blocks = [
7 | {
8 | _type: "block",
9 | children: [
10 | {
11 | _type: "span",
12 | text: "emphasize",
13 | marks: ["em"],
14 | },
15 | ],
16 | },
17 | ];
18 | ---
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/lab/src/pages/block/blockquote.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 |
5 | const blocks = [
6 | {
7 | _type: "block",
8 | style: "blockquote",
9 | children: [
10 | {
11 | _type: "block",
12 | children: [
13 | {
14 | _type: "span",
15 | text: "Quote",
16 | },
17 | ],
18 | },
19 | ],
20 | },
21 | ];
22 | ---
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/lab/src/pages/render/block.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 | import BlockWithRender from "../../components/BlockWithRender.astro";
5 |
6 | const blocks = [
7 | {
8 | _type: "block",
9 | style: "normal",
10 | children: [
11 | {
12 | _type: "span",
13 | text: "Rocket launch",
14 | },
15 | ],
16 | },
17 | ];
18 | ---
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/astro-portabletext/components/List.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { List, Props as $ } from "../lib/types";
3 |
4 | export type Props = $;
5 |
6 | const { node, index, isInline, ...attrs } = Astro.props;
7 | const listItemIs = (listItem: string) => listItem === node.listItem;
8 | ---
9 |
10 | {
11 | listItemIs("menu") ? (
12 |
13 |
14 |
15 | ) : listItemIs("number") ? (
16 |
17 |
18 |
19 | ) : (
20 |
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/lab/src/pages/mark/link-missing-href.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 |
5 | const blocks = [
6 | {
7 | _type: "block",
8 | children: [
9 | {
10 | _type: "span",
11 | text: "test.com",
12 | marks: ["link-key"],
13 | },
14 | ],
15 | markDefs: [
16 | {
17 | _key: "link-key",
18 | _type: "link",
19 | },
20 | ],
21 | },
22 | ];
23 | ---
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/lab/src/pages/text/replace.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { PortableText } from "astro-portabletext";
3 | import Layout from "../../layouts/Default.astro";
4 | import TextReplace from "../../components/TextReplace.astro";
5 |
6 | const blocks = [
7 | {
8 | _type: "block",
9 | children: [
10 | {
11 | _type: "span",
12 | text: "Why did the programmer quit his job? Because he didn't get arrays.",
13 | },
14 | ],
15 | },
16 | ];
17 | ---
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/lab/src/pages/block/custom-handler.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { PortableText } from "astro-portabletext";
3 | import Layout from "../../layouts/Default.astro";
4 | import MyBlock from "../../components/MyBlock.astro";
5 |
6 | const blocks = [
7 | {
8 | _type: "block",
9 | style: "bordered",
10 | children: [
11 | {
12 | _type: "span",
13 | text: "Heading L1",
14 | },
15 | ],
16 | },
17 | ];
18 | ---
19 |
20 |
21 |
27 |
28 |
--------------------------------------------------------------------------------
/lab/src/pages/block/default-handler.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 | import { Block } from "astro-portabletext/components";
5 |
6 | const blocks = [
7 | {
8 | _type: "block",
9 | style: "bordered",
10 | children: [
11 | {
12 | _type: "span",
13 | text: "Heading L1",
14 | },
15 | ],
16 | },
17 | ];
18 | ---
19 |
20 |
21 |
27 |
28 |
--------------------------------------------------------------------------------
/examples/portabletext-basic.astro:
--------------------------------------------------------------------------------
1 | ---
2 | /**
3 | * This example demonstrates the most basic usage of the `PortableText` component.
4 | * You will need to provide custom components when dealing with custom types or
5 | * marks for example.
6 | *
7 | * Refer to the `portabletext-mapped-components.astro` and `portabletext-mapped-types.astro`
8 | * examples for more advanced usage.
9 | */
10 | // @ts-nocheck
11 | import { PortableText } from "astro-portabletext";
12 |
13 | const portableText = [
14 | /* Portable Text payload */
15 | ];
16 | ---
17 |
18 |
19 |
--------------------------------------------------------------------------------
/lab/src/pages/block/override.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 | import MyH1 from "../../components/MyH1.astro";
5 |
6 | const blocks = [
7 | {
8 | _type: "block",
9 | style: "h1",
10 | children: [
11 | {
12 | _type: "span",
13 | text: "Heading L1",
14 | },
15 | ],
16 | },
17 | ];
18 | ---
19 |
20 |
21 |
29 |
30 |
--------------------------------------------------------------------------------
/astro-portabletext/components/UnknownType.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { TypedObject, Props as $ } from "../lib/types";
3 | import { getWarningMessage } from "../lib/warnings";
4 |
5 | export type Props = $;
6 |
7 | const { node, isInline } = Astro.props;
8 | const warning = getWarningMessage("type", node._type);
9 | ---
10 |
11 | {
12 | isInline ? (
13 |
14 | {warning}
15 |
16 | ) : (
17 |
18 | {warning}
19 |
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/demo/src/components/portabletext/Type.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { Props as $, TypedObject } from "astro-portabletext/types";
3 | import { usePortableText } from "astro-portabletext/utils";
4 | import { Counter } from "../counter";
5 |
6 | export type Props = $;
7 |
8 | const props = Astro.props;
9 | const typeIs = (type: string) => type === props.node._type;
10 | const { getDefaultComponent } = usePortableText(props.node);
11 |
12 | const Cmp =
13 | typeIs("jsxCounter") || typeIs("svelteCounter")
14 | ? Counter
15 | : getDefaultComponent();
16 | ---
17 |
18 |
19 |
--------------------------------------------------------------------------------
/lab/src/pages/list/styled.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { PortableText } from "astro-portabletext";
3 | import Layout from "../../layouts/Default.astro";
4 | import BlockWithBanner from "../../components/BlockWithBanner.astro";
5 |
6 | const blocks = [
7 | {
8 | _type: "block",
9 | listItem: "bullet",
10 | level: 1,
11 | style: "banner",
12 | children: [
13 | {
14 | _type: "span",
15 | text: "List Item 1",
16 | },
17 | ],
18 | },
19 | ];
20 | ---
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/lab/src/pages/mark/link.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 |
5 | const blocks = [
6 | {
7 | _type: "block",
8 | children: [
9 | {
10 | _type: "span",
11 | text: "test.com",
12 | marks: ["link-key"],
13 | },
14 | ],
15 | markDefs: [
16 | {
17 | _key: "link-key",
18 | _type: "link",
19 | href: "https://test.com/",
20 | },
21 | ],
22 | },
23 | ];
24 | ---
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/demo/src/components/counter/Counter.svelte:
--------------------------------------------------------------------------------
1 |
17 |
18 | {#if isInline}
19 | {count}
20 | {:else}
21 | {count}
22 | {/if}
23 |
--------------------------------------------------------------------------------
/docs/types/type-aliases/List.md:
--------------------------------------------------------------------------------
1 | [**`astro-portabletext` • Type Definitions**](../README.md)
2 |
3 | ***
4 |
5 | # Type Alias: List
6 |
7 | ```ts
8 | type List = ToolkitPortableTextList;
9 | ```
10 |
11 | Alias to [ToolkitPortableTextList](https://portabletext.github.io/toolkit/types/ToolkitPortableTextList.html)
12 |
13 | ## Example
14 |
15 | ```ts
16 | ---
17 | import type { List, Props as $ } from "astro-portabletext/types";
18 |
19 | type Props = $;
20 | ---
21 | ```
22 |
23 | ## Remarks
24 |
25 | To concisely achieve the same result in the example, use the convenience type [ListProps](ListProps.md) instead.
26 |
--------------------------------------------------------------------------------
/docs/types/type-aliases/Component.md:
--------------------------------------------------------------------------------
1 | [**`astro-portabletext` • Type Definitions**](../README.md)
2 |
3 | ***
4 |
5 | # Type Alias: Component()\
6 |
7 | ```ts
8 | type Component = (props) => any;
9 | ```
10 |
11 | **`Internal`**
12 |
13 | Generic Portable Text component
14 |
15 | ## Type Parameters
16 |
17 | | Type Parameter | Default type |
18 | | ------ | ------ |
19 | | `T` *extends* [`TypedObject`](../interfaces/TypedObject.md) | `any` |
20 |
21 | ## Parameters
22 |
23 | | Parameter | Type |
24 | | ------ | ------ |
25 | | `props` | [`Props`](../interfaces/Props.md)\<`T`\> |
26 |
27 | ## Returns
28 |
29 | `any`
30 |
--------------------------------------------------------------------------------
/docs/types/type-aliases/TextNode.md:
--------------------------------------------------------------------------------
1 | [**`astro-portabletext` • Type Definitions**](../README.md)
2 |
3 | ***
4 |
5 | # Type Alias: TextNode
6 |
7 | ```ts
8 | type TextNode = ToolkitTextNode;
9 | ```
10 |
11 | Alias to [ToolkitTextNode](https://portabletext.github.io/toolkit/interfaces/ToolkitTextNode.html)
12 |
13 | ## Example
14 |
15 | ```ts
16 | ---
17 | import type { TextNode, Props as $ } from "astro-portabletext/types";
18 |
19 | type Props = $;
20 | ---
21 | ```
22 |
23 | ## Remarks
24 |
25 | To concisely achieve the same result in the example, use the convenience type [TextNodeProps](TextNodeProps.md) instead.
26 |
--------------------------------------------------------------------------------
/docs/types/type-aliases/ListItem.md:
--------------------------------------------------------------------------------
1 | [**`astro-portabletext` • Type Definitions**](../README.md)
2 |
3 | ***
4 |
5 | # Type Alias: ListItem
6 |
7 | ```ts
8 | type ListItem = ToolkitPortableTextListItem;
9 | ```
10 |
11 | Alias to [ToolkitPortableTextListItem](https://portabletext.github.io/toolkit/interfaces/ToolkitPortableTextListItem.html)
12 |
13 | ## Example
14 |
15 | ```ts
16 | ---
17 | import type { ListItem, Props as $ } from "astro-portabletext/types";
18 |
19 | type Props = $;
20 | ---
21 | ```
22 |
23 | ## Remarks
24 |
25 | To concisely achieve the same result in the example, use the convenience type [ListItemProps](ListItemProps.md) instead.
26 |
--------------------------------------------------------------------------------
/lab/src/pages/block/merge.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 | import Grid from "../../components/Grid.astro";
5 |
6 | const blocks = [
7 | {
8 | _type: "block",
9 | style: "grid",
10 | children: [
11 | {
12 | _type: "span",
13 | text: "1",
14 | },
15 | {
16 | _type: "span",
17 | text: "2",
18 | },
19 | ],
20 | },
21 | ];
22 | ---
23 |
24 |
25 |
33 |
34 |
--------------------------------------------------------------------------------
/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demo",
3 | "version": "0.2.0",
4 | "type": "module",
5 | "private": true,
6 | "author": "Tom Theisel ",
7 | "scripts": {
8 | "dev": "astro dev",
9 | "start": "pnpm up astro-portabletext@latest && pnpm dev",
10 | "build": "astro build",
11 | "preview": "astro preview"
12 | },
13 | "dependencies": {
14 | "@astrojs/solid-js": "^5.1.3",
15 | "@astrojs/svelte": "^7.2.2",
16 | "astro": "^5.15.5",
17 | "astro-portabletext": "^0.12.0",
18 | "solid-js": "^1.9.10",
19 | "svelte": "^5.43.5"
20 | },
21 | "engines": {
22 | "node": ">=20.19 <22 || >=22.12"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/docs/types/type-aliases/MissingComponentHandler.md:
--------------------------------------------------------------------------------
1 | [**`astro-portabletext` • Type Definitions**](../README.md)
2 |
3 | ***
4 |
5 | # Type Alias: MissingComponentHandler()
6 |
7 | ```ts
8 | type MissingComponentHandler = (message, context) => void;
9 | ```
10 |
11 | The shape of the [onMissingComponent](../interfaces/PortableTextProps.md#onmissingcomponent) function
12 |
13 | ## Parameters
14 |
15 | | Parameter | Type |
16 | | ------ | ------ |
17 | | `message` | `string` |
18 | | `context` | \{ `type`: `string`; `nodeType`: [`NodeType`](NodeType.md); \} |
19 | | `context.type` | `string` |
20 | | `context.nodeType` | [`NodeType`](NodeType.md) |
21 |
22 | ## Returns
23 |
24 | `void`
25 |
--------------------------------------------------------------------------------
/demo/src/components/portabletext/Mark.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { Props as $, Mark } from "astro-portabletext/types";
3 | import { Mark as DefaultMark } from "astro-portabletext/components";
4 | import Unicorn from "../Unicorn.astro";
5 |
6 | export type Props = $;
7 |
8 | const props = Astro.props;
9 | const markTypeIs = (markType: string) => markType === props.node.markType;
10 |
11 | const Cmp = markTypeIs("unicorn") ? Unicorn : DefaultMark;
12 | ---
13 |
14 |
15 |
16 |
25 |
--------------------------------------------------------------------------------
/lab/src/components/BlockWithBanner.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { Block, Props as $ } from "astro-portabletext/types";
3 | import { usePortableText } from "astro-portabletext";
4 |
5 | export type Props = $;
6 |
7 | const props = Astro.props;
8 | const { node, index, isInline, ...attrs } = props;
9 | const styleIs = (style: string) => style === node.style;
10 |
11 | const { getDefaultComponent } = usePortableText(node);
12 |
13 | const Default = getDefaultComponent();
14 | ---
15 |
16 | {
17 | styleIs("banner") ? (
18 |
19 |
20 |
21 | ) : (
22 |
23 |
24 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/lab/src/pages/text/style-by-index.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { PortableText } from "astro-portabletext";
3 | import Layout from "../../layouts/Default.astro";
4 | import TextStylebyIndex from "../../components/TextStylebyIndex.astro";
5 |
6 | const blocks = [
7 | {
8 | _type: "block",
9 | children: [
10 | {
11 | _type: "span",
12 | text: "Red",
13 | },
14 | {
15 | _type: "span",
16 | text: " Green",
17 | },
18 | {
19 | _type: "span",
20 | text: " Blue",
21 | },
22 | ],
23 | },
24 | ];
25 | ---
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/typedoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://typedoc.org/schema.json",
3 | "entryPoints": ["astro-portabletext/lib/types.ts"],
4 | "plugin": ["typedoc-plugin-markdown"],
5 | "out": "docs/types",
6 | "readme": "none",
7 | "githubPages": false,
8 | "disableSources": true,
9 | "sort": ["source-order", "required-first"],
10 | "preserveAnchorCasing": true,
11 | "hideBreadcrumbs": true,
12 | "useCodeBlocks": true,
13 | "parametersFormat": "table",
14 | "typeDeclarationFormat": "table",
15 | "indexFormat": "table",
16 | "pageTitleTemplates": {
17 | "index": "Type Definitions"
18 | },
19 | "textContentMappings": {
20 | "header.title": "`{projectName}` • Type Definitions"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/demo/src/components/counter/Counter.tsx:
--------------------------------------------------------------------------------
1 | import type { Props as $, TypedObject } from "astro-portabletext/types";
2 | import { createSignal, onCleanup } from "solid-js";
3 |
4 | export function Counter(props: $) {
5 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
6 | const { node, index, isInline, ...attrs } = props;
7 | const [count, setCount] = createSignal(0);
8 | const timer = setInterval(() => setCount(count() + 1), 1000);
9 |
10 | onCleanup(() => clearInterval(timer));
11 |
12 | const Block = () => {count()}
;
13 | const Inline = () => {count()} ;
14 | const Cmp = isInline ? Inline : Block;
15 |
16 | return ;
17 | }
18 |
--------------------------------------------------------------------------------
/docs/types/interfaces/TypedObject.md:
--------------------------------------------------------------------------------
1 | [**`astro-portabletext` • Type Definitions**](../README.md)
2 |
3 | ***
4 |
5 | # Interface: TypedObject
6 |
7 | Any object with an `_type` property (which is required in portable text arrays),
8 | as well as a _potential_ `_key` (highly encouraged)
9 |
10 | ## Properties
11 |
12 | ### \_type
13 |
14 | ```ts
15 | _type: string;
16 | ```
17 |
18 | Identifies the type of object/span this is, and is used to pick the correct React components
19 | to use when rendering a span or inline object with this type.
20 |
21 | ***
22 |
23 | ### \_key?
24 |
25 | ```ts
26 | optional _key: string;
27 | ```
28 |
29 | Uniquely identifies this object within its parent block.
30 | Not _required_, but highly encouraged.
31 |
--------------------------------------------------------------------------------
/docs/types/type-aliases/MarkProps.md:
--------------------------------------------------------------------------------
1 | [**`astro-portabletext` • Type Definitions**](../README.md)
2 |
3 | ***
4 |
5 | # Type Alias: MarkProps\
6 |
7 | ```ts
8 | type MarkProps = Props>;
9 | ```
10 |
11 | Convenience type for [Mark](../interfaces/Mark.md) component props
12 |
13 | ## Type Parameters
14 |
15 | | Type Parameter | Default type |
16 | | ------ | ------ |
17 | | `MarkDef` *extends* `Record`\<`string`, `unknown`\> \| `undefined` | `undefined` |
18 |
19 | ## Remarks
20 |
21 | Added in: `v0.11.0`
22 |
23 | ## Example
24 |
25 | ```ts
26 | ---
27 | import type { MarkProps } from "astro-portabletext/types";
28 |
29 | type Greet = { msg: string };
30 | type Props = MarkProps;
31 | ---
32 | ```
33 |
--------------------------------------------------------------------------------
/lab/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lab",
3 | "description": "Laboratory for testing `astro-portabletext`",
4 | "version": "0.0.0",
5 | "type": "module",
6 | "private": true,
7 | "author": "Tom Theisel ",
8 | "license": "ISC",
9 | "scripts": {
10 | "build:fixture": "astro build --silent",
11 | "check": "astro check",
12 | "test:component": "pnpm build:fixture && pnpm exec uvu src/test .test.js",
13 | "test:lib": "pnpm exec uvu -r tsm src/lib ",
14 | "test:ci": "./scripts/ci.sh",
15 | "test": "pnpm test:ci"
16 | },
17 | "dependencies": {
18 | "astro-portabletext": "workspace:*"
19 | },
20 | "devDependencies": {
21 | "cheerio": "1.0.0",
22 | "tsm": "^2.3.0",
23 | "uvu": "^0.5.6"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/lab/src/components/MarkWithRender.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { Mark, Props as $ } from "astro-portabletext/types";
3 | import { usePortableText } from "astro-portabletext";
4 |
5 | export type Props = $;
6 |
7 | const { node, index, isInline, ...attrs } = Astro.props;
8 | const markTypeIs = (markType: string) => markType === node.markType;
9 |
10 | const { getDefaultComponent, render } = usePortableText(node);
11 |
12 | const Default = getDefaultComponent();
13 | ---
14 |
15 | {
16 | markTypeIs("em") ? (
17 |
18 | {render({
19 | text: ({ props }) => {props.node.text} ,
20 | })}
21 |
22 | ) : (
23 |
24 |
25 |
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/lab/src/components/ListWithRender.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { List, Props as $ } from "astro-portabletext/types";
3 | import { usePortableText } from "astro-portabletext";
4 |
5 | export type Props = $;
6 |
7 | const { node, index, isInline, ...attrs } = Astro.props;
8 | const listItemIs = (listItem: string) => listItem === node.listItem;
9 | const { render, getDefaultComponent } = usePortableText(node);
10 |
11 | const Default = getDefaultComponent();
12 | ---
13 |
14 | {
15 | listItemIs("bullet") ? (
16 |
17 | {render({
18 | text: ({ props }) => {props.node.text} ,
19 | })}
20 |
21 | ) : (
22 |
23 |
24 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/examples/Text.astro:
--------------------------------------------------------------------------------
1 | ---
2 | /**
3 | * Works with `v0.11.0+`
4 | *
5 | * This `text` component handles the output of `text` nodes in Portable Text.
6 | * It demonstrates how to modify the text content of all text nodes.
7 | *
8 | * Note:
9 | * This example affects ALL text nodes. To target specific text nodes,
10 | * use the `render` function from `usePortableText`.
11 | * (See examples/render-function.astro)
12 | *
13 | * Usage:
14 | *
15 | */
16 | // @ts-nocheck
17 | import type { TextNodeProps } from "astro-portabletext/types";
18 |
19 | export type Props = TextNodeProps;
20 |
21 | // Replace "rocket" with an emoji in all text nodes
22 | ---
23 |
24 | {Astro.props.node.text.replace("rocket", "🚀")}
25 |
--------------------------------------------------------------------------------
/demo/src/components/counter/JsxCounter.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { Props as $, TypedObject } from "astro-portabletext/types";
3 | import { Counter } from "./Counter";
4 |
5 | export type Props = $;
6 |
7 | const props = Astro.props;
8 | const { node, index, isInline, ...attrs } = props;
9 | ---
10 |
11 | {
12 | isInline ? (
13 |
14 | JSX Inline Counter
15 | {` `}
16 |
17 |
18 | ) : (
19 |
20 |
JSX Block Counter
21 |
22 |
23 | )
24 | }
25 |
26 |
32 |
--------------------------------------------------------------------------------
/docs/types/type-aliases/RenderHandler.md:
--------------------------------------------------------------------------------
1 | [**`astro-portabletext` • Type Definitions**](../README.md)
2 |
3 | ***
4 |
5 | # Type Alias: RenderHandler()\
6 |
7 | ```ts
8 | type RenderHandler = (props) => any;
9 | ```
10 |
11 | The shape of the render component function
12 |
13 | ## Type Parameters
14 |
15 | | Type Parameter | Default type | Description |
16 | | ------ | ------ | ------ |
17 | | `T` *extends* [`TypedObject`](../interfaces/TypedObject.md) | [`TypedObject`](../interfaces/TypedObject.md) | Type of Portable Text payload |
18 | | `Children` | `unknown` | Type of children |
19 |
20 | ## Parameters
21 |
22 | | Parameter | Type |
23 | | ------ | ------ |
24 | | `props` | [`RenderHandlerProps`](RenderHandlerProps.md)\<`T`, `Children`\> |
25 |
26 | ## Returns
27 |
28 | `any`
29 |
--------------------------------------------------------------------------------
/demo/src/components/counter/SvelteCounter.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { Props as $, TypedObject } from "astro-portabletext/types";
3 | import Counter from "./Counter.svelte";
4 |
5 | export type Props = $;
6 |
7 | const props = Astro.props;
8 | const { node, index, isInline, ...attrs } = props;
9 | ---
10 |
11 | {
12 | isInline ? (
13 |
14 | Svelte Inline Counter
15 | {` `}
16 |
17 |
18 | ) : (
19 |
20 |
Svelte Block Counter
21 |
22 |
23 | )
24 | }
25 |
26 |
32 |
--------------------------------------------------------------------------------
/demo/src/components/portabletext/Block.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { Props as $, Block } from "astro-portabletext/types";
3 | import { Block as DefaultBlock } from "astro-portabletext/components";
4 | import FancyBorder from "../FancyBorder.astro";
5 |
6 | export type Props = $;
7 |
8 | const props = Astro.props;
9 | const styleIs = (style: string) => style === props.node.style;
10 | const Cmp = styleIs("fancyBorder") ? FancyBorder : DefaultBlock;
11 | ---
12 |
13 |
14 |
15 |
32 |
--------------------------------------------------------------------------------
/docs/types/interfaces/Props.md:
--------------------------------------------------------------------------------
1 | [**`astro-portabletext` • Type Definitions**](../README.md)
2 |
3 | ***
4 |
5 | # Interface: Props\
6 |
7 | Component Props
8 |
9 | ## Type Parameters
10 |
11 | | Type Parameter | Description |
12 | | ------ | ------ |
13 | | `N` *extends* [`TypedObject`](TypedObject.md) | Type of Portable Text payload that this component will receive on its `node` property |
14 |
15 | ## Properties
16 |
17 | ### node
18 |
19 | ```ts
20 | node: N;
21 | ```
22 |
23 | Portable Text data for this node
24 |
25 | ***
26 |
27 | ### index
28 |
29 | ```ts
30 | index: number;
31 | ```
32 |
33 | Index of the current node within its parent's child list
34 |
35 | ***
36 |
37 | ### isInline
38 |
39 | ```ts
40 | isInline: boolean;
41 | ```
42 |
43 | Indicates whether the node should render as an inline or block element
44 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | ISC License (ISC)
2 |
3 | Copyright 2022 - CURRENT Tom Theisel
4 |
5 | Permission to use, copy, modify, and/or distribute this software for any
6 | purpose with or without fee is hereby granted, provided that the above
7 | copyright notice and this permission notice appear in all copies.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
11 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
13 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
14 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
15 | THIS SOFTWARE.
16 |
--------------------------------------------------------------------------------
/astro-portabletext/LICENSE:
--------------------------------------------------------------------------------
1 | ISC License (ISC)
2 |
3 | Copyright 2022 - CURRENT Tom Theisel
4 |
5 | Permission to use, copy, modify, and/or distribute this software for any
6 | purpose with or without fee is hereby granted, provided that the above
7 | copyright notice and this permission notice appear in all copies.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
11 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
13 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
14 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
15 | THIS SOFTWARE.
16 |
--------------------------------------------------------------------------------
/lab/src/pages/slot/type.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import assert from "node:assert";
3 | import { PortableText } from "astro-portabletext";
4 | import UnknownType from "../../../../astro-portabletext/components/UnknownType.astro";
5 | import Layout from "../../layouts/Default.astro";
6 |
7 | const blocks = [
8 | {
9 | _type: "helloWorld",
10 | },
11 | ];
12 | ---
13 |
14 |
15 |
16 | {
18 | ({ Component, props }) => {
19 | assert(Component === UnknownType, "Component is not UnknownType");
20 |
21 | switch (props.node._type) {
22 | case "helloWorld":
23 | return Hello World
;
24 | }
25 | return ;
26 | }
27 | }
29 |
30 |
31 |
--------------------------------------------------------------------------------
/lab/src/pages/issues/issue-175.astro:
--------------------------------------------------------------------------------
1 | ---
2 | // Refer to https://github.com/theisel/astro-portabletext/issues/175
3 |
4 | import { PortableText } from "astro-portabletext";
5 | import BlockStandFirst from "../../components/issues/BlockStandFirst.astro";
6 |
7 | const value = {
8 | _key: "a8f6c37f1f5d",
9 | _type: "block",
10 | children: [
11 | {
12 | _key: "f69fa0972de1",
13 | _type: "span",
14 | marks: [],
15 | text: "list standfirst ",
16 | },
17 | {
18 | _key: "12e6dbf8f10a",
19 | _type: "span",
20 | marks: ["strong"],
21 | text: "bold",
22 | },
23 | ],
24 | level: 1,
25 | listItem: "bullet",
26 | markDefs: [],
27 | style: "standfirst",
28 | };
29 | ---
30 |
31 |
39 |
--------------------------------------------------------------------------------
/lab/src/pages/block/block-index.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 | import Block from "../../components/BlockIndex.astro";
5 |
6 | const blocks = [
7 | {
8 | _type: "block",
9 | style: "normal",
10 | children: [
11 | {
12 | _type: "span",
13 | text: "Paragraph 1",
14 | },
15 | ],
16 | },
17 | {
18 | _type: "block",
19 | style: "normal",
20 | children: [
21 | {
22 | _type: "span",
23 | text: "Paragraph 2",
24 | },
25 | ],
26 | },
27 | {
28 | _type: "block",
29 | style: "normal",
30 | children: [
31 | {
32 | _type: "span",
33 | text: "Paragraph 3",
34 | },
35 | ],
36 | },
37 | ];
38 | ---
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/astro-portabletext/lib/context.ts:
--------------------------------------------------------------------------------
1 | import type { TypedObject } from "@portabletext/types";
2 | import type { Context } from "./types";
3 |
4 | export const key = Symbol("astro-portabletext");
5 |
6 | /**
7 | * This function returns rendering utility functions within a Portable Text tree. It should
8 | * only be used within an Astro component that has been passed into the PortableText `components` prop.
9 | * It follows a naming convention similar to React hooks, though it is not a hook as such.
10 | *
11 | * @param node - The Portable Text node that was passed into the Astro component
12 | * @returns Rendering utility functions
13 | */
14 | export function usePortableText(node: TypedObject) {
15 | if (!(key in globalThis)) {
16 | throw new Error(`PortableText "context" has not been initialised`);
17 | }
18 |
19 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
20 | return (globalThis as any)[key](node) as Context;
21 | }
22 |
--------------------------------------------------------------------------------
/lab/src/pages/slot/mark.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import assert from "node:assert";
3 | import Layout from "../../layouts/Default.astro";
4 | import { PortableText } from "astro-portabletext";
5 | import { Mark as DefaultMark } from "astro-portabletext/components";
6 |
7 | const blocks = [
8 | {
9 | _type: "block",
10 | children: [
11 | {
12 | _type: "span",
13 | text: "bold",
14 | marks: ["strong"],
15 | },
16 | ],
17 | },
18 | ];
19 | ---
20 |
21 |
22 |
23 |
24 | {
25 | ({ Component, props, children }) => {
26 | assert(Component === DefaultMark, "Component is not DefaultMark");
27 |
28 | return (
29 |
30 | {children}
31 |
32 | );
33 | }
34 | }
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/.github/workflows/fix-lockfile.yml:
--------------------------------------------------------------------------------
1 | name: Fix lockfile
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches:
7 | - 'release-please--**'
8 |
9 | concurrency:
10 | group: ${{ github.workflow }}-${{ github.ref }}
11 | cancel-in-progress: true
12 |
13 | permissions:
14 | contents: write
15 |
16 | jobs:
17 | format:
18 | runs-on: ubuntu-latest
19 |
20 | steps:
21 | - uses: actions/checkout@v4
22 | with:
23 | token: ${{ secrets.CI_TOKEN }}
24 |
25 | - name: Setup PNPM
26 | uses: pnpm/action-setup@v3
27 |
28 | - name: Setup Node
29 | uses: actions/setup-node@v4
30 | with:
31 | node-version: 20
32 | cache: 'pnpm'
33 |
34 | - name: Install Dependencies
35 | run: pnpm install
36 |
37 | - name: Commit changes
38 | uses: stefanzweifel/git-auto-commit-action@v5
39 | with:
40 | commit_message: "chore: [ci] lockfile"
--------------------------------------------------------------------------------
/lab/src/pages/slot/list.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import assert from "node:assert";
3 | import Layout from "../../layouts/Default.astro";
4 | import { PortableText } from "astro-portabletext";
5 | import { List as DefaultList } from "astro-portabletext/components";
6 |
7 | const blocks = [
8 | {
9 | _type: "block",
10 | listItem: "bullet",
11 | children: [
12 | {
13 | _type: "span",
14 | text: "List Item 1",
15 | },
16 | ],
17 | },
18 | ];
19 | ---
20 |
21 |
22 |
23 |
24 | {
25 | ({ Component, props, children }) => {
26 | assert(Component === DefaultList, "Component is not DefaultList");
27 |
28 | return (
29 |
30 | {children}
31 |
32 | );
33 | }
34 | }
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/lab/src/pages/slot/mark-custom.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import assert from "node:assert";
3 | import Layout from "../../layouts/Default.astro";
4 | import { PortableText } from "astro-portabletext";
5 | import Mark from "../../components/Mark.astro";
6 |
7 | const blocks = [
8 | {
9 | _type: "block",
10 | children: [
11 | {
12 | _type: "span",
13 | text: "bold",
14 | marks: ["strong"],
15 | },
16 | ],
17 | },
18 | ];
19 | ---
20 |
21 |
22 |
23 |
24 | {
25 | ({ Component, props, children }) => {
26 | assert(Component === Mark, "Component is not Mark");
27 |
28 | return (
29 |
30 | {children}
31 |
32 | );
33 | }
34 | }
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/lab/src/pages/slot/block.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import assert from "node:assert";
3 | import Layout from "../../layouts/Default.astro";
4 | import { PortableText } from "astro-portabletext";
5 | import { Block as DefaultBlock } from "astro-portabletext/components";
6 |
7 | const blocks = [
8 | {
9 | _type: "block",
10 | style: "normal",
11 | children: [
12 | {
13 | _type: "span",
14 | text: "I'm a paragraph",
15 | },
16 | ],
17 | },
18 | ];
19 | ---
20 |
21 |
22 |
23 |
24 | {
25 | ({ Component, props, children }) => {
26 | assert(Component === DefaultBlock, "Component is not DefaultBlock");
27 |
28 | return (
29 |
30 | {children}
31 |
32 | );
33 | }
34 | }
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches:
7 | - main
8 |
9 | permissions:
10 | contents: write
11 | pull-requests: write
12 |
13 | jobs:
14 | release:
15 | runs-on: ubuntu-latest
16 | steps:
17 | - name: Release / Pull Request
18 | uses: googleapis/release-please-action@v4
19 | id: release
20 |
21 | - name: Publish to NPM?
22 | id: npm
23 | run: |
24 | echo "dispatch=${{ steps.release.outputs['astro-portabletext--release_created'] || 'false' }}" >> $GITHUB_OUTPUT
25 |
26 | - uses: actions/checkout@v4
27 | if: ${{ steps.npm.outputs.dispatch == 'true' }}
28 | with:
29 | token: ${{ secrets.CI_TOKEN }}
30 |
31 | - name: Dispatch NPM Workflow
32 | if: ${{ steps.npm.outputs.dispatch == 'true' }}
33 | run: gh workflow run NPM
34 | env:
35 | GH_TOKEN: ${{ secrets.CI_TOKEN }}
36 |
--------------------------------------------------------------------------------
/lab/src/pages/slot/block-custom.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import assert from "node:assert";
3 | import Layout from "../../layouts/Default.astro";
4 | import { PortableText } from "astro-portabletext";
5 | import MyBlock from "../../components/MyBlock.astro";
6 |
7 | const blocks = [
8 | {
9 | _type: "block",
10 | style: "normal",
11 | children: [
12 | {
13 | _type: "span",
14 | text: "I'm a paragraph",
15 | },
16 | ],
17 | },
18 | ];
19 | ---
20 |
21 |
22 |
23 |
24 | {
25 | ({ Component, props, children }) => {
26 | assert(Component === MyBlock, "Component is not MyBlock");
27 |
28 | return (
29 |
30 | {children}
31 |
32 | );
33 | }
34 | }
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/lab/src/pages/slot/listitem-custom.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import assert from "node:assert";
3 | import Layout from "../../layouts/Default.astro";
4 | import { PortableText } from "astro-portabletext";
5 | import ListItem from "../../components/ListItem.astro";
6 |
7 | const blocks = [
8 | {
9 | _type: "block",
10 | listItem: "bullet",
11 | children: [
12 | {
13 | _type: "span",
14 | text: "List Item 1",
15 | },
16 | ],
17 | },
18 | ];
19 | ---
20 |
21 |
22 |
23 |
24 | {
25 | ({ Component, props, children }) => {
26 | assert(Component === ListItem, "Component is not ListItem");
27 |
28 | return (
29 |
30 | {children}
31 |
32 | );
33 | }
34 | }
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/.github/workflows/format.yml:
--------------------------------------------------------------------------------
1 | name: Format
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches:
7 | - main
8 |
9 | concurrency:
10 | group: ${{ github.workflow }}-${{ github.ref }}
11 | cancel-in-progress: true
12 |
13 | permissions:
14 | contents: write
15 |
16 | jobs:
17 | format:
18 | runs-on: ubuntu-latest
19 |
20 | steps:
21 | - uses: actions/checkout@v4
22 | with:
23 | token: ${{ secrets.CI_TOKEN }}
24 |
25 | - name: Setup PNPM
26 | uses: pnpm/action-setup@v3
27 |
28 | - name: Setup Node
29 | uses: actions/setup-node@v4
30 | with:
31 | node-version: 20
32 | cache: 'pnpm'
33 |
34 | - name: Install Dependencies
35 | run: pnpm install
36 |
37 | - name: Format code
38 | run: pnpm format
39 |
40 | - name: Commit changes
41 | uses: stefanzweifel/git-auto-commit-action@v5
42 | with:
43 | commit_message: "style(format): [ci] format"
--------------------------------------------------------------------------------
/examples/portabletext-mapped-type.astro:
--------------------------------------------------------------------------------
1 | ---
2 | /**
3 | * This example demonstrates how to add custom components to the `PortableText` component.
4 | * The custom component will handle rendering of all rich text nodes of the specified type.
5 | *
6 | * The `components` prop accepts an object with the following keys:
7 | * - `type` — Custom type component.
8 | * - `block` — Custom block component.
9 | * - `list` — Custom list component.
10 | * - `listItem` — Custom list item component.
11 | * - `mark` — Custom mark component.
12 | * - `text` — Custom text component.
13 | * - `hardBreak` — Custom hard break component.
14 | */
15 | // @ts-nocheck
16 | import { PortableText } from "astro-portabletext";
17 | import Block from "./Block.astro";
18 | import Type from "./Type.astro";
19 |
20 | const portableText = [
21 | /* Portable Text payload */
22 | ];
23 |
24 | const components = {
25 | type: Type,
26 | block: Block,
27 | };
28 | ---
29 |
30 |
31 |
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | name: Docs
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | paths:
7 | - 'astro-portabletext/lib/**'
8 |
9 | concurrency:
10 | group: ${{ github.workflow }}-${{ github.ref }}
11 | cancel-in-progress: true
12 |
13 | permissions:
14 | contents: write
15 |
16 | jobs:
17 | format:
18 | runs-on: ubuntu-latest
19 |
20 | steps:
21 | - uses: actions/checkout@v4
22 | with:
23 | token: ${{ secrets.CI_TOKEN }}
24 |
25 | - name: Setup PNPM
26 | uses: pnpm/action-setup@v3
27 |
28 | - name: Setup Node
29 | uses: actions/setup-node@v4
30 | with:
31 | node-version: 20
32 | cache: 'pnpm'
33 |
34 | - name: Install Dependencies
35 | run: pnpm install
36 |
37 | - name: Generate docs
38 | run: pnpm -r generate-docs
39 |
40 | - name: Commit changes
41 | uses: stefanzweifel/git-auto-commit-action@v5
42 | with:
43 | commit_message: "docs: generated documentation"
--------------------------------------------------------------------------------
/lab/src/pages/slot/listitem.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import assert from "node:assert";
3 | import Layout from "../../layouts/Default.astro";
4 | import { PortableText } from "astro-portabletext";
5 | import { ListItem as DefaultListItem } from "astro-portabletext/components";
6 |
7 | const blocks = [
8 | {
9 | _type: "block",
10 | listItem: "bullet",
11 | children: [
12 | {
13 | _type: "span",
14 | text: "List Item 1",
15 | },
16 | ],
17 | },
18 | ];
19 | ---
20 |
21 |
22 |
23 |
24 | {
25 | ({ Component, props, children }) => {
26 | assert(
27 | Component === DefaultListItem,
28 | "Component is not DefaultListItem"
29 | );
30 |
31 | return (
32 |
33 | {children}
34 |
35 | );
36 | }
37 | }
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/docs/types/type-aliases/RenderOptions.md:
--------------------------------------------------------------------------------
1 | [**`astro-portabletext` • Type Definitions**](../README.md)
2 |
3 | ***
4 |
5 | # Type Alias: RenderOptions
6 |
7 | ```ts
8 | type RenderOptions = object;
9 | ```
10 |
11 | Options for the `render` function accessed via `usePortableText`
12 |
13 | ## Properties
14 |
15 | ### type?
16 |
17 | ```ts
18 | optional type: RenderHandler;
19 | ```
20 |
21 | ***
22 |
23 | ### block?
24 |
25 | ```ts
26 | optional block: RenderHandler;
27 | ```
28 |
29 | ***
30 |
31 | ### list?
32 |
33 | ```ts
34 | optional list: RenderHandler;
35 | ```
36 |
37 | ***
38 |
39 | ### listItem?
40 |
41 | ```ts
42 | optional listItem: RenderHandler;
43 | ```
44 |
45 | ***
46 |
47 | ### mark?
48 |
49 | ```ts
50 | optional mark: RenderHandler;
51 | ```
52 |
53 | ***
54 |
55 | ### text?
56 |
57 | ```ts
58 | optional text: RenderHandler;
59 | ```
60 |
61 | ***
62 |
63 | ### hardBreak?
64 |
65 | ```ts
66 | optional hardBreak: RenderHandler;
67 | ```
68 |
--------------------------------------------------------------------------------
/lab/src/pages/slot/list-custom.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import assert from "node:assert";
3 | import Layout from "../../layouts/Default.astro";
4 | import { PortableText } from "astro-portabletext";
5 | import ListWithRender from "../../components/ListWithRender.astro";
6 |
7 | const blocks = [
8 | {
9 | _type: "block",
10 | listItem: "bullet",
11 | children: [
12 | {
13 | _type: "span",
14 | text: "List Item 1",
15 | },
16 | ],
17 | },
18 | ];
19 | ---
20 |
21 |
22 |
23 |
24 | {
25 | ({ Component, props, children }) => {
26 | assert(
27 | Component === ListWithRender,
28 | "Component is not ListWithRender"
29 | );
30 |
31 | return (
32 |
33 | {children}
34 |
35 | );
36 | }
37 | }
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "astro-portabletext development",
3 | "image": "mcr.microsoft.com/devcontainers/typescript-node:24",
4 | "features": {
5 | "ghcr.io/devcontainers/features/git:1": {},
6 | "ghcr.io/devcontainers-extra/features/pnpm:2": {
7 | "version": "10.21.0"
8 | }
9 | },
10 | // Configure tool-specific properties.
11 | "customizations": {
12 | "vscode": {
13 | "extensions": [
14 | "astro-build.astro-vscode",
15 | "editorconfig.editorconfig",
16 | "dbaeumer.vscode-eslint",
17 | "esbenp.prettier-vscode"
18 | ],
19 | "settings": {
20 | "editor.defaultFormatter": "esbenp.prettier-vscode",
21 | "editor.formatOnSave": true,
22 | "editor.formatOnSaveMode": "file",
23 | "prettier.prettierPath": "node_modules/prettier",
24 | "prettier.ignorePath": ".prettierignore",
25 | "typescript.tsdk": "node_modules/typescript/lib"
26 | }
27 | }
28 | },
29 | "postCreateCommand": "pnpm install --frozen-lockfile"
30 | }
31 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - 'release-please--**'
8 |
9 | pull_request:
10 | paths-ignore:
11 | - ".vscode/**"
12 | - "**/*.md"
13 |
14 | concurrency:
15 | group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
16 | cancel-in-progress: true
17 |
18 | jobs:
19 | test:
20 | name: Test
21 | runs-on: ubuntu-latest
22 |
23 | strategy:
24 | fail-fast: false
25 | matrix:
26 | node-version: [18, 20, 22]
27 |
28 | steps:
29 | - uses: actions/checkout@v4
30 |
31 | - name: Setup PNPM
32 | uses: pnpm/action-setup@v3
33 |
34 | - name: Setup Node ${{ matrix.node-version }}
35 | uses: actions/setup-node@v4
36 | with:
37 | node-version: ${{ matrix.node-version }}
38 | cache: 'pnpm'
39 |
40 | - name: Install Dependencies
41 | run: pnpm install
42 |
43 | - name: Run Linting
44 | run: pnpm lint
45 |
46 | - name: Run Test
47 | run: pnpm test:ci
--------------------------------------------------------------------------------
/lab/src/test/render.test.js:
--------------------------------------------------------------------------------
1 | import { suite } from "uvu";
2 | import * as assert from "uvu/assert";
3 | import { fetchContent } from "../utils.mjs";
4 |
5 | const render = suite("render");
6 |
7 | render("block", async () => {
8 | const $ = await fetchContent("render/block");
9 | const $block = $("div[data-custom='block']");
10 | const $span = $block.find("span[data-custom='text']");
11 |
12 | assert.is($block.length, 1);
13 | assert.is($span.length, 1);
14 | assert.is($span.text(), "Rocket launch 🚀");
15 | });
16 |
17 | render("list", async () => {
18 | const $ = await fetchContent("render/list");
19 | const $list = $("ul[data-custom='list']");
20 | const $span = $list.find("span[data-custom='text']");
21 |
22 | assert.is($list.length, 1);
23 | assert.is($span.length, 1);
24 | });
25 |
26 | render("mark", async () => {
27 | const $ = await fetchContent("render/mark");
28 | const $em = $("em");
29 | const $span = $em.find("span[data-custom='text']");
30 |
31 | assert.is($em.length, 1);
32 | assert.is($span.length, 1);
33 | });
34 |
35 | render.run();
36 |
--------------------------------------------------------------------------------
/demo/src/components/counter/Counter.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import JsxCounter from "./JsxCounter.astro";
3 | import SvelteCounter from "./SvelteCounter.astro";
4 |
5 | const props = Astro.props;
6 | const typeIs = (type: string) => type === props.node._type;
7 |
8 | const error = () => {
9 | if (import.meta.env.PROD) {
10 | console.log("Unknown PortableText Counter type:", props.node._type);
11 | return null;
12 | }
13 | throw new Error(`Unknown PortableText Counter type: ${props.node._type}`);
14 | };
15 | ---
16 |
17 | {
18 | typeIs("jsxCounter") ? (
19 |
20 | ) : typeIs("svelteCounter") ? (
21 |
22 | ) : (
23 | error()
24 | )
25 | }
26 |
27 |
43 |
--------------------------------------------------------------------------------
/examples/Type.astro:
--------------------------------------------------------------------------------
1 | ---
2 | /**
3 | * This `type` component handles custom `node._type` values. `astro-portabletext` uses this to render custom types.
4 | * It conditionally renders different components based on the `_type` and provides a fallback for unknown types.
5 | *
6 | * Note:
7 | * Use the `isInline` prop to determine if the component should be layed out as inline or block.
8 | *
9 | * Usage:
10 | *
11 | */
12 | // @ts-nocheck
13 | import type { TypedObject, Props as $ } from "astro-portabletext/types";
14 | import { usePortableText } from "astro-portabletext";
15 | import Hero from "./Hero.astro";
16 | import Map from "./Map.astro";
17 |
18 | type Props = $;
19 |
20 | const props = Astro.props;
21 | const { getUnknownComponent } = usePortableText(props.node);
22 | const typeIs = (type: string) => type === props.node._type;
23 |
24 | const Cmp = typeIs("hero") ? Hero : typeIs("map") ? Map : getUnknownComponent(); // Fallback to `components.unknownType` component
25 | ---
26 |
27 |
28 |
--------------------------------------------------------------------------------
/astro-portabletext/components/Block.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { Block, Props as $ } from "../lib/types";
3 | import { usePortableText } from "../lib/utils";
4 |
5 | export type Props = $;
6 |
7 | const props = Astro.props;
8 | const { node, index, isInline, ...attrs } = props;
9 | const styleIs = (style: string) => style === node.style;
10 |
11 | const { getUnknownComponent } = usePortableText(node);
12 |
13 | const UnknownStyle = getUnknownComponent();
14 | ---
15 |
16 | {
17 | styleIs("h1") ? (
18 |
19 | ) : styleIs("h2") ? (
20 |
21 | ) : styleIs("h3") ? (
22 |
23 | ) : styleIs("h4") ? (
24 |
25 | ) : styleIs("h5") ? (
26 |
27 | ) : styleIs("h6") ? (
28 |
29 | ) : styleIs("blockquote") ? (
30 |
31 | ) : styleIs("normal") ? (
32 |
33 | ) : (
34 |
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import path from "node:path";
2 | import { fileURLToPath } from "node:url";
3 | import { FlatCompat } from "@eslint/eslintrc";
4 | import js from "@eslint/js";
5 | import typescriptEslint from "@typescript-eslint/eslint-plugin";
6 | import tsParser from "@typescript-eslint/parser";
7 | import globals from "globals";
8 |
9 | const __filename = fileURLToPath(import.meta.url);
10 | const __dirname = path.dirname(__filename);
11 | const compat = new FlatCompat({
12 | baseDirectory: __dirname,
13 | recommendedConfig: js.configs.recommended,
14 | allConfig: js.configs.all,
15 | });
16 |
17 | export default [
18 | {
19 | ignores: ["**/*.d.ts", ".github/*", "**/dist"],
20 | },
21 | ...compat.extends(
22 | "eslint:recommended",
23 | "plugin:@typescript-eslint/recommended",
24 | "prettier"
25 | ),
26 | {
27 | plugins: {
28 | "@typescript-eslint": typescriptEslint,
29 | },
30 |
31 | languageOptions: {
32 | globals: {
33 | ...globals.node,
34 | globalThis: true,
35 | },
36 |
37 | parser: tsParser,
38 | },
39 | },
40 | ];
41 |
--------------------------------------------------------------------------------
/examples/List.astro:
--------------------------------------------------------------------------------
1 | ---
2 | /**
3 | * This `list` component customizes the rendering of Portable Text lists.
4 | * It conditionally renders different components based on the `listItem` value
5 | * and provides a fallback to the default `astro-portabletext` List component.
6 | *
7 | * Usage:
8 | *
9 | */
10 | // @ts-nocheck
11 | import type { ListProps } from "astro-portabletext/types";
12 | import { usePortableText } from "astro-portabletext";
13 | import BulletList from "./BulletList.astro";
14 | import SquareList from "./SquareList.astro";
15 |
16 | export type Props = ListProps;
17 |
18 | const props = Astro.props;
19 | const { getDefaultComponent } = usePortableText(props.node);
20 | const listItemIs = (listItem: string) => listItem === props.node.listItem;
21 |
22 | const Cmp = listItemIs("square") // Custom list item
23 | ? SquareList
24 | : listItemIs("bullet") // Override default
25 | ? BulletList
26 | : getDefaultComponent(); // Fallback to `astro-portabletext` List component
27 | ---
28 |
29 |
30 |
--------------------------------------------------------------------------------
/astro-portabletext/components/Mark.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import type { Mark, Props as $ } from "../lib/types";
3 | import { usePortableText } from "../lib/utils";
4 |
5 | export type Props = $;
6 |
7 | const props = Astro.props;
8 | const { node, index, isInline, ...attrs } = props;
9 | const markTypeIs = (markType: string) => markType === node.markType;
10 |
11 | const { getUnknownComponent } = usePortableText(node);
12 |
13 | const UnknownMarkType = getUnknownComponent();
14 | ---
15 |
16 | {
17 | markTypeIs("code") ? (
18 |
19 | ) : markTypeIs("em") ? (
20 |
21 | ) : markTypeIs("link") ? (
22 | ).markDef.href} {...attrs}>
23 | ) : markTypeIs("strike-through") ? (
24 |
25 | ) : markTypeIs("strong") ? (
26 |
27 | ) : markTypeIs("underline") ? (
28 |
29 | ) : (
30 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/lab/src/pages/list/nested.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import Layout from "../../layouts/Default.astro";
3 | import { PortableText } from "astro-portabletext";
4 |
5 | const blocks = [
6 | {
7 | _type: "block",
8 | style: "normal",
9 | children: [
10 | {
11 | _type: "span",
12 | text: "1",
13 | },
14 | ],
15 | level: 1,
16 | listItem: "bullet",
17 | },
18 | {
19 | _type: "block",
20 | style: "normal",
21 | level: 2,
22 | listItem: "bullet",
23 | children: [
24 | {
25 | _type: "span",
26 | text: "1.1",
27 | },
28 | ],
29 | },
30 | {
31 | _type: "block",
32 | style: "normal",
33 | level: 3,
34 | listItem: "bullet",
35 | children: [
36 | {
37 | _type: "span",
38 | text: "1.1.1",
39 | },
40 | ],
41 | },
42 | {
43 | _type: "block",
44 | style: "normal",
45 | children: [
46 | {
47 | _type: "span",
48 | text: "2",
49 | },
50 | ],
51 | level: 1,
52 | listItem: "bullet",
53 | },
54 | ];
55 | ---
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/examples/Mark.astro:
--------------------------------------------------------------------------------
1 | ---
2 | /**
3 | * This `mark` component customizes the rendering of Portable Text marks.
4 | * It conditionally renders different components based on the `markType`
5 | * and provides a fallback to the default `astro-portabletext` Mark component.
6 | *
7 | * Usage:
8 | *
9 | */
10 | // @ts-nocheck
11 | import type { MarkProps } from "astro-portabletext/types";
12 | import { usePortableText } from "astro-portabletext";
13 | import Emphasis from "./Emphasis";
14 | import Highlight from "./Highlight.astro";
15 |
16 | export type Props = MarkProps; // Use `never` for type parameter if the mark doesn't carry additional data
17 |
18 | const props = Astro.props;
19 | const { getDefaultComponent } = usePortableText(props.node);
20 | const markTypeIs = (markType: string) => markType === props.node.markType;
21 |
22 | const Cmp = markTypeIs("highlight") // Custom mark
23 | ? Highlight
24 | : markTypeIs("em") // Override default
25 | ? Emphasis
26 | : getDefaultComponent(); // Fallback to `astro-portabletext` Mark component
27 | ---
28 |
29 |
30 |
--------------------------------------------------------------------------------
/examples/ListItem.astro:
--------------------------------------------------------------------------------
1 | ---
2 | /**
3 | * This `listItem` component customizes the rendering of Portable Text list items.
4 | * It conditionally renders different components based on the `listItem` value
5 | * and provides a fallback to the default `astro-portabletext` ListItem component.
6 | *
7 | * Usage:
8 | *
9 | */
10 | // @ts-nocheck
11 | import type { ListItemProps } from "astro-portabletext/types";
12 | import { usePortableText } from "astro-portabletext";
13 | import BulletListItem from "./BulletListItem.astro";
14 | import SquareListItem from "./SquareListItem.astro";
15 |
16 | export type Props = ListItemProps;
17 |
18 | const props = Astro.props;
19 | const { getDefaultComponent } = usePortableText(props.node);
20 | const listItemIs = (listItem: string) => listItem === props.node.listItem;
21 |
22 | const Cmp = listItemIs("square") // Custom list item
23 | ? SquareListItem
24 | : listItemIs("bullet") // Override default
25 | ? BulletListItem
26 | : getDefaultComponent(); // Fallback to `astro-portabletext` ListItem component
27 | ---
28 |
29 |
30 |
--------------------------------------------------------------------------------
/astro-portabletext/lib/utils.d.ts:
--------------------------------------------------------------------------------
1 | declare module "astro-portabletext/utils" {
2 | /**
3 | * @deprecated Use `toPlainText` from `astro-portabletext` instead
4 | */
5 | export function toPlainText(
6 | ...args: Parameters
7 | ): ReturnType;
8 | /**
9 | * @deprecated Use `spanToPlainText` from `astro-portabletext` instead
10 | */
11 | export function spanToPlainText(
12 | ...args: Parameters
13 | ): ReturnType;
14 | /**
15 | * @deprecated Use `mergeComponents` from `astro-portabletext` instead
16 | */
17 | export function mergeComponents(
18 | ...args: Parameters
19 | ): ReturnType;
20 | /**
21 | * @deprecated Use `usePortableText` from `astro-portabletext` instead
22 | */
23 | export function usePortableText(
24 | ...args: Parameters
25 | ): ReturnType;
26 | }
27 |
--------------------------------------------------------------------------------
/astro-portabletext/lib/warnings.ts:
--------------------------------------------------------------------------------
1 | import type { NodeType } from "./types";
2 |
3 | const getTemplate = (prop: string, type: string): string =>
4 | `PortableText [components.${prop}] is missing "${type}"`;
5 |
6 | export const unknownTypeWarning = (type: string): string =>
7 | getTemplate("type", type);
8 |
9 | export const unknownMarkWarning = (markType: string): string =>
10 | getTemplate("mark", markType);
11 |
12 | export const unknownBlockWarning = (style: string): string =>
13 | getTemplate("block", style);
14 |
15 | export const unknownListWarning = (listItem: string): string =>
16 | getTemplate("list", listItem);
17 |
18 | export const unknownListItemWarning = (listStyle: string): string =>
19 | getTemplate("listItem", listStyle);
20 |
21 | export const getWarningMessage = (nodeType: NodeType, type: string) => {
22 | const fncs = {
23 | block: unknownBlockWarning,
24 | list: unknownListWarning,
25 | listItem: unknownListItemWarning,
26 | mark: unknownMarkWarning,
27 | type: unknownTypeWarning,
28 | };
29 |
30 | return fncs[nodeType](type);
31 | };
32 |
33 | export function printWarning(message: string): void {
34 | console.warn(message);
35 | }
36 |
--------------------------------------------------------------------------------
/docs/types/type-aliases/RenderHandlerProps.md:
--------------------------------------------------------------------------------
1 | [**`astro-portabletext` • Type Definitions**](../README.md)
2 |
3 | ***
4 |
5 | # Type Alias: RenderHandlerProps\
6 |
7 | ```ts
8 | type RenderHandlerProps = object;
9 | ```
10 |
11 | Properties for the `RenderHandler` function
12 |
13 | ## Type Parameters
14 |
15 | | Type Parameter | Default type | Description |
16 | | ------ | ------ | ------ |
17 | | `T` *extends* [`TypedObject`](../interfaces/TypedObject.md) | [`TypedObject`](../interfaces/TypedObject.md) | Type of Portable Text payload |
18 | | `Children` | `unknown` | Type of children |
19 |
20 | ## Properties
21 |
22 | ### Component
23 |
24 | ```ts
25 | Component: Component;
26 | ```
27 |
28 | The component that is associated with the Portable Text node.
29 |
30 | ***
31 |
32 | ### props
33 |
34 | ```ts
35 | props: Props;
36 | ```
37 |
38 | The component props
39 |
40 | ***
41 |
42 | ### children?
43 |
44 | ```ts
45 | optional children: Children;
46 | ```
47 |
48 | The children related to the Portable Text node.
49 | If the node is a custom [type](../interfaces/PortableTextComponents.md#type) or a [TextNode](TextNode.md), then children will be `undefined`.
50 |
--------------------------------------------------------------------------------
/examples/Block.astro:
--------------------------------------------------------------------------------
1 | ---
2 | /**
3 | * This `block` component customizes the rendering of Portable Text blocks.
4 | * It conditionally renders different components based on the `style` value
5 | * and provides a fallback to the default `astro-portabletext` Block component.
6 | *
7 | * Usage:
8 | *
9 | */
10 | // @ts-nocheck
11 | import type { BlockProps } from "astro-portabletext/types";
12 | import { usePortableText } from "astro-portabletext";
13 | import Billboard from "./Billboard.astro";
14 | import Quote from "./Quote.astro";
15 |
16 | type Props = BlockProps;
17 |
18 | const props = Astro.props;
19 | const { getDefaultComponent } = usePortableText(props.node);
20 | const styleIs = (style: string) => style === props.node.style;
21 |
22 | const Cmp = styleIs("billboard") // Custom style
23 | ? Billboard
24 | : styleIs("blockquote") // Override default
25 | ? Quote
26 | : getDefaultComponent(); // Fallback to `astro-portabletext` Block component
27 | ---
28 |
29 |
30 |
31 |
36 |
--------------------------------------------------------------------------------
/lab/src/lib/warnings.test.ts:
--------------------------------------------------------------------------------
1 | import { test } from "uvu";
2 | import * as assert from "uvu/assert";
3 | import {
4 | unknownTypeWarning,
5 | unknownMarkWarning,
6 | unknownListWarning,
7 | unknownListItemWarning,
8 | unknownBlockWarning,
9 | } from "../../../astro-portabletext/lib/warnings";
10 |
11 | test("unknownTypeWarning", () => {
12 | assert.is(
13 | unknownTypeWarning("custom"),
14 | 'PortableText [components.type] is missing "custom"'
15 | );
16 | });
17 |
18 | test("unknownMarkWarning", () => {
19 | assert.is(
20 | unknownMarkWarning("em"),
21 | 'PortableText [components.mark] is missing "em"'
22 | );
23 | });
24 |
25 | test("unknownListWarning", () => {
26 | assert.is(
27 | unknownListWarning("bullet"),
28 | 'PortableText [components.list] is missing "bullet"'
29 | );
30 | });
31 |
32 | test("unknownListItemWarning", () => {
33 | assert.is(
34 | unknownListItemWarning("bullet"),
35 | 'PortableText [components.listItem] is missing "bullet"'
36 | );
37 | });
38 |
39 | test("unknownBlockWarning", () => {
40 | assert.is(
41 | unknownBlockWarning("normal"),
42 | 'PortableText [components.block] is missing "normal"'
43 | );
44 | });
45 |
46 | test.run();
47 |
--------------------------------------------------------------------------------
/demo/src/layouts/Default.astro:
--------------------------------------------------------------------------------
1 | ---
2 | const packageName = "astro-portabletext";
3 | ---
4 |
5 |
6 |
7 |
8 |
9 | {packageName} :: demo
10 |
11 |
12 |
21 |
22 |
23 |
24 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/examples/BlockWithRenderFunction.astro:
--------------------------------------------------------------------------------
1 | ---
2 | /**
3 | * Works with `v0.11.0+`
4 | *
5 | * This `Block` component demonstrates how to customize Portable Text block rendering
6 | * using the `render` function from `usePortableText`.
7 | *
8 | * `render` allows fine-grained control over child node rendering and can be used
9 | * within `block`, `list`, `listItem`, and `mark` components as they contain child nodes.
10 | *
11 | * This example customizes the rendering of `h1` blocks to replace the word "rocket"
12 | * with a 🚀 emoji.
13 | */
14 | // @ts-nocheck
15 | import type { BlockProps } from "astro-portabletext/types";
16 | import { usePortableText } from "astro-portabletext";
17 |
18 | type Props = BlockProps;
19 |
20 | const { node, isInline, index, ...attrs } = Astro.props;
21 | const { getDefaultComponent, render } = usePortableText(node);
22 | const styleIs = (style: string) => style === node.style;
23 |
24 | const DefaultBlock = getDefaultComponent(); // Returns `astro-portabletext` Block component
25 | ---
26 |
27 | {
28 | styleIs("h1") ? (
29 |
30 | {render({
31 | text: ({ props }) => props.node.text.replace("rocket", "🚀"),
32 | })}
33 |
34 | ) : (
35 |
36 |
37 |
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/lab/src/test/type.test.js:
--------------------------------------------------------------------------------
1 | import { suite } from "uvu";
2 | import * as assert from "uvu/assert";
3 | import { fetchContent } from "../utils.mjs";
4 |
5 | const type = suite("type");
6 |
7 | type("block", async () => {
8 | const $ = await fetchContent("type/block");
9 | const $el = $("p");
10 |
11 | assert.is($el.length, 1);
12 | assert.is($el.text(), "Hello World");
13 | });
14 |
15 | type("inline", async () => {
16 | const $ = await fetchContent("type/inline");
17 | const $el = $("span");
18 |
19 | assert.is($el.length, 1);
20 | assert.is($el.text(), "Hello World");
21 | });
22 |
23 | type("unknown.block", async () => {
24 | const $ = await fetchContent("type/unknown-block");
25 | const $el = $("[data-portabletext-unknown]");
26 |
27 | assert.is($el.length, 1);
28 | assert.is($el.get(0).name, "div");
29 | assert.is($el.attr("style"), "display:none");
30 | assert.is($el.attr("data-portabletext-unknown"), "type");
31 | });
32 |
33 | type("unknown.inline", async () => {
34 | const $ = await fetchContent("type/unknown-inline");
35 | const $el = $("[data-portabletext-unknown]");
36 |
37 | assert.is($el.length, 1);
38 | assert.is($el.get(0).name, "span");
39 | assert.is($el.attr("style"), "display:none");
40 | assert.is($el.attr("data-portabletext-unknown"), "type");
41 | });
42 |
43 | type.run();
44 |
--------------------------------------------------------------------------------
/release-please-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "last-release-sha": "f3f2ce5146d3ee5bdf93955a229125b7b783645d",
3 | "bump-minor-pre-major": true,
4 | "separate-pull-requests": false,
5 | "include-component-in-tag": true,
6 | "include-v-in-tag": false,
7 | "tag-separator": "@",
8 | "pull-request-header": ":robot: release created",
9 | "plugins": ["node-workspace"],
10 | "release-type": "node",
11 | "changelog-sections": [
12 | { "type": "build", "section": "Build System", "hidden": true },
13 | { "type": "ci", "section": "Continuous Integration", "hidden": true },
14 | { "type": "chore", "section": "Miscellaneous Chores", "hidden": true },
15 | { "type": "docs", "section": "Documentation", "hidden": true },
16 | { "type": "feat", "section": "Features" },
17 | { "type": "feature", "section": "Features" },
18 | { "type": "fix", "section": "Bug Fixes" },
19 | { "type": "perf", "section": "Performance Improvements" },
20 | { "type": "revert", "section": "Reverts" },
21 | { "type": "refactor", "section": "Code Refactoring", "hidden": true },
22 | { "type": "style", "section": "Styles", "hidden": true },
23 | { "type": "test", "section": "Tests", "hidden": true }
24 | ],
25 | "packages": {
26 | "astro-portabletext": {
27 | "package-name": "astro-portabletext"
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/demo/src/components/FancyBorder.astro:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
52 |
--------------------------------------------------------------------------------
/examples/portabletext-mapped-type-property.astro:
--------------------------------------------------------------------------------
1 | ---
2 | /**
3 | * This example demonstrates how to map custom components to different
4 | * Portable Text node type properties.
5 | *
6 | * It shows how to use the `components` prop to customize rendering for:
7 | * - Custom types (`_type`)
8 | * - Block styles (`style`)
9 | * - List types (`listItem`)
10 | * - Mark types (`markType`)
11 | */
12 | // @ts-nocheck
13 | import { PortableText } from "astro-portabletext";
14 | import Hero from "./Hero.astro";
15 | import Map from "./Map.astro";
16 | import PageHeading from "./PageHeading.astro";
17 | import BulletListWrapper from "./BulletListWrapper.astro";
18 | import BulletListItem from "./BulletListItem.astro";
19 | import Highlight from "./Highlight.astro";
20 |
21 | const portableText = [
22 | /* Portable Text payload */
23 | ];
24 |
25 | const components = {
26 | type: {
27 | hero: Hero,
28 | map: Map,
29 | // [node._type]: Component
30 | },
31 | block: {
32 | h1: PageHeading,
33 | // [node.style]: Component
34 | },
35 | list: {
36 | bullet: BulletListWrapper,
37 | // [node.listItem]: Component
38 | },
39 | listItem: {
40 | bullet: BulletListItem,
41 | // [node.listItem]: Component
42 | },
43 | mark: {
44 | highlight: Highlight,
45 | // [node.markType]: Component
46 | },
47 | };
48 | ---
49 |
50 |
51 |
--------------------------------------------------------------------------------
/demo/src/pages/index.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { PortableText } from "astro-portabletext";
3 | import { components } from "../components/portabletext";
4 | import Layout from "../layouts/Default.astro";
5 | import value from "../data/portabletext.json";
6 | ---
7 |
8 |
9 |
10 | {
11 | /* Uncomment this section to customize how some block-level elements (e.g., headings) are styled */
12 | }
13 |
22 | {
23 | /* Uncomment this section to see how some list elements (e.g., ordered lists) are styled */
24 | }
25 |
34 |
35 |
36 |
37 |
54 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | `astro-portabletext` • Documentation
2 |
3 | ---
4 |
5 | # `astro-portabletext` Documentation
6 |
7 | This directory serves as a dedicated resource for developers
8 | using `astro-portabletext`. It complements the information provided by the
9 | [package README](../astro-portabletext/README.md) with in-depth details about type definitions and
10 | usage examples.
11 |
12 | ## Table of Contents
13 |
14 | - **Getting started**: [Installation and basic usage](getting-started.md "Getting started guide for astro-portabletext").
15 | - **Component documentation**: [PortableText component](portabletext-component.md "PortableText component documentation for astro-portabletext") outlining usage and configuration options.
16 | - **Utility functions**: [Functions documentation](utility-functions.md "Utility functions for astro-portabletext") to help you work with Portable Text content.
17 | - **TypeScript types**: Get detailed type definitions [here](types/README.md "TypeScript types README for astro-portabletext").
18 |
19 | ## Resources
20 |
21 | - **Examples**: Explore practical usage examples in the
22 | [examples directory](../examples/README.md "Examples README for astro-portabletext").
23 |
24 | ## Contributing
25 |
26 | Has something been missed or needs improvement? Feel free to open a [pull request](https://github.com/theisel/astro-portabletext/pulls) or an [issue](https://github.com/theisel/astro-portabletext/issues) in the main repository. Contributions are always welcome!
27 |
--------------------------------------------------------------------------------
/.github/workflows/npm.yml:
--------------------------------------------------------------------------------
1 | name: NPM
2 |
3 | on: workflow_dispatch
4 |
5 | defaults:
6 | run:
7 | shell: bash
8 |
9 | env:
10 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
11 | NPM_CONFIG_PROVENANCE: true
12 |
13 | permissions:
14 | contents: read
15 | id-token: write
16 |
17 | jobs:
18 | npm:
19 | name: Publish to NPM
20 | runs-on: ubuntu-latest
21 |
22 | steps:
23 | - uses: actions/checkout@v4
24 | with:
25 | fetch-depth: 0
26 | token: ${{ secrets.CI_TOKEN }}
27 |
28 | - name: Setup PNPM
29 | uses: pnpm/action-setup@v3
30 |
31 | - name: Setup Node
32 | uses: actions/setup-node@v4
33 | with:
34 | node-version: 20
35 | registry-url: 'https://registry.npmjs.org'
36 | cache: 'pnpm'
37 |
38 | - name: Setup User
39 | run: |
40 | git config user.name "github-actions[bot]"
41 | git config user.email "github-actions[bot]@users.noreply.github.com"
42 |
43 | - name: Get Latest Tag
44 | id: get_latest_tag
45 | run: echo "tag=$(git describe --tags --match="astro-portabletext*" --abbrev=0)" >> $GITHUB_OUTPUT
46 |
47 | - name: Reset to Latest Tag
48 | run: git reset --hard ${{ steps.get_latest_tag.outputs.tag }}
49 |
50 | - name: Install Dependencies
51 | run: pnpm install --filter astro-portabletext
52 |
53 | - name: Publish
54 | run: pnpm publish --filter astro-portabletext --no-git-checks
55 |
--------------------------------------------------------------------------------
/lab/src/test/list.test.js:
--------------------------------------------------------------------------------
1 | import { suite } from "uvu";
2 | import * as assert from "uvu/assert";
3 | import { fetchContent } from "../utils.mjs";
4 |
5 | const list = suite("list");
6 |
7 | list("menu", async () => {
8 | const $ = await fetchContent("list/menu");
9 | const $el = $("menu");
10 |
11 | assert.is($el.length, 1);
12 | });
13 |
14 | list("ol", async () => {
15 | const $ = await fetchContent("list/ordered");
16 | const $el = $("ol");
17 |
18 | assert.is($el.length, 1);
19 | });
20 |
21 | list("ul", async () => {
22 | const $ = await fetchContent("list/unordered");
23 | const $el = $("ul");
24 |
25 | assert.is($el.length, 1);
26 | });
27 |
28 | list("unknown", async () => {
29 | const $ = await fetchContent("list/unknown");
30 | const $el = $("ul");
31 |
32 | assert.is($el.length, 1);
33 | assert.is($el.attr("data-portabletext-unknown"), "list");
34 | });
35 |
36 | list("nested", async () => {
37 | const $ = await fetchContent("list/nested");
38 |
39 | assert.match(
40 | $("body").html().trim(),
41 | /^\s*\s*1\s*\s*\s*1\.1\s*\s*\s*1\.1\.1\s*<\/li>\s*<\/ul>\s*<\/li>\s*<\/ul>\s*<\/li>\s* \s*2\s*<\/li>\s*<\/ul>$/
42 | );
43 | });
44 |
45 | list("styled", async () => {
46 | const $ = await fetchContent("list/styled");
47 | const $ul = $("ul");
48 |
49 | assert.is($ul.length, 1);
50 |
51 | const $li = $ul.find("li");
52 |
53 | assert.is($li.length, 1);
54 |
55 | const $banner = $li.find("div.banner");
56 |
57 | assert.is($banner.length, 1);
58 | assert.is($banner.text(), "List Item 1");
59 | });
60 |
61 | list.run();
62 |
--------------------------------------------------------------------------------
/astro-portabletext/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "astro-portabletext",
3 | "version": "0.12.0",
4 | "type": "module",
5 | "description": "A flexible and customizable library for rendering Portable Text content in your Astro projects",
6 | "keywords": [
7 | "astro",
8 | "withastro",
9 | "astro-component",
10 | "portable-text",
11 | "typescript",
12 | "ui"
13 | ],
14 | "author": "Tom Theisel ",
15 | "license": "ISC",
16 | "exports": {
17 | ".": "./lib/index.ts",
18 | "./components": "./lib/components.ts",
19 | "./types": "./lib/types.ts",
20 | "./utils": {
21 | "types": "./lib/utils.d.ts",
22 | "import": "./lib/utils.ts",
23 | "default": "./lib/utils.ts"
24 | }
25 | },
26 | "typesVersions": {
27 | "*": {
28 | "*": [
29 | "lib/*"
30 | ]
31 | }
32 | },
33 | "files": [
34 | "CHANGELOG.md",
35 | "components",
36 | "lib"
37 | ],
38 | "repository": {
39 | "type": "git",
40 | "url": "https://github.com/theisel/astro-portabletext.git",
41 | "directory": "astro-portabletext"
42 | },
43 | "bugs": {
44 | "url": "https://github.com/theisel/astro-portabletext/issues"
45 | },
46 | "homepage": "https://github.com/theisel/astro-portabletext#readme",
47 | "scripts": {
48 | "check": "astro check --root components"
49 | },
50 | "dependencies": {
51 | "@portabletext/toolkit": "^4.0.0",
52 | "@portabletext/types": "^3.0.0"
53 | },
54 | "peerDependencies": {
55 | "astro": ">=4.6.0"
56 | },
57 | "engines": {
58 | "node": ">=20.19 <22 || >=22.12"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/docs/types/interfaces/PortableTextProps.md:
--------------------------------------------------------------------------------
1 | [**`astro-portabletext` • Type Definitions**](../README.md)
2 |
3 | ***
4 |
5 | # Interface: PortableTextProps\
6 |
7 | Properties for the `PortableText` component
8 |
9 | ## Type Parameters
10 |
11 | | Type Parameter | Default type | Description |
12 | | ------ | ------ | ------ |
13 | | `Value` *extends* [`TypedObject`](TypedObject.md) | `PortableTextBlock` \| `ArbitraryTypedObject` | Type of Portable Text payload |
14 |
15 | ## Properties
16 |
17 | ### value
18 |
19 | ```ts
20 | value: Value | Value[];
21 | ```
22 |
23 | Portable Text payload
24 |
25 | ***
26 |
27 | ### components?
28 |
29 | ```ts
30 | optional components: SomePortableTextComponents;
31 | ```
32 |
33 | Components for rendering
34 |
35 | ***
36 |
37 | ### onMissingComponent?
38 |
39 | ```ts
40 | optional onMissingComponent:
41 | | boolean
42 | | MissingComponentHandler;
43 | ```
44 |
45 | Function to call when faced with unknown types.
46 |
47 | #### Remarks
48 |
49 | - Prints a warning message to the console by default.
50 | - Use `false` to disable.
51 |
52 | ***
53 |
54 | ### listNestingMode?
55 |
56 | ```ts
57 | optional listNestingMode: ToolkitListNestMode;
58 | ```
59 |
60 | Defines the nesting mode for lists. The value can be `html` or `direct`, and defaults to `html`.
61 |
62 | #### Remarks
63 |
64 | - `html` - Deeper list nodes will appear as a child of the last list item in the parent list
65 | - `direct` - Deeper list nodes will appear as a direct child of the parent list
66 |
67 | #### See
68 |
69 | [ToolkitListNestMode](https://portabletext.github.io/toolkit/types/ToolkitListNestMode.html)
70 |
--------------------------------------------------------------------------------
/docs/getting-started.md:
--------------------------------------------------------------------------------
1 | [`astro-portabletext` • Documentation](README.md)
2 |
3 | ---
4 |
5 | # Getting started
6 |
7 | ## Installation
8 |
9 | Pick your favorite package manager and run one of these:
10 |
11 | ```bash
12 | npm install astro-portabletext
13 | # or
14 | pnpm add astro-portabletext
15 | # or
16 | yarn add astro-portabletext
17 | # or
18 | bun add astro-portabletext
19 | ```
20 |
21 | ## Usage
22 |
23 | ### `PortableText` component
24 |
25 | The `PortableText` component provides a simple and flexible way to render rich text content. It includes:
26 |
27 | - **Default rendering** for common Portable Text elements (paragraphs, headings, lists, etc.).
28 | - **Customizable rendering** with your own components or `slots`.
29 |
30 | #### Basic usage
31 |
32 | Here's a minimal example:
33 |
34 | ```js
35 | /* .astro */
36 | ---
37 | import { PortableText } from "astro-portabletext";
38 |
39 | const portableText = [
40 | {
41 | _type: "block",
42 | children: [
43 | {
44 | _type: "span",
45 | marks: [],
46 | text: "This is a ",
47 | },
48 | {
49 | _type: "span",
50 | marks: ["strong"],
51 | text: "bold",
52 | },
53 | {
54 | _type: "span",
55 | marks: [],
56 | text: " text example!",
57 | },
58 | ],
59 | markDefs: [],
60 | style: "normal",
61 | },
62 | ];
63 | ---
64 |
65 |
66 | ```
67 |
68 | > 📚 **Learn more:**
69 | >
70 | > For details on **custom components**, **slots**, and advanced usage, check out the [PortableText component documentation](portabletext-component.md).
71 |
--------------------------------------------------------------------------------
/lab/src/test/text.test.js:
--------------------------------------------------------------------------------
1 | import { suite } from "uvu";
2 | import * as assert from "uvu/assert";
3 | import { fetchContent } from "../utils.mjs";
4 |
5 | const text = suite("text");
6 |
7 | text("should have `hello world`", async () => {
8 | const $ = await fetchContent("text/default");
9 | const $el = $("p");
10 |
11 | assert.is($el.length, 1);
12 | assert.is($el.text(), "hello world");
13 | });
14 |
15 | text("should have `hello world` with undefined component", async () => {
16 | const $ = await fetchContent("text/undefined");
17 | const $el = $("p");
18 |
19 | assert.is($el.length, 1);
20 | assert.is($el.text(), "hello world");
21 | });
22 |
23 | text("should change joke", async () => {
24 | const $ = await fetchContent("text/replace");
25 | const $el = $("p");
26 |
27 | assert.is($el.length, 1);
28 | assert.is(
29 | $el.text(),
30 | "Why did the JavaScript developer quit his job? Because he didn't get callbacks."
31 | );
32 | });
33 |
34 | text("should style first word by string split", async () => {
35 | const $ = await fetchContent("text/style-by-split");
36 | const $head = $("head");
37 | const $p = $("p");
38 |
39 | assert.is($head.children("style").length, 1);
40 | assert.is($p.length, 1);
41 | assert.is($p.children("span").length, 1);
42 | assert.is($p.children("span").text(), "Yellow");
43 | });
44 |
45 | text("should style first word by index position", async () => {
46 | const $ = await fetchContent("text/style-by-index");
47 | const $head = $("head");
48 | const $p = $("p");
49 |
50 | assert.is($head.children("style").length, 1);
51 | assert.is($p.length, 1);
52 | assert.is($p.children("span").length, 1);
53 | assert.is($p.children("span").text(), "Green");
54 | });
55 |
56 | text.run();
57 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "root",
3 | "version": "0.0.0",
4 | "type": "module",
5 | "private": true,
6 | "author": "Tom Theisel ",
7 | "scripts": {
8 | "launch-demo": "astro --root demo dev",
9 | "install:all": "pnpm recursive install",
10 | "format": "prettier -w **/*.{astro,js,jsx,mjs,svelte,ts,tsx}",
11 | "lint": "npx eslint .",
12 | "prepare": "husky || true",
13 | "generate-docs": "typedoc",
14 | "test:ci": "pnpm --filter lab test:ci",
15 | "test": "pnpm --filter lab test"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "https://github.com/theisel/astro-portabletext.git"
20 | },
21 | "bugs": {
22 | "url": "https://github.com/theisel/astro-portabletext/issues"
23 | },
24 | "homepage": "https://github.com/theisel/astro-portabletext#readme",
25 | "devDependencies": {
26 | "@astrojs/check": "^0.9.5",
27 | "@changesets/cli": "^2.29.7",
28 | "@commitlint/cli": "^20.1.0",
29 | "@commitlint/config-conventional": "^20.0.0",
30 | "@eslint/eslintrc": "^3.3.1",
31 | "@eslint/js": "^9.39.1",
32 | "@types/node": "^24.10.0",
33 | "@typescript-eslint/eslint-plugin": "^8.46.4",
34 | "@typescript-eslint/parser": "^8.46.4",
35 | "eslint": "^9.39.1",
36 | "eslint-config-prettier": "^10.1.8",
37 | "eslint-plugin-prettier": "^5.5.4",
38 | "globals": "^16.5.0",
39 | "husky": "^9.1.7",
40 | "lint-staged": "^16.2.6",
41 | "prettier": "^3.6.2",
42 | "prettier-plugin-astro": "^0.14.1",
43 | "prettier-plugin-svelte": "^3.4.0",
44 | "typedoc": "^0.28.14",
45 | "typedoc-plugin-markdown": "^4.9.0",
46 | "typescript": "^5.9.3"
47 | },
48 | "engines": {
49 | "node": ">=24.0.0"
50 | },
51 | "packageManager": "pnpm@10.21.0"
52 | }
53 |
--------------------------------------------------------------------------------
/lab/src/test/slot.test.js:
--------------------------------------------------------------------------------
1 | import { suite } from "uvu";
2 | import * as assert from "uvu/assert";
3 | import { fetchContent } from "../utils.mjs";
4 |
5 | const slot = suite("extras");
6 |
7 | slot("block", async () => {
8 | const $ = await fetchContent("slot/block");
9 | const $el = $("p[data-slot='block']");
10 |
11 | assert.is($el.length, 1);
12 | });
13 |
14 | slot("custom block", async () => {
15 | const $ = await fetchContent("slot/block-custom");
16 | const $el = $("p[data-slot='custom-block']");
17 |
18 | assert.is($el.length, 1);
19 | });
20 |
21 | slot("list", async () => {
22 | const $ = await fetchContent("slot/list");
23 | const $el = $("ul[data-slot='list']");
24 |
25 | assert.is($el.length, 1);
26 | });
27 |
28 | slot("custom list", async () => {
29 | const $ = await fetchContent("slot/list-custom");
30 | const $el = $("ul[data-slot='custom-list']");
31 |
32 | assert.is($el.length, 1);
33 | });
34 |
35 | slot("listitem", async () => {
36 | const $ = await fetchContent("slot/listitem");
37 | const $el = $("li[data-slot='listitem']");
38 |
39 | assert.is($el.length, 1);
40 | });
41 |
42 | slot("custom listitem", async () => {
43 | const $ = await fetchContent("slot/listitem-custom");
44 | const $el = $("li[data-slot='custom-listitem']");
45 |
46 | assert.is($el.length, 1);
47 | assert.is($el.text(), "List Item 1");
48 | });
49 |
50 | slot("mark", async () => {
51 | const $ = await fetchContent("slot/mark");
52 | const $el = $("strong[data-slot='mark']");
53 |
54 | assert.is($el.length, 1);
55 | });
56 |
57 | slot("custom mark", async () => {
58 | const $ = await fetchContent("slot/mark-custom");
59 | const $el = $("strong[data-slot='custom-mark']");
60 |
61 | assert.is($el.length, 1);
62 | assert.is($el.text(), "bold");
63 | });
64 |
65 | slot("type", async () => {
66 | const $ = await fetchContent("slot/type");
67 | const $el = $("[data-slot='type']");
68 |
69 | assert.is($el.length, 1);
70 | assert.is($el.text(), "Hello World");
71 | });
72 |
73 | slot.run();
74 |
--------------------------------------------------------------------------------
/lab/src/test/mark.test.js:
--------------------------------------------------------------------------------
1 | import { suite } from "uvu";
2 | import * as assert from "uvu/assert";
3 | import { fetchContent } from "../utils.mjs";
4 |
5 | const mark = suite("mark");
6 |
7 | mark("code", async () => {
8 | const $ = await fetchContent("mark/code");
9 | const $el = $("code");
10 |
11 | assert.is($el.length, 1);
12 | assert.is($el.text(), "function test() {}");
13 | });
14 |
15 | mark("em", async () => {
16 | const $ = await fetchContent("mark/em");
17 | const $el = $("em");
18 |
19 | assert.is($el.length, 1);
20 | assert.is($el.text(), "emphasize");
21 | });
22 |
23 | mark("link", async () => {
24 | const $ = await fetchContent("mark/link");
25 | const $el = $("a");
26 |
27 | assert.is($el.length, 1);
28 | assert.is($el.attr("href"), "https://test.com/");
29 | assert.is($el.text(), "test.com");
30 | });
31 |
32 | mark("link_missing_href", async () => {
33 | const $ = await fetchContent("mark/link-missing-href");
34 | const $el = $("a");
35 |
36 | assert.is($el.length, 1);
37 | assert.is($el.attr("href"), undefined);
38 | assert.is($el.text(), "test.com");
39 | });
40 |
41 | mark("strike-through", async () => {
42 | const $ = await fetchContent("mark/strike-through");
43 | const $el = $("del");
44 |
45 | assert.is($el.length, 1);
46 | assert.is($el.text(), "deleted");
47 | });
48 |
49 | mark("strong", async () => {
50 | const $ = await fetchContent("mark/strong");
51 | const $el = $("strong");
52 |
53 | assert.is($el.length, 1);
54 | assert.is($el.text(), "bold");
55 | });
56 |
57 | mark("underline", async () => {
58 | const $ = await fetchContent("mark/underline");
59 | const $el = $("span");
60 |
61 | assert.is($el.length, 1);
62 | assert.is($el.attr("style"), "text-decoration: underline;");
63 | });
64 |
65 | mark("unknown", async () => {
66 | const $ = await fetchContent("mark/unknown");
67 | const $el = $("[data-portabletext-unknown]");
68 |
69 | assert.is($el.length, 1);
70 | assert.is($el.attr("data-portabletext-unknown"), "mark");
71 | assert.is($el.text(), "highlighted");
72 | assert.is($el[0].name, "span");
73 | });
74 |
75 | mark.run();
76 |
--------------------------------------------------------------------------------
/examples/portabletext-slots.astro:
--------------------------------------------------------------------------------
1 | ---
2 | /**
3 | * Works with `v0.11.0+`
4 | *
5 | * This example demonstrates how to use the `PortableText` component with `slots`.
6 | * Slots allow you to add attributes (e.g. classes) for styling or wrap elements.
7 | * It also shows how to use custom components with the `components` prop.
8 | *
9 | * Each slot receives:
10 | * - `Component`: The component to render (custom or default).
11 | * - `props`: Props for the component.
12 | * - `children` (optional): Children to render (only for block, list, listItem, mark) otherwise it's undefined.
13 | *
14 | * Available slot names:
15 | * - `type`
16 | * - `block`
17 | * - `list`
18 | * - `listItem`
19 | * - `mark`
20 | * - `text`
21 | * - `hardBreak`
22 | *
23 | * Note: There is no `default` slot.
24 | */
25 | // @ts-nocheck
26 | import { PortableText } from "astro-portabletext";
27 | import Type from "./Type.astro";
28 | import Block from "./BlockWithRenderFunction.astro";
29 |
30 | const portableText = [
31 | /* Portable Text payload */
32 | ];
33 |
34 | const components = {
35 | type: Type,
36 | block: Block,
37 | };
38 | ---
39 |
40 |
41 | {
43 | ({ Component /* custom component - Type */, props }) => (
44 |
45 | )
46 | }
48 | {
50 | ({ Component /* custom component - Block */, props, children }) => (
51 |
52 | {children}
53 |
54 | )
55 | }
57 | {
59 | ({
60 | Component /* default component from `astro-portabletext` */,
61 | props,
62 | children,
63 | }) => (
64 |
65 | {children}
66 |
67 | )
68 | }
70 |
71 |
72 |
83 |
--------------------------------------------------------------------------------
/docs/utility-functions.md:
--------------------------------------------------------------------------------
1 | [`astro-portabletext` • Documentation](README.md)
2 |
3 | ---
4 |
5 | # Utility functions
6 |
7 | This library provides utility functions to help you work with Portable Text content:
8 |
9 | ```js
10 | // v0.11.0+
11 | import {
12 | usePortableText,
13 | mergeComponents,
14 | toPlainText,
15 | spanToPlainText,
16 | } from "astro-portabletext";
17 |
18 | // Deprecated
19 | import {
20 | usePortableText,
21 | mergeComponents,
22 | toPlainText,
23 | } from "astro-portabletext/utils";
24 | ```
25 |
26 | ## `usePortableText`
27 |
28 | > **usePortableText**(`node`: [TypedObject](types/interfaces/TypedObject.md)): [Context](types/interfaces/Context.md)
29 |
30 | This function gives you access to helper functions like `render` (added in `v0.11.0`), which allows you to fine-tune the output of child nodes in your custom components. It should only be used within an Astro component that has been passed into the PortableText `components` prop. It follows a naming convention similar to React hooks, though it is not a hook as such.
31 |
32 | 💡 Refer to [BlockWithRenderFunction.astro](../examples/BlockWithRenderFunction.astro) example for guidance.
33 |
34 | ## `mergeComponents`
35 |
36 | > **mergeComponents**(`components`: [SomePortableTextComponents](types/type-aliases/SomePortableTextComponents.md), `overrideComponents`: [SomePortableTextComponents](types/type-aliases/SomePortableTextComponents.md)): `object`
37 |
38 | Combines two sets of `components` options, where `overrideComponents` takes precedence.
39 |
40 | ## `toPlainText`
41 |
42 | > **toPlainText**(`block`): `string`
43 |
44 | Extracts the text content from Portable Text blocks, preserving spacing.
45 |
46 | 💡 Refer to `@portabletext/toolkit` [toPlainText](https://portabletext.github.io/toolkit/functions/toPlainText.html) documentation for more details.
47 |
48 | ## `spanToPlainText`
49 |
50 | > **spanToPlainText**(`span`): `string`
51 |
52 |
53 | > Added in v0.11.0
54 |
55 |
56 | Returns plain text from a Portable Text span, useful for extracting text from nested nodes.
57 |
58 | 💡 Refer to `@portabletext/toolkit` [spanToPlainText](https://portabletext.github.io/toolkit/functions/spanToPlainText.html) documentation for more details.
59 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | # `astro-portabletext` Examples
2 |
3 | This directory provides concise code examples demonstrating specific techniques
4 | and features of the `astro-portabletext` library. These examples are designed to
5 | help you learn and adapt the code snippets for your own Astro projects.
6 |
7 | 💡 **Looking for documentation?** Refer to the [package documentation](../astro-portabletext/README.md).
8 |
9 | _**Note:** These are not full applications._
10 |
11 | ## Available examples
12 |
13 | ### Customizing Components
14 |
15 | - [Block.astro](Block.astro) — Shows how to extend and override the default rendering of specific block styles (`node.style`).
16 | - [List.astro](List.astro) / [ListItem.astro](ListItem.astro) — Covers extending and overriding the default rendering of specific list items (`node.listItem`).
17 | - [Mark.astro](Mark.astro) — Explains how to extend and override the rendering of specific mark types (`node.markType`).
18 | - [Text.astro](Text.astro) — `v0.11.0+` Illustrates handling the output of `@text` nodes in Portable Text.
19 | - [Type.astro](Type.astro) — Demonstrates handling custom Portable Text types (`node._type`) to render different components for each type, including a fallback for unknown types.
20 |
21 | ### PortableText component
22 |
23 | - [portabletext-basic.astro](portabletext-basic.astro) — Provides an example of the most basic usage.
24 | - [portabletext-mapped-type.astro](portabletext-mapped-type.astro) — Shows how to associate custom components to different node types.
25 | - [portabletext-mapped-type-property.astro](portabletext-mapped-type-property.astro) — Shows how to associate custom components to different node type properties.
26 | - [portabletext-slots.astro](portabletext-slots.astro) — `v0.11.0+` Illustrates using the component with slots for enhanced customization.
27 |
28 | ### Advanced techniques
29 |
30 | - [BlockWithRenderFunction.astro](BlockWithRenderFunction.astro) — `v0.11.0+` Shows how to use the `render` function from `usePortableText` to target and alter specific child nodes.
31 |
32 | ## Contributing
33 |
34 | Have suggestions for new examples or improvements? Feel free to open a [pull request](https://github.com/theisel/astro-portabletext/pulls) or an [issue](https://github.com/theisel/astro-portabletext/issues) in the main repository. Contributions are always welcome!
35 |
--------------------------------------------------------------------------------
/lab/src/lib/utils.test.ts:
--------------------------------------------------------------------------------
1 | import { suite } from "uvu";
2 | import * as assert from "uvu/assert";
3 | /**
4 | * Importing `mergeComponents` from `astro-portabletext/utils` is deprecated and
5 | * should be imported via `astro-portabletext` instead.
6 | * However, due to the `PortableText` Astro component being part of `astro-portabletext`,
7 | * it is not possible to import `mergeComponents` directly, as `tsm` throws an error.
8 | * Therefore, importing `mergeComponents` using a relative path to `lib/utils` is necessary
9 | * to mitigate the deprecation warning.
10 | */
11 | import { mergeComponents } from "../../../astro-portabletext/lib/utils";
12 |
13 | // ----------------------------------------------------------------------------
14 | // Test `mergeComponents`
15 | // ----------------------------------------------------------------------------
16 | const testMergeComponents = suite("mergeComponents");
17 |
18 | testMergeComponents("should merge components", () => {
19 | const a = {
20 | block: {
21 | h1: () => null,
22 | h2: () => null,
23 | },
24 | };
25 |
26 | const b = {
27 | block: {
28 | h2: () => null,
29 | },
30 | };
31 |
32 | const c = mergeComponents(a, b);
33 |
34 | assert.equal(c, { block: { h1: a.block.h1, h2: b.block.h2 } });
35 | });
36 |
37 | testMergeComponents("`block` should be a function", () => {
38 | const a = {
39 | block: {
40 | h1: () => null,
41 | h2: () => null,
42 | },
43 | };
44 |
45 | const b = {
46 | block: () => null,
47 | };
48 |
49 | const c = mergeComponents(a, b);
50 |
51 | assert.equal(c, { block: b.block });
52 | });
53 |
54 | testMergeComponents("`block` should be a plain object", () => {
55 | const a = {
56 | block: () => null,
57 | };
58 |
59 | const b = {
60 | block: {
61 | h1: () => null,
62 | h2: () => null,
63 | },
64 | };
65 |
66 | const c = mergeComponents(a, b);
67 |
68 | assert.equal(c, { block: { h1: b.block.h1, h2: b.block.h2 } });
69 | });
70 |
71 | testMergeComponents("should extend components", () => {
72 | const a = {
73 | block: () => null,
74 | mark: () => null,
75 | };
76 |
77 | const b = {
78 | type: () => null,
79 | };
80 |
81 | const c = mergeComponents(a, b);
82 |
83 | assert.equal(c, { block: a.block, mark: a.mark, type: b.type });
84 | });
85 |
86 | testMergeComponents.run();
87 |
--------------------------------------------------------------------------------
/astro-portabletext/lib/internal.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | Component,
3 | ComponentOrRecord,
4 | SomePortableTextComponents,
5 | } from "./types";
6 |
7 | /**
8 | * Helper for component to throw an error
9 | * @param err
10 | */
11 | export function throwError(err: Error | string): never {
12 | throw err;
13 | }
14 |
15 | /**
16 | * Returns true if `it` is component
17 | */
18 | export function isComponent(it: unknown): it is Component {
19 | return typeof it === "function";
20 | }
21 |
22 | /**
23 | * Merges two {@link SomePortableTextComponents} objects, giving priority to overrides.
24 | *
25 | * This function combines two component objects used in Portable Text rendering.
26 | * If both objects have the same key, the value from `overrides` takes precedence.
27 | * This is useful for customizing the rendering of specific components while keeping
28 | * the default behavior for others.
29 | *
30 | * @typeParam Components - The type of the base components object.
31 | * @typeParam Overrides - The type of the overrides components object.
32 | * @typeParam MergedComponents - The type of the resulting merged components object.
33 | *
34 | * @param components - The base components object.
35 | * @param overrides - The overrides components object.
36 | *
37 | * @returns A new object with the merged components.
38 | */
39 | export function mergeComponents<
40 | Components extends SomePortableTextComponents,
41 | Overrides extends SomePortableTextComponents,
42 | MergedComponents = {
43 | [Key in keyof (Components & Overrides)]: Key extends keyof (
44 | | Overrides
45 | | Components
46 | )
47 | ? Overrides[Key] extends Component
48 | ? Overrides[Key]
49 | : Components[Key] extends Component
50 | ? Overrides[Key]
51 | : (Overrides & Components)[Key]
52 | : (Overrides & Components)[Key];
53 | },
54 | >(components: Components, overrides: Overrides) {
55 | const cmps = { ...components } as Record;
56 |
57 | for (const [key, override] of Object.entries(overrides)) {
58 | const current = components[key as keyof typeof components];
59 |
60 | const value =
61 | !current || isComponent(override) || isComponent(current)
62 | ? override
63 | : {
64 | ...current,
65 | ...override,
66 | };
67 |
68 | cmps[key] = value;
69 | }
70 |
71 | return cmps as MergedComponents;
72 | }
73 |
--------------------------------------------------------------------------------
/docs/types/interfaces/PortableTextComponents.md:
--------------------------------------------------------------------------------
1 | [**`astro-portabletext` • Type Definitions**](../README.md)
2 |
3 | ***
4 |
5 | # Interface: PortableTextComponents
6 |
7 | Defines how Portable Text types should be rendered.
8 |
9 | ## Properties
10 |
11 | ### type
12 |
13 | ```ts
14 | type: ComponentOrRecord;
15 | ```
16 |
17 | Component or mapping of components for rendering `custom` types.
18 |
19 | ***
20 |
21 | ### unknownType
22 |
23 | ```ts
24 | unknownType: Component;
25 | ```
26 |
27 | Used when a [type](#type) component isn't found.
28 |
29 | ***
30 |
31 | ### block
32 |
33 | ```ts
34 | block: ComponentOrRecord;
35 | ```
36 |
37 | Component or mapping of components for rendering `block` styles.
38 |
39 | ***
40 |
41 | ### unknownBlock
42 |
43 | ```ts
44 | unknownBlock: Component;
45 | ```
46 |
47 | Used when a [block](#block) component isn't found.
48 |
49 | ***
50 |
51 | ### list
52 |
53 | ```ts
54 | list: ComponentOrRecord;
55 | ```
56 |
57 | Component or mapping of components for rendering `list` item type.
58 |
59 | ***
60 |
61 | ### unknownList
62 |
63 | ```ts
64 | unknownList: Component;
65 | ```
66 |
67 | Used when a [list](#list) component isn't found.
68 |
69 | ***
70 |
71 | ### listItem
72 |
73 | ```ts
74 | listItem: ComponentOrRecord;
75 | ```
76 |
77 | Component or mapping of components for rendering `list` item type.
78 |
79 | ***
80 |
81 | ### unknownListItem
82 |
83 | ```ts
84 | unknownListItem: Component;
85 | ```
86 |
87 | Used when a [listItem](#listitem) component isn't found.
88 |
89 | ***
90 |
91 | ### mark
92 |
93 | ```ts
94 | mark: ComponentOrRecord>;
95 | ```
96 |
97 | Component or mapping of components for rendering `mark` definition type.
98 |
99 | ***
100 |
101 | ### unknownMark
102 |
103 | ```ts
104 | unknownMark: Component>;
105 | ```
106 |
107 | Used when a [mark](#mark) component isn't found.
108 |
109 | ***
110 |
111 | ### text
112 |
113 | ```ts
114 | text: Component;
115 | ```
116 |
117 | Component for rendering `spans` of text.
118 |
119 | #### Remarks
120 |
121 | Added in: `v0.11.0`
122 |
123 | ***
124 |
125 | ### hardBreak
126 |
127 | ```ts
128 | hardBreak: Component;
129 | ```
130 |
131 | Component for rendering a newline `\n` of text.
132 |
--------------------------------------------------------------------------------
/demo/README.md:
--------------------------------------------------------------------------------
1 | # `astro-portabletext` Demo
2 |
3 | This demo shows how to use `astro-portabletext` to create richly customized content experiences in your Astro projects. Feel free to experiment with the code and adapt the techniques demonstrated here to your own needs.
4 |
5 | ## Features
6 |
7 | This demo highlights two key methods for customizing Portable Text content:
8 |
9 | 1. **Custom components:** Override the default rendering of Portable Text elements (e.g., `blocks`, `lists`, `marks`, etc.) by providing custom components through the `components` prop of the `PortableText` component. See the `src/components/portabletext` directory for examples.
10 | 2. **Slot-based customization:** Use Astro's named slot functions for granular control over specific Portable Text elements. This demo shows how to pass `data-*` attributes (e.g., `data-slot`) within these slots to enable targeted CSS styling, avoiding class name conflicts.
11 |
12 | ## Getting Started
13 |
14 | 1. **Prerequisites:**
15 | - Node.js (`>=20.19 <22` or `>=22.12`)
16 | - Astro (`>=4.6.0`)
17 |
18 | 2. **Installation:**
19 |
20 | Install the dependencies using your preferred package manager. For example:
21 |
22 | ```bash
23 | # npm
24 | npm install
25 | ```
26 |
27 | 3. **Development:**
28 |
29 | Start the Astro development server:
30 |
31 | ```bash
32 | # npm
33 | npm run dev
34 | ```
35 |
36 | Open your browser and navigate to the URL provided in the terminal (typically `http://localhost:4321`).
37 |
38 | ## Exploring the Demo
39 |
40 | The demo page (`src/pages/index.astro`) initially renders Portable Text content with custom components from `src/components/portabletext`.
41 |
42 | To explore slot-based customization:
43 |
44 | 1. Open `src/pages/index.astro`.
45 | 2. Locate the `` component.
46 | 3. Uncomment the `` or `` sections.
47 | 4. Observe the updated rendering in your browser.
48 |
49 | The CSS in `src/pages/index.astro` targets elements with the `data-slot` attribute, demonstrating how to apply specific styles predictably.
50 |
51 | ## Why use `data-*` attributes?
52 |
53 | While `class` attributes can be passed to custom components, `data-*` attributes offer key advantages:
54 |
55 | - **Predictability:**
56 |
57 | Custom components might override or ignore `class` attributes, but `data-*` attributes are reliably passed through to rendered elements.
58 |
59 | - **Conflict-free styling:**
60 |
61 | `data-*` attributes provide a dedicated mechanism for targeting specific elements, reducing the risk of conflicts with class names used internally or elsewhere in your application.
62 |
63 | ## Resources
64 |
65 | - **Documentation:** [Full documentation](https://github.com/theisel/astro-portabletext/blob/main/docs/README.md)
66 | - **Examples:** [Browse examples](https://github.com/theisel/astro-portabletext/blob/main/examples/README.md)
67 |
--------------------------------------------------------------------------------
/docs/types/interfaces/Mark.md:
--------------------------------------------------------------------------------
1 | [**`astro-portabletext` • Type Definitions**](../README.md)
2 |
3 | ***
4 |
5 | # Interface: Mark\
6 |
7 | Extends [ToolkitNestedPortableTextSpan](https://portabletext.github.io/toolkit/interfaces/ToolkitNestedPortableTextSpan.html)
8 | with consisting `markDef` and `markKey` properties
9 |
10 | ## Remarks
11 |
12 | To concisely achieve the same result in the example, use the convenience type [MarkProps](../type-aliases/MarkProps.md) instead.
13 |
14 | ## Example
15 |
16 | ```ts
17 | ---
18 | import type { Mark, Props as $ } from "astro-portabletext/types";
19 |
20 | type Greet = { msg: string };
21 | type Props = $>;
22 | ---
23 | ```
24 |
25 | ## Extends
26 |
27 | - `ToolkitNestedPortableTextSpan`
28 |
29 | ## Type Parameters
30 |
31 | | Type Parameter | Default type | Description |
32 | | ------ | ------ | ------ |
33 | | `MarkDef` *extends* `Record`\<`string`, `unknown`\> \| `undefined` | `undefined` | Defines the shape of `markDef` property |
34 |
35 | ## Properties
36 |
37 | ### \_type
38 |
39 | ```ts
40 | _type: "@span";
41 | ```
42 |
43 | Type name, prefixed with `@` to signal that this is a toolkit-specific node.
44 |
45 | #### Inherited from
46 |
47 | ```ts
48 | ToolkitNestedPortableTextSpan._type
49 | ```
50 |
51 | ***
52 |
53 | ### \_key?
54 |
55 | ```ts
56 | optional _key: string;
57 | ```
58 |
59 | Unique key for this span
60 |
61 | #### Inherited from
62 |
63 | ```ts
64 | ToolkitNestedPortableTextSpan._key
65 | ```
66 |
67 | ***
68 |
69 | ### markType
70 |
71 | ```ts
72 | markType: string;
73 | ```
74 |
75 | Type of the mark. For annotations, this is the `_type` property of the value.
76 | For decorators, it will hold the name of the decorator (strong, em or similar).
77 |
78 | #### Inherited from
79 |
80 | ```ts
81 | ToolkitNestedPortableTextSpan.markType
82 | ```
83 |
84 | ***
85 |
86 | ### children
87 |
88 | ```ts
89 | children: (
90 | | ArbitraryTypedObject
91 | | ToolkitTextNode
92 | | ToolkitNestedPortableTextSpan)[];
93 | ```
94 |
95 | Child nodes of this span. Can be toolkit-specific text nodes, nested spans
96 | or any inline object type.
97 |
98 | #### Inherited from
99 |
100 | ```ts
101 | ToolkitNestedPortableTextSpan.children
102 | ```
103 |
104 | ***
105 |
106 | ### markDef
107 |
108 | ```ts
109 | markDef: MarkDef & PortableTextMarkDefinition;
110 | ```
111 |
112 | Holds the value (definition) of the mark in the case of annotations.
113 | `undefined` if the mark is a decorator (strong, em or similar).
114 |
115 | #### Overrides
116 |
117 | ```ts
118 | ToolkitNestedPortableTextSpan.markDef
119 | ```
120 |
121 | ***
122 |
123 | ### markKey
124 |
125 | ```ts
126 | markKey: string;
127 | ```
128 |
129 | The key of the mark definition (in the case of annotations).
130 | `undefined` if the mark is a decorator (strong, em or similar).
131 |
132 | #### Overrides
133 |
134 | ```ts
135 | ToolkitNestedPortableTextSpan.markKey
136 | ```
137 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # astro-portabletext
6 |
7 | [](https://npmjs.com/package/astro-portabletext)
8 | [](https://npmjs.com/package/astro-portabletext)
9 | 
10 |
11 | A flexible and customizable library for rendering [Portable Text](https://portabletext.org) content in [Astro](https://astro.build) projects.
12 |
13 | ## 🚀 Features
14 |
15 | - **Core components** for common Portable Text elements.
16 | - **Customizable rendering** using `slots` or your own `components`.
17 | - **Flexible control** with the `render` function via `usePortableText`.
18 | - **Built with** TypeScript support.
19 |
20 | ## 🎮 Demonstration
21 |
22 | Jump in and see it in action:
23 |
24 |
32 |
33 | ## 📖 Resources
34 |
35 | - **Installation & usage docs:** [Read the full documentation](docs/README.md)
36 | - **TypeScript types:** [Type definitions](docs/types/README.md)
37 | - **Examples:** [Browse practical examples](examples/README.md)
38 |
39 | **Versions:**
40 |
41 | - [Latest stable version (v0.12.0)](https://github.com/theisel/astro-portabletext/tree/astro-portabletext%400.12.0/astro-portabletext "astro-portabletext v0.12.0 documentation")
42 | - [Development branch](astro-portabletext/README.md "astro-portabletext main branch documentation")
43 |
44 | ##  Sanity Integration
45 |
46 | This library is [officially recommended](https://www.sanity.io/plugins/sanity-astro#rendering-rich-text-and-block-content-with-portable-text) by [Sanity](https://sanity.io) for rendering Portable Text in Astro projects.
47 |
48 | Helpful resources:
49 |
50 | - [Sanity Integration for Astro](https://www.sanity.io/plugins/sanity-astro)
51 | - [Guide: Building a Blog with Sanity and Astro](https://www.sanity.io/guides/sanity-astro-blog)
52 |
53 | ## 🙌 Contributing
54 |
55 | We welcome contributions to improve `astro-portabletext`!
56 |
57 | If you find a bug or have a feature request, please open an [issue](https://github.com/theisel/astro-portabletext/issues).
58 | If you'd like to contribute code, feel free to submit a [pull request](https://github.com/theisel/astro-portabletext/pulls).
59 |
60 | ## 📄 License
61 |
62 | This project is licensed under the [ISC License](https://github.com/theisel/astro-portabletext/blob/main/LICENSE).
63 |
--------------------------------------------------------------------------------
/docs/types/interfaces/Context.md:
--------------------------------------------------------------------------------
1 | [**`astro-portabletext` • Type Definitions**](../README.md)
2 |
3 | ***
4 |
5 | # Interface: Context
6 |
7 | Context object returned by `usePortableText`, providing utilities for rendering and customizing Portable Text components.
8 |
9 | The `Context` type includes functions to retrieve default or unknown components and
10 | to customize rendering behavior for specific node types.
11 |
12 | ## Properties
13 |
14 | ### getDefaultComponent()
15 |
16 | ```ts
17 | getDefaultComponent: () => Component;
18 | ```
19 |
20 | Retrieves the default `astro-portabletext` component associated with a Portable Text node.
21 |
22 | #### Returns
23 |
24 | [`Component`](../type-aliases/Component.md)
25 |
26 | The default component for the node, such as `Block`, `List`, etc.
27 |
28 | #### Example
29 |
30 | ```ts
31 | ---
32 | const { getDefaultComponent } = usePortableText(node);
33 | const Component = getDefaultComponent();
34 | ---
35 |
36 |
37 |
38 | ```
39 |
40 | ***
41 |
42 | ### getUnknownComponent()
43 |
44 | ```ts
45 | getUnknownComponent: () => Component;
46 | ```
47 |
48 | Retrieves the `unknown` component associated with a Portable Text node.
49 |
50 | #### Returns
51 |
52 | [`Component`](../type-aliases/Component.md)
53 |
54 | The component used for unknown nodes, such as `unknownBlock` or `unknownList`.
55 |
56 | #### Example
57 |
58 | ```ts
59 | ---
60 | const { getUnknownComponent } = usePortableText(node);
61 | const Component = getUnknownComponent();
62 | ---
63 |
64 |
65 |
66 | ```
67 |
68 | ***
69 |
70 | ### render()
71 |
72 | ```ts
73 | render: (options) => any;
74 | ```
75 |
76 | Customizes rendering for specific Portable Text node types.
77 |
78 | The `render` function enables developers to define custom behavior for specific node types,
79 | such as overriding the default text or mark rendering.
80 |
81 | #### Parameters
82 |
83 | | Parameter | Type | Description |
84 | | ------ | ------ | ------ |
85 | | `options` | [`RenderOptions`](../type-aliases/RenderOptions.md) | [RenderOptions](../type-aliases/RenderOptions.md) - Configuration for customizing node rendering |
86 |
87 | #### Returns
88 |
89 | `any`
90 |
91 | The desired output for the Portable Text node
92 |
93 | #### Remarks
94 |
95 | Added in: `v0.11.0`
96 |
97 | #### Example
98 |
99 | ```ts
100 | ---
101 | import { usePortableText } from "astro-portabletext";
102 |
103 | const { node } = Astro.props;
104 | const { getDefaultComponent, render } = usePortableText(node);
105 | const Component = getDefaultComponent();
106 | ---
107 |
108 | {render({
109 | text: ({ props }) => props.node.text.toUpperCase(),
110 | mark: ({ Component, props, children }) => (
111 | {children}
112 | ),
113 |
114 |
115 |
120 | ```
121 |
--------------------------------------------------------------------------------
/docs/types/interfaces/Block.md:
--------------------------------------------------------------------------------
1 | [**`astro-portabletext` • Type Definitions**](../README.md)
2 |
3 | ***
4 |
5 | # Interface: Block
6 |
7 | Alias to [PortableTextBlock](https://portabletext.github.io/types/interfaces/PortableTextBlock.html)
8 | with `style` set to `normal` when undefined
9 |
10 | ## Example
11 |
12 | ```ts
13 | ---
14 | import type { Block, Props as $ } from "astro-portabletext/types";
15 |
16 | type Props = $;
17 | ---
18 | ```
19 |
20 | ## Remarks
21 |
22 | To concisely achieve the same result in the example, use the convenience type [BlockProps](../type-aliases/BlockProps.md) instead.
23 |
24 | ## Extends
25 |
26 | - `PortableTextBlock`
27 |
28 | ## Properties
29 |
30 | ### \_type
31 |
32 | ```ts
33 | _type: string;
34 | ```
35 |
36 | Type name identifying this as a portable text block.
37 | All items within a portable text array should have a `_type` property.
38 |
39 | Usually 'block', but can be customized to other values
40 |
41 | #### Inherited from
42 |
43 | ```ts
44 | PortableTextBlock._type
45 | ```
46 |
47 | ***
48 |
49 | ### \_key?
50 |
51 | ```ts
52 | optional _key: string;
53 | ```
54 |
55 | A key that identifies this block uniquely within the parent array. Used to more easily address
56 | the block when editing collaboratively, but is also very useful for keys inside of React and
57 | other rendering frameworks that can use keys to optimize operations.
58 |
59 | #### Inherited from
60 |
61 | ```ts
62 | PortableTextBlock._key
63 | ```
64 |
65 | ***
66 |
67 | ### children
68 |
69 | ```ts
70 | children: (ArbitraryTypedObject | PortableTextSpan)[];
71 | ```
72 |
73 | Array of inline items for this block. Usually contain text spans, but can be
74 | configured to include inline objects of other types as well.
75 |
76 | #### Inherited from
77 |
78 | ```ts
79 | PortableTextBlock.children
80 | ```
81 |
82 | ***
83 |
84 | ### markDefs?
85 |
86 | ```ts
87 | optional markDefs: PortableTextMarkDefinition[];
88 | ```
89 |
90 | Array of mark definitions used in child text spans. By having them be on the block level,
91 | the same mark definition can be reused for multiple text spans, which is often the case
92 | with nested marks.
93 |
94 | #### Inherited from
95 |
96 | ```ts
97 | PortableTextBlock.markDefs
98 | ```
99 |
100 | ***
101 |
102 | ### listItem?
103 |
104 | ```ts
105 | optional listItem: string;
106 | ```
107 |
108 | If this block is a list item, identifies which style of list item this is
109 | Common values: 'bullet', 'number', but can be configured
110 |
111 | #### Inherited from
112 |
113 | ```ts
114 | PortableTextBlock.listItem
115 | ```
116 |
117 | ***
118 |
119 | ### level?
120 |
121 | ```ts
122 | optional level: number;
123 | ```
124 |
125 | If this block is a list item, identifies which level of nesting it belongs within
126 |
127 | #### Inherited from
128 |
129 | ```ts
130 | PortableTextBlock.level
131 | ```
132 |
133 | ***
134 |
135 | ### style
136 |
137 | ```ts
138 | style: string;
139 | ```
140 |
141 | Visual style of the block
142 | Common values: 'normal', 'blockquote', 'h1'...'h6'
143 |
144 | #### Overrides
145 |
146 | ```ts
147 | PortableTextBlock.style
148 | ```
149 |
--------------------------------------------------------------------------------
/docs/types/README.md:
--------------------------------------------------------------------------------
1 | **`astro-portabletext` • Type Definitions**
2 |
3 | ***
4 |
5 | # Type Definitions
6 |
7 | ## Interfaces
8 |
9 | | Interface | Description |
10 | | ------ | ------ |
11 | | [TypedObject](interfaces/TypedObject.md) | Any object with an `_type` property (which is required in portable text arrays), as well as a _potential_ `_key` (highly encouraged) |
12 | | [PortableTextProps](interfaces/PortableTextProps.md) | Properties for the `PortableText` component |
13 | | [PortableTextComponents](interfaces/PortableTextComponents.md) | Defines how Portable Text types should be rendered. |
14 | | [Props](interfaces/Props.md) | Component Props |
15 | | [Block](interfaces/Block.md) | Alias to [PortableTextBlock](https://portabletext.github.io/types/interfaces/PortableTextBlock.html) with `style` set to `normal` when undefined |
16 | | [Mark](interfaces/Mark.md) | Extends [ToolkitNestedPortableTextSpan](https://portabletext.github.io/toolkit/interfaces/ToolkitNestedPortableTextSpan.html) with consisting `markDef` and `markKey` properties |
17 | | [Context](interfaces/Context.md) | Context object returned by `usePortableText`, providing utilities for rendering and customizing Portable Text components. |
18 |
19 | ## Type Aliases
20 |
21 | | Type Alias | Description |
22 | | ------ | ------ |
23 | | [SomePortableTextComponents](type-aliases/SomePortableTextComponents.md) | Defines how some Portable Text types should be rendered. |
24 | | [BlockProps](type-aliases/BlockProps.md) | Convenience type for [Block](interfaces/Block.md) component props |
25 | | [List](type-aliases/List.md) | Alias to [ToolkitPortableTextList](https://portabletext.github.io/toolkit/types/ToolkitPortableTextList.html) |
26 | | [ListProps](type-aliases/ListProps.md) | Convenience type for [List](type-aliases/List.md) component props |
27 | | [ListItem](type-aliases/ListItem.md) | Alias to [ToolkitPortableTextListItem](https://portabletext.github.io/toolkit/interfaces/ToolkitPortableTextListItem.html) |
28 | | [ListItemProps](type-aliases/ListItemProps.md) | Convenience type for [ListItem](type-aliases/ListItem.md) component props |
29 | | [MarkProps](type-aliases/MarkProps.md) | Convenience type for [Mark](interfaces/Mark.md) component props |
30 | | [TextNode](type-aliases/TextNode.md) | Alias to [ToolkitTextNode](https://portabletext.github.io/toolkit/interfaces/ToolkitTextNode.html) |
31 | | [TextNodeProps](type-aliases/TextNodeProps.md) | Convenience type for [TextNode](type-aliases/TextNode.md) component props |
32 | | [MissingComponentHandler](type-aliases/MissingComponentHandler.md) | The shape of the [onMissingComponent](interfaces/PortableTextProps.md#onmissingcomponent) function |
33 | | [RenderHandlerProps](type-aliases/RenderHandlerProps.md) | Properties for the `RenderHandler` function |
34 | | [RenderHandler](type-aliases/RenderHandler.md) | The shape of the render component function |
35 | | [RenderOptions](type-aliases/RenderOptions.md) | Options for the `render` function accessed via `usePortableText` |
36 | | [Component](type-aliases/Component.md) | Generic Portable Text component |
37 | | [ComponentOrRecord](type-aliases/ComponentOrRecord.md) | Defines a component or a mapping of components |
38 | | [NodeType](type-aliases/NodeType.md) | Defines the type of Portable Text node |
39 |
--------------------------------------------------------------------------------
/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/lab/src/test/block.test.js:
--------------------------------------------------------------------------------
1 | import { suite } from "uvu";
2 | import * as assert from "uvu/assert";
3 | import { fetchContent } from "../utils.mjs";
4 |
5 | const block = suite("block");
6 |
7 | block("with style `h1`", async () => {
8 | const $ = await fetchContent("block/h1");
9 | const $el = $("h1");
10 |
11 | assert.is($el.length, 1);
12 | assert.is($el.text(), "Heading L1");
13 | });
14 |
15 | block("with style `h2`", async () => {
16 | const $ = await fetchContent("block/h2");
17 | const $el = $("h2");
18 |
19 | assert.is($el.length, 1);
20 | assert.is($el.text(), "Heading L2");
21 | });
22 |
23 | block("with style `h3`", async () => {
24 | const $ = await fetchContent("block/h3");
25 | const $el = $("h3");
26 |
27 | assert.is($el.length, 1);
28 | assert.is($el.text(), "Heading L3");
29 | });
30 |
31 | block("with style `h4`", async () => {
32 | const $ = await fetchContent("block/h4");
33 | const $el = $("h4");
34 |
35 | assert.is($el.length, 1);
36 | assert.is($el.text(), "Heading L4");
37 | });
38 |
39 | block("with style `h5`", async () => {
40 | const $ = await fetchContent("block/h5");
41 | const $el = $("h5");
42 |
43 | assert.is($el.length, 1);
44 | assert.is($el.text(), "Heading L5");
45 | });
46 |
47 | block("with style `h6`", async () => {
48 | const $ = await fetchContent("block/h6");
49 | const $el = $("h6");
50 |
51 | assert.is($el.length, 1);
52 | assert.is($el.text(), "Heading L6");
53 | });
54 |
55 | block("custom-handler", async () => {
56 | const $ = await fetchContent("block/custom-handler");
57 | const $el = $("[data-portabletext-unknown]");
58 |
59 | assert.is($el.length, 1);
60 | assert.is($el.attr("data-portabletext-unknown"), "block");
61 | });
62 |
63 | block("default-handler", async () => {
64 | const $ = await fetchContent("block/default-handler");
65 | const $el = $("[data-portabletext-unknown]");
66 |
67 | assert.is($el.length, 1);
68 | assert.is($el.attr("data-portabletext-unknown"), "block");
69 | });
70 |
71 | block("with style `blockquote`", async () => {
72 | const $ = await fetchContent("block/blockquote");
73 | const $el = $("blockquote");
74 | const $el2 = $el.children("p");
75 |
76 | assert.is($el.length, 1);
77 | assert.is($el.children().length, 1);
78 | assert.is($el2.length, 1);
79 | assert.is($el2.text(), "Quote");
80 | });
81 |
82 | block("with style `normal`", async () => {
83 | const $ = await fetchContent("block/normal");
84 | const $el = $("p");
85 |
86 | assert.is($el.length, 1);
87 | assert.is($el.text(), "I'm a paragraph");
88 | });
89 |
90 | block("missing style", async () => {
91 | const $ = await fetchContent("block/missing-style");
92 | const $el = $("p");
93 |
94 | assert.is($el.length, 1);
95 | assert.is($el.text(), "I'm a paragraph");
96 | });
97 |
98 | block("with style", async () => {
99 | const $ = await fetchContent("block/with-style");
100 | const $el = $("p").get(0);
101 |
102 | assert.ok($el);
103 | assert.not($el.attribs.class?.indexOf("astro-"), -1);
104 | });
105 |
106 | block("unknown", async () => {
107 | const $ = await fetchContent("block/unknown");
108 | const $el = $("[data-portabletext-unknown]");
109 |
110 | assert.is($el.length, 1);
111 | assert.is($el.attr("data-portabletext-unknown"), "block");
112 | assert.is($el[0].name, "p");
113 | });
114 |
115 | block("override", async () => {
116 | const $ = await fetchContent("block/override");
117 | const $el = $("[data-myh1-cmp]");
118 |
119 | assert.is($el.length, 1);
120 | });
121 |
122 | block("merge", async () => {
123 | const $ = await fetchContent("block/merge");
124 | const $el = $("[data-grid-cmp]");
125 |
126 | assert.is($el.length, 1);
127 | });
128 |
129 | block("block index", async () => {
130 | const $ = await fetchContent("block/block-index");
131 | const $el = $("[data-block-index]");
132 |
133 | assert.is($el.length, 3);
134 | assert.is($el.eq(0).attr("data-block-index"), "0");
135 | assert.is($el.eq(1).attr("data-block-index"), "1");
136 | assert.is($el.eq(2).attr("data-block-index"), "2");
137 | });
138 |
139 | block.run();
140 |
--------------------------------------------------------------------------------
/demo/src/data/portabletext.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "_type": "block",
4 | "style": "h1",
5 | "children": [
6 | {
7 | "_type": "span",
8 | "text": "Heading 1"
9 | }
10 | ]
11 | },
12 | {
13 | "_type": "block",
14 | "style": "h2",
15 | "children": [
16 | {
17 | "_type": "span",
18 | "text": "Heading 2"
19 | }
20 | ]
21 | },
22 | {
23 | "_type": "block",
24 | "style": "h3",
25 | "children": [
26 | {
27 | "_type": "span",
28 | "text": "Heading 3"
29 | }
30 | ]
31 | },
32 | {
33 | "_type": "block",
34 | "style": "h4",
35 | "children": [
36 | {
37 | "_type": "span",
38 | "text": "Heading 4"
39 | }
40 | ]
41 | },
42 | {
43 | "_type": "block",
44 | "style": "h5",
45 | "children": [
46 | {
47 | "_type": "span",
48 | "text": "Heading 5"
49 | }
50 | ]
51 | },
52 | {
53 | "_type": "block",
54 | "style": "h6",
55 | "children": [
56 | {
57 | "_type": "span",
58 | "text": "Heading 6"
59 | }
60 | ]
61 | },
62 | {
63 | "_type": "block",
64 | "style": "blockquote",
65 | "children": [
66 | {
67 | "_type": "span",
68 | "text": "metus vulputate eu scelerisque felis imperdiet proin "
69 | },
70 | {
71 | "_type": "span",
72 | "marks": ["link"],
73 | "text": "fermentum"
74 | },
75 | {
76 | "_type": "span",
77 | "text": " leo vel orci porta non pulvinar neque laoreet suspendisse interdum consectetur libero id faucibus nisl tincidunt eget nullam non nisi est sit"
78 | }
79 | ],
80 | "markDefs": [
81 | {
82 | "_key": "link",
83 | "_type": "link",
84 | "href": "#"
85 | }
86 | ]
87 | },
88 | {
89 | "_type": "block",
90 | "style": "normal",
91 | "children": [
92 | {
93 | "_type": "span",
94 | "marks": ["strong"],
95 | "text": "posuere morbi"
96 | },
97 | {
98 | "_type": "span",
99 | "text": " leo urna "
100 | },
101 | {
102 | "_type": "jsxCounter"
103 | },
104 | {
105 | "_type": "span",
106 | "marks": ["em"],
107 | "text": " molestie at elementum"
108 | },
109 | {
110 | "_type": "span",
111 | "text": " eu facilisis "
112 | },
113 | {
114 | "_type": "span",
115 | "marks": ["strike-through"],
116 | "text": "sed odio"
117 | },
118 | {
119 | "_type": "span",
120 | "text": " morbi quis "
121 | },
122 | {
123 | "_type": "span",
124 | "marks": ["underline"],
125 | "text": "commodo odio"
126 | },
127 | {
128 | "_type": "span",
129 | "text": " aenean sed adipiscing diam donec adipiscing "
130 | },
131 | {
132 | "_type": "span",
133 | "marks": ["b234c4839a00"],
134 | "text": " unicorn "
135 | },
136 | {
137 | "_type": "span",
138 | "text": " tristique risus nec feugiat in \nfermentum posuere urna nec"
139 | }
140 | ],
141 | "markDefs": [
142 | {
143 | "_type": "unicorn",
144 | "_key": "b234c4839a00"
145 | }
146 | ]
147 | },
148 | {
149 | "_type": "block",
150 | "style": "normal",
151 | "children": [
152 | {
153 | "_type": "span",
154 | "marks": ["code"],
155 | "text": "const hey = () => \"there\""
156 | }
157 | ]
158 | },
159 | {
160 | "_type": "block",
161 | "style": "normal",
162 | "level": 1,
163 | "listItem": "bullet",
164 | "children": [
165 | {
166 | "_type": "span",
167 | "text": "Unordered list item 1"
168 | }
169 | ]
170 | },
171 | {
172 | "_type": "block",
173 | "style": "normal",
174 | "level": 1,
175 | "listItem": "bullet",
176 | "children": [
177 | {
178 | "_type": "span",
179 | "text": "Unordered list item 2"
180 | }
181 | ]
182 | },
183 | {
184 | "_type": "block",
185 | "style": "normal",
186 | "level": 1,
187 | "listItem": "number",
188 | "children": [
189 | {
190 | "_type": "span",
191 | "text": "Ordered list item 1"
192 | }
193 | ]
194 | },
195 | {
196 | "_type": "block",
197 | "style": "normal",
198 | "level": 1,
199 | "listItem": "number",
200 | "children": [
201 | {
202 | "_type": "span",
203 | "text": "Ordered list item 2"
204 | }
205 | ]
206 | },
207 | {
208 | "_type": "block",
209 | "style": "normal",
210 | "level": 1,
211 | "listItem": "star",
212 | "children": [
213 | {
214 | "_type": "span",
215 | "text": "Starred list item 1"
216 | }
217 | ]
218 | },
219 | {
220 | "_type": "block",
221 | "style": "normal",
222 | "level": 1,
223 | "listItem": "star",
224 | "children": [
225 | {
226 | "_type": "span",
227 | "text": "Starred list item 2"
228 | }
229 | ]
230 | },
231 | {
232 | "_type": "jsxCounter"
233 | },
234 | {
235 | "_type": "svelteCounter"
236 | },
237 |
238 | {
239 | "_type": "block",
240 | "style": "fancyBorder",
241 | "children": [
242 | {
243 | "_type": "span",
244 | "text": "What a fancy border!"
245 | },
246 | {
247 | "_type": "span",
248 | "text": " 👊"
249 | }
250 | ]
251 | }
252 | ]
253 |
--------------------------------------------------------------------------------
/demo/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ### Dependencies
4 |
5 | * The following workspace dependencies were updated
6 | * dependencies
7 | * astro-portabletext bumped from ^0.9.6 to ^0.9.7
8 |
9 | ### Dependencies
10 |
11 | * The following workspace dependencies were updated
12 | * dependencies
13 | * astro-portabletext bumped from ^0.9.7 to ^0.9.8
14 |
15 | ## [0.2.0](https://github.com/theisel/astro-portabletext/compare/demo@0.1.5...demo@0.2.0) (2024-05-07)
16 |
17 |
18 | ### Features
19 |
20 | * add component, add mark definition to portabletext, including fixes and refactoring ([#144](https://github.com/theisel/astro-portabletext/issues/144)) ([e779758](https://github.com/theisel/astro-portabletext/commit/e779758ddc48114f79a377f7d6ad3f9429f9ce61))
21 |
22 |
23 | ### Dependencies
24 |
25 | * The following workspace dependencies were updated
26 | * dependencies
27 | * astro-portabletext bumped from ^0.9.9 to ^0.10.0
28 |
29 | ## [0.1.5](https://github.com/theisel/astro-portabletext/compare/demo@0.1.4...demo@0.1.5) (2024-04-21)
30 |
31 |
32 | ### Dependencies
33 |
34 | * The following workspace dependencies were updated
35 | * dependencies
36 | * astro-portabletext bumped from ^0.9.8 to ^0.9.9
37 |
38 | ## [0.1.2](https://github.com/theisel/astro-portabletext/compare/demo@0.1.1...demo@0.1.2) (2023-12-28)
39 |
40 |
41 | ### Bug Fixes
42 |
43 | * **deps:** update `demo` dependencies ([#121](https://github.com/theisel/astro-portabletext/issues/121)) ([1494739](https://github.com/theisel/astro-portabletext/commit/1494739f109cc86ded379daf50b5f06aee517a33))
44 |
45 | ## [0.1.1](https://github.com/theisel/astro-portabletext/compare/demo@0.1.0...demo@0.1.1) (2023-12-12)
46 |
47 |
48 | ### Bug Fixes
49 |
50 | * **deps:** update `demo` dependencies ([#118](https://github.com/theisel/astro-portabletext/issues/118)) ([2b7e358](https://github.com/theisel/astro-portabletext/commit/2b7e358996bd64a16663f46b22b43badb4a45bbb))
51 |
52 | ## [0.1.0](https://github.com/theisel/astro-portabletext/compare/demo@0.0.10...demo@0.1.0) (2023-12-02)
53 |
54 |
55 | ### Features
56 |
57 | * **deps:** update `demo` dependencies ([#115](https://github.com/theisel/astro-portabletext/issues/115)) ([0f5c228](https://github.com/theisel/astro-portabletext/commit/0f5c22814dbbe9150288d48046e62a9d5e914453))
58 |
59 | ## [0.0.10](https://github.com/theisel/astro-portabletext/compare/demo@0.0.9...demo@0.0.10) (2023-11-11)
60 |
61 |
62 | ### Bug Fixes
63 |
64 | * **deps:** update `demo` dependencies ([#112](https://github.com/theisel/astro-portabletext/issues/112)) ([f59d515](https://github.com/theisel/astro-portabletext/commit/f59d51593390d8663db53add94a9c767e2fab937))
65 |
66 | ## [0.0.9](https://github.com/theisel/astro-portabletext/compare/demo@0.0.8...demo@0.0.9) (2023-10-21)
67 |
68 |
69 | ### Bug Fixes
70 |
71 | * **demo:** update dependencies ([#109](https://github.com/theisel/astro-portabletext/issues/109)) ([9042b92](https://github.com/theisel/astro-portabletext/commit/9042b92d0a50e0270cd6b4a08bfb258912eaae4f))
72 |
73 | ## [0.0.8](https://github.com/theisel/astro-portabletext/compare/demo@0.0.7...demo@0.0.8) (2023-10-13)
74 |
75 |
76 | ### Bug Fixes
77 |
78 | * **demo:** update dependencies ([#105](https://github.com/theisel/astro-portabletext/issues/105)) ([213686c](https://github.com/theisel/astro-portabletext/commit/213686ca3892e9de7dc0de045f7dfaa05f68e7b0))
79 |
80 |
81 | ### Dependencies
82 |
83 | * The following workspace dependencies were updated
84 | * dependencies
85 | * astro-portabletext bumped from ^0.9.5 to ^0.9.6
86 |
87 | ## [0.0.7](https://github.com/theisel/astro-portabletext/compare/demo@0.0.6...demo@0.0.7) (2023-10-11)
88 |
89 |
90 | ### Bug Fixes
91 |
92 | * **demo:** add `is:global` directive to style ([#97](https://github.com/theisel/astro-portabletext/issues/97)) ([1b42231](https://github.com/theisel/astro-portabletext/commit/1b422312a11cad3542be0c520cbcc9ec534ed80e))
93 | * **demo:** fix import declaration ([#95](https://github.com/theisel/astro-portabletext/issues/95)) ([e2168b7](https://github.com/theisel/astro-portabletext/commit/e2168b7399d1366c36c2a9d193e04cee694b5f97))
94 | * **demo:** fix PortableText is not defined ([#96](https://github.com/theisel/astro-portabletext/issues/96)) ([6765eae](https://github.com/theisel/astro-portabletext/commit/6765eaec24f7f0fc887bb1075869fafe5464a6f1))
95 | * **demo:** update `demo` package ([#93](https://github.com/theisel/astro-portabletext/issues/93)) ([3efa450](https://github.com/theisel/astro-portabletext/commit/3efa450c86681a504765af75910a550fc4dd66d6))
96 |
97 |
98 | ### Dependencies
99 |
100 | * The following workspace dependencies were updated
101 | * dependencies
102 | * astro-portabletext bumped from ^0.9.4 to ^0.9.5
103 |
104 | ## [0.0.6](https://github.com/theisel/astro-portabletext/compare/demo@0.0.5...demo@0.0.6) (2023-08-24)
105 |
106 | Bumped version to force new release as lockfile was "not up to date" ([#82](https://github.com/theisel/astro-portabletext/issues/82)) ([8504f8f](https://github.com/theisel/astro-portabletext/commit/8504f8fcd19a77518975acbce1ae4b848f503e59))
107 |
108 | ### Dependencies
109 |
110 | - The following workspace dependencies were updated
111 | - dependencies
112 | - astro-portabletext bumped from ^0.9.3 to ^0.9.4
113 |
114 | ## [0.0.5](https://github.com/theisel/astro-portabletext/compare/demo@0.0.4...demo@0.0.5) (2023-08-24)
115 |
116 | Bumped version to force new release as lockfile was "not up to date" ([#80](https://github.com/theisel/astro-portabletext/issues/80)) ([a50bc83](https://github.com/theisel/astro-portabletext/commit/a50bc8391ae656bb202d72da17d0830e11c3c480))
117 |
118 | ### Dependencies
119 |
120 | - The following workspace dependencies were updated
121 | - dependencies
122 | - astro-portabletext bumped from ^0.9.2 to ^0.9.3
123 |
124 | ## [0.0.4](https://github.com/theisel/astro-portabletext/compare/demo-v0.0.3...demo@0.0.4) (2023-08-24)
125 |
126 | ### Bug Fixes
127 |
128 | - **deps:** update `demo` dependencies ([#72](https://github.com/theisel/astro-portabletext/issues/72)) ([c33800e](https://github.com/theisel/astro-portabletext/commit/c33800eb098379ae9766783eee0bda8b8b19f1a0))
129 |
130 | ### Dependencies
131 |
132 | - The following workspace dependencies were updated
133 | - dependencies
134 | - astro-portabletext bumped from ^0.9.1 to ^0.9.2
135 |
--------------------------------------------------------------------------------
/docs/portabletext-component.md:
--------------------------------------------------------------------------------
1 | [`astro-portabletext` • Documentation](README.md)
2 |
3 | ---
4 |
5 | # PortableText component
6 |
7 | ```js
8 | import { PortableText } from "astro-portabletext";
9 | ```
10 |
11 | This component provides a simple and flexible way to display rich text, from
12 | using `slots` to custom `components`.
13 |
14 | ## Examples
15 |
16 | - [Basic example](../examples/portabletext-basic.astro)
17 | - Custom component [mapped to node type](../examples/portabletext-mapped-type.astro)
18 | - Custom component [mapped to node type property](../examples/portabletext-mapped-type-property.astro)
19 | - `v0.11.0+` Using PortableText component with [slots](../examples/portabletext-slots.astro)
20 |
21 | ## Basic usage
22 |
23 | Import the `PortableText` component and start rendering. This library provides sensible defaults for rendering common Portable Text elements, which you can easily override.
24 |
25 | > Use the following default mapping to understand what each node type outputs.
26 |
27 |
28 | View the default structure and output
29 |
30 | ```js
31 | {
32 | type: {
33 | /* Custom types go here */
34 | },
35 | block: {
36 | h1: /* */,
37 | h2: /* */,
38 | h3: /* */,
39 | h4: /* */,
40 | h5: /* */,
41 | h6: /* */,
42 | blockquote: /* */,
43 | normal: /*
*/
44 | },
45 | list: {
46 | bullet: /* */,
47 | number: /* */,
48 | menu: /* */,
49 | },
50 | listItem: {
51 | bullet: /* */,
52 | number: /* */,
53 | menu: /* */,
54 | },
55 | mark: {
56 | code: /* */,
57 | em: /* */,
58 | link: /* */,
59 | 'strike-through': /* */,
60 | strong: /* */,
61 | underline: /* */
62 | },
63 | text: /* Renders plain text */
64 | hardBreak: /* */,
65 | }
66 | ```
67 |
68 |
69 |
70 | ```js
71 | /* .astro */
72 | ---
73 | import { PortableText } from "astro-portabletext";
74 |
75 | const portableText = [
76 | {
77 | _type: "block",
78 | children: [
79 | {
80 | _type: "span",
81 | marks: [],
82 | text: "This is a ",
83 | },
84 | {
85 | _type: "span",
86 | marks: ["strong"],
87 | text: "bold",
88 | },
89 | {
90 | _type: "span",
91 | marks: [],
92 | text: " text example!",
93 | },
94 | ],
95 | markDefs: [],
96 | style: "normal",
97 | },
98 | ];
99 | ---
100 |
101 |
102 | ```
103 |
104 | ## Custom components
105 |
106 | Custom components allow for better control over rendering of rich text elements. You can map a component to a node type or map the component to the property of the node type.
107 |
108 | ```js
109 | /* .astro */
110 | ---
111 | import { PortableText } from "astro-portabletext";
112 |
113 | const portableText = [
114 | // ... your Portable Text content
115 | ];
116 |
117 | const components = {
118 | // custom types
119 | type: { [_type]: Component } | Component,
120 | unknownType: Component,
121 | // block style
122 | block: { [style]: Component } | Component,
123 | unknownBlock: Component,
124 | // list
125 | list: { [listItem]: Component } | Component,
126 | unknownList: Component,
127 | // list item
128 | listItem: { [listItem]: Component } | Component,
129 | unknownListItem: Component,
130 | // mark
131 | mark: { [markType]: Component } | Component,
132 | unknownMark: Component,
133 | // strings; added in `v0.11.0`
134 | text: Component,
135 | // line break
136 | hardBreak: Component
137 | };
138 | ---
139 |
140 |
141 | ```
142 |
143 | 💡 Refer to mapping [component to node type](../examples/portabletext-mapped-type.astro) and [component to node type property](../examples/portabletext-mapped-type-property.astro) examples for more guidance.
144 |
145 | ### Slots
146 |
147 | > **Added in `v0.11.0`**
148 |
149 | Slots provide a flexible way to enhance the rendering of Portable Text elements by passing additional props to the component. This allows you to customize the output in various ways, such as:
150 |
151 | - Applying custom styles or classes
152 | - Wrapping elements in custom components
153 | - Modifying the output based on specific conditions
154 |
155 | Here's an example of using a slot to apply custom styles to `strong` elements:
156 |
157 | ```ts
158 | /* .astro */
159 | ---
160 | import { PortableText } from "astro-portabletext";
161 |
162 | const portableText = [
163 | // ... your Portable Text content
164 | ];
165 | ---
166 |
167 |
168 | {({ Component, props, children }) => (
169 | {children}
170 | )}
171 |
172 |
173 |
178 | ```
179 |
180 | The available slot names are:
181 |
182 | - `block`
183 | - `hardBreak`
184 | - `list`
185 | - `listItem`
186 | - `mark`
187 | - `text`
188 | - `type`
189 |
190 | 💡 Refer to [slot example](../examples/portabletext-slots.astro) for more details.
191 |
192 | ## `PortableText` component properties
193 |
194 | | Property | Type | Description |
195 | | ------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
196 | | `value` | `array` or `object` | Portable Text payload |
197 | | `components (optional)` | `object` | Mapping of components to node types or its properties. |
198 | | `onMissingComponent (optional)` | `function` or `boolean` | Disable warning messages or handle unknown types. **Default** prints to console. |
199 | | `listNestingMode (optional)` | `"html"` or `"direct"` | List nesting mode. **Default** is `html`. See [ToolkitListNestMode](https://portabletext.github.io/toolkit/types/ToolkitListNestMode.html) for more details. |
200 |
--------------------------------------------------------------------------------
/astro-portabletext/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # astro-portabletext
6 |
7 | [](https://www.npmjs.com/package/astro-portabletext)
8 | 
9 |
10 | A flexible and customizable library for rendering [Portable Text](https://portabletext.org) content in [Astro](https://astro.build) projects.
11 |
12 | ⚠️ **Prerequisites**:
13 |
14 | - Astro v4.6+ (as of `v0.11.0`)
15 |
16 | ## Table of Contents
17 |
18 | - [Features](#features)
19 | - [Demonstration](#demo)
20 | - [Resources](#resources)
21 | - [Installation](#installation)
22 | - [Usage](#usage)
23 | - [Sanity Integration](#sanity-integration)
24 | - [`PortableText` component](#portabletext-component)
25 | - [Basic usage](#basic-usage)
26 | - [Custom components](#custom-components)
27 | - [Slots](#slots)
28 | - [`PortableText` component properties](#portabletext-component-properties)
29 | - [Utility functions](#utility-functions)
30 | - [usePortableText](#useportabletext)
31 | - [mergeComponents](#mergecomponents)
32 | - [toPlainText](#toplaintext)
33 | - [Contributing](#contributing)
34 | - [License](#license)
35 |
36 | 🚀 Features
37 |
38 | - 🧩 **Core components:** Provides pre-built components for common Portable Text elements.
39 | - 🔧 **Customizable:** Use `components` or `slots` to tailor output to your needs.
40 | - 🛠 **Flexible control:** Use `render` function via `usePortableText` to fine-tune rendering.
41 | - 📘 **Typescript:** Built with full TypeScript support.
42 |
43 | 🎮 Demonstration
44 |
45 | Jump in and see it in action:
46 |
47 |
55 |
56 | 📖 Resources
57 |
58 | - **Documentation:** [Read the full documentation](https://github.com/theisel/astro-portabletext/blob/main/docs/README.md "Full documentation for astro-portabletext") including TypeScript type definitions.
59 | - **Examples:** [Browse practical examples](https://github.com/theisel/astro-portabletext/tree/main/examples "Browse examples for astro-portabletext") to help you learn.
60 |
61 | 📦 Installation
62 |
63 | Pick your favorite package manager and run one of these:
64 |
65 | ```bash
66 | npm install astro-portabletext
67 | # or
68 | pnpm add astro-portabletext
69 | # or
70 | yarn add astro-portabletext
71 | # or
72 | bun add astro-portabletext
73 | ```
74 |
75 | 🧑💻 Usage
76 |
77 | ### Sanity Integration
78 |
79 | This library is [officially recommended](https://www.sanity.io/plugins/sanity-astro#rendering-rich-text-and-block-content-with-portable-text) by [Sanity](https://sanity.io) for rendering Portable Text in Astro projects.
80 |
81 | Helpful resources:
82 |
83 | - [Sanity Integration for Astro](https://www.sanity.io/plugins/sanity-astro)
84 | - [Guide: Building a Blog with Sanity and Astro](https://www.sanity.io/guides/sanity-astro-blog)
85 |
86 | ### `PortableText` component
87 |
88 | This component provides a simple and flexible way to display rich text, from
89 | using `slots` to custom `components`.
90 |
91 | #### Basic usage
92 |
93 | Import the `PortableText` component and start rendering. This library provides sensible defaults for rendering common Portable Text elements, which you can easily override.
94 |
95 | > Use the following default mapping to understand what each node type outputs.
96 |
97 |
98 | View the default structure and output
99 |
100 | ```js
101 | {
102 | type: {
103 | /* Custom types go here */
104 | },
105 | block: {
106 | h1: /* */,
107 | h2: /* */,
108 | h3: /* */,
109 | h4: /* */,
110 | h5: /* */,
111 | h6: /* */,
112 | blockquote: /* */,
113 | normal: /*
*/
114 | },
115 | list: {
116 | bullet: /* */,
117 | number: /* */,
118 | menu: /* */,
119 | },
120 | listItem: {
121 | bullet: /* */,
122 | number: /* */,
123 | menu: /* */,
124 | },
125 | mark: {
126 | code: /* */,
127 | em: /* */,
128 | link: /* */,
129 | 'strike-through': /* */,
130 | strong: /* */,
131 | underline: /* */
132 | },
133 | text: /* Renders plain text */
134 | hardBreak: /* */,
135 | }
136 | ```
137 |
138 |
139 |
140 | ```js
141 | /* .astro */
142 | ---
143 | import { PortableText } from "astro-portabletext";
144 |
145 | const portableText = [
146 | {
147 | _type: "block",
148 | children: [
149 | {
150 | _type: "span",
151 | marks: [],
152 | text: "This is a ",
153 | },
154 | {
155 | _type: "span",
156 | marks: ["strong"],
157 | text: "bold",
158 | },
159 | {
160 | _type: "span",
161 | marks: [],
162 | text: " text example!",
163 | },
164 | ],
165 | markDefs: [],
166 | style: "normal",
167 | },
168 | ];
169 | ---
170 |
171 |
172 | ```
173 |
174 | #### Custom components
175 |
176 | Custom components allow for better control over rendering of rich text elements. You can map a component to a node type or map the component to the property of the node type.
177 |
178 | ```js
179 | /* .astro */
180 | ---
181 | import { PortableText } from "astro-portabletext";
182 |
183 | const portableText = [
184 | // ... your Portable Text content
185 | ];
186 |
187 | const components = {
188 | // custom types
189 | type: { [_type]: Component } | Component,
190 | unknownType: Component,
191 | // block style
192 | block: { [style]: Component } | Component,
193 | unknownBlock: Component,
194 | // list
195 | list: { [listItem]: Component } | Component,
196 | unknownList: Component,
197 | // list item
198 | listItem: { [listItem]: Component } | Component,
199 | unknownListItem: Component,
200 | // mark
201 | mark: { [markType]: Component } | Component,
202 | unknownMark: Component,
203 | // strings; added in `v0.11.0`
204 | text: Component,
205 | // line break
206 | hardBreak: Component
207 | };
208 | ---
209 |
210 |
211 | ```
212 |
213 | 💡 Refer to [`custom` components documentation](https://github.com/theisel/astro-portabletext/blob/main/docs/portabletext-component.md#custom-components "Custom components documentation for astro-portabletext") for more details.
214 |
215 | #### Slots
216 |
217 | > **Added in `v0.11.0`**
218 |
219 | Slots provide a flexible way to enhance the rendering of Portable Text elements by passing additional props to the component. This allows you to customize the output in various ways, such as:
220 |
221 | - Applying custom styles or classes
222 | - Wrapping elements in custom components
223 | - Modifying the output based on specific conditions
224 |
225 | Here's an example of using a slot to apply custom styles to `strong` elements:
226 |
227 | ```ts
228 | /* .astro */
229 | ---
230 | import { PortableText } from "astro-portabletext";
231 |
232 | const portableText = [
233 | // ... your Portable Text content
234 | ];
235 | ---
236 |
237 |
238 | {({ Component, props, children }) => (
239 | {children}
240 | )}
241 |
242 |
243 |
248 | ```
249 |
250 | 💡 Refer to [`slots` documentation](https://github.com/theisel/astro-portabletext/blob/main/docs/portabletext-component.md#slots "Slots documentation for astro-portabletext") for more details.
251 |
252 | ### `PortableText` component properties
253 |
254 | | Property | Type | Description |
255 | | ------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
256 | | `value` | `array` or `object` | Portable Text payload |
257 | | `components (optional)` | `object` | Mapping of components to node types or its properties. |
258 | | `onMissingComponent (optional)` | `function` or `boolean` | Disable warning messages or handle unknown types. **Default** prints to console. |
259 | | `listNestingMode (optional)` | `"html"` or `"direct"` | List nesting mode. **Default** is `html`. See [ToolkitListNestMode](https://portabletext.github.io/toolkit/types/ToolkitListNestMode.html) |
260 |
261 | ### Utility functions
262 |
263 | This library provides utility functions to help you work with Portable Text content:
264 |
265 | ```js
266 | import {
267 | usePortableText,
268 | mergeComponents,
269 | toPlainText,
270 | spanToPlainText, // added in `v0.11.0`
271 | } from "astro-portabletext";
272 | ```
273 |
274 | 💡 Refer to the [utility functions](https://github.com/theisel/astro-portabletext/blob/main/docs/utility-functions.md) documentation for more details.
275 |
276 | 🙌 Contributing
277 |
278 | We welcome contributions to improve `astro-portabletext`!
279 |
280 | If you find a bug or have a feature request, please open an [issue](https://github.com/theisel/astro-portabletext/issues) on GitHub.
281 | If you'd like to contribute code, feel free to submit a [pull request](https://github.com/theisel/astro-portabletext/pulls).
282 |
283 | 📄 License
284 |
285 | This project is licensed under the [ISC License](https://github.com/theisel/astro-portabletext/blob/main/LICENSE).
286 |
--------------------------------------------------------------------------------