@ -0,0 +1,44 @@ | |||
# frozen_string_literal: true | |||
module Admin | |||
class TagsController < BaseController | |||
before_action :set_tags, only: :index | |||
before_action :set_tag, except: :index | |||
before_action :set_filter_params | |||
def index | |||
authorize :tag, :index? | |||
end | |||
def hide | |||
authorize @tag, :hide? | |||
@tag.account_tag_stat.update!(hidden: true) | |||
redirect_to admin_tags_path(@filter_params) | |||
end | |||
def unhide | |||
authorize @tag, :unhide? | |||
@tag.account_tag_stat.update!(hidden: true) | |||
redirect_to admin_tags_path(@filter_params) | |||
end | |||
private | |||
def set_tags | |||
@tags = Tag.discoverable | |||
@tags.merge!(Tag.hidden) if filter_params[:hidden] | |||
end | |||
def set_tag | |||
@tag = Tag.find(params[:id]) | |||
end | |||
def set_filter_params | |||
@filter_params = filter_params.to_hash.symbolize_keys | |||
end | |||
def filter_params | |||
params.permit(:hidden) | |||
end | |||
end | |||
end |
@ -0,0 +1,48 @@ | |||
# frozen_string_literal: true | |||
class DirectoriesController < ApplicationController | |||
layout 'public' | |||
before_action :set_instance_presenter | |||
before_action :set_tag, only: :show | |||
before_action :set_tags | |||
before_action :set_accounts | |||
def index | |||
render :index | |||
end | |||
def show | |||
render :index | |||
end | |||
private | |||
def set_tag | |||
@tag = Tag.discoverable.find_by!(name: params[:id].downcase) | |||
end | |||
def set_tags | |||
@tags = Tag.discoverable.limit(30) | |||
end | |||
def set_accounts | |||
@accounts = Account.searchable.discoverable.page(params[:page]).per(50).tap do |query| | |||
query.merge!(Account.tagged_with(@tag.id)) if @tag | |||
if popular_requested? | |||
query.merge!(Account.popular) | |||
else | |||
query.merge!(Account.by_recent_status) | |||
end | |||
end | |||
end | |||
def set_instance_presenter | |||
@instance_presenter = InstancePresenter.new | |||
end | |||
def popular_requested? | |||
request.path.ends_with?('/popular') | |||
end | |||
end |
@ -0,0 +1,24 @@ | |||
# frozen_string_literal: true | |||
# == Schema Information | |||
# | |||
# Table name: account_tag_stats | |||
# | |||
# id :bigint(8) not null, primary key | |||
# tag_id :bigint(8) not null | |||
# accounts_count :bigint(8) default(0), not null | |||
# hidden :boolean default(FALSE), not null | |||
# created_at :datetime not null | |||
# updated_at :datetime not null | |||
# | |||
class AccountTagStat < ApplicationRecord | |||
belongs_to :tag, inverse_of: :account_tag_stat | |||
def increment_count!(key) | |||
update(key => public_send(key) + 1) | |||
end | |||
def decrement_count!(key) | |||
update(key => [public_send(key) - 1, 0].max) | |||
end | |||
end |
@ -0,0 +1,15 @@ | |||
# frozen_string_literal: true | |||
class TagPolicy < ApplicationPolicy | |||
def index? | |||
staff? | |||
end | |||
def hide? | |||
staff? | |||
end | |||
def unhide? | |||
staff? | |||
end | |||
end |
@ -0,0 +1,12 @@ | |||
%tr | |||
%td | |||
= link_to explore_hashtag_path(tag) do | |||
= fa_icon 'hashtag' | |||
= tag.name | |||
%td | |||
= t('directories.people', count: tag.accounts_count) | |||
%td | |||
- if tag.hidden? | |||
= table_link_to 'eye', t('admin.tags.unhide'), unhide_admin_tag_path(tag.id, **@filter_params), method: :post | |||
- else | |||
= table_link_to 'eye-slash', t('admin.tags.hide'), hide_admin_tag_path(tag.id, **@filter_params), method: :post |
@ -0,0 +1,19 @@ | |||
- content_for :page_title do | |||
= t('admin.tags.title') | |||
.filters | |||
.filter-subset | |||
%strong= t('admin.reports.status') | |||
%ul | |||
%li= filter_link_to t('admin.tags.visible'), hidden: nil | |||
%li= filter_link_to t('admin.tags.hidden'), hidden: '1' | |||
.table-wrapper | |||
%table.table | |||
%thead | |||
%tr | |||
%th= t('admin.tags.name') | |||
%th= t('admin.tags.accounts') | |||
%th | |||
%tbody | |||
= render @tags |
@ -0,0 +1,59 @@ | |||
- content_for :page_title do | |||
= t('directories.explore_mastodon') | |||
- content_for :header_tags do | |||
%meta{ name: 'description', content: t('directories.explanation') } | |||
= opengraph 'og:site_name', site_title | |||
= opengraph 'og:title', t('directories.explore_mastodon', title: site_title) | |||
= opengraph 'og:description', t('directories.explanation') | |||
.page-header | |||
%h1= t('directories.explore_mastodon', title: site_title) | |||
%p= t('directories.explanation') | |||
.grid | |||
.column-0 | |||
.account__section-headline | |||
= active_link_to t('directories.most_recently_active'), @tag ? explore_hashtag_path(@tag) : explore_path | |||
= active_link_to t('directories.most_popular'), @tag ? explore_hashtag_popular_path(@tag) : explore_popular_path | |||
- if @accounts.empty? | |||
= nothing_here | |||
- else | |||
.directory | |||
%table.accounts-table | |||
%tbody | |||
- @accounts.each do |account| | |||
%tr | |||
%td= account_link_to account | |||
%td.accounts-table__count | |||
= number_to_human account.statuses_count, strip_insignificant_zeros: true | |||
%small= t('accounts.posts', count: account.statuses_count) | |||
%td.accounts-table__count | |||
= number_to_human account.followers_count, strip_insignificant_zeros: true | |||
%small= t('accounts.followers', count: account.followers_count) | |||
%td.accounts-table__count | |||
- if account.last_status_at.present? | |||
%time.time-ago{ datetime: account.last_status_at.iso8601, title: l(account.last_status_at) }= l account.last_status_at | |||
- else | |||
\- | |||
%small= t('accounts.last_active') | |||
= paginate @accounts | |||
.column-1 | |||
- if @tags.empty? | |||
.nothing-here.nothing-here--flexible | |||
- else | |||
- @tags.each do |tag| | |||
.directory__tag{ class: tag.id == @tag&.id ? 'active' : nil } | |||
= link_to explore_hashtag_path(tag) do | |||
%h4 | |||
= fa_icon 'hashtag' | |||
= tag.name | |||
%small= t('directories.people', count: tag.accounts_count) | |||
.avatar-stack | |||
- tag.accounts.limit(3).each do |account| | |||
= image_tag current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url, width: 48, height: 48, alt: '', class: 'account__avatar' |
@ -0,0 +1,8 @@ | |||
class CreateAccountsTagsJoinTable < ActiveRecord::Migration[5.2] | |||
def change | |||
create_join_table :accounts, :tags do |t| | |||
t.index [:account_id, :tag_id] | |||
t.index [:tag_id, :account_id], unique: true | |||
end | |||
end | |||
end |
@ -0,0 +1,5 @@ | |||
class AddDiscoverableToAccounts < ActiveRecord::Migration[5.2] | |||
def change | |||
add_column :accounts, :discoverable, :boolean | |||
end | |||
end |
@ -0,0 +1,5 @@ | |||
class AddLastStatusAtToAccountStats < ActiveRecord::Migration[5.2] | |||
def change | |||
add_column :account_stats, :last_status_at, :datetime | |||
end | |||
end |
@ -0,0 +1,11 @@ | |||
class CreateAccountTagStats < ActiveRecord::Migration[5.2] | |||
def change | |||
create_table :account_tag_stats do |t| | |||
t.belongs_to :tag, null: false, foreign_key: { on_delete: :cascade }, index: { unique: true } | |||
t.bigint :accounts_count, default: 0, null: false | |||
t.boolean :hidden, default: false, null: false | |||
t.timestamps | |||
end | |||
end | |||
end |
@ -0,0 +1,3 @@ | |||
Fabricator(:account_tag_stat) do | |||
accounts_count "" | |||
end |
@ -0,0 +1,5 @@ | |||
require 'rails_helper' | |||
RSpec.describe AccountTagStat, type: :model do | |||
pending "add some examples to (or delete) #{__FILE__}" | |||
end |