headmin 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -15
- data/Gemfile +1 -1
- data/Gemfile.lock +24 -2
- data/README.md +5 -34
- 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/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 +93 -61
- 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
|
+
```
|
|
@@ -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
|
+
}
|