Editor Blocks API proposal #300

Closed
Afraithe opened this Issue Mar 21, 2017 · 4 comments

Comments

6 participants
@Afraithe

Afraithe commented Mar 21, 2017

Editor Blocks API proposal

This is a rough draft of an blocks api for TinyMCE to be used to manage complex block components within tinymce. The blocks can only be inserted at root level this is a difference from the current contenteditable false support we have in tinymce but it would make them easier to handle especially the more complex blocks.

Editor instance api

editor.blocks.register(id:String, config:Object)

Registers a new block type in the editor. Registered blocks will appear in the insert blocks toolbar.

editor.blocks.insert(id:String)

Inserts the block by id into the editor at caret position splitting any text block in the process.

editor.blocks.canInsert(id:String):Boolean

Returns true/false if the block by id can be inserted at caret position. For example you can't insert a block inside another block in some cases since the block can’t be split when inserting a block inside a caption part of a figure.

editor.blocks.getAll():Object

Returns an name/value object with all registered blocks.

Blocks data structure

type:String (Optional)

Can be the following types:

  • simple - Default type just contenteditable=false. Used for figure element etc.
  • shimmed - Places a shimmed mask on top of the block when it's not selected. Used for youtube etc.
  • unmanaged - Selection needs to be managed by the block. Undo manager would ignore changes inside the block. Events just passthrough etc. Used for codemirror etc.

title:String

This is the title that will be displayed in the insert blocks toolbar.

icon:String

This is the icon that will be displayed in the inserts blocks toolbar.

toolbar:[Object] (Optional)

This is an array of objects describing the toolbar items.

insert(api:Api, callback:function(element:Element))

Gets called when the user wants to insert a block into the editor. This is where the user constructs the block to be inserted into the editor. The api is provided since in unmanaged mode you might want to do selection on events inside the complex block. The callback is needed if the operation is async or if you want to perform other actions after the insert has been done by tiny for example converting an element into an codemirror instance. The api methods would either be no-ops or return null until the element callback is executed.

remove(api:Api) (Optional)

Gets called when the block is about to be removed by the user on say delete/backspace. This means the api.dom() function will return the reference to the currently attached dom node things like cleanups for event unbinding can be done before it’s removed.

load(api:Api):Element (Optional)

Gets called when the block was loaded into the editor after contents was parsed and attached to the dom. Similar to insert this is where you can construct a more complex version of the block converting it from say a pre element into a codemirror instance.

save(api:Api):Element (Optional)

Gets called when the block is about to be saved before serialization.

Toolbar items structure

type:String

Optional type of what UI element to create. Defaults to “button” and would in version 1 be the only thing supported. Other things could be listbox/selectbox/menubutton etc this is yet to be determined.

icon:String

Icon to be displayed in the blocks contextual toolbar.

tooltip:String

Tooltip to be displayed when hovering the contextual toolbar items.

action(api:Api)

Callback that gets executed when the toolbar item is clicked/pressed.

Block interaction api

dom():Element

Returns the dom element created by the block.

select()

Selects the block element. Useful in unmanaged mode.

unselect(forward:Boolean)

Unselects the block and moves the selection back to the editor before/after the block.

remove()

Removes the block from the editor this will fire the remove operation but will reposition the caret into the closest valid location.

Example of usage

editor.blocks.register('my-fancy-block', {
    type: 'simple',
    title: 'My fancy block',
    icon: 'my-fancy-icon',
    toolbar: [
        {
            icon: 'align-left',
            tooltip: 'Align left',
            action: function (api) {
                api.dom().className = 'align-left';
            }
        },
        {
            icon: 'align-right',
            tooltip: 'Align right',
            action: function (api) {
                api.dom().className = 'align-right';
            }
        }
    ],

    insert: function (api, callback) {
        var element = document.createElement('div');
        element.innerHTML = 'My fancy block';
        callback(element);
    },

    remove: function (api) {
        // Unregister custom events etc
    },

    load: function (api) {
        return api.dom();
    },

    save: function (api) {
        return api.dom();
    }
});

Example of contents produced by the editor on serialization

<div data-mce-block="my-fancy-block">
    My fancy block
</div>

Example of contents produced while within the editor

<div data-mce-block="my-fancy-block" contenteditable="false" data-mce-id="guid-123">
    My fancy block
</div>

Examples of operations for various user interactions

User Interaction Block operations
Insert insert
Delete/Backspace remove
Cut save, remove
Copy save
Paste load
Drag & Drop (internally) save, remove, load
Drag & Drop (externally) load
Drag & Drop (to external) save
Load editor contents load
Save editor contents save
Undo save, remove (for unmanaged blocks)
Redo load (for unmanaged blocks)

Scenarios

Mobile

Some of the more advanced block types won’t work on mobile. There are multiple ways this can be solved by an implementor. One way is to just register a different block type with the same id but as a placeholder though load/save. The other way is to produce a simpler form of the block for example a codemirror instance could be represented as a pre element or a textarea.

Custom UI

If you want to roll your own UI and just use the block api for state management you can just register blocks without toolbars and then based on selection render your own custom toolbar. This might be how wordpress would use this api if they want to do their own react ui but still get the benefits we provide around these blocks.

React blocks

Unmanaged blocks can be rendered by anything since they are more or less just raw dom nodes. This opens up using things like react to render these blocks. It’s then up to the implementor to handle the complexity.

Insert dialogs

Since the insert operation is async you could present say a dialog for some blocks where the insert is delayed until the callback is executed.

Oembed

Since the insert operation is async a service could be queried and then the callback would insert the embed block.

Modern theme

There is no reason why the concept of these blocks can’t be represented in both the inlite and the modern theme. There just needs to be a different UI for inserting them say under the “+” toolbar button. That means all users of tiny could benefit from these blocks not just those that use inline editing.

Converting comments from/to dom

Wordpress has a concept of wrapping the blocks in comments while it’s being stored in the wp_post field and then parsing and converting those comments into blocks while editing. It’s possible to do this though a plugin in tinymce separate from this block api. We can provide Wordpress with an example of this process.

@aduth

This comment has been minimized.

Show comment
Hide comment
@aduth

aduth Mar 21, 2017

Member

Related: #104, #302

Member

aduth commented Mar 21, 2017

Related: #104, #302

@aduth

This comment has been minimized.

Show comment
Hide comment
@aduth

aduth Mar 21, 2017

Member

With this proposal, to implement something like an image with caption, would the implementation of edit effectively be to apply a contenteditable="true" attribute to the nested figcaption element?

Member

aduth commented Mar 21, 2017

With this proposal, to implement something like an image with caption, would the implementation of edit effectively be to apply a contenteditable="true" attribute to the nested figcaption element?

@spocke

This comment has been minimized.

Show comment
Hide comment
@spocke

spocke Mar 21, 2017

Yes the toolbar would be for block level things and when you have an selection within a nested contenteditable=true you get the text formatting controls. We might need to abstract away the adding/removing of contenteditable attributes though. I think this is stuff that gets more clear when we do the spike poc of this.

spocke commented Mar 21, 2017

Yes the toolbar would be for block level things and when you have an selection within a nested contenteditable=true you get the text formatting controls. We might need to abstract away the adding/removing of contenteditable attributes though. I think this is stuff that gets more clear when we do the spike poc of this.

@iseulde

This comment has been minimized.

Show comment
Hide comment
@iseulde

iseulde Jun 22, 2017

Member

Let's close this ticket from the prototype phase.

Member

iseulde commented Jun 22, 2017

Let's close this ticket from the prototype phase.

@iseulde iseulde closed this Jun 22, 2017

@jasmussen jasmussen moved this from To Do to Done in Ephox Team Jun 26, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment