1 |
|
2 |
|
3 |
|
4 | import classnames from 'classnames';
|
5 |
|
6 |
|
7 |
|
8 |
|
9 | import { createBlobURL } from '@wordpress/blob';
|
10 | import {
|
11 | createBlock,
|
12 | getBlockAttributes,
|
13 | getPhrasingContentSchema,
|
14 | } from '@wordpress/blocks';
|
15 | import { RichText } from '@wordpress/block-editor';
|
16 | import { Fragment } from '@wordpress/element';
|
17 | import { __ } from '@wordpress/i18n';
|
18 |
|
19 |
|
20 |
|
21 |
|
22 | import edit from './edit';
|
23 | import icon from './icon';
|
24 |
|
25 | export const name = 'core/image';
|
26 |
|
27 | const 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 |
|
88 | const imageSchema = {
|
89 | img: {
|
90 | attributes: [ 'src', 'alt' ],
|
91 | classes: [ 'alignleft', 'aligncenter', 'alignright', 'alignnone', /^wp-image-\d+$/ ],
|
92 | },
|
93 | };
|
94 |
|
95 | const 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 |
|
111 | function 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 |
|
126 | export 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 |
|
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 |
|
145 | export 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',
|
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 |
|
169 |
|
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 |
|
192 |
|
193 |
|
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 | };
|