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:
12
app/controllers/api/v1/preferences_controller.rb
Normal file
12
app/controllers/api/v1/preferences_controller.rb
Normal 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
|
@@ -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
|
||||
|
98
app/controllers/relationships_controller.rb
Normal file
98
app/controllers/relationships_controller.rb
Normal 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
|
@@ -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
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
39
app/javascript/mastodon/components/error_boundary.js
Normal file
39
app/javascript/mastodon/components/error_boundary.js
Normal 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>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@@ -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>
|
||||
);
|
||||
|
@@ -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) {
|
||||
|
@@ -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",
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
|
60
app/models/form/account_batch.rb
Normal file
60
app/models/form/account_batch.rb
Normal 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
|
@@ -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 }
|
||||
|
||||
|
@@ -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
|
||||
|
30
app/serializers/rest/preferences_serializer.rb
Normal file
30
app/serializers/rest/preferences_serializer.rb
Normal 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
|
@@ -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?
|
||||
|
@@ -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|
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
@@ -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')
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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'
|
||||
|
@@ -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
|
||||
|
@@ -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'
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
20
app/views/relationships/_account.html.haml
Normal file
20
app/views/relationships/_account.html.haml
Normal 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')
|
43
app/views/relationships/show.html.haml
Normal file
43
app/views/relationships/show.html.haml
Normal 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
|
@@ -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
|
@@ -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'
|
||||
|
@@ -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'
|
||||
|
||||
|
@@ -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'
|
||||
|
||||
|
@@ -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'
|
||||
|
@@ -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'
|
||||
|
@@ -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'
|
||||
|
@@ -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'
|
||||
|
@@ -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}"
|
||||
|
||||
|
@@ -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'
|
||||
|
Reference in New Issue
Block a user