Skip to content

Commit

Permalink
Squashed commit of the following:
Browse files Browse the repository at this point in the history
commit 9cb3fb9d980e3ee066083076f508c5ab1447176a
Author: noellabo <noel.yoshiba@gmail.com>
Date:   Sat Sep 5 07:15:19 2020 +0900

    Move the link to the mention list to the menu

commit b32dd87b43f4e09b8e2c437f1fb5d3ebd6221215
Author: noellabo <noel.yoshiba@gmail.com>
Date:   Sat Sep 5 00:56:12 2020 +0900

    Change limited visibility icon

commit 8db0d024119d1c2cef8de849f2501496a166a2dd
Author: noellabo <noel.yoshiba@gmail.com>
Date:   Tue Sep 1 01:42:13 2020 +0900

    Fix to disallow getting the list of mentions in limited replies

commit 490a9d65a59a3dd0d86e81f6780e879dc4313dff
Author: noellabo <noel.yoshiba@gmail.com>
Date:   Fri Jul 24 11:36:24 2020 +0900

    Add column to list mentioned accounts of limited status

commit 62a423ac2729c16f26fafe111f257bc373218df2
Author: noellabo <noel.yoshiba@gmail.com>
Date:   Thu Jul 23 13:30:17 2020 +0900

    Fix visibility compatibility more

commit a5cfa54b259054f41e89037f299fa928a2361818
Author: noellabo <noel.yoshiba@gmail.com>
Date:   Mon Jul 20 05:39:49 2020 +0900

    Fix visibility compatibility

commit 7900ca5650c77565b86ddc594a221dfa3b5321b4
Author: noellabo <noel.yoshiba@gmail.com>
Date:   Mon Jul 20 02:01:27 2020 +0900

    Add limited visibility icon to status
  • Loading branch information
noellabo committed Sep 5, 2020
1 parent 66b8396 commit a639e18
Show file tree
Hide file tree
Showing 24 changed files with 291 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# frozen_string_literal: true

class Api::V1::Statuses::MentionedByAccountsController < Api::BaseController
include Authorization

before_action -> { doorkeeper_authorize! :read, :'read:accounts' }
before_action :set_status
after_action :insert_pagination_headers

def index
@accounts = load_accounts
render json: @accounts, each_serializer: REST::AccountSerializer
end

private

def load_accounts
scope = default_accounts
scope.merge(paginated_mentions).to_a
end

def default_accounts
Account
.includes(:mentions, :account_stat)
.references(:mentions)
.where(mentions: { status_id: @status.id, silent: true })
end

def paginated_mentions
Mention.paginate_by_max_id(
limit_param(DEFAULT_ACCOUNTS_LIMIT),
params[:max_id],
params[:since_id]
)
end

def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end

def next_path
api_v1_status_mentioned_by_index_url pagination_params(max_id: pagination_max_id) if records_continue?
end

def prev_path
api_v1_status_mentioned_by_index_url pagination_params(since_id: pagination_since_id) unless @accounts.empty?
end

def pagination_max_id
@accounts.last.mentions.last.id
end

def pagination_since_id
@accounts.first.mentions.first.id
end

def records_continue?
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end

def set_status
@status = Status.find(params[:status_id])
authorize @status, :show_mentions?
rescue Mastodon::NotPermittedError
not_found
end

def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
end
4 changes: 3 additions & 1 deletion app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,10 @@ def visibility_icon(status)
fa_icon('globe', title: I18n.t('statuses.visibilities.public'))
elsif status.unlisted_visibility?
fa_icon('unlock', title: I18n.t('statuses.visibilities.unlisted'))
elsif status.private_visibility? || status.limited_visibility?
elsif status.private_visibility?
fa_icon('lock', title: I18n.t('statuses.visibilities.private'))
elsif status.limited_visibility?
fa_icon('user-circle', title: I18n.t('statuses.visibilities.limited'))
elsif status.direct_visibility?
fa_icon('envelope', title: I18n.t('statuses.visibilities.direct'))
end
Expand Down
2 changes: 2 additions & 0 deletions app/javascript/mastodon/actions/importer/normalizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export function normalizeStatus(status, normalOldStatus) {
normalStatus.contentHtml = normalOldStatus.get('contentHtml');
normalStatus.spoilerHtml = normalOldStatus.get('spoilerHtml');
normalStatus.hidden = normalOldStatus.get('hidden');
normalStatus.visibility = normalOldStatus.get('visibility');
} else {
const spoilerText = normalStatus.spoiler_text || '';
const searchContent = ([spoilerText, status.content].concat((status.poll && status.poll.options) ? status.poll.options.map(option => option.title) : [])).join('\n\n').replace(/<br\s*\/?>/g, '\n').replace(/<\/p><p>/g, '\n\n');
Expand All @@ -69,6 +70,7 @@ export function normalizeStatus(status, normalOldStatus) {
normalStatus.contentHtml = emojify(normalStatus.content, emojiMap);
normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(spoilerText), emojiMap);
normalStatus.hidden = expandSpoilers ? false : spoilerText.length > 0 || normalStatus.sensitive;
normalStatus.visibility = normalStatus.limited ? 'limited' : normalStatus.visibility;
}

return normalStatus;
Expand Down
39 changes: 39 additions & 0 deletions app/javascript/mastodon/actions/interactions.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ export const FAVOURITES_FETCH_REQUEST = 'FAVOURITES_FETCH_REQUEST';
export const FAVOURITES_FETCH_SUCCESS = 'FAVOURITES_FETCH_SUCCESS';
export const FAVOURITES_FETCH_FAIL = 'FAVOURITES_FETCH_FAIL';

export const MENTIONS_FETCH_REQUEST = 'MENTIONS_FETCH_REQUEST';
export const MENTIONS_FETCH_SUCCESS = 'MENTIONS_FETCH_SUCCESS';
export const MENTIONS_FETCH_FAIL = 'MENTIONS_FETCH_FAIL';

export const PIN_REQUEST = 'PIN_REQUEST';
export const PIN_SUCCESS = 'PIN_SUCCESS';
export const PIN_FAIL = 'PIN_FAIL';
Expand Down Expand Up @@ -337,6 +341,41 @@ export function fetchFavouritesFail(id, error) {
};
};

export function fetchMentions(id) {
return (dispatch, getState) => {
dispatch(fetchMentionsRequest(id));

api(getState).get(`/api/v1/statuses/${id}/mentioned_by`).then(response => {
dispatch(importFetchedAccounts(response.data));
dispatch(fetchMentionsSuccess(id, response.data));
}).catch(error => {
dispatch(fetchMentionsFail(id, error));
});
};
};

export function fetchMentionsRequest(id) {
return {
type: MENTIONS_FETCH_REQUEST,
id,
};
};

export function fetchMentionsSuccess(id, accounts) {
return {
type: MENTIONS_FETCH_SUCCESS,
id,
accounts,
};
};

export function fetchMentionsFail(id, error) {
return {
type: MENTIONS_FETCH_FAIL,
error,
};
};

export function pin(status) {
return (dispatch, getState) => {
dispatch(pinRequest(status));
Expand Down
9 changes: 7 additions & 2 deletions app/javascript/mastodon/components/status.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import { MediaGallery, Video, Audio } from '../features/ui/util/async-components';
import { HotKeys } from 'react-hotkeys';
import classNames from 'classnames';
import { Link } from 'react-router-dom';
import Icon from 'mastodon/components/icon';
import { displayMedia } from '../initial_state';
import { displayMedia, me } from '../initial_state';

// We use the component (and not the container) since we do not want
// to use the progress bar to show download progress
Expand Down Expand Up @@ -55,6 +56,7 @@ const messages = defineMessages({
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
limited_short: { id: 'privacy.limited.short', defaultMessage: 'Circle' },
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
});

Expand All @@ -75,6 +77,7 @@ class Status extends ImmutablePureComponent {
onReblog: PropTypes.func,
onDelete: PropTypes.func,
onDirect: PropTypes.func,
onMemberList: PropTypes.func,
onMention: PropTypes.func,
onPin: PropTypes.func,
onOpenMedia: PropTypes.func,
Expand Down Expand Up @@ -431,10 +434,12 @@ class Status extends ImmutablePureComponent {
'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) },
'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) },
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
'limited': { icon: 'user-circle', text: intl.formatMessage(messages.limited_short) },
'direct': { icon: 'envelope', text: intl.formatMessage(messages.direct_short) },
};

const visibilityIcon = visibilityIconInfo[status.get('visibility')];
const visibilityLink = <Icon id={visibilityIcon.icon} title={visibilityIcon.text} />;

return (
<HotKeys handlers={handlers}>
Expand All @@ -445,7 +450,7 @@ class Status extends ImmutablePureComponent {
<div className='status__expand' onClick={this.handleExpandClick} role='presentation' />
<div className='status__info'>
<a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener noreferrer'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
<span className='status__visibility-icon'><Icon id={visibilityIcon.icon} title={visibilityIcon.text} /></span>
<span className='status__visibility-icon'>{visibilityLink}</span>

<a onClick={this.handleAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} title={status.getIn(['account', 'acct'])} className='status__display-name' target='_blank' rel='noopener noreferrer'>
<div className='status__avatar'>
Expand Down
11 changes: 11 additions & 0 deletions app/javascript/mastodon/components/status_action_bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const messages = defineMessages({
delete: { id: 'status.delete', defaultMessage: 'Delete' },
redraft: { id: 'status.redraft', defaultMessage: 'Delete & re-draft' },
direct: { id: 'status.direct', defaultMessage: 'Direct message @{name}' },
showMemberList: { id: 'status.show_member_list', defaultMessage: 'Show member list' },
mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' },
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
Expand Down Expand Up @@ -73,6 +74,7 @@ class StatusActionBar extends ImmutablePureComponent {
onReblog: PropTypes.func,
onDelete: PropTypes.func,
onDirect: PropTypes.func,
onMemberList: PropTypes.func,
onMention: PropTypes.func,
onMute: PropTypes.func,
onUnmute: PropTypes.func,
Expand Down Expand Up @@ -159,6 +161,10 @@ class StatusActionBar extends ImmutablePureComponent {
this.props.onDirect(this.props.status.get('account'), this.context.router.history);
}

handleMemberListClick = () => {
this.props.onMemberList(this.props.status, this.context.router.history);
}

handleMuteClick = () => {
const { status, relationship, onMute, onUnmute } = this.props;
const account = status.get('account');
Expand Down Expand Up @@ -250,6 +256,11 @@ class StatusActionBar extends ImmutablePureComponent {
menu.push({ text: intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark), action: this.handleBookmarkClick });
menu.push(null);

if (status.getIn(['account', 'id']) === me && status.get('visibility') === 'limited' && !status.get('in_reply_to_id')) {
menu.push({ text: intl.formatMessage(messages.show_member_list), action: this.handleMemberListClick });
menu.push(null);
}

if (status.getIn(['account', 'id']) === me || withDismiss) {
menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
menu.push(null);
Expand Down
4 changes: 4 additions & 0 deletions app/javascript/mastodon/containers/status_container.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
dispatch(directCompose(account, router));
},

onMemberList (status, history) {
history.push(`/statuses/${status.get('id')}/mentions`);
},

onMention (account, router) {
dispatch(mentionCompose(account, router));
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ class ComposeForm extends ImmutablePureComponent {
const disabledButton = disabled || this.props.isUploading || this.props.isChangingUpload || length(text) > 500 || (text.length !== 0 && text.trim().length === 0 && !anyMedia);
let publishText = '';

if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
if (this.props.privacy !== 'public' && this.props.privacy !== 'unlisted') {
publishText = <span className='compose-form__publish-private'><Icon id='lock' /> {intl.formatMessage(messages.publish)}</span>;
} else {
publishText = this.props.privacy !== 'unlisted' ? intl.formatMessage(messages.publishLoud, { publish: intl.formatMessage(messages.publish) }) : intl.formatMessage(messages.publish);
Expand Down
77 changes: 77 additions & 0 deletions app/javascript/mastodon/features/mentions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React from 'react';
import { connect } from 'react-redux';
import ImmutablePureComponent from 'react-immutable-pure-component';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import LoadingIndicator from '../../components/loading_indicator';
import { fetchMentions } from '../../actions/interactions';
import { injectIntl, FormattedMessage } from 'react-intl';
import AccountContainer from '../../containers/account_container';
import Column from '../ui/components/column';
import ScrollableList from '../../components/scrollable_list';
import ColumnHeader from '../../components/column_header';

const mapStateToProps = (state, props) => ({
accountIds: state.getIn(['user_lists', 'mentioned_by', props.params.statusId]),
});

export default @connect(mapStateToProps)
@injectIntl
class Mentions extends ImmutablePureComponent {

static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
accountIds: ImmutablePropTypes.list,
multiColumn: PropTypes.bool,
intl: PropTypes.object.isRequired,
};

componentWillMount () {
if (!this.props.accountIds) {
this.props.dispatch(fetchMentions(this.props.params.statusId));
}
}

componentWillReceiveProps (nextProps) {
if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
this.props.dispatch(fetchMentions(nextProps.params.statusId));
}
}

render () {
const { shouldUpdateScroll, accountIds, multiColumn } = this.props;

if (!accountIds) {
return (
<Column>
<LoadingIndicator />
</Column>
);
}

const emptyMessage = <FormattedMessage id='empty_column.mentions' defaultMessage='No one has mentioned this toot.' />;

return (
<Column bindToDocument={!multiColumn}>
<ColumnHeader
showBackButton
multiColumn={multiColumn}
/>

<ScrollableList
scrollKey='mentions'
shouldUpdateScroll={shouldUpdateScroll}
emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
>
{accountIds.map(id =>
<AccountContainer key={id} id={id} withNote={false} />,
)}
</ScrollableList>
</Column>
);
}

}
10 changes: 10 additions & 0 deletions app/javascript/mastodon/features/status/components/action_bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const messages = defineMessages({
delete: { id: 'status.delete', defaultMessage: 'Delete' },
redraft: { id: 'status.redraft', defaultMessage: 'Delete & re-draft' },
direct: { id: 'status.direct', defaultMessage: 'Direct message @{name}' },
showMemberList: { id: 'status.show_member_list', defaultMessage: 'Show member list' },
mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' },
reply: { id: 'status.reply', defaultMessage: 'Reply' },
reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
Expand Down Expand Up @@ -60,6 +61,7 @@ class ActionBar extends React.PureComponent {
onBookmark: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
onDirect: PropTypes.func.isRequired,
onMemberList: PropTypes.func.isRequired,
onMention: PropTypes.func.isRequired,
onMute: PropTypes.func,
onUnmute: PropTypes.func,
Expand Down Expand Up @@ -102,6 +104,10 @@ class ActionBar extends React.PureComponent {
this.props.onDirect(this.props.status.get('account'), this.context.router.history);
}

handleMemberListClick = () => {
this.props.onMemberList(this.props.status, this.context.router.history);
}

handleMentionClick = () => {
this.props.onMention(this.props.status.get('account'), this.context.router.history);
}
Expand Down Expand Up @@ -204,6 +210,10 @@ class ActionBar extends React.PureComponent {
menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
}

if (status.get('visibility') === 'limited' && !status.get('in_reply_to_id')) {
menu.push({ text: intl.formatMessage(messages.showMemberList), action: this.handleMemberListClick });
}

menu.push(null);
menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
menu.push(null);
Expand Down
Loading

0 comments on commit a639e18

Please sign in to comment.