Merge pull request #2251 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changesth-downstream
commit
f3c2035fce
@ -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
|
@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Settings::VerificationsController < Settings::BaseController
|
||||
before_action :set_account
|
||||
|
||||
def show
|
||||
@verified_links = @account.fields.select(&:verified?)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = current_account
|
||||
end
|
||||
end
|
@ -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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -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>
|
||||
);
|
||||
};
|
@ -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} heigh={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;
|
@ -0,0 +1,7 @@
|
||||
import { CircularProgress } from './circular_progress';
|
||||
|
||||
export const LoadingIndicator: React.FC = () => (
|
||||
<div className='loading-indicator'>
|
||||
<CircularProgress size={50} strokeWidth={6} />
|
||||
</div>
|
||||
);
|
@ -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/flavours/glitch/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/flavours/glitch/features/emoji/emoji_compressed.js}
|
||||
*/
|
||||
declare const emojiCompressed: EmojiCompressed;
|
||||
|
||||
export default emojiCompressed; // eslint-disable-line import/no-default-export
|
@ -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,
|
||||
};
|
@ -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 };
|
@ -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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -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>
|
||||
);
|
||||
};
|
@ -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;
|
@ -0,0 +1,7 @@
|
||||
import { CircularProgress } from './circular_progress';
|
||||
|
||||
export const LoadingIndicator: React.FC = () => (
|
||||
<div className='loading-indicator'>
|
||||
<CircularProgress size={50} strokeWidth={6} />
|
||||
</div>
|
||||
);
|
@ -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
|
@ -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,
|
||||
};
|
@ -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 };
|
@ -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)
|
@ -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
|
@ -1,76 +1,76 @@
|
||||
- content_for :page_title do
|
||||
= t('settings.edit_profile')
|
||||
|
||||
- content_for :heading_actions do
|
||||
= button_tag t('generic.save_changes'), class: 'button', form: 'edit_profile'
|
||||
- content_for :heading do
|
||||
%h2= t('settings.profile')
|
||||
= render partial: 'settings/shared/profile_navigation'
|
||||
|
||||
= simple_form_for @account, url: settings_profile_path, html: { method: :put, id: 'edit_profile' } do |f|
|
||||
= render 'shared/error_messages', object: @account
|
||||
|
||||
.fields-row
|
||||
.fields-row__column.fields-group.fields-row__column-6
|
||||
= f.input :display_name, wrapper: :with_label, input_html: { maxlength: Account::MAX_DISPLAY_NAME_LENGTH, data: { default: @account.username } }, hint: false
|
||||
= f.input :note, wrapper: :with_label, input_html: { maxlength: Account::MAX_NOTE_LENGTH }, hint: false
|
||||
%p.lead= t('edit_profile.hint_html')
|
||||
|
||||
%h4= t('edit_profile.basic_information')
|
||||
|
||||
.fields-row
|
||||
.fields-row__column.fields-row__column-6
|
||||
= render 'application/card', account: @account
|
||||
|
||||
.fields-row__column.fields-group.fields-row__column-6
|
||||
= f.input :header, wrapper: :with_label, input_html: { accept: AccountHeader::IMAGE_MIME_TYPES.join(',') }, hint: picture_hint(t('simple_form.hints.defaults.header', dimensions: '1500x500', size: number_to_human_size(AccountHeader::LIMIT)), @account.header)
|
||||
|
||||
= f.input :avatar, wrapper: :with_label, input_html: { accept: AccountAvatar::IMAGE_MIME_TYPES.join(',') }, hint: picture_hint(t('simple_form.hints.defaults.avatar', dimensions: '400x400', size: number_to_human_size(AccountAvatar::LIMIT)), @account.avatar)
|
||||
|
||||
%hr.spacer/
|
||||
|
||||
.fields-group
|
||||
= f.input :locked, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.locked')
|
||||
.fields-group
|
||||
= f.input :display_name, wrapper: :with_block_label, input_html: { maxlength: Account::MAX_DISPLAY_NAME_LENGTH, data: { default: @account.username } }
|
||||
|
||||
.fields-group
|
||||
= f.input :bot, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.bot')
|
||||
|
||||
.fields-group
|
||||
= f.input :discoverable, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.discoverable'), recommended: true
|
||||
.fields-group
|
||||
= f.input :note, wrapper: :with_block_label, input_html: { maxlength: Account::MAX_NOTE_LENGTH }
|
||||
|
||||
.fields-group
|
||||
= f.input :hide_collections, as: :boolean, wrapper: :with_label, label: t('simple_form.labels.defaults.setting_hide_network'), hint: t('simple_form.hints.defaults.setting_hide_network')
|
||||
|
||||
%hr.spacer/
|
||||
|
||||
.fields-row
|
||||
.fields-row__column.fields-group.fields-row__column-6
|
||||
.input.with_block_label
|
||||
%label= t('simple_form.labels.defaults.fields')
|
||||
%span.hint= t('simple_form.hints.defaults.fields', count: Account::DEFAULT_FIELDS_SIZE)
|
||||
%span.hint= t('simple_form.hints.account.fields')
|
||||
|
||||
= f.simple_fields_for :fields do |fields_f|
|
||||
.row
|
||||
= fields_f.input :name, placeholder: t('simple_form.labels.account.fields.name'), input_html: { maxlength: 255 }
|
||||
= fields_f.input :value, placeholder: t('simple_form.labels.account.fields.value'), input_html: { maxlength: 255 }
|
||||
|
||||
.fields-row__column.fields-group.fields-row__column-6
|
||||
%h6= t('verification.verification')
|
||||
%p.hint= t('verification.explanation_html')
|
||||
.fields-row
|
||||
.fields-row__column.fields-row__column-6
|
||||
.fields-group
|
||||
= f.input :avatar, wrapper: :with_block_label, input_html: { accept: AccountAvatar::IMAGE_MIME_TYPES.join(',') }, hint: t('simple_form.hints.defaults.avatar', dimensions: '400x400', size: number_to_human_size(AccountAvatar::LIMIT))
|
||||
|
||||
.input-copy
|
||||
.input-copy__wrapper
|
||||
%input{ type: :text, maxlength: '999', spellcheck: 'false', readonly: 'true', value: link_to('Mastodon', ActivityPub::TagManager.instance.url_for(@account), rel: 'me').to_str }
|
||||
%button{ type: :button }= t('generic.copy')
|
||||
- if @account.avatar.present?
|
||||
.fields-row__column.fields-row__column-6
|
||||
.fields-group
|
||||
= image_tag @account.avatar.url, class: 'fields-group__thumbnail', width: 90, height: 90
|
||||
= link_to settings_profile_picture_path('avatar'), data: { method: :delete }, class: 'link-button link-button--destructive' do
|
||||
= fa_icon 'trash fw'
|
||||
= t('generic.delete')
|
||||
|
||||
.actions
|
||||
= f.button :button, t('generic.save_changes'), type: :submit
|
||||
.fields-row
|
||||
.fields-row__column.fields-row__column-6
|
||||
.fields-group
|
||||
= f.input :header, wrapper: :with_block_label, input_html: { accept: AccountHeader::IMAGE_MIME_TYPES.join(',') }, hint: t('simple_form.hints.defaults.header', dimensions: '1500x500', size: number_to_human_size(AccountHeader::LIMIT))
|
||||
|
||||
%hr/
|
||||
- if @account.header.present?
|
||||
.fields-row__column.fields-row__column-6
|
||||
.fields-group
|
||||
= image_tag @account.header.url, class: 'fields-group__thumbnail'
|
||||
= link_to settings_profile_picture_path('header'), data: { method: :delete }, class: 'link-button link-button--destructive' do
|
||||
= fa_icon 'trash fw'
|
||||
= t('generic.delete')
|
||||
|
||||
%h6= t('auth.migrate_account')
|
||||
%p.muted-hint= t('auth.migrate_account_html', path: settings_migration_path)
|
||||
%h4= t('edit_profile.safety_and_privacy')
|
||||
|
||||
%hr.spacer/
|
||||
.fields-group
|
||||
= f.input :discoverable, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.discoverable'), recommended: true
|
||||
|
||||
%h6= t 'migrations.incoming_migrations'
|
||||
%p.muted-hint= t('migrations.incoming_migrations_html', path: settings_aliases_path)
|
||||
.fields-group
|
||||
= f.input :locked, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.locked')
|
||||
|
||||
%hr.spacer/
|
||||
.fields-group
|
||||
= f.input :hide_collections, as: :boolean, wrapper: :with_label, label: t('simple_form.labels.defaults.setting_hide_network'), hint: t('simple_form.hints.defaults.setting_hide_network')
|
||||
|
||||
%h6= t('auth.delete_account')
|
||||
%p.muted-hint= t('auth.delete_account_html', path: settings_delete_path)
|
||||
%h4= t('edit_profile.other')
|
||||
|
||||
.fields-group
|
||||
= f.input :bot, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.bot')
|
||||
|
||||
.actions
|
||||
= f.button :button, t('generic.save_changes'), type: :submit
|
||||
|
@ -1,10 +0,0 @@
|
||||
%ul.no-list
|
||||
- if controller_name != 'profiles'
|
||||
%li= link_to t('settings.edit_profile'), settings_profile_path
|
||||
- if controller_name != 'preferences'
|
||||
%li= link_to t('settings.preferences'), settings_preferences_path
|
||||
- if controller_name != 'registrations'
|
||||
%li= link_to t('auth.change_password'), edit_user_registration_path
|
||||
- if controller_name != 'two_factor_authentications'
|
||||
%li= link_to t('settings.two_factor_authentication'), settings_two_factor_authentication_path
|
||||
%li= link_to t('settings.back'), root_path
|
@ -0,0 +1,6 @@
|
||||
.content__heading__tabs
|
||||
= render_navigation renderer: :links do |primary|
|
||||
:ruby
|
||||
primary.item :profile, safe_join([fa_icon('user fw'), t('settings.edit_profile')]), settings_profile_path
|
||||
primary.item :verification, safe_join([fa_icon('check fw'), t('verification.verification')]), settings_verification_path
|
||||
primary.item :featured_tags, safe_join([fa_icon('hashtag fw'), t('settings.featured_tags')]), settings_featured_tags_path
|
@ -0,0 +1,30 @@
|
||||
- content_for :page_title do
|
||||
= t('verification.verification')
|
||||
|
||||
- content_for :heading do
|
||||
%h2= t('settings.profile')
|
||||
= render partial: 'settings/shared/profile_navigation'
|
||||
|
||||
.simple_form
|
||||
%p.lead= t('verification.hint_html')
|
||||
|
||||
%h4= t('verification.here_is_how')
|
||||
|
||||
%p.lead= t('verification.instructions_html')
|
||||
|
||||
.input-copy.lead
|
||||
.input-copy__wrapper
|
||||
%input{ type: :text, maxlength: '999', spellcheck: 'false', readonly: 'true', value: link_to('Mastodon', ActivityPub::TagManager.instance.url_for(@account), rel: 'me').to_str }
|
||||
%button{ type: :button }= t('generic.copy')
|
||||
|
||||
%p.lead= t('verification.extra_instructions_html')
|
||||
|
||||
- if @verified_links.any?
|
||||
%h4= t('verification.verified_links')
|
||||
|
||||
%ul.lead
|
||||
- @verified_links.each do |field|
|
||||
%li
|
||||
%span.verified-badge
|
||||
= fa_icon 'check', class: 'verified-badge__mark'
|
||||
%span= field.value
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue