Add reaction deck

This commit is contained in:
KMY 2023-05-26 15:15:48 +09:00
parent db5c358f4f
commit f1625fe101
24 changed files with 404 additions and 13 deletions

View file

@ -0,0 +1,45 @@
# frozen_string_literal: true
class Api::V1::ReactionDeckController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:lists' }, only: [:index]
before_action -> { doorkeeper_authorize! :write, :'write:lists' }, only: [:create]
before_action :require_user!
before_action :set_deck, only: [:index, :create]
rescue_from ArgumentError do |e|
render json: { error: e.to_s }, status: 422
end
def index
render json: @deck
end
def create
(deck_params['emojis'] || []).each do |data|
raise ArgumentError if data['id'].to_i >= 16 || data['id'].to_i.negative?
exists = @deck.find { |dd| dd['id'] == data['id'] }
if exists
exists['emoji'] = data['emoji'].delete(':')
else
@deck << { id: data['id'], emoji: data['emoji'].delete(':') }
end
end
@deck = @deck.sort_by { |a| a['id'].to_i }
current_user.settings['reaction_deck'] = @deck.to_json
current_user.save!
render json: @deck
end
private
def set_deck
@deck = current_user.setting_reaction_deck ? JSON.parse(current_user.setting_reaction_deck) : []
end
def deck_params
params
end
end

View file

@ -0,0 +1,79 @@
import api from '../api';
export const REACTION_DECK_FETCH_REQUEST = 'REACTION_DECK_FETCH_REQUEST';
export const REACTION_DECK_FETCH_SUCCESS = 'REACTION_DECK_FETCH_SUCCESS';
export const REACTION_DECK_FETCH_FAIL = 'REACTION_DECK_FETCH_FAIL';
export const REACTION_DECK_UPDATE_REQUEST = 'REACTION_DECK_UPDATE_REQUEST';
export const REACTION_DECK_UPDATE_SUCCESS = 'REACTION_DECK_UPDATE_SUCCESS';
export const REACTION_DECK_UPDATE_FAIL = 'REACTION_DECK_UPDATE_FAIL';
export function fetchReactionDeck() {
return (dispatch, getState) => {
dispatch(fetchReactionDeckRequest());
api(getState).get('/api/v1/reaction_deck').then(response => {
dispatch(fetchReactionDeckSuccess(response.data));
}).catch(error => {
dispatch(fetchReactionDeckFail(error));
});
};
}
export function fetchReactionDeckRequest() {
return {
type: REACTION_DECK_FETCH_REQUEST,
skipLoading: true,
};
}
export function fetchReactionDeckSuccess(emojis) {
return {
type: REACTION_DECK_FETCH_SUCCESS,
emojis,
skipLoading: true,
};
}
export function fetchReactionDeckFail(error) {
return {
type: REACTION_DECK_FETCH_FAIL,
error,
skipLoading: true,
};
}
export function updateReactionDeck(id, emoji) {
return (dispatch, getState) => {
dispatch(updateReactionDeckRequest());
api(getState).post('/api/v1/reaction_deck', { emojis: [{ id, emoji: emoji.native || emoji.id }] }).then(response => {
dispatch(updateReactionDeckSuccess(response.data));
}).catch(error => {
dispatch(updateReactionDeckFail(error));
});
};
}
export function updateReactionDeckRequest() {
return {
type: REACTION_DECK_UPDATE_REQUEST,
skipLoading: true,
};
}
export function updateReactionDeckSuccess(emojis) {
return {
type: REACTION_DECK_UPDATE_SUCCESS,
emojis,
skipLoading: true,
};
}
export function updateReactionDeckFail(error) {
return {
type: REACTION_DECK_UPDATE_FAIL,
error,
skipLoading: true,
};
}

View file

@ -11,6 +11,7 @@ import { Provider as ReduxProvider } from 'react-redux';
import { ScrollContext } from 'react-router-scroll-4'; import { ScrollContext } from 'react-router-scroll-4';
import { fetchCustomEmojis } from 'mastodon/actions/custom_emojis'; import { fetchCustomEmojis } from 'mastodon/actions/custom_emojis';
import { fetchReactionDeck } from 'mastodon/actions/reaction_deck';
import { hydrateStore } from 'mastodon/actions/store'; import { hydrateStore } from 'mastodon/actions/store';
import { connectUserStream } from 'mastodon/actions/streaming'; import { connectUserStream } from 'mastodon/actions/streaming';
import ErrorBoundary from 'mastodon/components/error_boundary'; import ErrorBoundary from 'mastodon/components/error_boundary';
@ -29,6 +30,7 @@ const hydrateAction = hydrateStore(initialState);
store.dispatch(hydrateAction); store.dispatch(hydrateAction);
if (initialState.meta.me) { if (initialState.meta.me) {
store.dispatch(fetchCustomEmojis()); store.dispatch(fetchCustomEmojis());
store.dispatch(fetchReactionDeck());
} }
const createIdentityContext = state => ({ const createIdentityContext = state => ({

View file

@ -11,6 +11,7 @@ const messages = defineMessages({
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned posts' }, pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned posts' },
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
reaction_deck: { id: 'navigation_bar.reaction_deck', defaultMessage: 'Reaction deck' },
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' }, follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' }, favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
emoji_reactions: { id: 'navigation_bar.emoji_reactions', defaultMessage: 'Stamps' }, emoji_reactions: { id: 'navigation_bar.emoji_reactions', defaultMessage: 'Stamps' },
@ -44,6 +45,7 @@ class ActionBar extends PureComponent {
menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' }); menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' });
menu.push({ text: intl.formatMessage(messages.preferences), href: '/settings/preferences' }); menu.push({ text: intl.formatMessage(messages.preferences), href: '/settings/preferences' });
menu.push({ text: intl.formatMessage(messages.reaction_deck), to: '/reaction_deck' });
menu.push({ text: intl.formatMessage(messages.pins), to: '/pinned' }); menu.push({ text: intl.formatMessage(messages.pins), to: '/pinned' });
menu.push(null); menu.push(null);
menu.push({ text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' }); menu.push({ text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' });

View file

@ -1,11 +1,16 @@
import { Map as ImmutableMap } from 'immutable'; import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { hideRecentEmojis } from 'mastodon/initial_state';
import { useEmoji } from '../../../actions/emojis'; import { useEmoji } from '../../../actions/emojis';
import { changeSetting } from '../../../actions/settings'; import { changeSetting } from '../../../actions/settings';
import { shortCodes } from '../../emoji/emoji_mart_data_light';
import EmojiPickerDropdown from '../components/emoji_picker_dropdown'; import EmojiPickerDropdown from '../components/emoji_picker_dropdown';
const perLine = 8; const perLine = 8;
const lines = 2; const lines = 2;
@ -28,20 +33,42 @@ const DEFAULTS = [
'ok_hand', 'ok_hand',
]; ];
const RECENT_SIZE = DEFAULTS.length;
const DECK_SIZE = 16;
const getFrequentlyUsedEmojis = createSelector([ const getFrequentlyUsedEmojis = createSelector([
state => state.getIn(['settings', 'frequentlyUsedEmojis'], ImmutableMap()), state => { return {
], emojiCounters => { emojiCounters: state.getIn(['settings', 'frequentlyUsedEmojis'], ImmutableMap()),
let emojis = emojiCounters reactionDeck: state.get('reaction_deck', ImmutableList()),
}; },
], data => {
const { emojiCounters, reactionDeck } = data;
let deckEmojis = reactionDeck
.toArray()
.map((e) => e.get('emoji'))
.filter((e) => e)
.map((e) => shortCodes[e] || e);
deckEmojis = [...new Set(deckEmojis)];
let emojis;
if (!hideRecentEmojis) {
emojis = emojiCounters
.keySeq() .keySeq()
.filter((ee) => deckEmojis.indexOf(ee) < 0)
.sort((a, b) => emojiCounters.get(a) - emojiCounters.get(b)) .sort((a, b) => emojiCounters.get(a) - emojiCounters.get(b))
.reverse() .reverse()
.slice(0, perLine * lines) .slice(0, perLine * lines)
.toArray(); .toArray();
if (emojis.length < DEFAULTS.length) { if (emojis.length < RECENT_SIZE) {
let uniqueDefaults = DEFAULTS.filter(emoji => !emojis.includes(emoji)); let uniqueDefaults = DEFAULTS.filter(emoji => !emojis.includes(emoji));
emojis = emojis.concat(uniqueDefaults.slice(0, DEFAULTS.length - emojis.length)); emojis = emojis.concat(uniqueDefaults.slice(0, RECENT_SIZE - emojis.length));
} }
} else {
emojis = [];
}
emojis = deckEmojis.slice(0, DECK_SIZE).concat(emojis);
return emojis; return emojis;
}); });

View file

@ -7,6 +7,7 @@ import { unicodeToUnifiedName } from './unicode_to_unified_name';
const [ shortCodesToEmojiData, skins, categories, short_names ] = emojiCompressed; const [ shortCodesToEmojiData, skins, categories, short_names ] = emojiCompressed;
const emojis = {}; const emojis = {};
const shortCodes = {};
// decompress // decompress
Object.keys(shortCodesToEmojiData).forEach((shortCode) => { Object.keys(shortCodesToEmojiData).forEach((shortCode) => {
@ -33,10 +34,12 @@ Object.keys(shortCodesToEmojiData).forEach((shortCode) => {
short_names, short_names,
unified, unified,
}; };
shortCodes[native] = shortCode;
}); });
export { export {
emojis, emojis,
shortCodes,
skins, skins,
categories, categories,
short_names, short_names,

View file

@ -0,0 +1,75 @@
import PropTypes from 'prop-types';
import { injectIntl } from 'react-intl';
import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import { updateReactionDeck } from 'mastodon/actions/reaction_deck';
import EmojiPickerDropdownContainer from 'mastodon/features/compose/containers/emoji_picker_dropdown_container';
import emojify from 'mastodon/features/emoji/emoji';
import { autoPlayGif } from 'mastodon/initial_state';
const MapStateToProps = (state, { emojiId, emojiMap }) => ({
emoji: (state.get('reaction_deck', ImmutableList()).toArray().find(em => em.get('id') === emojiId) || ImmutableMap({ emoji: '' })).get('emoji'),
emojiMap,
});
const mapDispatchToProps = (dispatch, { emojiId }) => ({
onChange: (emoji) => dispatch(updateReactionDeck(emojiId, emoji)),
});
class ReactionEmoji extends ImmutablePureComponent {
static propTypes = {
emoji: PropTypes.string,
emojiMap: ImmutablePropTypes.map.isRequired,
onChange: PropTypes.func.isRequired,
};
static defaultProps = {
emoji: '',
};
render () {
const { emojiMap, emoji, onChange } = this.props;
let content = null;
if (emojiMap.get(emoji)) {
const filename = autoPlayGif ? emojiMap.getIn([emoji, 'url']) : emojiMap.getIn([emoji, 'static_url']);
const shortCode = `:${emoji}:`;
content = (
<img
draggable='false'
className='emojione custom-emoji'
alt={shortCode}
title={shortCode}
src={filename}
/>
);
} else {
const html = { __html: emojify(emoji) };
content = (
<span dangerouslySetInnerHTML={html} />
)
}
return (
<div className='reaction_deck__emoji'>
<div className='reaction_deck__emoji__wrapper'>
<EmojiPickerDropdownContainer onPickEmoji={onChange} />
<div>
{content}
</div>
</div>
</div>
);
}
}
export default connect(MapStateToProps, mapDispatchToProps)(injectIntl(ReactionEmoji));

View file

@ -0,0 +1,86 @@
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import { Helmet } from 'react-helmet';
import { Map as ImmutableMap } from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import ColumnHeader from 'mastodon/components/column_header';
import LoadingIndicator from 'mastodon/components/loading_indicator';
import ScrollableList from 'mastodon/components/scrollable_list';
import Column from 'mastodon/features/ui/components/column';
import ReactionEmoji from './components/reaction_emoji';
const DECK_SIZE = 16;
const customEmojiMap = createSelector([state => state.get('custom_emojis')], items => items.reduce((map, emoji) => map.set(emoji.get('shortcode'), emoji), ImmutableMap()));
const messages = defineMessages({
refresh: { id: 'refresh', defaultMessage: 'Refresh' },
heading: { id: 'column.reaction_deck', defaultMessage: 'Reaction deck' },
});
const mapStateToProps = (state, props) => ({
accountIds: state.getIn(['user_lists', 'favourited_by', props.params.statusId]),
deck: state.get('reaction_deck'),
emojiMap: customEmojiMap(state),
});
class ReactionDeck extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
deck: ImmutablePropTypes.list,
emojiMap: ImmutablePropTypes.map,
multiColumn: PropTypes.bool,
intl: PropTypes.object.isRequired,
};
render () {
const { intl, deck, emojiMap, multiColumn } = this.props;
if (!deck) {
return (
<Column>
<LoadingIndicator />
</Column>
);
}
return (
<Column bindToDocument={!multiColumn}>
<ColumnHeader
icon='smile-o'
title={intl.formatMessage(messages.heading)}
multiColumn={multiColumn}
showBackButton
/>
<ScrollableList
scrollKey='reaction_deck'
bindToDocument={!multiColumn}
>
{[...Array(DECK_SIZE).keys()].map(emojiId =>
<ReactionEmoji key={emojiId} emojiMap={emojiMap} emojiId={emojiId} />
)}
</ScrollableList>
<Helmet>
<meta name='robots' content='noindex' />
</Helmet>
</Column>
);
}
}
export default connect(mapStateToProps)(injectIntl(ReactionDeck));

View file

@ -62,6 +62,7 @@ import {
Lists, Lists,
Directory, Directory,
Explore, Explore,
ReactionDeck,
Onboarding, Onboarding,
About, About,
PrivacyPolicy, PrivacyPolicy,
@ -203,6 +204,8 @@ class SwitchingColumnsArea extends PureComponent {
<WrappedRoute path='/bookmarks' component={BookmarkedStatuses} content={children} /> <WrappedRoute path='/bookmarks' component={BookmarkedStatuses} content={children} />
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} /> <WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
<WrappedRoute path='/reaction_deck' component={ReactionDeck} content={children} />
<WrappedRoute path='/start' exact component={Onboarding} content={children} /> <WrappedRoute path='/start' exact component={Onboarding} content={children} />
<WrappedRoute path='/directory' component={Directory} content={children} /> <WrappedRoute path='/directory' component={Directory} content={children} />
<WrappedRoute path={['/explore', '/search']} component={Explore} content={children} /> <WrappedRoute path={['/explore', '/search']} component={Explore} content={children} />

View file

@ -166,6 +166,10 @@ export function Onboarding () {
return import(/* webpackChunkName: "features/onboarding" */'../../onboarding'); return import(/* webpackChunkName: "features/onboarding" */'../../onboarding');
} }
export function ReactionDeck () {
return import(/* webpackChunkName: "features/reaction_deck" */'../../reaction_deck');
}
export function CompareHistoryModal () { export function CompareHistoryModal () {
return import(/*webpackChunkName: "modals/compare_history_modal" */'../components/compare_history_modal'); return import(/*webpackChunkName: "modals/compare_history_modal" */'../components/compare_history_modal');
} }

View file

@ -60,6 +60,7 @@
* @property {string} domain * @property {string} domain
* @property {boolean} enable_login_privacy * @property {boolean} enable_login_privacy
* @property {boolean=} expand_spoilers * @property {boolean=} expand_spoilers
* @property {boolean} hide_recent_emojis
* @property {boolean} limited_federation_mode * @property {boolean} limited_federation_mode
* @property {string} locale * @property {string} locale
* @property {string | null} mascot * @property {string | null} mascot
@ -115,6 +116,7 @@ export const domain = getMeta('domain');
export const enableLoginPrivacy = getMeta('enable_login_privacy'); export const enableLoginPrivacy = getMeta('enable_login_privacy');
export const expandSpoilers = getMeta('expand_spoilers'); export const expandSpoilers = getMeta('expand_spoilers');
export const forceSingleColumn = !getMeta('advanced_layout'); export const forceSingleColumn = !getMeta('advanced_layout');
export const hideRecentEmojis = getMeta('hide_recent_emojis');
export const limitedFederationMode = getMeta('limited_federation_mode'); export const limitedFederationMode = getMeta('limited_federation_mode');
export const mascot = getMeta('mascot'); export const mascot = getMeta('mascot');
export const me = getMeta('me'); export const me = getMeta('me');

View file

@ -34,6 +34,7 @@ import notifications from './notifications';
import picture_in_picture from './picture_in_picture'; import picture_in_picture from './picture_in_picture';
import polls from './polls'; import polls from './polls';
import push_notifications from './push_notifications'; import push_notifications from './push_notifications';
import reaction_deck from './reaction_deck';
import relationships from './relationships'; import relationships from './relationships';
import search from './search'; import search from './search';
import server from './server'; import server from './server';
@ -92,6 +93,7 @@ const reducers = {
history, history,
tags, tags,
followed_tags, followed_tags,
reaction_deck,
}; };
const rootReducer = combineReducers(reducers); const rootReducer = combineReducers(reducers);

View file

@ -0,0 +1,13 @@
import { List as ImmutableList, fromJS as ConvertToImmutable } from 'immutable';
import { REACTION_DECK_FETCH_SUCCESS, REACTION_DECK_UPDATE_SUCCESS } from '../actions/reaction_deck';
const initialState = ImmutableList([]);
export default function reaction_deck(state = initialState, action) {
if(action.type === REACTION_DECK_FETCH_SUCCESS || action.type === REACTION_DECK_UPDATE_SUCCESS) {
state = ConvertToImmutable(action.emojis);
}
return state;
}

View file

@ -7544,6 +7544,26 @@ noscript {
} }
} }
.reaction_deck__emoji {
&__wrapper {
display: flex;
margin: 8px 4px;
height: 32px;
.emojione {
min-width: 24px;
height: 24px;
}
.emoji-button {
margin-left: 20px;
margin-right: 24px;
padding: 0;
}
}
}
.focal-point { .focal-point {
position: relative; position: relative;
cursor: move; cursor: move;

View file

@ -23,10 +23,18 @@ module HasUserSettings
settings['web.auto_play'] settings['web.auto_play']
end end
def setting_reaction_deck
settings['reaction_deck']
end
def setting_enable_login_privacy def setting_enable_login_privacy
settings['web.enable_login_privacy'] settings['web.enable_login_privacy']
end end
def setting_hide_recent_emojis
settings['web.hide_recent_emojis']
end
def setting_default_sensitive def setting_default_sensitive
settings['default_sensitive'] settings['default_sensitive']
end end

View file

@ -25,6 +25,7 @@ class UserSettings
setting :reject_public_unlisted_subscription, default: false setting :reject_public_unlisted_subscription, default: false
setting :reject_unlisted_subscription, default: false setting :reject_unlisted_subscription, default: false
setting :send_without_domain_blocks, default: false setting :send_without_domain_blocks, default: false
setting :reaction_deck, default: nil
setting :stop_emoji_reaction_streaming, default: false setting :stop_emoji_reaction_streaming, default: false
setting :emoji_reaction_streaming_notify_impl2, default: false setting :emoji_reaction_streaming_notify_impl2, default: false
@ -38,6 +39,7 @@ class UserSettings
setting :disable_swiping, default: false setting :disable_swiping, default: false
setting :delete_modal, default: true setting :delete_modal, default: true
setting :enable_login_privacy, default: false setting :enable_login_privacy, default: false
setting :hide_recent_emojis, default: false
setting :reblog_modal, default: false setting :reblog_modal, default: false
setting :unfollow_modal, default: true setting :unfollow_modal, default: true
setting :reduce_motion, default: false setting :reduce_motion, default: false

View file

@ -44,6 +44,7 @@ class InitialStateSerializer < ActiveModel::Serializer
store[:display_media_expand] = object.current_account.user.setting_display_media_expand store[:display_media_expand] = object.current_account.user.setting_display_media_expand
store[:expand_spoilers] = object.current_account.user.setting_expand_spoilers store[:expand_spoilers] = object.current_account.user.setting_expand_spoilers
store[:enable_login_privacy] = object.current_account.user.setting_enable_login_privacy store[:enable_login_privacy] = object.current_account.user.setting_enable_login_privacy
store[:hide_recent_emojis] = object.current_account.user.setting_hide_recent_emojis
store[:reduce_motion] = object.current_account.user.setting_reduce_motion store[:reduce_motion] = object.current_account.user.setting_reduce_motion
store[:disable_swiping] = object.current_account.user.setting_disable_swiping store[:disable_swiping] = object.current_account.user.setting_disable_swiping
store[:advanced_layout] = object.current_account.user.setting_advanced_layout store[:advanced_layout] = object.current_account.user.setting_advanced_layout

View file

@ -85,6 +85,10 @@ class REST::InstanceSerializer < ActiveModel::Serializer
max_reactions_per_account: EmojiReaction::EMOJI_REACTION_PER_ACCOUNT_LIMIT, max_reactions_per_account: EmojiReaction::EMOJI_REACTION_PER_ACCOUNT_LIMIT,
}, },
reaction_deck: {
max_items: 16,
},
reactions: { reactions: {
max_reactions: EmojiReaction::EMOJI_REACTION_PER_ACCOUNT_LIMIT, max_reactions: EmojiReaction::EMOJI_REACTION_PER_ACCOUNT_LIMIT,
}, },
@ -110,6 +114,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer
:kmyblue_searchability, :kmyblue_searchability,
:searchability, :searchability,
:kmyblue_markdown, :kmyblue_markdown,
:kmyblue_reaction_deck,
] ]
capabilities << :profile_search unless Chewy.enabled? capabilities << :profile_search unless Chewy.enabled?

View file

@ -91,6 +91,10 @@ class REST::V1::InstanceSerializer < ActiveModel::Serializer
max_reactions_per_account: EmojiReaction::EMOJI_REACTION_PER_ACCOUNT_LIMIT, max_reactions_per_account: EmojiReaction::EMOJI_REACTION_PER_ACCOUNT_LIMIT,
}, },
reaction_deck: {
max_items: 16,
},
reactions: { reactions: {
max_reactions: EmojiReaction::EMOJI_REACTION_PER_ACCOUNT_LIMIT, max_reactions: EmojiReaction::EMOJI_REACTION_PER_ACCOUNT_LIMIT,
}, },
@ -119,6 +123,7 @@ class REST::V1::InstanceSerializer < ActiveModel::Serializer
:kmyblue_searchability, :kmyblue_searchability,
:searchability, :searchability,
:kmyblue_markdown, :kmyblue_markdown,
:kmyblue_reaction_deck,
] ]
capabilities << :profile_search unless Chewy.enabled? capabilities << :profile_search unless Chewy.enabled?

View file

@ -39,6 +39,9 @@
.fields-group .fields-group
= ff.input :'web.crop_images', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_crop_images') = ff.input :'web.crop_images', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_crop_images')
.fields-group
= ff.input :'web.hide_recent_emojis', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_hide_recent_emojis'), hint: false
.fields-group .fields-group
= ff.input :emoji_reaction_streaming_notify_impl2, as: :boolean, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_emoji_reaction_streaming_notify_impl2'), hint: I18n.t('simple_form.hints.defaults.setting_emoji_reaction_streaming_notify_impl2') = ff.input :emoji_reaction_streaming_notify_impl2, as: :boolean, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_emoji_reaction_streaming_notify_impl2'), hint: I18n.t('simple_form.hints.defaults.setting_emoji_reaction_streaming_notify_impl2')

View file

@ -225,6 +225,7 @@ en:
setting_hide_followers_count: Hide followers count setting_hide_followers_count: Hide followers count
setting_hide_following_count: Hide following count setting_hide_following_count: Hide following count
setting_hide_network: Hide your social graph setting_hide_network: Hide your social graph
setting_hide_recent_emojis: Hide recent emojis
setting_hide_statuses_count: Hide statuses count setting_hide_statuses_count: Hide statuses count
setting_noai: Set noai meta tags setting_noai: Set noai meta tags
setting_noindex: Opt-out of search engine indexing setting_noindex: Opt-out of search engine indexing

View file

@ -233,6 +233,7 @@ ja:
setting_hide_followers_count: フォロワー数を隠す setting_hide_followers_count: フォロワー数を隠す
setting_hide_following_count: フォロー数を隠す setting_hide_following_count: フォロー数を隠す
setting_hide_network: 繋がりを隠す setting_hide_network: 繋がりを隠す
setting_hide_recent_emojis: 絵文字ピッカーで最近使用した絵文字を隠す(リアクションデッキのみを表示する)
setting_hide_statuses_count: 投稿数を隠す setting_hide_statuses_count: 投稿数を隠す
setting_noai: 自分のコンテンツのAI学習利用に対して不快感を表明する setting_noai: 自分のコンテンツのAI学習利用に対して不快感を表明する
setting_noindex: 検索エンジンによるインデックスを拒否する setting_noindex: 検索エンジンによるインデックスを拒否する

View file

@ -19,6 +19,7 @@ Rails.application.routes.draw do
/emoji_reactions /emoji_reactions
/bookmarks /bookmarks
/pinned /pinned
/reaction_deck
/start /start
/directory /directory
/explore/(*any) /explore/(*any)

View file

@ -54,6 +54,7 @@ namespace :api, format: false do
get '/streaming/(*any)', to: 'streaming#index' get '/streaming/(*any)', to: 'streaming#index'
resources :custom_emojis, only: [:index] resources :custom_emojis, only: [:index]
resources :reaction_deck, only: [:index, :create]
resources :suggestions, only: [:index, :destroy] resources :suggestions, only: [:index, :destroy]
resources :scheduled_statuses, only: [:index, :show, :update, :destroy] resources :scheduled_statuses, only: [:index, :show, :update, :destroy]
resources :preferences, only: [:index] resources :preferences, only: [:index]