Giter Site home page Giter Site logo

Comments (11)

jasonbahl avatar jasonbahl commented on May 31, 2024 3

I think @justlevine understands the spirit of my suggestions well.

I believe we will benefit by having various interfaces that have meaning and can be used in different places to convey meaning.

The reason I suggested EditorBlock instead of Block is that it felt slightly less prone to Type conflicts in the schema.

The word Block could be used in different contexts unrelated to Gutenberg. The primary definitions of the word "Block" have nothing to do with Gutenberg or WordPress...

CleanShot 2023-03-09 at 10 41 50

...and I could see some sites having a "Block" post type to represent things unrelated to Gutenberg.

I suggested EditorBlock to reduce the chances of conflict with other possible uses of the word "Block", as it seemed to be a bit more descriptive that it's a Block used with the Block Editor. There could easily be a better name, and possibly it is simply "Block". I'm open to that, and then any site that does have a Post Type or Taxonomy called block to store information about:

CleanShot 2023-03-09 at 10 44 31

would be required to chose a different graphql_single_name for their post type / taxonomy 😆.

Regardless, the intent was to introduce a common Interface that defined the fields that all blocks will have in any context blocks could be used. . .which could be the content editor, nav menus, comments (btw, I was replying to some support threads on WordPress.org and noticed it's now using blocks in comments!), etc.

(For simplicity sake, I'll stick with "EditorBlock" as the Interface that means "Any block used with the block Editor, regardless of context". )

The idea was that for any specific context there would be another interface representing that specific contextual use of blocks:

  • Interface ContentEditorBlock implements EditorBlock
  • Interface NavMenuBlock implements EditorBlock
  • Interface LayoutBlock implements EditorBlock
  • Interface TemplateBlock implements EditorBlock
  • Interface CommentEditorBlock implements EditorBlock
  • Interface SomeNewFutureContextForBlocks implements EditorBlock

Then, when it comes to specific blocks, they should implement the interfaces that are relevant.

For example, if we had a SiteHeader block that was only intended to be used in full site editing as a block in that context, it could be:

type SiteHeader implements TemplateBlock {
  ...
}

Then, wherever we have fields that return a list of TemplateBlock, we would know that we could query for SiteHeader, but in a context where we have a field that returns a list of ContentBlock we would know that SiteHeader will never be a possible block to be returned from that field.

Same goes with the inverse.

If we know that a Paragraph block is intended to only be used within specific contexts (say, the "Content Editor" and "Comment Editor") we can describe that like so:

type CoreParagraph implements ContentEditorBlock && CommentEditorBlock {
  ...
}

Now, the schema will show the CoreParagraph block as a possible type when I query for a field that returns a list of ContentBlock or a list of CommentEditorBlock, but that field will not show SiteHeader as a possible block.

So, EditorBlock, in my mind, was the simplest Interface that should define all common fields of blocks used in any context (Content Editor, Nav Menu, Comments, etc)

Then, we would have more specific interfaces for those more specific contexts. Sometimes they would introduce new fields, sometimes they wouldn't but would just introduce more semantic meaning.

A CommentEditorBlock might even have the exact same fields as the EditorBlock, but it would convey that it's a bock used in the "comment editor".

Then the fields that expose the list of blocks would be able to expose a more narrow list of blocks.

Exposing ALL blocks in every field that returns blocks is misleading to the client developer trying to build something.

We know for a fact that we will never return a SiteHeader block in the Post.contentBlocks, and we know we will never expose a CoreParagraph when querying for RootQuery.templateBlocks (if that were a field, for example).

The schema should reflect what's possible, and we already know this isn't possible, but the Schema is misleading and saying it is possible.

That was my intent with the post-type specific interfaces as well.

If we know that a specific Block has been registered to only one specific post type, why would the Schema show it as a possible block to be returned on other post types?

As a client developer trying to make sense of the schema, it now makes no sense.

I want to know what's actually possible with the Schema, and right now the Schema is showing a lot of things that are impossible.


If on the other hand, one decides to use Faust and pass on all the block fragments in order then they could get errors for using block fragments not available in the GraphQL schema. Which ones to use for which page/layout/navbar?

Oh I know let's use three different fragment packages of blocks.

const pageBlockFragments = gql`...`; // collect fragments for page
const layoutBlockFragments = gql`...`; // collect fragments for layout
const navbarBlockFragments = gql`...`; // collect fragments for nav

Component.query = gql`
...
${pageBlockFragments}
${layoutBlockFragments}
${navbarBlockFragments}
editorBlocks {
        name
        __typename
        renderedHtml
        id: nodeId
        parentId
        ...
      }

Oh no. what happens if some blocks are common between them? Now if you add duplicate fragments you will get an error in the GraphQL request since you cannot re-declare them.

^ I think this feels solveable with a de-dupe method of some sort in Faust.

Something to the tune of:

const pageBlockFragments = gql`...`; // collect fragments for page
const layoutBlockFragments = gql`...`; // collect fragments for layout
const navbarBlockFragments = gql`...`; // collect fragments for nav

// a function that dedupes any fragments, should there be any duplicates
// and maybe does some other useful things. . .not sure what, 
// but I'm sure there will be something else some day we may need / want to centralize
const fragments = faustFragments([
  pageBlockFragments,
  layoutBlockFragments,
  navbarBlockFragments
]);

Component.query = gql`
...
${fragments}
editorBlocks {
        name
        __typename
        renderedHtml
        id: nodeId
        parentId
        ...
      }

I think one of the benefits of using Fragments / GraphQL is being able to ask for specifically what you need anyway, so I'm not sure using generic packages that define 100 fragments for blocks you have no intention of using, or that the field will never return is the way to go anyway.

Lets say I had a post type that I limited to ONLY have the CoreParagraph and CoreImage block.

Why would I want a query that looks like this:

{
  customPostTypeThatOnlyHasParagraphAndImage( id: "asdfasd" ) {
    id
    title
    contentBlocks { 
      ...FaustBlocks 
    }
  }
}

fragment FaustBlocks on EditorBlock {
  name
  renderedHtml
  ...on CoreQuote {...}
  ...on CoreHeading {...}
  ...on SiteHeader {...}
  ...on CoreColumn {...}
  ...on CoreOembed {...}
  ...on CoreCategories {...}
  ...on CoreWidgetGroup{...}
  ...on CoreArchives{...}
  ...on CoreAvatar{...}
  ...on CoreCalendar{...}
  ...on CoreComments{...}
}

This query makes no sense, in my opinion.

But, if the Post Type had a specific Interface that blocks available on that post type could implement, it would be much more useful to be able to construct queries.

If I were to have an interface like so:

interface PostTypeThatOnlyHasParagraphAndImageContentEditorBlock implements ContentEditorBlock && EditorBlock {
 ...
}

Then, any block that's allowed to be queried on that post type could implement PostTypeThatOnlyHasParagraphAndImageContentEditorBlock

i.e.:

type CoreParagraph implements ContentEditorBlock && EditorBlock && PostTypeThatOnlyHasParagraphAndImageContentEditorBlock { ... }

type CoreImage implements ContentEditorBlock && EditorBlock && PostTypeThatOnlyHasParagraphAndImageContentEditorBlock

Now, if the schema for the Post Type (PostTypeThatOnlyHasParagraphAndImage) looked like so:

type PostTypeThatOnlyHasParagraphAndImage implements ContentNode && Node && NodeWithTitle && ... {
   id: ID!
   title: String
   ...
   contentBlocks: [PostTypeThatOnlyHasParagraphAndImageContentEditorBlock]
}

Then when I looked at GraphiQL, I would accurately see that the contentBlocks when querying that type of node will only return CoreImage and CoreParagraph as possible types.

I don't have to write queries that need to anticipate every possible block registered, just the blocks that are actually possible on that post type.

I could query like this, instead:

{
  customPostTypeThatOnlyHasParagraphAndImage( id: "asdfasd" ) {
    id
    title
    contentBlocks { 
      ...on CoreParagraph {}
      ...on CoreaImage {}
    }
  }
}

This is much easier to debug as I know that this query is asking specifically for those 2 blocks (not the entire block library), and the Schema is telling me exactly what blocks I can expect for customPostTypeThatOnlyHasParagraphAndImage.contentBlocks, the 2 blocks that are actually possible to be returned.

from wp-graphql-content-blocks.

justlevine avatar justlevine commented on May 31, 2024 1

I'm curious how this relates to #25 , as that PR didn't add the EditorBlock as a parent interface but instead replaced the ContentBlock interface entirely and even changed the field name to editorBlocks and the corresponding NodeWith interface.

This seems like a big step backwards from @jasonbahl 's suggestion (or rather, how I understood it).

Since GraphQL is supposed to be self-documenting, an Interface name works best when it describes its shape/purpose in relation to others that share the same tree. So EditorBlock defines the generic shape (personally I'd have gone even broader with Block), and then the child interface names describe how/why those shapes differ.

(This early on in schema exploration, it might not be so evident what the differences are / if we need specific ContentBlock or LayoutBlock types. A clearer example is StaticBlock and DynamicBlock; the latter e.g. has both raw and rendered versions of the output.).

GraphQL fields however supply the context and the usage for the defined data shape. In this light editorBlocks: EditorBlock[] (or an inheriting type) is a list of... what? All blocks? Blocks used to render the Block Editor? It even makes you start to doubt what EditorBlock as a type is supposed to mean (why I prefer the generic Block).
contentBlocks (the blocks used to generate the content field), templateBlocks, layoutBlocks (the blocks used to render the page content around the contentField), and even blocks: Block[]` (the entire block markup for the URI) are significantly more semantic and self documenting.

Tl;Dr

In the short term, I highly recommend reverting parts of #25, specifically restoring the contentBlocks field and its parent NodeWithContentBlocks interface). If there's no defining difference at this point between an EditorBlock and a ContentBlock, then that change is fine, but otherwise ContentBlock should be restored as well, with it's non-defining fields being what remains on EditorBlock.

More broadly, I suggest mapping out the possible difference in data shapes (type names) and contexts (field names), so the naming patterns are more intuitive and less prone to requiring future breaking changes.

from wp-graphql-content-blocks.

theodesp avatar theodesp commented on May 31, 2024

Thanks @jasonbahl. We've created some tickets for review. We will have to meet to discuss the advanced schema you are proposing as I'm not confident on how to implement this one.

from wp-graphql-content-blocks.

jasonbahl avatar jasonbahl commented on May 31, 2024

@theodesp sounds good! Happy to meet and chat about it!

from wp-graphql-content-blocks.

justlevine avatar justlevine commented on May 31, 2024

@theodesp if 'blessed' tasks are added to the repo issues, I'm happy to volunteer some PRs.

from wp-graphql-content-blocks.

theodesp avatar theodesp commented on May 31, 2024

Hey @justlevine . Thanks for the feedback. My understanding is that:

EditorBlock: All blocks that can be used within the Block Editor

is the base interface for all other subtypes of blocks (ContentBlock, LayoutBlock, etc) hence I reverted back the original intent of this interface name.

However I should mention here that we haven't decided to go forward with the more specific Interfaces (Content, Layout, etc) and the reason is that we need to think about how those blocks are going to be queried in a headless site.

I can imagine a chaotic way of doing it because the developer won't be able to determine which block would be available as Content, Layout, NavMenuBlock since that information would be coded in the schema.

If on the other hand, one decides to use Faust and pass on all the block fragments in order then they could get errors for using block fragments not available in the GraphQL schema. Which ones to use for which page/layout/navbar?

Oh I know let's use three different fragment packages of blocks.

const pageBlockFragments = gql`...`; // collect fragments for page
const layoutBlockFragments = gql`...`; // collect fragments for layout
const navbarBlockFragments = gql`...`; // collect fragments for nav

Component.query = gql`
...
${pageBlockFragments}
${layoutBlockFragments}
${navbarBlockFragments}
editorBlocks {
        name
        __typename
        renderedHtml
        id: nodeId
        parentId
        ...
      }

Oh no. what happens if some blocks are common between them? Now if you add duplicate fragments you will get an error in the GraphQL request since you cannot re-declare them.

Fragment management suddenly could become harder and less convenient so that's why I'm only restoring the original intent on using the EditorBlock per WPGraphQL Block Editor as the base interface for now and gradually refine it until we find a workable solution.

from wp-graphql-content-blocks.

justlevine avatar justlevine commented on May 31, 2024

Thanks for the detailed reply @theodesp !

Appreciate the clarification that the EditorBlock interface is intended to be the parent type. From your response, I still dont understand why the field name was changed to editorBlock (along with the corresponding NodeWith* interface) - my more immediate concern.

To me it seems the confusion you're worried about is a direct result of this change: using a specifier ( editor block) to describe both something vague (anything shaped like Block data) and something else that has a conflicting specific meaning ( the editor blocks related to the post's content.

When faced with post.editorBlock, a user indeed doesn't immediately know that the field only contains block data parsed from the content field. Does this include the other block data that traditional WordPress outputs (e.g. the template wrapper or resolved block parts)? We know it doesnt, but the name itself is ambiguous and as you demonstrated with your query example, makes it really hard for us to scale up the schema without causing even more semantic confusion.

Now take this hypothetical query:

fragment BlockData on EditorBlock {
  name
  __typename
  id: nodeId
  parentId
  # We dont know if 'LayoutBlock', 'NavBlock' etc have semantic meaning yet, so using these for example's sake.
  ... on StaticEditorBlock {
      renderedHtml
  }
  ... on DynamicEditorBlock {
    ... BlockDataAttributesFragment
  }
}

query GetPosts( $uri: String! ) {
  nodeByUri( uri: $uri ) {
    ... on NodeWithTitle { # existing
      title
    }
    ... on NodeWithContent { #existing
      content
    }
    ... on NodeWithEditorBlocks {
      # Both the field and the interface are confusing to the user. Why doesnt the field include all editor blocks, and why does the interface only match WP_Post objects?
      editorBlocks {
        ...BlockData
     }
    ... on NodeWithContentBlocks {
      # This clearly tells the user what data is epected.
      contentBlocks {
        ...BlockData
      }
    
    # Above were the issues with the current naming.
    # Now lets theorycraft different possibilitiesto see the impact on extensibility.
    ... on Page {
      content
      contentBlocks {
        ...BlockData
      }
      anyOtherGroupOfBlocks { # has immediate semantic meaning because it doesnt conflict with a vague 'editorBlocks' field
        ...BlockData
      }
      template {
        blocks( format:RENDERED ) { # implies *all* blocks for the page. Could be editorBlocks to mirror your choice of Interface name.
          ...BlockData
        }
        layoutBlocks { # implies the blocks that wrap the content, i.e. Header/Footer.
          ...BlockData
        }
        activeTemplatePart( name: "navigation" ) {
          editorBlocks { # `why are these 'editor' blocks? Is it because theyre raw, and there's a separate frontendBlocks that handles dynamic data? 
            ...BlockData
          }
      }
      templatePart( name: HEADER ) {
        contentBlocks { # Even here this is more semantic than `editorBlocks`.
          ...BlockData
        }
      }
    }
  }
}

(I gave a bunch of various patterns; not advocating here for any specific one). In all of them, editorBlocks - at least as a field name - is both more confusing in and of itself and makes pretty much any other schema usage of blocks more confusing as well.

from wp-graphql-content-blocks.

theodesp avatar theodesp commented on May 31, 2024

Hey @justlevine since currently we are limiting the types of blocks registered using this function here that filters the blocks registered for only the post_type_supports( $block_editor_post_type->name, 'editor' ) helper. so my understanding again is that we would use only those as a base list of blocks hence the interface NodeWithEditorBlocks.

However would changing editorBlocks-> blocks would make more sense here since we do not care about the origin?

For example here is the comparison with wp-graphql-gutenberg plugin that filters the same list of blocks using
get_post_types_by_support( 'editor' )

https://github.com/pristas-peter/wp-graphql-gutenberg/blob/bacabca662698871ecc8b297c7af3b5b95d431c1/src/Blocks/Utils.php#L23-L32

{
  nodeByUri(uri: "/events/this-is-new-post") {
    ...on BlockEditorContentNode {
      blocks {
        name
      }
    }
    ...on NodeWithEditorBlocks {
      editorBlocks {
        name
      }
    }
  }
}

The BlockEditorContentNode filters the list of blocks only available in the editor similar to what this plugin does.
There is no interface the returns the list of all blocks using this plugin so we stick with that approach for now.

But again since editorBlocks is a field you can definitely use an alias so that it fits semantically for your use case:

{
  nodeByUri(uri: "/events/this-is-new-post") {
    ...on BlockEditorContentNode {
      blocks {
        name
      }
    }
    ...on NodeWithEditorBlocks {
      contentBlocks: editorBlocks {
        name
      }
    }
     ...on NodeWithEditorBlocks {
      blocks: editorBlocks {
        name
      }
    }
  }
}

from wp-graphql-content-blocks.

justlevine avatar justlevine commented on May 31, 2024

@theodesp your response seems to strengthen my argument even further, as you're acknowledging that even currently (meaning before accounting for future schema evolution) the semantic meaning of NodeWithEditorBlocks and editorBlocks does not match their user-facing function.

Ultimately it's your project. I only wanted to bring attention that despite your comment, the changes in #25 are actually a step back from (how I understood) @jasonbahl 's suggestions.

Since the changes in #25 seems to be an intentional decision and not just a misunderstanding of this ticket, I'll shut up now and let you get back to work 😎

from wp-graphql-content-blocks.

theodesp avatar theodesp commented on May 31, 2024

I'm closing this one. In the future we would prefer to have a more specific feature request format when opening PRs so that we can address them promptly.

from wp-graphql-content-blocks.

jasonbahl avatar jasonbahl commented on May 31, 2024

I'm closing this one. In the future we would prefer to have a more specific feature request format when opening PRs so that we can address them promptly.

@theodesp how would you have liked this to be done more specifically?

I feel like I gave pretty good detail in the original issue and in the follow-up discussion.

from wp-graphql-content-blocks.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.