activeadmin_favorites 0.1.9 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3509bb00158d9a807b7ff01e637302b061ae0a30b6e3adf2ca985c4c23c2376c
4
- data.tar.gz: '0876219df16bdf67f790adb9541b55677a2938eef7ad81e006951aad6e875905'
3
+ metadata.gz: bb66b5a12099039bdfcb6e541cfad6bf15be19aab694d260b3440a026fab49ed
4
+ data.tar.gz: fed666eb55e41a6cf53b1dae6cb8ec88e13cc84980514e454d81c93227a7b84b
5
5
  SHA512:
6
- metadata.gz: ed84a8a3710e2b13a3509afec58f33ad49b8d050c290ba8375029bc6ef28bdc491d4887d66b93ac464f1604a25f41ee7bf880e5f7a62c043156a662afd1b9d82
7
- data.tar.gz: d368aef63d1250df6627bafb17d6956082473873dcd3a2a3a2f27b525c7a69eccedd1cbc9ef2604b8af88725aa3b40803ac238b933b6aa887dd13188d370f1c0
6
+ metadata.gz: 7a5771c7a4aeaef966f22fe485af17c9c7f76053b788f53a53b54fb63d4b171e13f2cb60280fd6348edefd695e5701e69086b1fc7a56585ec31efe8868755234
7
+ data.tar.gz: 35f952fa4d89e25bfcbeb7c5c389b80b7256d607e7ec0605be9f269a8dad56e0101b9a37513014f610c7bfba9f548161d374a53ae4a0e0a9f244b147a3d29ab2
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 0.1.10 (2026-11-04)
4
+
5
+ - Save a private filter favorite from the index star in one step (suggested name; stay on the filtered index).
6
+ - Keep the full favorites form for edit and as a no-JavaScript fallback from the star.
7
+ - Drop the required marker on "Published for everyone".
8
+ - Show macros JSON only when "Edit macros as JSON" is checked; disable template rows in that mode.
9
+ - Hide Dynamic filters and Advanced macros on create when no date ranges are detected.
10
+
3
11
  ## 0.1.9 (2026-11-04)
4
12
 
5
13
  - Show Add to favorites / Edit favorite as a star icon button on index action rows (accessible name only; filled when an owned favorite matches).
data/README.md CHANGED
@@ -108,7 +108,7 @@ Catalog entries are discovered at render time from ActiveAdmin columns, filters,
108
108
 
109
109
  The gem installs:
110
110
 
111
- - **Add to favorites** star icon on index pages (outline when unsaved, filled when an owned filter favorite matches the current query; label is the accessible name only)
111
+ - **Add to favorites** star icon on index pages (outline when unsaved, filled when an owned filter favorite matches the current query; label is the accessible name only). With JavaScript, the star saves a private filter favorite under a suggested name and stays on the filtered index. The full form remains for edit and as a no-JavaScript fallback.
112
112
  - **Personalize page** cogwheel on index and show pages (hidden when the page lens catalog is empty)
113
113
  - **Favorites** hub at `/admin/favorites` with type tabs (all, filters, lens, combined)
114
114
 
@@ -0,0 +1,38 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static values = {
5
+ createUrl: String,
6
+ path: String,
7
+ queryString: String,
8
+ resourceKey: String,
9
+ actionName: String
10
+ }
11
+
12
+ async quickCreate(event) {
13
+ event.preventDefault()
14
+
15
+ const body = new FormData()
16
+ body.append("path", this.pathValue)
17
+ body.append("query_string", this.queryStringValue || "")
18
+ body.append("resource_key", this.resourceKeyValue)
19
+ body.append("action_name", this.actionNameValue || "index")
20
+ body.append("authenticity_token", this.csrfToken())
21
+
22
+ const response = await fetch(this.createUrlValue, {
23
+ method: "POST",
24
+ body,
25
+ headers: { Accept: "text/html" }
26
+ })
27
+
28
+ if (response.redirected) {
29
+ window.location.href = response.url
30
+ } else if (response.ok) {
31
+ window.location.reload()
32
+ }
33
+ }
34
+
35
+ csrfToken() {
36
+ return document.querySelector("meta[name='csrf-token']")?.content
37
+ }
38
+ }
@@ -0,0 +1,34 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["jsonField", "templates", "hint"]
5
+
6
+ connect() {
7
+ this.sync(this.advancedChecked())
8
+ }
9
+
10
+ toggle(event) {
11
+ this.sync(event.target.checked)
12
+ }
13
+
14
+ sync(advanced) {
15
+ if (this.hasJsonFieldTarget) {
16
+ this.jsonFieldTarget.hidden = !advanced
17
+ this.jsonFieldTarget.disabled = !advanced
18
+ }
19
+ if (this.hasHintTarget) {
20
+ this.hintTarget.hidden = !advanced
21
+ }
22
+ if (this.hasTemplatesTarget) {
23
+ this.templatesTarget.querySelectorAll("input, select").forEach((element) => {
24
+ if (element.type === "hidden") return
25
+ element.disabled = advanced
26
+ })
27
+ }
28
+ }
29
+
30
+ advancedChecked() {
31
+ const checkbox = this.element.querySelector("#favorite_use_advanced_macros")
32
+ return Boolean(checkbox?.checked)
33
+ }
34
+ }
@@ -22,7 +22,6 @@ module ActiveAdmin
22
22
  attr_accessor :macros_json, :use_advanced_macros, :include_layout
23
23
 
24
24
  validates :name, :resource_key, :path, presence: true
25
- validates :published, inclusion: {in: [true, false]}
26
25
  validates :macros_version, numericality: {only_integer: true, greater_than: 0}
27
26
  validates :action, inclusion: {in: ACTIONS}, allow_nil: true
28
27
 
@@ -31,6 +30,7 @@ module ActiveAdmin
31
30
  validate :validate_macros_schema
32
31
 
33
32
  before_validation :normalize_blank_query_string
33
+ before_validation :normalize_published
34
34
 
35
35
  before_save :sync_published_at
36
36
  before_validation :assign_default_type
@@ -294,6 +294,10 @@ module ActiveAdmin
294
294
  self.query_string = "" if query_string.nil?
295
295
  end
296
296
 
297
+ def normalize_published
298
+ self.published = false if published.nil?
299
+ end
300
+
297
301
  def unique_path_query_and_type_per_user
298
302
  foreign_key = ActiveAdmin::Favorites.config.user_foreign_key
299
303
  owner_id = public_send(foreign_key)
@@ -1,8 +1,23 @@
1
+ <%
2
+ stimulus_controller = "activeadmin-favorites--filter-favorite"
3
+ link_data = {testid: "filter-favorite", favorited: favorited}
4
+ unless favorited
5
+ link_data.merge!(
6
+ controller: stimulus_controller,
7
+ action: "click->#{stimulus_controller}#quickCreate",
8
+ "#{stimulus_controller}-create-url-value": quick_create_url,
9
+ "#{stimulus_controller}-path-value": path,
10
+ "#{stimulus_controller}-query-string-value": query_string.to_s,
11
+ "#{stimulus_controller}-resource-key-value": resource_key,
12
+ "#{stimulus_controller}-action-name-value": action_name
13
+ )
14
+ end
15
+ %>
1
16
  <%= link_to(
2
17
  destination,
3
18
  class: "action-item-button inline-flex items-center justify-center",
4
19
  aria: {label: label},
5
- data: {testid: "filter-favorite", favorited: favorited},
20
+ data: link_data,
6
21
  title: label
7
22
  ) do %>
8
23
  <% if favorited %>
@@ -1,53 +1,68 @@
1
1
  <% bookmark = f.object %>
2
- <% template_options = ActiveAdmin::Favorites::Favorite::MacroCatalog.options_for_select %>
3
- <%= hidden_field_tag :macro_form_submitted, "1" %>
2
+ <% show_templates = bookmark.macro_form_rows.any? %>
3
+ <% show_advanced = show_templates || bookmark.persisted? || ActiveModel::Type::Boolean.new.cast(bookmark.use_advanced_macros) %>
4
+ <% if show_templates || show_advanced %>
5
+ <% template_options = ActiveAdmin::Favorites::Favorite::MacroCatalog.options_for_select %>
6
+ <%= hidden_field_tag :macro_form_submitted, "1" %>
4
7
 
5
- <fieldset class="inputs">
6
- <legend><span><%= t("active_admin.favorites.macros_panel") %></span></legend>
7
- <p class="inline-hints"><%= t("active_admin.favorites.macros_hint") %></p>
8
+ <div data-controller="activeadmin-favorites--macro-fields">
9
+ <% if show_templates %>
10
+ <fieldset class="inputs" data-activeadmin-favorites--macro-fields-target="templates">
11
+ <legend><span><%= t("active_admin.favorites.macros_panel") %></span></legend>
12
+ <p class="inline-hints"><%= t("active_admin.favorites.macros_hint") %></p>
8
13
 
9
- <% if bookmark.macro_form_rows.any? %>
10
- <ol class="space-y-4 list-none p-0">
11
- <% bookmark.macro_form_rows.each_with_index do |row, index| %>
12
- <li class="rounded-md border border-gray-200 p-4 dark:border-white/10">
13
- <div class="font-medium text-sm mb-2">
14
- <%= row[:attribute].to_s.tr("_", " ") %>
15
- <% if row[:gteq].present? || row[:lteq].present? %>
16
- <span class="text-gray-500">
17
- (<%= [row[:gteq], row[:lteq]].compact.join(" – ") %>)
18
- </span>
19
- <% end %>
20
- </div>
21
- <%= hidden_field_tag "macro_template_assignments[#{index}][attribute]", row[:attribute] %>
22
- <div class="flex flex-wrap items-center gap-4">
23
- <label class="inline-flex items-center gap-2 text-sm">
24
- <%= check_box_tag "macro_template_assignments[#{index}][enabled]", "1", row[:enabled], id: "macro_template_assignments_#{index}_enabled" %>
25
- <%= t("active_admin.favorites.use_dynamic_range") %>
26
- </label>
27
- <%= select_tag "macro_template_assignments[#{index}][name]",
28
- options_for_select(template_options, row[:name]),
29
- id: "macro_template_assignments_#{index}_name",
30
- class: "rounded-md border-gray-300 text-sm dark:border-white/20 dark:bg-gray-900" %>
31
- </div>
32
- </li>
33
- <% end %>
34
- </ol>
35
- <% else %>
36
- <p class="text-sm text-gray-600 dark:text-gray-300"><%= t("active_admin.favorites.no_detected_ranges") %></p>
37
- <% end %>
38
- </fieldset>
14
+ <ol class="space-y-4 list-none p-0">
15
+ <% bookmark.macro_form_rows.each_with_index do |row, index| %>
16
+ <li class="rounded-md border border-gray-200 p-4 dark:border-white/10">
17
+ <div class="font-medium text-sm mb-2">
18
+ <%= row[:attribute].to_s.tr("_", " ") %>
19
+ <% if row[:gteq].present? || row[:lteq].present? %>
20
+ <span class="text-gray-500">
21
+ (<%= [row[:gteq], row[:lteq]].compact.join(" – ") %>)
22
+ </span>
23
+ <% end %>
24
+ </div>
25
+ <%= hidden_field_tag "macro_template_assignments[#{index}][attribute]", row[:attribute] %>
26
+ <div class="flex flex-wrap items-center gap-4">
27
+ <label class="inline-flex items-center gap-2 text-sm">
28
+ <%= check_box_tag "macro_template_assignments[#{index}][enabled]", "1", row[:enabled], id: "macro_template_assignments_#{index}_enabled" %>
29
+ <%= t("active_admin.favorites.use_dynamic_range") %>
30
+ </label>
31
+ <%= select_tag "macro_template_assignments[#{index}][name]",
32
+ options_for_select(template_options, row[:name]),
33
+ id: "macro_template_assignments_#{index}_name",
34
+ class: "rounded-md border-gray-300 text-sm dark:border-white/20 dark:bg-gray-900" %>
35
+ </div>
36
+ </li>
37
+ <% end %>
38
+ </ol>
39
+ </fieldset>
40
+ <% end %>
39
41
 
40
- <details class="mt-6 rounded-md border border-gray-200 p-4 dark:border-white/10">
41
- <summary class="cursor-pointer text-sm font-medium"><%= t("active_admin.favorites.advanced_macros") %></summary>
42
- <div class="mt-4 space-y-3">
43
- <label class="inline-flex items-center gap-2 text-sm">
44
- <%= check_box_tag "favorite[use_advanced_macros]", "1", bookmark.use_advanced_macros, id: "favorite_use_advanced_macros" %>
45
- <%= t("active_admin.favorites.use_advanced_macros") %>
46
- </label>
47
- <%= text_area_tag "favorite[macros_json]", bookmark.macros_json_for_form,
48
- rows: 10,
49
- class: "w-full font-mono text-xs rounded-md border-gray-300 dark:border-white/20 dark:bg-gray-900",
50
- spellcheck: false %>
51
- <p class="text-xs text-gray-500"><%= t("active_admin.favorites.macros_json_hint") %></p>
42
+ <% if show_advanced %>
43
+ <details class="mt-6 rounded-md border border-gray-200 p-4 dark:border-white/10" <%= "open" if ActiveModel::Type::Boolean.new.cast(bookmark.use_advanced_macros) %>>
44
+ <summary class="cursor-pointer text-sm font-medium"><%= t("active_admin.favorites.advanced_macros") %></summary>
45
+ <div class="mt-4 space-y-3">
46
+ <label class="inline-flex items-center gap-2 text-sm">
47
+ <%= check_box_tag "favorite[use_advanced_macros]", "1", bookmark.use_advanced_macros,
48
+ id: "favorite_use_advanced_macros",
49
+ data: {action: "change->activeadmin-favorites--macro-fields#toggle"} %>
50
+ <%= t("active_admin.favorites.use_advanced_macros") %>
51
+ </label>
52
+ <p class="text-xs text-gray-500"><%= t("active_admin.favorites.use_advanced_macros_hint") %></p>
53
+ <%= text_area_tag "favorite[macros_json]", bookmark.macros_json_for_form,
54
+ rows: 10,
55
+ id: "favorite_macros_json",
56
+ class: "w-full font-mono text-xs rounded-md border-gray-300 dark:border-white/20 dark:bg-gray-900",
57
+ spellcheck: false,
58
+ hidden: !ActiveModel::Type::Boolean.new.cast(bookmark.use_advanced_macros),
59
+ disabled: !ActiveModel::Type::Boolean.new.cast(bookmark.use_advanced_macros),
60
+ data: {"activeadmin-favorites--macro-fields-target": "jsonField"} %>
61
+ <p class="text-xs text-gray-500" data-activeadmin-favorites--macro-fields-target="hint" <%= "hidden" unless ActiveModel::Type::Boolean.new.cast(bookmark.use_advanced_macros) %>>
62
+ <%= t("active_admin.favorites.macros_json_hint") %>
63
+ </p>
64
+ </div>
65
+ </details>
66
+ <% end %>
52
67
  </div>
53
- </details>
68
+ <% end %>
@@ -19,6 +19,7 @@ en:
19
19
  published: Published for everyone
20
20
  updated: Favorite updated
21
21
  use_advanced_macros: Edit macros as JSON
22
+ use_advanced_macros_hint: When checked, the JSON below replaces template assignments on save.
22
23
  use_dynamic_range: Use dynamic range
23
24
  visibility:
24
25
  mine: Only me
@@ -29,6 +29,8 @@ module ActiveAdmin
29
29
  next unless app.config.respond_to?(:assets)
30
30
 
31
31
  app.config.assets.precompile << "activeadmin_favorites/personalization_controller.js"
32
+ app.config.assets.precompile << "activeadmin_favorites/filter_favorite_controller.js"
33
+ app.config.assets.precompile << "activeadmin_favorites/macro_fields_controller.js"
32
34
  app.config.assets.precompile << "activeadmin_favorites/personalization_menu.css"
33
35
  end
34
36
 
@@ -36,6 +38,10 @@ module ActiveAdmin
36
38
  pin_controller = proc do |importmap|
37
39
  importmap.pin "controllers/activeadmin_favorites/personalization_controller",
38
40
  to: "activeadmin_favorites/personalization_controller.js"
41
+ importmap.pin "controllers/activeadmin_favorites/filter_favorite_controller",
42
+ to: "activeadmin_favorites/filter_favorite_controller.js"
43
+ importmap.pin "controllers/activeadmin_favorites/macro_fields_controller",
44
+ to: "activeadmin_favorites/macro_fields_controller.js"
39
45
  end
40
46
 
41
47
  application_importmap = Rails.application.importmap if Rails.application.respond_to?(:importmap)
@@ -96,7 +96,16 @@ module ActiveAdmin
96
96
  end
97
97
 
98
98
  render partial: "active_admin/favorites/filter_favorite_button",
99
- locals: {label: label, destination: destination, favorited: favorited}
99
+ locals: {
100
+ label: label,
101
+ destination: destination,
102
+ favorited: favorited,
103
+ quick_create_url: quick_create_admin_favorites_path,
104
+ path: request.path,
105
+ query_string: sanitized_query,
106
+ resource_key: active_admin_config.resource_name.route_key,
107
+ action_name: "index"
108
+ }
100
109
  end
101
110
  end
102
111
 
@@ -19,6 +19,36 @@ module ActiveAdmin
19
19
  permit_params :name, :note, :path, :query_string, :resource_key, :published, :macros_json,
20
20
  :use_advanced_macros, :kind, :type, :action, :include_layout, layout: {}
21
21
 
22
+ collection_action :quick_create, method: :post do
23
+ user = current_favorites_user
24
+ path = params.require(:path)
25
+ query_string = Favorite.sanitize_query_string(params[:query_string])
26
+ resource_key = params.require(:resource_key)
27
+ action_name = params[:action_name].presence || "index"
28
+ return_to = query_string.present? ? "#{path}?#{query_string}" : path
29
+
30
+ bookmark = FiltersFavorite.new(
31
+ name: Favorite.suggested_name(
32
+ resource_key: resource_key,
33
+ query_string: query_string,
34
+ kind: "filters"
35
+ ),
36
+ path: path,
37
+ query_string: query_string,
38
+ resource_key: resource_key,
39
+ action: action_name,
40
+ published: false
41
+ )
42
+ bookmark.public_send(:"#{ActiveAdmin::Favorites.config.user_foreign_key}=", user.id)
43
+ authorize! ActiveAdmin::Auth::CREATE, bookmark
44
+
45
+ if bookmark.save
46
+ redirect_to return_to, notice: I18n.t("active_admin.favorites.created")
47
+ else
48
+ redirect_to return_to, alert: bookmark.errors.full_messages.to_sentence
49
+ end
50
+ end
51
+
22
52
  controller do
23
53
  helper_method :current_favorites_user, :favorites_kind_tab_active?
24
54
 
@@ -37,7 +67,8 @@ module ActiveAdmin
37
67
  get_resource_ivar || begin
38
68
  kind = params.dig(:favorite, :kind).presence || params[:kind].presence || "filters"
39
69
  record = Favorite.sti_class_for_kind(kind).new
40
- record = assign_attributes(record, resource_params)
70
+ assign_new_favorite_defaults(record)
71
+ record = assign_attributes(record, resource_params) if params[:favorite].present?
41
72
  authorize_resource!(record)
42
73
  set_resource_ivar(record)
43
74
  end
@@ -236,6 +267,7 @@ module ActiveAdmin
236
267
  f.input :name, input_html: {autofocus: true}
237
268
  f.input :note, as: :text, input_html: {rows: 3}
238
269
  f.input :published, as: :boolean,
270
+ required: false,
239
271
  label: I18n.t("active_admin.favorites.published")
240
272
  if f.object.new_record?
241
273
  f.input :path, as: :hidden
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ActiveAdmin
4
4
  module Favorites
5
- VERSION = "0.1.9"
5
+ VERSION = "0.1.10"
6
6
  end
7
7
  end
@@ -18,6 +18,7 @@ en:
18
18
  published: Published for everyone
19
19
  updated: Favorite updated
20
20
  use_advanced_macros: Edit macros as JSON
21
+ use_advanced_macros_hint: When checked, the JSON below replaces template assignments on save.
21
22
  use_dynamic_range: Use dynamic range
22
23
  visibility:
23
24
  mine: Only me
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activeadmin_favorites
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrei Makarov
@@ -339,6 +339,8 @@ files:
339
339
  - README.md
340
340
  - SECURITY.md
341
341
  - activeadmin_favorites.gemspec
342
+ - app/assets/controllers/activeadmin_favorites/filter_favorite_controller.js
343
+ - app/assets/controllers/activeadmin_favorites/macro_fields_controller.js
342
344
  - app/assets/controllers/activeadmin_favorites/personalization_controller.js
343
345
  - app/assets/stylesheets/activeadmin_favorites/personalization_menu.css
344
346
  - app/models/active_admin/favorites.rb