-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
POC - Migrating to Plate #575
base: main
Are you sure you want to change the base?
POC - Migrating to Plate #575
Conversation
…plate-common package
c150327
to
b2d7993
Compare
"module": "esnext", | ||
"moduleResolution": "bundler", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These changes were required for Plate packages to be able to be imported.
Surprised to see noone has commented or reviewed 😠 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow! This was a lot to read in one go :D
Great job! So many code touched and converted to the new API! 🏆
I've left some comments/questions below. Some are just to not forget to address them before merging the code to master.
My main source of cognitive load in this PR was the distinction between Slate and Plate types, as well as different APIs seemingly doing the same thing. A few examples:
Element
,Text
andNode
fromslate
vsTElement
,TText
andTNode
fromplate
Editor
,Transforms
,Node
fromslate
vseditor.{method}
or helper functions fromplate
Editor
vsReactEditor
vsSlateEditor
vsPlateEditor
What are the differences? Should we always use the Plate's version? Is there a danger of using the original Slate's APIs? Can it cause problems with Plate?
packages/slate-editor/src/extensions/flash-nodes/FlashNodesExtension.tsx
Outdated
Show resolved
Hide resolved
@@ -10,4 +11,4 @@ import type { NodeEntry, Range } from 'slate'; | |||
* keywords, where changes to the content (or some external data) has the | |||
* potential to change the formatting. | |||
*/ | |||
export type Decorate = (entry: NodeEntry) => Range[]; | |||
export type Decorate = (options: { editor: SlateEditor; entry: TNodeEntry }) => Range[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say that the function interface should be (editor: SlateEditor, entry: TNodeEntry) => Range[]
for consistency with other editor extension points below.
|
||
import type { HierarchyNormalizer } from '../types'; | ||
|
||
export function removeWhenNoChildren(): HierarchyNormalizer { | ||
return (editor, node, path) => { | ||
if ('children' in node && node.children.length === 0) { | ||
Transforms.removeNodes(editor, { at: path, match: (n) => n === node }); | ||
if ((isEditor(node) || isElement(node)) && node.children.length === 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isEditor(node)
I don't think it should be here. We never want to remove the editor node :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed. I didn't find a good way to narrow down the type so TS is satisfied. Will take another look
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But can you not do this?
if (isElement(node) && node.children.length === 0) {
editor.removeNodes({ at: path, match: (n) => n === node });
return true;
}
return false;
@@ -13,7 +18,7 @@ export function isEditorValueEqual<T extends Descendant>(editor: Editor, a: T[], | |||
if (!isNodeText && !isAnotherText) { | |||
const equal = editor.isElementEqual(node as Element, another as Element); | |||
if (typeof equal !== 'undefined') { | |||
if (editor.isVoid(node) || editor.isVoid(another)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why in other cases we switch from helper functions to editor.{method}()
APIs, but in this case it's the opposite? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think my only answer would be that it's easier to use editor.{method}
because you don't have to import extra stuff?
Or, in case the editor.{method}
does not exist, you import the specific function.
I don't remember exactly why I did it this way, but I'd say it's better to go with the editor.{method}
approach for less overal imports.
[ | ||
events, | ||
withAutoformat, | ||
withBlockquotes, | ||
withCallouts, | ||
withDivider, | ||
withHeadings, | ||
withLists, | ||
withTextStyling, | ||
], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should plugins
be in the dependency list?
@@ -108,8 +101,5 @@ export function getAllExtensions() { | |||
} | |||
|
|||
export function createEditor(input: JSX.Element) { | |||
return createBaseEditor(input as unknown as Editor, getAllExtensions, [ | |||
withEvents(events), | |||
withNodesHierarchy(hierarchySchema), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's make sure these two (or at least withNodesHierarchy()
) are used in tests before we merge the PR.
// withAutoformat | ||
// withButtonBlocks | ||
// withFloatingAddMenu={{ | ||
// tooltip: { | ||
// placement: 'left', | ||
// content: 'Add content to your story', | ||
// }, | ||
// }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leaving a note to either get this back to working, or remove before we merge the PR.
I am still wrapping my head around the API, but from my understanding, I believe we should be using the Plate wrapper functions as those are the ones returning the It gets a little bit confusing because when you use the shortcut When I was migrating the code, I didn't replace all the usages of Slate functions and types with the Plate counterpart because it would take too much time + it would also require reworking all the extensions as well, which would turn this large PR into even larger one. So in most places I've only updated the API calls to satisfy TS, which seems like it's enough for the most part, but of course ideally we'll update everything along the way as we work our way through the extensions. It's still a learning curve but should get clearer as we refactor more code. |
I've converted it to draft to avoid accidentally merging :) |
This is the first part of the migration to Plate, replacing the main React component and all the code that depends on it with the Plate counterpart or Plate types.
For now, most of the existing plugins (extensions) have been left as-is so the PR doesn't become massive. This should also make sure that the editor behaviour stays the same.
One of the changes I've made has been to the
plugins
prop of theEditor
component, which now accepts an array ofPlatePlugin
interface, allowing us to provide plugins from the outside, which is something we're planning to do eventually.In the meantime, when this PR is in review and we can test the editor in Prezly, I'll work on refactoring the plugins to use Plate plugin approach and/or replacing them with existing Plate plugins to get rid of as much custom code as possible.