mirror of https://github.com/Siphonay/mastodon
Merge branch 'master' into glitch-soc/merge-upstream
Conflicts: - app/javascript/packs/public.js - app/models/user.rb - config/settings.yml - db/schema.rb Moved public.js changes to settings.js.
This commit is contained in:
commit
e7f1bfdc2d
|
@ -527,7 +527,7 @@ GEM
|
||||||
rspec-core (~> 3.0, >= 3.0.0)
|
rspec-core (~> 3.0, >= 3.0.0)
|
||||||
sidekiq (>= 2.4.0)
|
sidekiq (>= 2.4.0)
|
||||||
rspec-support (3.8.0)
|
rspec-support (3.8.0)
|
||||||
rubocop (0.61.0)
|
rubocop (0.61.1)
|
||||||
jaro_winkler (~> 1.5.1)
|
jaro_winkler (~> 1.5.1)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
parser (>= 2.5, != 2.5.1.1)
|
parser (>= 2.5, != 2.5.1.1)
|
||||||
|
|
|
@ -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
|
|
@ -21,7 +21,7 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
|
||||||
private
|
private
|
||||||
|
|
||||||
def account_params
|
def account_params
|
||||||
params.permit(:display_name, :note, :avatar, :header, :locked, :bot, fields_attributes: [:name, :value])
|
params.permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable, fields_attributes: [:name, :value])
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_settings_params
|
def user_settings_params
|
||||||
|
|
|
@ -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
|
|
@ -43,6 +43,7 @@ class Settings::PreferencesController < Settings::BaseController
|
||||||
:setting_system_font_ui,
|
:setting_system_font_ui,
|
||||||
:setting_noindex,
|
:setting_noindex,
|
||||||
:setting_hide_network,
|
:setting_hide_network,
|
||||||
|
:setting_aggregate_reblogs,
|
||||||
notification_emails: %i(follow follow_request reblog favourite mention digest report),
|
notification_emails: %i(follow follow_request reblog favourite mention digest report),
|
||||||
interactions: %i(must_be_follower must_be_following)
|
interactions: %i(must_be_follower must_be_following)
|
||||||
)
|
)
|
||||||
|
|
|
@ -25,7 +25,7 @@ class Settings::ProfilesController < Settings::BaseController
|
||||||
private
|
private
|
||||||
|
|
||||||
def account_params
|
def account_params
|
||||||
params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, :bot, fields_attributes: [:name, :value])
|
params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable, fields_attributes: [:name, :value])
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_account
|
def set_account
|
||||||
|
|
|
@ -5,8 +5,9 @@ module Admin::FilterHelper
|
||||||
REPORT_FILTERS = %i(resolved account_id target_account_id).freeze
|
REPORT_FILTERS = %i(resolved account_id target_account_id).freeze
|
||||||
INVITE_FILTER = %i(available expired).freeze
|
INVITE_FILTER = %i(available expired).freeze
|
||||||
CUSTOM_EMOJI_FILTERS = %i(local remote by_domain shortcode).freeze
|
CUSTOM_EMOJI_FILTERS = %i(local remote by_domain shortcode).freeze
|
||||||
|
TAGS_FILTERS = %i(hidden).freeze
|
||||||
|
|
||||||
FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS + INVITE_FILTER + CUSTOM_EMOJI_FILTERS
|
FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS + INVITE_FILTER + CUSTOM_EMOJI_FILTERS + TAGS_FILTERS
|
||||||
|
|
||||||
def filter_link_to(text, link_to_params, link_class_params = link_to_params)
|
def filter_link_to(text, link_to_params, link_class_params = link_to_params)
|
||||||
new_url = filtered_url_for(link_to_params)
|
new_url = filtered_url_for(link_to_params)
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
// This file will be loaded on settings pages, regardless of theme.
|
// This file will be loaded on settings pages, regardless of theme.
|
||||||
|
|
||||||
|
import escapeTextContentForBrowser from 'escape-html';
|
||||||
const { delegate } = require('rails-ujs');
|
const { delegate } = require('rails-ujs');
|
||||||
import emojify from '../mastodon/features/emoji/emoji';
|
import emojify from '../mastodon/features/emoji/emoji';
|
||||||
|
|
||||||
delegate(document, '#account_display_name', 'input', ({ target }) => {
|
delegate(document, '#account_display_name', 'input', ({ target }) => {
|
||||||
const name = document.querySelector('.card .display-name strong');
|
const name = document.querySelector('.card .display-name strong');
|
||||||
|
|
||||||
if (name) {
|
if (name) {
|
||||||
name.innerHTML = emojify(target.value);
|
if (target.value) {
|
||||||
|
name.innerHTML = emojify(escapeTextContentForBrowser(target.value));
|
||||||
|
} else {
|
||||||
|
name.textContent = document.querySelector('#default_account_display_name').textContent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -189,6 +189,11 @@
|
||||||
&--under-tabs {
|
&--under-tabs {
|
||||||
border-radius: 0 0 4px 4px;
|
border-radius: 0 0 4px 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--flexible {
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.account-role {
|
.account-role {
|
||||||
|
|
|
@ -876,7 +876,8 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.status__relative-time {
|
.status__relative-time,
|
||||||
|
.notification__relative_time {
|
||||||
color: $dark-text-color;
|
color: $dark-text-color;
|
||||||
float: right;
|
float: right;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|
|
@ -240,3 +240,171 @@
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
background: lighten($ui-base-color, 8%);
|
||||||
|
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 60px 15px;
|
||||||
|
text-align: center;
|
||||||
|
margin: 10px 0;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: $primary-text-color;
|
||||||
|
font-size: 36px;
|
||||||
|
line-height: 1.1;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 15px;
|
||||||
|
color: $darker-text-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.directory {
|
||||||
|
background: $ui-base-color;
|
||||||
|
border-radius: 0 0 4px 4px;
|
||||||
|
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
|
||||||
|
|
||||||
|
&__tag {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
background: $ui-base-color;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 15px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:active,
|
||||||
|
&:focus {
|
||||||
|
background: lighten($ui-base-color, 8%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active a {
|
||||||
|
background: $ui-highlight-color;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $primary-text-color;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
.fa {
|
||||||
|
color: $darker-text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
small {
|
||||||
|
display: block;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 15px;
|
||||||
|
margin-top: 8px;
|
||||||
|
color: $darker-text-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active h4 {
|
||||||
|
&,
|
||||||
|
.fa,
|
||||||
|
small {
|
||||||
|
color: $primary-text-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-stack {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: (36px + 4px) * 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active .avatar-stack .account__avatar {
|
||||||
|
border-color: $ui-highlight-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-stack {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
.account__avatar {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 50%;
|
||||||
|
position: relative;
|
||||||
|
margin-left: -10px;
|
||||||
|
border: 2px solid $ui-base-color;
|
||||||
|
|
||||||
|
&:nth-child(1) {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(2) {
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(3) {
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.accounts-table {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.account {
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead th {
|
||||||
|
text-align: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: $darker-text-color;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody td {
|
||||||
|
padding: 15px 0;
|
||||||
|
vertical-align: middle;
|
||||||
|
border-bottom: 1px solid lighten($ui-base-color, 8%);
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr:last-child td {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__count {
|
||||||
|
width: 120px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: $primary-text-color;
|
||||||
|
|
||||||
|
small {
|
||||||
|
display: block;
|
||||||
|
color: $darker-text-color;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ class FeedManager
|
||||||
end
|
end
|
||||||
|
|
||||||
def push_to_home(account, status)
|
def push_to_home(account, status)
|
||||||
return false unless add_to_feed(:home, account.id, status)
|
return false unless add_to_feed(:home, account.id, status, account.user&.aggregates_reblogs?)
|
||||||
trim(:home, account.id)
|
trim(:home, account.id)
|
||||||
PushUpdateWorker.perform_async(account.id, status.id, "timeline:#{account.id}") if push_update_required?("timeline:#{account.id}")
|
PushUpdateWorker.perform_async(account.id, status.id, "timeline:#{account.id}") if push_update_required?("timeline:#{account.id}")
|
||||||
true
|
true
|
||||||
|
@ -46,7 +46,7 @@ class FeedManager
|
||||||
should_filter &&= !(list.show_list_replies? && ListAccount.where(list_id: list.id, account_id: status.in_reply_to_account_id).exists?)
|
should_filter &&= !(list.show_list_replies? && ListAccount.where(list_id: list.id, account_id: status.in_reply_to_account_id).exists?)
|
||||||
return false if should_filter
|
return false if should_filter
|
||||||
end
|
end
|
||||||
return false unless add_to_feed(:list, list.id, status)
|
return false unless add_to_feed(:list, list.id, status, list.account.user&.aggregates_reblogs?)
|
||||||
trim(:list, list.id)
|
trim(:list, list.id)
|
||||||
PushUpdateWorker.perform_async(list.account_id, status.id, "timeline:list:#{list.id}") if push_update_required?("timeline:list:#{list.id}")
|
PushUpdateWorker.perform_async(list.account_id, status.id, "timeline:list:#{list.id}") if push_update_required?("timeline:list:#{list.id}")
|
||||||
true
|
true
|
||||||
|
@ -94,7 +94,7 @@ class FeedManager
|
||||||
|
|
||||||
query.each do |status|
|
query.each do |status|
|
||||||
next if status.direct_visibility? || status.limited_visibility? || filter?(:home, status, into_account)
|
next if status.direct_visibility? || status.limited_visibility? || filter?(:home, status, into_account)
|
||||||
add_to_feed(:home, into_account.id, status)
|
add_to_feed(:home, into_account.id, status, into_account.user&.aggregates_reblogs?)
|
||||||
end
|
end
|
||||||
|
|
||||||
trim(:home, into_account.id)
|
trim(:home, into_account.id)
|
||||||
|
@ -132,7 +132,7 @@ class FeedManager
|
||||||
|
|
||||||
statuses.each do |status|
|
statuses.each do |status|
|
||||||
next if filter_from_home?(status, account)
|
next if filter_from_home?(status, account)
|
||||||
added += 1 if add_to_feed(:home, account.id, status)
|
added += 1 if add_to_feed(:home, account.id, status, account.user&.aggregates_reblogs?)
|
||||||
end
|
end
|
||||||
|
|
||||||
break unless added.zero?
|
break unless added.zero?
|
||||||
|
@ -231,11 +231,11 @@ class FeedManager
|
||||||
# added, and false if it was not added to the feed. Note that this is
|
# added, and false if it was not added to the feed. Note that this is
|
||||||
# an internal helper: callers must call trim or push updates if
|
# an internal helper: callers must call trim or push updates if
|
||||||
# either action is appropriate.
|
# either action is appropriate.
|
||||||
def add_to_feed(timeline_type, account_id, status)
|
def add_to_feed(timeline_type, account_id, status, aggregate_reblogs = true)
|
||||||
timeline_key = key(timeline_type, account_id)
|
timeline_key = key(timeline_type, account_id)
|
||||||
reblog_key = key(timeline_type, account_id, 'reblogs')
|
reblog_key = key(timeline_type, account_id, 'reblogs')
|
||||||
|
|
||||||
if status.reblog?
|
if status.reblog? && (aggregate_reblogs.nil? || aggregate_reblogs)
|
||||||
# If the original status or a reblog of it is within
|
# If the original status or a reblog of it is within
|
||||||
# REBLOG_FALLOFF statuses from the top, do not re-insert it into
|
# REBLOG_FALLOFF statuses from the top, do not re-insert it into
|
||||||
# the feed
|
# the feed
|
||||||
|
|
|
@ -33,6 +33,7 @@ class UserSettingsDecorator
|
||||||
user.settings['flavour'] = flavour_preference if change?('setting_flavour')
|
user.settings['flavour'] = flavour_preference if change?('setting_flavour')
|
||||||
user.settings['skin'] = skin_preference if change?('setting_skin')
|
user.settings['skin'] = skin_preference if change?('setting_skin')
|
||||||
user.settings['hide_network'] = hide_network_preference if change?('setting_hide_network')
|
user.settings['hide_network'] = hide_network_preference if change?('setting_hide_network')
|
||||||
|
user.settings['aggregate_reblogs'] = aggregate_reblogs_preference if change?('setting_aggregate_reblogs')
|
||||||
end
|
end
|
||||||
|
|
||||||
def merged_notification_emails
|
def merged_notification_emails
|
||||||
|
@ -107,6 +108,10 @@ class UserSettingsDecorator
|
||||||
settings['setting_default_language']
|
settings['setting_default_language']
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def aggregate_reblogs_preference
|
||||||
|
boolean_cast_setting 'setting_aggregate_reblogs'
|
||||||
|
end
|
||||||
|
|
||||||
def boolean_cast_setting(key)
|
def boolean_cast_setting(key)
|
||||||
ActiveModel::Type::Boolean.new.cast(settings[key])
|
ActiveModel::Type::Boolean.new.cast(settings[key])
|
||||||
end
|
end
|
||||||
|
|
|
@ -43,11 +43,13 @@
|
||||||
# featured_collection_url :string
|
# featured_collection_url :string
|
||||||
# fields :jsonb
|
# fields :jsonb
|
||||||
# actor_type :string
|
# actor_type :string
|
||||||
|
# discoverable :boolean
|
||||||
#
|
#
|
||||||
|
|
||||||
class Account < ApplicationRecord
|
class Account < ApplicationRecord
|
||||||
USERNAME_RE = /[a-z0-9_]+([a-z0-9_\.-]+[a-z0-9_]+)?/i
|
USERNAME_RE = /[a-z0-9_]+([a-z0-9_\.-]+[a-z0-9_]+)?/i
|
||||||
MENTION_RE = /(?<=^|[^\/[:word:]])@((#{USERNAME_RE})(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i
|
MENTION_RE = /(?<=^|[^\/[:word:]])@((#{USERNAME_RE})(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i
|
||||||
|
MIN_FOLLOWERS_DISCOVERY = 10
|
||||||
|
|
||||||
include AccountAssociations
|
include AccountAssociations
|
||||||
include AccountAvatar
|
include AccountAvatar
|
||||||
|
@ -93,6 +95,10 @@ class Account < ApplicationRecord
|
||||||
scope :matches_display_name, ->(value) { where(arel_table[:display_name].matches("#{value}%")) }
|
scope :matches_display_name, ->(value) { where(arel_table[:display_name].matches("#{value}%")) }
|
||||||
scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
|
scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
|
||||||
scope :searchable, -> { where(suspended: false).where(moved_to_account_id: nil) }
|
scope :searchable, -> { where(suspended: false).where(moved_to_account_id: nil) }
|
||||||
|
scope :discoverable, -> { where(silenced: false).where(discoverable: true).joins(:account_stat).where(AccountStat.arel_table[:followers_count].gteq(MIN_FOLLOWERS_DISCOVERY)) }
|
||||||
|
scope :tagged_with, ->(tag) { joins(:accounts_tags).where(accounts_tags: { tag_id: tag }) }
|
||||||
|
scope :popular, -> { order('account_stats.followers_count desc') }
|
||||||
|
scope :by_recent_status, -> { order(Arel.sql('(case when account_stats.last_status_at is null then 1 else 0 end) asc, account_stats.last_status_at desc')) }
|
||||||
|
|
||||||
delegate :email,
|
delegate :email,
|
||||||
:unconfirmed_email,
|
:unconfirmed_email,
|
||||||
|
@ -178,6 +184,40 @@ class Account < ApplicationRecord
|
||||||
@keypair ||= OpenSSL::PKey::RSA.new(private_key || public_key)
|
@keypair ||= OpenSSL::PKey::RSA.new(private_key || public_key)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def tags_as_strings=(tag_names)
|
||||||
|
tag_names.map! { |name| name.mb_chars.downcase.to_s }
|
||||||
|
tag_names.uniq!
|
||||||
|
|
||||||
|
# Existing hashtags
|
||||||
|
hashtags_map = Tag.where(name: tag_names).each_with_object({}) { |tag, h| h[tag.name] = tag }
|
||||||
|
|
||||||
|
# Initialize not yet existing hashtags
|
||||||
|
tag_names.each do |name|
|
||||||
|
next if hashtags_map.key?(name)
|
||||||
|
hashtags_map[name] = Tag.new(name: name)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Remove hashtags that are to be deleted
|
||||||
|
tags.each do |tag|
|
||||||
|
if hashtags_map.key?(tag.name)
|
||||||
|
hashtags_map.delete(tag.name)
|
||||||
|
else
|
||||||
|
transaction do
|
||||||
|
tags.delete(tag)
|
||||||
|
tag.decrement_count!(:accounts_count)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Add hashtags that were so far missing
|
||||||
|
hashtags_map.each_value do |tag|
|
||||||
|
transaction do
|
||||||
|
tags << tag
|
||||||
|
tag.increment_count!(:accounts_count)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def fields
|
def fields
|
||||||
(self[:fields] || []).map { |f| Field.new(self, f) }
|
(self[:fields] || []).map { |f| Field.new(self, f) }
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# == Schema Information
|
# == Schema Information
|
||||||
#
|
#
|
||||||
# Table name: account_stats
|
# Table name: account_stats
|
||||||
|
@ -11,16 +10,25 @@
|
||||||
# followers_count :bigint(8) default(0), not null
|
# followers_count :bigint(8) default(0), not null
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
|
# last_status_at :datetime
|
||||||
#
|
#
|
||||||
|
|
||||||
class AccountStat < ApplicationRecord
|
class AccountStat < ApplicationRecord
|
||||||
belongs_to :account, inverse_of: :account_stat
|
belongs_to :account, inverse_of: :account_stat
|
||||||
|
|
||||||
def increment_count!(key)
|
def increment_count!(key)
|
||||||
update(key => public_send(key) + 1)
|
update(attributes_for_increment(key))
|
||||||
end
|
end
|
||||||
|
|
||||||
def decrement_count!(key)
|
def decrement_count!(key)
|
||||||
update(key => [public_send(key) - 1, 0].max)
|
update(key => [public_send(key) - 1, 0].max)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def attributes_for_increment(key)
|
||||||
|
attrs = { key => public_send(key) + 1 }
|
||||||
|
attrs[:last_status_at] = Time.now.utc if key == :statuses_count
|
||||||
|
attrs
|
||||||
|
end
|
||||||
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
|
|
@ -50,5 +50,8 @@ module AccountAssociations
|
||||||
|
|
||||||
# Account migrations
|
# Account migrations
|
||||||
belongs_to :moved_to_account, class_name: 'Account', optional: true
|
belongs_to :moved_to_account, class_name: 'Account', optional: true
|
||||||
|
|
||||||
|
# Hashtags
|
||||||
|
has_and_belongs_to_many :tags
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,6 +16,7 @@ module AccountCounters
|
||||||
:followers_count=,
|
:followers_count=,
|
||||||
:increment_count!,
|
:increment_count!,
|
||||||
:decrement_count!,
|
:decrement_count!,
|
||||||
|
:last_status_at,
|
||||||
to: :account_stat
|
to: :account_stat
|
||||||
|
|
||||||
def account_stat
|
def account_stat
|
||||||
|
|
|
@ -11,12 +11,36 @@
|
||||||
|
|
||||||
class Tag < ApplicationRecord
|
class Tag < ApplicationRecord
|
||||||
has_and_belongs_to_many :statuses
|
has_and_belongs_to_many :statuses
|
||||||
|
has_and_belongs_to_many :accounts
|
||||||
|
has_and_belongs_to_many :sample_accounts, -> { searchable.discoverable.popular.limit(3) }, class_name: 'Account'
|
||||||
|
|
||||||
|
has_one :account_tag_stat, dependent: :destroy
|
||||||
|
|
||||||
HASHTAG_NAME_RE = '[[:word:]_]*[[:alpha:]_·][[:word:]_]*'
|
HASHTAG_NAME_RE = '[[:word:]_]*[[:alpha:]_·][[:word:]_]*'
|
||||||
HASHTAG_RE = /(?:^|[^\/\)\w])#(#{HASHTAG_NAME_RE})/i
|
HASHTAG_RE = /(?:^|[^\/\)\w])#(#{HASHTAG_NAME_RE})/i
|
||||||
|
|
||||||
validates :name, presence: true, uniqueness: true, format: { with: /\A#{HASHTAG_NAME_RE}\z/i }
|
validates :name, presence: true, uniqueness: true, format: { with: /\A#{HASHTAG_NAME_RE}\z/i }
|
||||||
|
|
||||||
|
scope :discoverable, -> { joins(:account_tag_stat).where(AccountTagStat.arel_table[:accounts_count].gt(0)).where(account_tag_stats: { hidden: false }).order(Arel.sql('account_tag_stats.accounts_count desc')) }
|
||||||
|
scope :hidden, -> { where(account_tag_stats: { hidden: true }) }
|
||||||
|
|
||||||
|
delegate :accounts_count,
|
||||||
|
:accounts_count=,
|
||||||
|
:increment_count!,
|
||||||
|
:decrement_count!,
|
||||||
|
:hidden?,
|
||||||
|
to: :account_tag_stat
|
||||||
|
|
||||||
|
after_save :save_account_tag_stat
|
||||||
|
|
||||||
|
def account_tag_stat
|
||||||
|
super || build_account_tag_stat
|
||||||
|
end
|
||||||
|
|
||||||
|
def cached_sample_accounts
|
||||||
|
Rails.cache.fetch("#{cache_key}/sample_accounts", expires_in: 12.hours) { sample_accounts }
|
||||||
|
end
|
||||||
|
|
||||||
def to_param
|
def to_param
|
||||||
name
|
name
|
||||||
end
|
end
|
||||||
|
@ -43,4 +67,11 @@ class Tag < ApplicationRecord
|
||||||
Tag.where('lower(name) like lower(?)', pattern).order(:name).limit(limit)
|
Tag.where('lower(name) like lower(?)', pattern).order(:name).limit(limit)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def save_account_tag_stat
|
||||||
|
return unless account_tag_stat&.changed?
|
||||||
|
account_tag_stat.save
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -95,7 +95,7 @@ class User < ApplicationRecord
|
||||||
|
|
||||||
delegate :auto_play_gif, :default_sensitive, :unfollow_modal, :boost_modal, :favourite_modal, :delete_modal,
|
delegate :auto_play_gif, :default_sensitive, :unfollow_modal, :boost_modal, :favourite_modal, :delete_modal,
|
||||||
:reduce_motion, :system_font_ui, :noindex, :flavour, :skin, :display_media, :hide_network,
|
:reduce_motion, :system_font_ui, :noindex, :flavour, :skin, :display_media, :hide_network,
|
||||||
:expand_spoilers, :default_language, to: :settings, prefix: :setting, allow_nil: false
|
:expand_spoilers, :default_language, :aggregate_reblogs, to: :settings, prefix: :setting, allow_nil: false
|
||||||
|
|
||||||
attr_reader :invite_code
|
attr_reader :invite_code
|
||||||
|
|
||||||
|
@ -231,6 +231,10 @@ class User < ApplicationRecord
|
||||||
@hides_network ||= settings.hide_network
|
@hides_network ||= settings.hide_network
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def aggregates_reblogs?
|
||||||
|
@aggregates_reblogs ||= settings.aggregate_reblogs
|
||||||
|
end
|
||||||
|
|
||||||
def token_for_app(a)
|
def token_for_app(a)
|
||||||
return nil if a.nil? || a.owner != self
|
return nil if a.nil? || a.owner != self
|
||||||
Doorkeeper::AccessToken
|
Doorkeeper::AccessToken
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class TagPolicy < ApplicationPolicy
|
||||||
|
def index?
|
||||||
|
staff?
|
||||||
|
end
|
||||||
|
|
||||||
|
def hide?
|
||||||
|
staff?
|
||||||
|
end
|
||||||
|
|
||||||
|
def unhide?
|
||||||
|
staff?
|
||||||
|
end
|
||||||
|
end
|
|
@ -10,6 +10,7 @@ class UpdateAccountService < BaseService
|
||||||
|
|
||||||
authorize_all_follow_requests(account) if was_locked && !account.locked
|
authorize_all_follow_requests(account) if was_locked && !account.locked
|
||||||
check_links(account)
|
check_links(account)
|
||||||
|
process_hashtags(account)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -24,4 +25,8 @@ class UpdateAccountService < BaseService
|
||||||
def check_links(account)
|
def check_links(account)
|
||||||
VerifyAccountLinksWorker.perform_async(account.id)
|
VerifyAccountLinksWorker.perform_async(account.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def process_hashtags(account)
|
||||||
|
account.tags_as_strings = Extractor.extract_hashtags(account.note)
|
||||||
|
end
|
||||||
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
|
|
@ -9,6 +9,7 @@
|
||||||
= image_tag account.avatar.url, alt: '', width: 48, height: 48, class: 'u-photo'
|
= image_tag account.avatar.url, alt: '', width: 48, height: 48, class: 'u-photo'
|
||||||
|
|
||||||
.display-name
|
.display-name
|
||||||
|
%span{id: "default_account_display_name", style: "display:none;"}= account.username
|
||||||
%bdi
|
%bdi
|
||||||
%strong.emojify.p-name= display_name(account, custom_emojify: true)
|
%strong.emojify.p-name= display_name(account, custom_emojify: true)
|
||||||
%span
|
%span
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
- content_for :page_title do
|
||||||
|
= t('directories.explore_mastodon', title: site_title)
|
||||||
|
|
||||||
|
- content_for :header_tags do
|
||||||
|
%meta{ name: 'description', content: t('directories.explanation') }
|
||||||
|
|
||||||
|
= opengraph 'og:site_name', t('about.hosted_on', domain: site_hostname)
|
||||||
|
= opengraph 'og:type', 'website'
|
||||||
|
= opengraph 'og:title', t('directories.explore_mastodon', title: site_title)
|
||||||
|
= opengraph 'og:description', t('directories.explanation')
|
||||||
|
= opengraph 'og:image', File.join(root_url, 'android-chrome-192x192.png')
|
||||||
|
|
||||||
|
.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).downcase
|
||||||
|
%td.accounts-table__count
|
||||||
|
= number_to_human account.followers_count, strip_insignificant_zeros: true
|
||||||
|
%small= t('accounts.followers', count: account.followers_count).downcase
|
||||||
|
%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.cached_sample_accounts.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'
|
|
@ -5,6 +5,10 @@
|
||||||
.nav-left
|
.nav-left
|
||||||
= link_to root_url, class: 'brand' do
|
= link_to root_url, class: 'brand' do
|
||||||
= image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
|
= image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
|
||||||
|
|
||||||
|
= link_to t('directories.directory'), explore_path, class: 'nav-link'
|
||||||
|
= link_to t('about.about_this'), about_more_path, class: 'nav-link'
|
||||||
|
= link_to t('about.apps'), 'https://joinmastodon.org/apps', class: 'nav-link'
|
||||||
.nav-center
|
.nav-center
|
||||||
.nav-right
|
.nav-right
|
||||||
- if user_signed_in?
|
- if user_signed_in?
|
||||||
|
|
|
@ -42,6 +42,9 @@
|
||||||
= f.input :setting_favourite_modal, as: :boolean, wrapper: :with_label
|
= f.input :setting_favourite_modal, as: :boolean, wrapper: :with_label
|
||||||
= f.input :setting_delete_modal, as: :boolean, wrapper: :with_label
|
= f.input :setting_delete_modal, as: :boolean, wrapper: :with_label
|
||||||
|
|
||||||
|
.fields-group
|
||||||
|
= f.input :setting_aggregate_reblogs, as: :boolean, wrapper: :with_label
|
||||||
|
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.input :setting_auto_play_gif, as: :boolean, wrapper: :with_label
|
= f.input :setting_auto_play_gif, as: :boolean, wrapper: :with_label
|
||||||
= f.input :setting_display_media, collection: ['default', 'show_all', 'hide_all'], wrapper: :with_label, include_blank: false, label_method: lambda { |item| safe_join([t("simple_form.labels.defaults.setting_display_media_#{item}"), content_tag(:span, t("simple_form.hints.defaults.setting_display_media_#{item}"), class: 'hint')]) }, required: false, as: :radio_buttons, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
|
= f.input :setting_display_media, collection: ['default', 'show_all', 'hide_all'], wrapper: :with_label, include_blank: false, label_method: lambda { |item| safe_join([t("simple_form.labels.defaults.setting_display_media_#{item}"), content_tag(:span, t("simple_form.hints.defaults.setting_display_media_#{item}"), class: 'hint')]) }, required: false, as: :radio_buttons, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
|
|
||||||
= f.input :avatar, wrapper: :with_label, input_html: { accept: AccountAvatar::IMAGE_MIME_TYPES.join(',') }, hint: t('simple_form.hints.defaults.avatar', dimensions: '400x400', size: number_to_human_size(AccountAvatar::LIMIT))
|
= f.input :avatar, wrapper: :with_label, input_html: { accept: AccountAvatar::IMAGE_MIME_TYPES.join(',') }, hint: t('simple_form.hints.defaults.avatar', dimensions: '400x400', size: number_to_human_size(AccountAvatar::LIMIT))
|
||||||
|
|
||||||
|
|
||||||
%hr.spacer/
|
%hr.spacer/
|
||||||
|
|
||||||
.fields-group
|
.fields-group
|
||||||
|
@ -27,6 +26,9 @@
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.input :bot, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.bot')
|
= f.input :bot, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.bot')
|
||||||
|
|
||||||
|
.fields-group
|
||||||
|
= f.input :discoverable, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.discoverable_html', min_followers: Account::MIN_FOLLOWERS_DISCOVERY, path: explore_path)
|
||||||
|
|
||||||
%hr.spacer/
|
%hr.spacer/
|
||||||
|
|
||||||
.fields-row
|
.fields-row
|
||||||
|
|
|
@ -48,6 +48,7 @@ en:
|
||||||
other: Followers
|
other: Followers
|
||||||
following: Following
|
following: Following
|
||||||
joined: Joined %{date}
|
joined: Joined %{date}
|
||||||
|
last_active: last active
|
||||||
link_verified_on: Ownership of this link was checked on %{date}
|
link_verified_on: Ownership of this link was checked on %{date}
|
||||||
media: Media
|
media: Media
|
||||||
moved_html: "%{name} has moved to %{new_profile_link}:"
|
moved_html: "%{name} has moved to %{new_profile_link}:"
|
||||||
|
@ -114,6 +115,7 @@ en:
|
||||||
media_attachments: Media attachments
|
media_attachments: Media attachments
|
||||||
memorialize: Turn into memoriam
|
memorialize: Turn into memoriam
|
||||||
moderation:
|
moderation:
|
||||||
|
active: Active
|
||||||
all: All
|
all: All
|
||||||
silenced: Silenced
|
silenced: Silenced
|
||||||
suspended: Suspended
|
suspended: Suspended
|
||||||
|
@ -439,6 +441,14 @@ en:
|
||||||
proceed: Proceed
|
proceed: Proceed
|
||||||
title: Suspend %{acct}
|
title: Suspend %{acct}
|
||||||
warning_html: 'Suspending this account will <strong>irreversibly</strong> delete data from this account, which includes:'
|
warning_html: 'Suspending this account will <strong>irreversibly</strong> delete data from this account, which includes:'
|
||||||
|
tags:
|
||||||
|
accounts: Accounts
|
||||||
|
hidden: Hidden
|
||||||
|
hide: Hide from directory
|
||||||
|
name: Hashtag
|
||||||
|
title: Hashtags
|
||||||
|
unhide: Show in directory
|
||||||
|
visible: Visible
|
||||||
title: Administration
|
title: Administration
|
||||||
admin_mailer:
|
admin_mailer:
|
||||||
new_report:
|
new_report:
|
||||||
|
@ -517,6 +527,15 @@ en:
|
||||||
success_msg: Your account was successfully deleted
|
success_msg: Your account was successfully deleted
|
||||||
warning_html: Only deletion of content from this particular instance is guaranteed. Content that has been widely shared is likely to leave traces. Offline servers and servers that have unsubscribed from your updates will not update their databases.
|
warning_html: Only deletion of content from this particular instance is guaranteed. Content that has been widely shared is likely to leave traces. Offline servers and servers that have unsubscribed from your updates will not update their databases.
|
||||||
warning_title: Disseminated content availability
|
warning_title: Disseminated content availability
|
||||||
|
directories:
|
||||||
|
directory: Profile directory
|
||||||
|
explanation: Discover users based on their interests
|
||||||
|
explore_mastodon: Explore %{title}
|
||||||
|
most_popular: Most popular
|
||||||
|
most_recently_active: Most recently active
|
||||||
|
people:
|
||||||
|
one: "%{count} person"
|
||||||
|
other: "%{count} people"
|
||||||
errors:
|
errors:
|
||||||
'403': You don't have permission to view this page.
|
'403': You don't have permission to view this page.
|
||||||
'404': The page you were looking for doesn't exist.
|
'404': The page you were looking for doesn't exist.
|
||||||
|
|
|
@ -54,6 +54,7 @@ pl:
|
||||||
other: Śledzących
|
other: Śledzących
|
||||||
following: Śledzonych
|
following: Śledzonych
|
||||||
joined: Dołączył(a) %{date}
|
joined: Dołączył(a) %{date}
|
||||||
|
last_active: ostatnio aktywny(-a)
|
||||||
link_verified_on: Własność tego odnośnika została sprawdzona %{date}
|
link_verified_on: Własność tego odnośnika została sprawdzona %{date}
|
||||||
media: Zawartość multimedialna
|
media: Zawartość multimedialna
|
||||||
moved_html: "%{name} korzysta teraz z konta %{new_profile_link}:"
|
moved_html: "%{name} korzysta teraz z konta %{new_profile_link}:"
|
||||||
|
@ -122,6 +123,7 @@ pl:
|
||||||
media_attachments: Załączniki multimedialne
|
media_attachments: Załączniki multimedialne
|
||||||
memorialize: Przełącz na „In Memoriam”
|
memorialize: Przełącz na „In Memoriam”
|
||||||
moderation:
|
moderation:
|
||||||
|
active: Aktywne
|
||||||
all: Wszystkie
|
all: Wszystkie
|
||||||
silenced: Wyciszone
|
silenced: Wyciszone
|
||||||
suspended: Zawieszone
|
suspended: Zawieszone
|
||||||
|
@ -445,6 +447,14 @@ pl:
|
||||||
proceed: Przejdź
|
proceed: Przejdź
|
||||||
title: Zawieś %{acct}
|
title: Zawieś %{acct}
|
||||||
warning_html: 'Zawieszenie konta będzie skutkowało <strong>nieodwracalnym</strong> usunięciem danych z tego konta, wliczając:'
|
warning_html: 'Zawieszenie konta będzie skutkowało <strong>nieodwracalnym</strong> usunięciem danych z tego konta, wliczając:'
|
||||||
|
tags:
|
||||||
|
accounts: Konta
|
||||||
|
hidden: Ukryte
|
||||||
|
hide: Ukryj w katalogu
|
||||||
|
name: Hashtag
|
||||||
|
title: Hashtagi
|
||||||
|
unhide: Pokazuj w katalogu
|
||||||
|
visible: Widoczne
|
||||||
title: Administracja
|
title: Administracja
|
||||||
admin_mailer:
|
admin_mailer:
|
||||||
new_report:
|
new_report:
|
||||||
|
@ -523,6 +533,17 @@ pl:
|
||||||
success_msg: Twoje konto zostało pomyślnie usunięte
|
success_msg: Twoje konto zostało pomyślnie usunięte
|
||||||
warning_html: Możemy usunąć zawartość jedynie w obrębie tej instancji. Zawartość udostępniona publicznie pozostawia trwałe ślady. Serwery niepodłączone do sieci bądź nieśledzące Twoich aktualizacji mogą zachować Twoje dane.
|
warning_html: Możemy usunąć zawartość jedynie w obrębie tej instancji. Zawartość udostępniona publicznie pozostawia trwałe ślady. Serwery niepodłączone do sieci bądź nieśledzące Twoich aktualizacji mogą zachować Twoje dane.
|
||||||
warning_title: Dostępność usuniętej zawartości
|
warning_title: Dostępność usuniętej zawartości
|
||||||
|
directories:
|
||||||
|
directory: Katalog profilów
|
||||||
|
explanation: Poznaj profile na podstawie zainteresowań
|
||||||
|
explore_mastodon: Odkrywaj %{title}
|
||||||
|
most_popular: Napopularniejsi
|
||||||
|
most_recently_active: Ostatnio aktywni
|
||||||
|
people:
|
||||||
|
few: "%{count} osoby"
|
||||||
|
many: "%{count} osób"
|
||||||
|
one: "%{count} osoba"
|
||||||
|
other: "%{count} osób"
|
||||||
errors:
|
errors:
|
||||||
'403': Nie masz uprawnień, aby wyświetlić tę stronę.
|
'403': Nie masz uprawnień, aby wyświetlić tę stronę.
|
||||||
'404': Strona, którą próbujesz odwiedzić, nie istnieje.
|
'404': Strona, którą próbujesz odwiedzić, nie istnieje.
|
||||||
|
|
|
@ -8,6 +8,7 @@ en:
|
||||||
bot: This account mainly performs automated actions and might not be monitored
|
bot: This account mainly performs automated actions and might not be monitored
|
||||||
context: One or multiple contexts where the filter should apply
|
context: One or multiple contexts where the filter should apply
|
||||||
digest: Only sent after a long period of inactivity and only if you have received any personal messages in your absence
|
digest: Only sent after a long period of inactivity and only if you have received any personal messages in your absence
|
||||||
|
discoverable_html: The <a href="%{path}" target="_blank">directory</a> lets people find accounts based on interests and activity. Requires at least %{min_followers} followers
|
||||||
email: You will be sent a confirmation e-mail
|
email: You will be sent a confirmation e-mail
|
||||||
fields: You can have up to 4 items displayed as a table on your profile
|
fields: You can have up to 4 items displayed as a table on your profile
|
||||||
header: PNG, GIF or JPG. At most %{size}. Will be downscaled to %{dimensions}px
|
header: PNG, GIF or JPG. At most %{size}. Will be downscaled to %{dimensions}px
|
||||||
|
@ -18,6 +19,7 @@ en:
|
||||||
password: Use at least 8 characters
|
password: Use at least 8 characters
|
||||||
phrase: Will be matched regardless of casing in text or content warning of a toot
|
phrase: Will be matched regardless of casing in text or content warning of a toot
|
||||||
scopes: Which APIs the application will be allowed to access. If you select a top-level scope, you don't need to select individual ones.
|
scopes: Which APIs the application will be allowed to access. If you select a top-level scope, you don't need to select individual ones.
|
||||||
|
setting_aggregate_reblogs: Do not show new boosts for toots that have been recently boosted (only affects newly-received boosts)
|
||||||
setting_default_language: The language of your toots can be detected automatically, but it's not always accurate
|
setting_default_language: The language of your toots can be detected automatically, but it's not always accurate
|
||||||
setting_display_media_default: Hide media marked as sensitive
|
setting_display_media_default: Hide media marked as sensitive
|
||||||
setting_display_media_hide_all: Always hide all media
|
setting_display_media_hide_all: Always hide all media
|
||||||
|
@ -48,6 +50,7 @@ en:
|
||||||
context: Filter contexts
|
context: Filter contexts
|
||||||
current_password: Current password
|
current_password: Current password
|
||||||
data: Data
|
data: Data
|
||||||
|
discoverable: List this account on the directory
|
||||||
display_name: Display name
|
display_name: Display name
|
||||||
email: E-mail address
|
email: E-mail address
|
||||||
expires_in: Expire after
|
expires_in: Expire after
|
||||||
|
@ -63,6 +66,7 @@ en:
|
||||||
otp_attempt: Two-factor code
|
otp_attempt: Two-factor code
|
||||||
password: Password
|
password: Password
|
||||||
phrase: Keyword or phrase
|
phrase: Keyword or phrase
|
||||||
|
setting_aggregate_reblogs: Group boosts in timelines
|
||||||
setting_auto_play_gif: Auto-play animated GIFs
|
setting_auto_play_gif: Auto-play animated GIFs
|
||||||
setting_boost_modal: Show confirmation dialog before boosting
|
setting_boost_modal: Show confirmation dialog before boosting
|
||||||
setting_default_language: Posting language
|
setting_default_language: Posting language
|
||||||
|
|
|
@ -8,6 +8,7 @@ pl:
|
||||||
bot: To konto wykonuje głównie zautomatyzowane działania i może nie być monitorowane
|
bot: To konto wykonuje głównie zautomatyzowane działania i może nie być monitorowane
|
||||||
context: Jedno lub wiele miejsc, w których filtr zostanie zastosowany
|
context: Jedno lub wiele miejsc, w których filtr zostanie zastosowany
|
||||||
digest: Wysyłane tylko po długiej nieaktywności, jeżeli w tym czasie otrzymaleś jakąś wiadomość bezpośrednią
|
digest: Wysyłane tylko po długiej nieaktywności, jeżeli w tym czasie otrzymaleś jakąś wiadomość bezpośrednią
|
||||||
|
discoverable_html: <a href="%{path}" target="_blank">Katalog</a> pozwala znaleźć konta na podstawie zainteresowań i aktywności. Profil musi śledzić przynajmniej %{min_followers} osób
|
||||||
fields: Możesz ustawić maksymalnie 4 niestandardowe pola wyświetlane jako tabela na Twoim profilu
|
fields: Możesz ustawić maksymalnie 4 niestandardowe pola wyświetlane jako tabela na Twoim profilu
|
||||||
header: PNG, GIF lub JPG. Maksymalnie %{size}. Zostanie zmniejszony do %{dimensions}px
|
header: PNG, GIF lub JPG. Maksymalnie %{size}. Zostanie zmniejszony do %{dimensions}px
|
||||||
inbox_url: Skopiuj adres ze strony głównej przekaźnika, którego chcesz użyć
|
inbox_url: Skopiuj adres ze strony głównej przekaźnika, którego chcesz użyć
|
||||||
|
@ -42,6 +43,7 @@ pl:
|
||||||
context: Filtruj zawartość
|
context: Filtruj zawartość
|
||||||
current_password: Obecne hasło
|
current_password: Obecne hasło
|
||||||
data: Dane
|
data: Dane
|
||||||
|
discoverable: Wyświetlaj ten profil w katalogu
|
||||||
display_name: Widoczna nazwa
|
display_name: Widoczna nazwa
|
||||||
email: Adres e-mail
|
email: Adres e-mail
|
||||||
expires_in: Wygaśnie po
|
expires_in: Wygaśnie po
|
||||||
|
|
|
@ -34,6 +34,7 @@ SimpleNavigation::Configuration.run do |navigation|
|
||||||
admin.item :reports, safe_join([fa_icon('flag fw'), t('admin.reports.title')]), admin_reports_url, highlights_on: %r{/admin/reports}
|
admin.item :reports, safe_join([fa_icon('flag fw'), t('admin.reports.title')]), admin_reports_url, highlights_on: %r{/admin/reports}
|
||||||
admin.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_url, highlights_on: %r{/admin/accounts}
|
admin.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_url, highlights_on: %r{/admin/accounts}
|
||||||
admin.item :invites, safe_join([fa_icon('user-plus fw'), t('admin.invites.title')]), admin_invites_path
|
admin.item :invites, safe_join([fa_icon('user-plus fw'), t('admin.invites.title')]), admin_invites_path
|
||||||
|
admin.item :tags, safe_join([fa_icon('tag fw'), t('admin.tags.title')]), admin_tags_path
|
||||||
admin.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_url, highlights_on: %r{/admin/instances}, if: -> { current_user.admin? }
|
admin.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_url, highlights_on: %r{/admin/instances}, if: -> { current_user.admin? }
|
||||||
admin.item :domain_blocks, safe_join([fa_icon('lock fw'), t('admin.domain_blocks.title')]), admin_domain_blocks_url, highlights_on: %r{/admin/domain_blocks}, if: -> { current_user.admin? }
|
admin.item :domain_blocks, safe_join([fa_icon('lock fw'), t('admin.domain_blocks.title')]), admin_domain_blocks_url, highlights_on: %r{/admin/domain_blocks}, if: -> { current_user.admin? }
|
||||||
admin.item :email_domain_blocks, safe_join([fa_icon('envelope fw'), t('admin.email_domain_blocks.title')]), admin_email_domain_blocks_url, highlights_on: %r{/admin/email_domain_blocks}, if: -> { current_user.admin? }
|
admin.item :email_domain_blocks, safe_join([fa_icon('envelope fw'), t('admin.email_domain_blocks.title')]), admin_email_domain_blocks_url, highlights_on: %r{/admin/email_domain_blocks}, if: -> { current_user.admin? }
|
||||||
|
|
|
@ -80,6 +80,11 @@ Rails.application.routes.draw do
|
||||||
get '/interact/:id', to: 'remote_interaction#new', as: :remote_interaction
|
get '/interact/:id', to: 'remote_interaction#new', as: :remote_interaction
|
||||||
post '/interact/:id', to: 'remote_interaction#create'
|
post '/interact/:id', to: 'remote_interaction#create'
|
||||||
|
|
||||||
|
get '/explore', to: 'directories#index', as: :explore
|
||||||
|
get '/explore/popular', to: 'directories#index', as: :explore_popular
|
||||||
|
get '/explore/:id', to: 'directories#show', as: :explore_hashtag
|
||||||
|
get '/explore/:id/popular', to: 'directories#show', as: :explore_hashtag_popular
|
||||||
|
|
||||||
namespace :settings do
|
namespace :settings do
|
||||||
resource :profile, only: [:show, :update]
|
resource :profile, only: [:show, :update]
|
||||||
|
|
||||||
|
@ -210,6 +215,13 @@ Rails.application.routes.draw do
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :account_moderation_notes, only: [:create, :destroy]
|
resources :account_moderation_notes, only: [:create, :destroy]
|
||||||
|
|
||||||
|
resources :tags, only: [:index] do
|
||||||
|
member do
|
||||||
|
post :hide
|
||||||
|
post :unhide
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
get '/admin', to: redirect('/admin/dashboard', status: 302)
|
get '/admin', to: redirect('/admin/dashboard', status: 302)
|
||||||
|
|
|
@ -35,6 +35,7 @@ defaults: &defaults
|
||||||
noindex: false
|
noindex: false
|
||||||
flavour: 'glitch'
|
flavour: 'glitch'
|
||||||
skin: 'default'
|
skin: 'default'
|
||||||
|
aggregate_reblogs: true
|
||||||
notification_emails:
|
notification_emails:
|
||||||
follow: false
|
follow: false
|
||||||
reblog: false
|
reblog: false
|
||||||
|
|
|
@ -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
|
21
db/schema.rb
21
db/schema.rb
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 2018_11_27_165847) do
|
ActiveRecord::Schema.define(version: 2018_12_04_215309) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
@ -63,9 +63,19 @@ ActiveRecord::Schema.define(version: 2018_11_27_165847) do
|
||||||
t.bigint "followers_count", default: 0, null: false
|
t.bigint "followers_count", default: 0, null: false
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
|
t.datetime "last_status_at"
|
||||||
t.index ["account_id"], name: "index_account_stats_on_account_id", unique: true
|
t.index ["account_id"], name: "index_account_stats_on_account_id", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "account_tag_stats", force: :cascade do |t|
|
||||||
|
t.bigint "tag_id", null: false
|
||||||
|
t.bigint "accounts_count", default: 0, null: false
|
||||||
|
t.boolean "hidden", default: false, null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["tag_id"], name: "index_account_tag_stats_on_tag_id", unique: true
|
||||||
|
end
|
||||||
|
|
||||||
create_table "accounts", force: :cascade do |t|
|
create_table "accounts", force: :cascade do |t|
|
||||||
t.string "username", default: "", null: false
|
t.string "username", default: "", null: false
|
||||||
t.string "domain"
|
t.string "domain"
|
||||||
|
@ -106,6 +116,7 @@ ActiveRecord::Schema.define(version: 2018_11_27_165847) do
|
||||||
t.string "featured_collection_url"
|
t.string "featured_collection_url"
|
||||||
t.jsonb "fields"
|
t.jsonb "fields"
|
||||||
t.string "actor_type"
|
t.string "actor_type"
|
||||||
|
t.boolean "discoverable"
|
||||||
t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin
|
t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin
|
||||||
t.index "lower((username)::text), lower((domain)::text)", name: "index_accounts_on_username_and_domain_lower", unique: true
|
t.index "lower((username)::text), lower((domain)::text)", name: "index_accounts_on_username_and_domain_lower", unique: true
|
||||||
t.index ["moved_to_account_id"], name: "index_accounts_on_moved_to_account_id"
|
t.index ["moved_to_account_id"], name: "index_accounts_on_moved_to_account_id"
|
||||||
|
@ -113,6 +124,13 @@ ActiveRecord::Schema.define(version: 2018_11_27_165847) do
|
||||||
t.index ["url"], name: "index_accounts_on_url"
|
t.index ["url"], name: "index_accounts_on_url"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "accounts_tags", id: false, force: :cascade do |t|
|
||||||
|
t.bigint "account_id", null: false
|
||||||
|
t.bigint "tag_id", null: false
|
||||||
|
t.index ["account_id", "tag_id"], name: "index_accounts_tags_on_account_id_and_tag_id"
|
||||||
|
t.index ["tag_id", "account_id"], name: "index_accounts_tags_on_tag_id_and_account_id", unique: true
|
||||||
|
end
|
||||||
|
|
||||||
create_table "admin_action_logs", force: :cascade do |t|
|
create_table "admin_action_logs", force: :cascade do |t|
|
||||||
t.bigint "account_id"
|
t.bigint "account_id"
|
||||||
t.string "action", default: "", null: false
|
t.string "action", default: "", null: false
|
||||||
|
@ -649,6 +667,7 @@ ActiveRecord::Schema.define(version: 2018_11_27_165847) do
|
||||||
add_foreign_key "account_pins", "accounts", column: "target_account_id", on_delete: :cascade
|
add_foreign_key "account_pins", "accounts", column: "target_account_id", on_delete: :cascade
|
||||||
add_foreign_key "account_pins", "accounts", on_delete: :cascade
|
add_foreign_key "account_pins", "accounts", on_delete: :cascade
|
||||||
add_foreign_key "account_stats", "accounts", on_delete: :cascade
|
add_foreign_key "account_stats", "accounts", on_delete: :cascade
|
||||||
|
add_foreign_key "account_tag_stats", "tags", on_delete: :cascade
|
||||||
add_foreign_key "accounts", "accounts", column: "moved_to_account_id", on_delete: :nullify
|
add_foreign_key "accounts", "accounts", column: "moved_to_account_id", on_delete: :nullify
|
||||||
add_foreign_key "admin_action_logs", "accounts", on_delete: :cascade
|
add_foreign_key "admin_action_logs", "accounts", on_delete: :cascade
|
||||||
add_foreign_key "backups", "users", on_delete: :nullify
|
add_foreign_key "backups", "users", on_delete: :nullify
|
||||||
|
|
|
@ -1,7 +1,29 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe ActivityPub::InboxesController, type: :controller do
|
RSpec.describe ActivityPub::InboxesController, type: :controller do
|
||||||
describe 'POST #create' do
|
describe 'POST #create' do
|
||||||
pending
|
context 'if signed_request_account' do
|
||||||
|
it 'returns 202' do
|
||||||
|
allow(controller).to receive(:signed_request_account) do
|
||||||
|
Fabricate(:account)
|
||||||
|
end
|
||||||
|
|
||||||
|
post :create
|
||||||
|
expect(response).to have_http_status(202)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'not signed_request_account' do
|
||||||
|
it 'returns 401' do
|
||||||
|
allow(controller).to receive(:signed_request_account) do
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
post :create
|
||||||
|
expect(response).to have_http_status(401)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
Fabricator(:account_tag_stat) do
|
||||||
|
accounts_count ""
|
||||||
|
end
|
|
@ -1,15 +1,55 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
# Specs in this file have access to a helper object that includes
|
|
||||||
# the Admin::AccountModerationNotesHelper. For example:
|
|
||||||
#
|
|
||||||
# describe Admin::AccountModerationNotesHelper do
|
|
||||||
# describe "string concat" do
|
|
||||||
# it "concats two strings with spaces" do
|
|
||||||
# expect(helper.concat_strings("this","that")).to eq("this that")
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
RSpec.describe Admin::AccountModerationNotesHelper, type: :helper do
|
RSpec.describe Admin::AccountModerationNotesHelper, type: :helper do
|
||||||
pending "add some examples to (or delete) #{__FILE__}"
|
include StreamEntriesHelper
|
||||||
|
|
||||||
|
describe '#admin_account_link_to' do
|
||||||
|
context 'account is nil' do
|
||||||
|
let(:account) { nil }
|
||||||
|
|
||||||
|
it 'returns nil' do
|
||||||
|
expect(helper.admin_account_link_to(account)).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with account' do
|
||||||
|
let(:account) { Fabricate(:account) }
|
||||||
|
|
||||||
|
it 'calls #link_to' do
|
||||||
|
expect(helper).to receive(:link_to).with(
|
||||||
|
admin_account_path(account.id),
|
||||||
|
class: name_tag_classes(account),
|
||||||
|
title: account.acct
|
||||||
|
)
|
||||||
|
|
||||||
|
helper.admin_account_link_to(account)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#admin_account_inline_link_to' do
|
||||||
|
context 'account is nil' do
|
||||||
|
let(:account) { nil }
|
||||||
|
|
||||||
|
it 'returns nil' do
|
||||||
|
expect(helper.admin_account_inline_link_to(account)).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with account' do
|
||||||
|
let(:account) { Fabricate(:account) }
|
||||||
|
|
||||||
|
it 'calls #link_to' do
|
||||||
|
expect(helper).to receive(:link_to).with(
|
||||||
|
admin_account_path(account.id),
|
||||||
|
class: name_tag_classes(account, true),
|
||||||
|
title: account.acct
|
||||||
|
)
|
||||||
|
|
||||||
|
helper.admin_account_inline_link_to(account)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe AccountPin, type: :model do
|
|
||||||
pending "add some examples to (or delete) #{__FILE__}"
|
|
||||||
end
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe AccountTagStat, type: :model do
|
||||||
|
key = 'accounts_count'
|
||||||
|
let(:account_tag_stat) { Fabricate(:tag).account_tag_stat }
|
||||||
|
|
||||||
|
describe '#increment_count!' do
|
||||||
|
it 'calls #update' do
|
||||||
|
args = { key => account_tag_stat.public_send(key) + 1 }
|
||||||
|
expect(account_tag_stat).to receive(:update).with(args)
|
||||||
|
account_tag_stat.increment_count!(key)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'increments value by 1' do
|
||||||
|
expect do
|
||||||
|
account_tag_stat.increment_count!(key)
|
||||||
|
end.to change { account_tag_stat.accounts_count }.by(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#decrement_count!' do
|
||||||
|
it 'calls #update' do
|
||||||
|
args = { key => [account_tag_stat.public_send(key) - 1, 0].max }
|
||||||
|
expect(account_tag_stat).to receive(:update).with(args)
|
||||||
|
account_tag_stat.decrement_count!(key)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'decrements value by 1' do
|
||||||
|
account_tag_stat.update(key => 1)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
account_tag_stat.decrement_count!(key)
|
||||||
|
end.to change { account_tag_stat.accounts_count }.by(-1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -15,14 +15,6 @@ describe UniqueUsernameValidator do
|
||||||
expect(account).to be_valid
|
expect(account).to be_valid
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'adds an error when the username is already used with ignoring dots' do
|
|
||||||
pending 'allowing dots in username is still in development'
|
|
||||||
Fabricate(:account, username: 'abcd.ef')
|
|
||||||
account = double(username: 'ab.cdef', persisted?: false, errors: double(add: nil))
|
|
||||||
subject.validate(account)
|
|
||||||
expect(account.errors).to have_received(:add)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'adds an error when the username is already used with ignoring cases' do
|
it 'adds an error when the username is already used with ignoring cases' do
|
||||||
Fabricate(:account, username: 'ABCdef')
|
Fabricate(:account, username: 'ABCdef')
|
||||||
account = double(username: 'abcDEF', persisted?: false, errors: double(add: nil))
|
account = double(username: 'abcDEF', persisted?: false, errors: double(add: nil))
|
||||||
|
|
Loading…
Reference in New Issue