Skip to content
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

Implement a plugin that tracks node positions. #33

Merged
merged 2 commits into from
Jun 1, 2023

Conversation

smoores-dev
Copy link
Collaborator

The plugin tracks a unique key for each (non-text) node in the document, identified by its current position. This provides a mechanism for node views to uniquely identify themselves via a key, as well as determine the position of other node views from their respective keys. Keys remain mostly stable across changes to the document.

This will enable other improvements, like a safer mechanism for accessing node position from within NodeViews and proper React Context hierarchies across node view components.

The plugin provides a mechanism for node views to uniquely identify
themselves via a key, as well as determine the position of other node
views from their respective keys. Keys remain mostly stable across
changes to the document.

This will enable other improvements, like a safer mechanism for
accessing node position from within NodeViews and proper React Context
hierarchies across node view components.
@smoores-dev smoores-dev requested a review from a team as a code owner May 31, 2023 16:07
@smoores-dev
Copy link
Collaborator Author

This change is pre-work for #27, #30, and potentially #32!

Copy link
Contributor

@tilgovi tilgovi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great.

What do you think about following ProseMirror's naming convention for plugin factories a little bit more by dropping the Plugin suffix from the filenames and factory names?

In other words, just:

import { reactNodeViews } from 'react-prosemirror';

EditorState.create({ schema, plugins: [reactNodeViews(), collab()] })

@smoores-dev
Copy link
Collaborator Author

I'm fine with the factory functions, but I'm a little worried about the idea of just exporting something called reactNodeViews! It just seems confusing

@tilgovi
Copy link
Contributor

tilgovi commented May 31, 2023

I'm curious whether the relatively close naming of reactNodeViews (the plugin) and useNodeViews (the hook) is an unfortunate near collision or actually suggests something insightful about maybe the hook being the wrong API.

@smoores-dev
Copy link
Collaborator Author

It's kind of interesting... the reactNodeViews plugin is mostly named as such because, opaquely, to users, you need to use it in order to use React-based node views. But it doesn't intrinsically have anything to do with node views, just provides a convenient way to track nodes across transactions.

@tilgovi
Copy link
Contributor

tilgovi commented May 31, 2023

I'm kinda okay with it being less descriptive, I think. Contextually, it'll make sense, and especially with API documentation and examples. Folks will import it and include it without thinking much of it, I think.

@tilgovi
Copy link
Contributor

tilgovi commented May 31, 2023

Yeah, I guess we probably do not want to track state of the node views within the reactNodeViews plugin. So, maybe we need another name for it, and that'll help resolve the suffix issue, too.

@smoores-dev
Copy link
Collaborator Author

I'm kinda okay with it being less descriptive, I think. Contextually, it'll make sense, and especially with API documentation and examples. Folks will import it and include it without thinking much of it, I think.

Taking that a little further, then: What if we just named it react? That feels more inline with ProseMirror's first-party plugins, and is also how, say, Vite does its plugin naming

@tilgovi
Copy link
Contributor

tilgovi commented May 31, 2023

Taking that a little further, then: What if we just named it react? That feels more inline with ProseMirror's first-party plugins, and is also how, say, Vite does its plugin naming

I feel good about that. Also, it feels like the key (:smirk:) thing that integrates React with ProseMirror is keying of nodes. That's what bridges the gap between React and ProseMirror reconciliation.

@tilgovi
Copy link
Contributor

tilgovi commented May 31, 2023

Although, will that feel really awful if you import it in a file where you also import React?

@tilgovi
Copy link
Contributor

tilgovi commented May 31, 2023

nodeKeys()?

@smoores-dev
Copy link
Collaborator Author

Although, will that feel really awful if you import it in a file where you also import React?

I think it won't? I feel like, since React is always and must be imported specifically as "React", the big "R" is really part of it. I think it would be fine to have both.

nodeKeys()?

I don't like this because explaining why it's called this requires explaining a bunch of internal details that consumers definitely do not need to know. So either it's just gibberish by design or it's way too in-the-weeds.

@tilgovi
Copy link
Contributor

tilgovi commented May 31, 2023

I'm fine to try react.

@tilgovi
Copy link
Contributor

tilgovi commented May 31, 2023

Now is the time to play with names and get feedback, after all!

@smoores-dev
Copy link
Collaborator Author

Done!

@smoores-dev
Copy link
Collaborator Author

Cleaned those up, thanks for spotting haha

Copy link
Contributor

@tilgovi tilgovi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plugins are meant to be public, yeah? Do we need to create an index for the plugins directory and re-export the plugins, then re-export them from the root?

Copy link
Contributor

@tilgovi tilgovi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plugins are meant to be public, yeah? Do we need to create an index for the plugins directory and re-export the plugins, then re-export them from the root?

@tilgovi
Copy link
Contributor

tilgovi commented May 31, 2023

Do we need any README changes?

@smoores-dev
Copy link
Collaborator Author

The plugins are meant to be public, yeah? Do we need to create an index for the plugins directory and re-export the plugins, then re-export them from the root?

The componentEventListeners plugin is private; it gets automatically added to the EditorView by the ProseMirror component.

The react plugin will be public but I was thinking maybe we'd expose it in a later change when we actually use it for something (and add the docs then, as well)? Right now, adding it doesn't actually do anything for users

@smoores-dev smoores-dev merged commit ae94a1a into main Jun 1, 2023
1 check passed
@smoores-dev smoores-dev deleted the position-tracking-plugin branch June 1, 2023 00:36
state: {
init(_, state) {
const next = {
posToKey: new Map<number, string>(),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason that we are using a string on line 36 and NodeKey on the following line?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh interesting, yeah this is worth thinking about a bit more, maybe. NodeKey is just "string + Symbol('portal rogistry root key')", and technically we never actually put that root symbol in the plugin's bimap. The reason that keyToPos is typed as having NodeKey is just so that consumers don't have to do a special-case check to see whether they have the root symbol or a string before trying to get the position from the keyToPos map (that is, it's type-safe to do pluginState.keyToPos.get(nodeKey), where nodeKey is of type NodeKey).

On the other hand, since we never actually put that root symbol in the bimap, technically it's correct that pluginState.posToKey(pos) always returns string, and you can use a string with any interface that accepts a NodeKey (since NodeKey is a subtype of string). So I think from a purely type-checking standpoint, this is "correct", but I also think it's somewhat unintuitive and might seem wrong to someone who's just using the API (which isn't actually exposed to users, so this would just be "us in the future", but we should avoid confusing "us in the future", too!).

The only downside I can think of to typing posToKey as Map<number, NodeKey> is that you can technically take any result of posToKey and pass it to any function that expects a string, but if it was typed as NodeKey, Typescript wouldn't let you do that! I dunno what functions you'd be passing a node key to that weren't typed as NodeKey, so this seems pretty minor, as downsides go (and it's an internal API so it's not a big deal if we have to change it later).

Any thoughts after all that? Haha

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also we should probably change the Symbol name! That was from a previous version of this plugin, and it doesn't really make sense here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants