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

Update inner selection when outer selection updates selection within a text field #400

Merged
merged 8 commits into from
May 20, 2024

Conversation

rhystmills
Copy link
Contributor

@rhystmills rhystmills commented May 14, 2024

Background

In prosemirror-elements, elements are a collection of fields representing user-editable data. Most logic managing these fields is in the relevant FieldView. Text fields are all based on ProseMirrorFieldView, via implementations that extend it, e.g. TextFieldView. These contain instances of ProseMirror ('inner editors'), and the 'outer editor' (the editor that contains the element) delegates control of certain actions to the 'inner editor' managed by the FieldView.

Currently, two kinds of changes from the outer editor can cause changes in the field view:

  1. Changes to the nodes that the inner editor manages:
  2. Changes to Decorations that affect the range covered by the field view.

However, if the selection is updated by the outer editor, the selection within a relevant field is not updated.

This hasn't been obvious in the past, because usually the selection is updated by the field's 'inner' editor. With notes being supported in nested element fields, when a user hits 'F10' in Composer with text selected inside the nested element, the selection is updated by the outer editor (to which the menu belongs), and should be moved to the end of the note as users expect. Because the selection is not passed to the inner editor, the note content remains selected, so when the user types, they overwrite the content of the note.

What does this change?

This PR updates field views when the selection changes, and the text fields (extensions of ProsemirrorFieldView) update their inner editor selection to match the incoming selection. This means that the usual F10 behaviour for notes also works inside our test fields.

Kapture 2024-05-14 at 15 49 59

This happens as follows:

  1. The outer selection changes
  2. Updateinplugin.ts, a function at the boundary of the plugin, receives state changes from the outer editor, which included the updated Selection`.
  3. We pass this into the FieldViews via some intermediate functions.
  4. The only FieldView that needs to handle the selection change is the abstract ProseMirrorFieldView, which all text FieldViews extend.
  5. Every text FieldView checks if the selection covers its own range. If it does, it updates its internal selection.

How to test

  1. Use prosemirror-elements locally with flexible-content using yalc.
  2. Create a list format document, like Key Takeaways
  3. Type some text in the nested element field, highlight some of it, and hit F10. The cursor should move to the end of the note.

n.b. - due to an unrelated bug with menu clicks causing the editor to lose focus, this only works with the F10 shortcut, not the notes menu button

I have updated existing tests to match the new update behaviour and added a test to reflect the selection behaviour itself, making sure that the inner selection is properly updated when there is a relevant incoming selection.

@rhystmills rhystmills requested a review from a team as a code owner May 14, 2024 14:50
@rhystmills rhystmills force-pushed the rm/update-inner-selection-from-outer-editor branch from e7007f9 to 6a8a328 Compare May 15, 2024 13:19
Copy link
Contributor

@simonbyford simonbyford left a comment

Choose a reason for hiding this comment

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

This looks good and seems to work fine locally! One suggestion you are free to ignore.

I appreciate the time taken to go over this with me 👍

src/plugin/fieldViews/ProseMirrorFieldView.ts Outdated Show resolved Hide resolved
Co-authored-by: Simon Byford <simon.byford@guardian.co.uk>
@rhystmills rhystmills merged commit 6ac5753 into main May 20, 2024
3 checks passed
@rhystmills rhystmills deleted the rm/update-inner-selection-from-outer-editor branch May 20, 2024 12:29
Copy link
Contributor

@jonathonherbert jonathonherbert 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, top work 👏 Two non-blocking questions about tests.

@@ -288,18 +289,23 @@ describe("createPlugin", () => {
// By inserting content before the element, we enable the content to move
// upward, changing the command output.
const positionThatEnablesUpCommand = 0;
const tr = view.state.tr.replaceWith(
const tr = view.state.tr.replace(
Copy link
Contributor

Choose a reason for hiding this comment

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

These tests have changed – do we have a test for the case when content changes but the selection does not?

if (overlap > 0) {
endOfOuterDiff += overlap;
endOfInnerDiff += overlap;
if (shouldDispatchTransaction) {
Copy link
Contributor

Choose a reason for hiding this comment

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

More a note to self: shame we can't ask the transaction itself if it's changed! (There's a this.updated field, but it's private.) We might be able to avoid this state with ('transaction selection has changed' && 'transaction.steps.length > 0), but it's a bit of a weird one as it relies on the dev knowing that .setSelection mutates the selection, and .replace adds steps. So this is fine.

// nestedElement FieldViews are always updated as a workaround for a bug
// with merging text elements in the zipRoot plugin after an intermediate element is
// deleted.
if (
fieldValuesChanged ||
innerDecosChanged ||
anyDescendantFieldIsNestedElementField(newNode)
anyDescendantFieldIsNestedElementField(newNode) ||
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we have any tests for this logic?

@jonathonherbert
Copy link
Contributor

(hah – too slow!)

@rhystmills
Copy link
Contributor Author

Whoops, apologies!

@jonathonherbert
Copy link
Contributor

(There was already an approval, I was just sticking my oar in ⛵ 👍)

@rhystmills
Copy link
Contributor Author

(There was already an approval, I was just sticking my oar in ⛵ 👍)

I'll try and address these in another PR.

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