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
@@ -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
+ }