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.
- checksums.yaml +4 -4
- data/README.md +35 -14
- data/app/assets/images/block_editor/contact-form-block.svg +1 -0
- data/app/assets/stylesheets/block_editor/_utilities.scss +16 -0
- data/app/assets/stylesheets/block_editor/backend.scss +70 -2
- data/app/assets/stylesheets/block_editor/blocks/_backend.scss +7 -0
- data/app/assets/stylesheets/block_editor/blocks/_frontend.scss +11 -0
- data/app/assets/stylesheets/block_editor/blocks/be-accordion/_frontend.scss +3 -0
- data/app/assets/stylesheets/block_editor/blocks/be-alert/_frontend.scss +13 -0
- data/app/assets/stylesheets/block_editor/blocks/be-card/_backend.scss +7 -0
- data/app/assets/stylesheets/block_editor/{backend/blocks.scss → blocks/be-card/_frontend.scss} +0 -0
- data/app/assets/stylesheets/block_editor/blocks/be-contact-form/_backend.scss +22 -0
- data/app/assets/stylesheets/block_editor/blocks/be-cover/_backend.scss +8 -0
- data/app/assets/stylesheets/block_editor/blocks/be-cover/_frontend.scss +37 -0
- data/app/assets/stylesheets/block_editor/blocks/block/_backend.scss +11 -0
- data/app/assets/stylesheets/block_editor/blocks/button/_frontend.scss +46 -0
- data/app/assets/stylesheets/block_editor/blocks/buttons/_backend.scss +10 -0
- data/app/assets/stylesheets/block_editor/blocks/buttons/_frontend.scss +3 -0
- data/app/assets/stylesheets/block_editor/blocks/column/_frontend.scss +18 -0
- data/app/assets/stylesheets/block_editor/blocks/columns/_backend.scss +8 -0
- data/app/assets/stylesheets/block_editor/blocks/columns/_frontend.scss +14 -0
- data/app/assets/stylesheets/block_editor/blocks/image/_backend.scss +10 -0
- data/app/assets/stylesheets/block_editor/blocks/image/_frontend.scss +21 -0
- data/app/assets/stylesheets/block_editor/blocks/seperator/_frontend.scss +19 -0
- data/app/assets/stylesheets/block_editor/blocks/table/_frontend.scss +67 -0
- data/app/assets/stylesheets/block_editor/host_app/_variables.scss +1 -0
- data/app/assets/stylesheets/block_editor/host_app/blocks/_backend.scss +1 -0
- data/app/assets/stylesheets/block_editor/host_app/blocks/_frontend.scss +1 -0
- data/app/javascript/block_editor/blocks/be-accordion/index.js +108 -0
- data/app/javascript/block_editor/blocks/be-alert/index.js +51 -0
- data/app/javascript/block_editor/blocks/be-card/index.js +205 -0
- data/app/javascript/block_editor/blocks/be-contact-form/index.js +24 -0
- data/app/javascript/block_editor/blocks/be-cover/index.js +135 -0
- data/app/javascript/block_editor/blocks/block/edit-panel/index.js +132 -0
- data/app/javascript/block_editor/blocks/block/edit.js +163 -0
- data/app/javascript/block_editor/blocks/button/edit.js +0 -13
- data/app/javascript/block_editor/blocks/index.js +51 -102
- data/app/javascript/block_editor/components/block-editor/index.js +107 -36
- data/app/javascript/block_editor/components/block-editor/popover-wrapper.js +60 -0
- data/app/javascript/block_editor/components/block-editor/styles.scss +0 -11
- data/app/javascript/block_editor/components/header/index.js +28 -6
- data/app/javascript/block_editor/components/header/redo.js +1 -1
- data/app/javascript/block_editor/components/header/styles.scss +12 -11
- data/app/javascript/block_editor/components/media-upload/index.js +3 -3
- data/app/javascript/block_editor/components/sidebar/index.js +1 -3
- data/app/javascript/block_editor/components/sidebar/styles.scss +35 -35
- data/app/javascript/block_editor/stores/actions.js +12 -0
- data/app/javascript/block_editor/stores/reducer.js +23 -3
- data/app/javascript/block_editor/stores/selectors.js +14 -3
- data/app/javascript/controllers/block_editor_controller.jsx +15 -8
- data/app/javascript/controllers/index.js +2 -0
- data/app/javascript/packs/block_editor/application.scss +70 -26
- data/app/models/block_editor/block_list.rb +45 -1
- data/app/models/block_editor/block_list_connection.rb +6 -0
- data/app/views/block_editor/blocks/be/contact-form/_block.html +3 -0
- data/db/migrate/20210506220328_create_block_list_connections.rb +8 -0
- data/lib/block_editor.rb +3 -1
- data/lib/block_editor/block_list_renderer.rb +12 -6
- data/lib/block_editor/blocks/base.rb +1 -1
- data/lib/block_editor/blocks/contact_form.rb +11 -0
- data/lib/block_editor/blocks/reusable.rb +16 -0
- data/lib/block_editor/version.rb +1 -1
- data/package.json +16 -7
- data/yarn.lock +727 -530
- metadata +42 -8
- data/app/assets/stylesheets/block_editor/frontend.scss +0 -1
- data/app/assets/stylesheets/block_editor/frontend/blocks.scss +0 -0
- 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
|
+
}
|