activeadmin-command_palette 0.1.6

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 (33) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +51 -0
  3. data/CODE_OF_CONDUCT.md +26 -0
  4. data/CONTRIBUTING.md +23 -0
  5. data/GOVERNANCE.md +25 -0
  6. data/LICENSE.md +21 -0
  7. data/README.md +152 -0
  8. data/SECURITY.md +25 -0
  9. data/activeadmin-command_palette.gemspec +68 -0
  10. data/app/assets/controllers/activeadmin_command_palette/command_palette_controller.js +817 -0
  11. data/app/controllers/active_admin/command_palette/options_controller.rb +61 -0
  12. data/app/views/active_admin/command_palette/_palette.html.erb +45 -0
  13. data/config/locales/activeadmin_command_palette.en.yml +21 -0
  14. data/config/locales/activeadmin_command_palette.fi.yml +21 -0
  15. data/config/locales/activeadmin_command_palette.sv.yml +21 -0
  16. data/config/polyrun_coverage.yml +8 -0
  17. data/config/routes.rb +7 -0
  18. data/lib/activeadmin/command_palette/catalog.rb +184 -0
  19. data/lib/activeadmin/command_palette/compiler.rb +159 -0
  20. data/lib/activeadmin/command_palette/configuration.rb +148 -0
  21. data/lib/activeadmin/command_palette/dsl.rb +13 -0
  22. data/lib/activeadmin/command_palette/engine.rb +67 -0
  23. data/lib/activeadmin/command_palette/finder.rb +103 -0
  24. data/lib/activeadmin/command_palette/helper.rb +136 -0
  25. data/lib/activeadmin/command_palette/integration.rb +17 -0
  26. data/lib/activeadmin/command_palette/resource_config.rb +52 -0
  27. data/lib/activeadmin/command_palette/resource_dsl.rb +47 -0
  28. data/lib/activeadmin/command_palette/resource_methods.rb +11 -0
  29. data/lib/activeadmin/command_palette/url_helpers.rb +22 -0
  30. data/lib/activeadmin/command_palette/version.rb +7 -0
  31. data/lib/activeadmin/command_palette.rb +38 -0
  32. data/sig/activeadmin/command_palette.rbs +5 -0
  33. metadata +392 -0
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module CommandPalette
5
+ class Engine < ::Rails::Engine
6
+ engine_name "activeadmin_command_palette"
7
+
8
+ initializer "activeadmin_command_palette.load_lib" do
9
+ require "activeadmin/command_palette"
10
+ ActiveAdmin::CommandPalette::Integration.install!
11
+ end
12
+
13
+ # Compile after ActiveAdmin loads admin files. A plain to_prepare can run after
14
+ # ActiveAdmin.unload! and before load!, which would leave an empty catalog.
15
+ initializer "activeadmin_command_palette.reload", after: :load_config_initializers do
16
+ ActiveAdmin.after_load do
17
+ ActiveAdmin::CommandPalette.reload!
18
+ end
19
+
20
+ if ActiveAdmin.application.loaded?
21
+ ActiveAdmin::CommandPalette.reload!
22
+ end
23
+ end
24
+
25
+ initializer "activeadmin_command_palette.assets" do |app|
26
+ assets_path = root.join("app/assets")
27
+ if app.config.respond_to?(:importmap)
28
+ app.config.importmap.cache_sweepers << assets_path.join("controllers")
29
+ end
30
+ end
31
+
32
+ initializer "activeadmin_command_palette.importmap", after: "active_admin.importmap" do
33
+ next unless defined?(ActiveAdmin) && ActiveAdmin.respond_to?(:importmap) && ActiveAdmin.importmap
34
+
35
+ ActiveAdmin.importmap.draw do
36
+ pin "controllers/activeadmin_command_palette/command_palette_controller",
37
+ to: "activeadmin_command_palette/command_palette_controller.js"
38
+ end
39
+ end
40
+
41
+ initializer "activeadmin_command_palette.i18n" do
42
+ I18n.load_path += Dir[root.join("config/locales/*.yml")]
43
+ end
44
+
45
+ initializer "activeadmin_command_palette.filter_parameters" do |app|
46
+ app.config.filter_parameters |= [:q]
47
+ end
48
+
49
+ initializer "activeadmin_command_palette.helpers", after: :load_config_initializers do
50
+ ActiveSupport.on_load(:action_controller) do
51
+ helper ActiveAdmin::CommandPalette::Helper if respond_to?(:helper)
52
+ end
53
+
54
+ if defined?(ActiveAdmin::BaseController)
55
+ ActiveAdmin::BaseController.helper ActiveAdmin::CommandPalette::Helper
56
+ end
57
+ end
58
+
59
+ initializer "activeadmin_command_palette.view_paths", after: :add_view_paths do
60
+ views_path = root.join("app/views").to_s
61
+ ActiveSupport.on_load(:action_controller) do
62
+ prepend_view_path(views_path)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module CommandPalette
5
+ class Finder
6
+ class << self
7
+ def options(kind:, query:, helpers:, user:)
8
+ config = ActiveAdmin::CommandPalette.configuration
9
+ term = query.to_s.strip
10
+ return [] if term.length < config.min_query_length
11
+
12
+ options_for_kind(
13
+ kind: kind,
14
+ query: term,
15
+ helpers: helpers,
16
+ user: user,
17
+ limit: config.limit
18
+ )
19
+ end
20
+
21
+ def options_for(kinds:, query:, helpers:, user:)
22
+ config = ActiveAdmin::CommandPalette.configuration
23
+ term = query.to_s.strip
24
+ return [] if term.length < config.min_query_length
25
+
26
+ collect_options(kinds: kinds, query: term, helpers: helpers, user: user, config: config)
27
+ end
28
+
29
+ def kinds
30
+ ActiveAdmin::CommandPalette.configuration.finder_kinds
31
+ end
32
+
33
+ private
34
+
35
+ def collect_options(kinds:, query:, helpers:, user:, config:)
36
+ remaining = [config.limit.to_i, 0].max
37
+ requested_kinds = bounded_kinds(kinds, config.search_kind_limit)
38
+
39
+ requested_kinds.each_with_object([]) do |kind, results|
40
+ break results if remaining.zero?
41
+
42
+ items = options_for_kind(
43
+ kind: kind,
44
+ query: query,
45
+ helpers: helpers,
46
+ user: user,
47
+ limit: remaining
48
+ )
49
+ results.concat(items)
50
+ remaining -= items.length
51
+ end
52
+ end
53
+
54
+ def bounded_kinds(kinds, limit)
55
+ Array(kinds).first([limit.to_i, 0].max).map(&:to_s).uniq
56
+ end
57
+
58
+ def options_for_kind(kind:, query:, helpers:, user:, limit:)
59
+ kind = kind.to_s
60
+ finder = ActiveAdmin::CommandPalette.configuration.finders[kind]
61
+ return [] unless finder
62
+
63
+ results = finder.call(query: query, helpers: helpers, user: user, limit: limit)
64
+ normalize_results(results, kind: kind, limit: limit)
65
+ end
66
+
67
+ def normalize_results(results, kind:, limit:)
68
+ results.take(limit).map { |item| normalize(item, kind) }
69
+ end
70
+
71
+ def normalize(item, default_kind)
72
+ hash = item.respond_to?(:to_h) ? item.to_h : item
73
+ hash = hash.transform_keys(&:to_sym)
74
+ normalized = normalized_fields(hash, default_kind)
75
+ add_optional_fields(normalized, hash)
76
+ end
77
+
78
+ def normalized_fields(hash, default_kind)
79
+ {
80
+ id: hash[:id],
81
+ kind: (hash[:kind] || default_kind).to_s,
82
+ label: hash[:label].to_s,
83
+ url: hash[:url].to_s,
84
+ method: (hash[:method] || "get").to_s
85
+ }
86
+ end
87
+
88
+ def add_optional_fields(normalized, hash)
89
+ detail = hash[:detail].presence || hash[:subtitle].presence
90
+ keywords = normalized_keywords(hash[:keywords])
91
+
92
+ normalized[:detail] = detail.to_s if detail.present?
93
+ normalized[:keywords] = keywords if keywords.any?
94
+ normalized
95
+ end
96
+
97
+ def normalized_keywords(value)
98
+ Array(value).map { |keyword| keyword.to_s.presence }.compact
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module CommandPalette
5
+ module Helper
6
+ CONTROLLER_NAME = "activeadmin-command-palette--command-palette"
7
+
8
+ def command_palette_controller_name
9
+ CONTROLLER_NAME
10
+ end
11
+
12
+ def command_palette_navigate_items
13
+ Catalog.navigate(self)
14
+ end
15
+
16
+ def command_palette_action_items
17
+ Catalog.actions(self, context: command_palette_context)
18
+ end
19
+
20
+ def command_palette_options_url
21
+ active_admin_command_palette.options_path
22
+ end
23
+
24
+ def command_palette_context
25
+ config = active_admin_config_for_palette
26
+ action = controller_action_for_palette
27
+ namespace_name = config&.namespace&.name&.to_s.presence || "admin"
28
+
29
+ return other_context(namespace_name, action) if config.blank?
30
+ return page_context(namespace_name, action) if config.is_a?(ActiveAdmin::Page)
31
+
32
+ resource_name = config.resource_class.name.demodulize
33
+ resource_param = "id"
34
+ record_param = params[resource_param].presence || params[:id].presence
35
+ if record_param.blank? && %w[show edit update destroy].include?(action)
36
+ record_param = resource_param_for_palette
37
+ end
38
+
39
+ {
40
+ namespace: namespace_name,
41
+ resource: resource_name,
42
+ resource_param: resource_param,
43
+ record_param: record_param,
44
+ action: action,
45
+ page: "resource"
46
+ }
47
+ end
48
+
49
+ def command_palette_data_attributes
50
+ prefix = CONTROLLER_NAME
51
+ {
52
+ "data-#{prefix}-navigate-value" => command_palette_navigate_items.to_json,
53
+ "data-#{prefix}-actions-value" => command_palette_action_items.to_json,
54
+ "data-#{prefix}-context-value" => command_palette_context.to_json,
55
+ "data-#{prefix}-options-url-value" => command_palette_options_url,
56
+ "data-#{prefix}-csrf-value" => form_authenticity_token,
57
+ "data-#{prefix}-prefixes-value" => ActiveAdmin::CommandPalette.configuration.prefixes.to_json,
58
+ "data-#{prefix}-kinds-value" => ActiveAdmin::CommandPalette.configuration.finder_kinds.to_json,
59
+ "data-#{prefix}-min-query-length-value" => ActiveAdmin::CommandPalette.configuration.min_query_length,
60
+ "data-#{prefix}-empty-value" => I18n.t("active_admin.command_palette.empty"),
61
+ "data-#{prefix}-loading-value" => I18n.t("active_admin.command_palette.loading"),
62
+ "data-#{prefix}-error-value" => I18n.t("active_admin.command_palette.error"),
63
+ "data-#{prefix}-unauthorized-value" => I18n.t("active_admin.command_palette.unauthorized"),
64
+ "data-#{prefix}-forbidden-value" => I18n.t("active_admin.command_palette.forbidden"),
65
+ "data-#{prefix}-required-value" => I18n.t("active_admin.command_palette.required"),
66
+ "data-#{prefix}-optional-value" => I18n.t("active_admin.command_palette.optional"),
67
+ "data-#{prefix}-sign-in-path-value" => command_palette_sign_in_path,
68
+ "data-#{prefix}-help-items-value" => command_palette_help_items.to_json
69
+ }
70
+ end
71
+
72
+ def command_palette_sign_in_path
73
+ if respond_to?(:new_user_session_path)
74
+ new_user_session_path
75
+ else
76
+ "/user/sign_in"
77
+ end
78
+ end
79
+
80
+ def command_palette_help_items
81
+ [
82
+ {id: "help-go", kind: "help", label: I18n.t("active_admin.command_palette.help.go"), url: nil},
83
+ {id: "help-actions", kind: "help", label: I18n.t("active_admin.command_palette.help.actions"), url: nil},
84
+ {id: "help-resource", kind: "help", label: I18n.t("active_admin.command_palette.help.resource"), url: nil},
85
+ {id: "help-modes", kind: "help", label: I18n.t("active_admin.command_palette.help.modes"), url: nil}
86
+ ]
87
+ end
88
+
89
+ def render_command_palette
90
+ render partial: "active_admin/command_palette/palette"
91
+ end
92
+
93
+ private
94
+
95
+ def active_admin_config_for_palette
96
+ return active_admin_config if respond_to?(:active_admin_config)
97
+
98
+ nil
99
+ end
100
+
101
+ def controller_action_for_palette
102
+ return controller.action_name.to_s if respond_to?(:controller) && controller.respond_to?(:action_name)
103
+
104
+ "other"
105
+ end
106
+
107
+ def resource_param_for_palette
108
+ return resource.to_param if respond_to?(:resource) && resource.respond_to?(:to_param)
109
+
110
+ nil
111
+ end
112
+
113
+ def other_context(namespace_name, action)
114
+ {
115
+ namespace: namespace_name,
116
+ resource: nil,
117
+ resource_param: nil,
118
+ record_param: nil,
119
+ action: action,
120
+ page: "other"
121
+ }
122
+ end
123
+
124
+ def page_context(namespace_name, action)
125
+ {
126
+ namespace: namespace_name,
127
+ resource: nil,
128
+ resource_param: nil,
129
+ record_param: nil,
130
+ action: action,
131
+ page: "page"
132
+ }
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module CommandPalette
5
+ module Integration
6
+ class << self
7
+ def install!
8
+ return if @installed
9
+
10
+ ActiveAdmin::Resource.include(ResourceMethods)
11
+ ActiveAdmin::ResourceDSL.prepend(DSL)
12
+ @installed = true
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module CommandPalette
5
+ class ResourceConfig
6
+ attr_accessor :kind, :noun, :label_method, :order_column
7
+ attr_reader :find_columns, :standard_actions, :create_help, :create_params, :members
8
+
9
+ def initialize
10
+ @kind = nil
11
+ @noun = nil
12
+ @label_method = nil
13
+ @order_column = nil
14
+ @find_columns = []
15
+ @standard_actions = []
16
+ @create_help = nil
17
+ @create_params = []
18
+ @members = []
19
+ @enabled = false
20
+ end
21
+
22
+ def enabled?
23
+ @enabled
24
+ end
25
+
26
+ def enable!
27
+ @enabled = true
28
+ end
29
+
30
+ def find_by(*columns)
31
+ enable!
32
+ @find_columns = columns.flatten.map(&:to_s)
33
+ end
34
+
35
+ def actions(*names)
36
+ enable!
37
+ @standard_actions = names.flatten.map(&:to_sym)
38
+ end
39
+
40
+ def create(help: nil, params: [])
41
+ enable!
42
+ @create_help = help
43
+ @create_params = Array(params).map { |param| param.transform_keys(&:to_sym) }
44
+ end
45
+
46
+ def member(label, **options)
47
+ enable!
48
+ @members << options.transform_keys(&:to_sym).merge(label: label.to_s)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module CommandPalette
5
+ class ResourceDSL
6
+ def initialize(config)
7
+ @config = config
8
+ end
9
+
10
+ def kind(value)
11
+ @config.enable!
12
+ @config.kind = value.to_s
13
+ end
14
+
15
+ def noun(value)
16
+ @config.enable!
17
+ @config.noun = value.to_s
18
+ end
19
+
20
+ def label_method(value)
21
+ @config.enable!
22
+ @config.label_method = value.to_sym
23
+ end
24
+
25
+ def order(value)
26
+ @config.enable!
27
+ @config.order_column = value.to_sym
28
+ end
29
+
30
+ def find_by(*columns)
31
+ @config.find_by(*columns)
32
+ end
33
+
34
+ def actions(*names)
35
+ @config.actions(*names)
36
+ end
37
+
38
+ def create(help: nil, params: [])
39
+ @config.create(help: help, params: params)
40
+ end
41
+
42
+ def member(label, **options)
43
+ @config.member(label, **options)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module CommandPalette
5
+ module ResourceMethods
6
+ def command_palette_config
7
+ @command_palette_config ||= ResourceConfig.new
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module CommandPalette
5
+ # Path helpers for finder callbacks. Uses the host application routes so
6
+ # engine mounting cannot hide ActiveAdmin member paths.
7
+ class UrlHelpers
8
+ def method_missing(method_name, ...)
9
+ helpers = Rails.application.routes.url_helpers
10
+ if helpers.respond_to?(method_name)
11
+ helpers.public_send(method_name, ...)
12
+ else
13
+ super
14
+ end
15
+ end
16
+
17
+ def respond_to_missing?(method_name, include_private = false)
18
+ Rails.application.routes.url_helpers.respond_to?(method_name) || super
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module CommandPalette
5
+ VERSION = "0.1.6"
6
+ end
7
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "activeadmin/command_palette/version"
4
+ require "activeadmin/command_palette/configuration"
5
+ require "activeadmin/command_palette/resource_config"
6
+ require "activeadmin/command_palette/resource_dsl"
7
+ require "activeadmin/command_palette/resource_methods"
8
+ require "activeadmin/command_palette/dsl"
9
+ require "activeadmin/command_palette/compiler"
10
+ require "activeadmin/command_palette/catalog"
11
+ require "activeadmin/command_palette/finder"
12
+ require "activeadmin/command_palette/helper"
13
+ require "activeadmin/command_palette/url_helpers"
14
+ require "activeadmin/command_palette/integration"
15
+
16
+ module ActiveAdmin
17
+ module CommandPalette
18
+ class << self
19
+ def configuration
20
+ @configuration ||= Configuration.new
21
+ end
22
+
23
+ def configure
24
+ yield configuration
25
+ end
26
+
27
+ def reset_configuration!
28
+ @configuration = Configuration.new
29
+ end
30
+
31
+ def reload!
32
+ configuration.reload!
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ require "activeadmin/command_palette/engine"
@@ -0,0 +1,5 @@
1
+ module ActiveAdmin
2
+ module CommandPalette
3
+ VERSION: String
4
+ end
5
+ end