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

Implement File block #6805

Closed
wants to merge 50 commits into from
Closed

Implement File block #6805

wants to merge 50 commits into from

Conversation

mirka
Copy link
Member

@mirka mirka commented May 17, 2018

Closes #5462

Description

Implements a File block with the following features:

  • Editable text link
  • Text link can be copy and pasted to create a normal link
  • HTML5 download link
  • Copy URL button to copy the media file URL to clipboard
  • Accepts both Media Library files and external URLs only Media Library files
  • Text link can be changed to point to the Attachment Page
  • Text link can be changed to open in a new window
  • Accepts and uploads a file drag-and-dropped into the editor (or placeholder)
  • Transforms to/from Audio and Video blocks
  • Download button text is editable
  • Download button is optional
  • (?) Background Color and Text Color options in sidebar

How has this been tested?

Try the features above.

Screenshots

screen shot 2018-05-27 at 5 56 09

Things I don't plan on including in this PR (but should be done eventually)

@jasmussen
Copy link
Contributor

Tried to check this out and run it. VERY cool work! Very very nice, definitely getting there.

A few niggles:

Can we make it so you can drag and drop a PDF file directly into the canvas, in addition to on the file block placeholder?

It would be nice if the Download button text were editable. Perhaps even that you could toggle off the download button entirely in the sidebar. If you remove the actual link from the button inside the editor, so you can't download from inside the editor but only on the published front-end, then you could make the button clickable to edit, just like the normal Button block.

Given that you can edit the text of the link, right now the "Edit" button taking you back to the placeholder feels a little bit jarring. Could you make it so instead of having the layout alignment buttons and an edit button, it has instead text-block-like editing tools?

Instead of:

screen shot 2018-05-18 at 09 03 34

You'd have:

screen shot 2018-05-18 at 09 03 30

This would essentially be like a text field with a button. You could write your own text, link, unlink, bold, italic, etc. You'd never be able to go back to the placeholder block from the final state, but that's fine too, and the button would always hold the link. What do you think?

I notice by the way that if you edit the link, you're typing in reverse :) — but I'll bet that's what you refer to in the remaining bullet.

reverse

Background Color and Text Color options in sidebar

We probably don't need these, but nice idea otherwise!

Very cool work, thanks for working on this, and keep up he good work!

@mirka
Copy link
Member Author

mirka commented May 18, 2018

Thanks for the review, @jasmussen!

Can we make it so you can drag and drop a PDF file directly into the canvas, in addition to on the file block placeholder?

Do you mean like this?
direct-pdf-drop

If so, it is already supposed to be working. You might be seeing a bug. Could you give me some more details so I can reproduce it?

(BTW are you rebasing onto a branch that is not master? It looks like you have icons screen shot 2018-05-18 at 23 02 20 in the toolbar that I don't) I get it now 🙂

It would be nice if the Download button text were editable. Perhaps even that you could toggle off the download button entirely in the sidebar.

Sure! We could do that.

This would essentially be like a text field with a button. You could write your own text, link, unlink, bold, italic, etc. You'd never be able to go back to the placeholder block from the final state, but that's fine too, and the button would always hold the link. What do you think?

Hmm, I thought about this too. At one point I even considered a hybrid approach:

artboard
(I scrapped this idea, because having a link icon there would make the pencil icon a lot less discoverable)

My main concern is that we’d confuse the user’s mental model of where the “source of truth” for the file link is.

Since choosing a file from the placeholder sets both links, you can reasonably assume that both links will change if you choose another file from the same interface:

artboard 3

But when you introduce a second way to change the link, it’s harder to tell what’s going to happen. If only the text link were to change, it would become hard to tell that the text link and button link are out of sync. On the other hand, if both links were to change, it would break the expectation of the standard rich text UI:

artboard 2

Is this a tradeoff we are willing to take? Let me know what you think 🙂

@ZebulanStanphill
Copy link
Member

ZebulanStanphill commented May 18, 2018

(BTW are you rebasing onto a branch that is not master? It looks like you have icons in the toolbar that I don't)

Those are the wide and full-width alignment options, which do not show unless your theme declares support for them, since those kinds of alignments extend out of the standard content width and may not look right depending on the theme. Since Gutenberg is so new, there are very few themes that have declared support for those alignment types, so most of the time you will only see those options if you use a custom child theme which has declared support for them.

@mirka
Copy link
Member Author

mirka commented May 18, 2018

@SuperGeniusZeb Oh I see, that makes sense now. Thank you!

@mirka mirka changed the title [WIP] Implement File block Implement File block May 26, 2018
Placeholder text state was not properly updating when uploading a file from the media placeholder.
@jasmussen
Copy link
Contributor

Very nice work! Here's a GIF of it:

file block

Experience wise, this seems to behave as we've discussed, and given the paradigms Gutenberg works with, this seems like it solves the task at hand:

  • allows file uploads
  • allows inserting files easily
  • allows copying the link so you can use it to create a more regular in-text link

For the scope of this task, 👍 👍, I think this is cool! Nice work.

Did you include/merge the work from master that unifies the placeholder block?


At some point in the future, as a separate project to this, I imagine that we'll want to take a look at the media library in WordPress in general. In doing so, there will likely also be an opportunity to look at how the placeholder block can work. For example it would be nice if we could show part of the media library itself inside the placeholder block, and allow you to simply drag into it there, and then insert as file block or insert as plaintext link. But that's something to consider for the future.

@mirka
Copy link
Member Author

mirka commented May 28, 2018

@jasmussen Thank you for all your guidance, and the cool gif! There are so many possibilities with the Gutenberg framework, and I'm excited to see how the UX will evolve. Hope I'll have a chance to contribute more.

Did you include/merge the work from master that unifies the placeholder block?

Yes. The code is a lot cleaner thanks to that. And there's another modification in the works (#6957), so if that gets merged in first I'll get to delete a few more lines 🙂

// the document.activeElement at the moment when the copy event fires.
// This causes documentHasSelection() in the copy-handler component to
// mistakenly override the ClipboardButton, and copy a serialized string
// of the current block instead.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the detailed comment, explaining why do we have the workaround.

Copy link
Member

@nb nb May 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One problem in Safari, though, is that the while block (<!-- wp:file {"href":…) ends up in the clipboard.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, it looks like this workaround hadn't fixed the Safari bug completely. I did some testing and narrowed down the repro condition a bit. My workaround seems to be effective for pre-existing posts (i.e. posts that were already saved when the page loaded), but not for new posts. Not sure what's going on, but that's a start... I'll have to look into it further.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in #7106

getBlobByURL( href )
.then( ( file ) => {
editorMediaUpload( {
allowedType: '*',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to make sure I understand correctly, once #6968 gets in we will feed the list if types here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, that allowedType is being used for limiting the files to a suggested file type, similar to the accept attribute of an <input type="file">. The naming is very confusing right now. I want to rename that to accept (and maybe enhance it to take comma-separated and file extension arguments like the actual <input accept>) in a separate PR once this and #6968 are merged in.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From #5462:

WordPress also supports a number of filetypes that aren't media, such as pdf, doc, and others. See full list here: https://codex.wordpress.org/Uploading_Files

I don't think that editorMediaUpload in the current shape is what we really want to use in here, because non-media files can be uploaded, too. It seems like we need to refactor the existing method or add a more generic one which editorMediaUpload uses behind the scenes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I was under the impression that the "media" in editorMediaUpload and mediaUpload is being used in the broader sense of the word, and not to mean "images, audio, and video". This is in line with how WordPress uses the term — every kind of uploadable file, including pdf and other documents, are uploaded to the "Media Library", and are retrieved via the media endpoint.

Even /edit-post/hooks/blocks/media-upload, which includes specific logic for when gallery == true, seems generic enough for any kind of file. Is there a reason we want to keep editorMediaUpload specifically for image/audio/video files?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I posted a separate issue for the allowedType problem: #7155

}
}

isBlobURL( url = '' ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to move this one to utils, like the other blob functions? At which point would you split out utils/blob?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think so. But in a separate PR, because I think I saw a couple of other places in the codebase where we’re doing blob url detection, and I’d want to change them all at once.

<a
href={ textLinkHref }
target={ openInNewWindow }
rel={ openInNewWindow ? 'noreferrer noopener' : false }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the noreferrer?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it’s because noopener doesn’t work in IE/Edge. (see also: #6186)


// edit component has its own attributes in the state so it can be edited
// without setting the actual values outside of the edit UI
this.state = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure I follow the reason we keep the attributes in the state, too. What do we gain?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, I've cleaned them out.


if ( id !== undefined ) {
// Get Attachment Page URL
wp.apiRequest( {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason we’re not using withAPIData here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't realize we had that! Cool. I put that in place, however it turns out that the snapshot test helper for our components (blockEditRender()) isn't equipped to inject context in order for it to work. I've made a patch for it and I'll propose it in a separate PR, but not sure if my implementation is good. Anyway, I'm going to remove the snapshot test for this temporarily.

} )
.then(
( result ) => {
this.setState( { attachmentPage: result.link } );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would the setState work correctly if the component hasn't been rendered yet?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Should've been in componentDidMount().


componentDidUpdate( prevProps ) {
// Reset copy confirmation state when block is deselected
if ( prevProps.isSelected && ! this.props.isSelected ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This worked great for me as a user.


componentDidUpdate( prevProps ) {
if ( prevProps.text !== this.props.text ) {
this.setState( { showPlaceholder: ! this.props.text } );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the trade-offs of using state here vs. just using the text prop for rendering logic?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since I'm resorting to use a content-editable here, this is not a "controlled" component and therefore the text prop isn't updated on each keystroke.

mirka added 4 commits May 31, 2018 03:59
Requires a modification in the `blockEditRender()` test helper to inject context, or else the withAPIData HOC won’t work.
this.setState( {
attachmentPage: media.link,
} );
}, this.props.media.get /* refetch attachment page url */ );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does setAttributes accept a second argument? When is this.props.media.get called?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally assumed setAttributes took the same arguments as setState! I meant it as a callback. Thank you.


export default compose( [
withAPIData( ( props ) => ( {
media: `/wp/v2/media/${ props.attributes.id }`,
Copy link
Member

@gziolo gziolo Jun 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be rather using getMedia selector instead of withAPIData which is going to be deprecated as soon as we find a way to port all existing code that handles REST API requests. This will also remove the need to introducing changes proposed in #7108.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

export default compose( [
    withSelect( ( select, props ) => {
        const { getMedia } = select( 'core' );
        return {
            image: getMedia( props.attributes.id ), // not sure if getMedia handles undefined values
        };
    } ),
    withNotices,
] )( FileEdit );

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, I wasn't aware of this. I'll look into it.

@@ -33,7 +33,9 @@ export function mediaUpload( {
filesSet[ idx ] = value;
onFileChange( compact( filesSet ) );
};
const isAllowedType = ( fileType ) => startsWith( fileType, `${ allowedType }/` );
const isAllowedType = ( fileType ) => {
return ( allowedType === '*' ) || startsWith( fileType, `${ allowedType }/` );
Copy link
Member

@gziolo gziolo Jun 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it rather list all allowed file types by allowing the array format?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think it should accept arrays, as well as have a more predictable syntax. More on that here: #7155

I opted for a wildcard here as a stop-gap measure, or else we'd have something like ['audio', 'application', 'video', 'text', 'image', 'font'] which I think is more error-prone for consumers.

@mtias mtias added the New Block Suggestion for a new block label Jun 19, 2018
@mirka
Copy link
Member Author

mirka commented Jun 27, 2018

By the way, here are some of the RichText issues that caused me to resort to a contenteditable for the text link part: #7575, #5657, #7591.

Now that I've sorted through the repro conditions and workarounds for them, I feel like #7591 is the only "blocker" for using a RichText here. If that is addressed, I think we could safely replace the contenteditable.

@noisysocks noisysocks mentioned this pull request Jun 29, 2018
@noisysocks
Copy link
Member

Thanks for your excellent work on this @mirka. @talldan and I are going to pick up on this in #7622.

@noisysocks noisysocks closed this Jun 29, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
New Block Suggestion for a new block
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants