block_editor 0.1.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 (59) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +147 -0
  4. data/Rakefile +18 -0
  5. data/app/assets/config/block_editor_manifest.js +1 -0
  6. data/app/assets/stylesheets/block_editor/backend.scss +4 -0
  7. data/app/assets/stylesheets/block_editor/backend/blocks.scss +0 -0
  8. data/app/assets/stylesheets/block_editor/frontend.scss +1 -0
  9. data/app/assets/stylesheets/block_editor/frontend/blocks.scss +0 -0
  10. data/app/controllers/block_editor/application_controller.rb +4 -0
  11. data/app/helpers/block_editor/application_helper.rb +11 -0
  12. data/app/javascript/block_editor/blocks/button/edit.js +240 -0
  13. data/app/javascript/block_editor/blocks/column/edit.js +93 -0
  14. data/app/javascript/block_editor/blocks/image/edit.js +656 -0
  15. data/app/javascript/block_editor/blocks/index.js +263 -0
  16. data/app/javascript/block_editor/components/block-editor/index.js +88 -0
  17. data/app/javascript/block_editor/components/block-editor/styles.scss +39 -0
  18. data/app/javascript/block_editor/components/header/index.js +45 -0
  19. data/app/javascript/block_editor/components/header/redo.js +36 -0
  20. data/app/javascript/block_editor/components/header/styles.scss +14 -0
  21. data/app/javascript/block_editor/components/header/undo.js +36 -0
  22. data/app/javascript/block_editor/components/media-upload/index.js +37 -0
  23. data/app/javascript/block_editor/components/notices/index.js +26 -0
  24. data/app/javascript/block_editor/components/notices/styles.scss +9 -0
  25. data/app/javascript/block_editor/components/sidebar/index.js +31 -0
  26. data/app/javascript/block_editor/components/sidebar/styles.scss +43 -0
  27. data/app/javascript/block_editor/stores/action-types.js +4 -0
  28. data/app/javascript/block_editor/stores/actions.js +41 -0
  29. data/app/javascript/block_editor/stores/controls.js +21 -0
  30. data/app/javascript/block_editor/stores/index.js +30 -0
  31. data/app/javascript/block_editor/stores/reducer.js +20 -0
  32. data/app/javascript/block_editor/stores/resolvers.js +10 -0
  33. data/app/javascript/block_editor/stores/selectors.js +13 -0
  34. data/app/javascript/controllers/block_editor_controller.jsx +42 -0
  35. data/app/javascript/controllers/index.js +6 -0
  36. data/app/javascript/packs/block_editor/application.js +2 -0
  37. data/app/javascript/packs/block_editor/application.scss +108 -0
  38. data/app/jobs/block_editor/application_job.rb +4 -0
  39. data/app/mailers/block_editor/application_mailer.rb +6 -0
  40. data/app/models/block_editor/application_record.rb +5 -0
  41. data/app/models/block_editor/block_list.rb +7 -0
  42. data/app/models/concerns/block_editor/listable.rb +24 -0
  43. data/app/views/layouts/block_editor/application.html.erb +15 -0
  44. data/config/initializers/webpacker_extension.rb +12 -0
  45. data/config/routes.rb +2 -0
  46. data/config/webpack/development.js +5 -0
  47. data/config/webpack/environment.js +3 -0
  48. data/config/webpack/production.js +5 -0
  49. data/config/webpack/test.js +5 -0
  50. data/config/webpacker.yml +92 -0
  51. data/db/migrate/20210312032114_create_block_lists.rb +11 -0
  52. data/lib/block_editor.rb +26 -0
  53. data/lib/block_editor/block_list_renderer.rb +43 -0
  54. data/lib/block_editor/blocks/base.rb +32 -0
  55. data/lib/block_editor/engine.rb +34 -0
  56. data/lib/block_editor/instance.rb +19 -0
  57. data/lib/block_editor/version.rb +3 -0
  58. data/lib/tasks/block_editor_tasks.rake +59 -0
  59. metadata +131 -0
@@ -0,0 +1,263 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import React from 'react';
5
+ import ReactDOM from 'react-dom';
6
+ import classnames from 'classnames';
7
+ import { assign } from 'lodash';
8
+
9
+ /**
10
+ * WordPress dependencies
11
+ */
12
+ import '@wordpress/core-data';
13
+ import { registerCoreBlocks } from '@wordpress/block-library'
14
+ import '@wordpress/block-editor';
15
+ import {
16
+ registerBlockType,
17
+ unregisterBlockType,
18
+ registerBlockStyle,
19
+ unregisterBlockStyle,
20
+ unregisterBlockVariation
21
+ } from '@wordpress/blocks';
22
+ import { createHigherOrderComponent } from '@wordpress/compose'
23
+ import { addFilter } from '@wordpress/hooks';
24
+
25
+ /**
26
+ * Internal dependencies
27
+ */
28
+ import ColumnEdit from './column/edit';
29
+ import ButtonEdit from './button/edit';
30
+ import ImageEdit from './image/edit';
31
+ import MediaUpload from '../components/media-upload';
32
+
33
+ export const registerBlocks = () => {
34
+ const replaceButtonBlockEdit = ( settings, name ) => {
35
+ if ( name !== 'core/button' ) {
36
+ return settings;
37
+ }
38
+
39
+ return assign( {}, settings, {
40
+ edit: ButtonEdit, // Removes & replaces some styling options
41
+ attributes: assign( {}, settings.attributes, {
42
+ hasHollowStyle: {
43
+ type: 'boolean',
44
+ default: false
45
+ },
46
+ hasLargeStyle: {
47
+ type: 'boolean',
48
+ default: false
49
+ }
50
+ })
51
+ } );
52
+ }
53
+
54
+ const replaceColumnBlockEdit = ( settings, name ) => {
55
+ if ( name !== 'core/column' ) {
56
+ return settings;
57
+ }
58
+
59
+ return assign( {}, settings, {
60
+ edit: ColumnEdit // Removes column width options
61
+ } );
62
+ }
63
+
64
+ const replaceImageBlockEdit = ( settings, name ) => {
65
+ if ( name !== 'core/image' ) {
66
+ return settings;
67
+ }
68
+
69
+ return assign( {}, settings, {
70
+ edit: ImageEdit // Removes ImageSizeControl options
71
+ } );
72
+ }
73
+
74
+ const applyExtraClass = ( extraProps, blockType, attributes ) => {
75
+ if ( blockType.name !== 'core/button' ) {
76
+ return extraProps;
77
+ }
78
+
79
+ if (attributes.hasLargeStyle) {
80
+ extraProps.className = classnames( extraProps.className, 'large' );
81
+ }
82
+
83
+ if (attributes.hasHollowStyle) {
84
+ extraProps.className = classnames( extraProps.className, 'is-style-hollow' );
85
+ }
86
+
87
+ return extraProps;
88
+ }
89
+
90
+ const withClientIdClassName = createHigherOrderComponent( ( BlockListBlock ) => {
91
+ return ( props ) => {
92
+ if ( props.name !== 'core/button' ) {
93
+ return <BlockListBlock { ...props } />;
94
+ }
95
+
96
+ let classNames = '';
97
+ if (props.attributes.hasLargeStyle) {
98
+ classNames = classnames( classNames, 'large' );
99
+ }
100
+
101
+ if (props.attributes.hasHollowStyle) {
102
+ classNames = classnames( classNames, 'is-style-hollow' );
103
+ }
104
+
105
+ return <BlockListBlock { ...props } className={ classNames } />;
106
+ };
107
+ }, 'withClientIdClassName' );
108
+
109
+ const replaceMediaUpload = () => MediaUpload;
110
+
111
+ addFilter(
112
+ 'editor.BlockListBlock',
113
+ 'block-editor/filters/core-button-block-list',
114
+ withClientIdClassName
115
+ );
116
+
117
+ addFilter(
118
+ 'blocks.registerBlockType',
119
+ 'block-editor/filters/core-button',
120
+ replaceButtonBlockEdit
121
+ );
122
+
123
+ addFilter(
124
+ 'blocks.getSaveContent.extraProps',
125
+ 'block-editor/filters/core-button-classes',
126
+ applyExtraClass
127
+ );
128
+
129
+ addFilter(
130
+ 'blocks.registerBlockType',
131
+ 'block-editor/filters/core-column',
132
+ replaceColumnBlockEdit
133
+ );
134
+
135
+ addFilter(
136
+ 'blocks.registerBlockType',
137
+ 'block-editor/filters/core-image',
138
+ replaceImageBlockEdit
139
+ );
140
+
141
+ addFilter(
142
+ 'editor.MediaUpload',
143
+ 'block-editor/filters/media-upload',
144
+ replaceMediaUpload
145
+ );
146
+
147
+ // Register WP blocks
148
+ registerCoreBlocks();
149
+
150
+ // Unregister WP blocks which are not supported
151
+ unregisterBlockType('core/gallery');
152
+ unregisterBlockType('core/quote');
153
+ unregisterBlockType('core/shortcode');
154
+ unregisterBlockType('core/archives');
155
+ unregisterBlockType('core/audio');
156
+ unregisterBlockType('core/calendar');
157
+ unregisterBlockType('core/categories');
158
+ unregisterBlockType('core/code');
159
+ unregisterBlockType('core/cover');
160
+ unregisterBlockType('core/embed');
161
+ unregisterBlockType('core-embed/twitter');
162
+ unregisterBlockType('core-embed/youtube');
163
+ unregisterBlockType('core-embed/facebook');
164
+ unregisterBlockType('core-embed/instagram');
165
+ unregisterBlockType('core-embed/wordpress');
166
+ unregisterBlockType('core-embed/soundcloud');
167
+ unregisterBlockType('core-embed/spotify');
168
+ unregisterBlockType('core-embed/flickr');
169
+ unregisterBlockType('core-embed/vimeo');
170
+ unregisterBlockType('core-embed/animoto');
171
+ unregisterBlockType('core-embed/cloudup');
172
+ unregisterBlockType('core-embed/collegehumor');
173
+ unregisterBlockType('core-embed/crowdsignal');
174
+ unregisterBlockType('core-embed/dailymotion');
175
+ unregisterBlockType('core-embed/hulu');
176
+ unregisterBlockType('core-embed/imgur');
177
+ unregisterBlockType('core-embed/issuu');
178
+ unregisterBlockType('core-embed/kickstarter');
179
+ unregisterBlockType('core-embed/meetup-com');
180
+ unregisterBlockType('core-embed/mixcloud');
181
+ unregisterBlockType('core-embed/polldaddy');
182
+ unregisterBlockType('core-embed/reddit');
183
+ unregisterBlockType('core-embed/reverbnation');
184
+ unregisterBlockType('core-embed/screencast');
185
+ unregisterBlockType('core-embed/scribd');
186
+ unregisterBlockType('core-embed/slideshare');
187
+ unregisterBlockType('core-embed/smugmug');
188
+ unregisterBlockType('core-embed/speaker');
189
+ unregisterBlockType('core-embed/speaker-deck');
190
+ unregisterBlockType('core-embed/tiktok');
191
+ unregisterBlockType('core-embed/ted');
192
+ unregisterBlockType('core-embed/tumblr');
193
+ unregisterBlockType('core-embed/videopress');
194
+ unregisterBlockType('core-embed/wordpress-tv');
195
+ unregisterBlockType('core-embed/amazon-kindle');
196
+ unregisterBlockType('core/file');
197
+ unregisterBlockType('core/media-text');
198
+ unregisterBlockType('core/latest-comments');
199
+ unregisterBlockType('core/latest-posts');
200
+ unregisterBlockType('core/more');
201
+ unregisterBlockType('core/nextpage');
202
+ unregisterBlockType('core/preformatted');
203
+ unregisterBlockType('core/pullquote');
204
+ unregisterBlockType('core/rss');
205
+ unregisterBlockType('core/search');
206
+ // unregisterBlockType('core/reusable-block'); // ?
207
+ // unregisterBlockType('core/reusable'); // ?
208
+ unregisterBlockType('core/social-links');
209
+ unregisterBlockType('core/social-link');
210
+ unregisterBlockType('core/spacer');
211
+ unregisterBlockType('core/subhead');
212
+ unregisterBlockType('core/tag-cloud');
213
+ unregisterBlockType('core/text-columns');
214
+ unregisterBlockType('core/verse');
215
+ unregisterBlockType('core/video');
216
+
217
+ // Unregister WP block styles
218
+ unregisterBlockStyle('core/separator', 'wide');
219
+ unregisterBlockStyle('core/button', 'fill');
220
+ unregisterBlockStyle('core/button', 'outline');
221
+ unregisterBlockStyle('core/image', 'default');
222
+ unregisterBlockStyle('core/image', 'rounded');
223
+ unregisterBlockStyle('core/table', 'regular');
224
+ unregisterBlockStyle('core/table', 'stripes');
225
+
226
+ // Unregister WP block variations
227
+ unregisterBlockVariation('core/columns', 'two-columns-one-third-two-thirds');
228
+ unregisterBlockVariation('core/columns', 'two-columns-two-thirds-one-third');
229
+ unregisterBlockVariation('core/columns', 'three-columns-wider-center');
230
+
231
+ // Register custom block styles
232
+ registerBlockStyle( 'core/button', {
233
+ name: 'primary',
234
+ label: 'Primary',
235
+ isDefault: true
236
+ } );
237
+ registerBlockStyle( 'core/button', {
238
+ name: 'secondary',
239
+ label: 'Secondary'
240
+ } );
241
+ registerBlockStyle( 'core/table', {
242
+ name: 'striped',
243
+ label: 'Striped',
244
+ isDefault: true
245
+ } );
246
+ registerBlockStyle( 'core/table', {
247
+ name: 'unstriped',
248
+ label: 'Unstriped'
249
+ } );
250
+ registerBlockStyle( 'core/image', {
251
+ name: 'default',
252
+ label: 'Default',
253
+ isDefault: true
254
+ } );
255
+ registerBlockStyle( 'core/image', {
256
+ name: 'padded',
257
+ label: 'Padded'
258
+ } );
259
+ registerBlockStyle( 'core/columns', {
260
+ name: 'no-stack',
261
+ label: 'No Stacking'
262
+ } );
263
+ };
@@ -0,0 +1,88 @@
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom'
3
+ /**
4
+ * WordPress dependencies
5
+ */
6
+ import '@wordpress/editor'; // This shouldn't be necessary - currently required otherwise notices fails to initialized, think the data store is being registered
7
+ import '@wordpress/format-library';
8
+ import { useSelect, useDispatch } from '@wordpress/data';
9
+ import { useEffect, useState, useMemo } from '@wordpress/element';
10
+ import { serialize, parse } from '@wordpress/blocks';
11
+ import { InterfaceSkeleton as EditorSkeleton } from '@wordpress/interface';
12
+ import {
13
+ BlockEditorKeyboardShortcuts,
14
+ BlockEditorProvider,
15
+ BlockList,
16
+ BlockInspector,
17
+ WritingFlow,
18
+ ObserveTyping,
19
+ BlockBreadcrumb,
20
+ } from '@wordpress/block-editor';
21
+ import {
22
+ Popover,
23
+ SlotFillProvider,
24
+ DropZoneProvider,
25
+ } from '@wordpress/components';
26
+
27
+ /**
28
+ * Internal dependencies
29
+ */
30
+ import Sidebar from '../sidebar';
31
+ import Header from '../header';
32
+ import Notices from '../notices';
33
+ import '../../stores'; // TODO: Think this store registering needs to be moved somewhere else so that it happens everytime a BlockEditor is initialized
34
+
35
+ function BlockEditor( { input, settings: _settings } ) {
36
+ const blocks = useSelect((select) => select("block-editor").getBlocks());
37
+ const { updateBlocks } = useDispatch("block-editor");
38
+
39
+ function handleInput(newBlocks, persist) {
40
+ updateBlocks(newBlocks);
41
+ input.value = serialize(newBlocks);
42
+ }
43
+
44
+ function handleChange(newBlocks) {
45
+ updateBlocks(newBlocks, true);
46
+ input.value = serialize(newBlocks);
47
+ }
48
+
49
+ return (
50
+ <SlotFillProvider>
51
+ <DropZoneProvider>
52
+ <EditorSkeleton
53
+ sidebar={<Sidebar />}
54
+ content={
55
+ <>
56
+ <Notices />
57
+ <BlockEditorProvider
58
+ value={ blocks }
59
+ onInput={ handleInput }
60
+ onChange={ handleChange }
61
+ settings={ _settings }
62
+ >
63
+ <Header />
64
+ <BlockBreadcrumb />
65
+ <Sidebar.InspectorFill>
66
+ <BlockInspector />
67
+ </Sidebar.InspectorFill>
68
+ <div className="block-editor__inner-wrapper">
69
+ <BlockEditorKeyboardShortcuts.Register />
70
+ <BlockEditorKeyboardShortcuts />
71
+ <WritingFlow>
72
+ <ObserveTyping>
73
+ <BlockList className="editor-styles-wrapper" />
74
+ </ObserveTyping>
75
+ </WritingFlow>
76
+ </div>
77
+ </BlockEditorProvider>
78
+ </>
79
+ }
80
+ />
81
+ <Popover.Slot />
82
+ </DropZoneProvider>
83
+ </SlotFillProvider>
84
+ );
85
+ }
86
+
87
+ export default BlockEditor;
88
+
@@ -0,0 +1,39 @@
1
+ .block-editor__block-list {
2
+ padding-bottom: $grid-unit-30;
3
+ padding-top: $grid-unit-30 + 5;
4
+ margin-left: auto;
5
+ margin-right: auto;
6
+ }
7
+
8
+ .block-editor-block-list__layout {
9
+ margin-bottom: 50px;
10
+ }
11
+
12
+ .block-editor__inner-wrapper {
13
+ -webkit-overflow-scrolling: touch;
14
+ background: $white;
15
+ height: calc(100% - #{$header-height});
16
+ left: 0;
17
+ overflow: auto;
18
+ position: absolute;
19
+ top: $header-height;
20
+ width: 100%;
21
+
22
+ @include break-small {
23
+ width: calc(100% - #{$sidebar-width});
24
+ }
25
+ }
26
+
27
+ // Grab the default Editor styles for visual consistency with WP
28
+ .editor-styles-wrapper {
29
+ // We need the styles scoped to "body" to be added to the
30
+ // div.editor-styles wrapper
31
+ font-family: $editor-font;
32
+ font-size: $editor-font-size;
33
+ line-height: $editor-line-height;
34
+ color: #555;
35
+
36
+ // Important all other selectors scoped underneath
37
+ // `div.editor-styles-wrapper`
38
+ @import "~@wordpress/editor/src/editor-styles.scss";
39
+ }
@@ -0,0 +1,45 @@
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom'
3
+
4
+ /**
5
+ * WordPress dependencies
6
+ */
7
+ import { NavigableToolbar } from '@wordpress/block-editor';
8
+ import { Button } from '@wordpress/components';
9
+ import { minus as minimizeIcon } from '@wordpress/icons';
10
+ import { plus as maximizeIcon } from '@wordpress/icons';
11
+
12
+ import HistoryUndo from './undo';
13
+ import HistoryRedo from './redo';
14
+
15
+ export default function Header() {
16
+ return (
17
+ <div
18
+ className="block-editor__header"
19
+ role="region"
20
+ tabIndex="-1"
21
+ >
22
+ <NavigableToolbar
23
+ className="edit-post-header-toolbar"
24
+ >
25
+ <HistoryUndo />
26
+ <HistoryRedo />
27
+ <Button
28
+ icon={ maximizeIcon }
29
+ label={ 'Fullscreen' }
30
+ // shortcut={ displayShortcut.primary( 'x' ) }
31
+ className="block-editor__size-toggle-button block-editor__size-toggle-button__maximize"
32
+ onClick={() => document.querySelector('.block-editor').classList.add('block-editor__fullscreen')}
33
+ />
34
+ <Button
35
+ icon={ minimizeIcon }
36
+ label={ 'Minimize' }
37
+ // shortcut={ displayShortcut.primary( 'x' ) }
38
+ className="block-editor__size-toggle-button block-editor__size-toggle-button__minimize"
39
+ onClick={() => document.querySelector('.block-editor').classList.remove('block-editor__fullscreen')}
40
+ />
41
+
42
+ </NavigableToolbar>
43
+ </div>
44
+ );
45
+ }
@@ -0,0 +1,36 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom';
3
+ import { __ } from '@wordpress/i18n';
4
+ import { Button } from '@wordpress/components';
5
+ import { withSelect, withDispatch } from '@wordpress/data';
6
+ import { compose } from '@wordpress/compose';
7
+ import { displayShortcut } from '@wordpress/keycodes';
8
+ import { redo as redoIcon } from '@wordpress/icons';
9
+
10
+ function HistoryRedo( { hasRedo, redo, ...props } ) {
11
+ return (
12
+ <Button
13
+ { ...props }
14
+ icon={ redoIcon }
15
+ label={ __( 'Redo' ) }
16
+ shortcut={ displayShortcut.primary( 'x' ) }
17
+ // If there are no redo levels we don't want to actually disable this
18
+ // button, because it will remove focus for keyboard users.
19
+ // See: https://github.com/WordPress/gutenberg/issues/3486
20
+ aria-disabled={ ! hasRedo }
21
+ onClick={ hasRedo ? redo : undefined }
22
+ className="editor-history__redo"
23
+ />
24
+ );
25
+ }
26
+
27
+ const EnhancedHistoryRedo = compose( [
28
+ withSelect( ( select ) => ( {
29
+ hasRedo: select( 'block-editor' ).hasRedo(),
30
+ } ) ),
31
+ withDispatch( ( dispatch ) => ( {
32
+ redo: dispatch( 'block-editor' ).redo,
33
+ } ) ),
34
+ ] )( HistoryRedo );
35
+
36
+ export default EnhancedHistoryRedo;