Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 14 additions & 0 deletions app/assets/stylesheets/components/topics.css
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,20 @@ a.topic-icon {
}
}

.activity-ignore {
display: none;
color: var(--color-text-secondary);
}

.activity-ignore.is-ignored {
display: inline-block;
background-color: var(--color-bg-activity-team);
}

.topic-row:hover .activity-ignore {
display: inline-block;
}

.is-hidden {
display: none !important;
}
Expand Down
76 changes: 69 additions & 7 deletions app/controllers/topics_controller.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
class TopicsController < ApplicationController
include DraftSidebarLoader

before_action :set_topic, only: [ :show, :message_batch, :attachments_sidebar, :patchsets_sidebar, :aware, :read_all, :unread_all, :star, :unstar, :latest_patchset, :summary, :messages ]
before_action :require_authentication, only: [ :aware, :aware_bulk, :aware_all, :read_all, :unread_all, :star, :unstar ]
before_action :set_topic, only: [ :show, :message_batch, :attachments_sidebar, :patchsets_sidebar, :aware, :read_all, :unread_all, :star, :unstar, :ignore, :unignore, :latest_patchset, :summary, :messages ]
before_action :require_authentication, only: [ :aware, :aware_bulk, :aware_all, :read_all, :unread_all, :star, :unstar, :ignore, :unignore ]

def index
@search_query = nil
Expand All @@ -13,7 +13,17 @@ def index
preload_commitfest_summaries
preload_topic_mailing_lists
@new_topics_count = 0
@page_cache_key = topics_page_cache_key

if user_signed_in?
preload_topic_states
preload_note_counts
preload_participation_flags
preload_star_counts
preload_ignore_states
@page_cache_key = nil
else
@page_cache_key = topics_page_cache_key
end

load_visible_tags if user_signed_in?
load_saved_searches
Expand All @@ -22,10 +32,14 @@ def index
respond_to do |format|
format.html
format.turbo_stream do
body = topics_turbo_stream_cache_fetch do
render_to_string(:index, formats: [ :turbo_stream ])
if user_signed_in?
render :index
else
body = topics_turbo_stream_cache_fetch do
render_to_string(:index, formats: [ :turbo_stream ])
end
render body:, content_type: "text/vnd.turbo-stream.html"
end
render body:, content_type: "text/vnd.turbo-stream.html"
end
end
end
Expand Down Expand Up @@ -235,6 +249,30 @@ def unstar
end
end

def ignore
TopicIgnore.find_or_create_by!(user: current_user, topic: @topic)
respond_to do |format|
format.turbo_stream { render :update_ignore_state }
format.json { render json: { ignored: true } }
format.html { redirect_to topic_path(@topic) }
end
rescue ActiveRecord::RecordNotUnique
respond_to do |format|
format.turbo_stream { render :update_ignore_state }
format.json { render json: { ignored: true } }
format.html { redirect_to topic_path(@topic) }
end
end

def unignore
TopicIgnore.where(user: current_user, topic: @topic).destroy_all
respond_to do |format|
format.turbo_stream { render :update_ignore_state }
format.json { render json: { ignored: false } }
format.html { redirect_to topic_path(@topic) }
end
end

def latest_patchset
latest_message = latest_patchset_message
return head :not_found unless latest_message
Expand Down Expand Up @@ -384,7 +422,13 @@ def search
preload_topic_participants
preload_commitfest_summaries
preload_topic_mailing_lists
preload_participation_flags if user_signed_in?
if user_signed_in?
preload_topic_states
preload_note_counts
preload_participation_flags
preload_star_counts
preload_ignore_states
end
load_visible_tags if user_signed_in?
load_saved_searches
@all_mailing_lists = MailingList.order(:display_name)
Expand Down Expand Up @@ -448,6 +492,7 @@ def user_state_frame
preload_participation_flags
preload_commitfest_summaries
preload_star_counts
preload_ignore_states
preload_topic_participants
preload_topic_mailing_lists

Expand Down Expand Up @@ -646,6 +691,15 @@ def apply_cursor_pagination(base_query)
.having("MAX(messages.created_at) <= ?", @viewing_since)
.select("topics.*, MAX(messages.created_at) as last_activity")

if user_signed_in?
user_id = current_user.id
windowed_query = windowed_query.where(
"topics.id NOT IN (SELECT topic_id FROM topic_ignores WHERE user_id = ?) " \
"OR topics.id IN (SELECT topic_id FROM topic_stars WHERE user_id = ?)",
user_id, user_id
)
end

if params[:cursor].present?
cursor_time, cursor_id = params[:cursor].split("_")
@topics = windowed_query.having("(MAX(messages.created_at), topics.id) < (?, ?)",
Expand Down Expand Up @@ -747,6 +801,14 @@ def preload_star_counts
end
end

def preload_ignore_states
topic_ids = @topics.map(&:id)
return unless user_signed_in? && topic_ids.any?

ignored_ids = TopicIgnore.where(user: current_user, topic_id: topic_ids).pluck(:topic_id).to_set
@topic_ignore_data = topic_ids.index_with { |id| ignored_ids.include?(id) }
end

def load_visible_tags
@available_note_tags = NoteTag.joins(:note)
.merge(Note.active.visible_to(current_user))
Expand Down
18 changes: 18 additions & 0 deletions app/helpers/topics_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,24 @@ def star_icon_html(topic:, star_data:)
end
end

def ignore_icon_html(topic:, ignored:)
ignored = ignored || false
path = ignored ? unignore_topic_path(topic) : ignore_topic_path(topic)
method = ignored ? :delete : :post
icon_class = ignored ? "fa-solid fa-eye-slash" : "fa-regular fa-eye-slash"
classes = ["topic-icon", "activity-ignore"]
classes << "is-ignored" if ignored

link_to path,
method: method,
data: { turbo_method: method, turbo_stream: true },
class: classes.join(" "),
title: ignored ? "Unignore" : "Ignore",
id: dom_id(topic, "ignore_button") do
tag.i(class: icon_class)
end
end

# Replaces app/views/topics/_participation_icon.html.slim
def participation_icon_html(topic:, participation:)
participation = participation || {}
Expand Down
21 changes: 21 additions & 0 deletions app/models/topic_ignore.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
class TopicIgnore < ApplicationRecord
belongs_to :user
belongs_to :topic

validates :user_id, uniqueness: { scope: :topic_id }

def self.toggle_ignore(user:, topic:)
existing = find_by(user: user, topic: topic)
if existing
existing.destroy
false
else
create!(user: user, topic: topic)
true
end
end

def self.ignored_by_user?(user:, topic:)
exists?(user: user, topic: topic)
end
end
45 changes: 45 additions & 0 deletions app/services/search/query_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ def build
return Result.new(relation: Topic.none, warnings: []) if @ast.nil?

relation = apply_node(@ast, Topic.all)

if @user && !has_ignored_selector?(@ast)
relation = apply_default_ignore_filter(relation)
end

Result.new(relation: relation, warnings: @warnings)
end

Expand Down Expand Up @@ -99,6 +104,8 @@ def apply_selector(node, relation)
apply_new_selector(value, relation, negated: negated)
when :starred
apply_starred_selector(value, relation, negated: negated)
when :ignored
apply_ignored_selector(value, relation, negated: negated)
when :notes
apply_notes_selector(value, relation, negated: negated)
when :tag
Expand Down Expand Up @@ -579,6 +586,44 @@ def apply_starred_selector(value, relation, negated:)
end
end

def apply_ignored_selector(value, relation, negated:)
result = @value_resolver.resolve_state_subject(value)
@warnings.concat(result.warnings)

user_ids = result.user_ids
return relation if user_ids.empty?

ignored_topic_ids = TopicIgnore.where(user_id: user_ids).select(:topic_id)

if negated
relation.where.not(id: ignored_topic_ids)
else
relation.where(id: ignored_topic_ids)
end
end

def apply_default_ignore_filter(relation)
user_id = @user.id
relation.where(
"topics.id NOT IN (SELECT topic_id FROM topic_ignores WHERE user_id = ?) " \
"OR topics.id IN (SELECT topic_id FROM topic_stars WHERE user_id = ?)",
user_id, user_id
)
end

def has_ignored_selector?(node)
return false if node.nil?

case node[:type]
when :selector
node[:key] == :ignored
when :and, :or
node[:children].any? { |child| has_ignored_selector?(child) }
else
false
end
end

def apply_notes_selector(value, relation, negated:)
result = @value_resolver.resolve_state_subject(value)
@warnings.concat(result.warnings)
Expand Down
2 changes: 1 addition & 1 deletion app/services/search/query_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class Grammar < Parslet::Parser
str("title") | str("body") |
str("contributors") | str("participants") | str("messages") |
str("unread") | str("reading") | str("read") | str("new") |
str("starred") | str("notes") | str("tag") |
str("starred") | str("ignored") | str("notes") | str("tag") |
str("has") | str("commitfest") | str("list")
).as(:selector_key)
end
Expand Down
2 changes: 1 addition & 1 deletion app/services/search/query_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class QueryValidator

AUTHOR_SELECTORS = %i[from starter last_from].freeze

STATE_SELECTORS = %i[unread read reading new starred notes].freeze
STATE_SELECTORS = %i[unread read reading new starred ignored notes].freeze

CONTENT_SELECTORS = %i[title body].freeze

Expand Down
2 changes: 2 additions & 0 deletions app/views/topics/_status_cell.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
- reading_unread_count = [total_count - read_count, 0].max
- status_class = "status-#{status}"
- status_class = "#{status_class} has-new-replies" if status.to_s == "reading"
- ignored = local_assigns[:ignored] || false
- star_data = star_data || {}
- icons_html = capture do
- if status.to_s == "reading"
= link_to topic_path(topic, anchor: "first-unread"), class: "topic-icon topic-icon-reading", title: "Jump to first unread message (#{reading_unread_count} unread)" do
i.fa-solid.fa-envelope
span.topic-icon-badge.topic-icon-badge-sup = reading_unread_count
= star_icon_html(topic: topic, star_data: star_data)
= ignore_icon_html(topic: topic, ignored: ignored)
= note_icon_html(topic: topic, count: note_count.to_i)
= team_readers_icon_html(topic: topic, readers: team_readers)
- commitfest_summary = @commitfest_summaries&.dig(topic.id)
Expand Down
3 changes: 2 additions & 1 deletion app/views/topics/_topic_row_user.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
- participation = participation || {}
- team_readers = team_readers || []
- star_data = star_data || {}
- ignored = local_assigns[:ignored] || false
- status = state[:status] || "new"
- row_class = ["topic-row", "topic-#{status}"]
- row_class << "has-new-replies" if status.to_s == "reading"
Expand All @@ -12,7 +13,7 @@
- contributor_participants = tp_data[:contributors] || []

tr id=dom_id(topic) class=row_class.join(" ") data-topic-id=topic.id data-last-message-id=topic.last_message_id
= render partial: "topics/status_cell", locals: { topic: topic, state: state, note_count: note_count, team_readers: team_readers, star_data: star_data }
= render partial: "topics/status_cell", locals: { topic: topic, state: state, note_count: note_count, team_readers: team_readers, star_data: star_data, ignored: ignored }
td.topic-mailing-lists data-label="Mailing Lists"
- topic_lists = @topic_mailing_lists_map&.dig(topic.id) || []
- if topic_lists.any?
Expand Down
21 changes: 21 additions & 0 deletions app/views/topics/_topics_authed.html.slim
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
- @topic_states ||= {}
- @topic_note_counts ||= {}
- @participation_flags ||= {}
- @topic_star_data ||= {}
- @topic_ignore_data ||= {}

- if topics.empty?
tr.topic-empty-row
td colspan=3
.topics-empty-state
i.fa-regular.fa-folder-open aria-hidden="true"
p No topics found.

- topics.each do |topic|
- state = @topic_states[topic.id] || {}
- note_count = @topic_note_counts&.dig(topic.id).to_i
- participation = @participation_flags&.dig(topic.id) || {}
- team_readers = state[:team_readers] || []
- star_data = @topic_star_data&.dig(topic.id) || {}
- ignored = @topic_ignore_data[topic.id] || false
= render partial: "topics/topic_row_user", locals: { topic: topic, state: state, note_count: note_count, participation: participation, team_readers: team_readers, star_data: star_data, ignored: ignored }
12 changes: 7 additions & 5 deletions app/views/topics/index.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
th.activity-header Activity
th.participants-header Participants
tbody#topics
= render partial: "topics", locals: { topics: @topics }
- if user_signed_in?
= render partial: "topics/topics_authed", locals: { topics: @topics }
- else
= render partial: "topics", locals: { topics: @topics }

- if @topics.size == 25
- last_topic = @topics.last
Expand All @@ -23,10 +26,9 @@
= turbo_frame_tag "pagination"

- if user_signed_in?
#user-state-requests
= turbo_frame_tag "user-state-root", src: user_state_frame_topics_path(topic_ids: @topics.map(&:id), format: :turbo_stream), loading: :eager

- if @page_cache_key
= render partial: "sidebar", locals: { available_note_tags: @available_note_tags, search_query: @search_query }
- cache_block.call
- elsif @page_cache_key
= render partial: "sidebar", locals: { available_note_tags: @available_note_tags, search_query: @search_query }
- cache(@page_cache_key) do
- cache_block.call
Expand Down
10 changes: 6 additions & 4 deletions app/views/topics/index.turbo_stream.slim
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
= turbo_stream.append "topics" do
= render partial: "topics", locals: { topics: @topics }

- if user_signed_in?
- frame_id = params[:cursor].present? ? "user-state-#{params[:cursor]}" : "user-state-root"
= turbo_stream.append "topics" do
= render partial: "topics/topics_authed", locals: { topics: @topics }
- else
= turbo_stream.append "topics" do
= render partial: "topics", locals: { topics: @topics }
= turbo_stream.append "user-state-requests" do
- frame_id = params[:cursor].present? ? "user-state-#{params[:cursor]}" : "user-state-root"
= turbo_frame_tag frame_id, src: user_state_frame_topics_path(topic_ids: @topics.map(&:id), format: :turbo_stream), loading: :eager

- if @topics.size == 25
Expand Down
Loading
Loading