-
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 'insert from url' for image block #9264
Changes from all commits
2ac4df9
8b887c0
99dffe3
af9c1f6
cb269c1
1a9c378
e4809ae
3184bb6
c63ea9f
a8d43ca
1dced64
ace51f8
e4b3121
fe941a0
b7e90e1
b5c0dc1
4b48e5d
dd001e2
c824c12
3994517
7f5cad3
a433a7a
df2947e
72f302a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -45,3 +45,17 @@ export function revokeBlobURL( url ) { | |
|
||
delete cache[ url ]; | ||
} | ||
|
||
/** | ||
* Check whether a url is a blob url. | ||
* | ||
* @param {string} url The URL. | ||
* | ||
* @return {boolean} Is the url a blob url? | ||
*/ | ||
export function isBlobURL( url ) { | ||
if ( ! url || ! url.indexOf ) { | ||
return false; | ||
} | ||
return url.indexOf( 'blob:' ) === 0; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could use lodash here if you're into that kind of thing. export function isBlobURL( url ) {
return startsWith( url, 'blob:' );
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I avoided it there as it'd mean adding lodash as a dependency to the blob package. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can also check whether we polyfill |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { | ||
isBlobURL, | ||
} from '../'; | ||
|
||
describe( 'isBlobURL', () => { | ||
it( 'returns true if the url starts with "blob:"', () => { | ||
expect( isBlobURL( 'blob:thisbitdoesnotmatter' ) ).toBe( true ); | ||
} ); | ||
|
||
it( 'returns false if the url does not start with "blob:"', () => { | ||
expect( isBlobURL( 'https://www.example.com' ) ).toBe( false ); | ||
} ); | ||
|
||
it( 'returns false if the url is not defined', () => { | ||
expect( isBlobURL() ).toBe( false ); | ||
} ); | ||
} ); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ import { __ } from '@wordpress/i18n'; | |
import { BACKSPACE, DELETE } from '@wordpress/keycodes'; | ||
import { withSelect } from '@wordpress/data'; | ||
import { RichText } from '@wordpress/editor'; | ||
import { isBlobURL } from '@wordpress/blob'; | ||
|
||
class GalleryImage extends Component { | ||
constructor() { | ||
|
@@ -105,7 +106,7 @@ class GalleryImage extends Component { | |
|
||
const className = classnames( { | ||
'is-selected': isSelected, | ||
'is-transient': url && 0 === url.indexOf( 'blob:' ), | ||
'is-transient': isBlobURL( url ), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lots of nice refactoring with this new utility method 👍 |
||
} ); | ||
|
||
// Disable reason: Each block can be selected by clicking on it and we should keep the same saved markup | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -15,7 +15,7 @@ import { | |||||
*/ | ||||||
import { __ } from '@wordpress/i18n'; | ||||||
import { Component, Fragment } from '@wordpress/element'; | ||||||
import { getBlobByURL, revokeBlobURL } from '@wordpress/blob'; | ||||||
import { getBlobByURL, revokeBlobURL, isBlobURL } from '@wordpress/blob'; | ||||||
import { | ||||||
Button, | ||||||
ButtonGroup, | ||||||
|
@@ -60,31 +60,56 @@ export const pickRelevantMediaFiles = ( image ) => { | |||||
return pick( image, [ 'alt', 'id', 'link', 'url', 'caption' ] ); | ||||||
}; | ||||||
|
||||||
/** | ||||||
* Is the URL a temporary blob URL? A blob URL is one that is used temporarily | ||||||
* while the image is being uploaded and will not have an id yet allocated. | ||||||
* | ||||||
* @param {number=} id The id of the image. | ||||||
* @param {string=} url The url of the image. | ||||||
* | ||||||
* @return {boolean} Is the URL a Blob URL | ||||||
*/ | ||||||
const isTemporaryImage = ( id, url ) => ! id && isBlobURL( url ); | ||||||
|
||||||
/** | ||||||
* Is the url for the image hosted externally. An externally hosted image has no id | ||||||
* and is not a blob url. | ||||||
* | ||||||
* @param {number=} id The id of the image. | ||||||
* @param {string=} url The url of the image. | ||||||
* | ||||||
* @return {boolean} Is the url an externally hosted url? | ||||||
*/ | ||||||
const isExternalImage = ( id, url ) => url && ! id && ! isBlobURL( url ); | ||||||
|
||||||
class ImageEdit extends Component { | ||||||
constructor() { | ||||||
constructor( { attributes } ) { | ||||||
super( ...arguments ); | ||||||
this.updateAlt = this.updateAlt.bind( this ); | ||||||
this.updateAlignment = this.updateAlignment.bind( this ); | ||||||
this.onFocusCaption = this.onFocusCaption.bind( this ); | ||||||
this.onImageClick = this.onImageClick.bind( this ); | ||||||
this.onSelectImage = this.onSelectImage.bind( this ); | ||||||
this.onSelectURL = this.onSelectURL.bind( this ); | ||||||
this.updateImageURL = this.updateImageURL.bind( this ); | ||||||
this.updateWidth = this.updateWidth.bind( this ); | ||||||
this.updateHeight = this.updateHeight.bind( this ); | ||||||
this.updateDimensions = this.updateDimensions.bind( this ); | ||||||
this.onSetCustomHref = this.onSetCustomHref.bind( this ); | ||||||
this.onSetLinkDestination = this.onSetLinkDestination.bind( this ); | ||||||
this.toggleIsEditing = this.toggleIsEditing.bind( this ); | ||||||
|
||||||
this.state = { | ||||||
captionFocused: false, | ||||||
isEditing: ! attributes.url, | ||||||
}; | ||||||
} | ||||||
|
||||||
componentDidMount() { | ||||||
const { attributes, setAttributes } = this.props; | ||||||
const { id, url = '' } = attributes; | ||||||
|
||||||
if ( ! id && url.indexOf( 'blob:' ) === 0 ) { | ||||||
if ( isTemporaryImage( id, url ) ) { | ||||||
const file = getBlobByURL( url ); | ||||||
|
||||||
if ( file ) { | ||||||
|
@@ -100,10 +125,10 @@ class ImageEdit extends Component { | |||||
} | ||||||
|
||||||
componentDidUpdate( prevProps ) { | ||||||
const { id: prevID, url: prevUrl = '' } = prevProps.attributes; | ||||||
const { id: prevID, url: prevURL = '' } = prevProps.attributes; | ||||||
const { id, url = '' } = this.props.attributes; | ||||||
|
||||||
if ( ! prevID && prevUrl.indexOf( 'blob:' ) === 0 && id && url.indexOf( 'blob:' ) === -1 ) { | ||||||
if ( isTemporaryImage( prevID, prevURL ) && ! isTemporaryImage( id, url ) ) { | ||||||
revokeBlobURL( url ); | ||||||
} | ||||||
|
||||||
|
@@ -125,6 +150,10 @@ class ImageEdit extends Component { | |||||
return; | ||||||
} | ||||||
|
||||||
this.setState( { | ||||||
isEditing: false, | ||||||
} ); | ||||||
|
||||||
this.props.setAttributes( { | ||||||
...pickRelevantMediaFiles( media ), | ||||||
width: undefined, | ||||||
|
@@ -151,6 +180,21 @@ class ImageEdit extends Component { | |||||
} ); | ||||||
} | ||||||
|
||||||
onSelectURL( newURL ) { | ||||||
const { url } = this.props.attributes; | ||||||
|
||||||
if ( newURL !== url ) { | ||||||
this.props.setAttributes( { | ||||||
url: newURL, | ||||||
id: undefined, | ||||||
} ); | ||||||
} | ||||||
|
||||||
this.setState( { | ||||||
isEditing: false, | ||||||
} ); | ||||||
} | ||||||
|
||||||
onSetCustomHref( value ) { | ||||||
this.props.setAttributes( { href: value } ); | ||||||
} | ||||||
|
@@ -213,17 +257,33 @@ class ImageEdit extends Component { | |||||
]; | ||||||
} | ||||||
|
||||||
toggleIsEditing() { | ||||||
this.setState( { | ||||||
isEditing: ! this.state.isEditing, | ||||||
} ); | ||||||
} | ||||||
|
||||||
render() { | ||||||
const { isEditing } = this.state; | ||||||
const { attributes, setAttributes, isLargeViewport, isSelected, className, maxWidth, noticeOperations, noticeUI, toggleSelection, isRTL } = this.props; | ||||||
const { url, alt, caption, align, id, href, linkDestination, width, height } = attributes; | ||||||
const isExternal = isExternalImage( id, url ); | ||||||
|
||||||
const controls = ( | ||||||
<BlockControls> | ||||||
<BlockAlignmentToolbar | ||||||
value={ align } | ||||||
onChange={ this.updateAlignment } | ||||||
/> | ||||||
{ !! url && ( | ||||||
let toolbarEditButton; | ||||||
if ( url ) { | ||||||
if ( isExternal ) { | ||||||
toolbarEditButton = ( | ||||||
<Toolbar> | ||||||
<IconButton | ||||||
className="components-icon-button components-toolbar__control" | ||||||
label={ __( 'Edit image' ) } | ||||||
onClick={ this.toggleIsEditing } | ||||||
icon="edit" | ||||||
/> | ||||||
</Toolbar> | ||||||
); | ||||||
} else { | ||||||
toolbarEditButton = ( | ||||||
<Toolbar> | ||||||
<MediaUpload | ||||||
onSelect={ this.onSelectImage } | ||||||
|
@@ -239,11 +299,22 @@ class ImageEdit extends Component { | |||||
) } | ||||||
/> | ||||||
</Toolbar> | ||||||
) } | ||||||
); | ||||||
} | ||||||
} | ||||||
|
||||||
const controls = ( | ||||||
<BlockControls> | ||||||
<BlockAlignmentToolbar | ||||||
value={ align } | ||||||
onChange={ this.updateAlignment } | ||||||
/> | ||||||
{ toolbarEditButton } | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The name
Suggested change
|
||||||
</BlockControls> | ||||||
); | ||||||
|
||||||
if ( ! url ) { | ||||||
if ( isEditing ) { | ||||||
const src = isExternal ? url : undefined; | ||||||
return ( | ||||||
<Fragment> | ||||||
{ controls } | ||||||
|
@@ -255,17 +326,19 @@ class ImageEdit extends Component { | |||||
} } | ||||||
className={ className } | ||||||
onSelect={ this.onSelectImage } | ||||||
onSelectURL={ this.onSelectURL } | ||||||
notices={ noticeUI } | ||||||
onError={ noticeOperations.createErrorNotice } | ||||||
accept="image/*" | ||||||
allowedTypes={ ALLOWED_MEDIA_TYPES } | ||||||
value={ { id, src } } | ||||||
/> | ||||||
</Fragment> | ||||||
); | ||||||
} | ||||||
|
||||||
const classes = classnames( className, { | ||||||
'is-transient': 0 === url.indexOf( 'blob:' ), | ||||||
'is-transient': isBlobURL( url ), | ||||||
'is-resized': !! width || !! height, | ||||||
'is-focused': isSelected, | ||||||
} ); | ||||||
|
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.
We should update changelog for
@wordpress/blob
package and list this function as a new feature, which will require minor version increment.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 https://github.com/WordPress/gutenberg/blob/master/CONTRIBUTING.md#maintaining-changelogs.
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.
Does this look correct - aaeee84?