Security News
Fluent Assertions Faces Backlash After Abandoning Open Source Licensing
Fluent Assertions is facing backlash after dropping the Apache license for a commercial model, leaving users blindsided and questioning contributor rights.
@portabletext/editor
Advanced tools
The official editor for editing Portable Text – the JSON based rich text specification for modern content editing platforms.
This library provides you with the building blocks to create a completely custom editor experience built on top of Portable Text. We recommend checking out the official documentation. The following guide includes the basics to get your started.
In order to set up an editor you'll need to:
Check out example application in this repo for a basic implementation of the editor. Most of the source code from this example app can also be found in the instructions below.
# npm
npm i @portabletext/editor
# pnpm
pnpm add @portabletext/editor
# yarn
yarn add @portabletext/editor
Next, in your app or the component you're building, import EditorProvider
, EditorEventListener
, PortableTextEditable
, defineSchema
, and the types in the code below.
// App.tsx
import {
defineSchema,
EditorEventListener,
EditorProvider,
PortableTextEditable,
} from '@portabletext/editor'
import type {
PortableTextBlock,
RenderDecoratorFunction,
RenderStyleFunction,
} from '@portabletext/editor'
Before you can render the editor, you need a schema. The editor schema configures the types of content rendered by the editor.
We'll start with a schema that includes some common rich text elements.
[!NOTE] This guide includes a limited set of schema types, or rich text elements, to get you started. See the rendering guide for additional examples.
// App.tsx
// ...
const schemaDefinition = defineSchema({
// Decorators are simple marks that don't hold any data
decorators: [{name: 'strong'}, {name: 'em'}, {name: 'underline'}],
// Styles apply to entire text blocks
// There's always a 'normal' style that can be considered the paragraph style
styles: [
{name: 'normal'},
{name: 'h1'},
{name: 'h2'},
{name: 'h3'},
{name: 'blockquote'},
],
// The types below are left empty for this example.
// See the rendering guide to learn more about each type.
// Annotations are more complex marks that can hold data (for example, hyperlinks).
annotations: [],
// Lists apply to entire text blocks as well (for example, bullet, numbered).
lists: [],
// Inline objects hold arbitrary data that can be inserted into the text (for example, custom emoji).
inlineObjects: [],
// Block objects hold arbitrary data that live side-by-side with text blocks (for example, images, code blocks, and tables).
blockObjects: [],
})
Learn more about the different types that exist in schema in the Portable Text Overview.
With a schema defined, you have enough to render the editor. It won't do much yet, but you can confirm your progress.
Add useState
from React, then scaffold out a basic application component. For example:
// app.tsx
import {
defineSchema,
EditorEventListener,
EditorProvider,
PortableTextEditable,
} from '@portabletext/editor'
import type {
PortableTextBlock,
RenderDecoratorFunction,
RenderStyleFunction,
} from '@portabletext/editor'
import {useState} from 'react'
const schemaDefinition = defineSchema({
/* your schema from the previous step */
})
function App() {
// Set up the initial state getter and setter. Leave the starting value as undefined for now.
const [value, setValue] = useState<Array<PortableTextBlock> | undefined>(
undefined,
)
return (
<>
<EditorProvider
initialConfig={{
schemaDefinition,
initialValue: value,
}}
>
<EditorEventListener
on={(event) => {
if (event.type === 'mutation') {
setValue(event.value)
}
}}
/>
<PortableTextEditable
// Add an optional style to see it more easily on the page
style={{border: '1px solid black', padding: '0.5em'}}
/>
</EditorProvider>
</>
)
}
export default App
Include the App
component in your application and run it. You should see an outlined editor that accepts text, but doesn't do much else.
At this point the PTE only has a schema, but it doesn't know how to render anything. Fix that by creating render functions for each property in the schema.
Start by creating a render function for styles.
const renderStyle: RenderStyleFunction = (props) => {
if (props.schemaType.value === 'h1') {
return <h1>{props.children}</h1>
}
if (props.schemaType.value === 'h2') {
return <h2>{props.children}</h2>
}
if (props.schemaType.value === 'h3') {
return <h3>{props.children}</h3>
}
if (props.schemaType.value === 'blockquote') {
return <blockquote>{props.children}</blockquote>
}
return <>{props.children}</>
}
Render functions all follow the same format.
children
as a fallback.With this in mind, continue for the remaining schema types.
Create a render function for decorators.
const renderDecorator: RenderDecoratorFunction = (props) => {
if (props.value === 'strong') {
return <strong>{props.children}</strong>
}
if (props.value === 'em') {
return <em>{props.children}</em>
}
if (props.value === 'underline') {
return <u>{props.children}</u>
}
return <>{props.children}</>
}
[!NOTE] By default, text is rendered as an inline
span
element in the editor. While most render functions return a fragment (<>
) as the fallback, make sure block level elements return blocks, like<div>
elements.
Update the PortableTextEditable
with each corresponding function to attach them to the editor.
You may notice that we skipped a few types from the schema. Declare these inline in the configuration, like in the code below. You can learn more about customizing the render functions in the documentation.
<PortableTextEditable
style={{border: '1px solid black', padding: '0.5em'}}
renderStyle={renderStyle}
renderDecorator={renderDecorator}
renderBlock={(props) => <div>{props.children}</div>}
renderListItem={(props) => <>{props.children}</>}
/>
Before you can see if anything changed, you need a way to interact with the editor.
A toolbar is a collection of UI elements for interacting with the editor. The PTE library gives you the necessary hooks to create a toolbar however you like. Learn more about creating your own toolbar in the documentation.
Toolbar
component in the same file.useEditor
hook, and declare an editor
constant in the component.// App.tsx
// ...
import {useEditor} from '@portabletext/editor'
function Toolbar() {
// useEditor provides access to the PTE
const editor = useEditor()
// Iterate over the schema (defined earlier), or manually create buttons.
const styleButtons = schemaDefinition.styles.map((style) => (
<button
key={style.name}
onClick={() => {
// Send style toggle event
editor.send({
type: 'style.toggle',
style: style.name,
})
editor.send({
type: 'focus',
})
}}
>
{style.name}
</button>
))
const decoratorButtons = schemaDefinition.decorators.map((decorator) => (
<button
key={decorator.name}
onClick={() => {
// Send decorator toggle event
editor.send({
type: 'decorator.toggle',
decorator: decorator.name,
})
editor.send({
type: 'focus',
})
}}
>
{decorator.name}
</button>
))
return (
<>
{styleButtons}
{decoratorButtons}
</>
)
}
The useEditor
hook gives you access to the active editor. send
lets you send events to the editor. You can view the full list of events in the Behavior API reference.
[!NOTE] The example above sends a
focus
event after each action. Normally when interacting with a button, the browser removes focus from the text editing area. This event returns focus to the field to prevent interrupting the user.
With render functions created and a toolbar in place, you can fully render the editor. Add the Toolbar
inside the EditorProvider
.
// App.tsx
// ...
function App() {
const [value, setValue] = useState<Array<PortableTextBlock> | undefined>(
undefined,
)
return (
<>
<EditorProvider
initialConfig={{
schemaDefinition,
initialValue: value,
}}
>
<EditorEventListener
on={(event) => {
if (event.type === 'mutation') {
setValue(event.value)
}
}}
/>
<Toolbar />
<PortableTextEditable
style={{border: '1px solid black', padding: '0.5em'}}
renderStyle={renderStyle}
renderDecorator={renderDecorator}
renderBlock={(props) => <div>{props.children}</div>}
renderListItem={(props) => <>{props.children}</>}
/>
</EditorProvider>
</>
)
}
// ...
You can now enter text and interact with the toolbar buttons to toggle the styles and decorators. These are only a small portion of the types of things you can do. Check out the custom rendering guide and the toolbar customization guide for options.
You can preview the Portable Text from the editor by reading the state. Add the following after the EditorProvider
.
<pre style={{border: '1px dashed black', padding: '0.5em'}}>
{JSON.stringify(value, null, 2)}
</pre>
This displays the raw Portable Text. To customize how Portable Text renders in your apps, explore the collection of serializers.
The Behavior API is a new way of interfacing with the Portable Text Editor. It allows you to think of and treat the editor as a state machine by:
Learn more about the Behaviors and how to create your own behaviors in the documentation.
In order to provide a robust and consistent end-user experience, the editor is backed by an elaborate E2E test suite generated from a human-readable Gherkin spec.
pnpm build:editor
to make sure it builds correctlypnpm dev:editor
to run it in dev modecd
into the sanity
package and run pnpm link <relative path to the **editor** package in this repo>
Now, you should be able to run pnpm dev:test-studio
in the sanity
repo to test Studio with a locally running Portable Text Editor.
FAQs
Portable Text Editor made in React
The npm package @portabletext/editor receives a total of 108,105 weekly downloads. As such, @portabletext/editor popularity was classified as popular.
We found that @portabletext/editor demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 9 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Fluent Assertions is facing backlash after dropping the Apache license for a commercial model, leaving users blindsided and questioning contributor rights.
Research
Security News
Socket researchers uncover the risks of a malicious Python package targeting Discord developers.
Security News
The UK is proposing a bold ban on ransomware payments by public entities to disrupt cybercrime, protect critical services, and lead global cybersecurity efforts.