katalyst-koi 4.18.1 → 5.0.0.alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/images/koi/icons/add.svg +3 -0
  3. data/app/assets/images/koi/icons/close.svg +1 -0
  4. data/app/assets/images/koi/koi.png +0 -0
  5. data/app/assets/javascripts/koi/controllers/file_field_controller.js +2 -2
  6. data/app/assets/javascripts/koi/controllers/index.js +0 -3
  7. data/app/assets/javascripts/koi/controllers/koi/modal_controller.js +40 -0
  8. data/app/assets/javascripts/koi/controllers/navigation_controller.js +14 -21
  9. data/app/assets/javascripts/koi/controllers/webauthn_registration_controller.js +4 -1
  10. data/app/assets/stylesheets/koi/blocks/actions.css +8 -0
  11. data/app/assets/stylesheets/koi/blocks/application-header.css +15 -0
  12. data/app/assets/stylesheets/koi/blocks/application-navigation.css +54 -0
  13. data/app/assets/stylesheets/koi/blocks/button.css +90 -0
  14. data/app/assets/stylesheets/koi/blocks/flash.css +19 -0
  15. data/app/assets/stylesheets/koi/blocks/icon.css +15 -0
  16. data/app/assets/stylesheets/koi/blocks/index.css +13 -0
  17. data/app/assets/stylesheets/koi/blocks/modal.css +26 -0
  18. data/app/assets/stylesheets/koi/blocks/navigation.css +23 -0
  19. data/app/assets/stylesheets/koi/blocks/page-header.css +31 -0
  20. data/app/assets/stylesheets/koi/blocks/pagy.css +82 -0
  21. data/app/assets/stylesheets/koi/blocks/prose.css +37 -0
  22. data/app/assets/stylesheets/koi/blocks/tables/index.css +4 -0
  23. data/app/assets/stylesheets/koi/{components/_query.scss → blocks/tables/query.css} +13 -13
  24. data/app/assets/stylesheets/koi/{base/_tables.scss → blocks/tables/table.css} +11 -59
  25. data/app/assets/stylesheets/koi/compositions/cover.css +17 -0
  26. data/app/assets/stylesheets/koi/{base/_flow.scss → compositions/flow.css} +1 -1
  27. data/app/assets/stylesheets/koi/compositions/index.css +4 -0
  28. data/app/assets/stylesheets/koi/compositions/wrapper.css +11 -0
  29. data/app/assets/stylesheets/koi/forms/caption.css +22 -0
  30. data/app/assets/stylesheets/koi/forms/checkboxes.css +153 -0
  31. data/app/assets/stylesheets/koi/forms/date-input.css +12 -0
  32. data/app/assets/stylesheets/koi/{components/_document-field.scss → forms/document-field.css} +20 -15
  33. data/app/assets/stylesheets/koi/forms/errors.css +38 -0
  34. data/app/assets/stylesheets/koi/forms/fieldset.css +73 -0
  35. data/app/assets/stylesheets/koi/forms/file-upload.css +20 -0
  36. data/app/assets/stylesheets/koi/forms/form-group.css +19 -0
  37. data/app/assets/stylesheets/koi/forms/hint.css +11 -0
  38. data/app/assets/stylesheets/koi/forms/image-field.css +96 -0
  39. data/app/assets/stylesheets/koi/forms/index.css +44 -0
  40. data/app/assets/stylesheets/koi/forms/input.css +194 -0
  41. data/app/assets/stylesheets/koi/forms/label.css +43 -0
  42. data/app/assets/stylesheets/koi/forms/password.css +18 -0
  43. data/app/assets/stylesheets/koi/forms/radios.css +162 -0
  44. data/app/assets/stylesheets/koi/forms/select.css +18 -0
  45. data/app/assets/stylesheets/koi/forms/textarea.css +3 -0
  46. data/app/assets/stylesheets/koi/forms/trix.css +33 -0
  47. data/app/assets/stylesheets/koi/global/fonts.css +22 -0
  48. data/app/assets/stylesheets/koi/global/global-styles.css +297 -0
  49. data/app/assets/stylesheets/koi/global/reset.css +98 -0
  50. data/app/assets/stylesheets/koi/global/variables.css +97 -0
  51. data/app/assets/stylesheets/koi/icons.css +14 -0
  52. data/app/assets/stylesheets/koi/{admin.scss → index.css} +16 -7
  53. data/app/assets/stylesheets/koi/login.css +26 -0
  54. data/app/assets/stylesheets/koi/themes/_index.scss +0 -1
  55. data/app/assets/stylesheets/koi/utilities/index.css +1 -0
  56. data/app/assets/stylesheets/koi/utilities/visually-hidden.css +18 -0
  57. data/app/components/concerns/koi/tables/cells.rb +3 -3
  58. data/app/components/koi/header_component.html.erb +12 -11
  59. data/app/components/koi/header_component.rb +2 -0
  60. data/app/components/koi/table_component.rb +8 -0
  61. data/app/controllers/admin/admin_users_controller.rb +24 -18
  62. data/app/controllers/admin/application_controller.rb +1 -3
  63. data/app/controllers/admin/credentials_controller.rb +18 -14
  64. data/app/controllers/admin/otps_controller.rb +15 -13
  65. data/app/controllers/admin/sessions_controller.rb +12 -1
  66. data/app/controllers/admin/url_rewrites_controller.rb +19 -17
  67. data/app/controllers/admin/well_knowns_controller.rb +20 -18
  68. data/app/controllers/concerns/koi/controller.rb +37 -0
  69. data/app/helpers/koi/form_helper.rb +18 -0
  70. data/app/helpers/koi/header_helper.rb +122 -0
  71. data/app/helpers/koi/index_actions_helper.rb +3 -2
  72. data/app/helpers/koi/modal_helper.rb +71 -0
  73. data/app/models/admin/user.rb +7 -1
  74. data/app/models/url_rewrite.rb +1 -9
  75. data/app/views/admin/admin_users/_form.html+self.erb +8 -0
  76. data/app/views/admin/admin_users/_form.html.erb +8 -0
  77. data/app/views/admin/admin_users/archived.html.erb +7 -4
  78. data/app/views/admin/admin_users/edit.html+self.erb +12 -0
  79. data/app/views/admin/admin_users/edit.html.erb +13 -8
  80. data/app/views/admin/admin_users/index.html.erb +10 -5
  81. data/app/views/admin/admin_users/new.html.erb +8 -8
  82. data/app/views/admin/admin_users/show.html+self.erb +26 -14
  83. data/app/views/admin/admin_users/show.html.erb +22 -20
  84. data/app/views/admin/credentials/_credentials.html+self.erb +8 -6
  85. data/app/views/admin/credentials/_credentials.html.erb +3 -1
  86. data/app/views/admin/credentials/create.turbo_stream.erb +4 -3
  87. data/app/views/admin/credentials/destroy.turbo_stream.erb +4 -2
  88. data/app/views/admin/credentials/new.html.erb +42 -36
  89. data/app/views/admin/dashboards/show.html.erb +13 -1
  90. data/app/views/admin/otps/_form.html.erb +7 -7
  91. data/app/views/admin/otps/create.turbo_stream.erb +3 -3
  92. data/app/views/admin/otps/new.html.erb +5 -3
  93. data/app/views/admin/sessions/new.html.erb +2 -3
  94. data/app/views/admin/sessions/otp.html.erb +1 -3
  95. data/app/views/admin/sessions/password.html.erb +1 -3
  96. data/app/views/admin/tokens/show.html.erb +4 -6
  97. data/app/views/admin/url_rewrites/_form.html.erb +9 -0
  98. data/app/views/admin/url_rewrites/edit.html.erb +13 -9
  99. data/app/views/admin/url_rewrites/index.html.erb +10 -7
  100. data/app/views/admin/url_rewrites/new.html.erb +8 -8
  101. data/app/views/admin/url_rewrites/show.html.erb +17 -12
  102. data/app/views/admin/well_knowns/_form.html.erb +9 -0
  103. data/app/views/admin/well_knowns/edit.html.erb +13 -9
  104. data/app/views/admin/well_knowns/index.html.erb +8 -5
  105. data/app/views/admin/well_knowns/new.html.erb +8 -8
  106. data/app/views/admin/well_knowns/show.html.erb +14 -13
  107. data/app/views/katalyst/content/asides/_aside.html+form.erb +6 -4
  108. data/app/views/katalyst/content/columns/_column.html+form.erb +5 -3
  109. data/app/views/katalyst/content/contents/_content.html+form.erb +8 -6
  110. data/app/views/katalyst/content/figures/_figure.html+form.erb +8 -5
  111. data/app/views/katalyst/content/groups/_group.html+form.erb +5 -3
  112. data/app/views/katalyst/content/items/_item.html+form.erb +5 -3
  113. data/app/views/katalyst/content/sections/_section.html+form.erb +5 -3
  114. data/app/views/katalyst/content/tables/_table.html+form.erb +16 -11
  115. data/app/views/katalyst/navigation/items/_button.html.erb +6 -12
  116. data/app/views/katalyst/navigation/items/_heading.html.erb +3 -10
  117. data/app/views/katalyst/navigation/items/_link.html.erb +6 -11
  118. data/app/views/katalyst/navigation/menus/edit.html.erb +10 -6
  119. data/app/views/katalyst/navigation/menus/index.html.erb +4 -2
  120. data/app/views/katalyst/navigation/menus/new.html.erb +5 -3
  121. data/app/views/katalyst/navigation/menus/show.html.erb +8 -7
  122. data/app/views/layouts/koi/_application_header.html.erb +20 -0
  123. data/app/views/layouts/koi/_application_navigation.html.erb +34 -0
  124. data/app/views/layouts/koi/_flash.html.erb +6 -3
  125. data/app/views/layouts/koi/_navigation_header.html.erb +0 -2
  126. data/app/views/layouts/koi/application.html.erb +22 -27
  127. data/app/views/layouts/koi/frame.html.erb +1 -3
  128. data/app/views/layouts/koi/login.html.erb +12 -5
  129. data/config/locales/koi.en.yml +9 -1
  130. data/config/routes.rb +1 -1
  131. data/lib/generators/koi/admin/admin_generator.rb +3 -12
  132. data/lib/generators/koi/admin_controller/admin_controller_generator.rb +6 -16
  133. data/lib/generators/koi/admin_controller/templates/controller.rb.tt +82 -18
  134. data/lib/generators/koi/admin_controller/templates/controller_spec.rb.tt +113 -47
  135. data/lib/generators/koi/admin_route/admin_route_generator.rb +60 -6
  136. data/lib/generators/koi/admin_views/USAGE +18 -7
  137. data/lib/generators/koi/admin_views/admin_views_generator.rb +19 -11
  138. data/lib/generators/koi/admin_views/templates/_form.html.erb.tt +8 -0
  139. data/lib/generators/koi/admin_views/templates/archived.html.erb.tt +33 -0
  140. data/lib/generators/koi/admin_views/templates/edit.html.erb.tt +17 -9
  141. data/lib/generators/koi/admin_views/templates/index.html.erb.tt +31 -3
  142. data/lib/generators/koi/admin_views/templates/new.html.erb.tt +8 -8
  143. data/lib/generators/koi/admin_views/templates/show.html.erb.tt +15 -18
  144. data/lib/generators/koi/helpers/attribute_helpers.rb +147 -0
  145. data/lib/generators/koi/helpers/attribute_types.rb +218 -0
  146. data/lib/generators/koi/helpers/resource_helpers.rb +121 -0
  147. data/lib/generators/koi/{active_record/active_record_generator.rb → model/model_generator.rb} +1 -1
  148. data/lib/koi/config.rb +3 -1
  149. data/lib/koi/engine.rb +0 -9
  150. data/lib/koi/form/builder.rb +4 -4
  151. data/lib/koi/form/content.rb +55 -0
  152. data/lib/koi/form/elements/document.rb +1 -1
  153. data/lib/koi/form/elements/image.rb +1 -1
  154. data/lib/koi/form_builder.rb +1 -0
  155. data/lib/koi/menu.rb +14 -1
  156. data/spec/factories/admins.rb +1 -1
  157. metadata +90 -99
  158. data/app/assets/builds/koi/admin.css +0 -1
  159. data/app/assets/stylesheets/koi/base/_button.scss +0 -122
  160. data/app/assets/stylesheets/koi/base/_icon.scss +0 -29
  161. data/app/assets/stylesheets/koi/base/_index.scss +0 -21
  162. data/app/assets/stylesheets/koi/base/_input.scss +0 -19
  163. data/app/assets/stylesheets/koi/base/_link.scss +0 -26
  164. data/app/assets/stylesheets/koi/base/_list.scss +0 -11
  165. data/app/assets/stylesheets/koi/base/_typography.scss +0 -160
  166. data/app/assets/stylesheets/koi/components/_actions-group.scss +0 -7
  167. data/app/assets/stylesheets/koi/components/_image-field.scss +0 -95
  168. data/app/assets/stylesheets/koi/components/_index.scss +0 -9
  169. data/app/assets/stylesheets/koi/components/_pagy.scss +0 -29
  170. data/app/assets/stylesheets/koi/components/_summary-list.scss +0 -40
  171. data/app/assets/stylesheets/koi/layouts/_banner.scss +0 -7
  172. data/app/assets/stylesheets/koi/layouts/_content.scss +0 -40
  173. data/app/assets/stylesheets/koi/layouts/_flash.scss +0 -41
  174. data/app/assets/stylesheets/koi/layouts/_header.scss +0 -61
  175. data/app/assets/stylesheets/koi/layouts/_index.scss +0 -48
  176. data/app/assets/stylesheets/koi/layouts/_main.scss +0 -23
  177. data/app/assets/stylesheets/koi/layouts/_navigation.scss +0 -180
  178. data/app/assets/stylesheets/koi/layouts/_stack.scss +0 -13
  179. data/app/assets/stylesheets/koi/pages/_index.scss +0 -1
  180. data/app/assets/stylesheets/koi/pages/_login.scss +0 -46
  181. data/app/assets/stylesheets/koi/themes/_govuk.scss +0 -56
  182. data/app/assets/stylesheets/koi/themes/_kpop.scss +0 -5
  183. data/app/assets/stylesheets/koi/utils/_breakpoints.scss +0 -13
  184. data/app/assets/stylesheets/koi/utils/_hide.scss +0 -11
  185. data/app/assets/stylesheets/koi/utils/_index.scss +0 -2
  186. data/app/assets/stylesheets/koi/utils/_typography.scss +0 -42
  187. data/app/components/koi/content/editor/item_form_component.html.erb +0 -11
  188. data/app/components/koi/content/editor/item_form_component.rb +0 -94
  189. data/app/components/koi/summary_list/attachment_component.rb +0 -47
  190. data/app/components/koi/summary_list/base.rb +0 -59
  191. data/app/components/koi/summary_list/boolean_component.rb +0 -15
  192. data/app/components/koi/summary_list/date_component.rb +0 -17
  193. data/app/components/koi/summary_list/datetime_component.rb +0 -17
  194. data/app/components/koi/summary_list/item_component.rb +0 -26
  195. data/app/components/koi/summary_list/number_component.rb +0 -21
  196. data/app/components/koi/summary_list/rich_text_component.rb +0 -8
  197. data/app/components/koi/summary_list/text_component.rb +0 -8
  198. data/app/components/koi/summary_list_component.html.erb +0 -5
  199. data/app/components/koi/summary_list_component.rb +0 -75
  200. data/app/controllers/concerns/koi/controller/is_admin_controller.rb +0 -66
  201. data/app/helpers/koi/application_helper.rb +0 -7
  202. data/app/helpers/koi/date_helper.rb +0 -26
  203. data/app/helpers/koi/definition_list_helper.rb +0 -10
  204. data/app/views/admin/admin_users/_fields.html+self.erb +0 -3
  205. data/app/views/admin/admin_users/_fields.html.erb +0 -3
  206. data/app/views/admin/url_rewrites/_fields.html.erb +0 -4
  207. data/app/views/admin/well_knowns/_fields.html.erb +0 -6
  208. data/app/views/layouts/koi/_environment.html.erb +0 -4
  209. data/app/views/layouts/koi/_header.html.erb +0 -11
  210. data/app/views/layouts/koi/_navigation.html.erb +0 -23
  211. data/app/views/layouts/koi/_navigation_collapse.html.erb +0 -3
  212. data/lib/generators/koi/admin_views/templates/_fields.html.erb.tt +0 -3
  213. data/lib/generators/koi/helpers/admin_generator_attributes.rb +0 -66
  214. data/lib/koi/extensions/dartsass.rb +0 -23
  215. /data/app/assets/stylesheets/koi/{components/_clipboard.scss → blocks/clipboard.css} +0 -0
  216. /data/app/assets/stylesheets/koi/{components/_index-actions.scss → blocks/index-actions.css} +0 -0
  217. /data/app/assets/stylesheets/koi/{components/_toolbar.scss → blocks/toolbar.css} +0 -0
  218. /data/app/assets/stylesheets/koi/{base/_repel.scss → compositions/repel.css} +0 -0
@@ -1,7 +1,6 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en">
3
3
  <head>
4
- <!-- meta -->
5
4
  <meta charset="UTF-8">
6
5
 
7
6
  <!-- title -->
@@ -18,12 +17,7 @@
18
17
  <meta name="turbo-refresh-scroll" content="preserve">
19
18
  <%= yield :head %>
20
19
 
21
- <%# Deprecated: meta, use :head instead %>
22
- <%= yield :meta %>
23
-
24
20
  <!-- STYLES -->
25
- <link rel="stylesheet" href="https://unpkg.com/modern-css-reset/dist/reset.min.css">
26
- <link rel="stylesheet" href="https://rsms.me/inter/inter.css">
27
21
  <%= stylesheet_link_tag Koi.config.admin_stylesheet, "data-turbo-track": "reload" %>
28
22
 
29
23
  <!-- SCRIPTS -->
@@ -35,33 +29,34 @@
35
29
  Slash->shortcut:search
36
30
  KeyN->shortcut:create
37
31
  KeyG->shortcut:go
38
- BracketLeft->shortcut:nav-toggle
39
32
  Escape->shortcut:cancel
40
33
  ArrowLeft->shortcut:page-prev
41
34
  ArrowRight->shortcut:page-next
42
35
  ">
43
- <%= render "layouts/koi/navigation" %>
44
- <main>
45
- <% if content_for? :header %>
46
- <%= yield :header %>
47
- <% else %>
48
- <%= render "layouts/koi/header" %>
49
- <% end %>
36
+ <!-- application header -->
37
+ <%= render "layouts/koi/application_header" %>
50
38
 
51
- <div class="page <%= "has-sidebar" if content_for?(:sidebar) %>">
52
- <div class="page--content stack" role="main">
53
- <%= render "layouts/koi/flash" unless flash.empty? %>
54
- <%= yield %>
39
+ <!-- header -->
40
+ <% if content_for?(:header) %>
41
+ <header class="page-header">
42
+ <div class="flow wrapper">
43
+ <%= yield(:header) %>
55
44
  </div>
56
- <%= tag.div class: "page--sidebar" do %>
57
- <%= yield :sidebar %>
58
- <% end if content_for?(:sidebar) %>
59
- </div>
60
- </main>
61
- <%= render "layouts/koi/environment" %>
62
- <%= render ScrimComponent.new %>
63
- <%= render Kpop::FrameComponent.new do %>
64
- <%= content_for :kpop %>
45
+ </header>
65
46
  <% end %>
47
+
48
+ <main class="flow wrapper">
49
+ <% unless flash.empty? %>
50
+ <!-- flash -->
51
+ <%= render "layouts/koi/flash" %>
52
+ <% end %>
53
+
54
+ <!-- content -->
55
+ <%= yield %>
56
+ </main>
57
+
58
+ <!-- application navigation -->
59
+ <%= render "layouts/koi/application_navigation" %>
60
+
66
61
  </body>
67
62
  </html>
@@ -10,8 +10,6 @@
10
10
  <%= yield :head %>
11
11
  </head>
12
12
  <body>
13
- <%= render Kpop::FrameComponent.new do %>
14
- <%= yield %>
15
- <% end %>
13
+ <%= yield %>
16
14
  </body>
17
15
  </html>
@@ -14,16 +14,23 @@
14
14
  <%= yield :meta %>
15
15
 
16
16
  <!-- STYLES -->
17
- <link rel="stylesheet" href="https://unpkg.com/modern-css-reset/dist/reset.min.css">
18
- <link rel="stylesheet" href="https://rsms.me/inter/inter.css">
17
+ <%= stylesheet_link_tag path_to_stylesheet("koi/login.css"), "data-turbo-track": "reload" %>
19
18
  <%= stylesheet_link_tag Koi.config.admin_stylesheet, "data-turbo-track": "reload" %>
20
19
 
21
20
  <!-- SCRIPTS -->
22
21
  <%= javascript_importmap_tags "koi/admin" %>
23
22
  </head>
24
- <body class="admin-login">
25
- <main class="stack">
26
- <%= yield %>
23
+ <body>
24
+ <main class="cover" data-centered>
25
+ <div class="login | modal">
26
+ <header>
27
+ <img src="<%= path_to_image("koi/koi.png") %>" alt="Koi" aria-hidden>
28
+ <h1>Koi</h1>
29
+ <h2 class="site-name"><%= URI.parse(root_url).host %></h2>
30
+ </header>
31
+
32
+ <%= yield %>
33
+ </div>
27
34
  </main>
28
35
  </body>
29
36
  </html>
@@ -16,9 +16,17 @@ en:
16
16
  new: New
17
17
  search: Search
18
18
  activerecord:
19
+ attributes:
20
+ url_rewrite/status_code:
21
+ 303: "See other (303)"
22
+ 307: "Temporary redirect (307)"
23
+ 308: "Permanent redirect (308)"
24
+ well_known/content_type:
25
+ text: "Plain text"
26
+ json: "JSON"
19
27
  errors:
20
28
  models:
21
- admin:
29
+ admin_user:
22
30
  invalid: "Invalid login credentials"
23
31
  helpers:
24
32
  hint:
data/config/routes.rb CHANGED
@@ -30,5 +30,5 @@ Rails.application.routes.draw do
30
30
  mount Flipper::UI.app(Flipper) => "flipper" if Object.const_defined?("Flipper::UI")
31
31
  end
32
32
 
33
- resources :well_knowns, path: ".well-known", param: :name, only: %i[show], name: /[^\/]+/
33
+ resources :well_knowns, path: ".well-known", param: :name, only: %i[show], name: /.+/
34
34
  end
@@ -1,17 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "rails/generators/rails/scaffold/scaffold_generator"
3
+ require "rails/generators/named_base"
4
4
 
5
5
  module Koi
6
- class AdminGenerator < Rails::Generators::ScaffoldGenerator
7
- # Replace the default model generator with our own
8
- remove_hook_for(:orm)
9
- hook_for(:orm, in: :koi, as: :admin, default: true)
6
+ class AdminGenerator < Rails::Generators::NamedBase
10
7
 
11
- # Disable default controller generation as we do not want to generate public
12
- # controllers by default
13
- remove_hook_for(:scaffold_controller)
14
- remove_hook_for(:resource_route)
8
+ class_option :force, type: :boolean, default: true
15
9
 
16
10
  hook_for :admin_controller, in: :koi, as: :admin, type: :boolean, default: true do |instance, controller|
17
11
  args, opts, config = @_initializer
@@ -20,8 +14,5 @@ module Koi
20
14
  # setting model_name so that generators will use the controller_class_path
21
15
  instance.invoke controller, args, { model_name: instance.name, **opts }, config
22
16
  end
23
-
24
- Rails::Generators::ModelGenerator.hook_for :admin_search, type: :boolean, default: true
25
- Rails::Generators::ModelGenerator.hook_for :ordinal_scope, type: :boolean, default: true
26
17
  end
27
18
  end
@@ -3,12 +3,13 @@
3
3
  require "rails/generators/named_base"
4
4
  require "rails/generators/resource_helpers"
5
5
 
6
- require_relative "../helpers/admin_generator_attributes"
6
+ require_relative "../helpers/attribute_helpers"
7
+ require_relative "../helpers/resource_helpers"
7
8
 
8
9
  module Koi
9
10
  class AdminControllerGenerator < Rails::Generators::NamedBase
10
- include Rails::Generators::ResourceHelpers
11
- include Helpers::AdminGeneratorAttributes
11
+ include Helpers::AttributeHelpers
12
+ include Helpers::ResourceHelpers
12
13
 
13
14
  source_root File.expand_path("templates", __dir__)
14
15
 
@@ -16,6 +17,8 @@ module Koi
16
17
 
17
18
  argument :attributes, type: :array, default: [], banner: "field:type field:type"
18
19
 
20
+ class_option :force, type: :boolean, default: true
21
+
19
22
  def create_controller_files
20
23
  template("controller.rb",
21
24
  File.join("app/controllers",
@@ -35,24 +38,11 @@ module Koi
35
38
 
36
39
  private
37
40
 
38
- def controller_class_path
39
- ["admin"] + super
40
- end
41
-
42
41
  def permitted_params
43
42
  attachments, others = attributes_names.partition { |name| attachments?(name) }
44
43
  params = others.map { |name| ":#{name}" }
45
44
  params += attachments.map { |name| "#{name}: []" }
46
45
  params.join(", ")
47
46
  end
48
-
49
- def attachments?(name)
50
- attribute = attributes.find { |attr| attr.name == name }
51
- attribute&.attachments?
52
- end
53
-
54
- def sort_attribute
55
- attributes.find { |attr| attr.type == :string }&.name
56
- end
57
47
  end
58
48
  end
@@ -3,51 +3,110 @@
3
3
  class <%= controller_class_name %>Controller < ApplicationController
4
4
  before_action :set_<%= singular_name %>, only: %i[show edit update destroy]
5
5
 
6
+ attr_reader :collection, :<%= singular_name %>
7
+
6
8
  def index
7
- collection = Collection.new.with_params(params).apply(::<%= class_name %>.strict_loading.all)
9
+ <%- if archivable? -%>
10
+ @collection = Collection.with_params(params).apply(<%= class_name %>.not_archived.strict_loading)
11
+ <%- else -%>
12
+ @collection = Collection.with_params(params).apply(<%= class_name %>.strict_loading)
13
+ <%- end -%>
14
+
15
+ render locals: { collection: }
16
+ end
17
+
18
+ <%- if archivable? -%>
19
+ def archived
20
+ collection = Collection.with_params(params).apply(<%= class_name %>.archived.strict_loading)
8
21
 
9
22
  render locals: { collection: }
10
23
  end
24
+ <%- end -%>
11
25
 
12
26
  def show
13
- render locals: { <%= singular_name %>: @<%= singular_name %> }
27
+ render locals: { <%= singular_name %>: }
14
28
  end
15
29
 
16
30
  def new
17
- render locals: { <%= singular_name %>: ::<%= class_name %>.new }
31
+ @<%= singular_name %> = <%= class_name %>.new
32
+
33
+ render locals: { <%= singular_name %>: }
18
34
  end
19
35
 
20
36
  def edit
21
- render locals: { <%= singular_name %>: @<%= singular_name %> }
37
+ render locals: { <%= singular_name %>: }
22
38
  end
23
39
 
24
40
  def create
25
- @<%= singular_name %> = ::<%= class_name %>.new(<%= singular_table_name %>_params)
41
+ @<%= singular_name %> = <%= class_name %>.new(<%= singular_table_name %>_params)
26
42
 
27
- if @<%= singular_name %>.save
28
- redirect_to [:admin, @<%= singular_name %>], status: :see_other
43
+ if <%= singular_name %>.save
44
+ redirect_to <%= admin_show_helper %>, status: :see_other
29
45
  else
30
- render :new, locals: { <%= singular_name %>: @<%= singular_name %> }, status: :unprocessable_content
46
+ render :new, locals: { <%= singular_name %>: }, status: :unprocessable_content
31
47
  end
32
48
  end
33
49
 
34
50
  def update
35
- if @<%= singular_name %>.update(<%= singular_table_name %>_params)
36
- redirect_to action: :show, status: :see_other
51
+ if <%= singular_name %>.update(<%= singular_table_name %>_params)
52
+ redirect_to <%= admin_show_helper %>, status: :see_other
37
53
  else
38
- render :edit, locals: { <%= singular_name %>: @<%= singular_name %> }, status: :unprocessable_content
54
+ render :edit, locals: { <%= singular_name %>: }, status: :unprocessable_content
55
+ end
56
+ end
57
+
58
+ <%- if archivable? -%>
59
+ def archive
60
+ <%= class_name %>.where(id: params[:id]).each(&:archive!)
61
+
62
+ redirect_back(fallback_location: <%= admin_index_helper %>, status: :see_other)
63
+ end
64
+
65
+ def restore
66
+ <%= class_name %>.archived.where(id: params[:id]).each(&:restore!)
67
+
68
+ redirect_back(fallback_location: <%= admin_index_helper %>, status: :see_other)
69
+ end
70
+ <%- end -%>
71
+
72
+ <%- if orderable? -%>
73
+ def order
74
+ order_params[:<%= table_name %>].each do |id, attrs|
75
+ <%= class_name %>.find(id).update(attrs)
39
76
  end
77
+
78
+ redirect_back(fallback_location: <%= admin_index_helper %>, status: :see_other)
40
79
  end
80
+ <%- end -%>
41
81
 
42
82
  def destroy
43
- @<%= singular_name %>.destroy!
83
+ <%- if archivable? -%>
84
+ if <%= singular_name %>.archived?
85
+ <%= singular_name %>.destroy!
86
+
87
+ redirect_to <%= admin_index_helper %>, status: :see_other
88
+ else
89
+ <%= singular_name %>.archive!
44
90
 
45
- redirect_to action: :index, status: :see_other
91
+ redirect_back_or_to(<%= admin_show_helper %>, status: :see_other)
92
+ end
93
+ <%- else -%>
94
+ <%= singular_name %>.destroy!
95
+
96
+ redirect_to <%= admin_index_helper %>, status: :see_other
97
+ <%- end -%>
46
98
  end
47
99
 
48
100
  private
49
101
 
50
- # Only allow a list of trusted parameters through.
102
+ def set_<%= singular_name %>
103
+ <%- if archivable? -%>
104
+ @<%= singular_name %> = <%= class_name %>.with_archived.find(params[:id])
105
+ <%- else -%>
106
+ @<%= singular_name %> = <%= class_name %>.find(params[:id])
107
+ <%- end -%>
108
+ end
109
+
51
110
  def <%= "#{singular_table_name}_params" %>
52
111
  <%- if attributes_names.empty? -%>
53
112
  params.expect(<%= singular_table_name %>: [])
@@ -56,14 +115,19 @@ class <%= controller_class_name %>Controller < ApplicationController
56
115
  <%- end -%>
57
116
  end
58
117
 
59
- # Use callbacks to share common setup or constraints between actions.
60
- def set_<%= singular_name %>
61
- @<%= singular_name %> = ::<%= class_name %>.find(params[:id])
118
+ <%- if orderable? -%>
119
+ def order_params
120
+ params.expect(order: { <%= table_name %>: [[:ordinal]] })
62
121
  end
122
+ <%- end -%>
63
123
 
64
124
  class Collection < Admin::Collection
65
- config.sorting = :<%= sort_attribute %>
125
+ <%- if sortable? %>
126
+ config.sorting = :<%= default_sort_attribute %>
127
+ <%- end -%>
128
+ <%- if paginate? -%>
66
129
  config.paginate = true
130
+ <%- end -%>
67
131
 
68
132
  <%- attributes.each do |attribute| -%>
69
133
  <% definition = collection_attribute_for(attribute) -%>
@@ -4,12 +4,12 @@ require "rails_helper"
4
4
 
5
5
  RSpec.describe <%= controller_class_name %>Controller do
6
6
  let(:admin) { create(:admin) }
7
- let(:model) { create(:<%= singular_name %>) }
7
+ let(:<%= singular_name %>) { create(:<%= singular_name %>) }
8
8
 
9
9
  include_context "with admin session"
10
10
 
11
11
  describe "GET /admin/<%= plural_name %>" do
12
- let(:action) { get polymorphic_path([:admin, <%= class_name %>]) }
12
+ let(:action) { get <%= admin_index_helper %> }
13
13
 
14
14
  it_behaves_like "requires admin"
15
15
 
@@ -17,52 +17,35 @@ RSpec.describe <%= controller_class_name %>Controller do
17
17
  action
18
18
  expect(response).to have_http_status(:success)
19
19
  end
20
+ <%- if orderable? -%>
20
21
 
21
- context "with a large collection" do
22
- before { create_list(:<%= singular_name %>, 25) } # rubocop:disable FactoryBot/ExcessiveCreateList
22
+ it "sorts by ordinal" do
23
+ first = create(:<%= singular_name %>, ordinal: 1)
24
+ second = create(:<%= singular_name %>, ordinal: 0)
23
25
 
24
- it "paginates the collection" do
25
- action
26
- expect(response.body).to have_css("tbody tr", count: 20)
27
- end
28
- end
29
-
30
- context "with sort parameter" do
31
- let(:action) { get polymorphic_path([:admin, <%= class_name %>], sort: "<%= sort_attribute %> desc") }
32
-
33
- before do
34
- create(:<%= singular_name %>, <%= sort_attribute %>: "first")
35
- create(:<%= singular_name %>, <%= sort_attribute %>: "second")
36
- end
26
+ action
37
27
 
38
- it "finds first in second place" do
39
- action
40
- expect(response.body).to have_css("tbody tr + tr td", text: "first")
41
- end
28
+ expect(response.parsed_body.css("tbody td a").pluck(:href))
29
+ .to eq([second, first].map { |<%= singular_name %>| <%= admin_show_helper %> })
42
30
  end
31
+ <%- end -%>
32
+ end
43
33
 
44
- context "with search parameter" do
45
- let(:action) { get polymorphic_path([:admin, <%= class_name %>], search: "first") }
46
-
47
- before do
48
- create(:<%= singular_name %>, <%= sort_attribute %>: "first")
49
- create(:<%= singular_name %>, <%= sort_attribute %>: "second")
50
- end
34
+ <%- if archivable? -%>
35
+ describe "GET /admin/<%= plural_name %>/archived" do
36
+ let(:action) { get <%= archived_admin_helper %> }
51
37
 
52
- it "finds the needle" do
53
- action
54
- expect(response.body).to have_css("table td", text: "first")
55
- end
38
+ it_behaves_like "requires admin"
56
39
 
57
- it "removes the chaff" do
58
- action
59
- expect(response.body).to have_no_css("table td", text: "second")
60
- end
40
+ it "renders successfully" do
41
+ action
42
+ expect(response).to have_http_status(:success)
61
43
  end
62
44
  end
63
45
 
46
+ <%- end -%>
64
47
  describe "GET /admin/<%= plural_name %>/new" do
65
- let(:action) { get new_polymorphic_path([:admin, <%= class_name %>]) }
48
+ let(:action) { get <%= new_admin_helper %> }
66
49
 
67
50
  it_behaves_like "requires admin"
68
51
 
@@ -73,14 +56,14 @@ RSpec.describe <%= controller_class_name %>Controller do
73
56
  end
74
57
 
75
58
  describe "POST /admin/<%= plural_name %>" do
76
- let(:action) { post polymorphic_path([:admin, <%= class_name %>]), params: { <%= singular_name %>: params } }
59
+ let(:action) { post <%= admin_index_helper %>, params: { <%= singular_name %>: params } }
77
60
  let(:params) { attributes_for(:<%= singular_name %>) }
78
61
 
79
62
  it_behaves_like "requires admin"
80
63
 
81
64
  it "renders successfully" do
82
65
  action
83
- expect(response).to redirect_to([:admin, assigns(:<%= singular_name %>)])
66
+ expect(response).to redirect_to(<%= admin_show_helper("assigns(:#{singular_name})") %>)
84
67
  end
85
68
 
86
69
  it "creates a <%= singular_name %>" do
@@ -89,7 +72,7 @@ RSpec.describe <%= controller_class_name %>Controller do
89
72
  end
90
73
 
91
74
  describe "GET /admin/<%= plural_name %>/:id" do
92
- let(:action) { get polymorphic_path([:admin, model]) }
75
+ let(:action) { get <%= admin_show_helper %> }
93
76
 
94
77
  it_behaves_like "requires admin"
95
78
 
@@ -100,7 +83,7 @@ RSpec.describe <%= controller_class_name %>Controller do
100
83
  end
101
84
 
102
85
  describe "GET /admin/<%= plural_name %>/:id/edit" do
103
- let(:action) { get edit_polymorphic_path([:admin, model]) }
86
+ let(:action) { get <%= edit_admin_helper %> }
104
87
 
105
88
  it_behaves_like "requires admin"
106
89
 
@@ -111,30 +94,113 @@ RSpec.describe <%= controller_class_name %>Controller do
111
94
  end
112
95
 
113
96
  describe "PATCH /admin/<%= plural_name %>/:id" do
114
- let(:action) { patch polymorphic_path([:admin, model]), params: { <%= singular_name %>: params } }
97
+ let(:action) { patch <%= admin_show_helper %>, params: { <%= singular_name %>: params } }
115
98
  let(:params) { attributes_for(:<%= singular_name %>) }
116
99
 
117
100
  it_behaves_like "requires admin"
118
101
 
119
102
  it "renders successfully" do
120
103
  action
121
- expect(response).to redirect_to(polymorphic_path([:admin, model]))
104
+ expect(response).to redirect_to(<%= admin_show_helper %>)
122
105
  end
123
106
  end
124
107
 
108
+ <%- if archivable? -%>
109
+ describe "PUT /admin/<%= plural_name %>/archive" do
110
+ let(:action) { put <%= archive_admin_helper %>, params: }
111
+ let(:params) { { id: <%= plural_name %>.take(2).pluck(:id) } }
112
+ let(:<%= plural_name %>) { create_list(:<%= singular_name %>, 3) }
113
+
114
+ it_behaves_like "requires admin"
115
+
116
+ it "renders successfully" do
117
+ action
118
+ expect(response).to redirect_to(<%= admin_index_helper %>)
119
+ end
120
+
121
+ it "updates archived" do
122
+ expect { action }.to(change { <%= class_name %>.archived.count }.to(2))
123
+ end
124
+ end
125
+
126
+ <%- end -%>
127
+ <%- if archivable? -%>
128
+ describe "PUT /admin/<%= plural_name %>/restore" do
129
+ let(:action) { put <%= restore_admin_helper %>, params: }
130
+ let(:params) { { id: <%= plural_name %>.take(2).pluck(:id) } }
131
+ let(:<%= plural_name %>) { create_list(:<%= singular_name %>, 3, archived: true) }
132
+
133
+ it_behaves_like "requires admin"
134
+
135
+ it "renders successfully" do
136
+ action
137
+ expect(response).to redirect_to(<%= admin_index_helper %>)
138
+ end
139
+
140
+ it "updates archived" do
141
+ expect { action }.to(change { <%= class_name %>.not_archived.count }.by(2))
142
+ end
143
+ end
144
+
145
+ <%- end -%>
146
+ <%- if orderable? -%>
147
+ describe "PATCH /admin/<%= plural_name %>/order" do
148
+ let(:action) { patch <%= order_admin_helper %>, params: { order: params } }
149
+ let(:params) { { <%= plural_name %>: { first.id => { ordinal: 1 }, second.id => { ordinal: 0 } } } }
150
+ let(:first) { create(:<%= singular_name %>, ordinal: 0) }
151
+ let(:second) { create(:<%= singular_name %>, ordinal: 1) }
152
+
153
+ it_behaves_like "requires admin"
154
+
155
+ it "redirects back" do
156
+ action
157
+ expect(response).to redirect_to(<%= admin_index_helper %>)
158
+ end
159
+
160
+ it "updates ordinals" do
161
+ expect { action }.to change { first.reload.ordinal }.to(1)
162
+ end
163
+ end
164
+
165
+ <%- end -%>
125
166
  describe "DELETE /admin/<%= plural_name %>/:id" do
126
- let(:action) { delete polymorphic_path([:admin, model]) }
127
- let!(:model) { create(:<%= singular_name %>) }
167
+ let(:action) { delete <%= admin_show_helper %> }
168
+ let!(:<%= singular_name %>) { create(:<%= singular_name %>) }
128
169
 
129
170
  it_behaves_like "requires admin"
130
171
 
172
+ <%- if !archivable? -%>
131
173
  it "renders successfully" do
132
174
  action
133
- expect(response).to redirect_to(polymorphic_path([:admin, <%= class_name %>]))
175
+ expect(response).to redirect_to(<%= admin_index_helper %>)
134
176
  end
135
177
 
136
- it "deletes the <%= singular_name %>" do
178
+ it "deletes the <%= human_name %>" do
137
179
  expect { action }.to change(<%= class_name %>, :count).by(-1)
138
180
  end
181
+ <%- else -%>
182
+ it "renders successfully" do
183
+ action
184
+ expect(response).to redirect_to(<%= admin_show_helper %>)
185
+ end
186
+
187
+ it "archives the <%= human_name %>" do
188
+ expect { action }.to change { <%= singular_name %>.reload.archived }.to(true)
189
+ end
190
+
191
+ context "when the <%= human_name %> is archived" do
192
+ let(:<%= singular_name %>) { create(:<%= singular_name %>, archived_at: 1.day.ago) }
193
+
194
+ it "renders successfully" do
195
+ action
196
+ expect(response).to redirect_to(<%= admin_index_helper %>)
197
+ end
198
+
199
+ it "deletes the <%= human_name %>" do
200
+ <%= singular_name %>
201
+ expect { action }.to change(<%= class_name %>.with_archived, :count).by(-1)
202
+ end
203
+ end
204
+ <%- end -%>
139
205
  end
140
206
  end