Merge commit '69db507924
' into kb_migration
This commit is contained in:
commit
f8cd77b8b1
276 changed files with 3984 additions and 3972 deletions
|
@ -42,7 +42,7 @@ module Admin
|
|||
if @webhook.update(resource_params)
|
||||
redirect_to admin_webhook_path(@webhook)
|
||||
else
|
||||
render :show
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ class Api::V1::ConversationsController < Api::BaseController
|
|||
},
|
||||
]
|
||||
)
|
||||
.to_a_paginated_by_id(limit_param(LIMIT), **params_slice(:max_id, :since_id, :min_id))
|
||||
.to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
|
|
|
@ -18,6 +18,14 @@ class Api::V2::Admin::AccountsController < Api::V1::Admin::AccountsController
|
|||
|
||||
private
|
||||
|
||||
def next_path
|
||||
api_v2_admin_accounts_url(pagination_params(max_id: pagination_max_id)) if records_continue?
|
||||
end
|
||||
|
||||
def prev_path
|
||||
api_v2_admin_accounts_url(pagination_params(min_id: pagination_since_id)) unless @accounts.empty?
|
||||
end
|
||||
|
||||
def filtered_accounts
|
||||
AccountFilter.new(translated_filter_params).results
|
||||
end
|
||||
|
|
|
@ -83,8 +83,10 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
|
|||
def after_confirmation_path_for(_resource_name, user)
|
||||
if user.created_by_application && truthy_param?(:redirect_to_app)
|
||||
user.created_by_application.confirmation_redirect_uri
|
||||
elsif user_signed_in?
|
||||
web_url('start')
|
||||
else
|
||||
super
|
||||
new_user_session_path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
15
app/controllers/settings/verifications_controller.rb
Normal file
15
app/controllers/settings/verifications_controller.rb
Normal file
|
@ -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
|
|
@ -24,13 +24,4 @@ module SettingsHelper
|
|||
safe_join([image_tag(account.avatar.url, width: 15, height: 15, alt: display_name(account), class: 'avatar'), content_tag(:span, account.acct, class: 'username')], ' ')
|
||||
end
|
||||
end
|
||||
|
||||
def picture_hint(hint, picture)
|
||||
if picture.original_filename.nil?
|
||||
hint
|
||||
else
|
||||
link = link_to t('generic.delete'), settings_profile_picture_path(picture.name.to_s), data: { method: :delete }
|
||||
safe_join([hint, link], '<br/>'.html_safe)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import ShortNumber from 'mastodon/components/short_number';
|
||||
|
||||
export default class AutosuggestHashtag extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
tag: PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
url: PropTypes.string,
|
||||
history: PropTypes.array,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { tag } = this.props;
|
||||
const weeklyUses = tag.history && (
|
||||
<ShortNumber
|
||||
value={tag.history.reduce((total, day) => total + day.uses * 1, 0)}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='autosuggest-hashtag'>
|
||||
<div className='autosuggest-hashtag__name'>
|
||||
#<strong>{tag.name}</strong>
|
||||
</div>
|
||||
{tag.history !== undefined && (
|
||||
<div className='autosuggest-hashtag__uses'>
|
||||
<FormattedMessage
|
||||
id='autosuggest_hashtag.per_week'
|
||||
defaultMessage='{count} per week'
|
||||
values={{ count: weeklyUses }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
42
app/javascript/mastodon/components/autosuggest_hashtag.tsx
Normal file
42
app/javascript/mastodon/components/autosuggest_hashtag.tsx
Normal file
|
@ -0,0 +1,42 @@
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import ShortNumber from 'mastodon/components/short_number';
|
||||
|
||||
interface Props {
|
||||
tag: {
|
||||
name: string;
|
||||
url?: string;
|
||||
history?: Array<{
|
||||
uses: number;
|
||||
accounts: string;
|
||||
day: string;
|
||||
}>;
|
||||
following?: boolean;
|
||||
type: 'hashtag';
|
||||
};
|
||||
}
|
||||
|
||||
export const AutosuggestHashtag: React.FC<Props> = ({ tag }) => {
|
||||
const weeklyUses = tag.history && (
|
||||
<ShortNumber
|
||||
value={tag.history.reduce((total, day) => total + day.uses * 1, 0)}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='autosuggest-hashtag'>
|
||||
<div className='autosuggest-hashtag__name'>
|
||||
#<strong>{tag.name}</strong>
|
||||
</div>
|
||||
{tag.history !== undefined && (
|
||||
<div className='autosuggest-hashtag__uses'>
|
||||
<FormattedMessage
|
||||
id='autosuggest_hashtag.per_week'
|
||||
defaultMessage='{count} per week'
|
||||
values={{ count: weeklyUses }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -8,7 +8,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
|||
import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container';
|
||||
|
||||
import AutosuggestEmoji from './autosuggest_emoji';
|
||||
import AutosuggestHashtag from './autosuggest_hashtag';
|
||||
import { AutosuggestHashtag } from './autosuggest_hashtag';
|
||||
|
||||
const textAtCursorMatchesToken = (str, caretPosition, searchTokens) => {
|
||||
let word;
|
||||
|
|
|
@ -10,7 +10,7 @@ import Textarea from 'react-textarea-autosize';
|
|||
import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container';
|
||||
|
||||
import AutosuggestEmoji from './autosuggest_emoji';
|
||||
import AutosuggestHashtag from './autosuggest_hashtag';
|
||||
import { AutosuggestHashtag } from './autosuggest_hashtag';
|
||||
|
||||
const textAtCursorMatchesToken = (str, caretPosition) => {
|
||||
let word;
|
||||
|
|
27
app/javascript/mastodon/components/circular_progress.tsx
Normal file
27
app/javascript/mastodon/components/circular_progress.tsx
Normal 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>
|
||||
);
|
||||
};
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
7
app/javascript/mastodon/components/loading_indicator.tsx
Normal file
7
app/javascript/mastodon/components/loading_indicator.tsx
Normal 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>
|
||||
);
|
|
@ -17,7 +17,7 @@ import IntersectionObserverWrapper from '../features/ui/util/intersection_observ
|
|||
|
||||
import { LoadMore } from './load_more';
|
||||
import { LoadPending } from './load_pending';
|
||||
import LoadingIndicator from './loading_indicator';
|
||||
import { LoadingIndicator } from './loading_indicator';
|
||||
|
||||
const MOUSE_IDLE_DELAY = 300;
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -408,7 +408,7 @@ class EmojiPickerDropdown extends PureComponent {
|
|||
{button || <img
|
||||
className={classNames('emojione', { 'pulse-loading': active && loading })}
|
||||
alt='🙂'
|
||||
src={`${assetHost}/emoji/1f602.svg`}
|
||||
src={`${assetHost}/emoji/1f642.svg`}
|
||||
/>}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ export default class Upload extends ImmutablePureComponent {
|
|||
const y = ((focusY / -2) + .5) * 100;
|
||||
|
||||
return (
|
||||
<div className='compose-form__upload' tabIndex={0} role='button'>
|
||||
<div className='compose-form__upload'>
|
||||
<Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}>
|
||||
{({ scale }) => (
|
||||
<div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}>
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
51
app/javascript/mastodon/features/emoji/emoji_compressed.d.ts
vendored
Normal file
51
app/javascript/mastodon/features/emoji/emoji_compressed.d.ts
vendored
Normal 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
|
|
@ -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,
|
||||
|
|
|
@ -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 };
|
|
@ -11,13 +11,13 @@ import { connect } from 'react-redux';
|
|||
import { fetchEmojiReactions } 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 ScrollableList from 'mastodon/components/scrollable_list';
|
||||
import AccountContainer from 'mastodon/containers/account_container';
|
||||
import Column from 'mastodon/features/ui/components/column';
|
||||
|
||||
|
||||
import EmojiView from '../../components/emoji_view';
|
||||
import { LoadingIndicator } from '../../components/loading_indicator';
|
||||
|
||||
const messages = defineMessages({
|
||||
refresh: { id: 'refresh', defaultMessage: 'Refresh' },
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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 => ({
|
||||
|
|
|
@ -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']),
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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' },
|
||||
});
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ import { updateReactionDeck } from 'mastodon/actions/reaction_deck';
|
|||
import Button from 'mastodon/components/button';
|
||||
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 Column from 'mastodon/features/ui/components/column';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 }) => ({
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -402,11 +402,6 @@ class UI extends PureComponent {
|
|||
navigator.serviceWorker.addEventListener('message', this.handleServiceWorkerPostMessage);
|
||||
}
|
||||
|
||||
// On first launch, redirect to the follow recommendations page
|
||||
if (signedIn && this.props.firstLaunch) {
|
||||
this.context.router.history.replace('/start');
|
||||
}
|
||||
|
||||
if (signedIn) {
|
||||
this.props.dispatch(fetchMarkers());
|
||||
this.props.dispatch(expandHomeTimeline());
|
||||
|
|
|
@ -569,6 +569,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",
|
||||
|
|
|
@ -1048,7 +1048,9 @@ code {
|
|||
}
|
||||
|
||||
.simple_form .h-captcha {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.permissions-list {
|
||||
|
|
|
@ -28,8 +28,9 @@ class RequestPool
|
|||
end
|
||||
|
||||
MAX_IDLE_TIME = 30
|
||||
WAIT_TIMEOUT = 5
|
||||
MAX_POOL_SIZE = ENV.fetch('MAX_REQUEST_POOL_SIZE', 512).to_i
|
||||
REAPER_FREQUENCY = 30
|
||||
WAIT_TIMEOUT = 5
|
||||
|
||||
class Connection
|
||||
attr_reader :site, :last_used_at, :created_at, :in_use, :dead, :fresh
|
||||
|
@ -98,7 +99,7 @@ class RequestPool
|
|||
|
||||
def initialize
|
||||
@pool = ConnectionPool::SharedConnectionPool.new(size: MAX_POOL_SIZE, timeout: WAIT_TIMEOUT) { |site| Connection.new(site) }
|
||||
@reaper = Reaper.new(self, 30)
|
||||
@reaper = Reaper.new(self, REAPER_FREQUENCY)
|
||||
@reaper.run
|
||||
end
|
||||
|
||||
|
|
|
@ -84,7 +84,7 @@ class TextFormatter
|
|||
cutoff = url[prefix.length..-1].length > 30
|
||||
|
||||
<<~HTML.squish
|
||||
<a href="#{h(url)}" target="_blank" rel="#{rel.join(' ')}"><span class="invisible">#{h(prefix)}</span><span class="#{cutoff ? 'ellipsis' : ''}">#{h(display_url)}</span><span class="invisible">#{h(suffix)}</span></a>
|
||||
<a href="#{h(url)}" target="_blank" rel="#{rel.join(' ')}" translate="no"><span class="invisible">#{h(prefix)}</span><span class="#{cutoff ? 'ellipsis' : ''}">#{h(display_url)}</span><span class="invisible">#{h(suffix)}</span></a>
|
||||
HTML
|
||||
rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError
|
||||
h(entity[:url])
|
||||
|
@ -127,7 +127,7 @@ class TextFormatter
|
|||
display_username = same_username_hits&.positive? || with_domains? ? account.pretty_acct : account.username
|
||||
|
||||
<<~HTML.squish
|
||||
<span class="h-card"><a href="#{h(url)}" class="u-url mention">@<span>#{h(display_username)}</span></a></span>
|
||||
<span class="h-card" translate="no"><a href="#{h(url)}" class="u-url mention">@<span>#{h(display_username)}</span></a></span>
|
||||
HTML
|
||||
end
|
||||
|
||||
|
|
|
@ -32,33 +32,26 @@ class AccountConversation < ApplicationRecord
|
|||
end
|
||||
|
||||
def participant_accounts
|
||||
@participant_accounts ||= begin
|
||||
if participant_account_ids.empty?
|
||||
[account]
|
||||
else
|
||||
participants = Account.where(id: participant_account_ids).to_a
|
||||
participants.empty? ? [account] : participants
|
||||
end
|
||||
end
|
||||
@participant_accounts ||= Account.where(id: participant_account_ids).to_a
|
||||
@participant_accounts.presence || [account]
|
||||
end
|
||||
|
||||
class << self
|
||||
def to_a_paginated_by_id(limit, min_id: nil, max_id: nil, since_id: nil, preload_participants: true)
|
||||
def to_a_paginated_by_id(limit, options = {})
|
||||
array = begin
|
||||
if min_id
|
||||
paginate_by_min_id(limit, min_id, max_id).reverse
|
||||
if options[:min_id]
|
||||
paginate_by_min_id(limit, options[:min_id], options[:max_id]).reverse
|
||||
else
|
||||
paginate_by_max_id(limit, max_id, since_id).to_a
|
||||
paginate_by_max_id(limit, options[:max_id], options[:since_id]).to_a
|
||||
end
|
||||
end
|
||||
|
||||
if preload_participants
|
||||
participant_ids = array.flat_map(&:participant_account_ids)
|
||||
accounts_by_id = Account.where(id: participant_ids).index_by(&:id)
|
||||
# Preload participants
|
||||
participant_ids = array.flat_map(&:participant_account_ids)
|
||||
accounts_by_id = Account.where(id: participant_ids).index_by(&:id)
|
||||
|
||||
array.each do |conversation|
|
||||
conversation.participant_accounts = conversation.participant_account_ids.filter_map { |id| accounts_by_id[id] }
|
||||
end
|
||||
array.each do |conversation|
|
||||
conversation.participant_accounts = conversation.participant_account_ids.filter_map { |id| accounts_by_id[id] }
|
||||
end
|
||||
|
||||
array
|
||||
|
|
|
@ -26,6 +26,7 @@ class Admin::AccountAction
|
|||
alias include_statuses? include_statuses
|
||||
|
||||
validates :type, :target_account, :current_account, presence: true
|
||||
validates :type, inclusion: { in: TYPES }
|
||||
|
||||
def initialize(attributes = {})
|
||||
@send_email_notification = true
|
||||
|
@ -71,6 +72,10 @@ class Admin::AccountAction
|
|||
TYPES - %w(none disable)
|
||||
end
|
||||
end
|
||||
|
||||
def i18n_scope
|
||||
:activerecord
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -18,9 +18,9 @@ class UserSettings
|
|||
setting :show_application, default: true
|
||||
setting :default_language, default: nil
|
||||
setting :default_sensitive, default: false
|
||||
setting :default_privacy, default: nil
|
||||
setting :default_privacy, default: nil, in: %w(public public_unlisted login unlisted private)
|
||||
setting :default_reblog_privacy, default: nil
|
||||
setting :default_searchability, default: :direct
|
||||
setting :default_searchability, default: :direct, in: %w(public private direct limited)
|
||||
setting :public_post_to_unlisted, default: false
|
||||
setting :reject_public_unlisted_subscription, default: false
|
||||
setting :reject_unlisted_subscription, default: false
|
||||
|
@ -88,7 +88,10 @@ class UserSettings
|
|||
|
||||
raise KeyError, "Undefined setting: #{key}" unless self.class.definition_for?(key)
|
||||
|
||||
typecast_value = self.class.definition_for(key).type_cast(value)
|
||||
setting_definition = self.class.definition_for(key)
|
||||
typecast_value = setting_definition.type_cast(value)
|
||||
|
||||
raise ArgumentError, "Invalid value for setting #{key}: #{typecast_value}" if setting_definition.in.present? && setting_definition.in.exclude?(typecast_value)
|
||||
|
||||
if typecast_value.nil?
|
||||
@original_hash.delete(key)
|
||||
|
|
|
@ -12,6 +12,7 @@ class RemoveStatusService < BaseService
|
|||
# @option [Boolean] :immediate
|
||||
# @option [Boolean] :preserve
|
||||
# @option [Boolean] :original_removed
|
||||
# @option [Boolean] :skip_streaming
|
||||
def call(status, **options)
|
||||
@payload = Oj.dump(event: :delete, payload: status.id.to_s)
|
||||
@status = status
|
||||
|
@ -53,6 +54,9 @@ class RemoveStatusService < BaseService
|
|||
|
||||
private
|
||||
|
||||
# The following FeedManager calls all do not result in redis publishes for
|
||||
# streaming, as the `:update` option is false
|
||||
|
||||
def remove_from_self
|
||||
FeedManager.instance.unpush_from_home(@account, @status)
|
||||
end
|
||||
|
@ -85,6 +89,8 @@ class RemoveStatusService < BaseService
|
|||
# followers. Here we send a delete to actively mentioned accounts
|
||||
# that may not follow the account
|
||||
|
||||
return if skip_streaming?
|
||||
|
||||
@status.active_mentions.find_each do |mention|
|
||||
redis.publish("timeline:#{mention.account_id}", @payload)
|
||||
end
|
||||
|
@ -113,7 +119,7 @@ class RemoveStatusService < BaseService
|
|||
# without us being able to do all the fancy stuff
|
||||
|
||||
@status.reblogs.rewhere(deleted_at: [nil, @status.deleted_at]).includes(:account).reorder(nil).find_each do |reblog|
|
||||
RemoveStatusService.new.call(reblog, original_removed: true)
|
||||
RemoveStatusService.new.call(reblog, original_removed: true, skip_streaming: skip_streaming?)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -124,6 +130,8 @@ class RemoveStatusService < BaseService
|
|||
|
||||
return unless @status.public_visibility?
|
||||
|
||||
return if skip_streaming?
|
||||
|
||||
@status.tags.map(&:name).each do |hashtag|
|
||||
redis.publish("timeline:hashtag:#{hashtag.mb_chars.downcase}", @payload)
|
||||
redis.publish("timeline:hashtag:#{hashtag.mb_chars.downcase}:local", @payload) if @status.local?
|
||||
|
@ -133,6 +141,8 @@ class RemoveStatusService < BaseService
|
|||
def remove_from_public
|
||||
return unless @status.public_visibility?
|
||||
|
||||
return if skip_streaming?
|
||||
|
||||
redis.publish('timeline:public', @payload)
|
||||
redis.publish(@status.local? ? 'timeline:public:local' : 'timeline:public:remote', @payload)
|
||||
end
|
||||
|
@ -140,6 +150,8 @@ class RemoveStatusService < BaseService
|
|||
def remove_from_media
|
||||
return unless @status.public_visibility?
|
||||
|
||||
return if skip_streaming?
|
||||
|
||||
redis.publish('timeline:public:media', @payload)
|
||||
redis.publish(@status.local? ? 'timeline:public:local:media' : 'timeline:public:remote:media', @payload)
|
||||
end
|
||||
|
@ -153,4 +165,8 @@ class RemoveStatusService < BaseService
|
|||
def permanently?
|
||||
@options[:immediate] || !(@options[:preserve] || @status.reported?)
|
||||
end
|
||||
|
||||
def skip_streaming?
|
||||
!!@options[:skip_streaming]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
= stylesheet_pack_tag 'common', media: 'all', crossorigin: 'anonymous'
|
||||
= stylesheet_pack_tag current_theme, media: 'all', crossorigin: 'anonymous'
|
||||
= javascript_pack_tag 'common', crossorigin: 'anonymous'
|
||||
= preload_pack_asset "locale/#{I18n.locale}-json.js"
|
||||
= preload_pack_asset "locale/#{I18n.locale}-json.js", crossorigin: 'anonymous'
|
||||
= csrf_meta_tags unless skip_csrf_meta_tags?
|
||||
%meta{ name: 'style-nonce', content: request.content_security_policy_nonce }
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
= stylesheet_pack_tag 'common', media: 'all', crossorigin: 'anonymous'
|
||||
= stylesheet_pack_tag Setting.default_settings['theme'], media: 'all', crossorigin: 'anonymous'
|
||||
= javascript_pack_tag 'common', integrity: true, crossorigin: 'anonymous'
|
||||
= preload_pack_asset "locale/#{I18n.locale}-json.js"
|
||||
= preload_pack_asset "locale/#{I18n.locale}-json.js", crossorigin: 'anonymous'
|
||||
= render_initial_state
|
||||
= javascript_pack_tag 'public', integrity: true, crossorigin: 'anonymous'
|
||||
%body.embed
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
- content_for :page_title do
|
||||
= t('settings.featured_tags')
|
||||
|
||||
%p= t('featured_tags.hint_html')
|
||||
|
||||
%hr.spacer/
|
||||
- content_for :heading do
|
||||
%h2= t('settings.profile')
|
||||
= render partial: 'settings/shared/profile_navigation'
|
||||
|
||||
= simple_form_for @featured_tag, url: settings_featured_tags_path do |f|
|
||||
= render 'shared/error_messages', object: @featured_tag
|
||||
|
||||
%p.lead= t('featured_tags.hint_html')
|
||||
|
||||
.fields-group
|
||||
= f.input :name, wrapper: :with_block_label, hint: safe_join([t('simple_form.hints.featured_tag.name'), safe_join(@recently_used_tags.map { |tag| link_to("##{tag.display_name}", settings_featured_tags_path(featured_tag: { name: tag.name }), method: :post) }, ', ')], ' ')
|
||||
|
||||
|
|
|
@ -1,82 +1,82 @@
|
|||
- 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: 30, data: { default: @account.username } }, hint: false
|
||||
= f.input :note, wrapper: :with_label, input_html: { maxlength: 500 }, 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-group
|
||||
= f.input :display_name, wrapper: :with_block_label, input_html: { maxlength: 30, data: { default: @account.username } }
|
||||
|
||||
.fields-group
|
||||
= f.input :note, wrapper: :with_block_label, input_html: { maxlength: 500 }
|
||||
|
||||
.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)
|
||||
.input.with_block_label
|
||||
%label= t('simple_form.labels.defaults.fields')
|
||||
%span.hint= t('simple_form.hints.account.fields')
|
||||
|
||||
= 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)
|
||||
= 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 }
|
||||
|
||||
%hr.spacer/
|
||||
.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))
|
||||
|
||||
.fields-group
|
||||
= f.input :locked, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.locked')
|
||||
- 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')
|
||||
|
||||
.fields-group
|
||||
= f.input :my_actor_type, collection: ['person', 'bot', 'group'],label_method: lambda { |item| safe_join([t("simple_form.labels.defaults.#{item}"), content_tag(:span, I18n.t("simple_form.hints.defaults.#{item}"), class: 'hint')]) }, as: :radio_buttons, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li', wrapper: :with_floating_label
|
||||
.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))
|
||||
|
||||
.fields-group
|
||||
= f.input :group_allow_private_message, as: :boolean, wrapper: :with_label, kmyblue: true, hint: t('simple_form.hints.defaults.group_allow_private_message')
|
||||
- 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')
|
||||
|
||||
%h4= t('edit_profile.safety_and_privacy')
|
||||
|
||||
.fields-group
|
||||
= f.input :discoverable, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.discoverable'), recommended: true
|
||||
|
||||
.fields-group
|
||||
= f.input :locked, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.locked')
|
||||
|
||||
.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')
|
||||
|
||||
.fields-group
|
||||
= f.input :dissubscribable, as: :boolean, wrapper: :with_label, kmyblue: true, hint: t('simple_form.hints.defaults.dissubscribable')
|
||||
|
||||
%hr.spacer/
|
||||
%h4= t('edit_profile.other')
|
||||
|
||||
.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')
|
||||
.fields-group
|
||||
= f.input :my_actor_type, collection: ['person', 'bot', 'group'],label_method: lambda { |item| safe_join([t("simple_form.labels.defaults.#{item}"), content_tag(:span, I18n.t("simple_form.hints.defaults.#{item}"), class: 'hint')]) }, as: :radio_buttons, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li', wrapper: :with_floating_label
|
||||
|
||||
= 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')
|
||||
|
||||
.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')
|
||||
.fields-group
|
||||
= f.input :group_allow_private_message, as: :boolean, wrapper: :with_label, kmyblue: true, hint: t('simple_form.hints.defaults.group_allow_private_message')
|
||||
|
||||
.actions
|
||||
= f.button :button, t('generic.save_changes'), type: :submit
|
||||
|
||||
%hr/
|
||||
|
||||
%h6= t('auth.migrate_account')
|
||||
%p.muted-hint= t('auth.migrate_account_html', path: settings_migration_path)
|
||||
|
||||
%hr.spacer/
|
||||
|
||||
%h6= t 'migrations.incoming_migrations'
|
||||
%p.muted-hint= t('migrations.incoming_migrations_html', path: settings_aliases_path)
|
||||
|
||||
%hr.spacer/
|
||||
|
||||
%h6= t('auth.delete_account')
|
||||
%p.muted-hint= t('auth.delete_account_html', path: settings_delete_path)
|
||||
|
|
|
@ -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
|
6
app/views/settings/shared/_profile_navigation.html.haml
Normal file
6
app/views/settings/shared/_profile_navigation.html.haml
Normal file
|
@ -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
|
30
app/views/settings/verifications/show.html.haml
Normal file
30
app/views/settings/verifications/show.html.haml
Normal file
|
@ -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
|
|
@ -24,7 +24,7 @@ class Scheduler::UserCleanupScheduler
|
|||
def clean_discarded_statuses!
|
||||
Status.unscoped.discarded.where('deleted_at <= ?', 30.days.ago).find_in_batches do |statuses|
|
||||
RemovalWorker.push_bulk(statuses) do |status|
|
||||
[status.id, { 'immediate' => true }]
|
||||
[status.id, { 'immediate' => true, 'skip_streaming' => true }]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue