Replies: 8 comments 23 replies
-
Plate or Slate-Plugin would be a very great choice, I already try to implement the plugins method like the way in Docs and Demo do, but yeah it's pretty hard. I can't make it work! so I was looking into this #126 and hope it would be Plate soon! This should be the default |
Beta Was this translation helpful? Give feedback.
-
Any update on this coming soon? We are missing Tables in Slate and the Plate is looking great! |
Beta Was this translation helpful? Give feedback.
-
Over the next few months, we’ll be undergoing an overhaul of the rich text editor to make the experience of creating powerful, rich content more performant and intuitive for both developers and editors. This will involve an evaluation of which editor to build upon, improve the extensibility of the editor with blocks and custom elements, improving the user experience and adding interactive features, and exploring ways to introduce AI capabilities for content editing. Easier extensibilityThe current process of building custom elements into the editor is challenging. The goal is to simplify the extension mechanism, allowing users to seamlessly add custom elements, such as tables, without difficulty. This change could also mean adding more elements to the default text editor, such as tables. Embed Blocks within the editor itselfWe plan to add the ability to specify blocks to be embeddable within the rich text editor itself, allowing you to create custom blocks that can be reused across your content. This will allow for more flexibility and consistency in formatting. Adding in blocks will be similar to how relationships are currently handled in the editor. Dragging and dropping blocks between other content should be seamless, allowing for super fast reorganizing your content. Embedded block data would be stored within the rich text editor’s JSON format. Blocks will still be able to be used outside of the editor as normal. Improved UXThe editing experience should be improved to provide a more intuitive experience. Some of the improvement to be made are:
Introduce AI capabilitiesTo create a more intelligent editor, we will explore the possibility of integrating AI capabilities. This could involve using machine learning algorithms to suggest text formatting, auto-complete commonly used phrases, and other assistive uses for content editors. Before developing these improvements, we'll be doing an in-depth iteration of the interface design, and considering the best ways to implement these features. We'll be considering not just the visual look and feel, but also how to make keyboard interaction and AI integration feel as natural and beneficial as possible. |
Beta Was this translation helpful? Give feedback.
-
@jmikrut absolutely, this is the path! |
Beta Was this translation helpful? Give feedback.
-
Hi All, Assuming I can contribute anything meaningful to this discussion - here are a few thoughts...
Hope some of this helps. |
Beta Was this translation helpful? Give feedback.
-
RichText extensibilityWe’re currently working on a new richtext editor based on Lexical - a new richtext editor by Meta - into Payload CMS’s core. Before moving forward, I’d like to gather your insights and feedback on the extensibility aspect of a new editor. For those interested, you can view the design considerations and visual aspects of this proposal here. Extending the current Slate editorCurrently, extending the Slate editor in Payload CMS involves specifying a list of elements, leaves, and additional properties specific to uploads and links. Here’s a brief overview of the present extension configuration: admin: {
elements: [
"h2",
"h3",
"h4",
"link",
"blockquote",
{
name: "cta",
Button: CustomCallToActionButton,
Element: CustomCallToActionElement,
plugins: [
// any plugins that are required by this element go here
],
},
],
leaves: [
"bold",
"italic",
{
name: "highlight",
Button: CustomHighlightButton,
Leaf: CustomHighlightLeaf,
plugins: [
// any plugins that are required by this leaf go here
],
},
],
link: {
// Inject your own fields into the Link element
fields: [
{
name: "rel",
label: "Rel Attribute",
type: "select",
hasMany: true,
options: ["noopener", "noreferrer", "nofollow"],
},
],
},
upload: {
collections: {
media: {
fields: [
// any fields that you would like to save
// on an upload element in the `media` collection
],
},
},
}, One significant limitation with this approach is that it doesn’t allow for passing extra options for default elements (e.g., “link”), as default elements are specified using a simple string list. That’s why the additional properties 1. Improvement Step: passing in elements directlyThe goal of a new Lexical-based editor is to overcome this limitation and establish a more powerful system where you pass the actual element/leaf instead of just their names. So, instead of elements: {
...
"h2",
"link",
},
link: {
fields: {
{
name: "rel",
label: "Rel Attribute",
type: "select",
hasMany: true,
options: ["noopener", "noreferrer", "nofollow"],
},
},
// Other link-specific options
} You’d do import { H2Element, LinkElement } from '@payloadcms/lexical/elements'
elements: {
H2Element({}),
LinkElement({
fields: {
{
name: "rel",
label: "Rel Attribute",
type: "select",
hasMany: true,
options: ["noopener", "noreferrer", "nofollow"],
},
},
// Other link-specific options
}),
}, 2. Improvement Step: “feature” system instead of elements/leavesOne piece of functionality can have multiple touchpoints! We might encounter scenarios where some features engender both leaves and elements, as well as other functionalities. For instance, a “Translation” feature could simply add a new option in the popover menu to translate selected text, whereas a “Preview” feature might create a collapsible under the editor to toggle a preview of how the content looks like. These wouldn’t fit into elements or leaves, would they? We’d need to have many more properties than just elements and leaves. And in the end, you would have features being present in multiple areas of the configuration at the same time, just because they hook into different areas of the editor. It would be a messy configuration clusterfuck. Another example: uploads! A basic upload adds a drawer, a button in the toolbar to enable it, and the upload element. For the new lexical editor, the uploads feature would also need
To encapsulate this new approach, I’m contemplating a streamlined list of “Features”, with each feature representing a single, isolated piece of functionality. How extensibility would look like: Before: import { H2Element, LinkElement } from '@payloadcms/lexical/elements'
import { BoldLeave, ItalicLeave } from '@payloadcms/lexical/leaves'
elements: {
H2Element({}),
LinkElement({
fields: {},
// Other link-specific options
}),
},
leaves: {
BoldLeave({}),
ItalicLeave({}),
} After: import { H2, Links, BoldText, ItalicText, AI, Translator } from '@payloadcms/lexical'
features: {
H2({}),
Links({
fields: {},
// Other link-specific options
}),
BoldText({}),
ItalicText({}),
AI({}),
Translator({})
} as Feature[], One key aspect to focus here is the structure of the export interface Feature {
plugins?: Array<{
// plugins are anything which is not directly part of the editor. Like, creating a command which creates a node, or opens a modal, or some other more "outside" functionality
component: JSX.Element;
position?: 'normal' | 'bottom' | 'outside';
onlyIfNotEditable?: boolean;
}>;
floatingAnchorElemPlugins?: Array<(floatingAnchorElem: HTMLDivElement) => JSX.Element>; // Plugins which are put in the floating anchor element (the thing which appears when you select text)
subEditorPlugins?: JSX.Element[]; // Plugins which are embedded in other sub-editors, like image captions (which is basically an editor inside of an editor)
tablePlugins?: JSX.Element[]; // Plugins which are put inside of the table plugin
nodes?: Array<Klass<LexicalNode>>; // Nodes = Leaves and elements in Slate. Nodes are what's actually part of the editor JSON
tableCellNodes?: Array<Klass<LexicalNode>>; // Nodes which are put inside of the table plugin
modals?: Array<{
// Modals / Drawers. They can be defined here in order to be able to open or close them with a simple lexical command. This also ensures the modals/drawers are "placed" at the correct position
openModalCommand: {
type: string;
command: (toggleModal: (slug: string) => void, editDepth: number, uuid: string) => void;
};
modal: (props: { editorConfig: EditorConfig }) => JSX.Element;
}>;
floatingTextFormatToolbar?: {
// The floating toolbar which appears when you select text
components?: Array<(editor: LexicalEditor, editorConfig: EditorConfig) => JSX.Element>;
};
componentPicker?: {
// Component picker is the thing which pops up when you type "/". So, slash commands.
componentPickerOptions: Array<
(editor: LexicalEditor, editorConfig: EditorConfig) => ComponentPickerOption
>;
};
markdownTransformers?: Transformer[]; // Handle transforming the editor state into markdown, and the other way around
embedConfigs?: EmbedConfig[]; // Every embed plugin / node (like twitter, youtube or figma embeds) should define one of those
actions?: JSX.Element[]; // Actions are added in the ActionsPlugin - it's the buttons you see on the bottom right of the editor
} In the Nodes encapsulate similar entities to leaves and elements in Slate, representing the integral parts of the editor’s JSON. The In the end, a Feature is one logical piece of functionality which is able to hook into any part of the editor it needs to hook into. Example 1: EquationsTo provide a tangible example of how a feature is implemented in this new system, let’s examine the EquationsFeature: return {
plugins: [
{
component: <EquationsPlugin key="equations" />,
},
],
nodes: [EquationNode],
tableCellNodes: [EquationNode],
modals: [
{
modal: InsertEquationDrawer,
openModalCommand: {
type: 'equation',
command: (toggleModal, editDepth, uuid: string) => {
const addEquationDrawerSlug = formatDrawerSlug({
slug: `lexicalRichText-add-equation` + uuid,
depth: editDepth,
});
toggleModal(addEquationDrawerSlug);
},
},
},
],
componentPicker: {
componentPickerOptions: [componentPickerOption],
},
markdownTransformers: [equationMarkdownTextMatchTransformer],
}; Additionally to the ability to contain new nodes and plugins, an independent feature like EquationsFeature also brings in component picker options (slash menu) and markdown transformers. Example 2: AutoCompleteThe autocomplete feature automatically suggests completions for new words - similarly to GitHub copilot. Its feature would look like this: export function AutoCompleteFeature(): Feature {
return {
plugins: [
{
component: <AutocompletePlugin key=“autocomplete” />,
},
],
nodes: [AutocompleteNode],
tableCellNodes: [AutocompleteNode],
};
} All it needs is a plugin which handles listening to editorState changes so that it can suggest autocompletions at the current cursor’s position, as well as the nodes, which represent the gray suggested autocompletion text next to your cursor. All clearly defined - one feature which hooks into the places it needs to hook into. The plugin adds new functionality, not a node. I wouldn’t want to clump it all into one “Element”. I also wouldn’t want this to be present three times in the same editor configuration. 3. Improvement Step: function instead of features arrayPreviously, if you wanted to extend the existing base editor, e.g. by adding an AI and Translator plugin, you’d have to verbosely specify every single leave/element already present in the base editor and then add your own: import { H2, H3, H4, H5, Links, Uploads, Embeds, BoldText, ItalicText, AI, Translator } from '@payloadcms/lexical'
features: {
H2({}),
H3({}),
H4({}),
H5({}),
Links({}),
Uploads({}),
Embeds({}),
BoldText({}),
ItalicText({}),
// .... an eternity later
AI({}),
Translator({}),
}, Instead of an array of features, I’m proposing a function which gives you the “default” feature array, as an argument and accepts the new feature array as a return value. If you wanted to add the AI feature to the base editor, it would work like this: import { AI, Translator } from '@payloadcms/lexical'
features: ({ defaultFeatures }) => {
return defaultFeatures.features.concat([
AI({}),
Translator({}),
])
} Or if you prefer the spread syntax: import { AI, Translator } from '@payloadcms/lexical'
features: ({ defaultFeatures }) => {
return {
...defaultFeatures,
AI({}),
Translator({}),
},
} This would make it easier to do minor edits to the default editor, instead of having to redefine the entire Features array. Another goal is: if you pass in an empty Features array, the richtext editor should be as minimalistic as possible. Most of the functionality should lie outside the actual editor core, in the form of features. This will give you maximum extensibility. Flexibility: A Double-Edged SwordGranting too much flexibility might make the richtext editor daunting and cumbersome to extend through features. Consider the challenge of adding a new tool to the hover-over-text toolbar. There are two plausible approaches:
Or, we can just have both - but this will long-term lead to a very large number of properties in the We need to find the perfect balance between maximum flexibility and maximum simplicity / being opinionated. The eventual design of the |
Beta Was this translation helpful? Give feedback.
-
@AlessioGr (cc @jmikrut) a few logistical questions about this for folks in active development around slate and content migrations into slate from other systems:
|
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
Description
Although the
richText
field type is currently extremely extensible, the SlateJS editor is often difficult to understand and develop for. This feature will see Payload exporting many commonly needed components to make therichText
field a bit easier to work with. It also may see the introduction of the plate plugins system.Implementation Detail
onBlur
,onFocus
, and similarrichText
admin UI component that renders aplate
-friendly instance of Slate instead of the vanilla Slate editorPotential Breaking Changes
None
Effort
High
Beta Was this translation helpful? Give feedback.
All reactions