Portable Text Editor
repository·main·Indexed 42 days ago
https://github.com/portabletext/editorA headless, schema-driven block content editor for React designed for the Portable Text open specification. It features a Behavior API for state machine interaction, a plugin system for custom node registrations via NodePlugin, and tools for schema definition using defineSchema. The ecosystem includes @portabletext/editor for core functionality, @portabletext/toolbar for UI components, and block-tools for HTML conversion and normalization.
What's inside Portable Text Editor
- Racetrack is a hosted playground and scenario-driven test runner designed specifically for Portable Text Editor plugins. It provides tools for visual scenario authoring, an in-browser scenario runner, a plugin garage, and an engine viewer to help developers test and showcase their editor extensions.
Overview of the Portable Text Editor
mainThe Portable Text Editor (
@portabletext/editor) is a headless, schema-driven block content editor designed for React. It is built around the Portable Text specification and follows a 'bring your own UI' philosophy: the editor manages the content state and editing logic, while you provide the visual implementation.Key architectural components include:
- Schema: Defines the allowed styles, decorators, annotations, lists, block objects, and inline objects.
- Rendering: Controlled via node registrations and render props on the
PortableTextEditablecomponent. - Containers: Allow complex structures (like callouts, code blocks, or tables) to host editable block content.
- Behavior API: An extensible system for defining how users interact with the content.
- Plugins: A modular system to extend editor functionality.
- @portabletext/toolbar: A companion package providing headless hooks for building custom toolbar interfaces.
Overview of the Portable Text Editor Monorepo
mainThe Portable Text Editor monorepo provides
@portabletext/editor, a headless, schema-driven block content editor for React. Unlike renderers (which are found in separate repositories like@portabletext/react), this project focuses on the editing experience. It is designed so that developers provide the UI, while the editor handles the underlying block content logic.Key architectural components include:
- Core: The editor engine, schema definition, and toolbar hooks.
- Plugins: Extensions for features like Markdown shortcuts, emoji pickers, tables, and typography.
- Utilities: Libraries for converting between Portable Text and HTML/Markdown, keyboard shortcuts, and testing utilities.
Supported Markdown features for Portable Text conversion
mainThe
@portabletext/markdownpackage (and related conversion logic) supports a wide range of Markdown syntax to transform it into structured Portable Text. Supported features include:- Text Formatting: Bold (
**bold**), italic (_italic_), inline code (`code`), and strikethrough (~~strikethrough~~). The parser supports nested formatting (e.g., bold and italic combined). - Links and Images:
- Inline links:
[text](url) - Reference-style links:
[text][ref1]with definitions like[ref1]: url - Implicit references
- Block images:
 - Inline images:
text  text - Autolinks:
<https://example.com>and<mailto:email@example.com>
- Inline links:
- Code Blocks: Fenced code blocks (with syntax highlighting support) and indented code blocks.
- Blockquotes: Single and nested blockquotes.
- Lists:
- Unordered lists (
-or*) - Ordered lists (
1.) - Mixed lists (nesting ordered lists inside unordered lists and vice versa)
- Unordered lists (
- Tables: Standard Markdown table syntax.
- Horizontal Rules: Separators using
---. - Headings: All levels from
# Heading 1to###### Heading 6. - Other: Hard line breaks (
\), HTML passthrough (e.g.,<div>or<span>), and special character handling (&,<,>).
- Text Formatting: Bold (
Supported Markdown features in Portable Text conversion
mainThe
@portabletext/markdownpackage (and related conversion logic) supports a wide range of Markdown syntax to transform it into structured Portable Text. Supported features include:- Text Formatting: Bold (
**), Italic (_), Inline Code (`), and Strikethrough (~~). The parser supports deep nesting (e.g., bold inside italic). - Links and Images: Inline links, reference-style links, and both block and inline images.
- Code Blocks: Fenced code blocks (with syntax highlighting) and indented code blocks.
- Blockquotes: Single and nested blockquotes.
- Lists: Unordered lists, ordered lists, and mixed lists (ordered parents with unordered children or vice versa) with deep nesting.
- Tables: Standard Markdown tables.
- Horizontal Rules: Separators using
---. - Headings: All levels from
#(H1) to######(H6). - Advanced Elements: Autolinks (URLs and mailto:), hard line breaks, and HTML passthrough (preserving raw HTML tags).
This comprehensive support ensures that the resulting Portable Text preserves both the visual structure and the semantic meaning of the original Markdown.
- Text Formatting: Bold (
Core concepts demonstrated in the Basic Editor example
mainThe basic example serves as a reference for implementing a full-featured Portable Text editor using the following patterns:
- Schema Definition: Using
defineSchemato configure decorators, annotations, styles, lists, block objects (e.g., images), and inline objects (e.g., stock tickers). - Node Registrations: Mounting specific node types using
NodePlugin. This involves using registration functions such as:defineTextBlockdefineBlockObjectdefineInlineObjectdefineDecoratordefineAnnotation
- List Management: Implementing list numbering via
@portabletext/plugin-list-index. - Toolbar Implementation: Building custom toolbars that toggle marks, styles, and lists by using the
useEditoranduseEditorSelectorhooks, combined with selectors from@portabletext/editor/selectors. - Value Monitoring: Reading and reacting to editor value changes using
EventListenerPluginto render live JSON previews.
- Schema Definition: Using
Use @portabletext/toolbar to build custom toolbars
mainThe@portabletext/toolbarpackage provides a set of hooks and types designed to help developers build custom toolbar interfaces for the Portable Text Editor. You can use these primitives to create UI elements that interact with the editor's state and commands.What is Portable Text?
mainPortable Text is a JSON-based specification for structured block content. Instead of using HTML strings or Markdown, content is represented as an array of typed blocks. Each block has a
_typeproperty and carries its own specific data.Key characteristics:
- Array of Blocks: The document is a top-level array containing various block types (e.g.,
block,image,callToAction). - Typed Content: Every block identifies itself via
_type, allowing serializers to know how to render it. - Structured Data: Because it is JSON, content is easily queryable and can be rendered into multiple formats (React, HTML, Markdown, PDF, etc.) using a serializer.
[ { "_type": "block", "style": "h1", "children": [{ "_type": "span", "text": "Why Portable Text?" }] }, { "_type": "image", "url": "photo.jpg", "alt": "A mountain landscape" } ]- Array of Blocks: The document is a top-level array containing various block types (e.g.,
What are Containers in Portable Text?
mainA container is a block object that holds editable block content. It is defined by having an array field whose members include text blocks or further containers. This allows you to nest editable content inside custom structures like callouts, code blocks, or tables without leaving the Portable Text format.
In the underlying data, a container is just a plain Portable Text object where one field holds an array of blocks. Serializers and queries treat them as nested Portable Text.
{ "_type": "callout", "_key": "a1b2c3", "tone": "note", "content": [ { "_type": "block", "_key": "d4e5f6", "children": [ { "_type": "span", "_key": "g7h8i9", "text": "Editable text inside the callout.", "marks": [] } ], "markDefs": [], "style": "normal" } ] }Understand the Portable Text Editor building blocks
mainThe Portable Text Editor is composed of several interconnected parts that define how content is structured and displayed. Key components include:
- Schema: Defines the structure and rules for content.
- Styles: Define the visual representation of elements.
- Decorators: Used to augment the visual appearance of nodes.
- Annotations: Metadata attached to text ranges.
- Lists, Block objects, and Inline objects: The fundamental structural units of the Portable Text format.
Understanding how these pieces connect is essential for configuring the editor's content model.
Extend the editor with Plugins
mainThe editor is extensible via a plugin system. Available plugin types and features include:
- Markdown shortcuts: Automatic formatting based on markdown syntax.
- Emoji picker: UI for inserting emojis.
- Input rules: Automatic transformations during typing.
- Character pair decorators: Special handling for specific character sequences.
- Paste link: Enhanced link handling during paste operations.
- One-line mode: Specialized editing mode.
- Typeahead picker: Autocomplete/suggestion UI.
- Typography transforms: Automatic typographic improvements.
Understand the Portable Text data model
mainPortable Text stores content as structured JSON data rather than HTML strings or Markdown. This approach treats content as a sequence of typed blocks, making it platform-agnostic and highly queryable.
Key Characteristics
- No rendering assumptions: The data doesn't assume a target format (like HTML). You can render the same JSON to React, HTML, PDF, or plain text.
- Structured Blocks: Content is organized into blocks (e.g.,
_type: 'block') which containchildren(spans of text) andmarkDefs(definitions for inline annotations like links). - Extensibility: You can define custom block types (e.g.,
callToAction,imageGallery) or custom inline annotations (e.g.,stockTicker) without breaking existing renderers. - Queryability: Because it is JSON, you can perform precise queries (e.g., "find all blocks containing a link to /docs") without fragile regex or HTML parsing.
[ { "_type": "block", "style": "normal", "children": [ {"_type": "span", "text": "Read the "}, { "_type": "span", "text": "documentation", "marks": ["a1b2c3"] }, {"_type": "span", "text": " for "}, { "_type": "span", "text": "details", "marks": ["strong"] }, {"_type": "span", "text": "."} ], "markDefs": [ { "_key": "a1b2c3", "_type": "link", "href": "/docs" } ] } ]