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

Adding a controlled editor using the grammar parser #113

Closed
wants to merge 1 commit into from

Conversation

youknowriad
Copy link
Contributor

@youknowriad youknowriad commented Feb 22, 2017

The UI Prototype is starting to get strong enough, I thought it was time to tackle some technical aspects and try to tie the moving pieces together. So, I'm sharing this prototype (buggy?) with the following characteristics:

  • First and probably the most important property, is that it's a "controlled" editor which means it has a global state object, containing all the data needed to display the editor. the UI is a projection of this state.

  • It also uses the WP Post Grammar parser, to parse the initial data from a string and store it as the initial state. (I intentionally limited the parsing to the first level, no nested blocks yet)

  • Not yet implemented but easy enough, writing a serializer that takes the blocks stored in the global state and generate the new string to be stored as post content

  • It has most of the UI present in the UI prototype. I can take this further to match the UI Prototype, but sharing the prototype sooner seemed a good idea before going further.

  • This prototype also borrows some ideas from @aduth prototype here to allow multi-block selection.

  • Notice the render* functions. These functions can easily be switched to use @aduth wpBlocks, preact, react or similar, I avoided libraries intentionally.

The hardest issues

  • I said earlier that it may be a bit buggy, because one difficult task to achieve when dealing with contenteditable on a controlled editor is how to save and update the caret position and the selected text. In this early prototype I'm using the rangy lib to save/restore the position between two renders. Not a perfect solution, but a temporary one.

  • Another tricky part, is how to catch changes on the contenteditable blocks and save them on the state. My solution to this is having a update function as part of the block API, each type of block takes the previous saved block, the current contenteditable dom element corresponding to this block and should save these updates and generate a new block object to save in state.

Block API

So for this prototype, the block API for now is something like :

 var blockDefinition = {
	id: 'paragraph',
	label: 'Paragraph',
	render: function( block, blockDomNode ) { blockDomNode.innerHTML = 'something';  },
	update: function( block, blockDomNode) { reutrn { ...block, rawContent: blockDomNode.innerHTML },
	blockControls: [ 'alignLeft', 'alignCenter', 'alignRight' ],
	inlineControls: [ 'textBold', 'textItalic', 'link', 'textStrikeThrought', 'textColor' ],
	icon: '<svg>',
	switch: [ 'paragraph', 'heading' ],
	category: 'common'
}

@youknowriad youknowriad self-assigned this Feb 22, 2017
@youknowriad youknowriad requested a review from aduth February 22, 2017 15:08
@aduth
Copy link
Member

aduth commented Feb 22, 2017

blockControls: [ 'alignLeft', 'alignCenter', 'alignRight' ],

Where are these controls defined? I see the benefit being that it's easy to reuse controls between blocks, but we should also make it easy to create new controls as well.

switch: [ 'paragraph', 'heading' ],

Similarly, how does one control the behavior for switching between block types? There'd been some discussion previously about whether a block should define what it can turn into vs. what it can be initialized from, or whether a block must be able to convert itself back to a basic text form from which any block can be initialized, etc. etc.

icon: '<svg>',

Should a block be expected to provide icon markup? What if multiple blocks use the same icon (avoid duplicate markup)? What if a block implementer wants to leverage a standard icon, and we need to update that standard icon over time?

@aduth
Copy link
Member

aduth commented Feb 22, 2017

Cross-linking #104 for API explorations here.

@youknowriad
Copy link
Contributor Author

youknowriad commented Feb 22, 2017

Yeah the API is not the main point of this prototype, I wanted to test/validate the principles (controlled, grammar, state, a contenteditable wrapper).

blockControls: [ 'alignLeft', 'alignCenter', 'alignRight' ]

For now the controls are defined on the global config variable

controls: {
alignLeft: {
icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>Align Left</title><rect x="0" fill="none" width="24" height="24"/><g><path d="M4 19h16v-2H4v2zm10-6H4v2h10v-2zM4 9v2h16V9H4zm10-4H4v2h10V5z"/></g></svg>'
},
alignCenter: {
icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>Align Center</title><rect x="0" fill="none" width="24" height="24"/><g><path d="M4 19h16v-2H4v2zm13-6H7v2h10v-2zM4 9v2h16V9H4zm13-4H7v2h10V5z"/></g></svg>'
},
alignRight: {
icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>Align Right</title><rect x="0" fill="none" width="24" height="24"/><g><path d="M20 17H4v2h16v-2zm-10-2h10v-2H10v2zM4 9v2h16V9H4zm6-2h10V5H10v2z"/></g></svg>'
},
imageNoAlign: {
icon: '<svg class="gridicon gridicons-align-image-center" height="24" width="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g><path d="M3 5h18v2H3V5zm0 14h18v-2H3v2zm5-4h8V9H8v6z"></path></g></svg>'
},
imageAlignLeft: {
icon: '<svg class="gridicon gridicons-align-image-left" height="24" width="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g><path d="M3 5h18v2H3V5zm0 14h18v-2H3v2zm0-4h8V9H3v6zm10 0h8v-2h-8v2zm0-4h8V9h-8v2z"></path></g></svg>'
},
imageAlignRight: {
icon: '<svg class="gridicon gridicons-align-image-right" height="24" width="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g><path d="M21 7H3V5h18v2zm0 10H3v2h18v-2zm0-8h-8v6h8V9zm-10 4H3v2h8v-2zm0-4H3v2h8V9z"></path></g></svg>'
},
imageFullWidth: {
icon: '<svg class="gridicon gridicons-fullscreen" height="24" width="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g><title>Full Bleed</title><path d="M21 3v6h-2V6.41l-3.29 3.3-1.42-1.42L17.59 5H15V3zM3 3v6h2V6.41l3.29 3.3 1.42-1.42L6.41 5H9V3zm18 18v-6h-2v2.59l-3.29-3.29-1.41 1.41L17.59 19H15v2zM9 21v-2H6.41l3.29-3.29-1.41-1.42L5 17.59V15H3v6z"></path></g></svg>'
},
textBold: {
icon: '<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>Bold</title><rect x="0" fill="none" width="24" height="24"/><g><path d="M7 5.01h4.547c2.126 0 3.67.302 4.632.906.96.605 1.44 1.567 1.44 2.887 0 .896-.21 1.63-.63 2.205-.42.574-.98.92-1.678 1.036v.103c.95.212 1.637.608 2.057 1.19.42.58.63 1.35.63 2.315 0 1.367-.494 2.434-1.482 3.2-.99.765-2.332 1.148-4.027 1.148H7V5.01zm3 5.936h2.027c.862 0 1.486-.133 1.872-.4.386-.267.578-.708.578-1.323 0-.574-.21-.986-.63-1.236-.42-.25-1.087-.374-1.996-.374H10v3.333zm0 2.523v3.905h2.253c.876 0 1.52-.167 1.94-.502.416-.335.625-.848.625-1.54 0-1.243-.89-1.864-2.668-1.864H10z"/></g></svg>'
},
textItalic: {
icon: '<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>Italic</title><rect x="0" fill="none" width="24" height="24"/><g><path d="M10.536 5l-.427 2h1.5L9.262 18h-1.5l-.427 2h6.128l.426-2h-1.5l2.347-11h1.5l.427-2"/></g></svg>'
},
link: {
icon: '<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>Link</title><rect x="0" fill="none" width="24" height="24"/><g><path d="M17 13H7v-2h10v2zm1-6h-1c-1.63 0-3.065.792-3.977 2H18c1.103 0 2 .897 2 2v2c0 1.103-.897 2-2 2h-4.977c.913 1.208 2.347 2 3.977 2h1c2.21 0 4-1.79 4-4v-2c0-2.21-1.79-4-4-4zM2 11v2c0 2.21 1.79 4 4 4h1c1.63 0 3.065-.792 3.977-2H6c-1.103 0-2-.897-2-2v-2c0-1.103.897-2 2-2h4.977C10.065 7.792 8.63 7 7 7H6c-2.21 0-4 1.79-4 4z"/></g></svg>'
},
textStrikeThrought: {
icon: '<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>Strikethrough</title><rect x="0" fill="none" width="24" height="24"/><g><path d="M14.348 12H21v2h-4.613c.24.515.368 1.094.368 1.748 0 1.317-.474 2.355-1.423 3.114-.947.76-2.266 1.138-3.956 1.138-1.557 0-2.934-.293-4.132-.878v-2.874c.985.44 1.818.75 2.5.928.682.18 1.306.27 1.872.27.68 0 1.2-.13 1.562-.39.363-.26.545-.644.545-1.158 0-.285-.08-.54-.24-.763-.16-.222-.394-.437-.704-.643-.18-.12-.483-.287-.88-.49H3v-2H14.347zm-3.528-2c-.073-.077-.143-.155-.193-.235-.126-.202-.19-.44-.19-.713 0-.44.157-.795.47-1.068.313-.273.762-.41 1.348-.41.492 0 .993.064 1.502.19.51.127 1.153.35 1.93.67l1-2.405c-.753-.327-1.473-.58-2.16-.76-.69-.18-1.414-.27-2.173-.27-1.544 0-2.753.37-3.628 1.108-.874.738-1.312 1.753-1.312 3.044 0 .302.036.58.088.848h3.318z"/></g></svg>'
},
textColor: {
icon: '<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>Text Color</title><rect x="0" fill="none" width="24" height="24"/><g><path d="M3 19h18v3H3v-3zM15.82 17h3.424L14 3h-4L4.756 17H8.18l1.067-3.5h5.506L15.82 17zm-1.952-6h-3.73l1.868-5.725L13.868 11z"/></g></svg>'
}
},

Similarly, how does one control the behavior for switching between block types?

Right! for now this prototype does not answer this question, the switch prototype is only used to show the switch UI. But I imagine adding a switcher function in each block type to generate the new block from the old one.

Should a block be expected to provide icon markup?

This is temporary due to the lack of build etc... it was just handy for now, we probably should use "classnames" or an "iconID" (Gridicons or Dashicons)

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.

2 participants