kommandant 0.1.0 → 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.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/builds/kommandant.css +634 -0
  3. data/app/assets/stylesheets/kommandant/application.tailwind.css +3 -0
  4. data/app/controllers/concerns/kommandant/recent_commands.rb +21 -0
  5. data/app/controllers/kommandant/application_controller.rb +6 -1
  6. data/app/controllers/kommandant/commands/searches_controller.rb +40 -0
  7. data/app/controllers/kommandant/commands_controller.rb +17 -0
  8. data/app/controllers/kommandant/searches_controller.rb +14 -0
  9. data/app/models/kommandant/command.rb +88 -0
  10. data/app/models/kommandant/commands/search_result.rb +44 -0
  11. data/app/views/kommandant/commands/searches/show.html.erb +69 -0
  12. data/app/views/kommandant/commands/show.html.erb +20 -0
  13. data/app/views/kommandant/searches/index.html.erb +1 -0
  14. data/app/views/kommandant/searches/new.html.erb +1 -0
  15. data/app/views/kommandant/shared/_command_palette.html.erb +53 -0
  16. data/app/views/kommandant/shared/command_palette/_command.html.erb +14 -0
  17. data/app/views/kommandant/shared/command_palette/_default_state.html.erb +14 -0
  18. data/app/views/kommandant/shared/command_palette/_empty_state.html.erb +7 -0
  19. data/app/views/kommandant/shared/command_palette/_loading_message.html.erb +7 -0
  20. data/app/views/kommandant/shared/command_palette/_result.html.erb +6 -0
  21. data/app/views/kommandant/shared/icons/_chevron_right.html.erb +3 -0
  22. data/app/views/kommandant/shared/icons/_command.erb +7 -0
  23. data/app/views/kommandant/shared/icons/_search.erb +3 -0
  24. data/app/views/kommandant/shared/icons/_spinner.erb +4 -0
  25. data/config/locales/da.yml +19 -0
  26. data/config/locales/en.yml +19 -0
  27. data/config/routes.rb +4 -0
  28. data/lib/kommandant/engine.rb +7 -0
  29. data/lib/kommandant/version.rb +1 -1
  30. data/lib/kommandant.rb +21 -1
  31. metadata +89 -3
@@ -0,0 +1,17 @@
1
+ module Kommandant
2
+ class CommandsController < ApplicationController
3
+ before_action :set_command
4
+
5
+ def show
6
+ if Kommandant.config.recent_commands.enabled
7
+ current_user.recent_commands.prepend(@command.id)
8
+ end
9
+ end
10
+
11
+ private
12
+
13
+ def set_command
14
+ @command = Kommandant::Command.find(params[:id])
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ module Kommandant
2
+ class SearchesController < ApplicationController
3
+ def index
4
+ @results = Kommandant::Command.search(params[:query])
5
+
6
+ unless Kommandant.config.admin_only_filter_lambda.present? && Kommandant.config.admin_only_filter_lambda.call(current_user, current_admin)
7
+ @results.reject!(&:admin_only?)
8
+ end
9
+ end
10
+
11
+ def new
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,88 @@
1
+ module Kommandant
2
+ class Command
3
+ class << self
4
+ def reindex!
5
+ index.add_documents(
6
+ JSON.parse(File.read(Kommandant.config.commands_path))
7
+ )
8
+ end
9
+
10
+ def search(query)
11
+ result = index.search(query)["hits"]
12
+
13
+ result.map do |command|
14
+ new(**command.symbolize_keys)
15
+ end
16
+ end
17
+
18
+ def find(id)
19
+ search(id).find { |command| command.id == id }
20
+ end
21
+
22
+ def all
23
+ JSON.parse(File.read(Kommandant.config.commands_path)).map do |hash|
24
+ Kommandant::Command.new(**hash.symbolize_keys)
25
+ end
26
+ end
27
+
28
+ def index
29
+ client.index("commands")
30
+ end
31
+
32
+ def client
33
+ MeiliSearch::Rails.client
34
+ end
35
+ end
36
+
37
+ def initialize(id:, icon:, path:, http_method:, resource_class: nil, redirect_path: nil, text_keys: [], admin_only: false, translations: {})
38
+ @id = id
39
+ @icon = icon
40
+ @path = path
41
+ @http_method = http_method
42
+ @resource_class = resource_class
43
+ @redirect_path = redirect_path
44
+ @text_keys = text_keys
45
+ @admin_only = admin_only
46
+ @translator = I18n::Backend::KeyValue.new({})
47
+ translations.each_pair do |locale, data|
48
+ @translator.store_translations(locale, data)
49
+ end
50
+ end
51
+
52
+ def ==(other)
53
+ instance_variables.all? do |ivar|
54
+ instance_variable_get(ivar) == other.instance_variable_get(ivar)
55
+ end
56
+ end
57
+
58
+ attr_reader :id, :path, :http_method, :resource_class, :redirect_path, :text_keys, :translator
59
+
60
+ def name
61
+ return "no translation for #{I18n.locale}.name in Meilisearch" unless translator.exists?(I18n.locale, "name")
62
+
63
+ translator.translate(I18n.locale, "name")
64
+ end
65
+
66
+ def placeholder
67
+ return unless translator.exists?(I18n.locale, "placeholder")
68
+
69
+ translator.translate(I18n.locale, "placeholder")
70
+ end
71
+
72
+ def admin_only?
73
+ admin_only
74
+ end
75
+
76
+ def icon?
77
+ icon.present?
78
+ end
79
+
80
+ def icon
81
+ @icon.to_s.dasherize
82
+ end
83
+
84
+ private
85
+
86
+ attr_reader :admin_only
87
+ end
88
+ end
@@ -0,0 +1,44 @@
1
+ module Kommandant
2
+ class Commands::SearchResult
3
+ def initialize(command:, resource:)
4
+ @command = command
5
+ @resource = resource
6
+ @translator = command.translator
7
+ end
8
+
9
+ attr_reader :resource
10
+ delegate :icon, to: :command
11
+
12
+ def name
13
+ if translator.exists?(I18n.locale, "result_text")
14
+ translation_attributes = {}
15
+ command.text_keys.each do |key|
16
+ translation_attributes[key.to_sym] = indexed_attribute_for(resource, key)
17
+ end
18
+ translator.translate(I18n.locale, "result_text", **translation_attributes)
19
+ else
20
+ command.text_keys.map do |text_key|
21
+ indexed_attribute_for(resource, text_key)
22
+ end.join(", ")
23
+ end
24
+ end
25
+
26
+ def path
27
+ return command.redirect_path.gsub(":id", resource.id.to_s) if command.redirect_path
28
+
29
+ "/#{command.resource_class.downcase.pluralize}/#{resource.id}"
30
+ end
31
+
32
+ def http_method
33
+ :get
34
+ end
35
+
36
+ private
37
+
38
+ attr_reader :command, :translator
39
+
40
+ def indexed_attribute_for(resource, text_key)
41
+ resource.meilisearch_settings.get_attributes(resource)[text_key]
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,69 @@
1
+ <%= turbo_frame_tag :command_palette do %>
2
+ <div data-command-palette-target="content">
3
+ <div class="kommandant-flex kommandant-px-4">
4
+ <div class="kommandant-flex kommandant-items-center kommandant-space-x-1 kommandant-text-gray-400">
5
+ <%= link_to(Kommandant::Engine.routes.url_helpers.searches_path, class: "kommandant-inline-flex kommandant-items-center kommandant-space-x-2 kommandant-border kommandant-shadow-sm kommandant-font-medium kommandant-rounded-md focus:kommandant-outline-none focus:kommandant-ring-2 focus:kommandant-ring-offset-2 disabled:kommandant-opacity-50 disabled:kommandant-cursor-not-allowed kommandant-border-gray-300 kommandant-text-gray-700 kommandant-bg-white hover:kommandant-bg-gray-50 hover:kommandant-cursor-pointer focus:kommandant-ring-gray-500 kommandant-text-xs kommandant-px-2.5 kommandant-py-1.5") do %>
6
+ <span><%= @command.name %></span>
7
+ <% end %>
8
+
9
+ <%= render "kommandant/shared/icons/chevron_right" %>
10
+ </div>
11
+
12
+ <%= form_with url: Kommandant::Engine.routes.url_helpers.command_searches_path(@command.id), method: :get, class: "kommandant-flex-grow" do |form| %>
13
+ <%= form.text_field :query, class: "kommandant-h-12 kommandant-w-full kommandant-border-0 kommandant-bg-transparent kommandant-pl-1 kommandant-pr-4 kommandant-text-gray-800 kommandant-placeholder-gray-400 focus:kommandant-ring-0 sm:kommandant-text-sm", value: params[:query], placeholder: @command.placeholder, autocomplete: "off", data: {command_palette_target: "input", action: "keyup->command-palette#submit"} %>
14
+ <% end %>
15
+ </div>
16
+
17
+ <% if @results.any? %>
18
+ <ul class="kommandant-overflow-y-auto kommandant-p-2 kommandant-text-sm kommandant-text-gray-700">
19
+ <% @results.each do |result| %>
20
+ <%# TODO: How about a default? %>
21
+ <% if partial_exists_for_result?(result) %>
22
+ <%= render result.resource, result: result %>
23
+ <% else %>
24
+ <%= render "kommandant/shared/command_palette/result", result: result %>
25
+ <% end %>
26
+ <% end %>
27
+ </ul>
28
+
29
+ <% if Kommandant.config.pagination.enabled %>
30
+ <div class="kommandant-px-4 kommandant-py-2 kommandant-flex kommandant-justify-between kommandant-items-baseline">
31
+ <span class="text-sm"><%= @pagination_info_label %></span>
32
+
33
+ <div class="kommandant-space-x-4">
34
+ <%= link_to(t("kommandant.pagination.prev"), Kommandant::Engine.routes.url_helpers.command_searches_path(@command.id, page: @pagination.prev, query: @query), class: "kommandant-font-medium kommandant-text-sm kommandant-text-gray-600 hover:kommandant-text-gray-800", data: {command_palette_target: "previousPageLink"}) if @pagination.prev %>
35
+ <%= link_to(t("kommandant.pagination.next"), Kommandant::Engine.routes.url_helpers.command_searches_path(@command.id, page: @pagination.next, query: @query), class: "kommandant-font-medium kommandant-text-sm kommandant-text-gray-600 hover:kommandant-text-gray-800", data: {command_palette_target: "nextPageLink"}) if @pagination.next %>
36
+ </div>
37
+ </div>
38
+ <% end %>
39
+ <% else %>
40
+ <%= render "kommandant/shared/command_palette/empty_state", query: params[:query] %>
41
+ <% end %>
42
+ </div>
43
+
44
+ <%= render "kommandant/shared/command_palette/loading_message" %>
45
+
46
+ <% if Kommandant.config.pagination.enabled && @pagination.pages > 1 %>
47
+ <div class="kommandant-flex kommandant-justify-end kommandant-items-center kommandant-divide-x kommandant-bg-gray-50 kommandant-py-2.5 kommandant-px-4 kommandant-text-xs kommandant-text-gray-700">
48
+ <div class="kommandant-pr-2 kommandant-flex kommandant-flex-wrap kommandant-justify-end kommandant-items-center kommandant-space-x-2">
49
+ <span><%= t("kommandant.pagination.prev").html_safe %>:</span>
50
+
51
+ <div class="kommandant-flex kommandant-items-center kommandant-space-x-1">
52
+ <kbd class="kommandant-px-1 kommandant-py-0.5 kommandant-rounded kommandant-border kommandant-border-gray-400 kommandant-bg-white kommandant-font-semibold kommandant-text-gray-900">Ctrl</kbd>
53
+ <span>+</span>
54
+ <kbd class="kommandant-px-1 kommandant-py-0.5 kommandant-rounded kommandant-border kommandant-border-gray-400 kommandant-bg-white kommandant-font-semibold kommandant-text-gray-900">J</kbd>
55
+ </div>
56
+ </div>
57
+
58
+ <div class="kommandant-pl-2 kommandant-flex kommandant-flex-wrap kommandant-justify-end kommandant-items-center kommandant-space-x-2">
59
+ <span><%= t("kommandant.pagination.next").html_safe %>:</span>
60
+
61
+ <div class="kommandant-flex kommandant-items-center kommandant-space-x-1">
62
+ <kbd class="kommandant-px-1 kommandant-py-0.5 kommandant-rounded kommandant-border kommandant-border-gray-400 kommandant-bg-white kommandant-font-semibold kommandant-text-gray-900">Ctrl</kbd>
63
+ <span>+</span>
64
+ <kbd class="kommandant-px-1 kommandant-py-0.5 kommandant-rounded kommandant-border kommandant-border-gray-400 kommandant-bg-white kommandant-font-semibold kommandant-text-gray-900">L</kbd>
65
+ </div>
66
+ </div>
67
+ </div>
68
+ <% end %>
69
+ <% end %>
@@ -0,0 +1,20 @@
1
+ <%= turbo_frame_tag :command_palette do %>
2
+ <div data-command-palette-target="content">
3
+ <div class="kommandant-flex kommandant-px-4">
4
+ <div class="kommandant-flex kommandant-items-center kommandant-space-x-1 kommandant-text-gray-400">
5
+ <%= link_to(Kommandant::Engine.routes.url_helpers.searches_path, class: "kommandant-inline-flex kommandant-items-center kommandant-space-x-2 kommandant-border kommandant-shadow-sm kommandant-font-medium kommandant-rounded-md focus:kommandant-outline-none focus:kommandant-ring-2 focus:kommandant-ring-offset-2 disabled:kommandant-opacity-50 disabled:kommandant-cursor-not-allowed kommandant-border-gray-300 kommandant-text-gray-700 kommandant-bg-white hover:kommandant-bg-gray-50 hover:kommandant-cursor-pointer focus:kommandan
6
+ tring-gray-500 kommandant-text-xs kommandant-px-2.5 kommandant-py-1.5") do %>
7
+ <span><%= @command.name %></span>
8
+ <% end %>
9
+
10
+ <%= render "kommandant/shared/icons/chevron_right" %>
11
+ </div>
12
+
13
+ <%= form_with url: Kommandant::Engine.routes.url_helpers.command_searches_path(@command.id), method: :get, class: "kommandant-flex-grow" do |form| %>
14
+ <%= form.text_field :query, class: "kommandant-h-12 kommandant-w-full kommandant-border-0 kommandant-bg-transparent kommandant-pl-1 kommandant-pr-4 kommandant-text-gray-800 kommandant-placeholder-gray-400 focus:kommandant-ring-0 sm:kommandant-text-sm", value: @query, placeholder: @command.placeholder, autocomplete: "off", data: {command_palette_target: "input", action: "keyup->command-palette#submit"} %>
15
+ <% end %>
16
+ </div>
17
+ </div>
18
+
19
+ <%= render "kommandant/shared/command_palette/loading_message" %>
20
+ <% end %>
@@ -0,0 +1 @@
1
+ <%= render "kommandant/shared/command_palette", results: @results, query: params[:query] %>
@@ -0,0 +1 @@
1
+ <%= render "kommandant/shared/command_palette", recent_commands: @recent_commands %>
@@ -0,0 +1,53 @@
1
+ <div class="kommandant-relative kommandant-z-50" role="dialog" aria-modal="true" data-action="click->command-palette#hideWithBackgroundOverlay keydown.ctrl+l->command-palette#nextPage keydown.ctrl+j->command-palette#previousPage keydown.meta+l->command-palette#nextPage keydown.meta+j->command-palette#previousPage">
2
+ <div class="hidden kommandant-fixed kommandant-inset-0 kommandant-bg-gray-500 kommandant-bg-opacity-25 kommandant-transition-opacity"
3
+ data-command-palette-target="background"
4
+ data-transition-enter="kommandant-ease-out kommandant-duration-300"
5
+ data-transition-enter-start="kommandant-opacity-0"
6
+ data-transition-enter-end="kommandant-opacity-100"
7
+ data-transition-leave="kommandant-ease-in kommandant-duration-200"
8
+ data-transition-leave-start="kommandant-opacity-100"
9
+ data-transition-leave-end="kommandant-opacity-0">
10
+ </div>
11
+
12
+ <div class="hidden kommandant-fixed kommandant-inset-0 kommandant-z-10 kommandant-overflow-y-auto kommandant-p-4 sm:kommandant-p-6 md:kommandant-p-20"
13
+ data-command-palette-target="panel"
14
+ data-transition-enter="kommandant-ease-out kommandant-duration-300"
15
+ data-transition-enter-start="kommandant-opacity-0 kommandant-scale-95"
16
+ data-transition-enter-end="kommandant-opacity-100 kommandant-scale-100"
17
+ data-transition-leave="kommandant-ease-in kommandant-duration-200"
18
+ data-transition-leave-start="kommandant-opacity-100 kommandant-scale-100"
19
+ data-transition-leave-end="kommandant-opacity-0 kommandant-scale-95">
20
+
21
+ <div class="kommandant-mx-auto kommandant-max-w-2xl kommandant-transform kommandant-divide-y kommandant-divide-gray-100 kommandant-overflow-hidden kommandant-rounded-xl kommandant-bg-white kommandant-shadow-2xl kommandant-ring-1 kommandant-ring-black kommandant-ring-opacity-5 kommandant-transition-all">
22
+ <%= turbo_frame_tag :command_palette do %>
23
+ <div data-command-palette-target="content">
24
+ <div class="kommandant-relative">
25
+ <div class="kommandant-pointer-events-none kommandant-absolute kommandant-top-3 kommandant-left-4 kommandant-text-gray-400">
26
+ <%= render "kommandant/shared/icons/search" %>
27
+ </div>
28
+ <%= form_with url: Kommandant::Engine.routes.url_helpers.searches_path, method: :get do |form| %>
29
+ <%= form.text_field :query, value: local_assigns[:query], class: "kommandant-h-12 kommandant-w-full kommandant-border-0 kommandant-bg-transparent kommandant-pl-11 kommandant-pr-4 kommandant-text-gray-800 kommandant-placeholder-gray-400 focus:kommandant-ring-0 sm:kommandant-text-sm", placeholder: t(".input_placeholder"), autocomplete: "off", data: {command_palette_target: "input", action: "keyup->command-palette#submit"} %>
30
+ <% end %>
31
+ </div>
32
+ <% if local_assigns[:results] %>
33
+ <% if local_assigns[:results].empty? && local_assigns[:query] %>
34
+ <%= render "kommandant/shared/command_palette/empty_state", query: query %>
35
+ <% elsif local_assigns[:results].any? %>
36
+ <!-- Results, show/hide based on command palette state. -->
37
+ <ul class="kommandant-max-h-96 kommandant-overflow-y-auto kommandant-p-2 kommandant-text-sm kommandant-text-gray-700">
38
+ <% local_assigns[:results].each do |result| %>
39
+ <%= render "kommandant/shared/command_palette/command", command: result %>
40
+ <% end %>
41
+ </ul>
42
+ <% end %>
43
+ <% else %>
44
+ <%= render "kommandant/shared/command_palette/default_state", recent_commands: local_assigns[:recent_commands] %>
45
+ <% end %>
46
+ </div>
47
+
48
+ <%= render "kommandant/shared/command_palette/loading_message" %>
49
+ <% end %>
50
+ </div>
51
+ </div>
52
+ </div>
53
+
@@ -0,0 +1,14 @@
1
+ <li>
2
+ <%= link_to command.path, class: "kommandant-group kommandant-cursor-default kommandant-select-none kommandant-flex kommandant-items-center kommandant-px-3 kommandant-py-2 kommandant-rounded-md data-[active=true]:kommandant-bg-slate-600 data-[active=true]:kommandant-text-white group-data-[active=true]:kommandant-outline-none", data: {active: false, keyboard_navigation_target: "focusable", action: "mouseenter->keyboard-navigation#focus click->command-palette#showLoadingMessage", turbo_method: command.http_method} do %>
3
+ <% if command.icon? %>
4
+ <!-- For some reason the line below works, but the uncommented line does not color the icon... I have no idea why... -->
5
+ <!-- <div class="kommandant-flex-none kommandant-text-gray-400 group-data-[active=true]:text-white"> -->
6
+ <div class="kommandant-flex-none kommandant-text-gray-400 group-data-[active=true]:kommandant-text-white">
7
+ <%= render "kommandant/shared/icons/command", icon: command.icon %>
8
+ </div>
9
+ <% end %>
10
+
11
+ <span class="kommandant-ml-3 kommandant-flex-auto kommandant-truncate"><%= command.name %></span>
12
+ <span class="kommandant-ml-3 kommandant-hidden group-data[active=true]:kommandant-inline kommandant-flex-none kommandant-text-indigo-100"><%= t("kommandant.navigation.jump_to") %></span>
13
+ <% end %>
14
+ </li>
@@ -0,0 +1,14 @@
1
+ <% if local_assigns[:recent_commands] %>
2
+ <ul class="kommandant-max-h-80 kommandant-scroll-py-2 kommandant-divide-y kommandant-divide-gray-100 kommandant-overflow-y-auto">
3
+ <% if recent_commands.any? %>
4
+ <li class="kommandant-p-2">
5
+ <h2 class="kommandant-mt-4 kommandant-mb-2 kommandant-px-3 kommandant-text-xs kommandant-font-semibold kommandant-text-gray-500"><%= t(".recent_commands") %></h2>
6
+ <ul class="kommandant-text-sm kommandant-text-gray-700">
7
+ <% recent_commands.each do |command| %>
8
+ <%= render "kommandant/shared/command_palette/command", command: command %>
9
+ <% end %>
10
+ </ul>
11
+ </li>
12
+ <% end %>
13
+ </ul>
14
+ <% end %>
@@ -0,0 +1,7 @@
1
+ <div class="kommandant-py-14 kommandant-px-6 kommandant-text-center sm:kommandant-px-14">
2
+ <span class="kommandant-p-2 kommandant-shadow-sm kommandant-inline-flex kommandant-items-center kommandant-justify-center kommandant-text-xs kommandant-leading-5 kommandant-font-semibold kommandant-rounded-full kommandant-flex-shrink-0 kommandant-h-10 kommandant-w-10 kommandant-bg-slate-700 kommandant-text-white">
3
+ <%= render "kommandant/shared/icons/search" %>
4
+ </span>
5
+
6
+ <p class="kommandant-mt-4 kommandant-text-sm kommandant-text-gray-900"><%= t(".text") %> <span class="kommandant-font-medium"><%= query %></span></p>
7
+ </div>
@@ -0,0 +1,7 @@
1
+ <div data-command-palette-target="loadingMessage" class="hidden">
2
+ <div class="kommandant-flex kommandant-items-center kommandant-space-x-2 kommandant-px-4 kommandant-h-12">
3
+ <%= render "kommandant/shared/icons/spinner" %>
4
+
5
+ <p class="kommandant-text-sm kommandant-font-medium kommandant-text-gray-700"><%= t(".text") %></p>
6
+ </div>
7
+ </div>
@@ -0,0 +1,6 @@
1
+ <li>
2
+ <%= link_to result.path, data: {active: false, keyboard_navigation_target: "focusable", action: "mouseenter->keyboard-navigation#focus click->command-palette#showLoadingMessage", turbo_method: result.http_method, turbo_frame: "_top"}, class: "kommandant-group kommandant-cursor-default kommandant-select-none kommandant-flex kommandant-items-center kommandant-px-3 kommandant-py-2 kommandant-rounded-md data-[active=true]:kommandant-bg-slate-600 data-[active=true]:kommandant-text-white group-data-[active=true]:kommandant-outline-none" do %>
3
+ <span class="kommandant-ml-3 kommandant-flex-auto kommandant-truncate"><%= result.name %></span>
4
+ <span class="kommandant-ml-3 kommandant-hidden group-data-[active=true]:kommandant-inline kommandant-flex-none kommandant-text-indigo-100"><%= t("kommandant.navigation.jump_to") %></span>
5
+ <% end %>
6
+ </li>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="kommandant-w-6 kommandant-h-6">
2
+ <path fill-rule="evenodd" d="M16.28 11.47a.75.75 0 010 1.06l-7.5 7.5a.75.75 0 01-1.06-1.06L14.69 12 7.72 5.03a.75.75 0 011.06-1.06l7.5 7.5z" clip-rule="evenodd" />
3
+ </svg>
@@ -0,0 +1,7 @@
1
+ <div class="kommandant-flex kommandant-space-x-2">
2
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="kommandant-w-5 kommandant-h-5">
3
+ <path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
4
+ </svg>
5
+
6
+ <span><%= t(".icon_missing", icon_name: icon) %></span>
7
+ </div>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="kommandant-w-5 kommandant-h-5">
2
+ <path fill-rule="evenodd" d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z" clip-rule="evenodd" />
3
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg class="kommandant-animate-spin kommandant-h-5 kommandant-w-5 kommandant-text-gray-900" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
2
+ <circle class="kommandant-opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
3
+ <path class="kommandant-opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
4
+ </svg>
@@ -0,0 +1,19 @@
1
+ da:
2
+ kommandant:
3
+ shared:
4
+ command_palette:
5
+ input_placeholder: Søg
6
+ default_state:
7
+ recent_commands: Sidst brugte
8
+ empty_state:
9
+ text: Vi kunne ikke finde nogen resultater for
10
+ loading_message:
11
+ text: Henter...
12
+ icons:
13
+ command:
14
+ icon_missing: Command icon mangler med navn '%{icon_name}'
15
+ navigation:
16
+ jump_to: Gå til
17
+ pagination:
18
+ prev: Forrige
19
+ next: Næste
@@ -0,0 +1,19 @@
1
+ en:
2
+ kommandant:
3
+ shared:
4
+ command_palette:
5
+ input_placeholder: Search
6
+ default_state:
7
+ recent_commands: Recently used
8
+ empty_state:
9
+ text: No results found for
10
+ loading_message:
11
+ text: Loading...
12
+ icons:
13
+ command:
14
+ icon_missing: Command icon missing with name '%{icon_name}'
15
+ navigation:
16
+ jump_to: Jump to
17
+ pagination:
18
+ prev: Previous
19
+ next: Next
data/config/routes.rb CHANGED
@@ -1,2 +1,6 @@
1
1
  Kommandant::Engine.routes.draw do
2
+ resources :searches, only: [:index, :new]
3
+ resources :commands, only: [:show] do
4
+ resource :searches, module: :commands, only: [:show]
5
+ end
2
6
  end
@@ -1,5 +1,12 @@
1
1
  module Kommandant
2
2
  class Engine < ::Rails::Engine
3
3
  isolate_namespace Kommandant
4
+
5
+ PRECOMPILE_ASSETS = %w( kommandant.js )
6
+ initializer "kommandant.assets" do |app|
7
+ if Rails.application.config.respond_to?(:assets)
8
+ Rails.application.config.assets.precompile += PRECOMPILE_ASSETS
9
+ end
10
+ end
4
11
  end
5
12
  end
@@ -1,3 +1,3 @@
1
1
  module Kommandant
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/kommandant.rb CHANGED
@@ -1,6 +1,26 @@
1
1
  require "kommandant/version"
2
2
  require "kommandant/engine"
3
+ require "meilisearch-rails"
4
+ require "dry-configurable"
3
5
 
4
6
  module Kommandant
5
- # Your code goes here...
7
+ extend Dry::Configurable
8
+
9
+ setting :commands_path, default: "config/kommandant/commands.json"
10
+ setting :search_result_filter_lambda
11
+ setting :admin_only_filter_lambda
12
+ setting :parent_controller, default: "::ApplicationController"
13
+ setting :current_user_method, default: "current_user"
14
+ setting :recent_commands do
15
+ setting :enabled, default: true
16
+ end
17
+ setting :pagination do
18
+ setting :enabled, default: true
19
+ setting :items_per_page, default: 10
20
+ setting :pagination_lambda, default: ->(results, items, controller) { controller.send(:pagy_array, results, items: items) }
21
+ setting :info_label_lambda, default: ->(pagination, controller) { controller.send(:pagy_info, pagination).html_safe }
22
+ setting :module, default: "Pagy::Frontend"
23
+ end
24
+
25
+ autoload :Command, "kommandant/command"
6
26
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kommandant
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nicolai Bach Woller
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-06-19 00:00:00.000000000 Z
11
+ date: 2024-02-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -24,6 +24,68 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 7.0.5
27
+ - !ruby/object:Gem::Dependency
28
+ name: meilisearch-rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.8.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.8.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: turbo-rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.4'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.4'
55
+ - !ruby/object:Gem::Dependency
56
+ name: dry-configurable
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.0'
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: 1.0.1
65
+ type: :runtime
66
+ prerelease: false
67
+ version_requirements: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - "~>"
70
+ - !ruby/object:Gem::Version
71
+ version: '1.0'
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 1.0.1
75
+ - !ruby/object:Gem::Dependency
76
+ name: tailwindcss-rails
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: 2.0.30
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: 2.0.30
27
89
  description: A command palette built with Hotwire and Meilisearch
28
90
  email:
29
91
  - woller@traels.it
@@ -34,14 +96,38 @@ files:
34
96
  - MIT-LICENSE
35
97
  - README.md
36
98
  - Rakefile
99
+ - app/assets/builds/kommandant.css
37
100
  - app/assets/config/kommandant_manifest.js
38
101
  - app/assets/stylesheets/kommandant/application.css
102
+ - app/assets/stylesheets/kommandant/application.tailwind.css
103
+ - app/controllers/concerns/kommandant/recent_commands.rb
39
104
  - app/controllers/kommandant/application_controller.rb
105
+ - app/controllers/kommandant/commands/searches_controller.rb
106
+ - app/controllers/kommandant/commands_controller.rb
107
+ - app/controllers/kommandant/searches_controller.rb
40
108
  - app/helpers/kommandant/application_helper.rb
41
109
  - app/jobs/kommandant/application_job.rb
42
110
  - app/mailers/kommandant/application_mailer.rb
43
111
  - app/models/kommandant/application_record.rb
112
+ - app/models/kommandant/command.rb
113
+ - app/models/kommandant/commands/search_result.rb
114
+ - app/views/kommandant/commands/searches/show.html.erb
115
+ - app/views/kommandant/commands/show.html.erb
116
+ - app/views/kommandant/searches/index.html.erb
117
+ - app/views/kommandant/searches/new.html.erb
118
+ - app/views/kommandant/shared/_command_palette.html.erb
119
+ - app/views/kommandant/shared/command_palette/_command.html.erb
120
+ - app/views/kommandant/shared/command_palette/_default_state.html.erb
121
+ - app/views/kommandant/shared/command_palette/_empty_state.html.erb
122
+ - app/views/kommandant/shared/command_palette/_loading_message.html.erb
123
+ - app/views/kommandant/shared/command_palette/_result.html.erb
124
+ - app/views/kommandant/shared/icons/_chevron_right.html.erb
125
+ - app/views/kommandant/shared/icons/_command.erb
126
+ - app/views/kommandant/shared/icons/_search.erb
127
+ - app/views/kommandant/shared/icons/_spinner.erb
44
128
  - app/views/layouts/kommandant/application.html.erb
129
+ - config/locales/da.yml
130
+ - config/locales/en.yml
45
131
  - config/routes.rb
46
132
  - lib/kommandant.rb
47
133
  - lib/kommandant/engine.rb
@@ -68,7 +154,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
68
154
  - !ruby/object:Gem::Version
69
155
  version: '0'
70
156
  requirements: []
71
- rubygems_version: 3.3.7
157
+ rubygems_version: 3.5.6
72
158
  signing_key:
73
159
  specification_version: 4
74
160
  summary: A command palette for Rails