block_editor 0.1.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -14
  3. data/app/assets/images/block_editor/contact-form-block.svg +1 -0
  4. data/app/assets/stylesheets/block_editor/_utilities.scss +16 -0
  5. data/app/assets/stylesheets/block_editor/backend.scss +70 -2
  6. data/app/assets/stylesheets/block_editor/blocks/_backend.scss +7 -0
  7. data/app/assets/stylesheets/block_editor/blocks/_frontend.scss +11 -0
  8. data/app/assets/stylesheets/block_editor/blocks/be-accordion/_frontend.scss +3 -0
  9. data/app/assets/stylesheets/block_editor/blocks/be-alert/_frontend.scss +13 -0
  10. data/app/assets/stylesheets/block_editor/blocks/be-card/_backend.scss +7 -0
  11. data/app/assets/stylesheets/block_editor/{backend/blocks.scss → blocks/be-card/_frontend.scss} +0 -0
  12. data/app/assets/stylesheets/block_editor/blocks/be-contact-form/_backend.scss +22 -0
  13. data/app/assets/stylesheets/block_editor/blocks/be-cover/_backend.scss +8 -0
  14. data/app/assets/stylesheets/block_editor/blocks/be-cover/_frontend.scss +37 -0
  15. data/app/assets/stylesheets/block_editor/blocks/block/_backend.scss +11 -0
  16. data/app/assets/stylesheets/block_editor/blocks/button/_frontend.scss +46 -0
  17. data/app/assets/stylesheets/block_editor/blocks/buttons/_backend.scss +10 -0
  18. data/app/assets/stylesheets/block_editor/blocks/buttons/_frontend.scss +3 -0
  19. data/app/assets/stylesheets/block_editor/blocks/column/_frontend.scss +18 -0
  20. data/app/assets/stylesheets/block_editor/blocks/columns/_backend.scss +8 -0
  21. data/app/assets/stylesheets/block_editor/blocks/columns/_frontend.scss +14 -0
  22. data/app/assets/stylesheets/block_editor/blocks/image/_backend.scss +10 -0
  23. data/app/assets/stylesheets/block_editor/blocks/image/_frontend.scss +21 -0
  24. data/app/assets/stylesheets/block_editor/blocks/seperator/_frontend.scss +19 -0
  25. data/app/assets/stylesheets/block_editor/blocks/table/_frontend.scss +67 -0
  26. data/app/assets/stylesheets/block_editor/host_app/_variables.scss +1 -0
  27. data/app/assets/stylesheets/block_editor/host_app/blocks/_backend.scss +1 -0
  28. data/app/assets/stylesheets/block_editor/host_app/blocks/_frontend.scss +1 -0
  29. data/app/javascript/block_editor/blocks/be-accordion/index.js +108 -0
  30. data/app/javascript/block_editor/blocks/be-alert/index.js +51 -0
  31. data/app/javascript/block_editor/blocks/be-card/index.js +205 -0
  32. data/app/javascript/block_editor/blocks/be-contact-form/index.js +24 -0
  33. data/app/javascript/block_editor/blocks/be-cover/index.js +135 -0
  34. data/app/javascript/block_editor/blocks/block/edit-panel/index.js +132 -0
  35. data/app/javascript/block_editor/blocks/block/edit.js +163 -0
  36. data/app/javascript/block_editor/blocks/button/edit.js +0 -13
  37. data/app/javascript/block_editor/blocks/index.js +51 -102
  38. data/app/javascript/block_editor/components/block-editor/index.js +107 -36
  39. data/app/javascript/block_editor/components/block-editor/popover-wrapper.js +60 -0
  40. data/app/javascript/block_editor/components/block-editor/styles.scss +0 -11
  41. data/app/javascript/block_editor/components/header/index.js +28 -6
  42. data/app/javascript/block_editor/components/header/redo.js +1 -1
  43. data/app/javascript/block_editor/components/header/styles.scss +12 -11
  44. data/app/javascript/block_editor/components/media-upload/index.js +3 -3
  45. data/app/javascript/block_editor/components/sidebar/index.js +1 -3
  46. data/app/javascript/block_editor/components/sidebar/styles.scss +35 -35
  47. data/app/javascript/block_editor/stores/actions.js +12 -0
  48. data/app/javascript/block_editor/stores/reducer.js +23 -3
  49. data/app/javascript/block_editor/stores/selectors.js +14 -3
  50. data/app/javascript/controllers/block_editor_controller.jsx +15 -8
  51. data/app/javascript/controllers/index.js +2 -0
  52. data/app/javascript/packs/block_editor/application.scss +70 -26
  53. data/app/models/block_editor/block_list.rb +45 -1
  54. data/app/models/block_editor/block_list_connection.rb +6 -0
  55. data/app/views/block_editor/blocks/be/contact-form/_block.html +3 -0
  56. data/db/migrate/20210506220328_create_block_list_connections.rb +8 -0
  57. data/lib/block_editor.rb +3 -1
  58. data/lib/block_editor/block_list_renderer.rb +12 -6
  59. data/lib/block_editor/blocks/base.rb +1 -1
  60. data/lib/block_editor/blocks/contact_form.rb +11 -0
  61. data/lib/block_editor/blocks/reusable.rb +16 -0
  62. data/lib/block_editor/version.rb +1 -1
  63. data/package.json +16 -7
  64. data/yarn.lock +727 -530
  65. metadata +42 -8
  66. data/app/assets/stylesheets/block_editor/frontend.scss +0 -1
  67. data/app/assets/stylesheets/block_editor/frontend/blocks.scss +0 -0
  68. data/app/javascript/block_editor/blocks/image/edit.js +0 -656
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: block_editor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrick Lindsay
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-17 00:00:00.000000000 Z
11
+ date: 2021-06-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -38,9 +38,9 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '5.1'
41
- description: WYSIWYG editor but much better..
41
+ description: A block editor for Ruby on Rails built from the Wordpress Gutenberg project
42
42
  email:
43
- - ptrick@yamasolutions.com
43
+ - patrick@yamasolutions.com
44
44
  executables: []
45
45
  extensions: []
46
46
  extra_rdoc_files: []
@@ -49,17 +49,46 @@ files:
49
49
  - README.md
50
50
  - Rakefile
51
51
  - app/assets/config/block_editor_manifest.js
52
+ - app/assets/images/block_editor/contact-form-block.svg
53
+ - app/assets/stylesheets/block_editor/_utilities.scss
52
54
  - app/assets/stylesheets/block_editor/backend.scss
53
- - app/assets/stylesheets/block_editor/backend/blocks.scss
54
- - app/assets/stylesheets/block_editor/frontend.scss
55
- - app/assets/stylesheets/block_editor/frontend/blocks.scss
55
+ - app/assets/stylesheets/block_editor/blocks/_backend.scss
56
+ - app/assets/stylesheets/block_editor/blocks/_frontend.scss
57
+ - app/assets/stylesheets/block_editor/blocks/be-accordion/_frontend.scss
58
+ - app/assets/stylesheets/block_editor/blocks/be-alert/_frontend.scss
59
+ - app/assets/stylesheets/block_editor/blocks/be-card/_backend.scss
60
+ - app/assets/stylesheets/block_editor/blocks/be-card/_frontend.scss
61
+ - app/assets/stylesheets/block_editor/blocks/be-contact-form/_backend.scss
62
+ - app/assets/stylesheets/block_editor/blocks/be-cover/_backend.scss
63
+ - app/assets/stylesheets/block_editor/blocks/be-cover/_frontend.scss
64
+ - app/assets/stylesheets/block_editor/blocks/block/_backend.scss
65
+ - app/assets/stylesheets/block_editor/blocks/button/_frontend.scss
66
+ - app/assets/stylesheets/block_editor/blocks/buttons/_backend.scss
67
+ - app/assets/stylesheets/block_editor/blocks/buttons/_frontend.scss
68
+ - app/assets/stylesheets/block_editor/blocks/column/_frontend.scss
69
+ - app/assets/stylesheets/block_editor/blocks/columns/_backend.scss
70
+ - app/assets/stylesheets/block_editor/blocks/columns/_frontend.scss
71
+ - app/assets/stylesheets/block_editor/blocks/image/_backend.scss
72
+ - app/assets/stylesheets/block_editor/blocks/image/_frontend.scss
73
+ - app/assets/stylesheets/block_editor/blocks/seperator/_frontend.scss
74
+ - app/assets/stylesheets/block_editor/blocks/table/_frontend.scss
75
+ - app/assets/stylesheets/block_editor/host_app/_variables.scss
76
+ - app/assets/stylesheets/block_editor/host_app/blocks/_backend.scss
77
+ - app/assets/stylesheets/block_editor/host_app/blocks/_frontend.scss
56
78
  - app/controllers/block_editor/application_controller.rb
57
79
  - app/helpers/block_editor/application_helper.rb
80
+ - app/javascript/block_editor/blocks/be-accordion/index.js
81
+ - app/javascript/block_editor/blocks/be-alert/index.js
82
+ - app/javascript/block_editor/blocks/be-card/index.js
83
+ - app/javascript/block_editor/blocks/be-contact-form/index.js
84
+ - app/javascript/block_editor/blocks/be-cover/index.js
85
+ - app/javascript/block_editor/blocks/block/edit-panel/index.js
86
+ - app/javascript/block_editor/blocks/block/edit.js
58
87
  - app/javascript/block_editor/blocks/button/edit.js
59
88
  - app/javascript/block_editor/blocks/column/edit.js
60
- - app/javascript/block_editor/blocks/image/edit.js
61
89
  - app/javascript/block_editor/blocks/index.js
62
90
  - app/javascript/block_editor/components/block-editor/index.js
91
+ - app/javascript/block_editor/components/block-editor/popover-wrapper.js
63
92
  - app/javascript/block_editor/components/block-editor/styles.scss
64
93
  - app/javascript/block_editor/components/header/index.js
65
94
  - app/javascript/block_editor/components/header/redo.js
@@ -85,7 +114,9 @@ files:
85
114
  - app/mailers/block_editor/application_mailer.rb
86
115
  - app/models/block_editor/application_record.rb
87
116
  - app/models/block_editor/block_list.rb
117
+ - app/models/block_editor/block_list_connection.rb
88
118
  - app/models/concerns/block_editor/listable.rb
119
+ - app/views/block_editor/blocks/be/contact-form/_block.html
89
120
  - app/views/layouts/block_editor/application.html.erb
90
121
  - bin/rails
91
122
  - bin/webpack
@@ -98,9 +129,12 @@ files:
98
129
  - config/webpack/test.js
99
130
  - config/webpacker.yml
100
131
  - db/migrate/20210312032114_create_block_lists.rb
132
+ - db/migrate/20210506220328_create_block_list_connections.rb
101
133
  - lib/block_editor.rb
102
134
  - lib/block_editor/block_list_renderer.rb
103
135
  - lib/block_editor/blocks/base.rb
136
+ - lib/block_editor/blocks/contact_form.rb
137
+ - lib/block_editor/blocks/reusable.rb
104
138
  - lib/block_editor/engine.rb
105
139
  - lib/block_editor/instance.rb
106
140
  - lib/block_editor/version.rb
@@ -1 +0,0 @@
1
- @import "block_editor/frontend/blocks";
@@ -1,656 +0,0 @@
1
- /**
2
- * External dependencies
3
- */
4
- import classnames from 'classnames';
5
- import React from 'react';
6
- import ReactDOM from 'react-dom';
7
- import { get, filter, map, last, omit, pick, includes } from 'lodash';
8
-
9
- /**
10
- * WordPress dependencies
11
- */
12
- import { getBlobByURL, isBlobURL, revokeBlobURL } from '@wordpress/blob';
13
- import {
14
- ExternalLink,
15
- PanelBody,
16
- ResizableBox,
17
- Spinner,
18
- TextareaControl,
19
- TextControl,
20
- ToolbarGroup,
21
- withNotices,
22
- } from '@wordpress/components';
23
- import { useViewportMatch } from '@wordpress/compose';
24
- import { useSelect, useDispatch } from '@wordpress/data';
25
- import {
26
- BlockAlignmentToolbar,
27
- BlockControls,
28
- BlockIcon,
29
- InspectorControls,
30
- InspectorAdvancedControls,
31
- MediaPlaceholder,
32
- MediaReplaceFlow,
33
- RichText,
34
- __experimentalBlock as Block,
35
- __experimentalImageSizeControl as ImageSizeControl,
36
- __experimentalImageURLInputUI as ImageURLInputUI,
37
- } from '@wordpress/block-editor';
38
- import { useEffect, useState, useRef } from '@wordpress/element';
39
- import { __, sprintf } from '@wordpress/i18n';
40
- import { getPath } from '@wordpress/url';
41
- import { image as icon } from '@wordpress/icons';
42
- import { createBlock } from '@wordpress/blocks';
43
-
44
- /**
45
- * Internal dependencies
46
- */
47
- import { calculatePreferedImageSize } from '@wordpress/block-library/src/image/utils';
48
-
49
- /**
50
- * Module constants
51
- */
52
- import {
53
- MIN_SIZE,
54
- LINK_DESTINATION_MEDIA,
55
- LINK_DESTINATION_ATTACHMENT,
56
- ALLOWED_MEDIA_TYPES,
57
- DEFAULT_SIZE_SLUG,
58
- } from '@wordpress/block-library/src/image/constants';
59
-
60
- export const pickRelevantMediaFiles = ( image ) => {
61
- const imageProps = pick( image, [ 'alt', 'id', 'link', 'caption' ] );
62
- imageProps.url =
63
- get( image, [ 'sizes', 'large', 'url' ] ) ||
64
- get( image, [ 'media_details', 'sizes', 'large', 'source_url' ] ) ||
65
- image.url;
66
- return imageProps;
67
- };
68
-
69
-
70
- const useImageSize = ( ref, src, dependencies ) => {
71
- const [ state, setState ] = useState( {
72
- imageWidth: null,
73
- imageHeight: null,
74
- imageWidthWithinContainer: null,
75
- imageHeightWithinContainer: null,
76
- } );
77
-
78
- useEffect( () => {
79
- if ( ! src ) {
80
- return;
81
- }
82
-
83
- const { defaultView } = ref.current.ownerDocument;
84
- const image = new defaultView.Image();
85
-
86
- function calculateSize() {
87
- const { width, height } = calculatePreferedImageSize(
88
- image,
89
- ref.current
90
- );
91
-
92
- setState( {
93
- imageWidth: image.width,
94
- imageHeight: image.height,
95
- imageWidthWithinContainer: width,
96
- imageHeightWithinContainer: height,
97
- } );
98
- }
99
-
100
- defaultView.addEventListener( 'resize', calculateSize );
101
- image.addEventListener( 'load', calculateSize );
102
- image.src = src;
103
-
104
- return () => {
105
- defaultView.removeEventListener( 'resize', calculateSize );
106
- image.removeEventListener( 'load', calculateSize );
107
- };
108
- }, [ src, ...dependencies ] );
109
-
110
- return state;
111
- }
112
-
113
- /**
114
- * Is the URL a temporary blob URL? A blob URL is one that is used temporarily
115
- * while the image is being uploaded and will not have an id yet allocated.
116
- *
117
- * @param {number=} id The id of the image.
118
- * @param {string=} url The url of the image.
119
- *
120
- * @return {boolean} Is the URL a Blob URL
121
- */
122
- const isTemporaryImage = ( id, url ) => ! id && isBlobURL( url );
123
-
124
- /**
125
- * Is the url for the image hosted externally. An externally hosted image has no
126
- * id and is not a blob url.
127
- *
128
- * @param {number=} id The id of the image.
129
- * @param {string=} url The url of the image.
130
- *
131
- * @return {boolean} Is the url an externally hosted url?
132
- */
133
- const isExternalImage = ( id, url ) => url && ! id && ! isBlobURL( url );
134
-
135
- function getFilename( url ) {
136
- const path = getPath( url );
137
- if ( path ) {
138
- return last( path.split( '/' ) );
139
- }
140
- }
141
-
142
- export function ImageEdit( {
143
- attributes: {
144
- url = '',
145
- alt,
146
- caption,
147
- align,
148
- id,
149
- href,
150
- rel,
151
- linkClass,
152
- linkDestination,
153
- title,
154
- width,
155
- height,
156
- linkTarget,
157
- sizeSlug,
158
- },
159
- setAttributes,
160
- isSelected,
161
- className,
162
- noticeUI,
163
- insertBlocksAfter,
164
- noticeOperations,
165
- onReplace,
166
- } ) {
167
- const ref = useRef();
168
- const { image, maxWidth, isRTL, imageSizes, mediaUpload } = useSelect(
169
- ( select ) => {
170
- const { getMedia } = select( 'core' );
171
- const { getSettings } = select( 'core/block-editor' );
172
- return {
173
- ...pick( getSettings(), [
174
- 'mediaUpload',
175
- 'imageSizes',
176
- 'isRTL',
177
- 'maxWidth',
178
- ] ),
179
- image: id && isSelected ? getMedia( id ) : null,
180
- };
181
- },
182
- [ id, isSelected ]
183
- );
184
- const { toggleSelection } = useDispatch( 'core/block-editor' );
185
- const isLargeViewport = useViewportMatch( 'medium' );
186
- const [ captionFocused, setCaptionFocused ] = useState( false );
187
- const isWideAligned = includes( [ 'wide', 'full' ], align );
188
-
189
- function onResizeStart() {
190
- toggleSelection( false );
191
- }
192
-
193
- function onResizeStop() {
194
- toggleSelection( true );
195
- }
196
-
197
- function onUploadError( message ) {
198
- noticeOperations.removeAllNotices();
199
- noticeOperations.createErrorNotice( message );
200
- }
201
-
202
- function onSelectImage( media ) {
203
- if ( ! media || ! media.url ) {
204
- setAttributes( {
205
- url: undefined,
206
- alt: undefined,
207
- id: undefined,
208
- title: undefined,
209
- caption: undefined,
210
- } );
211
- return;
212
- }
213
-
214
- let mediaAttributes = pickRelevantMediaFiles( media );
215
-
216
- // If the current image is temporary but an alt text was meanwhile
217
- // written by the user, make sure the text is not overwritten.
218
- if ( isTemporaryImage( id, url ) ) {
219
- if ( alt ) {
220
- mediaAttributes = omit( mediaAttributes, [ 'alt' ] );
221
- }
222
- }
223
-
224
- // If a caption text was meanwhile written by the user,
225
- // make sure the text is not overwritten by empty captions.
226
- if ( caption && ! get( mediaAttributes, [ 'caption' ] ) ) {
227
- mediaAttributes = omit( mediaAttributes, [ 'caption' ] );
228
- }
229
-
230
- let additionalAttributes;
231
- // Reset the dimension attributes if changing to a different image.
232
- if ( ! media.id || media.id !== id ) {
233
- additionalAttributes = {
234
- width: undefined,
235
- height: undefined,
236
- sizeSlug: DEFAULT_SIZE_SLUG,
237
- };
238
- } else {
239
- // Keep the same url when selecting the same file, so "Image Size"
240
- // option is not changed.
241
- additionalAttributes = { url };
242
- }
243
-
244
- // Check if the image is linked to it's media.
245
- if ( linkDestination === LINK_DESTINATION_MEDIA ) {
246
- // Update the media link.
247
- mediaAttributes.href = media.url;
248
- }
249
-
250
- // Check if the image is linked to the attachment page.
251
- if ( linkDestination === LINK_DESTINATION_ATTACHMENT ) {
252
- // Update the media link.
253
- mediaAttributes.href = media.link;
254
- }
255
-
256
- setAttributes( {
257
- ...mediaAttributes,
258
- ...additionalAttributes,
259
- } );
260
- }
261
-
262
- function onSelectURL( newURL ) {
263
- if ( newURL !== url ) {
264
- setAttributes( {
265
- url: newURL,
266
- id: undefined,
267
- sizeSlug: DEFAULT_SIZE_SLUG,
268
- } );
269
- }
270
- }
271
-
272
- function onImageError() {
273
- console.log('Block Editor - ImageBlock: An image error occurred.')
274
- // // Check if there's an embed block that handles this URL.
275
- // const embedBlock = createUpgradedEmbedBlock( { attributes: { url } } );
276
- // if ( undefined !== embedBlock ) {
277
- // onReplace( embedBlock );
278
- // }
279
- }
280
-
281
- function onSetHref( props ) {
282
- setAttributes( props );
283
- }
284
-
285
- function onSetTitle( value ) {
286
- // This is the HTML title attribute, separate from the media object
287
- // title.
288
- setAttributes( { title: value } );
289
- }
290
-
291
- function onFocusCaption() {
292
- if ( ! captionFocused ) {
293
- setCaptionFocused( true );
294
- }
295
- }
296
-
297
- function onImageClick() {
298
- if ( captionFocused ) {
299
- setCaptionFocused( false );
300
- }
301
- }
302
-
303
- function updateAlt( newAlt ) {
304
- setAttributes( { alt: newAlt } );
305
- }
306
-
307
- function updateAlignment( nextAlign ) {
308
- const extraUpdatedAttributes = isWideAligned
309
- ? { width: undefined, height: undefined }
310
- : {};
311
- setAttributes( {
312
- ...extraUpdatedAttributes,
313
- align: nextAlign,
314
- } );
315
- }
316
-
317
- function updateImage( newSizeSlug ) {
318
- const newUrl = get( image, [
319
- 'media_details',
320
- 'sizes',
321
- newSizeSlug,
322
- 'source_url',
323
- ] );
324
- if ( ! newUrl ) {
325
- return null;
326
- }
327
-
328
- setAttributes( {
329
- url,
330
- width: undefined,
331
- height: undefined,
332
- sizeSlug: newSizeSlug,
333
- } );
334
- }
335
-
336
- function getImageSizeOptions() {
337
- return map(
338
- filter( imageSizes, ( { slug } ) =>
339
- get( image, [ 'media_details', 'sizes', slug, 'source_url' ] )
340
- ),
341
- ( { name, slug } ) => ( { value: slug, label: name } )
342
- );
343
- }
344
-
345
- const isTemp = isTemporaryImage( id, url );
346
-
347
- // Upload a temporary image on mount.
348
- useEffect( () => {
349
- if ( ! isTemp ) {
350
- return;
351
- }
352
-
353
- const file = getBlobByURL( url );
354
-
355
- if ( file ) {
356
- mediaUpload( {
357
- filesList: [ file ],
358
- onFileChange: ( [ img ] ) => {
359
- onSelectImage( img );
360
- },
361
- allowedTypes: ALLOWED_MEDIA_TYPES,
362
- onError: ( message ) => {
363
- noticeOperations.createErrorNotice( message );
364
- },
365
- } );
366
- }
367
- }, [] );
368
-
369
- // If an image is temporary, revoke the Blob url when it is uploaded (and is
370
- // no longer temporary).
371
- useEffect( () => {
372
- if ( ! isTemp ) {
373
- return;
374
- }
375
-
376
- return () => {
377
- revokeBlobURL( url );
378
- };
379
- }, [ isTemp ] );
380
-
381
- useEffect( () => {
382
- if ( ! isSelected ) {
383
- setCaptionFocused( false );
384
- }
385
- }, [ isSelected ] );
386
-
387
- const isExternal = isExternalImage( id, url );
388
- const controls = (
389
- <BlockControls>
390
- <BlockAlignmentToolbar
391
- value={ align }
392
- onChange={ updateAlignment }
393
- />
394
- { url && (
395
- <MediaReplaceFlow
396
- mediaId={ id }
397
- mediaURL={ url }
398
- allowedTypes={ ALLOWED_MEDIA_TYPES }
399
- accept="image/*"
400
- onSelect={ onSelectImage }
401
- onSelectURL={ onSelectURL }
402
- onError={ onUploadError }
403
- />
404
- ) }
405
- { url && (
406
- <ToolbarGroup>
407
- <ImageURLInputUI
408
- url={ href || '' }
409
- onChangeUrl={ onSetHref }
410
- linkDestination={ linkDestination }
411
- mediaUrl={ image && image.source_url }
412
- mediaLink={ image && image.link }
413
- linkTarget={ linkTarget }
414
- linkClass={ linkClass }
415
- rel={ rel }
416
- />
417
- </ToolbarGroup>
418
- ) }
419
- </BlockControls>
420
- );
421
- const src = isExternal ? url : undefined;
422
- const mediaPreview = !! url && (
423
- <img
424
- alt={ __( 'Edit image' ) }
425
- title={ __( 'Edit image' ) }
426
- className={ 'edit-image-preview' }
427
- src={ url }
428
- />
429
- );
430
-
431
- const mediaPlaceholder = (
432
- <MediaPlaceholder
433
- icon={ <BlockIcon icon={ icon } /> }
434
- onSelect={ onSelectImage }
435
- onSelectURL={ onSelectURL }
436
- notices={ noticeUI }
437
- onError={ onUploadError }
438
- accept="image/*"
439
- allowedTypes={ ALLOWED_MEDIA_TYPES }
440
- value={ { id, src } }
441
- mediaPreview={ mediaPreview }
442
- disableMediaButtons={ url }
443
- />
444
- );
445
-
446
- const {
447
- imageWidthWithinContainer,
448
- imageHeightWithinContainer,
449
- imageWidth,
450
- imageHeight,
451
- } = useImageSize( ref, url, [ align ] );
452
-
453
- if ( ! url ) {
454
- return (
455
- <>
456
- { controls }
457
- <Block.div>{ mediaPlaceholder }</Block.div>
458
- </>
459
- );
460
- }
461
-
462
- const classes = classnames( className, {
463
- 'is-transient': isBlobURL( url ),
464
- 'is-resized': !! width || !! height,
465
- 'is-focused': isSelected,
466
- [ `size-${ sizeSlug }` ]: sizeSlug,
467
- } );
468
-
469
- const isResizable = false;
470
- const imageSizeOptions = getImageSizeOptions();
471
-
472
- const inspectorControls = (
473
- <>
474
- <InspectorControls>
475
- <PanelBody title={ __( 'Image settings' ) }>
476
- <TextareaControl
477
- label={ __( 'Alt text (alternative text)' ) }
478
- value={ alt }
479
- onChange={ updateAlt }
480
- help={
481
- <>
482
- <ExternalLink href="https://www.w3.org/WAI/tutorials/images/decision-tree">
483
- { __(
484
- 'Describe the purpose of the image'
485
- ) }
486
- </ExternalLink>
487
- { __(
488
- 'Leave empty if the image is purely decorative.'
489
- ) }
490
- </>
491
- }
492
- />
493
- </PanelBody>
494
- </InspectorControls>
495
- <InspectorAdvancedControls>
496
- <TextControl
497
- label={ __( 'Title attribute' ) }
498
- value={ title || '' }
499
- onChange={ onSetTitle }
500
- help={
501
- <>
502
- { __(
503
- 'Describe the role of this image on the page.'
504
- ) }
505
- <ExternalLink href="https://www.w3.org/TR/html52/dom.html#the-title-attribute">
506
- { __(
507
- '(Note: many devices and browsers do not display this text.)'
508
- ) }
509
- </ExternalLink>
510
- </>
511
- }
512
- />
513
- </InspectorAdvancedControls>
514
- </>
515
- );
516
-
517
- const filename = getFilename( url );
518
- let defaultedAlt;
519
-
520
- if ( alt ) {
521
- defaultedAlt = alt;
522
- } else if ( filename ) {
523
- defaultedAlt = sprintf(
524
- /* translators: %s: file name */
525
- __( 'This image has an empty alt attribute; its file name is %s' ),
526
- filename
527
- );
528
- } else {
529
- defaultedAlt = __( 'This image has an empty alt attribute' );
530
- }
531
-
532
- let img = (
533
- // Disable reason: Image itself is not meant to be interactive, but
534
- // should direct focus to block.
535
- /* eslint-disable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events */
536
- <>
537
- { inspectorControls }
538
- <img
539
- src={ url }
540
- alt={ defaultedAlt }
541
- onClick={ onImageClick }
542
- onError={ () => onImageError() }
543
- />
544
- { isBlobURL( url ) && <Spinner /> }
545
- </>
546
- /* eslint-enable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/click-events-have-key-events */
547
- );
548
-
549
- if ( ! isResizable || ! imageWidthWithinContainer ) {
550
- img = <div style={ { width, height } }>{ img }</div>;
551
- } else {
552
- const currentWidth = width || imageWidthWithinContainer;
553
- const currentHeight = height || imageHeightWithinContainer;
554
-
555
- const ratio = imageWidth / imageHeight;
556
- const minWidth = imageWidth < imageHeight ? MIN_SIZE : MIN_SIZE * ratio;
557
- const minHeight =
558
- imageHeight < imageWidth ? MIN_SIZE : MIN_SIZE / ratio;
559
-
560
- // With the current implementation of ResizableBox, an image needs an
561
- // explicit pixel value for the max-width. In absence of being able to
562
- // set the content-width, this max-width is currently dictated by the
563
- // vanilla editor style. The following variable adds a buffer to this
564
- // vanilla style, so 3rd party themes have some wiggleroom. This does,
565
- // in most cases, allow you to scale the image beyond the width of the
566
- // main column, though not infinitely.
567
- // @todo It would be good to revisit this once a content-width variable
568
- // becomes available.
569
- const maxWidthBuffer = maxWidth * 2.5;
570
-
571
- let showRightHandle = false;
572
- let showLeftHandle = false;
573
-
574
- /* eslint-disable no-lonely-if */
575
- // See https://github.com/WordPress/gutenberg/issues/7584.
576
- if ( align === 'center' ) {
577
- // When the image is centered, show both handles.
578
- showRightHandle = true;
579
- showLeftHandle = true;
580
- } else if ( isRTL ) {
581
- // In RTL mode the image is on the right by default.
582
- // Show the right handle and hide the left handle only when it is
583
- // aligned left. Otherwise always show the left handle.
584
- if ( align === 'left' ) {
585
- showRightHandle = true;
586
- } else {
587
- showLeftHandle = true;
588
- }
589
- } else {
590
- // Show the left handle and hide the right handle only when the
591
- // image is aligned right. Otherwise always show the right handle.
592
- if ( align === 'right' ) {
593
- showLeftHandle = true;
594
- } else {
595
- showRightHandle = true;
596
- }
597
- }
598
- /* eslint-enable no-lonely-if */
599
-
600
- img = (
601
- <ResizableBox
602
- size={ { width, height } }
603
- showHandle={ isSelected }
604
- minWidth={ minWidth }
605
- maxWidth={ maxWidthBuffer }
606
- minHeight={ minHeight }
607
- maxHeight={ maxWidthBuffer / ratio }
608
- lockAspectRatio
609
- enable={ {
610
- top: false,
611
- right: showRightHandle,
612
- bottom: true,
613
- left: showLeftHandle,
614
- } }
615
- onResizeStart={ onResizeStart }
616
- onResizeStop={ ( event, direction, elt, delta ) => {
617
- onResizeStop();
618
- setAttributes( {
619
- width: parseInt( currentWidth + delta.width, 10 ),
620
- height: parseInt( currentHeight + delta.height, 10 ),
621
- } );
622
- } }
623
- >
624
- { img }
625
- </ResizableBox>
626
- );
627
- }
628
-
629
- return (
630
- <>
631
- { controls }
632
- <Block.figure ref={ ref } className={ classes }>
633
- { img }
634
- { ( ! RichText.isEmpty( caption ) || isSelected ) && (
635
- <RichText
636
- tagName="figcaption"
637
- placeholder={ __( 'Write caption…' ) }
638
- value={ caption }
639
- unstableOnFocus={ onFocusCaption }
640
- onChange={ ( value ) =>
641
- setAttributes( { caption: value } )
642
- }
643
- isSelected={ captionFocused }
644
- inlineToolbar
645
- __unstableOnSplitAtEnd={ () =>
646
- insertBlocksAfter( createBlock( 'core/paragraph' ) )
647
- }
648
- />
649
- ) }
650
- { mediaPlaceholder }
651
- </Block.figure>
652
- </>
653
- );
654
- }
655
-
656
- export default withNotices( ImageEdit );