Merge commit '39110d1d0af5e3d9cf452ae47496a52797249fd0' into glitch-soc/merge-upstream

This commit is contained in:
Claire
2023-06-18 10:36:14 +02:00
76 changed files with 2108 additions and 1483 deletions

View File

@@ -2,6 +2,7 @@
module CaptchaConcern
extend ActiveSupport::Concern
include Hcaptcha::Adapters::ViewMethods
included do
@@ -35,18 +36,22 @@ module CaptchaConcern
flash.delete(:hcaptcha_error)
yield message
end
false
end
end
def extend_csp_for_captcha!
policy = request.content_security_policy
return unless captcha_required? && policy.present?
%w(script_src frame_src style_src connect_src).each do |directive|
values = policy.send(directive)
values << 'https://hcaptcha.com' unless values.include?('https://hcaptcha.com') || values.include?('https:')
values << 'https://*.hcaptcha.com' unless values.include?('https://*.hcaptcha.com') || values.include?('https:')
policy.send(directive, *values)
end
end

View File

@@ -0,0 +1,41 @@
# frozen_string_literal: true
class MailSubscriptionsController < ApplicationController
layout 'auth'
skip_before_action :require_functional!
before_action :set_body_classes
before_action :set_user
before_action :set_type
def show; end
def create
@user.settings[email_type_from_param] = false
@user.save!
end
private
def set_user
@user = GlobalID::Locator.locate_signed(params[:token], for: 'unsubscribe')
end
def set_body_classes
@body_classes = 'lighter'
end
def set_type
@type = email_type_from_param
end
def email_type_from_param
case params[:type]
when 'follow', 'reblog', 'favourite', 'mention', 'follow_request'
"notification_emails.#{params[:type]}"
else
raise ArgumentError
end
end
end

View File

@@ -0,0 +1,27 @@
interface Props {
size: number;
strokeWidth: number;
}
export const CircularProgress: React.FC<Props> = ({ size, strokeWidth }) => {
const viewBox = `0 0 ${size} ${size}`;
const radius = (size - strokeWidth) / 2;
return (
<svg
width={size}
height={size}
viewBox={viewBox}
className='circular-progress'
role='progressbar'
>
<circle
fill='none'
cx={size / 2}
cy={size / 2}
r={radius}
strokeWidth={`${strokeWidth}px`}
/>
</svg>
);
};

View File

@@ -8,8 +8,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { supportsPassiveEvents } from 'detect-passive-events';
import Overlay from 'react-overlays/Overlay';
import { CircularProgress } from 'mastodon/components/loading_indicator';
import { CircularProgress } from "./circular_progress";
import { IconButton } from './icon_button';
const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true;

View File

@@ -1,23 +0,0 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { FormattedMessage } from 'react-intl';
export default class LoadPending extends PureComponent {
static propTypes = {
onClick: PropTypes.func,
count: PropTypes.number,
};
render() {
const { count } = this.props;
return (
<button className='load-more load-gap' onClick={this.props.onClick}>
<FormattedMessage id='load_pending' defaultMessage='{count, plural, one {# new item} other {# new items}}' values={{ count }} />
</button>
);
}
}

View File

@@ -0,0 +1,18 @@
import { FormattedMessage } from 'react-intl';
interface Props {
onClick: (event: React.MouseEvent) => void;
count: number;
}
export const LoadPending: React.FC<Props> = ({ onClick, count }) => {
return (
<button className='load-more load-gap' onClick={onClick}>
<FormattedMessage
id='load_pending'
defaultMessage='{count, plural, one {# new item} other {# new items}}'
values={{ count }}
/>
</button>
);
};

View File

@@ -1,31 +0,0 @@
import PropTypes from 'prop-types';
export const CircularProgress = ({ size, strokeWidth }) => {
const viewBox = `0 0 ${size} ${size}`;
const radius = (size - strokeWidth) / 2;
return (
<svg width={size} height={size} viewBox={viewBox} className='circular-progress' role='progressbar'>
<circle
fill='none'
cx={size / 2}
cy={size / 2}
r={radius}
strokeWidth={`${strokeWidth}px`}
/>
</svg>
);
};
CircularProgress.propTypes = {
size: PropTypes.number.isRequired,
strokeWidth: PropTypes.number.isRequired,
};
const LoadingIndicator = () => (
<div className='loading-indicator'>
<CircularProgress size={50} strokeWidth={6} />
</div>
);
export default LoadingIndicator;

View File

@@ -0,0 +1,7 @@
import { CircularProgress } from './circular_progress';
export const LoadingIndicator: React.FC = () => (
<div className='loading-indicator'>
<CircularProgress size={50} strokeWidth={6} />
</div>
);

View File

@@ -16,8 +16,8 @@ import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from
import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper';
import { LoadMore } from './load_more';
import LoadPending from './load_pending';
import LoadingIndicator from './loading_indicator';
import { LoadPending } from './load_pending';
import { LoadingIndicator } from './loading_indicator';
const MOUSE_IDLE_DELAY = 300;

View File

@@ -374,7 +374,7 @@ class Header extends ImmutablePureComponent {
let badge;
if (account.get('bot')) {
badge = (<div className='account-role bot'><FormattedMessage id='account.badges.bot' defaultMessage='Bot' /></div>);
badge = (<div className='account-role bot'><FormattedMessage id='account.badges.bot' defaultMessage='Automated' /></div>);
} else if (account.get('group')) {
badge = (<div className='account-role group'><FormattedMessage id='account.badges.group' defaultMessage='Group' /></div>);
} else {

View File

@@ -10,7 +10,7 @@ import { lookupAccount, fetchAccount } from 'mastodon/actions/accounts';
import { openModal } from 'mastodon/actions/modal';
import ColumnBackButton from 'mastodon/components/column_back_button';
import { LoadMore } from 'mastodon/components/load_more';
import LoadingIndicator from 'mastodon/components/loading_indicator';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import ScrollContainer from 'mastodon/containers/scroll_container';
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
import { normalizeForLookup } from 'mastodon/reducers/accounts_map';

View File

@@ -17,7 +17,7 @@ import { lookupAccount, fetchAccount } from '../../actions/accounts';
import { fetchFeaturedTags } from '../../actions/featured_tags';
import { expandAccountFeaturedTimeline, expandAccountTimeline, connectTimeline, disconnectTimeline } from '../../actions/timelines';
import ColumnBackButton from '../../components/column_back_button';
import LoadingIndicator from '../../components/loading_indicator';
import { LoadingIndicator } from '../../components/loading_indicator';
import StatusList from '../../components/status_list';
import Column from '../ui/components/column';

View File

@@ -10,7 +10,7 @@ import { debounce } from 'lodash';
import { fetchBlocks, expandBlocks } from '../../actions/blocks';
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import LoadingIndicator from '../../components/loading_indicator';
import { LoadingIndicator } from '../../components/loading_indicator';
import ScrollableList from '../../components/scrollable_list';
import AccountContainer from '../../containers/account_container';
import Column from '../ui/components/column';

View File

@@ -14,7 +14,7 @@ import { fetchDirectory, expandDirectory } from 'mastodon/actions/directory';
import Column from 'mastodon/components/column';
import ColumnHeader from 'mastodon/components/column_header';
import { LoadMore } from 'mastodon/components/load_more';
import LoadingIndicator from 'mastodon/components/loading_indicator';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import { RadioButton } from 'mastodon/components/radio_button';
import ScrollContainer from 'mastodon/containers/scroll_container';

View File

@@ -12,7 +12,7 @@ import { debounce } from 'lodash';
import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks';
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import LoadingIndicator from '../../components/loading_indicator';
import { LoadingIndicator } from '../../components/loading_indicator';
import ScrollableList from '../../components/scrollable_list';
import DomainContainer from '../../containers/domain_container';
import Column from '../ui/components/column';

View File

@@ -0,0 +1,51 @@
import type { BaseEmoji, EmojiData, NimbleEmojiIndex } from 'emoji-mart';
import type { Category, Data, Emoji } from 'emoji-mart/dist-es/utils/data';
/*
* The 'search' property, although not defined in the [`Emoji`]{@link node_modules/@types/emoji-mart/dist-es/utils/data.d.ts#Emoji} type,
* is used in the application.
* This could be due to an oversight by the library maintainer.
* The `search` property is defined and used [here]{@link node_modules/emoji-mart/dist/utils/data.js#uncompress}.
*/
export type Search = string;
/*
* The 'skins' property does not exist in the application data.
* This could be a potential area of refactoring or error handling.
* The non-existence of 'skins' property is evident at [this location]{@link app/javascript/mastodon/features/emoji/emoji_compressed.js:121}.
*/
export type Skins = null;
export type FilenameData = string[] | string[][];
export type ShortCodesToEmojiDataKey =
| EmojiData['id']
| BaseEmoji['native']
| keyof NimbleEmojiIndex['emojis'];
export type SearchData = [
BaseEmoji['native'],
Emoji['short_names'],
Search,
Emoji['unified']
];
export interface ShortCodesToEmojiData {
[key: ShortCodesToEmojiDataKey]: [FilenameData, SearchData];
}
export type EmojisWithoutShortCodes = FilenameData[];
export type EmojiCompressed = [
ShortCodesToEmojiData,
Skins,
Category[],
Data['aliases'],
EmojisWithoutShortCodes
];
/*
* `emoji_compressed.js` uses `babel-plugin-preval`, which makes it difficult to convert to TypeScript.
* As a temporary solution, we are allowing a default export here to apply the TypeScript type `EmojiCompressed` to the JS file export.
* - {@link app/javascript/mastodon/features/emoji/emoji_compressed.js}
*/
declare const emojiCompressed: EmojiCompressed;
export default emojiCompressed; // eslint-disable-line import/no-default-export

View File

@@ -118,6 +118,16 @@ Object.keys(emojiIndex.emojis).forEach(key => {
// inconsistent behavior in dev mode
module.exports = JSON.parse(JSON.stringify([
shortCodesToEmojiData,
/*
* The property `skins` is not found in the current context.
* This could potentially lead to issues when interacting with modules or data structures
* that expect the presence of `skins` property.
* Currently, no definitions or references to `skins` property can be found in:
* - {@link node_modules/emoji-mart/dist/utils/data.js}
* - {@link node_modules/emoji-mart/data/all.json}
* - {@link app/javascript/mastodon/features/emoji/emoji_compressed.d.ts#Skins}
* Future refactorings or updates should consider adding definitions or handling for `skins` property.
*/
emojiMartData.skins,
emojiMartData.categories,
emojiMartData.aliases,

View File

@@ -1,43 +0,0 @@
// The output of this module is designed to mimic emoji-mart's
// "data" object, such that we can use it for a light version of emoji-mart's
// emojiIndex.search functionality.
import emojiCompressed from './emoji_compressed';
import { unicodeToUnifiedName } from './unicode_to_unified_name';
const [ shortCodesToEmojiData, skins, categories, short_names ] = emojiCompressed;
const emojis = {};
// decompress
Object.keys(shortCodesToEmojiData).forEach((shortCode) => {
let [
filenameData, // eslint-disable-line @typescript-eslint/no-unused-vars
searchData,
] = shortCodesToEmojiData[shortCode];
let [
native,
short_names,
search,
unified,
] = searchData;
if (!unified) {
// unified name can be derived from unicodeToUnifiedName
unified = unicodeToUnifiedName(native);
}
short_names = [shortCode].concat(short_names);
emojis[shortCode] = {
native,
search,
short_names,
unified,
};
});
export {
emojis,
skins,
categories,
short_names,
};

View File

@@ -0,0 +1,52 @@
// The output of this module is designed to mimic emoji-mart's
// "data" object, such that we can use it for a light version of emoji-mart's
// emojiIndex.search functionality.
import type { BaseEmoji } from 'emoji-mart';
import type { Emoji } from 'emoji-mart/dist-es/utils/data';
import type { Search, ShortCodesToEmojiData } from './emoji_compressed';
import emojiCompressed from './emoji_compressed';
import { unicodeToUnifiedName } from './unicode_to_unified_name';
type Emojis = {
[key in keyof ShortCodesToEmojiData]: {
native: BaseEmoji['native'];
search: Search;
short_names: Emoji['short_names'];
unified: Emoji['unified'];
};
};
const [
shortCodesToEmojiData,
skins,
categories,
short_names,
_emojisWithoutShortCodes,
] = emojiCompressed;
const emojis: Emojis = {};
// decompress
Object.keys(shortCodesToEmojiData).forEach((shortCode) => {
const [_filenameData, searchData] = shortCodesToEmojiData[shortCode];
const native = searchData[0];
let short_names = searchData[1];
const search = searchData[2];
let unified = searchData[3];
if (!unified) {
// unified name can be derived from unicodeToUnifiedName
unified = unicodeToUnifiedName(native);
}
if (short_names) short_names = [shortCode].concat(short_names);
emojis[shortCode] = {
native,
search,
short_names,
unified,
};
});
export { emojis, skins, categories, short_names };

View File

@@ -8,7 +8,7 @@ import { connect } from 'react-redux';
import { fetchTrendingLinks } from 'mastodon/actions/trends';
import DismissableBanner from 'mastodon/components/dismissable_banner';
import LoadingIndicator from 'mastodon/components/loading_indicator';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import Story from './components/story';

View File

@@ -12,7 +12,7 @@ import { connect } from 'react-redux';
import { expandSearch } from 'mastodon/actions/search';
import { ImmutableHashtag as Hashtag } from 'mastodon/components/hashtag';
import { LoadMore } from 'mastodon/components/load_more';
import LoadingIndicator from 'mastodon/components/loading_indicator';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import Account from 'mastodon/containers/account_container';
import Status from 'mastodon/containers/status_container';

View File

@@ -7,7 +7,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import { fetchSuggestions } from 'mastodon/actions/suggestions';
import LoadingIndicator from 'mastodon/components/loading_indicator';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import AccountCard from 'mastodon/features/directory/components/account_card';
const mapStateToProps = state => ({

View File

@@ -9,7 +9,7 @@ import { connect } from 'react-redux';
import { fetchTrendingHashtags } from 'mastodon/actions/trends';
import DismissableBanner from 'mastodon/components/dismissable_banner';
import { ImmutableHashtag as Hashtag } from 'mastodon/components/hashtag';
import LoadingIndicator from 'mastodon/components/loading_indicator';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
const mapStateToProps = state => ({
hashtags: state.getIn(['trends', 'tags', 'items']),

View File

@@ -11,7 +11,7 @@ import { connect } from 'react-redux';
import { fetchFavourites } from 'mastodon/actions/interactions';
import ColumnHeader from 'mastodon/components/column_header';
import { Icon } from 'mastodon/components/icon';
import LoadingIndicator from 'mastodon/components/loading_indicator';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import ScrollableList from 'mastodon/components/scrollable_list';
import AccountContainer from 'mastodon/containers/account_container';
import Column from 'mastodon/features/ui/components/column';

View File

@@ -20,7 +20,7 @@ import {
expandFollowers,
} from '../../actions/accounts';
import ColumnBackButton from '../../components/column_back_button';
import LoadingIndicator from '../../components/loading_indicator';
import { LoadingIndicator } from '../../components/loading_indicator';
import ScrollableList from '../../components/scrollable_list';
import AccountContainer from '../../containers/account_container';
import LimitedAccountHint from '../account_timeline/components/limited_account_hint';

View File

@@ -20,7 +20,7 @@ import {
expandFollowing,
} from '../../actions/accounts';
import ColumnBackButton from '../../components/column_back_button';
import LoadingIndicator from '../../components/loading_indicator';
import { LoadingIndicator } from '../../components/loading_indicator';
import ScrollableList from '../../components/scrollable_list';
import AccountContainer from '../../containers/account_container';
import LimitedAccountHint from '../account_timeline/components/limited_account_hint';

View File

@@ -18,7 +18,7 @@ import { expandListTimeline } from 'mastodon/actions/timelines';
import Column from 'mastodon/components/column';
import ColumnHeader from 'mastodon/components/column_header';
import { Icon } from 'mastodon/components/icon';
import LoadingIndicator from 'mastodon/components/loading_indicator';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import { RadioButton } from 'mastodon/components/radio_button';
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
import StatusListContainer from 'mastodon/features/ui/containers/status_list_container';

View File

@@ -12,7 +12,7 @@ import { createSelector } from 'reselect';
import { fetchLists } from 'mastodon/actions/lists';
import Column from 'mastodon/components/column';
import ColumnHeader from 'mastodon/components/column_header';
import LoadingIndicator from 'mastodon/components/loading_indicator';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import ScrollableList from 'mastodon/components/scrollable_list';
import ColumnLink from 'mastodon/features/ui/components/column_link';
import ColumnSubheading from 'mastodon/features/ui/components/column_subheading';

View File

@@ -12,7 +12,7 @@ import { debounce } from 'lodash';
import { fetchMutes, expandMutes } from '../../actions/mutes';
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import LoadingIndicator from '../../components/loading_indicator';
import { LoadingIndicator } from '../../components/loading_indicator';
import ScrollableList from '../../components/scrollable_list';
import AccountContainer from '../../containers/account_container';
import Column from '../ui/components/column';

View File

@@ -8,10 +8,12 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import { AvatarOverlay } from 'mastodon/components/avatar_overlay';
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
// This needs to be kept in sync with app/models/report.rb
const messages = defineMessages({
openReport: { id: 'report_notification.open', defaultMessage: 'Open report' },
other: { id: 'report_notification.categories.other', defaultMessage: 'Other' },
spam: { id: 'report_notification.categories.spam', defaultMessage: 'Spam' },
legal: { id: 'report_notification.categories.legal', defaultMessage: 'Legal' },
violation: { id: 'report_notification.categories.violation', defaultMessage: 'Rule violation' },
});

View File

@@ -12,7 +12,7 @@ import { Icon } from 'mastodon/components/icon';
import { fetchReblogs } from '../../actions/interactions';
import ColumnHeader from '../../components/column_header';
import LoadingIndicator from '../../components/loading_indicator';
import { LoadingIndicator } from '../../components/loading_indicator';
import ScrollableList from '../../components/scrollable_list';
import AccountContainer from '../../containers/account_container';
import Column from '../ui/components/column';

View File

@@ -8,7 +8,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import Button from 'mastodon/components/button';
import LoadingIndicator from 'mastodon/components/loading_indicator';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import StatusCheckBox from 'mastodon/features/report/containers/status_check_box_container';
const mapStateToProps = (state, { accountId }) => ({

View File

@@ -14,7 +14,7 @@ import { createSelector } from 'reselect';
import { HotKeys } from 'react-hotkeys';
import { Icon } from 'mastodon/components/icon';
import LoadingIndicator from 'mastodon/components/loading_indicator';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import ScrollContainer from 'mastodon/containers/scroll_container';
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';

View File

@@ -1,4 +1,4 @@
import LoadingIndicator from '../../../components/loading_indicator';
import { LoadingIndicator } from '../../../components/loading_indicator';
// Keep the markup in sync with <BundleModalError />
// (make sure they have the same dimensions)

View File

@@ -13,7 +13,7 @@
"about.rules": "Server rules",
"account.account_note_header": "Note",
"account.add_or_remove_from_list": "Add or Remove from lists",
"account.badges.bot": "Bot",
"account.badges.bot": "Automated",
"account.badges.group": "Group",
"account.block": "Block @{name}",
"account.block_domain": "Block domain {domain}",
@@ -553,6 +553,7 @@
"report.unfollow": "Unfollow @{name}",
"report.unfollow_explanation": "You are following this account. To not see their posts in your home feed anymore, unfollow them.",
"report_notification.attached_statuses": "{count, plural, one {{count} post} other {{count} posts}} attached",
"report_notification.categories.legal": "Legal",
"report_notification.categories.other": "Other",
"report_notification.categories.spam": "Spam",
"report_notification.categories.violation": "Rule violation",

View File

@@ -1048,7 +1048,9 @@ code {
}
.simple_form .h-captcha {
text-align: center;
display: flex;
justify-content: center;
margin-bottom: 30px;
}
.permissions-list {

View File

@@ -8,61 +8,71 @@ class NotificationMailer < ApplicationMailer
def mention(recipient, notification)
@me = recipient
@user = recipient.user
@type = 'mention'
@status = notification.target_status
return unless @me.user.functional? && @status.present?
return unless @user.functional? && @status.present?
locale_for_account(@me) do
thread_by_conversation(@status.conversation)
mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.mention.subject', name: @status.account.acct)
mail to: email_address_with_name(@user.email, @me.username), subject: I18n.t('notification_mailer.mention.subject', name: @status.account.acct)
end
end
def follow(recipient, notification)
@me = recipient
@user = recipient.user
@type = 'follow'
@account = notification.from_account
return unless @me.user.functional?
return unless @user.functional?
locale_for_account(@me) do
mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.follow.subject', name: @account.acct)
mail to: email_address_with_name(@user.email, @me.username), subject: I18n.t('notification_mailer.follow.subject', name: @account.acct)
end
end
def favourite(recipient, notification)
@me = recipient
@user = recipient.user
@type = 'favourite'
@account = notification.from_account
@status = notification.target_status
return unless @me.user.functional? && @status.present?
return unless @user.functional? && @status.present?
locale_for_account(@me) do
thread_by_conversation(@status.conversation)
mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.favourite.subject', name: @account.acct)
mail to: email_address_with_name(@user.email, @me.username), subject: I18n.t('notification_mailer.favourite.subject', name: @account.acct)
end
end
def reblog(recipient, notification)
@me = recipient
@user = recipient.user
@type = 'reblog'
@account = notification.from_account
@status = notification.target_status
return unless @me.user.functional? && @status.present?
return unless @user.functional? && @status.present?
locale_for_account(@me) do
thread_by_conversation(@status.conversation)
mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.reblog.subject', name: @account.acct)
mail to: email_address_with_name(@user.email, @me.username), subject: I18n.t('notification_mailer.reblog.subject', name: @account.acct)
end
end
def follow_request(recipient, notification)
@me = recipient
@user = recipient.user
@type = 'follow_request'
@account = notification.from_account
return unless @me.user.functional?
return unless @user.functional?
locale_for_account(@me) do
mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.follow_request.subject', name: @account.acct)
mail to: email_address_with_name(@user.email, @me.username), subject: I18n.t('notification_mailer.follow_request.subject', name: @account.acct)
end
end

View File

@@ -48,6 +48,7 @@ class Report < ApplicationRecord
validate :validate_rule_ids
# entries here needs to be kept in sync with app/javascript/mastodon/features/notifications/components/report.jsx
enum category: {
other: 0,
spam: 1_000,

View File

@@ -7,10 +7,12 @@
= hidden_field_tag :confirmation_token, params[:confirmation_token]
= hidden_field_tag :redirect_to_app, params[:redirect_to_app]
%h1.title= t('auth.captcha_confirmation.title')
%p.lead= t('auth.captcha_confirmation.hint_html')
.field-group
= render_captcha
= render_captcha
%p.lead= t('auth.captcha_confirmation.help_html', email: mail_to(Setting.site_contact_email, nil))
.actions
%button.button= t('challenge.confirm')
= button_tag t('challenge.confirm'), class: 'button', type: :submit

View File

@@ -44,7 +44,11 @@
%tbody
%td.column-cell
%p= t 'about.hosted_on', domain: site_hostname
%p= link_to t('application_mailer.notification_preferences'), settings_preferences_notifications_url
%p
= link_to t('application_mailer.notification_preferences'), settings_preferences_notifications_url
- if defined?(@type)
·
= link_to t('application_mailer.unsubscribe'), unsubscribe_url(token: @user.to_sgid(for: 'unsubscribe').to_s, type: @type)
%td.column-cell.text-right
= link_to root_url do
= image_tag full_pack_url('media/images/mailer/logo.png'), alt: 'Mastodon', height: 24

View File

@@ -0,0 +1,9 @@
- content_for :page_title do
= t('mail_subscriptions.unsubscribe.title')
.simple_form
%h1.title= t('mail_subscriptions.unsubscribe.complete')
%p.lead
= t('mail_subscriptions.unsubscribe.success_html', domain: content_tag(:strong, site_hostname), type: content_tag(:strong, I18n.t(@type, scope: 'mail_subscriptions.unsubscribe.emails')), email: content_tag(:strong, @user.email))
%p.lead
= t('mail_subscriptions.unsubscribe.resubscribe_html', settings_path: settings_preferences_notifications_path)

View File

@@ -0,0 +1,12 @@
- content_for :page_title do
= t('mail_subscriptions.unsubscribe.title')
.simple_form
%h1.title= t('mail_subscriptions.unsubscribe.title')
%p.lead
= t('mail_subscriptions.unsubscribe.confirmation_html', domain: content_tag(:strong, site_hostname), type: content_tag(:strong, I18n.t(@type, scope: 'mail_subscriptions.unsubscribe.emails')), email: content_tag(:strong, @user.email), settings_path: settings_preferences_notifications_path)
= form_tag unsubscribe_path, method: :post do
= hidden_field_tag :token, params[:token]
= hidden_field_tag :type, params[:type]
= button_tag t('mail_subscriptions.unsubscribe.action'), type: :submit