Merge branch 'master' into glitch-soc/merge-upstream

Conflicts:
- app/controllers/settings/follower_domains_controller.rb
  Removed upstream. Did the same here. Maybe we should not have?
- config/locales/en.yml
  Upstream removed the “Authorized followers” page and associated
  translations. This is too close in the file to our glitch-soc-specific
  “flavour” string. No actual conflict.
- config/locales/ja.yml
  Same as above.
- config/locales/pl.yml
  Same as above.
- config/navigation.rb
  No real conflict. New route added too close to the glitch-soc-specific
  “flavours” one.
- config/webpack/configuration.js
  Upstream refactored the webpack(er) configuration quite a bit.
  Tried to keep up.
- config/webpack/loaders/babel.js
  Upstream refactored the webpack(er) configuration quite a bit.
  Tried to keep up.
  The contents of this file have been moved to package.json.
- config/webpack/shared.js
  Upstream refactored the webpack(er) configuration quite a bit.
  Tried to keep up.
- config/webpacker.yml
  Upstream refactored the webpack(er) configuration quite a bit.
  Tried to keep up.
- jest.config.js
  The contents of this file have been moved to package.json.
- package.json
  Upstream refactored the webpack(er) configuration quite a bit.
  Tried to keep up.
- yarn.lock
  Upstream refactored the webpack(er) configuration quite a bit.
  Tried to keep up.
This commit is contained in:
Thibaut Girka
2019-03-16 13:52:55 +01:00
122 changed files with 2456 additions and 2454 deletions

View File

@@ -0,0 +1,12 @@
# frozen_string_literal: true
class Api::V1::PreferencesController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:accounts' }
before_action :require_user!
respond_to :json
def index
render json: current_account, serializer: REST::PreferencesSerializer
end
end

View File

@@ -9,7 +9,7 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController
respond_to :json
def create
@status = ReblogService.new.call(current_user.account, status_for_reblog)
@status = ReblogService.new.call(current_user.account, status_for_reblog, reblog_params)
render json: @status, serializer: REST::StatusSerializer
end
@@ -32,4 +32,8 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController
def status_for_destroy
current_user.account.statuses.where(reblog_of_id: params[:status_id]).first!
end
def reblog_params
params.permit(:visibility)
end
end

View File

@@ -0,0 +1,98 @@
# frozen_string_literal: true
class RelationshipsController < ApplicationController
layout 'admin'
before_action :authenticate_user!
before_action :set_accounts, only: :show
before_action :set_body_classes
helper_method :following_relationship?, :followed_by_relationship?, :mutual_relationship?
def show
@form = Form::AccountBatch.new
end
def update
@form = Form::AccountBatch.new(form_account_batch_params.merge(current_account: current_account, action: action_from_button))
@form.save
rescue ActionController::ParameterMissing
# Do nothing
ensure
redirect_to relationships_path(current_params)
end
private
def set_accounts
@accounts = relationships_scope.page(params[:page]).per(40)
end
def relationships_scope
scope = begin
if following_relationship?
current_account.following.includes(:account_stat)
else
current_account.followers.includes(:account_stat)
end
end
scope.merge!(Follow.recent)
scope.merge!(mutual_relationship_scope) if mutual_relationship?
scope.merge!(abandoned_account_scope) if params[:status] == 'abandoned'
scope.merge!(active_account_scope) if params[:status] == 'active'
scope.merge!(by_domain_scope) if params[:by_domain].present?
scope
end
def mutual_relationship_scope
Account.where(id: current_account.following)
end
def abandoned_account_scope
Account.where.not(moved_to_account_id: nil)
end
def active_account_scope
Account.where(moved_to_account_id: nil)
end
def by_domain_scope
Account.where(domain: params[:by_domain])
end
def form_account_batch_params
params.require(:form_account_batch).permit(:action, account_ids: [])
end
def following_relationship?
params[:relationship].blank? || params[:relationship] == 'following'
end
def mutual_relationship?
params[:relationship] == 'mutual'
end
def followed_by_relationship?
params[:relationship] == 'followed_by'
end
def current_params
params.slice(:page, :status, :relationship, :by_domain).permit(:page, :status, :relationship, :by_domain)
end
def action_from_button
if params[:unfollow]
'unfollow'
elsif params[:remove_from_followers]
'remove_from_followers'
elsif params[:block_domains]
'block_domains'
end
end
def set_body_classes
@body_classes = 'admin'
end
end

View File

@@ -1,24 +0,0 @@
# frozen_string_literal: true
class Settings::FollowerDomainsController < Settings::BaseController
def show
@account = current_account
@domains = current_account.followers.reorder(Arel.sql('MIN(follows.id) DESC')).group('accounts.domain').select('accounts.domain, count(accounts.id) as accounts_from_domain').page(params[:page]).per(10)
end
def update
domains = bulk_params[:select] || []
AfterAccountDomainBlockWorker.push_bulk(domains) do |domain|
[current_account.id, domain]
end
redirect_to settings_follower_domains_path, notice: I18n.t('followers.success', count: domains.size)
end
private
def bulk_params
params.permit(select: [])
end
end

View File

@@ -9,42 +9,6 @@ module Admin::ActionLogsHelper
end
end
def linkable_log_target(record)
case record.class.name
when 'Account'
link_to record.acct, admin_account_path(record.id)
when 'User'
link_to record.account.acct, admin_account_path(record.account_id)
when 'CustomEmoji'
record.shortcode
when 'Report'
link_to "##{record.id}", admin_report_path(record)
when 'DomainBlock', 'EmailDomainBlock'
link_to record.domain, "https://#{record.domain}"
when 'Status'
link_to record.account.acct, TagManager.instance.url_for(record)
when 'AccountWarning'
link_to record.target_account.acct, admin_account_path(record.target_account_id)
end
end
def log_target_from_history(type, attributes)
case type
when 'CustomEmoji'
attributes['shortcode']
when 'DomainBlock', 'EmailDomainBlock'
link_to attributes['domain'], "https://#{attributes['domain']}"
when 'Status'
tmp_status = Status.new(attributes.except('reblogs_count', 'favourites_count'))
if tmp_status.account
link_to tmp_status.account&.acct || "##{tmp_status.account_id}", admin_account_path(tmp_status.account_id)
else
I18n.t('admin.action_logs.deleted_status')
end
end
end
def relevant_log_changes(log)
if log.target_type == 'CustomEmoji' && [:enable, :disable, :destroy].include?(log.action)
log.recorded_changes.slice('domain')
@@ -111,4 +75,40 @@ module Admin::ActionLogsHelper
def opposite_verbs?(log)
%w(DomainBlock EmailDomainBlock AccountWarning).include?(log.target_type)
end
def linkable_log_target(record)
case record.class.name
when 'Account'
link_to record.acct, admin_account_path(record.id)
when 'User'
link_to record.account.acct, admin_account_path(record.account_id)
when 'CustomEmoji'
record.shortcode
when 'Report'
link_to "##{record.id}", admin_report_path(record)
when 'DomainBlock', 'EmailDomainBlock'
link_to record.domain, "https://#{record.domain}"
when 'Status'
link_to record.account.acct, TagManager.instance.url_for(record)
when 'AccountWarning'
link_to record.target_account.acct, admin_account_path(record.target_account_id)
end
end
def log_target_from_history(type, attributes)
case type
when 'CustomEmoji'
attributes['shortcode']
when 'DomainBlock', 'EmailDomainBlock'
link_to attributes['domain'], "https://#{attributes['domain']}"
when 'Status'
tmp_status = Status.new(attributes.except('reblogs_count', 'favourites_count'))
if tmp_status.account
link_to tmp_status.account&.acct || "##{tmp_status.account_id}", admin_account_path(tmp_status.account_id)
else
I18n.t('admin.action_logs.deleted_status')
end
end
end
end

View File

@@ -7,8 +7,9 @@ module Admin::FilterHelper
CUSTOM_EMOJI_FILTERS = %i(local remote by_domain shortcode).freeze
TAGS_FILTERS = %i(hidden).freeze
INSTANCES_FILTERS = %i(limited by_domain).freeze
FOLLOWERS_FILTERS = %i(relationship status by_domain).freeze
FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS + INVITE_FILTER + CUSTOM_EMOJI_FILTERS + TAGS_FILTERS + INSTANCES_FILTERS
FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS + INVITE_FILTER + CUSTOM_EMOJI_FILTERS + TAGS_FILTERS + INSTANCES_FILTERS + FOLLOWERS_FILTERS
def filter_link_to(text, link_to_params, link_class_params = link_to_params)
new_url = filtered_url_for(link_to_params)

View File

@@ -0,0 +1,39 @@
import React from 'react';
import PropTypes from 'prop-types';
import illustration from '../../images/elephant_ui_disappointed.svg';
export default class ErrorBoundary extends React.PureComponent {
static propTypes = {
children: PropTypes.node,
};
state = {
hasError: false,
stackTrace: undefined,
componentStack: undefined,
}
componentDidCatch(error, info) {
this.setState({
hasError: true,
stackTrace: error.stack,
componentStack: info && info.componentStack,
});
}
render() {
const { hasError } = this.state;
if (!hasError) {
return this.props.children;
}
return (
<div>
<img src={illustration} alt='' />
</div>
);
}
}

View File

@@ -13,6 +13,7 @@ import { connectUserStream } from '../actions/streaming';
import { IntlProvider, addLocaleData } from 'react-intl';
import { getLocale } from '../locales';
import initialState from '../initial_state';
import ErrorBoundary from '../components/error_boundary';
const { localeData, messages } = getLocale();
addLocaleData(localeData);
@@ -75,7 +76,9 @@ export default class Mastodon extends React.PureComponent {
return (
<IntlProvider locale={locale} messages={messages}>
<Provider store={store}>
<MastodonMount />
<ErrorBoundary>
<MastodonMount />
</ErrorBoundary>
</Provider>
</IntlProvider>
);

View File

@@ -53,6 +53,11 @@ class Bundle extends React.PureComponent {
const { fetchComponent, onFetch, onFetchSuccess, onFetchFail, renderDelay } = props || this.props;
const cachedMod = Bundle.cache.get(fetchComponent);
if (fetchComponent === undefined) {
this.setState({ mod: null });
return Promise.resolve();
}
onFetch();
if (cachedMod) {

View File

@@ -73,6 +73,10 @@
"compose_form.lock_disclaimer": "Váš účet není {locked}. Kdokoliv vás může sledovat a vidět vaše příspěvky pouze pro sledující.",
"compose_form.lock_disclaimer.lock": "uzamčen",
"compose_form.placeholder": "Co se vám honí hlavou?",
"compose_form.poll.add_option": "Přidat volbu",
"compose_form.poll.duration": "Délka ankety",
"compose_form.poll.option_placeholder": "Volba {number}",
"compose_form.poll.remove_option": "Odstranit tuto volbu",
"compose_form.publish": "Tootnout",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive.marked": "Mediální obsah je označen jako citlivý",
@@ -151,6 +155,9 @@
"home.column_settings.basic": "Základní",
"home.column_settings.show_reblogs": "Zobrazit boosty",
"home.column_settings.show_replies": "Zobrazit odpovědi",
"intervals.full.days": "{number, plural, one {# den} few {# dny} many {# dne} other {# dní}}",
"intervals.full.hours": "{number, plural, one {# hodina} few {# hodiny} many {# hodiny} other {# hodin}}",
"intervals.full.minutes": "{number, plural, one {# minuta} few {# minuty} many {# minuty} other {# minut}}",
"introduction.federation.action": "Další",
"introduction.federation.federated.headline": "Federovaná",
"introduction.federation.federated.text": "Veřejné příspěvky z jiných serverů na fediverse se zobrazí na federované časové ose.",
@@ -240,6 +247,7 @@
"notification.favourite": "{name} si oblíbil/a váš toot",
"notification.follow": "{name} vás začal/a sledovat",
"notification.mention": "{name} vás zmínil/a",
"notification.poll": "Anketa, ve které jste hlasoval/a, skončila",
"notification.reblog": "{name} boostnul/a váš toot",
"notifications.clear": "Vymazat oznámení",
"notifications.clear_confirmation": "Jste si jistý/á, že chcete trvale vymazat všechna vaše oznámení?",
@@ -264,6 +272,8 @@
"poll.refresh": "Refresh",
"poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
"poll.vote": "Vote",
"poll_button.add_poll": "Přidat anketu",
"poll_button.remove_poll": "Odstranit anketu",
"privacy.change": "Změnit soukromí tootu",
"privacy.direct.long": "Odeslat pouze zmíněným uživatelům",
"privacy.direct.short": "Přímý",
@@ -356,6 +366,7 @@
"upload_area.title": "Přetažením nahrajete",
"upload_button.label": "Přidat média (JPEG, PNG, GIF, WebM, MP4, MOV)",
"upload_error.limit": "Byl překročen limit nahraných souborů.",
"upload_error.poll": "Nahrávání souborů není povoleno u anket.",
"upload_form.description": "Popis pro zrakově postižené",
"upload_form.focus": "Změnit náhled",
"upload_form.undo": "Smazat",

View File

@@ -140,6 +140,15 @@ a.table-action-link {
input {
margin-top: 8px;
}
&--aligned {
display: flex;
align-items: center;
input {
margin-top: 0;
}
}
}
&__actions,
@@ -183,6 +192,10 @@ a.table-action-link {
&__content {
padding-top: 12px;
padding-bottom: 16px;
&--unpadded {
padding: 0;
}
}
}
@@ -197,4 +210,10 @@ a.table-action-link {
font-weight: 700;
}
}
.nothing-here {
border: 1px solid darken($ui-base-color, 8%);
border-top: 0;
box-shadow: none;
}
}

View File

@@ -3,7 +3,8 @@
class LanguageDetector
include Singleton
CHARACTER_THRESHOLD = 140
CHARACTER_THRESHOLD = 140
RELIABLE_CHARACTERS_RE = /[\p{Hebrew}\p{Arabic}\p{Syriac}\p{Thaana}\p{Nko}\p{Han}\p{Katakana}\p{Hiragana}\p{Hangul}]+/m
def initialize
@identifier = CLD3::NNetLanguageIdentifier.new(1, 2048)
@@ -11,15 +12,14 @@ class LanguageDetector
def detect(text, account)
input_text = prepare_text(text)
return if input_text.blank?
detect_language_code(input_text) || default_locale(account)
end
def language_names
@language_names =
CLD3::TaskContextParams::LANGUAGE_NAMES.map { |name| iso6391(name.to_s).to_sym }
.uniq
@language_names = CLD3::TaskContextParams::LANGUAGE_NAMES.map { |name| iso6391(name.to_s).to_sym }.uniq
end
private
@@ -29,12 +29,29 @@ class LanguageDetector
end
def unreliable_input?(text)
text.size < CHARACTER_THRESHOLD
!reliable_input?(text)
end
def reliable_input?(text)
sufficient_text_length?(text) || language_specific_character_set?(text)
end
def sufficient_text_length?(text)
text.size >= CHARACTER_THRESHOLD
end
def language_specific_character_set?(text)
words = text.scan(RELIABLE_CHARACTERS_RE)
if words.present?
words.reduce(0) { |acc, elem| acc + elem.size }.to_f / text.size.to_f > 0.3
else
false
end
end
def detect_language_code(text)
return if unreliable_input?(text)
result = @identifier.find_language(text)
iso6391(result.language.to_s).to_sym if result.reliable?
end
@@ -77,6 +94,6 @@ class LanguageDetector
end
def default_locale(account)
return account.user_locale&.to_sym || I18n.default_locale if account.local?
account.user_locale&.to_sym || I18n.default_locale if account.local?
end
end

View File

@@ -0,0 +1,60 @@
# frozen_string_literal: true
class Form::AccountBatch
include ActiveModel::Model
attr_accessor :account_ids, :action, :current_account
def save
case action
when 'unfollow'
unfollow!
when 'remove_from_followers'
remove_from_followers!
when 'block_domains'
block_domains!
end
end
private
def unfollow!
accounts.find_each do |target_account|
UnfollowService.new.call(current_account, target_account)
end
end
def remove_from_followers!
current_account.passive_relationships.where(account_id: account_ids).find_each do |follow|
reject_follow!(follow)
end
end
def block_domains!
AfterAccountDomainBlockWorker.push_bulk(account_domains) do |domain|
[current_account.id, domain]
end
end
def account_domains
accounts.pluck(Arel.sql('distinct domain')).compact
end
def accounts
Account.where(id: account_ids)
end
def reject_follow!(follow)
follow.destroy
return unless follow.account.activitypub?
json = ActiveModelSerializers::SerializableResource.new(
follow,
serializer: ActivityPub::RejectFollowSerializer,
adapter: ActivityPub::Adapter
).to_json
ActivityPub::DeliveryWorker.perform_async(json, current_account.id, follow.account.inbox_url)
end
end

View File

@@ -73,7 +73,9 @@ class Status < ApplicationRecord
validates_with StatusLengthValidator
validates_with DisallowedHashtagsValidator
validates :reblog, uniqueness: { scope: :account }, if: :reblog?
validates_associated :owned_poll
validates :visibility, exclusion: { in: %w(direct limited) }, if: :reblog?
accepts_nested_attributes_for :owned_poll
default_scope { recent }

View File

@@ -32,7 +32,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer
end
def thumbnail
instance_presenter.thumbnail ? full_asset_url(instance_presenter.thumbnail.file.url) : full_pack_url('preview.jpg')
instance_presenter.thumbnail ? full_asset_url(instance_presenter.thumbnail.file.url) : full_pack_url('media/images/preview.jpg')
end
def max_toot_chars

View File

@@ -0,0 +1,30 @@
# frozen_string_literal: true
class REST::PreferencesSerializer < ActiveModel::Serializer
attribute :posting_default_privacy, key: 'posting:default:visibility'
attribute :posting_default_sensitive, key: 'posting:default:sensitive'
attribute :posting_default_language, key: 'posting:default:language'
attribute :reading_default_sensitive_media, key: 'reading:expand:media'
attribute :reading_default_sensitive_text, key: 'reading:expand:spoilers'
def posting_default_privacy
object.user.setting_default_privacy
end
def posting_default_sensitive
object.user.setting_default_sensitive
end
def posting_default_language
object.user.setting_default_language.presence
end
def reading_default_sensitive_media
object.user.setting_display_media
end
def reading_default_sensitive_text
object.user.setting_expand_spoilers
end
end

View File

@@ -11,7 +11,7 @@ class RSS::AccountSerializer
builder.title("#{display_name(account)} (@#{account.local_username_and_domain})")
.description(account_description(account))
.link(TagManager.instance.url_for(account))
.logo(full_asset_url(asset_pack_path('logo.svg')))
.logo(full_pack_url('media/images/logo.svg'))
.accent_color('2b90d9')
builder.image(full_asset_url(account.avatar.url(:original))) if account.avatar?

View File

@@ -12,7 +12,7 @@ class RSS::TagSerializer
builder.title("##{tag.name}")
.description(strip_tags(I18n.t('about.about_hashtag_html', hashtag: tag.name)))
.link(tag_url(tag))
.logo(full_asset_url(asset_pack_path('logo.svg')))
.logo(full_pack_url('media/images/logo.svg'))
.accent_color('2b90d9')
statuses.each do |status|

View File

@@ -29,7 +29,6 @@ class PostStatusService < BaseService
return idempotency_duplicate if idempotency_given? && idempotency_duplicate?
validate_media!
validate_poll!
preprocess_attributes!
if scheduled?
@@ -74,6 +73,7 @@ class PostStatusService < BaseService
def schedule_status!
status_for_validation = @account.statuses.build(status_attributes)
if status_for_validation.valid?
status_for_validation.destroy
@@ -110,12 +110,6 @@ class PostStatusService < BaseService
raise Mastodon::ValidationError, I18n.t('media_attachments.validations.images_and_video') if @media.size > 1 && @media.find(&:video?)
end
def validate_poll!
return if @options[:poll].blank?
@poll = @account.polls.new(@options[:poll])
end
def language_from_option(str)
ISO_639.find(str)&.alpha2
end
@@ -168,13 +162,13 @@ class PostStatusService < BaseService
text: @text,
media_attachments: @media || [],
thread: @in_reply_to,
owned_poll: @poll,
owned_poll_attributes: poll_attributes,
sensitive: (@options[:sensitive].nil? ? @account.user&.setting_default_sensitive : @options[:sensitive]) || @options[:spoiler_text].present?,
spoiler_text: @options[:spoiler_text] || '',
visibility: @visibility,
language: language_from_option(@options[:language]) || @account.user&.setting_default_language&.presence || LanguageDetector.instance.detect(@text, @account),
application: @options[:application],
}
}.compact
end
def scheduled_status_attributes
@@ -185,6 +179,12 @@ class PostStatusService < BaseService
}
end
def poll_attributes
return if @options[:poll].blank?
@options[:poll].merge(account: @account)
end
def scheduled_options
@options.tap do |options_hash|
options_hash[:in_reply_to_id] = options_hash.delete(:thread)&.id

View File

@@ -7,8 +7,9 @@ class ReblogService < BaseService
# Reblog a status and notify its remote author
# @param [Account] account Account to reblog from
# @param [Status] reblogged_status Status to be reblogged
# @param [Hash] options
# @return [Status]
def call(account, reblogged_status)
def call(account, reblogged_status, options = {})
reblogged_status = reblogged_status.reblog if reblogged_status.reblog?
authorize_with account, reblogged_status, :reblog?
@@ -17,7 +18,7 @@ class ReblogService < BaseService
return reblog unless reblog.nil?
reblog = account.statuses.create!(reblog: reblogged_status, text: '')
reblog = account.statuses.create!(reblog: reblogged_status, text: '', visibility: options[:visibility] || account.user&.setting_default_privacy)
DistributionWorker.perform_async(reblog.id)
@@ -38,7 +39,7 @@ class ReblogService < BaseService
reblogged_status = reblog.reblog
if reblogged_status.account.local?
NotifyService.new.call(reblogged_status.account, reblog)
LocalNotificationWorker.perform_async(reblogged_status.account_id, reblog.id, reblog.class.name)
elsif reblogged_status.account.ostatus?
NotificationWorker.perform_async(stream_entry_to_xml(reblog.stream_entry), reblog.account_id, reblogged_status.account_id)
elsif reblogged_status.account.activitypub? && !reblogged_status.account.following?(reblog.account)

View File

@@ -8,7 +8,7 @@
.column-0
.public-account-header.public-account-header--no-bar
.public-account-header__image
= image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('preview.jpg'), alt: @instance_presenter.site_title, class: 'parallax'
= image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('media/images/preview.jpg'), alt: @instance_presenter.site_title, class: 'parallax'
.column-1
.landing-page__call-to-action{ dir: 'ltr' }
@@ -24,7 +24,7 @@
%span= t 'about.status_count_after', count: @instance_presenter.status_count
.row__mascot
.landing-page__mascot
= image_tag @instance_presenter.mascot&.file&.url || asset_pack_path('elephant_ui_plane.svg'), alt: ''
= image_tag @instance_presenter.mascot&.file&.url || asset_pack_path('media/images/elephant_ui_plane.svg'), alt: ''
.column-2
.landing-page__information.contact-widget

View File

@@ -8,7 +8,7 @@
.landing
.landing__brand
= link_to root_url, class: 'brand' do
= image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
= image_pack_tag 'logo_full.svg', alt: 'Mastodon'
%span.brand__tagline=t 'about.tagline'
.landing__grid
@@ -48,7 +48,7 @@
.hero-widget
.hero-widget__img
= image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('preview.jpg'), alt: @instance_presenter.site_title
= image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('media/images/preview.jpg'), alt: @instance_presenter.site_title
- if @instance_presenter.site_short_description.present?
.hero-widget__text

View File

@@ -10,10 +10,7 @@
= image_tag invite.user.account.avatar.url(:original), alt: '', width: 16, height: 16, class: 'avatar'
%span.username= invite.user.account.username
- if invite.expired?
%td{ colspan: 2 }
= t('invites.expired')
- else
- if invite.valid_for_use?
%td
= fa_icon 'user fw'
= invite.uses
@@ -24,6 +21,10 @@
- else
%time.formatted{ datetime: invite.expires_at.iso8601, title: l(invite.expires_at) }
= l invite.expires_at
- else
%td{ colspan: 2 }
= t('invites.expired')
%td
- if !invite.expired? && policy(invite).destroy?
- if invite.valid_for_use? && policy(invite).destroy?
= table_link_to 'times', t('invites.delete'), admin_invite_path(invite), method: :delete

View File

@@ -1,6 +1,6 @@
.hero-widget
.hero-widget__img
= image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('preview.jpg'), alt: @instance_presenter.site_title
= image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('media/images/preview.jpg'), alt: @instance_presenter.site_title
.hero-widget__text
%p= @instance_presenter.site_short_description.html_safe.presence || @instance_presenter.site_description.html_safe.presence || t('about.generic_description', domain: site_hostname)

View File

@@ -9,7 +9,7 @@
.app-holder#mastodon{ data: { props: Oj.dump(default_props) } }
%noscript
= image_tag asset_pack_path('logo.svg'), alt: 'Mastodon'
= image_pack_tag 'logo.svg', alt: 'Mastodon'
%div
= t('errors.noscript_html', apps_path: 'https://joinmastodon.org/apps')

View File

@@ -5,10 +5,7 @@
%input{ type: :text, maxlength: '999', spellcheck: 'false', readonly: 'true', value: public_invite_url(invite_code: invite.code) }
%button{ type: :button }= t('generic.copy')
- if invite.expired?
%td{ colspan: 2 }
= t('invites.expired')
- else
- if invite.valid_for_use?
%td
= fa_icon 'user fw'
= invite.uses
@@ -19,7 +16,10 @@
- else
%time.formatted{ datetime: invite.expires_at.iso8601, title: l(invite.expires_at) }
= l invite.expires_at
- else
%td{ colspan: 2 }
= t('invites.expired')
%td
- if !invite.expired? && policy(invite).destroy?
- if invite.valid_for_use? && policy(invite).destroy?
= table_link_to 'times', t('invites.delete'), invite_path(invite), method: :delete

View File

@@ -3,7 +3,7 @@
.sidebar-wrapper
.sidebar
= link_to root_path do
= image_tag asset_pack_path('logo.svg'), class: 'logo', alt: 'Mastodon'
= image_pack_tag 'logo.svg', class: 'logo', alt: 'Mastodon'
= render_navigation
.content-wrapper

View File

@@ -3,7 +3,7 @@
.logo-container
%h1
= link_to root_path do
= image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
= image_pack_tag 'logo_full.svg', alt: 'Mastodon'
.form-container
= render 'flashes'

View File

@@ -24,7 +24,7 @@
%tr
%td.column-cell
= link_to root_url do
= image_tag full_pack_url('logo_full.png'), alt: 'Mastodon', height: 34, class: 'logo'
= image_tag full_pack_url('media/images/mailer/logo_full.png'), alt: 'Mastodon', height: 34, class: 'logo'
= yield
@@ -49,4 +49,4 @@
%p= link_to t('application_mailer.notification_preferences'), settings_notifications_url
%td.column-cell.text-right
= link_to root_url do
= image_tag full_pack_url('logo_transparent.png'), alt: 'Mastodon', height: 24
= image_tag full_pack_url('media/images/mailer/logo_transparent.png'), alt: 'Mastodon', height: 24

View File

@@ -5,7 +5,7 @@
%nav.header
.nav-left
= link_to root_url, class: 'brand' do
= image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'
= image_pack_tag 'logo_full.svg', alt: 'Mastodon'
= link_to t('directories.directory'), explore_path, class: 'nav-link optional' if Setting.profile_directory
= link_to t('about.about_this'), about_more_path, class: 'nav-link optional'

View File

@@ -17,7 +17,7 @@
%tbody
%tr
%td
= image_tag full_pack_url('icon_grade.png'), alt:''
= image_tag full_pack_url('media/images/mailer/icon_grade.png'), alt:''
%h1= t 'notification_mailer.favourite.title'
%p.lead= t('notification_mailer.favourite.body', name: @account.acct)

View File

@@ -17,7 +17,7 @@
%tbody
%tr
%td
= image_tag full_pack_url('icon_person_add.png'), alt: ''
= image_tag full_pack_url('media/images/mailer/icon_person_add.png'), alt: ''
%h1= t 'notification_mailer.follow.title'
%p.lead= t('notification_mailer.follow.body', name: @account.acct)

View File

@@ -17,7 +17,7 @@
%tbody
%tr
%td
= image_tag full_pack_url('icon_person_add.png'), alt: ''
= image_tag full_pack_url('media/images/mailer/icon_person_add.png'), alt: ''
%h1= t 'notification_mailer.follow_request.title'
%p.lead= t('notification_mailer.follow_request.body', name: @account.acct)

View File

@@ -17,7 +17,7 @@
%tbody
%tr
%td
= image_tag full_pack_url('icon_reply.png'), alt: ''
= image_tag full_pack_url('media/images/mailer/icon_reply.png'), alt: ''
%h1= t 'notification_mailer.mention.title'
%p.lead= t('notification_mailer.mention.body', name: @status.account.acct)

View File

@@ -17,7 +17,7 @@
%tbody
%tr
%td
= image_tag full_pack_url('icon_cached.png'), alt: ''
= image_tag full_pack_url('media/images/mailer/icon_cached.png'), alt: ''
%h1= t 'notification_mailer.reblog.title'
%p.lead= t('notification_mailer.reblog.body', name: @account.acct)

View File

@@ -0,0 +1,20 @@
.batch-table__row
%label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox
= f.check_box :account_ids, { multiple: true, include_hidden: false }, account.id
.batch-table__row__content.batch-table__row__content--unpadded
%table.accounts-table
%tbody
%tr
%td= account_link_to account
%td.accounts-table__count.optional
= number_to_human account.statuses_count, strip_insignificant_zeros: true
%small= t('accounts.posts', count: account.statuses_count).downcase
%td.accounts-table__count.optional
= 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')

View File

@@ -0,0 +1,43 @@
- content_for :page_title do
= t('settings.relationships')
- content_for :header_tags do
= javascript_pack_tag 'admin', integrity: true, async: true, crossorigin: 'anonymous'
.filters
.filter-subset
%strong= t 'relationships.relationship'
%ul
%li= filter_link_to t('accounts.following', count: current_account.following_count), relationship: nil
%li= filter_link_to t('accounts.followers', count: current_account.followers_count), relationship: 'followed_by'
%li= filter_link_to t('relationships.mutual'), relationship: 'mutual'
.filter-subset
%strong= t 'relationships.status'
%ul
%li= filter_link_to t('generic.all'), status: nil
%li= filter_link_to t('relationships.active'), status: 'active'
%li= filter_link_to t('relationships.abandoned'), status: 'abandoned'
= form_for(@form, url: relationships_path, method: :patch) do |f|
= hidden_field_tag :page, params[:page] || 1
= hidden_field_tag :relationship, params[:relationship]
= hidden_field_tag :status, params[:status]
.batch-table
.batch-table__toolbar
%label.batch-table__toolbar__select.batch-checkbox-all
= check_box_tag :batch_checkbox_all, nil, false
.batch-table__toolbar__actions
= f.button safe_join([fa_icon('user-times'), t('relationships.remove_selected_follows')]), name: :unfollow, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } unless followed_by_relationship?
= f.button safe_join([fa_icon('trash'), t('relationships.remove_selected_followers')]), name: :remove_from_followers, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } unless following_relationship?
= f.button safe_join([fa_icon('trash'), t('relationships.remove_selected_domains')]), name: :block_domains, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } if followed_by_relationship?
.batch-table__body
- if @accounts.empty?
= nothing_here 'nothing-here--under-tabs'
- else
= render partial: 'account', collection: @accounts, locals: { f: f }
= paginate @accounts

View File

@@ -1,34 +0,0 @@
- content_for :page_title do
= t('settings.followers')
= form_tag settings_follower_domains_path, method: :patch, class: 'table-form' do
- unless @account.locked?
.warning
%strong
= fa_icon('warning')
= t('followers.unlocked_warning_title')
= t('followers.unlocked_warning_html', lock_link: link_to(t('followers.lock_link'), settings_profile_url))
%p= t('followers.explanation_html')
%p= t('followers.true_privacy_html')
.table-wrapper
%table.table
%thead
%tr
%th
%th= t('followers.domain')
%th= t('followers.followers_count')
%tbody
- @domains.each do |domain|
%tr
%td
= check_box_tag 'select[]', domain.domain, false, disabled: !@account.locked? unless domain.domain.nil?
%td
%samp= domain.domain.presence || Rails.configuration.x.local_domain
%td= number_with_delimiter domain.accounts_from_domain
.action-pagination
.actions
= button_tag t('followers.purge'), type: :submit, class: 'button', disabled: !@account.locked?
= paginate @domains

View File

@@ -8,7 +8,7 @@
= opengraph 'og:type', 'website'
= opengraph 'og:title', @instance_presenter.site_title
= opengraph 'og:description', description
= opengraph 'og:image', full_asset_url(thumbnail&.file&.url || asset_pack_path('preview.jpg', protocol: :request))
= opengraph 'og:image', full_asset_url(thumbnail&.file&.url || asset_pack_path('media/images/preview.jpg', protocol: :request))
= opengraph 'og:image:width', thumbnail ? thumbnail.meta['width'] : '1200'
= opengraph 'og:image:height', thumbnail ? thumbnail.meta['height'] : '630'
= opengraph 'twitter:card', 'summary_large_image'

View File

@@ -17,7 +17,7 @@
%tbody
%tr
%td
= image_tag full_pack_url('icon_file_download.png'), alt: ''
= image_tag full_pack_url('media/images/mailer/icon_file_download.png'), alt: ''
%h1= t 'user_mailer.backup_ready.title'

View File

@@ -17,7 +17,7 @@
%tbody
%tr
%td
= image_tag full_pack_url('icon_email.png'), alt: ''
= image_tag full_pack_url('media/images/mailer/icon_email.png'), alt: ''
%h1= t 'devise.mailer.confirmation_instructions.title'

View File

@@ -17,7 +17,7 @@
%tbody
%tr
%td
= image_tag full_pack_url('icon_email.png'), alt: ''
= image_tag full_pack_url('media/images/mailer/icon_email.png'), alt: ''
%h1= t 'devise.mailer.email_changed.title'
%p.lead= t 'devise.mailer.email_changed.explanation'

View File

@@ -17,7 +17,7 @@
%tbody
%tr
%td
= image_tag full_pack_url('icon_lock_open.png'), alt: ''
= image_tag full_pack_url('media/images/mailer/icon_lock_open.png'), alt: ''
%h1= t 'devise.mailer.password_change.title'
%p.lead= t 'devise.mailer.password_change.explanation'

View File

@@ -17,7 +17,7 @@
%tbody
%tr
%td
= image_tag full_pack_url('icon_email.png'), alt: ''
= image_tag full_pack_url('media/images/mailer/icon_email.png'), alt: ''
%h1= t 'devise.mailer.reconfirmation_instructions.title'
%p.lead= t 'devise.mailer.reconfirmation_instructions.explanation'

View File

@@ -17,7 +17,7 @@
%tbody
%tr
%td
= image_tag full_pack_url('icon_lock_open.png'), alt: ''
= image_tag full_pack_url('media/images/mailer/icon_lock_open.png'), alt: ''
%h1= t 'devise.mailer.reset_password_instructions.title'
%p.lead= t 'devise.mailer.reset_password_instructions.explanation'

View File

@@ -17,7 +17,7 @@
%tbody
%tr
%td
= image_tag full_pack_url('icon_warning.png'), alt: ''
= image_tag full_pack_url('media/images/mailer/icon_warning.png'), alt: ''
%h1= t "user_mailer.warning.title.#{@warning.action}"

View File

@@ -17,7 +17,7 @@
%tbody
%tr
%td
= image_tag full_pack_url('icon_done.png'), alt: ''
= image_tag full_pack_url('media/images/mailer/icon_done.png'), alt: ''
%h1= t 'user_mailer.welcome.title', name: @resource.account.username
%p.lead= t 'user_mailer.welcome.explanation'