diff --git a/app/assets/stylesheets/components/settings.css b/app/assets/stylesheets/components/settings.css index 864037d0..2856ed86 100644 --- a/app/assets/stylesheets/components/settings.css +++ b/app/assets/stylesheets/components/settings.css @@ -313,6 +313,14 @@ background-color: var(--color-bg-hover); } +.settings-page .email-table tbody tr.saved-search-hidden td:not(:last-child) { + opacity: 0.55; +} + +.settings-page .email-table tbody tr.saved-search-hidden td:first-child { + font-style: italic; +} + .settings-page .email-table td:last-child { display: flex; gap: var(--spacing-2); @@ -732,6 +740,12 @@ color: var(--color-text-secondary); } +.settings-page .badge-default { + background: var(--color-primary-100); + color: var(--color-primary-700); + margin-left: var(--spacing-2); +} + .sent-header { margin-bottom: var(--spacing-6); } diff --git a/app/controllers/settings/saved_search_preferences_controller.rb b/app/controllers/settings/saved_search_preferences_controller.rb index 11bc741e..d519ac8e 100644 --- a/app/controllers/settings/saved_search_preferences_controller.rb +++ b/app/controllers/settings/saved_search_preferences_controller.rb @@ -15,5 +15,19 @@ def create redirect_back fallback_location: settings_saved_searches_path, notice: hidden ? "Search hidden" : "Search shown" end + + def set_default + saved_search = SavedSearch.visible_to(current_user).find(params[:saved_search_id]) + default = ActiveModel::Type::Boolean.new.cast(params[:default]) + + pref = SavedSearchPreference.find_or_initialize_by( + saved_search: saved_search, + user: current_user + ) + pref.default = default + pref.save! + + redirect_back fallback_location: settings_saved_searches_path, notice: default ? "Default search set" : "Default search cleared" + end end end diff --git a/app/controllers/settings/saved_searches_controller.rb b/app/controllers/settings/saved_searches_controller.rb index f941cdc3..e21c43e5 100644 --- a/app/controllers/settings/saved_searches_controller.rb +++ b/app/controllers/settings/saved_searches_controller.rb @@ -3,6 +3,7 @@ module Settings class SavedSearchesController < Settings::BaseController before_action :set_saved_search, only: [ :edit, :update, :destroy ] + before_action :set_default_saved_search_id, only: [ :index ] def index @saved_searches = current_user.saved_searches.order(:position, :name) @@ -66,6 +67,10 @@ def set_saved_search @saved_search = current_user.saved_searches.find(params[:id]) end + def set_default_saved_search_id + @default_saved_search_id = current_user.default_saved_search_preference&.saved_search_id + end + def saved_search_params params.require(:saved_search).permit(:name, :query) end diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb index fc9f4361..286fd5ff 100644 --- a/app/controllers/topics_controller.rb +++ b/app/controllers/topics_controller.rb @@ -8,6 +8,11 @@ class TopicsController < ApplicationController ROW_STATES_LIMIT = 50 def index + if user_signed_in? && request.format.html? && params[:cursor].blank? && params[:commit].blank? + default_search = current_user.default_saved_search + return redirect_to search_topics_path(saved_search_id: default_search.id) if default_search + end + @search_query = nil base_query = Topic.includes(*TOPIC_LIST_PRELOADS) base_query = apply_default_ignore_filter(base_query) if user_signed_in? diff --git a/app/models/saved_search_preference.rb b/app/models/saved_search_preference.rb index 79f84232..cb3557fc 100644 --- a/app/models/saved_search_preference.rb +++ b/app/models/saved_search_preference.rb @@ -3,4 +3,12 @@ class SavedSearchPreference < ApplicationRecord belongs_to :user validates :saved_search_id, uniqueness: { scope: :user_id } + + before_save :clear_other_defaults, if: -> { default? && will_save_change_to_default? } + + private + + def clear_other_defaults + self.class.where(user_id: user_id, default: true).where.not(id: id).update_all(default: false) + end end diff --git a/app/models/user.rb b/app/models/user.rb index 15a5aad1..e878f641 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -15,6 +15,8 @@ class User < ApplicationRecord has_many :starred_topics, through: :topic_stars, source: :topic has_many :saved_searches has_many :saved_search_preferences + has_one :default_saved_search_preference, -> { where(default: true) }, class_name: "SavedSearchPreference" + has_one :default_saved_search, through: :default_saved_search_preference, source: :saved_search has_many :outgoing_drafts, dependent: :destroy has_many :user_features, dependent: :destroy diff --git a/app/views/settings/saved_searches/index.html.slim b/app/views/settings/saved_searches/index.html.slim index 733e030f..d9d599fd 100644 --- a/app/views/settings/saved_searches/index.html.slim +++ b/app/views/settings/saved_searches/index.html.slim @@ -18,8 +18,12 @@ th Actions tbody - @saved_searches.each do |ss| + - is_default = @default_saved_search_id == ss.id tr - td = ss.name + td + = ss.name + - if is_default + span.badge.badge-default Default td code = ss.query td @@ -28,6 +32,7 @@ i.fa-solid.fa-ellipsis-vertical .row-actions-menu = link_to "Edit", edit_settings_saved_search_path(ss), class: "row-actions-item" + = button_to is_default ? "Remove default" : "Set as default", set_default_settings_saved_search_preferences_path(saved_search_id: ss.id, default: !is_default), method: :post, class: "row-actions-item" = button_to "Delete", settings_saved_search_path(ss), method: :delete, class: "row-actions-item row-actions-item-danger", data: { turbo_confirm: "Delete this saved search?" } - if @global_searches.any? @@ -39,16 +44,25 @@ tr th Name th Query - th Visible + th Actions tbody - @global_searches.each do |ss| - tr - td = ss.name + - hidden = @hidden_ids.include?(ss.id) + - is_default = @default_saved_search_id == ss.id + tr class=("saved-search-hidden" if hidden) + td + = ss.name + - if is_default + span.badge.badge-default Default td code = ss.query td - - hidden = @hidden_ids.include?(ss.id) - = button_to hidden ? "Show" : "Hide", settings_saved_search_preferences_path(saved_search_id: ss.id, hidden: !hidden), method: :post, class: "button-secondary button-small" + details.row-actions data-controller="nav-dropdown" + summary.row-actions-toggle aria-label="Actions" + i.fa-solid.fa-ellipsis-vertical + .row-actions-menu + = button_to hidden ? "Show" : "Hide", settings_saved_search_preferences_path(saved_search_id: ss.id, hidden: !hidden), method: :post, class: "row-actions-item" + = button_to is_default ? "Remove default" : "Set as default", set_default_settings_saved_search_preferences_path(saved_search_id: ss.id, default: !is_default), method: :post, class: "row-actions-item" - if @system_searches.any? h2 Default Searches @@ -59,13 +73,22 @@ tr th Name th Query - th Visible + th Actions tbody - @system_searches.each do |ss| - tr - td = ss.name + - hidden = @hidden_ids.include?(ss.id) + - is_default = @default_saved_search_id == ss.id + tr class=("saved-search-hidden" if hidden) + td + = ss.name + - if is_default + span.badge.badge-default Default td code = ss.query td - - hidden = @hidden_ids.include?(ss.id) - = button_to hidden ? "Show" : "Hide", settings_saved_search_preferences_path(saved_search_id: ss.id, hidden: !hidden), method: :post, class: "button-secondary button-small" + details.row-actions data-controller="nav-dropdown" + summary.row-actions-toggle aria-label="Actions" + i.fa-solid.fa-ellipsis-vertical + .row-actions-menu + = button_to hidden ? "Show" : "Hide", settings_saved_search_preferences_path(saved_search_id: ss.id, hidden: !hidden), method: :post, class: "row-actions-item" + = button_to is_default ? "Remove default" : "Set as default", set_default_settings_saved_search_preferences_path(saved_search_id: ss.id, default: !is_default), method: :post, class: "row-actions-item" diff --git a/config/routes.rb b/config/routes.rb index c7bb766f..624f03aa 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -61,7 +61,9 @@ resource :import, only: [ :show, :create ] resource :deletion, only: [ :show, :create ] resources :saved_searches - resources :saved_search_preferences, only: [ :create ] + resources :saved_search_preferences, only: [ :create ] do + post :set_default, on: :collection + end resources :teams, only: [ :index, :show, :create, :update, :destroy ] do resources :team_members, only: [ :create, :update, :destroy ] diff --git a/db/migrate/20260819090534_add_default_to_saved_search_preferences.rb b/db/migrate/20260819090534_add_default_to_saved_search_preferences.rb new file mode 100644 index 00000000..12418b7d --- /dev/null +++ b/db/migrate/20260819090534_add_default_to_saved_search_preferences.rb @@ -0,0 +1,10 @@ +class AddDefaultToSavedSearchPreferences < ActiveRecord::Migration[8.0] + def change + add_column :saved_search_preferences, :default, :boolean, default: false, null: false + + add_index :saved_search_preferences, :user_id, + unique: true, + where: '"default" = true', + name: "idx_one_default_saved_search_per_user" + end +end diff --git a/db/schema.rb b/db/schema.rb index 2a97de37..807601e7 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2026_08_18_120000) do +ActiveRecord::Schema[8.0].define(version: 2026_08_19_090534) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" enable_extension "pg_stat_statements" @@ -570,8 +570,10 @@ t.boolean "hidden", default: false, null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.boolean "default", default: false, null: false t.index ["saved_search_id", "user_id"], name: "idx_saved_search_prefs_unique", unique: true t.index ["saved_search_id"], name: "index_saved_search_preferences_on_saved_search_id" + t.index ["user_id"], name: "idx_one_default_saved_search_per_user", unique: true, where: "(\"default\" = true)" t.index ["user_id"], name: "index_saved_search_preferences_on_user_id" end