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 <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({
-  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 (
-        <Column>
-          <LoadingIndicator />
-        </Column>
-      );
-    }
-
-
-    return (
-      <Column bindToDocument={!multiColumn}>
-        <ColumnHeader
-          icon='smile-o'
-          iconComponent={EmojiReactionIcon}
-          title={intl.formatMessage(messages.heading)}
-          multiColumn={multiColumn}
-          showBackButton
-        />
-
-        <ScrollableList
-          scrollKey='reaction_deck'
-          bindToDocument={!multiColumn}
-        >
-          <DragDropContext onDragEnd={this.handleReorder}>
-            <StrictModeDroppable droppableId='deckitems'>
-              {(provided) => (
-                <div className='deckitems reaction_deck_container' {...provided.droppableProps} ref={provided.innerRef}>
-                  {deck.map((emoji, index) => (
-                    <Draggable key={index} draggableId={'' + index} index={index}>
-                      {(provided2) => (
-                        <div className='reaction_deck_container__row' ref={provided2.innerRef} {...provided2.draggableProps}>
-                          <span {...provided2.dragHandleProps}>
-                            <Icon id='bars' icon={MenuIcon} className='handle'  />
-                          </span>
-                          <ReactionEmoji emojiMap={emojiMap}
-                            emoji={emoji.get('name')}
-                            index={index}
-                            onChange={this.handleChange}
-                            onRemove={this.handleRemove}
-                            className='reaction_emoji'
-                          />
-                        </div>
-                      )}
-                    </Draggable>
-                  ))}
-                  {provided.placeholder}
-
-                  <Button text={intl.formatMessage(messages.reaction_deck_add)} onClick={this.handleAdd} />
-                </div>
-              )}
-            </StrictModeDroppable>
-          </DragDropContext>
-        </ScrollableList>
-
-        <Helmet>
-          <meta name='robots' content='noindex' />
-        </Helmet>
-      </Column>
-    );
-  }
-
-}
-
-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 = (
-        <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'>
-          <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>
-    );
-  }
-
-}
-
-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 = <span dangerouslySetInnerHTML={html} />;
   }
 
+  if (overlay) {
+    return <div>{content}</div>;
+  }
+
   return (
     <div
       className='reaction_deck_container__row'
@@ -148,15 +154,6 @@ export const ReactionDeck: React.FC<{
   const deckToArray = (deckData: any) =>
     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<UniqueIdentifier | null>(null);
+  const [activeEmoji, setActiveEmoji] = useState<any>(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<{
           ))}
         </SortableContext>
 
-        <DragOverlay>{activeId ? <span>Test</span> : null}</DragOverlay>
+        <DragOverlay>
+          {activeId ? (
+            <ReactionEmoji
+              emojiMap={emojiMap}
+              emoji={activeEmoji.get('name')}
+              index={-1}
+              overlay
+            />
+          ) : null}
+        </DragOverlay>
       </DndContext>
 
       <div>
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)