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

Add NestedElementField #324

Merged
merged 33 commits into from
Jan 16, 2024
Merged

Add NestedElementField #324

merged 33 commits into from
Jan 16, 2024

Conversation

rhystmills
Copy link
Contributor

@rhystmills rhystmills commented Dec 18, 2023

Co-authored with @davidfurey and @jonathonherbert - first few commits were David's but I unintentionally rebased his name out of the commit history.

image

What does this change?

This PR adds the nested element field. This field is a rich text editor that is itself able to contain elements. This will enable us to build larger, 'structural' elements within Composer that can contain elements like images and videos.

There is some further work to do in future PRs:

  • Add some helper functions to provide a content expression for a nested element field that disallows certain elements (for example, we probably don't want elements that contain nested element fields within other nested element fields).
  • There is a known bug in repeater fields involving applying Decorations . Trying to insert an element (e.g. via pasting) into any node other than the initial one in a repeater field now works, but the Decorations for notes and Typerighter matches within nestedElements aren't currently working.
  • Make sure that the element controls are usable within the field. (e.g. move, delete, select)

This PR could do with some tests for nested element fields - I'll add some when I'm back from holiday.

How to test

  1. Run the demo app for this branch with yarn start.
  2. Add the 'Alt Style' element.
  3. Can you past other elements into the 'Content' field? (You should be able to).

You may also want to test this PR in Composer via yalc with: https://github.com/guardian/flexible-content/pull/4492

@rhystmills rhystmills changed the title wip but seems to work Add NestedElementField Dec 18, 2023
@davidfurey
Copy link
Member

As discussed before, I'm unsure on the best name for this. Perhaps ElementsField?

@rhystmills
Copy link
Contributor Author

As discussed before, I'm unsure on the best name for this. Perhaps ElementsField?

I went with nestedElement rather than just nested in the end - going for just 'element' or 'elements' felt confusing in some places in the codebase. Does that seem alright to you?

@rhystmills rhystmills marked this pull request as ready for review January 4, 2024 12:15
@rhystmills rhystmills requested a review from a team as a code owner January 4, 2024 12:15
getPos,
offset,
decorations,
// Allow plugins without a plugin key, but exclude those that are explicitly blocked
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// Allow plugins without a plugin key, but exclude those that are explicitly blocked
Disallow plugins which have a plugin key, or are explicitly blocked.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think this is clearer, but not sure, feel free to ignore.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think my initial comment is not great here (mostly duplicating the code), I think the useful information would actually be:
"Keys are an optional unique identifier for plugins; we should expect to encounter plugins without keys."

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Currently more plugins exist without keys than with keys in Composer, and they are likely to be doing important work. That's why I've gone with a blocklist rather than an allowlist.

Copy link
Contributor

@Fweddi Fweddi Jan 9, 2024

Choose a reason for hiding this comment

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

@rhystmills and I paired on this briefly to clarify the code. Worth noting my suggestion above was incorrect!

demo/index.ts Outdated
Comment on lines 266 to 269
/*
* We must allow 2 text elements to exists side by side
* so that when we delete an element we can then join it.
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

Not entirely sure I understand this. Is this just a general bug that's being fixed here, or is it related to the nested elements?

Also, the comment here mentions two text elements but the content field is talking in terms of blocks, just thought I would flag in case that's unintended.

Copy link
Contributor Author

@rhystmills rhystmills Jan 9, 2024

Choose a reason for hiding this comment

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

Whoops - I think I unintentionally committed that after borrowing the base node spec from Composer's block editor.

Definitely not relevant to the PR!

const nodeSpec = getNodeSpecForField(
"exampleElement",
"exampleNestedElementField",
createNestedElementField({ content: "element*" })
Copy link
Contributor

Choose a reason for hiding this comment

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

Elsewhere this is defined with content as block*, is this an inconsistency?

I would have thought that a nested element would contain element*, but I suppose the nested elements are also nested in blocks?

I've said nested too many times... 🪆

Copy link
Contributor Author

@rhystmills rhystmills Jan 9, 2024

Choose a reason for hiding this comment

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

🪆

The top level block* content expression is specific to the demo in prosemirror-elements - element+/element* is closer to what we'll see in flexible-content where the top level editor has an element+ content expression. At the moment there's a bit of a compromise to allow Composer and the demo to both work.

It would be good to align the demo more closely with what we see in flexible-content so we don't have this discrepancy. I could add that as a follow-up card to follow this one, or we could fix in this PR?

The demo could do with some love in general, e.g. having far fewer elements.

Copy link
Contributor

Choose a reason for hiding this comment

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

That makes sense and sounds good to me. The difference between demo and Composer does confuse me from time to time (the Alt Styles element in this PR for example threw me).

@@ -55,6 +55,8 @@ export const getFieldsFromNode = <FDesc extends FieldDescriptions<string>>({
const fieldDescription = fieldDescriptions[fieldName];
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- strictly, we should check.
if (!fieldDescription) {
console.log({ fieldNode, fieldDescriptions, fieldName });
Copy link
Contributor

Choose a reason for hiding this comment

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

Might not be needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Tis gone.

import { createRepeaterField } from "../../fieldViews/RepeaterFieldView";
import { createTextField } from "../../fieldViews/TextFieldView";
import { RepeaterFieldMapIDKey } from "../constants";
import { createEditorWithElements, createNoopElement } from "../test";
import { maxLength, required } from "../validation";

export const elements = {
nestedElement: createNoopElement({
content: createTextField({
Copy link
Contributor

@Fweddi Fweddi Jan 8, 2024

Choose a reason for hiding this comment

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

Interested in this as it seems to be doing something similar but different to the nestedElementField below. Also it's called nestedElement, but seems to be a text no-op element with a text field? Is that what a nestedElement is, secretly?

Copy link
Contributor Author

@rhystmills rhystmills Jan 9, 2024

Choose a reason for hiding this comment

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

Unclear naming on my part I think - this is essentially a test element, separate from the 'example' element used in the tests, to put in the nestedElementField in the tests.

I think exampleElementToNest might be better. Have now renamed.

@Fweddi
Copy link
Contributor

Fweddi commented Jan 8, 2024

I can't copy / cut an element that is nested - I can select it, but the copy and cut functions don't seem to work. I don't know if this is related to the repeater fields bug, or is something separate.

@Fweddi
Copy link
Contributor

Fweddi commented Jan 8, 2024

I also can't reorder nested elements using the single arrows (I can using the double arrows).

@Fweddi
Copy link
Contributor

Fweddi commented Jan 8, 2024

This might be an issue with the demo tool, but if I delete all the text in a Nested Element Field, and there's another element nested, I can't edit the field (or can't click into it to type / add elements).

@Fweddi
Copy link
Contributor

Fweddi commented Jan 8, 2024

I also can't reorder nested elements using the single arrows (I can using the double arrows).

I see you've mentioned this as work for a follow-up PR. 👍

Copy link
Contributor

@Fweddi Fweddi left a comment

Choose a reason for hiding this comment

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

A great, hefty piece of work that moves us closer to our OKR, amazing stuff.

There are a few bugs and edge cases, but I think it makes sense to fix forwards and address these in smaller PRs. It would be good too to document some of these changes in a README somewhere; at a glance I found myself getting confused between Nested and Repeater fields, but the code is also fairly self-explanatory.

Co-authored-by: Tom Richards <tom.richards@guardian.co.uk>
rhystmills and others added 3 commits January 12, 2024 15:34
…ocklist with allowedPlugin list

Co-authored-by: Freddie Preece <40991816+fweddi@users.noreply.github.com>
…w` since it's just a vehicle for other elements so doesn't need to worry about decorations itself

Co-authored-by: Rhys Mills <34686302+rhystmills@users.noreply.github.com>
rhystmills and others added 3 commits January 15, 2024 12:19
Co-authored-by: Freddie Preece <40991816+Fweddi@users.noreply.github.com>
Comment on lines 95 to 96
// We use a disallow list (rather than an allow list) because we expect most plugins to work,
// and only want to disallow those that cause problems.
Copy link
Contributor

Choose a reason for hiding this comment

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

We'll need to remove this comment now we've changed to an allowlist.

Copy link
Contributor

@Fweddi Fweddi left a comment

Choose a reason for hiding this comment

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

Subsequent nodes of the repeater field now work as expected 👍

There are still some small issues around decorations but I think this puts us in a good place to merge.

@rhystmills rhystmills merged commit 3cc94dc into main Jan 16, 2024
3 checks passed
@rhystmills rhystmills deleted the nest-elements branch January 16, 2024 15:53
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

5 participants