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
@@ -0,0 +1,24 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom';
3
+
4
+ import { widget as icon } from '@wordpress/icons';
5
+
6
+ const name = 'be/contact-form';
7
+
8
+ export { name };
9
+
10
+ export const settings = {
11
+ title: 'Contact Form',
12
+ description: 'Allow users to get in touch using a contact form.',
13
+ icon,
14
+ category: 'widgets',
15
+ edit({attributes, className, setAttributes, isSelected}) {
16
+ return ([
17
+ <div className={ className }>
18
+ <p>Contact Form</p>
19
+ <div className='be-contact-form-outline'>
20
+ </div>
21
+ </div>
22
+ ]);
23
+ }
24
+ };
@@ -0,0 +1,135 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom';
3
+
4
+ import {
5
+ InnerBlocks
6
+ } from '@wordpress/block-editor';
7
+ import { registerBlockStyle } from '@wordpress/blocks';
8
+ import { createBlock } from '@wordpress/blocks';
9
+ import { rawHandler } from '@wordpress/blocks';
10
+
11
+ import { TextControl, PanelBody, ToggleControl, Button } from '@wordpress/components';
12
+ import { InspectorControls, RichText, MediaUpload, PlainText } from '@wordpress/block-editor'
13
+ import { cover as icon } from '@wordpress/icons';
14
+
15
+ const BLOCKS_TEMPLATE = [
16
+ [ 'core/heading', { content: 'Example Cover Title' } ],
17
+ [ 'core/paragraph', { content: 'Break up content and draw attention to something using a cover block' } ]
18
+ ];
19
+ const ALLOWED_BLOCKS = [ 'core/buttons', 'core/heading', 'core/paragraph', 'core/list' ];
20
+ const name = 'be/cover';
21
+
22
+ export { name };
23
+
24
+ export const settings = {
25
+ title: 'Cover',
26
+ description: 'Add an image with a text overlay - great for breaking up content.',
27
+ icon,
28
+ category: 'formatting',
29
+ example: {
30
+ innerBlocks: [
31
+ {
32
+ name: 'core/heading',
33
+ attributes: {
34
+ content: 'Example Cover Title'
35
+ }
36
+ },
37
+ {
38
+ name: 'core/paragraph',
39
+ attributes: {
40
+ content: 'Break up content and draw attention to something using a cover block'
41
+ }
42
+ },
43
+ {
44
+ name: 'core/button',
45
+ attributes: {
46
+ content: 'Example Call To Action'
47
+ }
48
+ }
49
+ ]
50
+ },
51
+ attributes: {
52
+ imageAlt: {
53
+ attribute: 'alt',
54
+ selector: '.wp-block-be-cover-image'
55
+ },
56
+ imageUrl: {
57
+ attribute: 'src',
58
+ selector: '.wp-block-be-cover-image'
59
+ }
60
+ },
61
+ edit({attributes, className, setAttributes, isSelected}) {
62
+ const getImageButton = (openEvent) => {
63
+ if(attributes.imageUrl) {
64
+ return (
65
+ <>
66
+ { isSelected &&
67
+ <Button
68
+ onClick={ openEvent }
69
+ className="button"
70
+ >
71
+ Edit image
72
+ </Button>
73
+ }
74
+ <img
75
+ src={ attributes.imageUrl }
76
+ className="wp-block-be-cover-image"
77
+ />
78
+ </>
79
+ );
80
+ }
81
+ else {
82
+ return (
83
+ <>
84
+ { isSelected &&
85
+ <Button
86
+ onClick={ openEvent }
87
+ className="button button-large"
88
+ >
89
+ Pick an image
90
+ </Button>
91
+ }
92
+ </>
93
+ );
94
+ }
95
+ };
96
+ return (
97
+ <div className={ className }>
98
+ <MediaUpload
99
+ onSelect={ media => { setAttributes({ imageAlt: media.alt, imageUrl: media.url }); } }
100
+ type="image"
101
+ value={ attributes.imageID }
102
+ render={ ({ open }) => getImageButton(open) }
103
+ />
104
+ <div className='wp-block-be-cover-content'>
105
+ <InnerBlocks
106
+ allowedBlocks={ ALLOWED_BLOCKS }
107
+ template={ BLOCKS_TEMPLATE }
108
+ />
109
+ </div>
110
+ </div>
111
+ );
112
+ },
113
+ save({ attributes }) {
114
+ const cardImage = (src, alt) => {
115
+ if(!src) return null;
116
+
117
+ return (
118
+ <img
119
+ className="wp-block-be-cover-image"
120
+ src={ src }
121
+ alt={ alt }
122
+ />
123
+ );
124
+ }
125
+
126
+ return (
127
+ <div>
128
+ { cardImage(attributes.imageUrl, attributes.imageAlt) }
129
+ <div className='wp-block-be-cover-content'>
130
+ <InnerBlocks.Content />
131
+ </div>
132
+ </div>
133
+ );
134
+ },
135
+ };
@@ -0,0 +1,132 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom';
3
+
4
+ /**
5
+ * WordPress dependencies
6
+ */
7
+ import { Button } from '@wordpress/components';
8
+ import { useInstanceId, usePrevious } from '@wordpress/compose';
9
+ import { useEffect, useRef } from '@wordpress/element';
10
+ import { __ } from '@wordpress/i18n';
11
+
12
+ /** @typedef {import('@wordpress/element').WPComponent} WPComponent */
13
+
14
+ /**
15
+ * ReusableBlockEditPanel props.
16
+ *
17
+ * @typedef WPReusableBlockEditPanelProps
18
+ *
19
+ * @property {boolean} isEditDisabled Is editing the reusable
20
+ * block disabled.
21
+ * @property {boolean} isEditing Is the reusable block
22
+ * being edited.
23
+ * @property {boolean} isSaving Is the reusable block
24
+ * being saved.
25
+ * @property {()=>void} onCancel Callback to run when
26
+ * editing is canceled.
27
+ * @property {(newTitle:string)=>void} onChangeTitle Callback to run when the
28
+ * title input value is
29
+ * changed.
30
+ * @property {()=>void} onEdit Callback to run when
31
+ * editing begins.
32
+ * @property {()=>void} onSave Callback to run when
33
+ * saving.
34
+ * @property {string} title Title of the reusable
35
+ * block.
36
+ */
37
+
38
+ /**
39
+ * Panel for enabling the editing and saving of a reusable block.
40
+ *
41
+ * @param {WPReusableBlockEditPanelProps} props Component props.
42
+ *
43
+ * @return {WPComponent} The panel.
44
+ */
45
+ export default function ReusableBlockEditPanel( {
46
+ isEditDisabled,
47
+ isEditing,
48
+ isSaving,
49
+ onChangeTitle,
50
+ onEdit,
51
+ onSave,
52
+ title,
53
+ } ) {
54
+ const instanceId = useInstanceId( ReusableBlockEditPanel );
55
+ const titleField = useRef();
56
+ const editButton = useRef();
57
+ const wasEditing = usePrevious( isEditing );
58
+ const wasSaving = usePrevious( isSaving );
59
+
60
+ // Select the title input when the form opens.
61
+ useEffect( () => {
62
+ if ( ! wasEditing && isEditing ) {
63
+ titleField.current.select();
64
+ }
65
+ }, [ isEditing ] );
66
+
67
+ // Move focus back to the Edit button after pressing the Escape key or Save.
68
+ useEffect( () => {
69
+ if ( ( wasEditing || wasSaving ) && ! isEditing && ! isSaving ) {
70
+ editButton.current.focus();
71
+ }
72
+ }, [ isEditing, isSaving ] );
73
+
74
+ function handleFormSubmit( event ) {
75
+ event.preventDefault();
76
+ onSave();
77
+ }
78
+
79
+ function handleTitleChange( event ) {
80
+ onChangeTitle( event.target.value );
81
+ }
82
+
83
+ return (
84
+ <>
85
+ { ! isEditing && ! isSaving && (
86
+ <div className="reusable-block-edit-panel">
87
+ <b className="reusable-block-edit-panel__info">{ title }</b>
88
+ <Button
89
+ ref={ editButton }
90
+ isSecondary
91
+ className="reusable-block-edit-panel__button"
92
+ disabled={ isEditDisabled }
93
+ onClick={ onEdit }
94
+ >
95
+ { __( 'Edit' ) }
96
+ </Button>
97
+ </div>
98
+ ) }
99
+ { ( isEditing || isSaving ) && (
100
+ <form
101
+ className="reusable-block-edit-panel"
102
+ onSubmit={ handleFormSubmit }
103
+ >
104
+ <label
105
+ htmlFor={ `reusable-block-edit-panel__title-${ instanceId }` }
106
+ className="reusable-block-edit-panel__label"
107
+ >
108
+ { __( 'Name:' ) }
109
+ </label>
110
+ <input
111
+ ref={ titleField }
112
+ type="text"
113
+ disabled={ isSaving }
114
+ className="reusable-block-edit-panel__title"
115
+ value={ title }
116
+ onChange={ handleTitleChange }
117
+ id={ `reusable-block-edit-panel__title-${ instanceId }` }
118
+ />
119
+ <Button
120
+ type="submit"
121
+ isSecondary
122
+ isBusy={ isSaving }
123
+ disabled={ ! title || isSaving }
124
+ className="reusable-block-edit-panel__button"
125
+ >
126
+ { __( 'Save' ) }
127
+ </Button>
128
+ </form>
129
+ ) }
130
+ </>
131
+ );
132
+ }
@@ -0,0 +1,163 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom';
3
+
4
+ /**
5
+ * WordPress dependencies
6
+ */
7
+ import { useSelect, useDispatch } from '@wordpress/data';
8
+ import { useEntityBlockEditor } from '@wordpress/core-data';
9
+ import { useCallback } from '@wordpress/element';
10
+ import {
11
+ Placeholder,
12
+ Spinner,
13
+ Disabled,
14
+ ToolbarGroup,
15
+ ToolbarButton,
16
+ } from '@wordpress/components';
17
+ import { __ } from '@wordpress/i18n';
18
+ import {
19
+ BlockEditorProvider,
20
+ WritingFlow,
21
+ BlockList,
22
+ BlockControls,
23
+ useBlockProps,
24
+ } from '@wordpress/block-editor';
25
+
26
+ /**
27
+ * Internal dependencies
28
+ */
29
+ import ReusableBlockEditPanel from './edit-panel';
30
+
31
+ export default function ReusableBlockEdit( {
32
+ attributes: { ref },
33
+ clientId,
34
+ isSelected,
35
+ } ) {
36
+ const recordArgs = [ 'postType', 'wp_block', ref ];
37
+
38
+ const {
39
+ reusableBlock,
40
+ hasResolved,
41
+ isEditing,
42
+ isSaving,
43
+ canUserUpdate,
44
+ settings,
45
+ } = useSelect(
46
+ ( select ) => ( {
47
+ reusableBlock: select( 'core' ).getEditedEntityRecord(
48
+ ...recordArgs
49
+ ),
50
+ hasResolved: select( 'core' ).hasFinishedResolution(
51
+ 'getEditedEntityRecord',
52
+ recordArgs
53
+ ),
54
+ isSaving: select( 'core' ).isSavingEntityRecord( ...recordArgs ),
55
+ canUserUpdate: select( 'core' ).canUser( 'update', 'blocks', ref ),
56
+ isEditing: select(
57
+ 'core/reusable-blocks'
58
+ ).__experimentalIsEditingReusableBlock( clientId ),
59
+ settings: select( 'core/block-editor' ).getSettings(),
60
+ } ),
61
+ [ ref, clientId ]
62
+ );
63
+
64
+ const { editEntityRecord, saveEditedEntityRecord } = useDispatch( 'core' );
65
+ const { __experimentalSetEditingReusableBlock } = useDispatch(
66
+ 'core/reusable-blocks'
67
+ );
68
+ const setIsEditing = useCallback(
69
+ ( value ) => {
70
+ __experimentalSetEditingReusableBlock( clientId, value );
71
+ },
72
+ [ clientId ]
73
+ );
74
+
75
+ const {
76
+ __experimentalConvertBlockToStatic: convertBlockToStatic,
77
+ } = useDispatch( 'core/reusable-blocks' );
78
+
79
+ const { createSuccessNotice, createErrorNotice } = useDispatch(
80
+ 'core/notices'
81
+ );
82
+ const save = useCallback( async function () {
83
+ try {
84
+ await saveEditedEntityRecord( ...recordArgs );
85
+ createSuccessNotice( __( 'Block updated.' ), {
86
+ type: 'snackbar',
87
+ } );
88
+ } catch ( error ) {
89
+ createErrorNotice( error.message, {
90
+ type: 'snackbar',
91
+ } );
92
+ }
93
+ }, recordArgs );
94
+
95
+ const [ blocks, onInput, onChange ] = useEntityBlockEditor(
96
+ 'postType',
97
+ 'wp_block',
98
+ { id: ref }
99
+ );
100
+
101
+ const blockProps = useBlockProps();
102
+
103
+ if ( ! hasResolved ) {
104
+ return (
105
+ <div { ...blockProps }>
106
+ <Placeholder>
107
+ <Spinner />
108
+ </Placeholder>
109
+ </div>
110
+ );
111
+ }
112
+
113
+ if ( ! reusableBlock ) {
114
+ return (
115
+ <div { ...blockProps }>
116
+ <Placeholder>
117
+ { __( 'Block has been deleted or is unavailable.' ) }
118
+ </Placeholder>
119
+ </div>
120
+ );
121
+ }
122
+
123
+ let element = (
124
+ <BlockEditorProvider
125
+ value={ blocks }
126
+ onInput={ onInput }
127
+ onChange={ onChange }
128
+ settings={ settings }
129
+ >
130
+ <WritingFlow>
131
+ <BlockList />
132
+ </WritingFlow>
133
+ </BlockEditorProvider>
134
+ );
135
+
136
+ if ( ! isEditing ) {
137
+ element = <Disabled>{ element }</Disabled>;
138
+ }
139
+
140
+ return (
141
+ <div { ...blockProps }>
142
+ <div className="block-library-block__reusable-block-container">
143
+ { ( isSelected || isEditing ) && (
144
+ <ReusableBlockEditPanel
145
+ isEditing={ isEditing }
146
+ title={ reusableBlock.title }
147
+ isSaving={ isSaving }
148
+ isEditDisabled={ ! canUserUpdate }
149
+ onEdit={ () => setIsEditing( true ) }
150
+ onChangeTitle={ ( title ) =>
151
+ editEntityRecord( ...recordArgs, { title } )
152
+ }
153
+ onSave={ () => {
154
+ save();
155
+ setIsEditing( false );
156
+ } }
157
+ />
158
+ ) }
159
+ { element }
160
+ </div>
161
+ </div>
162
+ );
163
+ }