Merge pull request #2984 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changes up to e5655a5f65eb8116640ae434125553e0fe77f35e
This commit is contained in:
commit
75c65fba59
2
.github/workflows/lint-haml.yml
vendored
2
.github/workflows/lint-haml.yml
vendored
@ -43,4 +43,4 @@ jobs:
|
|||||||
- name: Run haml-lint
|
- name: Run haml-lint
|
||||||
run: |
|
run: |
|
||||||
echo "::add-matcher::.github/workflows/haml-lint-problem-matcher.json"
|
echo "::add-matcher::.github/workflows/haml-lint-problem-matcher.json"
|
||||||
bin/haml-lint --parallel --reporter github
|
bin/haml-lint --reporter github
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# This configuration was generated by
|
# This configuration was generated by
|
||||||
# `rubocop --auto-gen-config --auto-gen-only-exclude --no-offense-counts --no-auto-gen-timestamp`
|
# `rubocop --auto-gen-config --auto-gen-only-exclude --no-offense-counts --no-auto-gen-timestamp`
|
||||||
# using RuboCop version 1.72.2.
|
# using RuboCop version 1.73.1.
|
||||||
# The point is for the user to remove these configuration records
|
# The point is for the user to remove these configuration records
|
||||||
# one by one as the offenses are removed from the code base.
|
# one by one as the offenses are removed from the code base.
|
||||||
# Note that changes in the inspected code, or installation of new
|
# Note that changes in the inspected code, or installation of new
|
||||||
|
@ -719,7 +719,7 @@ GEM
|
|||||||
rspec-mocks (~> 3.0)
|
rspec-mocks (~> 3.0)
|
||||||
sidekiq (>= 5, < 8)
|
sidekiq (>= 5, < 8)
|
||||||
rspec-support (3.13.2)
|
rspec-support (3.13.2)
|
||||||
rubocop (1.72.2)
|
rubocop (1.73.1)
|
||||||
json (~> 2.3)
|
json (~> 2.3)
|
||||||
language_server-protocol (~> 3.17.0.2)
|
language_server-protocol (~> 3.17.0.2)
|
||||||
lint_roller (~> 1.1.0)
|
lint_roller (~> 1.1.0)
|
||||||
@ -730,7 +730,7 @@ GEM
|
|||||||
rubocop-ast (>= 1.38.0, < 2.0)
|
rubocop-ast (>= 1.38.0, < 2.0)
|
||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (>= 2.4.0, < 4.0)
|
unicode-display_width (>= 2.4.0, < 4.0)
|
||||||
rubocop-ast (1.38.0)
|
rubocop-ast (1.38.1)
|
||||||
parser (>= 3.3.1.0)
|
parser (>= 3.3.1.0)
|
||||||
rubocop-capybara (2.21.0)
|
rubocop-capybara (2.21.0)
|
||||||
rubocop (~> 1.41)
|
rubocop (~> 1.41)
|
||||||
@ -738,7 +738,7 @@ GEM
|
|||||||
lint_roller (~> 1.1)
|
lint_roller (~> 1.1)
|
||||||
rubocop (>= 1.72.1, < 2.0)
|
rubocop (>= 1.72.1, < 2.0)
|
||||||
rubocop-ast (>= 1.38.0, < 2.0)
|
rubocop-ast (>= 1.38.0, < 2.0)
|
||||||
rubocop-rails (2.30.2)
|
rubocop-rails (2.30.3)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
lint_roller (~> 1.1)
|
lint_roller (~> 1.1)
|
||||||
rack (>= 1.1)
|
rack (>= 1.1)
|
||||||
@ -854,7 +854,7 @@ GEM
|
|||||||
unicode-display_width (3.1.4)
|
unicode-display_width (3.1.4)
|
||||||
unicode-emoji (~> 4.0, >= 4.0.4)
|
unicode-emoji (~> 4.0, >= 4.0.4)
|
||||||
unicode-emoji (4.0.4)
|
unicode-emoji (4.0.4)
|
||||||
uri (1.0.2)
|
uri (1.0.3)
|
||||||
useragent (0.16.11)
|
useragent (0.16.11)
|
||||||
validate_email (0.1.6)
|
validate_email (0.1.6)
|
||||||
activemodel (>= 3.0)
|
activemodel (>= 3.0)
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
class Api::V1::MediaController < Api::BaseController
|
class Api::V1::MediaController < Api::BaseController
|
||||||
before_action -> { doorkeeper_authorize! :write, :'write:media' }
|
before_action -> { doorkeeper_authorize! :write, :'write:media' }
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
before_action :set_media_attachment, except: [:create]
|
before_action :set_media_attachment, except: [:create, :destroy]
|
||||||
before_action :check_processing, except: [:create]
|
before_action :check_processing, except: [:create, :destroy]
|
||||||
|
|
||||||
def show
|
def show
|
||||||
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment
|
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment
|
||||||
@ -25,6 +25,15 @@ class Api::V1::MediaController < Api::BaseController
|
|||||||
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment
|
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@media_attachment = current_account.media_attachments.find(params[:id])
|
||||||
|
|
||||||
|
return render json: in_usage_error, status: 422 unless @media_attachment.status_id.nil?
|
||||||
|
|
||||||
|
@media_attachment.destroy
|
||||||
|
render_empty
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def status_code_for_media_attachment
|
def status_code_for_media_attachment
|
||||||
@ -54,4 +63,8 @@ class Api::V1::MediaController < Api::BaseController
|
|||||||
def processing_error
|
def processing_error
|
||||||
{ error: 'Error processing thumbnail for uploaded media' }
|
{ error: 'Error processing thumbnail for uploaded media' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def in_usage_error
|
||||||
|
{ error: 'Media attachment is currently used by a status' }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -113,7 +113,7 @@ class Api::V1::StatusesController < Api::BaseController
|
|||||||
@status.account.statuses_count = @status.account.statuses_count - 1
|
@status.account.statuses_count = @status.account.statuses_count - 1
|
||||||
json = render_to_body json: @status, serializer: REST::StatusSerializer, source_requested: true
|
json = render_to_body json: @status, serializer: REST::StatusSerializer, source_requested: true
|
||||||
|
|
||||||
RemovalWorker.perform_async(@status.id, { 'redraft' => true })
|
RemovalWorker.perform_async(@status.id, { 'redraft' => !truthy_param?(:delete_media) })
|
||||||
|
|
||||||
render json: json
|
render json: json
|
||||||
end
|
end
|
||||||
|
@ -142,6 +142,13 @@ export function fetchAccountFail(id, error) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} id
|
||||||
|
* @param {Object} options
|
||||||
|
* @param {boolean} [options.reblogs]
|
||||||
|
* @param {boolean} [options.notify]
|
||||||
|
* @returns {function(): void}
|
||||||
|
*/
|
||||||
export function followAccount(id, options = { reblogs: true }) {
|
export function followAccount(id, options = { reblogs: true }) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const alreadyFollowing = getState().getIn(['relationships', id, 'following']);
|
const alreadyFollowing = getState().getIn(['relationships', id, 'following']);
|
||||||
|
@ -139,7 +139,7 @@ export function deleteStatus(id, withRedraft = false) {
|
|||||||
|
|
||||||
dispatch(deleteStatusRequest(id));
|
dispatch(deleteStatusRequest(id));
|
||||||
|
|
||||||
api().delete(`/api/v1/statuses/${id}`).then(response => {
|
api().delete(`/api/v1/statuses/${id}`, { params: { delete_media: !withRedraft } }).then(response => {
|
||||||
dispatch(deleteStatusSuccess(id));
|
dispatch(deleteStatusSuccess(id));
|
||||||
dispatch(deleteFromTimelines(id));
|
dispatch(deleteFromTimelines(id));
|
||||||
dispatch(importFetchedAccount(response.data.account));
|
dispatch(importFetchedAccount(response.data.account));
|
||||||
|
@ -57,7 +57,7 @@ export const FollowButton: React.FC<{
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!relationship) return;
|
if (!relationship || !accountId) return;
|
||||||
|
|
||||||
if (accountId === me) {
|
if (accountId === me) {
|
||||||
return;
|
return;
|
||||||
|
@ -1,91 +0,0 @@
|
|||||||
import { PureComponent } from 'react';
|
|
||||||
|
|
||||||
import { FormattedMessage, FormattedNumber } from 'react-intl';
|
|
||||||
|
|
||||||
import { NavLink } from 'react-router-dom';
|
|
||||||
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
|
|
||||||
import InfoIcon from '@/material-icons/400-24px/info.svg?react';
|
|
||||||
import { Icon } from 'flavours/glitch/components/icon';
|
|
||||||
|
|
||||||
|
|
||||||
class ActionBar extends PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
account: ImmutablePropTypes.map.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
isStatusesPageActive = (match, location) => {
|
|
||||||
if (!match) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return !location.pathname.match(/\/(followers|following)\/?$/);
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { account } = this.props;
|
|
||||||
|
|
||||||
if (account.get('suspended')) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div className='account__disclaimer'>
|
|
||||||
<Icon id='info-circle' icon={InfoIcon} />
|
|
||||||
<FormattedMessage
|
|
||||||
id='account.suspended_disclaimer_full'
|
|
||||||
defaultMessage='This user has been suspended by a moderator.'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let extraInfo = '';
|
|
||||||
|
|
||||||
if (account.get('acct') !== account.get('username')) {
|
|
||||||
extraInfo = (
|
|
||||||
<div className='account__disclaimer'>
|
|
||||||
<Icon id='info-circle' icon={InfoIcon} />
|
|
||||||
<div>
|
|
||||||
<FormattedMessage
|
|
||||||
id='account.disclaimer_full'
|
|
||||||
defaultMessage="Information below may reflect the user's profile incompletely."
|
|
||||||
/>
|
|
||||||
{' '}
|
|
||||||
<a target='_blank' rel='noopener' href={account.get('url')}>
|
|
||||||
<FormattedMessage id='account.view_full_profile' defaultMessage='View full profile' />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{extraInfo}
|
|
||||||
|
|
||||||
<div className='account__action-bar'>
|
|
||||||
<div className='account__action-bar-links'>
|
|
||||||
<NavLink isActive={this.isStatusesPageActive} activeClassName='active' className='account__action-bar__tab' to={`/@${account.get('acct')}`}>
|
|
||||||
<FormattedMessage id='account.posts' defaultMessage='Posts' />
|
|
||||||
<strong><FormattedNumber value={account.get('statuses_count')} /></strong>
|
|
||||||
</NavLink>
|
|
||||||
|
|
||||||
<NavLink exact activeClassName='active' className='account__action-bar__tab' to={`/@${account.get('acct')}/following`}>
|
|
||||||
<FormattedMessage id='account.follows' defaultMessage='Follows' />
|
|
||||||
<strong><FormattedNumber value={account.get('following_count')} /></strong>
|
|
||||||
</NavLink>
|
|
||||||
|
|
||||||
<NavLink exact activeClassName='active' className='account__action-bar__tab' to={`/@${account.get('acct')}/followers`}>
|
|
||||||
<FormattedMessage id='account.followers' defaultMessage='Followers' />
|
|
||||||
<strong>{ account.get('followers_count') < 0 ? '-' : <FormattedNumber value={account.get('followers_count')} /> }</strong>
|
|
||||||
</NavLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ActionBar;
|
|
@ -0,0 +1,106 @@
|
|||||||
|
import { FormattedMessage, FormattedNumber } from 'react-intl';
|
||||||
|
|
||||||
|
import { NavLink } from 'react-router-dom';
|
||||||
|
import type { NavLinkProps } from 'react-router-dom';
|
||||||
|
|
||||||
|
import InfoIcon from '@/material-icons/400-24px/info.svg?react';
|
||||||
|
import { Icon } from 'flavours/glitch/components/icon';
|
||||||
|
import type { Account } from 'flavours/glitch/models/account';
|
||||||
|
|
||||||
|
const isStatusesPageActive: NavLinkProps['isActive'] = (match, location) => {
|
||||||
|
if (!match) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !/\/(followers|following)\/?$/.exec(location.pathname);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ActionBar: React.FC<{ account: Account }> = ({ account }) => {
|
||||||
|
if (account.suspended) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className='account__disclaimer'>
|
||||||
|
<Icon id='info-circle' icon={InfoIcon} />
|
||||||
|
<FormattedMessage
|
||||||
|
id='account.suspended_disclaimer_full'
|
||||||
|
defaultMessage='This user has been suspended by a moderator.'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let extraInfo = null;
|
||||||
|
|
||||||
|
if (account.get('acct') !== account.get('username')) {
|
||||||
|
extraInfo = (
|
||||||
|
<div className='account__disclaimer'>
|
||||||
|
<Icon id='info-circle' icon={InfoIcon} />
|
||||||
|
<div>
|
||||||
|
<FormattedMessage
|
||||||
|
id='account.disclaimer_full'
|
||||||
|
defaultMessage="Information below may reflect the user's profile incompletely."
|
||||||
|
/>{' '}
|
||||||
|
<a target='_blank' rel='noopener' href={account.get('url')}>
|
||||||
|
<FormattedMessage
|
||||||
|
id='account.view_full_profile'
|
||||||
|
defaultMessage='View full profile'
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{extraInfo}
|
||||||
|
|
||||||
|
<div className='account__action-bar'>
|
||||||
|
<div className='account__action-bar-links'>
|
||||||
|
<NavLink
|
||||||
|
isActive={isStatusesPageActive}
|
||||||
|
activeClassName='active'
|
||||||
|
className='account__action-bar__tab'
|
||||||
|
to={`/@${account.get('acct')}`}
|
||||||
|
>
|
||||||
|
<FormattedMessage id='account.posts' defaultMessage='Posts' />
|
||||||
|
<strong>
|
||||||
|
<FormattedNumber value={account.get('statuses_count')} />
|
||||||
|
</strong>
|
||||||
|
</NavLink>
|
||||||
|
|
||||||
|
<NavLink
|
||||||
|
exact
|
||||||
|
activeClassName='active'
|
||||||
|
className='account__action-bar__tab'
|
||||||
|
to={`/@${account.get('acct')}/following`}
|
||||||
|
>
|
||||||
|
<FormattedMessage id='account.follows' defaultMessage='Follows' />
|
||||||
|
<strong>
|
||||||
|
<FormattedNumber value={account.get('following_count')} />
|
||||||
|
</strong>
|
||||||
|
</NavLink>
|
||||||
|
|
||||||
|
<NavLink
|
||||||
|
exact
|
||||||
|
activeClassName='active'
|
||||||
|
className='account__action-bar__tab'
|
||||||
|
to={`/@${account.get('acct')}/followers`}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='account.followers'
|
||||||
|
defaultMessage='Followers'
|
||||||
|
/>
|
||||||
|
<strong>
|
||||||
|
{account.get('followers_count') < 0 ? (
|
||||||
|
'-'
|
||||||
|
) : (
|
||||||
|
<FormattedNumber value={account.get('followers_count')} />
|
||||||
|
)}
|
||||||
|
</strong>
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -1,415 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { Helmet } from 'react-helmet';
|
|
||||||
import { withRouter } from 'react-router-dom';
|
|
||||||
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
|
|
||||||
import CheckIcon from '@/material-icons/400-24px/check.svg?react';
|
|
||||||
import LockIcon from '@/material-icons/400-24px/lock.svg?react';
|
|
||||||
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
|
||||||
import NotificationsIcon from '@/material-icons/400-24px/notifications.svg?react';
|
|
||||||
import NotificationsActiveIcon from '@/material-icons/400-24px/notifications_active-fill.svg?react';
|
|
||||||
import ShareIcon from '@/material-icons/400-24px/share.svg?react';
|
|
||||||
import { Avatar } from 'flavours/glitch/components/avatar';
|
|
||||||
import { Badge, AutomatedBadge, GroupBadge } from 'flavours/glitch/components/badge';
|
|
||||||
import { Button } from 'flavours/glitch/components/button';
|
|
||||||
import { CopyIconButton } from 'flavours/glitch/components/copy_icon_button';
|
|
||||||
import { Icon } from 'flavours/glitch/components/icon';
|
|
||||||
import { IconButton } from 'flavours/glitch/components/icon_button';
|
|
||||||
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
|
||||||
import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container';
|
|
||||||
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
|
|
||||||
import { autoPlayGif, me, domain as localDomain } from 'flavours/glitch/initial_state';
|
|
||||||
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions';
|
|
||||||
import { preferencesLink, profileLink, accountAdminLink } from 'flavours/glitch/utils/backend_links';
|
|
||||||
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
|
|
||||||
|
|
||||||
import AccountNoteContainer from '../containers/account_note_container';
|
|
||||||
import FollowRequestNoteContainer from '../containers/follow_request_note_container';
|
|
||||||
|
|
||||||
import { DomainPill } from './domain_pill';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
|
||||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
|
||||||
cancel_follow_request: { id: 'account.cancel_follow_request', defaultMessage: 'Withdraw follow request' },
|
|
||||||
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
|
|
||||||
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
|
|
||||||
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
|
|
||||||
linkVerifiedOn: { id: 'account.link_verified_on', defaultMessage: 'Ownership of this link was checked on {date}' },
|
|
||||||
account_locked: { id: 'account.locked_info', defaultMessage: 'This account privacy status is set to locked. The owner manually reviews who can follow them.' },
|
|
||||||
mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' },
|
|
||||||
direct: { id: 'account.direct', defaultMessage: 'Privately mention @{name}' },
|
|
||||||
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
|
|
||||||
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
|
|
||||||
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
|
|
||||||
report: { id: 'account.report', defaultMessage: 'Report @{name}' },
|
|
||||||
share: { id: 'account.share', defaultMessage: 'Share @{name}\'s profile' },
|
|
||||||
copy: { id: 'account.copy', defaultMessage: 'Copy link to profile' },
|
|
||||||
media: { id: 'account.media', defaultMessage: 'Media' },
|
|
||||||
blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' },
|
|
||||||
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
|
|
||||||
hideReblogs: { id: 'account.hide_reblogs', defaultMessage: 'Hide boosts from @{name}' },
|
|
||||||
showReblogs: { id: 'account.show_reblogs', defaultMessage: 'Show boosts from @{name}' },
|
|
||||||
enableNotifications: { id: 'account.enable_notifications', defaultMessage: 'Notify me when @{name} posts' },
|
|
||||||
disableNotifications: { id: 'account.disable_notifications', defaultMessage: 'Stop notifying me when @{name} posts' },
|
|
||||||
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned posts' },
|
|
||||||
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
|
|
||||||
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
|
|
||||||
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favorites' },
|
|
||||||
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
|
|
||||||
followed_tags: { id: 'navigation_bar.followed_tags', defaultMessage: 'Followed hashtags' },
|
|
||||||
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
|
|
||||||
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Blocked domains' },
|
|
||||||
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
|
|
||||||
endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' },
|
|
||||||
unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
|
|
||||||
add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' },
|
|
||||||
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
|
|
||||||
admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' },
|
|
||||||
languages: { id: 'account.languages', defaultMessage: 'Change subscribed languages' },
|
|
||||||
openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const titleFromAccount = account => {
|
|
||||||
const displayName = account.get('display_name');
|
|
||||||
const acct = account.get('acct') === account.get('username') ? `${account.get('username')}@${localDomain}` : account.get('acct');
|
|
||||||
const prefix = displayName.trim().length === 0 ? account.get('username') : displayName;
|
|
||||||
|
|
||||||
return `${prefix} (@${acct})`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const dateFormatOptions = {
|
|
||||||
month: 'short',
|
|
||||||
day: 'numeric',
|
|
||||||
year: 'numeric',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit',
|
|
||||||
};
|
|
||||||
|
|
||||||
class Header extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
identity: identityContextPropShape,
|
|
||||||
account: ImmutablePropTypes.record,
|
|
||||||
identity_props: ImmutablePropTypes.list,
|
|
||||||
onFollow: PropTypes.func.isRequired,
|
|
||||||
onBlock: PropTypes.func.isRequired,
|
|
||||||
onMention: PropTypes.func.isRequired,
|
|
||||||
onDirect: PropTypes.func.isRequired,
|
|
||||||
onReblogToggle: PropTypes.func.isRequired,
|
|
||||||
onNotifyToggle: PropTypes.func.isRequired,
|
|
||||||
onReport: PropTypes.func.isRequired,
|
|
||||||
onMute: PropTypes.func.isRequired,
|
|
||||||
onBlockDomain: PropTypes.func.isRequired,
|
|
||||||
onUnblockDomain: PropTypes.func.isRequired,
|
|
||||||
onEndorseToggle: PropTypes.func.isRequired,
|
|
||||||
onAddToList: PropTypes.func.isRequired,
|
|
||||||
onChangeLanguages: PropTypes.func.isRequired,
|
|
||||||
onInteractionModal: PropTypes.func.isRequired,
|
|
||||||
onOpenAvatar: PropTypes.func.isRequired,
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
domain: PropTypes.string.isRequired,
|
|
||||||
hidden: PropTypes.bool,
|
|
||||||
...WithRouterPropTypes,
|
|
||||||
};
|
|
||||||
|
|
||||||
openEditProfile = () => {
|
|
||||||
window.open(profileLink, '_blank');
|
|
||||||
};
|
|
||||||
|
|
||||||
handleMouseEnter = ({ currentTarget }) => {
|
|
||||||
if (autoPlayGif) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
|
||||||
|
|
||||||
for (var i = 0; i < emojis.length; i++) {
|
|
||||||
let emoji = emojis[i];
|
|
||||||
emoji.src = emoji.getAttribute('data-original');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleMouseLeave = ({ currentTarget }) => {
|
|
||||||
if (autoPlayGif) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
|
||||||
|
|
||||||
for (var i = 0; i < emojis.length; i++) {
|
|
||||||
let emoji = emojis[i];
|
|
||||||
emoji.src = emoji.getAttribute('data-static');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleAvatarClick = e => {
|
|
||||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
|
||||||
e.preventDefault();
|
|
||||||
this.props.onOpenAvatar();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleShare = () => {
|
|
||||||
const { account } = this.props;
|
|
||||||
|
|
||||||
navigator.share({
|
|
||||||
url: account.get('url'),
|
|
||||||
}).catch((e) => {
|
|
||||||
if (e.name !== 'AbortError') console.error(e);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { account, hidden, intl } = this.props;
|
|
||||||
const { signedIn, permissions } = this.props.identity;
|
|
||||||
|
|
||||||
if (!account) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const suspended = account.get('suspended');
|
|
||||||
const isRemote = account.get('acct') !== account.get('username');
|
|
||||||
const remoteDomain = isRemote ? account.get('acct').split('@')[1] : null;
|
|
||||||
|
|
||||||
let actionBtn, bellBtn, lockedIcon, shareBtn;
|
|
||||||
|
|
||||||
let info = [];
|
|
||||||
let menu = [];
|
|
||||||
|
|
||||||
if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) {
|
|
||||||
info.push(<span className='relationship-tag'><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span>);
|
|
||||||
} else if (me !== account.get('id') && account.getIn(['relationship', 'blocking'])) {
|
|
||||||
info.push(<span className='relationship-tag'><FormattedMessage id='account.blocked' defaultMessage='Blocked' /></span>);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (me !== account.get('id') && account.getIn(['relationship', 'muting'])) {
|
|
||||||
info.push(<span className='relationship-tag'><FormattedMessage id='account.muted' defaultMessage='Muted' /></span>);
|
|
||||||
} else if (me !== account.get('id') && account.getIn(['relationship', 'domain_blocking'])) {
|
|
||||||
info.push(<span className='relationship-tag'><FormattedMessage id='account.domain_blocked' defaultMessage='Domain blocked' /></span>);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (account.getIn(['relationship', 'requested']) || account.getIn(['relationship', 'following'])) {
|
|
||||||
bellBtn = <IconButton icon={account.getIn(['relationship', 'notifying']) ? 'bell' : 'bell-o'} iconComponent={account.getIn(['relationship', 'notifying']) ? NotificationsActiveIcon : NotificationsIcon} active={account.getIn(['relationship', 'notifying'])} title={intl.formatMessage(account.getIn(['relationship', 'notifying']) ? messages.disableNotifications : messages.enableNotifications, { name: account.get('username') })} onClick={this.props.onNotifyToggle} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('share' in navigator) {
|
|
||||||
shareBtn = <IconButton className='optional' iconComponent={ShareIcon} title={intl.formatMessage(messages.share, { name: account.get('username') })} onClick={this.handleShare} />;
|
|
||||||
} else {
|
|
||||||
shareBtn = <CopyIconButton className='optional' title={intl.formatMessage(messages.copy)} value={account.get('url')} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (me !== account.get('id')) {
|
|
||||||
if (signedIn && !account.get('relationship')) { // Wait until the relationship is loaded
|
|
||||||
actionBtn = <Button disabled><LoadingIndicator /></Button>;
|
|
||||||
} else if (account.getIn(['relationship', 'requested'])) {
|
|
||||||
actionBtn = <Button text={intl.formatMessage(messages.cancel_follow_request)} title={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />;
|
|
||||||
} else if (!account.getIn(['relationship', 'blocking'])) {
|
|
||||||
actionBtn = <Button className={classNames({ 'button--destructive': account.getIn(['relationship', 'following']) })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={signedIn ? this.props.onFollow : this.props.onInteractionModal} />;
|
|
||||||
} else if (account.getIn(['relationship', 'blocking'])) {
|
|
||||||
actionBtn = <Button text={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />;
|
|
||||||
}
|
|
||||||
} else if (profileLink) {
|
|
||||||
actionBtn = <Button text={intl.formatMessage(messages.edit_profile)} onClick={this.openEditProfile} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (account.get('moved') && !account.getIn(['relationship', 'following'])) {
|
|
||||||
actionBtn = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (account.get('suspended') && !account.getIn(['relationship', 'following'])) {
|
|
||||||
actionBtn = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (account.get('locked')) {
|
|
||||||
lockedIcon = <Icon id='lock' icon={LockIcon} title={intl.formatMessage(messages.account_locked)} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (signedIn && account.get('id') !== me && !account.get('suspended')) {
|
|
||||||
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
|
|
||||||
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.props.onDirect });
|
|
||||||
menu.push(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isRemote) {
|
|
||||||
menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: account.get('url') });
|
|
||||||
menu.push(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (account.get('id') === me) {
|
|
||||||
if (profileLink) menu.push({ text: intl.formatMessage(messages.edit_profile), href: profileLink });
|
|
||||||
if (preferencesLink) menu.push({ text: intl.formatMessage(messages.preferences), href: preferencesLink });
|
|
||||||
menu.push({ text: intl.formatMessage(messages.pins), to: '/pinned' });
|
|
||||||
menu.push(null);
|
|
||||||
menu.push({ text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' });
|
|
||||||
menu.push({ text: intl.formatMessage(messages.favourites), to: '/favourites' });
|
|
||||||
menu.push({ text: intl.formatMessage(messages.lists), to: '/lists' });
|
|
||||||
menu.push({ text: intl.formatMessage(messages.followed_tags), to: '/followed_tags' });
|
|
||||||
menu.push(null);
|
|
||||||
menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' });
|
|
||||||
menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' });
|
|
||||||
menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' });
|
|
||||||
} else if (signedIn) {
|
|
||||||
if (account.getIn(['relationship', 'following'])) {
|
|
||||||
if (!account.getIn(['relationship', 'muting'])) {
|
|
||||||
if (account.getIn(['relationship', 'showing_reblogs'])) {
|
|
||||||
menu.push({ text: intl.formatMessage(messages.hideReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
|
|
||||||
} else {
|
|
||||||
menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
|
|
||||||
}
|
|
||||||
|
|
||||||
menu.push({ text: intl.formatMessage(messages.languages), action: this.props.onChangeLanguages });
|
|
||||||
menu.push(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle });
|
|
||||||
menu.push({ text: intl.formatMessage(messages.add_or_remove_from_list), action: this.props.onAddToList });
|
|
||||||
menu.push(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (account.getIn(['relationship', 'muting'])) {
|
|
||||||
menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.props.onMute });
|
|
||||||
} else {
|
|
||||||
menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.props.onMute, dangerous: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (account.getIn(['relationship', 'blocking'])) {
|
|
||||||
menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.props.onBlock });
|
|
||||||
} else {
|
|
||||||
menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.props.onBlock, dangerous: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!account.get('suspended')) {
|
|
||||||
menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport, dangerous: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (signedIn && isRemote) {
|
|
||||||
menu.push(null);
|
|
||||||
|
|
||||||
if (account.getIn(['relationship', 'domain_blocking'])) {
|
|
||||||
menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain: remoteDomain }), action: this.props.onUnblockDomain });
|
|
||||||
} else {
|
|
||||||
menu.push({ text: intl.formatMessage(messages.blockDomain, { domain: remoteDomain }), action: this.props.onBlockDomain, dangerous: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (account.get('id') !== me && ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS && accountAdminLink) || (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION)) {
|
|
||||||
menu.push(null);
|
|
||||||
if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS && accountAdminLink) {
|
|
||||||
menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: accountAdminLink(account.get('id')) });
|
|
||||||
}
|
|
||||||
if (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION) {
|
|
||||||
menu.push({ text: intl.formatMessage(messages.admin_domain, { domain: remoteDomain }), href: `/admin/instances/${remoteDomain}` });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const content = { __html: account.get('note_emojified') };
|
|
||||||
const displayNameHtml = { __html: account.get('display_name_html') };
|
|
||||||
const fields = account.get('fields');
|
|
||||||
const isLocal = account.get('acct').indexOf('@') === -1;
|
|
||||||
const username = account.get('acct').split('@')[0];
|
|
||||||
const domain = isLocal ? localDomain : account.get('acct').split('@')[1];
|
|
||||||
const isIndexable = !account.get('noindex');
|
|
||||||
|
|
||||||
const badges = [];
|
|
||||||
|
|
||||||
if (account.get('bot')) {
|
|
||||||
badges.push(<AutomatedBadge key='bot-badge' />);
|
|
||||||
} else if (account.get('group')) {
|
|
||||||
badges.push(<GroupBadge key='group-badge' />);
|
|
||||||
}
|
|
||||||
|
|
||||||
account.get('roles', []).forEach((role) => {
|
|
||||||
badges.push(<Badge key={`role-badge-${role.get('id')}`} label={<span>{role.get('name')}</span>} domain={domain} roleId={role.get('id')} />);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classNames('account__header', { inactive: !!account.get('moved') })} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
|
||||||
{!(suspended || hidden || account.get('moved')) && account.getIn(['relationship', 'requested_by']) && <FollowRequestNoteContainer account={account} />}
|
|
||||||
|
|
||||||
<div className='account__header__image'>
|
|
||||||
<div className='account__header__info'>
|
|
||||||
{info}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{!(suspended || hidden) && <img src={autoPlayGif ? account.get('header') : account.get('header_static')} alt='' className='parallax' />}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='account__header__bar'>
|
|
||||||
<div className='account__header__tabs'>
|
|
||||||
<a className='avatar' href={account.get('avatar')} rel='noopener' target='_blank' onClick={this.handleAvatarClick}>
|
|
||||||
<Avatar account={suspended || hidden ? undefined : account} size={90} />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div className='account__header__tabs__buttons'>
|
|
||||||
{!hidden && bellBtn}
|
|
||||||
{!hidden && shareBtn}
|
|
||||||
<DropdownMenuContainer disabled={menu.length === 0} items={menu} icon='ellipsis-v' iconComponent={MoreHorizIcon} size={24} direction='right' />
|
|
||||||
{!hidden && actionBtn}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='account__header__tabs__name'>
|
|
||||||
<h1>
|
|
||||||
<span dangerouslySetInnerHTML={displayNameHtml} />
|
|
||||||
<small>
|
|
||||||
<span>@{username}<span className='invisible'>@{domain}</span></span>
|
|
||||||
<DomainPill username={username} domain={domain} isSelf={me === account.get('id')} />
|
|
||||||
{lockedIcon}
|
|
||||||
</small>
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{badges.length > 0 && (
|
|
||||||
<div className='account__header__badges'>
|
|
||||||
{badges}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!(suspended || hidden) && (
|
|
||||||
<div className='account__header__extra'>
|
|
||||||
<div className='account__header__bio'>
|
|
||||||
{(account.get('id') !== me && signedIn) && <AccountNoteContainer account={account} />}
|
|
||||||
|
|
||||||
{account.get('note').length > 0 && account.get('note') !== '<p></p>' && <div className='account__header__content translate' dangerouslySetInnerHTML={content} />}
|
|
||||||
|
|
||||||
<div className='account__header__fields'>
|
|
||||||
<dl>
|
|
||||||
<dt><FormattedMessage id='account.joined_short' defaultMessage='Joined' /></dt>
|
|
||||||
<dd>{intl.formatDate(account.get('created_at'), { year: 'numeric', month: 'short', day: '2-digit' })}</dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
{fields.map((pair, i) => (
|
|
||||||
<dl key={i} className={classNames({ verified: pair.get('verified_at') })}>
|
|
||||||
<dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} className='translate' />
|
|
||||||
|
|
||||||
<dd className='translate' title={pair.get('value_plain')}>
|
|
||||||
{pair.get('verified_at') && <span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(pair.get('verified_at'), dateFormatOptions) })}><Icon id='check' icon={CheckIcon} className='verified__mark' /></span>} <span dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} />
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Helmet>
|
|
||||||
<title>{titleFromAccount(account)}</title>
|
|
||||||
<meta name='robots' content={(isLocal && isIndexable) ? 'all' : 'noindex'} />
|
|
||||||
<link rel='canonical' href={account.get('url')} />
|
|
||||||
</Helmet>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withRouter(withIdentity(injectIntl(Header)));
|
|
@ -17,7 +17,7 @@ import { normalizeForLookup } from 'flavours/glitch/reducers/accounts_map';
|
|||||||
import { getAccountGallery } from 'flavours/glitch/selectors';
|
import { getAccountGallery } from 'flavours/glitch/selectors';
|
||||||
|
|
||||||
import { expandAccountMediaTimeline } from '../../actions/timelines';
|
import { expandAccountMediaTimeline } from '../../actions/timelines';
|
||||||
import HeaderContainer from '../account_timeline/containers/header_container';
|
import { AccountHeader } from '../account_timeline/components/account_header';
|
||||||
import Column from '../ui/components/column';
|
import Column from '../ui/components/column';
|
||||||
|
|
||||||
import { MediaItem } from './components/media_item';
|
import { MediaItem } from './components/media_item';
|
||||||
@ -205,7 +205,7 @@ class AccountGallery extends ImmutablePureComponent {
|
|||||||
|
|
||||||
<ScrollContainer scrollKey='account_gallery'>
|
<ScrollContainer scrollKey='account_gallery'>
|
||||||
<div className='scrollable scrollable--flex' onScroll={this.handleScroll}>
|
<div className='scrollable scrollable--flex' onScroll={this.handleScroll}>
|
||||||
<HeaderContainer accountId={this.props.accountId} />
|
<AccountHeader accountId={this.props.accountId} />
|
||||||
|
|
||||||
{suspended ? (
|
{suspended ? (
|
||||||
<div className='empty-column-indicator'>
|
<div className='empty-column-indicator'>
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,158 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
import { NavLink } from 'react-router-dom';
|
|
||||||
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
|
|
||||||
import ActionBar from '../../account/components/action_bar';
|
|
||||||
import InnerHeader from '../../account/components/header';
|
|
||||||
|
|
||||||
import MemorialNote from './memorial_note';
|
|
||||||
import MovedNote from './moved_note';
|
|
||||||
|
|
||||||
class Header extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
account: ImmutablePropTypes.record,
|
|
||||||
onFollow: PropTypes.func.isRequired,
|
|
||||||
onBlock: PropTypes.func.isRequired,
|
|
||||||
onMention: PropTypes.func.isRequired,
|
|
||||||
onDirect: PropTypes.func.isRequired,
|
|
||||||
onReblogToggle: PropTypes.func.isRequired,
|
|
||||||
onReport: PropTypes.func.isRequired,
|
|
||||||
onMute: PropTypes.func.isRequired,
|
|
||||||
onBlockDomain: PropTypes.func.isRequired,
|
|
||||||
onUnblockDomain: PropTypes.func.isRequired,
|
|
||||||
onEndorseToggle: PropTypes.func.isRequired,
|
|
||||||
onAddToList: PropTypes.func.isRequired,
|
|
||||||
onChangeLanguages: PropTypes.func.isRequired,
|
|
||||||
onInteractionModal: PropTypes.func.isRequired,
|
|
||||||
onOpenAvatar: PropTypes.func.isRequired,
|
|
||||||
hideTabs: PropTypes.bool,
|
|
||||||
domain: PropTypes.string.isRequired,
|
|
||||||
hidden: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
handleFollow = () => {
|
|
||||||
this.props.onFollow(this.props.account);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleBlock = () => {
|
|
||||||
this.props.onBlock(this.props.account);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleMention = () => {
|
|
||||||
this.props.onMention(this.props.account);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleDirect = () => {
|
|
||||||
this.props.onDirect(this.props.account);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleReport = () => {
|
|
||||||
this.props.onReport(this.props.account);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleReblogToggle = () => {
|
|
||||||
this.props.onReblogToggle(this.props.account);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleNotifyToggle = () => {
|
|
||||||
this.props.onNotifyToggle(this.props.account);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleMute = () => {
|
|
||||||
this.props.onMute(this.props.account);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleBlockDomain = () => {
|
|
||||||
this.props.onBlockDomain(this.props.account);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleUnblockDomain = () => {
|
|
||||||
const domain = this.props.account.get('acct').split('@')[1];
|
|
||||||
|
|
||||||
if (!domain) return;
|
|
||||||
|
|
||||||
this.props.onUnblockDomain(domain);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleEndorseToggle = () => {
|
|
||||||
this.props.onEndorseToggle(this.props.account);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleAddToList = () => {
|
|
||||||
this.props.onAddToList(this.props.account);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleEditAccountNote = () => {
|
|
||||||
this.props.onEditAccountNote(this.props.account);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleChangeLanguages = () => {
|
|
||||||
this.props.onChangeLanguages(this.props.account);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleInteractionModal = () => {
|
|
||||||
this.props.onInteractionModal(this.props.account);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleOpenAvatar = () => {
|
|
||||||
this.props.onOpenAvatar(this.props.account);
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { account, hidden, hideTabs } = this.props;
|
|
||||||
|
|
||||||
if (account === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='account-timeline__header'>
|
|
||||||
{(!hidden && account.get('memorial')) && <MemorialNote />}
|
|
||||||
{(!hidden && account.get('moved')) && <MovedNote from={account} to={account.get('moved')} />}
|
|
||||||
|
|
||||||
<InnerHeader
|
|
||||||
account={account}
|
|
||||||
onFollow={this.handleFollow}
|
|
||||||
onBlock={this.handleBlock}
|
|
||||||
onMention={this.handleMention}
|
|
||||||
onDirect={this.handleDirect}
|
|
||||||
onReblogToggle={this.handleReblogToggle}
|
|
||||||
onNotifyToggle={this.handleNotifyToggle}
|
|
||||||
onReport={this.handleReport}
|
|
||||||
onMute={this.handleMute}
|
|
||||||
onBlockDomain={this.handleBlockDomain}
|
|
||||||
onUnblockDomain={this.handleUnblockDomain}
|
|
||||||
onEndorseToggle={this.handleEndorseToggle}
|
|
||||||
onAddToList={this.handleAddToList}
|
|
||||||
onEditAccountNote={this.handleEditAccountNote}
|
|
||||||
onChangeLanguages={this.handleChangeLanguages}
|
|
||||||
onInteractionModal={this.handleInteractionModal}
|
|
||||||
onOpenAvatar={this.handleOpenAvatar}
|
|
||||||
domain={this.props.domain}
|
|
||||||
hidden={hidden}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ActionBar
|
|
||||||
account={account}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{!(hideTabs || hidden) && (
|
|
||||||
<div className='account__section-headline'>
|
|
||||||
<NavLink exact to={`/@${account.get('acct')}`}><FormattedMessage id='account.posts' defaultMessage='Posts' /></NavLink>
|
|
||||||
<NavLink exact to={`/@${account.get('acct')}/with_replies`}><FormattedMessage id='account.posts_with_replies' defaultMessage='Posts and replies' /></NavLink>
|
|
||||||
<NavLink exact to={`/@${account.get('acct')}/media`}><FormattedMessage id='account.media' defaultMessage='Media' /></NavLink>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Header;
|
|
@ -1,11 +1,12 @@
|
|||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
const MemorialNote = () => (
|
export const MemorialNote: React.FC = () => (
|
||||||
<div className='account-memorial-banner'>
|
<div className='account-memorial-banner'>
|
||||||
<div className='account-memorial-banner__message'>
|
<div className='account-memorial-banner__message'>
|
||||||
<FormattedMessage id='account.in_memoriam' defaultMessage='In Memoriam.' />
|
<FormattedMessage
|
||||||
|
id='account.in_memoriam'
|
||||||
|
defaultMessage='In Memoriam.'
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default MemorialNote;
|
|
@ -1,38 +0,0 @@
|
|||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
|
|
||||||
import { AvatarOverlay } from '../../../components/avatar_overlay';
|
|
||||||
import { DisplayName } from '../../../components/display_name';
|
|
||||||
import { Permalink } from '../../../components/permalink';
|
|
||||||
|
|
||||||
export default class MovedNote extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
from: ImmutablePropTypes.map.isRequired,
|
|
||||||
to: ImmutablePropTypes.map.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { from, to } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='moved-account-banner'>
|
|
||||||
<div className='moved-account-banner__message'>
|
|
||||||
<FormattedMessage id='account.moved_to' defaultMessage='{name} has indicated that their new account is now:' values={{ name: <bdi><strong dangerouslySetInnerHTML={{ __html: from.get('display_name_html') }} /></bdi> }} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='moved-account-banner__action'>
|
|
||||||
<Permalink to={`/@${to.get('acct')}`} href={to.get('url')} className='detailed-status__display-name'>
|
|
||||||
<div className='detailed-status__display-avatar'><AvatarOverlay account={to} friend={from} /></div>
|
|
||||||
<DisplayName account={to} />
|
|
||||||
</Permalink>
|
|
||||||
|
|
||||||
<Permalink to={`/@${to.get('acct')}`} href={to.get('url')} className='button'><FormattedMessage id='account.go_to_profile' defaultMessage='Go to profile' /></Permalink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,56 @@
|
|||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import { AvatarOverlay } from 'flavours/glitch/components/avatar_overlay';
|
||||||
|
import { DisplayName } from 'flavours/glitch/components/display_name';
|
||||||
|
import { Permalink } from 'flavours/glitch/components/permalink';
|
||||||
|
import { useAppSelector } from 'flavours/glitch/store';
|
||||||
|
|
||||||
|
export const MovedNote: React.FC<{
|
||||||
|
accountId: string;
|
||||||
|
targetAccountId: string;
|
||||||
|
}> = ({ accountId, targetAccountId }) => {
|
||||||
|
const from = useAppSelector((state) => state.accounts.get(accountId));
|
||||||
|
const to = useAppSelector((state) => state.accounts.get(targetAccountId));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='moved-account-banner'>
|
||||||
|
<div className='moved-account-banner__message'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='account.moved_to'
|
||||||
|
defaultMessage='{name} has indicated that their new account is now:'
|
||||||
|
values={{
|
||||||
|
name: (
|
||||||
|
<bdi>
|
||||||
|
<strong
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: from?.display_name_html ?? '',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</bdi>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='moved-account-banner__action'>
|
||||||
|
<Permalink
|
||||||
|
to={`/@${to?.acct}`}
|
||||||
|
href={to?.url}
|
||||||
|
className='detailed-status__display-name'
|
||||||
|
>
|
||||||
|
<div className='detailed-status__display-avatar'>
|
||||||
|
<AvatarOverlay account={to} friend={from} />
|
||||||
|
</div>
|
||||||
|
<DisplayName account={to} />
|
||||||
|
</Permalink>
|
||||||
|
|
||||||
|
<Permalink to={`/@${to?.acct}`} href={to?.url} className='button'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='account.go_to_profile'
|
||||||
|
defaultMessage='Go to profile'
|
||||||
|
/>
|
||||||
|
</Permalink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -1,147 +0,0 @@
|
|||||||
import { injectIntl } from 'react-intl';
|
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import {
|
|
||||||
followAccount,
|
|
||||||
unblockAccount,
|
|
||||||
unmuteAccount,
|
|
||||||
pinAccount,
|
|
||||||
unpinAccount,
|
|
||||||
} from '../../../actions/accounts';
|
|
||||||
import { initBlockModal } from '../../../actions/blocks';
|
|
||||||
import {
|
|
||||||
mentionCompose,
|
|
||||||
directCompose,
|
|
||||||
} from '../../../actions/compose';
|
|
||||||
import { initDomainBlockModal, unblockDomain } from '../../../actions/domain_blocks';
|
|
||||||
import { openModal } from '../../../actions/modal';
|
|
||||||
import { initMuteModal } from '../../../actions/mutes';
|
|
||||||
import { initReport } from '../../../actions/reports';
|
|
||||||
import { makeGetAccount, getAccountHidden } from '../../../selectors';
|
|
||||||
import Header from '../components/header';
|
|
||||||
|
|
||||||
const makeMapStateToProps = () => {
|
|
||||||
const getAccount = makeGetAccount();
|
|
||||||
|
|
||||||
const mapStateToProps = (state, { accountId }) => ({
|
|
||||||
account: getAccount(state, accountId),
|
|
||||||
domain: state.getIn(['meta', 'domain']),
|
|
||||||
hidden: getAccountHidden(state, accountId),
|
|
||||||
});
|
|
||||||
|
|
||||||
return mapStateToProps;
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
|
|
||||||
onFollow (account) {
|
|
||||||
if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
|
|
||||||
dispatch(openModal({ modalType: 'CONFIRM_UNFOLLOW', modalProps: { account } }));
|
|
||||||
} else {
|
|
||||||
dispatch(followAccount(account.get('id')));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onInteractionModal (account) {
|
|
||||||
dispatch(openModal({
|
|
||||||
modalType: 'INTERACTION',
|
|
||||||
modalProps: {
|
|
||||||
type: 'follow',
|
|
||||||
accountId: account.get('id'),
|
|
||||||
url: account.get('uri'),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
onBlock (account) {
|
|
||||||
if (account.getIn(['relationship', 'blocking'])) {
|
|
||||||
dispatch(unblockAccount(account.get('id')));
|
|
||||||
} else {
|
|
||||||
dispatch(initBlockModal(account));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onMention (account) {
|
|
||||||
dispatch(mentionCompose(account));
|
|
||||||
},
|
|
||||||
|
|
||||||
onDirect (account) {
|
|
||||||
dispatch(directCompose(account));
|
|
||||||
},
|
|
||||||
|
|
||||||
onReblogToggle (account) {
|
|
||||||
if (account.getIn(['relationship', 'showing_reblogs'])) {
|
|
||||||
dispatch(followAccount(account.get('id'), { reblogs: false }));
|
|
||||||
} else {
|
|
||||||
dispatch(followAccount(account.get('id'), { reblogs: true }));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onEndorseToggle (account) {
|
|
||||||
if (account.getIn(['relationship', 'endorsed'])) {
|
|
||||||
dispatch(unpinAccount(account.get('id')));
|
|
||||||
} else {
|
|
||||||
dispatch(pinAccount(account.get('id')));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onNotifyToggle (account) {
|
|
||||||
if (account.getIn(['relationship', 'notifying'])) {
|
|
||||||
dispatch(followAccount(account.get('id'), { notify: false }));
|
|
||||||
} else {
|
|
||||||
dispatch(followAccount(account.get('id'), { notify: true }));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onReport (account) {
|
|
||||||
dispatch(initReport(account));
|
|
||||||
},
|
|
||||||
|
|
||||||
onMute (account) {
|
|
||||||
if (account.getIn(['relationship', 'muting'])) {
|
|
||||||
dispatch(unmuteAccount(account.get('id')));
|
|
||||||
} else {
|
|
||||||
dispatch(initMuteModal(account));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onBlockDomain (account) {
|
|
||||||
dispatch(initDomainBlockModal(account));
|
|
||||||
},
|
|
||||||
|
|
||||||
onUnblockDomain (domain) {
|
|
||||||
dispatch(unblockDomain(domain));
|
|
||||||
},
|
|
||||||
|
|
||||||
onAddToList (account) {
|
|
||||||
dispatch(openModal({
|
|
||||||
modalType: 'LIST_ADDER',
|
|
||||||
modalProps: {
|
|
||||||
accountId: account.get('id'),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
onChangeLanguages (account) {
|
|
||||||
dispatch(openModal({
|
|
||||||
modalType: 'SUBSCRIBED_LANGUAGES',
|
|
||||||
modalProps: {
|
|
||||||
accountId: account.get('id'),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
onOpenAvatar (account) {
|
|
||||||
dispatch(openModal({
|
|
||||||
modalType: 'IMAGE',
|
|
||||||
modalProps: {
|
|
||||||
src: account.get('avatar'),
|
|
||||||
alt: account.get('acct'),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Header));
|
|
@ -11,7 +11,7 @@ import { TimelineHint } from 'flavours/glitch/components/timeline_hint';
|
|||||||
import ProfileColumnHeader from 'flavours/glitch/features/account/components/profile_column_header';
|
import ProfileColumnHeader from 'flavours/glitch/features/account/components/profile_column_header';
|
||||||
import BundleColumnError from 'flavours/glitch/features/ui/components/bundle_column_error';
|
import BundleColumnError from 'flavours/glitch/features/ui/components/bundle_column_error';
|
||||||
import { normalizeForLookup } from 'flavours/glitch/reducers/accounts_map';
|
import { normalizeForLookup } from 'flavours/glitch/reducers/accounts_map';
|
||||||
import { getAccountHidden } from 'flavours/glitch/selectors';
|
import { getAccountHidden } from 'flavours/glitch/selectors/accounts';
|
||||||
import { useAppSelector } from 'flavours/glitch/store';
|
import { useAppSelector } from 'flavours/glitch/store';
|
||||||
|
|
||||||
import { lookupAccount, fetchAccount } from '../../actions/accounts';
|
import { lookupAccount, fetchAccount } from '../../actions/accounts';
|
||||||
@ -21,8 +21,8 @@ import { LoadingIndicator } from '../../components/loading_indicator';
|
|||||||
import StatusList from '../../components/status_list';
|
import StatusList from '../../components/status_list';
|
||||||
import Column from '../ui/components/column';
|
import Column from '../ui/components/column';
|
||||||
|
|
||||||
|
import { AccountHeader } from './components/account_header';
|
||||||
import { LimitedAccountHint } from './components/limited_account_hint';
|
import { LimitedAccountHint } from './components/limited_account_hint';
|
||||||
import HeaderContainer from './containers/header_container';
|
|
||||||
|
|
||||||
const emptyList = ImmutableList();
|
const emptyList = ImmutableList();
|
||||||
|
|
||||||
@ -199,7 +199,7 @@ class AccountTimeline extends ImmutablePureComponent {
|
|||||||
<ProfileColumnHeader onClick={this.handleHeaderClick} multiColumn={multiColumn} />
|
<ProfileColumnHeader onClick={this.handleHeaderClick} multiColumn={multiColumn} />
|
||||||
|
|
||||||
<StatusList
|
<StatusList
|
||||||
prepend={<HeaderContainer accountId={this.props.accountId} hideTabs={forceEmptyState} tagged={this.props.params.tagged} />}
|
prepend={<AccountHeader accountId={this.props.accountId} hideTabs={forceEmptyState} tagged={this.props.params.tagged} />}
|
||||||
alwaysPrepend
|
alwaysPrepend
|
||||||
append={remoteMessage}
|
append={remoteMessage}
|
||||||
scrollKey='account_timeline'
|
scrollKey='account_timeline'
|
||||||
|
@ -132,7 +132,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.onSubmit(missingAltTextModal && this.props.missingAltText, overridePrivacy);
|
this.props.onSubmit(missingAltTextModal && this.props.missingAltText && this.props.privacy !== 'direct', overridePrivacy);
|
||||||
|
|
||||||
if (e) {
|
if (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -10,9 +10,10 @@ import { debounce } from 'lodash';
|
|||||||
|
|
||||||
import { Account } from 'flavours/glitch/components/account';
|
import { Account } from 'flavours/glitch/components/account';
|
||||||
import { TimelineHint } from 'flavours/glitch/components/timeline_hint';
|
import { TimelineHint } from 'flavours/glitch/components/timeline_hint';
|
||||||
|
import { AccountHeader } from 'flavours/glitch/features/account_timeline/components/account_header';
|
||||||
import BundleColumnError from 'flavours/glitch/features/ui/components/bundle_column_error';
|
import BundleColumnError from 'flavours/glitch/features/ui/components/bundle_column_error';
|
||||||
import { normalizeForLookup } from 'flavours/glitch/reducers/accounts_map';
|
import { normalizeForLookup } from 'flavours/glitch/reducers/accounts_map';
|
||||||
import { getAccountHidden } from 'flavours/glitch/selectors';
|
import { getAccountHidden } from 'flavours/glitch/selectors/accounts';
|
||||||
import { useAppSelector } from 'flavours/glitch/store';
|
import { useAppSelector } from 'flavours/glitch/store';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -25,7 +26,6 @@ import { LoadingIndicator } from '../../components/loading_indicator';
|
|||||||
import ScrollableList from '../../components/scrollable_list';
|
import ScrollableList from '../../components/scrollable_list';
|
||||||
import ProfileColumnHeader from '../account/components/profile_column_header';
|
import ProfileColumnHeader from '../account/components/profile_column_header';
|
||||||
import { LimitedAccountHint } from '../account_timeline/components/limited_account_hint';
|
import { LimitedAccountHint } from '../account_timeline/components/limited_account_hint';
|
||||||
import HeaderContainer from '../account_timeline/containers/header_container';
|
|
||||||
import Column from '../ui/components/column';
|
import Column from '../ui/components/column';
|
||||||
|
|
||||||
const mapStateToProps = (state, { params: { acct, id } }) => {
|
const mapStateToProps = (state, { params: { acct, id } }) => {
|
||||||
@ -172,7 +172,7 @@ class Followers extends ImmutablePureComponent {
|
|||||||
hasMore={!forceEmptyState && hasMore}
|
hasMore={!forceEmptyState && hasMore}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
onLoadMore={this.handleLoadMore}
|
onLoadMore={this.handleLoadMore}
|
||||||
prepend={<HeaderContainer accountId={this.props.accountId} hideTabs />}
|
prepend={<AccountHeader accountId={this.props.accountId} hideTabs />}
|
||||||
alwaysPrepend
|
alwaysPrepend
|
||||||
append={remoteMessage}
|
append={remoteMessage}
|
||||||
emptyMessage={emptyMessage}
|
emptyMessage={emptyMessage}
|
||||||
|
@ -10,9 +10,10 @@ import { debounce } from 'lodash';
|
|||||||
|
|
||||||
import { Account } from 'flavours/glitch/components/account';
|
import { Account } from 'flavours/glitch/components/account';
|
||||||
import { TimelineHint } from 'flavours/glitch/components/timeline_hint';
|
import { TimelineHint } from 'flavours/glitch/components/timeline_hint';
|
||||||
|
import { AccountHeader } from 'flavours/glitch/features/account_timeline/components/account_header';
|
||||||
import BundleColumnError from 'flavours/glitch/features/ui/components/bundle_column_error';
|
import BundleColumnError from 'flavours/glitch/features/ui/components/bundle_column_error';
|
||||||
import { normalizeForLookup } from 'flavours/glitch/reducers/accounts_map';
|
import { normalizeForLookup } from 'flavours/glitch/reducers/accounts_map';
|
||||||
import { getAccountHidden } from 'flavours/glitch/selectors';
|
import { getAccountHidden } from 'flavours/glitch/selectors/accounts';
|
||||||
import { useAppSelector } from 'flavours/glitch/store';
|
import { useAppSelector } from 'flavours/glitch/store';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -25,7 +26,6 @@ import { LoadingIndicator } from '../../components/loading_indicator';
|
|||||||
import ScrollableList from '../../components/scrollable_list';
|
import ScrollableList from '../../components/scrollable_list';
|
||||||
import ProfileColumnHeader from '../account/components/profile_column_header';
|
import ProfileColumnHeader from '../account/components/profile_column_header';
|
||||||
import { LimitedAccountHint } from '../account_timeline/components/limited_account_hint';
|
import { LimitedAccountHint } from '../account_timeline/components/limited_account_hint';
|
||||||
import HeaderContainer from '../account_timeline/containers/header_container';
|
|
||||||
import Column from '../ui/components/column';
|
import Column from '../ui/components/column';
|
||||||
|
|
||||||
const mapStateToProps = (state, { params: { acct, id } }) => {
|
const mapStateToProps = (state, { params: { acct, id } }) => {
|
||||||
@ -172,7 +172,7 @@ class Following extends ImmutablePureComponent {
|
|||||||
hasMore={!forceEmptyState && hasMore}
|
hasMore={!forceEmptyState && hasMore}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
onLoadMore={this.handleLoadMore}
|
onLoadMore={this.handleLoadMore}
|
||||||
prepend={<HeaderContainer accountId={this.props.accountId} hideTabs />}
|
prepend={<AccountHeader accountId={this.props.accountId} hideTabs />}
|
||||||
alwaysPrepend
|
alwaysPrepend
|
||||||
append={remoteMessage}
|
append={remoteMessage}
|
||||||
emptyMessage={emptyMessage}
|
emptyMessage={emptyMessage}
|
||||||
|
24
app/javascript/flavours/glitch/models/dropdown_menu.ts
Normal file
24
app/javascript/flavours/glitch/models/dropdown_menu.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
interface BaseMenuItem {
|
||||||
|
text: string;
|
||||||
|
dangerous?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ActionMenuItem extends BaseMenuItem {
|
||||||
|
action: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LinkMenuItem extends BaseMenuItem {
|
||||||
|
to: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExternalLinkMenuItem extends BaseMenuItem {
|
||||||
|
href: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MenuItem =
|
||||||
|
| ActionMenuItem
|
||||||
|
| LinkMenuItem
|
||||||
|
| ExternalLinkMenuItem
|
||||||
|
| null;
|
||||||
|
|
||||||
|
export type DropdownMenu = MenuItem[];
|
@ -1,6 +1,7 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { Record as ImmutableRecord } from 'immutable';
|
import { Record as ImmutableRecord } from 'immutable';
|
||||||
|
|
||||||
|
import { me } from 'flavours/glitch/initial_state';
|
||||||
import { accountDefaultValues } from 'flavours/glitch/models/account';
|
import { accountDefaultValues } from 'flavours/glitch/models/account';
|
||||||
import type { Account, AccountShape } from 'flavours/glitch/models/account';
|
import type { Account, AccountShape } from 'flavours/glitch/models/account';
|
||||||
import type { Relationship } from 'flavours/glitch/models/relationship';
|
import type { Relationship } from 'flavours/glitch/models/relationship';
|
||||||
@ -45,3 +46,16 @@ export function makeGetAccount() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getAccountHidden = createSelector(
|
||||||
|
[
|
||||||
|
(state: RootState, id: string) => state.accounts.get(id)?.hidden,
|
||||||
|
(state: RootState, id: string) =>
|
||||||
|
state.relationships.get(id)?.following ||
|
||||||
|
state.relationships.get(id)?.requested,
|
||||||
|
(state: RootState, id: string) => id === me,
|
||||||
|
],
|
||||||
|
(hidden, followingOrRequested, isSelf) => {
|
||||||
|
return hidden && !(isSelf || followingOrRequested);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
@ -94,27 +94,23 @@ export const makeGetReport = () => createSelector([
|
|||||||
|
|
||||||
export const getAccountGallery = createSelector([
|
export const getAccountGallery = createSelector([
|
||||||
(state, id) => state.getIn(['timelines', `account:${id}:media`, 'items'], ImmutableList()),
|
(state, id) => state.getIn(['timelines', `account:${id}:media`, 'items'], ImmutableList()),
|
||||||
state => state.get('statuses'),
|
state => state.get('statuses'),
|
||||||
(state, id) => state.getIn(['accounts', id]),
|
(state, id) => state.getIn(['accounts', id]),
|
||||||
], (statusIds, statuses, account) => {
|
], (statusIds, statuses, account) => {
|
||||||
let medias = ImmutableList();
|
let medias = ImmutableList();
|
||||||
|
|
||||||
statusIds.forEach(statusId => {
|
statusIds.forEach(statusId => {
|
||||||
const status = statuses.get(statusId);
|
let status = statuses.get(statusId);
|
||||||
medias = medias.concat(status.get('media_attachments').map(media => media.set('status', status).set('account', account)));
|
|
||||||
|
if (status) {
|
||||||
|
status = status.set('account', account);
|
||||||
|
medias = medias.concat(status.get('media_attachments').map(media => media.set('status', status)));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return medias;
|
return medias;
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getAccountHidden = createSelector([
|
|
||||||
(state, id) => state.getIn(['accounts', id, 'hidden']),
|
|
||||||
(state, id) => state.getIn(['relationships', id, 'following']) || state.getIn(['relationships', id, 'requested']),
|
|
||||||
(state, id) => id === me,
|
|
||||||
], (hidden, followingOrRequested, isSelf) => {
|
|
||||||
return hidden && !(isSelf || followingOrRequested);
|
|
||||||
});
|
|
||||||
|
|
||||||
export const getStatusList = createSelector([
|
export const getStatusList = createSelector([
|
||||||
(state, type) => state.getIn(['status_lists', type, 'items']),
|
(state, type) => state.getIn(['status_lists', type, 'items']),
|
||||||
], (items) => items.toList());
|
], (items) => items.toList());
|
||||||
|
@ -138,7 +138,7 @@ export function deleteStatus(id, withRedraft = false) {
|
|||||||
|
|
||||||
dispatch(deleteStatusRequest(id));
|
dispatch(deleteStatusRequest(id));
|
||||||
|
|
||||||
api().delete(`/api/v1/statuses/${id}`).then(response => {
|
api().delete(`/api/v1/statuses/${id}`, { params: { delete_media: !withRedraft } }).then(response => {
|
||||||
dispatch(deleteStatusSuccess(id));
|
dispatch(deleteStatusSuccess(id));
|
||||||
dispatch(deleteFromTimelines(id));
|
dispatch(deleteFromTimelines(id));
|
||||||
dispatch(importFetchedAccount(response.data.account));
|
dispatch(importFetchedAccount(response.data.account));
|
||||||
|
@ -58,8 +58,8 @@ import {
|
|||||||
import { getAccountHidden } from 'mastodon/selectors/accounts';
|
import { getAccountHidden } from 'mastodon/selectors/accounts';
|
||||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||||
|
|
||||||
import MemorialNote from './memorial_note';
|
import { MemorialNote } from './memorial_note';
|
||||||
import MovedNote from './moved_note';
|
import { MovedNote } from './moved_note';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
||||||
@ -833,7 +833,7 @@ export const AccountHeader: React.FC<{
|
|||||||
<div className='account-timeline__header'>
|
<div className='account-timeline__header'>
|
||||||
{!hidden && account.memorial && <MemorialNote />}
|
{!hidden && account.memorial && <MemorialNote />}
|
||||||
{!hidden && account.moved && (
|
{!hidden && account.moved && (
|
||||||
<MovedNote from={account} to={account.moved} />
|
<MovedNote accountId={account.id} targetAccountId={account.moved} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
const MemorialNote = () => (
|
export const MemorialNote: React.FC = () => (
|
||||||
<div className='account-memorial-banner'>
|
<div className='account-memorial-banner'>
|
||||||
<div className='account-memorial-banner__message'>
|
<div className='account-memorial-banner__message'>
|
||||||
<FormattedMessage id='account.in_memoriam' defaultMessage='In Memoriam.' />
|
<FormattedMessage
|
||||||
|
id='account.in_memoriam'
|
||||||
|
defaultMessage='In Memoriam.'
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default MemorialNote;
|
|
@ -1,39 +0,0 @@
|
|||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
|
|
||||||
import { AvatarOverlay } from '../../../components/avatar_overlay';
|
|
||||||
import { DisplayName } from '../../../components/display_name';
|
|
||||||
|
|
||||||
export default class MovedNote extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
from: ImmutablePropTypes.map.isRequired,
|
|
||||||
to: ImmutablePropTypes.map.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { from, to } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='moved-account-banner'>
|
|
||||||
<div className='moved-account-banner__message'>
|
|
||||||
<FormattedMessage id='account.moved_to' defaultMessage='{name} has indicated that their new account is now:' values={{ name: <bdi><strong dangerouslySetInnerHTML={{ __html: from.get('display_name_html') }} /></bdi> }} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='moved-account-banner__action'>
|
|
||||||
<Link to={`/@${to.get('acct')}`} className='detailed-status__display-name'>
|
|
||||||
<div className='detailed-status__display-avatar'><AvatarOverlay account={to} friend={from} /></div>
|
|
||||||
<DisplayName account={to} />
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<Link to={`/@${to.get('acct')}`} className='button'><FormattedMessage id='account.go_to_profile' defaultMessage='Go to profile' /></Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,53 @@
|
|||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { AvatarOverlay } from 'mastodon/components/avatar_overlay';
|
||||||
|
import { DisplayName } from 'mastodon/components/display_name';
|
||||||
|
import { useAppSelector } from 'mastodon/store';
|
||||||
|
|
||||||
|
export const MovedNote: React.FC<{
|
||||||
|
accountId: string;
|
||||||
|
targetAccountId: string;
|
||||||
|
}> = ({ accountId, targetAccountId }) => {
|
||||||
|
const from = useAppSelector((state) => state.accounts.get(accountId));
|
||||||
|
const to = useAppSelector((state) => state.accounts.get(targetAccountId));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='moved-account-banner'>
|
||||||
|
<div className='moved-account-banner__message'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='account.moved_to'
|
||||||
|
defaultMessage='{name} has indicated that their new account is now:'
|
||||||
|
values={{
|
||||||
|
name: (
|
||||||
|
<bdi>
|
||||||
|
<strong
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: from?.display_name_html ?? '',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</bdi>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='moved-account-banner__action'>
|
||||||
|
<Link to={`/@${to?.acct}`} className='detailed-status__display-name'>
|
||||||
|
<div className='detailed-status__display-avatar'>
|
||||||
|
<AvatarOverlay account={to} friend={from} />
|
||||||
|
</div>
|
||||||
|
<DisplayName account={to} />
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link to={`/@${to?.acct}`} className='button'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='account.go_to_profile'
|
||||||
|
defaultMessage='Go to profile'
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -120,7 +120,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.onSubmit(missingAltTextModal && this.props.missingAltText);
|
this.props.onSubmit(missingAltTextModal && this.props.missingAltText && this.props.privacy !== 'direct');
|
||||||
|
|
||||||
if (e) {
|
if (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -697,6 +697,7 @@
|
|||||||
"poll_button.remove_poll": "Премахване на анкета",
|
"poll_button.remove_poll": "Премахване на анкета",
|
||||||
"privacy.change": "Промяна на поверителността на публикация",
|
"privacy.change": "Промяна на поверителността на публикация",
|
||||||
"privacy.direct.long": "Споменатите в публикацията",
|
"privacy.direct.long": "Споменатите в публикацията",
|
||||||
|
"privacy.direct.short": "Частно споменаване",
|
||||||
"privacy.private.long": "Само последователите ви",
|
"privacy.private.long": "Само последователите ви",
|
||||||
"privacy.private.short": "Последователи",
|
"privacy.private.short": "Последователи",
|
||||||
"privacy.public.long": "Всеки във и извън Mastodon",
|
"privacy.public.long": "Всеки във и извън Mastodon",
|
||||||
|
@ -195,7 +195,7 @@
|
|||||||
"compose_form.publish": "Publicar",
|
"compose_form.publish": "Publicar",
|
||||||
"compose_form.publish_form": "Nueva publicación",
|
"compose_form.publish_form": "Nueva publicación",
|
||||||
"compose_form.reply": "Respuesta",
|
"compose_form.reply": "Respuesta",
|
||||||
"compose_form.save_changes": "Actualización",
|
"compose_form.save_changes": "Actualizar",
|
||||||
"compose_form.spoiler.marked": "Quitar advertencia de contenido",
|
"compose_form.spoiler.marked": "Quitar advertencia de contenido",
|
||||||
"compose_form.spoiler.unmarked": "Añadir advertencia de contenido",
|
"compose_form.spoiler.unmarked": "Añadir advertencia de contenido",
|
||||||
"compose_form.spoiler_placeholder": "Advertencia de contenido (opcional)",
|
"compose_form.spoiler_placeholder": "Advertencia de contenido (opcional)",
|
||||||
|
@ -218,6 +218,10 @@
|
|||||||
"confirmations.logout.confirm": "خروج از حساب",
|
"confirmations.logout.confirm": "خروج از حساب",
|
||||||
"confirmations.logout.message": "مطمئنید میخواهید خارج شوید؟",
|
"confirmations.logout.message": "مطمئنید میخواهید خارج شوید؟",
|
||||||
"confirmations.logout.title": "خروج؟",
|
"confirmations.logout.title": "خروج؟",
|
||||||
|
"confirmations.missing_alt_text.confirm": "متن جایگزین را اضافه کنید",
|
||||||
|
"confirmations.missing_alt_text.message": "پست شما حاوی رسانه بدون متن جایگزین است. افزودن توضیحات کمک می کند تا محتوای شما برای افراد بیشتری قابل دسترسی باشد.",
|
||||||
|
"confirmations.missing_alt_text.secondary": "به هر حال پست کن",
|
||||||
|
"confirmations.missing_alt_text.title": "متن جایگزین اضافه شود؟",
|
||||||
"confirmations.mute.confirm": "خموش",
|
"confirmations.mute.confirm": "خموش",
|
||||||
"confirmations.redraft.confirm": "حذف و بازنویسی",
|
"confirmations.redraft.confirm": "حذف و بازنویسی",
|
||||||
"confirmations.redraft.message": "مطمئنید که میخواهید این فرسته را حذف کنید و از نو بنویسید؟ با این کار تقویتها و پسندهایش از دست رفته و پاسخها به آن بیمرجع میشود.",
|
"confirmations.redraft.message": "مطمئنید که میخواهید این فرسته را حذف کنید و از نو بنویسید؟ با این کار تقویتها و پسندهایش از دست رفته و پاسخها به آن بیمرجع میشود.",
|
||||||
@ -693,6 +697,7 @@
|
|||||||
"poll_button.remove_poll": "برداشتن نظرسنجی",
|
"poll_button.remove_poll": "برداشتن نظرسنجی",
|
||||||
"privacy.change": "تغییر محرمانگی فرسته",
|
"privacy.change": "تغییر محرمانگی فرسته",
|
||||||
"privacy.direct.long": "هرکسی که در فرسته نام برده شده",
|
"privacy.direct.long": "هرکسی که در فرسته نام برده شده",
|
||||||
|
"privacy.direct.short": "ذکر خصوصی",
|
||||||
"privacy.private.long": "تنها پیگیرندگانتان",
|
"privacy.private.long": "تنها پیگیرندگانتان",
|
||||||
"privacy.private.short": "پیگیرندگان",
|
"privacy.private.short": "پیگیرندگان",
|
||||||
"privacy.public.long": "هرکسی در و بیرون از ماستودون",
|
"privacy.public.long": "هرکسی در و بیرون از ماستودون",
|
||||||
|
@ -598,11 +598,11 @@
|
|||||||
"notification_requests.confirm_accept_multiple.button": "{count, plural, one {Samþykkja beiðni} other {Samþykkja beiðnir}}",
|
"notification_requests.confirm_accept_multiple.button": "{count, plural, one {Samþykkja beiðni} other {Samþykkja beiðnir}}",
|
||||||
"notification_requests.confirm_accept_multiple.message": "Þú ert að fara að samþykkja {count, plural, one {eina beiðni um tilkynningar} other {# beiðnir um tilkynningar}}. Ertu viss um að þú viljir halda áfram?",
|
"notification_requests.confirm_accept_multiple.message": "Þú ert að fara að samþykkja {count, plural, one {eina beiðni um tilkynningar} other {# beiðnir um tilkynningar}}. Ertu viss um að þú viljir halda áfram?",
|
||||||
"notification_requests.confirm_accept_multiple.title": "Samþykkja beiðnir um tilkynningar?",
|
"notification_requests.confirm_accept_multiple.title": "Samþykkja beiðnir um tilkynningar?",
|
||||||
"notification_requests.confirm_dismiss_multiple.button": "{count, plural, one {Afgreiða beiðni} other {Afgreiða beiðnir}}",
|
"notification_requests.confirm_dismiss_multiple.button": "{count, plural, one {Hafna beiðni} other {Hafna beiðnum}}",
|
||||||
"notification_requests.confirm_dismiss_multiple.message": "Þú ert að fara að hunsa {count, plural, one {eina beiðni um tilkynningar} other {# beiðnir um tilkynningar}}. Þú munt ekki eiga auðvelt með að skoða {count, plural, one {hana} other {þær}} aftur síðar. Ertu viss um að þú viljir halda áfram?",
|
"notification_requests.confirm_dismiss_multiple.message": "Þú ert að fara að hunsa {count, plural, one {eina beiðni um tilkynningar} other {# beiðnir um tilkynningar}}. Þú munt ekki eiga auðvelt með að skoða {count, plural, one {hana} other {þær}} aftur síðar. Ertu viss um að þú viljir halda áfram?",
|
||||||
"notification_requests.confirm_dismiss_multiple.title": "Hunsa beiðnir um tilkynningar?",
|
"notification_requests.confirm_dismiss_multiple.title": "Hunsa beiðnir um tilkynningar?",
|
||||||
"notification_requests.dismiss": "Afgreiða",
|
"notification_requests.dismiss": "Hafna",
|
||||||
"notification_requests.dismiss_multiple": "{count, plural, one {Afgreiða # beiðni…} other {Afgreiða # beiðnir…}}",
|
"notification_requests.dismiss_multiple": "{count, plural, one {Hafna # beiðni…} other {Hafna # beiðnum…}}",
|
||||||
"notification_requests.edit_selection": "Breyta",
|
"notification_requests.edit_selection": "Breyta",
|
||||||
"notification_requests.exit_selection": "Lokið",
|
"notification_requests.exit_selection": "Lokið",
|
||||||
"notification_requests.explainer_for_limited_account": "Tilkynningar frá þessum notanda hafa verið síaðar þar sem aðgangur hans hefur verið takmarkaður af umsjónarmanni.",
|
"notification_requests.explainer_for_limited_account": "Tilkynningar frá þessum notanda hafa verið síaðar þar sem aðgangur hans hefur verið takmarkaður af umsjónarmanni.",
|
||||||
|
@ -311,6 +311,7 @@
|
|||||||
"filter_modal.select_filter.subtitle": "Izmanto esošu kategoriju vai izveido jaunu",
|
"filter_modal.select_filter.subtitle": "Izmanto esošu kategoriju vai izveido jaunu",
|
||||||
"filter_modal.select_filter.title": "Filtrēt šo ziņu",
|
"filter_modal.select_filter.title": "Filtrēt šo ziņu",
|
||||||
"filter_modal.title.status": "Filtrēt ziņu",
|
"filter_modal.title.status": "Filtrēt ziņu",
|
||||||
|
"filtered_notifications_banner.title": "Filtrētie paziņojumi",
|
||||||
"firehose.all": "Visi",
|
"firehose.all": "Visi",
|
||||||
"firehose.local": "Šis serveris",
|
"firehose.local": "Šis serveris",
|
||||||
"firehose.remote": "Citi serveri",
|
"firehose.remote": "Citi serveri",
|
||||||
@ -321,6 +322,8 @@
|
|||||||
"follow_suggestions.dismiss": "Vairs nerādīt",
|
"follow_suggestions.dismiss": "Vairs nerādīt",
|
||||||
"follow_suggestions.friends_of_friends_longer": "Populārs to cilvēku vidū, kuriem tu seko",
|
"follow_suggestions.friends_of_friends_longer": "Populārs to cilvēku vidū, kuriem tu seko",
|
||||||
"follow_suggestions.personalized_suggestion": "Pielāgots ieteikums",
|
"follow_suggestions.personalized_suggestion": "Pielāgots ieteikums",
|
||||||
|
"follow_suggestions.popular_suggestion": "Populārs ieteikums",
|
||||||
|
"follow_suggestions.popular_suggestion_longer": "Populārs {domain}",
|
||||||
"follow_suggestions.similar_to_recently_followed_longer": "Līdzīgi profieliem, kuriem nesen sāki sekot",
|
"follow_suggestions.similar_to_recently_followed_longer": "Līdzīgi profieliem, kuriem nesen sāki sekot",
|
||||||
"follow_suggestions.view_all": "Skatīt visu",
|
"follow_suggestions.view_all": "Skatīt visu",
|
||||||
"follow_suggestions.who_to_follow": "Kam sekot",
|
"follow_suggestions.who_to_follow": "Kam sekot",
|
||||||
@ -496,6 +499,7 @@
|
|||||||
"notifications.column_settings.filter_bar.category": "Atrās atlasīšanas josla",
|
"notifications.column_settings.filter_bar.category": "Atrās atlasīšanas josla",
|
||||||
"notifications.column_settings.follow": "Jauni sekotāji:",
|
"notifications.column_settings.follow": "Jauni sekotāji:",
|
||||||
"notifications.column_settings.follow_request": "Jauni sekošanas pieprasījumi:",
|
"notifications.column_settings.follow_request": "Jauni sekošanas pieprasījumi:",
|
||||||
|
"notifications.column_settings.group": "Grupēt",
|
||||||
"notifications.column_settings.mention": "Pieminēšanas:",
|
"notifications.column_settings.mention": "Pieminēšanas:",
|
||||||
"notifications.column_settings.poll": "Aptaujas rezultāti:",
|
"notifications.column_settings.poll": "Aptaujas rezultāti:",
|
||||||
"notifications.column_settings.push": "Uznirstošie paziņojumi",
|
"notifications.column_settings.push": "Uznirstošie paziņojumi",
|
||||||
@ -520,6 +524,7 @@
|
|||||||
"notifications.permission_denied_alert": "Darbvirsmas paziņojumus nevar iespējot, jo pārlūkprogrammai atļauja tika iepriekš atteikta",
|
"notifications.permission_denied_alert": "Darbvirsmas paziņojumus nevar iespējot, jo pārlūkprogrammai atļauja tika iepriekš atteikta",
|
||||||
"notifications.permission_required": "Darbvirsmas paziņojumi nav pieejami, jo nav piešķirta nepieciešamā atļauja.",
|
"notifications.permission_required": "Darbvirsmas paziņojumi nav pieejami, jo nav piešķirta nepieciešamā atļauja.",
|
||||||
"notifications.policy.accept": "Pieņemt",
|
"notifications.policy.accept": "Pieņemt",
|
||||||
|
"notifications.policy.drop": "Ignorēt",
|
||||||
"notifications.policy.filter_new_accounts_title": "Jauni konti",
|
"notifications.policy.filter_new_accounts_title": "Jauni konti",
|
||||||
"notifications.policy.filter_not_followers_title": "Cilvēki, kuri Tev neseko",
|
"notifications.policy.filter_not_followers_title": "Cilvēki, kuri Tev neseko",
|
||||||
"notifications.policy.filter_not_following_hint": "Līdz tos pašrocīgi apstiprināsi",
|
"notifications.policy.filter_not_following_hint": "Līdz tos pašrocīgi apstiprināsi",
|
||||||
@ -527,6 +532,7 @@
|
|||||||
"notifications_permission_banner.enable": "Iespējot darbvirsmas paziņojumus",
|
"notifications_permission_banner.enable": "Iespējot darbvirsmas paziņojumus",
|
||||||
"notifications_permission_banner.how_to_control": "Lai saņemtu paziņojumus, kad Mastodon nav atvērts, iespējo darbvirsmas paziņojumus. Vari precīzi kontrolēt, kāda veida mijiedarbības rada darbvirsmas paziņojumus, izmantojot augstāk redzamo pogu {icon}, kad tie būs iespējoti.",
|
"notifications_permission_banner.how_to_control": "Lai saņemtu paziņojumus, kad Mastodon nav atvērts, iespējo darbvirsmas paziņojumus. Vari precīzi kontrolēt, kāda veida mijiedarbības rada darbvirsmas paziņojumus, izmantojot augstāk redzamo pogu {icon}, kad tie būs iespējoti.",
|
||||||
"notifications_permission_banner.title": "Nekad nepalaid neko garām",
|
"notifications_permission_banner.title": "Nekad nepalaid neko garām",
|
||||||
|
"onboarding.follows.back": "Atpakaļ",
|
||||||
"onboarding.follows.empty": "Diemžēl pašlaik nevar parādīt rezultātus. Vari mēģināt izmantot meklēšanu vai pārlūkot izpētes lapu, lai atrastu cilvēkus, kuriem sekot, vai vēlāk mēģināt vēlreiz.",
|
"onboarding.follows.empty": "Diemžēl pašlaik nevar parādīt rezultātus. Vari mēģināt izmantot meklēšanu vai pārlūkot izpētes lapu, lai atrastu cilvēkus, kuriem sekot, vai vēlāk mēģināt vēlreiz.",
|
||||||
"onboarding.profile.discoverable": "Padarīt manu profilu atklājamu",
|
"onboarding.profile.discoverable": "Padarīt manu profilu atklājamu",
|
||||||
"onboarding.profile.display_name": "Attēlojamais vārds",
|
"onboarding.profile.display_name": "Attēlojamais vārds",
|
||||||
|
@ -115,7 +115,7 @@ module Account::Interactions
|
|||||||
end
|
end
|
||||||
|
|
||||||
def follow!(other_account, reblogs: nil, notify: nil, languages: nil, uri: nil, rate_limit: false, bypass_limit: false)
|
def follow!(other_account, reblogs: nil, notify: nil, languages: nil, uri: nil, rate_limit: false, bypass_limit: false)
|
||||||
rel = active_relationships.create_with(show_reblogs: reblogs.nil? ? true : reblogs, notify: notify.nil? ? false : notify, languages: languages, uri: uri, rate_limit: rate_limit, bypass_follow_limit: bypass_limit)
|
rel = active_relationships.create_with(show_reblogs: reblogs.nil? || reblogs, notify: notify.nil? ? false : notify, languages: languages, uri: uri, rate_limit: rate_limit, bypass_follow_limit: bypass_limit)
|
||||||
.find_or_create_by!(target_account: other_account)
|
.find_or_create_by!(target_account: other_account)
|
||||||
|
|
||||||
rel.show_reblogs = reblogs unless reblogs.nil?
|
rel.show_reblogs = reblogs unless reblogs.nil?
|
||||||
@ -128,7 +128,7 @@ module Account::Interactions
|
|||||||
end
|
end
|
||||||
|
|
||||||
def request_follow!(other_account, reblogs: nil, notify: nil, languages: nil, uri: nil, rate_limit: false, bypass_limit: false)
|
def request_follow!(other_account, reblogs: nil, notify: nil, languages: nil, uri: nil, rate_limit: false, bypass_limit: false)
|
||||||
rel = follow_requests.create_with(show_reblogs: reblogs.nil? ? true : reblogs, notify: notify.nil? ? false : notify, uri: uri, languages: languages, rate_limit: rate_limit, bypass_follow_limit: bypass_limit)
|
rel = follow_requests.create_with(show_reblogs: reblogs.nil? || reblogs, notify: notify.nil? ? false : notify, uri: uri, languages: languages, rate_limit: rate_limit, bypass_follow_limit: bypass_limit)
|
||||||
.find_or_create_by!(target_account: other_account)
|
.find_or_create_by!(target_account: other_account)
|
||||||
|
|
||||||
rel.show_reblogs = reblogs unless reblogs.nil?
|
rel.show_reblogs = reblogs unless reblogs.nil?
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
- if tag.trendable?
|
- if tag.trendable?
|
||||||
·
|
·
|
||||||
%abbr{ title: t('admin.trends.tags.current_score', score: tag.trend.score) }= t('admin.trends.tags.trending_rank', rank: tag.trend.rank + 1)
|
%abbr{ title: t('admin.trends.tags.current_score', score: tag.trend.score) }= t('admin.trends.tags.trending_rank', rank: tag.trend.rank)
|
||||||
|
|
||||||
- if tag.decaying?
|
- if tag.decaying?
|
||||||
·
|
·
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
= opengraph 'og:title', "#{display_name(@account)} (#{acct(@account)})"
|
= opengraph 'og:title', "#{display_name(@account)} (#{acct(@account)})"
|
||||||
= opengraph 'og:url', short_account_status_url(@account, @status)
|
= opengraph 'og:url', short_account_status_url(@account, @status)
|
||||||
= opengraph 'og:published_time', @status.created_at.iso8601
|
= opengraph 'og:published_time', @status.created_at.iso8601
|
||||||
|
- if @status.language.present?
|
||||||
|
= opengraph 'og:locale', @status.language
|
||||||
= opengraph 'profile:username', acct(@account)[1..]
|
= opengraph 'profile:username', acct(@account)[1..]
|
||||||
|
|
||||||
= render 'og_description', activity: @status
|
= render 'og_description', activity: @status
|
||||||
|
@ -194,7 +194,7 @@ ko:
|
|||||||
destroy_domain_allow: 도메인 허용 삭제
|
destroy_domain_allow: 도메인 허용 삭제
|
||||||
destroy_domain_block: 도메인 차단 삭제
|
destroy_domain_block: 도메인 차단 삭제
|
||||||
destroy_email_domain_block: 이메일 도메인 차단 삭제
|
destroy_email_domain_block: 이메일 도메인 차단 삭제
|
||||||
destroy_instance: 도메인 제거
|
destroy_instance: 도메인 퍼지
|
||||||
destroy_ip_block: IP 규칙 삭제
|
destroy_ip_block: IP 규칙 삭제
|
||||||
destroy_relay: 릴레이 삭제
|
destroy_relay: 릴레이 삭제
|
||||||
destroy_status: 게시물 삭제
|
destroy_status: 게시물 삭제
|
||||||
@ -536,7 +536,7 @@ ko:
|
|||||||
title: 중재
|
title: 중재
|
||||||
private_comment: 비공개 주석
|
private_comment: 비공개 주석
|
||||||
public_comment: 공개 주석
|
public_comment: 공개 주석
|
||||||
purge: 제거
|
purge: 퍼지
|
||||||
purge_description_html: 이 도메인이 영구적으로 오프라인 상태라고 생각되면, 스토리지에서 이 도메인의 모든 계정 레코드와 관련 데이터를 삭제할 수 있습니다. 이 작업은 시간이 좀 걸릴 수 있습니다.
|
purge_description_html: 이 도메인이 영구적으로 오프라인 상태라고 생각되면, 스토리지에서 이 도메인의 모든 계정 레코드와 관련 데이터를 삭제할 수 있습니다. 이 작업은 시간이 좀 걸릴 수 있습니다.
|
||||||
title: 연합
|
title: 연합
|
||||||
total_blocked_by_us: 우리에게 차단 됨
|
total_blocked_by_us: 우리에게 차단 됨
|
||||||
|
@ -137,6 +137,7 @@ fa:
|
|||||||
admin_email: اخطارهای حقوقی شامل اخطارهای متقابل، دستورها دادگاه، درخواستهای حذف و درخواستهای اجرای قانون است.
|
admin_email: اخطارهای حقوقی شامل اخطارهای متقابل، دستورها دادگاه، درخواستهای حذف و درخواستهای اجرای قانون است.
|
||||||
arbitration_address: میتواند مانند آدرس فیزیکی بالا باشد، یا در صورت استفاده از ایمیل، «N/A» باشد
|
arbitration_address: میتواند مانند آدرس فیزیکی بالا باشد، یا در صورت استفاده از ایمیل، «N/A» باشد
|
||||||
arbitration_website: اگر از ایمیل استفاده می کنید، می تواند یک فرم وب یا "N/A" باشد
|
arbitration_website: اگر از ایمیل استفاده می کنید، می تواند یک فرم وب یا "N/A" باشد
|
||||||
|
choice_of_law: شهر، منطقه، قلمرو یا ایالتی که قوانین ماهوی داخلی آن بر هر یک از دعاوی حاکم است.
|
||||||
dmca_address: برای اپراتورهای ایالات متحده، از آدرس ثبت شده در دایرکتوری نماینده تعیین شده DMCA استفاده کنید. یک P.O. فهرست جعبه در صورت درخواست مستقیم در دسترس است، از درخواست چشم پوشی از صندوق پست دفتر پست DMCA برای ایمیل به دفتر حق نسخه برداری استفاده کنید و توضیح دهید که شما یک ناظر محتوای خانگی هستید که از انتقام یا تلافی برای اعمال خود می ترسید و باید از P.O استفاده کنید. کادری برای حذف آدرس خانه شما از نمای عمومی.
|
dmca_address: برای اپراتورهای ایالات متحده، از آدرس ثبت شده در دایرکتوری نماینده تعیین شده DMCA استفاده کنید. یک P.O. فهرست جعبه در صورت درخواست مستقیم در دسترس است، از درخواست چشم پوشی از صندوق پست دفتر پست DMCA برای ایمیل به دفتر حق نسخه برداری استفاده کنید و توضیح دهید که شما یک ناظر محتوای خانگی هستید که از انتقام یا تلافی برای اعمال خود می ترسید و باید از P.O استفاده کنید. کادری برای حذف آدرس خانه شما از نمای عمومی.
|
||||||
dmca_email: میتواند همان ایمیلی باشد که برای “آدرس ایمیل برای اطلاعیههای قانونی“ در بالا استفاده شده است
|
dmca_email: میتواند همان ایمیلی باشد که برای “آدرس ایمیل برای اطلاعیههای قانونی“ در بالا استفاده شده است
|
||||||
domain: شناسایی منحصر به فرد سرویس آنلاینی که ارائه می کنید.
|
domain: شناسایی منحصر به فرد سرویس آنلاینی که ارائه می کنید.
|
||||||
@ -233,6 +234,7 @@ fa:
|
|||||||
setting_display_media_show_all: نمایش همه
|
setting_display_media_show_all: نمایش همه
|
||||||
setting_expand_spoilers: همیشه فرستههایی را که هشدار محتوا دارند کامل نشان بده
|
setting_expand_spoilers: همیشه فرستههایی را که هشدار محتوا دارند کامل نشان بده
|
||||||
setting_hide_network: نهفتن شبکهٔ ارتباطی
|
setting_hide_network: نهفتن شبکهٔ ارتباطی
|
||||||
|
setting_missing_alt_text_modal: نمایش گفتگوی تایید قبل از ارسال رسانه بدون متن جایگزین
|
||||||
setting_reduce_motion: کاستن از حرکت در پویانماییها
|
setting_reduce_motion: کاستن از حرکت در پویانماییها
|
||||||
setting_system_font_ui: بهکاربردن قلم پیشفرض سیستم
|
setting_system_font_ui: بهکاربردن قلم پیشفرض سیستم
|
||||||
setting_system_scrollbars_ui: از نوار اسکرول پیش فرض سیستم استفاده کنید
|
setting_system_scrollbars_ui: از نوار اسکرول پیش فرض سیستم استفاده کنید
|
||||||
@ -337,6 +339,7 @@ fa:
|
|||||||
admin_email: آدرس ایمیل برای اطلاعیه های حقوقی
|
admin_email: آدرس ایمیل برای اطلاعیه های حقوقی
|
||||||
arbitration_address: آدرس فیزیکی اعلامیه های داوری
|
arbitration_address: آدرس فیزیکی اعلامیه های داوری
|
||||||
arbitration_website: وب سایت ارسال اخطارهای داوری
|
arbitration_website: وب سایت ارسال اخطارهای داوری
|
||||||
|
choice_of_law: انتخاب قانون
|
||||||
dmca_address: آدرس فیزیکی اعلامیههای DMCA/حق نسخهبرداری
|
dmca_address: آدرس فیزیکی اعلامیههای DMCA/حق نسخهبرداری
|
||||||
dmca_email: آدرس ایمیل برای اعلامیههای DMCA/حق نسخهبرداری
|
dmca_email: آدرس ایمیل برای اعلامیههای DMCA/حق نسخهبرداری
|
||||||
domain: دامنه
|
domain: دامنه
|
||||||
|
@ -337,6 +337,7 @@ ko:
|
|||||||
admin_email: 법적 조치를 위한 이메일 주소
|
admin_email: 법적 조치를 위한 이메일 주소
|
||||||
arbitration_address: 중재 통지를 위한 실제 주소
|
arbitration_address: 중재 통지를 위한 실제 주소
|
||||||
arbitration_website: 중재 통지를 제출하기 위한 웹사이트
|
arbitration_website: 중재 통지를 제출하기 위한 웹사이트
|
||||||
|
choice_of_law: 준거법 지정
|
||||||
dmca_address: DMCA/저작권 통지를 위한 실제 주소
|
dmca_address: DMCA/저작권 통지를 위한 실제 주소
|
||||||
dmca_email: DMCA/저작권 통지를 위한 이메일 주소
|
dmca_email: DMCA/저작권 통지를 위한 이메일 주소
|
||||||
domain: 도메인
|
domain: 도메인
|
||||||
|
@ -137,6 +137,7 @@ pl:
|
|||||||
admin_email: Zawiadomienia prawne obejmują środki zapobiegawcze, nakazy sądowe, wnioski o popełnienie sprawy oraz wnioski organów ścigania.
|
admin_email: Zawiadomienia prawne obejmują środki zapobiegawcze, nakazy sądowe, wnioski o popełnienie sprawy oraz wnioski organów ścigania.
|
||||||
arbitration_address: Może być taki sam jak adres fizyczny powyżej lub „N/A” jeśli używasz adresu e-mail
|
arbitration_address: Może być taki sam jak adres fizyczny powyżej lub „N/A” jeśli używasz adresu e-mail
|
||||||
arbitration_website: Może być formularzem internetowym lub „N/A”, jeśli używasz adresu e-mail
|
arbitration_website: Może być formularzem internetowym lub „N/A”, jeśli używasz adresu e-mail
|
||||||
|
choice_of_law: Miasto, region, terytorium lub stan, którego wewnętrzne prawo będzie regulowało wszelkie roszczenia.
|
||||||
dmca_address: W przypadku operatorów z USA należy użyć adresu zarejestrowanego w DMCA Designated Agent Directory. Lista skrytek pocztowych dostępna jest na bezpośrednią prośbę użytkownika. Użyj DMCA Agent Post Office Box Waiver Request, aby wysłać email do Copyright Office z informacją, że jesteś domowym administratorm treści i z powodu obawy o zemstę lub odwetu za swoje działania, musisz użyć skrytki pocztowej, żeby usunąć swój adres domowy z dostępu publicznego.
|
dmca_address: W przypadku operatorów z USA należy użyć adresu zarejestrowanego w DMCA Designated Agent Directory. Lista skrytek pocztowych dostępna jest na bezpośrednią prośbę użytkownika. Użyj DMCA Agent Post Office Box Waiver Request, aby wysłać email do Copyright Office z informacją, że jesteś domowym administratorm treści i z powodu obawy o zemstę lub odwetu za swoje działania, musisz użyć skrytki pocztowej, żeby usunąć swój adres domowy z dostępu publicznego.
|
||||||
dmca_email: Adres email może być taki sam jak wcześniejszy "adres e-mail przeznaczony do celów prawnych"
|
dmca_email: Adres email może być taki sam jak wcześniejszy "adres e-mail przeznaczony do celów prawnych"
|
||||||
domain: Unikalny numer identyfikacji świadczonej przez Ciebie usługi online.
|
domain: Unikalny numer identyfikacji świadczonej przez Ciebie usługi online.
|
||||||
@ -338,6 +339,7 @@ pl:
|
|||||||
admin_email: Adres e-mail przeznaczony do celów prawnych
|
admin_email: Adres e-mail przeznaczony do celów prawnych
|
||||||
arbitration_address: Adres fizyczny powiadomień arbitrażowych
|
arbitration_address: Adres fizyczny powiadomień arbitrażowych
|
||||||
arbitration_website: Strona internetowa do składania zgłoszeń arbitrażowych
|
arbitration_website: Strona internetowa do składania zgłoszeń arbitrażowych
|
||||||
|
choice_of_law: Wybór prawa
|
||||||
dmca_address: Adres fizyczny dla zgłoszeń naruszenia DMCA/praw autorskich
|
dmca_address: Adres fizyczny dla zgłoszeń naruszenia DMCA/praw autorskich
|
||||||
dmca_email: Adres e-mail dla zgłoszeń naruszenia DMCA/praw autorskich
|
dmca_email: Adres e-mail dla zgłoszeń naruszenia DMCA/praw autorskich
|
||||||
domain: Domena
|
domain: Domena
|
||||||
|
@ -25,10 +25,12 @@ sk:
|
|||||||
one: Príspevok
|
one: Príspevok
|
||||||
other: Príspevkov
|
other: Príspevkov
|
||||||
posts_tab_heading: Príspevky
|
posts_tab_heading: Príspevky
|
||||||
|
self_follow_error: Nieje povolené nasledovať svoj vlastný účet
|
||||||
admin:
|
admin:
|
||||||
account_actions:
|
account_actions:
|
||||||
action: Vykonaj
|
action: Vykonaj
|
||||||
already_silenced: Tento účet už bol obmedzený.
|
already_silenced: Tento účet už bol obmedzený.
|
||||||
|
already_suspended: Tento účet už bol vylúčený.
|
||||||
title: Vykonaj moderovací úkon voči %{acct}
|
title: Vykonaj moderovací úkon voči %{acct}
|
||||||
account_moderation_notes:
|
account_moderation_notes:
|
||||||
create: Zanechaj poznámku
|
create: Zanechaj poznámku
|
||||||
|
@ -78,7 +78,7 @@ namespace :api, format: false do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :media, only: [:create, :update, :show]
|
resources :media, only: [:create, :update, :show, :destroy]
|
||||||
resources :blocks, only: [:index]
|
resources :blocks, only: [:index]
|
||||||
resources :mutes, only: [:index]
|
resources :mutes, only: [:index]
|
||||||
resources :favourites, only: [:index]
|
resources :favourites, only: [:index]
|
||||||
|
@ -3,7 +3,7 @@ const config = {
|
|||||||
'Gemfile|*.{rb,ruby,ru,rake}': 'bin/rubocop --force-exclusion -a',
|
'Gemfile|*.{rb,ruby,ru,rake}': 'bin/rubocop --force-exclusion -a',
|
||||||
'*.{js,jsx,ts,tsx}': 'eslint --fix',
|
'*.{js,jsx,ts,tsx}': 'eslint --fix',
|
||||||
'*.{css,scss}': 'stylelint --fix',
|
'*.{css,scss}': 'stylelint --fix',
|
||||||
'*.haml': 'bin/haml-lint -a --parallel',
|
'*.haml': 'bin/haml-lint -a',
|
||||||
'**/*.ts?(x)': () => 'tsc -p tsconfig.json --noEmit',
|
'**/*.ts?(x)': () => 'tsc -p tsconfig.json --noEmit',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe Admin::RelationshipsController do
|
|
||||||
render_views
|
|
||||||
|
|
||||||
let(:user) { Fabricate(:admin_user) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
sign_in user, scope: :user
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'GET #index' do
|
|
||||||
let(:account) { Fabricate(:account) }
|
|
||||||
|
|
||||||
it 'returns http success' do
|
|
||||||
get :index, params: { account_id: account.id }
|
|
||||||
|
|
||||||
expect(response).to have_http_status(:success)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,21 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe Admin::Trends::Links::PreviewCardProvidersController do
|
|
||||||
render_views
|
|
||||||
|
|
||||||
let(:user) { Fabricate(:admin_user) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
sign_in user, scope: :user
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'GET #index' do
|
|
||||||
it 'returns http success' do
|
|
||||||
get :index
|
|
||||||
|
|
||||||
expect(response).to have_http_status(:success)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,21 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe Admin::Trends::LinksController do
|
|
||||||
render_views
|
|
||||||
|
|
||||||
let(:user) { Fabricate(:admin_user) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
sign_in user, scope: :user
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'GET #index' do
|
|
||||||
it 'returns http success' do
|
|
||||||
get :index
|
|
||||||
|
|
||||||
expect(response).to have_http_status(:success)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,21 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe Admin::Trends::StatusesController do
|
|
||||||
render_views
|
|
||||||
|
|
||||||
let(:user) { Fabricate(:admin_user) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
sign_in user, scope: :user
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'GET #index' do
|
|
||||||
it 'returns http success' do
|
|
||||||
get :index
|
|
||||||
|
|
||||||
expect(response).to have_http_status(:success)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,21 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe Admin::Trends::TagsController do
|
|
||||||
render_views
|
|
||||||
|
|
||||||
let(:user) { Fabricate(:admin_user) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
sign_in user, scope: :user
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'GET #index' do
|
|
||||||
it 'returns http success' do
|
|
||||||
get :index
|
|
||||||
|
|
||||||
expect(response).to have_http_status(:success)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -193,4 +193,57 @@ RSpec.describe 'Media' do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'DELETE /api/v1/media/:id' do
|
||||||
|
subject do
|
||||||
|
delete "/api/v1/media/#{media.id}", headers: headers
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when media is not attached to a status' do
|
||||||
|
let(:media) { Fabricate(:media_attachment, account: user.account, status: nil) }
|
||||||
|
|
||||||
|
it 'returns http empty response' do
|
||||||
|
subject
|
||||||
|
|
||||||
|
expect(response).to have_http_status(200)
|
||||||
|
expect(response.content_type)
|
||||||
|
.to start_with('application/json')
|
||||||
|
|
||||||
|
expect(MediaAttachment.where(id: media.id)).to_not exist
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when media is attached to a status' do
|
||||||
|
let(:media) { Fabricate(:media_attachment, account: user.account, status: Fabricate.build(:status)) }
|
||||||
|
|
||||||
|
it 'returns http unprocessable entity' do
|
||||||
|
subject
|
||||||
|
|
||||||
|
expect(response).to have_http_status(422)
|
||||||
|
expect(response.content_type)
|
||||||
|
.to start_with('application/json')
|
||||||
|
expect(response.parsed_body).to match(
|
||||||
|
a_hash_including(
|
||||||
|
error: 'Media attachment is currently used by a status'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(MediaAttachment.where(id: media.id)).to exist
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the media belongs to somebody else' do
|
||||||
|
let(:media) { Fabricate(:media_attachment, status: nil) }
|
||||||
|
|
||||||
|
it 'returns http not found' do
|
||||||
|
subject
|
||||||
|
|
||||||
|
expect(response).to have_http_status(404)
|
||||||
|
expect(response.content_type)
|
||||||
|
.to start_with('application/json')
|
||||||
|
|
||||||
|
expect(MediaAttachment.where(id: media.id)).to exist
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -257,13 +257,30 @@ RSpec.describe '/api/v1/statuses' do
|
|||||||
|
|
||||||
it_behaves_like 'forbidden for wrong scope', 'read read:statuses'
|
it_behaves_like 'forbidden for wrong scope', 'read read:statuses'
|
||||||
|
|
||||||
it 'removes the status', :aggregate_failures do
|
it 'discards the status and schedules removal as a redraft', :aggregate_failures do
|
||||||
subject
|
subject
|
||||||
|
|
||||||
expect(response).to have_http_status(200)
|
expect(response).to have_http_status(200)
|
||||||
expect(response.content_type)
|
expect(response.content_type)
|
||||||
.to start_with('application/json')
|
.to start_with('application/json')
|
||||||
expect(Status.find_by(id: status.id)).to be_nil
|
expect(Status.find_by(id: status.id)).to be_nil
|
||||||
|
expect(RemovalWorker).to have_enqueued_sidekiq_job(status.id, { 'redraft' => true })
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when called with truthy delete_media' do
|
||||||
|
subject do
|
||||||
|
delete "/api/v1/statuses/#{status.id}?delete_media=true", headers: headers
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'discards the status and schedules removal without the redraft flag', :aggregate_failures do
|
||||||
|
subject
|
||||||
|
|
||||||
|
expect(response).to have_http_status(200)
|
||||||
|
expect(response.content_type)
|
||||||
|
.to start_with('application/json')
|
||||||
|
expect(Status.find_by(id: status.id)).to be_nil
|
||||||
|
expect(RemovalWorker).to have_enqueued_sidekiq_job(status.id, { 'redraft' => false })
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
66
spec/requests/status_show_page_spec.rb
Normal file
66
spec/requests/status_show_page_spec.rb
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Statuses' do
|
||||||
|
describe 'GET /@:account_username/:id' do
|
||||||
|
include AccountsHelper
|
||||||
|
|
||||||
|
def site_hostname
|
||||||
|
Rails.configuration.x.web_domain || Rails.configuration.x.local_domain
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has valid opengraph tags' do
|
||||||
|
account = Fabricate(:account, username: 'alice', display_name: 'Alice')
|
||||||
|
status = Fabricate(:status, account: account, text: 'Hello World')
|
||||||
|
|
||||||
|
get "/@#{account.username}/#{status.id}"
|
||||||
|
|
||||||
|
expect(head_link_icons.size).to eq(3) # Three favicons with sizes
|
||||||
|
|
||||||
|
expect(head_meta_content('og:title')).to match "#{display_name(account)} (#{acct(account)})"
|
||||||
|
expect(head_meta_content('og:type')).to eq 'article'
|
||||||
|
expect(head_meta_content('og:published_time')).to eq status.created_at.iso8601
|
||||||
|
expect(head_meta_content('og:url')).to eq short_account_status_url(account_username: account.username, id: status.id)
|
||||||
|
expect(head_meta_exists('og:locale')).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'has og:locale opengraph tag if the status has is written in a given language' do
|
||||||
|
status_text = "Una prova d'estatus català"
|
||||||
|
account = Fabricate(:account, username: 'alice', display_name: 'Alice')
|
||||||
|
status = Fabricate(:status, account: account, text: status_text, language: 'ca')
|
||||||
|
|
||||||
|
get "/@#{account.username}/#{status.id}"
|
||||||
|
|
||||||
|
expect(head_meta_content('og:title')).to match "#{display_name(account)} (#{acct(account)})"
|
||||||
|
expect(head_meta_content('og:type')).to eq 'article'
|
||||||
|
expect(head_meta_content('og:published_time')).to eq status.created_at.iso8601
|
||||||
|
expect(head_meta_content('og:url')).to eq short_account_status_url(account_username: account.username, id: status.id)
|
||||||
|
|
||||||
|
expect(head_meta_exists('og:locale')).to be true
|
||||||
|
expect(head_meta_content('og:locale')).to eq 'ca'
|
||||||
|
expect(head_meta_content('og:description')).to eq status_text
|
||||||
|
end
|
||||||
|
|
||||||
|
def head_link_icons
|
||||||
|
response
|
||||||
|
.parsed_body
|
||||||
|
.search('html head link[rel=icon]')
|
||||||
|
end
|
||||||
|
|
||||||
|
def head_meta_content(property)
|
||||||
|
response
|
||||||
|
.parsed_body
|
||||||
|
.search("html head meta[property='#{property}']")
|
||||||
|
.attr('content')
|
||||||
|
.text
|
||||||
|
end
|
||||||
|
|
||||||
|
def head_meta_exists(property)
|
||||||
|
!response
|
||||||
|
.parsed_body
|
||||||
|
.search("html head meta[property='#{property}']")
|
||||||
|
.empty?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
18
spec/system/admin/relationships_spec.rb
Normal file
18
spec/system/admin/relationships_spec.rb
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Admin Relationships' do
|
||||||
|
before { sign_in(admin_user) }
|
||||||
|
|
||||||
|
describe 'Viewing account relationships page' do
|
||||||
|
let(:account) { Fabricate(:account) }
|
||||||
|
|
||||||
|
it 'shows page with relationships for account' do
|
||||||
|
visit admin_account_relationships_path(account.id)
|
||||||
|
|
||||||
|
expect(page)
|
||||||
|
.to have_title(I18n.t('admin.relationships.title', acct: account.pretty_acct))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -11,6 +11,8 @@ RSpec.describe 'Admin::Trends::Links::PreviewCardProviders' do
|
|||||||
context 'without selecting any records' do
|
context 'without selecting any records' do
|
||||||
it 'displays a notice about selection' do
|
it 'displays a notice about selection' do
|
||||||
visit admin_trends_links_preview_card_providers_path
|
visit admin_trends_links_preview_card_providers_path
|
||||||
|
expect(page)
|
||||||
|
.to have_title(I18n.t('admin.trends.preview_card_providers.title'))
|
||||||
|
|
||||||
click_on button_for_allow
|
click_on button_for_allow
|
||||||
|
|
||||||
|
@ -11,6 +11,8 @@ RSpec.describe 'Admin::Trends::Links' do
|
|||||||
context 'without selecting any records' do
|
context 'without selecting any records' do
|
||||||
it 'displays a notice about selection' do
|
it 'displays a notice about selection' do
|
||||||
visit admin_trends_links_path
|
visit admin_trends_links_path
|
||||||
|
expect(page)
|
||||||
|
.to have_title(I18n.t('admin.trends.links.title'))
|
||||||
|
|
||||||
click_on button_for_allow
|
click_on button_for_allow
|
||||||
|
|
||||||
|
@ -11,6 +11,8 @@ RSpec.describe 'Admin::Trends::Statuses' do
|
|||||||
context 'without selecting any records' do
|
context 'without selecting any records' do
|
||||||
it 'displays a notice about selection' do
|
it 'displays a notice about selection' do
|
||||||
visit admin_trends_statuses_path
|
visit admin_trends_statuses_path
|
||||||
|
expect(page)
|
||||||
|
.to have_title(I18n.t('admin.trends.statuses.title'))
|
||||||
|
|
||||||
click_on button_for_allow
|
click_on button_for_allow
|
||||||
|
|
||||||
|
@ -11,6 +11,8 @@ RSpec.describe 'Admin::Trends::Tags' do
|
|||||||
context 'without selecting any records' do
|
context 'without selecting any records' do
|
||||||
it 'displays a notice about selection' do
|
it 'displays a notice about selection' do
|
||||||
visit admin_trends_tags_path
|
visit admin_trends_tags_path
|
||||||
|
expect(page)
|
||||||
|
.to have_title(I18n.t('admin.trends.tags.title'))
|
||||||
|
|
||||||
click_on button_for_allow
|
click_on button_for_allow
|
||||||
|
|
||||||
|
@ -3910,9 +3910,9 @@ __metadata:
|
|||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@types/lodash@npm:^4.14.195":
|
"@types/lodash@npm:^4.14.195":
|
||||||
version: 4.17.15
|
version: 4.17.16
|
||||||
resolution: "@types/lodash@npm:4.17.15"
|
resolution: "@types/lodash@npm:4.17.16"
|
||||||
checksum: 10c0/2eb2dc6d231f5fb4603d176c08c8d7af688f574d09af47466a179cd7812d9f64144ba74bb32ca014570ffdc544eedc51b7a5657212bad083b6eecbd72223f9bb
|
checksum: 10c0/cf017901b8ab1d7aabc86d5189d9288f4f99f19a75caf020c0e2c77b8d4cead4db0d0b842d009b029339f92399f49f34377dd7c2721053388f251778b4c23534
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user