UNPKG

9.29 kBJavaScriptView Raw
1/**
2 * External dependencies
3 */
4import classnames from 'classnames';
5
6/**
7 * WordPress dependencies
8 */
9import { createBlobURL } from '@wordpress/blob';
10import {
11 createBlock,
12 getBlockAttributes,
13 getPhrasingContentSchema,
14} from '@wordpress/blocks';
15import { RichText } from '@wordpress/block-editor';
16import { Fragment } from '@wordpress/element';
17import { __ } from '@wordpress/i18n';
18
19/**
20 * Internal dependencies
21 */
22import edit from './edit';
23import icon from './icon';
24
25export const name = 'core/image';
26
27const blockAttributes = {
28 url: {
29 type: 'string',
30 source: 'attribute',
31 selector: 'img',
32 attribute: 'src',
33 },
34 alt: {
35 type: 'string',
36 source: 'attribute',
37 selector: 'img',
38 attribute: 'alt',
39 default: '',
40 },
41 caption: {
42 type: 'string',
43 source: 'html',
44 selector: 'figcaption',
45 },
46 href: {
47 type: 'string',
48 source: 'attribute',
49 selector: 'figure > a',
50 attribute: 'href',
51 },
52 rel: {
53 type: 'string',
54 source: 'attribute',
55 selector: 'figure > a',
56 attribute: 'rel',
57 },
58 linkClass: {
59 type: 'string',
60 source: 'attribute',
61 selector: 'figure > a',
62 attribute: 'class',
63 },
64 id: {
65 type: 'number',
66 },
67 align: {
68 type: 'string',
69 },
70 width: {
71 type: 'number',
72 },
73 height: {
74 type: 'number',
75 },
76 linkDestination: {
77 type: 'string',
78 default: 'none',
79 },
80 linkTarget: {
81 type: 'string',
82 source: 'attribute',
83 selector: 'figure > a',
84 attribute: 'target',
85 },
86};
87
88const imageSchema = {
89 img: {
90 attributes: [ 'src', 'alt' ],
91 classes: [ 'alignleft', 'aligncenter', 'alignright', 'alignnone', /^wp-image-\d+$/ ],
92 },
93};
94
95const schema = {
96 figure: {
97 require: [ 'img' ],
98 children: {
99 ...imageSchema,
100 a: {
101 attributes: [ 'href', 'rel', 'target' ],
102 children: imageSchema,
103 },
104 figcaption: {
105 children: getPhrasingContentSchema(),
106 },
107 },
108 },
109};
110
111function getFirstAnchorAttributeFormHTML( html, attributeName ) {
112 const { body } = document.implementation.createHTMLDocument( '' );
113
114 body.innerHTML = html;
115
116 const { firstElementChild } = body;
117
118 if (
119 firstElementChild &&
120 firstElementChild.nodeName === 'A'
121 ) {
122 return firstElementChild.getAttribute( attributeName ) || undefined;
123 }
124}
125
126export function stripFirstImage( attributes, { shortcode } ) {
127 const { body } = document.implementation.createHTMLDocument( '' );
128
129 body.innerHTML = shortcode.content;
130
131 let nodeToRemove = body.querySelector( 'img' );
132
133 // if an image has parents, find the topmost node to remove
134 while ( nodeToRemove && nodeToRemove.parentNode && nodeToRemove.parentNode !== body ) {
135 nodeToRemove = nodeToRemove.parentNode;
136 }
137
138 if ( nodeToRemove ) {
139 nodeToRemove.parentNode.removeChild( nodeToRemove );
140 }
141
142 return body.innerHTML.trim();
143}
144
145export const settings = {
146 title: __( 'Image' ),
147
148 description: __( 'Insert an image to make a visual statement.' ),
149
150 icon,
151
152 category: 'common',
153
154 keywords: [
155 'img', // "img" is not translated as it is intended to reflect the HTML <img> tag.
156 __( 'photo' ),
157 ],
158
159 attributes: blockAttributes,
160
161 transforms: {
162 from: [
163 {
164 type: 'raw',
165 isMatch: ( node ) => node.nodeName === 'FIGURE' && !! node.querySelector( 'img' ),
166 schema,
167 transform: ( node ) => {
168 // Search both figure and image classes. Alignment could be
169 // set on either. ID is set on the image.
170 const className = node.className + ' ' + node.querySelector( 'img' ).className;
171 const alignMatches = /(?:^|\s)align(left|center|right)(?:$|\s)/.exec( className );
172 const align = alignMatches ? alignMatches[ 1 ] : undefined;
173 const idMatches = /(?:^|\s)wp-image-(\d+)(?:$|\s)/.exec( className );
174 const id = idMatches ? Number( idMatches[ 1 ] ) : undefined;
175 const anchorElement = node.querySelector( 'a' );
176 const linkDestination = anchorElement && anchorElement.href ? 'custom' : undefined;
177 const href = anchorElement && anchorElement.href ? anchorElement.href : undefined;
178 const rel = anchorElement && anchorElement.rel ? anchorElement.rel : undefined;
179 const linkClass = anchorElement && anchorElement.className ? anchorElement.className : undefined;
180 const attributes = getBlockAttributes( 'core/image', node.outerHTML, { align, id, linkDestination, href, rel, linkClass } );
181 return createBlock( 'core/image', attributes );
182 },
183 },
184 {
185 type: 'files',
186 isMatch( files ) {
187 return files.length === 1 && files[ 0 ].type.indexOf( 'image/' ) === 0;
188 },
189 transform( files ) {
190 const file = files[ 0 ];
191 // We don't need to upload the media directly here
192 // It's already done as part of the `componentDidMount`
193 // int the image block
194 const block = createBlock( 'core/image', {
195 url: createBlobURL( file ),
196 } );
197
198 return block;
199 },
200 },
201 {
202 type: 'shortcode',
203 tag: 'caption',
204 attributes: {
205 url: {
206 type: 'string',
207 source: 'attribute',
208 attribute: 'src',
209 selector: 'img',
210 },
211 alt: {
212 type: 'string',
213 source: 'attribute',
214 attribute: 'alt',
215 selector: 'img',
216 },
217 caption: {
218 shortcode: stripFirstImage,
219 },
220 href: {
221 shortcode: ( attributes, { shortcode } ) => {
222 return getFirstAnchorAttributeFormHTML( shortcode.content, 'href' );
223 },
224 },
225 rel: {
226 shortcode: ( attributes, { shortcode } ) => {
227 return getFirstAnchorAttributeFormHTML( shortcode.content, 'rel' );
228 },
229 },
230 linkClass: {
231 shortcode: ( attributes, { shortcode } ) => {
232 return getFirstAnchorAttributeFormHTML( shortcode.content, 'class' );
233 },
234 },
235 id: {
236 type: 'number',
237 shortcode: ( { named: { id } } ) => {
238 if ( ! id ) {
239 return;
240 }
241
242 return parseInt( id.replace( 'attachment_', '' ), 10 );
243 },
244 },
245 align: {
246 type: 'string',
247 shortcode: ( { named: { align = 'alignnone' } } ) => {
248 return align.replace( 'align', '' );
249 },
250 },
251 },
252 },
253 ],
254 },
255
256 getEditWrapperProps( attributes ) {
257 const { align, width } = attributes;
258 if ( 'left' === align || 'center' === align || 'right' === align || 'wide' === align || 'full' === align ) {
259 return { 'data-align': align, 'data-resized': !! width };
260 }
261 },
262
263 edit,
264
265 save( { attributes } ) {
266 const {
267 url,
268 alt,
269 caption,
270 align,
271 href,
272 rel,
273 linkClass,
274 width,
275 height,
276 id,
277 linkTarget,
278 } = attributes;
279
280 const classes = classnames( {
281 [ `align${ align }` ]: align,
282 'is-resized': width || height,
283 } );
284
285 const image = (
286 <img
287 src={ url }
288 alt={ alt }
289 className={ id ? `wp-image-${ id }` : null }
290 width={ width }
291 height={ height }
292 />
293 );
294
295 const figure = (
296 <Fragment>
297 { href ? (
298 <a
299 className={ linkClass }
300 href={ href }
301 target={ linkTarget }
302 rel={ rel }
303 >
304 { image }
305 </a>
306 ) : image }
307 { ! RichText.isEmpty( caption ) && <RichText.Content tagName="figcaption" value={ caption } /> }
308 </Fragment>
309 );
310
311 if ( 'left' === align || 'right' === align || 'center' === align ) {
312 return (
313 <div>
314 <figure className={ classes }>
315 { figure }
316 </figure>
317 </div>
318 );
319 }
320
321 return (
322 <figure className={ classes }>
323 { figure }
324 </figure>
325 );
326 },
327
328 deprecated: [
329 {
330 attributes: blockAttributes,
331 save( { attributes } ) {
332 const { url, alt, caption, align, href, width, height, id } = attributes;
333
334 const classes = classnames( {
335 [ `align${ align }` ]: align,
336 'is-resized': width || height,
337 } );
338
339 const image = (
340 <img
341 src={ url }
342 alt={ alt }
343 className={ id ? `wp-image-${ id }` : null }
344 width={ width }
345 height={ height }
346 />
347 );
348
349 return (
350 <figure className={ classes }>
351 { href ? <a href={ href }>{ image }</a> : image }
352 { ! RichText.isEmpty( caption ) && <RichText.Content tagName="figcaption" value={ caption } /> }
353 </figure>
354 );
355 },
356 },
357 {
358 attributes: blockAttributes,
359 save( { attributes } ) {
360 const { url, alt, caption, align, href, width, height, id } = attributes;
361
362 const image = (
363 <img
364 src={ url }
365 alt={ alt }
366 className={ id ? `wp-image-${ id }` : null }
367 width={ width }
368 height={ height }
369 />
370 );
371
372 return (
373 <figure className={ align ? `align${ align }` : null } >
374 { href ? <a href={ href }>{ image }</a> : image }
375 { ! RichText.isEmpty( caption ) && <RichText.Content tagName="figcaption" value={ caption } /> }
376 </figure>
377 );
378 },
379 },
380 {
381 attributes: blockAttributes,
382 save( { attributes } ) {
383 const { url, alt, caption, align, href, width, height } = attributes;
384 const extraImageProps = width || height ? { width, height } : {};
385 const image = <img src={ url } alt={ alt } { ...extraImageProps } />;
386
387 let figureStyle = {};
388
389 if ( width ) {
390 figureStyle = { width };
391 } else if ( align === 'left' || align === 'right' ) {
392 figureStyle = { maxWidth: '50%' };
393 }
394
395 return (
396 <figure className={ align ? `align${ align }` : null } style={ figureStyle }>
397 { href ? <a href={ href }>{ image }</a> : image }
398 { ! RichText.isEmpty( caption ) && <RichText.Content tagName="figcaption" value={ caption } /> }
399 </figure>
400 );
401 },
402 },
403 ],
404};