diff --git a/app/controllers/api/v1/reaction_deck_controller.rb b/app/controllers/api/v1/reaction_deck_controller.rb
index d3e06f3b09..6229eb89d4 100644
--- a/app/controllers/api/v1/reaction_deck_controller.rb
+++ b/app/controllers/api/v1/reaction_deck_controller.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class Api::V1::ReactionDeckController < Api::BaseController
+ include RoutingHelper
+
before_action -> { doorkeeper_authorize! :read, :'read:lists' }, only: [:index]
before_action -> { doorkeeper_authorize! :write, :'write:lists' }, only: [:create]
@@ -12,31 +14,76 @@ class Api::V1::ReactionDeckController < Api::BaseController
end
def index
- render json: @deck
+ render json: remove_metas(@deck)
end
def create
- (deck_params['emojis'] || []).each do |data|
- raise ArgumentError if data['id'].to_i >= 16 || data['id'].to_i.negative?
+ deck = []
- 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
+ shortcodes = []
+ (deck_params['emojis'] || []).each do |shortcode|
+ shortcodes << shortcode.delete(':')
+ break if shortcodes.length >= User::REACTION_DECK_MAX
end
- @deck = @deck.sort_by { |a| a['id'].to_i }
- current_user.settings['reaction_deck'] = @deck.to_json
+
+ custom_emojis = CustomEmoji.where(shortcode: shortcodes, domain: nil)
+
+ shortcodes.each do |shortcode|
+ custom_emoji = custom_emojis.find { |em| em.shortcode == shortcode }
+
+ emoji_data = {}
+
+ if custom_emoji
+ emoji_data['name'] = custom_emoji.shortcode
+ emoji_data['url'] = full_asset_url(custom_emoji.image.url)
+ emoji_data['static_url'] = full_asset_url(custom_emoji.image.url(:static))
+ emoji_data['width'] = custom_emoji.image_width
+ emoji_data['height'] = custom_emoji.image_height
+ emoji_data['custom_emoji_id'] = custom_emoji.id
+ else
+ emoji_data['name'] = shortcode
+ end
+
+ deck << emoji_data
+ end
+
+ current_user.settings['reaction_deck'] = deck.to_json
current_user.save!
- render json: @deck
+ render json: remove_metas(deck)
end
private
def set_deck
- @deck = current_user.setting_reaction_deck ? JSON.parse(current_user.setting_reaction_deck) : []
+ deck = current_user.setting_reaction_deck ? JSON.parse(current_user.setting_reaction_deck) : []
+ @deck = remove_unused_custom_emojis(deck)
+ end
+
+ def remove_unused_custom_emojis(deck)
+ custom_ids = []
+ deck.each do |item|
+ custom_ids << item['custom_emoji_id'].to_i if item.key?('custom_emoji_id')
+ end
+ custom_emojis = CustomEmoji.where(id: custom_ids)
+
+ deck.each do |item|
+ next if item['custom_emoji_id'].nil?
+
+ custom_emoji = custom_emojis.find { |em| em.id == item['custom_emoji_id'].to_i }
+ remove = custom_emoji.nil? || custom_emoji.disabled
+ item['remove'] = remove if remove
+ end
+ deck.filter { |item| !item.key?('remove') }
+ end
+
+ def remove_metas(deck)
+ deck.tap do |d|
+ d.each do |item|
+ item.delete('custom_emoji_id')
+ # item.delete('id') if item.key?('id')
+ end
+ end
end
def deck_params
diff --git a/app/javascript/mastodon/actions/reaction_deck.js b/app/javascript/mastodon/actions/reaction_deck.js
index 2914e8ceef..11510d5d0d 100644
--- a/app/javascript/mastodon/actions/reaction_deck.js
+++ b/app/javascript/mastodon/actions/reaction_deck.js
@@ -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));
diff --git a/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js b/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js
index 2fc8a6c59f..455157b860 100644
--- a/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js
+++ b/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js
@@ -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;
});
diff --git a/app/javascript/mastodon/features/emoji/emoji_mart_data_light.js b/app/javascript/mastodon/features/emoji/emoji_mart_data_light.js
index 32565b450d..11698937c0 100644
--- a/app/javascript/mastodon/features/emoji/emoji_mart_data_light.js
+++ b/app/javascript/mastodon/features/emoji/emoji_mart_data_light.js
@@ -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,
diff --git a/app/javascript/mastodon/features/interaction_modal/index.jsx b/app/javascript/mastodon/features/interaction_modal/index.jsx
index bc0575740c..e8340235e8 100644
--- a/app/javascript/mastodon/features/interaction_modal/index.jsx
+++ b/app/javascript/mastodon/features/interaction_modal/index.jsx
@@ -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) => ({
diff --git a/app/javascript/mastodon/features/reaction_deck/components/reaction_emoji.jsx b/app/javascript/mastodon/features/reaction_deck/components/reaction_emoji.jsx
index 26036c4901..7fb6f49285 100644
--- a/app/javascript/mastodon/features/reaction_deck/components/reaction_emoji.jsx
+++ b/app/javascript/mastodon/features/reaction_deck/components/reaction_emoji.jsx
@@ -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 (
@@ -72,4 +81,4 @@ class ReactionEmoji extends ImmutablePureComponent {
}
-export default connect(MapStateToProps, mapDispatchToProps)(injectIntl(ReactionEmoji));
+export default connect()(injectIntl(ReactionEmoji));
diff --git a/app/javascript/mastodon/features/reaction_deck/index.jsx b/app/javascript/mastodon/features/reaction_deck/index.jsx
index a457272da5..b1bc4ccfb9 100644
--- a/app/javascript/mastodon/features/reaction_deck/index.jsx
+++ b/app/javascript/mastodon/features/reaction_deck/index.jsx
@@ -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
{children};
+};
+/* 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 =>
-
- )}
+
+
+ {(provided) => (
+
+ {deck.map((emoji, index) => (
+
+ {(provided2) => (
+
+
+
+ )}
+
+ ))}
+ {provided.placeholder}
+
+
+
+ )}
+
+
@@ -83,4 +162,4 @@ class ReactionDeck extends ImmutablePureComponent {
}
-export default connect(mapStateToProps)(injectIntl(ReactionDeck));
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(ReactionDeck));
diff --git a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx
index 81f594ca6e..f97d785c1f 100644
--- a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx
+++ b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx
@@ -61,7 +61,10 @@ class NavigationPanel extends Component {
{signedIn && (
-
+ <>
+ } text={intl.formatMessage(messages.notifications)} />
+
+ >
)}
{!signedIn && explorer}
@@ -82,7 +85,6 @@ class NavigationPanel extends Component {
{signedIn && (
<>
- } text={intl.formatMessage(messages.notifications)} />
>
diff --git a/app/javascript/mastodon/features/ui/components/sign_in_banner.jsx b/app/javascript/mastodon/features/ui/components/sign_in_banner.jsx
index dad36134cf..a4289d45db 100644
--- a/app/javascript/mastodon/features/ui/components/sign_in_banner.jsx
+++ b/app/javascript/mastodon/features/ui/components/sign_in_banner.jsx
@@ -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 = (
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index 1ad38d9c2b..4b386cc154 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -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",
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
index 3da571fb1a..9b49eafd2c 100644
--- a/app/javascript/mastodon/locales/ja.json
+++ b/app/javascript/mastodon/locales/ja.json
@@ -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": "フィルターされました",
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 7b5eff3f5c..5dddda6e0c 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -7547,8 +7547,7 @@ noscript {
.reaction_deck__emoji {
&__wrapper {
display: flex;
-
- margin: 8px 4px;
+ margin: 8px 16px 8px 4px;
height: 32px;
.emojione {
@@ -7561,6 +7560,11 @@ noscript {
margin-right: 24px;
padding: 0;
}
+
+ &__content {
+ display: flex;
+ flex: 1;
+ }
}
}
diff --git a/app/models/status.rb b/app/models/status.rb
index c210633941..2653140864 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -532,8 +532,8 @@ class Status < ApplicationRecord
elsif visibility == 'limited'
self.searchability = Status.searchabilities['limited']
else
- s = [Status.searchabilities[searchability], Status.visibilities[visibility]].max
- s = [s, 3].max
+ s = [Status.searchabilities[searchability], Status.visibilities[visibility] - 1].max
+ s = [s, 3].min
self.searchability = s
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index b903344be9..a0678117d4 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -65,6 +65,8 @@ class User < ApplicationRecord
# to check their feed
ACTIVE_DURATION = ENV.fetch('USER_ACTIVE_DAYS', 7).to_i.days.freeze
+ REACTION_DECK_MAX = 256
+
devise :two_factor_authenticatable,
otp_secret_encryption_key: Rails.configuration.x.otp_secret
diff --git a/app/serializers/rest/instance_serializer.rb b/app/serializers/rest/instance_serializer.rb
index 833fc93d07..5a06f51e59 100644
--- a/app/serializers/rest/instance_serializer.rb
+++ b/app/serializers/rest/instance_serializer.rb
@@ -86,7 +86,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer
},
reaction_deck: {
- max_items: 16,
+ max_emojis: User::REACTION_DECK_MAX,
},
reactions: {
@@ -115,6 +115,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer
:searchability,
:kmyblue_markdown,
:kmyblue_reaction_deck,
+ :kmyblue_visibility_login,
]
capabilities << :profile_search unless Chewy.enabled?
diff --git a/app/serializers/rest/v1/instance_serializer.rb b/app/serializers/rest/v1/instance_serializer.rb
index b5a1d3abe6..537132375e 100644
--- a/app/serializers/rest/v1/instance_serializer.rb
+++ b/app/serializers/rest/v1/instance_serializer.rb
@@ -92,7 +92,7 @@ class REST::V1::InstanceSerializer < ActiveModel::Serializer
},
reaction_deck: {
- max_items: 16,
+ max_emojis: User::REACTION_DECK_MAX,
},
reactions: {
@@ -124,6 +124,7 @@ class REST::V1::InstanceSerializer < ActiveModel::Serializer
:searchability,
:kmyblue_markdown,
:kmyblue_reaction_deck,
+ :kmyblue_visibility_login,
]
capabilities << :profile_search unless Chewy.enabled?
diff --git a/package.json b/package.json
index 13720c56b4..d96e9b189b 100644
--- a/package.json
+++ b/package.json
@@ -88,6 +88,7 @@
"prop-types": "^15.8.1",
"punycode": "^2.3.0",
"react": "^18.2.0",
+ "react-beautiful-dnd": "^13.1.1",
"react-dom": "^18.2.0",
"react-helmet": "^6.1.0",
"react-hotkeys": "^1.1.4",
diff --git a/yarn.lock b/yarn.lock
index ae89f81d37..773070a0a3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1062,7 +1062,7 @@
dependencies:
regenerator-runtime "^0.12.0"
-"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.13.8", "@babel/runtime@^7.2.0", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
+"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.13.8", "@babel/runtime@^7.15.4", "@babel/runtime@^7.2.0", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
version "7.21.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200"
integrity sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==
@@ -2006,7 +2006,7 @@
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64"
integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==
-"@types/hoist-non-react-statics@^3.3.1":
+"@types/hoist-non-react-statics@^3.3.0", "@types/hoist-non-react-statics@^3.3.1":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
@@ -2222,6 +2222,16 @@
dependencies:
react-overlays "*"
+"@types/react-redux@^7.1.20":
+ version "7.1.25"
+ resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.25.tgz#de841631205b24f9dfb4967dd4a7901e048f9a88"
+ integrity sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg==
+ dependencies:
+ "@types/hoist-non-react-statics" "^3.3.0"
+ "@types/react" "*"
+ hoist-non-react-statics "^3.3.0"
+ redux "^4.0.0"
+
"@types/react-router-dom@^5.3.3":
version "5.3.3"
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83"
@@ -4222,6 +4232,13 @@ crypto-random-string@^2.0.0:
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==
+css-box-model@^1.2.0:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1"
+ integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==
+ dependencies:
+ tiny-invariant "^1.0.6"
+
css-declaration-sorter@^6.3.1:
version "6.3.1"
resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz#be5e1d71b7a992433fb1c542c7a1b835e45682ec"
@@ -7940,6 +7957,11 @@ media-typer@0.3.0:
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
+memoize-one@^5.1.1:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
+ integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
+
memoize-one@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045"
@@ -9445,6 +9467,11 @@ quick-lru@^4.0.1:
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==
+raf-schd@^4.0.2:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a"
+ integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==
+
raf@^3.1.0:
version "3.4.1"
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
@@ -9482,6 +9509,19 @@ raw-body@2.5.1:
iconv-lite "0.4.24"
unpipe "1.0.0"
+react-beautiful-dnd@^13.1.1:
+ version "13.1.1"
+ resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz#b0f3087a5840920abf8bb2325f1ffa46d8c4d0a2"
+ integrity sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==
+ dependencies:
+ "@babel/runtime" "^7.9.2"
+ css-box-model "^1.2.0"
+ memoize-one "^5.1.1"
+ raf-schd "^4.0.2"
+ react-redux "^7.2.0"
+ redux "^4.0.4"
+ use-memo-one "^1.1.1"
+
react-dom@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
@@ -9568,7 +9608,7 @@ react-is@^16.13.1, react-is@^16.7.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
-react-is@^17.0.1:
+react-is@^17.0.1, react-is@^17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
@@ -9621,6 +9661,18 @@ react-redux-loading-bar@^5.0.4:
prop-types "^15.7.2"
react-lifecycles-compat "^3.0.4"
+react-redux@^7.2.0:
+ version "7.2.9"
+ resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.9.tgz#09488fbb9416a4efe3735b7235055442b042481d"
+ integrity sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==
+ dependencies:
+ "@babel/runtime" "^7.15.4"
+ "@types/react-redux" "^7.1.20"
+ hoist-non-react-statics "^3.3.2"
+ loose-envify "^1.4.0"
+ prop-types "^15.7.2"
+ react-is "^17.0.2"
+
react-redux@^8.0.4:
version "8.0.5"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.0.5.tgz#e5fb8331993a019b8aaf2e167a93d10af469c7bd"
@@ -9871,7 +9923,7 @@ redux-thunk@^2.4.2:
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.2.tgz#b9d05d11994b99f7a91ea223e8b04cf0afa5ef3b"
integrity sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==
-redux@^4.0.0, redux@^4.2.1:
+redux@^4.0.0, redux@^4.0.4, redux@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197"
integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==
@@ -11281,6 +11333,11 @@ tiny-invariant@^1.0.2:
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
+tiny-invariant@^1.0.6:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642"
+ integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==
+
tiny-queue@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/tiny-queue/-/tiny-queue-0.2.1.tgz#25a67f2c6e253b2ca941977b5ef7442ef97a6046"
@@ -11685,6 +11742,11 @@ use-latest@^1.2.1:
dependencies:
use-isomorphic-layout-effect "^1.1.1"
+use-memo-one@^1.1.1:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.3.tgz#2fd2e43a2169eabc7496960ace8c79efef975e99"
+ integrity sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==
+
use-sync-external-store@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"