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

Two Types of Blocks for Better User and Developer Experience #371

Closed
nb opened this issue Apr 6, 2017 · 4 comments
Closed

Two Types of Blocks for Better User and Developer Experience #371

nb opened this issue Apr 6, 2017 · 4 comments

Comments

@nb
Copy link
Member

nb commented Apr 6, 2017

Hi! I’ve been talking a bit with @mtias and @aduth in the past several weeks and the problem of representing blocks in the markup has become increasingly interesting. So, here’s an idea from an outsider – I may be missing a lot of finer details, but had to get it out of my head :-)

First, some definitions.

A block is a set of four functions:

forSaving( user-supplied: object ) → attributes: object, WPHTML: string

forEditing( attributes: object, WPHTML: string ) → user-editable: object

forRendering( attributes: object, WPHTML: string, external world™ ) → WPHTML: string

ui() → UI for (user input → user-supplied: object)

And one more:

WPHTML is the type of HTML that is understood by WordPress’s the_content filters, like converting empty lines to paragraphs, normal quotes to curly, parsing shortcodes, embeds, etc.

Encoding user supplied data – the process of converting the object with data entered by the user to a string that makes easy to parse attributes and WPHTML. The current format is of the form markup .

Flows

Here are the main flows that we need to cover:

  • (edit-blocks) A user is adding/editing a block in a visual block-aware editor.
    • The user enters data for the block via the UI from ui().
    • If the user is editing an existing block, the UI is prefilled with the result from forEditing.
    • The object with all of the user-supplied data is passed to forSaving and the result is encoded in the post content.
  • (edit-text) A user is editing the WPHTML of a block in the non-visual editor, next to a block-aware visual editor.
    • We may be able to warn/stop them if they’re about to edit (mess up) a block’s attributes or WPHTML.
  • (edit-legacy) A user is editing the WPHTML of a block in an editor that doesn’t support blocks (through REST API, XML-RPC, e.g. mobile app, MarsEdit, etc.), no matter whether it’s visual or not.
    • We may be able to warn/stop them if they’re trying to save a block’s attributes or HTML.
  • (view-blocks) A user is just viewing the block in the visual block-aware editor.
    • Using forRendering we render the final HTML that will be shown in the browser.
    • In some cases, if WPHTML contains the final markup, we may skip this step.
  • (render) A end user open a public post with a block in the content.
    • Using forRendering we render the final HTML that will be shown in the browser.
    • The final markup may depend on outside data – information from the database or from outside remote services.
  • (third-party) Outside, third-party tools working with the post content – rendering, parsing, editing.

Implementation Approaches

When implementing those flows as a block developer, the hardest task is to decide what of the user-supplied data to keep in attributes and what to convert to WPHTML.

Putting all the data into structured attributes will make forEditing and forSaving trivial to implement and it will put the most weight to forRendering to know about the markup. The approach of keeping all the data in attributes and none in WPHTML also makes few of the flows a lot less desirable – edit-text becomes a huge chore for simple blocks (imagine writing a whole paragraph in the form ), because wading through a special format when simple HTML would do is unknown (⇒ scary) to users and probably error-prone – messing up a less-forgiving structured format seems and easy task. Other flows are victims, too – view-blocks will always require the full rendering experience and without much markup the post content will become unusable for other services, except the WordPress.

The other extreme is having as much as possible in the markup. While much better end user editing and third-party tools experience, this means heavy parsing of HTML and that’s a path leading to both enormous development complexity and high barrier to entry for the casual block author. Not to mention the disappointed users working with that half-working parsing implementations. Even something seemingly simple as a gallery be awkward to parse – do we find out the number of columns by guessing based on the CSS cut-off?

The truth, of course, is somewhere in the middle. But in what do we keep in attributes and what do we keep in markup for the ultimate trade-off and keeping everybody happy?

Whether that’s the best one or not, I don’t know, but here’s an idea: to have two types of blocks: Simple & Advanced.

Simple blocks

  • The source-of truth is the the combination of simple (or none) attributes + WPHTML.
  • Limited to 3 attributes, all simple data (number, fixed string, boolean). Good examples are alignment, number of columns, heading level.
  • Each piece of user-editable data has a direct correspondence to a specific HTML element/attribute in the markup. This way we can keep forEditing simple and robust, while not keeping long and confusing data in the block attributes. At the same time this lets users to edit the HTML directly, without affecting the block structure much – because of the direct correspondence, if a user adds and extra image after , we will still be able to allow them to edit the blockquote, without overriding their changes.
  • The WPHTML in the post content is the final WPHTML.

Advanced blocks

  • Source-of-truth is entirely in the attributes. The WPHTML is just a cache.
  • Suited for blocks with more complicated markup, for which parsing will be tedious and error prone.
  • If a user changes the markup manually, a subsequent edit using the blocks interface will probably override their changes, because forEditing doesn’t take into account any markup changes, but uses the attributes only. This keeps the implementation simple.
  • Contact Form is a great example – there might be field types, validations, visual properties that both are tough to extract from the HTML and also the chance of a user fiddling with those directly is much lower, since the graphical UI will be more convenient anyway.

What do you think?

@joyously
Copy link

joyously commented Apr 6, 2017

You just described what the existing editor already does:

  • simple blocks - HTML (remember the ML stands for markup language)
  • advanced blocks - shortcodes.

@nb
Copy link
Member Author

nb commented Apr 6, 2017

@joyously is this good or bad? :)

@aduth
Copy link
Member

aduth commented Apr 6, 2017

Related: #104

The object with all of the user-supplied data is passed to forSaving and the result is encoded in the post content.

How does this differ from forRendering, since it appears the latter is used in deciding what's to be shown on the front-end. Are we assuming blocks can change their rendering behavior on the front-end to be different than what's stored in post content?

The other extreme is having as much as possible in the markup. While much better end user editing and third-party tools experience, this means heavy parsing of HTML and that’s a path leading to both enormous development complexity and high barrier to entry for the casual block author.

I too struggled with these extremes (#104 (comment)) reflected in your conclusion as the contrast between simple and advanced blocks. I'd taken aim at more of singular approach, first akin to your advanced block (HTML as a cache) and later toward leveraging markup after concerns were raised about syncing, data duplication, and revisioning post meta.

Addressing the quoted text specifically, I eventually landed at what I think is a comfortable parsing approach in the hpq utility, which I find scales well to even complex markup. Here's the parse of the current top Hacker News articles:

hpq

Demo: https://aduth.github.io/hpq/

Even something seemingly simple as a gallery be awkward to parse – do we find out the number of columns by guessing based on the CSS cut-off?

I wouldn't go so far as to call gallery a simple example. 😄

When markup is complex enough that it can't easily be parsed, such as a gallery column count, I think it's entirely reasonable to mix hints into the attributes. We're running with this idea in #348, in that by treating render-time attributes as a bucket and diffing against parsed attributes, we can find the subset of "extra" attributes that need to be encoded (as of now into the comment syntax).

Limited to 3 attributes

How did you arrive at this number?

Each piece of user-editable data has a direct correspondence to a specific HTML element/attribute in the markup.

What's a good example of a simple block, and what do you imagine the forEditing implementation would be for it? Seems inevitable that for anything aside from single-parent nodes (<p>, <h2>) we'd still encounter same parsing difficulty you expressed earlier in the "Implementation Approaches" section.

@joyously
Copy link

joyously commented Apr 6, 2017

Instead of calling them Simple and Advanced, maybe you should think of them as Static and Dynamic.

  • Static is just HTML, and doesn't need a whole lot of help to look like it's going to look. The editor would need to know where the start and end of each tag is, so the "block" can be moved and styled (style being adding classes). Other than that, there's not that much to do.
  • Dynamic is currently the shortcode syntax, which it sounds like everyone wants to change so that it doesn't render the parameters when the plugin is disabled. It should not be stored as its rendered HTML, at least not anywhere that the user can change the rendered markup, because it is dynamic. The parameters would be different for each shortcode, as you know, and the user should only be manipulating those (as if in "text mode"). I personally think the user should have to request a preview for that block and have the default be the parameters. Again, this type is fairly straightforward to edit, since the editor knows the start and end in order to move it around and edit the parameters. The rendering would be a call to do_shortcode() to get the HTML, and show it in an iframe or in the same way the simple HTML is shown, but with an internal flag that disallows editing.

@mtias mtias closed this as completed Nov 20, 2017
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

No branches or pull requests

4 participants