@infonomic/payload-alternative-lexical-editor 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Payload CMS 3.0 Alternative Lexical Rich Text Editor
|
|
2
|
+
|
|
3
|
+
This is a Payload 3.0 demo with a custom Lexical Rich Text adapter.
|
|
4
|
+
|
|
5
|
+
NOTE: 2025-12-29 - the Lexical editor/adapter has been moved into `packages/payload-alternative-lexical-editor` and will be published to npm soon.
|
|
6
|
+
|
|
7
|
+
> [!IMPORTANT]
|
|
8
|
+
> Unless you have very specific needs, or a lot of experience with Lexical and custom Payload fields, you should almost certainly be using the new and official [Payload Lexical Rich Text editor](https://payloadcms.com/docs/lexical/overview).
|
|
9
|
+
>
|
|
10
|
+
|
|
11
|
+
It's also important to mention that creating a generalized and extensible editor with a 'pluggable' feature system - such as the one currently being developed by Alessio and the team at Payload, is an order of magnitude more difficult than creating an 'opinionated' adapter with zero extensibility. The Payload team are doing amazing work - and this repo and our editor is in no way a criticism of the work being done at Payload.
|
|
12
|
+
|
|
13
|
+
## Our Lexical Editor
|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
## Background
|
|
18
|
+
|
|
19
|
+
We started working with [Lexical](https://lexical.dev/) in 2022 while searching for a replacement CMS for our agency. We then discovered [Payload CMS](https://payloadcms.com/) - which ticked nearly every box, with one notable exception - the use of Slate as their rich text editor. We'd worked with Slate and other editors previously and really wanted to use Lexical.
|
|
20
|
+
|
|
21
|
+
And so we started work on a Lexical-based rich text field for Payload. Early in 2023 we discovered [Alessio Gravili's Payload Lexical Plugin](https://github.com/AlessioGr/payload-plugin-lexical) which helped enormously in getting started with Payload and custom fields. We also attempted to 'give back' to the work Alessio was doing with contributions to his public repo.
|
|
22
|
+
|
|
23
|
+
Thanks largely to Alessio's efforts, Lexical has now been adopted by the Payload team and is on its way to becoming the default editor for Payload, which is fantastic.
|
|
24
|
+
|
|
25
|
+
There are still however, a few cases that meant for us, continuing with our own editor is our preferred approach for the moment, although it's likely that most of these issues will be resolved over time and we'll eventually shift to converting our Lexical plugins to official Payload Lexical features.
|
|
26
|
+
|
|
27
|
+
## Rationale
|
|
28
|
+
|
|
29
|
+
Here are the main drivers for us wanting to maintain our own editor:
|
|
30
|
+
|
|
31
|
+
1. We'd already created a custom Lexical rich text field (before Lexical was included in Payload) and felt that at the time it would be easier to convert this to an adapter than convert our plugins and nodes to features.
|
|
32
|
+
|
|
33
|
+
2. As a candidate editor for existing projects - in particular for our Drupal users - we needed an 'across the top' editor [toolbar](https://github.com/infonomic/payload-alternative-lexical-editor/blob/main/packages/payload-alternative-lexical-editor/src/field/plugins/toolbar-plugin/index.tsx) including support for `LexicalNestedComposer`. The good news is that a fixed toolbar is on its way to the official Payload Lexical editor.
|
|
34
|
+
|
|
35
|
+
3. We needed a way to call `setValue` for the RichText field from `LexicalNestedComposer` within our image plugin captions and admonition plugin text, and so created `SharedOnChangeContext`. When versions are enabled, this means that the 'Save Draft, and 'Publish Changes' buttons become 'enabled' when `LexicalNestedComposer` text is changed. Overall, our structure for [context providers](https://github.com/infonomic/payload-alternative-lexical-editor/blob/main/packages/payload-alternative-lexical-editor/src/field/editor-context.tsx) for the editor is a little different as well.
|
|
36
|
+
|
|
37
|
+
4. We wanted control over the serialization of internal links. See the special section below on Richtext Internal Links Strategy.
|
|
38
|
+
|
|
39
|
+
5. In Payload 3.0 - we wanted to experiment with client-only forms using the new field api and `RenderFields`. You can see an example here in our [Admonition plugin](https://github.com/infonomic/payload-alternative-lexical-editor/blob/main/packages/payload-alternative-lexical-editor/src/field/plugins/admonition-plugin/admonition-drawer.tsx). This is totally experimental. It works (as far as we can tell) and we're using this for all of our custom components that require modals or drawers with Payload fields.
|
|
40
|
+
|
|
41
|
+
6. We wanted to share our plugins - in particular our Inline Image plugin which was accepted into the Lexical playground and our Admonition plugin. In fact, our Inline Image plugin was one of the main reasons we chose Lexical as our preferred editor. Try creating a floated inline element that appears correctly in both the admin editor and the front end application - inside any of the 'other editors', and you'll see why ;-). Most of the other plugins in this repo track Lexical Playground plugins and are updated from there.
|
|
42
|
+
|
|
43
|
+
7. And lastly, we wanted to keep our editor lightweight and fast, in particular for longer documents.
|
|
44
|
+
|
|
45
|
+
## Richtext Internal Links Strategy
|
|
46
|
+
|
|
47
|
+
As mentioned in the rationale section above, we wanted control over the serialization of internal links. Instead of retrieving and populating an entire document for each internal link in the editor via an `afterRead` field hook, we wanted to augment the relationship with just the slug and title.
|
|
48
|
+
|
|
49
|
+
Here's our version of the Lexical link node:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"direction": "ltr",
|
|
54
|
+
"format": "",
|
|
55
|
+
"indent": 0,
|
|
56
|
+
"type": "link",
|
|
57
|
+
"version": 2,
|
|
58
|
+
"attributes": {
|
|
59
|
+
"newTab": false,
|
|
60
|
+
"linkType": "internal",
|
|
61
|
+
"doc": {
|
|
62
|
+
"value": "6635e07947922a2b9194d9a2",
|
|
63
|
+
"relationTo": "minimal",
|
|
64
|
+
"data": {
|
|
65
|
+
"id": "6635e07947922a2b9194d9a2",
|
|
66
|
+
"title": "This is a Test Minimal Page",
|
|
67
|
+
"slug": "this-is-a-test-minimal-page"
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"text": "Click Me!"
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
We've added an additional property called `data`, to which we've added the id, title and slug for the target document. When combined with the relationTo property - this is everything the front end application needs to create a complete link or router link to the target document.
|
|
75
|
+
|
|
76
|
+
> [!IMPORTANT]
|
|
77
|
+
> We have two strategies for populating the data property above. The first, via an `afterRead` hook, and the second via a `beforeChange` hook. You can choose which to implement based on your requirements.
|
|
78
|
+
>
|
|
79
|
+
|
|
80
|
+
### afterRead
|
|
81
|
+
|
|
82
|
+
When using an `afterRead` hook - we add the `data` property and populated the title and slug for the related document dynamically during document read. Here's our [`afterRead`](https://github.com/infonomic/payload-alternative-lexical-editor/blob/main/packages/payload-alternative-lexical-editor/src/field/lexical-after-read-populate-links.ts) field hook. Note however, that for documents that contain more than one or two links, this can add a significant number of document requests for a single source document since the related document for each internal link will need to be retrieved in order to populate our data property (O(n) linear time complexity). In our experience, this can have a major impact on overall performance and user experience.
|
|
83
|
+
|
|
84
|
+
### beforeChange
|
|
85
|
+
|
|
86
|
+
When using a `beforeChange` hook - we add the `data` property to the document itself when the document is being saved. Here's our [`beforeChange`](https://github.com/infonomic/payload-alternative-lexical-editor/blob/main/packages/payload-alternative-lexical-editor/src/field/lexical-before-change-populate-links.ts) hook. Obviously this has implications for stale links (source documents who's title or slug may have changed). However, there is no impact on overall performance and user experience, since the source document already contains the data it needs for internal links (O(1) constant time complexity).
|
|
87
|
+
|
|
88
|
+
The configuration in this repo is using the `beforeChange` strategy, although this can be changed here in the hooks property for the [richtext adapter](https://github.com/infonomic/payload-alternative-lexical-editor/blob/main/packages/payload-alternative-lexical-editor/src/adapter.ts).
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
## Getting Started
|
|
93
|
+
|
|
94
|
+
1. Clone this repo
|
|
95
|
+
2. If you don't already have an instance of MongoDB running locally we've provided a docker composer file and a shell start script. To start `cd mongodb` from the project root. `mkdir data` and then `./mongo.sh up` to start a local instance of MongoDB with a fresh database.
|
|
96
|
+
3. In the `apps/next` directory - copy `.env.example` to `.env` (Note: Don't deploy this to production or a public service without changing your PAYLOAD_SECRET).
|
|
97
|
+
4. From the root - run `pnpm install` followed by `pnpm dev`.
|
|
98
|
+
5. To run a production build - from the root run `pnpm build` followed by `pnpm start`.
|
|
99
|
+
|
|
100
|
+
Thoughts, suggestions or contributions more than welcome. We hope that some of this helps.
|
|
101
|
+
|