├── .commitlintrc ├── .github └── workflows │ ├── docs.yml │ ├── fix-lockfile.yml │ ├── format.yml │ ├── npm.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .husky ├── commit-msg ├── post-merge └── pre-commit ├── .huskyrc ├── .lintstagedrc ├── .npmrc ├── .prettierignore ├── .prettierrc ├── .release-please-manifest.json ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── astro-portabletext ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── components │ ├── Block.astro │ ├── HardBreak.astro │ ├── List.astro │ ├── ListItem.astro │ ├── Mark.astro │ ├── PortableText.astro │ ├── Text.astro │ ├── UnknownBlock.astro │ ├── UnknownList.astro │ ├── UnknownListItem.astro │ ├── UnknownMark.astro │ └── UnknownType.astro ├── lib │ ├── astro.d.ts │ ├── components.ts │ ├── context.ts │ ├── index.ts │ ├── internal.ts │ ├── types.ts │ ├── utils.d.ts │ ├── utils.ts │ └── warnings.ts ├── package.json └── tsconfig.json ├── demo ├── .codesandbox │ └── Dockerfile ├── .devcontainer │ └── devcontainer.json ├── .gitignore ├── .stackblitzrc ├── .vscode │ ├── extensions.json │ └── launch.json ├── CHANGELOG.md ├── README.md ├── astro.config.mjs ├── package.json ├── public │ └── favicon.ico ├── src │ ├── astro.d.ts │ ├── components │ │ ├── FancyBorder.astro │ │ ├── ListItemStyleStar.astro │ │ ├── ListStyleStar.astro │ │ ├── Unicorn.astro │ │ ├── counter │ │ │ ├── Counter.astro │ │ │ ├── Counter.svelte │ │ │ ├── Counter.tsx │ │ │ ├── JsxCounter.astro │ │ │ ├── SvelteCounter.astro │ │ │ └── index.ts │ │ └── portabletext │ │ │ ├── Block.astro │ │ │ ├── List.astro │ │ │ ├── ListItem.astro │ │ │ ├── Mark.astro │ │ │ ├── Type.astro │ │ │ └── index.ts │ ├── data │ │ └── portabletext.json │ ├── env.d.ts │ ├── layouts │ │ └── Default.astro │ └── pages │ │ └── index.astro └── tsconfig.json ├── docs ├── README.md ├── getting-started.md ├── portabletext-component.md ├── types │ ├── README.md │ ├── interfaces │ │ ├── Block.md │ │ ├── Context.md │ │ ├── Mark.md │ │ ├── PortableTextComponents.md │ │ ├── PortableTextProps.md │ │ ├── Props.md │ │ └── TypedObject.md │ └── type-aliases │ │ ├── BlockProps.md │ │ ├── Component.md │ │ ├── ComponentOrRecord.md │ │ ├── List.md │ │ ├── ListItem.md │ │ ├── ListItemProps.md │ │ ├── ListProps.md │ │ ├── MarkProps.md │ │ ├── MissingComponentHandler.md │ │ ├── NodeType.md │ │ ├── RenderHandler.md │ │ ├── RenderHandlerProps.md │ │ ├── RenderOptions.md │ │ ├── SomePortableTextComponents.md │ │ ├── TextNode.md │ │ └── TextNodeProps.md └── utility-functions.md ├── eslint.config.mjs ├── examples ├── Block.astro ├── BlockWithRenderFunction.astro ├── List.astro ├── ListItem.astro ├── Mark.astro ├── README.md ├── Text.astro ├── Type.astro ├── portabletext-basic.astro ├── portabletext-mapped-type-property.astro ├── portabletext-mapped-type.astro └── portabletext-slots.astro ├── lab ├── README.md ├── astro.config.mjs ├── package.json ├── scripts │ └── ci.sh ├── src │ ├── components │ │ ├── BlockIndex.astro │ │ ├── BlockWithBanner.astro │ │ ├── BlockWithRender.astro │ │ ├── Grid.astro │ │ ├── HelloWorld.astro │ │ ├── ListItem.astro │ │ ├── ListWithRender.astro │ │ ├── Mark.astro │ │ ├── MarkWithRender.astro │ │ ├── MyBlock.astro │ │ ├── MyH1.astro │ │ ├── TextReplace.astro │ │ ├── TextStyleBySplit.astro │ │ ├── TextStylebyIndex.astro │ │ └── issues │ │ │ └── BlockStandFirst.astro │ ├── env.d.ts │ ├── layouts │ │ └── Default.astro │ ├── lib │ │ ├── internal.test.ts │ │ ├── utils.test.ts │ │ └── warnings.test.ts │ ├── pages │ │ ├── block │ │ │ ├── block-index.astro │ │ │ ├── blockquote.astro │ │ │ ├── custom-handler.astro │ │ │ ├── default-handler.astro │ │ │ ├── h1.astro │ │ │ ├── h2.astro │ │ │ ├── h3.astro │ │ │ ├── h4.astro │ │ │ ├── h5.astro │ │ │ ├── h6.astro │ │ │ ├── merge.astro │ │ │ ├── missing-style.astro │ │ │ ├── normal.astro │ │ │ ├── override.astro │ │ │ ├── unknown.astro │ │ │ └── with-style.astro │ │ ├── hardbreak.astro │ │ ├── issues │ │ │ └── issue-175.astro │ │ ├── list │ │ │ ├── menu.astro │ │ │ ├── nested.astro │ │ │ ├── ordered.astro │ │ │ ├── styled.astro │ │ │ ├── unknown.astro │ │ │ └── unordered.astro │ │ ├── mark │ │ │ ├── code.astro │ │ │ ├── em.astro │ │ │ ├── link-missing-href.astro │ │ │ ├── link.astro │ │ │ ├── strike-through.astro │ │ │ ├── strong.astro │ │ │ ├── underline.astro │ │ │ └── unknown.astro │ │ ├── render │ │ │ ├── block.astro │ │ │ ├── list.astro │ │ │ └── mark.astro │ │ ├── slot │ │ │ ├── block-custom.astro │ │ │ ├── block.astro │ │ │ ├── list-custom.astro │ │ │ ├── list.astro │ │ │ ├── listitem-custom.astro │ │ │ ├── listitem.astro │ │ │ ├── mark-custom.astro │ │ │ ├── mark.astro │ │ │ └── type.astro │ │ ├── text │ │ │ ├── default.astro │ │ │ ├── replace.astro │ │ │ ├── style-by-index.astro │ │ │ ├── style-by-split.astro │ │ │ └── undefined.astro │ │ └── type │ │ │ ├── block.astro │ │ │ ├── inline.astro │ │ │ ├── unknown-block.astro │ │ │ └── unknown-inline.astro │ ├── test │ │ ├── block.test.js │ │ ├── extras.test.js │ │ ├── issue.test.js │ │ ├── list.test.js │ │ ├── mark.test.js │ │ ├── render.test.js │ │ ├── slot.test.js │ │ ├── text.test.js │ │ └── type.test.js │ └── utils.mjs └── tsconfig.json ├── logo.svg ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── release-please-config.json └── typedoc.json /.commitlintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } 4 | -------------------------------------------------------------------------------- /.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" -------------------------------------------------------------------------------- /.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" -------------------------------------------------------------------------------- /.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" -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .astro/ 4 | *.log -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | pnpm exec commitlint --edit 5 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.huskyrc: -------------------------------------------------------------------------------- 1 | export NVM_DIR="$HOME/.nvm" 2 | [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,jsx,mjs,ts,tsx}": ["pnpm lint"], 3 | "*.json": "prettier --list-different --write" 4 | } 5 | -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | {"astro-portabletext":"0.11.1"} 2 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { "prettier.prettierPath": "./node_modules/prettier" } 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | astro-portabletext logo 3 |
4 | 5 | # astro-portabletext 6 | 7 | [![npm version](https://img.shields.io/npm/v/astro-portabletext.svg?style=flat-square)](https://npmjs.com/package/astro-portabletext) 8 | [![npm downloads](https://img.shields.io/npm/dm/astro-portabletext.svg?style=flat-square)](https://npmjs.com/package/astro-portabletext) 9 | ![license](https://img.shields.io/npm/l/astro-portabletext?style=flat-square) 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 |
25 | 26 | Open in StackBlitz 27 | 28 | 29 | Open in CodeSandbox 30 | 31 |
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.11.0)](https://github.com/theisel/astro-portabletext/tree/astro-portabletext%400.11.0/astro-portabletext "astro-portabletext v0.10.1 documentation") 42 | - [Development branch](astro-portabletext/README.md "astro-portabletext main branch documentation") 43 | 44 | ## ![Sanity Logo](https://avatars.githubusercontent.com/u/17177659?s=24) 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 | -------------------------------------------------------------------------------- /astro-portabletext/.gitignore: -------------------------------------------------------------------------------- 1 | env.d.ts 2 | generated-docs -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /astro-portabletext/README.md: -------------------------------------------------------------------------------- 1 |
2 | astro-portabletext logo 3 |
4 | 5 | # astro-portabletext 6 | 7 | [![npm](https://img.shields.io/npm/v/astro-portabletext?style=flat-square)](https://www.npmjs.com/package/astro-portabletext) 8 | ![license](https://img.shields.io/npm/l/astro-portabletext?style=flat-square) 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 |
48 | 49 | Open in StackBlitz 50 | 51 | 52 | Open in CodeSandbox 53 | 54 |
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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /astro-portabletext/components/HardBreak.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { TextNode, Props as $ } from "../lib/types"; 3 | 4 | export type Props = $; 5 | --- 6 | 7 |
    8 | -------------------------------------------------------------------------------- /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 |
      21 | 22 |
    23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /astro-portabletext/components/PortableText.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { 3 | isPortableTextBlock, 4 | isPortableTextListItemBlock, 5 | isPortableTextToolkitList, 6 | isPortableTextToolkitSpan, 7 | isPortableTextToolkitTextNode, 8 | nestLists, 9 | buildMarksTree, 10 | LIST_NEST_MODE_HTML, 11 | } from "@portabletext/toolkit"; 12 | 13 | import type { 14 | Component, 15 | Context, 16 | MissingComponentHandler, 17 | NodeType, 18 | PortableTextComponents, 19 | PortableTextProps, 20 | Props as ComponentProps, 21 | RenderOptions, 22 | TypedObject, 23 | } from "../lib/types"; 24 | 25 | import { isComponent, mergeComponents } from "../lib/internal"; 26 | 27 | import { getWarningMessage, printWarning } from "../lib/warnings"; 28 | import { key as contextRef } from "../lib/context"; 29 | 30 | import Block from "./Block.astro"; 31 | import HardBreak from "./HardBreak.astro"; 32 | import List from "./List.astro"; 33 | import ListItem from "./ListItem.astro"; 34 | import Mark from "./Mark.astro"; 35 | import Text from "./Text.astro"; 36 | import UnknownBlock from "./UnknownBlock.astro"; 37 | import UnknownList from "./UnknownList.astro"; 38 | import UnknownListItem from "./UnknownListItem.astro"; 39 | import UnknownMark from "./UnknownMark.astro"; 40 | import UnknownType from "./UnknownType.astro"; 41 | 42 | export type Props = PortableTextProps; 43 | 44 | const { 45 | value, 46 | components: componentOverrides = {}, 47 | listNestingMode = LIST_NEST_MODE_HTML, 48 | onMissingComponent = true, 49 | } = Astro.props; 50 | 51 | const components = mergeComponents( 52 | { 53 | type: {}, 54 | unknownType: UnknownType, 55 | block: { 56 | h1: Block, 57 | h2: Block, 58 | h3: Block, 59 | h4: Block, 60 | h5: Block, 61 | h6: Block, 62 | blockquote: Block, 63 | normal: Block, 64 | }, 65 | unknownBlock: UnknownBlock, 66 | list: { 67 | bullet: List, 68 | number: List, 69 | menu: List, 70 | }, 71 | unknownList: UnknownList, 72 | listItem: { 73 | bullet: ListItem, 74 | number: ListItem, 75 | menu: ListItem, 76 | }, 77 | unknownListItem: UnknownListItem, 78 | mark: { 79 | code: Mark, 80 | em: Mark, 81 | link: Mark, 82 | "strike-through": Mark, 83 | strong: Mark, 84 | underline: Mark, 85 | }, 86 | unknownMark: UnknownMark, 87 | text: Text, 88 | hardBreak: HardBreak, 89 | }, 90 | componentOverrides 91 | ) as PortableTextComponents; 92 | 93 | const noop = () => {}; 94 | 95 | const missingComponentHandler = (( 96 | handler: unknown 97 | ): MissingComponentHandler => { 98 | if (typeof handler === "function") { 99 | return handler as MissingComponentHandler; 100 | } 101 | return !handler ? noop : printWarning; 102 | })(onMissingComponent); 103 | 104 | const asComponentProps = ( 105 | node: T, 106 | index: number, 107 | isInline: boolean 108 | ): ComponentProps => ({ 109 | node, 110 | index, 111 | isInline, 112 | }); 113 | 114 | const provideComponent = ( 115 | nodeType: NodeType, 116 | type: string, 117 | fallbackComponent: Component 118 | ): Component => { 119 | const component: Component | undefined = ((component) => { 120 | return component[type as keyof typeof component] || component; 121 | })(components[nodeType]); 122 | 123 | if (isComponent(component)) { 124 | return component; 125 | } 126 | 127 | missingComponentHandler(getWarningMessage(nodeType, type), { 128 | nodeType, 129 | type, 130 | }); 131 | 132 | return fallbackComponent; 133 | }; 134 | 135 | const cachedNodes = new WeakMap< 136 | TypedObject, 137 | { Default?: Component; Unknown?: Component } 138 | >(); 139 | 140 | // The local render function will override these options 141 | let fallbackRenderOptions: Required | undefined; 142 | 143 | const portableTextRender = (options: RenderOptions, isInline?: boolean) => { 144 | if (!fallbackRenderOptions) { 145 | throw new Error( 146 | "[PortableText portableTextRender] fallbackRenderOptions is undefined" 147 | ); 148 | } 149 | 150 | return function renderNode(node: TypedObject, index: number): any { 151 | const renderOptions = { ...fallbackRenderOptions, ...(options ?? {}) }; 152 | 153 | function run any, P = Parameters[0]>( 154 | handler: H | undefined, 155 | props: P 156 | ) { 157 | if (!isComponent(handler)) { 158 | throw new Error( 159 | `[PortableText render] No handler found for node type ${node._type}.` 160 | ); 161 | } 162 | 163 | return handler(props); 164 | } 165 | 166 | if (isPortableTextToolkitList(node)) { 167 | const UnknownComponent = components.unknownList ?? UnknownList; 168 | 169 | cachedNodes.set(node, { Default: List, Unknown: UnknownComponent }); 170 | 171 | return run(renderOptions.list, { 172 | Component: provideComponent("list", node.listItem, UnknownComponent), 173 | props: asComponentProps(node, index, false), 174 | children: node.children?.map(portableTextRender(options, false)), 175 | }); 176 | } 177 | 178 | if (isPortableTextListItemBlock(node)) { 179 | const { listItem, ...blockNode } = node; 180 | const isStyled = node.style && node.style !== "normal"; 181 | // Apply block style if defined (and not "normal"), otherwise render with marks. 182 | node.children = isStyled 183 | ? renderNode(blockNode, index) 184 | : buildMarksTree(node); 185 | 186 | const UnknownComponent = components.unknownListItem ?? UnknownListItem; 187 | 188 | cachedNodes.set(node, { Default: ListItem, Unknown: UnknownComponent }); 189 | 190 | return run(renderOptions.listItem, { 191 | Component: provideComponent( 192 | "listItem", 193 | node.listItem, 194 | UnknownComponent 195 | ), 196 | props: asComponentProps(node, index, false), 197 | children: isStyled 198 | ? node.children 199 | : node.children.map(portableTextRender(options, true)), 200 | }); 201 | } 202 | 203 | if (isPortableTextToolkitSpan(node)) { 204 | const UnknownComponent = components.unknownMark ?? UnknownMark; 205 | 206 | cachedNodes.set(node, { Default: Mark, Unknown: UnknownComponent }); 207 | 208 | return run(renderOptions.mark, { 209 | Component: provideComponent("mark", node.markType, UnknownComponent), 210 | props: asComponentProps(node, index, true), 211 | children: node.children?.map(portableTextRender(options, true)), 212 | }); 213 | } 214 | 215 | if (isPortableTextBlock(node)) { 216 | node.style ??= "normal"; /* Make sure style has been set */ 217 | node.children = buildMarksTree(node); 218 | 219 | const UnknownComponent = components.unknownBlock ?? UnknownBlock; 220 | 221 | cachedNodes.set(node, { Default: Block, Unknown: UnknownComponent }); 222 | 223 | return run(renderOptions.block, { 224 | Component: provideComponent("block", node.style, UnknownComponent), 225 | props: asComponentProps(node, index, false), 226 | children: node.children.map(portableTextRender(options, true)), 227 | }); 228 | } 229 | 230 | if (isPortableTextToolkitTextNode(node)) { 231 | const isHardBreak = "\n" === node.text; 232 | const props = asComponentProps(node, index, true); 233 | 234 | if (isHardBreak) { 235 | return run(renderOptions.hardBreak, { 236 | Component: isComponent(components.hardBreak) 237 | ? components.hardBreak 238 | : HardBreak, 239 | props, 240 | }); 241 | } 242 | 243 | return run(renderOptions.text, { 244 | Component: isComponent(components.text) ? components.text : Text, 245 | props, 246 | }); 247 | } 248 | 249 | // Custom type 250 | const UnknownComponent = components.unknownType ?? UnknownType; 251 | 252 | return run(renderOptions.type, { 253 | Component: provideComponent("type", node._type, UnknownComponent), 254 | props: asComponentProps( 255 | node, 256 | index, 257 | isInline ?? false /* default to block */ 258 | ), 259 | }); 260 | }; 261 | }; 262 | 263 | (globalThis as any)[contextRef] = ( 264 | node: TypedObject & { children?: TypedObject[] } 265 | ): Context => { 266 | return { 267 | getDefaultComponent: provideDefaultComponent.bind(null, node), 268 | getUnknownComponent: provideUnknownComponent.bind(null, node), 269 | render: (options) => node.children?.map(portableTextRender(options)), 270 | }; 271 | }; 272 | 273 | // Returns the `default` component related to the passed in node 274 | const provideDefaultComponent = (node: TypedObject) => { 275 | const DefaultComponent = cachedNodes.get(node)?.Default; 276 | 277 | if (DefaultComponent) return DefaultComponent; 278 | // Cache missed use manual lookup 279 | 280 | if (isPortableTextToolkitList(node)) return List; 281 | if (isPortableTextListItemBlock(node)) return ListItem; 282 | if (isPortableTextToolkitSpan(node)) return Mark; 283 | if (isPortableTextBlock(node)) return Block; 284 | 285 | if (isPortableTextToolkitTextNode(node)) { 286 | return "\n" === node.text ? HardBreak : Text; 287 | } 288 | 289 | return UnknownType; 290 | }; 291 | 292 | // Returns the `unknown` component related to the passed in node 293 | const provideUnknownComponent = (node: TypedObject) => { 294 | const UnknownComponent = cachedNodes.get(node)?.Unknown; 295 | 296 | if (UnknownComponent) return UnknownComponent; 297 | // Cache missed use manual lookup 298 | 299 | if (isPortableTextToolkitList(node)) { 300 | return components.unknownList ?? UnknownList; 301 | } 302 | 303 | if (isPortableTextListItemBlock(node)) { 304 | return components.unknownListItem ?? UnknownListItem; 305 | } 306 | 307 | if (isPortableTextToolkitSpan(node)) { 308 | return components.unknownMark ?? UnknownMark; 309 | } 310 | 311 | if (isPortableTextBlock(node)) { 312 | return components.unknownBlock ?? UnknownBlock; 313 | } 314 | 315 | if (!isPortableTextToolkitTextNode(node)) { 316 | return components.unknownType ?? UnknownType; 317 | } 318 | 319 | throw new Error( 320 | `[PortableText getUnknownComponent] Unable to provide component with node type ${node._type}` 321 | ); 322 | }; 323 | 324 | // Make sure we have an array of blocks 325 | const blocks = Array.isArray(value) ? value : [value]; 326 | const nodes = nestLists(blocks, listNestingMode); 327 | 328 | const render = (options: NonNullable) => { 329 | fallbackRenderOptions = options; 330 | return portableTextRender(options); 331 | }; 332 | 333 | const hasTypeSlot = Astro.slots.has("type"); 334 | const hasBlockSlot = Astro.slots.has("block"); 335 | const hasListSlot = Astro.slots.has("list"); 336 | const hasListItemSlot = Astro.slots.has("listItem"); 337 | const hasMarkSlot = Astro.slots.has("mark"); 338 | const hasTextSlot = Astro.slots.has("text"); 339 | const hasHardBreakSlot = Astro.slots.has("hardBreak"); 340 | 341 | // Create a slot renderer for a specific slot 342 | const createSlotRenderer = (slotName: string) => 343 | Astro.slots.render.bind(Astro.slots, slotName); 344 | 345 | type RenderNode = ( 346 | slotRenderer: ReturnType | undefined 347 | ) => ( 348 | props: Parameters>[0] 349 | ) => any; 350 | --- 351 | 352 | { 353 | (() => { 354 | const renderNode: RenderNode = (slotRenderer) => { 355 | return ({ Component, props, children }) => 356 | slotRenderer?.([{ Component, props, children }]) ?? ( 357 | {children} 358 | ); 359 | }; 360 | 361 | return nodes.map( 362 | render({ 363 | type: renderNode(hasTypeSlot ? createSlotRenderer("type") : undefined), 364 | block: renderNode( 365 | hasBlockSlot ? createSlotRenderer("block") : undefined 366 | ), 367 | list: renderNode(hasListSlot ? createSlotRenderer("list") : undefined), 368 | listItem: renderNode( 369 | hasListItemSlot ? createSlotRenderer("listItem") : undefined 370 | ), 371 | mark: renderNode(hasMarkSlot ? createSlotRenderer("mark") : undefined), 372 | text: renderNode(hasTextSlot ? createSlotRenderer("text") : undefined), 373 | hardBreak: renderNode( 374 | hasHardBreakSlot ? createSlotRenderer("hardBreak") : undefined 375 | ), 376 | }) 377 | ); 378 | })() 379 | } 380 | -------------------------------------------------------------------------------- /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/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/UnknownListItem.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { Props as $, ListItem } 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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /astro-portabletext/lib/index.ts: -------------------------------------------------------------------------------- 1 | export { default as PortableText } from "../components/PortableText.astro"; 2 | export * from "./utils"; 3 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/utils.ts: -------------------------------------------------------------------------------- 1 | export { toPlainText, spanToPlainText } from "@portabletext/toolkit"; 2 | export { mergeComponents } from "./internal"; 3 | export { usePortableText } from "./context"; 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /astro-portabletext/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astro-portabletext", 3 | "version": "0.11.1", 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": "^2.0.17", 52 | "@portabletext/types": "^2.0.13" 53 | }, 54 | "peerDependencies": { 55 | "astro": ">=4.6.0" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /astro-portabletext/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "include": ["lib"] 4 | } 5 | -------------------------------------------------------------------------------- /demo/.codesandbox/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:22-bullseye -------------------------------------------------------------------------------- /demo/.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Devcontainer", 3 | "image": "ghcr.io/codesandbox/devcontainers/typescript-node:latest" 4 | } 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /demo/.stackblitzrc: -------------------------------------------------------------------------------- 1 | { 2 | "installDependencies": false, 3 | "startCommand": "pnpm install && pnpm start" 4 | } 5 | -------------------------------------------------------------------------------- /demo/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | # `astro-portabletext` Demo: Advanced Customization 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 | 16 | - Node.js (`^18.17.1`, `^20.3.0`, or `>=22.0.0`) 17 | - Astro (`>=4.6.0`) 18 | 19 | 2. **Installation:** 20 | 21 | Install the dependencies using your preferred package manager. For example: 22 | 23 | ```bash 24 | # npm 25 | npm install 26 | ``` 27 | 28 | 3. **Development:** 29 | 30 | Start the Astro development server: 31 | 32 | ```bash 33 | # npm 34 | npm run dev 35 | ``` 36 | 37 | Open your browser and navigate to the URL provided in the terminal (typically `http://localhost:4321`). 38 | 39 | ## Exploring the Demo 40 | 41 | The demo page (`src/pages/index.astro`) initially renders Portable Text content with custom components from `src/components/portabletext`. 42 | 43 | To explore slot-based customization: 44 | 45 | 1. Open `src/pages/index.astro`. 46 | 2. Locate the `` component. 47 | 3. Uncomment the `` or `` sections. 48 | 4. Observe the updated rendering in your browser. 49 | 50 | The CSS in `src/pages/index.astro` targets elements with the `data-slot` attribute, demonstrating how to apply specific styles predictably. 51 | 52 | ## Why use `data-*` attributes? 53 | 54 | While `class` attributes can be passed to custom components, `data-*` attributes offer key advantages: 55 | 56 | - **Predictability:** 57 | 58 | Custom components might override or ignore `class` attributes, but `data-*` attributes are reliably passed through to rendered elements. 59 | 60 | - **Conflict-free styling:** 61 | 62 | `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. 63 | 64 | ## Resources 65 | 66 | - **Documentation:** [Full documentation](https://github.com/theisel/astro-portabletext/blob/main/docs/README.md) 67 | 68 | - **Examples:** [Browse examples](https://github.com/theisel/astro-portabletext/blob/main/examples/README.md) 69 | -------------------------------------------------------------------------------- /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/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": "astro dev", 10 | "build": "astro build", 11 | "preview": "astro preview" 12 | }, 13 | "dependencies": { 14 | "@astrojs/solid-js": "^5.0.4", 15 | "@astrojs/svelte": "^7.0.4", 16 | "astro": "^5.3.0", 17 | "astro-portabletext": "^0.11.1", 18 | "solid-js": "^1.9.4", 19 | "svelte": "^5.20.1" 20 | }, 21 | "engines": { 22 | "node": "^18.17.1 || ^20.3.0 || >=22.0.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /demo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theisel/astro-portabletext/8bb9f929824fc826517cd85948a90463df0d3e52/demo/public/favicon.ico -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /demo/src/components/FancyBorder.astro: -------------------------------------------------------------------------------- 1 |
    2 | 3 |
    4 | 5 | 52 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /demo/src/components/counter/Counter.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 | {#if isInline} 19 | {count} 20 | {:else} 21 |
    {count}
    22 | {/if} 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/counter/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Counter } from "./Counter.astro"; 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /demo/src/layouts/Default.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const packageName = "astro-portabletext"; 3 | --- 4 | 5 | 6 | 7 | 8 | 9 | {packageName} :: demo 10 | 11 | 12 |
    13 |

    14 | {packageName} 18 |

    19 |

    A simple demonstration

    20 |
    21 |
    22 | 23 |
    24 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/base", 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "jsxImportSource": "solid-js" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/types/README.md: -------------------------------------------------------------------------------- 1 | **`astro-portabletext` • Type Definitions** 2 | 3 | *** 4 | 5 | # Type Definitions 6 | 7 | ## Interfaces 8 | 9 | | Interface | Description | 10 | | ------ | ------ | 11 | | [PortableTextProps](interfaces/PortableTextProps.md) | Properties for the `PortableText` component | 12 | | [PortableTextComponents](interfaces/PortableTextComponents.md) | Defines how Portable Text types should be rendered. | 13 | | [Props](interfaces/Props.md) | Component Props | 14 | | [Block](interfaces/Block.md) | Alias to [PortableTextBlock](https://portabletext.github.io/types/interfaces/PortableTextBlock.html) with `style` set to `normal` when undefined | 15 | | [Mark](interfaces/Mark.md) | Extends [ToolkitNestedPortableTextSpan](https://portabletext.github.io/toolkit/interfaces/ToolkitNestedPortableTextSpan.html) with consisting `markDef` and `markKey` properties | 16 | | [Context](interfaces/Context.md) | Context object returned by `usePortableText`, providing utilities for rendering and customizing Portable Text components. | 17 | | [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) | 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 | -------------------------------------------------------------------------------- /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 | ### style 31 | 32 | ```ts 33 | style: string; 34 | ``` 35 | 36 | Visual style of the block 37 | Common values: 'normal', 'blockquote', 'h1'...'h6' 38 | 39 | #### Overrides 40 | 41 | `PortableTextBlock.style` 42 | 43 | *** 44 | 45 | ### \_type 46 | 47 | ```ts 48 | _type: string; 49 | ``` 50 | 51 | Type name identifying this as a portable text block. 52 | All items within a portable text array should have a `_type` property. 53 | 54 | Usually 'block', but can be customized to other values 55 | 56 | #### Inherited from 57 | 58 | `PortableTextBlock._type` 59 | 60 | *** 61 | 62 | ### \_key? 63 | 64 | ```ts 65 | optional _key: string; 66 | ``` 67 | 68 | A key that identifies this block uniquely within the parent array. Used to more easily address 69 | the block when editing collaboratively, but is also very useful for keys inside of React and 70 | other rendering frameworks that can use keys to optimize operations. 71 | 72 | #### Inherited from 73 | 74 | `PortableTextBlock._key` 75 | 76 | *** 77 | 78 | ### children 79 | 80 | ```ts 81 | children: (ArbitraryTypedObject | PortableTextSpan)[]; 82 | ``` 83 | 84 | Array of inline items for this block. Usually contain text spans, but can be 85 | configured to include inline objects of other types as well. 86 | 87 | #### Inherited from 88 | 89 | `PortableTextBlock.children` 90 | 91 | *** 92 | 93 | ### markDefs? 94 | 95 | ```ts 96 | optional markDefs: PortableTextMarkDefinition[]; 97 | ``` 98 | 99 | Array of mark definitions used in child text spans. By having them be on the block level, 100 | the same mark definition can be reused for multiple text spans, which is often the case 101 | with nested marks. 102 | 103 | #### Inherited from 104 | 105 | `PortableTextBlock.markDefs` 106 | 107 | *** 108 | 109 | ### listItem? 110 | 111 | ```ts 112 | optional listItem: string; 113 | ``` 114 | 115 | If this block is a list item, identifies which style of list item this is 116 | Common values: 'bullet', 'number', but can be configured 117 | 118 | #### Inherited from 119 | 120 | `PortableTextBlock.listItem` 121 | 122 | *** 123 | 124 | ### level? 125 | 126 | ```ts 127 | optional level: number; 128 | ``` 129 | 130 | If this block is a list item, identifies which level of nesting it belongs within 131 | 132 | #### Inherited from 133 | 134 | `PortableTextBlock.level` 135 | -------------------------------------------------------------------------------- /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)\<`any`\> 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)\<`any`\> 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/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 | ### markDef 38 | 39 | ```ts 40 | markDef: MarkDef & PortableTextMarkDefinition; 41 | ``` 42 | 43 | Holds the value (definition) of the mark in the case of annotations. 44 | `undefined` if the mark is a decorator (strong, em or similar). 45 | 46 | #### Overrides 47 | 48 | `ToolkitNestedPortableTextSpan.markDef` 49 | 50 | *** 51 | 52 | ### markKey 53 | 54 | ```ts 55 | markKey: string; 56 | ``` 57 | 58 | The key of the mark definition (in the case of annotations). 59 | `undefined` if the mark is a decorator (strong, em or similar). 60 | 61 | #### Overrides 62 | 63 | `ToolkitNestedPortableTextSpan.markKey` 64 | 65 | *** 66 | 67 | ### \_type 68 | 69 | ```ts 70 | _type: "@span"; 71 | ``` 72 | 73 | Type name, prefixed with `@` to signal that this is a toolkit-specific node. 74 | 75 | #### Inherited from 76 | 77 | `ToolkitNestedPortableTextSpan._type` 78 | 79 | *** 80 | 81 | ### \_key? 82 | 83 | ```ts 84 | optional _key: string; 85 | ``` 86 | 87 | Unique key for this span 88 | 89 | #### Inherited from 90 | 91 | `ToolkitNestedPortableTextSpan._key` 92 | 93 | *** 94 | 95 | ### markType 96 | 97 | ```ts 98 | markType: string; 99 | ``` 100 | 101 | Type of the mark. For annotations, this is the `_type` property of the value. 102 | For decorators, it will hold the name of the decorator (strong, em or similar). 103 | 104 | #### Inherited from 105 | 106 | `ToolkitNestedPortableTextSpan.markType` 107 | 108 | *** 109 | 110 | ### children 111 | 112 | ```ts 113 | children: (ArbitraryTypedObject | ToolkitTextNode | ToolkitNestedPortableTextSpan)[]; 114 | ``` 115 | 116 | Child nodes of this span. Can be toolkit-specific text nodes, nested spans 117 | or any inline object type. 118 | 119 | #### Inherited from 120 | 121 | `ToolkitNestedPortableTextSpan.children` 122 | -------------------------------------------------------------------------------- /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](PortableTextComponents.md#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](PortableTextComponents.md#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](PortableTextComponents.md#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](PortableTextComponents.md#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](PortableTextComponents.md#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 | -------------------------------------------------------------------------------- /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: Partial; 31 | ``` 32 | 33 | Components for rendering 34 | 35 | *** 36 | 37 | ### onMissingComponent? 38 | 39 | ```ts 40 | optional onMissingComponent: boolean | MissingComponentHandler; 41 | ``` 42 | 43 | Function to call when faced with unknown types. 44 | 45 | #### Remarks 46 | 47 | - Prints a warning message to the console by default. 48 | - Use `false` to disable. 49 | 50 | *** 51 | 52 | ### listNestingMode? 53 | 54 | ```ts 55 | optional listNestingMode: ToolkitListNestMode; 56 | ``` 57 | 58 | Defines the nesting mode for lists. The value can be `html` or `direct`, and defaults to `html`. 59 | 60 | #### Remarks 61 | 62 | - `html` - Deeper list nodes will appear as a child of the last list item in the parent list 63 | - `direct` - Deeper list nodes will appear as a direct child of the parent list 64 | 65 | #### See 66 | 67 | [ToolkitListNestMode](https://portabletext.github.io/toolkit/types/ToolkitListNestMode.html) 68 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/ComponentOrRecord.md: -------------------------------------------------------------------------------- 1 | [**`astro-portabletext` • Type Definitions**](../README.md) 2 | 3 | *** 4 | 5 | # Type Alias: ComponentOrRecord\ 6 | 7 | ```ts 8 | type ComponentOrRecord: Component | Record>; 9 | ``` 10 | 11 | **`Internal`** 12 | 13 | Defines a component or a mapping of components 14 | 15 | ## Type Parameters 16 | 17 | | Type Parameter | Default type | 18 | | ------ | ------ | 19 | | `T` *extends* [`TypedObject`](../interfaces/TypedObject.md) | `any` | 20 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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: 9 | | "type" 10 | | "block" 11 | | "list" 12 | | "listItem" 13 | | "mark"; 14 | ``` 15 | 16 | **`Internal`** 17 | 18 | Defines the type of Portable Text node 19 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | ## Type declaration 21 | 22 | | Name | Type | Description | 23 | | ------ | ------ | ------ | 24 | | `Component` | [`Component`](Component.md)\<`T`\> | The component that is associated with the Portable Text node. | 25 | | `props` | [`Props`](../interfaces/Props.md)\<`T`\> | The component props | 26 | | `children`? | `Children` | The children related to the Portable Text node. If the node is a custom [type](../interfaces/PortableTextComponents.md#type) or a [TextNode](TextNode.md), then children will be `undefined`. | 27 | -------------------------------------------------------------------------------- /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 | ## Type declaration 14 | 15 | | Name | Type | 16 | | ------ | ------ | 17 | | `type`? | [`RenderHandler`](RenderHandler.md)\<[`TypedObject`](../interfaces/TypedObject.md), `never`\> | 18 | | `block`? | [`RenderHandler`](RenderHandler.md)\<[`Block`](../interfaces/Block.md)\> | 19 | | `list`? | [`RenderHandler`](RenderHandler.md)\<[`List`](List.md)\> | 20 | | `listItem`? | [`RenderHandler`](RenderHandler.md)\<[`ListItem`](ListItem.md)\> | 21 | | `mark`? | [`RenderHandler`](RenderHandler.md)\<[`Mark`](../interfaces/Mark.md)\> | 22 | | `text`? | [`RenderHandler`](RenderHandler.md)\<[`TextNode`](TextNode.md), `never`\> | 23 | | `hardBreak`? | [`RenderHandler`](RenderHandler.md)\<[`TextNode`](TextNode.md), `never`\> | 24 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lab/README.md: -------------------------------------------------------------------------------- 1 | # `astro-portabletext` laboratory 2 | 3 | This directory handles testing of the Astro component 4 | -------------------------------------------------------------------------------- /lab/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "astro/config"; 2 | 3 | export default defineConfig(); 4 | -------------------------------------------------------------------------------- /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/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 $? -------------------------------------------------------------------------------- /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/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 | 21 | ) : ( 22 | 23 | 24 | 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lab/src/components/Grid.astro: -------------------------------------------------------------------------------- 1 |
    2 | -------------------------------------------------------------------------------- /lab/src/components/HelloWorld.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { node, isInline, index, ...attrs } = Astro.props; 3 | --- 4 | 5 | {isInline ? Hello World :

    Hello World

    } 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/components/MyH1.astro: -------------------------------------------------------------------------------- 1 |

    2 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /lab/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /lab/src/layouts/Default.astro: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/base" 3 | } 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 install", 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.4", 27 | "@changesets/cli": "^2.27.12", 28 | "@commitlint/cli": "^19.7.1", 29 | "@commitlint/config-conventional": "^19.7.1", 30 | "@eslint/eslintrc": "^3.2.0", 31 | "@eslint/js": "^9.19.0", 32 | "@types/node": "^22.13.1", 33 | "@typescript-eslint/eslint-plugin": "^8.23.0", 34 | "@typescript-eslint/parser": "^8.23.0", 35 | "eslint": "^9.19.0", 36 | "eslint-config-prettier": "^10.0.1", 37 | "eslint-plugin-prettier": "^5.2.3", 38 | "globals": "^15.14.0", 39 | "husky": "^9.1.7", 40 | "lint-staged": "^15.4.3", 41 | "prettier": "^3.4.2", 42 | "prettier-plugin-astro": "^0.14.1", 43 | "prettier-plugin-svelte": "^3.3.3", 44 | "typedoc": "^0.27.6", 45 | "typedoc-plugin-markdown": "^4.4.1", 46 | "typescript": "^5.7.3" 47 | }, 48 | "engines": { 49 | "node": ">=20.10.0", 50 | "pnpm": ">=10.2.1" 51 | }, 52 | "packageManager": "pnpm@10.2.1" 53 | } 54 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "astro-portabletext" 3 | - "demo" 4 | - "lab" 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------