Skip to content

Commit 0a9ad72

Browse files
authored
Add featured hashtags to profiles (mastodon#9755)
* Add hashtag filter to profiles GET /@:username/tagged/:hashtag GET /api/v1/accounts/:id/statuses?tagged=:hashtag * Display featured hashtags on public profile * Use separate model for featured tags * Update featured hashtag counters on-write * Limit featured tags to 10
1 parent 1a3ba97 commit 0a9ad72

24 files changed

Lines changed: 238 additions & 8 deletions

app/controllers/accounts_controller.rb

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ def show_pinned_statuses?
5757

5858
def filtered_statuses
5959
default_statuses.tap do |statuses|
60+
statuses.merge!(hashtag_scope) if tag_requested?
6061
statuses.merge!(only_media_scope) if media_requested?
6162
statuses.merge!(no_replies_scope) unless replies_requested?
6263
end
@@ -78,12 +79,15 @@ def no_replies_scope
7879
Status.without_replies
7980
end
8081

82+
def hashtag_scope
83+
Status.tagged_with(Tag.find_by(name: params[:tag].downcase)&.id)
84+
end
85+
8186
def set_account
8287
@account = Account.find_local!(params[:username])
8388
end
8489

8590
def older_url
86-
::Rails.logger.info("older: max_id #{@statuses.last.id}, url #{pagination_url(max_id: @statuses.last.id)}")
8791
pagination_url(max_id: @statuses.last.id)
8892
end
8993

@@ -92,7 +96,9 @@ def newer_url
9296
end
9397

9498
def pagination_url(max_id: nil, min_id: nil)
95-
if media_requested?
99+
if tag_requested?
100+
short_account_tag_url(@account, params[:tag], max_id: max_id, min_id: min_id)
101+
elsif media_requested?
96102
short_account_media_url(@account, max_id: max_id, min_id: min_id)
97103
elsif replies_requested?
98104
short_account_with_replies_url(@account, max_id: max_id, min_id: min_id)
@@ -109,6 +115,10 @@ def replies_requested?
109115
request.path.ends_with?('/with_replies')
110116
end
111117

118+
def tag_requested?
119+
request.path.ends_with?("/tagged/#{params[:tag]}")
120+
end
121+
112122
def filtered_status_page(params)
113123
if params[:min_id].present?
114124
filtered_statuses.paginate_by_min_id(PAGE_SIZE, params[:min_id]).reverse

app/controllers/api/v1/accounts/statuses_controller.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def account_statuses
3333
statuses.merge!(only_media_scope) if truthy_param?(:only_media)
3434
statuses.merge!(no_replies_scope) if truthy_param?(:exclude_replies)
3535
statuses.merge!(no_reblogs_scope) if truthy_param?(:exclude_reblogs)
36+
statuses.merge!(hashtag_scope) if params[:tagged].present?
3637

3738
statuses
3839
end
@@ -67,6 +68,10 @@ def no_reblogs_scope
6768
Status.without_reblogs
6869
end
6970

71+
def hashtag_scope
72+
Status.tagged_with(Tag.find_by(name: params[:tagged])&.id)
73+
end
74+
7075
def pagination_params(core_params)
7176
params.slice(:limit, :only_media, :exclude_replies).permit(:limit, :only_media, :exclude_replies).merge(core_params)
7277
end
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# frozen_string_literal: true
2+
3+
class Settings::FeaturedTagsController < Settings::BaseController
4+
layout 'admin'
5+
6+
before_action :authenticate_user!
7+
before_action :set_featured_tags, only: :index
8+
before_action :set_featured_tag, except: [:index, :create]
9+
before_action :set_most_used_tags, only: :index
10+
11+
def index
12+
@featured_tag = FeaturedTag.new
13+
end
14+
15+
def create
16+
@featured_tag = current_account.featured_tags.new(featured_tag_params)
17+
@featured_tag.reset_data
18+
19+
if @featured_tag.save
20+
redirect_to settings_featured_tags_path
21+
else
22+
set_featured_tags
23+
set_most_used_tags
24+
25+
render :index
26+
end
27+
end
28+
29+
def destroy
30+
@featured_tag.destroy!
31+
redirect_to settings_featured_tags_path
32+
end
33+
34+
private
35+
36+
def set_featured_tag
37+
@featured_tag = current_account.featured_tags.find(params[:id])
38+
end
39+
40+
def set_featured_tags
41+
@featured_tags = current_account.featured_tags.reject(&:new_record?)
42+
end
43+
44+
def set_most_used_tags
45+
@most_used_tags = Tag.most_used(current_account).where.not(id: @featured_tags.map(&:id)).limit(10)
46+
end
47+
48+
def featured_tag_params
49+
params.require(:featured_tag).permit(:name)
50+
end
51+
end

app/controllers/settings/profiles_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,6 @@ def account_params
3232
end
3333

3434
def set_account
35-
@account = current_user.account
35+
@account = current_account
3636
end
3737
end

app/controllers/settings/sessions_controller.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# frozen_string_literal: true
22

33
class Settings::SessionsController < Settings::BaseController
4+
before_action :authenticate_user!
45
before_action :set_session, only: :destroy
56

67
def destroy

app/javascript/styles/mastodon/accounts.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,3 +288,7 @@
288288
border-bottom: 0;
289289
}
290290
}
291+
292+
.directory__tag .trends__item__current {
293+
width: auto;
294+
}

app/javascript/styles/mastodon/admin.scss

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,15 @@ $content-width: 840px;
153153
font-weight: 500;
154154
}
155155

156-
.directory__tag a {
156+
.directory__tag > a,
157+
.directory__tag > div {
157158
box-shadow: none;
158159
}
159160

161+
.directory__tag .table-action-link .fa {
162+
color: inherit;
163+
}
164+
160165
.directory__tag h4 {
161166
font-size: 18px;
162167
font-weight: 700;

app/javascript/styles/mastodon/widgets.scss

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,8 @@
269269
box-sizing: border-box;
270270
margin-bottom: 10px;
271271

272-
a {
272+
& > a,
273+
& > div {
273274
display: flex;
274275
align-items: center;
275276
justify-content: space-between;
@@ -279,15 +280,17 @@
279280
text-decoration: none;
280281
color: inherit;
281282
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
283+
}
282284

285+
& > a {
283286
&:hover,
284287
&:active,
285288
&:focus {
286289
background: lighten($ui-base-color, 8%);
287290
}
288291
}
289292

290-
&.active a {
293+
&.active > a {
291294
background: $ui-highlight-color;
292295
cursor: default;
293296
}

app/models/concerns/account_associations.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,6 @@ module AccountAssociations
5555

5656
# Hashtags
5757
has_and_belongs_to_many :tags
58+
has_many :featured_tags, -> { includes(:tag) }, dependent: :destroy, inverse_of: :account
5859
end
5960
end

app/models/featured_tag.rb

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# frozen_string_literal: true
2+
# == Schema Information
3+
#
4+
# Table name: featured_tags
5+
#
6+
# id :bigint(8) not null, primary key
7+
# account_id :bigint(8)
8+
# tag_id :bigint(8)
9+
# statuses_count :bigint(8) default(0), not null
10+
# last_status_at :datetime
11+
# created_at :datetime not null
12+
# updated_at :datetime not null
13+
#
14+
15+
class FeaturedTag < ApplicationRecord
16+
belongs_to :account, inverse_of: :featured_tags, required: true
17+
belongs_to :tag, inverse_of: :featured_tags, required: true
18+
19+
delegate :name, to: :tag, allow_nil: true
20+
21+
validates :name, presence: true
22+
validate :validate_featured_tags_limit, on: :create
23+
24+
def name=(str)
25+
self.tag = Tag.find_or_initialize_by(name: str.delete('#').mb_chars.downcase.to_s)
26+
end
27+
28+
def increment(timestamp)
29+
update(statuses_count: statuses_count + 1, last_status_at: timestamp)
30+
end
31+
32+
def decrement(deleted_status_id)
33+
update(statuses_count: [0, statuses_count - 1].max, last_status_at: account.statuses.where(visibility: %i(public unlisted)).tagged_with(tag).where.not(id: deleted_status_id).select(:created_at).first&.created_at)
34+
end
35+
36+
def reset_data
37+
self.statuses_count = account.statuses.where(visibility: %i(public unlisted)).tagged_with(tag).count
38+
self.last_status_at = account.statuses.where(visibility: %i(public unlisted)).tagged_with(tag).select(:created_at).first&.created_at
39+
end
40+
41+
private
42+
43+
def validate_featured_tags_limit
44+
errors.add(:base, I18n.t('featured_tags.errors.limit')) if account.featured_tags.count >= 10
45+
end
46+
end

0 commit comments

Comments
 (0)