block_editor 0.1.3 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 );