2017-04-21 18:05:35 +00:00
|
|
|
import PropTypes from 'prop-types';
|
2023-05-23 15:15:17 +00:00
|
|
|
|
2016-11-18 14:36:16 +00:00
|
|
|
import { defineMessages, injectIntl } from 'react-intl';
|
2023-05-23 15:15:17 +00:00
|
|
|
|
2020-08-24 12:13:44 +00:00
|
|
|
import classNames from 'classnames';
|
2023-10-19 17:44:55 +00:00
|
|
|
import { withRouter } from 'react-router-dom';
|
2023-05-23 15:15:17 +00:00
|
|
|
|
|
|
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
|
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
|
|
import { connect } from 'react-redux';
|
|
|
|
|
2024-01-12 20:16:48 +00:00
|
|
|
import BookmarkIcon from 'mastodon/../material-icons/400-24px/bookmark-fill.svg';
|
|
|
|
import BookmarkBorderIcon from 'mastodon/../material-icons/400-24px/bookmark.svg?react';
|
|
|
|
import MoreHorizIcon from 'mastodon/../material-icons/400-24px/more_horiz.svg?react';
|
|
|
|
import RepeatIcon from 'mastodon/../material-icons/400-24px/repeat.svg?react';
|
|
|
|
import ReplyIcon from 'mastodon/../material-icons/400-24px/reply.svg?react';
|
|
|
|
import ReplyAllIcon from 'mastodon/../material-icons/400-24px/reply_all.svg?react';
|
|
|
|
import StarIcon from 'mastodon/../material-icons/400-24px/star-fill.svg?react';
|
|
|
|
import StarBorderIcon from 'mastodon/../material-icons/400-24px/star.svg?react';
|
|
|
|
import VisibilityIcon from 'mastodon/../material-icons/400-24px/visibility.svg?react';
|
2024-01-12 10:31:24 +00:00
|
|
|
import RepeatDisabledIcon from 'mastodon/../svg-icons/repeat_disabled.svg?react';
|
|
|
|
import RepeatPrivateIcon from 'mastodon/../svg-icons/repeat_private.svg?react';
|
2023-01-05 13:03:46 +00:00
|
|
|
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
|
2023-10-19 17:44:55 +00:00
|
|
|
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
2016-11-18 14:36:16 +00:00
|
|
|
|
2023-05-23 15:15:17 +00:00
|
|
|
import DropdownMenuContainer from '../containers/dropdown_menu_container';
|
|
|
|
import { me } from '../initial_state';
|
|
|
|
|
|
|
|
import { IconButton } from './icon_button';
|
|
|
|
|
2016-11-18 14:36:16 +00:00
|
|
|
const messages = defineMessages({
|
|
|
|
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
2018-06-04 22:17:38 +00:00
|
|
|
redraft: { id: 'status.redraft', defaultMessage: 'Delete & re-draft' },
|
2022-02-09 23:15:30 +00:00
|
|
|
edit: { id: 'status.edit', defaultMessage: 'Edit' },
|
2023-03-30 13:16:20 +00:00
|
|
|
direct: { id: 'status.direct', defaultMessage: 'Privately mention @{name}' },
|
2017-02-28 23:53:11 +00:00
|
|
|
mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' },
|
2017-04-03 07:56:01 +00:00
|
|
|
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
|
2017-02-28 23:53:11 +00:00
|
|
|
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
|
2016-11-18 14:36:16 +00:00
|
|
|
reply: { id: 'status.reply', defaultMessage: 'Reply' },
|
2017-07-25 18:38:39 +00:00
|
|
|
share: { id: 'status.share', defaultMessage: 'Share' },
|
2017-10-16 07:31:47 +00:00
|
|
|
more: { id: 'status.more', defaultMessage: 'More' },
|
2017-04-19 12:20:04 +00:00
|
|
|
replyAll: { id: 'status.replyAll', defaultMessage: 'Reply to thread' },
|
2017-04-27 10:03:28 +00:00
|
|
|
reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
|
2020-08-22 22:08:31 +00:00
|
|
|
reblog_private: { id: 'status.reblog_private', defaultMessage: 'Boost with original visibility' },
|
2018-04-17 21:35:45 +00:00
|
|
|
cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
|
2017-04-27 10:03:28 +00:00
|
|
|
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
|
2023-07-21 17:09:13 +00:00
|
|
|
favourite: { id: 'status.favourite', defaultMessage: 'Favorite' },
|
2019-11-13 22:02:10 +00:00
|
|
|
bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' },
|
2019-11-19 20:24:16 +00:00
|
|
|
removeBookmark: { id: 'status.remove_bookmark', defaultMessage: 'Remove bookmark' },
|
2017-02-28 23:53:11 +00:00
|
|
|
open: { id: 'status.open', defaultMessage: 'Expand this status' },
|
Feature conversations muting (#3017)
* Add <ostatus:conversation /> tag to Atom input/output
Only uses ref attribute (not href) because href would be
the alternate link that's always included also.
Creates new conversation for every non-reply status. Carries
over conversation for every reply. Keeps remote URIs verbatim,
generates local URIs on the fly like the rest of them.
* Conversation muting - prevents notifications that reference a conversation
(including replies, favourites, reblogs) from being created. API endpoints
/api/v1/statuses/:id/mute and /api/v1/statuses/:id/unmute
Currently no way to tell when a status/conversation is muted, so the web UI
only has a "disable notifications" button, doesn't work as a toggle
* Display "Dismiss notifications" on all statuses in notifications column, not just own
* Add "muted" as a boolean attribute on statuses JSON
For now always false on contained reblogs, since it's only relevant for
statuses returned from the notifications endpoint, which are not nested
Remove "Disable notifications" from detailed status view, since it's
only relevant in the notifications column
* Up max class length
* Remove pending test for conversation mute
* Add tests, clean up
* Rename to "mute conversation" and "unmute conversation"
* Raise validation error when trying to mute/unmute status without conversation
2017-05-15 01:04:13 +00:00
|
|
|
report: { id: 'status.report', defaultMessage: 'Report @{name}' },
|
|
|
|
muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' },
|
|
|
|
unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' },
|
2017-08-24 23:41:18 +00:00
|
|
|
pin: { id: 'status.pin', defaultMessage: 'Pin on profile' },
|
|
|
|
unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' },
|
2017-09-01 19:30:13 +00:00
|
|
|
embed: { id: 'status.embed', defaultMessage: 'Embed' },
|
2019-01-04 12:10:43 +00:00
|
|
|
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
|
2023-01-05 12:45:01 +00:00
|
|
|
admin_status: { id: 'status.admin_status', defaultMessage: 'Open this post in the moderation interface' },
|
2023-01-05 13:03:46 +00:00
|
|
|
admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' },
|
2023-01-05 12:45:01 +00:00
|
|
|
copy: { id: 'status.copy', defaultMessage: 'Copy link to post' },
|
|
|
|
hide: { id: 'status.hide', defaultMessage: 'Hide post' },
|
2020-03-05 22:20:49 +00:00
|
|
|
blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' },
|
|
|
|
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
|
2019-11-19 20:24:16 +00:00
|
|
|
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
|
|
|
|
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
|
2022-08-25 02:27:47 +00:00
|
|
|
filter: { id: 'status.filter', defaultMessage: 'Filter this post' },
|
2022-11-10 07:49:35 +00:00
|
|
|
openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' },
|
2016-11-18 14:36:16 +00:00
|
|
|
});
|
2016-09-29 22:00:45 +00:00
|
|
|
|
2019-11-19 20:24:16 +00:00
|
|
|
const mapStateToProps = (state, { status }) => ({
|
|
|
|
relationship: state.getIn(['relationships', status.getIn(['account', 'id'])]),
|
|
|
|
});
|
|
|
|
|
2018-09-14 15:59:48 +00:00
|
|
|
class StatusActionBar extends ImmutablePureComponent {
|
2017-04-21 18:05:35 +00:00
|
|
|
|
2017-05-12 12:44:10 +00:00
|
|
|
static contextTypes = {
|
2022-07-05 00:41:40 +00:00
|
|
|
identity: PropTypes.object,
|
2017-05-12 12:44:10 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static propTypes = {
|
|
|
|
status: ImmutablePropTypes.map.isRequired,
|
2019-11-19 20:24:16 +00:00
|
|
|
relationship: ImmutablePropTypes.map,
|
2017-05-12 12:44:10 +00:00
|
|
|
onReply: PropTypes.func,
|
|
|
|
onFavourite: PropTypes.func,
|
|
|
|
onReblog: PropTypes.func,
|
|
|
|
onDelete: PropTypes.func,
|
2018-04-09 15:09:11 +00:00
|
|
|
onDirect: PropTypes.func,
|
2017-05-12 12:44:10 +00:00
|
|
|
onMention: PropTypes.func,
|
|
|
|
onMute: PropTypes.func,
|
2019-11-19 20:24:16 +00:00
|
|
|
onUnmute: PropTypes.func,
|
2017-05-12 12:44:10 +00:00
|
|
|
onBlock: PropTypes.func,
|
2019-11-19 20:24:16 +00:00
|
|
|
onUnblock: PropTypes.func,
|
|
|
|
onBlockDomain: PropTypes.func,
|
|
|
|
onUnblockDomain: PropTypes.func,
|
2017-05-12 12:44:10 +00:00
|
|
|
onReport: PropTypes.func,
|
2017-09-01 19:30:13 +00:00
|
|
|
onEmbed: PropTypes.func,
|
Feature conversations muting (#3017)
* Add <ostatus:conversation /> tag to Atom input/output
Only uses ref attribute (not href) because href would be
the alternate link that's always included also.
Creates new conversation for every non-reply status. Carries
over conversation for every reply. Keeps remote URIs verbatim,
generates local URIs on the fly like the rest of them.
* Conversation muting - prevents notifications that reference a conversation
(including replies, favourites, reblogs) from being created. API endpoints
/api/v1/statuses/:id/mute and /api/v1/statuses/:id/unmute
Currently no way to tell when a status/conversation is muted, so the web UI
only has a "disable notifications" button, doesn't work as a toggle
* Display "Dismiss notifications" on all statuses in notifications column, not just own
* Add "muted" as a boolean attribute on statuses JSON
For now always false on contained reblogs, since it's only relevant for
statuses returned from the notifications endpoint, which are not nested
Remove "Disable notifications" from detailed status view, since it's
only relevant in the notifications column
* Up max class length
* Remove pending test for conversation mute
* Add tests, clean up
* Rename to "mute conversation" and "unmute conversation"
* Raise validation error when trying to mute/unmute status without conversation
2017-05-15 01:04:13 +00:00
|
|
|
onMuteConversation: PropTypes.func,
|
2017-08-24 23:41:18 +00:00
|
|
|
onPin: PropTypes.func,
|
2019-11-13 22:02:10 +00:00
|
|
|
onBookmark: PropTypes.func,
|
Revamp post filtering system (#18058)
* Add model for custom filter keywords
* Use CustomFilterKeyword internally
Does not change the API
* Fix /filters/edit and /filters/new
* Add migration tests
* Remove whole_word column from custom_filters (covered by custom_filter_keywords)
* Redesign /filters
Instead of a list, present a card that displays more information and handles
multiple keywords per filter.
* Redesign /filters/new and /filters/edit to add and remove keywords
This adds a new gem dependency: cocoon, as well as a npm dependency:
cocoon-js-vanilla. Those are used to easily populate and remove form fields
from the user interface when manipulating multiple keyword filters at once.
* Add /api/v2/filters to edit filter with multiple keywords
Entities:
- `Filter`: `id`, `title`, `filter_action` (either `hide` or `warn`), `context`
`keywords`
- `FilterKeyword`: `id`, `keyword`, `whole_word`
API endpoits:
- `GET /api/v2/filters` to list filters (including keywords)
- `POST /api/v2/filters` to create a new filter
`keywords_attributes` can also be passed to create keywords in one request
- `GET /api/v2/filters/:id` to read a particular filter
- `PUT /api/v2/filters/:id` to update a new filter
`keywords_attributes` can also be passed to edit, delete or add keywords in
one request
- `DELETE /api/v2/filters/:id` to delete a particular filter
- `GET /api/v2/filters/:id/keywords` to list keywords for a filter
- `POST /api/v2/filters/:filter_id/keywords/:id` to add a new keyword to a
filter
- `GET /api/v2/filter_keywords/:id` to read a particular keyword
- `PUT /api/v2/filter_keywords/:id` to edit a particular keyword
- `DELETE /api/v2/filter_keywords/:id` to delete a particular keyword
* Change from `irreversible` boolean to `action` enum
* Remove irrelevent `irreversible_must_be_within_context` check
* Fix /filters/new and /filters/edit with update for filter_action
* Fix Rubocop/Codeclimate complaining about task names
* Refactor FeedManager#phrase_filtered?
This moves regexp building and filter caching to the `CustomFilter` class.
This does not change the functional behavior yet, but this changes how the
cache is built, doing per-custom_filter regexps so that filters can be matched
independently, while still offering caching.
* Perform server-side filtering and output result in REST API
* Fix numerous filters_changed events being sent when editing multiple keywords at once
* Add some tests
* Use the new API in the WebUI
- use client-side logic for filters we have fetched rules for.
This is so that filter changes can be retroactively applied without
reloading the UI.
- use server-side logic for filters we haven't fetched rules for yet
(e.g. network error, or initial timeline loading)
* Minor optimizations and refactoring
* Perform server-side filtering on the streaming server
* Change the wording of filter action labels
* Fix issues pointed out by linter
* Change design of “Show anyway” link in accordence to review comments
* Drop “irreversible” filtering behavior
* Move /api/v2/filter_keywords to /api/v1/filters/keywords
* Rename `filter_results` attribute to `filtered`
* Rename REST::LegacyFilterSerializer to REST::V1::FilterSerializer
* Fix systemChannelId value in streaming server
* Simplify code by removing client-side filtering code
The simplifcation comes at a cost though: filters aren't retroactively
applied anymore.
2022-06-28 07:42:13 +00:00
|
|
|
onFilter: PropTypes.func,
|
2022-08-25 02:27:47 +00:00
|
|
|
onAddFilter: PropTypes.func,
|
2022-10-07 08:14:31 +00:00
|
|
|
onInteractionModal: PropTypes.func,
|
Feature conversations muting (#3017)
* Add <ostatus:conversation /> tag to Atom input/output
Only uses ref attribute (not href) because href would be
the alternate link that's always included also.
Creates new conversation for every non-reply status. Carries
over conversation for every reply. Keeps remote URIs verbatim,
generates local URIs on the fly like the rest of them.
* Conversation muting - prevents notifications that reference a conversation
(including replies, favourites, reblogs) from being created. API endpoints
/api/v1/statuses/:id/mute and /api/v1/statuses/:id/unmute
Currently no way to tell when a status/conversation is muted, so the web UI
only has a "disable notifications" button, doesn't work as a toggle
* Display "Dismiss notifications" on all statuses in notifications column, not just own
* Add "muted" as a boolean attribute on statuses JSON
For now always false on contained reblogs, since it's only relevant for
statuses returned from the notifications endpoint, which are not nested
Remove "Disable notifications" from detailed status view, since it's
only relevant in the notifications column
* Up max class length
* Remove pending test for conversation mute
* Add tests, clean up
* Rename to "mute conversation" and "unmute conversation"
* Raise validation error when trying to mute/unmute status without conversation
2017-05-15 01:04:13 +00:00
|
|
|
withDismiss: PropTypes.bool,
|
2022-02-24 23:34:33 +00:00
|
|
|
withCounters: PropTypes.bool,
|
2020-07-09 13:09:19 +00:00
|
|
|
scrollKey: PropTypes.string,
|
2017-05-20 15:31:47 +00:00
|
|
|
intl: PropTypes.object.isRequired,
|
2023-10-19 17:44:55 +00:00
|
|
|
...WithRouterPropTypes,
|
2017-05-12 12:44:10 +00:00
|
|
|
};
|
|
|
|
|
2017-05-26 12:05:52 +00:00
|
|
|
// Avoid checking props that are functions (and whose equality will always
|
|
|
|
// evaluate to false. See react-immutable-pure-component for usage.
|
|
|
|
updateOnProps = [
|
|
|
|
'status',
|
2019-11-19 20:24:16 +00:00
|
|
|
'relationship',
|
2017-05-26 12:05:52 +00:00
|
|
|
'withDismiss',
|
2023-01-30 00:45:35 +00:00
|
|
|
];
|
2017-05-26 12:05:52 +00:00
|
|
|
|
2017-05-12 12:44:10 +00:00
|
|
|
handleReplyClick = () => {
|
2022-10-07 08:14:31 +00:00
|
|
|
const { signedIn } = this.context.identity;
|
|
|
|
|
|
|
|
if (signedIn) {
|
2023-10-19 17:44:55 +00:00
|
|
|
this.props.onReply(this.props.status, this.props.history);
|
2019-02-15 18:43:09 +00:00
|
|
|
} else {
|
2022-10-07 08:14:31 +00:00
|
|
|
this.props.onInteractionModal('reply', this.props.status);
|
2019-02-15 18:43:09 +00:00
|
|
|
}
|
2023-01-30 00:45:35 +00:00
|
|
|
};
|
2016-09-29 22:00:45 +00:00
|
|
|
|
2017-07-25 18:38:39 +00:00
|
|
|
handleShareClick = () => {
|
|
|
|
navigator.share({
|
|
|
|
url: this.props.status.get('url'),
|
2018-03-31 11:16:38 +00:00
|
|
|
}).catch((e) => {
|
|
|
|
if (e.name !== 'AbortError') console.error(e);
|
2017-07-25 18:38:39 +00:00
|
|
|
});
|
2023-01-30 00:45:35 +00:00
|
|
|
};
|
2017-07-25 18:38:39 +00:00
|
|
|
|
2017-05-12 12:44:10 +00:00
|
|
|
handleFavouriteClick = () => {
|
2022-10-07 08:14:31 +00:00
|
|
|
const { signedIn } = this.context.identity;
|
|
|
|
|
|
|
|
if (signedIn) {
|
2019-02-15 18:43:09 +00:00
|
|
|
this.props.onFavourite(this.props.status);
|
|
|
|
} else {
|
2022-10-07 08:14:31 +00:00
|
|
|
this.props.onInteractionModal('favourite', this.props.status);
|
2019-02-15 18:43:09 +00:00
|
|
|
}
|
2023-01-30 00:45:35 +00:00
|
|
|
};
|
2019-02-15 18:43:09 +00:00
|
|
|
|
|
|
|
handleReblogClick = e => {
|
2022-10-07 08:14:31 +00:00
|
|
|
const { signedIn } = this.context.identity;
|
|
|
|
|
|
|
|
if (signedIn) {
|
2019-02-15 18:43:09 +00:00
|
|
|
this.props.onReblog(this.props.status, e);
|
|
|
|
} else {
|
2022-10-07 08:14:31 +00:00
|
|
|
this.props.onInteractionModal('reblog', this.props.status);
|
2019-02-15 18:43:09 +00:00
|
|
|
}
|
2023-01-30 00:45:35 +00:00
|
|
|
};
|
2016-09-29 22:00:45 +00:00
|
|
|
|
2019-11-13 22:02:10 +00:00
|
|
|
handleBookmarkClick = () => {
|
|
|
|
this.props.onBookmark(this.props.status);
|
2023-01-30 00:45:35 +00:00
|
|
|
};
|
2019-11-13 22:02:10 +00:00
|
|
|
|
2017-05-12 12:44:10 +00:00
|
|
|
handleDeleteClick = () => {
|
2023-10-19 17:44:55 +00:00
|
|
|
this.props.onDelete(this.props.status, this.props.history);
|
2023-01-30 00:45:35 +00:00
|
|
|
};
|
2016-09-29 22:00:45 +00:00
|
|
|
|
2018-06-04 22:17:38 +00:00
|
|
|
handleRedraftClick = () => {
|
2023-10-19 17:44:55 +00:00
|
|
|
this.props.onDelete(this.props.status, this.props.history, true);
|
2023-01-30 00:45:35 +00:00
|
|
|
};
|
2018-06-04 22:17:38 +00:00
|
|
|
|
2022-02-09 23:15:30 +00:00
|
|
|
handleEditClick = () => {
|
2023-10-19 17:44:55 +00:00
|
|
|
this.props.onEdit(this.props.status, this.props.history);
|
2023-01-30 00:45:35 +00:00
|
|
|
};
|
2022-02-09 23:15:30 +00:00
|
|
|
|
2017-08-24 23:41:18 +00:00
|
|
|
handlePinClick = () => {
|
|
|
|
this.props.onPin(this.props.status);
|
2023-01-30 00:45:35 +00:00
|
|
|
};
|
2017-08-24 23:41:18 +00:00
|
|
|
|
2017-05-12 12:44:10 +00:00
|
|
|
handleMentionClick = () => {
|
2023-10-19 17:44:55 +00:00
|
|
|
this.props.onMention(this.props.status.get('account'), this.props.history);
|
2023-01-30 00:45:35 +00:00
|
|
|
};
|
2016-10-24 15:11:02 +00:00
|
|
|
|
2018-04-09 15:09:11 +00:00
|
|
|
handleDirectClick = () => {
|
2023-10-19 17:44:55 +00:00
|
|
|
this.props.onDirect(this.props.status.get('account'), this.props.history);
|
2023-01-30 00:45:35 +00:00
|
|
|
};
|
2018-04-09 15:09:11 +00:00
|
|
|
|
2017-05-12 12:44:10 +00:00
|
|
|
handleMuteClick = () => {
|
2019-11-19 20:24:16 +00:00
|
|
|
const { status, relationship, onMute, onUnmute } = this.props;
|
|
|
|
const account = status.get('account');
|
|
|
|
|
|
|
|
if (relationship && relationship.get('muting')) {
|
|
|
|
onUnmute(account);
|
|
|
|
} else {
|
|
|
|
onMute(account);
|
|
|
|
}
|
2023-01-30 00:45:35 +00:00
|
|
|
};
|
2017-04-03 07:56:01 +00:00
|
|
|
|
2017-05-12 12:44:10 +00:00
|
|
|
handleBlockClick = () => {
|
2019-11-19 20:24:16 +00:00
|
|
|
const { status, relationship, onBlock, onUnblock } = this.props;
|
|
|
|
const account = status.get('account');
|
|
|
|
|
|
|
|
if (relationship && relationship.get('blocking')) {
|
|
|
|
onUnblock(account);
|
2019-12-02 17:25:24 +00:00
|
|
|
} else {
|
|
|
|
onBlock(status);
|
2019-11-19 20:24:16 +00:00
|
|
|
}
|
2023-01-30 00:45:35 +00:00
|
|
|
};
|
2019-11-19 20:24:16 +00:00
|
|
|
|
|
|
|
handleBlockDomain = () => {
|
|
|
|
const { status, onBlockDomain } = this.props;
|
|
|
|
const account = status.get('account');
|
|
|
|
|
|
|
|
onBlockDomain(account.get('acct').split('@')[1]);
|
2023-01-30 00:45:35 +00:00
|
|
|
};
|
2019-11-19 20:24:16 +00:00
|
|
|
|
|
|
|
handleUnblockDomain = () => {
|
|
|
|
const { status, onUnblockDomain } = this.props;
|
|
|
|
const account = status.get('account');
|
|
|
|
|
|
|
|
onUnblockDomain(account.get('acct').split('@')[1]);
|
2023-01-30 00:45:35 +00:00
|
|
|
};
|
2016-11-23 21:57:57 +00:00
|
|
|
|
2017-05-12 12:44:10 +00:00
|
|
|
handleOpen = () => {
|
2023-10-19 17:44:55 +00:00
|
|
|
this.props.history.push(`/@${this.props.status.getIn(['account', 'acct'])}/${this.props.status.get('id')}`);
|
2023-01-30 00:45:35 +00:00
|
|
|
};
|
2016-12-11 21:49:25 +00:00
|
|
|
|
2017-09-01 19:30:13 +00:00
|
|
|
handleEmbed = () => {
|
|
|
|
this.props.onEmbed(this.props.status);
|
2023-01-30 00:45:35 +00:00
|
|
|
};
|
2017-09-01 19:30:13 +00:00
|
|
|
|
2017-05-12 12:44:10 +00:00
|
|
|
handleReport = () => {
|
2017-02-14 19:59:26 +00:00
|
|
|
this.props.onReport(this.props.status);
|
2023-01-30 00:45:35 +00:00
|
|
|
};
|
2017-02-14 19:59:26 +00:00
|
|
|
|
Feature conversations muting (#3017)
* Add <ostatus:conversation /> tag to Atom input/output
Only uses ref attribute (not href) because href would be
the alternate link that's always included also.
Creates new conversation for every non-reply status. Carries
over conversation for every reply. Keeps remote URIs verbatim,
generates local URIs on the fly like the rest of them.
* Conversation muting - prevents notifications that reference a conversation
(including replies, favourites, reblogs) from being created. API endpoints
/api/v1/statuses/:id/mute and /api/v1/statuses/:id/unmute
Currently no way to tell when a status/conversation is muted, so the web UI
only has a "disable notifications" button, doesn't work as a toggle
* Display "Dismiss notifications" on all statuses in notifications column, not just own
* Add "muted" as a boolean attribute on statuses JSON
For now always false on contained reblogs, since it's only relevant for
statuses returned from the notifications endpoint, which are not nested
Remove "Disable notifications" from detailed status view, since it's
only relevant in the notifications column
* Up max class length
* Remove pending test for conversation mute
* Add tests, clean up
* Rename to "mute conversation" and "unmute conversation"
* Raise validation error when trying to mute/unmute status without conversation
2017-05-15 01:04:13 +00:00
|
|
|
handleConversationMuteClick = () => {
|
|
|
|
this.props.onMuteConversation(this.props.status);
|
2023-01-30 00:45:35 +00:00
|
|
|
};
|
Feature conversations muting (#3017)
* Add <ostatus:conversation /> tag to Atom input/output
Only uses ref attribute (not href) because href would be
the alternate link that's always included also.
Creates new conversation for every non-reply status. Carries
over conversation for every reply. Keeps remote URIs verbatim,
generates local URIs on the fly like the rest of them.
* Conversation muting - prevents notifications that reference a conversation
(including replies, favourites, reblogs) from being created. API endpoints
/api/v1/statuses/:id/mute and /api/v1/statuses/:id/unmute
Currently no way to tell when a status/conversation is muted, so the web UI
only has a "disable notifications" button, doesn't work as a toggle
* Display "Dismiss notifications" on all statuses in notifications column, not just own
* Add "muted" as a boolean attribute on statuses JSON
For now always false on contained reblogs, since it's only relevant for
statuses returned from the notifications endpoint, which are not nested
Remove "Disable notifications" from detailed status view, since it's
only relevant in the notifications column
* Up max class length
* Remove pending test for conversation mute
* Add tests, clean up
* Rename to "mute conversation" and "unmute conversation"
* Raise validation error when trying to mute/unmute status without conversation
2017-05-15 01:04:13 +00:00
|
|
|
|
2022-08-25 02:27:47 +00:00
|
|
|
handleFilterClick = () => {
|
|
|
|
this.props.onAddFilter(this.props.status);
|
2023-01-30 00:45:35 +00:00
|
|
|
};
|
Revamp post filtering system (#18058)
* Add model for custom filter keywords
* Use CustomFilterKeyword internally
Does not change the API
* Fix /filters/edit and /filters/new
* Add migration tests
* Remove whole_word column from custom_filters (covered by custom_filter_keywords)
* Redesign /filters
Instead of a list, present a card that displays more information and handles
multiple keywords per filter.
* Redesign /filters/new and /filters/edit to add and remove keywords
This adds a new gem dependency: cocoon, as well as a npm dependency:
cocoon-js-vanilla. Those are used to easily populate and remove form fields
from the user interface when manipulating multiple keyword filters at once.
* Add /api/v2/filters to edit filter with multiple keywords
Entities:
- `Filter`: `id`, `title`, `filter_action` (either `hide` or `warn`), `context`
`keywords`
- `FilterKeyword`: `id`, `keyword`, `whole_word`
API endpoits:
- `GET /api/v2/filters` to list filters (including keywords)
- `POST /api/v2/filters` to create a new filter
`keywords_attributes` can also be passed to create keywords in one request
- `GET /api/v2/filters/:id` to read a particular filter
- `PUT /api/v2/filters/:id` to update a new filter
`keywords_attributes` can also be passed to edit, delete or add keywords in
one request
- `DELETE /api/v2/filters/:id` to delete a particular filter
- `GET /api/v2/filters/:id/keywords` to list keywords for a filter
- `POST /api/v2/filters/:filter_id/keywords/:id` to add a new keyword to a
filter
- `GET /api/v2/filter_keywords/:id` to read a particular keyword
- `PUT /api/v2/filter_keywords/:id` to edit a particular keyword
- `DELETE /api/v2/filter_keywords/:id` to delete a particular keyword
* Change from `irreversible` boolean to `action` enum
* Remove irrelevent `irreversible_must_be_within_context` check
* Fix /filters/new and /filters/edit with update for filter_action
* Fix Rubocop/Codeclimate complaining about task names
* Refactor FeedManager#phrase_filtered?
This moves regexp building and filter caching to the `CustomFilter` class.
This does not change the functional behavior yet, but this changes how the
cache is built, doing per-custom_filter regexps so that filters can be matched
independently, while still offering caching.
* Perform server-side filtering and output result in REST API
* Fix numerous filters_changed events being sent when editing multiple keywords at once
* Add some tests
* Use the new API in the WebUI
- use client-side logic for filters we have fetched rules for.
This is so that filter changes can be retroactively applied without
reloading the UI.
- use server-side logic for filters we haven't fetched rules for yet
(e.g. network error, or initial timeline loading)
* Minor optimizations and refactoring
* Perform server-side filtering on the streaming server
* Change the wording of filter action labels
* Fix issues pointed out by linter
* Change design of “Show anyway” link in accordence to review comments
* Drop “irreversible” filtering behavior
* Move /api/v2/filter_keywords to /api/v1/filters/keywords
* Rename `filter_results` attribute to `filtered`
* Rename REST::LegacyFilterSerializer to REST::V1::FilterSerializer
* Fix systemChannelId value in streaming server
* Simplify code by removing client-side filtering code
The simplifcation comes at a cost though: filters aren't retroactively
applied anymore.
2022-06-28 07:42:13 +00:00
|
|
|
|
2019-02-11 03:19:49 +00:00
|
|
|
handleCopy = () => {
|
2022-11-10 07:49:35 +00:00
|
|
|
const url = this.props.status.get('url');
|
|
|
|
navigator.clipboard.writeText(url);
|
2023-01-30 00:45:35 +00:00
|
|
|
};
|
2019-02-11 03:19:49 +00:00
|
|
|
|
2022-08-25 02:27:47 +00:00
|
|
|
handleHideClick = () => {
|
Revamp post filtering system (#18058)
* Add model for custom filter keywords
* Use CustomFilterKeyword internally
Does not change the API
* Fix /filters/edit and /filters/new
* Add migration tests
* Remove whole_word column from custom_filters (covered by custom_filter_keywords)
* Redesign /filters
Instead of a list, present a card that displays more information and handles
multiple keywords per filter.
* Redesign /filters/new and /filters/edit to add and remove keywords
This adds a new gem dependency: cocoon, as well as a npm dependency:
cocoon-js-vanilla. Those are used to easily populate and remove form fields
from the user interface when manipulating multiple keyword filters at once.
* Add /api/v2/filters to edit filter with multiple keywords
Entities:
- `Filter`: `id`, `title`, `filter_action` (either `hide` or `warn`), `context`
`keywords`
- `FilterKeyword`: `id`, `keyword`, `whole_word`
API endpoits:
- `GET /api/v2/filters` to list filters (including keywords)
- `POST /api/v2/filters` to create a new filter
`keywords_attributes` can also be passed to create keywords in one request
- `GET /api/v2/filters/:id` to read a particular filter
- `PUT /api/v2/filters/:id` to update a new filter
`keywords_attributes` can also be passed to edit, delete or add keywords in
one request
- `DELETE /api/v2/filters/:id` to delete a particular filter
- `GET /api/v2/filters/:id/keywords` to list keywords for a filter
- `POST /api/v2/filters/:filter_id/keywords/:id` to add a new keyword to a
filter
- `GET /api/v2/filter_keywords/:id` to read a particular keyword
- `PUT /api/v2/filter_keywords/:id` to edit a particular keyword
- `DELETE /api/v2/filter_keywords/:id` to delete a particular keyword
* Change from `irreversible` boolean to `action` enum
* Remove irrelevent `irreversible_must_be_within_context` check
* Fix /filters/new and /filters/edit with update for filter_action
* Fix Rubocop/Codeclimate complaining about task names
* Refactor FeedManager#phrase_filtered?
This moves regexp building and filter caching to the `CustomFilter` class.
This does not change the functional behavior yet, but this changes how the
cache is built, doing per-custom_filter regexps so that filters can be matched
independently, while still offering caching.
* Perform server-side filtering and output result in REST API
* Fix numerous filters_changed events being sent when editing multiple keywords at once
* Add some tests
* Use the new API in the WebUI
- use client-side logic for filters we have fetched rules for.
This is so that filter changes can be retroactively applied without
reloading the UI.
- use server-side logic for filters we haven't fetched rules for yet
(e.g. network error, or initial timeline loading)
* Minor optimizations and refactoring
* Perform server-side filtering on the streaming server
* Change the wording of filter action labels
* Fix issues pointed out by linter
* Change design of “Show anyway” link in accordence to review comments
* Drop “irreversible” filtering behavior
* Move /api/v2/filter_keywords to /api/v1/filters/keywords
* Rename `filter_results` attribute to `filtered`
* Rename REST::LegacyFilterSerializer to REST::V1::FilterSerializer
* Fix systemChannelId value in streaming server
* Simplify code by removing client-side filtering code
The simplifcation comes at a cost though: filters aren't retroactively
applied anymore.
2022-06-28 07:42:13 +00:00
|
|
|
this.props.onFilter();
|
2023-01-30 00:45:35 +00:00
|
|
|
};
|
Revamp post filtering system (#18058)
* Add model for custom filter keywords
* Use CustomFilterKeyword internally
Does not change the API
* Fix /filters/edit and /filters/new
* Add migration tests
* Remove whole_word column from custom_filters (covered by custom_filter_keywords)
* Redesign /filters
Instead of a list, present a card that displays more information and handles
multiple keywords per filter.
* Redesign /filters/new and /filters/edit to add and remove keywords
This adds a new gem dependency: cocoon, as well as a npm dependency:
cocoon-js-vanilla. Those are used to easily populate and remove form fields
from the user interface when manipulating multiple keyword filters at once.
* Add /api/v2/filters to edit filter with multiple keywords
Entities:
- `Filter`: `id`, `title`, `filter_action` (either `hide` or `warn`), `context`
`keywords`
- `FilterKeyword`: `id`, `keyword`, `whole_word`
API endpoits:
- `GET /api/v2/filters` to list filters (including keywords)
- `POST /api/v2/filters` to create a new filter
`keywords_attributes` can also be passed to create keywords in one request
- `GET /api/v2/filters/:id` to read a particular filter
- `PUT /api/v2/filters/:id` to update a new filter
`keywords_attributes` can also be passed to edit, delete or add keywords in
one request
- `DELETE /api/v2/filters/:id` to delete a particular filter
- `GET /api/v2/filters/:id/keywords` to list keywords for a filter
- `POST /api/v2/filters/:filter_id/keywords/:id` to add a new keyword to a
filter
- `GET /api/v2/filter_keywords/:id` to read a particular keyword
- `PUT /api/v2/filter_keywords/:id` to edit a particular keyword
- `DELETE /api/v2/filter_keywords/:id` to delete a particular keyword
* Change from `irreversible` boolean to `action` enum
* Remove irrelevent `irreversible_must_be_within_context` check
* Fix /filters/new and /filters/edit with update for filter_action
* Fix Rubocop/Codeclimate complaining about task names
* Refactor FeedManager#phrase_filtered?
This moves regexp building and filter caching to the `CustomFilter` class.
This does not change the functional behavior yet, but this changes how the
cache is built, doing per-custom_filter regexps so that filters can be matched
independently, while still offering caching.
* Perform server-side filtering and output result in REST API
* Fix numerous filters_changed events being sent when editing multiple keywords at once
* Add some tests
* Use the new API in the WebUI
- use client-side logic for filters we have fetched rules for.
This is so that filter changes can be retroactively applied without
reloading the UI.
- use server-side logic for filters we haven't fetched rules for yet
(e.g. network error, or initial timeline loading)
* Minor optimizations and refactoring
* Perform server-side filtering on the streaming server
* Change the wording of filter action labels
* Fix issues pointed out by linter
* Change design of “Show anyway” link in accordence to review comments
* Drop “irreversible” filtering behavior
* Move /api/v2/filter_keywords to /api/v1/filters/keywords
* Rename `filter_results` attribute to `filtered`
* Rename REST::LegacyFilterSerializer to REST::V1::FilterSerializer
* Fix systemChannelId value in streaming server
* Simplify code by removing client-side filtering code
The simplifcation comes at a cost though: filters aren't retroactively
applied anymore.
2022-06-28 07:42:13 +00:00
|
|
|
|
2016-09-29 22:00:45 +00:00
|
|
|
render () {
|
2022-02-24 23:34:33 +00:00
|
|
|
const { status, relationship, intl, withDismiss, withCounters, scrollKey } = this.props;
|
2023-01-05 13:03:46 +00:00
|
|
|
const { signedIn, permissions } = this.context.identity;
|
2017-09-02 12:01:44 +00:00
|
|
|
|
|
|
|
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
|
2022-01-16 23:49:55 +00:00
|
|
|
const pinnableStatus = ['public', 'unlisted', 'private'].includes(status.get('visibility'));
|
2021-02-11 00:05:04 +00:00
|
|
|
const mutingConversation = status.get('muted');
|
2019-11-19 20:24:16 +00:00
|
|
|
const account = status.get('account');
|
2021-02-11 00:05:04 +00:00
|
|
|
const writtenByMe = status.getIn(['account', 'id']) === me;
|
2022-11-10 07:49:35 +00:00
|
|
|
const isRemote = status.getIn(['account', 'username']) !== status.getIn(['account', 'acct']);
|
2017-05-10 22:28:10 +00:00
|
|
|
|
2016-10-09 20:19:15 +00:00
|
|
|
let menu = [];
|
2016-09-29 22:00:45 +00:00
|
|
|
|
2016-12-11 21:49:25 +00:00
|
|
|
menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen });
|
2017-09-02 12:01:44 +00:00
|
|
|
|
2022-12-15 14:43:16 +00:00
|
|
|
if (publicStatus && isRemote) {
|
|
|
|
menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: status.get('url') });
|
|
|
|
}
|
2022-11-10 07:49:35 +00:00
|
|
|
|
2022-12-15 14:43:16 +00:00
|
|
|
menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy });
|
|
|
|
|
2023-05-24 09:04:43 +00:00
|
|
|
if (publicStatus && 'share' in navigator) {
|
|
|
|
menu.push({ text: intl.formatMessage(messages.share), action: this.handleShareClick });
|
|
|
|
}
|
|
|
|
|
2023-07-13 13:53:03 +00:00
|
|
|
if (publicStatus && (signedIn || !isRemote)) {
|
2017-09-02 12:01:44 +00:00
|
|
|
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
|
|
|
|
}
|
|
|
|
|
2023-07-08 18:00:52 +00:00
|
|
|
if (signedIn) {
|
Feature conversations muting (#3017)
* Add <ostatus:conversation /> tag to Atom input/output
Only uses ref attribute (not href) because href would be
the alternate link that's always included also.
Creates new conversation for every non-reply status. Carries
over conversation for every reply. Keeps remote URIs verbatim,
generates local URIs on the fly like the rest of them.
* Conversation muting - prevents notifications that reference a conversation
(including replies, favourites, reblogs) from being created. API endpoints
/api/v1/statuses/:id/mute and /api/v1/statuses/:id/unmute
Currently no way to tell when a status/conversation is muted, so the web UI
only has a "disable notifications" button, doesn't work as a toggle
* Display "Dismiss notifications" on all statuses in notifications column, not just own
* Add "muted" as a boolean attribute on statuses JSON
For now always false on contained reblogs, since it's only relevant for
statuses returned from the notifications endpoint, which are not nested
Remove "Disable notifications" from detailed status view, since it's
only relevant in the notifications column
* Up max class length
* Remove pending test for conversation mute
* Add tests, clean up
* Rename to "mute conversation" and "unmute conversation"
* Raise validation error when trying to mute/unmute status without conversation
2017-05-15 01:04:13 +00:00
|
|
|
menu.push(null);
|
|
|
|
|
2023-07-08 18:00:52 +00:00
|
|
|
menu.push({ text: intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark), action: this.handleBookmarkClick });
|
2019-11-19 20:24:16 +00:00
|
|
|
|
2023-07-08 18:00:52 +00:00
|
|
|
if (writtenByMe && pinnableStatus) {
|
|
|
|
menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
|
2019-11-19 20:24:16 +00:00
|
|
|
}
|
|
|
|
|
2023-07-08 18:00:52 +00:00
|
|
|
menu.push(null);
|
2019-11-19 20:24:16 +00:00
|
|
|
|
2023-07-08 18:00:52 +00:00
|
|
|
if (writtenByMe || withDismiss) {
|
|
|
|
menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
|
2022-08-25 02:27:47 +00:00
|
|
|
menu.push(null);
|
|
|
|
}
|
|
|
|
|
2023-07-08 18:00:52 +00:00
|
|
|
if (writtenByMe) {
|
|
|
|
menu.push({ text: intl.formatMessage(messages.edit), action: this.handleEditClick });
|
|
|
|
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick, dangerous: true });
|
|
|
|
menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick, dangerous: true });
|
|
|
|
} else {
|
|
|
|
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.handleMentionClick });
|
|
|
|
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.handleDirectClick });
|
2019-11-19 20:24:16 +00:00
|
|
|
menu.push(null);
|
|
|
|
|
2023-07-08 18:00:52 +00:00
|
|
|
if (relationship && relationship.get('muting')) {
|
|
|
|
menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.handleMuteClick });
|
2019-11-19 20:24:16 +00:00
|
|
|
} else {
|
2023-07-08 18:00:52 +00:00
|
|
|
menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.handleMuteClick, dangerous: true });
|
2019-11-19 20:24:16 +00:00
|
|
|
}
|
2019-02-11 03:19:49 +00:00
|
|
|
|
2023-07-08 18:00:52 +00:00
|
|
|
if (relationship && relationship.get('blocking')) {
|
|
|
|
menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.handleBlockClick });
|
|
|
|
} else {
|
|
|
|
menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.handleBlockClick, dangerous: true });
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this.props.onFilter) {
|
|
|
|
menu.push(null);
|
|
|
|
menu.push({ text: intl.formatMessage(messages.filter), action: this.handleFilterClick, dangerous: true });
|
|
|
|
menu.push(null);
|
2023-01-05 13:03:46 +00:00
|
|
|
}
|
2023-07-08 18:00:52 +00:00
|
|
|
|
|
|
|
menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.handleReport, dangerous: true });
|
|
|
|
|
|
|
|
if (account.get('acct') !== account.get('username')) {
|
2023-01-05 13:03:46 +00:00
|
|
|
const domain = account.get('acct').split('@')[1];
|
2023-07-08 18:00:52 +00:00
|
|
|
|
|
|
|
menu.push(null);
|
|
|
|
|
|
|
|
if (relationship && relationship.get('domain_blocking')) {
|
|
|
|
menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.handleUnblockDomain });
|
|
|
|
} else {
|
|
|
|
menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.handleBlockDomain, dangerous: true });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS || (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION)) {
|
|
|
|
menu.push(null);
|
|
|
|
if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) {
|
|
|
|
menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` });
|
|
|
|
menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` });
|
|
|
|
}
|
|
|
|
if (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION) {
|
|
|
|
const domain = account.get('acct').split('@')[1];
|
|
|
|
menu.push({ text: intl.formatMessage(messages.admin_domain, { domain: domain }), href: `/admin/instances/${domain}` });
|
|
|
|
}
|
2023-01-05 13:03:46 +00:00
|
|
|
}
|
2019-01-04 12:10:43 +00:00
|
|
|
}
|
2016-09-29 22:00:45 +00:00
|
|
|
}
|
|
|
|
|
2020-06-25 20:44:19 +00:00
|
|
|
let replyIcon;
|
2023-10-24 17:45:08 +00:00
|
|
|
let replyIconComponent;
|
2020-06-25 20:44:19 +00:00
|
|
|
let replyTitle;
|
2023-11-13 14:39:45 +00:00
|
|
|
|
2017-04-19 12:20:04 +00:00
|
|
|
if (status.get('in_reply_to_id', null) === null) {
|
2018-12-28 02:51:32 +00:00
|
|
|
replyIcon = 'reply';
|
2023-10-24 17:45:08 +00:00
|
|
|
replyIconComponent = ReplyIcon;
|
2017-05-10 22:28:10 +00:00
|
|
|
replyTitle = intl.formatMessage(messages.reply);
|
2017-04-19 12:20:04 +00:00
|
|
|
} else {
|
2018-12-28 02:51:32 +00:00
|
|
|
replyIcon = 'reply-all';
|
2023-10-24 17:45:08 +00:00
|
|
|
replyIconComponent = ReplyAllIcon;
|
2017-05-10 22:28:10 +00:00
|
|
|
replyTitle = intl.formatMessage(messages.replyAll);
|
2017-04-19 12:20:04 +00:00
|
|
|
}
|
2017-04-15 00:57:26 +00:00
|
|
|
|
2020-06-25 20:44:19 +00:00
|
|
|
const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility') === 'private';
|
|
|
|
|
2023-11-13 14:39:45 +00:00
|
|
|
let reblogTitle, reblogIconComponent;
|
|
|
|
|
2020-06-25 20:44:19 +00:00
|
|
|
if (status.get('reblogged')) {
|
|
|
|
reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
|
2023-11-13 14:39:45 +00:00
|
|
|
reblogIconComponent = publicStatus ? RepeatIcon : RepeatPrivateIcon;
|
2020-06-25 20:44:19 +00:00
|
|
|
} else if (publicStatus) {
|
|
|
|
reblogTitle = intl.formatMessage(messages.reblog);
|
2023-11-13 14:39:45 +00:00
|
|
|
reblogIconComponent = RepeatIcon;
|
2020-06-25 20:44:19 +00:00
|
|
|
} else if (reblogPrivate) {
|
|
|
|
reblogTitle = intl.formatMessage(messages.reblog_private);
|
2023-11-13 14:39:45 +00:00
|
|
|
reblogIconComponent = RepeatPrivateIcon;
|
2020-06-25 20:44:19 +00:00
|
|
|
} else {
|
|
|
|
reblogTitle = intl.formatMessage(messages.cannot_reblog);
|
2023-11-13 14:39:45 +00:00
|
|
|
reblogIconComponent = RepeatDisabledIcon;
|
2020-06-25 20:44:19 +00:00
|
|
|
}
|
|
|
|
|
Revamp post filtering system (#18058)
* Add model for custom filter keywords
* Use CustomFilterKeyword internally
Does not change the API
* Fix /filters/edit and /filters/new
* Add migration tests
* Remove whole_word column from custom_filters (covered by custom_filter_keywords)
* Redesign /filters
Instead of a list, present a card that displays more information and handles
multiple keywords per filter.
* Redesign /filters/new and /filters/edit to add and remove keywords
This adds a new gem dependency: cocoon, as well as a npm dependency:
cocoon-js-vanilla. Those are used to easily populate and remove form fields
from the user interface when manipulating multiple keyword filters at once.
* Add /api/v2/filters to edit filter with multiple keywords
Entities:
- `Filter`: `id`, `title`, `filter_action` (either `hide` or `warn`), `context`
`keywords`
- `FilterKeyword`: `id`, `keyword`, `whole_word`
API endpoits:
- `GET /api/v2/filters` to list filters (including keywords)
- `POST /api/v2/filters` to create a new filter
`keywords_attributes` can also be passed to create keywords in one request
- `GET /api/v2/filters/:id` to read a particular filter
- `PUT /api/v2/filters/:id` to update a new filter
`keywords_attributes` can also be passed to edit, delete or add keywords in
one request
- `DELETE /api/v2/filters/:id` to delete a particular filter
- `GET /api/v2/filters/:id/keywords` to list keywords for a filter
- `POST /api/v2/filters/:filter_id/keywords/:id` to add a new keyword to a
filter
- `GET /api/v2/filter_keywords/:id` to read a particular keyword
- `PUT /api/v2/filter_keywords/:id` to edit a particular keyword
- `DELETE /api/v2/filter_keywords/:id` to delete a particular keyword
* Change from `irreversible` boolean to `action` enum
* Remove irrelevent `irreversible_must_be_within_context` check
* Fix /filters/new and /filters/edit with update for filter_action
* Fix Rubocop/Codeclimate complaining about task names
* Refactor FeedManager#phrase_filtered?
This moves regexp building and filter caching to the `CustomFilter` class.
This does not change the functional behavior yet, but this changes how the
cache is built, doing per-custom_filter regexps so that filters can be matched
independently, while still offering caching.
* Perform server-side filtering and output result in REST API
* Fix numerous filters_changed events being sent when editing multiple keywords at once
* Add some tests
* Use the new API in the WebUI
- use client-side logic for filters we have fetched rules for.
This is so that filter changes can be retroactively applied without
reloading the UI.
- use server-side logic for filters we haven't fetched rules for yet
(e.g. network error, or initial timeline loading)
* Minor optimizations and refactoring
* Perform server-side filtering on the streaming server
* Change the wording of filter action labels
* Fix issues pointed out by linter
* Change design of “Show anyway” link in accordence to review comments
* Drop “irreversible” filtering behavior
* Move /api/v2/filter_keywords to /api/v1/filters/keywords
* Rename `filter_results` attribute to `filtered`
* Rename REST::LegacyFilterSerializer to REST::V1::FilterSerializer
* Fix systemChannelId value in streaming server
* Simplify code by removing client-side filtering code
The simplifcation comes at a cost though: filters aren't retroactively
applied anymore.
2022-06-28 07:42:13 +00:00
|
|
|
const filterButton = this.props.onFilter && (
|
2023-10-24 17:45:08 +00:00
|
|
|
<IconButton className='status__action-bar__button' title={intl.formatMessage(messages.hide)} icon='eye' iconComponent={VisibilityIcon} onClick={this.handleHideClick} />
|
Revamp post filtering system (#18058)
* Add model for custom filter keywords
* Use CustomFilterKeyword internally
Does not change the API
* Fix /filters/edit and /filters/new
* Add migration tests
* Remove whole_word column from custom_filters (covered by custom_filter_keywords)
* Redesign /filters
Instead of a list, present a card that displays more information and handles
multiple keywords per filter.
* Redesign /filters/new and /filters/edit to add and remove keywords
This adds a new gem dependency: cocoon, as well as a npm dependency:
cocoon-js-vanilla. Those are used to easily populate and remove form fields
from the user interface when manipulating multiple keyword filters at once.
* Add /api/v2/filters to edit filter with multiple keywords
Entities:
- `Filter`: `id`, `title`, `filter_action` (either `hide` or `warn`), `context`
`keywords`
- `FilterKeyword`: `id`, `keyword`, `whole_word`
API endpoits:
- `GET /api/v2/filters` to list filters (including keywords)
- `POST /api/v2/filters` to create a new filter
`keywords_attributes` can also be passed to create keywords in one request
- `GET /api/v2/filters/:id` to read a particular filter
- `PUT /api/v2/filters/:id` to update a new filter
`keywords_attributes` can also be passed to edit, delete or add keywords in
one request
- `DELETE /api/v2/filters/:id` to delete a particular filter
- `GET /api/v2/filters/:id/keywords` to list keywords for a filter
- `POST /api/v2/filters/:filter_id/keywords/:id` to add a new keyword to a
filter
- `GET /api/v2/filter_keywords/:id` to read a particular keyword
- `PUT /api/v2/filter_keywords/:id` to edit a particular keyword
- `DELETE /api/v2/filter_keywords/:id` to delete a particular keyword
* Change from `irreversible` boolean to `action` enum
* Remove irrelevent `irreversible_must_be_within_context` check
* Fix /filters/new and /filters/edit with update for filter_action
* Fix Rubocop/Codeclimate complaining about task names
* Refactor FeedManager#phrase_filtered?
This moves regexp building and filter caching to the `CustomFilter` class.
This does not change the functional behavior yet, but this changes how the
cache is built, doing per-custom_filter regexps so that filters can be matched
independently, while still offering caching.
* Perform server-side filtering and output result in REST API
* Fix numerous filters_changed events being sent when editing multiple keywords at once
* Add some tests
* Use the new API in the WebUI
- use client-side logic for filters we have fetched rules for.
This is so that filter changes can be retroactively applied without
reloading the UI.
- use server-side logic for filters we haven't fetched rules for yet
(e.g. network error, or initial timeline loading)
* Minor optimizations and refactoring
* Perform server-side filtering on the streaming server
* Change the wording of filter action labels
* Fix issues pointed out by linter
* Change design of “Show anyway” link in accordence to review comments
* Drop “irreversible” filtering behavior
* Move /api/v2/filter_keywords to /api/v1/filters/keywords
* Rename `filter_results` attribute to `filtered`
* Rename REST::LegacyFilterSerializer to REST::V1::FilterSerializer
* Fix systemChannelId value in streaming server
* Simplify code by removing client-side filtering code
The simplifcation comes at a cost though: filters aren't retroactively
applied anymore.
2022-06-28 07:42:13 +00:00
|
|
|
);
|
|
|
|
|
2023-10-24 17:45:08 +00:00
|
|
|
const isReply = status.get('in_reply_to_account_id') === status.getIn(['account', 'id']);
|
|
|
|
|
2016-09-29 22:00:45 +00:00
|
|
|
return (
|
2017-04-23 02:26:55 +00:00
|
|
|
<div className='status__action-bar'>
|
2023-10-24 17:45:08 +00:00
|
|
|
<IconButton className='status__action-bar__button' title={replyTitle} icon={isReply ? 'reply' : replyIcon} iconComponent={isReply ? ReplyIcon : replyIconComponent} onClick={this.handleReplyClick} counter={status.get('replies_count')} />
|
2023-11-13 14:39:45 +00:00
|
|
|
<IconButton className={classNames('status__action-bar__button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' iconComponent={reblogIconComponent} onClick={this.handleReblogClick} counter={withCounters ? status.get('reblogs_count') : undefined} />
|
2023-10-24 17:45:08 +00:00
|
|
|
<IconButton className='status__action-bar__button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' iconComponent={status.get('favourited') ? StarIcon : StarBorderIcon} onClick={this.handleFavouriteClick} counter={withCounters ? status.get('favourites_count') : undefined} />
|
|
|
|
<IconButton className='status__action-bar__button bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' iconComponent={status.get('bookmarked') ? BookmarkIcon : BookmarkBorderIcon} onClick={this.handleBookmarkClick} />
|
2020-09-28 11:29:43 +00:00
|
|
|
|
Revamp post filtering system (#18058)
* Add model for custom filter keywords
* Use CustomFilterKeyword internally
Does not change the API
* Fix /filters/edit and /filters/new
* Add migration tests
* Remove whole_word column from custom_filters (covered by custom_filter_keywords)
* Redesign /filters
Instead of a list, present a card that displays more information and handles
multiple keywords per filter.
* Redesign /filters/new and /filters/edit to add and remove keywords
This adds a new gem dependency: cocoon, as well as a npm dependency:
cocoon-js-vanilla. Those are used to easily populate and remove form fields
from the user interface when manipulating multiple keyword filters at once.
* Add /api/v2/filters to edit filter with multiple keywords
Entities:
- `Filter`: `id`, `title`, `filter_action` (either `hide` or `warn`), `context`
`keywords`
- `FilterKeyword`: `id`, `keyword`, `whole_word`
API endpoits:
- `GET /api/v2/filters` to list filters (including keywords)
- `POST /api/v2/filters` to create a new filter
`keywords_attributes` can also be passed to create keywords in one request
- `GET /api/v2/filters/:id` to read a particular filter
- `PUT /api/v2/filters/:id` to update a new filter
`keywords_attributes` can also be passed to edit, delete or add keywords in
one request
- `DELETE /api/v2/filters/:id` to delete a particular filter
- `GET /api/v2/filters/:id/keywords` to list keywords for a filter
- `POST /api/v2/filters/:filter_id/keywords/:id` to add a new keyword to a
filter
- `GET /api/v2/filter_keywords/:id` to read a particular keyword
- `PUT /api/v2/filter_keywords/:id` to edit a particular keyword
- `DELETE /api/v2/filter_keywords/:id` to delete a particular keyword
* Change from `irreversible` boolean to `action` enum
* Remove irrelevent `irreversible_must_be_within_context` check
* Fix /filters/new and /filters/edit with update for filter_action
* Fix Rubocop/Codeclimate complaining about task names
* Refactor FeedManager#phrase_filtered?
This moves regexp building and filter caching to the `CustomFilter` class.
This does not change the functional behavior yet, but this changes how the
cache is built, doing per-custom_filter regexps so that filters can be matched
independently, while still offering caching.
* Perform server-side filtering and output result in REST API
* Fix numerous filters_changed events being sent when editing multiple keywords at once
* Add some tests
* Use the new API in the WebUI
- use client-side logic for filters we have fetched rules for.
This is so that filter changes can be retroactively applied without
reloading the UI.
- use server-side logic for filters we haven't fetched rules for yet
(e.g. network error, or initial timeline loading)
* Minor optimizations and refactoring
* Perform server-side filtering on the streaming server
* Change the wording of filter action labels
* Fix issues pointed out by linter
* Change design of “Show anyway” link in accordence to review comments
* Drop “irreversible” filtering behavior
* Move /api/v2/filter_keywords to /api/v1/filters/keywords
* Rename `filter_results` attribute to `filtered`
* Rename REST::LegacyFilterSerializer to REST::V1::FilterSerializer
* Fix systemChannelId value in streaming server
* Simplify code by removing client-side filtering code
The simplifcation comes at a cost though: filters aren't retroactively
applied anymore.
2022-06-28 07:42:13 +00:00
|
|
|
{filterButton}
|
|
|
|
|
2023-10-24 17:45:08 +00:00
|
|
|
<DropdownMenuContainer
|
|
|
|
scrollKey={scrollKey}
|
|
|
|
status={status}
|
|
|
|
items={menu}
|
|
|
|
icon='ellipsis-h'
|
|
|
|
iconComponent={MoreHorizIcon}
|
|
|
|
direction='right'
|
|
|
|
title={intl.formatMessage(messages.more)}
|
|
|
|
/>
|
2016-09-29 22:00:45 +00:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2017-04-21 18:05:35 +00:00
|
|
|
}
|
2023-03-24 02:17:53 +00:00
|
|
|
|
2023-10-19 17:44:55 +00:00
|
|
|
export default withRouter(connect(mapStateToProps)(injectIntl(StatusActionBar)));
|