Use new PR 2462 for reactions
This commit is contained in:
commit
10ad23fdb4
@ -70,7 +70,6 @@ export const defaultMediaVisibility = (status, settings) => {
|
||||
class Status extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
identity: PropTypes.object,
|
||||
};
|
||||
|
||||
@ -788,6 +787,9 @@ class Status extends ImmutablePureComponent {
|
||||
muted,
|
||||
}, 'focusable');
|
||||
|
||||
const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status);
|
||||
contentMedia.push(hashtagBar);
|
||||
|
||||
return (
|
||||
<HotKeys handlers={handlers}>
|
||||
<div
|
||||
@ -837,6 +839,16 @@ class Status extends ImmutablePureComponent {
|
||||
disabled={!history}
|
||||
tagLinks={settings.get('tag_misleading_links')}
|
||||
rewriteMentions={settings.get('rewrite_mentions')}
|
||||
{...statusContentProps}
|
||||
/>
|
||||
|
||||
<StatusReactions
|
||||
statusId={status.get('id')}
|
||||
reactions={status.get('reactions')}
|
||||
numVisible={visibleReactions}
|
||||
addReaction={this.props.onReactionAdd}
|
||||
removeReaction={this.props.onReactionRemove}
|
||||
canReact={this.context.identity.signedIn}
|
||||
/>
|
||||
|
||||
<StatusReactions
|
||||
|
@ -9,6 +9,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container';
|
||||
import EmojiPickerDropdown from 'flavours/glitch/features/compose/containers/emoji_picker_dropdown_container';
|
||||
import { me, maxReactions } from 'flavours/glitch/initial_state';
|
||||
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions';
|
||||
import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links';
|
||||
@ -341,7 +342,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
? <EmojiPickerDropdown className='status__action-bar-button' onPickEmoji={this.handleEmojiPick} button={reactButton} disabled={!canReact} />
|
||||
: reactButton
|
||||
}
|
||||
<IconButton className='status__action-bar-button bookmark-icon' disabled={anonymousAccess} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} />
|
||||
<IconButton className='status__action-bar-button bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} />
|
||||
|
||||
{filterButton}
|
||||
|
||||
|
@ -1,15 +1,20 @@
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { autoPlayGif, reduceMotion } from '../initial_state';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import TransitionMotion from 'react-motion/lib/TransitionMotion';
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
import TransitionMotion from 'react-motion/lib/TransitionMotion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
|
||||
import { unicodeMapping } from '../features/emoji/emoji_unicode_mapping_light';
|
||||
import { AnimatedNumber } from './animated_number';
|
||||
import { autoPlayGif, reduceMotion } from '../initial_state';
|
||||
import { assetHost } from '../utils/config';
|
||||
|
||||
import { AnimatedNumber } from './animated_number';
|
||||
|
||||
export default class StatusReactions extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -10,6 +10,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
|
||||
import { IconButton } from 'flavours/glitch/components/icon_button';
|
||||
import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container';
|
||||
import EmojiPickerDropdown from 'flavours/glitch/features/compose/containers/emoji_picker_dropdown_container';
|
||||
import { me, maxReactions } from 'flavours/glitch/initial_state';
|
||||
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions';
|
||||
import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links';
|
||||
@ -88,7 +89,7 @@ class ActionBar extends PureComponent {
|
||||
|
||||
handleEmojiPick = data => {
|
||||
this.props.onReactionAdd(this.props.status.get('id'), data.native.replace(/:/g, ''), data.imageUrl);
|
||||
}
|
||||
};
|
||||
|
||||
handleBookmarkClick = (e) => {
|
||||
this.props.onBookmark(this.props.status, e);
|
||||
@ -149,7 +150,7 @@ class ActionBar extends PureComponent {
|
||||
navigator.clipboard.writeText(url);
|
||||
};
|
||||
|
||||
handleNoOp = () => {} // hack for reaction add button
|
||||
handleNoOp = () => {}; // hack for reaction add button
|
||||
|
||||
render () {
|
||||
const { status, intl } = this.props;
|
||||
|
@ -13,6 +13,7 @@ import AttachmentList from 'flavours/glitch/components/attachment_list';
|
||||
import { Avatar } from 'flavours/glitch/components/avatar';
|
||||
import { DisplayName } from 'flavours/glitch/components/display_name';
|
||||
import EditedTimestamp from 'flavours/glitch/components/edited_timestamp';
|
||||
import { getHashtagBarForStatus } from 'flavours/glitch/components/hashtag_bar';
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
import MediaGallery from 'flavours/glitch/components/media_gallery';
|
||||
import PictureInPicturePlaceholder from 'flavours/glitch/components/picture_in_picture_placeholder';
|
||||
@ -31,7 +32,6 @@ import Card from './card';
|
||||
class DetailedStatus extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
identity: PropTypes.object,
|
||||
};
|
||||
|
||||
@ -311,6 +311,9 @@ class DetailedStatus extends ImmutablePureComponent {
|
||||
);
|
||||
}
|
||||
|
||||
const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status);
|
||||
contentMedia.push(hashtagBar);
|
||||
|
||||
return (
|
||||
<div style={outerStyle}>
|
||||
<div ref={this.setRef} className={classNames('detailed-status', `detailed-status-${status.get('visibility')}`, { compact })} data-status-by={status.getIn(['account', 'acct'])}>
|
||||
@ -333,6 +336,15 @@ class DetailedStatus extends ImmutablePureComponent {
|
||||
tagLinks={settings.get('tag_misleading_links')}
|
||||
rewriteMentions={settings.get('rewrite_mentions')}
|
||||
disabled
|
||||
{...statusContentProps}
|
||||
/>
|
||||
|
||||
<StatusReactions
|
||||
statusId={status.get('id')}
|
||||
reactions={status.get('reactions')}
|
||||
addReaction={this.props.onReactionAdd}
|
||||
removeReaction={this.props.onReactionRemove}
|
||||
canReact={this.context.identity.signedIn}
|
||||
/>
|
||||
|
||||
<StatusReactions
|
||||
|
@ -62,6 +62,7 @@
|
||||
* @property {boolean} limited_federation_mode
|
||||
* @property {string} locale
|
||||
* @property {string | null} mascot
|
||||
* @property {number} max_reactions
|
||||
* @property {string=} me
|
||||
* @property {string=} moved_to_account_id
|
||||
* @property {string=} owner
|
||||
|
@ -191,6 +191,7 @@
|
||||
"status.react": "React",
|
||||
"status.sensitive_toggle": "Click to view",
|
||||
"status.uncollapse": "Uncollapse",
|
||||
"tooltips.reactions": "Reactions",
|
||||
"web_app_crash.change_your_settings": "Change your {settings}",
|
||||
"web_app_crash.content": "You could try any of the following:",
|
||||
"web_app_crash.debug_info": "Debug information",
|
||||
|
@ -62,6 +62,7 @@ const initialState = ImmutableMap({
|
||||
follow: true,
|
||||
follow_request: false,
|
||||
favourite: true,
|
||||
reaction: true,
|
||||
reblog: true,
|
||||
reaction: true,
|
||||
mention: true,
|
||||
@ -76,6 +77,7 @@ const initialState = ImmutableMap({
|
||||
follow: true,
|
||||
follow_request: false,
|
||||
favourite: true,
|
||||
reaction: true,
|
||||
reblog: true,
|
||||
reaction: true,
|
||||
mention: true,
|
||||
|
@ -18,6 +18,11 @@ import {
|
||||
REACTION_REMOVE_REQUEST,
|
||||
UNBOOKMARK_REQUEST,
|
||||
UNBOOKMARK_FAIL,
|
||||
REACTION_UPDATE,
|
||||
REACTION_ADD_FAIL,
|
||||
REACTION_REMOVE_FAIL,
|
||||
REACTION_ADD_REQUEST,
|
||||
REACTION_REMOVE_REQUEST,
|
||||
} from 'flavours/glitch/actions/interactions';
|
||||
import {
|
||||
STATUS_MUTE_SUCCESS,
|
||||
@ -50,6 +55,43 @@ const deleteStatus = (state, id, references) => {
|
||||
return state.delete(id);
|
||||
};
|
||||
|
||||
const updateReaction = (state, id, name, updater) => state.update(
|
||||
id,
|
||||
status => status.update(
|
||||
'reactions',
|
||||
reactions => {
|
||||
const index = reactions.findIndex(reaction => reaction.get('name') === name);
|
||||
if (index > -1) {
|
||||
return reactions.update(index, reaction => updater(reaction));
|
||||
} else {
|
||||
return reactions.push(updater(fromJS({ name, count: 0 })));
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const updateReactionCount = (state, reaction) => updateReaction(state, reaction.status_id, reaction.name, x => x.set('count', reaction.count));
|
||||
|
||||
// The url parameter is only used when adding a new custom emoji reaction
|
||||
// (one that wasn't in the reactions list before) because we don't have its
|
||||
// URL yet. In all other cases, it's undefined.
|
||||
const addReaction = (state, id, name, url) => updateReaction(
|
||||
state,
|
||||
id,
|
||||
name,
|
||||
x => x.set('me', true)
|
||||
.update('count', n => n + 1)
|
||||
.update('url', old => old ? old : url)
|
||||
.update('static_url', old => old ? old : url),
|
||||
);
|
||||
|
||||
const removeReaction = (state, id, name) => updateReaction(
|
||||
state,
|
||||
id,
|
||||
name,
|
||||
x => x.set('me', false).update('count', n => n - 1),
|
||||
);
|
||||
|
||||
const statusTranslateSuccess = (state, id, translation) => {
|
||||
return state.withMutations(map => {
|
||||
map.setIn([id, 'translation'], fromJS(normalizeStatusTranslation(translation, map.get(id))));
|
||||
|
BIN
app/javascript/images/mailer/icon_add.png
Normal file
BIN
app/javascript/images/mailer/icon_add.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
@ -32,6 +32,7 @@ import { RelativeTimestamp } from './relative_timestamp';
|
||||
import StatusActionBar from './status_action_bar';
|
||||
import StatusReactions from './status_reactions';
|
||||
import StatusContent from './status_content';
|
||||
import StatusReactions from './status_reactions';
|
||||
import { VisibilityIcon } from './visibility_icon';
|
||||
|
||||
const domParser = new DOMParser();
|
||||
@ -80,7 +81,6 @@ const messages = defineMessages({
|
||||
class Status extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
identity: PropTypes.object,
|
||||
};
|
||||
|
||||
|
@ -9,6 +9,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { ReactComponent as AddIcon } from '@material-symbols/svg-600/outlined/add.svg';
|
||||
import { ReactComponent as BookmarkIcon } from '@material-symbols/svg-600/outlined/bookmark-fill.svg';
|
||||
import { ReactComponent as BookmarkBorderIcon } from '@material-symbols/svg-600/outlined/bookmark.svg';
|
||||
import { ReactComponent as MoreHorizIcon } from '@material-symbols/svg-600/outlined/more_horiz.svg';
|
||||
@ -24,6 +25,7 @@ import EmojiPickerDropdown from '../features/compose/containers/emoji_picker_dro
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
||||
import DropdownMenuContainer from '../containers/dropdown_menu_container';
|
||||
import EmojiPickerDropdown from '../features/compose/containers/emoji_picker_dropdown_container';
|
||||
import { me, maxReactions } from '../initial_state';
|
||||
|
||||
import { IconButton } from './icon_button';
|
||||
@ -146,8 +148,8 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
};
|
||||
|
||||
handleEmojiPick = data => {
|
||||
this.props.onReactionAdd(this.props.status.get('id'), data.native.replace(/:/g, ''), data.imageUrl);
|
||||
}
|
||||
this.props.onReactionAdd(this.props.status.get('id'), data.native.replace(/:/g, ''), data.imageUrl);
|
||||
};
|
||||
|
||||
handleReblogClick = e => {
|
||||
const { signedIn } = this.context.identity;
|
||||
@ -252,7 +254,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
this.props.onFilter();
|
||||
};
|
||||
|
||||
handleNoOp = () => {} // hack for reaction add button
|
||||
handleNoOp = () => {}; // hack for reaction add button
|
||||
|
||||
render () {
|
||||
const { status, relationship, intl, withDismiss, withCounters, scrollKey } = this.props;
|
||||
@ -396,6 +398,17 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
);
|
||||
|
||||
const isReply = status.get('in_reply_to_account_id') === status.getIn(['account', 'id']);
|
||||
const canReact = signedIn && status.get('reactions').filter(r => r.get('count') > 0 && r.get('me')).size < maxReactions;
|
||||
const reactButton = (
|
||||
<IconButton
|
||||
className='status__action-bar-button'
|
||||
onClick={this.handleNoOp} // EmojiPickerDropdown handles that
|
||||
title={intl.formatMessage(messages.react)}
|
||||
disabled={!canReact}
|
||||
icon='plus'
|
||||
iconComponent={AddIcon}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='status__action-bar'>
|
||||
|
@ -1,3 +1,4 @@
|
||||
<<<<<<< HEAD
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
@ -10,6 +11,25 @@ import { unicodeMapping } from '../features/emoji/emoji_unicode_mapping_light';
|
||||
import { AnimatedNumber } from './animated_number';
|
||||
import { assetHost } from '../utils/config';
|
||||
|
||||
=======
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
import TransitionMotion from 'react-motion/lib/TransitionMotion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
|
||||
import { unicodeMapping } from '../features/emoji/emoji_unicode_mapping_light';
|
||||
import { autoPlayGif, reduceMotion } from '../initial_state';
|
||||
import { assetHost } from '../utils/config';
|
||||
|
||||
import { AnimatedNumber } from './animated_number';
|
||||
|
||||
>>>>>>> pr2462
|
||||
export default class StatusReactions extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -9,6 +9,7 @@ import { withRouter } from 'react-router-dom';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { ReactComponent as AddIcon } from '@material-symbols/svg-600/outlined/add.svg';
|
||||
import { ReactComponent as BookmarkIcon } from '@material-symbols/svg-600/outlined/bookmark-fill.svg';
|
||||
import { ReactComponent as BookmarkBorderIcon } from '@material-symbols/svg-600/outlined/bookmark.svg';
|
||||
import { ReactComponent as MoreHorizIcon } from '@material-symbols/svg-600/outlined/more_horiz.svg';
|
||||
@ -23,8 +24,13 @@ import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
||||
import { IconButton } from '../../../components/icon_button';
|
||||
import DropdownMenuContainer from '../../../containers/dropdown_menu_container';
|
||||
<<<<<<< HEAD
|
||||
import EmojiPickerDropdown from '../../compose/containers/emoji_picker_dropdown_container';
|
||||
import { me, maxReactions } from '../../../initial_state';
|
||||
=======
|
||||
import { me, maxReactions } from '../../../initial_state';
|
||||
import EmojiPickerDropdown from '../../compose/containers/emoji_picker_dropdown_container';
|
||||
>>>>>>> pr2462
|
||||
|
||||
const messages = defineMessages({
|
||||
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
||||
@ -111,7 +117,11 @@ class ActionBar extends PureComponent {
|
||||
|
||||
handleEmojiPick = data => {
|
||||
this.props.onReactionAdd(this.props.status.get('id'), data.native.replace(/:/g, ''));
|
||||
<<<<<<< HEAD
|
||||
}
|
||||
=======
|
||||
};
|
||||
>>>>>>> pr2462
|
||||
|
||||
handleBookmarkClick = (e) => {
|
||||
this.props.onBookmark(this.props.status, e);
|
||||
@ -200,7 +210,11 @@ class ActionBar extends PureComponent {
|
||||
navigator.clipboard.writeText(url);
|
||||
};
|
||||
|
||||
<<<<<<< HEAD
|
||||
handleNoOp = () => {} // hack for reaction add button
|
||||
=======
|
||||
handleNoOp = () => {}; // hack for reaction add button
|
||||
>>>>>>> pr2462
|
||||
|
||||
render () {
|
||||
const { status, relationship, intl } = this.props;
|
||||
@ -295,6 +309,10 @@ class ActionBar extends PureComponent {
|
||||
title={intl.formatMessage(messages.react)}
|
||||
disabled={!canReact}
|
||||
icon='plus'
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
iconComponent={AddIcon}
|
||||
>>>>>>> pr2462
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -34,7 +34,6 @@ import Card from './card';
|
||||
class DetailedStatus extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
identity: PropTypes.object,
|
||||
};
|
||||
|
||||
@ -326,6 +325,14 @@ class DetailedStatus extends ImmutablePureComponent {
|
||||
|
||||
{expanded && hashtagBar}
|
||||
|
||||
<StatusReactions
|
||||
statusId={status.get('id')}
|
||||
reactions={status.get('reactions')}
|
||||
addReaction={this.props.onReactionAdd}
|
||||
removeReaction={this.props.onReactionRemove}
|
||||
canReact={this.context.identity.signedIn}
|
||||
/>
|
||||
|
||||
<div className='detailed-status__meta'>
|
||||
<a className='detailed-status__datetime' href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} target='_blank' rel='noopener noreferrer'>
|
||||
<FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' />
|
||||
|
@ -431,6 +431,7 @@
|
||||
"notification.mention": "{name} mentioned you",
|
||||
"notification.own_poll": "Your poll has ended",
|
||||
"notification.poll": "A poll you have voted in has ended",
|
||||
"notification.reaction": "{name} reacted to your post",
|
||||
"notification.reblog": "{name} boosted your post",
|
||||
"notification.status": "{name} just posted",
|
||||
"notification.update": "{name} edited a post",
|
||||
@ -449,6 +450,7 @@
|
||||
"notifications.column_settings.mention": "Mentions:",
|
||||
"notifications.column_settings.poll": "Poll results:",
|
||||
"notifications.column_settings.push": "Push notifications",
|
||||
"notifications.column_settings.reaction": "Reactions:",
|
||||
"notifications.column_settings.reblog": "Boosts:",
|
||||
"notifications.column_settings.show": "Show in column",
|
||||
"notifications.column_settings.sound": "Play sound",
|
||||
@ -651,6 +653,7 @@
|
||||
"status.pin": "Pin on profile",
|
||||
"status.pinned": "Pinned post",
|
||||
"status.read_more": "Read more",
|
||||
"status.react": "React",
|
||||
"status.reblog": "Boost",
|
||||
"status.reblog_private": "Boost with original visibility",
|
||||
"status.reblogged_by": "{name} boosted",
|
||||
@ -689,6 +692,7 @@
|
||||
"timeline_hint.resources.followers": "Followers",
|
||||
"timeline_hint.resources.follows": "Follows",
|
||||
"timeline_hint.resources.statuses": "Older posts",
|
||||
"tooltips.reactions": "Reactions",
|
||||
"trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {{days} days}}",
|
||||
"trends.trending_now": "Trending now",
|
||||
"tooltips.reactions": "Reactions",
|
||||
|
@ -57,6 +57,7 @@ const initialState = ImmutableMap({
|
||||
follow: true,
|
||||
follow_request: false,
|
||||
favourite: true,
|
||||
reaction: true,
|
||||
reblog: true,
|
||||
reaction: true,
|
||||
mention: true,
|
||||
@ -71,6 +72,7 @@ const initialState = ImmutableMap({
|
||||
follow: true,
|
||||
follow_request: false,
|
||||
favourite: true,
|
||||
reaction: true,
|
||||
reblog: true,
|
||||
reaction: true,
|
||||
mention: true,
|
||||
|
@ -20,6 +20,11 @@ import {
|
||||
REACTION_REMOVE_REQUEST,
|
||||
UNBOOKMARK_REQUEST,
|
||||
UNBOOKMARK_FAIL,
|
||||
REACTION_UPDATE,
|
||||
REACTION_ADD_FAIL,
|
||||
REACTION_REMOVE_FAIL,
|
||||
REACTION_ADD_REQUEST,
|
||||
REACTION_REMOVE_REQUEST,
|
||||
} from '../actions/interactions';
|
||||
import {
|
||||
STATUS_MUTE_SUCCESS,
|
||||
@ -47,6 +52,43 @@ const deleteStatus = (state, id, references) => {
|
||||
return state.delete(id);
|
||||
};
|
||||
|
||||
const updateReaction = (state, id, name, updater) => state.update(
|
||||
id,
|
||||
status => status.update(
|
||||
'reactions',
|
||||
reactions => {
|
||||
const index = reactions.findIndex(reaction => reaction.get('name') === name);
|
||||
if (index > -1) {
|
||||
return reactions.update(index, reaction => updater(reaction));
|
||||
} else {
|
||||
return reactions.push(updater(fromJS({ name, count: 0 })));
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const updateReactionCount = (state, reaction) => updateReaction(state, reaction.status_id, reaction.name, x => x.set('count', reaction.count));
|
||||
|
||||
// The url parameter is only used when adding a new custom emoji reaction
|
||||
// (one that wasn't in the reactions list before) because we don't have its
|
||||
// URL yet. In all other cases, it's undefined.
|
||||
const addReaction = (state, id, name, url) => updateReaction(
|
||||
state,
|
||||
id,
|
||||
name,
|
||||
x => x.set('me', true)
|
||||
.update('count', n => n + 1)
|
||||
.update('url', old => old ? old : url)
|
||||
.update('static_url', old => old ? old : url),
|
||||
);
|
||||
|
||||
const removeReaction = (state, id, name) => updateReaction(
|
||||
state,
|
||||
id,
|
||||
name,
|
||||
x => x.set('me', false).update('count', n => n - 1),
|
||||
);
|
||||
|
||||
const statusTranslateSuccess = (state, id, translation) => {
|
||||
return state.withMutations(map => {
|
||||
map.setIn([id, 'translation'], fromJS(normalizeStatusTranslation(translation, map.get(id))));
|
||||
|
@ -6,8 +6,8 @@ class NotificationMailer < ApplicationMailer
|
||||
:routing
|
||||
|
||||
before_action :process_params
|
||||
before_action :set_status, only: [:mention, :favourite, :reblog]
|
||||
before_action :set_account, only: [:follow, :favourite, :reblog, :follow_request]
|
||||
before_action :set_status, only: [:mention, :favourite, :reaction, :reblog]
|
||||
before_action :set_account, only: [:follow, :favourite, :reaction, :reblog, :follow_request]
|
||||
after_action :set_list_headers!
|
||||
|
||||
default to: -> { email_address_with_name(@user.email, @me.username) }
|
||||
@ -38,6 +38,15 @@ class NotificationMailer < ApplicationMailer
|
||||
end
|
||||
end
|
||||
|
||||
def reaction
|
||||
return unless @user.functional? && @status.present?
|
||||
|
||||
locale_for_account(@me) do
|
||||
thread_by_conversation(@status.conversation)
|
||||
mail subject: default_i18n_subject(name: @account.acct)
|
||||
end
|
||||
end
|
||||
|
||||
def reblog
|
||||
return unless @user.functional? && @status.present?
|
||||
|
||||
|
@ -22,6 +22,10 @@ class StatusReaction < ApplicationRecord
|
||||
validates :name, presence: true
|
||||
validates_with StatusReactionValidator
|
||||
|
||||
before_validation do
|
||||
self.status = status.reblog if status&.reblog?
|
||||
end
|
||||
|
||||
before_validation :set_custom_emoji
|
||||
|
||||
private
|
||||
|
@ -45,6 +45,7 @@ class UserSettings
|
||||
setting :follow, default: true
|
||||
setting :reblog, default: false
|
||||
setting :favourite, default: false
|
||||
setting :reaction, default: false
|
||||
setting :mention, default: true
|
||||
setting :follow_request, default: true
|
||||
setting :report, default: true
|
||||
|
45
app/views/notification_mailer/reaction.html.haml
Normal file
45
app/views/notification_mailer/reaction.html.haml
Normal file
@ -0,0 +1,45 @@
|
||||
%table.email-table{ cellspacing: 0, cellpadding: 0 }
|
||||
%tbody
|
||||
%tr
|
||||
%td.email-body
|
||||
.email-container
|
||||
%table.content-section{ cellspacing: 0, cellpadding: 0 }
|
||||
%tbody
|
||||
%tr
|
||||
%td.content-cell.hero
|
||||
.email-row
|
||||
.col-6
|
||||
%table.column{ cellspacing: 0, cellpadding: 0 }
|
||||
%tbody
|
||||
%tr
|
||||
%td.column-cell.text-center.padded
|
||||
%table.hero-icon{ align: 'center', cellspacing: 0, cellpadding: 0 }
|
||||
%tbody
|
||||
%tr
|
||||
%td
|
||||
= image_tag full_pack_url('media/images/mailer/icon_add.png'), alt: ''
|
||||
|
||||
%h1= t 'notification_mailer.reaction.title'
|
||||
%p.lead= t('notification_mailer.reaction.body', name: @account.pretty_acct)
|
||||
|
||||
= render 'status', status: @status, time_zone: @me.user_time_zone
|
||||
|
||||
%table.email-table{ cellspacing: 0, cellpadding: 0 }
|
||||
%tbody
|
||||
%tr
|
||||
%td.email-body
|
||||
.email-container
|
||||
%table.content-section{ cellspacing: 0, cellpadding: 0 }
|
||||
%tbody
|
||||
%tr
|
||||
%td.content-cell.content-start.border-top
|
||||
%table.column{ cellspacing: 0, cellpadding: 0 }
|
||||
%tbody
|
||||
%tr
|
||||
%td.column-cell.button-cell
|
||||
%table.button{ align: 'center', cellspacing: 0, cellpadding: 0 }
|
||||
%tbody
|
||||
%tr
|
||||
%td.button-primary
|
||||
= link_to web_url("@#{@status.account.pretty_acct}/#{@status.id}") do
|
||||
%span= t 'application_mailer.view_status'
|
5
app/views/notification_mailer/reaction.text.erb
Normal file
5
app/views/notification_mailer/reaction.text.erb
Normal file
@ -0,0 +1,5 @@
|
||||
<%= raw t('application_mailer.salutation', name: display_name(@me)) %>
|
||||
|
||||
<%= raw t('notification_mailer.reaction.body', name: @account.pretty_acct) %>
|
||||
|
||||
<%= render 'status', status: @status %>
|
@ -36,8 +36,6 @@
|
||||
= ff.input :'web.use_system_font', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_system_font_ui')
|
||||
= ff.input :'web.use_system_emoji_font', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_system_emoji_font'), glitch_only: true
|
||||
|
||||
%h4= t 'appearance.toot_layout'
|
||||
|
||||
.fields-group.fields-row__column.fields-row__column-6
|
||||
= ff.input :'visible_reactions', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_visible_reactions'), input_html: { type: 'number', min: '0', data: { default: '6' } }, hint: false
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
= ff.input :'notification_emails.follow_request', wrapper: :with_label, label: I18n.t('simple_form.labels.notification_emails.follow_request')
|
||||
= ff.input :'notification_emails.reblog', wrapper: :with_label, label: I18n.t('simple_form.labels.notification_emails.reblog')
|
||||
= ff.input :'notification_emails.favourite', wrapper: :with_label, label: I18n.t('simple_form.labels.notification_emails.favourite')
|
||||
= ff.input :'notification_emails.reaction', wrapper: :with_label, label: I18n.t('simple_form.labels.notification_emails.reaction')
|
||||
= ff.input :'notification_emails.mention', wrapper: :with_label, label: I18n.t('simple_form.labels.notification_emails.mention')
|
||||
|
||||
.fields-group
|
||||
|
@ -22,6 +22,7 @@ en:
|
||||
setting_system_emoji_font: Use system's default font for emojis (applies to Glitch flavour only)
|
||||
setting_visible_reactions: Number of visible emoji reactions
|
||||
notification_emails:
|
||||
reaction: Someone reacted to your post
|
||||
trending_link: New trending link requires review
|
||||
trending_status: New trending post requires review
|
||||
trending_tag: New trending tag requires review
|
||||
|
@ -0,0 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class FixForeignKeysStatusReactions < ActiveRecord::Migration[6.1]
|
||||
disable_ddl_transaction!
|
||||
|
||||
def change
|
||||
# Fixes an oversight in a previous version of the CreateStatusReactions migration
|
||||
remove_foreign_key :status_reactions, :accounts
|
||||
add_foreign_key :status_reactions, :accounts, on_delete: :cascade, validate: false
|
||||
validate_foreign_key :status_reactions, :accounts
|
||||
remove_foreign_key :status_reactions, :statuses
|
||||
add_foreign_key :status_reactions, :statuses, on_delete: :cascade, validate: false
|
||||
validate_foreign_key :status_reactions, :statuses
|
||||
remove_foreign_key :status_reactions, :custom_emojis
|
||||
add_foreign_key :status_reactions, :custom_emojis, on_delete: :cascade, validate: false
|
||||
validate_foreign_key :status_reactions, :custom_emojis
|
||||
end
|
||||
end
|
@ -947,7 +947,6 @@ ActiveRecord::Schema[7.0].define(version: 2023_09_07_150100) do
|
||||
t.datetime "created_at", precision: 6, null: false
|
||||
t.datetime "updated_at", precision: 6, null: false
|
||||
t.index ["account_id", "status_id", "name"], name: "index_status_reactions_on_account_id_and_status_id", unique: true
|
||||
t.index ["account_id"], name: "index_status_reactions_on_account_id"
|
||||
t.index ["custom_emoji_id"], name: "index_status_reactions_on_custom_emoji_id"
|
||||
t.index ["status_id"], name: "index_status_reactions_on_status_id"
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user