Merge commit 'dee69e4f83
' into kb_migration
This commit is contained in:
commit
f010acbde2
18 changed files with 286 additions and 59 deletions
|
@ -43,11 +43,11 @@ export function fetchReactionDeckFail(error) {
|
|||
};
|
||||
}
|
||||
|
||||
export function updateReactionDeck(id, emoji) {
|
||||
export function updateReactionDeck(emojis) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(updateReactionDeckRequest());
|
||||
|
||||
api(getState).post('/api/v1/reaction_deck', { emojis: [{ id, emoji: emoji.native || emoji.id }] }).then(response => {
|
||||
api(getState).post('/api/v1/reaction_deck', { emojis }).then(response => {
|
||||
dispatch(updateReactionDeckSuccess(response.data));
|
||||
}).catch(error => {
|
||||
dispatch(updateReactionDeckFail(error));
|
||||
|
|
|
@ -6,7 +6,7 @@ import { hideRecentEmojis } from 'mastodon/initial_state';
|
|||
|
||||
import { useEmoji } from '../../../actions/emojis';
|
||||
import { changeSetting } from '../../../actions/settings';
|
||||
import { shortCodes } from '../../emoji/emoji_mart_data_light';
|
||||
import unicodeMapping from '../../emoji/emoji_unicode_mapping_light';
|
||||
import EmojiPickerDropdown from '../components/emoji_picker_dropdown';
|
||||
|
||||
|
||||
|
@ -34,7 +34,6 @@ const DEFAULTS = [
|
|||
];
|
||||
|
||||
const RECENT_SIZE = DEFAULTS.length;
|
||||
const DECK_SIZE = 16;
|
||||
|
||||
const getFrequentlyUsedEmojis = createSelector([
|
||||
state => { return {
|
||||
|
@ -43,11 +42,12 @@ const getFrequentlyUsedEmojis = createSelector([
|
|||
}; },
|
||||
], data => {
|
||||
const { emojiCounters, reactionDeck } = data;
|
||||
|
||||
let deckEmojis = reactionDeck
|
||||
.toArray()
|
||||
.map((e) => e.get('emoji'))
|
||||
.map((e) => e.get('name'))
|
||||
.filter((e) => e)
|
||||
.map((e) => shortCodes[e] || e);
|
||||
.map((e) => unicodeMapping[e] ? unicodeMapping[e].shortCode : e);
|
||||
deckEmojis = [...new Set(deckEmojis)];
|
||||
|
||||
let emojis;
|
||||
|
@ -68,7 +68,9 @@ const getFrequentlyUsedEmojis = createSelector([
|
|||
emojis = [];
|
||||
}
|
||||
|
||||
emojis = deckEmojis.slice(0, DECK_SIZE).concat(emojis);
|
||||
emojis = deckEmojis.concat(emojis);
|
||||
|
||||
if (emojis.length <= 0) emojis = ['+1'];
|
||||
|
||||
return emojis;
|
||||
});
|
||||
|
|
|
@ -7,7 +7,6 @@ import { unicodeToUnifiedName } from './unicode_to_unified_name';
|
|||
const [ shortCodesToEmojiData, skins, categories, short_names ] = emojiCompressed;
|
||||
|
||||
const emojis = {};
|
||||
const shortCodes = {};
|
||||
|
||||
// decompress
|
||||
Object.keys(shortCodesToEmojiData).forEach((shortCode) => {
|
||||
|
@ -34,12 +33,10 @@ Object.keys(shortCodesToEmojiData).forEach((shortCode) => {
|
|||
short_names,
|
||||
unified,
|
||||
};
|
||||
shortCodes[native] = shortCode;
|
||||
});
|
||||
|
||||
export {
|
||||
emojis,
|
||||
shortCodes,
|
||||
skins,
|
||||
categories,
|
||||
short_names,
|
||||
|
|
|
@ -13,7 +13,7 @@ import { registrationsOpen } from 'mastodon/initial_state';
|
|||
|
||||
const mapStateToProps = (state, { accountId }) => ({
|
||||
displayNameHtml: state.getIn(['accounts', accountId, 'display_name_html']),
|
||||
signupUrl: state.getIn(['server', 'server', 'registrations', 'url'], '/auth/sign_up'),
|
||||
signupUrl: state.getIn(['server', 'server', 'registrations', 'url'], '/auth/sign_up') || '/auth/sign_up',
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
|
|
|
@ -1,40 +1,44 @@
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import { injectIntl } from 'react-intl';
|
||||
import { defineMessages, 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 Button from 'mastodon/components/button';
|
||||
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)),
|
||||
const messages = defineMessages({
|
||||
remove: { id: 'reaction_deck.remove', defaultMessage: 'Remove' },
|
||||
});
|
||||
|
||||
class ReactionEmoji extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
index: PropTypes.number,
|
||||
emoji: PropTypes.string,
|
||||
emojiMap: ImmutablePropTypes.map.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onRemove: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
emoji: '',
|
||||
};
|
||||
|
||||
handleChange = (emoji) => {
|
||||
this.props.onChange(this.props.index, emoji);
|
||||
};
|
||||
|
||||
handleRemove = () => {
|
||||
this.props.onRemove(this.props.index);
|
||||
};
|
||||
|
||||
render () {
|
||||
const { emojiMap, emoji, onChange } = this.props;
|
||||
const { intl, emojiMap, emoji } = this.props;
|
||||
|
||||
let content = null;
|
||||
|
||||
|
@ -61,9 +65,14 @@ class ReactionEmoji extends ImmutablePureComponent {
|
|||
return (
|
||||
<div className='reaction_deck__emoji'>
|
||||
<div className='reaction_deck__emoji__wrapper'>
|
||||
<EmojiPickerDropdownContainer onPickEmoji={onChange} />
|
||||
<div>
|
||||
{content}
|
||||
<div className='reaction_deck__emoji__wrapper__content'>
|
||||
<EmojiPickerDropdownContainer onPickEmoji={this.handleChange} />
|
||||
<div>
|
||||
{content}
|
||||
</div>
|
||||
</div>
|
||||
<div className='reaction_deck__emoji__wrapper__options'>
|
||||
<Button secondary text={intl.formatMessage(messages.remove)} onClick={this.handleRemove} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -72,4 +81,4 @@ class ReactionEmoji extends ImmutablePureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default connect(MapStateToProps, mapDispatchToProps)(injectIntl(ReactionEmoji));
|
||||
export default connect()(injectIntl(ReactionEmoji));
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
|
@ -10,20 +11,41 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
|||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
|
||||
|
||||
import { updateReactionDeck } from 'mastodon/actions/reaction_deck';
|
||||
import Button from 'mastodon/components/button';
|
||||
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;
|
||||
// https://medium.com/@wbern/getting-react-18s-strict-mode-to-work-with-react-beautiful-dnd-47bc909348e4
|
||||
/* eslint react/prop-types: 0 */
|
||||
const StrictModeDroppable = ({ children, ...props }) => {
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
useEffect(() => {
|
||||
const animation = requestAnimationFrame(() => setEnabled(true));
|
||||
return () => {
|
||||
cancelAnimationFrame(animation);
|
||||
setEnabled(false);
|
||||
};
|
||||
}, []);
|
||||
if (!enabled) {
|
||||
return null;
|
||||
}
|
||||
return <Droppable {...props}>{children}</Droppable>;
|
||||
};
|
||||
/* eslint react/prop-types: 0 */
|
||||
|
||||
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' },
|
||||
reaction_deck_add: { id: 'reaction_deck.add', defaultMessage: 'Add' },
|
||||
heading: { id: 'column.reaction_deck', defaultMessage: 'Reaction deck' },
|
||||
});
|
||||
|
||||
|
@ -33,17 +55,52 @@ const mapStateToProps = (state, props) => ({
|
|||
emojiMap: customEmojiMap(state),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onChange: (emojis) => dispatch(updateReactionDeck(emojis)),
|
||||
});
|
||||
|
||||
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,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
deckToArray = () => {
|
||||
const { deck } = this.props;
|
||||
|
||||
return deck.map((item) => item.get('name')).toArray();
|
||||
};
|
||||
|
||||
handleReorder = (result) => {
|
||||
const newDeck = this.deckToArray();
|
||||
const deleted = newDeck.splice(result.source.index, 1);
|
||||
newDeck.splice(result.destination.index, 0, deleted[0]);
|
||||
this.props.onChange(newDeck);
|
||||
};
|
||||
|
||||
handleChange = (index, emoji) => {
|
||||
const newDeck = this.deckToArray();
|
||||
newDeck[index] = emoji.native || emoji.id.replace(':', '');
|
||||
this.props.onChange(newDeck);
|
||||
};
|
||||
|
||||
handleRemove = (index) => {
|
||||
const newDeck = this.deckToArray();
|
||||
newDeck.splice(index, 1);
|
||||
this.props.onChange(newDeck);
|
||||
};
|
||||
|
||||
handleAdd = () => {
|
||||
const newDeck = this.deckToArray();
|
||||
newDeck.push('👍');
|
||||
this.props.onChange(newDeck);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { intl, deck, emojiMap, multiColumn } = this.props;
|
||||
|
||||
|
@ -69,9 +126,31 @@ class ReactionDeck extends ImmutablePureComponent {
|
|||
scrollKey='reaction_deck'
|
||||
bindToDocument={!multiColumn}
|
||||
>
|
||||
{[...Array(DECK_SIZE).keys()].map(emojiId =>
|
||||
<ReactionEmoji key={emojiId} emojiMap={emojiMap} emojiId={emojiId} />
|
||||
)}
|
||||
<DragDropContext onDragEnd={this.handleReorder}>
|
||||
<StrictModeDroppable droppableId='deckitems'>
|
||||
{(provided) => (
|
||||
<div className='deckitems' {...provided.droppableProps} ref={provided.innerRef}>
|
||||
{deck.map((emoji, index) => (
|
||||
<Draggable key={index} draggableId={'' + index} index={index}>
|
||||
{(provided2) => (
|
||||
<div ref={provided2.innerRef} {...provided2.draggableProps} {...provided2.dragHandleProps}>
|
||||
<ReactionEmoji emojiMap={emojiMap}
|
||||
emoji={emoji.get('name')}
|
||||
index={index}
|
||||
onChange={this.handleChange}
|
||||
onRemove={this.handleRemove}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
|
||||
<Button text={intl.formatMessage(messages.reaction_deck_add)} onClick={this.handleAdd} />
|
||||
</div>
|
||||
)}
|
||||
</StrictModeDroppable>
|
||||
</DragDropContext>
|
||||
</ScrollableList>
|
||||
|
||||
<Helmet>
|
||||
|
@ -83,4 +162,4 @@ class ReactionDeck extends ImmutablePureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(injectIntl(ReactionDeck));
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(ReactionDeck));
|
||||
|
|
|
@ -61,7 +61,10 @@ class NavigationPanel extends Component {
|
|||
</div>
|
||||
|
||||
{signedIn && (
|
||||
<ColumnLink transparent to='/home' icon='home' text={intl.formatMessage(messages.home)} />
|
||||
<>
|
||||
<ColumnLink transparent to='/notifications' icon={<NotificationsCounterIcon className='column-link__icon' />} text={intl.formatMessage(messages.notifications)} />
|
||||
<ColumnLink transparent to='/home' icon='home' text={intl.formatMessage(messages.home)} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{!signedIn && explorer}
|
||||
|
@ -82,7 +85,6 @@ class NavigationPanel extends Component {
|
|||
|
||||
{signedIn && (
|
||||
<>
|
||||
<ColumnLink transparent to='/notifications' icon={<NotificationsCounterIcon className='column-link__icon' />} text={intl.formatMessage(messages.notifications)} />
|
||||
<FollowRequestsColumnLink />
|
||||
<ColumnLink transparent to='/conversations' icon='at' text={intl.formatMessage(messages.direct)} />
|
||||
</>
|
||||
|
|
|
@ -17,7 +17,7 @@ const SignInBanner = () => {
|
|||
|
||||
let signupButton;
|
||||
|
||||
const signupUrl = useAppSelector((state) => state.getIn(['server', 'server', 'registrations', 'url'], '/auth/sign_up'));
|
||||
const signupUrl = useAppSelector((state) => state.getIn(['server', 'server', 'registrations', 'url'], '/auth/sign_up') || '/auth/sign_up');
|
||||
|
||||
if (registrationsOpen) {
|
||||
signupButton = (
|
||||
|
|
|
@ -116,6 +116,7 @@
|
|||
"column.notifications": "Notifications",
|
||||
"column.pins": "Pinned posts",
|
||||
"column.public": "Federated timeline",
|
||||
"column.reaction_deck": "Reaction deck",
|
||||
"column_back_button.label": "Back",
|
||||
"column_header.hide_settings": "Hide settings",
|
||||
"column_header.moveLeft_settings": "Move column to the left",
|
||||
|
@ -134,6 +135,8 @@
|
|||
"compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is not public. Only public posts can be searched by hashtag.",
|
||||
"compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
|
||||
"compose_form.lock_disclaimer.lock": "locked",
|
||||
"compose_form.markdown.marked": "Markdown is available",
|
||||
"compose_form.markdown.unmarked": "Markdown is NOT available",
|
||||
"compose_form.placeholder": "What's on your mind?",
|
||||
"compose_form.searchability_warning": "Self only searchability is not available other mastodon servers. Others can search your post.",
|
||||
"compose_form.poll.add_option": "Add a choice",
|
||||
|
@ -371,6 +374,7 @@
|
|||
"mute_modal.hide_notifications": "Hide notifications from this user?",
|
||||
"mute_modal.indefinite": "Indefinite",
|
||||
"navigation_bar.about": "About",
|
||||
"navigation_bar.antennas": "Antenna",
|
||||
"navigation_bar.blocks": "Blocked users",
|
||||
"navigation_bar.bookmarks": "Bookmarks",
|
||||
"navigation_bar.community_timeline": "Local timeline",
|
||||
|
@ -392,6 +396,7 @@
|
|||
"navigation_bar.pins": "Pinned posts",
|
||||
"navigation_bar.preferences": "Preferences",
|
||||
"navigation_bar.public_timeline": "Federated timeline",
|
||||
"navigation_bar.reaction_deck": "Reaction deck",
|
||||
"navigation_bar.search": "Search",
|
||||
"navigation_bar.security": "Security",
|
||||
"not_signed_in_indicator.not_signed_in": "You need to login to access this resource.",
|
||||
|
@ -487,6 +492,8 @@
|
|||
"privacy.change": "Change post privacy",
|
||||
"privacy.direct.long": "Visible for mentioned users only",
|
||||
"privacy.direct.short": "Mentioned people only",
|
||||
"privacy.login.long": "Visible for login users only",
|
||||
"privacy.login.short": "Login only",
|
||||
"privacy.private.long": "Visible for followers only",
|
||||
"privacy.private.short": "Followers only",
|
||||
"privacy.public.long": "Visible for all",
|
||||
|
@ -497,6 +504,8 @@
|
|||
"privacy.unlisted.short": "Unlisted",
|
||||
"privacy_policy.last_updated": "Last updated {date}",
|
||||
"privacy_policy.title": "Privacy Policy",
|
||||
"reaction_deck.add": "Add",
|
||||
"reaction_deck.remove": "Remove",
|
||||
"refresh": "Refresh",
|
||||
"regeneration_indicator.label": "Loading…",
|
||||
"regeneration_indicator.sublabel": "Your home feed is being prepared!",
|
||||
|
@ -606,6 +615,7 @@
|
|||
"status.edited": "Edited {date}",
|
||||
"status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
|
||||
"status.embed": "Embed",
|
||||
"status.expiration.add": "Set status expired time",
|
||||
"status.favourite": "Favourite",
|
||||
"status.filter": "Filter this post",
|
||||
"status.filtered": "Filtered",
|
||||
|
|
|
@ -117,6 +117,7 @@
|
|||
"column.notifications": "通知",
|
||||
"column.pins": "固定された投稿",
|
||||
"column.public": "連合タイムライン",
|
||||
"column.reaction_deck": "絵文字デッキ",
|
||||
"column_back_button.label": "戻る",
|
||||
"column_header.hide_settings": "設定を隠す",
|
||||
"column_header.moveLeft_settings": "カラムを左に移動する",
|
||||
|
@ -135,6 +136,8 @@
|
|||
"compose_form.hashtag_warning": "この投稿は公開設定ではないのでハッシュタグの一覧に表示されません。公開投稿だけがハッシュタグで検索できます。",
|
||||
"compose_form.lock_disclaimer": "あなたのアカウントは{locked}になっていません。誰でもあなたをフォローすることができ、フォロワー限定の投稿を見ることができます。",
|
||||
"compose_form.lock_disclaimer.lock": "承認制",
|
||||
"compose_form.markdown.marked": "Markdown有効",
|
||||
"compose_form.markdown.unmarked": "Markdownは有効になっていません",
|
||||
"compose_form.placeholder": "今なにしてる?",
|
||||
"compose_form.searchability_warning": "検索許可「自分のみ」はkmyblue内の検索でのみ有効です。他のサーバーでは「リアクションした人のみ」と同等に扱われます",
|
||||
"compose_form.poll.add_option": "追加",
|
||||
|
@ -373,6 +376,7 @@
|
|||
"mute_modal.hide_notifications": "このユーザーからの通知を隠しますか?",
|
||||
"mute_modal.indefinite": "無期限",
|
||||
"navigation_bar.about": "概要",
|
||||
"navigation_bar.antennas": "アンテナ",
|
||||
"navigation_bar.blocks": "ブロックしたユーザー",
|
||||
"navigation_bar.bookmarks": "ブックマーク",
|
||||
"navigation_bar.community_timeline": "ローカルタイムライン",
|
||||
|
@ -395,6 +399,7 @@
|
|||
"navigation_bar.pins": "固定した投稿",
|
||||
"navigation_bar.preferences": "ユーザー設定",
|
||||
"navigation_bar.public_timeline": "連合タイムライン",
|
||||
"navigation_bar.reaction_deck": "絵文字デッキ",
|
||||
"navigation_bar.search": "検索",
|
||||
"navigation_bar.security": "セキュリティ",
|
||||
"not_signed_in_indicator.not_signed_in": "この機能を使うにはログインする必要があります。",
|
||||
|
@ -492,6 +497,8 @@
|
|||
"privacy.change": "公開範囲を変更",
|
||||
"privacy.direct.long": "指定された相手のみ閲覧可",
|
||||
"privacy.direct.short": "指定された相手のみ",
|
||||
"privacy.login.long": "ログインユーザーのみ閲覧可、公開",
|
||||
"privacy.login.short": "ログインユーザーのみ",
|
||||
"privacy.private.long": "フォロワーのみ閲覧可",
|
||||
"privacy.private.short": "フォロワーのみ",
|
||||
"privacy.public.long": "誰でも閲覧可、ホーム+ローカル+連合TL",
|
||||
|
@ -502,6 +509,8 @@
|
|||
"privacy.unlisted.short": "未収載",
|
||||
"privacy_policy.last_updated": "{date}に更新",
|
||||
"privacy_policy.title": "プライバシーポリシー",
|
||||
"reaction_deck.add": "追加",
|
||||
"reaction_deck.remove": "削除",
|
||||
"refresh": "更新",
|
||||
"regeneration_indicator.label": "読み込み中…",
|
||||
"regeneration_indicator.sublabel": "ホームタイムラインは準備中です!",
|
||||
|
@ -612,6 +621,7 @@
|
|||
"status.edited_x_times": "{count}回編集",
|
||||
"status.embed": "埋め込み",
|
||||
"status.emoji_reaction": "スタンプ",
|
||||
"status.expiration.add": "時限投稿を設定",
|
||||
"status.favourite": "お気に入り",
|
||||
"status.filter": "この投稿をフィルターする",
|
||||
"status.filtered": "フィルターされました",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue