-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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 Inline Images and Inline Blocks API #5794
Conversation
Nice work, very good description and plan seems to make sense. Also nice first step. Here's a GIF: This is a great way to start, because it can help us validate whether the plan as laid out in #2043 indeed works or not. My initial impression is that yes, it does, but we need to be mindful of a couple of things:
Good items on your checklist. Wanted to elaborate on this:
Forgive me if this was implied or obvious, but the blue line that appears after the block should not be present in this situation. That is the indicator for where block level block would go. Tagging this ticket and CC'ing a few. Nice work! What help do you need with next steps? |
blocks/library/paragraph/index.js
Outdated
@@ -311,6 +311,10 @@ const schema = { | |||
customFontSize: { | |||
type: 'number', | |||
}, | |||
canInsertTokens: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It probably shouldn't be a part of attributes
, because their role is to help with parsing block's definition into the data to consume. It should be rather inside supports
(see https://github.com/WordPress/gutenberg/blob/master/docs/block-api.md#supports-optional) property or directly inside settings
part of the block's definition.
Minor thing :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, thanks. I don't currently see a way to access the supports
or settings
in the editor store. Perhaps this is a sign that I need to add new Redux actions? The design discussion happening now as to where the button to add inline images should be may have some implications here as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The store only tracks the name, UUID, and current attribute state of a block. But the name is enough to get the supports of a block, via getBlockSupport( name, feature, defaultSupports )
, hasBlockSupport( name, feature, defaultSupports )
, or simply getBlockType( name ).supports
.
(Aside: Wondering if these ought to be called getBlockTypeSupport
, hasBlockTypeSupport
for terminology consistency)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, thanks for pointing out those functions! I hadn't noticed them yet and they should be useful.
I also think it does work and maybe sets a nice precedent for the future. I have a few points to consider on discoverability though. I am concerned firstly if inline will be what someone would look for, secondly if it's findable. For example, how do I know to look for inline blocks? This is a bigger question but does open it. Should the action to add maybe be 'by' the block if it's inline? |
From @jasmussen:
Noted, thanks!
Thanks, I added that to the task description. From @karmatosed:
I could see how this might be easier for the user to find. Would it be an image button on the toolbar for the block? |
I'd like to avoid having this by the block as it fragments even further the ways in which you can insert content and is going to be confusing — people might expect that's the way of adding any image, for example. The current plan still seems sound to me. |
Great, then I'll plan on sticking with the mockups by @jasmussen. As far as next steps, I just added a Technical Plan to the PR description which I would appreciate a review of. Also, @mtias mentioned in #2043 (comment) the possibility of globally registering inline blocks/tokens. I'm wondering if this is in the scope of this PR? Or is it ok to hard code the 'Inline Image' button in the Inserter for now, to see if we're happy with the interactions, and save the inline blocks API for a future PR? |
4d05ad0
to
eb0244c
Compare
Up to you if you want to explore it, but not a requirement for initial implementation, I'd say, even though it's going to be needed at some point. Probably still good to have it in a separate PR for ease of reviewing and implementing. Also related to some questions here: #4670 |
I'm currently working on adding the inline insertion point (the blue square that appears after the caret showing where the image will be inserted). I did a lot of experimenting with TinyMCE and adding the square by inserting an element into the RichText/TinyMCE content. This approach seems problematic, as I was running into the following problems:
Because of these potential issues, I'm now thinking it may be better to have the insertion point be absolutely positioned above the RichText rather than altering the content. Do you foresee any problems with this approach @jasmussen? If I absolutely position the blue square, the square may potentially cover up a letter if the caret was in the middle of a word. Otherwise, when the caret is at the end of a word, as I would think would normally be the case when adding an inline image, the appearance would look like the mockup. If this description isn't clear, I can provide screenshots/gifs. |
While I don't foresee any problems with this approach, the blue insertion indicator has less value than the inline image itself. If you like, you could postpone or even ignore that aspect of the mockups, and focus on getting the inline image insertion to work instead. Totally up to you 😄 |
b8e54e8
to
0a7d547
Compare
@jasmussen Thanks for that perspective! I probably spent too much time going down the TinyMCE rabbit hole... but I think the knowledge I gained about its API will be useful when I do the inline image insertion. Using absolute positioning for the blue insertion indicator was much simpler so I went ahead and added that. Next steps: rendering the media library and inserting the inline image upon selection. |
Happy to report that the feature is working now! I added a gif to the PR description. Still need to work out a bug, write tests, and documentation, but I would appreciate a review of what I have done so far @jasmussen. |
Really nice work! 🔥 Here's a GIF: I can't seem to break it. This seems to work solidly well! It also, to me, feels as natural to insert images inline in this way (place cursor in text, use top inserter), as we hoped it would be. I quickly tested in Chrome's mobile emulator, where resizing images doesn't seem to work. But this could either be due to it being the mobile emulator not behaving and it might still work on an actual device, or it could simply be a limitation of TinyMCE. In either case addressing that is not necessary as part of this PR, and definitely something we can look at separately. (And in doing so it would be nice if we could get the big friendly blue drag handles that the image block uses for resizing). But again, a separate thing. Stellar work! 👏I think we're ready for final code review! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is really great!
I think that's a really solid point @noisysocks makes. And this is one of those questions that's come up under the development, where we weren't quite sure what would work until we tried it. We could revisit an idea originally posed on the mockups, which is to always show the blocks, but also show inline blocks when in text: CC: @mtias because I think he had some thoughts about this. |
I agree that the UI concern @noisysocks raised makes sense, and I'll look forward to seeing if there is consensus around the mockup @jasmussen referred to. If we did combine the menus as in the mockup, we would need to consider what the behavior of the insertion indicator would be (for instance, would it switch between the inline indicator and the block indicator while the menu was open?). I had a conversation with @nb today about this PR, and here's a summary of what we discussed -- would be great to get others input: First was a concern about using the term Thinking ahead to when we add Inline Blocks to other blocks that use RichText, wondering whether checking a block's This also brings up the question of whether other blocks with RichText should behave similarly to the Paragraph block, in that if the RichText is empty, the regular blocks menu should render, and only if the RichText has content should the Inline Blocks menu render. Another question that was brought up, was if there is anything more that needs to be added to the HTML that the Inline Image generates. Currently it is just an |
520efcf
to
41b87a1
Compare
- try white background - remove outline and selection background
For now just tests adding an Inline Image in a Paragraph.
RichText directory was moved from blocks/ to editor/components when I rebased. This commit moves the InlineInsertionPoint and InlineBlocks to the new location as well.
e704bfd
to
f922183
Compare
Since a lot has changed since I originally wrote the PR description, I've updated the description with details about the current Technical Design, what tasks I'm currently working on, and a new gif. I welcome any feedback on that. I also changed the title of the PR to reflect that it now includes an API for inline blocks. CC @nb. |
Shouldn’t |
Previously I had used only a single dash, in preparation for adding to webpack where only the first dash was being replaced by the `camelCaseDash` function. However, now that this function has been updated to replace all dashes, this is no longer a limitation.
@SuperGeniusZeb Agreed! I just renamed it in 2e5fca4. |
editor/components/rich-text/index.js
Outdated
@@ -439,6 +442,18 @@ export class RichText extends Component { | |||
}; | |||
} | |||
|
|||
getInsertPosition() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this not part of InlineBlocks
if the editor
instance is passed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had left if there since the containerRef
and the similar getFocusPosition
function were in RichText
, but I agree that it belongs with InlineBlocks
now, and moved it in b8a225a.
editor/components/rich-text/index.js
Outdated
@@ -122,6 +123,8 @@ export class RichText extends Component { | |||
this.onPaste = this.onPaste.bind( this ); | |||
this.onCreateUndoLevel = this.onCreateUndoLevel.bind( this ); | |||
this.setFocusedElement = this.setFocusedElement.bind( this ); | |||
this.getFocusPosition = this.getFocusPosition.bind( this ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remnant? Cannot see this in this diff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for catching that. Removed in f4a4358.
editor/components/rich-text/index.js
Outdated
@@ -723,9 +738,12 @@ export class RichText extends Component { | |||
} | |||
|
|||
componentDidUpdate( prevProps ) { | |||
if ( ! this.editor ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this can be moved back to minimise diff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in f4a4358
// When moving between two different RichText with the keyboard, we need to | ||
// make sure `setInsertAvailable` is called after `setInsertUnavailable` | ||
// from previous RichText so that editor state is correct | ||
setTimeout( this.props.setInsertAvailable ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be cleaned up. There's a HOC for this now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. Fixed in 7d6e6a1
super( ...arguments ); | ||
|
||
this.insert = this.insert.bind( this ); | ||
this.onSelectMedia = this.onSelectMedia.bind( this ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As previously commented, I don't like the image specific logic here. Would be good to be part of the block registration somehow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. It's tricky because if the registered block returns a React component, RichText will render it to a string so it can't use React methods after being inserted. I've been contemplating whether it would be possible to use slot/fill or another method to render an inline block's "menu" (in this case the Media Library) either in the Inserter
or InlineBlocks
component, and then have the menu component return the element to be rendered to string on select, but this still doesn't seem ideal.
Related: your #5794 (comment)
Also maybe related: the Edit toolbar #5794 (comment) and how that could be rendered by React after insertion.
// set width in style attribute to prevent Block CSS from overriding it | ||
const img = `<img class="wp-image-${ id }" style="width:${ imgWidth }px;" src="${ url }" alt="${ alt }" />`; | ||
|
||
return img; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be interesting if this is a React component and then render to string in RichText? Not sure though. Cc @youknowriad @aduth regarding RichText state and formats.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think this should be returning an element. If RichText
needs a string, it can serialize.
No need to break convention with block edit
/ save
, nor expose potential self-XSS vulnerabilities (or even just clumsy breakage like assigning an alt as My "fancy" image
which will fail here).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. @iseulde added edit
and save
functions that return React elements in https://github.com/WordPress/gutenberg/pull/6959/files#diff-157ef14c5b252d176bfb2fa95850763bR20. Happy to merge those changes if we want, but I think maybe further development is moving to #6959 now.
When testing, it seems to work nicely!
|
Thanks for doing another round of code review and for testing this out @iseulde! I will get working on the issues you brought up. |
icon: 'format-image', | ||
|
||
render( { id, url, alt, width } ) { | ||
const imgWidth = width > 150 ? 150 : width; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See Math.min
:
const imgWidth = Math.min( 150, width );
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call. I noticed @iseulde used Math.min
in https://github.com/WordPress/gutenberg/pull/6959/files#diff-157ef14c5b252d176bfb2fa95850763bR39. Happy to make that change here but it looks like development of this feature is moving to that branch now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I already changed this yes. 😄
// set width in style attribute to prevent Block CSS from overriding it | ||
const img = `<img class="wp-image-${ id }" style="width:${ imgWidth }px;" src="${ url }" alt="${ alt }" />`; | ||
|
||
return img; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think this should be returning an element. If RichText
needs a string, it can serialize.
No need to break convention with block edit
/ save
, nor expose potential self-XSS vulnerabilities (or even just clumsy breakage like assigning an alt as My "fancy" image
which will fail here).
Superseded by #6959. |
Resolves
Insert images inline #2043.
UI Design
Goal
The user should be able to add inline images in text, following the steps in the mockups by @jasmussen in #2043 (comment).
Tasks:
Future Plans:
Technical Design
Components/Modules Affected
Inserter
Determines whether to render the regular blocks menu (and the regular insertion point) or Inline Blocks menu (and inline insertion point) by communicating with the editor redux store.
InserterInlineMenu
Uses the existing
InserterGroup
component to render the buttons for the registered inline blocks.RichText
When selected, renders the
InlineBlocks
component.InlineBlocks
On mount, dispatches an action to the editor store signalling that inline block insertion is available. Responsible for communicating with editor store, and inserting inline block using the TinyMCE editor instance. Renders the
InlineInsertionPoint
when editor store signals it to.InlineInsertionPoint
Absolutely positioned inline insertion point (caret and indicator) where the inline block will be inserted.
MediaUpload
Used to interact with the media library to select the image for the inline image.
inline-blocks/
Provides an API for registering and getting inline block types. Modeled after the regular blocks API (in
blocks/
directory) to provide a similar interface to developers.core-inline-blocks/
Stores the core inline block types, mirroring the directory structure of
core-blocks/
. Currently just has the inline image, but later could be expanded to include other inline blocks like @ mentions.Editor store reducers/actions/selectors
Used to communicate between the
InlineBlocks
component and theInserter
.New state added:
isInlineInsertAvailable
(boolean
)Set to true by the
InlineBlocks
component whenRichText
is selected. This lets theInserter
know that it can render the inline blocks menu.isInlineInsertionPointVisible
(boolean
)Set to true by the
Inserter
when it determines that we are inserting inline and renders the Inline Blocks menu. TheInlineBlocks
component subscribes to this and renders theInlineInsertionPoint
.inlineBlockNameForInsert
(string
)Set by the
Inserter
when an inline block is selected from the menu. TheInlineBlocks
component subscribes to this to know which inline block to insert.Additional Design Choices
Previously considered and alternative approaches we chose not to do:
supports
to enable/disable inline blocks at the block levelCurrently working on:
#5794 (review)
MediaUpload
logic out ofInlineBlocks
component. This should be in the registered inline block or perhaps rendered by theInserter
.Future plans:
Screenshots (jpeg or gifs if applicable):
Tests