diff --git a/app/javascript/mastodon/features/reaction_deck/__index.jsx b/app/javascript/mastodon/features/reaction_deck/__index.jsx
deleted file mode 100644
index 9fa21041de..0000000000
--- a/app/javascript/mastodon/features/reaction_deck/__index.jsx
+++ /dev/null
@@ -1,174 +0,0 @@
-import PropTypes from 'prop-types';
-import { useEffect, useState } from "react";
-
-import { defineMessages, injectIntl } from 'react-intl';
-
-import { Helmet } from 'react-helmet';
-
-import { createSelector } from '@reduxjs/toolkit';
-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 { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd';
-
-import MenuIcon from '@/material-icons/400-24px/menu.svg?react';
-import EmojiReactionIcon from '@/material-icons/400-24px/mood.svg?react';
-import { updateReactionDeck } from 'mastodon/actions/reaction_deck';
-import { Button } from 'mastodon/components/button';
-import ColumnHeader from 'mastodon/components/column_header';
-import { Icon } from 'mastodon/components/icon';
-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';
-
-
-// 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({
- reaction_deck_add: { id: 'reaction_deck.add', defaultMessage: 'Add' },
- 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),
-});
-
-const mapDispatchToProps = (dispatch) => ({
- onChange: (emojis) => dispatch(updateReactionDeck(emojis)),
-});
-
-class ReactionDeck extends ImmutablePureComponent {
-
- static propTypes = {
- params: PropTypes.object.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;
-
- if (!deck) {
- return (
-
-
-
- );
- }
-
-
- return (
-
-
-
-
-
-
- {(provided) => (
-
- {deck.map((emoji, index) => (
-
- {(provided2) => (
-
-
-
-
-
-
- )}
-
- ))}
- {provided.placeholder}
-
-
-
- )}
-
-
-
-
-
-
-
-
- );
- }
-
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(ReactionDeck));
diff --git a/app/javascript/mastodon/features/reaction_deck/components/reaction_emoji.jsx b/app/javascript/mastodon/features/reaction_deck/components/reaction_emoji.jsx
deleted file mode 100644
index 5aa1144708..0000000000
--- a/app/javascript/mastodon/features/reaction_deck/components/reaction_emoji.jsx
+++ /dev/null
@@ -1,84 +0,0 @@
-import PropTypes from 'prop-types';
-
-import { defineMessages, injectIntl } from 'react-intl';
-
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { connect } from 'react-redux';
-
-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 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 { intl, emojiMap, emoji } = 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 = (
-
- );
- } else {
- const html = { __html: emojify(emoji) };
- content = (
-
- );
- }
-
- return (
-
- );
- }
-
-}
-
-export default connect()(injectIntl(ReactionEmoji));
diff --git a/app/javascript/mastodon/features/reaction_deck/index.tsx b/app/javascript/mastodon/features/reaction_deck/index.tsx
index 8f417268b3..0e5960d75b 100644
--- a/app/javascript/mastodon/features/reaction_deck/index.tsx
+++ b/app/javascript/mastodon/features/reaction_deck/index.tsx
@@ -2,7 +2,8 @@
@typescript-eslint/no-explicit-any,
@typescript-eslint/no-unsafe-call,
@typescript-eslint/no-unsafe-member-access,
- @typescript-eslint/no-unsafe-assignment */
+ @typescript-eslint/no-unsafe-assignment,
+ @typescript-eslint/no-confusing-void-expression */
import type { ReactNode } from 'react';
import { useState, useCallback } from 'react';
@@ -58,18 +59,19 @@ const ReactionEmoji: React.FC<{
index: number;
emoji: string;
emojiMap: any;
- onChange: (index: number, emoji: any) => void;
- onRemove: (index: number) => void;
-}> = ({ index, emoji, emojiMap, onChange, onRemove }) => {
+ overlay?: boolean;
+ onChange?: (index: number, emoji: any) => void;
+ onRemove?: (index: number) => void;
+}> = ({ index, emoji, emojiMap, overlay, onChange, onRemove }) => {
const handleChange = useCallback(
(emoji: any) => {
- onChange(index, emoji);
+ if (onChange) onChange(index, emoji);
},
[index, onChange],
);
const handleRemove = useCallback(() => {
- onRemove(index);
+ if (onRemove) onRemove(index);
}, [index, onRemove]);
const { attributes, listeners, setNodeRef, transform, transition } =
@@ -103,6 +105,10 @@ const ReactionEmoji: React.FC<{
content = ;
}
+ if (overlay) {
+ return {content}
;
+ }
+
return (
deckData.map((item: any) => item.get('name')).toArray();
- /*
- const handleReorder = useCallback((result: any) => {
- const newDeck = deckToArray(deck);
- const deleted = newDeck.splice(result.source.index, 1);
- newDeck.splice(result.destination.index, 0, deleted[0]);
- onChange(newDeck);
- }, [onChange, deck]);
- */
-
const handleChange = useCallback(
(index: number, emoji: any) => {
const newDeck = deckToArray(deck);
@@ -186,6 +183,7 @@ export const ReactionDeck: React.FC<{
);
const [activeId, setActiveId] = useState
(null);
+ const [activeEmoji, setActiveEmoji] = useState(null);
const sensors = useSensors(
useSensor(PointerSensor, {
@@ -203,21 +201,38 @@ export const ReactionDeck: React.FC<{
const { active } = e;
setActiveId(active.id);
+ setActiveEmoji(deck.get(idToNumber(active.id)));
},
- [setActiveId],
+ [setActiveId, setActiveEmoji, deck],
);
+ const idToNumber = (id: UniqueIdentifier): number => {
+ if (typeof id === 'string') {
+ return parseInt(id);
+ }
+ if (typeof id === 'number') {
+ return id;
+ }
+ return 0;
+ };
+
const handleDragEnd = useCallback(
(e: DragEndEvent) => {
const { active, over } = e;
if (over && active.id !== over.id) {
- //onChange(deck);
+ const newDeck = deckToArray(deck);
+ const old = newDeck[idToNumber(active.id)];
+ newDeck[idToNumber(active.id)] = newDeck[idToNumber(over.id)];
+ newDeck[idToNumber(over.id)] = old;
+
+ onChange(newDeck);
}
setActiveId(null);
+ setActiveEmoji(null);
},
- [setActiveId],
+ [setActiveId, setActiveEmoji, deck],
);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
@@ -259,7 +274,16 @@ export const ReactionDeck: React.FC<{
))}
- {activeId ? Test : null}
+
+ {activeId ? (
+
+ ) : null}
+
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index 98c5a86ea5..bda67034b1 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -918,7 +918,6 @@
"privacy_policy.last_updated": "Last updated {date}",
"privacy_policy.title": "Privacy Policy",
"reaction_deck.add": "Add",
- "reaction_deck.remove": "Remove",
"recommended": "Recommended",
"refresh": "Refresh",
"regeneration_indicator.please_stand_by": "Please stand by.",
diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb
index 896b49ce32..9c6efb17b5 100644
--- a/app/services/post_status_service.rb
+++ b/app/services/post_status_service.rb
@@ -42,7 +42,7 @@ class PostStatusService < BaseService
@options = options
@text = @options[:text] || ''
@in_reply_to = @options[:thread]
- @quoted_status = @options[:quoted_status]
+ @quoted_status = @options[:quoted_status] || quoted_status_from_text
@antispam = Antispam.new
@@ -296,7 +296,13 @@ class PostStatusService < BaseService
def quote_url
ProcessReferencesService.extract_quote(@text)
- # TODO: quote
+ end
+
+ def quoted_status_from_text
+ url = quote_url
+ return unless url
+
+ ActivityPub::TagManager.instance.uri_to_resource(url, Status, url: true)
end
def reference_urls
diff --git a/app/services/update_status_service.rb b/app/services/update_status_service.rb
index 0fe08ad209..9b57b6d1cf 100644
--- a/app/services/update_status_service.rb
+++ b/app/services/update_status_service.rb
@@ -138,8 +138,11 @@ class UpdateStatusService < BaseService
end
def quote_url
- ProcessReferencesService.extract_quote(text)
+ # ProcessReferencesService.extract_quote(text)
# TODO: quote
+ return unless @status.quote&.quoted_status
+
+ ActivityPub::TagManager.instance.uri_for(@status.quote.quoted_status)
end
def reference_urls
diff --git a/spec/services/post_status_service_spec.rb b/spec/services/post_status_service_spec.rb
index 51982f52df..630391eb69 100644
--- a/spec/services/post_status_service_spec.rb
+++ b/spec/services/post_status_service_spec.rb
@@ -548,6 +548,18 @@ RSpec.describe PostStatusService do
expect(hashtags_service).to have_received(:call).with(status)
end
+ it 'creates a quote by kmyblue format' do
+ target_status = Fabricate(:status)
+
+ account = Fabricate(:account)
+
+ status = subject.call(account, text: "test status QT #{ActivityPub::TagManager.instance.uri_for(target_status)}")
+
+ expect(status).to be_persisted
+ expect(status.quote).to be_persisted
+ expect(status.quote.quoted_status_id).to eq target_status.id
+ end
+
it 'gets distributed' do
allow(DistributionWorker).to receive(:perform_async)
allow(ActivityPub::DistributionWorker).to receive(:perform_async)