headmin 0.1.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +0 -1
- data/Gemfile.lock +24 -2
- data/README.md +8 -43
- data/app/controllers/concerns/headmin/authentication.rb +0 -8
- data/app/controllers/concerns/headmin/pagination.rb +1 -1
- data/app/helpers/headmin/admin_helper.rb +4 -4
- data/app/models/concerns/headmin/block.rb +11 -0
- data/app/models/concerns/headmin/blockable.rb +10 -0
- data/app/models/concerns/headmin/field.rb +17 -0
- data/app/models/concerns/headmin/fieldable.rb +44 -0
- data/app/services/block_service.rb +68 -0
- data/app/views/{layouts → examples}/admin.html.erb +0 -0
- data/app/views/{layouts/admin → examples}/auth.html.erb +0 -0
- data/app/views/headmin/_blocks.html.erb +24 -0
- data/app/views/headmin/_breadcrumbs.html.erb +9 -5
- data/app/views/headmin/_card.html.erb +48 -0
- data/app/views/headmin/_dropdown.html.erb +18 -0
- data/app/views/headmin/_filters.html.erb +56 -34
- data/app/views/headmin/_form.html.erb +60 -6
- data/app/views/headmin/_heading.html.erb +8 -4
- data/app/views/headmin/_index.html.erb +9 -8
- data/app/views/headmin/_notifications.html.erb +8 -0
- data/app/views/headmin/_pagination.html.erb +11 -7
- data/app/views/headmin/_popup.html.erb +26 -0
- data/app/views/headmin/_table.html.erb +11 -4
- data/app/views/headmin/dropdown/_button.html.erb +13 -0
- data/app/views/headmin/dropdown/_devise.html.erb +21 -0
- data/app/views/headmin/{layout/dropdown → dropdown}/_divider.html.erb +1 -1
- data/app/views/headmin/dropdown/_item.html.erb +18 -0
- data/app/views/headmin/dropdown/_list.html.erb +11 -0
- data/app/views/headmin/dropdown/_locale.html.erb +17 -0
- data/app/views/headmin/filters/_date.html.erb +21 -16
- data/app/views/headmin/filters/_search.html.erb +18 -18
- data/app/views/headmin/filters/_select.html.erb +28 -23
- data/app/views/headmin/filters/filter/_button.html.erb +15 -6
- data/app/views/headmin/filters/filter/_menu_item.html.erb +2 -2
- data/app/views/headmin/filters/filter/_template.html.erb +2 -2
- data/app/views/headmin/forms/_actions.html.erb +4 -8
- data/app/views/headmin/forms/_base.html.erb +60 -0
- data/app/views/headmin/forms/_blocks.html.erb +32 -0
- data/app/views/headmin/forms/_checkbox.html.erb +39 -0
- data/app/views/headmin/forms/_ckeditor.html.erb +42 -0
- data/app/views/headmin/forms/_date.html.erb +45 -0
- data/app/views/headmin/forms/_email.html.erb +39 -0
- data/app/views/headmin/forms/_file.html.erb +40 -0
- data/app/views/headmin/forms/_image.html.erb +55 -0
- data/app/views/headmin/forms/_label.html.erb +24 -0
- data/app/views/headmin/forms/_number.html.erb +50 -0
- data/app/views/headmin/forms/_password.html.erb +69 -0
- data/app/views/headmin/forms/_redactorx.html.erb +49 -0
- data/app/views/headmin/forms/_repeater.html.erb +133 -0
- data/app/views/headmin/forms/_select.html.erb +70 -0
- data/app/views/headmin/forms/_text.html.erb +62 -0
- data/app/views/headmin/forms/_textarea.html.erb +39 -0
- data/app/views/headmin/forms/_url.html.erb +39 -0
- data/app/views/headmin/forms/_validation.html.erb +21 -0
- data/app/views/headmin/forms/actions/_destroy.html.erb +13 -0
- data/app/views/headmin/forms/actions/_save.html.erb +12 -0
- data/app/views/headmin/forms/actions/_view.html.erb +15 -0
- data/app/views/headmin/forms/fields/_base.html.erb +25 -0
- data/app/views/headmin/forms/fields/_file.html.erb +15 -22
- data/app/views/headmin/forms/fields/_group.html.erb +38 -0
- data/app/views/headmin/forms/fields/_image.html.erb +15 -35
- data/app/views/headmin/forms/fields/_list.html.erb +26 -0
- data/app/views/headmin/forms/fields/_text.html.erb +15 -37
- data/app/views/headmin/forms/repeater/_row.html.erb +51 -0
- data/app/views/headmin/heading/_title.html.erb +1 -1
- data/app/views/headmin/layout/_body.html.erb +1 -1
- data/app/views/headmin/layout/_content.html.erb +1 -1
- data/app/views/headmin/layout/_footer.html.erb +1 -1
- data/app/views/headmin/layout/_header.html.erb +1 -1
- data/app/views/headmin/layout/_main.html.erb +2 -2
- data/app/views/headmin/layout/_sidebar.html.erb +1 -1
- data/app/views/headmin/layout/sidebar/{_menu.html.erb → _nav.html.erb} +1 -1
- data/app/views/headmin/nav/_item.html.erb +21 -0
- data/app/views/headmin/nav/item/_devise.html.erb +21 -0
- data/app/views/headmin/nav/item/_locale.html.erb +17 -0
- data/app/views/headmin/pagination/_per_page.html.erb +18 -0
- data/app/views/headmin/{kaminari → pagination/kaminari}/_first_page.html.erb +0 -0
- data/app/views/headmin/{kaminari → pagination/kaminari}/_gap.html.erb +0 -0
- data/app/views/headmin/{kaminari → pagination/kaminari}/_last_page.html.erb +0 -0
- data/app/views/headmin/{kaminari → pagination/kaminari}/_next_page.html.erb +0 -0
- data/app/views/headmin/{kaminari → pagination/kaminari}/_page.html.erb +1 -1
- data/app/views/headmin/{kaminari → pagination/kaminari}/_paginator.html.erb +0 -0
- data/app/views/headmin/{kaminari → pagination/kaminari}/_prev_page.html.erb +0 -0
- data/app/views/headmin/table/_actions.html.erb +24 -17
- data/app/views/headmin/table/_body.html.erb +12 -10
- data/app/views/headmin/table/_foot.html.erb +8 -4
- data/app/views/headmin/table/_footer.html.erb +3 -7
- data/app/views/headmin/table/_head.html.erb +3 -1
- data/app/views/headmin/table/_header.html.erb +3 -7
- data/app/views/headmin/table/actions/_action.html.erb +26 -9
- data/app/views/headmin/table/actions/_delete.html.erb +11 -7
- data/app/views/headmin/table/actions/_export.html.erb +11 -7
- data/app/views/headmin/table/body/_association.html.erb +1 -1
- data/app/views/headmin/table/body/_boolean.erb +1 -1
- data/app/views/headmin/table/body/_currency.html.erb +1 -1
- data/app/views/headmin/table/body/_date.html.erb +1 -1
- data/app/views/headmin/table/body/_id.html.erb +2 -2
- data/app/views/headmin/table/body/_row.html.erb +1 -1
- data/app/views/headmin/table/body/_string.html.erb +1 -1
- data/app/views/headmin/table/body/_text.html.erb +1 -1
- data/app/views/headmin/table/foot/_cell.html.erb +1 -1
- data/app/views/headmin/table/foot/_id.html.erb +2 -2
- data/app/views/headmin/table/head/_cell.html.erb +1 -1
- data/app/views/headmin/table/head/_id.html.erb +3 -3
- data/app/views/headmin/views/devise/confirmations/_new.html.erb +9 -0
- data/app/views/headmin/views/devise/passwords/_edit.html.erb +12 -0
- data/app/views/headmin/views/devise/passwords/_new.html.erb +9 -0
- data/app/views/{admin/users/registrations/edit.html.erb → headmin/views/devise/registrations/_edit.html.erb} +5 -5
- data/app/views/headmin/views/devise/registrations/_new.html.erb +11 -0
- data/app/views/headmin/views/devise/sessions/_new.html.erb +13 -0
- data/app/views/{admin/users → headmin/views/devise}/shared/_error_messages.html.erb +0 -0
- data/app/views/{admin/users → headmin/views/devise}/shared/_links.html.erb +0 -0
- data/app/views/headmin/views/devise/unlocks/_new.html.erb +10 -0
- data/config/initializers/customize_input_error.rb +9 -0
- data/config/locales/devise/en.yml +65 -0
- data/config/locales/en.yml +2 -134
- data/config/locales/headmin/filters/en.yml +13 -0
- data/config/locales/headmin/filters/nl.yml +13 -0
- data/config/locales/headmin/forms/en.yml +34 -0
- data/config/locales/headmin/forms/nl.yml +33 -0
- data/config/locales/headmin/heading/en.yml +5 -0
- data/config/locales/headmin/heading/nl.yml +5 -0
- data/config/locales/headmin/layout/en.yml +14 -0
- data/config/locales/headmin/layout/nl.yml +14 -0
- data/config/locales/headmin/pagination/en.yml +21 -0
- data/config/locales/headmin/pagination/nl.yml +21 -0
- data/config/locales/headmin/table/en.yml +23 -0
- data/config/locales/headmin/table/nl.yml +23 -0
- data/config/locales/headmin/views/en.yml +58 -0
- data/config/locales/headmin/views/nl.yml +58 -0
- data/config/locales/nl.yml +2 -135
- data/dist/css/headmin.css +3182 -743
- data/dist/js/headmin.js +729 -33
- data/docs/README.md +2 -1
- data/docs/blocks.md +70 -85
- data/docs/devise.md +1 -60
- data/docs/fields.md +57 -0
- data/headmin.gemspec +1 -0
- data/lib/generators/headmin/blocks_generator.rb +19 -0
- data/lib/generators/headmin/fields_generator.rb +19 -0
- data/lib/generators/templates/migrations/create_blocks.rb +10 -0
- data/lib/generators/templates/migrations/create_fields.rb +13 -0
- data/lib/generators/templates/models/block.rb +3 -0
- data/lib/generators/templates/models/field.rb +3 -0
- data/lib/headmin/version.rb +1 -1
- data/package.json +6 -6
- data/src/js/headmin/controllers/blocks_controller.js +103 -0
- data/src/js/headmin/controllers/filters_controller.js +23 -12
- data/src/js/headmin/controllers/popup_controller.js +68 -0
- data/src/js/headmin/controllers/repeater_controller.js +117 -11
- data/src/js/headmin/controllers/table_actions_controller.js +16 -5
- data/src/js/headmin/controllers/table_controller.js +84 -1
- data/src/js/headmin/headmin.js +29 -56
- data/src/scss/headmin/filters.scss +0 -14
- data/src/scss/headmin/form.scss +43 -3
- data/src/scss/headmin/popup.scss +17 -0
- data/src/scss/headmin/table.scss +9 -6
- data/src/scss/headmin.scss +7 -4
- data/src/scss/vendor/redactorx/override.css +3 -0
- data/src/scss/vendor/redactorx/redactorx.css +1460 -0
- data/yarn.lock +105 -2210
- metadata +108 -62
- data/app/controllers/admin/users/confirmations_controller.rb +0 -31
- data/app/controllers/admin/users/omniauth_callbacks_controller.rb +0 -31
- data/app/controllers/admin/users/passwords_controller.rb +0 -35
- data/app/controllers/admin/users/registrations_controller.rb +0 -63
- data/app/controllers/admin/users/sessions_controller.rb +0 -28
- data/app/controllers/admin/users/unlocks_controller.rb +0 -31
- data/app/views/admin/users/confirmations/new.html.erb +0 -9
- data/app/views/admin/users/mailer/confirmation_instructions.html.erb +0 -5
- data/app/views/admin/users/mailer/email_changed.html.erb +0 -7
- data/app/views/admin/users/mailer/password_change.html.erb +0 -3
- data/app/views/admin/users/mailer/reset_password_instructions.html.erb +0 -8
- data/app/views/admin/users/mailer/unlock_instructions.html.erb +0 -7
- data/app/views/admin/users/passwords/edit.html.erb +0 -12
- data/app/views/admin/users/passwords/new.html.erb +0 -9
- data/app/views/admin/users/registrations/new.html.erb +0 -11
- data/app/views/admin/users/sessions/new.html.erb +0 -13
- data/app/views/admin/users/unlocks/new.html.erb +0 -10
- data/app/views/headmin/forms/_group.html.erb +0 -36
- data/app/views/headmin/forms/fields/_checkbox.html.erb +0 -23
- data/app/views/headmin/forms/fields/_ckeditor.html.erb +0 -28
- data/app/views/headmin/forms/fields/_currency.html.erb +0 -24
- data/app/views/headmin/forms/fields/_date.html.erb +0 -36
- data/app/views/headmin/forms/fields/_email.html.erb +0 -39
- data/app/views/headmin/forms/fields/_label.html.erb +0 -9
- data/app/views/headmin/forms/fields/_multiple_select.html.erb +0 -37
- data/app/views/headmin/forms/fields/_password.html.erb +0 -39
- data/app/views/headmin/forms/fields/_repeater.html.erb +0 -48
- data/app/views/headmin/forms/fields/_select.html.erb +0 -36
- data/app/views/headmin/forms/fields/_select_tags.html.erb +0 -32
- data/app/views/headmin/forms/fields/_textarea.html.erb +0 -29
- data/app/views/headmin/forms/fields/_url.html.erb +0 -38
- data/app/views/headmin/forms/fields/_validation.html.erb +0 -12
- data/app/views/headmin/forms/fields/repeater/_row.html.erb +0 -16
- data/app/views/headmin/layout/dropdown/_item.html.erb +0 -17
- data/app/views/headmin/layout/header/_account.html.erb +0 -25
- data/app/views/headmin/layout/header/_locale.html.erb +0 -19
- data/app/views/headmin/layout/sidebar/menu/_account.html.erb +0 -25
- data/app/views/headmin/layout/sidebar/menu/_item.html.erb +0 -16
- data/app/views/headmin/layout/sidebar/menu/_locale.html.erb +0 -18
- data/src/js/headmin/controllers/index_controller.js +0 -79
- data/src/js/headmin/controllers/repeater_row_controller.js +0 -54
- data/src/scss/vendor/choices/cross-inverse.svg +0 -6
- data/src/scss/vendor/choices/cross.svg +0 -6
- data/src/scss/vendor/choices/custom.scss +0 -28
- data/src/scss/vendor/choices/variables.scss +0 -16
data/docs/README.md
CHANGED
data/docs/blocks.md
CHANGED
@@ -1,116 +1,101 @@
|
|
1
|
-
# Blocks
|
1
|
+
# Blocks
|
2
2
|
|
3
|
-
##
|
4
|
-
|
3
|
+
## Installation
|
4
|
+
Run the following generators to generate the migration files.
|
5
5
|
```
|
6
|
-
|
6
|
+
rails generate headmin:blocks
|
7
|
+
rails db:migrate
|
7
8
|
```
|
8
9
|
|
9
|
-
|
10
|
-
```
|
11
|
-
id = 1 block_id="1" name = 'people_title' type = 'string' value = 'test'
|
12
|
-
id = 2 block_id="1" name = 'people_people' type = 'list'
|
13
|
-
id = 3 block_id="1" name = 'people_people_1_person' type = 'group' parent_id = 2
|
14
|
-
id = 4 block_id="1" name = 'people_people_1_person_name' type = 'string' parent_id = 3 value = 'Jef'
|
15
|
-
id = 5 block_id="1" name = 'people_people_2_person' type = 'group' parent_id = 2
|
16
|
-
id = 6 block_id="1" name = 'people_people_2_person_name' type = 'string' parent_id = 6 value = 'Gert-Jan'
|
17
|
-
```
|
18
|
-
|
19
|
-
## Models
|
20
|
-
|
10
|
+
## Getting Started
|
21
11
|
|
22
|
-
|
23
|
-
# models/page.rb
|
12
|
+
### Setup model
|
24
13
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
```
|
29
|
-
|
30
|
-
```rb
|
31
|
-
# models/block.rb
|
32
|
-
|
33
|
-
class Block < ActiveRecord::Model
|
34
|
-
belongs_to :blockable, polymorphic: true
|
35
|
-
has_many :fields
|
36
|
-
|
37
|
-
# db to hash
|
38
|
-
def fields_hash
|
39
|
-
{
|
40
|
-
title: 'test',
|
41
|
-
people: [
|
42
|
-
{name: 'Jef'},
|
43
|
-
{name: 'Gert-Jan'}
|
44
|
-
]
|
45
|
-
}
|
46
|
-
end
|
14
|
+
```ruby
|
15
|
+
class Page < ApplicationRecord
|
16
|
+
include Headmin::Blockable
|
47
17
|
end
|
48
18
|
```
|
49
19
|
|
50
|
-
|
51
|
-
# models/block/fields.rb
|
20
|
+
### Setup forms
|
52
21
|
|
53
|
-
|
54
|
-
|
22
|
+
The view "headmin/forms/blocks" will render all the form views for the blocks in the database.
|
23
|
+
A hidden template form will be rendered for all types defined in `allow:`
|
55
24
|
|
56
|
-
|
57
|
-
|
25
|
+
```erb
|
26
|
+
# app/views/admin/pages/_form.html.erb
|
27
|
+
<%= render 'headmin/form', model: [:admin, @page] do |form| %>
|
28
|
+
<%= render "headmin/forms/blocks", form: form %>
|
29
|
+
<% end %>
|
58
30
|
```
|
59
31
|
|
60
|
-
|
61
|
-
|
62
|
-
# website/pages_controller.rb
|
32
|
+
For each type of block you want to include, create a template in `views/admin/blocks`.
|
33
|
+
Make sure to include a hidden field to store the name of the block.
|
63
34
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
end
|
35
|
+
```erb
|
36
|
+
# app/views/admin/blocks/_contact.html.erb
|
37
|
+
<%= form.hidden_field :name, value: :contact %>
|
38
|
+
...
|
69
39
|
```
|
70
40
|
|
71
|
-
|
41
|
+
### Render blocks in frontend
|
42
|
+
|
72
43
|
```erb
|
73
|
-
#
|
44
|
+
# app/views/website/pages/show.html.erb
|
45
|
+
<%= render 'headmin/blocks', blockable: @page %>
|
46
|
+
```
|
74
47
|
|
75
|
-
|
48
|
+
This will render all the blocks associated with the blockable model (e.g. Page).
|
76
49
|
|
77
|
-
|
78
|
-
<%= render 'blocks/menu', required: true, moveable: false %>
|
79
|
-
<%= render 'blocks/contact_form', required: true, moveable: false %>
|
80
|
-
<%= render 'blocks/map', required: true, moveable: false %>
|
50
|
+
For each block in the admin, you'll need to create a corresponding template in your frontend.
|
81
51
|
|
82
|
-
|
83
|
-
|
84
|
-
<%=
|
85
|
-
<%= render 'blocks/video', required: false, moveable: true %>
|
52
|
+
```erb
|
53
|
+
# app/views/website/blocks/_contact.html.erb
|
54
|
+
<%= block.name %>
|
86
55
|
```
|
87
56
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
57
|
+
## Blocks + fields = Magic
|
58
|
+
|
59
|
+
### Add fields to blocks
|
60
|
+
|
61
|
+
Setup a block model by including `Headmin::Fieldable`
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
class Block < ApplicationRecord
|
65
|
+
include Headmin::Block
|
66
|
+
include Headmin::Fieldable
|
67
|
+
end
|
98
68
|
```
|
99
69
|
|
70
|
+
### Use fields in admin blocks
|
71
|
+
|
100
72
|
```erb
|
101
|
-
#
|
73
|
+
# app/views/admin/blocks/_contact.html.erb
|
74
|
+
<%= form.hidden_field :name, value: :contact %>
|
102
75
|
|
103
|
-
|
104
|
-
|
76
|
+
<!-- Title -->
|
77
|
+
<%= render 'headmin/forms/fields/text', form: form, name: :title do |field, attribute, label| %>
|
78
|
+
<%= render 'headmin/forms/text', form: field, attribute: attribute, label: label %>
|
79
|
+
<% end %>
|
80
|
+
|
81
|
+
<!-- People list -->
|
82
|
+
<%= render 'headmin/forms/fields/list', form: form, name: :people do |item| %>
|
83
|
+
<%= render 'headmin/forms/fields/text', form: item, name: :name do |field, attribute, label| %>
|
84
|
+
<%= render 'headmin/forms/text', form: field, attribute: attribute, label: label %>
|
85
|
+
<% end %>
|
105
86
|
<% end %>
|
106
87
|
```
|
107
88
|
|
108
|
-
|
109
|
-
# website/blocks/_people.html.erb
|
89
|
+
### Render blocks in your frontend
|
110
90
|
|
91
|
+
```erb
|
92
|
+
# app/views/website/blocks/_contact.html.erb
|
111
93
|
<% fields = block.fields_hash %>
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
<%
|
116
|
-
|
94
|
+
|
95
|
+
<h1><%= fields["title"] %></h1>
|
96
|
+
<ul>
|
97
|
+
<% fields["people"].each do |person| %>
|
98
|
+
<li><%= person["name"] %></h2></li>
|
99
|
+
<% end %>
|
100
|
+
</ul>
|
101
|
+
```
|
data/docs/devise.md
CHANGED
@@ -1,62 +1,3 @@
|
|
1
1
|
# Authentication
|
2
2
|
|
3
|
-
|
4
|
-
Admin and normal User. Only admins have access to the headmin back office. It is optional if normal users
|
5
|
-
are allowed to login/register from the website.
|
6
|
-
|
7
|
-
##Installation
|
8
|
-
|
9
|
-
#### Setup devise
|
10
|
-
Add the gem devise and bundle install. Create a user model:
|
11
|
-
|
12
|
-
```
|
13
|
-
rails generate devise User
|
14
|
-
```
|
15
|
-
|
16
|
-
Add the field "type" to the user model
|
17
|
-
```
|
18
|
-
rails g migration AddTypeToUsers type:string
|
19
|
-
rails db:migrate
|
20
|
-
```
|
21
|
-
|
22
|
-
In models/ add the file admin.rb
|
23
|
-
```
|
24
|
-
class Admin < User
|
25
|
-
end
|
26
|
-
```
|
27
|
-
|
28
|
-
#### Setup routes
|
29
|
-
In routes.rb
|
30
|
-
```
|
31
|
-
namespace(:admin) do
|
32
|
-
devise_for :admins, path: 'users', singular: 'admin', controllers: {
|
33
|
-
sessions: 'admin/users/sessions',
|
34
|
-
registrations: 'admin/users/registrations',
|
35
|
-
passwords: 'admin/users/passwords',
|
36
|
-
unlocks: 'admin/users/unlocks',
|
37
|
-
confirmations: 'admin/users/confirmations',
|
38
|
-
}
|
39
|
-
end
|
40
|
-
```
|
41
|
-
|
42
|
-
In case, no normal users are necessary, you can discard the next step:
|
43
|
-
```
|
44
|
-
scope module: 'website' do
|
45
|
-
devise_for :users, controllers: {
|
46
|
-
sessions: 'website/users/sessions',
|
47
|
-
registrations: 'website/users/registrations',
|
48
|
-
passwords: 'website/users/passwords',
|
49
|
-
unlocks: 'website/users/unlocks',
|
50
|
-
confirmations: 'website/users/confirmations',
|
51
|
-
}
|
52
|
-
end
|
53
|
-
```
|
54
|
-
|
55
|
-
#### Setup protected routes and configuration for headmin
|
56
|
-
In controllers/admin_controller.rb
|
57
|
-
```
|
58
|
-
class AdminController < ApplicationController
|
59
|
-
alias_method :devise_current_user, :current_user
|
60
|
-
include Headmin::Authentication
|
61
|
-
end
|
62
|
-
```
|
3
|
+
TODO: rewrite
|
data/docs/fields.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# Fields
|
2
|
+
|
3
|
+
## Installation
|
4
|
+
Run the following generators to generate the migration files.
|
5
|
+
```
|
6
|
+
rails generate headmin:fields
|
7
|
+
rails db:migrate
|
8
|
+
```
|
9
|
+
|
10
|
+
## Getting Started
|
11
|
+
|
12
|
+
### Setup model
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
class Settings < ApplicationRecord
|
16
|
+
include Headmin::Fieldable
|
17
|
+
end
|
18
|
+
```
|
19
|
+
|
20
|
+
### Setup forms
|
21
|
+
|
22
|
+
```erb
|
23
|
+
# app/views/admin/settings/_form.html.erb
|
24
|
+
|
25
|
+
<!-- Company name -->
|
26
|
+
<%= render 'headmin/forms/fields/text', form: form, name: :company_name do |field, attribute, label| %>
|
27
|
+
<%= render 'headmin/forms/text', form: field, attribute: attribute, label: label %>
|
28
|
+
<% end %>
|
29
|
+
|
30
|
+
<!-- People list -->
|
31
|
+
<%= render 'headmin/forms/fields/list', form: form, name: :people do |item| %>
|
32
|
+
<%= render 'headmin/forms/fields/text', form: item, name: :name do |field, attribute, label| %>
|
33
|
+
<%= render 'headmin/forms/text', form: field, attribute: attribute, label: label %>
|
34
|
+
<% end %>
|
35
|
+
<% end %>
|
36
|
+
```
|
37
|
+
|
38
|
+
#### Type of fields
|
39
|
+
- Text: `headmin/forms/fields/text`
|
40
|
+
- File: `headmin/forms/fields/file`
|
41
|
+
- Image: `headmin/forms/fields/image`
|
42
|
+
- List: `headmin/forms/fields/list`
|
43
|
+
- Group: `headmin/forms/fields/group`
|
44
|
+
|
45
|
+
### Render fields in frontend
|
46
|
+
|
47
|
+
```erb
|
48
|
+
# app/views/website/settings/show.html.erb
|
49
|
+
<% fields = @setting.fields_hash %>
|
50
|
+
|
51
|
+
<h1><%= fields["company_name"] %></h1>
|
52
|
+
<ul>
|
53
|
+
<% fields["people"].each do |person| %>
|
54
|
+
<li><%= person["name"] %></h2></li>
|
55
|
+
<% end %>
|
56
|
+
</ul>
|
57
|
+
```
|
data/headmin.gemspec
CHANGED
@@ -29,6 +29,7 @@ Gem::Specification.new do |spec|
|
|
29
29
|
|
30
30
|
# Uncomment to register a new dependency of your gem
|
31
31
|
# spec.add_dependency "example-gem", "~> 1.0"
|
32
|
+
spec.add_runtime_dependency 'closure_tree', '~> 7.3'
|
32
33
|
|
33
34
|
# For more information and examples about making a new gem, checkout our
|
34
35
|
# guide at: https://bundler.io/guides/creating_gem.html
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Headmin
|
2
|
+
class BlocksGenerator < Rails::Generators::Base
|
3
|
+
include Rails::Generators::Migration
|
4
|
+
|
5
|
+
source_root File.expand_path('../../templates', __FILE__)
|
6
|
+
|
7
|
+
def blocks
|
8
|
+
template 'models/block.rb', 'app/models/block.rb'
|
9
|
+
migration_template 'migrations/create_blocks.rb', 'db/migrate/create_blocks.rb'
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def self.next_migration_number(dirname)
|
15
|
+
next_migration_number = current_migration_number(dirname) + 1
|
16
|
+
ActiveRecord::Migration.next_migration_number(next_migration_number)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Headmin
|
2
|
+
class FieldsGenerator < Rails::Generators::Base
|
3
|
+
include Rails::Generators::Migration
|
4
|
+
|
5
|
+
source_root File.expand_path('../../templates', __FILE__)
|
6
|
+
|
7
|
+
def blocks
|
8
|
+
template 'models/field.rb', 'app/models/field.rb'
|
9
|
+
migration_template 'migrations/create_fields.rb', 'db/migrate/create_fields.rb'
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def self.next_migration_number(dirname)
|
15
|
+
next_migration_number = current_migration_number(dirname) + 1
|
16
|
+
ActiveRecord::Migration.next_migration_number(next_migration_number)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class CreateFields < ActiveRecord::Migration[6.1]
|
2
|
+
def change
|
3
|
+
create_table :fields do |t|
|
4
|
+
t.references :fieldable, polymorphic: true
|
5
|
+
t.string :name
|
6
|
+
t.string :field_type
|
7
|
+
t.integer :parent_id
|
8
|
+
t.integer :position
|
9
|
+
t.text :value
|
10
|
+
t.timestamps
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/headmin/version.rb
CHANGED
data/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "headmin",
|
3
|
-
"version": "0.
|
4
|
-
"description": "
|
3
|
+
"version": "0.2.0",
|
4
|
+
"description": "Admin component library",
|
5
5
|
"main": "src/js/headmin.js",
|
6
6
|
"sass": "src/scss/headmin.scss",
|
7
7
|
"style": "src/css/headmin.css",
|
@@ -20,14 +20,14 @@
|
|
20
20
|
},
|
21
21
|
"homepage": "https://github.com/insiting/headmin#readme",
|
22
22
|
"dependencies": {
|
23
|
+
"@popperjs/core": "^2.9.3",
|
23
24
|
"@rails/ujs": "^6.0.0",
|
24
|
-
"bootstrap": "^5.
|
25
|
-
"choices.js": "^9.0.1",
|
25
|
+
"bootstrap": "^5.1.1",
|
26
26
|
"ckeditor5-build-classic-simple-upload-adapter-image-resize": "^1.0.4",
|
27
27
|
"flatpickr": "^4.6.9",
|
28
|
-
"popper": "^1.0.1",
|
29
28
|
"sortablejs": "^1.13.0",
|
30
|
-
"stimulus": "^2.0.0"
|
29
|
+
"stimulus": "^2.0.0",
|
30
|
+
"tom-select": "^2.0.0-rc.4"
|
31
31
|
},
|
32
32
|
"devDependencies": {
|
33
33
|
"@babel/core": "^7.12.10",
|
@@ -0,0 +1,103 @@
|
|
1
|
+
import {Controller} from "stimulus"
|
2
|
+
import Sortable from "sortablejs";
|
3
|
+
import { createPopper } from '@popperjs/core';
|
4
|
+
|
5
|
+
export default class extends Controller {
|
6
|
+
static get targets() {
|
7
|
+
return ["templateBlock", "block", "blocks", "templateEmpty", "button", "buttons"]
|
8
|
+
}
|
9
|
+
|
10
|
+
connect() {
|
11
|
+
new Sortable(this.blocksTarget, {
|
12
|
+
onEnd: () => {
|
13
|
+
this.reorderPositions()
|
14
|
+
}
|
15
|
+
})
|
16
|
+
|
17
|
+
this.toggleEmpty()
|
18
|
+
}
|
19
|
+
|
20
|
+
toggleEmpty() {
|
21
|
+
if(this.blockCount() > 0) {
|
22
|
+
const empty = this.blocksTarget.querySelector('#blocks-empty')
|
23
|
+
if(empty) {
|
24
|
+
empty.remove()
|
25
|
+
}
|
26
|
+
} else {
|
27
|
+
const empty = this.templateEmptyTarget.innerHTML
|
28
|
+
this.blocksTarget.insertAdjacentHTML('beforeend', empty)
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
add(event) {
|
33
|
+
event.preventDefault()
|
34
|
+
const blockType = event.target.dataset.type
|
35
|
+
let html = this.templateBlockTargets.filter(blockTarget => blockTarget.id === blockType)[0].innerHTML
|
36
|
+
|
37
|
+
html = this.setPosition(html)
|
38
|
+
|
39
|
+
const element = this.blocksTarget.querySelector(`li[data-position='${event.target.dataset.position}']`)
|
40
|
+
|
41
|
+
if (element) {
|
42
|
+
element.insertAdjacentHTML('afterend', html)
|
43
|
+
}
|
44
|
+
else {
|
45
|
+
this.blocksTarget.insertAdjacentHTML('afterbegin', html)
|
46
|
+
}
|
47
|
+
|
48
|
+
// Dispatch an event
|
49
|
+
this.blocksTarget.dispatchEvent(new CustomEvent('headmin:reinit', {bubbles: true}))
|
50
|
+
|
51
|
+
this.reorderPositions()
|
52
|
+
this.toggleEmpty()
|
53
|
+
}
|
54
|
+
|
55
|
+
remove(event) {
|
56
|
+
event.preventDefault()
|
57
|
+
|
58
|
+
const block = event.target.closest(".list-group-item")
|
59
|
+
const destroyInput = block.querySelector("input[name*='_destroy']")
|
60
|
+
|
61
|
+
if (destroyInput) {
|
62
|
+
destroyInput.value = 1
|
63
|
+
block.style.display = 'none'
|
64
|
+
} else {
|
65
|
+
block.remove()
|
66
|
+
}
|
67
|
+
|
68
|
+
this.reorderPositions()
|
69
|
+
this.toggleEmpty()
|
70
|
+
}
|
71
|
+
|
72
|
+
setPosition(html) {
|
73
|
+
const position = this.retrieveLastPosition() + 1
|
74
|
+
|
75
|
+
const regex = new RegExp('99999', "g");
|
76
|
+
return html.replace(regex, position)
|
77
|
+
}
|
78
|
+
|
79
|
+
retrieveLastPosition() {
|
80
|
+
const blocks = Array.from(this.blockTargets)
|
81
|
+
if (blocks.length < 1) {
|
82
|
+
return 0
|
83
|
+
}
|
84
|
+
|
85
|
+
const lastBlock = blocks.slice(-1)[0]
|
86
|
+
return parseInt(lastBlock.querySelector("[name*='position']").value)
|
87
|
+
}
|
88
|
+
|
89
|
+
reorderPositions() {
|
90
|
+
for (let [index, block] of this.blockTargets.entries()) {
|
91
|
+
this.changePositionInfo(block, index)
|
92
|
+
}
|
93
|
+
}
|
94
|
+
|
95
|
+
changePositionInfo(block, index) {
|
96
|
+
block.setAttribute("data-position", index)
|
97
|
+
block.querySelector("input[name*='position']").value = index
|
98
|
+
}
|
99
|
+
|
100
|
+
blockCount() {
|
101
|
+
return this.blockTargets.length;
|
102
|
+
}
|
103
|
+
}
|
@@ -1,9 +1,8 @@
|
|
1
1
|
import {Controller} from "stimulus"
|
2
|
-
import {Headmin} from "../headmin";
|
3
2
|
|
4
3
|
export default class extends Controller {
|
5
4
|
static get targets() {
|
6
|
-
return ["form", "list", "input", "template", "button"]
|
5
|
+
return ["form", "list", "input", "template", "button", "menuItem"]
|
7
6
|
}
|
8
7
|
|
9
8
|
add(event) {
|
@@ -13,13 +12,19 @@ export default class extends Controller {
|
|
13
12
|
if (button) {
|
14
13
|
this.openFilter(button)
|
15
14
|
} else {
|
16
|
-
|
17
|
-
this.listTarget.insertAdjacentHTML('beforeend', template.innerHTML)
|
18
|
-
const button = this.getButtonByName(name)
|
19
|
-
Headmin.initPlugins()
|
15
|
+
this.createFilter(name)
|
20
16
|
}
|
21
17
|
}
|
22
18
|
|
19
|
+
createFilter(name) {
|
20
|
+
let html = this.getTemplateHTML(name)
|
21
|
+
html = this.replaceIdsWithTimestamps(html)
|
22
|
+
this.listTarget.insertAdjacentHTML('beforeend', html)
|
23
|
+
|
24
|
+
// Dispatch an event
|
25
|
+
this.menuItemTarget.dispatchEvent(new CustomEvent('headmin:reinit', {bubbles: true}))
|
26
|
+
}
|
27
|
+
|
23
28
|
remove(event) {
|
24
29
|
const filter = event.currentTarget.closest('.h-filter')
|
25
30
|
filter.remove()
|
@@ -35,12 +40,6 @@ export default class extends Controller {
|
|
35
40
|
this.formTarget.submit()
|
36
41
|
}
|
37
42
|
|
38
|
-
getTemplateByName(name) {
|
39
|
-
return this.templateTargets.find(function (element) {
|
40
|
-
return element.dataset.filterName === name
|
41
|
-
})
|
42
|
-
}
|
43
|
-
|
44
43
|
getButtonByName(name) {
|
45
44
|
return this.buttonTargets.find(function (element) {
|
46
45
|
return element.dataset.filterName === name
|
@@ -50,4 +49,16 @@ export default class extends Controller {
|
|
50
49
|
openFilter(button) {
|
51
50
|
button.controller.open()
|
52
51
|
}
|
52
|
+
|
53
|
+
getTemplateHTML(name) {
|
54
|
+
const template = this.templateTargets.filter((element) => {
|
55
|
+
return element.dataset.filterName === name
|
56
|
+
})[0]
|
57
|
+
return template.innerHTML
|
58
|
+
}
|
59
|
+
|
60
|
+
replaceIdsWithTimestamps(html) {
|
61
|
+
const regex = new RegExp('template_id', "g");
|
62
|
+
return html.replace(regex, new Date().getTime())
|
63
|
+
}
|
53
64
|
}
|
@@ -0,0 +1,68 @@
|
|
1
|
+
import {Controller} from "stimulus"
|
2
|
+
import Sortable from "sortablejs";
|
3
|
+
import {createPopper} from '@popperjs/core';
|
4
|
+
|
5
|
+
export default class extends Controller {
|
6
|
+
static get targets() {
|
7
|
+
return ["popup", "button"]
|
8
|
+
}
|
9
|
+
|
10
|
+
static get values() {
|
11
|
+
return {id: String}
|
12
|
+
}
|
13
|
+
|
14
|
+
connect() {
|
15
|
+
// Clicked outside popup
|
16
|
+
document.addEventListener('click', (event) => {
|
17
|
+
this.handleOutsideClick(event)
|
18
|
+
})
|
19
|
+
}
|
20
|
+
|
21
|
+
handleOutsideClick(event) {
|
22
|
+
const inPopup = event.target.closest('[data-popup-target="popup"]') !== null
|
23
|
+
const inButton = event.target.closest('[data-popup-target="button"]') !== null
|
24
|
+
const openPopup = document.querySelector('[data-popup-target="popup"]:not(.closed)')
|
25
|
+
|
26
|
+
if(!inButton && !inPopup && openPopup) {
|
27
|
+
this.closePopup(openPopup)
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
open(event) {
|
32
|
+
const button = event.target.closest('[data-popup-target="button"]')
|
33
|
+
const popup = this.popupById(button.dataset['popupId'])
|
34
|
+
const passThru = button.dataset['popupPassThru']
|
35
|
+
|
36
|
+
if (passThru) {
|
37
|
+
// Pass click event to an element inside the popup
|
38
|
+
const passThruElement = popup.querySelector(passThru)
|
39
|
+
passThruElement.click()
|
40
|
+
|
41
|
+
} else {
|
42
|
+
// Open the popup
|
43
|
+
createPopper(button, popup)
|
44
|
+
this.openPopup(popup)
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
close(event) {
|
49
|
+
const button = event.target.closest('[data-popup-target="button"]')
|
50
|
+
const popup = this.popupById(button.dataset['popupId'])
|
51
|
+
|
52
|
+
this.closePopup(popup)
|
53
|
+
}
|
54
|
+
|
55
|
+
popupById(id) {
|
56
|
+
return this.popupTargets.find((popup) => {
|
57
|
+
return popup.dataset['popupId'] === id
|
58
|
+
})
|
59
|
+
}
|
60
|
+
|
61
|
+
openPopup(popup) {
|
62
|
+
popup.classList.remove('closed')
|
63
|
+
}
|
64
|
+
|
65
|
+
closePopup(popup) {
|
66
|
+
popup.classList.add('closed')
|
67
|
+
}
|
68
|
+
}
|