commit
f3c2035fce
314 changed files with 5483 additions and 5049 deletions
@ -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