kommandant 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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