Skip to content
Open
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
b3246cd
AO3-4666
ReverM Nov 18, 2025
aae1900
Added filter to user collection pages
ReverM Nov 27, 2025
c202951
Added test
ReverM Nov 27, 2025
960ef97
Added missing definition for test
ReverM Nov 27, 2025
f512be0
Revert "AO3-4666"
ReverM Nov 27, 2025
466ffa6
Added missing s to test
ReverM Nov 27, 2025
cdad56d
Removed test and cleaned things up 1/2
ReverM Nov 28, 2025
89eae21
Clean up 2/2
ReverM Nov 28, 2025
23a987b
Merge branch 'otwcode:master' into AO3-7143
ReverM Nov 28, 2025
a00bc74
Added test and fixed style
ReverM Dec 1, 2025
322e4bc
Missed an s
ReverM Dec 1, 2025
9e610be
Adressed rubocop for tests
ReverM Dec 2, 2025
c98d152
Adressed comments
ReverM Apr 14, 2026
2ad01c0
Removed newline so file is untouched by pr
ReverM Apr 14, 2026
6eb7ad8
Added filter to user collection pages
ReverM Nov 27, 2025
30e34b7
Added test
ReverM Nov 27, 2025
42163e5
Added missing definition for test
ReverM Nov 27, 2025
182f9fa
Added missing s to test
ReverM Nov 27, 2025
84bfc54
Removed test and cleaned things up 1/2
ReverM Nov 28, 2025
7b65fe4
Clean up 2/2
ReverM Nov 28, 2025
07776bb
Added test and fixed style
ReverM Dec 1, 2025
d2a6a0d
Missed an s
ReverM Dec 1, 2025
ec61299
Adressed rubocop for tests
ReverM Dec 2, 2025
1fef682
Adressed comments
ReverM Apr 14, 2026
d16b8d6
Removed newline so file is untouched by pr
ReverM Apr 14, 2026
2da1469
Adress comments
ReverM May 5, 2026
b180115
Better tests
ReverM May 5, 2026
67f0718
Merge branch 'AO3-7143' of https://github.com/ReverM/otwarchive into …
ReverM May 5, 2026
f9a5551
Styling
ReverM May 5, 2026
7eaedbe
Adressed PR comments
ReverM May 12, 2026
4a4036f
Making test not fail, hopefully
ReverM May 12, 2026
f2393fd
Properly addressed failing tests
ReverM May 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions app/controllers/collections_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ def index
flash_search_warnings(@collections)
@page_subtitle = t(".subcollections_page_title", collection_title: @collection.title)
elsif params[:user_id]
@sort_and_filter = true
@user = User.find_by!(login: params[:user_id])
@search = CollectionSearchForm.new({ maintainer_id: @user.id, sort_column: "title.keyword" }.merge(page: params[:page]))
@search = CollectionSearchForm.new(collection_filter_params.merge({ maintainer_id: @user.id }.merge(page: params[:page])))
@collections = @search.search_results.scope(:for_search)
flash_search_warnings(@collections)
@page_subtitle = ts("%{username} - Collections", username: @user.login)
Expand Down Expand Up @@ -194,7 +195,7 @@ def destroy
private

def collection_filter_params
params.permit(:commit, collection_search: [
params.permit(:commit, :user_id, collection_search: [
:title, :challenge_type, :moderated, :multifandom, :closed, :tag,
:sort_column, :sort_direction
])[:collection_search] || {}
Expand Down
13 changes: 11 additions & 2 deletions app/helpers/search_helper.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
module SearchHelper

# modified from mislav-will_paginate-2.3.2/lib/will_paginate/view_helpers.rb
def search_header(collection, search, item_name, parent=nil)
def search_header(collection, search, item_name, parent = nil)
header = []
if !collection.respond_to?(:total_pages)
header << ts("Recent #{item_name.pluralize}")
Expand Down Expand Up @@ -53,6 +52,16 @@ def works_original_path
)
end

def collections_original_path
url_for(
controller: :collections,
action: :index,
only_path: true,
**params.slice(:title, :challenge_type, :moderated, :multifandom, :closed, :tag,
:sort_column, :sort_direction).permit!
)
end

def bookmarks_original_path
url_for(
controller: :bookmarks,
Expand Down
10 changes: 8 additions & 2 deletions app/views/collections/_filters.html.erb
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
<%= form_for @search, as: :collection_search, url: collections_path, html: { method: :get, class: "narrow-hidden filters", id: "collection-filters" } do |f| %>
<%= form_for @search, as: :collection_search,
url: (@user ? user_collections_path(@user) : collections_path),
html: {
method: :get,
class: "narrow-hidden filters",
id: "collection-filters"
} do |f| %>
<h3 class="landmark heading"><%= t(".landmark") %></h3>
<%= field_set_tag t(".legend") do %>
<dl>
Expand Down Expand Up @@ -133,7 +139,7 @@
<dd class="submit actions"><%= submit_tag t(".submit.button") %></dd>
</dl>
<p class="footnote">
<%= link_to t(".clear_filters"), collections_path %>
<%= link_to t(".clear_filters"), collections_original_path %>
</p>
<% end %>
<% # On narrow screens, link jumps to top of index when JavaScript is disabled and closes filters when JavaScript is enabled %>
Expand Down
56 changes: 29 additions & 27 deletions app/views/collections/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -13,47 +13,49 @@

<h3 class="heading">
<% if @collections.empty? %>
<%= ts("Sorry, there were no collections found.") %>
<%= t(".page_heading.no_results") %>
<% else %>
<%= search_header @collections, @query, ts("Collection") %>
<%= search_header @collections, @query, t(".page_heading.search_header") %>
<% end %>
</h3>
<!--/descriptions-->

<!--Subnavigation, sorting and actions-->
<h3 class="landmark heading"><%= ts("Navigation") %></h3>
<ul class="navigation actions" role="navigation">
<% # Collections and Open Challenges links unless a logged in user viewing own collections page %>
<% unless logged_in? && @user && @user == current_user %>
<li><%= span_if_current ts("Collections"), collections_path %></li>
<li><%= link_to ts("Open Challenges"), list_challenges_collections_path %></li>
<% end %>
<% if logged_in? %>
<% # Logged in user on own collections index gets links for user Collections and Manage Collection Items %>
<% if @user && @user == current_user %>
<li><%= span_if_current ts("Collections"), user_collections_path(@user) %></li>
<li><%= link_to ts("Manage Collection Items"), user_collection_items_path(@user) %></li>
<div class="navigation actions module">
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only put our subnavigation in div like this in rare instances where we have complex navigation with multiple separate sets of actions, like two lists, or a list and a form outside the list. The div on works/index.html.erb is there because we can have both a form and a list... but I think it's wrong, actually, because they shouldn't be there at the same time.

I'm pretty sure the reason you've added this is to make the navigation take up the full width of #main and push the filters into the right position on the page. Interestingly, the bookmarks index doesn't have this div, nor does it have that problem -- and after some investigating, I figured out why. Both bookmark and work index pages leave the empty ol.index on the page, while the collection index removes it with the unless @collections.blank? logic on line 52. Empty list elements aren't great for accessibility, so we don't want to replicate that here, either.

I think the solution we need here is to add a clear to form.filters. That should keep the filters in the right position without either the empty list or the div.

Copy link
Copy Markdown
Contributor Author

@ReverM ReverM May 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that stylesheet 17 is taking priority over stylesheet 18. Regardless of if the index has filtered or not. The width: 100% seems to be the cause of the issue in that case? It is defined to be 75% in sheet 18 within line 9 but the one in style sheet 17 is applied first. Adding !important to the one in sheet 18 seems to fix it? But I wasn't sure if such a solution was desired.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which page and device/browser, and which width: 100%? After actioning this comment, the only width: 100% I can find in 17 is .dashboard.works-index h4.landmark, .dashboard.works-index ol.pagination, and I'm not running into issues on either the user collection index or work index, but I've only checked in desktop Safari.

0c99f89 has the changes I'm testing with.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see, I got confused and reintroduced the class in page 17.

<h3 class="landmark heading"><%= t(".navigation.landmark_heading") %></h3>
<ul class="navigation actions" role="navigation">
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove the role="navigation" while you're here -- it's not valid on list elements.

<%# Collections and Open Challenges links unless a logged in user viewing own collections page %>
<% unless logged_in? && @user && @user == current_user %>
<li><%= span_if_current t(".navigation.actions.collections"), collections_path %></li>
<li><%= link_to t(".navigation.actions.open_challenges"), list_challenges_collections_path %></li>
<% end %>
<% # Logged in collection maintainer on own collection gets link for New Subcollection and all other logged in users get New Collection link %>
<% if @collection && !@collection.parent && @collection.user_is_maintainer?(current_user) %>
<li><%= link_to ts("New Subcollection"), new_collection_collection_path(@collection) %></li>
<% else %>
<li><%= link_to ts("New Collection"), new_collection_path %></li>
<% if logged_in? %>
<%# Logged in user on own collections index gets links for user Collections and Manage Collection Items %>
<% if @user && @user == current_user %>
<li><%= span_if_current t(".navigation.actions.collections"), user_collections_path(@user) %></li>
<li><%= link_to t(".navigation.actions.manage_items"), user_collection_items_path(@user) %></li>
<% end %>
<%# Logged in collection maintainer on own collection gets link for New Subcollection and all other logged in users get New Collection link %>
<% if @collection && !@collection.parent && @collection.user_is_maintainer?(current_user) %>
<li><%= link_to t(".navigation.actions.new_subcollection"), new_collection_collection_path(@collection) %></li>
<% else %>
<li><%= link_to t(".navigation.actions.new_collection"), new_collection_path %></li>
<% end %>
<% end %>
<% end %>
<% if @sort_and_filter %>
<% # Filters button for narrow screens jumps to filters when JavaScript is disabled and opens filters when JavaScript is enabled %>
<li class="narrow-shown hidden"><a href="#collection-filters" id="go_to_filters"><%= ts("Filters") %></a></li>
<% end %>
</ul>
<% if @sort_and_filter %>
<%# Filters button for narrow screens jumps to filters when JavaScript is disabled and opens filters when JavaScript is enabled %>
<li class="narrow-shown hidden"><a href="#collection-filters" id="go_to_filters"><%= t(".navigation.actions.filters") %></a></li>
<% end %>
</ul>
</div>

<% unless @collections.blank? %>
<!--pagination here-->
<%= will_paginate @collections %>

<!--main content-->
<h3 class="landmark heading"><%= ts("List of Collections") %></h3>
<ul class="collection picture index group">
<h3 class="landmark heading"><%= t(".main_content.landmark_heading") %></h3>
<ul class="collection picture index group <%= "no-filter" unless @sort_and_filter %>">
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of adding a class, let's take a look at what's causing the problem here. (But just a quick note for the future that we have some rules for adding classes.)

It looks like all collection index pages get the filtered class, regardless of whether they have filters, thanks to this:

def page_has_filters?
@facets.present? || (controller.action_name == 'index' && controller.controller_name == 'collections') || (controller.action_name == 'unassigned' && controller.controller_name == 'fandoms')
end

def classes_for_main
class_names = controller.controller_name + '-' + controller.action_name
show_sidebar = (!@hide_dashboard && (@user || @admin_posts || @collection || show_wrangling_dashboard))
class_names += " dashboard" if show_sidebar
class_names += " filtered" if page_has_filters?

Unfortunately, collections don't use @facets, so we can't just remove the collections logic from page_has_filters?. But we can tweak it to (controller.controller_name == "collections" && @sort_and_filter) or even just @sort_and_filter, since collections are the only place we use that variable, and I think it's reasonable to reserve it so it's only ever used to determine whether we display filters. #5721 also uses that variable for filtered challenge pages.

As a bonus, this approach should also fix the issue where /works/:id/collections pages

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to find the root cause but couldn't quite find it. Thanks!

<% @collections.each do |collection| %>
<%= render :partial => "collection_blurb", :locals => {:collection => collection} %>
<% end %>
Expand Down
13 changes: 13 additions & 0 deletions config/locales/views/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -821,10 +821,23 @@ en:
footnote: Use this if your collection is not fandom-specific.
label: Multifandom
index:
main_content:
landmark_heading: List of Collections
navigation:
actions:
collections: Collections
filters: Filters
manage_items: Manage Collection Items
new_collection: New Collection
new_subcollection: New Subcollection
open_challenges: Open Challenges
landmark_heading: Navigation
page_heading:
challenges_subcollections_in: Challenges/Subcollections in %{collection}
collections_in_the: Collections in the %{archive}
collections_including: Collections including %{work}
no_results: Sorry, there were no collections found.
search_header: Collection
users_collections: "%{user}'s Collections"
sidebar:
bookmarks: Bookmarked Items (%{count})
Expand Down
68 changes: 68 additions & 0 deletions features/collections/collection_browse.feature
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,74 @@ Feature: Collection
And the 2nd collection result should contain "Privates"
And the 2nd collection result should contain "Bookmarked Items: 1"

Scenario: Sort collections by Works and Bookmarks from user's collection

Given I have a collection "Privates"
And I have a collection "Publics"
And a set of collections for searching
When I go to the collections page
Then I should not see "2 Collections"
When I am logged in as the owner of "Privates"
And I post the work "Private 1" in the collection "Privates"
And I lock the work "Private 1"
And I post the work "Private 2" in the collection "Privates"
And I lock the work "Private 2"
And I bookmark the work "Private 1" to the collection "Publics"
And I bookmark the work "Private 2" to the collection "Publics"
And I post the work "Public 1" in the collection "Publics"
And I bookmark the work "Public 1" to the collection "Privates"
And all indexing jobs have been run
And I change my username to "Owner"
And I go to Owner's collections page
And I select "Works" from "Sort by"
And I press "Sort and Filter"
Then the 1st collection result should contain "Privates"
And the 1st collection result should contain "Works: 2"
And the 2nd collection result should contain "Publics"
And the 2nd collection result should contain "Works: 1"
And I should see "2 Collections"
When I log out
Then the 1st collection result should contain "Publics"
And the 1st collection result should contain "Works: 1"
And the 2nd collection result should contain "Privates"
And the 2st collection result should contain "Works: 0"
And I should see "2 Collections"
When I am logged in as a super admin
And I go to Owner's collections page
And I select "Works" from "Sort by"
And I press "Sort and Filter"
Then the 1st collection result should contain "Privates"
And the 1st collection result should contain "Works: 2"
And the 2nd collection result should contain "Publics"
And the 2nd collection result should contain "Works: 1"
And I should see "2 Collections"
When I go to Owner's collections page
And I select "Bookmarked Items" from "Sort by"
And I press "Sort and Filter"
Then the 1st collection result should contain "Publics"
And the 1st collection result should contain "Bookmarked Items: 2"
And the 2nd collection result should contain "Privates"
And the 2nd collection result should contain "Bookmarked Items: 1"
And I should see "2 Collections"
When I log out
And I go to Owner's collections page
And I select "Bookmarked Items" from "Sort by"
And I press "Sort and Filter"
Then the 1nd collection result should contain "Privates"
And the 1st collection result should contain "Bookmarked Items: 1"
And the 2nd collection result should contain "Publics"
And the 2nd collection result should not contain "Bookmarked Items"
And I should see "2 Collections"
When I am logged in as a super admin
And I go to Owner's collections page
And I select "Bookmarked Items" from "collection_search_sort_column"
And I press "Sort and Filter"
Then the 1st collection result should contain "Publics"
And the 1st collection result should contain "Bookmarked Items: 2"
And the 2nd collection result should contain "Privates"
And the 2nd collection result should contain "Bookmarked Items: 1"
And I should see "2 Collections"

Scenario: Look at a collection, see the rules and intro and FAQ

Given a set of collections for searching
Expand Down
22 changes: 20 additions & 2 deletions features/search/filters.feature
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
@users
Feature: Filters
In order to ensure filtering works on works and bookmarks
In order to ensure filtering works on works, bookmarks and collections
As a humble user
I want to filter on a user's works and bookmarks
I want to filter on a user's works, bookmarks and collections

Background:
Given a canonical fandom "The Hobbit"
Expand Down Expand Up @@ -363,3 +363,21 @@ Feature: Filters
When I fill in "work_search_query" with "bad~query!!!"
And I press "Sort and Filter"
Then I should see "Your search failed because of a syntax error"

@collections
Scenario: Filtering on a user collection page should only return collections by that user.
Given a collection "Duplicate Name" with name "collection1" owned by "meatloaf"
And a collection "Duplicate Name" with name "collection2" owned by "recengine"
And a collection "The Hobbits" with name "collectionhobbit" owned by "recengine"
And a collection "Not Mine!" with name "yours" owned by "iminvisible"
And all indexing jobs have been run
When I go to recengine's collections page
And I fill in "Filter by title" with "Duplicate Name"
And I press "Sort and Filter"
Then I should see "1 Collection"
When I follow "Clear Filters"
Then I should see "2 Collections"
When I fill in "Filter by title" with "Not Mine!"
And I press "Sort and Filter"
Then I should not see "iminvisible"
And I should see "Sorry, there were no collections found."
17 changes: 10 additions & 7 deletions features/step_definitions/collection_steps.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@
collection.collection_preference.update_attribute(:closed, true) if closed.present?
end

Given "a collection {string} with name {string} owned by {string}" do |title, name, owner|
user = ensure_user(owner)
FactoryBot.create(:collection, title: title, name: (name.presence || title.gsub(/[^\w]/, "_")), owner: user.default_pseud)
end

Given /^I open the collection with the title "([^\"]*)"$/ do |title|
step %{I am logged in as "moderator"}
visit collection_path(Collection.find_by(title: title))
Expand All @@ -107,7 +112,7 @@
visit collection_path(Collection.find_by(title: title))
click_link("Membership")
step %{I fill in "participants_to_invite" with "#{name}"}
step %{I press "Submit"}
step %{I press "Submit"}

step %{I select "Moderator" from "#{name}_role"}
# TODO: fix the form, it is malformed right now
Expand Down Expand Up @@ -178,21 +183,19 @@

When /^I set up (?:a|the) collection "([^"]*)"(?: with name "([^"]*)")?$/ do |title, name|
visit new_collection_path
fill_in("collection_name", with: (name.blank? ? title.gsub(/[^\w]/, '_') : name))
fill_in("collection_name", with: (name.presence || title.gsub(/[^\w]/, "_")))
fill_in("collection_title", with: title)
end

When /^I create (?:a|the) collection "([^"]*)"(?: with name "([^"]*)")?$/ do |title, name|
name = title.gsub(/[^\w]/, '_') if name.blank?
name = title.gsub(/[^\w]/, "_") if name.blank?
step %{I set up the collection "#{title}" with name "#{name}"}
step %{I submit}
end

When /^I add (?:a|the) subcollection "([^"]*)"(?: with name "([^"]*)")? to (?:a|the) parent collection named "([^"]*)"$/ do |title, name, parent_name|
if Collection.find_by_name(parent_name).nil?
step %{I create the collection "#{parent_name}" with name "#{parent_name}"}
end
name = title.gsub(/[^\w]/, '_') if name.blank?
step %{I create the collection "#{parent_name}" with name "#{parent_name}"} if Collection.find_by_name(parent_name).nil?
name = title.gsub(/[^\w]/, "_") if name.blank?
step %{I set up the collection "#{title}" with name "#{name}"}
fill_in("collection_parent_name", with: parent_name)
step %{I submit}
Expand Down
2 changes: 2 additions & 0 deletions features/support/paths.rb
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ def path_to(page_name)
edit_skin_path(Skin.find_by(title: $1), wizard: true)
when /^the new collection page/
new_collection_path
when /^(.*?)(?:'s)? collections page$/i
user_collections_path(user_id: Regexp.last_match(1))
when /^"(.*)" collection's page$/i # e.g. when I go to "Collection name" collection's page
step %{all indexing jobs have been run} # reindex to show recent works/bookmarks
collection_path(Collection.find_by(title: $1))
Expand Down
2 changes: 1 addition & 1 deletion public/stylesheets/site/2.0/17-zone-home.css
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ user home, collections and challenges home, tag home, admin comms home

/* (no filters) */

.dashboard.collections-index .index {
.collections-index .index.no-filter {
width: 100%;
}

Expand Down
Loading