From 2fddfbf0c28dce66f848be4d573402add8688b5b Mon Sep 17 00:00:00 2001 From: KMY Date: Tue, 27 May 2025 17:38:45 +0900 Subject: [PATCH] =?UTF-8?q?Test:=20=E6=96=B0=E8=A6=8F=E6=8A=95=E7=A8=BF?= =?UTF-8?q?=E6=99=82=E3=81=AE=E3=81=BF=E5=BC=95=E7=94=A8=E8=A8=AD=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/reaction_deck/__index.jsx | 174 ------------------ .../components/reaction_emoji.jsx | 84 --------- .../mastodon/features/reaction_deck/index.tsx | 62 +++++-- app/javascript/mastodon/locales/en.json | 1 - app/services/post_status_service.rb | 10 +- app/services/update_status_service.rb | 5 +- spec/services/post_status_service_spec.rb | 12 ++ 7 files changed, 67 insertions(+), 281 deletions(-) delete mode 100644 app/javascript/mastodon/features/reaction_deck/__index.jsx delete mode 100644 app/javascript/mastodon/features/reaction_deck/components/reaction_emoji.jsx 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 = ( - {shortCode} - ); - } else { - const html = { __html: emojify(emoji) }; - content = ( - - ); - } - - return ( -
-
-
- -
- {content} -
-
-
-
-
-
- ); - } - -} - -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)