├── .gitignore
├── CHANGELOG.md
├── Gemfile
├── Gemfile.lock
├── MIT-LICENSE
├── README.md
├── Rakefile
├── app
├── assets
│ ├── config
│ │ └── block_editor_manifest.js
│ ├── images
│ │ └── block_editor
│ │ │ ├── .keep
│ │ │ └── contact-form-block.svg
│ └── stylesheets
│ │ └── block_editor
│ │ ├── _utilities.scss
│ │ ├── backend.scss
│ │ ├── blocks
│ │ ├── _backend.scss
│ │ ├── _frontend.scss
│ │ ├── be-accordion
│ │ │ └── _frontend.scss
│ │ ├── be-alert
│ │ │ └── _frontend.scss
│ │ ├── be-card
│ │ │ ├── _backend.scss
│ │ │ └── _frontend.scss
│ │ ├── be-contact-form
│ │ │ └── _backend.scss
│ │ ├── be-cover
│ │ │ ├── _backend.scss
│ │ │ └── _frontend.scss
│ │ ├── block
│ │ │ └── _backend.scss
│ │ ├── button
│ │ │ └── _frontend.scss
│ │ ├── buttons
│ │ │ ├── _backend.scss
│ │ │ └── _frontend.scss
│ │ ├── column
│ │ │ └── _frontend.scss
│ │ ├── columns
│ │ │ ├── _backend.scss
│ │ │ └── _frontend.scss
│ │ ├── image
│ │ │ ├── _backend.scss
│ │ │ └── _frontend.scss
│ │ ├── seperator
│ │ │ └── _frontend.scss
│ │ └── table
│ │ │ └── _frontend.scss
│ │ └── host_app
│ │ ├── _variables.scss
│ │ └── blocks
│ │ ├── _backend.scss
│ │ └── _frontend.scss
├── controllers
│ └── block_editor
│ │ └── application_controller.rb
├── helpers
│ └── block_editor
│ │ └── application_helper.rb
├── javascript
│ ├── block_editor
│ │ ├── blocks
│ │ │ ├── be-accordion
│ │ │ │ └── index.js
│ │ │ ├── be-alert
│ │ │ │ └── index.js
│ │ │ ├── be-card
│ │ │ │ └── index.js
│ │ │ ├── be-contact-form
│ │ │ │ └── index.js
│ │ │ ├── be-cover
│ │ │ │ └── index.js
│ │ │ ├── block
│ │ │ │ ├── edit-panel
│ │ │ │ │ └── index.js
│ │ │ │ └── edit.js
│ │ │ ├── button
│ │ │ │ └── edit.js
│ │ │ ├── column
│ │ │ │ └── edit.js
│ │ │ └── index.js
│ │ ├── components
│ │ │ ├── block-editor
│ │ │ │ ├── index.js
│ │ │ │ ├── popover-wrapper.js
│ │ │ │ └── styles.scss
│ │ │ ├── header
│ │ │ │ ├── index.js
│ │ │ │ ├── redo.js
│ │ │ │ ├── styles.scss
│ │ │ │ └── undo.js
│ │ │ ├── media-upload
│ │ │ │ └── index.js
│ │ │ ├── notices
│ │ │ │ ├── index.js
│ │ │ │ └── styles.scss
│ │ │ └── sidebar
│ │ │ │ ├── index.js
│ │ │ │ └── styles.scss
│ │ └── stores
│ │ │ ├── action-types.js
│ │ │ ├── actions.js
│ │ │ ├── controls.js
│ │ │ ├── index.js
│ │ │ ├── reducer.js
│ │ │ ├── resolvers.js
│ │ │ └── selectors.js
│ ├── controllers
│ │ ├── block_editor_controller.jsx
│ │ └── index.js
│ └── packs
│ │ └── block_editor
│ │ ├── application.js
│ │ └── application.scss
├── jobs
│ └── block_editor
│ │ └── application_job.rb
├── mailers
│ └── block_editor
│ │ └── application_mailer.rb
├── models
│ ├── block_editor
│ │ ├── application_record.rb
│ │ ├── block_list.rb
│ │ └── block_list_connection.rb
│ └── concerns
│ │ └── block_editor
│ │ └── listable.rb
└── views
│ ├── block_editor
│ └── blocks
│ │ └── be
│ │ └── contact-form
│ │ └── _block.html
│ └── layouts
│ └── block_editor
│ └── application.html.erb
├── bin
├── rails
├── webpack
└── webpack-dev-server
├── block_editor.gemspec
├── config
├── initializers
│ └── webpacker_extension.rb
├── routes.rb
├── webpack
│ ├── development.js
│ ├── environment.js
│ ├── production.js
│ └── test.js
└── webpacker.yml
├── db
└── migrate
│ ├── 20210312032114_create_block_lists.rb
│ └── 20210506220328_create_block_list_connections.rb
├── lib
├── block_editor.rb
├── block_editor
│ ├── block_list_renderer.rb
│ ├── blocks
│ │ ├── base.rb
│ │ ├── contact_form.rb
│ │ └── reusable.rb
│ ├── engine.rb
│ ├── instance.rb
│ └── version.rb
└── tasks
│ └── block_editor_tasks.rake
├── package.json
├── postcss.config.js
├── test
├── dummy
│ ├── Rakefile
│ ├── app
│ │ ├── assets
│ │ │ ├── config
│ │ │ │ └── manifest.js
│ │ │ ├── images
│ │ │ │ └── .keep
│ │ │ └── stylesheets
│ │ │ │ └── application.css
│ │ ├── channels
│ │ │ └── application_cable
│ │ │ │ ├── channel.rb
│ │ │ │ └── connection.rb
│ │ ├── controllers
│ │ │ ├── application_controller.rb
│ │ │ └── concerns
│ │ │ │ └── .keep
│ │ ├── helpers
│ │ │ └── application_helper.rb
│ │ ├── javascript
│ │ │ └── packs
│ │ │ │ └── application.js
│ │ ├── jobs
│ │ │ └── application_job.rb
│ │ ├── mailers
│ │ │ └── application_mailer.rb
│ │ ├── models
│ │ │ ├── application_record.rb
│ │ │ └── concerns
│ │ │ │ └── .keep
│ │ └── views
│ │ │ └── layouts
│ │ │ ├── application.html.erb
│ │ │ ├── mailer.html.erb
│ │ │ └── mailer.text.erb
│ ├── bin
│ │ ├── rails
│ │ ├── rake
│ │ └── setup
│ ├── config.ru
│ ├── config
│ │ ├── application.rb
│ │ ├── boot.rb
│ │ ├── cable.yml
│ │ ├── database.yml
│ │ ├── environment.rb
│ │ ├── environments
│ │ │ ├── development.rb
│ │ │ ├── production.rb
│ │ │ └── test.rb
│ │ ├── initializers
│ │ │ ├── application_controller_renderer.rb
│ │ │ ├── assets.rb
│ │ │ ├── backtrace_silencers.rb
│ │ │ ├── content_security_policy.rb
│ │ │ ├── cookies_serializer.rb
│ │ │ ├── filter_parameter_logging.rb
│ │ │ ├── inflections.rb
│ │ │ ├── mime_types.rb
│ │ │ ├── permissions_policy.rb
│ │ │ └── wrap_parameters.rb
│ │ ├── locales
│ │ │ └── en.yml
│ │ ├── puma.rb
│ │ ├── routes.rb
│ │ └── storage.yml
│ ├── lib
│ │ └── assets
│ │ │ └── .keep
│ ├── log
│ │ └── .keep
│ └── public
│ │ ├── 404.html
│ │ ├── 422.html
│ │ ├── 500.html
│ │ ├── apple-touch-icon-precomposed.png
│ │ ├── apple-touch-icon.png
│ │ └── favicon.ico
└── test_helper.rb
├── webpack
├── development.js
├── environment.js
├── production.js
└── test.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /doc/
3 | /log/*.log
4 | /pkg/
5 | /tmp/
6 | /test/dummy/db/*.sqlite3
7 | /test/dummy/db/*.sqlite3-*
8 | /test/dummy/log/*.log
9 | /test/dummy/storage/
10 | /test/dummy/tmp/
11 | .byebug_history
12 | /node_modules
13 | /public/packs/*
14 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | This project follows [semver 2.0.0](http://semver.org/spec/v2.0.0.html) and the
4 | recommendations of [keepachangelog.com](http://keepachangelog.com/).
5 |
6 | ## Unreleased
7 |
8 | ### Breaking Changes
9 | -
10 |
11 | ### Added
12 | -
13 |
14 | ### Fixed
15 | -
16 |
17 | ## v1.0.0 - 2021-06-15
18 |
19 | ### Breaking Changes
20 | -
21 |
22 | ### Added
23 | - Reusable Blocks
24 | - Block Inserter Library
25 | - Base styles for all blocks - this is using Bootstrap styles which adds a soft dependancy on the framework
26 | - Accordion block
27 | - Alert (callout) block
28 | - Card block
29 | - Cover block
30 | - Contact Form block
31 | - Keyboard shortcuts
32 |
33 | ### Fixed
34 | - Added margin-bottom to sidebar to account for footer
35 |
36 | ## v0.1.3 - 2021-03-16
37 |
38 | ### Added
39 | - Initial release
40 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" }
3 |
4 | # Specify your gem's dependencies in block_editor.gemspec.
5 | gemspec
6 |
7 | group :development do
8 | gem 'sqlite3'
9 | end
10 |
11 | # To use a debugger
12 | # gem 'byebug', group: [:development, :test]
13 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | block_editor (1.0.0)
5 | rails (~> 6.0)
6 | webpacker (~> 5.1)
7 |
8 | GEM
9 | remote: https://rubygems.org/
10 | specs:
11 | actioncable (6.1.3.2)
12 | actionpack (= 6.1.3.2)
13 | activesupport (= 6.1.3.2)
14 | nio4r (~> 2.0)
15 | websocket-driver (>= 0.6.1)
16 | actionmailbox (6.1.3.2)
17 | actionpack (= 6.1.3.2)
18 | activejob (= 6.1.3.2)
19 | activerecord (= 6.1.3.2)
20 | activestorage (= 6.1.3.2)
21 | activesupport (= 6.1.3.2)
22 | mail (>= 2.7.1)
23 | actionmailer (6.1.3.2)
24 | actionpack (= 6.1.3.2)
25 | actionview (= 6.1.3.2)
26 | activejob (= 6.1.3.2)
27 | activesupport (= 6.1.3.2)
28 | mail (~> 2.5, >= 2.5.4)
29 | rails-dom-testing (~> 2.0)
30 | actionpack (6.1.3.2)
31 | actionview (= 6.1.3.2)
32 | activesupport (= 6.1.3.2)
33 | rack (~> 2.0, >= 2.0.9)
34 | rack-test (>= 0.6.3)
35 | rails-dom-testing (~> 2.0)
36 | rails-html-sanitizer (~> 1.0, >= 1.2.0)
37 | actiontext (6.1.3.2)
38 | actionpack (= 6.1.3.2)
39 | activerecord (= 6.1.3.2)
40 | activestorage (= 6.1.3.2)
41 | activesupport (= 6.1.3.2)
42 | nokogiri (>= 1.8.5)
43 | actionview (6.1.3.2)
44 | activesupport (= 6.1.3.2)
45 | builder (~> 3.1)
46 | erubi (~> 1.4)
47 | rails-dom-testing (~> 2.0)
48 | rails-html-sanitizer (~> 1.1, >= 1.2.0)
49 | activejob (6.1.3.2)
50 | activesupport (= 6.1.3.2)
51 | globalid (>= 0.3.6)
52 | activemodel (6.1.3.2)
53 | activesupport (= 6.1.3.2)
54 | activerecord (6.1.3.2)
55 | activemodel (= 6.1.3.2)
56 | activesupport (= 6.1.3.2)
57 | activestorage (6.1.3.2)
58 | actionpack (= 6.1.3.2)
59 | activejob (= 6.1.3.2)
60 | activerecord (= 6.1.3.2)
61 | activesupport (= 6.1.3.2)
62 | marcel (~> 1.0.0)
63 | mini_mime (~> 1.0.2)
64 | activesupport (6.1.3.2)
65 | concurrent-ruby (~> 1.0, >= 1.0.2)
66 | i18n (>= 1.6, < 2)
67 | minitest (>= 5.1)
68 | tzinfo (~> 2.0)
69 | zeitwerk (~> 2.3)
70 | builder (3.2.4)
71 | concurrent-ruby (1.1.9)
72 | crass (1.0.6)
73 | erubi (1.10.0)
74 | globalid (0.4.2)
75 | activesupport (>= 4.2.0)
76 | i18n (1.8.10)
77 | concurrent-ruby (~> 1.0)
78 | loofah (2.10.0)
79 | crass (~> 1.0.2)
80 | nokogiri (>= 1.5.9)
81 | mail (2.7.1)
82 | mini_mime (>= 0.1.1)
83 | marcel (1.0.1)
84 | method_source (1.0.0)
85 | mini_mime (1.0.3)
86 | minitest (5.14.4)
87 | nio4r (2.5.7)
88 | nokogiri (1.11.7-x86_64-darwin)
89 | racc (~> 1.4)
90 | racc (1.5.2)
91 | rack (2.2.3)
92 | rack-proxy (0.7.0)
93 | rack
94 | rack-test (1.1.0)
95 | rack (>= 1.0, < 3)
96 | rails (6.1.3.2)
97 | actioncable (= 6.1.3.2)
98 | actionmailbox (= 6.1.3.2)
99 | actionmailer (= 6.1.3.2)
100 | actionpack (= 6.1.3.2)
101 | actiontext (= 6.1.3.2)
102 | actionview (= 6.1.3.2)
103 | activejob (= 6.1.3.2)
104 | activemodel (= 6.1.3.2)
105 | activerecord (= 6.1.3.2)
106 | activestorage (= 6.1.3.2)
107 | activesupport (= 6.1.3.2)
108 | bundler (>= 1.15.0)
109 | railties (= 6.1.3.2)
110 | sprockets-rails (>= 2.0.0)
111 | rails-dom-testing (2.0.3)
112 | activesupport (>= 4.2.0)
113 | nokogiri (>= 1.6)
114 | rails-html-sanitizer (1.3.0)
115 | loofah (~> 2.3)
116 | railties (6.1.3.2)
117 | actionpack (= 6.1.3.2)
118 | activesupport (= 6.1.3.2)
119 | method_source
120 | rake (>= 0.8.7)
121 | thor (~> 1.0)
122 | rake (13.0.3)
123 | semantic_range (3.0.0)
124 | sprockets (4.0.2)
125 | concurrent-ruby (~> 1.0)
126 | rack (> 1, < 3)
127 | sprockets-rails (3.2.2)
128 | actionpack (>= 4.0)
129 | activesupport (>= 4.0)
130 | sprockets (>= 3.0.0)
131 | sqlite3 (1.4.2)
132 | thor (1.1.0)
133 | tzinfo (2.0.4)
134 | concurrent-ruby (~> 1.0)
135 | webpacker (5.4.0)
136 | activesupport (>= 5.2)
137 | rack-proxy (>= 0.6.1)
138 | railties (>= 5.2)
139 | semantic_range (>= 2.3.0)
140 | websocket-driver (0.7.5)
141 | websocket-extensions (>= 0.1.0)
142 | websocket-extensions (0.1.5)
143 | zeitwerk (2.4.2)
144 |
145 | PLATFORMS
146 | x86_64-darwin-19
147 |
148 | DEPENDENCIES
149 | block_editor!
150 | sqlite3
151 |
152 | BUNDLED WITH
153 | 2.2.11
154 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2021 Patrick Lindsay
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Block Editor for Ruby on Rails
2 | This editor uses packages from the [Wordpress Gutenberg project](https://github.com/WordPress/gutenberg) to build a standalone block editor for Rails. This editor has been extracted from [Integral CMS](https://github.com/yamasolutions/integral) where it was built following the [Wordpress custom block editor tutorial](https://developer.wordpress.org/block-editor/how-to-guides/platform/custom-block-editor/).
3 |
4 | The editor currently uses the v9.2.1 Gutenberg release packages which were part of the Wordpress v5.6 release.
5 |
6 | Looking for a demo? Checkout this [simple host application example.](https://block-editor-rails.herokuapp.com/)
7 |
8 | More information;
9 |
10 | * [Simple host application example source repository](https://github.com/yamasolutions/block-editor-sample)
11 | * [Current Wordpress Editor Demo](https://wordpress.org/gutenberg/)
12 | * [Gutenberg Block Editor Developer Documentation](https://developer.wordpress.org/block-editor/)
13 |
14 | ## Installation
15 | Add this line to your application's Gemfile:
16 |
17 | ```ruby
18 | gem 'block_editor'
19 | ```
20 |
21 | And then execute:
22 | ```bash
23 | $ bundle
24 | ```
25 |
26 | Or install it yourself as:
27 | ```bash
28 | $ gem install block_editor
29 | ```
30 |
31 | ## Usage
32 |
33 | * Grab the required migrations and migrate;
34 | ```
35 | rails block_editor:install:migrations
36 | rails db:migrate
37 | ```
38 | * Add `include BlockEditor::Listable` to any model you wish to associate the block editor with, i.e.
39 |
40 | ```
41 | class Post < ApplicationRecord
42 | include BlockEditor::Listable
43 | end
44 | ```
45 | * Add the block editor to your model form;
46 | ```
47 | <%= form.fields_for :active_block_list do |block_list| %>
48 | <%= BlockEditor::Instance.render(block_list) %>
49 | <% end %>
50 | ```
51 | * Add the block editor Javascript and styles within your `HEAD` tag
52 | ```
53 | <%= javascript_pack_tag 'block_editor/application', 'data-turbolinks-track': 'reload', webpacker: 'BlockEditor' %>
54 | <%= stylesheet_pack_tag 'block_editor/application', 'data-turbolinks-track': 'reload', webpacker: 'BlockEditor' %>
55 | ```
56 | * Boom! You have a Block Editor linked to your model
57 |
58 | Note: If the error `Webpacker::Manifest::MissingEntryError` appears you need to run the following command to precompile the BlockEditor assets;
59 | ```
60 | rails block_editor:webpacker:compile
61 | ```
62 |
63 | ### Strong Parameters
64 | Remember to permit the block list attributes if you're using Strong Parameters (you should be), i.e;
65 | ```
66 | # Only allow a list of trusted parameters through.
67 | def post_params
68 | params.require(:post).permit(:title, active_block_list_attributes: [ :id, :content ])
69 | end
70 | ```
71 |
72 | Note: You may also need to specifically set the listable attribute within your controller before saving, i.e. -
73 | ```
74 | @post.active_block_list.listable = @post
75 | ```
76 |
77 | ### Styling
78 |
79 | BlockEditor has a soft dependency on Bootstrap. You will need to add this to your application in order for the default styles to be compiled. If you do not want to use the default styles do not include them in the your application stylesheet and override BlockEditor's backend stylesheet (`block_editor/frontend.scss`) with whatever custom styles you want to include.
80 |
81 | #### Frontend
82 | Include the default styles into your application stylesheet;
83 | ```
84 | @import "block_editor/blocks/frontend";
85 | @import "block_editor/utilities";
86 | ```
87 |
88 | ### Backend
89 | Add the backend stylesheet where you are rendering the block editor, for example admin dashboard;
90 | ```
91 | <%= stylesheet_link_tag 'block_editor/backend', media: 'all', 'data-turbolinks-track': 'reload' %>
92 | ```
93 |
94 | ### Overriding and/or adding custom styles
95 | The below files are provided by BlockEditor as entry points. Override them if you want to provide custom styling to your blocks;
96 | * `app/assets/stylesheets/block_editor/host_app/blocks/_frontend.scss` - Any styles that should be displayed within the frontend and backend
97 | * `app/assets/stylesheets/block_editor/host_app/blocks/_backend.scss` - Any styles that should _only_ be displayed within the block editor itself, i.e when creating or editing the blocks
98 |
99 | ### Media Uploader & Images
100 | There is no built in MediaUploader or media gallery, it is up to the host application to implement this.
101 |
102 | When the media uploader is requested the Block Editor checks if `window.BlockEditorMediaUploader` is defined. If it is defined the block editor will call `window.BlockEditorMediaUploader.open(callback)`, otherwise it will randomly select an image from [Unsplash](https://unsplash.com)
103 |
104 | When an image is successfully uploaded or selected the BlockEditorMediaUploader instance should call the callback which was passed into the `open` function;
105 | ```
106 | callback({url: imageUrl})
107 | ```
108 |
109 | ### Turbolinks
110 | Currently Block Editor is not compatible with Turbolinks as history is only being reset on full page loads. To disable Turbolinks per page add the following in your layout view file within your `
`;;
111 | ```
112 |
113 | ```
114 |
115 | ### Reusable Blocks
116 |
117 | BlockEditor will check the following endpoints for any available reusable blocks, if any are found they will appear in the inserter menus
118 | ```
119 | get '/wp/v2/types', to: 'backend/block_lists#wp_types'
120 | get '/wp/v2/types/wp_block', to: 'backend/block_lists#wp_type'
121 | get '/wp/v2/block_lists', to: 'backend/block_lists#block_lists'
122 | get '/wp/v2/block_list/:id', to: 'backend/block_lists#show'
123 | get '/wp/v2/blocks/:id', to: 'backend/block_lists#show'
124 | ```
125 |
126 | For an example of what the BlockEditor is expecting from these endpoints checkout how [Integral CMS has implemented this](https://github.com/yamasolutions/integral/blob/master/app/controllers/integral/backend/block_lists_controller.rb)
127 |
128 | ### Adding/Removing blocks
129 | *Currently there isn't a way of adding or removing blocks without forking the gem.*
130 |
131 | * Fork the gem
132 | * Edit `app/javascript/block_editor/blocks/index.js` where all blocks are registered
133 |
134 |
135 | ### Dynamic blocks
136 | Dynamic blocks are useful for non-static content such as a recent posts block or more complex blocks like a contact form block.
137 |
138 | *Currently the gem needs to be forked in order to create a dynamic block*
139 |
140 | A dynamic block is made up of 4 components;
141 | * Ruby class to handle frontend rendering
142 | * Partial to be rendered on the frontend
143 | * Frontend and backend styling
144 | * JS file to manage how the block behaves within the block editor itself (This part currently is not possible without forking the gem) [Read More](https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/writing-your-first-block-type)
145 |
146 | 1. Create block class which can be as simple as;
147 | ```
148 | class RecentPosts < BlockEditor::Base
149 | def self.name
150 | 'block-editor/recent-posts'
151 | end
152 | end
153 | ```
154 | 2. Add block class to ```BlockEditor.dynamic_blocks``` config;
155 | ```
156 | // application.rb
157 |
158 | BlockEditor.dynamic_blocks = [RecentPosts]
159 | ```
160 | 3. Create the view partial you want to be rendered as the block;
161 | ```
162 | // app/views/block_editor/blocks/block-editor/recent-posts/_block.html.erb
163 |
164 | My Recent Posts
165 | <%= @posts %>
166 | ```
167 | 4. Add any required styling to the frontend and backend stylesheets;
168 |
169 | ```
170 | app/assets/stylesheets/block_editor/host_app/blocks/_frontend.scss
171 | ```
172 |
173 | ```
174 | app/assets/stylesheets/block_editor/host_app/blocks/_backend.scss
175 | ```
176 | 5. Add the block to the block editor
177 | * Fork the gem
178 | * Create the block JS file i.e. `app/javascript/block_editor/blocks/contact_form/index.js`
179 | * Edit `app/javascript/block_editor/blocks/index.js` to register the block
180 |
181 | ## Contributing
182 | Contribution are very welcome! Currently the biggest issue that needs to be solved is extensibility. There is no way to modify and configure the editor behaviour (such as blocks to display, block output etc) without forking the engine.
183 |
184 | ## License
185 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
186 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/setup"
2 |
3 | APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4 | load "rails/tasks/engine.rake"
5 |
6 | load "rails/tasks/statistics.rake"
7 |
8 | require "bundler/gem_tasks"
9 |
10 | require "rake/testtask"
11 |
12 | Rake::TestTask.new(:test) do |t|
13 | t.libs << 'test'
14 | t.pattern = 'test/**/*_test.rb'
15 | t.verbose = false
16 | end
17 |
18 | task default: :test
19 |
--------------------------------------------------------------------------------
/app/assets/config/block_editor_manifest.js:
--------------------------------------------------------------------------------
1 | //= link_directory ../stylesheets/block_editor .css
2 |
--------------------------------------------------------------------------------
/app/assets/images/block_editor/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yamasolutions/block-editor/3454f449b22a041191a6eee53c645e0d764841d3/app/assets/images/block_editor/.keep
--------------------------------------------------------------------------------
/app/assets/images/block_editor/contact-form-block.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/block_editor/_utilities.scss:
--------------------------------------------------------------------------------
1 | .has-text-align-center {
2 | text-align: center;
3 | }
4 |
5 | .has-text-align-left {
6 | text-align: left;
7 | }
8 |
9 | .has-text-align-right {
10 | text-align: right;
11 | }
12 |
13 | .wp-block[data-align='center'] {
14 | text-align: center;
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/block_editor/backend.scss:
--------------------------------------------------------------------------------
1 | // Bootstrap functions (used within variables)
2 | @import "bootstrap/scss/functions";
3 |
4 | // Custom variables
5 | @import "block_editor/host_app/variables";
6 |
7 | // Bootstrap default variables, mixins & utilities
8 | @import "bootstrap/scss/variables";
9 | @import "bootstrap/scss/mixins";
10 | @import "bootstrap/scss/utilities";
11 |
12 | .editor-styles-wrapper {
13 | background-color: $body-bg;
14 | color: $body-color;
15 |
16 | textarea {
17 | min-height: unset;
18 | width: 100%;
19 | background-color: transparent;
20 | }
21 |
22 | .block-editor-image-placeholder {
23 | display: flex;
24 | height: 13rem;
25 | background-color: $gray-300;
26 | justify-content: center;
27 | align-items: center;
28 | }
29 |
30 | // Bootstrap components
31 | @import "bootstrap/scss/root";
32 | @import "bootstrap/scss/reboot";
33 | @import "bootstrap/scss/type";
34 | @import "bootstrap/scss/images";
35 | @import "bootstrap/scss/containers";
36 | @import "bootstrap/scss/grid";
37 | @import "bootstrap/scss/tables";
38 | @import "bootstrap/scss/forms";
39 | @import "bootstrap/scss/buttons";
40 | @import "bootstrap/scss/transitions";
41 | @import "bootstrap/scss/dropdown";
42 | @import "bootstrap/scss/button-group";
43 | @import "bootstrap/scss/nav";
44 | @import "bootstrap/scss/navbar";
45 | @import "bootstrap/scss/card";
46 | @import "bootstrap/scss/accordion";
47 | @import "bootstrap/scss/breadcrumb";
48 | @import "bootstrap/scss/pagination";
49 | @import "bootstrap/scss/badge";
50 | @import "bootstrap/scss/alert";
51 | @import "bootstrap/scss/progress";
52 | @import "bootstrap/scss/list-group";
53 | @import "bootstrap/scss/close";
54 | @import "bootstrap/scss/toasts";
55 | @import "bootstrap/scss/modal";
56 | @import "bootstrap/scss/tooltip";
57 | @import "bootstrap/scss/popover";
58 | @import "bootstrap/scss/carousel";
59 | @import "bootstrap/scss/spinners";
60 | @import "bootstrap/scss/offcanvas";
61 | @import "bootstrap/scss/helpers"; // Helpers
62 | @import "bootstrap/scss/utilities/api"; // Utilities
63 |
64 | // Block editor blocks
65 | @import "block_editor/blocks/frontend";
66 | @import "block_editor/blocks/backend";
67 | @import "block_editor/utilities";
68 |
69 | // Custom blocks & extensions
70 | @import "block_editor/host_app/blocks/frontend";
71 | @import "block_editor/host_app/blocks/backend";
72 | }
73 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/block_editor/blocks/_backend.scss:
--------------------------------------------------------------------------------
1 | @import "block_editor/blocks/be-card/backend";
2 | @import "block_editor/blocks/be-contact-form/backend";
3 | @import "block_editor/blocks/be-cover/backend";
4 | @import "block_editor/blocks/block/backend";
5 | @import "block_editor/blocks/buttons/backend";
6 | @import "block_editor/blocks/columns/backend";
7 | @import "block_editor/blocks/image/backend";
8 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/block_editor/blocks/_frontend.scss:
--------------------------------------------------------------------------------
1 | @import "block_editor/blocks/be-accordion/frontend";
2 | @import "block_editor/blocks/be-alert/frontend";
3 | @import "block_editor/blocks/be-card/frontend";
4 | @import "block_editor/blocks/be-cover/frontend";
5 | @import "block_editor/blocks/button/frontend";
6 | @import "block_editor/blocks/buttons/frontend";
7 | @import "block_editor/blocks/column/frontend";
8 | @import "block_editor/blocks/columns/frontend";
9 | @import "block_editor/blocks/image/frontend";
10 | @import "block_editor/blocks/seperator/frontend";
11 | @import "block_editor/blocks/table/frontend";
12 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/block_editor/blocks/be-accordion/_frontend.scss:
--------------------------------------------------------------------------------
1 | .wp-block-be-accordion {
2 | margin-bottom: $paragraph-margin-bottom;
3 | }
4 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/block_editor/blocks/be-alert/_frontend.scss:
--------------------------------------------------------------------------------
1 | .wp-block-be-alert {
2 | @each $state, $value in $theme-colors {
3 | $alert-background: shift-color($value, $alert-bg-scale);
4 | $alert-border: shift-color($value, $alert-border-scale);
5 | $alert-color: shift-color($value, $alert-color-scale);
6 | @if (contrast-ratio($alert-background, $alert-color) < $min-contrast-ratio) {
7 | $alert-color: mix($value, color-contrast($alert-background), abs($alert-color-scale));
8 | }
9 | &.is-style-#{$state} {
10 | @include alert-variant($alert-background, $alert-border, $alert-color);
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/block_editor/blocks/be-card/_backend.scss:
--------------------------------------------------------------------------------
1 | .block-editor-block-list__block[data-type="be/card"] {
2 | .components-button {
3 | position: absolute;
4 | background-color: $primary;
5 | color: $white;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/block_editor/blocks/be-card/_frontend.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yamasolutions/block-editor/3454f449b22a041191a6eee53c645e0d764841d3/app/assets/stylesheets/block_editor/blocks/be-card/_frontend.scss
--------------------------------------------------------------------------------
/app/assets/stylesheets/block_editor/blocks/be-contact-form/_backend.scss:
--------------------------------------------------------------------------------
1 | .block-editor-block-list__block[data-type="be/contact-form"] > div {
2 | display: flex;
3 | height: 300px;
4 | padding: 3rem;
5 | margin: 1rem 0;
6 | align-items: center;
7 | justify-content: center;
8 | background-color: $white;
9 | p {
10 | font-size: 1.25rem;
11 | margin-right: 3rem;
12 | }
13 | .be-contact-form-outline {
14 | background-image: image_url('block_editor/contact-form-block.svg');
15 | height: 100%;
16 | width: 25%;
17 | background-repeat: no-repeat;
18 | background-size: contain;
19 | background-position: center;
20 | }
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/block_editor/blocks/be-cover/_backend.scss:
--------------------------------------------------------------------------------
1 | .block-editor-block-list__block[data-type="be/cover"] {
2 | .components-button {
3 | position: absolute;
4 | background-color: $primary;
5 | color: $white;
6 | z-index: 1;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/block_editor/blocks/be-cover/_frontend.scss:
--------------------------------------------------------------------------------
1 | .wp-block-be-cover {
2 | position: relative;
3 | padding: 5rem 1rem;
4 | margin-bottom: $paragraph-margin-bottom;
5 | background-color: $primary;
6 |
7 | @include media-breakpoint-up(md) {
8 | padding: 5*$spacer;
9 | }
10 |
11 | &-image {
12 | position: absolute;
13 | width: 100%;
14 | max-width: 100%;
15 | object-fit: cover;
16 | width: 100%;
17 | height: 100%;
18 | top: 0;
19 | left: 0;
20 | filter: brightness(80%);
21 | }
22 |
23 | &-content {
24 | padding: $spacer;
25 | background: $body-bg;
26 | position: relative;
27 | min-height: 10rem;
28 | width: 100%;
29 | background: none;
30 | color: $white;
31 | text-align: center;
32 | }
33 |
34 | &.is-style-secondary {
35 | background-color: $secondary;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/block_editor/blocks/block/_backend.scss:
--------------------------------------------------------------------------------
1 | .block-editor-block-list__block[data-type="core/block"] {
2 | .block-editor-block-list__layout.is-root-container {
3 | padding: 0;
4 | }
5 | .reusable-block-edit-panel__button {
6 | display: none;
7 | }
8 | .block-list-appender {
9 | display: none;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/block_editor/blocks/button/_frontend.scss:
--------------------------------------------------------------------------------
1 | .wp-block-button {
2 | display: inline-block;
3 |
4 | &__link {
5 | display: inline-block;
6 | font-family: $btn-font-family;
7 | font-weight: $btn-font-weight;
8 | line-height: $btn-line-height;
9 | color: $body-color;
10 | text-align: center;
11 | text-decoration: if($link-decoration == none, null, none);
12 | white-space: $btn-white-space;
13 | vertical-align: middle;
14 | cursor: if($enable-button-pointers, pointer, null);
15 | user-select: none;
16 | background-color: transparent;
17 | border: $btn-border-width solid transparent;
18 | @include button-size($btn-padding-y, $btn-padding-x, $btn-font-size, $btn-border-radius);
19 | @include transition($btn-transition);
20 | // @include button-variant($primary, $white);
21 | &:hover {
22 | color: $body-color;
23 | text-decoration: if($link-hover-decoration == underline, none, null);
24 | }
25 | }
26 | &.large {
27 | .wp-block-button__link{
28 | @include button-size($btn-padding-y-lg, $btn-padding-x-lg, $btn-font-size-lg, $btn-border-radius-lg);
29 | }
30 | }
31 |
32 | @each $color, $value in $theme-colors {
33 | &.is-style-#{$color} {
34 | .wp-block-button__link{
35 | @include button-variant($value, $value);
36 | }
37 | }
38 | }
39 | @each $color, $value in $theme-colors {
40 | &.is-style-outline-#{$color} {
41 | .wp-block-button__link{
42 | @include button-outline-variant($value);
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/block_editor/blocks/buttons/_backend.scss:
--------------------------------------------------------------------------------
1 | .block-editor-block-list__block[data-type="core/buttons"] {
2 | &.is-selected {
3 | .block-list-appender {
4 | display: initial;
5 | }
6 | }
7 | .block-list-appender {
8 | display: none;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/block_editor/blocks/buttons/_frontend.scss:
--------------------------------------------------------------------------------
1 | .wp-block-buttons {
2 | margin-bottom: $paragraph-margin-bottom;
3 | }
4 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/block_editor/blocks/column/_frontend.scss:
--------------------------------------------------------------------------------
1 | .wp-block-column {
2 | flex: 1;
3 | padding-right: calc(var(--#{$variable-prefix}gutter-x) / 2); // stylelint-disable-line function-disallowed-list
4 | padding-left: calc(var(--#{$variable-prefix}gutter-x) / 2); // stylelint-disable-line function-disallowed-list
5 | margin-top: var(--#{$variable-prefix}gutter-y);
6 |
7 | &.is-vertically-aligned-top {
8 | align-self: flex-start;
9 | }
10 |
11 | &.is-vertically-aligned-center {
12 | align-self: center;
13 | }
14 |
15 | &.is-vertically-aligned-bottom {
16 | align-self: flex-end;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/block_editor/blocks/columns/_backend.scss:
--------------------------------------------------------------------------------
1 | .block-editor-block-list__block[data-type="core/columns"] {
2 | > .block-editor-inner-blocks {
3 | flex: 1;
4 | padding-right: calc(var(--#{$variable-prefix}gutter-x) / 2); // stylelint-disable-line function-disallowed-list
5 | padding-left: calc(var(--#{$variable-prefix}gutter-x) / 2); // stylelint-disable-line function-disallowed-list
6 | margin-top: var(--#{$variable-prefix}gutter-y);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/block_editor/blocks/columns/_frontend.scss:
--------------------------------------------------------------------------------
1 | .wp-block-columns {
2 | --#{$variable-prefix}gutter-x: #{$grid-gutter-width};
3 | --#{$variable-prefix}gutter-y: 0;
4 | display: flex;
5 | margin-top: calc(var(--#{$variable-prefix}gutter-y) * -1); // stylelint-disable-line function-disallowed-list
6 | margin-right: calc(var(--#{$variable-prefix}gutter-x) / -2); // stylelint-disable-line function-disallowed-list
7 | margin-left: calc(var(--#{$variable-prefix}gutter-x) / -2); // stylelint-disable-line function-disallowed-list
8 |
9 | &:not(.is-style-no-stack) {
10 | @include media-breakpoint-down(lg) {
11 | display: block;
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/block_editor/blocks/image/_backend.scss:
--------------------------------------------------------------------------------
1 | .block-editor-block-list__block[data-type="core/image"] {
2 | .block-editor-media-placeholder__url-input-container,
3 | .components-form-file-upload {
4 | display: none;
5 | }
6 | }
7 | .components-dropdown-menu__toggle[aria-label="Change alignment"] {
8 | display: none !important;
9 | }
10 |
11 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/block_editor/blocks/image/_frontend.scss:
--------------------------------------------------------------------------------
1 | .wp-block-image {
2 | margin-bottom: $paragraph-margin-bottom;
3 |
4 | img {
5 | max-width: 100%;
6 | }
7 | figcaption {
8 | margin-top: 5px;
9 | font-size: 0.9rem;
10 | }
11 | &.is-style-padded {
12 | max-width: 75%;
13 | margin-left: auto;
14 | margin-right: auto;
15 | }
16 | &.is-style-rounded {
17 | img {
18 | border-radius: $border-radius;
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/block_editor/blocks/seperator/_frontend.scss:
--------------------------------------------------------------------------------
1 | .wp-block-separator {
2 | &.is-style-dots {
3 | background: none !important;
4 | border: none;
5 | text-align: center;
6 | max-width: none;
7 | line-height: 1;
8 | height: 1.2*$spacer !important;
9 | opacity: 1;
10 | &:before{
11 | content: "\00b7 \00b7 \00b7";
12 | font-size: 1.2*$spacer;
13 | letter-spacing: 2em;
14 | padding-left: 2em;
15 | font-family: serif;
16 | color: $black;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/block_editor/blocks/table/_frontend.scss:
--------------------------------------------------------------------------------
1 | .wp-block-table{
2 | margin-bottom: $paragraph-margin-bottom;
3 | overflow-x: auto;
4 | table {
5 | --#{$variable-prefix}table-bg: #{$table-bg};
6 | --#{$variable-prefix}table-striped-color: #{$table-striped-color};
7 | --#{$variable-prefix}table-striped-bg: #{$table-striped-bg};
8 | --#{$variable-prefix}table-active-color: #{$table-active-color};
9 | --#{$variable-prefix}table-active-bg: #{$table-active-bg};
10 | --#{$variable-prefix}table-hover-color: #{$table-hover-color};
11 | --#{$variable-prefix}table-hover-bg: #{$table-hover-bg};
12 |
13 | width: 100%;
14 | color: $table-color;
15 | vertical-align: $table-cell-vertical-align;
16 | border-color: $table-border-color;
17 | overflow-x: auto;
18 | -webkit-overflow-scrolling: touch;
19 | > :not(caption) > * > * {
20 | padding: $table-cell-padding-y $table-cell-padding-x;
21 | background-color: var(--#{$variable-prefix}table-bg);
22 | border-bottom-width: $table-border-width;
23 | box-shadow: inset 0 0 0 9999px var(--#{$variable-prefix}table-accent-bg);
24 | }
25 |
26 | > tbody {
27 | vertical-align: inherit;
28 | }
29 | > tbody > tr > th {
30 | text-align: center;
31 | }
32 |
33 | > thead {
34 | vertical-align: bottom;
35 | background: $secondary;
36 | color: $white;
37 | }
38 | > thead > tr > th:first-child{
39 | text-align: center;
40 | }
41 |
42 | // Highlight border color between thead, tbody and tfoot.
43 | > :not(:last-child) > :last-child > * {
44 | border-bottom-color: $table-group-separator-color;
45 | }
46 | //border
47 | > :not(caption) > * {
48 | border-width: $table-border-width 0;
49 |
50 | // stylelint-disable-next-line selector-max-universal
51 | > * {
52 | border-width: 0 $table-border-width;
53 | }
54 | }
55 | }
56 |
57 | > figcaption {
58 | margin-top: $paragraph-margin-bottom / 4;
59 | }
60 |
61 | &.is-style-striped {
62 | table > tbody > tr:nth-of-type(#{$table-striped-order}) {
63 | --#{$variable-prefix}table-accent-bg: var(--#{$variable-prefix}table-striped-bg);
64 | color: var(--#{$variable-prefix}table-striped-color);
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/block_editor/host_app/_variables.scss:
--------------------------------------------------------------------------------
1 | // Override this file if you want to include any styling before default Block Editor styles are loaded. This would be a good place to add variables to customise Bootstrap
2 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/block_editor/host_app/blocks/_backend.scss:
--------------------------------------------------------------------------------
1 | // Override this file if you want to include backend block styles to the Block Editor and not have to worry about losing any base styles
2 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/block_editor/host_app/blocks/_frontend.scss:
--------------------------------------------------------------------------------
1 | // Override this file if you want to include frontend block styles to the Block Editor and not have to worry about losing any base styles
2 |
--------------------------------------------------------------------------------
/app/controllers/block_editor/application_controller.rb:
--------------------------------------------------------------------------------
1 | module BlockEditor
2 | class ApplicationController < ActionController::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/app/helpers/block_editor/application_helper.rb:
--------------------------------------------------------------------------------
1 | require "webpacker/helper"
2 |
3 | module BlockEditor
4 | module ApplicationHelper
5 | include ::Webpacker::Helper
6 |
7 | def current_webpacker_instance
8 | BlockEditor.webpacker
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/app/javascript/block_editor/blocks/be-accordion/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import classnames from 'classnames';
4 |
5 | import { PanelBody, ToggleControl } from '@wordpress/components';
6 | import { InnerBlocks, InspectorControls, PlainText } from '@wordpress/block-editor'
7 | import { box as icon } from '@wordpress/icons';
8 |
9 | const name = 'be/accordion';
10 |
11 | export { name };
12 |
13 | export const settings = {
14 | title: 'Accordion',
15 | description: 'Accordions are elements that help you organize and navigate multiple documents in a single container.',
16 | icon,
17 | category: 'layout',
18 | attributes: {
19 | blockId: {
20 | type: 'string'
21 | },
22 | title: {
23 | source: 'text',
24 | selector: '.accordion-button'
25 | },
26 | isOpenByDefault: {
27 | type: 'boolean',
28 | default: true
29 | }
30 | },
31 | example: {
32 | attributes: {
33 | title: 'Open by default accordion'
34 | },
35 | innerBlocks: [
36 | {
37 | name: 'core/paragraph',
38 | attributes: {
39 | content: 'Use an accordion to structure and optionally collapse content'
40 | }
41 | }
42 | ]
43 | },
44 | edit({clientId, attributes, className, setAttributes, isSelected}) {
45 | const { blockId } = attributes;
46 | if ( ! blockId ) {
47 | setAttributes( { blockId: `id-${clientId}` } );
48 | } else if ( blockId != clientId ) {
49 | setAttributes( { blockId: `id-${clientId}` } );
50 | }
51 |
52 | // TODO: Update this to check if this OR any of the innerblocks are selected
53 | // let inlineStyle = (isSelected || attributes.isOpenByDefault) ? { display: 'block' } : {};
54 | let inlineStyle = { display: 'block' };
55 |
56 | return [
57 |
58 |
59 | setAttributes({ isOpenByDefault: content }) }
62 | checked={ attributes.isOpenByDefault }
63 | />
64 |
65 |
66 | ,
67 |
68 |
69 |
70 |
71 | setAttributes({ title: content }) }
73 | value={ attributes.title }
74 | placeholder="Your accordion title"
75 | className="accordion-button"
76 | />
77 |
78 |
83 |
84 |
85 | ];
86 | },
87 | save({ attributes }) {
88 | let buttonVisibilityClass = attributes.isOpenByDefault ? "collapsed" : "";
89 | let accordionVisibilityClass = attributes.isOpenByDefault ? "show" : "";
90 |
91 | return (
92 |
93 |
94 |
95 |
98 |
99 |
104 |
105 |
106 | );
107 | }
108 | };
109 |
--------------------------------------------------------------------------------
/app/javascript/block_editor/blocks/be-alert/index.js:
--------------------------------------------------------------------------------
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 | import { box as icon } from '@wordpress/icons';
11 |
12 | const name = 'be/alert';
13 |
14 | export { name };
15 |
16 | export const settings = {
17 | title: 'Callout (Alert)',
18 | description: 'Container to help draw attention to content.',
19 | icon,
20 | category: 'formatting',
21 | styles: [
22 | { name: 'primary', label: 'Primary', isDefault: true },
23 | { name: 'secondary', label: 'Secondary' },
24 | { name: 'success', label: 'Success' },
25 | { name: 'danger', label: 'Danger' },
26 | { name: 'warning', label: 'Warning' },
27 | { name: 'info', label: 'Info' },
28 | { name: 'light', label: 'Light' },
29 | { name: 'dark', label: 'Dark' }
30 | ],
31 | example: {
32 | innerBlocks: [
33 | {
34 | name: 'core/paragraph',
35 | attributes: {
36 | content: 'Use a callout to grab the users attention.'
37 | }
38 | }
39 | ]
40 | },
41 | edit(props) {
42 | return (
43 |
44 |
45 |
46 | );
47 | },
48 | save() {
49 | return
;
50 | }
51 | };
52 |
--------------------------------------------------------------------------------
/app/javascript/block_editor/blocks/be-card/index.js:
--------------------------------------------------------------------------------
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 { box as icon } from '@wordpress/icons';
14 |
15 | const name = 'be/card';
16 |
17 | export { name };
18 |
19 | export const settings = {
20 | title: 'Card',
21 | description: 'Group a piece of content in an eye catching container.',
22 | icon,
23 | category: 'formatting',
24 | example: {
25 | attributes: {
26 | title: 'Container example',
27 | imageUrl: 'https://s.w.org/images/core/5.3/MtBlanc1.jpg',
28 | }
29 | },
30 | attributes: {
31 | title: {
32 | source: 'text',
33 | selector: '.card-title'
34 | },
35 | body: {
36 | type: 'array',
37 | source: 'children',
38 | selector: '.card-content'
39 | },
40 | imageAlt: {
41 | attribute: 'alt',
42 | selector: '.card-img-top'
43 | },
44 | imageUrl: {
45 | attribute: 'src',
46 | selector: '.card-img-top'
47 | },
48 | hasCallToAction: {
49 | type: 'boolean'
50 | },
51 | callToAction: {
52 | type: 'text'
53 | },
54 | url: {
55 | type: 'text'
56 | },
57 | openInNewTab: {
58 | type: 'boolean'
59 | }
60 | },
61 | edit({attributes, className, setAttributes, isSelected}) {
62 | const getImageButton = (openEvent) => {
63 | if(attributes.imageUrl) {
64 | return (
65 | <>
66 | { isSelected &&
67 |
73 | }
74 |
78 | >
79 | );
80 | }
81 | else {
82 | return (
83 |
84 |
90 |
91 | );
92 | }
93 | };
94 | return ([
95 |
96 |
97 | setAttributes({ url: content }) }
101 | />
102 | { attributes.url && (
103 | setAttributes({ hasCallToAction: content }) }
107 | />
108 | )}
109 | { attributes.url && (
110 | setAttributes({ openInNewTab: content }) }
114 | />
115 | )}
116 |
117 | ,
118 |
119 |
{ setAttributes({ imageAlt: media.alt, imageUrl: media.url }); } }
121 | type="image"
122 | value={ attributes.imageID }
123 | render={ ({ open }) => getImageButton(open) }
124 | />
125 |
126 |
setAttributes({ title: content }) }
128 | value={ attributes.title }
129 | placeholder="Your card title"
130 | className="card-title h2"
131 | />
132 | setAttributes({ body: content }) }
134 | value={ attributes.body }
135 | multiline="p"
136 | placeholder="Your card text"
137 | />
138 |
139 | { attributes.hasCallToAction && attributes.url &&
140 | setAttributes({ callToAction: content }) }
142 | value={ attributes.callToAction }
143 | placeholder="Your Call To Action"
144 | className="card-link"
145 | />
146 | }
147 |
148 | ]);
149 | },
150 | save({ attributes }) {
151 | const linkTarget = (attributes.openInNewTab) ? '_blank' : '_self';
152 | const cardImage = (src, alt) => {
153 | if(!src) return null;
154 |
155 | return (
156 |
161 | );
162 | }
163 |
164 | return (
165 |
203 | );
204 | }
205 | };
206 |
--------------------------------------------------------------------------------
/app/javascript/block_editor/blocks/be-contact-form/index.js:
--------------------------------------------------------------------------------
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 |
18 |
Contact Form
19 |
20 |
21 |
22 | ]);
23 | }
24 | };
25 |
--------------------------------------------------------------------------------
/app/javascript/block_editor/blocks/be-cover/index.js:
--------------------------------------------------------------------------------
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 |
73 | }
74 |
78 | >
79 | );
80 | }
81 | else {
82 | return (
83 | <>
84 | { isSelected &&
85 |
91 | }
92 | >
93 | );
94 | }
95 | };
96 | return (
97 |
98 |
{ setAttributes({ imageAlt: media.alt, imageUrl: media.url }); } }
100 | type="image"
101 | value={ attributes.imageID }
102 | render={ ({ open }) => getImageButton(open) }
103 | />
104 |
105 |
109 |
110 |
111 | );
112 | },
113 | save({ attributes }) {
114 | const cardImage = (src, alt) => {
115 | if(!src) return null;
116 |
117 | return (
118 |
123 | );
124 | }
125 |
126 | return (
127 |
128 | { cardImage(attributes.imageUrl, attributes.imageAlt) }
129 |
130 |
131 |
132 |
133 | );
134 | },
135 | };
136 |
--------------------------------------------------------------------------------
/app/javascript/block_editor/blocks/block/edit-panel/index.js:
--------------------------------------------------------------------------------
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 |
87 | { title }
88 |
97 |
98 | ) }
99 | { ( isEditing || isSaving ) && (
100 |
129 | ) }
130 | >
131 | );
132 | }
133 |
--------------------------------------------------------------------------------
/app/javascript/block_editor/blocks/block/edit.js:
--------------------------------------------------------------------------------
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 |
110 | );
111 | }
112 |
113 | if ( ! reusableBlock ) {
114 | return (
115 |
116 |
117 | { __( 'Block has been deleted or is unavailable.' ) }
118 |
119 |
120 | );
121 | }
122 |
123 | let element = (
124 |
130 |
131 |
132 |
133 |
134 | );
135 |
136 | if ( ! isEditing ) {
137 | element = { element };
138 | }
139 |
140 | return (
141 |
142 |
143 | { ( isSelected || isEditing ) && (
144 | setIsEditing( true ) }
150 | onChangeTitle={ ( title ) =>
151 | editEntityRecord( ...recordArgs, { title } )
152 | }
153 | onSave={ () => {
154 | save();
155 | setIsEditing( false );
156 | } }
157 | />
158 | ) }
159 | { element }
160 |
161 |
162 | );
163 | }
164 |
--------------------------------------------------------------------------------
/app/javascript/block_editor/blocks/button/edit.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import PropTypes from 'prop-types'
4 |
5 | /**
6 | * External dependencies
7 | */
8 | import classnames from 'classnames';
9 |
10 | /**
11 | * WordPress dependencies
12 | */
13 | import { __ } from '@wordpress/i18n';
14 | import { useCallback, useState } from '@wordpress/element';
15 | import {
16 | KeyboardShortcuts,
17 | PanelBody,
18 | RangeControl,
19 | TextControl,
20 | ToggleControl,
21 | ToolbarButton,
22 | ToolbarGroup,
23 | Popover,
24 | } from '@wordpress/components';
25 | import {
26 | BlockControls,
27 | InspectorControls,
28 | RichText,
29 | __experimentalBlock as Block,
30 | __experimentalLinkControl as LinkControl,
31 | } from '@wordpress/block-editor';
32 | import { rawShortcut, displayShortcut } from '@wordpress/keycodes';
33 | import { link } from '@wordpress/icons';
34 | import { createBlock } from '@wordpress/blocks';
35 |
36 | // #<{(|*
37 | // * Internal dependencies
38 | // |)}>#
39 | // import ColorEdit from './color-edit';
40 | // import getColorAndStyleProps from './color-props';
41 |
42 | const NEW_TAB_REL = 'noreferrer noopener';
43 | const MIN_BORDER_RADIUS_VALUE = 0;
44 | const MAX_BORDER_RADIUS_VALUE = 50;
45 | const INITIAL_BORDER_RADIUS_POSITION = 5;
46 |
47 | function BorderPanel( { borderRadius = '', setAttributes } ) {
48 | const setBorderRadius = useCallback(
49 | ( newBorderRadius ) => {
50 | setAttributes( { borderRadius: newBorderRadius } );
51 | },
52 | [ setAttributes ]
53 | );
54 | return (
55 |
56 |
65 |
66 | );
67 | }
68 |
69 | function URLPicker( {
70 | isSelected,
71 | url,
72 | setAttributes,
73 | opensInNewTab,
74 | onToggleOpenInNewTab,
75 | } ) {
76 | const [ isURLPickerOpen, setIsURLPickerOpen ] = useState( false );
77 | const openLinkControl = () => {
78 | setIsURLPickerOpen( true );
79 |
80 | // prevents default behaviour for event
81 | return false;
82 | };
83 | const linkControl = isURLPickerOpen && (
84 | setIsURLPickerOpen( false ) }
87 | >
88 | {
95 | setAttributes( { url: newURL } );
96 |
97 | if ( opensInNewTab !== newOpensInNewTab ) {
98 | onToggleOpenInNewTab( newOpensInNewTab );
99 | }
100 | } }
101 | />
102 |
103 | );
104 | return (
105 | <>
106 |
107 |
108 |
115 |
116 |
117 | { isSelected && (
118 |
124 | ) }
125 | { linkControl }
126 | >
127 | );
128 | }
129 |
130 | function ButtonEdit( props ) {
131 | const {
132 | attributes,
133 | setAttributes,
134 | className,
135 | isSelected,
136 | onReplace,
137 | mergeBlocks,
138 | } = props;
139 | const {
140 | borderRadius,
141 | linkTarget,
142 | placeholder,
143 | rel,
144 | text,
145 | url,
146 | } = attributes;
147 | const onSetLinkRel = useCallback(
148 | ( value ) => {
149 | setAttributes( { rel: value } );
150 | },
151 | [ setAttributes ]
152 | );
153 |
154 | const onToggleOpenInNewTab = useCallback(
155 | ( value ) => {
156 | const newLinkTarget = value ? '_blank' : undefined;
157 |
158 | let updatedRel = rel;
159 | if ( newLinkTarget && ! rel ) {
160 | updatedRel = NEW_TAB_REL;
161 | } else if ( ! newLinkTarget && rel === NEW_TAB_REL ) {
162 | updatedRel = undefined;
163 | }
164 |
165 | setAttributes( {
166 | linkTarget: newLinkTarget,
167 | rel: updatedRel,
168 | } );
169 | },
170 | [ rel, setAttributes ]
171 | );
172 |
173 | // const colorProps = getColorAndStyleProps( attributes );
174 |
175 | return (
176 | <>
177 |
178 | setAttributes( { text: value } ) }
182 | withoutInteractiveFormatting
183 | className={ classnames(
184 | className,
185 | 'wp-block-button__link',
186 | // colorProps.className,
187 | // {
188 | // 'no-border-radius': borderRadius === 0,
189 | // }
190 | ) }
191 | onSplit={ ( value ) =>
192 | createBlock( 'core/button', {
193 | ...attributes,
194 | text: value,
195 | } )
196 | }
197 | onReplace={ onReplace }
198 | onMerge={ mergeBlocks }
199 | identifier="text"
200 | />
201 |
202 |
209 |
210 |
211 |
216 |
221 |
222 |
223 | >
224 | );
225 | }
226 |
227 | export default ButtonEdit;
228 |
--------------------------------------------------------------------------------
/app/javascript/block_editor/blocks/column/edit.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import classnames from 'classnames';
5 | import React from 'react';
6 | import ReactDOM from 'react-dom';
7 |
8 | /**
9 | * WordPress dependencies
10 | */
11 | import {
12 | InnerBlocks,
13 | BlockControls,
14 | BlockVerticalAlignmentToolbar,
15 | InspectorControls,
16 | __experimentalBlock as Block,
17 | } from '@wordpress/block-editor';
18 | import { PanelBody, RangeControl } from '@wordpress/components';
19 | import { withDispatch, withSelect } from '@wordpress/data';
20 | import { compose } from '@wordpress/compose';
21 | import { __ } from '@wordpress/i18n';
22 |
23 | function ColumnEdit( {
24 | attributes,
25 | setAttributes,
26 | updateAlignment,
27 | hasChildBlocks,
28 | } ) {
29 | const { verticalAlignment, width } = attributes;
30 |
31 | const classes = classnames( 'block-core-columns', {
32 | [ `is-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment,
33 | } );
34 |
35 | const hasWidth = Number.isFinite( width );
36 |
37 | return (
38 | <>
39 |
40 |
44 |
45 |
51 | }
52 | __experimentalTagName={ Block.div }
53 | __experimentalPassedProps={ {
54 | className: classes,
55 | style: hasWidth ? { flexBasis: width + '%' } : undefined,
56 | } }
57 | />
58 | >
59 | );
60 | }
61 |
62 | export default compose(
63 | withSelect( ( select, ownProps ) => {
64 | const { clientId } = ownProps;
65 | const { getBlockOrder } = select( 'core/block-editor' );
66 |
67 | return {
68 | hasChildBlocks: getBlockOrder( clientId ).length > 0,
69 | };
70 | } ),
71 | withDispatch( ( dispatch, ownProps, registry ) => {
72 | return {
73 | updateAlignment( verticalAlignment ) {
74 | const { clientId, setAttributes } = ownProps;
75 | const { updateBlockAttributes } = dispatch(
76 | 'core/block-editor'
77 | );
78 | const { getBlockRootClientId } = registry.select(
79 | 'core/block-editor'
80 | );
81 |
82 | // Update own alignment.
83 | setAttributes( { verticalAlignment } );
84 |
85 | // Reset Parent Columns Block
86 | const rootClientId = getBlockRootClientId( clientId );
87 | updateBlockAttributes( rootClientId, {
88 | verticalAlignment: null,
89 | } );
90 | },
91 | };
92 | } )
93 | )( ColumnEdit );
94 |
--------------------------------------------------------------------------------
/app/javascript/block_editor/blocks/index.js:
--------------------------------------------------------------------------------
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 BlockEdit from './block/edit';
31 | import MediaUpload from '../components/media-upload';
32 |
33 | import * as accordion from './be-accordion';
34 | import * as callout from './be-alert';
35 | import * as card from './be-card';
36 | import * as cover from './be-cover';
37 | // import * as recentPosts from './recent-posts';
38 | import * as contactForm from './be-contact-form';
39 |
40 | export const registerBlocks = () => {
41 | // TODO: Remove this when upgrading to 10.5 -> https://github.com/WordPress/gutenberg/pull/30194
42 | const replaceButtonBlockEdit = ( settings, name ) => {
43 | if ( name !== 'core/button' ) {
44 | return settings;
45 | }
46 |
47 | return assign( {}, settings, {
48 | edit: ButtonEdit // Removes border radius panel
49 | })
50 | }
51 |
52 | const replaceBlockEdit = ( settings, name ) => {
53 | if ( name !== 'core/block' ) {
54 | return settings;
55 | }
56 |
57 | return assign( {}, settings, {
58 | edit: BlockEdit // Removes 'convert to regular blocks' toolbar button
59 | })
60 | }
61 |
62 | const replaceColumnBlockEdit = ( settings, name ) => {
63 | if ( name !== 'core/column' ) {
64 | return settings;
65 | }
66 |
67 | return assign( {}, settings, {
68 | edit: ColumnEdit // Removes column width options
69 | } );
70 | }
71 |
72 | // Set default alignment to 'full' for all images
73 | const setDefaultAlignment = createHigherOrderComponent( ( BlockListBlock ) => {
74 | return ( props ) => {
75 | if ( props.name !== 'core/image' ) {
76 | return ;
77 | }
78 |
79 | props.attributes.align = 'full';
80 |
81 | return ;
82 | };
83 | }, 'setDefaultAlignment' );
84 |
85 | const replaceMediaUpload = () => MediaUpload;
86 |
87 | addFilter(
88 | 'editor.BlockListBlock',
89 | 'block-editor/filters/core-image-block-list',
90 | setDefaultAlignment
91 | );
92 |
93 | addFilter(
94 | 'blocks.registerBlockType',
95 | 'block-editor/filters/core-block',
96 | replaceBlockEdit
97 | );
98 |
99 | addFilter(
100 | 'blocks.registerBlockType',
101 | 'block-editor/filters/core-button',
102 | replaceButtonBlockEdit
103 | );
104 |
105 | addFilter(
106 | 'blocks.registerBlockType',
107 | 'block-editor/filters/core-column',
108 | replaceColumnBlockEdit
109 | );
110 |
111 | addFilter(
112 | 'editor.MediaUpload',
113 | 'block-editor/filters/media-upload',
114 | replaceMediaUpload
115 | );
116 |
117 | // Register WP blocks
118 | registerCoreBlocks();
119 |
120 | // Unregister WP blocks which are not supported
121 | unregisterBlockType('core/gallery');
122 | unregisterBlockType('core/quote');
123 | unregisterBlockType('core/shortcode');
124 | unregisterBlockType('core/archives');
125 | unregisterBlockType('core/audio');
126 | unregisterBlockType('core/calendar');
127 | unregisterBlockType('core/categories');
128 | unregisterBlockType('core/code');
129 | unregisterBlockType('core/cover');
130 | unregisterBlockType('core/embed');
131 | unregisterBlockType('core/file');
132 | unregisterBlockType('core/media-text');
133 | unregisterBlockType('core/latest-comments');
134 | unregisterBlockType('core/latest-posts');
135 | unregisterBlockType('core/more');
136 | unregisterBlockType('core/nextpage');
137 | unregisterBlockType('core/preformatted');
138 | unregisterBlockType('core/pullquote');
139 | unregisterBlockType('core/rss');
140 | unregisterBlockType('core/search');
141 | unregisterBlockType('core/social-links');
142 | unregisterBlockType('core/social-link');
143 | unregisterBlockType('core/spacer');
144 | unregisterBlockType('core/subhead');
145 | unregisterBlockType('core/tag-cloud');
146 | unregisterBlockType('core/text-columns');
147 | unregisterBlockType('core/verse');
148 | unregisterBlockType('core/video');
149 |
150 | // Unregister WP block styles
151 | unregisterBlockStyle('core/separator', 'wide');
152 | unregisterBlockStyle('core/button', 'fill');
153 | unregisterBlockStyle('core/button', 'outline');
154 | unregisterBlockStyle('core/image', 'default');
155 | unregisterBlockStyle('core/image', 'rounded');
156 | unregisterBlockStyle('core/table', 'regular');
157 | unregisterBlockStyle('core/table', 'stripes');
158 |
159 | // Unregister WP block variations
160 | unregisterBlockVariation('core/columns', 'two-columns-one-third-two-thirds');
161 | unregisterBlockVariation('core/columns', 'two-columns-two-thirds-one-third');
162 | unregisterBlockVariation('core/columns', 'three-columns-wider-center');
163 |
164 | // Register custom blocks
165 | registerBlockType(accordion.name, accordion.settings);
166 | registerBlockType(callout.name, callout.settings);
167 | registerBlockType(card.name, card.settings);
168 | registerBlockType(contactForm.name, contactForm.settings);
169 | registerBlockType(cover.name, cover.settings);
170 | // registerBlockType(recentPosts.name, recentPosts.settings);
171 |
172 | // Register custom block styles
173 | registerBlockStyle( 'core/button', {
174 | name: 'primary',
175 | label: 'Primary',
176 | isDefault: true
177 | } );
178 | registerBlockStyle( 'core/button', {
179 | name: 'secondary',
180 | label: 'Secondary'
181 | } );
182 | registerBlockStyle( 'core/button', {
183 | name: 'outline-primary',
184 | label: 'Primary (Outlined)'
185 | } );
186 | registerBlockStyle( 'core/button', {
187 | name: 'outline-secondary',
188 | label: 'Secondary (Outlined)'
189 | } );
190 | registerBlockStyle( 'core/table', {
191 | name: 'unstriped',
192 | label: 'Unstriped',
193 | isDefault: true
194 | } );
195 | registerBlockStyle( 'core/table', {
196 | name: 'striped',
197 | label: 'Striped'
198 | } );
199 | registerBlockStyle( 'core/image', {
200 | name: 'default',
201 | label: 'Default',
202 | isDefault: true
203 | } );
204 | registerBlockStyle( 'core/image', {
205 | name: 'padded',
206 | label: 'Padded'
207 | } );
208 | registerBlockStyle( 'core/columns', {
209 | name: 'no-stack',
210 | label: 'No Stacking'
211 | } );
212 | };
213 |
--------------------------------------------------------------------------------
/app/javascript/block_editor/components/block-editor/index.js:
--------------------------------------------------------------------------------
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, FullscreenMode } from '@wordpress/interface';
12 | import { useShortcut } from '@wordpress/keyboard-shortcuts';
13 | import {
14 | BlockEditorKeyboardShortcuts,
15 | BlockEditorProvider,
16 | BlockList,
17 | BlockInspector,
18 | WritingFlow,
19 | ObserveTyping,
20 | BlockBreadcrumb,
21 | __experimentalLibrary as Library,
22 | } from '@wordpress/block-editor';
23 | import {
24 | Popover,
25 | SlotFillProvider,
26 | DropZoneProvider,
27 | FocusReturnProvider,
28 | Button
29 | } from '@wordpress/components';
30 |
31 | /**
32 | * Internal dependencies
33 | */
34 | import Sidebar from '../sidebar';
35 | import Header from '../header';
36 | import Notices from '../notices';
37 | import PopoverWrapper from './popover-wrapper';
38 | import '../../stores'; // TODO: Think this store registering needs to be moved somewhere else so that it happens everytime a BlockEditor is initialized
39 |
40 | function BlockEditor( { input, settings: _settings } ) {
41 | const blocks = useSelect((select) => select("block-editor").getBlocks());
42 | const { updateBlocks } = useDispatch("block-editor");
43 | const __experimentalReusableBlocks = useSelect((select) => select( 'core' ).getEntityRecords('postType', 'wp_block', { per_page: -1 }));
44 | const settings = { ..._settings, __experimentalReusableBlocks };
45 |
46 | function handleInput(newBlocks, persist) {
47 | updateBlocks(newBlocks);
48 | input.value = serialize(newBlocks);
49 | }
50 |
51 | function handleChange(newBlocks) {
52 | updateBlocks(newBlocks, true);
53 | input.value = serialize(newBlocks);
54 | }
55 |
56 | // Registering the shortcuts
57 | const { registerShortcut } = useDispatch( 'core/keyboard-shortcuts' );
58 | useEffect( () => {
59 | registerShortcut( {
60 | name: 'core/editor/undo',
61 | category: 'global',
62 | description: 'Undo your last changes.',
63 | keyCombination: {
64 | modifier: 'primary',
65 | character: 'z',
66 | },
67 | } );
68 |
69 | registerShortcut( {
70 | name: 'core/editor/redo',
71 | category: 'global',
72 | description: 'Redo your last undo.',
73 | keyCombination: {
74 | modifier: 'primaryShift',
75 | character: 'z',
76 | },
77 | } );
78 | })
79 |
80 | const { redo, undo } = useDispatch( 'block-editor' );
81 |
82 | useShortcut(
83 | 'core/editor/undo',
84 | ( event ) => {
85 | undo();
86 | event.preventDefault();
87 | },
88 | { bindGlobal: true }
89 | );
90 |
91 | useShortcut(
92 | 'core/editor/redo',
93 | ( event ) => {
94 | redo();
95 | event.preventDefault();
96 | },
97 | { bindGlobal: true }
98 | );
99 |
100 | const { setIsInserterOpened } = useDispatch( 'block-editor' );
101 | const isInserterOpened = useSelect((select) => select("block-editor").isInserterOpened());
102 |
103 | return (
104 | <>
105 |
106 |
107 |
108 |
114 |
115 | }
117 | footer={}
118 | sidebar={}
119 | leftSidebar={
120 | isInserterOpened && (
121 | setIsInserterOpened( false ) }
123 | >
124 |
131 |
132 | )
133 | }
134 | content={
135 | <>
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 | >
148 | }
149 | />
150 |
151 |
152 |
153 |
154 |
155 | >
156 | );
157 | }
158 |
159 | export default BlockEditor;
160 |
--------------------------------------------------------------------------------
/app/javascript/block_editor/components/block-editor/popover-wrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 |
4 | /**
5 | * WordPress dependencies
6 | */
7 | import {
8 | withConstrainedTabbing,
9 | withFocusReturn,
10 | withFocusOutside,
11 | } from '@wordpress/components';
12 | import { Component } from '@wordpress/element';
13 | import { ESCAPE } from '@wordpress/keycodes';
14 |
15 | function stopPropagation( event ) {
16 | event.stopPropagation();
17 | }
18 |
19 | const DetectOutside = withFocusOutside(
20 | class extends Component {
21 | handleFocusOutside( event ) {
22 | this.props.onFocusOutside( event );
23 | }
24 |
25 | render() {
26 | return this.props.children;
27 | }
28 | }
29 | );
30 |
31 | const FocusManaged = withConstrainedTabbing(
32 | withFocusReturn( ( { children } ) => children )
33 | );
34 |
35 | export default function PopoverWrapper( { onClose, children, className } ) {
36 | // Event handlers
37 | const maybeClose = ( event ) => {
38 | // Close on escape
39 | if ( event.keyCode === ESCAPE && onClose ) {
40 | event.stopPropagation();
41 | onClose();
42 | }
43 | };
44 |
45 | // Disable reason: this stops certain events from propagating outside of the component.
46 | // - onMouseDown is disabled as this can cause interactions with other DOM elements
47 | /* eslint-disable jsx-a11y/no-static-element-interactions */
48 | return (
49 |
54 |
55 | { children }
56 |
57 |
58 | );
59 | /* eslint-enable jsx-a11y/no-static-element-interactions */
60 | }
61 |
--------------------------------------------------------------------------------
/app/javascript/block_editor/components/block-editor/styles.scss:
--------------------------------------------------------------------------------
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__inner-wrapper {
9 | -webkit-overflow-scrolling: touch;
10 | background: $white;
11 | height: calc(100% - #{$header-height});
12 | left: 0;
13 | overflow: auto;
14 | position: absolute;
15 | top: $header-height;
16 | width: 100%;
17 |
18 | @include break-small {
19 | width: calc(100% - #{$sidebar-width});
20 | }
21 | }
22 |
23 | // Grab the default Editor styles for visual consistency with WP
24 | .editor-styles-wrapper {
25 | // Important all other selectors scoped underneath
26 | // `div.editor-styles-wrapper`
27 | @import "~@wordpress/editor/src/editor-styles.scss";
28 | }
29 |
--------------------------------------------------------------------------------
/app/javascript/block_editor/components/header/index.js:
--------------------------------------------------------------------------------
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 { resizeCornerNE as minimizeIcon } from '@wordpress/icons';
10 | import { close as closeIcon } from '@wordpress/icons';
11 | import { plus as openIcon } from '@wordpress/icons';
12 | import { fullscreen as maximizeIcon } from '@wordpress/icons';
13 | import { useSelect, useDispatch } from '@wordpress/data';
14 |
15 | import HistoryUndo from './undo';
16 | import HistoryRedo from './redo';
17 |
18 | export default function Header() {
19 | const { setIsInserterOpened } = useDispatch( 'block-editor' );
20 | const isInserterOpened = useSelect((select) => select("block-editor").isInserterOpened());
21 |
22 | return (
23 |
28 |
29 | { !isInserterOpened &&
30 |
65 |
66 | );
67 | }
68 |
--------------------------------------------------------------------------------
/app/javascript/block_editor/components/header/redo.js:
--------------------------------------------------------------------------------
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 |
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;
37 |
--------------------------------------------------------------------------------
/app/javascript/block_editor/components/header/styles.scss:
--------------------------------------------------------------------------------
1 | .block-editor__header {
2 | padding: 0 .5rem;
3 | align-items: center;
4 | background: $white;
5 | border-bottom: 1px solid $gray-200;
6 | display: flex;
7 | height: $header-height;
8 | justify-content: space-between;
9 | left: 0;
10 | // Stick the toolbar to the top, because the admin bar is not fixed on mobile.
11 | position: sticky;
12 | right: 0;
13 | top: 0;
14 | z-index: 1;
15 | }
16 |
--------------------------------------------------------------------------------
/app/javascript/block_editor/components/header/undo.js:
--------------------------------------------------------------------------------
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 { undo as undoIcon } from '@wordpress/icons';
9 |
10 | function HistoryUndo( { hasUndo, undo, ...props } ) {
11 | return (
12 |
24 | );
25 | }
26 |
27 | const EnhancedHistoryUndo = compose( [
28 | withSelect( ( select ) => ( {
29 | hasUndo: select( 'block-editor' ).hasUndo(),
30 | } ) ),
31 | withDispatch( ( dispatch ) => ( {
32 | undo: dispatch( 'block-editor' ).undo,
33 | } ) ),
34 | ] )( HistoryUndo );
35 |
36 | export default EnhancedHistoryUndo;
37 |
--------------------------------------------------------------------------------
/app/javascript/block_editor/components/media-upload/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 |
4 | /**
5 | * WordPress dependencies
6 | */
7 | import { Component } from '@wordpress/element';
8 |
9 | class MediaUpload extends Component {
10 | constructor( {
11 | allowedTypes,
12 | gallery = false,
13 | unstableFeaturedImageFlow = false,
14 | modalClass,
15 | multiple = false,
16 | title = 'Select or Upload Media',
17 | } ) {
18 | super( ...arguments );
19 | this.openUploader = this.openUploader.bind( this );
20 | }
21 |
22 | openUploader() {
23 | if (window.BlockEditorMediaUploader == undefined) {
24 | console.warn('window.BlockEditorMediaUploader undefined. Pulling in random image from Unsplash')
25 |
26 | this.props.onSelect( { url: 'https://source.unsplash.com/random/800x500' });
27 | } else {
28 | window.BlockEditorMediaUploader.open(this.props.onSelect);
29 | }
30 | }
31 |
32 | render() {
33 | return this.props.render( { open: this.openUploader } );
34 | }
35 | }
36 |
37 | export default MediaUpload;
38 |
--------------------------------------------------------------------------------
/app/javascript/block_editor/components/notices/index.js:
--------------------------------------------------------------------------------
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 { SnackbarList } from '@wordpress/components';
9 |
10 | export default function Notices() {
11 | const notices = useSelect(
12 | ( select ) =>
13 | select( 'core/notices' )
14 | .getNotices()
15 | .filter( ( notice ) => notice.type === 'snackbar' ),
16 | []
17 | );
18 | const { removeNotice } = useDispatch( 'core/notices' );
19 | return (
20 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/app/javascript/block_editor/components/notices/styles.scss:
--------------------------------------------------------------------------------
1 | .edit-site-notices {
2 | bottom: 20px;
3 | padding-left: 16px;
4 | padding-right: 16px;
5 | position: fixed;
6 | right: 0;
7 | }
8 |
9 | @include editor-left(".edit-site-notices");
--------------------------------------------------------------------------------
/app/javascript/block_editor/components/sidebar/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 |
4 | /**
5 | * WordPress dependencies
6 | */
7 | import { createSlotFill, Panel } from '@wordpress/components';
8 | import { __ } from '@wordpress/i18n';
9 |
10 | const { Slot: InspectorSlot, Fill: InspectorFill } = createSlotFill(
11 | 'StandAloneBlockEditorSidebarInspector'
12 | );
13 |
14 | function Sidebar() {
15 | return (
16 |
22 |
23 |
24 | );
25 | }
26 |
27 | Sidebar.InspectorFill = InspectorFill;
28 |
29 | export default Sidebar;
30 |
--------------------------------------------------------------------------------
/app/javascript/block_editor/components/sidebar/styles.scss:
--------------------------------------------------------------------------------
1 | .block-editor__sidebar {
2 | background: $white;
3 | border-left: $border-width solid $gray-200;
4 | bottom: 0;
5 | height: 100vh;
6 | overflow: hidden;
7 | position: absolute;
8 | right: 0;
9 | top: 0;
10 | width: $sidebar-width;
11 | margin-bottom: 33px;
12 | //z-index: z-index(".edit-site-sidebar");
13 |
14 | @include break-small() {
15 | -webkit-overflow-scrolling: touch;
16 | height: auto;
17 | overflow: auto;
18 | // top: $admin-bar-height-big + $header-height;
19 | }
20 |
21 | // @include break-medium() {
22 | // top: $admin-bar-height + $header-height;
23 | // }
24 |
25 | @include break-small() {
26 | display: block;
27 | }
28 |
29 | > .components-panel {
30 | border-left: 0;
31 | border-right: 0;
32 | margin-bottom: -1px;
33 | margin-top: -1px;
34 |
35 | > .components-panel__header {
36 | background: $gray-200;
37 | }
38 | }
39 |
40 | .block-editor-block-inspector__card {
41 | margin: 0;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/javascript/block_editor/stores/action-types.js:
--------------------------------------------------------------------------------
1 | export const UPDATE_BLOCKS = "UPDATE_BLOCKS";
2 | export const PERSIST_BLOCKS = "PERSIST_BLOCKS";
3 | export const FETCH_BLOCKS_FROM_STORAGE = "FETCH_BLOCKS_FROM_STORAGE";
4 | export const PERSIST_BLOCKS_TO_STORAGE = "PERSIST_BLOCKS_TO_STORAGE";
5 |
--------------------------------------------------------------------------------
/app/javascript/block_editor/stores/actions.js:
--------------------------------------------------------------------------------
1 | import {
2 | UPDATE_BLOCKS,
3 | PERSIST_BLOCKS,
4 | FETCH_BLOCKS_FROM_STORAGE,
5 | PERSIST_BLOCKS_TO_STORAGE,
6 | } from "./action-types";
7 | import { ActionCreators as ReduxUndo } from "redux-undo";
8 |
9 | /**
10 | * Returns an action object used to open/close the inserter.
11 | *
12 | * @param {boolean} value A boolean representing whether the inserter should be opened or closed.
13 | * @return {Object} Action object.
14 | */
15 | export function setIsInserterOpened( value ) {
16 | return {
17 | type: 'SET_IS_INSERTER_OPENED',
18 | value,
19 | };
20 | }
21 |
22 | export function undo() {
23 | return ReduxUndo.undo();
24 | }
25 |
26 | export function redo() {
27 | return ReduxUndo.redo();
28 | }
29 |
30 | export function *updateBlocks( blocks, persist = false ) {
31 |
32 | if( persist ) {
33 | yield persistBlocksToStorage(blocks);
34 | }
35 |
36 | return {
37 | type: persist ? PERSIST_BLOCKS : UPDATE_BLOCKS,
38 | blocks,
39 | };
40 | }
41 |
42 | export function fetchBlocksFromStorage() {
43 | return {
44 | type: FETCH_BLOCKS_FROM_STORAGE,
45 | };
46 | };
47 |
48 | export function persistBlocksToStorage(blocks) {
49 | return {
50 | type: PERSIST_BLOCKS_TO_STORAGE,
51 | blocks,
52 | };
53 | }
54 |
--------------------------------------------------------------------------------
/app/javascript/block_editor/stores/controls.js:
--------------------------------------------------------------------------------
1 | import {
2 | FETCH_BLOCKS_FROM_STORAGE,
3 | PERSIST_BLOCKS_TO_STORAGE,
4 | } from "./action-types";
5 | import { serialize } from "@wordpress/blocks";
6 |
7 | export default {
8 | [PERSIST_BLOCKS_TO_STORAGE](action) {
9 | return new Promise((resolve, reject) => {
10 | window.localStorage.setItem("blockEditorBlocks", serialize(action.blocks));
11 | resolve(action.blocks);
12 | });
13 | },
14 | [FETCH_BLOCKS_FROM_STORAGE]() {
15 | return new Promise((resolve, reject) => {
16 | const storedBlocks =
17 | window.localStorage.getItem("blockEditorBlocks") || [];
18 | resolve(storedBlocks);
19 | });
20 | },
21 | };
22 |
--------------------------------------------------------------------------------
/app/javascript/block_editor/stores/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { registerStore } from '@wordpress/data';
5 |
6 | /**
7 | * Internal dependencies
8 | */
9 | import reducer from './reducer';
10 | import * as selectors from './selectors';
11 | import * as actions from './actions';
12 | import * as resolvers from "./resolvers";
13 | import controls from './controls';
14 |
15 | /**
16 | * Module Constants
17 | */
18 | const MODULE_KEY = 'block-editor';
19 |
20 | const store = registerStore(MODULE_KEY, {
21 | reducer,
22 | selectors,
23 | actions,
24 | controls,
25 | resolvers,
26 | });
27 |
28 | window.blockEditorStore = store;
29 |
30 | export default store;
31 |
--------------------------------------------------------------------------------
/app/javascript/block_editor/stores/reducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from '@wordpress/data';
2 |
3 | import undoable, { groupByActionTypes, includeAction } from "redux-undo";
4 | import { UPDATE_BLOCKS, PERSIST_BLOCKS } from "./action-types";
5 |
6 | function blocksReducer(state = [], action) {
7 | switch (action.type) {
8 | case UPDATE_BLOCKS:
9 | case PERSIST_BLOCKS:
10 | const { blocks } = action;
11 |
12 | return {
13 | blocks,
14 | };
15 | }
16 |
17 | return state;
18 | }
19 |
20 |
21 | /**
22 | * Reducer tracking whether the inserter is open.
23 | *
24 | * @param {boolean} state
25 | * @param {Object} action
26 | */
27 | function isInserterOpened( state = false, action ) {
28 | switch ( action.type ) {
29 | case 'SET_IS_INSERTER_OPENED':
30 | return action.value;
31 | }
32 | return state;
33 | }
34 |
35 | export default combineReducers( {
36 | isInserterOpened: isInserterOpened,
37 | history: undoable(blocksReducer, {
38 | filter: includeAction('PERSIST_BLOCKS')
39 | })
40 | } );
41 |
--------------------------------------------------------------------------------
/app/javascript/block_editor/stores/resolvers.js:
--------------------------------------------------------------------------------
1 | import { parse } from "@wordpress/blocks";
2 | import { fetchBlocksFromStorage, updateBlocks } from "./actions";
3 |
4 | export function *getBlocks() {
5 | const rawBlocks = yield fetchBlocksFromStorage();
6 | const persist = false;
7 | const blocks = parse(rawBlocks);
8 | yield updateBlocks(blocks, persist);
9 | return blocks;
10 | }
11 |
--------------------------------------------------------------------------------
/app/javascript/block_editor/stores/selectors.js:
--------------------------------------------------------------------------------
1 | import { createRegistrySelector } from '@wordpress/data';
2 |
3 | export const getBlocks = ( state ) => {
4 | return state.history.present.blocks || [];
5 | }
6 |
7 | /**
8 | * Returns true if the inserter is opened.
9 | *
10 | * @param {Object} state Global application state.
11 | *
12 | * @return {boolean} Whether the inserter is opened.
13 | */
14 | export function isInserterOpened( state ) {
15 | return state.isInserterOpened;
16 | }
17 |
18 | export const hasUndo = (state) => {
19 | return state.history.past?.length;
20 | };
21 |
22 | export const hasRedo = (state) => {
23 | return state.history.future?.length;
24 | };
25 |
--------------------------------------------------------------------------------
/app/javascript/controllers/block_editor_controller.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import PropTypes from 'prop-types'
4 |
5 | import { Controller } from "stimulus"
6 |
7 | import { render } from '@wordpress/element'
8 |
9 | import BlockEditor from '../block_editor/components/block-editor'
10 | import { registerBlocks } from '../block_editor/blocks'
11 |
12 | registerBlocks()
13 |
14 | export default class extends Controller {
15 | static targets = [ "output", "input" ]
16 |
17 | connect() {
18 | const settings = {
19 | alignWide: true,
20 | __experimentalFeatures: {
21 | global: {
22 | color: {
23 | custom: false,
24 | palette: [],
25 | gradients: []
26 | },
27 | typography: {
28 | dropCap: false,
29 | fontSizes: false,
30 | customLineHeight: false
31 | }
32 | }
33 | },
34 | mediaUpload: function uploadMedia( {
35 | allowedTypes,
36 | additionalData = {},
37 | filesList,
38 | maxUploadFileSize,
39 | onError = noop,
40 | onFileChange,
41 | wpAllowedMimeTypes = null,
42 | } ) {
43 | }
44 | }
45 |
46 | window.localStorage.setItem("blockEditorBlocks", this.inputTarget.value);
47 | this.editor = render( , this.outputTarget )
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/javascript/controllers/index.js:
--------------------------------------------------------------------------------
1 | window.wp = null
2 |
3 | import { Application } from "stimulus"
4 | import { definitionsFromContext } from "stimulus/webpack-helpers"
5 |
6 | const application = Application.start()
7 | const context = require.context("controllers", true, /_controller\.jsx$/)
8 | application.load(definitionsFromContext(context))
9 |
--------------------------------------------------------------------------------
/app/javascript/packs/block_editor/application.js:
--------------------------------------------------------------------------------
1 | import "controllers"
2 | import './application.scss'
3 |
--------------------------------------------------------------------------------
/app/javascript/packs/block_editor/application.scss:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Noto+Serif:wght@400;700&display=swap');
2 |
3 | @import "~@wordpress/base-styles/colors";
4 | @import "~@wordpress/base-styles/variables";
5 | @import "~@wordpress/base-styles/mixins";
6 | @import "~@wordpress/base-styles/breakpoints";
7 | @import "~@wordpress/base-styles/animations";
8 | @import "~@wordpress/base-styles/z-index";
9 |
10 | @import "~@wordpress/components/src/style";
11 | @import "~@wordpress/interface/src/style";
12 | @import "~@wordpress/block-editor/src/style";
13 | @import "~@wordpress/block-library/src/style";
14 |
15 | $header-height: 50px;
16 |
17 | // Internal
18 | @import "../../block_editor/components/sidebar/styles";
19 | @import "../../block_editor/components/header/styles";
20 | @import "../../block_editor/components/block-editor/styles";
21 |
22 | .block-editor-block-list__layout.is-root-container {
23 | padding-top: 1rem;
24 | padding-bottom: 1rem;
25 | }
26 |
27 | .block-editor {
28 | @include reset;
29 | position: relative;
30 | margin: 1rem 0;
31 | padding: 0;
32 | font-family: $editor-font;
33 | font-size: $editor-font-size;
34 | line-height: $editor-line-height;
35 | border: 1px solid #f3f4f5;
36 | height: 500px;
37 | overflow-y: hidden;
38 |
39 | a,
40 | div {
41 | outline: 0;
42 | }
43 |
44 | label {
45 | font-size: $default-font-size;
46 | }
47 |
48 | .editor-styles-wrapper {
49 | font-family: $default-font;
50 | font-size: $default-font-size;
51 | }
52 |
53 | [type='text'], [type='password'], [type='date'], [type='datetime'], [type='datetime-local'], [type='month'], [type='week'], [type='email'], [type='number'], [type='search'], [type='tel'], [type='time'], [type='url'], [type='color'], textarea {
54 | margin-bottom: 0;
55 | }
56 |
57 | @include admin-scheme(#1b8ecf);
58 |
59 | .block-editor-inserter__main-area {
60 | padding-bottom: 2rem;
61 | }
62 |
63 | .block-editor-url-popover__additional-controls,
64 | .block-editor-media-replace-flow__options .components-form-file-upload {
65 | display: none;
66 | }
67 |
68 | .components-dropdown-menu__toggle[aria-label="Change alignment"] {
69 | display: none !important;
70 | }
71 |
72 | .components-toolbar-button[aria-label="Crop"] {
73 | display: none !important;
74 | }
75 |
76 | .block-editor-block-mover__move-button-container {
77 | display: inline-flex;
78 | }
79 |
80 | .block-editor-inserter__manage-reusable-blocks {
81 | display: none;
82 | }
83 |
84 | &:not(.block-editor__fullscreen) {
85 | .block-editor__size-toggle-button__minimize {
86 | display: none;
87 | }
88 | }
89 |
90 | &.block-editor__fullscreen {
91 | .block-editor__size-toggle-button__maximize {
92 | display: none;
93 | }
94 | }
95 |
96 | &__size-toggle-button {
97 | position: absolute;
98 | right: 0;
99 | top: 7px;
100 | z-index: 1;
101 | }
102 |
103 | &__fullscreen {
104 | position: absolute;
105 | top: 0px;
106 | right: 0px;
107 | bottom: 0px;
108 | left: 0px;
109 | padding: 0;
110 | z-index: 99;
111 | margin: 0;
112 | height: unset;
113 | .block-editor {
114 | height: 100%;
115 | }
116 | }
117 | }
118 |
119 | // In order to use mix-blend-mode, this element needs to have an explicitly set background-color.
120 | // We scope it to .wp-toolbar to be wp-admin only, to prevent bleed into other implementations.
121 | html.wp-toolbar {
122 | background: $white;
123 | }
124 |
125 | // The modals are shown outside the .block-editor wrapper, they need these styles.
126 | .block-editor, .components-modal__frame {
127 | @include reset;
128 | }
129 |
130 | .interface-interface {
131 | &-skeleton {
132 | position: absolute;
133 | top: 0;
134 | left: 0;
135 | background-color: white;
136 | &__footer {
137 | position: inherit;
138 | .block-editor-block-breadcrumb {
139 | position: absolute;
140 | bottom: 0;
141 | z-index: 1;
142 | background-color: $white;
143 | width: 100%;
144 | border-top: 1px solid #f3f4f5;
145 | padding: 1rem 0;
146 | }
147 | }
148 | &__sidebar {
149 | width: 280px;
150 | }
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/app/jobs/block_editor/application_job.rb:
--------------------------------------------------------------------------------
1 | module BlockEditor
2 | class ApplicationJob < ActiveJob::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/app/mailers/block_editor/application_mailer.rb:
--------------------------------------------------------------------------------
1 | module BlockEditor
2 | class ApplicationMailer < ActionMailer::Base
3 | default from: 'from@example.com'
4 | layout 'mailer'
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/app/models/block_editor/application_record.rb:
--------------------------------------------------------------------------------
1 | module BlockEditor
2 | class ApplicationRecord < ActiveRecord::Base
3 | self.abstract_class = true
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/app/models/block_editor/block_list.rb:
--------------------------------------------------------------------------------
1 | module BlockEditor
2 | # Represents a block list
3 | class BlockList < ApplicationRecord
4 | # Associations
5 | belongs_to :listable, polymorphic: true, touch: true, optional: true
6 | has_many :inverse_block_list_connections, class_name: "BlockListConnection", foreign_key: "child_id"
7 | has_many :block_list_connections, foreign_key: 'parent_id'
8 | has_many :parents, :through => :inverse_block_list_connections
9 | has_many :reusable_blocks, through: :block_list_connections, source: 'child'
10 |
11 | # Callbacks
12 | after_save_commit :touch_parents
13 | after_save_commit :set_reusable_blocks
14 | before_validation :set_defaults
15 |
16 | # Validations
17 | validates :name, presence: true
18 |
19 | # Scopes
20 | scope :reusable, -> { where(listable_id: nil, listable_type: nil) }
21 | scope :search, ->(query) { where('lower(name) LIKE ?', "%#{query.downcase}%") }
22 |
23 | def reusable?
24 | listable_type.nil? && listable_id.nil?
25 | end
26 |
27 | private
28 |
29 | def touch_parents
30 | parents.touch_all if reusable?
31 | end
32 |
33 | def set_reusable_blocks
34 | # Find all instances of a reusable block
35 | html = Nokogiri::HTML(content)
36 | ids = html.xpath('//comment()').select {|comment| comment.inner_text.starts_with?(" wp:#{BlockEditor::Blocks::Reusable.name}") }.map do |block_instance|
37 | block_attributes = block_instance.inner_text.split(" wp:#{BlockEditor::Blocks::Reusable.name}")[1][0...-1]
38 | block_attributes = block_attributes.blank? ? {} : JSON.parse(block_attributes)
39 | block_attributes['ref']
40 | end
41 |
42 | self.reusable_block_ids = ids || []
43 | end
44 |
45 | def set_defaults
46 | if self.name.blank?
47 | self.name = Time.now
48 | end
49 | end
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/app/models/block_editor/block_list_connection.rb:
--------------------------------------------------------------------------------
1 | module BlockEditor
2 | class BlockListConnection < ApplicationRecord
3 | belongs_to :parent, class_name: 'BlockEditor::BlockList'
4 | belongs_to :child, class_name: 'BlockEditor::BlockList'
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/app/models/concerns/block_editor/listable.rb:
--------------------------------------------------------------------------------
1 | module BlockEditor
2 | module Listable
3 | extend ActiveSupport::Concern
4 |
5 | included do
6 | has_many :block_lists, as: :listable, class_name: 'BlockEditor::BlockList'
7 | has_one :active_block_list, -> { where active: true }, class_name: 'BlockEditor::BlockList', as: :listable
8 |
9 | validates :active_block_list, presence: true
10 |
11 | accepts_nested_attributes_for :active_block_list
12 |
13 | after_initialize :set_block_list_defaults
14 | end
15 |
16 | private
17 |
18 | def set_block_list_defaults
19 | return if self.persisted?
20 |
21 | self.active_block_list ||= self.build_active_block_list(listable: self)
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/app/views/block_editor/blocks/be/contact-form/_block.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/views/layouts/block_editor/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Block editor
5 | <%= csrf_meta_tags %>
6 | <%= csp_meta_tag %>
7 |
8 | <%= stylesheet_link_tag "block_editor/application", media: "all" %>
9 |
10 |
11 |
12 | <%= yield %>
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # This command will automatically be run when you run "rails" with Rails gems
3 | # installed from the root of your application.
4 |
5 | ENGINE_ROOT = File.expand_path('..', __dir__)
6 | ENGINE_PATH = File.expand_path('../lib/block_editor/engine', __dir__)
7 | APP_PATH = File.expand_path('../test/dummy/config/application', __dir__)
8 |
9 | # Set up gems listed in the Gemfile.
10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
11 | require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
12 |
13 | require "rails/all"
14 | require "rails/engine/commands"
15 |
--------------------------------------------------------------------------------
/bin/webpack:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
4 | ENV["NODE_ENV"] ||= "development"
5 |
6 | require "pathname"
7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
8 | Pathname.new(__FILE__).realpath)
9 |
10 | require "bundler/setup"
11 |
12 | require "webpacker"
13 | require "webpacker/webpack_runner"
14 |
15 | APP_ROOT = File.expand_path("..", __dir__)
16 | Dir.chdir(APP_ROOT) do
17 | Webpacker::WebpackRunner.run(ARGV)
18 | end
19 |
--------------------------------------------------------------------------------
/bin/webpack-dev-server:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
4 | ENV["NODE_ENV"] ||= "development"
5 |
6 | require "pathname"
7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
8 | Pathname.new(__FILE__).realpath)
9 |
10 | require "bundler/setup"
11 |
12 | require "webpacker"
13 | require "webpacker/dev_server_runner"
14 |
15 | APP_ROOT = File.expand_path("..", __dir__)
16 | Dir.chdir(APP_ROOT) do
17 | Webpacker::DevServerRunner.run(ARGV)
18 | end
19 |
--------------------------------------------------------------------------------
/block_editor.gemspec:
--------------------------------------------------------------------------------
1 | require_relative "lib/block_editor/version"
2 |
3 | Gem::Specification.new do |spec|
4 | spec.name = "block_editor"
5 | spec.version = BlockEditor::VERSION
6 | spec.authors = ["Patrick Lindsay"]
7 | spec.email = ["patrick@yamasolutions.com"]
8 | spec.homepage = "https://github.com/yamasolutions/block-editor"
9 | spec.summary = "Ruby on Rails Block Editor"
10 | spec.description = "A block editor for Ruby on Rails built from the Wordpress Gutenberg project"
11 | spec.license = "MIT"
12 |
13 | spec.metadata["homepage_uri"] = spec.homepage
14 | spec.metadata["source_code_uri"] = "https://github.com/yamasolutions/block-editor"
15 | spec.metadata["changelog_uri"] = "https://github.com/yamasolutions/block-editor/blob/master/CHANGELOG.md"
16 |
17 | spec.files = Dir["{app,bin,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md", "package.json", "yarn.lock", "postcss.config.js"]
18 |
19 | spec.add_dependency "rails", "~> 6.0"
20 | spec.add_dependency 'webpacker', '~> 5.1'
21 | end
22 |
--------------------------------------------------------------------------------
/config/initializers/webpacker_extension.rb:
--------------------------------------------------------------------------------
1 | module Webpacker::DynamicTag
2 | def javascript_pack_tag(*names, **options)
3 | return super unless options[:webpacker]
4 | new_helper = self.dup
5 | new_helper.define_singleton_method(:current_webpacker_instance) do
6 | options[:webpacker].constantize.webpacker
7 | end
8 | new_helper.javascript_pack_tag(*names, **options.except(:webpacker))
9 | end
10 |
11 | def stylesheet_pack_tag(*names, **options)
12 | return super unless options[:webpacker]
13 | new_helper = self.dup
14 | new_helper.define_singleton_method(:current_webpacker_instance) do
15 | options[:webpacker].constantize.webpacker
16 | end
17 | new_helper.stylesheet_pack_tag(*names, **options.except(:webpacker))
18 | end
19 | end
20 |
21 | Webpacker::Helper.prepend Webpacker::DynamicTag
22 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | BlockEditor::Engine.routes.draw do
2 | end
3 |
--------------------------------------------------------------------------------
/config/webpack/development.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development'
2 |
3 | const environment = require('./environment')
4 |
5 | module.exports = environment.toWebpackConfig()
6 |
--------------------------------------------------------------------------------
/config/webpack/environment.js:
--------------------------------------------------------------------------------
1 | const { environment } = require('@rails/webpacker')
2 |
3 | module.exports = environment
4 |
--------------------------------------------------------------------------------
/config/webpack/production.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_ENV = process.env.NODE_ENV || 'production'
2 |
3 | const environment = require('./environment')
4 |
5 | module.exports = environment.toWebpackConfig()
6 |
--------------------------------------------------------------------------------
/config/webpack/test.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development'
2 |
3 | const environment = require('./environment')
4 |
5 | module.exports = environment.toWebpackConfig()
6 |
--------------------------------------------------------------------------------
/config/webpacker.yml:
--------------------------------------------------------------------------------
1 | # Note: You must restart bin/webpack-dev-server for changes to take effect
2 |
3 | default: &default
4 | source_path: app/javascript
5 | source_entry_path: packs
6 | public_root_path: public
7 | public_output_path: packs
8 | cache_path: tmp/cache/webpacker
9 | webpack_compile_output: true
10 |
11 | # Additional paths webpack should lookup modules
12 | # ['app/assets', 'engine/foo/app/assets']
13 | additional_paths: []
14 |
15 | # Reload manifest.json on all requests so we reload latest compiled packs
16 | cache_manifest: false
17 |
18 | # Extract and emit a css file
19 | extract_css: false
20 |
21 | static_assets_extensions:
22 | - .jpg
23 | - .jpeg
24 | - .png
25 | - .gif
26 | - .tiff
27 | - .ico
28 | - .svg
29 | - .eot
30 | - .otf
31 | - .ttf
32 | - .woff
33 | - .woff2
34 |
35 | extensions:
36 | - .mjs
37 | - .js
38 | - .sass
39 | - .scss
40 | - .css
41 | - .module.sass
42 | - .module.scss
43 | - .module.css
44 | - .png
45 | - .svg
46 | - .gif
47 | - .jpeg
48 | - .jpg
49 |
50 | development:
51 | <<: *default
52 | compile: true
53 |
54 | # Reference: https://webpack.js.org/configuration/dev-server/
55 | dev_server:
56 | https: false
57 | host: localhost
58 | port: 3035
59 | public: localhost:3035
60 | hmr: false
61 | # Inline should be set to true if using HMR
62 | inline: true
63 | overlay: true
64 | compress: true
65 | disable_host_check: true
66 | use_local_ip: false
67 | quiet: false
68 | pretty: false
69 | headers:
70 | 'Access-Control-Allow-Origin': '*'
71 | watch_options:
72 | ignored: '**/node_modules/**'
73 |
74 |
75 | test:
76 | <<: *default
77 | compile: true
78 |
79 | # Compile test packs to a separate directory
80 | public_output_path: packs-test
81 |
82 | production:
83 | <<: *default
84 |
85 | # Production depends on precompilation of packs prior to booting for performance.
86 | compile: false
87 |
88 | # Extract and emit a css file
89 | extract_css: true
90 |
91 | # Cache manifest.json for performance
92 | cache_manifest: true
93 |
--------------------------------------------------------------------------------
/db/migrate/20210312032114_create_block_lists.rb:
--------------------------------------------------------------------------------
1 | class CreateBlockLists < ActiveRecord::Migration[6.1]
2 | def change
3 | create_table :block_editor_block_lists do |t|
4 | t.string :name
5 | t.text :content
6 | t.boolean :active, default: false
7 | t.references :listable, polymorphic: true
8 | t.timestamps
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/db/migrate/20210506220328_create_block_list_connections.rb:
--------------------------------------------------------------------------------
1 | class CreateBlockListConnections < ActiveRecord::Migration[6.1]
2 | def change
3 | create_table :block_editor_block_list_connections do |t|
4 | t.belongs_to :parent
5 | t.belongs_to :child
6 | end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/lib/block_editor.rb:
--------------------------------------------------------------------------------
1 | require "block_editor/version"
2 | require "block_editor/engine"
3 |
4 | require "block_editor/instance"
5 | require 'block_editor/blocks/base'
6 | require 'block_editor/blocks/contact_form'
7 | require 'block_editor/blocks/reusable'
8 | require 'block_editor/block_list_renderer'
9 |
10 | module BlockEditor
11 | ROOT_PATH = Pathname.new(File.join(__dir__, ".."))
12 |
13 | class << self
14 | def webpacker
15 | @webpacker ||= ::Webpacker::Instance.new(
16 | root_path: ROOT_PATH,
17 | config_path: ROOT_PATH.join("config/webpacker.yml")
18 | )
19 | end
20 | end
21 |
22 | mattr_accessor :dynamic_blocks
23 | @@dynamic_blocks = ['BlockEditor::Blocks::Reusable', 'BlockEditor::Blocks::ContactForm']
24 |
25 | mattr_accessor :frontend_parent_controller
26 | @@frontend_parent_controller = 'ApplicationController'
27 | end
28 |
29 |
--------------------------------------------------------------------------------
/lib/block_editor/block_list_renderer.rb:
--------------------------------------------------------------------------------
1 | module BlockEditor
2 | # Handles the rendering of a block list including dynamic blocks and removing HTML comments
3 | class BlockListRenderer
4 | # Renders dynamic blocks within the HTML snippet then strips all HTML comments (including Gutenberg markup)
5 | #
6 | # @param raw_html [String]
7 | #
8 | # @return [String] Parsed content
9 | def self.render(raw_html)
10 | html = Nokogiri::HTML::DocumentFragment.parse(raw_html)
11 |
12 | # Find & render all instances of a dynamic block (including reusable blocks)
13 | BlockEditor.dynamic_blocks.each do |dynamic_block|
14 | dynamic_block = dynamic_block.constantize
15 |
16 | html.search('.//comment()').select {|comment| comment.inner_text.starts_with?(" wp:#{dynamic_block.name}") }.each do |block_instance|
17 | block_attributes = block_instance.inner_text.split(" wp:#{dynamic_block.name}")[1][0...-1]
18 | block_attributes = block_attributes.blank? ? {} : JSON.parse(block_attributes)
19 | block_content = render_block(dynamic_block, block_attributes)
20 |
21 | block_instance.replace(block_content)
22 | end
23 | end
24 |
25 | html.search('.//comment()').remove
26 | html.to_s.html_safe
27 | end
28 |
29 | # Renders a specific block using the provided options
30 | #
31 | # @param block [String] name of block
32 | # @param options [Hash] block options to use when rendering
33 | #
34 | # @return [String] block content (HTML)
35 | def self.render_block(block, options)
36 | block.render(options)
37 | rescue StandardError => e
38 | respond_with_block_error(e)
39 | end
40 |
41 | # Handles block errors
42 | def self.respond_with_block_error(error)
43 | Rails.logger.error("Error rendering block - #{error.message}")
44 | ''
45 | end
46 | end
47 | end
48 |
49 |
50 |
--------------------------------------------------------------------------------
/lib/block_editor/blocks/base.rb:
--------------------------------------------------------------------------------
1 | module BlockEditor
2 | # Blocks used to render dynamic content
3 | module Blocks
4 | # Base for dynamic blocks
5 | class Base
6 | def self.name
7 | raise NotImplementedError, 'Must specify block name'
8 | end
9 |
10 | # Render the block
11 | def self.render(options = {})
12 | options = options.reverse_merge(default_options.with_indifferent_access)
13 |
14 | controller.render(
15 | partial: "block_editor/blocks/#{name}/block",
16 | locals: { options: options },
17 | layout: false
18 | )
19 | end
20 |
21 | # Frontend controller used to render views
22 | def self.controller
23 | BlockEditor.frontend_parent_controller.constantize
24 | end
25 |
26 | # Default widget options
27 | def self.default_options
28 | {}
29 | end
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/lib/block_editor/blocks/contact_form.rb:
--------------------------------------------------------------------------------
1 | module BlockEditor
2 | # Blocks used to render dynamic content
3 | module Blocks
4 | # Outputs Contact Form block
5 | class ContactForm < Base
6 | def self.name
7 | 'be/contact-form'
8 | end
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/lib/block_editor/blocks/reusable.rb:
--------------------------------------------------------------------------------
1 | module BlockEditor
2 | # Blocks used to render dynamic content
3 | module Blocks
4 | # Outputs Reusable block
5 | class Reusable < Base
6 | def self.name
7 | 'block'
8 | end
9 |
10 | # Render the block
11 | def self.render(options = {})
12 | BlockListRenderer.render(BlockEditor::BlockList.find(options['ref']).content)
13 | end
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/block_editor/engine.rb:
--------------------------------------------------------------------------------
1 | module BlockEditor
2 | class Engine < ::Rails::Engine
3 | isolate_namespace BlockEditor
4 |
5 | initializer "webpacker.proxy" do |app|
6 | insert_middleware = begin
7 | BlockEditor.webpacker.config.dev_server.present?
8 | rescue
9 | nil
10 | end
11 | next unless insert_middleware
12 |
13 | app.middleware.insert_before(
14 | 0, Webpacker::DevServerProxy,
15 | ssl_verify_none: true,
16 | webpacker: BlockEditor.webpacker
17 | )
18 | end
19 |
20 | # Initializer to combine this engines static assets with the static assets of the host application
21 | initializer 'static assets' do |app|
22 | app.middleware.insert_before(::ActionDispatch::Static, ::ActionDispatch::Static, "#{root}/public")
23 | end
24 |
25 | initializer 'block_editor.assets.precompile' do |app|
26 | assets_for_precompile = [
27 | 'block_editor/frontend.css',
28 | 'block_editor/backend.css'
29 | ]
30 |
31 | app.config.assets.precompile.concat assets_for_precompile
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/lib/block_editor/instance.rb:
--------------------------------------------------------------------------------
1 | module BlockEditor
2 | class Instance
3 | include ActionView::Helpers::TagHelper
4 | include ActionView::Helpers::FormTagHelper
5 |
6 | attr_accessor :output_buffer
7 |
8 | def self.render(form_builder)
9 | self.new.render(form_builder)
10 | end
11 |
12 | def render(form_builder)
13 | content_tag(:div, data: { controller: 'block-editor' }) do
14 | form_builder.hidden_field(:content, data: { 'block-editor-target' => 'input' }) +
15 | content_tag('div', nil, { class: 'block-editor', data: { 'block-editor-target' => 'output' } })
16 | end
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/block_editor/version.rb:
--------------------------------------------------------------------------------
1 | module BlockEditor
2 | VERSION = '1.0.0'
3 | end
4 |
--------------------------------------------------------------------------------
/lib/tasks/block_editor_tasks.rake:
--------------------------------------------------------------------------------
1 | def ensure_log_goes_to_stdout
2 | old_logger = Webpacker.logger
3 | Webpacker.logger = ActiveSupport::Logger.new(STDOUT)
4 | yield
5 | ensure
6 | Webpacker.logger = old_logger
7 | end
8 |
9 |
10 | namespace :block_editor do
11 | namespace :webpacker do
12 | desc "Install deps with yarn"
13 | task :yarn_install do
14 | Dir.chdir(File.join(__dir__, "../..")) do
15 | system "yarn install --no-progress --production"
16 | end
17 | end
18 |
19 | desc "Compile JavaScript packs using webpack for production with digests"
20 | task compile: [:yarn_install, :environment] do
21 | Webpacker.with_node_env("production") do
22 | ensure_log_goes_to_stdout do
23 | if BlockEditor.webpacker.commands.compile
24 | # Successful compilation!
25 | else
26 | # Failed compilation
27 | exit!
28 | end
29 | end
30 | end
31 | end
32 | end
33 | end
34 |
35 | def yarn_install_available?
36 | rails_major = Rails::VERSION::MAJOR
37 | rails_minor = Rails::VERSION::MINOR
38 |
39 | rails_major > 5 || (rails_major == 5 && rails_minor >= 1)
40 | end
41 |
42 | def enhance_assets_precompile
43 | # yarn:install was added in Rails 5.1
44 | deps = yarn_install_available? ? [] : ["block_editor:webpacker:yarn_install"]
45 | Rake::Task["assets:precompile"].enhance(deps) do
46 | Rake::Task["block_editor:webpacker:compile"].invoke
47 | end
48 | end
49 |
50 | # Compile packs after we've compiled all other assets during precompilation
51 | skip_webpacker_precompile = %w(no false n f).include?(ENV["WEBPACKER_PRECOMPILE"])
52 |
53 | unless skip_webpacker_precompile
54 | if Rake::Task.task_defined?("assets:precompile")
55 | enhance_assets_precompile
56 | else
57 | Rake::Task.define_task("assets:precompile" => "block_editor:webpacker:compile")
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "block-editor",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@babel/core": "7.9.0",
7 | "@rails/webpacker": "^5.2.1",
8 | "@wordpress/base-styles": "3.2.0",
9 | "@wordpress/block-editor": "5.1.0",
10 | "@wordpress/block-library": "2.26.0",
11 | "@wordpress/components": "11.1.0",
12 | "@wordpress/editor": "9.24.0",
13 | "@wordpress/format-library": "1.25.0",
14 | "@wordpress/interface": "0.10.0",
15 | "@wordpress/reusable-blocks": "1.0.0",
16 | "@wordpress/server-side-render": "1.19.0",
17 | "babel-loader": "8.1.0",
18 | "babel-preset-react-app": "^9.1.2",
19 | "react": "^16.13.1",
20 | "react-dom": "^16.13.1",
21 | "redux-undo": "^1.0.1",
22 | "stimulus": "^2.0.0"
23 | },
24 | "babel": {
25 | "presets": [
26 | "react-app"
27 | ]
28 | },
29 | "devDependencies": {
30 | "webpack-dev-server": "^3.11.0"
31 | },
32 | "resolutions": {
33 | "**/@wordpress/block-editor": "5.1.0",
34 | "**/@wordpress/editor": "9.24.0",
35 | "**/@wordpress/reusable-blocks": "1.0.0",
36 | "**/@wordpress/components": "11.1.0",
37 | "**/@wordpress/server-side-render": "1.19.0"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | require('autoprefixer')
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/test/dummy/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require_relative "config/application"
5 |
6 | Rails.application.load_tasks
7 |
--------------------------------------------------------------------------------
/test/dummy/app/assets/config/manifest.js:
--------------------------------------------------------------------------------
1 | //= link_tree ../images
2 | //= link_directory ../stylesheets .css
3 | //= link block_editor_manifest.js
4 |
--------------------------------------------------------------------------------
/test/dummy/app/assets/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yamasolutions/block-editor/3454f449b22a041191a6eee53c645e0d764841d3/test/dummy/app/assets/images/.keep
--------------------------------------------------------------------------------
/test/dummy/app/assets/stylesheets/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10 | * files in this directory. Styles in this file should be added after the last require_* statement.
11 | * It is generally better to create a new file per style scope.
12 | *
13 | *= require_tree .
14 | *= require_self
15 | */
16 |
--------------------------------------------------------------------------------
/test/dummy/app/channels/application_cable/channel.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Channel < ActionCable::Channel::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/test/dummy/app/channels/application_cable/connection.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Connection < ActionCable::Connection::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/test/dummy/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | end
3 |
--------------------------------------------------------------------------------
/test/dummy/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yamasolutions/block-editor/3454f449b22a041191a6eee53c645e0d764841d3/test/dummy/app/controllers/concerns/.keep
--------------------------------------------------------------------------------
/test/dummy/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/test/dummy/app/javascript/packs/application.js:
--------------------------------------------------------------------------------
1 | // This is a manifest file that'll be compiled into application.js, which will include all the files
2 | // listed below.
3 | //
4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6 | //
7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8 | // compiled file. JavaScript code in this file should be added after the last require_* statement.
9 | //
10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11 | // about supported directives.
12 | //
13 | //= require rails-ujs
14 | //= require activestorage
15 | //= require_tree .
16 |
--------------------------------------------------------------------------------
/test/dummy/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | class ApplicationJob < ActiveJob::Base
2 | # Automatically retry jobs that encountered a deadlock
3 | # retry_on ActiveRecord::Deadlocked
4 |
5 | # Most jobs are safe to ignore if the underlying records are no longer available
6 | # discard_on ActiveJob::DeserializationError
7 | end
8 |
--------------------------------------------------------------------------------
/test/dummy/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
1 | class ApplicationMailer < ActionMailer::Base
2 | default from: 'from@example.com'
3 | layout 'mailer'
4 | end
5 |
--------------------------------------------------------------------------------
/test/dummy/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | class ApplicationRecord < ActiveRecord::Base
2 | self.abstract_class = true
3 | end
4 |
--------------------------------------------------------------------------------
/test/dummy/app/models/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yamasolutions/block-editor/3454f449b22a041191a6eee53c645e0d764841d3/test/dummy/app/models/concerns/.keep
--------------------------------------------------------------------------------
/test/dummy/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Dummy
5 |
6 | <%= csrf_meta_tags %>
7 | <%= csp_meta_tag %>
8 |
9 | <%= stylesheet_link_tag 'application', media: 'all' %>
10 |
11 |
12 |
13 | <%= yield %>
14 |
15 |
16 |
--------------------------------------------------------------------------------
/test/dummy/app/views/layouts/mailer.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/test/dummy/app/views/layouts/mailer.text.erb:
--------------------------------------------------------------------------------
1 | <%= yield %>
2 |
--------------------------------------------------------------------------------
/test/dummy/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_PATH = File.expand_path('../config/application', __dir__)
3 | require_relative "../config/boot"
4 | require "rails/commands"
5 |
--------------------------------------------------------------------------------
/test/dummy/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require_relative "../config/boot"
3 | require "rake"
4 | Rake.application.run
5 |
--------------------------------------------------------------------------------
/test/dummy/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require "fileutils"
3 |
4 | # path to your application root.
5 | APP_ROOT = File.expand_path('..', __dir__)
6 |
7 | def system!(*args)
8 | system(*args) || abort("\n== Command #{args} failed ==")
9 | end
10 |
11 | FileUtils.chdir APP_ROOT do
12 | # This script is a way to set up or update your development environment automatically.
13 | # This script is idempotent, so that you can run it at any time and get an expectable outcome.
14 | # Add necessary setup steps to this file.
15 |
16 | puts '== Installing dependencies =='
17 | system! 'gem install bundler --conservative'
18 | system('bundle check') || system!('bundle install')
19 |
20 | # puts "\n== Copying sample files =="
21 | # unless File.exist?('config/database.yml')
22 | # FileUtils.cp 'config/database.yml.sample', 'config/database.yml'
23 | # end
24 |
25 | puts "\n== Preparing database =="
26 | system! 'bin/rails db:prepare'
27 |
28 | puts "\n== Removing old logs and tempfiles =="
29 | system! 'bin/rails log:clear tmp:clear'
30 |
31 | puts "\n== Restarting application server =="
32 | system! 'bin/rails restart'
33 | end
34 |
--------------------------------------------------------------------------------
/test/dummy/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require_relative "config/environment"
4 |
5 | run Rails.application
6 | Rails.application.load_server
7 |
--------------------------------------------------------------------------------
/test/dummy/config/application.rb:
--------------------------------------------------------------------------------
1 | require_relative "boot"
2 |
3 | require "rails/all"
4 |
5 | # Require the gems listed in Gemfile, including any gems
6 | # you've limited to :test, :development, or :production.
7 | Bundler.require(*Rails.groups)
8 | require "block_editor"
9 |
10 | module Dummy
11 | class Application < Rails::Application
12 | config.load_defaults Rails::VERSION::STRING.to_f
13 |
14 | # Configuration for the application, engines, and railties goes here.
15 | #
16 | # These settings can be overridden in specific environments using the files
17 | # in config/environments, which are processed later.
18 | #
19 | # config.time_zone = "Central Time (US & Canada)"
20 | # config.eager_load_paths << Rails.root.join("extras")
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/test/dummy/config/boot.rb:
--------------------------------------------------------------------------------
1 | # Set up gems listed in the Gemfile.
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__)
3 |
4 | require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
5 | $LOAD_PATH.unshift File.expand_path('../../../lib', __dir__)
6 |
--------------------------------------------------------------------------------
/test/dummy/config/cable.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: async
3 |
4 | test:
5 | adapter: test
6 |
7 | production:
8 | adapter: redis
9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
10 | channel_prefix: dummy_production
11 |
--------------------------------------------------------------------------------
/test/dummy/config/database.yml:
--------------------------------------------------------------------------------
1 | # SQLite. Versions 3.8.0 and up are supported.
2 | # gem install sqlite3
3 | #
4 | # Ensure the SQLite 3 gem is defined in your Gemfile
5 | # gem 'sqlite3'
6 | #
7 | default: &default
8 | adapter: sqlite3
9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
10 | timeout: 5000
11 |
12 | development:
13 | <<: *default
14 | database: db/development.sqlite3
15 |
16 | # Warning: The database defined as "test" will be erased and
17 | # re-generated from your development database when you run "rake".
18 | # Do not set this db to the same as development or production.
19 | test:
20 | <<: *default
21 | database: db/test.sqlite3
22 |
23 | production:
24 | <<: *default
25 | database: db/production.sqlite3
26 |
--------------------------------------------------------------------------------
/test/dummy/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require_relative "application"
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/test/dummy/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | require "active_support/core_ext/integer/time"
2 |
3 | Rails.application.configure do
4 | # Settings specified here will take precedence over those in config/application.rb.
5 |
6 | # In the development environment your application's code is reloaded any time
7 | # it changes. This slows down response time but is perfect for development
8 | # since you don't have to restart the web server when you make code changes.
9 | config.cache_classes = false
10 |
11 | # Do not eager load code on boot.
12 | config.eager_load = false
13 |
14 | # Show full error reports.
15 | config.consider_all_requests_local = true
16 |
17 | # Enable/disable caching. By default caching is disabled.
18 | # Run rails dev:cache to toggle caching.
19 | if Rails.root.join('tmp', 'caching-dev.txt').exist?
20 | config.action_controller.perform_caching = true
21 | config.action_controller.enable_fragment_cache_logging = true
22 |
23 | config.cache_store = :memory_store
24 | config.public_file_server.headers = {
25 | 'Cache-Control' => "public, max-age=#{2.days.to_i}"
26 | }
27 | else
28 | config.action_controller.perform_caching = false
29 |
30 | config.cache_store = :null_store
31 | end
32 |
33 | # Store uploaded files on the local file system (see config/storage.yml for options).
34 | config.active_storage.service = :local
35 |
36 | # Don't care if the mailer can't send.
37 | config.action_mailer.raise_delivery_errors = false
38 |
39 | config.action_mailer.perform_caching = false
40 |
41 | # Print deprecation notices to the Rails logger.
42 | config.active_support.deprecation = :log
43 |
44 | # Raise exceptions for disallowed deprecations.
45 | config.active_support.disallowed_deprecation = :raise
46 |
47 | # Tell Active Support which deprecation messages to disallow.
48 | config.active_support.disallowed_deprecation_warnings = []
49 |
50 | # Raise an error on page load if there are pending migrations.
51 | config.active_record.migration_error = :page_load
52 |
53 | # Highlight code that triggered database queries in logs.
54 | config.active_record.verbose_query_logs = true
55 |
56 | # Debug mode disables concatenation and preprocessing of assets.
57 | # This option may cause significant delays in view rendering with a large
58 | # number of complex assets.
59 | config.assets.debug = true
60 |
61 | # Suppress logger output for asset requests.
62 | config.assets.quiet = true
63 |
64 | # Raises error for missing translations.
65 | # config.i18n.raise_on_missing_translations = true
66 |
67 | # Annotate rendered view with file names.
68 | # config.action_view.annotate_rendered_view_with_filenames = true
69 |
70 | # Use an evented file watcher to asynchronously detect changes in source code,
71 | # routes, locales, etc. This feature depends on the listen gem.
72 | # config.file_watcher = ActiveSupport::EventedFileUpdateChecker
73 |
74 | # Uncomment if you wish to allow Action Cable access from any origin.
75 | # config.action_cable.disable_request_forgery_protection = true
76 | end
77 |
--------------------------------------------------------------------------------
/test/dummy/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | require "active_support/core_ext/integer/time"
2 |
3 | Rails.application.configure do
4 | # Settings specified here will take precedence over those in config/application.rb.
5 |
6 | # Code is not reloaded between requests.
7 | config.cache_classes = true
8 |
9 | # Eager load code on boot. This eager loads most of Rails and
10 | # your application in memory, allowing both threaded web servers
11 | # and those relying on copy on write to perform better.
12 | # Rake tasks automatically ignore this option for performance.
13 | config.eager_load = true
14 |
15 | # Full error reports are disabled and caching is turned on.
16 | config.consider_all_requests_local = false
17 | config.action_controller.perform_caching = true
18 |
19 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
20 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
21 | # config.require_master_key = true
22 |
23 | # Disable serving static files from the `/public` folder by default since
24 | # Apache or NGINX already handles this.
25 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
26 |
27 | # Compress CSS using a preprocessor.
28 | # config.assets.css_compressor = :sass
29 |
30 | # Do not fallback to assets pipeline if a precompiled asset is missed.
31 | config.assets.compile = false
32 |
33 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
34 | # config.asset_host = 'http://assets.example.com'
35 |
36 | # Specifies the header that your server uses for sending files.
37 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
38 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
39 |
40 | # Store uploaded files on the local file system (see config/storage.yml for options).
41 | config.active_storage.service = :local
42 |
43 | # Mount Action Cable outside main process or domain.
44 | # config.action_cable.mount_path = nil
45 | # config.action_cable.url = 'wss://example.com/cable'
46 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
47 |
48 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
49 | # config.force_ssl = true
50 |
51 | # Include generic and useful information about system operation, but avoid logging too much
52 | # information to avoid inadvertent exposure of personally identifiable information (PII).
53 | config.log_level = :info
54 |
55 | # Prepend all log lines with the following tags.
56 | config.log_tags = [ :request_id ]
57 |
58 | # Use a different cache store in production.
59 | # config.cache_store = :mem_cache_store
60 |
61 | # Use a real queuing backend for Active Job (and separate queues per environment).
62 | # config.active_job.queue_adapter = :resque
63 | # config.active_job.queue_name_prefix = "dummy_production"
64 |
65 | config.action_mailer.perform_caching = false
66 |
67 | # Ignore bad email addresses and do not raise email delivery errors.
68 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
69 | # config.action_mailer.raise_delivery_errors = false
70 |
71 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
72 | # the I18n.default_locale when a translation cannot be found).
73 | config.i18n.fallbacks = true
74 |
75 | # Send deprecation notices to registered listeners.
76 | config.active_support.deprecation = :notify
77 |
78 | # Log disallowed deprecations.
79 | config.active_support.disallowed_deprecation = :log
80 |
81 | # Tell Active Support which deprecation messages to disallow.
82 | config.active_support.disallowed_deprecation_warnings = []
83 |
84 | # Use default logging formatter so that PID and timestamp are not suppressed.
85 | config.log_formatter = ::Logger::Formatter.new
86 |
87 | # Use a different logger for distributed setups.
88 | # require "syslog/logger"
89 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
90 |
91 | if ENV["RAILS_LOG_TO_STDOUT"].present?
92 | logger = ActiveSupport::Logger.new(STDOUT)
93 | logger.formatter = config.log_formatter
94 | config.logger = ActiveSupport::TaggedLogging.new(logger)
95 | end
96 |
97 | # Do not dump schema after migrations.
98 | config.active_record.dump_schema_after_migration = false
99 |
100 | # Inserts middleware to perform automatic connection switching.
101 | # The `database_selector` hash is used to pass options to the DatabaseSelector
102 | # middleware. The `delay` is used to determine how long to wait after a write
103 | # to send a subsequent read to the primary.
104 | #
105 | # The `database_resolver` class is used by the middleware to determine which
106 | # database is appropriate to use based on the time delay.
107 | #
108 | # The `database_resolver_context` class is used by the middleware to set
109 | # timestamps for the last write to the primary. The resolver uses the context
110 | # class timestamps to determine how long to wait before reading from the
111 | # replica.
112 | #
113 | # By default Rails will store a last write timestamp in the session. The
114 | # DatabaseSelector middleware is designed as such you can define your own
115 | # strategy for connection switching and pass that into the middleware through
116 | # these configuration options.
117 | # config.active_record.database_selector = { delay: 2.seconds }
118 | # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
119 | # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
120 | end
121 |
--------------------------------------------------------------------------------
/test/dummy/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | require "active_support/core_ext/integer/time"
2 |
3 | # The test environment is used exclusively to run your application's
4 | # test suite. You never need to work with it otherwise. Remember that
5 | # your test database is "scratch space" for the test suite and is wiped
6 | # and recreated between test runs. Don't rely on the data there!
7 |
8 | Rails.application.configure do
9 | # Settings specified here will take precedence over those in config/application.rb.
10 |
11 | config.cache_classes = true
12 |
13 | # Do not eager load code on boot. This avoids loading your whole application
14 | # just for the purpose of running a single test. If you are using a tool that
15 | # preloads Rails for running tests, you may have to set it to true.
16 | config.eager_load = false
17 |
18 | # Configure public file server for tests with Cache-Control for performance.
19 | config.public_file_server.enabled = true
20 | config.public_file_server.headers = {
21 | 'Cache-Control' => "public, max-age=#{1.hour.to_i}"
22 | }
23 |
24 | # Show full error reports and disable caching.
25 | config.consider_all_requests_local = true
26 | config.action_controller.perform_caching = false
27 | config.cache_store = :null_store
28 |
29 | # Raise exceptions instead of rendering exception templates.
30 | config.action_dispatch.show_exceptions = false
31 |
32 | # Disable request forgery protection in test environment.
33 | config.action_controller.allow_forgery_protection = false
34 |
35 | # Store uploaded files on the local file system in a temporary directory.
36 | config.active_storage.service = :test
37 |
38 | config.action_mailer.perform_caching = false
39 |
40 | # Tell Action Mailer not to deliver emails to the real world.
41 | # The :test delivery method accumulates sent emails in the
42 | # ActionMailer::Base.deliveries array.
43 | config.action_mailer.delivery_method = :test
44 |
45 | # Print deprecation notices to the stderr.
46 | config.active_support.deprecation = :stderr
47 |
48 | # Raise exceptions for disallowed deprecations.
49 | config.active_support.disallowed_deprecation = :raise
50 |
51 | # Tell Active Support which deprecation messages to disallow.
52 | config.active_support.disallowed_deprecation_warnings = []
53 |
54 | # Raises error for missing translations.
55 | # config.i18n.raise_on_missing_translations = true
56 |
57 | # Annotate rendered view with file names.
58 | # config.action_view.annotate_rendered_view_with_filenames = true
59 | end
60 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/application_controller_renderer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # ActiveSupport::Reloader.to_prepare do
4 | # ApplicationController.renderer.defaults.merge!(
5 | # http_host: 'example.org',
6 | # https: false
7 | # )
8 | # end
9 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Version of your assets, change this if you want to expire all your assets.
4 | Rails.application.config.assets.version = '1.0'
5 |
6 | # Add additional assets to the asset load path.
7 | # Rails.application.config.assets.paths << Emoji.images_path
8 |
9 | # Precompile additional assets.
10 | # application.js, application.css, and all non-JS/CSS in the app/assets
11 | # folder are already added.
12 | # Rails.application.config.assets.precompile += %w( admin.js admin.css )
13 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| /my_noisy_library/.match?(line) }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code
7 | # by setting BACKTRACE=1 before calling your invocation, like "BACKTRACE=1 ./bin/rails runner 'MyClass.perform'".
8 | Rails.backtrace_cleaner.remove_silencers! if ENV["BACKTRACE"]
9 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/content_security_policy.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Define an application-wide content security policy
4 | # For further information see the following documentation
5 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
6 |
7 | # Rails.application.config.content_security_policy do |policy|
8 | # policy.default_src :self, :https
9 | # policy.font_src :self, :https, :data
10 | # policy.img_src :self, :https, :data
11 | # policy.object_src :none
12 | # policy.script_src :self, :https
13 | # policy.style_src :self, :https
14 |
15 | # # Specify URI for violation reports
16 | # # policy.report_uri "/csp-violation-report-endpoint"
17 | # end
18 |
19 | # If you are using UJS then enable automatic nonce generation
20 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
21 |
22 | # Set the nonce only to specific directives
23 | # Rails.application.config.content_security_policy_nonce_directives = %w(script-src)
24 |
25 | # Report CSP violations to a specified URI
26 | # For further information see the following documentation:
27 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
28 | # Rails.application.config.content_security_policy_report_only = true
29 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Specify a serializer for the signed and encrypted cookie jars.
4 | # Valid options are :json, :marshal, and :hybrid.
5 | Rails.application.config.action_dispatch.cookies_serializer = :json
6 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [
5 | :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
6 | ]
7 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/permissions_policy.rb:
--------------------------------------------------------------------------------
1 | # Define an application-wide HTTP permissions policy. For further
2 | # information see https://developers.google.com/web/updates/2018/06/feature-policy
3 | #
4 | # Rails.application.config.permissions_policy do |f|
5 | # f.camera :none
6 | # f.gyroscope :none
7 | # f.microphone :none
8 | # f.usb :none
9 | # f.fullscreen :self
10 | # f.payment :self, "https://secure.example.com"
11 | # end
12 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json]
9 | end
10 |
11 | # To enable root element in JSON for ActiveRecord objects.
12 | # ActiveSupport.on_load(:active_record) do
13 | # self.include_root_in_json = true
14 | # end
15 |
--------------------------------------------------------------------------------
/test/dummy/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # The following keys must be escaped otherwise they will not be retrieved by
20 | # the default I18n backend:
21 | #
22 | # true, false, on, off, yes, no
23 | #
24 | # Instead, surround them with single quotes.
25 | #
26 | # en:
27 | # 'true': 'foo'
28 | #
29 | # To learn more, please read the Rails Internationalization guide
30 | # available at https://guides.rubyonrails.org/i18n.html.
31 |
32 | en:
33 | hello: "Hello world"
34 |
--------------------------------------------------------------------------------
/test/dummy/config/puma.rb:
--------------------------------------------------------------------------------
1 | # Puma can serve each request in a thread from an internal thread pool.
2 | # The `threads` method setting takes two numbers: a minimum and maximum.
3 | # Any libraries that use thread pools should be configured to match
4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum
5 | # and maximum; this matches the default thread size of Active Record.
6 | #
7 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
8 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
9 | threads min_threads_count, max_threads_count
10 |
11 | # Specifies the `worker_timeout` threshold that Puma will use to wait before
12 | # terminating a worker in development environments.
13 | #
14 | worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development"
15 |
16 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000.
17 | #
18 | port ENV.fetch("PORT") { 3000 }
19 |
20 | # Specifies the `environment` that Puma will run in.
21 | #
22 | environment ENV.fetch("RAILS_ENV") { "development" }
23 |
24 | # Specifies the `pidfile` that Puma will use.
25 | pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }
26 |
27 | # Specifies the number of `workers` to boot in clustered mode.
28 | # Workers are forked web server processes. If using threads and workers together
29 | # the concurrency of the application would be max `threads` * `workers`.
30 | # Workers do not work on JRuby or Windows (both of which do not support
31 | # processes).
32 | #
33 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 }
34 |
35 | # Use the `preload_app!` method when specifying a `workers` number.
36 | # This directive tells Puma to first boot the application and load code
37 | # before forking the application. This takes advantage of Copy On Write
38 | # process behavior so workers use less memory.
39 | #
40 | # preload_app!
41 |
42 | # Allow puma to be restarted by `rails restart` command.
43 | plugin :tmp_restart
44 |
--------------------------------------------------------------------------------
/test/dummy/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | mount BlockEditor::Engine => "/block_editor"
3 | end
4 |
--------------------------------------------------------------------------------
/test/dummy/config/storage.yml:
--------------------------------------------------------------------------------
1 | test:
2 | service: Disk
3 | root: <%= Rails.root.join("tmp/storage") %>
4 |
5 | local:
6 | service: Disk
7 | root: <%= Rails.root.join("storage") %>
8 |
9 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
10 | # amazon:
11 | # service: S3
12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
14 | # region: us-east-1
15 | # bucket: your_own_bucket
16 |
17 | # Remember not to checkin your GCS keyfile to a repository
18 | # google:
19 | # service: GCS
20 | # project: your_project
21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
22 | # bucket: your_own_bucket
23 |
24 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
25 | # microsoft:
26 | # service: AzureStorage
27 | # storage_account_name: your_account_name
28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
29 | # container: your_container_name
30 |
31 | # mirror:
32 | # service: Mirror
33 | # primary: local
34 | # mirrors: [ amazon, google, microsoft ]
35 |
--------------------------------------------------------------------------------
/test/dummy/lib/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yamasolutions/block-editor/3454f449b22a041191a6eee53c645e0d764841d3/test/dummy/lib/assets/.keep
--------------------------------------------------------------------------------
/test/dummy/log/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yamasolutions/block-editor/3454f449b22a041191a6eee53c645e0d764841d3/test/dummy/log/.keep
--------------------------------------------------------------------------------
/test/dummy/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/test/dummy/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/test/dummy/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
We're sorry, but something went wrong.
62 |
63 |
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/test/dummy/public/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yamasolutions/block-editor/3454f449b22a041191a6eee53c645e0d764841d3/test/dummy/public/apple-touch-icon-precomposed.png
--------------------------------------------------------------------------------
/test/dummy/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yamasolutions/block-editor/3454f449b22a041191a6eee53c645e0d764841d3/test/dummy/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/test/dummy/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yamasolutions/block-editor/3454f449b22a041191a6eee53c645e0d764841d3/test/dummy/public/favicon.ico
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | # Configure Rails Environment
2 | ENV["RAILS_ENV"] = "test"
3 |
4 | require_relative "../test/dummy/config/environment"
5 | ActiveRecord::Migrator.migrations_paths = [File.expand_path("../test/dummy/db/migrate", __dir__)]
6 | ActiveRecord::Migrator.migrations_paths << File.expand_path('../db/migrate', __dir__)
7 | require "rails/test_help"
8 |
9 |
10 | # Load fixtures from the engine
11 | if ActiveSupport::TestCase.respond_to?(:fixture_path=)
12 | ActiveSupport::TestCase.fixture_path = File.expand_path("fixtures", __dir__)
13 | ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path
14 | ActiveSupport::TestCase.file_fixture_path = ActiveSupport::TestCase.fixture_path + "/files"
15 | ActiveSupport::TestCase.fixtures :all
16 | end
17 |
--------------------------------------------------------------------------------
/webpack/development.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development'
2 |
3 | const environment = require('./environment')
4 |
5 | module.exports = environment.toWebpackConfig()
6 |
--------------------------------------------------------------------------------
/webpack/environment.js:
--------------------------------------------------------------------------------
1 | const { environment } = require('@rails/webpacker')
2 |
3 | module.exports = environment
4 |
--------------------------------------------------------------------------------
/webpack/production.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_ENV = process.env.NODE_ENV || 'production'
2 |
3 | const environment = require('./environment')
4 |
5 | module.exports = environment.toWebpackConfig()
6 |
--------------------------------------------------------------------------------
/webpack/test.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development'
2 |
3 | const environment = require('./environment')
4 |
5 | module.exports = environment.toWebpackConfig()
6 |
--------------------------------------------------------------------------------