diff --git a/app/javascript/mastodon/features/compose/components/compose_form.jsx b/app/javascript/mastodon/features/compose/components/compose_form.jsx index ca67c3f63e..468125afb0 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.jsx +++ b/app/javascript/mastodon/features/compose/components/compose_form.jsx @@ -14,7 +14,6 @@ import AutosuggestInput from '../../../components/autosuggest_input'; import AutosuggestTextarea from '../../../components/autosuggest_textarea'; import { Button } from '../../../components/button'; import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container'; -import LanguageDropdown from '../containers/language_dropdown_container'; import PollButtonContainer from '../containers/poll_button_container'; import PrivacyDropdownContainer from '../containers/privacy_dropdown_container'; import SpoilerButtonContainer from '../containers/spoiler_button_container'; @@ -24,6 +23,7 @@ import { countableText } from '../util/counter'; import { CharacterCounter } from './character_counter'; import { EditIndicator } from './edit_indicator'; +import { LanguageDropdown } from './language_dropdown'; import { NavigationBar } from './navigation_bar'; import { PollForm } from "./poll_form"; import { ReplyIndicator } from './reply_indicator'; diff --git a/app/javascript/mastodon/features/compose/components/language_dropdown.jsx b/app/javascript/mastodon/features/compose/components/language_dropdown.jsx index 20fba29ecb..c80aa27e46 100644 --- a/app/javascript/mastodon/features/compose/components/language_dropdown.jsx +++ b/app/javascript/mastodon/features/compose/components/language_dropdown.jsx @@ -1,10 +1,13 @@ import PropTypes from 'prop-types'; -import { PureComponent } from 'react'; +import { useCallback, useRef, useState, useEffect, PureComponent } from 'react'; -import { injectIntl, defineMessages } from 'react-intl'; +import { useIntl, defineMessages } from 'react-intl'; import classNames from 'classnames'; +import { createSelector } from '@reduxjs/toolkit'; +import { Map as ImmutableMap } from 'immutable'; + import { supportsPassiveEvents } from 'detect-passive-events'; import fuzzysort from 'fuzzysort'; import Overlay from 'react-overlays/Overlay'; @@ -12,8 +15,12 @@ import Overlay from 'react-overlays/Overlay'; import CancelIcon from '@/material-icons/400-24px/cancel-fill.svg?react'; import SearchIcon from '@/material-icons/400-24px/search.svg?react'; import TranslateIcon from '@/material-icons/400-24px/translate.svg?react'; +import { changeComposeLanguage } from 'mastodon/actions/compose'; import { Icon } from 'mastodon/components/icon'; import { languages as preloadedLanguages } from 'mastodon/initial_state'; +import { useAppSelector, useAppDispatch } from 'mastodon/store'; + +import { debouncedGuess } from '../util/language_detection'; const messages = defineMessages({ changeLanguage: { id: 'compose.language.change', defaultMessage: 'Change language' }, @@ -237,94 +244,90 @@ class LanguageDropdownMenu extends PureComponent { } -class LanguageDropdown extends PureComponent { +const getFrequentlyUsedLanguages = createSelector([ + state => state.getIn(['settings', 'frequentlyUsedLanguages'], ImmutableMap()), +], languageCounters => ( + languageCounters.keySeq() + .sort((a, b) => languageCounters.get(a) - languageCounters.get(b)) + .reverse() + .toArray() +)); - static propTypes = { - value: PropTypes.string, - frequentlyUsedLanguages: PropTypes.arrayOf(PropTypes.string), - guess: PropTypes.string, - intl: PropTypes.object.isRequired, - onChange: PropTypes.func, - }; +export const LanguageDropdown = () => { + const [open, setOpen] = useState(false); + const [placement, setPlacement] = useState('bottom'); + const [guess, setGuess] = useState(''); + const activeElementRef = useRef(null); + const targetRef = useRef(null); - state = { - open: false, - placement: 'bottom', - }; + const intl = useIntl(); - handleToggle = () => { - if (this.state.open && this.activeElement) { - this.activeElement.focus({ preventScroll: true }); + const dispatch = useAppDispatch(); + const frequentlyUsedLanguages = useAppSelector(getFrequentlyUsedLanguages); + const value = useAppSelector((state) => state.compose.get('language')); + const text = useAppSelector((state) => state.compose.get('text')); + + const current = preloadedLanguages.find(lang => lang[0] === value) ?? []; + + const handleToggle = useCallback(() => { + if (open && activeElementRef.current) + activeElementRef.current.focus({ preventScroll: true }); + + setOpen(!open); + }, [open, setOpen]); + + const handleClose = useCallback(() => { + if (open && activeElementRef.current) + activeElementRef.current.focus({ preventScroll: true }); + + setOpen(false); + }, [open, setOpen]); + + const handleChange = useCallback((value) => { + dispatch(changeComposeLanguage(value)); + }, [dispatch]); + + const handleOverlayEnter = useCallback(({ placement }) => { + setPlacement(placement); + }, [setPlacement]); + + useEffect(() => { + if (text.length > 20) { + debouncedGuess(text, setGuess); + } else { + setGuess(''); } + }, [text, setGuess]); - this.setState({ open: !this.state.open }); - }; + return ( +