Merge branch 'main' into glitch-soc/merge-upstream
Conflicts: - `app/controllers/settings/preferences_controller.rb`: Conflicts due to us having more user settings and upstream dropping `hide_network` (to replace it with an account attribute, properly migrated). Dropped `hide_network` like upstream. - `app/lib/user_settings_decorator.rb`: Conflicts due to us having more user settings and upstream dropping `hide_network` (to replace it with an account attribute, properly migrated). Dropped `hide_network` like upstream. - `app/models/status.rb`: Conflict because of slight change in how glitch-soc handles the scope to filter out local-only posts for anonymous viewers. Took upstream's changes and re-applied glitch-soc's change. - `app/models/user.rb`: Conflicts due to us having more user settings and upstream dropping `hide_network` (to replace it with an account attribute, properly migrated). Dropped `hide_network` like upstream. - `app/views/directories/index.html.haml`: Conflict because upstream redesigned that page while glitch-soc had a minor change to support hiding the number of followers. Ported glitch-soc's change on top of upstream's redesign. Additional changes: - `app/models/account_statuses_filter.rb`: See change to `app/models/status.rb`.
This commit is contained in:
@@ -62,7 +62,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
|
||||
return unless page_requested?
|
||||
|
||||
@statuses = cache_collection_paginated_by_id(
|
||||
@account.statuses.permitted_for(@account, signed_request_account),
|
||||
AccountStatusesFilter.new(@account, signed_request_account).results,
|
||||
Status,
|
||||
LIMIT,
|
||||
params_slice(:max_id, :min_id, :since_id)
|
||||
|
@@ -0,0 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Accounts::FamiliarFollowersController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:follows' }
|
||||
before_action :require_user!
|
||||
before_action :set_accounts
|
||||
|
||||
def index
|
||||
render json: familiar_followers.accounts, each_serializer: REST::FamiliarFollowersSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_accounts
|
||||
@accounts = Account.without_suspended.where(id: account_ids).select('id, hide_collections').index_by(&:id).values_at(*account_ids).compact
|
||||
end
|
||||
|
||||
def familiar_followers
|
||||
FamiliarFollowersPresenter.new(@accounts, current_user.account_id)
|
||||
end
|
||||
|
||||
def account_ids
|
||||
Array(params[:id]).map(&:to_i)
|
||||
end
|
||||
end
|
@@ -22,53 +22,16 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
|
||||
end
|
||||
|
||||
def cached_account_statuses
|
||||
statuses = truthy_param?(:pinned) ? pinned_scope : permitted_account_statuses
|
||||
|
||||
statuses.merge!(only_media_scope) if truthy_param?(:only_media)
|
||||
statuses.merge!(no_replies_scope) if truthy_param?(:exclude_replies)
|
||||
statuses.merge!(no_reblogs_scope) if truthy_param?(:exclude_reblogs)
|
||||
statuses.merge!(hashtag_scope) if params[:tagged].present?
|
||||
|
||||
cache_collection_paginated_by_id(
|
||||
statuses,
|
||||
AccountStatusesFilter.new(@account, current_account, params).results,
|
||||
Status,
|
||||
limit_param(DEFAULT_STATUSES_LIMIT),
|
||||
params_slice(:max_id, :since_id, :min_id)
|
||||
)
|
||||
end
|
||||
|
||||
def permitted_account_statuses
|
||||
@account.statuses.permitted_for(@account, current_account)
|
||||
end
|
||||
|
||||
def only_media_scope
|
||||
Status.joins(:media_attachments).merge(@account.media_attachments.reorder(nil)).group(:id)
|
||||
end
|
||||
|
||||
def pinned_scope
|
||||
@account.pinned_statuses.permitted_for(@account, current_account)
|
||||
end
|
||||
|
||||
def no_replies_scope
|
||||
Status.without_replies
|
||||
end
|
||||
|
||||
def no_reblogs_scope
|
||||
Status.without_reblogs
|
||||
end
|
||||
|
||||
def hashtag_scope
|
||||
tag = Tag.find_normalized(params[:tagged])
|
||||
|
||||
if tag
|
||||
Status.tagged_with(tag.id)
|
||||
else
|
||||
Status.none
|
||||
end
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit, :only_media, :exclude_replies).permit(:limit, :only_media, :exclude_replies).merge(core_params)
|
||||
params.slice(:limit, *AccountStatusesFilter::KEYS).permit(:limit, *AccountStatusesFilter::KEYS).merge(core_params)
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
|
@@ -16,13 +16,13 @@ class FollowerAccountsController < ApplicationController
|
||||
use_pack 'public'
|
||||
expires_in 0, public: true unless user_signed_in?
|
||||
|
||||
next if @account.user_hides_network?
|
||||
next if @account.hide_collections?
|
||||
|
||||
follows
|
||||
end
|
||||
|
||||
format.json do
|
||||
raise Mastodon::NotPermittedError if page_requested? && @account.user_hides_network?
|
||||
raise Mastodon::NotPermittedError if page_requested? && @account.hide_collections?
|
||||
|
||||
expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?)
|
||||
|
||||
@@ -83,7 +83,7 @@ class FollowerAccountsController < ApplicationController
|
||||
end
|
||||
|
||||
def restrict_fields_to
|
||||
if page_requested? || !@account.user_hides_network?
|
||||
if page_requested? || !@account.hide_collections?
|
||||
# Return all fields
|
||||
else
|
||||
%i(id type total_items)
|
||||
|
@@ -16,13 +16,13 @@ class FollowingAccountsController < ApplicationController
|
||||
use_pack 'public'
|
||||
expires_in 0, public: true unless user_signed_in?
|
||||
|
||||
next if @account.user_hides_network?
|
||||
next if @account.hide_collections?
|
||||
|
||||
follows
|
||||
end
|
||||
|
||||
format.json do
|
||||
raise Mastodon::NotPermittedError if page_requested? && @account.user_hides_network?
|
||||
raise Mastodon::NotPermittedError if page_requested? && @account.hide_collections?
|
||||
|
||||
expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?)
|
||||
|
||||
@@ -83,7 +83,7 @@ class FollowingAccountsController < ApplicationController
|
||||
end
|
||||
|
||||
def restrict_fields_to
|
||||
if page_requested? || !@account.user_hides_network?
|
||||
if page_requested? || !@account.hide_collections?
|
||||
# Return all fields
|
||||
else
|
||||
%i(id type total_items)
|
||||
|
@@ -48,7 +48,6 @@ class Settings::PreferencesController < Settings::BaseController
|
||||
:setting_system_font_ui,
|
||||
:setting_system_emoji_font,
|
||||
:setting_noindex,
|
||||
:setting_hide_network,
|
||||
:setting_hide_followers_count,
|
||||
:setting_aggregate_reblogs,
|
||||
:setting_show_application,
|
||||
|
@@ -20,7 +20,7 @@ class Settings::ProfilesController < Settings::BaseController
|
||||
private
|
||||
|
||||
def account_params
|
||||
params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable, fields_attributes: [:name, :value])
|
||||
params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable, :hide_collections, fields_attributes: [:name, :value])
|
||||
end
|
||||
|
||||
def set_account
|
||||
|
@@ -151,7 +151,7 @@ class ScrollableList extends PureComponent {
|
||||
|
||||
attachFullscreenListener(this.onFullScreenChange);
|
||||
|
||||
// Handle initial scroll posiiton
|
||||
// Handle initial scroll position
|
||||
this.handleScroll();
|
||||
}
|
||||
|
||||
|
@@ -43,7 +43,7 @@ export default class MediaContainer extends PureComponent {
|
||||
|
||||
handleOpenVideo = (options) => {
|
||||
const { components } = this.props;
|
||||
const { media } = JSON.parse(components[options.componetIndex].getAttribute('data-props'));
|
||||
const { media } = JSON.parse(components[options.componentIndex].getAttribute('data-props'));
|
||||
const mediaList = fromJS(media);
|
||||
|
||||
document.body.classList.add('with-modals--active');
|
||||
@@ -87,7 +87,7 @@ export default class MediaContainer extends PureComponent {
|
||||
...(hashtag ? { hashtag: fromJS(hashtag) } : {}),
|
||||
|
||||
...(componentName === 'Video' ? {
|
||||
componetIndex: i,
|
||||
componentIndex: i,
|
||||
onOpenVideo: this.handleOpenVideo,
|
||||
} : {
|
||||
onOpenMedia: this.handleOpenMedia,
|
||||
|
@@ -7,31 +7,28 @@ import { makeGetAccount } from 'mastodon/selectors';
|
||||
import Avatar from 'mastodon/components/avatar';
|
||||
import DisplayName from 'mastodon/components/display_name';
|
||||
import Permalink from 'mastodon/components/permalink';
|
||||
import RelativeTimestamp from 'mastodon/components/relative_timestamp';
|
||||
import IconButton from 'mastodon/components/icon_button';
|
||||
import Button from 'mastodon/components/button';
|
||||
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
|
||||
import { autoPlayGif, me, unfollowModal } from 'mastodon/initial_state';
|
||||
import ShortNumber from 'mastodon/components/short_number';
|
||||
import {
|
||||
followAccount,
|
||||
unfollowAccount,
|
||||
blockAccount,
|
||||
unblockAccount,
|
||||
unmuteAccount,
|
||||
} from 'mastodon/actions/accounts';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { initMuteModal } from 'mastodon/actions/mutes';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const messages = defineMessages({
|
||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
||||
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' },
|
||||
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
|
||||
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
|
||||
unfollowConfirm: {
|
||||
id: 'confirmations.unfollow.confirm',
|
||||
defaultMessage: 'Unfollow',
|
||||
},
|
||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
||||
cancel_follow_request: { id: 'account.cancel_follow_request', defaultMessage: 'Cancel follow request' },
|
||||
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
|
||||
unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' },
|
||||
unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' },
|
||||
unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
|
||||
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
|
||||
});
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
@@ -75,18 +72,15 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
onBlock(account) {
|
||||
if (account.getIn(['relationship', 'blocking'])) {
|
||||
dispatch(unblockAccount(account.get('id')));
|
||||
} else {
|
||||
dispatch(blockAccount(account.get('id')));
|
||||
}
|
||||
},
|
||||
|
||||
onMute(account) {
|
||||
if (account.getIn(['relationship', 'muting'])) {
|
||||
dispatch(unmuteAccount(account.get('id')));
|
||||
} else {
|
||||
dispatch(initMuteModal(account));
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default
|
||||
@@ -138,130 +132,92 @@ class AccountCard extends ImmutablePureComponent {
|
||||
|
||||
handleMute = () => {
|
||||
this.props.onMute(this.props.account);
|
||||
};
|
||||
}
|
||||
|
||||
handleEditProfile = () => {
|
||||
window.open('/settings/profile', '_blank');
|
||||
}
|
||||
|
||||
render() {
|
||||
const { account, intl } = this.props;
|
||||
|
||||
let buttons;
|
||||
let actionBtn;
|
||||
|
||||
if (
|
||||
account.get('id') !== me &&
|
||||
account.get('relationship', null) !== null
|
||||
) {
|
||||
const following = account.getIn(['relationship', 'following']);
|
||||
const requested = account.getIn(['relationship', 'requested']);
|
||||
const blocking = account.getIn(['relationship', 'blocking']);
|
||||
const muting = account.getIn(['relationship', 'muting']);
|
||||
|
||||
if (requested) {
|
||||
buttons = (
|
||||
<IconButton
|
||||
disabled
|
||||
icon='hourglass'
|
||||
title={intl.formatMessage(messages.requested)}
|
||||
/>
|
||||
);
|
||||
} else if (blocking) {
|
||||
buttons = (
|
||||
<IconButton
|
||||
active
|
||||
icon='unlock'
|
||||
title={intl.formatMessage(messages.unblock, {
|
||||
name: account.get('username'),
|
||||
})}
|
||||
onClick={this.handleBlock}
|
||||
/>
|
||||
);
|
||||
} else if (muting) {
|
||||
buttons = (
|
||||
<IconButton
|
||||
active
|
||||
icon='volume-up'
|
||||
title={intl.formatMessage(messages.unmute, {
|
||||
name: account.get('username'),
|
||||
})}
|
||||
onClick={this.handleMute}
|
||||
/>
|
||||
);
|
||||
} else if (!account.get('moved') || following) {
|
||||
buttons = (
|
||||
<IconButton
|
||||
icon={following ? 'user-times' : 'user-plus'}
|
||||
title={intl.formatMessage(
|
||||
following ? messages.unfollow : messages.follow,
|
||||
)}
|
||||
onClick={this.handleFollow}
|
||||
active={following}
|
||||
/>
|
||||
);
|
||||
if (me !== account.get('id')) {
|
||||
if (!account.get('relationship')) { // Wait until the relationship is loaded
|
||||
actionBtn = '';
|
||||
} else if (account.getIn(['relationship', 'requested'])) {
|
||||
actionBtn = <Button className={classNames('logo-button')} text={intl.formatMessage(messages.cancel_follow_request)} title={intl.formatMessage(messages.requested)} onClick={this.handleFollow} />;
|
||||
} else if (account.getIn(['relationship', 'muting'])) {
|
||||
actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unmute)} onClick={this.handleMute} />;
|
||||
} else if (!account.getIn(['relationship', 'blocking'])) {
|
||||
actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']) })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.handleFollow} />;
|
||||
} else if (account.getIn(['relationship', 'blocking'])) {
|
||||
actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unblock)} onClick={this.handleBlock} />;
|
||||
}
|
||||
} else {
|
||||
actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.edit_profile)} onClick={this.handleEditProfile} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='directory__card'>
|
||||
<div className='directory__card__img'>
|
||||
<img
|
||||
src={
|
||||
autoPlayGif ? account.get('header') : account.get('header_static')
|
||||
}
|
||||
alt=''
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='directory__card__bar'>
|
||||
<Permalink
|
||||
className='directory__card__bar__name'
|
||||
href={account.get('url')}
|
||||
to={`/@${account.get('acct')}`}
|
||||
>
|
||||
<Avatar account={account} size={48} />
|
||||
<DisplayName account={account} />
|
||||
</Permalink>
|
||||
|
||||
<div className='directory__card__bar__relationship account__relationship'>
|
||||
{buttons}
|
||||
<div className='account-card'>
|
||||
<Permalink href={account.get('url')} to={`/@${account.get('acct')}`} className='account-card__permalink'>
|
||||
<div className='account-card__header'>
|
||||
<img
|
||||
src={
|
||||
autoPlayGif ? account.get('header') : account.get('header_static')
|
||||
}
|
||||
alt=''
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='directory__card__extra' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
<div className='account-card__title'>
|
||||
<div className='account-card__title__avatar'><Avatar account={account} size={56} /></div>
|
||||
<DisplayName account={account} />
|
||||
</div>
|
||||
</Permalink>
|
||||
|
||||
{account.get('note').length > 0 && (
|
||||
<div
|
||||
className='account__header__content translate'
|
||||
className='account-card__bio translate'
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
dangerouslySetInnerHTML={{ __html: account.get('note_emojified') }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='directory__card__extra'>
|
||||
<div className='accounts-table__count'>
|
||||
<ShortNumber value={account.get('statuses_count')} />
|
||||
<small>
|
||||
<FormattedMessage id='account.posts' defaultMessage='Toots' />
|
||||
</small>
|
||||
<div className='account-card__actions'>
|
||||
<div className='account-card__counters'>
|
||||
<div className='account-card__counters__item'>
|
||||
<ShortNumber value={account.get('statuses_count')} />
|
||||
<small>
|
||||
<FormattedMessage id='account.posts' defaultMessage='Toots' />
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div className='account-card__counters__item'>
|
||||
<ShortNumber value={account.get('followers_count')} />{' '}
|
||||
<small>
|
||||
<FormattedMessage
|
||||
id='account.followers'
|
||||
defaultMessage='Followers'
|
||||
/>
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div className='account-card__counters__item'>
|
||||
<ShortNumber value={account.get('following_count')} />{' '}
|
||||
<small>
|
||||
<FormattedMessage
|
||||
id='account.following'
|
||||
defaultMessage='Following'
|
||||
/>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div className='accounts-table__count'>
|
||||
<ShortNumber value={account.get('followers_count')} />{' '}
|
||||
<small>
|
||||
<FormattedMessage
|
||||
id='account.followers'
|
||||
defaultMessage='Followers'
|
||||
/>
|
||||
</small>
|
||||
</div>
|
||||
<div className='accounts-table__count'>
|
||||
{account.get('last_status_at') === null ? (
|
||||
<FormattedMessage
|
||||
id='account.never_active'
|
||||
defaultMessage='Never'
|
||||
/>
|
||||
) : (
|
||||
<RelativeTimestamp timestamp={account.get('last_status_at')} />
|
||||
)}{' '}
|
||||
<small>
|
||||
<FormattedMessage
|
||||
id='account.last_status'
|
||||
defaultMessage='Last active'
|
||||
/>
|
||||
</small>
|
||||
|
||||
<div className='account-card__actions__button'>
|
||||
{actionBtn}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -10,9 +10,9 @@ import { fetchDirectory, expandDirectory } from 'mastodon/actions/directory';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
import AccountCard from './components/account_card';
|
||||
import RadioButton from 'mastodon/components/radio_button';
|
||||
import classNames from 'classnames';
|
||||
import LoadMore from 'mastodon/components/load_more';
|
||||
import ScrollContainer from 'mastodon/containers/scroll_container';
|
||||
import LoadingIndicator from 'mastodon/components/loading_indicator';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'column.directory', defaultMessage: 'Browse profiles' },
|
||||
@@ -129,7 +129,7 @@ class Directory extends React.PureComponent {
|
||||
const pinned = !!columnId;
|
||||
|
||||
const scrollableArea = (
|
||||
<div className='scrollable' style={{ background: 'transparent' }}>
|
||||
<div className='scrollable'>
|
||||
<div className='filter-form'>
|
||||
<div className='filter-form__column' role='group'>
|
||||
<RadioButton name='order' value='active' label={intl.formatMessage(messages.recentlyActive)} checked={order === 'active'} onChange={this.handleChangeOrder} />
|
||||
@@ -142,8 +142,10 @@ class Directory extends React.PureComponent {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={classNames('directory__list', { loading: isLoading })}>
|
||||
{accountIds.map(accountId => <AccountCard id={accountId} key={accountId} />)}
|
||||
<div className='directory__list'>
|
||||
{isLoading ? <LoadingIndicator /> : accountIds.map(accountId => (
|
||||
<AccountCard id={accountId} key={accountId} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<LoadMore onClick={this.handleLoadMore} visible={!isLoading} />
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import Account from 'mastodon/containers/account_container';
|
||||
import AccountCard from 'mastodon/features/directory/components/account_card';
|
||||
import LoadingIndicator from 'mastodon/components/loading_indicator';
|
||||
import { connect } from 'react-redux';
|
||||
import { fetchSuggestions } from 'mastodon/actions/suggestions';
|
||||
@@ -29,9 +29,9 @@ class Suggestions extends React.PureComponent {
|
||||
const { isLoading, suggestions } = this.props;
|
||||
|
||||
return (
|
||||
<div className='explore__links'>
|
||||
{isLoading ? (<LoadingIndicator />) : suggestions.map(suggestion => (
|
||||
<Account key={suggestion.get('account')} id={suggestion.get('account')} />
|
||||
<div className='explore__suggestions'>
|
||||
{isLoading ? <LoadingIndicator /> : suggestions.map(suggestion => (
|
||||
<AccountCard key={suggestion.get('account')} id={suggestion.get('account')} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
@@ -8,7 +8,7 @@ const messages = defineMessages({
|
||||
dislike: { id: 'report.reasons.dislike', defaultMessage: 'I don\'t like it' },
|
||||
dislike_description: { id: 'report.reasons.dislike_description', defaultMessage: 'It is not something you want to see' },
|
||||
spam: { id: 'report.reasons.spam', defaultMessage: 'It\'s spam' },
|
||||
spam_description: { id: 'report.reasons.spam_description', defaultMessage: 'Malicious links, fake engagement, or repetetive replies' },
|
||||
spam_description: { id: 'report.reasons.spam_description', defaultMessage: 'Malicious links, fake engagement, or repetitive replies' },
|
||||
violation: { id: 'report.reasons.violation', defaultMessage: 'It violates server rules' },
|
||||
violation_description: { id: 'report.reasons.violation_description', defaultMessage: 'You are aware that it breaks specific rules' },
|
||||
other: { id: 'report.reasons.other', defaultMessage: 'It\'s something else' },
|
||||
|
@@ -121,7 +121,7 @@ class Video extends React.PureComponent {
|
||||
autoPlay: PropTypes.bool,
|
||||
volume: PropTypes.number,
|
||||
muted: PropTypes.bool,
|
||||
componetIndex: PropTypes.number,
|
||||
componentIndex: PropTypes.number,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -502,7 +502,7 @@ class Video extends React.PureComponent {
|
||||
startTime: this.video.currentTime,
|
||||
autoPlay: !this.state.paused,
|
||||
defaultVolume: this.state.volume,
|
||||
componetIndex: this.props.componetIndex,
|
||||
componentIndex: this.props.componentIndex,
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -414,7 +414,7 @@
|
||||
"report.reasons.other": "It's something else",
|
||||
"report.reasons.other_description": "The issue does not fit into other categories",
|
||||
"report.reasons.spam": "It's spam",
|
||||
"report.reasons.spam_description": "Malicious links, fake engagement, or repetetive replies",
|
||||
"report.reasons.spam_description": "Malicious links, fake engagement, or repetitive replies",
|
||||
"report.reasons.violation": "It violates server rules",
|
||||
"report.reasons.violation_description": "You are aware that it breaks specific rules",
|
||||
"report.rules.subtitle": "Select all that apply",
|
||||
|
@@ -40,19 +40,11 @@ html {
|
||||
background: lighten($ui-base-color, 12%);
|
||||
}
|
||||
|
||||
.filter-form,
|
||||
.directory__card__bar {
|
||||
.filter-form {
|
||||
background: $white;
|
||||
border-bottom: 1px solid lighten($ui-base-color, 8%);
|
||||
}
|
||||
|
||||
.scrollable .directory__list {
|
||||
width: calc(100% + 2px);
|
||||
margin-left: -1px;
|
||||
margin-right: -1px;
|
||||
}
|
||||
|
||||
.directory__card,
|
||||
.table-of-contents {
|
||||
border: 1px solid lighten($ui-base-color, 8%);
|
||||
}
|
||||
@@ -75,8 +67,7 @@ html {
|
||||
.column-header__back-button,
|
||||
.column-header__button,
|
||||
.column-header__button.active,
|
||||
.account__header__bar,
|
||||
.directory__card__extra {
|
||||
.account__header__bar {
|
||||
background: $white;
|
||||
}
|
||||
|
||||
|
@@ -1236,6 +1236,11 @@ a.sparkline {
|
||||
background: $ui-base-color;
|
||||
border-radius: 4px;
|
||||
|
||||
&__permalink {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&__header {
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
@@ -1252,20 +1257,22 @@ a.sparkline {
|
||||
}
|
||||
|
||||
&__title {
|
||||
margin-top: -25px;
|
||||
margin-top: -(15px + 8px);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
|
||||
&__avatar {
|
||||
padding: 15px;
|
||||
padding: 14px;
|
||||
|
||||
img {
|
||||
img,
|
||||
.account__avatar {
|
||||
display: block;
|
||||
margin: 0;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
background: darken($ui-base-color, 8%);
|
||||
background-color: darken($ui-base-color, 8%);
|
||||
border-radius: 8px;
|
||||
border: 1px solid $ui-base-color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1273,30 +1280,34 @@ a.sparkline {
|
||||
color: $darker-text-color;
|
||||
padding-bottom: 15px;
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
|
||||
bdi {
|
||||
display: block;
|
||||
color: $primary-text-color;
|
||||
font-weight: 500;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__bio {
|
||||
padding: 0 15px;
|
||||
margin: 8px 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-wrap: break-word;
|
||||
max-height: 18px * 2;
|
||||
max-height: 21px * 2;
|
||||
position: relative;
|
||||
font-size: 15px;
|
||||
line-height: 21px;
|
||||
|
||||
&::after {
|
||||
display: block;
|
||||
content: "";
|
||||
width: 50px;
|
||||
height: 18px;
|
||||
height: 21px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
bottom: 8px;
|
||||
right: 15px;
|
||||
background: linear-gradient(to left, $ui-base-color, transparent);
|
||||
pointer-events: none;
|
||||
@@ -1309,10 +1320,6 @@ a.sparkline {
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
|
||||
.fa {
|
||||
color: lighten($dark-text-color, 7%);
|
||||
}
|
||||
}
|
||||
|
||||
&.mention {
|
||||
@@ -1329,12 +1336,21 @@ a.sparkline {
|
||||
|
||||
&__actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-top: 10px;
|
||||
|
||||
&__button {
|
||||
flex: 0 0 auto;
|
||||
flex-shrink: 1;
|
||||
padding: 0 15px;
|
||||
overflow: hidden;
|
||||
|
||||
.button {
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1343,19 +1359,23 @@ a.sparkline {
|
||||
display: grid;
|
||||
grid-auto-columns: minmax(0, 1fr);
|
||||
grid-auto-flow: column;
|
||||
max-width: 340px;
|
||||
min-width: 65px * 3;
|
||||
|
||||
&__item {
|
||||
padding: 15px;
|
||||
padding: 15px 0;
|
||||
text-align: center;
|
||||
color: $primary-text-color;
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
line-height: 21px;
|
||||
|
||||
small {
|
||||
display: block;
|
||||
color: $darker-text-color;
|
||||
font-weight: 400;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -50,7 +50,7 @@
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-family: inherit;
|
||||
font-size: 17px;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0;
|
||||
line-height: 22px;
|
||||
@@ -2333,17 +2333,7 @@ a.account__display-name {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.directory__list {
|
||||
display: grid;
|
||||
grid-gap: 10px;
|
||||
grid-template-columns: minmax(0, 50%) minmax(0, 50%);
|
||||
|
||||
@media screen and (max-width: $no-gap-breakpoint) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.directory__card {
|
||||
.account-card {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@@ -4315,7 +4305,7 @@ a.status-card.compact:hover {
|
||||
}
|
||||
}
|
||||
|
||||
.upload-progess__message {
|
||||
.upload-progress__message {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
@@ -6219,136 +6209,20 @@ a.status-card.compact:hover {
|
||||
}
|
||||
}
|
||||
|
||||
.directory {
|
||||
&__list {
|
||||
width: 100%;
|
||||
margin: 10px 0;
|
||||
transition: opacity 100ms ease-in;
|
||||
.scrollable .account-card {
|
||||
margin: 10px;
|
||||
background: lighten($ui-base-color, 8%);
|
||||
}
|
||||
|
||||
&.loading {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
@media screen and (max-width: $no-gap-breakpoint) {
|
||||
margin: 0;
|
||||
}
|
||||
.scrollable .account-card__title__avatar {
|
||||
img,
|
||||
.account__avatar {
|
||||
border-color: lighten($ui-base-color, 8%);
|
||||
}
|
||||
}
|
||||
|
||||
&__card {
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 10px;
|
||||
|
||||
&__img {
|
||||
height: 125px;
|
||||
position: relative;
|
||||
background: darken($ui-base-color, 12%);
|
||||
overflow: hidden;
|
||||
|
||||
img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
&__bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: lighten($ui-base-color, 4%);
|
||||
padding: 10px;
|
||||
|
||||
&__name {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__relationship {
|
||||
width: 23px;
|
||||
min-height: 1px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
flex: 0 0 auto;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
padding-top: 2px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
margin: 0;
|
||||
border-radius: 4px;
|
||||
background: darken($ui-base-color, 8%);
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.display-name {
|
||||
margin-left: 15px;
|
||||
text-align: left;
|
||||
|
||||
strong {
|
||||
font-size: 15px;
|
||||
color: $primary-text-color;
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
span {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: $darker-text-color;
|
||||
font-weight: 400;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__extra {
|
||||
background: $ui-base-color;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.accounts-table__count {
|
||||
width: 33.33%;
|
||||
flex: 0 0 auto;
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
||||
.account__header__content {
|
||||
box-sizing: border-box;
|
||||
padding: 15px 10px;
|
||||
border-bottom: 1px solid lighten($ui-base-color, 8%);
|
||||
width: 100%;
|
||||
min-height: 18px + 30px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
p {
|
||||
display: none;
|
||||
|
||||
&:first-child {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
br {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.scrollable .account-card__bio::after {
|
||||
background: linear-gradient(to left, lighten($ui-base-color, 8%), transparent);
|
||||
}
|
||||
|
||||
.account-gallery__container {
|
||||
@@ -6452,6 +6326,7 @@ a.status-card.compact:hover {
|
||||
|
||||
&__column {
|
||||
padding: 10px 15px;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.radio-button {
|
||||
|
@@ -409,14 +409,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.directory__card {
|
||||
border-radius: 4px;
|
||||
|
||||
@media screen and (max-width: $no-gap-breakpoint) {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.page-header {
|
||||
@media screen and (max-width: $no-gap-breakpoint) {
|
||||
border-bottom: 0;
|
||||
@@ -835,19 +827,21 @@
|
||||
grid-gap: 10px;
|
||||
grid-template-columns: minmax(0, 50%) minmax(0, 50%);
|
||||
|
||||
.account-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@media screen and (max-width: $no-gap-breakpoint) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
font-size: 18px;
|
||||
.account-card {
|
||||
margin-bottom: 10px;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.directory__card {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.card-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
@@ -69,7 +69,7 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.autossugest-input {
|
||||
.autosuggest-input {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
|
@@ -12,11 +12,6 @@ body.rtl {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.directory__card__bar .display-name {
|
||||
margin-left: 0;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.display-name,
|
||||
.announcements__item {
|
||||
text-align: right;
|
||||
|
@@ -32,7 +32,6 @@ class UserSettingsDecorator
|
||||
user.settings['system_font_ui'] = system_font_ui_preference if change?('setting_system_font_ui')
|
||||
user.settings['system_emoji_font'] = system_emoji_font_preference if change?('setting_system_emoji_font')
|
||||
user.settings['noindex'] = noindex_preference if change?('setting_noindex')
|
||||
user.settings['hide_followers_count']= hide_followers_count_preference if change?('setting_hide_followers_count')
|
||||
user.settings['flavour'] = flavour_preference if change?('setting_flavour')
|
||||
user.settings['skin'] = skin_preference if change?('setting_skin')
|
||||
user.settings['hide_network'] = hide_network_preference if change?('setting_hide_network')
|
||||
@@ -110,10 +109,6 @@ class UserSettingsDecorator
|
||||
boolean_cast_setting 'setting_noindex'
|
||||
end
|
||||
|
||||
def hide_followers_count_preference
|
||||
boolean_cast_setting 'setting_hide_followers_count'
|
||||
end
|
||||
|
||||
def flavour_preference
|
||||
settings['setting_flavour']
|
||||
end
|
||||
|
@@ -351,11 +351,11 @@ class Account < ApplicationRecord
|
||||
end
|
||||
|
||||
def hides_followers?
|
||||
hide_collections? || user_hides_network?
|
||||
hide_collections?
|
||||
end
|
||||
|
||||
def hides_following?
|
||||
hide_collections? || user_hides_network?
|
||||
hide_collections?
|
||||
end
|
||||
|
||||
def object_type
|
||||
|
134
app/models/account_statuses_filter.rb
Normal file
134
app/models/account_statuses_filter.rb
Normal file
@@ -0,0 +1,134 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AccountStatusesFilter
|
||||
KEYS = %i(
|
||||
pinned
|
||||
tagged
|
||||
only_media
|
||||
exclude_replies
|
||||
exclude_reblogs
|
||||
).freeze
|
||||
|
||||
attr_reader :params, :account, :current_account
|
||||
|
||||
def initialize(account, current_account, params = {})
|
||||
@account = account
|
||||
@current_account = current_account
|
||||
@params = params
|
||||
end
|
||||
|
||||
def results
|
||||
scope = initial_scope
|
||||
|
||||
scope.merge!(pinned_scope) if pinned?
|
||||
scope.merge!(only_media_scope) if only_media?
|
||||
scope.merge!(no_replies_scope) if exclude_replies?
|
||||
scope.merge!(no_reblogs_scope) if exclude_reblogs?
|
||||
scope.merge!(hashtag_scope) if tagged?
|
||||
|
||||
scope
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def initial_scope
|
||||
if suspended?
|
||||
Status.none
|
||||
elsif anonymous?
|
||||
account.statuses.not_local_only.where(visibility: %i(public unlisted))
|
||||
elsif author?
|
||||
account.statuses.all # NOTE: #merge! does not work without the #all
|
||||
elsif blocked?
|
||||
Status.none
|
||||
else
|
||||
filtered_scope
|
||||
end
|
||||
end
|
||||
|
||||
def filtered_scope
|
||||
scope = account.statuses.left_outer_joins(:mentions)
|
||||
|
||||
scope.merge!(scope.where(visibility: follower? ? %i(public unlisted private) : %i(public unlisted)).or(scope.where(mentions: { account_id: current_account.id })).group(Status.arel_table[:id]))
|
||||
scope.merge!(filtered_reblogs_scope) if reblogs_may_occur?
|
||||
|
||||
scope
|
||||
end
|
||||
|
||||
def filtered_reblogs_scope
|
||||
Status.left_outer_joins(:reblog).where(reblog_of_id: nil).or(Status.where.not(reblogs_statuses: { account_id: current_account.excluded_from_timeline_account_ids }))
|
||||
end
|
||||
|
||||
def only_media_scope
|
||||
Status.joins(:media_attachments).merge(account.media_attachments.reorder(nil)).group(Status.arel_table[:id])
|
||||
end
|
||||
|
||||
def no_replies_scope
|
||||
Status.without_replies
|
||||
end
|
||||
|
||||
def no_reblogs_scope
|
||||
Status.without_reblogs
|
||||
end
|
||||
|
||||
def pinned_scope
|
||||
account.pinned_statuses.group(Status.arel_table[:id], StatusPin.arel_table[:created_at])
|
||||
end
|
||||
|
||||
def hashtag_scope
|
||||
tag = Tag.find_normalized(params[:tagged])
|
||||
|
||||
if tag
|
||||
Status.tagged_with(tag.id)
|
||||
else
|
||||
Status.none
|
||||
end
|
||||
end
|
||||
|
||||
def suspended?
|
||||
account.suspended?
|
||||
end
|
||||
|
||||
def anonymous?
|
||||
current_account.nil?
|
||||
end
|
||||
|
||||
def author?
|
||||
current_account.id == account.id
|
||||
end
|
||||
|
||||
def blocked?
|
||||
account.blocking?(current_account) || (current_account.domain.present? && account.domain_blocking?(current_account.domain))
|
||||
end
|
||||
|
||||
def follower?
|
||||
current_account.following?(account)
|
||||
end
|
||||
|
||||
def reblogs_may_occur?
|
||||
!exclude_reblogs? && !only_media? && !tagged?
|
||||
end
|
||||
|
||||
def pinned?
|
||||
truthy_param?(:pinned)
|
||||
end
|
||||
|
||||
def only_media?
|
||||
truthy_param?(:only_media)
|
||||
end
|
||||
|
||||
def exclude_replies?
|
||||
truthy_param?(:exclude_replies)
|
||||
end
|
||||
|
||||
def exclude_reblogs?
|
||||
truthy_param?(:exclude_reblogs)
|
||||
end
|
||||
|
||||
def tagged?
|
||||
params[:tagged].present?
|
||||
end
|
||||
|
||||
def truthy_param?(key)
|
||||
ActiveModel::Type::Boolean.new.cast(params[key])
|
||||
end
|
||||
end
|
@@ -129,6 +129,6 @@ class Report < ApplicationRecord
|
||||
def validate_rule_ids
|
||||
return unless violation?
|
||||
|
||||
errors.add(:rule_ids, I18n.t('reports.errors.invalid_rules')) unless rules.size == rule_ids.size
|
||||
errors.add(:rule_ids, I18n.t('reports.errors.invalid_rules')) unless rules.size == rule_ids&.size
|
||||
end
|
||||
end
|
||||
|
@@ -399,28 +399,6 @@ class Status < ApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
def permitted_for(target_account, account)
|
||||
visibility = [:public, :unlisted]
|
||||
|
||||
if account.nil?
|
||||
where(visibility: visibility).not_local_only
|
||||
elsif target_account.blocking?(account) || (account.domain.present? && target_account.domain_blocking?(account.domain)) # get rid of blocked peeps
|
||||
none
|
||||
elsif account.id == target_account.id # author can see own stuff
|
||||
all
|
||||
else
|
||||
# followers can see followers-only stuff, but also things they are mentioned in.
|
||||
# non-followers can see everything that isn't private/direct, but can see stuff they are mentioned in.
|
||||
visibility.push(:private) if account.following?(target_account)
|
||||
|
||||
scope = left_outer_joins(:reblog)
|
||||
|
||||
scope.where(visibility: visibility)
|
||||
.or(scope.where(id: account.mentions.select(:status_id)))
|
||||
.merge(scope.where(reblog_of_id: nil).or(scope.where.not(reblogs_statuses: { account_id: account.excluded_from_timeline_account_ids })))
|
||||
end
|
||||
end
|
||||
|
||||
def from_text(text)
|
||||
return [] if text.blank?
|
||||
|
||||
|
@@ -126,7 +126,7 @@ class User < ApplicationRecord
|
||||
has_many :session_activations, dependent: :destroy
|
||||
|
||||
delegate :auto_play_gif, :default_sensitive, :unfollow_modal, :boost_modal, :favourite_modal, :delete_modal,
|
||||
:reduce_motion, :system_font_ui, :noindex, :flavour, :skin, :display_media, :hide_network, :hide_followers_count,
|
||||
:reduce_motion, :system_font_ui, :noindex, :flavour, :skin, :display_media, :hide_followers_count,
|
||||
:expand_spoilers, :default_language, :aggregate_reblogs, :show_application,
|
||||
:advanced_layout, :use_blurhash, :use_pending_items, :trends, :crop_images,
|
||||
:disable_swiping, :default_content_type, :system_emoji_font,
|
||||
@@ -281,10 +281,6 @@ class User < ApplicationRecord
|
||||
settings.notification_emails['trending_status']
|
||||
end
|
||||
|
||||
def hides_network?
|
||||
@hides_network ||= settings.hide_network
|
||||
end
|
||||
|
||||
def aggregates_reblogs?
|
||||
@aggregates_reblogs ||= settings.aggregate_reblogs
|
||||
end
|
||||
|
@@ -42,7 +42,7 @@ class UserPolicy < ApplicationPolicy
|
||||
end
|
||||
|
||||
def promote?
|
||||
admin? && promoteable?
|
||||
admin? && promotable?
|
||||
end
|
||||
|
||||
def demote?
|
||||
@@ -51,7 +51,7 @@ class UserPolicy < ApplicationPolicy
|
||||
|
||||
private
|
||||
|
||||
def promoteable?
|
||||
def promotable?
|
||||
record.approved? && (!record.staff? || !record.admin?)
|
||||
end
|
||||
|
||||
|
17
app/presenters/familiar_followers_presenter.rb
Normal file
17
app/presenters/familiar_followers_presenter.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class FamiliarFollowersPresenter
|
||||
class Result < ActiveModelSerializers::Model
|
||||
attributes :id, :accounts
|
||||
end
|
||||
|
||||
def initialize(accounts, current_account_id)
|
||||
@accounts = accounts
|
||||
@current_account_id = current_account_id
|
||||
end
|
||||
|
||||
def accounts
|
||||
map = Follow.includes(account: :account_stat).where(target_account_id: @accounts.map(&:id)).where(account_id: Follow.where(account_id: @current_account_id).joins(:target_account).merge(Account.where(hide_collections: [nil, false])).select(:target_account_id)).group_by(&:target_account_id)
|
||||
@accounts.map { |account| Result.new(id: account.id, accounts: (account.hide_collections? ? [] : (map[account.id] || [])).map(&:account)) }
|
||||
end
|
||||
end
|
11
app/serializers/rest/familiar_followers_serializer.rb
Normal file
11
app/serializers/rest/familiar_followers_serializer.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class REST::FamiliarFollowersSerializer < ActiveModel::Serializer
|
||||
attribute :id
|
||||
|
||||
has_many :accounts, serializer: REST::AccountSerializer
|
||||
|
||||
def id
|
||||
object.id.to_s
|
||||
end
|
||||
end
|
@@ -10,8 +10,8 @@
|
||||
= account_fields.input :username, wrapper: :with_label, label: false, required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username'), :autocomplete => 'off', placeholder: t('simple_form.labels.defaults.username'), pattern: '[a-zA-Z0-9_]+', maxlength: 30 }, append: "@#{site_hostname}", hint: false, disabled: disabled
|
||||
|
||||
= f.input :email, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email'), :autocomplete => 'off' }, hint: false, disabled: disabled
|
||||
= f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'off', :minlength => User.password_length.first, :maxlength => User.password_length.last }, hint: false, disabled: disabled
|
||||
= f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password'), :autocomplete => 'off' }, hint: false, disabled: disabled
|
||||
= f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'new-password', :minlength => User.password_length.first, :maxlength => User.password_length.last }, hint: false, disabled: disabled
|
||||
= f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password'), :autocomplete => 'new-password' }, hint: false, disabled: disabled
|
||||
|
||||
= f.input :confirm_password, as: :string, placeholder: t('simple_form.labels.defaults.honeypot', label: t('simple_form.labels.defaults.password')), required: false, input_html: { 'aria-label' => t('simple_form.labels.defaults.honeypot', label: t('simple_form.labels.defaults.password')), :autocomplete => 'off' }, hint: false, disabled: disabled
|
||||
= f.input :website, as: :url, placeholder: t('simple_form.labels.defaults.honeypot', label: 'Website'), required: false, input_html: { 'aria-label' => t('simple_form.labels.defaults.honeypot', label: 'Website'), :autocomplete => 'off' }, hint: false, disabled: disabled
|
||||
|
@@ -19,37 +19,36 @@
|
||||
- else
|
||||
.directory__list
|
||||
- @accounts.each do |account|
|
||||
.directory__card
|
||||
.directory__card__img
|
||||
= image_tag account.header.url, alt: ''
|
||||
.directory__card__bar
|
||||
= link_to TagManager.instance.url_for(account), class: 'directory__card__bar__name' do
|
||||
.avatar
|
||||
= image_tag account.avatar.url, alt: '', class: 'u-photo'
|
||||
|
||||
.account-card
|
||||
= link_to TagManager.instance.url_for(account), class: 'account-card__permalink' do
|
||||
.account-card__header
|
||||
= image_tag account.header.url, alt: ''
|
||||
.account-card__title
|
||||
.account-card__title__avatar
|
||||
= image_tag account.avatar.url, alt: ''
|
||||
.display-name
|
||||
%bdi
|
||||
%strong.emojify.p-name= display_name(account, custom_emojify: true)
|
||||
%span= acct(account)
|
||||
.directory__card__bar__relationship.account__relationship
|
||||
= minimal_account_action_button(account)
|
||||
|
||||
.directory__card__extra
|
||||
.account__header__content.emojify= Formatter.instance.simplified_format(account, custom_emojify: true)
|
||||
|
||||
.directory__card__extra
|
||||
.accounts-table__count
|
||||
= friendly_number_to_human account.statuses_count
|
||||
%small= t('accounts.posts', count: account.statuses_count).downcase
|
||||
.accounts-table__count
|
||||
= hide_followers_count?(account) ? '-' : (friendly_number_to_human account.followers_count)
|
||||
%small= t('accounts.followers', count: account.followers_count).downcase
|
||||
.accounts-table__count
|
||||
- if account.last_status_at.present?
|
||||
%time.time-ago{ datetime: account.last_status_at.to_date.iso8601, title: l(account.last_status_at.to_date) }= l account.last_status_at.to_date
|
||||
- else
|
||||
= t('accounts.never_active')
|
||||
|
||||
%small= t('accounts.last_active')
|
||||
%span
|
||||
= acct(account)
|
||||
= fa_icon('lock') if account.locked?
|
||||
- if account.note.present?
|
||||
.account-card__bio.emojify
|
||||
= Formatter.instance.simplified_format(account, custom_emojify: true)
|
||||
- else
|
||||
.flex-spacer
|
||||
.account-card__actions
|
||||
.account-card__counters
|
||||
.account-card__counters__item
|
||||
= friendly_number_to_human account.statuses_count
|
||||
%small= t('accounts.posts', count: account.statuses_count).downcase
|
||||
.account-card__counters__item
|
||||
= hide_followers_count?(account) ? '-' : (friendly_number_to_human account.followers_count)
|
||||
%small= t('accounts.followers', count: account.followers_count).downcase
|
||||
.account-card__counters__item
|
||||
= friendly_number_to_human account.following_count
|
||||
%small= t('accounts.following', count: account.following_count).downcase
|
||||
.account-card__actions__button
|
||||
= account_action_button(account)
|
||||
|
||||
= paginate @accounts
|
||||
|
@@ -7,7 +7,7 @@
|
||||
|
||||
= render 'accounts/header', account: @account
|
||||
|
||||
- if @account.user_hides_network?
|
||||
- if @account.hide_collections?
|
||||
.nothing-here= t('accounts.network_hidden')
|
||||
- elsif user_signed_in? && @account.blocking?(current_account)
|
||||
.nothing-here= t('accounts.unavailable')
|
||||
|
@@ -7,7 +7,7 @@
|
||||
|
||||
= render 'accounts/header', account: @account
|
||||
|
||||
- if @account.user_hides_network?
|
||||
- if @account.hide_collections?
|
||||
.nothing-here= t('accounts.network_hidden')
|
||||
- elsif user_signed_in? && @account.blocking?(current_account)
|
||||
.nothing-here= t('accounts.unavailable')
|
||||
|
@@ -10,9 +10,6 @@
|
||||
.fields-group
|
||||
= f.input :setting_noindex, as: :boolean, wrapper: :with_label
|
||||
|
||||
.fields-group
|
||||
= f.input :setting_hide_network, as: :boolean, wrapper: :with_label
|
||||
|
||||
.fields-group
|
||||
= f.input :setting_aggregate_reblogs, as: :boolean, wrapper: :with_label, recommended: true
|
||||
|
||||
|
@@ -30,7 +30,10 @@
|
||||
= 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(Setting.profile_directory ? 'simple_form.hints.defaults.discoverable' : 'simple_form.hints.defaults.discoverable_no_directory'), recommended: true
|
||||
= f.input :discoverable, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.discoverable'), recommended: true
|
||||
|
||||
.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/
|
||||
|
||||
|
Reference in New Issue
Block a user