diff options
Diffstat (limited to 'app/javascript/mastodon/features')
-rw-r--r-- | app/javascript/mastodon/features/about/index.jsx (renamed from app/javascript/mastodon/features/about/index.js) | 12 | ||||
-rw-r--r-- | app/javascript/mastodon/features/account/components/account_note.jsx (renamed from app/javascript/mastodon/features/account/components/account_note.js) | 9 | ||||
-rw-r--r-- | app/javascript/mastodon/features/account/components/featured_tags.jsx (renamed from app/javascript/mastodon/features/account/components/featured_tags.js) | 3 | ||||
-rw-r--r-- | app/javascript/mastodon/features/account/components/follow_request_note.jsx (renamed from app/javascript/mastodon/features/account/components/follow_request_note.js) | 0 | ||||
-rw-r--r-- | app/javascript/mastodon/features/account/components/header.jsx (renamed from app/javascript/mastodon/features/account/components/header.js) | 27 | ||||
-rw-r--r-- | app/javascript/mastodon/features/account/navigation.jsx (renamed from app/javascript/mastodon/features/account/navigation.js) | 3 | ||||
-rw-r--r-- | app/javascript/mastodon/features/account_gallery/components/media_item.jsx (renamed from app/javascript/mastodon/features/account_gallery/components/media_item.js) | 15 | ||||
-rw-r--r-- | app/javascript/mastodon/features/account_gallery/index.jsx (renamed from app/javascript/mastodon/features/account_gallery/index.js) | 15 | ||||
-rw-r--r-- | app/javascript/mastodon/features/account_timeline/components/header.jsx (renamed from app/javascript/mastodon/features/account_timeline/components/header.js) | 32 | ||||
-rw-r--r-- | app/javascript/mastodon/features/account_timeline/components/limited_account_hint.jsx (renamed from app/javascript/mastodon/features/account_timeline/components/limited_account_hint.js) | 5 | ||||
-rw-r--r-- | app/javascript/mastodon/features/account_timeline/components/moved_note.jsx (renamed from app/javascript/mastodon/features/account_timeline/components/moved_note.js) | 0 | ||||
-rw-r--r-- | app/javascript/mastodon/features/account_timeline/containers/header_container.jsx (renamed from app/javascript/mastodon/features/account_timeline/containers/header_container.js) | 0 | ||||
-rw-r--r-- | app/javascript/mastodon/features/account_timeline/index.jsx (renamed from app/javascript/mastodon/features/account_timeline/index.js) | 5 | ||||
-rw-r--r-- | app/javascript/mastodon/features/audio/index.jsx (renamed from app/javascript/mastodon/features/audio/index.js) | 99 | ||||
-rw-r--r-- | app/javascript/mastodon/features/blocks/index.jsx (renamed from app/javascript/mastodon/features/blocks/index.js) | 4 | ||||
-rw-r--r-- | app/javascript/mastodon/features/bookmarked_statuses/index.jsx (renamed from app/javascript/mastodon/features/bookmarked_statuses/index.js) | 14 | ||||
-rw-r--r-- | app/javascript/mastodon/features/closed_registrations_modal/index.jsx (renamed from app/javascript/mastodon/features/closed_registrations_modal/index.js) | 3 | ||||
-rw-r--r-- | app/javascript/mastodon/features/community_timeline/components/column_settings.jsx (renamed from app/javascript/mastodon/features/community_timeline/components/column_settings.js) | 3 | ||||
-rw-r--r-- | app/javascript/mastodon/features/community_timeline/index.jsx (renamed from app/javascript/mastodon/features/community_timeline/index.js) | 14 | ||||
-rw-r--r-- | app/javascript/mastodon/features/compose/components/action_bar.jsx (renamed from app/javascript/mastodon/features/compose/components/action_bar.js) | 7 | ||||
-rw-r--r-- | app/javascript/mastodon/features/compose/components/autosuggest_account.jsx (renamed from app/javascript/mastodon/features/compose/components/autosuggest_account.js) | 0 | ||||
-rw-r--r-- | app/javascript/mastodon/features/compose/components/character_counter.jsx (renamed from app/javascript/mastodon/features/compose/components/character_counter.js) | 0 | ||||
-rw-r--r-- | app/javascript/mastodon/features/compose/components/compose_form.jsx (renamed from app/javascript/mastodon/features/compose/components/compose_form.js) | 37 | ||||
-rw-r--r-- | app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.jsx (renamed from app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js) | 46 | ||||
-rw-r--r-- | app/javascript/mastodon/features/compose/components/language_dropdown.jsx (renamed from app/javascript/mastodon/features/compose/components/language_dropdown.js) | 37 | ||||
-rw-r--r-- | app/javascript/mastodon/features/compose/components/navigation_bar.jsx (renamed from app/javascript/mastodon/features/compose/components/navigation_bar.js) | 0 | ||||
-rw-r--r-- | app/javascript/mastodon/features/compose/components/poll_button.jsx (renamed from app/javascript/mastodon/features/compose/components/poll_button.js) | 6 | ||||
-rw-r--r-- | app/javascript/mastodon/features/compose/components/poll_form.jsx (renamed from app/javascript/mastodon/features/compose/components/poll_form.js) | 29 | ||||
-rw-r--r-- | app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx (renamed from app/javascript/mastodon/features/compose/components/privacy_dropdown.js) | 35 | ||||
-rw-r--r-- | app/javascript/mastodon/features/compose/components/reply_indicator.jsx (renamed from app/javascript/mastodon/features/compose/components/reply_indicator.js) | 7 | ||||
-rw-r--r-- | app/javascript/mastodon/features/compose/components/search.js | 147 | ||||
-rw-r--r-- | app/javascript/mastodon/features/compose/components/search.jsx | 335 | ||||
-rw-r--r-- | app/javascript/mastodon/features/compose/components/search_results.jsx (renamed from app/javascript/mastodon/features/compose/components/search_results.js) | 5 | ||||
-rw-r--r-- | app/javascript/mastodon/features/compose/components/text_icon_button.jsx (renamed from app/javascript/mastodon/features/compose/components/text_icon_button.js) | 0 | ||||
-rw-r--r-- | app/javascript/mastodon/features/compose/components/upload.jsx (renamed from app/javascript/mastodon/features/compose/components/upload.js) | 15 | ||||
-rw-r--r-- | app/javascript/mastodon/features/compose/components/upload_button.jsx (renamed from app/javascript/mastodon/features/compose/components/upload_button.js) | 10 | ||||
-rw-r--r-- | app/javascript/mastodon/features/compose/components/upload_form.jsx (renamed from app/javascript/mastodon/features/compose/components/upload_form.js) | 0 | ||||
-rw-r--r-- | app/javascript/mastodon/features/compose/components/upload_progress.jsx (renamed from app/javascript/mastodon/features/compose/components/upload_progress.js) | 0 | ||||
-rw-r--r-- | app/javascript/mastodon/features/compose/components/warning.jsx (renamed from app/javascript/mastodon/features/compose/components/warning.js) | 0 | ||||
-rw-r--r-- | app/javascript/mastodon/features/compose/containers/compose_form_container.js | 1 | ||||
-rw-r--r-- | app/javascript/mastodon/features/compose/containers/poll_form_container.js | 6 | ||||
-rw-r--r-- | app/javascript/mastodon/features/compose/containers/search_container.js | 22 | ||||
-rw-r--r-- | app/javascript/mastodon/features/compose/containers/sensitive_button_container.jsx (renamed from app/javascript/mastodon/features/compose/containers/sensitive_button_container.js) | 2 | ||||
-rw-r--r-- | app/javascript/mastodon/features/compose/containers/upload_container.js | 3 | ||||
-rw-r--r-- | app/javascript/mastodon/features/compose/containers/warning_container.jsx (renamed from app/javascript/mastodon/features/compose/containers/warning_container.js) | 30 | ||||
-rw-r--r-- | app/javascript/mastodon/features/compose/index.jsx (renamed from app/javascript/mastodon/features/compose/index.js) | 10 | ||||
-rw-r--r-- | app/javascript/mastodon/features/compose/util/counter.js | 2 | ||||
-rw-r--r-- | app/javascript/mastodon/features/direct_timeline/components/conversation.jsx (renamed from app/javascript/mastodon/features/direct_timeline/components/conversation.js) | 25 | ||||
-rw-r--r-- | app/javascript/mastodon/features/direct_timeline/components/conversations_list.jsx (renamed from app/javascript/mastodon/features/direct_timeline/components/conversations_list.js) | 14 | ||||
-rw-r--r-- | app/javascript/mastodon/features/direct_timeline/index.jsx (renamed from app/javascript/mastodon/features/direct_timeline/index.js) | 20 | ||||
-rw-r--r-- | app/javascript/mastodon/features/directory/components/account_card.jsx (renamed from app/javascript/mastodon/features/directory/components/account_card.js) | 13 | ||||
-rw-r--r-- | app/javascript/mastodon/features/directory/index.jsx (renamed from app/javascript/mastodon/features/directory/index.js) | 18 | ||||
-rw-r--r-- | app/javascript/mastodon/features/domain_blocks/index.jsx (renamed from app/javascript/mastodon/features/domain_blocks/index.js) | 4 | ||||
-rw-r--r-- | app/javascript/mastodon/features/emoji/emoji.js | 4 | ||||
-rw-r--r-- | app/javascript/mastodon/features/emoji/emoji_mart_data_light.js | 2 | ||||
-rw-r--r-- | app/javascript/mastodon/features/emoji/emoji_unicode_mapping_light.js | 6 | ||||
-rw-r--r-- | app/javascript/mastodon/features/emoji/emoji_utils.js | 12 | ||||
-rw-r--r-- | app/javascript/mastodon/features/explore/components/story.jsx (renamed from app/javascript/mastodon/features/explore/components/story.js) | 0 | ||||
-rw-r--r-- | app/javascript/mastodon/features/explore/index.jsx (renamed from app/javascript/mastodon/features/explore/index.js) | 12 | ||||
-rw-r--r-- | app/javascript/mastodon/features/explore/links.jsx (renamed from app/javascript/mastodon/features/explore/links.js) | 3 | ||||
-rw-r--r-- | app/javascript/mastodon/features/explore/results.jsx (renamed from app/javascript/mastodon/features/explore/results.js) | 6 | ||||
-rw-r--r-- | app/javascript/mastodon/features/explore/statuses.jsx (renamed from app/javascript/mastodon/features/explore/statuses.js) | 5 | ||||
-rw-r--r-- | app/javascript/mastodon/features/explore/suggestions.jsx (renamed from app/javascript/mastodon/features/explore/suggestions.js) | 3 | ||||
-rw-r--r-- | app/javascript/mastodon/features/explore/tags.jsx (renamed from app/javascript/mastodon/features/explore/tags.js) | 3 | ||||
-rw-r--r-- | app/javascript/mastodon/features/favourited_statuses/index.jsx (renamed from app/javascript/mastodon/features/favourited_statuses/index.js) | 14 | ||||
-rw-r--r-- | app/javascript/mastodon/features/favourites/index.jsx (renamed from app/javascript/mastodon/features/favourites/index.js) | 6 | ||||
-rw-r--r-- | app/javascript/mastodon/features/filters/added_to_filter.jsx (renamed from app/javascript/mastodon/features/filters/added_to_filter.js) | 3 | ||||
-rw-r--r-- | app/javascript/mastodon/features/filters/select_filter.jsx (renamed from app/javascript/mastodon/features/filters/select_filter.js) | 22 | ||||
-rw-r--r-- | app/javascript/mastodon/features/follow_recommendations/components/account.jsx (renamed from app/javascript/mastodon/features/follow_recommendations/components/account.js) | 8 | ||||
-rw-r--r-- | app/javascript/mastodon/features/follow_recommendations/index.jsx (renamed from app/javascript/mastodon/features/follow_recommendations/index.js) | 5 | ||||
-rw-r--r-- | app/javascript/mastodon/features/follow_requests/components/account_authorize.jsx (renamed from app/javascript/mastodon/features/follow_requests/components/account_authorize.js) | 3 | ||||
-rw-r--r-- | app/javascript/mastodon/features/follow_requests/index.jsx (renamed from app/javascript/mastodon/features/follow_requests/index.js) | 16 | ||||
-rw-r--r-- | app/javascript/mastodon/features/followed_tags/index.jsx | 89 | ||||
-rw-r--r-- | app/javascript/mastodon/features/followers/index.jsx (renamed from app/javascript/mastodon/features/followers/index.js) | 3 | ||||
-rw-r--r-- | app/javascript/mastodon/features/following/index.jsx (renamed from app/javascript/mastodon/features/following/index.js) | 3 | ||||
-rw-r--r-- | app/javascript/mastodon/features/generic_not_found/index.jsx (renamed from app/javascript/mastodon/features/generic_not_found/index.js) | 0 | ||||
-rw-r--r-- | app/javascript/mastodon/features/getting_started/components/announcements.jsx (renamed from app/javascript/mastodon/features/getting_started/components/announcements.js) | 34 | ||||
-rw-r--r-- | app/javascript/mastodon/features/getting_started/components/trends.jsx (renamed from app/javascript/mastodon/features/getting_started/components/trends.js) | 0 | ||||
-rw-r--r-- | app/javascript/mastodon/features/getting_started/index.jsx (renamed from app/javascript/mastodon/features/getting_started/index.js) | 6 | ||||
-rw-r--r-- | app/javascript/mastodon/features/hashtag_timeline/components/column_settings.jsx (renamed from app/javascript/mastodon/features/hashtag_timeline/components/column_settings.js) | 3 | ||||
-rw-r--r-- | app/javascript/mastodon/features/hashtag_timeline/index.jsx (renamed from app/javascript/mastodon/features/hashtag_timeline/index.js) | 26 | ||||
-rw-r--r-- | app/javascript/mastodon/features/home_timeline/components/column_settings.jsx (renamed from app/javascript/mastodon/features/home_timeline/components/column_settings.js) | 3 | ||||
-rw-r--r-- | app/javascript/mastodon/features/home_timeline/index.jsx (renamed from app/javascript/mastodon/features/home_timeline/index.js) | 16 | ||||
-rw-r--r-- | app/javascript/mastodon/features/interaction_modal/index.jsx (renamed from app/javascript/mastodon/features/interaction_modal/index.js) | 11 | ||||
-rw-r--r-- | app/javascript/mastodon/features/keyboard_shortcuts/index.jsx (renamed from app/javascript/mastodon/features/keyboard_shortcuts/index.js) | 3 | ||||
-rw-r--r-- | app/javascript/mastodon/features/list_adder/components/account.jsx (renamed from app/javascript/mastodon/features/list_adder/components/account.js) | 5 | ||||
-rw-r--r-- | app/javascript/mastodon/features/list_adder/components/list.jsx (renamed from app/javascript/mastodon/features/list_adder/components/list.js) | 4 | ||||
-rw-r--r-- | app/javascript/mastodon/features/list_adder/index.jsx (renamed from app/javascript/mastodon/features/list_adder/index.js) | 4 | ||||
-rw-r--r-- | app/javascript/mastodon/features/list_editor/components/account.jsx (renamed from app/javascript/mastodon/features/list_editor/components/account.js) | 4 | ||||
-rw-r--r-- | app/javascript/mastodon/features/list_editor/components/edit_list_form.jsx (renamed from app/javascript/mastodon/features/list_editor/components/edit_list_form.js) | 10 | ||||
-rw-r--r-- | app/javascript/mastodon/features/list_editor/components/search.jsx (renamed from app/javascript/mastodon/features/list_editor/components/search.js) | 12 | ||||
-rw-r--r-- | app/javascript/mastodon/features/list_editor/index.jsx (renamed from app/javascript/mastodon/features/list_editor/index.js) | 6 | ||||
-rw-r--r-- | app/javascript/mastodon/features/list_timeline/index.jsx (renamed from app/javascript/mastodon/features/list_timeline/index.js) | 24 | ||||
-rw-r--r-- | app/javascript/mastodon/features/lists/components/new_list_form.jsx (renamed from app/javascript/mastodon/features/lists/components/new_list_form.js) | 10 | ||||
-rw-r--r-- | app/javascript/mastodon/features/lists/index.jsx (renamed from app/javascript/mastodon/features/lists/index.js) | 4 | ||||
-rw-r--r-- | app/javascript/mastodon/features/mutes/index.jsx (renamed from app/javascript/mastodon/features/mutes/index.js) | 4 | ||||
-rw-r--r-- | app/javascript/mastodon/features/notifications/components/clear_column_button.jsx (renamed from app/javascript/mastodon/features/notifications/components/clear_column_button.js) | 2 | ||||
-rw-r--r-- | app/javascript/mastodon/features/notifications/components/column_settings.jsx (renamed from app/javascript/mastodon/features/notifications/components/column_settings.js) | 2 | ||||
-rw-r--r-- | app/javascript/mastodon/features/notifications/components/filter_bar.jsx (renamed from app/javascript/mastodon/features/notifications/components/filter_bar.js) | 3 | ||||
-rw-r--r-- | app/javascript/mastodon/features/notifications/components/follow_request.jsx (renamed from app/javascript/mastodon/features/notifications/components/follow_request.js) | 9 | ||||
-rw-r--r-- | app/javascript/mastodon/features/notifications/components/grant_permission_button.jsx (renamed from app/javascript/mastodon/features/notifications/components/grant_permission_button.js) | 2 | ||||
-rw-r--r-- | app/javascript/mastodon/features/notifications/components/notification.jsx (renamed from app/javascript/mastodon/features/notifications/components/notification.js) | 58 | ||||
-rw-r--r-- | app/javascript/mastodon/features/notifications/components/notifications_permission_banner.jsx (renamed from app/javascript/mastodon/features/notifications/components/notifications_permission_banner.js) | 8 | ||||
-rw-r--r-- | app/javascript/mastodon/features/notifications/components/report.jsx (renamed from app/javascript/mastodon/features/notifications/components/report.js) | 3 | ||||
-rw-r--r-- | app/javascript/mastodon/features/notifications/components/setting_toggle.jsx (renamed from app/javascript/mastodon/features/notifications/components/setting_toggle.js) | 4 | ||||
-rw-r--r-- | app/javascript/mastodon/features/notifications/containers/column_settings_container.js | 3 | ||||
-rw-r--r-- | app/javascript/mastodon/features/notifications/containers/notification_container.js | 2 | ||||
-rw-r--r-- | app/javascript/mastodon/features/notifications/index.jsx (renamed from app/javascript/mastodon/features/notifications/index.js) | 16 | ||||
-rw-r--r-- | app/javascript/mastodon/features/picture_in_picture/components/footer.jsx (renamed from app/javascript/mastodon/features/picture_in_picture/components/footer.js) | 10 | ||||
-rw-r--r-- | app/javascript/mastodon/features/picture_in_picture/components/header.jsx (renamed from app/javascript/mastodon/features/picture_in_picture/components/header.js) | 4 | ||||
-rw-r--r-- | app/javascript/mastodon/features/picture_in_picture/index.jsx (renamed from app/javascript/mastodon/features/picture_in_picture/index.js) | 5 | ||||
-rw-r--r-- | app/javascript/mastodon/features/pinned_statuses/index.jsx (renamed from app/javascript/mastodon/features/pinned_statuses/index.js) | 8 | ||||
-rw-r--r-- | app/javascript/mastodon/features/privacy_policy/index.jsx (renamed from app/javascript/mastodon/features/privacy_policy/index.js) | 3 | ||||
-rw-r--r-- | app/javascript/mastodon/features/public_timeline/components/column_settings.jsx (renamed from app/javascript/mastodon/features/public_timeline/components/column_settings.js) | 3 | ||||
-rw-r--r-- | app/javascript/mastodon/features/public_timeline/index.jsx (renamed from app/javascript/mastodon/features/public_timeline/index.js) | 14 | ||||
-rw-r--r-- | app/javascript/mastodon/features/reblogs/index.jsx (renamed from app/javascript/mastodon/features/reblogs/index.js) | 6 | ||||
-rw-r--r-- | app/javascript/mastodon/features/report/category.jsx (renamed from app/javascript/mastodon/features/report/category.js) | 4 | ||||
-rw-r--r-- | app/javascript/mastodon/features/report/comment.jsx (renamed from app/javascript/mastodon/features/report/comment.js) | 3 | ||||
-rw-r--r-- | app/javascript/mastodon/features/report/components/option.jsx (renamed from app/javascript/mastodon/features/report/components/option.js) | 6 | ||||
-rw-r--r-- | app/javascript/mastodon/features/report/components/status_check_box.jsx (renamed from app/javascript/mastodon/features/report/components/status_check_box.js) | 3 | ||||
-rw-r--r-- | app/javascript/mastodon/features/report/rules.jsx (renamed from app/javascript/mastodon/features/report/rules.js) | 3 | ||||
-rw-r--r-- | app/javascript/mastodon/features/report/statuses.jsx (renamed from app/javascript/mastodon/features/report/statuses.js) | 3 | ||||
-rw-r--r-- | app/javascript/mastodon/features/report/thanks.jsx (renamed from app/javascript/mastodon/features/report/thanks.js) | 3 | ||||
-rw-r--r-- | app/javascript/mastodon/features/standalone/compose/index.jsx (renamed from app/javascript/mastodon/features/standalone/compose/index.js) | 2 | ||||
-rw-r--r-- | app/javascript/mastodon/features/status/components/action_bar.jsx (renamed from app/javascript/mastodon/features/status/components/action_bar.js) | 44 | ||||
-rw-r--r-- | app/javascript/mastodon/features/status/components/card.jsx (renamed from app/javascript/mastodon/features/status/components/card.js) | 11 | ||||
-rw-r--r-- | app/javascript/mastodon/features/status/components/detailed_status.jsx (renamed from app/javascript/mastodon/features/status/components/detailed_status.js) | 30 | ||||
-rw-r--r-- | app/javascript/mastodon/features/status/index.jsx (renamed from app/javascript/mastodon/features/status/index.js) | 106 | ||||
-rw-r--r-- | app/javascript/mastodon/features/subscribed_languages_modal/index.jsx (renamed from app/javascript/mastodon/features/subscribed_languages_modal/index.js) | 6 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/__tests__/column-test.jsx (renamed from app/javascript/mastodon/features/ui/components/__tests__/column-test.js) | 0 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/actions_modal.jsx (renamed from app/javascript/mastodon/features/ui/components/actions_modal.js) | 4 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/audio_modal.jsx (renamed from app/javascript/mastodon/features/ui/components/audio_modal.js) | 8 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/block_modal.jsx (renamed from app/javascript/mastodon/features/ui/components/block_modal.js) | 12 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/boost_modal.jsx (renamed from app/javascript/mastodon/features/ui/components/boost_modal.js) | 12 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/bundle.jsx (renamed from app/javascript/mastodon/features/ui/components/bundle.js) | 10 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/bundle_column_error.jsx (renamed from app/javascript/mastodon/features/ui/components/bundle_column_error.js) | 11 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/bundle_modal_error.jsx (renamed from app/javascript/mastodon/features/ui/components/bundle_modal_error.js) | 4 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/column.jsx (renamed from app/javascript/mastodon/features/ui/components/column.js) | 6 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/column_header.jsx (renamed from app/javascript/mastodon/features/ui/components/column_header.js) | 2 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/column_link.jsx (renamed from app/javascript/mastodon/features/ui/components/column_link.js) | 0 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/column_loading.jsx (renamed from app/javascript/mastodon/features/ui/components/column_loading.js) | 0 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/column_subheading.jsx (renamed from app/javascript/mastodon/features/ui/components/column_subheading.js) | 0 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/columns_area.jsx (renamed from app/javascript/mastodon/features/ui/components/columns_area.js) | 12 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/compare_history_modal.jsx (renamed from app/javascript/mastodon/features/ui/components/compare_history_modal.js) | 14 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/compose_panel.jsx (renamed from app/javascript/mastodon/features/ui/components/compose_panel.js) | 7 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/confirmation_modal.jsx (renamed from app/javascript/mastodon/features/ui/components/confirmation_modal.js) | 11 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/disabled_account_banner.jsx (renamed from app/javascript/mastodon/features/ui/components/disabled_account_banner.js) | 6 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/drawer_loading.jsx (renamed from app/javascript/mastodon/features/ui/components/drawer_loading.js) | 0 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/embed_modal.jsx (renamed from app/javascript/mastodon/features/ui/components/embed_modal.js) | 9 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/filter_modal.jsx (renamed from app/javascript/mastodon/features/ui/components/filter_modal.js) | 4 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/focal_point_modal.jsx (renamed from app/javascript/mastodon/features/ui/components/focal_point_modal.js) | 44 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/follow_requests_column_link.jsx (renamed from app/javascript/mastodon/features/ui/components/follow_requests_column_link.js) | 4 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/header.jsx (renamed from app/javascript/mastodon/features/ui/components/header.js) | 4 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/image_loader.jsx (renamed from app/javascript/mastodon/features/ui/components/image_loader.js) | 13 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/image_modal.jsx (renamed from app/javascript/mastodon/features/ui/components/image_modal.js) | 3 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/link_footer.jsx (renamed from app/javascript/mastodon/features/ui/components/link_footer.js) | 22 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/list_panel.jsx (renamed from app/javascript/mastodon/features/ui/components/list_panel.js) | 4 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/media_modal.jsx (renamed from app/javascript/mastodon/features/ui/components/media_modal.js) | 29 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/modal_loading.jsx (renamed from app/javascript/mastodon/features/ui/components/modal_loading.js) | 0 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/modal_root.jsx (renamed from app/javascript/mastodon/features/ui/components/modal_root.js) | 12 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/mute_modal.jsx (renamed from app/javascript/mastodon/features/ui/components/mute_modal.js) | 14 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/navigation_panel.jsx (renamed from app/javascript/mastodon/features/ui/components/navigation_panel.js) | 7 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/report_modal.jsx (renamed from app/javascript/mastodon/features/ui/components/report_modal.js) | 6 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/sign_in_banner.jsx (renamed from app/javascript/mastodon/features/ui/components/sign_in_banner.js) | 2 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/upload_area.jsx (renamed from app/javascript/mastodon/features/ui/components/upload_area.js) | 2 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/video_modal.jsx (renamed from app/javascript/mastodon/features/ui/components/video_modal.js) | 13 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/zoomable_image.jsx (renamed from app/javascript/mastodon/features/ui/components/zoomable_image.js) | 36 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/index.jsx (renamed from app/javascript/mastodon/features/ui/index.js) | 86 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/util/async-components.js | 4 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/util/react_router_helpers.jsx (renamed from app/javascript/mastodon/features/ui/util/react_router_helpers.js) | 6 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/util/reduced_motion.jsx (renamed from app/javascript/mastodon/features/ui/util/reduced_motion.js) | 2 | ||||
-rw-r--r-- | app/javascript/mastodon/features/video/index.jsx (renamed from app/javascript/mastodon/features/video/index.js) | 65 |
172 files changed, 1442 insertions, 1023 deletions
diff --git a/app/javascript/mastodon/features/about/index.js b/app/javascript/mastodon/features/about/index.jsx index e59f73738..bb35f3203 100644 --- a/app/javascript/mastodon/features/about/index.js +++ b/app/javascript/mastodon/features/about/index.jsx @@ -59,7 +59,7 @@ class Section extends React.PureComponent { const { collapsed } = this.state; this.setState({ collapsed: !collapsed }, () => onOpen && onOpen()); - } + }; render () { const { title, children } = this.props; @@ -67,7 +67,7 @@ class Section extends React.PureComponent { return ( <div className={classNames('about__section', { active: !collapsed })}> - <div className='about__section__title' role='button' tabIndex='0' onClick={this.handleClick}> + <div className='about__section__title' role='button' tabIndex={0} onClick={this.handleClick}> <Icon id={collapsed ? 'chevron-right' : 'chevron-down'} fixedWidth /> {title} </div> @@ -80,8 +80,6 @@ class Section extends React.PureComponent { } -export default @connect(mapStateToProps) -@injectIntl class About extends React.PureComponent { static propTypes = { @@ -106,7 +104,7 @@ class About extends React.PureComponent { handleDomainBlocksOpen = () => { const { dispatch } = this.props; dispatch(fetchDomainBlocks()); - } + }; render () { const { multiColumn, intl, server, extendedDescription, domainBlocks } = this.props; @@ -125,7 +123,7 @@ class About extends React.PureComponent { <div className='about__meta__column'> <h4><FormattedMessage id='server_banner.administered_by' defaultMessage='Administered by:' /></h4> - <Account id={server.getIn(['contact', 'account', 'id'])} size={36} /> + <Account id={server.getIn(['contact', 'account', 'id'])} size={36} minimal /> </div> <hr className='about__meta__divider' /> @@ -217,3 +215,5 @@ class About extends React.PureComponent { } } + +export default connect(mapStateToProps)(injectIntl(About)); diff --git a/app/javascript/mastodon/features/account/components/account_note.js b/app/javascript/mastodon/features/account/components/account_note.jsx index 1787ce1ab..5201ebd4d 100644 --- a/app/javascript/mastodon/features/account/components/account_note.js +++ b/app/javascript/mastodon/features/account/components/account_note.jsx @@ -43,7 +43,6 @@ class InlineAlert extends React.PureComponent { } -export default @injectIntl class AccountNote extends ImmutablePureComponent { static propTypes = { @@ -90,7 +89,7 @@ class AccountNote extends ImmutablePureComponent { setTextareaRef = c => { this.textarea = c; - } + }; handleChange = e => { this.setState({ value: e.target.value, saving: false }); @@ -114,13 +113,13 @@ class AccountNote extends ImmutablePureComponent { } }); } - } + }; handleBlur = () => { if (this._isDirty()) { this._save(); } - } + }; _save (showMessage = true) { this.setState({ saving: true }, () => this.props.onSave(this.state.value)); @@ -168,3 +167,5 @@ class AccountNote extends ImmutablePureComponent { } } + +export default injectIntl(AccountNote); diff --git a/app/javascript/mastodon/features/account/components/featured_tags.js b/app/javascript/mastodon/features/account/components/featured_tags.jsx index 24a3f2171..52aa232ca 100644 --- a/app/javascript/mastodon/features/account/components/featured_tags.js +++ b/app/javascript/mastodon/features/account/components/featured_tags.jsx @@ -10,7 +10,6 @@ const messages = defineMessages({ empty: { id: 'account.featured_tags.last_status_never', defaultMessage: 'No posts' }, }); -export default @injectIntl class FeaturedTags extends ImmutablePureComponent { static contextTypes = { @@ -50,3 +49,5 @@ class FeaturedTags extends ImmutablePureComponent { } } + +export default injectIntl(FeaturedTags); diff --git a/app/javascript/mastodon/features/account/components/follow_request_note.js b/app/javascript/mastodon/features/account/components/follow_request_note.jsx index 300ae4266..300ae4266 100644 --- a/app/javascript/mastodon/features/account/components/follow_request_note.js +++ b/app/javascript/mastodon/features/account/components/follow_request_note.jsx diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.jsx index 2481e4783..72eb7e6b6 100644 --- a/app/javascript/mastodon/features/account/components/header.js +++ b/app/javascript/mastodon/features/account/components/header.jsx @@ -28,7 +28,7 @@ const messages = defineMessages({ linkVerifiedOn: { id: 'account.link_verified_on', defaultMessage: 'Ownership of this link was checked on {date}' }, account_locked: { id: 'account.locked_info', defaultMessage: 'This account privacy status is set to locked. The owner manually reviews who can follow them.' }, mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' }, - direct: { id: 'account.direct', defaultMessage: 'Direct message @{name}' }, + direct: { id: 'account.direct', defaultMessage: 'Privately mention @{name}' }, unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' }, block: { id: 'account.block', defaultMessage: 'Block @{name}' }, mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' }, @@ -46,6 +46,7 @@ const messages = defineMessages({ follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' }, favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' }, lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' }, + followed_tags: { id: 'navigation_bar.followed_tags', defaultMessage: 'Followed hashtags' }, blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' }, domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Blocked domains' }, mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' }, @@ -75,7 +76,6 @@ const dateFormatOptions = { minute: '2-digit', }; -export default @injectIntl class Header extends ImmutablePureComponent { static contextTypes = { @@ -108,7 +108,7 @@ class Header extends ImmutablePureComponent { openEditProfile = () => { window.open('/settings/profile', '_blank'); - } + }; isStatusesPageActive = (match, location) => { if (!match) { @@ -116,7 +116,7 @@ class Header extends ImmutablePureComponent { } return !location.pathname.match(/\/(followers|following)\/?$/); - } + }; handleMouseEnter = ({ currentTarget }) => { if (autoPlayGif) { @@ -129,7 +129,7 @@ class Header extends ImmutablePureComponent { let emoji = emojis[i]; emoji.src = emoji.getAttribute('data-original'); } - } + }; handleMouseLeave = ({ currentTarget }) => { if (autoPlayGif) { @@ -142,14 +142,14 @@ class Header extends ImmutablePureComponent { let emoji = emojis[i]; emoji.src = emoji.getAttribute('data-static'); } - } + }; handleAvatarClick = e => { if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { e.preventDefault(); this.props.onOpenAvatar(); } - } + }; handleShare = () => { const { account } = this.props; @@ -160,7 +160,7 @@ class Header extends ImmutablePureComponent { }).catch((e) => { if (e.name !== 'AbortError') console.error(e); }); - } + }; render () { const { account, hidden, intl, domain } = this.props; @@ -193,7 +193,7 @@ class Header extends ImmutablePureComponent { } if (account.getIn(['relationship', 'requested']) || account.getIn(['relationship', 'following'])) { - bellBtn = <IconButton icon='bell-o' size={24} active={account.getIn(['relationship', 'notifying'])} title={intl.formatMessage(account.getIn(['relationship', 'notifying']) ? messages.disableNotifications : messages.enableNotifications, { name: account.get('username') })} onClick={this.props.onNotifyToggle} />; + bellBtn = <IconButton icon={account.getIn(['relationship', 'notifying']) ? 'bell' : 'bell-o'} size={24} active={account.getIn(['relationship', 'notifying'])} title={intl.formatMessage(account.getIn(['relationship', 'notifying']) ? messages.disableNotifications : messages.enableNotifications, { name: account.get('username') })} onClick={this.props.onNotifyToggle} />; } if (me !== account.get('id')) { @@ -242,6 +242,7 @@ class Header extends ImmutablePureComponent { menu.push({ text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' }); menu.push({ text: intl.formatMessage(messages.favourites), to: '/favourites' }); menu.push({ text: intl.formatMessage(messages.lists), to: '/lists' }); + menu.push({ text: intl.formatMessage(messages.followed_tags), to: '/followed_tags' }); menu.push(null); menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' }); menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' }); @@ -294,9 +295,9 @@ class Header extends ImmutablePureComponent { if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) { menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${account.get('id')}` }); } - if (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION) { - menu.push({ text: intl.formatMessage(messages.admin_domain, { domain: remoteDomain }), href: `/admin/instances/${remoteDomain}` }); - } + if (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION) { + menu.push({ text: intl.formatMessage(messages.admin_domain, { domain: remoteDomain }), href: `/admin/instances/${remoteDomain}` }); + } } const content = { __html: account.get('note_emojified') }; @@ -417,3 +418,5 @@ class Header extends ImmutablePureComponent { } } + +export default injectIntl(Header); diff --git a/app/javascript/mastodon/features/account/navigation.js b/app/javascript/mastodon/features/account/navigation.jsx index eb9ff9a95..07dc3757c 100644 --- a/app/javascript/mastodon/features/account/navigation.js +++ b/app/javascript/mastodon/features/account/navigation.jsx @@ -19,7 +19,6 @@ const mapStateToProps = (state, { match: { params: { acct } } }) => { }; }; -export default @connect(mapStateToProps) class AccountNavigation extends React.PureComponent { static propTypes = { @@ -50,3 +49,5 @@ class AccountNavigation extends React.PureComponent { } } + +export default connect(mapStateToProps)(AccountNavigation); diff --git a/app/javascript/mastodon/features/account_gallery/components/media_item.js b/app/javascript/mastodon/features/account_gallery/components/media_item.jsx index 13fd7fe03..00526016d 100644 --- a/app/javascript/mastodon/features/account_gallery/components/media_item.js +++ b/app/javascript/mastodon/features/account_gallery/components/media_item.jsx @@ -22,20 +22,20 @@ export default class MediaItem extends ImmutablePureComponent { handleImageLoad = () => { this.setState({ loaded: true }); - } + }; handleMouseEnter = e => { if (this.hoverToPlay()) { e.target.play(); } - } + }; handleMouseLeave = e => { if (this.hoverToPlay()) { e.target.pause(); e.target.currentTime = 0; } - } + }; hoverToPlay () { return !autoPlayGif && ['gifv', 'video'].indexOf(this.props.attachment.get('type')) !== -1; @@ -51,7 +51,7 @@ export default class MediaItem extends ImmutablePureComponent { this.setState({ visible: true }); } } - } + }; render () { const { attachment, displayWidth } = this.props; @@ -74,8 +74,9 @@ export default class MediaItem extends ImmutablePureComponent { if (['audio', 'video'].includes(attachment.get('type'))) { content = ( <img - src={attachment.get('preview_url') || attachment.getIn(['account', 'avatar_static'])} + src={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])} alt={attachment.get('description')} + lang={status.get('language')} onLoad={this.handleImageLoad} /> ); @@ -95,6 +96,7 @@ export default class MediaItem extends ImmutablePureComponent { <img src={attachment.get('preview_url')} alt={attachment.get('description')} + lang={status.get('language')} style={{ objectPosition: `${x}% ${y}%` }} onLoad={this.handleImageLoad} /> @@ -105,6 +107,7 @@ export default class MediaItem extends ImmutablePureComponent { className='media-gallery__item-gifv-thumbnail' aria-label={attachment.get('description')} title={attachment.get('description')} + lang={status.get('language')} role='application' src={attachment.get('url')} onMouseEnter={this.handleMouseEnter} @@ -130,7 +133,7 @@ export default class MediaItem extends ImmutablePureComponent { return ( <div className='account-gallery__item' style={{ width, height }}> - <a className='media-gallery__item-thumbnail' href={`/@${status.getIn(['account', 'acct'])}\/${status.get('id')}`} onClick={this.handleClick} title={title} target='_blank' rel='noopener noreferrer'> + <a className='media-gallery__item-thumbnail' href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} onClick={this.handleClick} title={title} target='_blank' rel='noopener noreferrer'> <Blurhash hash={attachment.get('blurhash')} className={classNames('media-gallery__preview', { 'media-gallery__preview--hidden': visible && loaded })} diff --git a/app/javascript/mastodon/features/account_gallery/index.js b/app/javascript/mastodon/features/account_gallery/index.jsx index dc7556a9a..db76507db 100644 --- a/app/javascript/mastodon/features/account_gallery/index.js +++ b/app/javascript/mastodon/features/account_gallery/index.jsx @@ -47,7 +47,7 @@ class LoadMoreMedia extends ImmutablePureComponent { handleLoadMore = () => { this.props.onLoadMore(this.props.maxId); - } + }; render () { return ( @@ -60,7 +60,6 @@ class LoadMoreMedia extends ImmutablePureComponent { } -export default @connect(mapStateToProps) class AccountGallery extends ImmutablePureComponent { static propTypes = { @@ -114,7 +113,7 @@ class AccountGallery extends ImmutablePureComponent { if (this.props.hasMore) { this.handleLoadMore(this.props.attachments.size > 0 ? this.props.attachments.last().getIn(['status', 'id']) : undefined); } - } + }; handleScroll = e => { const { scrollTop, scrollHeight, clientHeight } = e.target; @@ -123,7 +122,7 @@ class AccountGallery extends ImmutablePureComponent { if (150 > offset && !this.props.isLoading) { this.handleScrollToBottom(); } - } + }; handleLoadMore = maxId => { this.props.dispatch(expandAccountMediaTimeline(this.props.accountId, { maxId })); @@ -132,7 +131,7 @@ class AccountGallery extends ImmutablePureComponent { handleLoadOlder = e => { e.preventDefault(); this.handleScrollToBottom(); - } + }; handleOpenMedia = attachment => { const { dispatch } = this.props; @@ -148,13 +147,13 @@ class AccountGallery extends ImmutablePureComponent { dispatch(openModal('MEDIA', { media, index, statusId })); } - } + }; handleRef = c => { if (c) { this.setState({ width: c.offsetWidth }); } - } + }; render () { const { attachments, isLoading, hasMore, isAccount, multiColumn, blockedBy, suspended } = this.props; @@ -226,3 +225,5 @@ class AccountGallery extends ImmutablePureComponent { } } + +export default connect(mapStateToProps)(AccountGallery); diff --git a/app/javascript/mastodon/features/account_timeline/components/header.js b/app/javascript/mastodon/features/account_timeline/components/header.jsx index d67e307ff..bffa5554b 100644 --- a/app/javascript/mastodon/features/account_timeline/components/header.js +++ b/app/javascript/mastodon/features/account_timeline/components/header.jsx @@ -36,35 +36,35 @@ export default class Header extends ImmutablePureComponent { handleFollow = () => { this.props.onFollow(this.props.account); - } + }; handleBlock = () => { this.props.onBlock(this.props.account); - } + }; handleMention = () => { this.props.onMention(this.props.account, this.context.router.history); - } + }; handleDirect = () => { this.props.onDirect(this.props.account, this.context.router.history); - } + }; handleReport = () => { this.props.onReport(this.props.account); - } + }; handleReblogToggle = () => { this.props.onReblogToggle(this.props.account); - } + }; handleNotifyToggle = () => { this.props.onNotifyToggle(this.props.account); - } + }; handleMute = () => { this.props.onMute(this.props.account); - } + }; handleBlockDomain = () => { const domain = this.props.account.get('acct').split('@')[1]; @@ -72,7 +72,7 @@ export default class Header extends ImmutablePureComponent { if (!domain) return; this.props.onBlockDomain(domain); - } + }; handleUnblockDomain = () => { const domain = this.props.account.get('acct').split('@')[1]; @@ -80,31 +80,31 @@ export default class Header extends ImmutablePureComponent { if (!domain) return; this.props.onUnblockDomain(domain); - } + }; handleEndorseToggle = () => { this.props.onEndorseToggle(this.props.account); - } + }; handleAddToList = () => { this.props.onAddToList(this.props.account); - } + }; handleEditAccountNote = () => { this.props.onEditAccountNote(this.props.account); - } + }; handleChangeLanguages = () => { this.props.onChangeLanguages(this.props.account); - } + }; handleInteractionModal = () => { this.props.onInteractionModal(this.props.account); - } + }; handleOpenAvatar = () => { this.props.onOpenAvatar(this.props.account); - } + }; render () { const { account, hidden, hideTabs } = this.props; diff --git a/app/javascript/mastodon/features/account_timeline/components/limited_account_hint.js b/app/javascript/mastodon/features/account_timeline/components/limited_account_hint.jsx index 80b967642..e6a111470 100644 --- a/app/javascript/mastodon/features/account_timeline/components/limited_account_hint.js +++ b/app/javascript/mastodon/features/account_timeline/components/limited_account_hint.jsx @@ -14,13 +14,12 @@ const mapDispatchToProps = (dispatch, { accountId }) => ({ }); -export default @connect(() => {}, mapDispatchToProps) class LimitedAccountHint extends React.PureComponent { static propTypes = { accountId: PropTypes.string.isRequired, reveal: PropTypes.func, - } + }; render () { const { reveal } = this.props; @@ -34,3 +33,5 @@ class LimitedAccountHint extends React.PureComponent { } } + +export default connect(() => {}, mapDispatchToProps)(LimitedAccountHint); diff --git a/app/javascript/mastodon/features/account_timeline/components/moved_note.js b/app/javascript/mastodon/features/account_timeline/components/moved_note.jsx index daff47a9d..daff47a9d 100644 --- a/app/javascript/mastodon/features/account_timeline/components/moved_note.js +++ b/app/javascript/mastodon/features/account_timeline/components/moved_note.jsx diff --git a/app/javascript/mastodon/features/account_timeline/containers/header_container.js b/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx index f53cd2480..f53cd2480 100644 --- a/app/javascript/mastodon/features/account_timeline/containers/header_container.js +++ b/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx diff --git a/app/javascript/mastodon/features/account_timeline/index.js b/app/javascript/mastodon/features/account_timeline/index.jsx index bdb430a33..d77563684 100644 --- a/app/javascript/mastodon/features/account_timeline/index.js +++ b/app/javascript/mastodon/features/account_timeline/index.jsx @@ -64,7 +64,6 @@ RemoteHint.propTypes = { url: PropTypes.string.isRequired, }; -export default @connect(mapStateToProps) class AccountTimeline extends ImmutablePureComponent { static propTypes = { @@ -145,7 +144,7 @@ class AccountTimeline extends ImmutablePureComponent { handleLoadMore = maxId => { this.props.dispatch(expandAccountTimeline(this.props.accountId, { maxId, withReplies: this.props.withReplies, tagged: this.props.params.tagged })); - } + }; render () { const { accountId, statusIds, featuredStatusIds, isLoading, hasMore, blockedBy, suspended, isAccount, hidden, multiColumn, remote, remoteUrl } = this.props; @@ -206,3 +205,5 @@ class AccountTimeline extends ImmutablePureComponent { } } + +export default connect(mapStateToProps)(AccountTimeline); diff --git a/app/javascript/mastodon/features/audio/index.js b/app/javascript/mastodon/features/audio/index.jsx index 00854d0e4..df40b48f0 100644 --- a/app/javascript/mastodon/features/audio/index.js +++ b/app/javascript/mastodon/features/audio/index.jsx @@ -1,12 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; -import { formatTime } from 'mastodon/features/video'; +import { formatTime, getPointerPosition, fileNameFromURL } from 'mastodon/features/video'; import Icon from 'mastodon/components/icon'; import classNames from 'classnames'; -import { throttle } from 'lodash'; -import { getPointerPosition, fileNameFromURL } from 'mastodon/features/video'; -import { debounce } from 'lodash'; +import { throttle, debounce } from 'lodash'; import Visualizer from './visualizer'; import { displayMedia, useBlurhash } from '../../initial_state'; import Blurhash from '../../components/blurhash'; @@ -24,12 +22,12 @@ const messages = defineMessages({ const TICK_SIZE = 10; const PADDING = 180; -export default @injectIntl class Audio extends React.PureComponent { static propTypes = { src: PropTypes.string.isRequired, alt: PropTypes.string, + lang: PropTypes.string, poster: PropTypes.string, duration: PropTypes.number, width: PropTypes.number, @@ -59,7 +57,7 @@ class Audio extends React.PureComponent { duration: null, paused: true, muted: false, - volume: 0.5, + volume: 1, dragging: false, revealed: this.props.visible !== undefined ? this.props.visible : (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all'), }; @@ -75,13 +73,13 @@ class Audio extends React.PureComponent { if (this.player) { this._setDimensions(); } - } + }; _pack() { return { src: this.props.src, - volume: this.audio.volume, - muted: this.audio.muted, + volume: this.state.volume, + muted: this.state.muted, currentTime: this.audio.currentTime, poster: this.props.poster, backgroundColor: this.props.backgroundColor, @@ -105,25 +103,26 @@ class Audio extends React.PureComponent { setSeekRef = c => { this.seek = c; - } + }; setVolumeRef = c => { this.volume = c; - } + }; setAudioRef = c => { this.audio = c; if (this.audio) { - this.setState({ volume: this.audio.volume, muted: this.audio.muted }); + this.audio.volume = 1; + this.audio.muted = false; } - } + }; setCanvasRef = c => { this.canvas = c; this.visualizer.setCanvas(c); - } + }; componentDidMount () { window.addEventListener('scroll', this.handleScroll); @@ -162,7 +161,7 @@ class Audio extends React.PureComponent { } else { this.setState({ paused: true }, () => this.audio.pause()); } - } + }; handleResize = debounce(() => { if (this.player) { @@ -180,7 +179,7 @@ class Audio extends React.PureComponent { } this._renderCanvas(); - } + }; handlePause = () => { this.setState({ paused: true }); @@ -188,7 +187,7 @@ class Audio extends React.PureComponent { if (this.audioContext) { this.audioContext.suspend(); } - } + }; handleProgress = () => { const lastTimeRange = this.audio.buffered.length - 1; @@ -196,15 +195,17 @@ class Audio extends React.PureComponent { if (lastTimeRange > -1) { this.setState({ buffer: Math.ceil(this.audio.buffered.end(lastTimeRange) / this.audio.duration * 100) }); } - } + }; toggleMute = () => { const muted = !this.state.muted; this.setState({ muted }, () => { - this.audio.muted = muted; + if (this.gainNode) { + this.gainNode.gain.value = muted ? 0 : this.state.volume; + } }); - } + }; toggleReveal = () => { if (this.props.onToggleVisibility) { @@ -212,7 +213,7 @@ class Audio extends React.PureComponent { } else { this.setState({ revealed: !this.state.revealed }); } - } + }; handleVolumeMouseDown = e => { document.addEventListener('mousemove', this.handleMouseVolSlide, true); @@ -224,14 +225,14 @@ class Audio extends React.PureComponent { e.preventDefault(); e.stopPropagation(); - } + }; handleVolumeMouseUp = () => { document.removeEventListener('mousemove', this.handleMouseVolSlide, true); document.removeEventListener('mouseup', this.handleVolumeMouseUp, true); document.removeEventListener('touchmove', this.handleMouseVolSlide, true); document.removeEventListener('touchend', this.handleVolumeMouseUp, true); - } + }; handleMouseDown = e => { document.addEventListener('mousemove', this.handleMouseMove, true); @@ -245,7 +246,7 @@ class Audio extends React.PureComponent { e.preventDefault(); e.stopPropagation(); - } + }; handleMouseUp = () => { document.removeEventListener('mousemove', this.handleMouseMove, true); @@ -255,7 +256,7 @@ class Audio extends React.PureComponent { this.setState({ dragging: false }); this.audio.play(); - } + }; handleMouseMove = throttle(e => { const { x } = getPointerPosition(this.seek, e); @@ -273,14 +274,16 @@ class Audio extends React.PureComponent { currentTime: this.audio.currentTime, duration: this.audio.duration, }); - } + }; handleMouseVolSlide = throttle(e => { const { x } = getPointerPosition(this.volume, e); if(!isNaN(x)) { this.setState({ volume: x }, () => { - this.audio.volume = x; + if (this.gainNode) { + this.gainNode.gain.value = this.state.muted ? 0 : x; + } }); } }, 15); @@ -306,41 +309,38 @@ class Audio extends React.PureComponent { handleMouseEnter = () => { this.setState({ hovered: true }); - } + }; handleMouseLeave = () => { this.setState({ hovered: false }); - } + }; handleLoadedData = () => { - const { autoPlay, currentTime, volume, muted } = this.props; + const { autoPlay, currentTime } = this.props; if (currentTime) { this.audio.currentTime = currentTime; } - if (volume !== undefined) { - this.audio.volume = volume; - } - - if (muted !== undefined) { - this.audio.muted = muted; - } - if (autoPlay) { this.togglePlay(); } - } + }; _initAudioContext () { const AudioContext = window.AudioContext || window.webkitAudioContext; const context = new AudioContext(); const source = context.createMediaElementSource(this.audio); + const gainNode = context.createGain(); + + gainNode.gain.value = this.state.muted ? 0 : this.state.volume; this.visualizer.setAudioContext(context, source); - source.connect(context.destination); + source.connect(gainNode); + gainNode.connect(context.destination); this.audioContext = context; + this.gainNode = gainNode; } handleDownload = () => { @@ -359,7 +359,7 @@ class Audio extends React.PureComponent { }).catch(err => { console.error(err); }); - } + }; _renderCanvas () { requestAnimationFrame(() => { @@ -430,7 +430,7 @@ class Audio extends React.PureComponent { e.stopPropagation(); this.togglePlay(); } - } + }; handleKeyDown = e => { switch(e.key) { @@ -455,10 +455,10 @@ class Audio extends React.PureComponent { this.seekBy(10); break; } - } + }; render () { - const { src, intl, alt, editable, autoPlay, sensitive, blurhash } = this.props; + const { src, intl, alt, lang, editable, autoPlay, sensitive, blurhash } = this.props; const { paused, muted, volume, currentTime, duration, buffer, dragging, revealed } = this.state; const progress = Math.min((currentTime / duration) * 100, 100); @@ -470,7 +470,7 @@ class Audio extends React.PureComponent { } return ( - <div className={classNames('audio-player', { editable, inactive: !revealed })} ref={this.setPlayerRef} style={{ backgroundColor: this._getBackgroundColor(), color: this._getForegroundColor(), width: '100%', height: this.props.fullscreen ? '100%' : (this.state.height || this.props.height) }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} tabIndex='0' onKeyDown={this.handleKeyDown}> + <div className={classNames('audio-player', { editable, inactive: !revealed })} ref={this.setPlayerRef} style={{ backgroundColor: this._getBackgroundColor(), color: this._getForegroundColor(), width: '100%', height: this.props.fullscreen ? '100%' : (this.state.height || this.props.height) }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} tabIndex={0} onKeyDown={this.handleKeyDown}> <Blurhash hash={blurhash} @@ -493,7 +493,7 @@ class Audio extends React.PureComponent { <canvas role='button' - tabIndex='0' + tabIndex={0} className='audio-player__canvas' width={this.state.width} height={this.state.height} @@ -503,6 +503,7 @@ class Audio extends React.PureComponent { onKeyDown={this.handleAudioKeyDown} title={alt} aria-label={alt} + lang={lang} /> <div className={classNames('spoiler-button', { 'spoiler-button--hidden': revealed || editable })}> @@ -525,7 +526,7 @@ class Audio extends React.PureComponent { <span className={classNames('video-player__seek__handle', { active: dragging })} - tabIndex='0' + tabIndex={0} style={{ left: `${progress}%`, backgroundColor: this._getAccentColor() }} onKeyDown={this.handleAudioKeyDown} /> @@ -542,7 +543,7 @@ class Audio extends React.PureComponent { <span className='video-player__volume__handle' - tabIndex='0' + tabIndex={0} style={{ left: `${volume * 100}%`, backgroundColor: this._getAccentColor() }} /> </div> @@ -567,3 +568,5 @@ class Audio extends React.PureComponent { } } + +export default injectIntl(Audio); diff --git a/app/javascript/mastodon/features/blocks/index.js b/app/javascript/mastodon/features/blocks/index.jsx index e00f2b60e..38616ba84 100644 --- a/app/javascript/mastodon/features/blocks/index.js +++ b/app/javascript/mastodon/features/blocks/index.jsx @@ -22,8 +22,6 @@ const mapStateToProps = state => ({ isLoading: state.getIn(['user_lists', 'blocks', 'isLoading'], true), }); -export default @connect(mapStateToProps) -@injectIntl class Blocks extends ImmutablePureComponent { static propTypes = { @@ -77,3 +75,5 @@ class Blocks extends ImmutablePureComponent { } } + +export default connect(mapStateToProps)(injectIntl(Blocks)); diff --git a/app/javascript/mastodon/features/bookmarked_statuses/index.js b/app/javascript/mastodon/features/bookmarked_statuses/index.jsx index 097be17c9..46fff856e 100644 --- a/app/javascript/mastodon/features/bookmarked_statuses/index.js +++ b/app/javascript/mastodon/features/bookmarked_statuses/index.jsx @@ -22,8 +22,6 @@ const mapStateToProps = state => ({ hasMore: !!state.getIn(['status_lists', 'bookmarks', 'next']), }); -export default @connect(mapStateToProps) -@injectIntl class Bookmarks extends ImmutablePureComponent { static propTypes = { @@ -48,24 +46,24 @@ class Bookmarks extends ImmutablePureComponent { } else { dispatch(addColumn('BOOKMARKS', {})); } - } + }; handleMove = (dir) => { const { columnId, dispatch } = this.props; dispatch(moveColumn(columnId, dir)); - } + }; handleHeaderClick = () => { this.column.scrollTop(); - } + }; setRef = c => { this.column = c; - } + }; handleLoadMore = debounce(() => { this.props.dispatch(expandBookmarkedStatuses()); - }, 300, { leading: true }) + }, 300, { leading: true }); render () { const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props; @@ -106,3 +104,5 @@ class Bookmarks extends ImmutablePureComponent { } } + +export default connect(mapStateToProps)(injectIntl(Bookmarks)); diff --git a/app/javascript/mastodon/features/closed_registrations_modal/index.js b/app/javascript/mastodon/features/closed_registrations_modal/index.jsx index e6540e825..40df804f2 100644 --- a/app/javascript/mastodon/features/closed_registrations_modal/index.js +++ b/app/javascript/mastodon/features/closed_registrations_modal/index.jsx @@ -9,7 +9,6 @@ const mapStateToProps = state => ({ message: state.getIn(['server', 'server', 'registrations', 'message']), }); -export default @connect(mapStateToProps) class ClosedRegistrationsModal extends ImmutablePureComponent { componentDidMount () { @@ -73,3 +72,5 @@ class ClosedRegistrationsModal extends ImmutablePureComponent { } } + +export default connect(mapStateToProps)(ClosedRegistrationsModal); diff --git a/app/javascript/mastodon/features/community_timeline/components/column_settings.js b/app/javascript/mastodon/features/community_timeline/components/column_settings.jsx index 0cb6db883..d6181919a 100644 --- a/app/javascript/mastodon/features/community_timeline/components/column_settings.js +++ b/app/javascript/mastodon/features/community_timeline/components/column_settings.jsx @@ -4,7 +4,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import { injectIntl, FormattedMessage } from 'react-intl'; import SettingToggle from '../../notifications/components/setting_toggle'; -export default @injectIntl class ColumnSettings extends React.PureComponent { static propTypes = { @@ -27,3 +26,5 @@ class ColumnSettings extends React.PureComponent { } } + +export default injectIntl(ColumnSettings); diff --git a/app/javascript/mastodon/features/community_timeline/index.js b/app/javascript/mastodon/features/community_timeline/index.jsx index 7b3f8845f..e41d11bb5 100644 --- a/app/javascript/mastodon/features/community_timeline/index.js +++ b/app/javascript/mastodon/features/community_timeline/index.jsx @@ -30,8 +30,6 @@ const mapStateToProps = (state, { columnId }) => { }; }; -export default @connect(mapStateToProps) -@injectIntl class CommunityTimeline extends React.PureComponent { static contextTypes = { @@ -60,16 +58,16 @@ class CommunityTimeline extends React.PureComponent { } else { dispatch(addColumn('COMMUNITY', { other: { onlyMedia } })); } - } + }; handleMove = (dir) => { const { columnId, dispatch } = this.props; dispatch(moveColumn(columnId, dir)); - } + }; handleHeaderClick = () => { this.column.scrollTop(); - } + }; componentDidMount () { const { dispatch, onlyMedia } = this.props; @@ -109,13 +107,13 @@ class CommunityTimeline extends React.PureComponent { setRef = c => { this.column = c; - } + }; handleLoadMore = maxId => { const { dispatch, onlyMedia } = this.props; dispatch(expandCommunityTimeline({ maxId, onlyMedia })); - } + }; render () { const { intl, hasUnread, columnId, multiColumn, onlyMedia } = this.props; @@ -158,3 +156,5 @@ class CommunityTimeline extends React.PureComponent { } } + +export default connect(mapStateToProps)(injectIntl(CommunityTimeline)); diff --git a/app/javascript/mastodon/features/compose/components/action_bar.js b/app/javascript/mastodon/features/compose/components/action_bar.jsx index ceed928bf..13760582e 100644 --- a/app/javascript/mastodon/features/compose/components/action_bar.js +++ b/app/javascript/mastodon/features/compose/components/action_bar.jsx @@ -11,6 +11,7 @@ const messages = defineMessages({ follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' }, favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' }, lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' }, + followed_tags: { id: 'navigation_bar.followed_tags', defaultMessage: 'Followed hashtags' }, blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' }, domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' }, mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' }, @@ -19,7 +20,6 @@ const messages = defineMessages({ bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' }, }); -export default @injectIntl class ActionBar extends React.PureComponent { static propTypes = { @@ -30,7 +30,7 @@ class ActionBar extends React.PureComponent { handleLogout = () => { this.props.onLogout(); - } + }; render () { const { intl } = this.props; @@ -45,6 +45,7 @@ class ActionBar extends React.PureComponent { menu.push({ text: intl.formatMessage(messages.favourites), to: '/favourites' }); menu.push({ text: intl.formatMessage(messages.bookmarks), to: '/bookmarks' }); menu.push({ text: intl.formatMessage(messages.lists), to: '/lists' }); + menu.push({ text: intl.formatMessage(messages.followed_tags), to: '/followed_tags' }); menu.push(null); menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' }); menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' }); @@ -63,3 +64,5 @@ class ActionBar extends React.PureComponent { } } + +export default injectIntl(ActionBar); diff --git a/app/javascript/mastodon/features/compose/components/autosuggest_account.js b/app/javascript/mastodon/features/compose/components/autosuggest_account.jsx index 1451be0e6..1451be0e6 100644 --- a/app/javascript/mastodon/features/compose/components/autosuggest_account.js +++ b/app/javascript/mastodon/features/compose/components/autosuggest_account.jsx diff --git a/app/javascript/mastodon/features/compose/components/character_counter.js b/app/javascript/mastodon/features/compose/components/character_counter.jsx index 0ecfc9141..0ecfc9141 100644 --- a/app/javascript/mastodon/features/compose/components/character_counter.js +++ b/app/javascript/mastodon/features/compose/components/character_counter.jsx diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.jsx index ebdd55d33..a40eb87e3 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.js +++ b/app/javascript/mastodon/features/compose/components/compose_form.jsx @@ -32,7 +32,6 @@ const messages = defineMessages({ saveChanges: { id: 'compose_form.save_changes', defaultMessage: 'Save changes' }, }); -export default @injectIntl class ComposeForm extends ImmutablePureComponent { static contextTypes = { @@ -65,6 +64,7 @@ class ComposeForm extends ImmutablePureComponent { anyMedia: PropTypes.bool, isInReply: PropTypes.bool, singleColumn: PropTypes.bool, + lang: PropTypes.string, }; static defaultProps = { @@ -73,17 +73,17 @@ class ComposeForm extends ImmutablePureComponent { handleChange = (e) => { this.props.onChange(e.target.value); - } + }; handleKeyDown = (e) => { if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) { this.handleSubmit(); } - } + }; getFulltextForCharacterCounting = () => { return [this.props.spoiler? this.props.spoilerText: '', countableText(this.props.text)].join(''); - } + }; canSubmit = () => { const { isSubmitting, isChangingUpload, isUploading, anyMedia } = this.props; @@ -91,7 +91,7 @@ class ComposeForm extends ImmutablePureComponent { const isOnlyWhitespace = fulltext.length !== 0 && fulltext.trim().length === 0; return !(isSubmitting || isUploading || isChangingUpload || length(fulltext) > maxChars || (isOnlyWhitespace && !anyMedia)); - } + }; handleSubmit = (e) => { if (this.props.text !== this.autosuggestTextarea.textarea.value) { @@ -109,27 +109,27 @@ class ComposeForm extends ImmutablePureComponent { if (e) { e.preventDefault(); } - } + }; onSuggestionsClearRequested = () => { this.props.onClearSuggestions(); - } + }; onSuggestionsFetchRequested = (token) => { this.props.onFetchSuggestions(token); - } + }; onSuggestionSelected = (tokenStart, token, value) => { this.props.onSuggestionSelected(tokenStart, token, value, ['text']); - } + }; onSpoilerSuggestionSelected = (tokenStart, token, value) => { this.props.onSuggestionSelected(tokenStart, token, value, ['spoiler_text']); - } + }; handleChangeSpoilerText = (e) => { this.props.onChangeSpoilerText(e.target.value); - } + }; handleFocus = () => { if (this.composeForm && !this.props.singleColumn) { @@ -138,7 +138,7 @@ class ComposeForm extends ImmutablePureComponent { this.composeForm.scrollIntoView(); } } - } + }; componentDidMount () { this._updateFocusAndSelection({ }); @@ -184,15 +184,15 @@ class ComposeForm extends ImmutablePureComponent { this.autosuggestTextarea.textarea.focus(); } } - } + }; setAutosuggestTextarea = (c) => { this.autosuggestTextarea = c; - } + }; setSpoilerText = (c) => { this.spoilerText = c; - } + }; setRef = c => { this.composeForm = c; @@ -204,7 +204,7 @@ class ComposeForm extends ImmutablePureComponent { const needsSpace = data.custom && position > 0 && !allowedAroundShortCode.includes(text[position - 1]); this.props.onPickEmoji(position, data, needsSpace); - } + }; render () { const { intl, onPaste, autoFocus } = this.props; @@ -241,6 +241,8 @@ class ComposeForm extends ImmutablePureComponent { searchTokens={[':']} id='cw-spoiler-input' className='spoiler-input__input' + lang={this.props.lang} + spellCheck /> </div> @@ -258,6 +260,7 @@ class ComposeForm extends ImmutablePureComponent { onSuggestionSelected={this.onSuggestionSelected} onPaste={onPaste} autoFocus={autoFocus} + lang={this.props.lang} > <EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} /> @@ -296,3 +299,5 @@ class ComposeForm extends ImmutablePureComponent { } } + +export default injectIntl(ComposeForm); diff --git a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.jsx index 76c9cda81..4fb131b47 100644 --- a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js +++ b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.jsx @@ -57,7 +57,7 @@ class ModifierPickerMenu extends React.PureComponent { handleClick = e => { this.props.onSelect(e.currentTarget.getAttribute('data-index') * 1); - } + }; componentWillReceiveProps (nextProps) { if (nextProps.active) { @@ -75,7 +75,7 @@ class ModifierPickerMenu extends React.PureComponent { if (this.node && !this.node.contains(e.target)) { this.props.onClose(); } - } + }; attachListeners () { document.addEventListener('click', this.handleDocumentClick, false); @@ -89,7 +89,7 @@ class ModifierPickerMenu extends React.PureComponent { setRef = c => { this.node = c; - } + }; render () { const { active } = this.props; @@ -124,12 +124,12 @@ class ModifierPicker extends React.PureComponent { } else { this.props.onOpen(); } - } + }; handleSelect = modifier => { this.props.onChange(modifier); this.props.onClose(); - } + }; render () { const { active, modifier } = this.props; @@ -144,8 +144,7 @@ class ModifierPicker extends React.PureComponent { } -@injectIntl -class EmojiPickerMenu extends React.PureComponent { +class EmojiPickerMenuImpl extends React.PureComponent { static propTypes = { custom_emojis: ImmutablePropTypes.list, @@ -174,7 +173,7 @@ class EmojiPickerMenu extends React.PureComponent { if (this.node && !this.node.contains(e.target)) { this.props.onClose(); } - } + }; componentDidMount () { document.addEventListener('click', this.handleDocumentClick, false); @@ -198,7 +197,7 @@ class EmojiPickerMenu extends React.PureComponent { setRef = c => { this.node = c; - } + }; getI18n = () => { const { intl } = this.props; @@ -219,7 +218,7 @@ class EmojiPickerMenu extends React.PureComponent { custom: intl.formatMessage(messages.custom), }, }; - } + }; handleClick = (emoji, event) => { if (!emoji.native) { @@ -229,19 +228,19 @@ class EmojiPickerMenu extends React.PureComponent { this.props.onClose(); } this.props.onPick(emoji); - } + }; handleModifierOpen = () => { this.setState({ modifierOpen: true }); - } + }; handleModifierClose = () => { this.setState({ modifierOpen: false }); - } + }; handleModifierChange = modifier => { this.props.onSkinTone(modifier); - } + }; render () { const { loading, style, intl, custom_emojis, skinTone, frequentlyUsedEmojis } = this.props; @@ -305,7 +304,8 @@ class EmojiPickerMenu extends React.PureComponent { } -export default @injectIntl +const EmojiPickerMenu = injectIntl(EmojiPickerMenuImpl); + class EmojiPickerDropdown extends React.PureComponent { static propTypes = { @@ -325,7 +325,7 @@ class EmojiPickerDropdown extends React.PureComponent { setRef = (c) => { this.dropdown = c; - } + }; onShowDropdown = () => { this.setState({ active: true }); @@ -342,11 +342,11 @@ class EmojiPickerDropdown extends React.PureComponent { this.setState({ loading: false, active: false }); }); } - } + }; onHideDropdown = () => { this.setState({ active: false }); - } + }; onToggle = (e) => { if (!this.state.loading && (!e.key || e.key === 'Enter')) { @@ -356,21 +356,21 @@ class EmojiPickerDropdown extends React.PureComponent { this.onShowDropdown(e); } } - } + }; handleKeyDown = e => { if (e.key === 'Escape') { this.onHideDropdown(); } - } + }; setTargetRef = c => { this.target = c; - } + }; findTarget = () => { return this.target; - } + }; render () { const { intl, onPickEmoji, onSkinTone, skinTone, frequentlyUsedEmojis, button } = this.props; @@ -409,3 +409,5 @@ class EmojiPickerDropdown extends React.PureComponent { } } + +export default injectIntl(EmojiPickerDropdown); diff --git a/app/javascript/mastodon/features/compose/components/language_dropdown.js b/app/javascript/mastodon/features/compose/components/language_dropdown.jsx index 2dd406b4b..08542e3a9 100644 --- a/app/javascript/mastodon/features/compose/components/language_dropdown.js +++ b/app/javascript/mastodon/features/compose/components/language_dropdown.jsx @@ -40,7 +40,7 @@ class LanguageDropdownMenu extends React.PureComponent { if (this.node && !this.node.contains(e.target)) { this.props.onClose(); } - } + }; componentDidMount () { document.addEventListener('click', this.handleDocumentClick, false); @@ -63,15 +63,15 @@ class LanguageDropdownMenu extends React.PureComponent { setRef = c => { this.node = c; - } + }; setListRef = c => { this.listNode = c; - } + }; handleSearchChange = ({ target }) => { this.setState({ searchValue: target.value }); - } + }; search () { const { languages, value, frequentlyUsedLanguages } = this.props; @@ -122,7 +122,7 @@ class LanguageDropdownMenu extends React.PureComponent { this.props.onClose(); this.props.onChange(value); - } + }; handleKeyDown = e => { const { onClose } = this.props; @@ -163,7 +163,7 @@ class LanguageDropdownMenu extends React.PureComponent { e.preventDefault(); e.stopPropagation(); } - } + }; handleSearchKeyDown = e => { const { onChange, onClose } = this.props; @@ -199,21 +199,21 @@ class LanguageDropdownMenu extends React.PureComponent { break; } - } + }; handleClear = () => { this.setState({ searchValue: '' }); - } + }; renderItem = lang => { const { value } = this.props; return ( - <div key={lang[0]} role='option' tabIndex='0' data-index={lang[0]} className={classNames('language-dropdown__dropdown__results__item', { active: lang[0] === value })} aria-selected={lang[0] === value} onClick={this.handleClick} onKeyDown={this.handleKeyDown}> - <span className='language-dropdown__dropdown__results__item__native-name'>{lang[2]}</span> <span className='language-dropdown__dropdown__results__item__common-name'>({lang[1]})</span> + <div key={lang[0]} role='option' tabIndex={0} data-index={lang[0]} className={classNames('language-dropdown__dropdown__results__item', { active: lang[0] === value })} aria-selected={lang[0] === value} onClick={this.handleClick} onKeyDown={this.handleKeyDown}> + <span className='language-dropdown__dropdown__results__item__native-name' lang={lang[0]}>{lang[2]}</span> <span className='language-dropdown__dropdown__results__item__common-name'>({lang[1]})</span> </div> ); - } + }; render () { const { intl } = this.props; @@ -237,7 +237,6 @@ class LanguageDropdownMenu extends React.PureComponent { } -export default @injectIntl class LanguageDropdown extends React.PureComponent { static propTypes = { @@ -259,7 +258,7 @@ class LanguageDropdown extends React.PureComponent { } this.setState({ open: !this.state.open }); - } + }; handleClose = () => { const { value, onClose } = this.props; @@ -270,24 +269,24 @@ class LanguageDropdown extends React.PureComponent { this.setState({ open: false }); onClose(value); - } + }; handleChange = value => { const { onChange } = this.props; onChange(value); - } + }; setTargetRef = c => { this.target = c; - } + }; findTarget = () => { return this.target; - } + }; handleOverlayEnter = (state) => { this.setState({ placement: state.placement }); - } + }; render () { const { value, intl, frequentlyUsedLanguages } = this.props; @@ -325,3 +324,5 @@ class LanguageDropdown extends React.PureComponent { } } + +export default injectIntl(LanguageDropdown); diff --git a/app/javascript/mastodon/features/compose/components/navigation_bar.js b/app/javascript/mastodon/features/compose/components/navigation_bar.jsx index be979af50..be979af50 100644 --- a/app/javascript/mastodon/features/compose/components/navigation_bar.js +++ b/app/javascript/mastodon/features/compose/components/navigation_bar.jsx diff --git a/app/javascript/mastodon/features/compose/components/poll_button.js b/app/javascript/mastodon/features/compose/components/poll_button.jsx index 76f96bfa4..6ad689ccc 100644 --- a/app/javascript/mastodon/features/compose/components/poll_button.js +++ b/app/javascript/mastodon/features/compose/components/poll_button.jsx @@ -13,8 +13,6 @@ const iconStyle = { lineHeight: '27px', }; -export default -@injectIntl class PollButton extends React.PureComponent { static propTypes = { @@ -27,7 +25,7 @@ class PollButton extends React.PureComponent { handleClick = () => { this.props.onClick(); - } + }; render () { const { intl, active, unavailable, disabled } = this.props; @@ -53,3 +51,5 @@ class PollButton extends React.PureComponent { } } + +export default injectIntl(PollButton); diff --git a/app/javascript/mastodon/features/compose/components/poll_form.js b/app/javascript/mastodon/features/compose/components/poll_form.jsx index 3aa527161..f81d7355a 100644 --- a/app/javascript/mastodon/features/compose/components/poll_form.js +++ b/app/javascript/mastodon/features/compose/components/poll_form.jsx @@ -20,11 +20,11 @@ const messages = defineMessages({ days: { id: 'intervals.full.days', defaultMessage: '{number, plural, one {# day} other {# days}}' }, }); -@injectIntl -class Option extends React.PureComponent { +class OptionIntl extends React.PureComponent { static propTypes = { title: PropTypes.string.isRequired, + lang: PropTypes.string, index: PropTypes.number.isRequired, isPollMultiple: PropTypes.bool, autoFocus: PropTypes.bool, @@ -57,22 +57,22 @@ class Option extends React.PureComponent { if (e.key === 'Enter' || e.key === ' ') { this.handleToggleMultiple(e); } - } + }; onSuggestionsClearRequested = () => { this.props.onClearSuggestions(); - } + }; onSuggestionsFetchRequested = (token) => { this.props.onFetchSuggestions(token); - } + }; onSuggestionSelected = (tokenStart, token, value) => { this.props.onSuggestionSelected(tokenStart, token, value, ['poll', 'options', this.props.index]); - } + }; render () { - const { isPollMultiple, title, index, autoFocus, intl } = this.props; + const { isPollMultiple, title, lang, index, autoFocus, intl } = this.props; return ( <li> @@ -82,7 +82,7 @@ class Option extends React.PureComponent { onClick={this.handleToggleMultiple} onKeyPress={this.handleCheckboxKeypress} role='button' - tabIndex='0' + tabIndex={0} title={intl.formatMessage(isPollMultiple ? messages.switchToSingle : messages.switchToMultiple)} aria-label={intl.formatMessage(isPollMultiple ? messages.switchToSingle : messages.switchToMultiple)} /> @@ -91,6 +91,8 @@ class Option extends React.PureComponent { placeholder={intl.formatMessage(messages.option_placeholder, { number: index + 1 })} maxLength={100} value={title} + lang={lang} + spellCheck onChange={this.handleOptionTitleChange} suggestions={this.props.suggestions} onSuggestionsFetchRequested={this.onSuggestionsFetchRequested} @@ -110,12 +112,13 @@ class Option extends React.PureComponent { } -export default -@injectIntl +const Option = injectIntl(OptionIntl); + class PollForm extends ImmutablePureComponent { static propTypes = { options: ImmutablePropTypes.list, + lang: PropTypes.string, expiresIn: PropTypes.number, isMultiple: PropTypes.bool, onChangeOption: PropTypes.func.isRequired, @@ -142,7 +145,7 @@ class PollForm extends ImmutablePureComponent { }; render () { - const { options, expiresIn, isMultiple, onChangeOption, onRemoveOption, intl, ...other } = this.props; + const { options, lang, expiresIn, isMultiple, onChangeOption, onRemoveOption, intl, ...other } = this.props; if (!options) { return null; @@ -153,7 +156,7 @@ class PollForm extends ImmutablePureComponent { return ( <div className='compose-form__poll-wrapper'> <ul> - {options.map((title, i) => <Option title={title} key={i} index={i} onChange={onChangeOption} onRemove={onRemoveOption} isPollMultiple={isMultiple} onToggleMultiple={this.handleToggleMultiple} autoFocus={i === autoFocusIndex} {...other} />)} + {options.map((title, i) => <Option title={title} lang={lang} key={i} index={i} onChange={onChangeOption} onRemove={onRemoveOption} isPollMultiple={isMultiple} onToggleMultiple={this.handleToggleMultiple} autoFocus={i === autoFocusIndex} {...other} />)} </ul> <div className='poll__footer'> @@ -176,3 +179,5 @@ class PollForm extends ImmutablePureComponent { } } + +export default injectIntl(PollForm); diff --git a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js b/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx index 545b67eda..e65c9cb72 100644 --- a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js +++ b/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx @@ -35,7 +35,7 @@ class PrivacyDropdownMenu extends React.PureComponent { if (this.node && !this.node.contains(e.target)) { this.props.onClose(); } - } + }; handleKeyDown = e => { const { items } = this.props; @@ -79,7 +79,7 @@ class PrivacyDropdownMenu extends React.PureComponent { e.preventDefault(); e.stopPropagation(); } - } + }; handleClick = e => { const value = e.currentTarget.getAttribute('data-index'); @@ -88,7 +88,7 @@ class PrivacyDropdownMenu extends React.PureComponent { this.props.onClose(); this.props.onChange(value); - } + }; componentDidMount () { document.addEventListener('click', this.handleDocumentClick, false); @@ -103,11 +103,11 @@ class PrivacyDropdownMenu extends React.PureComponent { setRef = c => { this.node = c; - } + }; setFocusRef = c => { this.focusedItem = c; - } + }; render () { const { style, items, value } = this.props; @@ -115,7 +115,7 @@ class PrivacyDropdownMenu extends React.PureComponent { return ( <div style={{ ...style }} role='listbox' ref={this.setRef}> {items.map(item => ( - <div role='option' tabIndex='0' key={item.value} data-index={item.value} onKeyDown={this.handleKeyDown} onClick={this.handleClick} className={classNames('privacy-dropdown__option', { active: item.value === value })} aria-selected={item.value === value} ref={item.value === value ? this.setFocusRef : null}> + <div role='option' tabIndex={0} key={item.value} data-index={item.value} onKeyDown={this.handleKeyDown} onClick={this.handleClick} className={classNames('privacy-dropdown__option', { active: item.value === value })} aria-selected={item.value === value} ref={item.value === value ? this.setFocusRef : null}> <div className='privacy-dropdown__option__icon'> <Icon id={item.icon} fixedWidth /> </div> @@ -132,7 +132,6 @@ class PrivacyDropdownMenu extends React.PureComponent { } -export default @injectIntl class PrivacyDropdown extends React.PureComponent { static propTypes = { @@ -168,7 +167,7 @@ class PrivacyDropdown extends React.PureComponent { } this.setState({ open: !this.state.open }); } - } + }; handleModalActionClick = (e) => { e.preventDefault(); @@ -177,7 +176,7 @@ class PrivacyDropdown extends React.PureComponent { this.props.onModalClose(); this.props.onChange(value); - } + }; handleKeyDown = e => { switch(e.key) { @@ -185,13 +184,13 @@ class PrivacyDropdown extends React.PureComponent { this.handleClose(); break; } - } + }; handleMouseDown = () => { if (!this.state.open) { this.activeElement = document.activeElement; } - } + }; handleButtonKeyDown = (e) => { switch(e.key) { @@ -200,18 +199,18 @@ class PrivacyDropdown extends React.PureComponent { this.handleMouseDown(); break; } - } + }; handleClose = () => { if (this.state.open && this.activeElement) { this.activeElement.focus({ preventScroll: true }); } this.setState({ open: false }); - } + }; handleChange = value => { this.props.onChange(value); - } + }; componentWillMount () { const { intl: { formatMessage } } = this.props; @@ -231,15 +230,15 @@ class PrivacyDropdown extends React.PureComponent { setTargetRef = c => { this.target = c; - } + }; findTarget = () => { return this.target; - } + }; handleOverlayEnter = (state) => { this.setState({ placement: state.placement }); - } + }; render () { const { value, container, disabled, intl } = this.props; @@ -285,3 +284,5 @@ class PrivacyDropdown extends React.PureComponent { } } + +export default injectIntl(PrivacyDropdown); diff --git a/app/javascript/mastodon/features/compose/components/reply_indicator.js b/app/javascript/mastodon/features/compose/components/reply_indicator.jsx index fc236882a..81de4ea76 100644 --- a/app/javascript/mastodon/features/compose/components/reply_indicator.js +++ b/app/javascript/mastodon/features/compose/components/reply_indicator.jsx @@ -12,7 +12,6 @@ const messages = defineMessages({ cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' }, }); -export default @injectIntl class ReplyIndicator extends ImmutablePureComponent { static contextTypes = { @@ -27,14 +26,14 @@ class ReplyIndicator extends ImmutablePureComponent { handleClick = () => { this.props.onCancel(); - } + }; handleAccountClick = (e) => { if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { e.preventDefault(); this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`); } - } + }; render () { const { status, intl } = this.props; @@ -69,3 +68,5 @@ class ReplyIndicator extends ImmutablePureComponent { } } + +export default injectIntl(ReplyIndicator); diff --git a/app/javascript/mastodon/features/compose/components/search.js b/app/javascript/mastodon/features/compose/components/search.js deleted file mode 100644 index 5820f8ca2..000000000 --- a/app/javascript/mastodon/features/compose/components/search.js +++ /dev/null @@ -1,147 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import Overlay from 'react-overlays/Overlay'; -import { searchEnabled } from '../../../initial_state'; -import Icon from 'mastodon/components/icon'; - -const messages = defineMessages({ - placeholder: { id: 'search.placeholder', defaultMessage: 'Search' }, - placeholderSignedIn: { id: 'search.search_or_paste', defaultMessage: 'Search or paste URL' }, -}); - -class SearchPopout extends React.PureComponent { - - render () { - const extraInformation = searchEnabled ? <FormattedMessage id='search_popout.tips.full_text' defaultMessage='Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.' /> : <FormattedMessage id='search_popout.tips.text' defaultMessage='Simple text returns matching display names, usernames and hashtags' />; - return ( - <div className='search-popout'> - <h4><FormattedMessage id='search_popout.search_format' defaultMessage='Advanced search format' /></h4> - - <ul> - <li><em>#example</em> <FormattedMessage id='search_popout.tips.hashtag' defaultMessage='hashtag' /></li> - <li><em>@username@domain</em> <FormattedMessage id='search_popout.tips.user' defaultMessage='user' /></li> - <li><em>URL</em> <FormattedMessage id='search_popout.tips.user' defaultMessage='user' /></li> - <li><em>URL</em> <FormattedMessage id='search_popout.tips.status' defaultMessage='status' /></li> - </ul> - - {extraInformation} - </div> - ); - } - -} - -export default @injectIntl -class Search extends React.PureComponent { - - static contextTypes = { - router: PropTypes.object.isRequired, - identity: PropTypes.object.isRequired, - }; - - static propTypes = { - value: PropTypes.string.isRequired, - submitted: PropTypes.bool, - onChange: PropTypes.func.isRequired, - onSubmit: PropTypes.func.isRequired, - onClear: PropTypes.func.isRequired, - onShow: PropTypes.func.isRequired, - openInRoute: PropTypes.bool, - intl: PropTypes.object.isRequired, - singleColumn: PropTypes.bool, - }; - - state = { - expanded: false, - }; - - setRef = c => { - this.searchForm = c; - } - - handleChange = (e) => { - this.props.onChange(e.target.value); - } - - handleClear = (e) => { - e.preventDefault(); - - if (this.props.value.length > 0 || this.props.submitted) { - this.props.onClear(); - } - } - - handleKeyUp = (e) => { - if (e.key === 'Enter') { - e.preventDefault(); - - this.props.onSubmit(); - - if (this.props.openInRoute) { - this.context.router.history.push('/search'); - } - } else if (e.key === 'Escape') { - document.querySelector('.ui').parentElement.focus(); - } - } - - handleFocus = () => { - this.setState({ expanded: true }); - this.props.onShow(); - - if (this.searchForm && !this.props.singleColumn) { - const { left, right } = this.searchForm.getBoundingClientRect(); - if (left < 0 || right > (window.innerWidth || document.documentElement.clientWidth)) { - this.searchForm.scrollIntoView(); - } - } - } - - handleBlur = () => { - this.setState({ expanded: false }); - } - - findTarget = () => { - return this.searchForm; - } - - render () { - const { intl, value, submitted } = this.props; - const { expanded } = this.state; - const { signedIn } = this.context.identity; - const hasValue = value.length > 0 || submitted; - - return ( - <div className='search'> - <input - ref={this.setRef} - className='search__input' - type='text' - placeholder={intl.formatMessage(signedIn ? messages.placeholderSignedIn : messages.placeholder)} - aria-label={intl.formatMessage(signedIn ? messages.placeholderSignedIn : messages.placeholder)} - value={value} - onChange={this.handleChange} - onKeyUp={this.handleKeyUp} - onFocus={this.handleFocus} - onBlur={this.handleBlur} - /> - - <div role='button' tabIndex='0' className='search__icon' onClick={this.handleClear}> - <Icon id='search' className={hasValue ? '' : 'active'} /> - <Icon id='times-circle' className={hasValue ? 'active' : ''} aria-label={intl.formatMessage(messages.placeholder)} /> - </div> - <Overlay show={expanded && !hasValue} placement='bottom' target={this.findTarget} popperConfig={{ strategy: 'fixed' }}> - {({ props, placement }) => ( - <div {...props} style={{ ...props.style, width: 285, zIndex: 2 }}> - <div className={`dropdown-animation ${placement}`}> - <SearchPopout /> - </div> - </div> - )} - </Overlay> - </div> - ); - } - -} diff --git a/app/javascript/mastodon/features/compose/components/search.jsx b/app/javascript/mastodon/features/compose/components/search.jsx new file mode 100644 index 000000000..46723f5cc --- /dev/null +++ b/app/javascript/mastodon/features/compose/components/search.jsx @@ -0,0 +1,335 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { searchEnabled } from 'mastodon/initial_state'; +import Icon from 'mastodon/components/icon'; +import classNames from 'classnames'; +import { HASHTAG_REGEX } from 'mastodon/utils/hashtags'; + +const messages = defineMessages({ + placeholder: { id: 'search.placeholder', defaultMessage: 'Search' }, + placeholderSignedIn: { id: 'search.search_or_paste', defaultMessage: 'Search or paste URL' }, +}); + +class Search extends React.PureComponent { + + static contextTypes = { + router: PropTypes.object.isRequired, + identity: PropTypes.object.isRequired, + }; + + static propTypes = { + value: PropTypes.string.isRequired, + recent: ImmutablePropTypes.orderedSet, + submitted: PropTypes.bool, + onChange: PropTypes.func.isRequired, + onSubmit: PropTypes.func.isRequired, + onOpenURL: PropTypes.func.isRequired, + onClickSearchResult: PropTypes.func.isRequired, + onForgetSearchResult: PropTypes.func.isRequired, + onClear: PropTypes.func.isRequired, + onShow: PropTypes.func.isRequired, + openInRoute: PropTypes.bool, + intl: PropTypes.object.isRequired, + singleColumn: PropTypes.bool, + }; + + state = { + expanded: false, + selectedOption: -1, + options: [], + }; + + setRef = c => { + this.searchForm = c; + }; + + handleChange = ({ target }) => { + const { onChange } = this.props; + + onChange(target.value); + + this._calculateOptions(target.value); + }; + + handleClear = e => { + const { value, submitted, onClear } = this.props; + + e.preventDefault(); + + if (value.length > 0 || submitted) { + onClear(); + this.setState({ options: [], selectedOption: -1 }); + } + }; + + handleKeyDown = (e) => { + const { selectedOption } = this.state; + const options = this._getOptions(); + + switch(e.key) { + case 'Escape': + e.preventDefault(); + this._unfocus(); + + break; + case 'ArrowDown': + e.preventDefault(); + + if (options.length > 0) { + this.setState({ selectedOption: Math.min(selectedOption + 1, options.length - 1) }); + } + + break; + case 'ArrowUp': + e.preventDefault(); + + if (options.length > 0) { + this.setState({ selectedOption: Math.max(selectedOption - 1, -1) }); + } + + break; + case 'Enter': + e.preventDefault(); + + if (selectedOption === -1) { + this._submit(); + } else if (options.length > 0) { + options[selectedOption].action(); + } + + this._unfocus(); + + break; + case 'Delete': + if (selectedOption > -1 && options.length > 0) { + const search = options[selectedOption]; + + if (typeof search.forget === 'function') { + e.preventDefault(); + search.forget(e); + } + } + + break; + } + }; + + handleFocus = () => { + const { onShow, singleColumn } = this.props; + + this.setState({ expanded: true, selectedOption: -1 }); + onShow(); + + if (this.searchForm && !singleColumn) { + const { left, right } = this.searchForm.getBoundingClientRect(); + + if (left < 0 || right > (window.innerWidth || document.documentElement.clientWidth)) { + this.searchForm.scrollIntoView(); + } + } + }; + + handleBlur = () => { + this.setState({ expanded: false, selectedOption: -1 }); + }; + + findTarget = () => { + return this.searchForm; + }; + + handleHashtagClick = () => { + const { router } = this.context; + const { value, onClickSearchResult } = this.props; + + const query = value.trim().replace(/^#/, ''); + + router.history.push(`/tags/${query}`); + onClickSearchResult(query, 'hashtag'); + }; + + handleAccountClick = () => { + const { router } = this.context; + const { value, onClickSearchResult } = this.props; + + const query = value.trim().replace(/^@/, ''); + + router.history.push(`/@${query}`); + onClickSearchResult(query, 'account'); + }; + + handleURLClick = () => { + const { router } = this.context; + const { onOpenURL } = this.props; + + onOpenURL(router.history); + }; + + handleStatusSearch = () => { + this._submit('statuses'); + }; + + handleAccountSearch = () => { + this._submit('accounts'); + }; + + handleRecentSearchClick = search => { + const { router } = this.context; + + if (search.get('type') === 'account') { + router.history.push(`/@${search.get('q')}`); + } else if (search.get('type') === 'hashtag') { + router.history.push(`/tags/${search.get('q')}`); + } + }; + + handleForgetRecentSearchClick = search => { + const { onForgetSearchResult } = this.props; + + onForgetSearchResult(search.get('q')); + }; + + _unfocus () { + document.querySelector('.ui').parentElement.focus(); + } + + _submit (type) { + const { onSubmit, openInRoute } = this.props; + const { router } = this.context; + + onSubmit(type); + + if (openInRoute) { + router.history.push('/search'); + } + } + + _getOptions () { + const { options } = this.state; + + if (options.length > 0) { + return options; + } + + const { recent } = this.props; + + return recent.toArray().map(search => ({ + label: search.get('type') === 'account' ? `@${search.get('q')}` : `#${search.get('q')}`, + + action: () => this.handleRecentSearchClick(search), + + forget: e => { + e.stopPropagation(); + this.handleForgetRecentSearchClick(search); + }, + })); + } + + _calculateOptions (value) { + const trimmedValue = value.trim(); + const options = []; + + if (trimmedValue.length > 0) { + const couldBeURL = trimmedValue.startsWith('https://') && !trimmedValue.includes(' '); + + if (couldBeURL) { + options.push({ key: 'open-url', label: <FormattedMessage id='search.quick_action.open_url' defaultMessage='Open URL in Mastodon' />, action: this.handleURLClick }); + } + + const couldBeHashtag = (trimmedValue.startsWith('#') && trimmedValue.length > 1) || trimmedValue.match(HASHTAG_REGEX); + + if (couldBeHashtag) { + options.push({ key: 'go-to-hashtag', label: <FormattedMessage id='search.quick_action.go_to_hashtag' defaultMessage='Go to hashtag {x}' values={{ x: <mark>#{trimmedValue.replace(/^#/, '')}</mark> }} />, action: this.handleHashtagClick }); + } + + const couldBeUsername = trimmedValue.match(/^@?[a-z0-9_-]+(@[^\s]+)?$/i); + + if (couldBeUsername) { + options.push({ key: 'go-to-account', label: <FormattedMessage id='search.quick_action.go_to_account' defaultMessage='Go to profile {x}' values={{ x: <mark>@{trimmedValue.replace(/^@/, '')}</mark> }} />, action: this.handleAccountClick }); + } + + const couldBeStatusSearch = searchEnabled; + + if (couldBeStatusSearch) { + options.push({ key: 'status-search', label: <FormattedMessage id='search.quick_action.status_search' defaultMessage='Posts matching {x}' values={{ x: <mark>{trimmedValue}</mark> }} />, action: this.handleStatusSearch }); + } + + const couldBeUserSearch = true; + + if (couldBeUserSearch) { + options.push({ key: 'account-search', label: <FormattedMessage id='search.quick_action.account_search' defaultMessage='Profiles matching {x}' values={{ x: <mark>{trimmedValue}</mark> }} />, action: this.handleAccountSearch }); + } + } + + this.setState({ options }); + } + + render () { + const { intl, value, submitted, recent } = this.props; + const { expanded, options, selectedOption } = this.state; + const { signedIn } = this.context.identity; + + const hasValue = value.length > 0 || submitted; + + return ( + <div className={classNames('search', { active: expanded })}> + <input + ref={this.setRef} + className='search__input' + type='text' + placeholder={intl.formatMessage(signedIn ? messages.placeholderSignedIn : messages.placeholder)} + aria-label={intl.formatMessage(signedIn ? messages.placeholderSignedIn : messages.placeholder)} + value={value} + onChange={this.handleChange} + onKeyDown={this.handleKeyDown} + onFocus={this.handleFocus} + onBlur={this.handleBlur} + /> + + <div role='button' tabIndex={0} className='search__icon' onClick={this.handleClear}> + <Icon id='search' className={hasValue ? '' : 'active'} /> + <Icon id='times-circle' className={hasValue ? 'active' : ''} aria-label={intl.formatMessage(messages.placeholder)} /> + </div> + + <div className='search__popout'> + {options.length === 0 && ( + <> + <h4><FormattedMessage id='search_popout.recent' defaultMessage='Recent searches' /></h4> + + <div className='search__popout__menu'> + {recent.size > 0 ? this._getOptions().map(({ label, action, forget }, i) => ( + <button key={label} onMouseDown={action} className={classNames('search__popout__menu__item search__popout__menu__item--flex', { selected: selectedOption === i })}> + <span>{label}</span> + <button className='icon-button' onMouseDown={forget}><Icon id='times' /></button> + </button> + )) : ( + <div className='search__popout__menu__message'> + <FormattedMessage id='search.no_recent_searches' defaultMessage='No recent searches' /> + </div> + )} + </div> + </> + )} + + {options.length > 0 && ( + <> + <h4><FormattedMessage id='search_popout.quick_actions' defaultMessage='Quick actions' /></h4> + + <div className='search__popout__menu'> + {options.map(({ key, label, action }, i) => ( + <button key={key} onMouseDown={action} className={classNames('search__popout__menu__item', { selected: selectedOption === i })}> + {label} + </button> + ))} + </div> + </> + )} + </div> + </div> + ); + } + +} + +export default injectIntl(Search); diff --git a/app/javascript/mastodon/features/compose/components/search_results.js b/app/javascript/mastodon/features/compose/components/search_results.jsx index 44ab43638..1dccd950c 100644 --- a/app/javascript/mastodon/features/compose/components/search_results.js +++ b/app/javascript/mastodon/features/compose/components/search_results.jsx @@ -14,7 +14,6 @@ const messages = defineMessages({ dismissSuggestion: { id: 'suggestions.dismiss', defaultMessage: 'Dismiss suggestion' }, }); -export default @injectIntl class SearchResults extends ImmutablePureComponent { static propTypes = { @@ -78,7 +77,7 @@ class SearchResults extends ImmutablePureComponent { count += results.get('accounts').size; accounts = ( <div className='search-results__section'> - <h5><Icon id='users' fixedWidth /><FormattedMessage id='search_results.accounts' defaultMessage='People' /></h5> + <h5><Icon id='users' fixedWidth /><FormattedMessage id='search_results.accounts' defaultMessage='Profiles' /></h5> {results.get('accounts').map(accountId => <AccountContainer key={accountId} id={accountId} />)} @@ -138,3 +137,5 @@ class SearchResults extends ImmutablePureComponent { } } + +export default injectIntl(SearchResults); diff --git a/app/javascript/mastodon/features/compose/components/text_icon_button.js b/app/javascript/mastodon/features/compose/components/text_icon_button.jsx index 73da32ad5..73da32ad5 100644 --- a/app/javascript/mastodon/features/compose/components/text_icon_button.js +++ b/app/javascript/mastodon/features/compose/components/text_icon_button.jsx diff --git a/app/javascript/mastodon/features/compose/components/upload.js b/app/javascript/mastodon/features/compose/components/upload.jsx index b08307ade..e3651c229 100644 --- a/app/javascript/mastodon/features/compose/components/upload.js +++ b/app/javascript/mastodon/features/compose/components/upload.jsx @@ -22,31 +22,36 @@ export default class Upload extends ImmutablePureComponent { handleUndoClick = e => { e.stopPropagation(); this.props.onUndo(this.props.media.get('id')); - } + }; handleFocalPointClick = e => { e.stopPropagation(); this.props.onOpenFocalPoint(this.props.media.get('id')); - } + }; render () { const { media } = this.props; + + if (!media) { + return null; + } + const focusX = media.getIn(['meta', 'focus', 'x']); const focusY = media.getIn(['meta', 'focus', 'y']); const x = ((focusX / 2) + .5) * 100; const y = ((focusY / -2) + .5) * 100; return ( - <div className='compose-form__upload' tabIndex='0' role='button'> + <div className='compose-form__upload' tabIndex={0} role='button'> <Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}> {({ scale }) => ( <div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}> <div className='compose-form__upload__actions'> <button type='button' className='icon-button' onClick={this.handleUndoClick}><Icon id='times' /> <FormattedMessage id='upload_form.undo' defaultMessage='Delete' /></button> - {!!media.get('unattached') && (<button type='button' className='icon-button' onClick={this.handleFocalPointClick}><Icon id='pencil' /> <FormattedMessage id='upload_form.edit' defaultMessage='Edit' /></button>)} + <button type='button' className='icon-button' onClick={this.handleFocalPointClick}><Icon id='pencil' /> <FormattedMessage id='upload_form.edit' defaultMessage='Edit' /></button> </div> - {(media.get('description') || '').length === 0 && !!media.get('unattached') && ( + {(media.get('description') || '').length === 0 && ( <div className='compose-form__upload__warning'> <button type='button' className='icon-button' onClick={this.handleFocalPointClick}><Icon id='info-circle' /> <FormattedMessage id='upload_form.description_missing' defaultMessage='No description added' /></button> </div> diff --git a/app/javascript/mastodon/features/compose/components/upload_button.js b/app/javascript/mastodon/features/compose/components/upload_button.jsx index 9cb36167a..f2e6ff85c 100644 --- a/app/javascript/mastodon/features/compose/components/upload_button.js +++ b/app/javascript/mastodon/features/compose/components/upload_button.jsx @@ -23,8 +23,6 @@ const iconStyle = { lineHeight: '27px', }; -export default @connect(makeMapStateToProps) -@injectIntl class UploadButton extends ImmutablePureComponent { static propTypes = { @@ -41,15 +39,15 @@ class UploadButton extends ImmutablePureComponent { if (e.target.files.length > 0) { this.props.onSelectFile(e.target.files); } - } + }; handleClick = () => { this.fileElement.click(); - } + }; setRef = (c) => { this.fileElement = c; - } + }; render () { const { intl, resetFileKey, unavailable, disabled, acceptContentTypes } = this.props; @@ -81,3 +79,5 @@ class UploadButton extends ImmutablePureComponent { } } + +export default connect(makeMapStateToProps)(injectIntl(UploadButton)); diff --git a/app/javascript/mastodon/features/compose/components/upload_form.js b/app/javascript/mastodon/features/compose/components/upload_form.jsx index 9ff2aa0fa..9ff2aa0fa 100644 --- a/app/javascript/mastodon/features/compose/components/upload_form.js +++ b/app/javascript/mastodon/features/compose/components/upload_form.jsx diff --git a/app/javascript/mastodon/features/compose/components/upload_progress.js b/app/javascript/mastodon/features/compose/components/upload_progress.jsx index cabf520fd..cabf520fd 100644 --- a/app/javascript/mastodon/features/compose/components/upload_progress.js +++ b/app/javascript/mastodon/features/compose/components/upload_progress.jsx diff --git a/app/javascript/mastodon/features/compose/components/warning.js b/app/javascript/mastodon/features/compose/components/warning.jsx index 803b7f86a..803b7f86a 100644 --- a/app/javascript/mastodon/features/compose/components/warning.js +++ b/app/javascript/mastodon/features/compose/components/warning.jsx diff --git a/app/javascript/mastodon/features/compose/containers/compose_form_container.js b/app/javascript/mastodon/features/compose/containers/compose_form_container.js index 14cf9230b..2b7642237 100644 --- a/app/javascript/mastodon/features/compose/containers/compose_form_container.js +++ b/app/javascript/mastodon/features/compose/containers/compose_form_container.js @@ -26,6 +26,7 @@ const mapStateToProps = state => ({ isUploading: state.getIn(['compose', 'is_uploading']), anyMedia: state.getIn(['compose', 'media_attachments']).size > 0, isInReply: state.getIn(['compose', 'in_reply_to']) !== null, + lang: state.getIn(['compose', 'language']), }); const mapDispatchToProps = (dispatch) => ({ diff --git a/app/javascript/mastodon/features/compose/containers/poll_form_container.js b/app/javascript/mastodon/features/compose/containers/poll_form_container.js index 1401371d0..479117e91 100644 --- a/app/javascript/mastodon/features/compose/containers/poll_form_container.js +++ b/app/javascript/mastodon/features/compose/containers/poll_form_container.js @@ -1,7 +1,10 @@ import { connect } from 'react-redux'; import PollForm from '../components/poll_form'; -import { addPollOption, removePollOption, changePollOption, changePollSettings } from '../../../actions/compose'; import { + addPollOption, + removePollOption, + changePollOption, + changePollSettings, clearComposeSuggestions, fetchComposeSuggestions, selectComposeSuggestion, @@ -10,6 +13,7 @@ import { const mapStateToProps = state => ({ suggestions: state.getIn(['compose', 'suggestions']), options: state.getIn(['compose', 'poll', 'options']), + lang: state.getIn(['compose', 'language']), expiresIn: state.getIn(['compose', 'poll', 'expires_in']), isMultiple: state.getIn(['compose', 'poll', 'multiple']), }); diff --git a/app/javascript/mastodon/features/compose/containers/search_container.js b/app/javascript/mastodon/features/compose/containers/search_container.js index 392bd0f56..3ee55fae5 100644 --- a/app/javascript/mastodon/features/compose/containers/search_container.js +++ b/app/javascript/mastodon/features/compose/containers/search_container.js @@ -4,12 +4,16 @@ import { clearSearch, submitSearch, showSearch, -} from '../../../actions/search'; + openURL, + clickSearchResult, + forgetSearchResult, +} from 'mastodon/actions/search'; import Search from '../components/search'; const mapStateToProps = state => ({ value: state.getIn(['search', 'value']), submitted: state.getIn(['search', 'submitted']), + recent: state.getIn(['search', 'recent']), }); const mapDispatchToProps = dispatch => ({ @@ -22,14 +26,26 @@ const mapDispatchToProps = dispatch => ({ dispatch(clearSearch()); }, - onSubmit () { - dispatch(submitSearch()); + onSubmit (type) { + dispatch(submitSearch(type)); }, onShow () { dispatch(showSearch()); }, + onOpenURL (routerHistory) { + dispatch(openURL(routerHistory)); + }, + + onClickSearchResult (q, type) { + dispatch(clickSearchResult(q, type)); + }, + + onForgetSearchResult (q) { + dispatch(forgetSearchResult(q)); + }, + }); export default connect(mapStateToProps, mapDispatchToProps)(Search); diff --git a/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js b/app/javascript/mastodon/features/compose/containers/sensitive_button_container.jsx index 1bcce5731..03f831d28 100644 --- a/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js +++ b/app/javascript/mastodon/features/compose/containers/sensitive_button_container.jsx @@ -54,8 +54,6 @@ class SensitiveButton extends React.PureComponent { disabled={disabled} /> - <span className={classNames('checkbox', { active })} /> - <FormattedMessage id='compose_form.sensitive.hide' defaultMessage='{count, plural, one {Mark media as sensitive} other {Mark media as sensitive}}' diff --git a/app/javascript/mastodon/features/compose/containers/upload_container.js b/app/javascript/mastodon/features/compose/containers/upload_container.js index 05cd2ecc1..5a8a64931 100644 --- a/app/javascript/mastodon/features/compose/containers/upload_container.js +++ b/app/javascript/mastodon/features/compose/containers/upload_container.js @@ -1,7 +1,6 @@ import { connect } from 'react-redux'; import Upload from '../components/upload'; -import { undoUploadCompose, initMediaEditModal } from '../../../actions/compose'; -import { submitCompose } from '../../../actions/compose'; +import { undoUploadCompose, initMediaEditModal, submitCompose } from '../../../actions/compose'; const mapStateToProps = (state, { id }) => ({ media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id), diff --git a/app/javascript/mastodon/features/compose/containers/warning_container.js b/app/javascript/mastodon/features/compose/containers/warning_container.jsx index 571d1d838..e99f5dacd 100644 --- a/app/javascript/mastodon/features/compose/containers/warning_container.js +++ b/app/javascript/mastodon/features/compose/containers/warning_container.jsx @@ -3,36 +3,12 @@ import { connect } from 'react-redux'; import Warning from '../components/warning'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; -import { me } from '../../../initial_state'; - -const buildHashtagRE = () => { - try { - const HASHTAG_SEPARATORS = '_\\u00b7\\u200c'; - const ALPHA = '\\p{L}\\p{M}'; - const WORD = '\\p{L}\\p{M}\\p{N}\\p{Pc}'; - return new RegExp( - '(?:^|[^\\/\\)\\w])#((' + - '[' + WORD + '_]' + - '[' + WORD + HASHTAG_SEPARATORS + ']*' + - '[' + ALPHA + HASHTAG_SEPARATORS + ']' + - '[' + WORD + HASHTAG_SEPARATORS +']*' + - '[' + WORD + '_]' + - ')|(' + - '[' + WORD + '_]*' + - '[' + ALPHA + ']' + - '[' + WORD + '_]*' + - '))', 'iu', - ); - } catch { - return /(?:^|[^\/\)\w])#(\w*[a-zA-Z·]\w*)/i; - } -}; - -const APPROX_HASHTAG_RE = buildHashtagRE(); +import { me } from 'mastodon/initial_state'; +import { HASHTAG_PATTERN_REGEX } from 'mastodon/utils/hashtags'; const mapStateToProps = state => ({ needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', me, 'locked']), - hashtagWarning: state.getIn(['compose', 'privacy']) !== 'public' && APPROX_HASHTAG_RE.test(state.getIn(['compose', 'text'])), + hashtagWarning: state.getIn(['compose', 'privacy']) !== 'public' && HASHTAG_PATTERN_REGEX.test(state.getIn(['compose', 'text'])), directMessageWarning: state.getIn(['compose', 'privacy']) === 'direct', }); diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.jsx index aead7776a..6cba1e9ad 100644 --- a/app/javascript/mastodon/features/compose/index.js +++ b/app/javascript/mastodon/features/compose/index.jsx @@ -38,8 +38,6 @@ const mapStateToProps = (state, ownProps) => ({ showSearch: ownProps.multiColumn ? state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']) : false, }); -export default @connect(mapStateToProps) -@injectIntl class Compose extends React.PureComponent { static propTypes = { @@ -74,15 +72,15 @@ class Compose extends React.PureComponent { })); return false; - } + }; onFocus = () => { this.props.dispatch(changeComposing(true)); - } + }; onBlur = () => { this.props.dispatch(changeComposing(false)); - } + }; render () { const { multiColumn, showSearch, intl } = this.props; @@ -148,3 +146,5 @@ class Compose extends React.PureComponent { } } + +export default connect(mapStateToProps)(injectIntl(Compose)); diff --git a/app/javascript/mastodon/features/compose/util/counter.js b/app/javascript/mastodon/features/compose/util/counter.js index 5a68bad99..ec2431096 100644 --- a/app/javascript/mastodon/features/compose/util/counter.js +++ b/app/javascript/mastodon/features/compose/util/counter.js @@ -5,5 +5,5 @@ const urlPlaceholder = '$2xxxxxxxxxxxxxxxxxxxxxxx'; export function countableText(inputText) { return inputText .replace(urlRegex, urlPlaceholder) - .replace(/(^|[^\/\w])@(([a-z0-9_]+)@[a-z0-9\.\-]+[a-z0-9]+)/ig, '$1@$3'); + .replace(/(^|[^/\w])@(([a-z0-9_]+)@[a-z0-9.-]+[a-z0-9]+)/ig, '$1@$3'); } diff --git a/app/javascript/mastodon/features/direct_timeline/components/conversation.js b/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx index 4a770970d..d0dbffe65 100644 --- a/app/javascript/mastodon/features/direct_timeline/components/conversation.js +++ b/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx @@ -24,7 +24,6 @@ const messages = defineMessages({ unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' }, }); -export default @injectIntl class Conversation extends ImmutablePureComponent { static contextTypes = { @@ -55,7 +54,7 @@ class Conversation extends ImmutablePureComponent { let emoji = emojis[i]; emoji.src = emoji.getAttribute('data-original'); } - } + }; handleMouseLeave = ({ currentTarget }) => { if (autoPlayGif) { @@ -68,7 +67,7 @@ class Conversation extends ImmutablePureComponent { let emoji = emojis[i]; emoji.src = emoji.getAttribute('data-static'); } - } + }; handleClick = () => { if (!this.context.router) { @@ -82,35 +81,35 @@ class Conversation extends ImmutablePureComponent { } this.context.router.history.push(`/@${lastStatus.getIn(['account', 'acct'])}/${lastStatus.get('id')}`); - } + }; handleMarkAsRead = () => { this.props.markRead(); - } + }; handleReply = () => { this.props.reply(this.props.lastStatus, this.context.router.history); - } + }; handleDelete = () => { this.props.delete(); - } + }; handleHotkeyMoveUp = () => { this.props.onMoveUp(this.props.conversationId); - } + }; handleHotkeyMoveDown = () => { this.props.onMoveDown(this.props.conversationId); - } + }; handleConversationMute = () => { this.props.onMute(this.props.lastStatus); - } + }; handleShowMore = () => { this.props.onToggleHidden(this.props.lastStatus); - } + }; render () { const { accounts, lastStatus, unread, scrollKey, intl } = this.props; @@ -145,7 +144,7 @@ class Conversation extends ImmutablePureComponent { return ( <HotKeys handlers={handlers}> - <div className={classNames('conversation focusable muted', { 'conversation--unread': unread })} tabIndex='0'> + <div className={classNames('conversation focusable muted', { 'conversation--unread': unread })} tabIndex={0}> <div className='conversation__avatar' onClick={this.handleClick} role='presentation'> <AvatarComposite accounts={accounts} size={48} /> </div> @@ -198,3 +197,5 @@ class Conversation extends ImmutablePureComponent { } } + +export default injectIntl(Conversation); diff --git a/app/javascript/mastodon/features/direct_timeline/components/conversations_list.js b/app/javascript/mastodon/features/direct_timeline/components/conversations_list.jsx index fd1df7256..04c404e1d 100644 --- a/app/javascript/mastodon/features/direct_timeline/components/conversations_list.js +++ b/app/javascript/mastodon/features/direct_timeline/components/conversations_list.jsx @@ -16,17 +16,17 @@ export default class ConversationsList extends ImmutablePureComponent { onLoadMore: PropTypes.func, }; - getCurrentIndex = id => this.props.conversations.findIndex(x => x.get('id') === id) + getCurrentIndex = id => this.props.conversations.findIndex(x => x.get('id') === id); handleMoveUp = id => { const elementIndex = this.getCurrentIndex(id) - 1; this._selectChild(elementIndex, true); - } + }; handleMoveDown = id => { const elementIndex = this.getCurrentIndex(id) + 1; this._selectChild(elementIndex, false); - } + }; _selectChild (index, align_top) { const container = this.node.node; @@ -44,7 +44,7 @@ export default class ConversationsList extends ImmutablePureComponent { setRef = c => { this.node = c; - } + }; handleLoadOlder = debounce(() => { const last = this.props.conversations.last(); @@ -52,13 +52,13 @@ export default class ConversationsList extends ImmutablePureComponent { if (last && last.get('last_status')) { this.props.onLoadMore(last.get('last_status')); } - }, 300, { leading: true }) + }, 300, { leading: true }); render () { - const { conversations, onLoadMore, ...other } = this.props; + const { conversations, isLoading, onLoadMore, ...other } = this.props; return ( - <ScrollableList {...other} onLoadMore={onLoadMore && this.handleLoadOlder} ref={this.setRef}> + <ScrollableList {...other} isLoading={isLoading} showLoading={isLoading && conversations.isEmpty()} onLoadMore={onLoadMore && this.handleLoadOlder} ref={this.setRef}> {conversations.map(item => ( <ConversationContainer key={item.get('id')} diff --git a/app/javascript/mastodon/features/direct_timeline/index.js b/app/javascript/mastodon/features/direct_timeline/index.jsx index 8dcc43e28..fc45ea69a 100644 --- a/app/javascript/mastodon/features/direct_timeline/index.js +++ b/app/javascript/mastodon/features/direct_timeline/index.jsx @@ -11,11 +11,9 @@ import ColumnHeader from 'mastodon/components/column_header'; import ConversationsListContainer from './containers/conversations_list_container'; const messages = defineMessages({ - title: { id: 'column.direct', defaultMessage: 'Direct messages' }, + title: { id: 'column.direct', defaultMessage: 'Private mentions' }, }); -export default @connect() -@injectIntl class DirectTimeline extends React.PureComponent { static propTypes = { @@ -34,16 +32,16 @@ class DirectTimeline extends React.PureComponent { } else { dispatch(addColumn('DIRECT', {})); } - } + }; handleMove = (dir) => { const { columnId, dispatch } = this.props; dispatch(moveColumn(columnId, dir)); - } + }; handleHeaderClick = () => { this.column.scrollTop(); - } + }; componentDidMount () { const { dispatch } = this.props; @@ -64,11 +62,11 @@ class DirectTimeline extends React.PureComponent { setRef = c => { this.column = c; - } + }; handleLoadMore = maxId => { this.props.dispatch(expandConversations({ maxId })); - } + }; render () { const { intl, hasUnread, columnId, multiColumn } = this.props; @@ -91,9 +89,11 @@ class DirectTimeline extends React.PureComponent { trackScroll={!pinned} scrollKey={`direct_timeline-${columnId}`} timelineId='direct' + bindToDocument={!multiColumn} onLoadMore={this.handleLoadMore} prepend={<div className='follow_requests-unlocked_explanation'><span><FormattedMessage id='compose_form.encryption_warning' defaultMessage='Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.' /> <a href='/terms' target='_blank'><FormattedMessage id='compose_form.direct_message_warning_learn_more' defaultMessage='Learn more' /></a></span></div>} - emptyMessage={<FormattedMessage id='empty_column.direct' defaultMessage="You don't have any direct messages yet. When you send or receive one, it will show up here." />} + alwaysPrepend + emptyMessage={<FormattedMessage id='empty_column.direct' defaultMessage="You don't have any private mentions yet. When you send or receive one, it will show up here." />} /> <Helmet> @@ -105,3 +105,5 @@ class DirectTimeline extends React.PureComponent { } } + +export default connect()(injectIntl(DirectTimeline)); diff --git a/app/javascript/mastodon/features/directory/components/account_card.js b/app/javascript/mastodon/features/directory/components/account_card.jsx index 977f0c32c..1cee3a4e4 100644 --- a/app/javascript/mastodon/features/directory/components/account_card.js +++ b/app/javascript/mastodon/features/directory/components/account_card.jsx @@ -91,9 +91,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ }); -export default -@injectIntl -@connect(makeMapStateToProps, mapDispatchToProps) class AccountCard extends ImmutablePureComponent { static propTypes = { @@ -115,7 +112,7 @@ class AccountCard extends ImmutablePureComponent { let emoji = emojis[i]; emoji.src = emoji.getAttribute('data-original'); } - } + }; handleMouseLeave = ({ currentTarget }) => { if (autoPlayGif) { @@ -128,7 +125,7 @@ class AccountCard extends ImmutablePureComponent { let emoji = emojis[i]; emoji.src = emoji.getAttribute('data-static'); } - } + }; handleFollow = () => { this.props.onFollow(this.props.account); @@ -140,11 +137,11 @@ class AccountCard extends ImmutablePureComponent { handleMute = () => { this.props.onMute(this.props.account); - } + }; handleEditProfile = () => { window.open('/settings/profile', '_blank'); - } + }; render() { const { account, intl } = this.props; @@ -233,3 +230,5 @@ class AccountCard extends ImmutablePureComponent { } } + +export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(AccountCard)); diff --git a/app/javascript/mastodon/features/directory/index.js b/app/javascript/mastodon/features/directory/index.jsx index b45faa049..afaf39b77 100644 --- a/app/javascript/mastodon/features/directory/index.js +++ b/app/javascript/mastodon/features/directory/index.jsx @@ -29,8 +29,6 @@ const mapStateToProps = state => ({ domain: state.getIn(['meta', 'domain']), }); -export default @connect(mapStateToProps) -@injectIntl class Directory extends React.PureComponent { static contextTypes = { @@ -64,7 +62,7 @@ class Directory extends React.PureComponent { } else { dispatch(addColumn('DIRECTORY', this.getParams(this.props, this.state))); } - } + }; getParams = (props, state) => ({ order: state.order === null ? (props.params.order || 'active') : state.order, @@ -74,11 +72,11 @@ class Directory extends React.PureComponent { handleMove = dir => { const { columnId, dispatch } = this.props; dispatch(moveColumn(columnId, dir)); - } + }; handleHeaderClick = () => { this.column.scrollTop(); - } + }; componentDidMount () { const { dispatch } = this.props; @@ -97,7 +95,7 @@ class Directory extends React.PureComponent { setRef = c => { this.column = c; - } + }; handleChangeOrder = e => { const { dispatch, columnId } = this.props; @@ -107,7 +105,7 @@ class Directory extends React.PureComponent { } else { this.setState({ order: e.target.value }); } - } + }; handleChangeLocal = e => { const { dispatch, columnId } = this.props; @@ -117,12 +115,12 @@ class Directory extends React.PureComponent { } else { this.setState({ local: e.target.value === '1' }); } - } + }; handleLoadMore = () => { const { dispatch } = this.props; dispatch(expandDirectory(this.getParams(this.props, this.state))); - } + }; render () { const { isLoading, accountIds, intl, columnId, multiColumn, domain } = this.props; @@ -176,3 +174,5 @@ class Directory extends React.PureComponent { } } + +export default connect(mapStateToProps)(injectIntl(Directory)); diff --git a/app/javascript/mastodon/features/domain_blocks/index.js b/app/javascript/mastodon/features/domain_blocks/index.jsx index 43b275c2d..0c22aa239 100644 --- a/app/javascript/mastodon/features/domain_blocks/index.js +++ b/app/javascript/mastodon/features/domain_blocks/index.jsx @@ -23,8 +23,6 @@ const mapStateToProps = state => ({ hasMore: !!state.getIn(['domain_lists', 'blocks', 'next']), }); -export default @connect(mapStateToProps) -@injectIntl class Blocks extends ImmutablePureComponent { static propTypes = { @@ -81,3 +79,5 @@ class Blocks extends ImmutablePureComponent { } } + +export default connect(mapStateToProps)(injectIntl(Blocks)); diff --git a/app/javascript/mastodon/features/emoji/emoji.js b/app/javascript/mastodon/features/emoji/emoji.js index bc3dd8c60..6ae406624 100644 --- a/app/javascript/mastodon/features/emoji/emoji.js +++ b/app/javascript/mastodon/features/emoji/emoji.js @@ -50,7 +50,7 @@ const emojifyTextNode = (node, customEmojis) => { if (shortname in customEmojis) { const filename = autoPlayGif ? customEmojis[shortname].url : customEmojis[shortname].static_url; replacement = document.createElement('img'); - replacement.setAttribute('draggable', false); + replacement.setAttribute('draggable', 'false'); replacement.setAttribute('class', 'emojione custom-emoji'); replacement.setAttribute('alt', shortname); replacement.setAttribute('title', shortname); @@ -65,7 +65,7 @@ const emojifyTextNode = (node, customEmojis) => { const { filename, shortCode } = unicodeMapping[match]; const title = shortCode ? `:${shortCode}:` : ''; replacement = document.createElement('img'); - replacement.setAttribute('draggable', false); + replacement.setAttribute('draggable', 'false'); replacement.setAttribute('class', 'emojione'); replacement.setAttribute('alt', match); replacement.setAttribute('title', title); 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 45086fc4c..49813537d 100644 --- a/app/javascript/mastodon/features/emoji/emoji_mart_data_light.js +++ b/app/javascript/mastodon/features/emoji/emoji_mart_data_light.js @@ -9,7 +9,7 @@ const emojis = {}; // decompress Object.keys(shortCodesToEmojiData).forEach((shortCode) => { let [ - filenameData, // eslint-disable-line no-unused-vars + filenameData, // eslint-disable-line @typescript-eslint/no-unused-vars searchData, ] = shortCodesToEmojiData[shortCode]; let [ diff --git a/app/javascript/mastodon/features/emoji/emoji_unicode_mapping_light.js b/app/javascript/mastodon/features/emoji/emoji_unicode_mapping_light.js index 918684c31..1a38fde23 100644 --- a/app/javascript/mastodon/features/emoji/emoji_unicode_mapping_light.js +++ b/app/javascript/mastodon/features/emoji/emoji_unicode_mapping_light.js @@ -4,9 +4,9 @@ const [ shortCodesToEmojiData, - skins, // eslint-disable-line no-unused-vars - categories, // eslint-disable-line no-unused-vars - short_names, // eslint-disable-line no-unused-vars + skins, // eslint-disable-line @typescript-eslint/no-unused-vars + categories, // eslint-disable-line @typescript-eslint/no-unused-vars + short_names, // eslint-disable-line @typescript-eslint/no-unused-vars emojisWithoutShortCodes, ] = require('./emoji_compressed'); const { unicodeToFilename } = require('./unicode_to_filename'); diff --git a/app/javascript/mastodon/features/emoji/emoji_utils.js b/app/javascript/mastodon/features/emoji/emoji_utils.js index dbf725c1f..be793526d 100644 --- a/app/javascript/mastodon/features/emoji/emoji_utils.js +++ b/app/javascript/mastodon/features/emoji/emoji_utils.js @@ -73,7 +73,7 @@ const stringFromCodePoint = _String.fromCodePoint || function () { const _JSON = JSON; -const COLONS_REGEX = /^(?:\:([^\:]+)\:)(?:\:skin-tone-(\d)\:)?$/; +const COLONS_REGEX = /^(?::([^:]+):)(?::skin-tone-(\d):)?$/; const SKINS = [ '1F3FA', '1F3FB', '1F3FC', '1F3FD', '1F3FE', '1F3FF', @@ -135,19 +135,19 @@ function getData(emoji, skin, set) { } } - if (data.short_names.hasOwnProperty(emoji)) { + if (Object.prototype.hasOwnProperty.call(data.short_names, emoji)) { emoji = data.short_names[emoji]; } - if (data.emojis.hasOwnProperty(emoji)) { + if (Object.prototype.hasOwnProperty.call(data.emojis, emoji)) { emojiData = data.emojis[emoji]; } } else if (emoji.id) { - if (data.short_names.hasOwnProperty(emoji.id)) { + if (Object.prototype.hasOwnProperty.call(data.short_names, emoji.id)) { emoji.id = data.short_names[emoji.id]; } - if (data.emojis.hasOwnProperty(emoji.id)) { + if (Object.prototype.hasOwnProperty.call(data.emojis, emoji.id)) { emojiData = data.emojis[emoji.id]; skin = skin || emoji.skin; } @@ -216,7 +216,7 @@ function deepMerge(a, b) { let originalValue = a[key], value = originalValue; - if (b.hasOwnProperty(key)) { + if (Object.prototype.hasOwnProperty.call(b, key)) { value = b[key]; } diff --git a/app/javascript/mastodon/features/explore/components/story.js b/app/javascript/mastodon/features/explore/components/story.jsx index 563128029..563128029 100644 --- a/app/javascript/mastodon/features/explore/components/story.js +++ b/app/javascript/mastodon/features/explore/components/story.jsx diff --git a/app/javascript/mastodon/features/explore/index.js b/app/javascript/mastodon/features/explore/index.jsx index 1ae249f45..939550d83 100644 --- a/app/javascript/mastodon/features/explore/index.js +++ b/app/javascript/mastodon/features/explore/index.jsx @@ -24,8 +24,6 @@ const mapStateToProps = state => ({ isSearching: state.getIn(['search', 'submitted']) || !showTrends, }); -export default @connect(mapStateToProps) -@injectIntl class Explore extends React.PureComponent { static contextTypes = { @@ -41,11 +39,11 @@ class Explore extends React.PureComponent { handleHeaderClick = () => { this.column.scrollTop(); - } + }; setRef = c => { this.column = c; - } + }; render() { const { intl, multiColumn, isSearching } = this.props; @@ -90,7 +88,9 @@ class Explore extends React.PureComponent { <Route path='/explore/tags' component={Tags} /> <Route path='/explore/links' component={Links} /> <Route path='/explore/suggestions' component={Suggestions} /> - <Route exact path={['/explore', '/explore/posts', '/search']} component={Statuses} componentParams={{ multiColumn }} /> + <Route exact path={['/explore', '/explore/posts', '/search']}> + <Statuses multiColumn={multiColumn} /> + </Route> </Switch> <Helmet> @@ -105,3 +105,5 @@ class Explore extends React.PureComponent { } } + +export default connect(mapStateToProps)(injectIntl(Explore)); diff --git a/app/javascript/mastodon/features/explore/links.js b/app/javascript/mastodon/features/explore/links.jsx index b47fc8fcf..193739916 100644 --- a/app/javascript/mastodon/features/explore/links.js +++ b/app/javascript/mastodon/features/explore/links.jsx @@ -13,7 +13,6 @@ const mapStateToProps = state => ({ isLoading: state.getIn(['trends', 'links', 'isLoading']), }); -export default @connect(mapStateToProps) class Links extends React.PureComponent { static propTypes = { @@ -68,3 +67,5 @@ class Links extends React.PureComponent { } } + +export default connect(mapStateToProps)(Links); diff --git a/app/javascript/mastodon/features/explore/results.js b/app/javascript/mastodon/features/explore/results.jsx index b2f6c72b7..9725cf35c 100644 --- a/app/javascript/mastodon/features/explore/results.js +++ b/app/javascript/mastodon/features/explore/results.jsx @@ -42,8 +42,6 @@ const renderStatuses = (results, onLoadMore) => appendLoadMore('statuses', resul <Status key={`status-${item}`} id={item} /> )), onLoadMore); -export default @connect(mapStateToProps) -@injectIntl class Results extends React.PureComponent { static propTypes = { @@ -107,7 +105,7 @@ class Results extends React.PureComponent { <React.Fragment> <div className='account__section-headline'> <button onClick={this.handleSelectAll} className={type === 'all' && 'active'}><FormattedMessage id='search_results.all' defaultMessage='All' /></button> - <button onClick={this.handleSelectAccounts} className={type === 'accounts' && 'active'}><FormattedMessage id='search_results.accounts' defaultMessage='People' /></button> + <button onClick={this.handleSelectAccounts} className={type === 'accounts' && 'active'}><FormattedMessage id='search_results.accounts' defaultMessage='Profiles' /></button> <button onClick={this.handleSelectHashtags} className={type === 'hashtags' && 'active'}><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></button> <button onClick={this.handleSelectStatuses} className={type === 'statuses' && 'active'}><FormattedMessage id='search_results.statuses' defaultMessage='Posts' /></button> </div> @@ -124,3 +122,5 @@ class Results extends React.PureComponent { } } + +export default connect(mapStateToProps)(injectIntl(Results)); diff --git a/app/javascript/mastodon/features/explore/statuses.js b/app/javascript/mastodon/features/explore/statuses.jsx index 791f11b9f..a98a6d046 100644 --- a/app/javascript/mastodon/features/explore/statuses.js +++ b/app/javascript/mastodon/features/explore/statuses.jsx @@ -14,7 +14,6 @@ const mapStateToProps = state => ({ hasMore: !!state.getIn(['status_lists', 'trending', 'next']), }); -export default @connect(mapStateToProps) class Statuses extends React.PureComponent { static propTypes = { @@ -33,7 +32,7 @@ class Statuses extends React.PureComponent { handleLoadMore = debounce(() => { const { dispatch } = this.props; dispatch(expandTrendingStatuses()); - }, 300, { leading: true }) + }, 300, { leading: true }); render () { const { isLoading, hasMore, statusIds, multiColumn } = this.props; @@ -62,3 +61,5 @@ class Statuses extends React.PureComponent { } } + +export default connect(mapStateToProps)(Statuses); diff --git a/app/javascript/mastodon/features/explore/suggestions.js b/app/javascript/mastodon/features/explore/suggestions.jsx index e6ad09974..53eb7ba4b 100644 --- a/app/javascript/mastodon/features/explore/suggestions.js +++ b/app/javascript/mastodon/features/explore/suggestions.jsx @@ -12,7 +12,6 @@ const mapStateToProps = state => ({ isLoading: state.getIn(['suggestions', 'isLoading']), }); -export default @connect(mapStateToProps) class Suggestions extends React.PureComponent { static propTypes = { @@ -49,3 +48,5 @@ class Suggestions extends React.PureComponent { } } + +export default connect(mapStateToProps)(Suggestions); diff --git a/app/javascript/mastodon/features/explore/tags.js b/app/javascript/mastodon/features/explore/tags.jsx index 258dc392f..3ba813c3f 100644 --- a/app/javascript/mastodon/features/explore/tags.js +++ b/app/javascript/mastodon/features/explore/tags.jsx @@ -13,7 +13,6 @@ const mapStateToProps = state => ({ isLoadingHashtags: state.getIn(['trends', 'tags', 'isLoading']), }); -export default @connect(mapStateToProps) class Tags extends React.PureComponent { static propTypes = { @@ -60,3 +59,5 @@ class Tags extends React.PureComponent { } } + +export default connect(mapStateToProps)(Tags); diff --git a/app/javascript/mastodon/features/favourited_statuses/index.js b/app/javascript/mastodon/features/favourited_statuses/index.jsx index 3741f68f6..2cbf00291 100644 --- a/app/javascript/mastodon/features/favourited_statuses/index.js +++ b/app/javascript/mastodon/features/favourited_statuses/index.jsx @@ -22,8 +22,6 @@ const mapStateToProps = state => ({ hasMore: !!state.getIn(['status_lists', 'favourites', 'next']), }); -export default @connect(mapStateToProps) -@injectIntl class Favourites extends ImmutablePureComponent { static propTypes = { @@ -48,24 +46,24 @@ class Favourites extends ImmutablePureComponent { } else { dispatch(addColumn('FAVOURITES', {})); } - } + }; handleMove = (dir) => { const { columnId, dispatch } = this.props; dispatch(moveColumn(columnId, dir)); - } + }; handleHeaderClick = () => { this.column.scrollTop(); - } + }; setRef = c => { this.column = c; - } + }; handleLoadMore = debounce(() => { this.props.dispatch(expandFavouritedStatuses()); - }, 300, { leading: true }) + }, 300, { leading: true }); render () { const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props; @@ -106,3 +104,5 @@ class Favourites extends ImmutablePureComponent { } } + +export default connect(mapStateToProps)(injectIntl(Favourites)); diff --git a/app/javascript/mastodon/features/favourites/index.js b/app/javascript/mastodon/features/favourites/index.jsx index 66ec6a31b..b1e81debd 100644 --- a/app/javascript/mastodon/features/favourites/index.js +++ b/app/javascript/mastodon/features/favourites/index.jsx @@ -21,8 +21,6 @@ const mapStateToProps = (state, props) => ({ accountIds: state.getIn(['user_lists', 'favourited_by', props.params.statusId]), }); -export default @connect(mapStateToProps) -@injectIntl class Favourites extends ImmutablePureComponent { static propTypes = { @@ -47,7 +45,7 @@ class Favourites extends ImmutablePureComponent { handleRefresh = () => { this.props.dispatch(fetchFavourites(this.props.params.statusId)); - } + }; render () { const { intl, accountIds, multiColumn } = this.props; @@ -90,3 +88,5 @@ class Favourites extends ImmutablePureComponent { } } + +export default connect(mapStateToProps)(injectIntl(Favourites)); diff --git a/app/javascript/mastodon/features/filters/added_to_filter.js b/app/javascript/mastodon/features/filters/added_to_filter.jsx index 3785eb3c5..d935d96e2 100644 --- a/app/javascript/mastodon/features/filters/added_to_filter.js +++ b/app/javascript/mastodon/features/filters/added_to_filter.jsx @@ -10,7 +10,6 @@ const mapStateToProps = (state, { filterId }) => ({ filter: state.getIn(['filters', filterId]), }); -export default @connect(mapStateToProps) class AddedToFilter extends React.PureComponent { static propTypes = { @@ -100,3 +99,5 @@ class AddedToFilter extends React.PureComponent { } } + +export default connect(mapStateToProps)(AddedToFilter); diff --git a/app/javascript/mastodon/features/filters/select_filter.js b/app/javascript/mastodon/features/filters/select_filter.jsx index b68a5de6c..5778eec8e 100644 --- a/app/javascript/mastodon/features/filters/select_filter.js +++ b/app/javascript/mastodon/features/filters/select_filter.jsx @@ -22,8 +22,6 @@ const mapStateToProps = (state, { contextType }) => ({ ]), }); -export default @connect(mapStateToProps) -@injectIntl class SelectFilter extends React.PureComponent { static propTypes = { @@ -67,15 +65,15 @@ class SelectFilter extends React.PureComponent { } return ( - <div key={filter[0]} role='button' tabIndex='0' data-index={filter[0]} className='language-dropdown__dropdown__results__item' onClick={this.handleItemClick} onKeyDown={this.handleKeyDown}> + <div key={filter[0]} role='button' tabIndex={0} data-index={filter[0]} className='language-dropdown__dropdown__results__item' onClick={this.handleItemClick} onKeyDown={this.handleKeyDown}> <span className='language-dropdown__dropdown__results__item__native-name'>{filter[1]}</span> {warning} </div> ); - } + }; renderCreateNew (name) { return ( - <div key='add-new-filter' role='button' tabIndex='0' className='language-dropdown__dropdown__results__item' onClick={this.handleNewFilterClick} onKeyDown={this.handleKeyDown}> + <div key='add-new-filter' role='button' tabIndex={0} className='language-dropdown__dropdown__results__item' onClick={this.handleNewFilterClick} onKeyDown={this.handleKeyDown}> <Icon id='plus' fixedWidth /> <FormattedMessage id='filter_modal.select_filter.prompt_new' defaultMessage='New category: {name}' values={{ name }} /> </div> ); @@ -83,11 +81,11 @@ class SelectFilter extends React.PureComponent { handleSearchChange = ({ target }) => { this.setState({ searchValue: target.value }); - } + }; setListRef = c => { this.listNode = c; - } + }; handleKeyDown = e => { const index = Array.from(this.listNode.childNodes).findIndex(node => node === e.currentTarget); @@ -125,7 +123,7 @@ class SelectFilter extends React.PureComponent { e.preventDefault(); e.stopPropagation(); } - } + }; handleSearchKeyDown = e => { let element = null; @@ -143,11 +141,11 @@ class SelectFilter extends React.PureComponent { break; } - } + }; handleClear = () => { this.setState({ searchValue: '' }); - } + }; handleItemClick = e => { const value = e.currentTarget.getAttribute('data-index'); @@ -155,7 +153,7 @@ class SelectFilter extends React.PureComponent { e.preventDefault(); this.props.onSelectFilter(value); - } + }; handleNewFilterClick = e => { e.preventDefault(); @@ -190,3 +188,5 @@ class SelectFilter extends React.PureComponent { } } + +export default connect(mapStateToProps)(injectIntl(SelectFilter)); diff --git a/app/javascript/mastodon/features/follow_recommendations/components/account.js b/app/javascript/mastodon/features/follow_recommendations/components/account.jsx index 14f4e7e1b..9cb26fe64 100644 --- a/app/javascript/mastodon/features/follow_recommendations/components/account.js +++ b/app/javascript/mastodon/features/follow_recommendations/components/account.jsx @@ -27,13 +27,11 @@ const makeMapStateToProps = () => { }; const getFirstSentence = str => { - const arr = str.split(/(([\.\?!]+\s)|[.。?!\n•])/); + const arr = str.split(/(([.?!]+\s)|[.。?!\n•])/); return arr[0]; }; -export default @connect(makeMapStateToProps) -@injectIntl class Account extends ImmutablePureComponent { static propTypes = { @@ -50,7 +48,7 @@ class Account extends ImmutablePureComponent { } else { dispatch(followAccount(account.get('id'))); } - } + }; render () { const { account, intl } = this.props; @@ -83,3 +81,5 @@ class Account extends ImmutablePureComponent { } } + +export default connect(makeMapStateToProps)(injectIntl(Account)); diff --git a/app/javascript/mastodon/features/follow_recommendations/index.js b/app/javascript/mastodon/features/follow_recommendations/index.jsx index 5f7baa64c..7ba34b51f 100644 --- a/app/javascript/mastodon/features/follow_recommendations/index.js +++ b/app/javascript/mastodon/features/follow_recommendations/index.jsx @@ -19,7 +19,6 @@ const mapStateToProps = state => ({ isLoading: state.getIn(['suggestions', 'isLoading']), }); -export default @connect(mapStateToProps) class FollowRecommendations extends ImmutablePureComponent { static contextTypes = { @@ -69,7 +68,7 @@ class FollowRecommendations extends ImmutablePureComponent { })); router.history.push('/home'); - } + }; render () { const { suggestions, isLoading } = this.props; @@ -114,3 +113,5 @@ class FollowRecommendations extends ImmutablePureComponent { } } + +export default connect(mapStateToProps)(FollowRecommendations); diff --git a/app/javascript/mastodon/features/follow_requests/components/account_authorize.js b/app/javascript/mastodon/features/follow_requests/components/account_authorize.jsx index d41f331e5..0bd45592c 100644 --- a/app/javascript/mastodon/features/follow_requests/components/account_authorize.js +++ b/app/javascript/mastodon/features/follow_requests/components/account_authorize.jsx @@ -13,7 +13,6 @@ const messages = defineMessages({ reject: { id: 'follow_request.reject', defaultMessage: 'Reject' }, }); -export default @injectIntl class AccountAuthorize extends ImmutablePureComponent { static propTypes = { @@ -47,3 +46,5 @@ class AccountAuthorize extends ImmutablePureComponent { } } + +export default injectIntl(AccountAuthorize); diff --git a/app/javascript/mastodon/features/follow_requests/index.js b/app/javascript/mastodon/features/follow_requests/index.jsx index d16aa7737..a8875bbd3 100644 --- a/app/javascript/mastodon/features/follow_requests/index.js +++ b/app/javascript/mastodon/features/follow_requests/index.jsx @@ -5,7 +5,6 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { debounce } from 'lodash'; -import LoadingIndicator from '../../components/loading_indicator'; import Column from '../ui/components/column'; import ColumnBackButtonSlim from '../../components/column_back_button_slim'; import AccountAuthorizeContainer from './containers/account_authorize_container'; @@ -26,8 +25,6 @@ const mapStateToProps = state => ({ domain: state.getIn(['meta', 'domain']), }); -export default @connect(mapStateToProps) -@injectIntl class FollowRequests extends ImmutablePureComponent { static propTypes = { @@ -53,16 +50,8 @@ class FollowRequests extends ImmutablePureComponent { render () { const { intl, accountIds, hasMore, multiColumn, locked, domain, isLoading } = this.props; - if (!accountIds) { - return ( - <Column> - <LoadingIndicator /> - </Column> - ); - } - const emptyMessage = <FormattedMessage id='empty_column.follow_requests' defaultMessage="You don't have any follow requests yet. When you receive one, it will show up here." />; - const unlockedPrependMessage = locked ? null : ( + const unlockedPrependMessage = !locked && accountIds.size > 0 && ( <div className='follow_requests-unlocked_explanation'> <FormattedMessage id='follow_requests.unlocked_explanation' @@ -80,6 +69,7 @@ class FollowRequests extends ImmutablePureComponent { onLoadMore={this.handleLoadMore} hasMore={hasMore} isLoading={isLoading} + showLoading={isLoading && accountIds.size === 0} emptyMessage={emptyMessage} bindToDocument={!multiColumn} prepend={unlockedPrependMessage} @@ -97,3 +87,5 @@ class FollowRequests extends ImmutablePureComponent { } } + +export default connect(mapStateToProps)(injectIntl(FollowRequests)); diff --git a/app/javascript/mastodon/features/followed_tags/index.jsx b/app/javascript/mastodon/features/followed_tags/index.jsx new file mode 100644 index 000000000..7c53542c2 --- /dev/null +++ b/app/javascript/mastodon/features/followed_tags/index.jsx @@ -0,0 +1,89 @@ +import { debounce } from 'lodash'; +import PropTypes from 'prop-types'; +import React from 'react'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { connect } from 'react-redux'; +import ColumnHeader from 'mastodon/components/column_header'; +import ScrollableList from 'mastodon/components/scrollable_list'; +import Column from 'mastodon/features/ui/components/column'; +import { Helmet } from 'react-helmet'; +import Hashtag from 'mastodon/components/hashtag'; +import { expandFollowedHashtags, fetchFollowedHashtags } from 'mastodon/actions/tags'; + +const messages = defineMessages({ + heading: { id: 'followed_tags', defaultMessage: 'Followed hashtags' }, +}); + +const mapStateToProps = state => ({ + hashtags: state.getIn(['followed_tags', 'items']), + isLoading: state.getIn(['followed_tags', 'isLoading'], true), + hasMore: !!state.getIn(['followed_tags', 'next']), +}); + +class FollowedTags extends ImmutablePureComponent { + + static propTypes = { + params: PropTypes.object.isRequired, + dispatch: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + hashtags: ImmutablePropTypes.list, + isLoading: PropTypes.bool, + hasMore: PropTypes.bool, + multiColumn: PropTypes.bool, + }; + + componentDidMount() { + this.props.dispatch(fetchFollowedHashtags()); + } + + handleLoadMore = debounce(() => { + this.props.dispatch(expandFollowedHashtags()); + }, 300, { leading: true }); + + render () { + const { intl, hashtags, isLoading, hasMore, multiColumn } = this.props; + + const emptyMessage = <FormattedMessage id='empty_column.followed_tags' defaultMessage='You have not followed any hashtags yet. When you do, they will show up here.' />; + + return ( + <Column bindToDocument={!multiColumn}> + <ColumnHeader + icon='hashtag' + title={intl.formatMessage(messages.heading)} + showBackButton + multiColumn={multiColumn} + /> + + <ScrollableList + scrollKey='followed_tags' + emptyMessage={emptyMessage} + hasMore={hasMore} + isLoading={isLoading} + onLoadMore={this.handleLoadMore} + bindToDocument={!multiColumn} + > + {hashtags.map((hashtag) => ( + <Hashtag + key={hashtag.get('name')} + name={hashtag.get('name')} + to={`/tags/${hashtag.get('name')}`} + withGraph={false} + // Taken from ImmutableHashtag. Should maybe refactor ImmutableHashtag to accept more options? + people={hashtag.getIn(['history', 0, 'accounts']) * 1 + hashtag.getIn(['history', 1, 'accounts']) * 1} + history={hashtag.get('history').reverse().map((day) => day.get('uses')).toArray()} + /> + ))} + </ScrollableList> + + <Helmet> + <meta name='robots' content='noindex' /> + </Helmet> + </Column> + ); + } + +} + +export default connect(mapStateToProps)(injectIntl(FollowedTags)); diff --git a/app/javascript/mastodon/features/followers/index.js b/app/javascript/mastodon/features/followers/index.jsx index 277eb702f..fe86ebb3b 100644 --- a/app/javascript/mastodon/features/followers/index.js +++ b/app/javascript/mastodon/features/followers/index.jsx @@ -54,7 +54,6 @@ RemoteHint.propTypes = { url: PropTypes.string.isRequired, }; -export default @connect(mapStateToProps) class Followers extends ImmutablePureComponent { static propTypes = { @@ -168,3 +167,5 @@ class Followers extends ImmutablePureComponent { } } + +export default connect(mapStateToProps)(Followers); diff --git a/app/javascript/mastodon/features/following/index.js b/app/javascript/mastodon/features/following/index.jsx index e23d9b35c..8095d73a3 100644 --- a/app/javascript/mastodon/features/following/index.js +++ b/app/javascript/mastodon/features/following/index.jsx @@ -54,7 +54,6 @@ RemoteHint.propTypes = { url: PropTypes.string.isRequired, }; -export default @connect(mapStateToProps) class Following extends ImmutablePureComponent { static propTypes = { @@ -168,3 +167,5 @@ class Following extends ImmutablePureComponent { } } + +export default connect(mapStateToProps)(Following); diff --git a/app/javascript/mastodon/features/generic_not_found/index.js b/app/javascript/mastodon/features/generic_not_found/index.jsx index 41cd61a5f..41cd61a5f 100644 --- a/app/javascript/mastodon/features/generic_not_found/index.js +++ b/app/javascript/mastodon/features/generic_not_found/index.jsx diff --git a/app/javascript/mastodon/features/getting_started/components/announcements.js b/app/javascript/mastodon/features/getting_started/components/announcements.jsx index 24db8cede..5f993d2c5 100644 --- a/app/javascript/mastodon/features/getting_started/components/announcements.js +++ b/app/javascript/mastodon/features/getting_started/components/announcements.jsx @@ -6,9 +6,8 @@ import PropTypes from 'prop-types'; import IconButton from 'mastodon/components/icon_button'; import Icon from 'mastodon/components/icon'; import { defineMessages, injectIntl, FormattedMessage, FormattedDate } from 'react-intl'; -import { autoPlayGif, reduceMotion, disableSwiping } from 'mastodon/initial_state'; +import { autoPlayGif, reduceMotion, disableSwiping, mascot } from 'mastodon/initial_state'; import elephantUIPlane from 'mastodon/../images/elephant_ui_plane.svg'; -import { mascot } from 'mastodon/initial_state'; import unicodeMapping from 'mastodon/features/emoji/emoji_unicode_mapping_light'; import classNames from 'classnames'; import EmojiPickerDropdown from 'mastodon/features/compose/containers/emoji_picker_dropdown_container'; @@ -35,7 +34,7 @@ class Content extends ImmutablePureComponent { setRef = c => { this.node = c; - } + }; componentDidMount () { this._updateLinks(); @@ -89,7 +88,7 @@ class Content extends ImmutablePureComponent { e.preventDefault(); this.context.router.history.push(`/@${mention.get('acct')}`); } - } + }; onHashtagClick = (hashtag, e) => { hashtag = hashtag.replace(/^#/, ''); @@ -98,14 +97,14 @@ class Content extends ImmutablePureComponent { e.preventDefault(); this.context.router.history.push(`/tags/${hashtag}`); } - } + }; onStatusClick = (status, e) => { if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { e.preventDefault(); this.context.router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`); } - } + }; handleMouseEnter = ({ currentTarget }) => { if (autoPlayGif) { @@ -118,7 +117,7 @@ class Content extends ImmutablePureComponent { let emoji = emojis[i]; emoji.src = emoji.getAttribute('data-original'); } - } + }; handleMouseLeave = ({ currentTarget }) => { if (autoPlayGif) { @@ -131,7 +130,7 @@ class Content extends ImmutablePureComponent { let emoji = emojis[i]; emoji.src = emoji.getAttribute('data-static'); } - } + }; render () { const { announcement } = this.props; @@ -216,11 +215,11 @@ class Reaction extends ImmutablePureComponent { } else { addReaction(announcementId, reaction.get('name')); } - } + }; - handleMouseEnter = () => this.setState({ hovered: true }) + handleMouseEnter = () => this.setState({ hovered: true }); - handleMouseLeave = () => this.setState({ hovered: false }) + handleMouseLeave = () => this.setState({ hovered: false }); render () { const { reaction } = this.props; @@ -254,7 +253,7 @@ class ReactionsBar extends ImmutablePureComponent { handleEmojiPick = data => { const { addReaction, announcementId } = this.props; addReaction(announcementId, data.native.replace(/:/g, '')); - } + }; willEnter () { return { scale: reduceMotion ? 1 : 0 }; @@ -356,7 +355,6 @@ class Announcement extends ImmutablePureComponent { } -export default @injectIntl class Announcements extends ImmutablePureComponent { static propTypes = { @@ -397,15 +395,15 @@ class Announcements extends ImmutablePureComponent { handleChangeIndex = index => { this.setState({ index: index % this.props.announcements.size }); - } + }; handleNextClick = () => { this.setState({ index: (this.state.index + 1) % this.props.announcements.size }); - } + }; handlePrevClick = () => { this.setState({ index: (this.props.announcements.size + this.state.index - 1) % this.props.announcements.size }); - } + }; render () { const { announcements, intl } = this.props; @@ -420,7 +418,7 @@ class Announcements extends ImmutablePureComponent { <img className='announcements__mastodon' alt='' draggable='false' src={mascot || elephantUIPlane} /> <div className='announcements__container'> - <ReactSwipeableViews animateHeight={!reduceMotion} adjustHeight={reduceMotion} index={index} onChangeIndex={this.handleChangeIndex}> + <ReactSwipeableViews animateHeight animateTransitions={!reduceMotion} index={index} onChangeIndex={this.handleChangeIndex}> {announcements.map((announcement, idx) => ( <Announcement key={announcement.get('id')} @@ -448,3 +446,5 @@ class Announcements extends ImmutablePureComponent { } } + +export default injectIntl(Announcements); diff --git a/app/javascript/mastodon/features/getting_started/components/trends.js b/app/javascript/mastodon/features/getting_started/components/trends.jsx index 8dcdb4f61..8dcdb4f61 100644 --- a/app/javascript/mastodon/features/getting_started/components/trends.js +++ b/app/javascript/mastodon/features/getting_started/components/trends.jsx diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.jsx index fc91070d1..29659acc7 100644 --- a/app/javascript/mastodon/features/getting_started/index.js +++ b/app/javascript/mastodon/features/getting_started/index.jsx @@ -23,7 +23,7 @@ const messages = defineMessages({ settings_subheading: { id: 'column_subheading.settings', defaultMessage: 'Settings' }, community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' }, explore: { id: 'navigation_bar.explore', defaultMessage: 'Explore' }, - direct: { id: 'navigation_bar.direct', defaultMessage: 'Direct messages' }, + direct: { id: 'navigation_bar.direct', defaultMessage: 'Private mentions' }, bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' }, preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' }, @@ -58,8 +58,6 @@ const badgeDisplay = (number, limit) => { } }; -export default @connect(mapStateToProps, mapDispatchToProps) -@injectIntl class GettingStarted extends ImmutablePureComponent { static contextTypes = { @@ -153,3 +151,5 @@ class GettingStarted extends ImmutablePureComponent { } } + +export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(GettingStarted)); diff --git a/app/javascript/mastodon/features/hashtag_timeline/components/column_settings.js b/app/javascript/mastodon/features/hashtag_timeline/components/column_settings.jsx index ac7863ed3..f140f2d01 100644 --- a/app/javascript/mastodon/features/hashtag_timeline/components/column_settings.js +++ b/app/javascript/mastodon/features/hashtag_timeline/components/column_settings.jsx @@ -12,7 +12,6 @@ const messages = defineMessages({ noOptions: { id: 'hashtag.column_settings.select.no_options_message', defaultMessage: 'No suggestions found' }, }); -export default @injectIntl class ColumnSettings extends React.PureComponent { static propTypes = { @@ -131,3 +130,5 @@ class ColumnSettings extends React.PureComponent { } } + +export default injectIntl(ColumnSettings); diff --git a/app/javascript/mastodon/features/hashtag_timeline/index.js b/app/javascript/mastodon/features/hashtag_timeline/index.jsx index 733f54ff3..a244dbdb2 100644 --- a/app/javascript/mastodon/features/hashtag_timeline/index.js +++ b/app/javascript/mastodon/features/hashtag_timeline/index.jsx @@ -26,8 +26,6 @@ const mapStateToProps = (state, props) => ({ tag: state.getIn(['tags', props.params.id]), }); -export default @connect(mapStateToProps) -@injectIntl class HashtagTimeline extends React.PureComponent { disconnects = []; @@ -54,7 +52,7 @@ class HashtagTimeline extends React.PureComponent { } else { dispatch(addColumn('HASHTAG', { id: this.props.params.id })); } - } + }; title = () => { const { id } = this.props.params; @@ -73,7 +71,7 @@ class HashtagTimeline extends React.PureComponent { } return title; - } + }; additionalFor = (mode) => { const { tags } = this.props.params; @@ -83,16 +81,16 @@ class HashtagTimeline extends React.PureComponent { } else { return ''; } - } + }; handleMove = (dir) => { const { columnId, dispatch } = this.props; dispatch(moveColumn(columnId, dir)); - } + }; handleHeaderClick = () => { this.column.scrollTop(); - } + }; _subscribe (dispatch, id, tags = {}, local) { const { signedIn } = this.context.identity; @@ -157,14 +155,14 @@ class HashtagTimeline extends React.PureComponent { setRef = c => { this.column = c; - } + }; handleLoadMore = maxId => { const { dispatch, params } = this.props; const { id, tags, local } = params; dispatch(expandHashtagTimeline(id, { maxId, tags, local })); - } + }; handleFollow = () => { const { dispatch, params, tag } = this.props; @@ -180,7 +178,7 @@ class HashtagTimeline extends React.PureComponent { } else { dispatch(followHashtag(id)); } - } + }; render () { const { hasUnread, columnId, multiColumn, tag, intl } = this.props; @@ -193,8 +191,12 @@ class HashtagTimeline extends React.PureComponent { if (tag) { const following = tag.get('following'); + const classes = classNames('column-header__button', { + active: following, + }); + followButton = ( - <button className={classNames('column-header__button')} onClick={this.handleFollow} disabled={!signedIn} active={following} title={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)} aria-label={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)}> + <button className={classes} onClick={this.handleFollow} disabled={!signedIn} title={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)} aria-label={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)}> <Icon id={following ? 'user-times' : 'user-plus'} fixedWidth className='column-header__icon' /> </button> ); @@ -235,3 +237,5 @@ class HashtagTimeline extends React.PureComponent { } } + +export default connect(mapStateToProps)(injectIntl(HashtagTimeline)); diff --git a/app/javascript/mastodon/features/home_timeline/components/column_settings.js b/app/javascript/mastodon/features/home_timeline/components/column_settings.jsx index 455e21881..bd15390c0 100644 --- a/app/javascript/mastodon/features/home_timeline/components/column_settings.js +++ b/app/javascript/mastodon/features/home_timeline/components/column_settings.jsx @@ -4,7 +4,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import { injectIntl, FormattedMessage } from 'react-intl'; import SettingToggle from '../../notifications/components/setting_toggle'; -export default @injectIntl class ColumnSettings extends React.PureComponent { static propTypes = { @@ -32,3 +31,5 @@ class ColumnSettings extends React.PureComponent { } } + +export default injectIntl(ColumnSettings); diff --git a/app/javascript/mastodon/features/home_timeline/index.js b/app/javascript/mastodon/features/home_timeline/index.jsx index ae11ccbe4..9aa4c6d82 100644 --- a/app/javascript/mastodon/features/home_timeline/index.js +++ b/app/javascript/mastodon/features/home_timeline/index.jsx @@ -30,8 +30,6 @@ const mapStateToProps = state => ({ showAnnouncements: state.getIn(['announcements', 'show']), }); -export default @connect(mapStateToProps) -@injectIntl class HomeTimeline extends React.PureComponent { static contextTypes = { @@ -58,24 +56,24 @@ class HomeTimeline extends React.PureComponent { } else { dispatch(addColumn('HOME', {})); } - } + }; handleMove = (dir) => { const { columnId, dispatch } = this.props; dispatch(moveColumn(columnId, dir)); - } + }; handleHeaderClick = () => { this.column.scrollTop(); - } + }; setRef = c => { this.column = c; - } + }; handleLoadMore = maxId => { this.props.dispatch(expandHomeTimeline({ maxId })); - } + }; componentDidMount () { setTimeout(() => this.props.dispatch(fetchAnnouncements()), 700); @@ -114,7 +112,7 @@ class HomeTimeline extends React.PureComponent { handleToggleAnnouncementsClick = (e) => { e.stopPropagation(); this.props.dispatch(toggleShowAnnouncements()); - } + }; render () { const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props; @@ -174,3 +172,5 @@ class HomeTimeline extends React.PureComponent { } } + +export default connect(mapStateToProps)(injectIntl(HomeTimeline)); diff --git a/app/javascript/mastodon/features/interaction_modal/index.js b/app/javascript/mastodon/features/interaction_modal/index.jsx index d4535378f..5742f1104 100644 --- a/app/javascript/mastodon/features/interaction_modal/index.js +++ b/app/javascript/mastodon/features/interaction_modal/index.jsx @@ -30,14 +30,14 @@ class Copypaste extends React.PureComponent { setRef = c => { this.input = c; - } + }; handleInputClick = () => { this.setState({ copied: false }); this.input.focus(); this.input.select(); this.input.setSelectionRange(0, this.input.value.length); - } + }; handleButtonClick = () => { const { value } = this.props; @@ -45,7 +45,7 @@ class Copypaste extends React.PureComponent { this.input.blur(); this.setState({ copied: true }); this.timeout = setTimeout(() => this.setState({ copied: false }), 700); - } + }; componentWillUnmount () { if (this.timeout) clearTimeout(this.timeout); @@ -74,7 +74,6 @@ class Copypaste extends React.PureComponent { } -export default @connect(mapStateToProps, mapDispatchToProps) class InteractionModal extends React.PureComponent { static propTypes = { @@ -86,7 +85,7 @@ class InteractionModal extends React.PureComponent { handleSignupClick = () => { this.props.onSignupClick(); - } + }; render () { const { url, type, displayNameHtml } = this.props; @@ -159,3 +158,5 @@ class InteractionModal extends React.PureComponent { } } + +export default connect(mapStateToProps, mapDispatchToProps)(InteractionModal); diff --git a/app/javascript/mastodon/features/keyboard_shortcuts/index.js b/app/javascript/mastodon/features/keyboard_shortcuts/index.jsx index 9a870478d..70f019712 100644 --- a/app/javascript/mastodon/features/keyboard_shortcuts/index.js +++ b/app/javascript/mastodon/features/keyboard_shortcuts/index.jsx @@ -10,7 +10,6 @@ const messages = defineMessages({ heading: { id: 'keyboard_shortcuts.heading', defaultMessage: 'Keyboard Shortcuts' }, }); -export default @injectIntl class KeyboardShortcuts extends ImmutablePureComponent { static propTypes = { @@ -174,3 +173,5 @@ class KeyboardShortcuts extends ImmutablePureComponent { } } + +export default injectIntl(KeyboardShortcuts); diff --git a/app/javascript/mastodon/features/list_adder/components/account.js b/app/javascript/mastodon/features/list_adder/components/account.jsx index 1369aac07..786af3bb1 100644 --- a/app/javascript/mastodon/features/list_adder/components/account.js +++ b/app/javascript/mastodon/features/list_adder/components/account.jsx @@ -17,9 +17,6 @@ const makeMapStateToProps = () => { return mapStateToProps; }; - -export default @connect(makeMapStateToProps) -@injectIntl class Account extends ImmutablePureComponent { static propTypes = { @@ -41,3 +38,5 @@ class Account extends ImmutablePureComponent { } } + +export default connect(makeMapStateToProps)(injectIntl(Account)); diff --git a/app/javascript/mastodon/features/list_adder/components/list.js b/app/javascript/mastodon/features/list_adder/components/list.jsx index 60c8958a7..34ccf8451 100644 --- a/app/javascript/mastodon/features/list_adder/components/list.js +++ b/app/javascript/mastodon/features/list_adder/components/list.jsx @@ -23,8 +23,6 @@ const mapDispatchToProps = (dispatch, { listId }) => ({ onAdd: () => dispatch(addToListAdder(listId)), }); -export default @connect(MapStateToProps, mapDispatchToProps) -@injectIntl class List extends ImmutablePureComponent { static propTypes = { @@ -67,3 +65,5 @@ class List extends ImmutablePureComponent { } } + +export default connect(MapStateToProps, mapDispatchToProps)(injectIntl(List)); diff --git a/app/javascript/mastodon/features/list_adder/index.js b/app/javascript/mastodon/features/list_adder/index.jsx index cb8a15e8c..45d5589f9 100644 --- a/app/javascript/mastodon/features/list_adder/index.js +++ b/app/javascript/mastodon/features/list_adder/index.jsx @@ -28,8 +28,6 @@ const mapDispatchToProps = dispatch => ({ onReset: () => dispatch(resetListAdder()), }); -export default @connect(mapStateToProps, mapDispatchToProps) -@injectIntl class ListAdder extends ImmutablePureComponent { static propTypes = { @@ -71,3 +69,5 @@ class ListAdder extends ImmutablePureComponent { } } + +export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(ListAdder)); diff --git a/app/javascript/mastodon/features/list_editor/components/account.js b/app/javascript/mastodon/features/list_editor/components/account.jsx index 48085af43..86209bb3c 100644 --- a/app/javascript/mastodon/features/list_editor/components/account.js +++ b/app/javascript/mastodon/features/list_editor/components/account.jsx @@ -31,8 +31,6 @@ const mapDispatchToProps = (dispatch, { accountId }) => ({ onAdd: () => dispatch(addToListEditor(accountId)), }); -export default @connect(makeMapStateToProps, mapDispatchToProps) -@injectIntl class Account extends ImmutablePureComponent { static propTypes = { @@ -75,3 +73,5 @@ class Account extends ImmutablePureComponent { } } + +export default connect(makeMapStateToProps, mapDispatchToProps)(injectIntl(Account)); diff --git a/app/javascript/mastodon/features/list_editor/components/edit_list_form.js b/app/javascript/mastodon/features/list_editor/components/edit_list_form.jsx index 3ccab12a8..9c1c244cb 100644 --- a/app/javascript/mastodon/features/list_editor/components/edit_list_form.js +++ b/app/javascript/mastodon/features/list_editor/components/edit_list_form.jsx @@ -19,8 +19,6 @@ const mapDispatchToProps = dispatch => ({ onSubmit: () => dispatch(submitListEditor(false)), }); -export default @connect(mapStateToProps, mapDispatchToProps) -@injectIntl class ListForm extends React.PureComponent { static propTypes = { @@ -33,16 +31,16 @@ class ListForm extends React.PureComponent { handleChange = e => { this.props.onChange(e.target.value); - } + }; handleSubmit = e => { e.preventDefault(); this.props.onSubmit(); - } + }; handleClick = () => { this.props.onSubmit(); - } + }; render () { const { value, disabled, intl } = this.props; @@ -68,3 +66,5 @@ class ListForm extends React.PureComponent { } } + +export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(ListForm)); diff --git a/app/javascript/mastodon/features/list_editor/components/search.js b/app/javascript/mastodon/features/list_editor/components/search.jsx index e3f069bb8..f70e272f7 100644 --- a/app/javascript/mastodon/features/list_editor/components/search.js +++ b/app/javascript/mastodon/features/list_editor/components/search.jsx @@ -20,8 +20,6 @@ const mapDispatchToProps = dispatch => ({ onChange: value => dispatch(changeListSuggestions(value)), }); -export default @connect(mapStateToProps, mapDispatchToProps) -@injectIntl class Search extends React.PureComponent { static propTypes = { @@ -34,17 +32,17 @@ class Search extends React.PureComponent { handleChange = e => { this.props.onChange(e.target.value); - } + }; handleKeyUp = e => { if (e.keyCode === 13) { this.props.onSubmit(this.props.value); } - } + }; handleClear = () => { this.props.onClear(); - } + }; render () { const { value, intl } = this.props; @@ -65,7 +63,7 @@ class Search extends React.PureComponent { /> </label> - <div role='button' tabIndex='0' className='search__icon' onClick={this.handleClear}> + <div role='button' tabIndex={0} className='search__icon' onClick={this.handleClear}> <Icon id='search' className={classNames({ active: !hasValue })} /> <Icon id='times-circle' aria-label={intl.formatMessage(messages.search)} className={classNames({ active: hasValue })} /> </div> @@ -74,3 +72,5 @@ class Search extends React.PureComponent { } } + +export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(Search)); diff --git a/app/javascript/mastodon/features/list_editor/index.js b/app/javascript/mastodon/features/list_editor/index.jsx index 48466604a..ed9d09132 100644 --- a/app/javascript/mastodon/features/list_editor/index.js +++ b/app/javascript/mastodon/features/list_editor/index.jsx @@ -22,8 +22,6 @@ const mapDispatchToProps = dispatch => ({ onReset: () => dispatch(resetListEditor()), }); -export default @connect(mapStateToProps, mapDispatchToProps) -@injectIntl class ListEditor extends ImmutablePureComponent { static propTypes = { @@ -62,7 +60,7 @@ class ListEditor extends ImmutablePureComponent { {accountIds.map(accountId => <Account key={accountId} accountId={accountId} added />)} </div> - {showSearch && <div role='button' tabIndex='-1' className='drawer__backdrop' onClick={onClear} />} + {showSearch && <div role='button' tabIndex={-1} className='drawer__backdrop' onClick={onClear} />} <Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}> {({ x }) => ( @@ -77,3 +75,5 @@ class ListEditor extends ImmutablePureComponent { } } + +export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(ListEditor)); diff --git a/app/javascript/mastodon/features/list_timeline/index.js b/app/javascript/mastodon/features/list_timeline/index.jsx index c2e72e2e9..c0b9a62ff 100644 --- a/app/javascript/mastodon/features/list_timeline/index.js +++ b/app/javascript/mastodon/features/list_timeline/index.jsx @@ -31,8 +31,6 @@ const mapStateToProps = (state, props) => ({ hasUnread: state.getIn(['timelines', `list:${props.params.id}`, 'unread']) > 0, }); -export default @connect(mapStateToProps) -@injectIntl class ListTimeline extends React.PureComponent { static contextTypes = { @@ -58,16 +56,16 @@ class ListTimeline extends React.PureComponent { dispatch(addColumn('LIST', { id: this.props.params.id })); this.context.router.history.push('/'); } - } + }; handleMove = (dir) => { const { columnId, dispatch } = this.props; dispatch(moveColumn(columnId, dir)); - } + }; handleHeaderClick = () => { this.column.scrollTop(); - } + }; componentDidMount () { const { dispatch } = this.props; @@ -105,16 +103,16 @@ class ListTimeline extends React.PureComponent { setRef = c => { this.column = c; - } + }; handleLoadMore = maxId => { const { id } = this.props.params; this.props.dispatch(expandListTimeline(id, { maxId })); - } + }; handleEditClick = () => { this.props.dispatch(openModal('LIST_EDITOR', { listId: this.props.params.id })); - } + }; handleDeleteClick = () => { const { dispatch, columnId, intl } = this.props; @@ -133,13 +131,13 @@ class ListTimeline extends React.PureComponent { } }, })); - } + }; handleRepliesPolicyChange = ({ target }) => { const { dispatch } = this.props; const { id } = this.props.params; dispatch(updateList(id, undefined, false, target.value)); - } + }; render () { const { hasUnread, columnId, multiColumn, list, intl } = this.props; @@ -178,11 +176,11 @@ class ListTimeline extends React.PureComponent { multiColumn={multiColumn} > <div className='column-settings__row column-header__links'> - <button type='button' className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.handleEditClick}> + <button type='button' className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleEditClick}> <Icon id='pencil' /> <FormattedMessage id='lists.edit' defaultMessage='Edit list' /> </button> - <button type='button' className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.handleDeleteClick}> + <button type='button' className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleDeleteClick}> <Icon id='trash' /> <FormattedMessage id='lists.delete' defaultMessage='Delete list' /> </button> </div> @@ -219,3 +217,5 @@ class ListTimeline extends React.PureComponent { } } + +export default connect(mapStateToProps)(injectIntl(ListTimeline)); diff --git a/app/javascript/mastodon/features/lists/components/new_list_form.js b/app/javascript/mastodon/features/lists/components/new_list_form.jsx index f790ccbe6..50b52518e 100644 --- a/app/javascript/mastodon/features/lists/components/new_list_form.js +++ b/app/javascript/mastodon/features/lists/components/new_list_form.jsx @@ -20,8 +20,6 @@ const mapDispatchToProps = dispatch => ({ onSubmit: () => dispatch(submitListEditor(true)), }); -export default @connect(mapStateToProps, mapDispatchToProps) -@injectIntl class NewListForm extends React.PureComponent { static propTypes = { @@ -34,16 +32,16 @@ class NewListForm extends React.PureComponent { handleChange = e => { this.props.onChange(e.target.value); - } + }; handleSubmit = e => { e.preventDefault(); this.props.onSubmit(); - } + }; handleClick = () => { this.props.onSubmit(); - } + }; render () { const { value, disabled, intl } = this.props; @@ -75,3 +73,5 @@ class NewListForm extends React.PureComponent { } } + +export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(NewListForm)); diff --git a/app/javascript/mastodon/features/lists/index.js b/app/javascript/mastodon/features/lists/index.jsx index 3a0b1373a..afd645a30 100644 --- a/app/javascript/mastodon/features/lists/index.js +++ b/app/javascript/mastodon/features/lists/index.jsx @@ -32,8 +32,6 @@ const mapStateToProps = state => ({ lists: getOrderedLists(state), }); -export default @connect(mapStateToProps) -@injectIntl class Lists extends ImmutablePureComponent { static propTypes = { @@ -87,3 +85,5 @@ class Lists extends ImmutablePureComponent { } } + +export default connect(mapStateToProps)(injectIntl(Lists)); diff --git a/app/javascript/mastodon/features/mutes/index.js b/app/javascript/mastodon/features/mutes/index.jsx index 65df6149f..5c05d2f70 100644 --- a/app/javascript/mastodon/features/mutes/index.js +++ b/app/javascript/mastodon/features/mutes/index.jsx @@ -23,8 +23,6 @@ const mapStateToProps = state => ({ isLoading: state.getIn(['user_lists', 'mutes', 'isLoading'], true), }); -export default @connect(mapStateToProps) -@injectIntl class Mutes extends ImmutablePureComponent { static propTypes = { @@ -82,3 +80,5 @@ class Mutes extends ImmutablePureComponent { } } + +export default connect(mapStateToProps)(injectIntl(Mutes)); diff --git a/app/javascript/mastodon/features/notifications/components/clear_column_button.js b/app/javascript/mastodon/features/notifications/components/clear_column_button.jsx index b82fd092f..9a076ce5e 100644 --- a/app/javascript/mastodon/features/notifications/components/clear_column_button.js +++ b/app/javascript/mastodon/features/notifications/components/clear_column_button.jsx @@ -11,7 +11,7 @@ export default class ClearColumnButton extends React.PureComponent { render () { return ( - <button className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.props.onClick}><Icon id='eraser' /> <FormattedMessage id='notifications.clear' defaultMessage='Clear notifications' /></button> + <button className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.props.onClick}><Icon id='eraser' /> <FormattedMessage id='notifications.clear' defaultMessage='Clear notifications' /></button> ); } diff --git a/app/javascript/mastodon/features/notifications/components/column_settings.js b/app/javascript/mastodon/features/notifications/components/column_settings.jsx index a38f8d3c2..9251847ba 100644 --- a/app/javascript/mastodon/features/notifications/components/column_settings.js +++ b/app/javascript/mastodon/features/notifications/components/column_settings.jsx @@ -26,7 +26,7 @@ export default class ColumnSettings extends React.PureComponent { onPushChange = (path, checked) => { this.props.onChange(['push', ...path], checked); - } + }; render () { const { settings, pushSettings, onChange, onClear, alertsEnabled, browserSupport, browserPermission, onRequestNotificationPermission } = this.props; diff --git a/app/javascript/mastodon/features/notifications/components/filter_bar.js b/app/javascript/mastodon/features/notifications/components/filter_bar.jsx index 368eb0b7e..88e95cf48 100644 --- a/app/javascript/mastodon/features/notifications/components/filter_bar.js +++ b/app/javascript/mastodon/features/notifications/components/filter_bar.jsx @@ -12,7 +12,6 @@ const tooltips = defineMessages({ statuses: { id: 'notifications.filter.statuses', defaultMessage: 'Updates from people you follow' }, }); -export default @injectIntl class FilterBar extends React.PureComponent { static propTypes = { @@ -108,3 +107,5 @@ class FilterBar extends React.PureComponent { } } + +export default injectIntl(FilterBar); diff --git a/app/javascript/mastodon/features/notifications/components/follow_request.js b/app/javascript/mastodon/features/notifications/components/follow_request.jsx index 08de875e3..01bf67065 100644 --- a/app/javascript/mastodon/features/notifications/components/follow_request.js +++ b/app/javascript/mastodon/features/notifications/components/follow_request.jsx @@ -1,4 +1,4 @@ -import React, { Fragment } from 'react'; +import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import Avatar from 'mastodon/components/avatar'; @@ -13,7 +13,6 @@ const messages = defineMessages({ reject: { id: 'follow_request.reject', defaultMessage: 'Reject' }, }); -export default @injectIntl class FollowRequest extends ImmutablePureComponent { static propTypes = { @@ -32,10 +31,10 @@ class FollowRequest extends ImmutablePureComponent { if (hidden) { return ( - <Fragment> + <React.Fragment> {account.get('display_name')} {account.get('username')} - </Fragment> + </React.Fragment> ); } @@ -57,3 +56,5 @@ class FollowRequest extends ImmutablePureComponent { } } + +export default injectIntl(FollowRequest); diff --git a/app/javascript/mastodon/features/notifications/components/grant_permission_button.js b/app/javascript/mastodon/features/notifications/components/grant_permission_button.jsx index 798e4c787..5b2db48fd 100644 --- a/app/javascript/mastodon/features/notifications/components/grant_permission_button.js +++ b/app/javascript/mastodon/features/notifications/components/grant_permission_button.jsx @@ -10,7 +10,7 @@ export default class GrantPermissionButton extends React.PureComponent { render () { return ( - <button className='text-btn column-header__permission-btn' tabIndex='0' onClick={this.props.onClick}> + <button className='text-btn column-header__permission-btn' tabIndex={0} onClick={this.props.onClick}> <FormattedMessage id='notifications.grant_permission' defaultMessage='Grant permission.' /> </button> ); diff --git a/app/javascript/mastodon/features/notifications/components/notification.js b/app/javascript/mastodon/features/notifications/components/notification.jsx index ea2c9c0a4..f3104cee0 100644 --- a/app/javascript/mastodon/features/notifications/components/notification.js +++ b/app/javascript/mastodon/features/notifications/components/notification.jsx @@ -33,7 +33,6 @@ const notificationForScreenReader = (intl, message, timestamp) => { return output.join(', '); }; -export default @injectIntl class Notification extends ImmutablePureComponent { static contextTypes = { @@ -61,12 +60,12 @@ class Notification extends ImmutablePureComponent { handleMoveUp = () => { const { notification, onMoveUp } = this.props; onMoveUp(notification.get('id')); - } + }; handleMoveDown = () => { const { notification, onMoveDown } = this.props; onMoveDown(notification.get('id')); - } + }; handleOpen = () => { const { notification } = this.props; @@ -76,34 +75,34 @@ class Notification extends ImmutablePureComponent { } else { this.handleOpenProfile(); } - } + }; handleOpenProfile = () => { const { notification } = this.props; this.context.router.history.push(`/@${notification.getIn(['account', 'acct'])}`); - } + }; handleMention = e => { e.preventDefault(); const { notification, onMention } = this.props; onMention(notification.get('account'), this.context.router.history); - } + }; handleHotkeyFavourite = () => { const { status } = this.props; if (status) this.props.onFavourite(status); - } + }; handleHotkeyBoost = e => { const { status } = this.props; if (status) this.props.onReblog(status, e); - } + }; handleHotkeyToggleHidden = () => { const { status } = this.props; if (status) this.props.onToggleHidden(status); - } + }; getHandlers () { return { @@ -124,7 +123,7 @@ class Notification extends ImmutablePureComponent { return ( <HotKeys handlers={this.getHandlers()}> - <div className={classNames('notification notification-follow focusable', { unread })} tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.follow, { name: account.get('acct') }), notification.get('created_at'))}> + <div className={classNames('notification notification-follow focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.follow, { name: account.get('acct') }), notification.get('created_at'))}> <div className='notification__message'> <div className='notification__favourite-icon-wrapper'> <Icon id='user-plus' fixedWidth /> @@ -146,7 +145,7 @@ class Notification extends ImmutablePureComponent { return ( <HotKeys handlers={this.getHandlers()}> - <div className={classNames('notification notification-follow-request focusable', { unread })} tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.follow_request', defaultMessage: '{name} has requested to follow you' }, { name: account.get('acct') }), notification.get('created_at'))}> + <div className={classNames('notification notification-follow-request focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.follow_request', defaultMessage: '{name} has requested to follow you' }, { name: account.get('acct') }), notification.get('created_at'))}> <div className='notification__message'> <div className='notification__favourite-icon-wrapper'> <Icon id='user' fixedWidth /> @@ -186,7 +185,7 @@ class Notification extends ImmutablePureComponent { return ( <HotKeys handlers={this.getHandlers()}> - <div className={classNames('notification notification-favourite focusable', { unread })} tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.favourite, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}> + <div className={classNames('notification notification-favourite focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.favourite, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}> <div className='notification__message'> <div className='notification__favourite-icon-wrapper'> <Icon id='star' className='star-icon' fixedWidth /> @@ -218,7 +217,7 @@ class Notification extends ImmutablePureComponent { return ( <HotKeys handlers={this.getHandlers()}> - <div className={classNames('notification notification-reblog focusable', { unread })} tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.reblog, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}> + <div className={classNames('notification notification-reblog focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.reblog, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}> <div className='notification__message'> <div className='notification__favourite-icon-wrapper'> <Icon id='retweet' fixedWidth /> @@ -246,11 +245,15 @@ class Notification extends ImmutablePureComponent { } renderStatus (notification, link) { - const { intl, unread } = this.props; + const { intl, unread, status } = this.props; + + if (!status) { + return null; + } return ( <HotKeys handlers={this.getHandlers()}> - <div className={classNames('notification notification-status focusable', { unread })} tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.status, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}> + <div className={classNames('notification notification-status focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.status, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}> <div className='notification__message'> <div className='notification__favourite-icon-wrapper'> <Icon id='home' fixedWidth /> @@ -264,6 +267,7 @@ class Notification extends ImmutablePureComponent { <StatusContainer id={notification.get('status')} account={notification.get('account')} + contextType='notifications' muted withDismiss hidden={this.props.hidden} @@ -278,11 +282,15 @@ class Notification extends ImmutablePureComponent { } renderUpdate (notification, link) { - const { intl, unread } = this.props; + const { intl, unread, status } = this.props; + + if (!status) { + return null; + } return ( <HotKeys handlers={this.getHandlers()}> - <div className={classNames('notification notification-update focusable', { unread })} tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.update, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}> + <div className={classNames('notification notification-update focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.update, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}> <div className='notification__message'> <div className='notification__favourite-icon-wrapper'> <Icon id='pencil' fixedWidth /> @@ -296,6 +304,7 @@ class Notification extends ImmutablePureComponent { <StatusContainer id={notification.get('status')} account={notification.get('account')} + contextType='notifications' muted withDismiss hidden={this.props.hidden} @@ -310,13 +319,17 @@ class Notification extends ImmutablePureComponent { } renderPoll (notification, account) { - const { intl, unread } = this.props; + const { intl, unread, status } = this.props; const ownPoll = me === account.get('id'); const message = ownPoll ? intl.formatMessage(messages.ownPoll) : intl.formatMessage(messages.poll); + if (!status) { + return null; + } + return ( <HotKeys handlers={this.getHandlers()}> - <div className={classNames('notification notification-poll focusable', { unread })} tabIndex='0' aria-label={notificationForScreenReader(intl, message, notification.get('created_at'))}> + <div className={classNames('notification notification-poll focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, message, notification.get('created_at'))}> <div className='notification__message'> <div className='notification__favourite-icon-wrapper'> <Icon id='tasks' fixedWidth /> @@ -334,6 +347,7 @@ class Notification extends ImmutablePureComponent { <StatusContainer id={notification.get('status')} account={account} + contextType='notifications' muted withDismiss hidden={this.props.hidden} @@ -352,7 +366,7 @@ class Notification extends ImmutablePureComponent { return ( <HotKeys handlers={this.getHandlers()}> - <div className={classNames('notification notification-admin-sign-up focusable', { unread })} tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.adminSignUp, { name: account.get('acct') }), notification.get('created_at'))}> + <div className={classNames('notification notification-admin-sign-up focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.adminSignUp, { name: account.get('acct') }), notification.get('created_at'))}> <div className='notification__message'> <div className='notification__favourite-icon-wrapper'> <Icon id='user-plus' fixedWidth /> @@ -382,7 +396,7 @@ class Notification extends ImmutablePureComponent { return ( <HotKeys handlers={this.getHandlers()}> - <div className={classNames('notification notification-admin-report focusable', { unread })} tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.adminReport, { name: account.get('acct'), target: notification.getIn(['report', 'target_account', 'acct']) }), notification.get('created_at'))}> + <div className={classNames('notification notification-admin-report focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.adminReport, { name: account.get('acct'), target: notification.getIn(['report', 'target_account', 'acct']) }), notification.get('created_at'))}> <div className='notification__message'> <div className='notification__favourite-icon-wrapper'> <Icon id='flag' fixedWidth /> @@ -432,3 +446,5 @@ class Notification extends ImmutablePureComponent { } } + +export default injectIntl(Notification); diff --git a/app/javascript/mastodon/features/notifications/components/notifications_permission_banner.js b/app/javascript/mastodon/features/notifications/components/notifications_permission_banner.jsx index df9b7fb1b..c54137e60 100644 --- a/app/javascript/mastodon/features/notifications/components/notifications_permission_banner.js +++ b/app/javascript/mastodon/features/notifications/components/notifications_permission_banner.jsx @@ -12,8 +12,6 @@ const messages = defineMessages({ close: { id: 'lightbox.close', defaultMessage: 'Close' }, }); -export default @connect() -@injectIntl class NotificationsPermissionBanner extends React.PureComponent { static propTypes = { @@ -23,11 +21,11 @@ class NotificationsPermissionBanner extends React.PureComponent { handleClick = () => { this.props.dispatch(requestBrowserPermission()); - } + }; handleClose = () => { this.props.dispatch(changeSetting(['notifications', 'dismissPermissionBanner'], true)); - } + }; render () { const { intl } = this.props; @@ -46,3 +44,5 @@ class NotificationsPermissionBanner extends React.PureComponent { } } + +export default connect()(injectIntl(NotificationsPermissionBanner)); diff --git a/app/javascript/mastodon/features/notifications/components/report.js b/app/javascript/mastodon/features/notifications/components/report.jsx index 3ce3eb9d3..4663b2359 100644 --- a/app/javascript/mastodon/features/notifications/components/report.js +++ b/app/javascript/mastodon/features/notifications/components/report.jsx @@ -13,7 +13,6 @@ const messages = defineMessages({ violation: { id: 'report_notification.categories.violation', defaultMessage: 'Rule violation' }, }); -export default @injectIntl class Report extends ImmutablePureComponent { static propTypes = { @@ -60,3 +59,5 @@ class Report extends ImmutablePureComponent { } } + +export default injectIntl(Report); diff --git a/app/javascript/mastodon/features/notifications/components/setting_toggle.js b/app/javascript/mastodon/features/notifications/components/setting_toggle.jsx index c4c8bffbe..c979e4383 100644 --- a/app/javascript/mastodon/features/notifications/components/setting_toggle.js +++ b/app/javascript/mastodon/features/notifications/components/setting_toggle.jsx @@ -13,11 +13,11 @@ export default class SettingToggle extends React.PureComponent { onChange: PropTypes.func.isRequired, defaultValue: PropTypes.bool, disabled: PropTypes.bool, - } + }; onChange = ({ target }) => { this.props.onChange(this.props.settingPath, target.checked); - } + }; render () { const { prefix, settings, settingPath, label, defaultValue, disabled } = this.props; diff --git a/app/javascript/mastodon/features/notifications/containers/column_settings_container.js b/app/javascript/mastodon/features/notifications/containers/column_settings_container.js index 9a70bd4f3..515afaca9 100644 --- a/app/javascript/mastodon/features/notifications/containers/column_settings_container.js +++ b/app/javascript/mastodon/features/notifications/containers/column_settings_container.js @@ -2,8 +2,7 @@ import { connect } from 'react-redux'; import { defineMessages, injectIntl } from 'react-intl'; import ColumnSettings from '../components/column_settings'; import { changeSetting } from '../../../actions/settings'; -import { setFilter } from '../../../actions/notifications'; -import { clearNotifications, requestBrowserPermission } from '../../../actions/notifications'; +import { setFilter, clearNotifications, requestBrowserPermission } from '../../../actions/notifications'; import { changeAlerts as changePushNotifications } from '../../../actions/push_notifications'; import { openModal } from '../../../actions/modal'; import { showAlert } from '../../../actions/alerts'; diff --git a/app/javascript/mastodon/features/notifications/containers/notification_container.js b/app/javascript/mastodon/features/notifications/containers/notification_container.js index 8bd5b3d78..8c5688acb 100644 --- a/app/javascript/mastodon/features/notifications/containers/notification_container.js +++ b/app/javascript/mastodon/features/notifications/containers/notification_container.js @@ -24,7 +24,7 @@ const makeMapStateToProps = () => { const notification = getNotification(state, props.notification, props.accountId); return { notification: notification, - status: notification.get('status') ? getStatus(state, { id: notification.get('status') }) : null, + status: notification.get('status') ? getStatus(state, { id: notification.get('status'), contextType: 'notifications' }) : null, report: notification.get('report') ? getReport(state, notification.get('report'), notification.getIn(['report', 'target_account', 'id'])) : null, }; }; diff --git a/app/javascript/mastodon/features/notifications/index.js b/app/javascript/mastodon/features/notifications/index.jsx index 826a7e9ad..bb8852abf 100644 --- a/app/javascript/mastodon/features/notifications/index.js +++ b/app/javascript/mastodon/features/notifications/index.jsx @@ -67,8 +67,6 @@ const mapStateToProps = state => ({ needsNotificationPermission: state.getIn(['settings', 'notifications', 'alerts']).includes(true) && state.getIn(['notifications', 'browserSupport']) && state.getIn(['notifications', 'browserPermission']) === 'default' && !state.getIn(['settings', 'notifications', 'dismissPermissionBanner']), }); -export default @connect(mapStateToProps) -@injectIntl class Notifications extends React.PureComponent { static contextTypes = { @@ -136,30 +134,30 @@ class Notifications extends React.PureComponent { } else { dispatch(addColumn('NOTIFICATIONS', {})); } - } + }; handleMove = (dir) => { const { columnId, dispatch } = this.props; dispatch(moveColumn(columnId, dir)); - } + }; handleHeaderClick = () => { this.column.scrollTop(); - } + }; setColumnRef = c => { this.column = c; - } + }; handleMoveUp = id => { const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) - 1; this._selectChild(elementIndex, true); - } + }; handleMoveDown = id => { const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) + 1; this._selectChild(elementIndex, false); - } + }; _selectChild (index, align_top) { const container = this.column.node; @@ -288,3 +286,5 @@ class Notifications extends React.PureComponent { } } + +export default connect(mapStateToProps)(injectIntl(Notifications)); diff --git a/app/javascript/mastodon/features/picture_in_picture/components/footer.js b/app/javascript/mastodon/features/picture_in_picture/components/footer.jsx index 0dff834c3..66124f331 100644 --- a/app/javascript/mastodon/features/picture_in_picture/components/footer.js +++ b/app/javascript/mastodon/features/picture_in_picture/components/footer.jsx @@ -37,8 +37,6 @@ const makeMapStateToProps = () => { return mapStateToProps; }; -export default @connect(makeMapStateToProps) -@injectIntl class Footer extends ImmutablePureComponent { static contextTypes = { @@ -112,7 +110,7 @@ class Footer extends ImmutablePureComponent { _performReblog = (status, privacy) => { const { dispatch } = this.props; dispatch(reblog(status, privacy)); - } + }; handleReblogClick = e => { const { dispatch, status } = this.props; @@ -149,7 +147,7 @@ class Footer extends ImmutablePureComponent { } router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`); - } + }; render () { const { status, intl, withOpenButton } = this.props; @@ -184,9 +182,11 @@ class Footer extends ImmutablePureComponent { <IconButton className='status__action-bar-button' title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} counter={status.get('replies_count')} obfuscateCount /> <IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} counter={status.get('reblogs_count')} /> <IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} counter={status.get('favourites_count')} /> - {withOpenButton && <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.open)} icon='external-link' onClick={this.handleOpenClick} href={`/@${status.getIn(['account', 'acct'])}\/${status.get('id')}`} />} + {withOpenButton && <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.open)} icon='external-link' onClick={this.handleOpenClick} href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} />} </div> ); } } + +export default connect(makeMapStateToProps)(injectIntl(Footer)); diff --git a/app/javascript/mastodon/features/picture_in_picture/components/header.js b/app/javascript/mastodon/features/picture_in_picture/components/header.jsx index e05d8c62e..100f9db7a 100644 --- a/app/javascript/mastodon/features/picture_in_picture/components/header.js +++ b/app/javascript/mastodon/features/picture_in_picture/components/header.jsx @@ -17,8 +17,6 @@ const mapStateToProps = (state, { accountId }) => ({ account: state.getIn(['accounts', accountId]), }); -export default @connect(mapStateToProps) -@injectIntl class Header extends ImmutablePureComponent { static propTypes = { @@ -45,3 +43,5 @@ class Header extends ImmutablePureComponent { } } + +export default connect(mapStateToProps)(injectIntl(Header)); diff --git a/app/javascript/mastodon/features/picture_in_picture/index.js b/app/javascript/mastodon/features/picture_in_picture/index.jsx index 1e59fbcd3..ae48a1b4e 100644 --- a/app/javascript/mastodon/features/picture_in_picture/index.js +++ b/app/javascript/mastodon/features/picture_in_picture/index.jsx @@ -11,7 +11,6 @@ const mapStateToProps = state => ({ ...state.get('picture_in_picture'), }); -export default @connect(mapStateToProps) class PictureInPicture extends React.Component { static propTypes = { @@ -32,7 +31,7 @@ class PictureInPicture extends React.Component { handleClose = () => { const { dispatch } = this.props; dispatch(removePictureInPicture()); - } + }; render () { const { type, src, currentTime, accountId, statusId } = this.props; @@ -83,3 +82,5 @@ class PictureInPicture extends React.Component { } } + +export default connect(mapStateToProps)(PictureInPicture); diff --git a/app/javascript/mastodon/features/pinned_statuses/index.js b/app/javascript/mastodon/features/pinned_statuses/index.jsx index c6790ea06..24a0b6d2e 100644 --- a/app/javascript/mastodon/features/pinned_statuses/index.js +++ b/app/javascript/mastodon/features/pinned_statuses/index.jsx @@ -19,8 +19,6 @@ const mapStateToProps = state => ({ hasMore: !!state.getIn(['status_lists', 'pins', 'next']), }); -export default @connect(mapStateToProps) -@injectIntl class PinnedStatuses extends ImmutablePureComponent { static propTypes = { @@ -37,11 +35,11 @@ class PinnedStatuses extends ImmutablePureComponent { handleHeaderClick = () => { this.column.scrollTop(); - } + }; setRef = c => { this.column = c; - } + }; render () { const { intl, statusIds, hasMore, multiColumn } = this.props; @@ -63,3 +61,5 @@ class PinnedStatuses extends ImmutablePureComponent { } } + +export default connect(mapStateToProps)(injectIntl(PinnedStatuses)); diff --git a/app/javascript/mastodon/features/privacy_policy/index.js b/app/javascript/mastodon/features/privacy_policy/index.jsx index 3df487e8f..d5bbda6a3 100644 --- a/app/javascript/mastodon/features/privacy_policy/index.js +++ b/app/javascript/mastodon/features/privacy_policy/index.jsx @@ -10,7 +10,6 @@ const messages = defineMessages({ title: { id: 'privacy_policy.title', defaultMessage: 'Privacy Policy' }, }); -export default @injectIntl class PrivacyPolicy extends React.PureComponent { static propTypes = { @@ -59,3 +58,5 @@ class PrivacyPolicy extends React.PureComponent { } } + +export default injectIntl(PrivacyPolicy); diff --git a/app/javascript/mastodon/features/public_timeline/components/column_settings.js b/app/javascript/mastodon/features/public_timeline/components/column_settings.jsx index 756b6fe06..bf8a8323e 100644 --- a/app/javascript/mastodon/features/public_timeline/components/column_settings.js +++ b/app/javascript/mastodon/features/public_timeline/components/column_settings.jsx @@ -4,7 +4,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import { injectIntl, FormattedMessage } from 'react-intl'; import SettingToggle from '../../notifications/components/setting_toggle'; -export default @injectIntl class ColumnSettings extends React.PureComponent { static propTypes = { @@ -28,3 +27,5 @@ class ColumnSettings extends React.PureComponent { } } + +export default injectIntl(ColumnSettings); diff --git a/app/javascript/mastodon/features/public_timeline/index.js b/app/javascript/mastodon/features/public_timeline/index.jsx index a41be07e1..f89caa2c9 100644 --- a/app/javascript/mastodon/features/public_timeline/index.js +++ b/app/javascript/mastodon/features/public_timeline/index.jsx @@ -31,8 +31,6 @@ const mapStateToProps = (state, { columnId }) => { }; }; -export default @connect(mapStateToProps) -@injectIntl class PublicTimeline extends React.PureComponent { static contextTypes = { @@ -62,16 +60,16 @@ class PublicTimeline extends React.PureComponent { } else { dispatch(addColumn(onlyRemote ? 'REMOTE' : 'PUBLIC', { other: { onlyMedia, onlyRemote } })); } - } + }; handleMove = (dir) => { const { columnId, dispatch } = this.props; dispatch(moveColumn(columnId, dir)); - } + }; handleHeaderClick = () => { this.column.scrollTop(); - } + }; componentDidMount () { const { dispatch, onlyMedia, onlyRemote } = this.props; @@ -111,13 +109,13 @@ class PublicTimeline extends React.PureComponent { setRef = c => { this.column = c; - } + }; handleLoadMore = maxId => { const { dispatch, onlyMedia, onlyRemote } = this.props; dispatch(expandPublicTimeline({ maxId, onlyMedia, onlyRemote })); - } + }; render () { const { intl, columnId, hasUnread, multiColumn, onlyMedia, onlyRemote } = this.props; @@ -160,3 +158,5 @@ class PublicTimeline extends React.PureComponent { } } + +export default connect(mapStateToProps)(injectIntl(PublicTimeline)); diff --git a/app/javascript/mastodon/features/reblogs/index.js b/app/javascript/mastodon/features/reblogs/index.jsx index 36ca11d1a..35edb0d6a 100644 --- a/app/javascript/mastodon/features/reblogs/index.js +++ b/app/javascript/mastodon/features/reblogs/index.jsx @@ -21,8 +21,6 @@ const mapStateToProps = (state, props) => ({ accountIds: state.getIn(['user_lists', 'reblogged_by', props.params.statusId]), }); -export default @connect(mapStateToProps) -@injectIntl class Reblogs extends ImmutablePureComponent { static propTypes = { @@ -47,7 +45,7 @@ class Reblogs extends ImmutablePureComponent { handleRefresh = () => { this.props.dispatch(fetchReblogs(this.props.params.statusId)); - } + }; render () { const { intl, accountIds, multiColumn } = this.props; @@ -90,3 +88,5 @@ class Reblogs extends ImmutablePureComponent { } } + +export default connect(mapStateToProps)(injectIntl(Reblogs)); diff --git a/app/javascript/mastodon/features/report/category.js b/app/javascript/mastodon/features/report/category.jsx index c6c0a506f..492a533f2 100644 --- a/app/javascript/mastodon/features/report/category.js +++ b/app/javascript/mastodon/features/report/category.jsx @@ -24,8 +24,6 @@ const mapStateToProps = state => ({ rules: state.getIn(['server', 'server', 'rules'], ImmutableList()), }); -export default @connect(mapStateToProps) -@injectIntl class Category extends React.PureComponent { static propTypes = { @@ -104,3 +102,5 @@ class Category extends React.PureComponent { } } + +export default connect(mapStateToProps)(injectIntl(Category)); diff --git a/app/javascript/mastodon/features/report/comment.js b/app/javascript/mastodon/features/report/comment.jsx index cde156415..ab29f6c22 100644 --- a/app/javascript/mastodon/features/report/comment.js +++ b/app/javascript/mastodon/features/report/comment.jsx @@ -8,7 +8,6 @@ const messages = defineMessages({ placeholder: { id: 'report.placeholder', defaultMessage: 'Type or paste additional comments' }, }); -export default @injectIntl class Comment extends React.PureComponent { static propTypes = { @@ -81,3 +80,5 @@ class Comment extends React.PureComponent { } } + +export default injectIntl(Comment); diff --git a/app/javascript/mastodon/features/report/components/option.js b/app/javascript/mastodon/features/report/components/option.jsx index 744d85268..342204e22 100644 --- a/app/javascript/mastodon/features/report/components/option.js +++ b/app/javascript/mastodon/features/report/components/option.jsx @@ -24,12 +24,12 @@ export default class Option extends React.PureComponent { e.preventDefault(); onToggle(value, !checked); } - } + }; handleChange = e => { const { value, onToggle } = this.props; onToggle(value, e.target.checked); - } + }; render () { const { name, value, checked, label, labelComponent, description, multiple } = this.props; @@ -40,7 +40,7 @@ export default class Option extends React.PureComponent { <span className={classNames('poll__input', { active: checked, checkbox: multiple })} - tabIndex='0' + tabIndex={0} role='radio' onKeyPress={this.handleKeyPress} aria-checked={checked} diff --git a/app/javascript/mastodon/features/report/components/status_check_box.js b/app/javascript/mastodon/features/report/components/status_check_box.jsx index 5366da90b..28d572e89 100644 --- a/app/javascript/mastodon/features/report/components/status_check_box.js +++ b/app/javascript/mastodon/features/report/components/status_check_box.jsx @@ -17,7 +17,6 @@ const messages = defineMessages({ direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' }, }); -export default @injectIntl class StatusCheckBox extends React.PureComponent { static propTypes = { @@ -80,3 +79,5 @@ class StatusCheckBox extends React.PureComponent { } } + +export default injectIntl(StatusCheckBox); diff --git a/app/javascript/mastodon/features/report/rules.js b/app/javascript/mastodon/features/report/rules.jsx index 920da68d6..b500c0503 100644 --- a/app/javascript/mastodon/features/report/rules.js +++ b/app/javascript/mastodon/features/report/rules.jsx @@ -10,7 +10,6 @@ const mapStateToProps = state => ({ rules: state.getIn(['server', 'server', 'rules']), }); -export default @connect(mapStateToProps) class Rules extends React.PureComponent { static propTypes = { @@ -62,3 +61,5 @@ class Rules extends React.PureComponent { } } + +export default connect(mapStateToProps)(Rules); diff --git a/app/javascript/mastodon/features/report/statuses.js b/app/javascript/mastodon/features/report/statuses.jsx index d5d86034f..87163aeb8 100644 --- a/app/javascript/mastodon/features/report/statuses.js +++ b/app/javascript/mastodon/features/report/statuses.jsx @@ -13,7 +13,6 @@ const mapStateToProps = (state, { accountId }) => ({ isLoading: state.getIn(['timelines', `account:${accountId}:with_replies`, 'isLoading']), }); -export default @connect(mapStateToProps) class Statuses extends React.PureComponent { static propTypes = { @@ -59,3 +58,5 @@ class Statuses extends React.PureComponent { } } + +export default connect(mapStateToProps)(Statuses); diff --git a/app/javascript/mastodon/features/report/thanks.js b/app/javascript/mastodon/features/report/thanks.jsx index d169b1e32..4e5b6e864 100644 --- a/app/javascript/mastodon/features/report/thanks.js +++ b/app/javascript/mastodon/features/report/thanks.jsx @@ -12,7 +12,6 @@ import { const mapStateToProps = () => ({}); -export default @connect(mapStateToProps) class Thanks extends React.PureComponent { static propTypes = { @@ -82,3 +81,5 @@ class Thanks extends React.PureComponent { } } + +export default connect(mapStateToProps)(Thanks); diff --git a/app/javascript/mastodon/features/standalone/compose/index.js b/app/javascript/mastodon/features/standalone/compose/index.jsx index 0d764575f..fbadef6f4 100644 --- a/app/javascript/mastodon/features/standalone/compose/index.js +++ b/app/javascript/mastodon/features/standalone/compose/index.jsx @@ -9,7 +9,7 @@ export default class Compose extends React.PureComponent { render () { return ( <div> - <ComposeFormContainer /> + <ComposeFormContainer autoFocus /> <NotificationsContainer /> <ModalContainer /> <LoadingBarContainer className='loading-bar' /> diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.jsx index 46ee9f6c1..0c74c4cc4 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.js +++ b/app/javascript/mastodon/features/status/components/action_bar.jsx @@ -13,7 +13,7 @@ const messages = defineMessages({ delete: { id: 'status.delete', defaultMessage: 'Delete' }, redraft: { id: 'status.redraft', defaultMessage: 'Delete & re-draft' }, edit: { id: 'status.edit', defaultMessage: 'Edit' }, - direct: { id: 'status.direct', defaultMessage: 'Direct message @{name}' }, + direct: { id: 'status.direct', defaultMessage: 'Privately mention @{name}' }, mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' }, reply: { id: 'status.reply', defaultMessage: 'Reply' }, reblog: { id: 'status.reblog', defaultMessage: 'Boost' }, @@ -47,8 +47,6 @@ const mapStateToProps = (state, { status }) => ({ relationship: state.getIn(['relationships', status.getIn(['account', 'id'])]), }); -export default @connect(mapStateToProps) -@injectIntl class ActionBar extends React.PureComponent { static contextTypes = { @@ -82,39 +80,39 @@ class ActionBar extends React.PureComponent { handleReplyClick = () => { this.props.onReply(this.props.status); - } + }; handleReblogClick = (e) => { this.props.onReblog(this.props.status, e); - } + }; handleFavouriteClick = () => { this.props.onFavourite(this.props.status); - } + }; handleBookmarkClick = (e) => { this.props.onBookmark(this.props.status, e); - } + }; handleDeleteClick = () => { this.props.onDelete(this.props.status, this.context.router.history); - } + }; handleRedraftClick = () => { this.props.onDelete(this.props.status, this.context.router.history, true); - } + }; handleEditClick = () => { this.props.onEdit(this.props.status, this.context.router.history); - } + }; handleDirectClick = () => { this.props.onDirect(this.props.status.get('account'), this.context.router.history); - } + }; handleMentionClick = () => { this.props.onMention(this.props.status.get('account'), this.context.router.history); - } + }; handleMuteClick = () => { const { status, relationship, onMute, onUnmute } = this.props; @@ -125,7 +123,7 @@ class ActionBar extends React.PureComponent { } else { onMute(account); } - } + }; handleBlockClick = () => { const { status, relationship, onBlock, onUnblock } = this.props; @@ -136,49 +134,49 @@ class ActionBar extends React.PureComponent { } else { onBlock(status); } - } + }; handleBlockDomain = () => { const { status, onBlockDomain } = this.props; const account = status.get('account'); onBlockDomain(account.get('acct').split('@')[1]); - } + }; handleUnblockDomain = () => { const { status, onUnblockDomain } = this.props; const account = status.get('account'); onUnblockDomain(account.get('acct').split('@')[1]); - } + }; handleConversationMuteClick = () => { this.props.onMuteConversation(this.props.status); - } + }; handleReport = () => { this.props.onReport(this.props.status); - } + }; handlePinClick = () => { this.props.onPin(this.props.status); - } + }; handleShare = () => { navigator.share({ text: this.props.status.get('search_index'), url: this.props.status.get('url'), }); - } + }; handleEmbed = () => { this.props.onEmbed(this.props.status); - } + }; handleCopy = () => { const url = this.props.status.get('url'); navigator.clipboard.writeText(url); - } + }; render () { const { status, relationship, intl } = this.props; @@ -298,3 +296,5 @@ class ActionBar extends React.PureComponent { } } + +export default connect(mapStateToProps)(injectIntl(ActionBar)); diff --git a/app/javascript/mastodon/features/status/components/card.js b/app/javascript/mastodon/features/status/components/card.jsx index 82537dd5d..b588c83e4 100644 --- a/app/javascript/mastodon/features/status/components/card.js +++ b/app/javascript/mastodon/features/status/components/card.jsx @@ -146,7 +146,7 @@ export default class Card extends React.PureComponent { } else { this.setState({ embedded: true }); } - } + }; setRef = c => { this.node = c; @@ -154,17 +154,17 @@ export default class Card extends React.PureComponent { if (this.node) { this._setDimensions(); } - } + }; handleImageLoad = () => { this.setState({ previewLoaded: true }); - } + }; handleReveal = e => { e.preventDefault(); e.stopPropagation(); this.setState({ revealed: true }); - } + }; renderVideo () { const { card } = this.props; @@ -196,11 +196,12 @@ export default class Card extends React.PureComponent { const interactive = card.get('type') !== 'link'; const className = classnames('status-card', { horizontal, compact, interactive }); const title = interactive ? <a className='status-card__title' href={card.get('url')} title={card.get('title')} rel='noopener noreferrer' target='_blank'><strong>{card.get('title')}</strong></a> : <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong>; + const language = card.get('language') || ''; const ratio = card.get('width') / card.get('height'); const height = (compact && !embedded) ? (width / (16 / 9)) : (width / ratio); const description = ( - <div className='status-card__content'> + <div className='status-card__content' lang={language}> {title} {!(horizontal || compact) && <p className='status-card__description'>{trim(card.get('description') || '', maxDescription)}</p>} <span className='status-card__host'>{provider}</span> diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.jsx index c62910e0e..e4e572026 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.js +++ b/app/javascript/mastodon/features/status/components/detailed_status.jsx @@ -6,7 +6,7 @@ import DisplayName from '../../../components/display_name'; import StatusContent from '../../../components/status_content'; import MediaGallery from '../../../components/media_gallery'; import { Link } from 'react-router-dom'; -import { injectIntl, defineMessages, FormattedDate } from 'react-intl'; +import { injectIntl, defineMessages, FormattedDate, FormattedMessage } from 'react-intl'; import Card from './card'; import ImmutablePureComponent from 'react-immutable-pure-component'; import Video from '../../video'; @@ -25,7 +25,6 @@ const messages = defineMessages({ direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' }, }); -export default @injectIntl class DetailedStatus extends ImmutablePureComponent { static contextTypes = { @@ -61,15 +60,15 @@ class DetailedStatus extends ImmutablePureComponent { } e.stopPropagation(); - } + }; handleOpenVideo = (options) => { this.props.onOpenVideo(this.props.status.getIn(['media_attachments', 0]), options); - } + }; handleExpandedToggle = () => { this.props.onToggleHidden(this.props.status); - } + }; _measureHeight (heightJustChanged) { if (this.props.measureHeight && this.node) { @@ -84,7 +83,7 @@ class DetailedStatus extends ImmutablePureComponent { setRef = c => { this.node = c; this._measureHeight(); - } + }; componentDidUpdate (prevProps, prevState) { this._measureHeight(prevState.height !== this.state.height); @@ -102,12 +101,12 @@ class DetailedStatus extends ImmutablePureComponent { } window.open(href, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes'); - } + }; handleTranslate = () => { const { onTranslate, status } = this.props; onTranslate(status); - } + }; render () { const status = (this.props.status && this.props.status.get('reblog')) ? this.props.status.get('reblog') : this.props.status; @@ -139,6 +138,7 @@ class DetailedStatus extends ImmutablePureComponent { <Audio src={attachment.get('url')} alt={attachment.get('description')} + lang={status.get('language')} duration={attachment.getIn(['meta', 'original', 'duration'], 0)} poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])} backgroundColor={attachment.getIn(['meta', 'colors', 'background'])} @@ -161,6 +161,7 @@ class DetailedStatus extends ImmutablePureComponent { blurhash={attachment.get('blurhash')} src={attachment.get('url')} alt={attachment.get('description')} + lang={status.get('language')} width={300} height={150} inline @@ -176,6 +177,7 @@ class DetailedStatus extends ImmutablePureComponent { standalone sensitive={status.get('sensitive')} media={status.get('media_attachments')} + lang={status.get('language')} height={300} onOpenMedia={this.props.onOpenMedia} visible={this.props.showMedia} @@ -260,7 +262,13 @@ class DetailedStatus extends ImmutablePureComponent { return ( <div style={outerStyle}> - <div ref={this.setRef} className={classNames('detailed-status', `detailed-status-${status.get('visibility')}`, { compact })}> + <div ref={this.setRef} className={classNames('detailed-status', { compact })}> + {status.get('visibility') === 'direct' && ( + <div className='status__prepend'> + <div className='status__prepend-icon-wrapper'><Icon id='at' className='status__prepend-icon' fixedWidth /></div> + <FormattedMessage id='status.direct_indicator' defaultMessage='Private mention' /> + </div> + )} <a href={`/@${status.getIn(['account', 'acct'])}`} onClick={this.handleAccountClick} className='detailed-status__display-name'> <div className='detailed-status__display-avatar'><Avatar account={status.get('account')} size={46} /></div> <DisplayName account={status.get('account')} localDomain={this.props.domain} /> @@ -276,7 +284,7 @@ class DetailedStatus extends ImmutablePureComponent { {media} <div className='detailed-status__meta'> - <a className='detailed-status__datetime' href={`/@${status.getIn(['account', 'acct'])}\/${status.get('id')}`} target='_blank' rel='noopener noreferrer'> + <a className='detailed-status__datetime' href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} target='_blank' rel='noopener noreferrer'> <FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' /> </a>{edited}{visibilityLink}{applicationLink}{reblogLink} · {favouriteLink} </div> @@ -286,3 +294,5 @@ class DetailedStatus extends ImmutablePureComponent { } } + +export default injectIntl(DetailedStatus); diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.jsx index 8a63cced2..2d18c6e9d 100644 --- a/app/javascript/mastodon/features/status/index.js +++ b/app/javascript/mastodon/features/status/index.jsx @@ -5,7 +5,17 @@ import PropTypes from 'prop-types'; import classNames from 'classnames'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { createSelector } from 'reselect'; -import { fetchStatus } from '../../actions/statuses'; +import { + fetchStatus, + muteStatus, + unmuteStatus, + deleteStatus, + editStatus, + hideStatus, + revealStatus, + translateStatus, + undoStatusTranslation, +} from '../../actions/statuses'; import MissingIndicator from '../../components/missing_indicator'; import LoadingIndicator from 'mastodon/components/loading_indicator'; import DetailedStatus from './components/detailed_status'; @@ -27,16 +37,6 @@ import { directCompose, } from '../../actions/compose'; import { - muteStatus, - unmuteStatus, - deleteStatus, - editStatus, - hideStatus, - revealStatus, - translateStatus, - undoStatusTranslation, -} from '../../actions/statuses'; -import { unblockAccount, unmuteAccount, } from '../../actions/accounts'; @@ -176,8 +176,6 @@ const titleFromStatus = status => { return `${prefix}: "${truncate(text, 30)}"`; }; -export default @injectIntl -@connect(makeMapStateToProps) class Status extends ImmutablePureComponent { static contextTypes = { @@ -233,7 +231,7 @@ class Status extends ImmutablePureComponent { handleToggleMediaVisibility = () => { this.setState({ showMedia: !this.state.showMedia }); - } + }; handleFavouriteClick = (status) => { const { dispatch } = this.props; @@ -252,7 +250,7 @@ class Status extends ImmutablePureComponent { url: status.get('url'), })); } - } + }; handlePin = (status) => { if (status.get('pinned')) { @@ -260,7 +258,7 @@ class Status extends ImmutablePureComponent { } else { this.props.dispatch(pin(status)); } - } + }; handleReplyClick = (status) => { const { askReplyConfirmation, dispatch, intl } = this.props; @@ -283,11 +281,11 @@ class Status extends ImmutablePureComponent { url: status.get('url'), })); } - } + }; handleModalReblog = (status, privacy) => { this.props.dispatch(reblog(status, privacy)); - } + }; handleReblogClick = (status, e) => { const { dispatch } = this.props; @@ -310,7 +308,7 @@ class Status extends ImmutablePureComponent { url: status.get('url'), })); } - } + }; handleBookmarkClick = (status) => { if (status.get('bookmarked')) { @@ -318,7 +316,7 @@ class Status extends ImmutablePureComponent { } else { this.props.dispatch(bookmark(status)); } - } + }; handleDeleteClick = (status, history, withRedraft = false) => { const { dispatch, intl } = this.props; @@ -332,27 +330,27 @@ class Status extends ImmutablePureComponent { onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)), })); } - } + }; handleEditClick = (status, history) => { this.props.dispatch(editStatus(status.get('id'), history)); - } + }; handleDirectClick = (account, router) => { this.props.dispatch(directCompose(account, router)); - } + }; handleMentionClick = (account, router) => { this.props.dispatch(mentionCompose(account, router)); - } + }; handleOpenMedia = (media, index) => { this.props.dispatch(openModal('MEDIA', { statusId: this.props.status.get('id'), media, index })); - } + }; handleOpenVideo = (media, options) => { this.props.dispatch(openModal('VIDEO', { statusId: this.props.status.get('id'), media, options })); - } + }; handleHotkeyOpenMedia = e => { const { status } = this.props; @@ -366,11 +364,11 @@ class Status extends ImmutablePureComponent { this.handleOpenMedia(status.get('media_attachments'), 0); } } - } + }; handleMuteClick = (account) => { this.props.dispatch(initMuteModal(account)); - } + }; handleConversationMuteClick = (status) => { if (status.get('muted')) { @@ -378,7 +376,7 @@ class Status extends ImmutablePureComponent { } else { this.props.dispatch(muteStatus(status.get('id'))); } - } + }; handleToggleHidden = (status) => { if (status.get('hidden')) { @@ -386,7 +384,7 @@ class Status extends ImmutablePureComponent { } else { this.props.dispatch(hideStatus(status.get('id'))); } - } + }; handleToggleAll = () => { const { status, ancestorsIds, descendantsIds } = this.props; @@ -397,7 +395,7 @@ class Status extends ImmutablePureComponent { } else { this.props.dispatch(hideStatus(statusIds)); } - } + }; handleTranslate = status => { const { dispatch } = this.props; @@ -407,29 +405,29 @@ class Status extends ImmutablePureComponent { } else { dispatch(translateStatus(status.get('id'))); } - } + }; handleBlockClick = (status) => { const { dispatch } = this.props; const account = status.get('account'); dispatch(initBlockModal(account)); - } + }; handleReport = (status) => { this.props.dispatch(initReport(status.get('account'), status)); - } + }; handleEmbed = (status) => { this.props.dispatch(openModal('EMBED', { url: status.get('url') })); - } + }; handleUnmuteClick = account => { this.props.dispatch(unmuteAccount(account.get('id'))); - } + }; handleUnblockClick = account => { this.props.dispatch(unblockAccount(account.get('id'))); - } + }; handleBlockDomainClick = domain => { this.props.dispatch(openModal('CONFIRM', { @@ -437,50 +435,50 @@ class Status extends ImmutablePureComponent { confirm: this.props.intl.formatMessage(messages.blockDomainConfirm), onConfirm: () => this.props.dispatch(blockDomain(domain)), })); - } + }; handleUnblockDomainClick = domain => { this.props.dispatch(unblockDomain(domain)); - } + }; handleHotkeyMoveUp = () => { this.handleMoveUp(this.props.status.get('id')); - } + }; handleHotkeyMoveDown = () => { this.handleMoveDown(this.props.status.get('id')); - } + }; handleHotkeyReply = e => { e.preventDefault(); this.handleReplyClick(this.props.status); - } + }; handleHotkeyFavourite = () => { this.handleFavouriteClick(this.props.status); - } + }; handleHotkeyBoost = () => { this.handleReblogClick(this.props.status); - } + }; handleHotkeyMention = e => { e.preventDefault(); this.handleMentionClick(this.props.status.get('account')); - } + }; handleHotkeyOpenProfile = () => { this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`); - } + }; handleHotkeyToggleHidden = () => { this.handleToggleHidden(this.props.status); - } + }; handleHotkeyToggleSensitive = () => { this.handleToggleMediaVisibility(); - } + }; handleMoveUp = id => { const { status, ancestorsIds, descendantsIds } = this.props; @@ -497,7 +495,7 @@ class Status extends ImmutablePureComponent { this._selectChild(index - 1, true); } } - } + }; handleMoveDown = id => { const { status, ancestorsIds, descendantsIds } = this.props; @@ -514,7 +512,7 @@ class Status extends ImmutablePureComponent { this._selectChild(index + 1, false); } } - } + }; _selectChild (index, align_top) { const container = this.node; @@ -544,7 +542,7 @@ class Status extends ImmutablePureComponent { setRef = c => { this.node = c; - } + }; componentDidUpdate () { if (this._scrolledIntoView) { @@ -569,7 +567,7 @@ class Status extends ImmutablePureComponent { onFullScreenChange = () => { this.setState({ fullscreen: isFullscreen() }); - } + }; render () { let ancestors, descendants; @@ -632,7 +630,7 @@ class Status extends ImmutablePureComponent { {ancestors} <HotKeys handlers={handlers}> - <div className={classNames('focusable', 'detailed-status__wrapper')} tabIndex='0' aria-label={textForScreenReader(intl, status, false)}> + <div className={classNames('focusable', 'detailed-status__wrapper', `detailed-status__wrapper-${status.get('visibility')}`)} tabIndex={0} aria-label={textForScreenReader(intl, status, false)}> <DetailedStatus key={`details-${status.get('id')}`} status={status} @@ -684,3 +682,5 @@ class Status extends ImmutablePureComponent { } } + +export default injectIntl(connect(makeMapStateToProps)(Status)); diff --git a/app/javascript/mastodon/features/subscribed_languages_modal/index.js b/app/javascript/mastodon/features/subscribed_languages_modal/index.jsx index a519ceabc..04fe31b5c 100644 --- a/app/javascript/mastodon/features/subscribed_languages_modal/index.js +++ b/app/javascript/mastodon/features/subscribed_languages_modal/index.jsx @@ -36,8 +36,6 @@ const mapDispatchToProps = (dispatch, { accountId }) => ({ }); -export default @connect(mapStateToProps, mapDispatchToProps) -@injectIntl class SubscribedLanguagesModal extends ImmutablePureComponent { static propTypes = { @@ -72,7 +70,7 @@ class SubscribedLanguagesModal extends ImmutablePureComponent { handleSubmit = () => { this.props.onSubmit(this.state.selectedLanguages.toArray()); this.props.onClose(); - } + }; renderItem (value) { const language = this.props.languages.find(language => language[0] === value); @@ -123,3 +121,5 @@ class SubscribedLanguagesModal extends ImmutablePureComponent { } } + +export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(SubscribedLanguagesModal)); diff --git a/app/javascript/mastodon/features/ui/components/__tests__/column-test.js b/app/javascript/mastodon/features/ui/components/__tests__/column-test.jsx index a56859be0..a56859be0 100644 --- a/app/javascript/mastodon/features/ui/components/__tests__/column-test.js +++ b/app/javascript/mastodon/features/ui/components/__tests__/column-test.jsx diff --git a/app/javascript/mastodon/features/ui/components/actions_modal.js b/app/javascript/mastodon/features/ui/components/actions_modal.jsx index 67be69d43..35090e242 100644 --- a/app/javascript/mastodon/features/ui/components/actions_modal.js +++ b/app/javascript/mastodon/features/ui/components/actions_modal.jsx @@ -23,7 +23,7 @@ export default class ActionsModal extends ImmutablePureComponent { return ( <li key={`${text}-${i}`}> <a href={href} target='_blank' rel='noopener noreferrer' onClick={this.props.onClick} data-index={i} className={classNames({ active })}> - {icon && <IconButton title={text} icon={icon} role='presentation' tabIndex='-1' inverted />} + {icon && <IconButton title={text} icon={icon} role='presentation' tabIndex={-1} inverted />} <div> <div className={classNames({ 'actions-modal__item-label': !!meta })}>{text}</div> <div>{meta}</div> @@ -31,7 +31,7 @@ export default class ActionsModal extends ImmutablePureComponent { </a> </li> ); - } + }; render () { return ( diff --git a/app/javascript/mastodon/features/ui/components/audio_modal.js b/app/javascript/mastodon/features/ui/components/audio_modal.jsx index c46fefce8..c0ac12ba7 100644 --- a/app/javascript/mastodon/features/ui/components/audio_modal.js +++ b/app/javascript/mastodon/features/ui/components/audio_modal.jsx @@ -7,15 +7,16 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import Footer from 'mastodon/features/picture_in_picture/components/footer'; const mapStateToProps = (state, { statusId }) => ({ + language: state.getIn(['statuses', statusId, 'language']), accountStaticAvatar: state.getIn(['accounts', state.getIn(['statuses', statusId, 'account']), 'avatar_static']), }); -export default @connect(mapStateToProps) class AudioModal extends ImmutablePureComponent { static propTypes = { media: ImmutablePropTypes.map.isRequired, statusId: PropTypes.string.isRequired, + language: PropTypes.string, accountStaticAvatar: PropTypes.string.isRequired, options: PropTypes.shape({ autoPlay: PropTypes.bool, @@ -25,7 +26,7 @@ class AudioModal extends ImmutablePureComponent { }; render () { - const { media, accountStaticAvatar, statusId, onClose } = this.props; + const { media, language, accountStaticAvatar, statusId, onClose } = this.props; const options = this.props.options || {}; return ( @@ -34,6 +35,7 @@ class AudioModal extends ImmutablePureComponent { <Audio src={media.get('url')} alt={media.get('description')} + lang={language} duration={media.getIn(['meta', 'original', 'duration'], 0)} height={150} poster={media.get('preview_url') || accountStaticAvatar} @@ -52,3 +54,5 @@ class AudioModal extends ImmutablePureComponent { } } + +export default connect(mapStateToProps, null, null, { forwardRef: true })(AudioModal); diff --git a/app/javascript/mastodon/features/ui/components/block_modal.js b/app/javascript/mastodon/features/ui/components/block_modal.jsx index a07baeaa6..a9506aa69 100644 --- a/app/javascript/mastodon/features/ui/components/block_modal.js +++ b/app/javascript/mastodon/features/ui/components/block_modal.jsx @@ -36,8 +36,6 @@ const mapDispatchToProps = dispatch => { }; }; -export default @connect(makeMapStateToProps, mapDispatchToProps) -@injectIntl class BlockModal extends React.PureComponent { static propTypes = { @@ -55,20 +53,20 @@ class BlockModal extends React.PureComponent { handleClick = () => { this.props.onClose(); this.props.onConfirm(this.props.account); - } + }; handleSecondary = () => { this.props.onClose(); this.props.onBlockAndReport(this.props.account); - } + }; handleCancel = () => { this.props.onClose(); - } + }; setRef = (c) => { this.button = c; - } + }; render () { const { account } = this.props; @@ -101,3 +99,5 @@ class BlockModal extends React.PureComponent { } } + +export default connect(makeMapStateToProps, mapDispatchToProps)(injectIntl(BlockModal)); diff --git a/app/javascript/mastodon/features/ui/components/boost_modal.js b/app/javascript/mastodon/features/ui/components/boost_modal.jsx index 077ce7b35..fe55c963a 100644 --- a/app/javascript/mastodon/features/ui/components/boost_modal.js +++ b/app/javascript/mastodon/features/ui/components/boost_modal.jsx @@ -38,8 +38,6 @@ const mapDispatchToProps = dispatch => { }; }; -export default @connect(mapStateToProps, mapDispatchToProps) -@injectIntl class BoostModal extends ImmutablePureComponent { static contextTypes = { @@ -62,7 +60,7 @@ class BoostModal extends ImmutablePureComponent { handleReblog = () => { this.props.onReblog(this.props.status, this.props.privacy); this.props.onClose(); - } + }; handleAccountClick = (e) => { if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { @@ -70,7 +68,7 @@ class BoostModal extends ImmutablePureComponent { this.props.onClose(); this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`); } - } + }; _findContainer = () => { return document.getElementsByClassName('modal-root__container')[0]; @@ -78,7 +76,7 @@ class BoostModal extends ImmutablePureComponent { setRef = (c) => { this.button = c; - } + }; render () { const { status, privacy, intl } = this.props; @@ -98,7 +96,7 @@ class BoostModal extends ImmutablePureComponent { <div className='boost-modal__container'> <div className={classNames('status', `status-${status.get('visibility')}`, 'light')}> <div className='status__info'> - <a href={`/@${status.getIn(['account', 'acct'])}\/${status.get('id')}`} className='status__relative-time' target='_blank' rel='noopener noreferrer'> + <a href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} className='status__relative-time' target='_blank' rel='noopener noreferrer'> <span className='status__visibility-icon'><Icon id={visibilityIcon.icon} title={visibilityIcon.text} /></span> <RelativeTimestamp timestamp={status.get('created_at')} /> </a> @@ -140,3 +138,5 @@ class BoostModal extends ImmutablePureComponent { } } + +export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(BoostModal)); diff --git a/app/javascript/mastodon/features/ui/components/bundle.js b/app/javascript/mastodon/features/ui/components/bundle.jsx index a60ace35b..1b10a218b 100644 --- a/app/javascript/mastodon/features/ui/components/bundle.js +++ b/app/javascript/mastodon/features/ui/components/bundle.jsx @@ -15,7 +15,7 @@ class Bundle extends React.PureComponent { onFetch: PropTypes.func, onFetchSuccess: PropTypes.func, onFetchFail: PropTypes.func, - } + }; static defaultProps = { loading: emptyComponent, @@ -24,14 +24,14 @@ class Bundle extends React.PureComponent { onFetch: noop, onFetchSuccess: noop, onFetchFail: noop, - } + }; - static cache = new Map + static cache = new Map; state = { mod: undefined, forceRender: false, - } + }; componentWillMount() { this.load(this.props); @@ -83,7 +83,7 @@ class Bundle extends React.PureComponent { this.setState({ mod: null }); onFetchFail(error); }); - } + }; render() { const { loading: Loading, error: Error, children, renderDelay } = this.props; diff --git a/app/javascript/mastodon/features/ui/components/bundle_column_error.js b/app/javascript/mastodon/features/ui/components/bundle_column_error.jsx index dfe970ad0..dc9b0e539 100644 --- a/app/javascript/mastodon/features/ui/components/bundle_column_error.js +++ b/app/javascript/mastodon/features/ui/components/bundle_column_error.jsx @@ -31,7 +31,7 @@ class GIF extends React.PureComponent { if (!animate) { this.setState({ hovering: true }); } - } + }; handleMouseLeave = () => { const { animate } = this.props; @@ -39,7 +39,7 @@ class GIF extends React.PureComponent { if (!animate) { this.setState({ hovering: false }); } - } + }; render () { const { src, staticSrc, className, animate } = this.props; @@ -75,7 +75,7 @@ class CopyButton extends React.PureComponent { navigator.clipboard.writeText(value); this.setState({ copied: true }); this.timeout = setTimeout(() => this.setState({ copied: false }), 700); - } + }; componentWillUnmount () { if (this.timeout) clearTimeout(this.timeout); @@ -92,7 +92,6 @@ class CopyButton extends React.PureComponent { } -export default @injectIntl class BundleColumnError extends React.PureComponent { static propTypes = { @@ -113,7 +112,7 @@ class BundleColumnError extends React.PureComponent { if (onRetry) { onRetry(); } - } + }; render () { const { errorType, multiColumn, stacktrace } = this.props; @@ -160,3 +159,5 @@ class BundleColumnError extends React.PureComponent { } } + +export default injectIntl(BundleColumnError); diff --git a/app/javascript/mastodon/features/ui/components/bundle_modal_error.js b/app/javascript/mastodon/features/ui/components/bundle_modal_error.jsx index f9365b95b..d79d0ca4a 100644 --- a/app/javascript/mastodon/features/ui/components/bundle_modal_error.js +++ b/app/javascript/mastodon/features/ui/components/bundle_modal_error.jsx @@ -16,11 +16,11 @@ class BundleModalError extends React.PureComponent { onRetry: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, - } + }; handleRetry = () => { this.props.onRetry(); - } + }; render () { const { onClose, intl: { formatMessage } } = this.props; diff --git a/app/javascript/mastodon/features/ui/components/column.js b/app/javascript/mastodon/features/ui/components/column.jsx index 15538ea38..7bc2f7e00 100644 --- a/app/javascript/mastodon/features/ui/components/column.js +++ b/app/javascript/mastodon/features/ui/components/column.jsx @@ -23,7 +23,7 @@ export default class Column extends React.PureComponent { } this._interruptScrollAnimation = scrollTop(scrollable); - } + }; scrollTop () { const scrollable = this.node.querySelector('.scrollable'); @@ -40,11 +40,11 @@ export default class Column extends React.PureComponent { if (typeof this._interruptScrollAnimation !== 'undefined') { this._interruptScrollAnimation(); } - }, 200) + }, 200); setRef = (c) => { this.node = c; - } + }; render () { const { heading, icon, children, active, hideHeadingOnMobile } = this.props; diff --git a/app/javascript/mastodon/features/ui/components/column_header.js b/app/javascript/mastodon/features/ui/components/column_header.jsx index b1a36e173..4ceef5957 100644 --- a/app/javascript/mastodon/features/ui/components/column_header.js +++ b/app/javascript/mastodon/features/ui/components/column_header.jsx @@ -15,7 +15,7 @@ export default class ColumnHeader extends React.PureComponent { handleClick = () => { this.props.onClick(); - } + }; render () { const { icon, type, active, columnHeaderId } = this.props; diff --git a/app/javascript/mastodon/features/ui/components/column_link.js b/app/javascript/mastodon/features/ui/components/column_link.jsx index 8eebbf526..8eebbf526 100644 --- a/app/javascript/mastodon/features/ui/components/column_link.js +++ b/app/javascript/mastodon/features/ui/components/column_link.jsx diff --git a/app/javascript/mastodon/features/ui/components/column_loading.js b/app/javascript/mastodon/features/ui/components/column_loading.jsx index e5ed22584..e5ed22584 100644 --- a/app/javascript/mastodon/features/ui/components/column_loading.js +++ b/app/javascript/mastodon/features/ui/components/column_loading.jsx diff --git a/app/javascript/mastodon/features/ui/components/column_subheading.js b/app/javascript/mastodon/features/ui/components/column_subheading.jsx index 8160c4aa3..8160c4aa3 100644 --- a/app/javascript/mastodon/features/ui/components/column_subheading.js +++ b/app/javascript/mastodon/features/ui/components/column_subheading.jsx diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.jsx index e7def800e..1dd6e34e8 100644 --- a/app/javascript/mastodon/features/ui/components/columns_area.js +++ b/app/javascript/mastodon/features/ui/components/columns_area.jsx @@ -57,7 +57,7 @@ export default class ColumnsArea extends ImmutablePureComponent { state = { renderComposePanel: !(this.mediaQuery && this.mediaQuery.matches), - } + }; componentDidMount() { if (!this.props.singleColumn) { @@ -111,7 +111,7 @@ export default class ColumnsArea extends ImmutablePureComponent { handleLayoutChange = (e) => { this.setState({ renderComposePanel: !e.matches }); - } + }; handleWheel = () => { if (typeof this._interruptScrollAnimation !== 'function') { @@ -119,19 +119,19 @@ export default class ColumnsArea extends ImmutablePureComponent { } this._interruptScrollAnimation(); - } + }; setRef = (node) => { this.node = node; - } + }; renderLoading = columnId => () => { return columnId === 'COMPOSE' ? <DrawerLoading /> : <ColumnLoading multiColumn />; - } + }; renderError = (props) => { return <BundleColumnError multiColumn errorType='network' {...props} />; - } + }; render () { const { columns, children, singleColumn, isModalOpen } = this.props; diff --git a/app/javascript/mastodon/features/ui/components/compare_history_modal.js b/app/javascript/mastodon/features/ui/components/compare_history_modal.jsx index ecccc8f7d..1802c167c 100644 --- a/app/javascript/mastodon/features/ui/components/compare_history_modal.js +++ b/app/javascript/mastodon/features/ui/components/compare_history_modal.jsx @@ -12,6 +12,7 @@ import RelativeTimestamp from 'mastodon/components/relative_timestamp'; import MediaAttachments from 'mastodon/components/media_attachments'; const mapStateToProps = (state, { statusId }) => ({ + language: state.getIn(['statuses', statusId, 'language']), versions: state.getIn(['history', statusId, 'items']), }); @@ -23,18 +24,18 @@ const mapDispatchToProps = dispatch => ({ }); -export default @connect(mapStateToProps, mapDispatchToProps) class CompareHistoryModal extends React.PureComponent { static propTypes = { onClose: PropTypes.func.isRequired, index: PropTypes.number.isRequired, statusId: PropTypes.string.isRequired, + language: PropTypes.string.isRequired, versions: ImmutablePropTypes.list.isRequired, }; render () { - const { index, versions, onClose } = this.props; + const { index, versions, language, onClose } = this.props; const currentVersion = versions.get(index); const emojiMap = currentVersion.get('emojis').reduce((obj, emoji) => { @@ -65,12 +66,12 @@ class CompareHistoryModal extends React.PureComponent { <div className='status__content'> {currentVersion.get('spoiler_text').length > 0 && ( <React.Fragment> - <div className='translate' dangerouslySetInnerHTML={spoilerContent} /> + <div className='translate' dangerouslySetInnerHTML={spoilerContent} lang={language} /> <hr /> </React.Fragment> )} - <div className='status__content__text status__content__text--visible translate' dangerouslySetInnerHTML={content} /> + <div className='status__content__text status__content__text--visible translate' dangerouslySetInnerHTML={content} lang={language} /> {!!currentVersion.get('poll') && ( <div className='poll'> @@ -82,6 +83,7 @@ class CompareHistoryModal extends React.PureComponent { <span className='poll__option__text translate' dangerouslySetInnerHTML={{ __html: emojify(escapeTextContentForBrowser(option.get('title')), emojiMap) }} + lang={language} /> </li> ))} @@ -89,7 +91,7 @@ class CompareHistoryModal extends React.PureComponent { </div> )} - <MediaAttachments status={currentVersion} /> + <MediaAttachments status={currentVersion} lang={language} /> </div> </div> </div> @@ -97,3 +99,5 @@ class CompareHistoryModal extends React.PureComponent { } } + +export default connect(mapStateToProps, mapDispatchToProps)(CompareHistoryModal); diff --git a/app/javascript/mastodon/features/ui/components/compose_panel.js b/app/javascript/mastodon/features/ui/components/compose_panel.jsx index 92d16b5b3..1c6f80ad5 100644 --- a/app/javascript/mastodon/features/ui/components/compose_panel.js +++ b/app/javascript/mastodon/features/ui/components/compose_panel.jsx @@ -8,7 +8,6 @@ import LinkFooter from './link_footer'; import ServerBanner from 'mastodon/components/server_banner'; import { changeComposing, mountCompose, unmountCompose } from 'mastodon/actions/compose'; -export default @connect() class ComposePanel extends React.PureComponent { static contextTypes = { @@ -22,12 +21,12 @@ class ComposePanel extends React.PureComponent { onFocus = () => { const { dispatch } = this.props; dispatch(changeComposing(true)); - } + }; onBlur = () => { const { dispatch } = this.props; dispatch(changeComposing(false)); - } + }; componentDidMount () { const { dispatch } = this.props; @@ -66,3 +65,5 @@ class ComposePanel extends React.PureComponent { } } + +export default connect()(ComposePanel); diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modal.js b/app/javascript/mastodon/features/ui/components/confirmation_modal.jsx index 65d97ca16..4437567a1 100644 --- a/app/javascript/mastodon/features/ui/components/confirmation_modal.js +++ b/app/javascript/mastodon/features/ui/components/confirmation_modal.jsx @@ -3,7 +3,6 @@ import PropTypes from 'prop-types'; import { injectIntl, FormattedMessage } from 'react-intl'; import Button from '../../../components/button'; -export default @injectIntl class ConfirmationModal extends React.PureComponent { static propTypes = { @@ -30,20 +29,20 @@ class ConfirmationModal extends React.PureComponent { this.props.onClose(); } this.props.onConfirm(); - } + }; handleSecondary = () => { this.props.onClose(); this.props.onSecondary(); - } + }; handleCancel = () => { this.props.onClose(); - } + }; setRef = (c) => { this.button = c; - } + }; render () { const { message, confirm, secondary } = this.props; @@ -68,3 +67,5 @@ class ConfirmationModal extends React.PureComponent { } } + +export default injectIntl(ConfirmationModal); diff --git a/app/javascript/mastodon/features/ui/components/disabled_account_banner.js b/app/javascript/mastodon/features/ui/components/disabled_account_banner.jsx index 038cc3553..ea11cea44 100644 --- a/app/javascript/mastodon/features/ui/components/disabled_account_banner.js +++ b/app/javascript/mastodon/features/ui/components/disabled_account_banner.jsx @@ -28,8 +28,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ }, }); -export default @injectIntl -@connect(mapStateToProps, mapDispatchToProps) class DisabledAccountBanner extends React.PureComponent { static propTypes = { @@ -46,7 +44,7 @@ class DisabledAccountBanner extends React.PureComponent { this.props.onLogout(); return false; - } + }; render () { const { disabledAcct, movedToAcct } = this.props; @@ -90,3 +88,5 @@ class DisabledAccountBanner extends React.PureComponent { } } + +export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(DisabledAccountBanner)); diff --git a/app/javascript/mastodon/features/ui/components/drawer_loading.js b/app/javascript/mastodon/features/ui/components/drawer_loading.jsx index 08b0d2347..08b0d2347 100644 --- a/app/javascript/mastodon/features/ui/components/drawer_loading.js +++ b/app/javascript/mastodon/features/ui/components/drawer_loading.jsx diff --git a/app/javascript/mastodon/features/ui/components/embed_modal.js b/app/javascript/mastodon/features/ui/components/embed_modal.jsx index 4679c9650..baf6be411 100644 --- a/app/javascript/mastodon/features/ui/components/embed_modal.js +++ b/app/javascript/mastodon/features/ui/components/embed_modal.jsx @@ -9,7 +9,6 @@ const messages = defineMessages({ close: { id: 'lightbox.close', defaultMessage: 'Close' }, }); -export default @injectIntl class EmbedModal extends ImmutablePureComponent { static propTypes = { @@ -17,7 +16,7 @@ class EmbedModal extends ImmutablePureComponent { onClose: PropTypes.func.isRequired, onError: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, - } + }; state = { loading: false, @@ -48,11 +47,11 @@ class EmbedModal extends ImmutablePureComponent { setIframeRef = c => { this.iframe = c; - } + }; handleTextareaClick = (e) => { e.target.select(); - } + }; render () { const { intl, onClose } = this.props; @@ -95,3 +94,5 @@ class EmbedModal extends ImmutablePureComponent { } } + +export default injectIntl(EmbedModal); diff --git a/app/javascript/mastodon/features/ui/components/filter_modal.js b/app/javascript/mastodon/features/ui/components/filter_modal.jsx index 376db961d..32ebaf7b7 100644 --- a/app/javascript/mastodon/features/ui/components/filter_modal.js +++ b/app/javascript/mastodon/features/ui/components/filter_modal.jsx @@ -13,8 +13,6 @@ const messages = defineMessages({ close: { id: 'lightbox.close', defaultMessage: 'Close' }, }); -export default @connect(undefined) -@injectIntl class FilterModal extends ImmutablePureComponent { static propTypes = { @@ -132,3 +130,5 @@ class FilterModal extends ImmutablePureComponent { } } + +export default connect(injectIntl(FilterModal)); diff --git a/app/javascript/mastodon/features/ui/components/focal_point_modal.js b/app/javascript/mastodon/features/ui/components/focal_point_modal.jsx index 479f4abd2..11c4c5237 100644 --- a/app/javascript/mastodon/features/ui/components/focal_point_modal.js +++ b/app/javascript/mastodon/features/ui/components/focal_point_modal.jsx @@ -39,6 +39,7 @@ const mapStateToProps = (state, { id }) => ({ account: state.getIn(['accounts', me]), isUploadingThumbnail: state.getIn(['compose', 'isUploadingThumbnail']), description: state.getIn(['compose', 'media_modal', 'description']), + lang: state.getIn(['compose', 'language']), focusX: state.getIn(['compose', 'media_modal', 'focusX']), focusY: state.getIn(['compose', 'media_modal', 'focusY']), dirty: state.getIn(['compose', 'media_modal', 'dirty']), @@ -99,8 +100,6 @@ class ImageLoader extends React.PureComponent { } -export default @connect(mapStateToProps, mapDispatchToProps, null, { forwardRef: true }) -@(component => injectIntl(component, { withRef: true })) class FocalPointModal extends ImmutablePureComponent { static propTypes = { @@ -134,7 +133,7 @@ class FocalPointModal extends ImmutablePureComponent { this.updatePosition(e); this.setState({ dragging: true }); - } + }; handleTouchStart = e => { document.addEventListener('touchmove', this.handleMouseMove); @@ -142,25 +141,25 @@ class FocalPointModal extends ImmutablePureComponent { this.updatePosition(e); this.setState({ dragging: true }); - } + }; handleMouseMove = e => { this.updatePosition(e); - } + }; handleMouseUp = () => { document.removeEventListener('mousemove', this.handleMouseMove); document.removeEventListener('mouseup', this.handleMouseUp); this.setState({ dragging: false }); - } + }; handleTouchEnd = () => { document.removeEventListener('touchmove', this.handleMouseMove); document.removeEventListener('touchend', this.handleTouchEnd); this.setState({ dragging: false }); - } + }; updatePosition = e => { const { x, y } = getPointerPosition(this.node, e); @@ -168,24 +167,24 @@ class FocalPointModal extends ImmutablePureComponent { const focusY = (y - .5) * -2; this.props.onChangeFocus(focusX, focusY); - } + }; handleChange = e => { this.props.onChangeDescription(e.target.value); - } + }; handleKeyDown = (e) => { if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) { this.props.onChangeDescription(e.target.value); this.handleSubmit(e); } - } + }; handleSubmit = (e) => { e.preventDefault(); e.stopPropagation(); this.props.onSave(this.props.description, this.props.focusX, this.props.focusY); - } + }; getCloseConfirmationMessage = () => { const { intl, dirty } = this.props; @@ -198,15 +197,15 @@ class FocalPointModal extends ImmutablePureComponent { } else { return null; } - } + }; setRef = c => { this.node = c; - } + }; handleTextDetection = () => { this._detectText(); - } + }; _detectText = (refreshCache = false) => { const { media } = this.props; @@ -257,24 +256,24 @@ class FocalPointModal extends ImmutablePureComponent { console.error(e); this.setState({ detecting: false }); }); - } + }; handleThumbnailChange = e => { if (e.target.files.length > 0) { this.props.onSelectThumbnail(e.target.files); } - } + }; setFileInputRef = c => { this.fileInput = c; - } + }; handleFileInputClick = () => { this.fileInput.click(); - } + }; render () { - const { media, intl, account, onClose, isUploadingThumbnail, description, focusX, focusY, dirty, is_changing_upload } = this.props; + const { media, intl, account, onClose, isUploadingThumbnail, description, lang, focusX, focusY, dirty, is_changing_upload } = this.props; const { dragging, detecting, progress, ocrStatus } = this.state; const x = (focusX / 2) + .5; const y = (focusY / -2) + .5; @@ -320,7 +319,7 @@ class FocalPointModal extends ImmutablePureComponent { <React.Fragment> <label className='setting-text-label' htmlFor='upload-modal__thumbnail'><FormattedMessage id='upload_form.thumbnail' defaultMessage='Change thumbnail' /></label> - <Button disabled={isUploadingThumbnail} text={intl.formatMessage(messages.chooseImage)} onClick={this.handleFileInputClick} /> + <Button disabled={isUploadingThumbnail || !media.get('unattached')} text={intl.formatMessage(messages.chooseImage)} onClick={this.handleFileInputClick} /> <label> <span style={{ display: 'none' }}>{intl.formatMessage(messages.chooseImage)}</span> @@ -349,6 +348,7 @@ class FocalPointModal extends ImmutablePureComponent { id='upload-modal__description' className='setting-text light' value={detecting ? '…' : description} + lang={lang} onChange={this.handleChange} onKeyDown={this.handleKeyDown} disabled={detecting || is_changing_upload} @@ -426,3 +426,7 @@ class FocalPointModal extends ImmutablePureComponent { } } + +export default connect(mapStateToProps, mapDispatchToProps, null, { + forwardRef: true, +})(injectIntl(FocalPointModal, { withRef: true })); diff --git a/app/javascript/mastodon/features/ui/components/follow_requests_column_link.js b/app/javascript/mastodon/features/ui/components/follow_requests_column_link.jsx index d04d58011..e6ffbdb84 100644 --- a/app/javascript/mastodon/features/ui/components/follow_requests_column_link.js +++ b/app/javascript/mastodon/features/ui/components/follow_requests_column_link.jsx @@ -15,8 +15,6 @@ const mapStateToProps = state => ({ count: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableList()).size, }); -export default @injectIntl -@connect(mapStateToProps) class FollowRequestsColumnLink extends React.Component { static propTypes = { @@ -45,3 +43,5 @@ class FollowRequestsColumnLink extends React.Component { } } + +export default injectIntl(connect(mapStateToProps)(FollowRequestsColumnLink)); diff --git a/app/javascript/mastodon/features/ui/components/header.js b/app/javascript/mastodon/features/ui/components/header.jsx index 1384bebda..c14c6faa7 100644 --- a/app/javascript/mastodon/features/ui/components/header.js +++ b/app/javascript/mastodon/features/ui/components/header.jsx @@ -22,8 +22,6 @@ const mapDispatchToProps = (dispatch) => ({ }, }); -export default @connect(null, mapDispatchToProps) -@withRouter class Header extends React.PureComponent { static contextTypes = { @@ -85,3 +83,5 @@ class Header extends React.PureComponent { } } + +export default withRouter(connect(null, mapDispatchToProps)(Header)); diff --git a/app/javascript/mastodon/features/ui/components/image_loader.js b/app/javascript/mastodon/features/ui/components/image_loader.jsx index dfa0efe49..9093eab28 100644 --- a/app/javascript/mastodon/features/ui/components/image_loader.js +++ b/app/javascript/mastodon/features/ui/components/image_loader.jsx @@ -8,16 +8,18 @@ export default class ImageLoader extends PureComponent { static propTypes = { alt: PropTypes.string, + lang: PropTypes.string, src: PropTypes.string.isRequired, previewSrc: PropTypes.string, width: PropTypes.number, height: PropTypes.number, onClick: PropTypes.func, zoomButtonHidden: PropTypes.bool, - } + }; static defaultProps = { alt: '', + lang: '', width: null, height: null, }; @@ -26,7 +28,7 @@ export default class ImageLoader extends PureComponent { loading: true, error: false, width: null, - } + }; removers = []; canvas = null; @@ -86,7 +88,7 @@ export default class ImageLoader extends PureComponent { image.addEventListener('load', handleLoad); image.src = previewSrc; this.removers.push(removeEventListeners); - }) + }); clearPreviewCanvas () { const { width, height } = this.canvas; @@ -126,10 +128,10 @@ export default class ImageLoader extends PureComponent { setCanvasRef = c => { this.canvas = c; if (c) this.setState({ width: c.offsetWidth }); - } + }; render () { - const { alt, src, width, height, onClick } = this.props; + const { alt, lang, src, width, height, onClick } = this.props; const { loading } = this.state; const className = classNames('image-loader', { @@ -154,6 +156,7 @@ export default class ImageLoader extends PureComponent { ) : ( <ZoomableImage alt={alt} + lang={lang} src={src} onClick={onClick} width={width} diff --git a/app/javascript/mastodon/features/ui/components/image_modal.js b/app/javascript/mastodon/features/ui/components/image_modal.jsx index 7522c3da5..ca93d7b4e 100644 --- a/app/javascript/mastodon/features/ui/components/image_modal.js +++ b/app/javascript/mastodon/features/ui/components/image_modal.jsx @@ -9,7 +9,6 @@ const messages = defineMessages({ close: { id: 'lightbox.close', defaultMessage: 'Close' }, }); -export default @injectIntl class ImageModal extends React.PureComponent { static propTypes = { @@ -57,3 +56,5 @@ class ImageModal extends React.PureComponent { } } + +export default injectIntl(ImageModal); diff --git a/app/javascript/mastodon/features/ui/components/link_footer.js b/app/javascript/mastodon/features/ui/components/link_footer.jsx index 3664a05bf..68ef015ab 100644 --- a/app/javascript/mastodon/features/ui/components/link_footer.js +++ b/app/javascript/mastodon/features/ui/components/link_footer.jsx @@ -3,7 +3,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; import { Link } from 'react-router-dom'; -import { domain, version, source_url, profile_directory as profileDirectory } from 'mastodon/initial_state'; +import { domain, version, source_url, statusPageUrl, profile_directory as profileDirectory } from 'mastodon/initial_state'; import { logOut } from 'mastodon/utils/log_out'; import { openModal } from 'mastodon/actions/modal'; import { PERMISSION_INVITE_USERS } from 'mastodon/permissions'; @@ -24,8 +24,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ }, }); -export default @injectIntl -@connect(null, mapDispatchToProps) class LinkFooter extends React.PureComponent { static contextTypes = { @@ -44,7 +42,7 @@ class LinkFooter extends React.PureComponent { this.props.onLogout(); return false; - } + }; render () { const { signedIn, permissions } = this.context.identity; @@ -59,21 +57,27 @@ class LinkFooter extends React.PureComponent { <p> <strong>{domain}</strong>: {' '} - <Link key='about' to='/about'><FormattedMessage id='footer.about' defaultMessage='About' /></Link> + <Link to='/about'><FormattedMessage id='footer.about' defaultMessage='About' /></Link> + {statusPageUrl && ( + <> + {DividingCircle} + <a href={statusPageUrl} target='_blank' rel='noopener'><FormattedMessage id='footer.status' defaultMessage='Status' /></a> + </> + )} {canInvite && ( <> {DividingCircle} - <a key='invites' href='/invites' target='_blank'><FormattedMessage id='footer.invite' defaultMessage='Invite people' /></a> + <a href='/invites' target='_blank'><FormattedMessage id='footer.invite' defaultMessage='Invite people' /></a> </> )} {canProfileDirectory && ( <> {DividingCircle} - <Link key='directory' to='/directory'><FormattedMessage id='footer.directory' defaultMessage='Profiles directory' /></Link> + <Link to='/directory'><FormattedMessage id='footer.directory' defaultMessage='Profiles directory' /></Link> </> )} {DividingCircle} - <Link key='privacy-policy' to='/privacy-policy'><FormattedMessage id='footer.privacy_policy' defaultMessage='Privacy policy' /></Link> + <Link to='/privacy-policy'><FormattedMessage id='footer.privacy_policy' defaultMessage='Privacy policy' /></Link> </p> <p> @@ -94,3 +98,5 @@ class LinkFooter extends React.PureComponent { } } + +export default injectIntl(connect(null, mapDispatchToProps)(LinkFooter)); diff --git a/app/javascript/mastodon/features/ui/components/list_panel.js b/app/javascript/mastodon/features/ui/components/list_panel.jsx index 2f92a9254..fcff4e37d 100644 --- a/app/javascript/mastodon/features/ui/components/list_panel.js +++ b/app/javascript/mastodon/features/ui/components/list_panel.jsx @@ -20,8 +20,6 @@ const mapStateToProps = state => ({ lists: getOrderedLists(state), }); -export default @withRouter -@connect(mapStateToProps) class ListPanel extends ImmutablePureComponent { static propTypes = { @@ -53,3 +51,5 @@ class ListPanel extends ImmutablePureComponent { } } + +export default withRouter(connect(mapStateToProps)(ListPanel)); diff --git a/app/javascript/mastodon/features/ui/components/media_modal.js b/app/javascript/mastodon/features/ui/components/media_modal.jsx index 7f50572e7..e8005e67a 100644 --- a/app/javascript/mastodon/features/ui/components/media_modal.js +++ b/app/javascript/mastodon/features/ui/components/media_modal.jsx @@ -3,6 +3,7 @@ import ReactSwipeableViews from 'react-swipeable-views'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import Video from 'mastodon/features/video'; +import { connect } from 'react-redux'; import classNames from 'classnames'; import { defineMessages, injectIntl } from 'react-intl'; import IconButton from 'mastodon/components/icon_button'; @@ -20,7 +21,10 @@ const messages = defineMessages({ next: { id: 'lightbox.next', defaultMessage: 'Next' }, }); -export default @injectIntl +const mapStateToProps = (state, { statusId }) => ({ + language: state.getIn(['statuses', statusId, 'language']), +}); + class MediaModal extends ImmutablePureComponent { static propTypes = { @@ -43,27 +47,27 @@ class MediaModal extends ImmutablePureComponent { handleSwipe = (index) => { this.setState({ index: index % this.props.media.size }); - } + }; handleTransitionEnd = () => { this.setState({ zoomButtonHidden: false, }); - } + }; handleNextClick = () => { this.setState({ index: (this.getIndex() + 1) % this.props.media.size, zoomButtonHidden: true, }); - } + }; handlePrevClick = () => { this.setState({ index: (this.props.media.size + this.getIndex() - 1) % this.props.media.size, zoomButtonHidden: true, }); - } + }; handleChangeIndex = (e) => { const index = Number(e.currentTarget.getAttribute('data-index')); @@ -72,7 +76,7 @@ class MediaModal extends ImmutablePureComponent { index: index % this.props.media.size, zoomButtonHidden: true, }); - } + }; handleKeyDown = (e) => { switch(e.key) { @@ -87,7 +91,7 @@ class MediaModal extends ImmutablePureComponent { e.stopPropagation(); break; } - } + }; componentDidMount () { window.addEventListener('keydown', this.handleKeyDown, false); @@ -110,13 +114,13 @@ class MediaModal extends ImmutablePureComponent { }; render () { - const { media, statusId, intl, onClose } = this.props; + const { media, language, statusId, intl, onClose } = this.props; const { navigationHidden } = this.state; const index = this.getIndex(); - const leftNav = media.size > 1 && <button tabIndex='0' className='media-modal__nav media-modal__nav--left' onClick={this.handlePrevClick} aria-label={intl.formatMessage(messages.previous)}><Icon id='chevron-left' fixedWidth /></button>; - const rightNav = media.size > 1 && <button tabIndex='0' className='media-modal__nav media-modal__nav--right' onClick={this.handleNextClick} aria-label={intl.formatMessage(messages.next)}><Icon id='chevron-right' fixedWidth /></button>; + const leftNav = media.size > 1 && <button tabIndex={0} className='media-modal__nav media-modal__nav--left' onClick={this.handlePrevClick} aria-label={intl.formatMessage(messages.previous)}><Icon id='chevron-left' fixedWidth /></button>; + const rightNav = media.size > 1 && <button tabIndex={0} className='media-modal__nav media-modal__nav--right' onClick={this.handleNextClick} aria-label={intl.formatMessage(messages.next)}><Icon id='chevron-right' fixedWidth /></button>; const content = media.map((image) => { const width = image.getIn(['meta', 'original', 'width']) || null; @@ -130,6 +134,7 @@ class MediaModal extends ImmutablePureComponent { width={width} height={height} alt={image.get('description')} + lang={language} key={image.get('url')} onClick={this.toggleNavigation} zoomButtonHidden={this.state.zoomButtonHidden} @@ -152,6 +157,7 @@ class MediaModal extends ImmutablePureComponent { onCloseVideo={onClose} detailed alt={image.get('description')} + lang={language} key={image.get('url')} /> ); @@ -163,6 +169,7 @@ class MediaModal extends ImmutablePureComponent { height={height} key={image.get('preview_url')} alt={image.get('description')} + lang={language} onClick={this.toggleNavigation} /> ); @@ -229,3 +236,5 @@ class MediaModal extends ImmutablePureComponent { } } + +export default connect(mapStateToProps, null, null, { forwardRef: true })(injectIntl(MediaModal)); diff --git a/app/javascript/mastodon/features/ui/components/modal_loading.js b/app/javascript/mastodon/features/ui/components/modal_loading.jsx index f403ca4c9..f403ca4c9 100644 --- a/app/javascript/mastodon/features/ui/components/modal_loading.js +++ b/app/javascript/mastodon/features/ui/components/modal_loading.jsx diff --git a/app/javascript/mastodon/features/ui/components/modal_root.js b/app/javascript/mastodon/features/ui/components/modal_root.jsx index 6c4aabae5..c252e6caa 100644 --- a/app/javascript/mastodon/features/ui/components/modal_root.js +++ b/app/javascript/mastodon/features/ui/components/modal_root.jsx @@ -73,23 +73,23 @@ export default class ModalRoot extends React.PureComponent { document.documentElement.style.marginRight = `${getScrollbarWidth()}px`; } else { document.body.classList.remove('with-modals--active'); - document.documentElement.style.marginRight = 0; + document.documentElement.style.marginRight = '0'; } } setBackgroundColor = color => { this.setState({ backgroundColor: color }); - } + }; renderLoading = modalId => () => { return ['MEDIA', 'VIDEO', 'BOOST', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? <ModalLoading /> : null; - } + }; renderError = (props) => { const { onClose } = this.props; return <BundleModalError {...props} onClose={onClose} />; - } + }; handleClose = (ignoreFocus = false) => { const { onClose } = this.props; @@ -102,11 +102,11 @@ export default class ModalRoot extends React.PureComponent { // This would be much smoother with react-intl 3+ and `forwardRef`. } onClose(message, ignoreFocus); - } + }; setModalRef = (c) => { this._modal = c; - } + }; render () { const { type, props, ignoreFocus } = this.props; diff --git a/app/javascript/mastodon/features/ui/components/mute_modal.js b/app/javascript/mastodon/features/ui/components/mute_modal.jsx index d8d8e68c3..fa64bf0f4 100644 --- a/app/javascript/mastodon/features/ui/components/mute_modal.js +++ b/app/javascript/mastodon/features/ui/components/mute_modal.jsx @@ -43,8 +43,6 @@ const mapDispatchToProps = dispatch => { }; }; -export default @connect(mapStateToProps, mapDispatchToProps) -@injectIntl class MuteModal extends React.PureComponent { static propTypes = { @@ -65,23 +63,23 @@ class MuteModal extends React.PureComponent { handleClick = () => { this.props.onClose(); this.props.onConfirm(this.props.account, this.props.notifications, this.props.muteDuration); - } + }; handleCancel = () => { this.props.onClose(); - } + }; setRef = (c) => { this.button = c; - } + }; toggleNotifications = () => { this.props.onToggleNotifications(); - } + }; changeMuteDuration = (e) => { this.props.onChangeMuteDuration(e); - } + }; render () { const { account, notifications, muteDuration, intl } = this.props; @@ -138,3 +136,5 @@ class MuteModal extends React.PureComponent { } } + +export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(MuteModal)); diff --git a/app/javascript/mastodon/features/ui/components/navigation_panel.js b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx index 9a9309be0..ee1a83cc6 100644 --- a/app/javascript/mastodon/features/ui/components/navigation_panel.js +++ b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx @@ -18,7 +18,7 @@ const messages = defineMessages({ explore: { id: 'explore.title', defaultMessage: 'Explore' }, local: { id: 'tabs_bar.local_timeline', defaultMessage: 'Local' }, federated: { id: 'tabs_bar.federated_timeline', defaultMessage: 'Federated' }, - direct: { id: 'navigation_bar.direct', defaultMessage: 'Direct messages' }, + direct: { id: 'navigation_bar.direct', defaultMessage: 'Private mentions' }, favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' }, bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' }, lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' }, @@ -28,7 +28,6 @@ const messages = defineMessages({ search: { id: 'navigation_bar.search', defaultMessage: 'Search' }, }); -export default @injectIntl class NavigationPanel extends React.Component { static contextTypes = { @@ -82,8 +81,8 @@ class NavigationPanel extends React.Component { {signedIn && ( <React.Fragment> <ColumnLink transparent to='/conversations' icon='at' text={intl.formatMessage(messages.direct)} /> - <ColumnLink transparent to='/favourites' icon='star' text={intl.formatMessage(messages.favourites)} /> <ColumnLink transparent to='/bookmarks' icon='bookmark' text={intl.formatMessage(messages.bookmarks)} /> + <ColumnLink transparent to='/favourites' icon='star' text={intl.formatMessage(messages.favourites)} /> <ColumnLink transparent to='/lists' icon='list-ul' text={intl.formatMessage(messages.lists)} /> <ListPanel /> @@ -105,3 +104,5 @@ class NavigationPanel extends React.Component { } } + +export default injectIntl(NavigationPanel); diff --git a/app/javascript/mastodon/features/ui/components/report_modal.js b/app/javascript/mastodon/features/ui/components/report_modal.jsx index 264da07ce..8b505b8bd 100644 --- a/app/javascript/mastodon/features/ui/components/report_modal.js +++ b/app/javascript/mastodon/features/ui/components/report_modal.jsx @@ -30,8 +30,6 @@ const makeMapStateToProps = () => { return mapStateToProps; }; -export default @connect(makeMapStateToProps) -@injectIntl class ReportModal extends ImmutablePureComponent { static propTypes = { @@ -95,7 +93,7 @@ class ReportModal extends ImmutablePureComponent { } else { this.setState({ selectedRuleIds: selectedRuleIds.remove(ruleId) }); } - } + }; handleChangeCategory = category => { this.setState({ category }); @@ -217,3 +215,5 @@ class ReportModal extends ImmutablePureComponent { } } + +export default connect(makeMapStateToProps)(injectIntl(ReportModal)); diff --git a/app/javascript/mastodon/features/ui/components/sign_in_banner.js b/app/javascript/mastodon/features/ui/components/sign_in_banner.jsx index 8bd32edf9..86fcc11b5 100644 --- a/app/javascript/mastodon/features/ui/components/sign_in_banner.js +++ b/app/javascript/mastodon/features/ui/components/sign_in_banner.jsx @@ -30,7 +30,7 @@ const SignInBanner = () => { return ( <div className='sign-in-banner'> - <p><FormattedMessage id='sign_in_banner.text' defaultMessage='Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.' /></p> + <p><FormattedMessage id='sign_in_banner.text' defaultMessage='Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.' /></p> <a href='/auth/sign_in' className='button button--block'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Sign in' /></a> {signupButton} </div> diff --git a/app/javascript/mastodon/features/ui/components/upload_area.js b/app/javascript/mastodon/features/ui/components/upload_area.jsx index 6c423b2c1..035fe7a26 100644 --- a/app/javascript/mastodon/features/ui/components/upload_area.js +++ b/app/javascript/mastodon/features/ui/components/upload_area.jsx @@ -22,7 +22,7 @@ export default class UploadArea extends React.PureComponent { break; } } - } + }; componentDidMount () { window.addEventListener('keyup', this.handleKeyUp, false); diff --git a/app/javascript/mastodon/features/ui/components/video_modal.js b/app/javascript/mastodon/features/ui/components/video_modal.jsx index a1533eba0..0e754ccc7 100644 --- a/app/javascript/mastodon/features/ui/components/video_modal.js +++ b/app/javascript/mastodon/features/ui/components/video_modal.jsx @@ -2,15 +2,21 @@ import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import Video from 'mastodon/features/video'; +import { connect } from 'react-redux'; import ImmutablePureComponent from 'react-immutable-pure-component'; import Footer from 'mastodon/features/picture_in_picture/components/footer'; import { getAverageFromBlurhash } from 'mastodon/blurhash'; -export default class VideoModal extends ImmutablePureComponent { +const mapStateToProps = (state, { statusId }) => ({ + language: state.getIn(['statuses', statusId, 'language']), +}); + +class VideoModal extends ImmutablePureComponent { static propTypes = { media: ImmutablePropTypes.map.isRequired, statusId: PropTypes.string, + language: PropTypes.string, options: PropTypes.shape({ startTime: PropTypes.number, autoPlay: PropTypes.bool, @@ -25,7 +31,7 @@ export default class VideoModal extends ImmutablePureComponent { } render () { - const { media, statusId, onClose } = this.props; + const { media, statusId, language, onClose } = this.props; const options = this.props.options || {}; return ( @@ -43,6 +49,7 @@ export default class VideoModal extends ImmutablePureComponent { autoFocus detailed alt={media.get('description')} + lang={language} /> </div> @@ -54,3 +61,5 @@ export default class VideoModal extends ImmutablePureComponent { } } + +export default connect(mapStateToProps, null, null, { forwardRef: true })(VideoModal); diff --git a/app/javascript/mastodon/features/ui/components/zoomable_image.js b/app/javascript/mastodon/features/ui/components/zoomable_image.jsx index 1cf263cb9..b41d0fe31 100644 --- a/app/javascript/mastodon/features/ui/components/zoomable_image.js +++ b/app/javascript/mastodon/features/ui/components/zoomable_image.jsx @@ -91,21 +91,22 @@ const normalizeWheel = event => { }; }; -export default @injectIntl class ZoomableImage extends React.PureComponent { static propTypes = { alt: PropTypes.string, + lang: PropTypes.string, src: PropTypes.string.isRequired, width: PropTypes.number, height: PropTypes.number, onClick: PropTypes.func, zoomButtonHidden: PropTypes.bool, intl: PropTypes.object.isRequired, - } + }; static defaultProps = { alt: '', + lang: '', width: null, height: null, }; @@ -132,7 +133,7 @@ class ZoomableImage extends React.PureComponent { dragged: false, lockScroll: { x: 0, y: 0 }, lockTranslate: { x: 0, y: 0 }, - } + }; removers = []; container = null; @@ -212,7 +213,7 @@ class ZoomableImage extends React.PureComponent { // lock horizontal scroll this.container.scrollLeft = Math.max(this.container.scrollLeft + event.pixelX, this.state.lockScroll.x); - } + }; mouseDownHandler = e => { this.container.style.cursor = 'grabbing'; @@ -228,7 +229,7 @@ class ZoomableImage extends React.PureComponent { this.image.addEventListener('mousemove', this.mouseMoveHandler); this.image.addEventListener('mouseup', this.mouseUpHandler); - } + }; mouseMoveHandler = e => { const dx = e.clientX - this.state.dragPosition.x; @@ -238,7 +239,7 @@ class ZoomableImage extends React.PureComponent { this.container.scrollTop = Math.max(this.state.dragPosition.top - dy, this.state.lockScroll.y); this.setState({ dragged: true }); - } + }; mouseUpHandler = () => { this.container.style.cursor = 'grab'; @@ -246,13 +247,13 @@ class ZoomableImage extends React.PureComponent { this.image.removeEventListener('mousemove', this.mouseMoveHandler); this.image.removeEventListener('mouseup', this.mouseUpHandler); - } + }; handleTouchStart = e => { if (e.touches.length !== 2) return; this.lastDistance = getDistance(...e.touches); - } + }; handleTouchMove = e => { const { scrollTop, scrollHeight, clientHeight } = this.container; @@ -275,7 +276,7 @@ class ZoomableImage extends React.PureComponent { this.lastMidpoint = midpoint; this.lastDistance = distance; - } + }; zoom(nextScale, midpoint) { const { scale, zoomMatrix } = this.state; @@ -314,11 +315,11 @@ class ZoomableImage extends React.PureComponent { const handler = this.props.onClick; if (handler) handler(); this.setState({ navigationHidden: !this.state.navigationHidden }); - } + }; handleMouseDown = e => { e.preventDefault(); - } + }; initZoomMatrix = () => { const { width, height } = this.props; @@ -350,7 +351,7 @@ class ZoomableImage extends React.PureComponent { translateY: translateY, }, }); - } + }; handleZoomClick = e => { e.preventDefault(); @@ -392,18 +393,18 @@ class ZoomableImage extends React.PureComponent { this.container.style.cursor = 'grab'; this.container.style.removeProperty('user-select'); - } + }; setContainerRef = c => { this.container = c; - } + }; setImageRef = c => { this.image = c; - } + }; render () { - const { alt, src, width, height, intl } = this.props; + const { alt, lang, src, width, height, intl } = this.props; const { scale, lockTranslate } = this.state; const overflow = scale === MIN_SCALE ? 'hidden' : 'scroll'; const zoomButtonShouldHide = this.state.navigationHidden || this.props.zoomButtonHidden || this.state.zoomMatrix.rate <= MIN_SCALE ? 'media-modal__zoom-button--hidden' : ''; @@ -431,6 +432,7 @@ class ZoomableImage extends React.PureComponent { ref={this.setImageRef} alt={alt} title={alt} + lang={lang} src={src} width={width} height={height} @@ -448,3 +450,5 @@ class ZoomableImage extends React.PureComponent { } } + +export default injectIntl(ZoomableImage); diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.jsx index b05956606..0e73f4c09 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.jsx @@ -13,7 +13,7 @@ import { debounce } from 'lodash'; import { uploadCompose, resetCompose, changeComposeSpoilerness } from '../../actions/compose'; import { expandHomeTimeline } from '../../actions/timelines'; import { expandNotifications } from '../../actions/notifications'; -import { fetchServer } from '../../actions/server'; +import { fetchServer, fetchServerTranslationLanguages } from '../../actions/server'; import { clearHeight } from '../../actions/height_cache'; import { focusApp, unfocusApp, changeLayout } from 'mastodon/actions/app'; import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodon/actions/markers'; @@ -42,6 +42,7 @@ import { FollowRequests, FavouritedStatuses, BookmarkedStatuses, + FollowedTags, ListTimeline, Blocks, DomainBlocks, @@ -54,7 +55,7 @@ import { About, PrivacyPolicy, } from './util/async-components'; -import initialState, { me, owner, singleUserMode, showTrends } from '../../initial_state'; +import initialState, { me, owner, singleUserMode, showTrends, trendsAsLanding } from '../../initial_state'; import { closeOnboarding, INTRODUCTION_VERSION } from 'mastodon/actions/onboarding'; import Header from './components/header'; @@ -147,7 +148,7 @@ class SwitchingColumnsArea extends React.PureComponent { if (c) { this.node = c; } - } + }; render () { const { children, mobile } = this.props; @@ -163,7 +164,7 @@ class SwitchingColumnsArea extends React.PureComponent { } } else if (singleUserMode && owner && initialState?.accounts[owner]) { redirect = <Redirect from='/' to={`/@${initialState.accounts[owner].username}`} exact />; - } else if (showTrends) { + } else if (showTrends && trendsAsLanding) { redirect = <Redirect from='/' to='/explore' exact />; } else { redirect = <Redirect from='/' to='/about' exact />; @@ -216,6 +217,7 @@ class SwitchingColumnsArea extends React.PureComponent { <WrappedRoute path='/follow_requests' component={FollowRequests} content={children} /> <WrappedRoute path='/blocks' component={Blocks} content={children} /> <WrappedRoute path='/domain_blocks' component={DomainBlocks} content={children} /> + <WrappedRoute path='/followed_tags' component={FollowedTags} content={children} /> <WrappedRoute path='/mutes' component={Mutes} content={children} /> <WrappedRoute path='/lists' component={Lists} content={children} /> @@ -227,9 +229,6 @@ class SwitchingColumnsArea extends React.PureComponent { } -export default @connect(mapStateToProps) -@injectIntl -@withRouter class UI extends React.PureComponent { static contextTypes = { @@ -268,16 +267,16 @@ class UI extends React.PureComponent { // but we set user-friendly message for other browsers, e.g. Edge. e.returnValue = intl.formatMessage(messages.beforeUnload); } - } + }; handleWindowFocus = () => { this.props.dispatch(focusApp()); this.props.dispatch(submitMarkers({ immediate: true })); - } + }; handleWindowBlur = () => { this.props.dispatch(unfocusApp()); - } + }; handleDragEnter = (e) => { e.preventDefault(); @@ -293,7 +292,7 @@ class UI extends React.PureComponent { if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files') && this.props.canUploadMore && this.context.identity.signedIn) { this.setState({ draggingOver: true }); } - } + }; handleDragOver = (e) => { if (this.dataTransferIsText(e.dataTransfer)) return false; @@ -308,7 +307,7 @@ class UI extends React.PureComponent { } return false; - } + }; handleDrop = (e) => { if (this.dataTransferIsText(e.dataTransfer)) return; @@ -321,7 +320,7 @@ class UI extends React.PureComponent { if (e.dataTransfer && e.dataTransfer.files.length >= 1 && this.props.canUploadMore && this.context.identity.signedIn) { this.props.dispatch(uploadCompose(e.dataTransfer.files)); } - } + }; handleDragLeave = (e) => { e.preventDefault(); @@ -334,15 +333,15 @@ class UI extends React.PureComponent { } this.setState({ draggingOver: false }); - } + }; dataTransferIsText = (dataTransfer) => { return (dataTransfer && Array.from(dataTransfer.types).filter((type) => type === 'text/plain').length === 1); - } + }; closeUploadModal = () => { this.setState({ draggingOver: false }); - } + }; handleServiceWorkerPostMessage = ({ data }) => { if (data.type === 'navigate') { @@ -350,7 +349,7 @@ class UI extends React.PureComponent { } else { console.warn('Unknown message type:', data.type); } - } + }; handleLayoutChange = debounce(() => { this.props.dispatch(clearHeight()); // The cached heights are no longer accurate, invalidate @@ -367,7 +366,7 @@ class UI extends React.PureComponent { } else { this.handleLayoutChange(); } - } + }; componentDidMount () { const { signedIn } = this.context.identity; @@ -397,6 +396,7 @@ class UI extends React.PureComponent { this.props.dispatch(fetchMarkers()); this.props.dispatch(expandHomeTimeline()); this.props.dispatch(expandNotifications()); + this.props.dispatch(fetchServerTranslationLanguages()); setTimeout(() => this.props.dispatch(fetchServer()), 3000); } @@ -421,7 +421,7 @@ class UI extends React.PureComponent { setRef = c => { this.node = c; - } + }; handleHotkeyNew = e => { e.preventDefault(); @@ -431,7 +431,7 @@ class UI extends React.PureComponent { if (element) { element.focus(); } - } + }; handleHotkeySearch = e => { e.preventDefault(); @@ -441,17 +441,17 @@ class UI extends React.PureComponent { if (element) { element.focus(); } - } + }; handleHotkeyForceNew = e => { this.handleHotkeyNew(e); this.props.dispatch(resetCompose()); - } + }; handleHotkeyToggleComposeSpoilers = e => { e.preventDefault(); this.props.dispatch(changeComposeSpoilerness()); - } + }; handleHotkeyFocusColumn = e => { const index = (e.key * 1) + 1; // First child is drawer, skip that @@ -469,19 +469,19 @@ class UI extends React.PureComponent { status.focus(); } } - } + }; handleHotkeyBack = () => { - if (window.history && window.history.length === 1) { - this.context.router.history.push('/'); - } else { + if (window.history && window.history.state) { this.context.router.history.goBack(); + } else { + this.context.router.history.push('/'); } - } + }; setHotkeysRef = c => { this.hotkeys = c; - } + }; handleHotkeyToggleHelp = () => { if (this.props.location.pathname === '/keyboard-shortcuts') { @@ -489,55 +489,55 @@ class UI extends React.PureComponent { } else { this.context.router.history.push('/keyboard-shortcuts'); } - } + }; handleHotkeyGoToHome = () => { this.context.router.history.push('/home'); - } + }; handleHotkeyGoToNotifications = () => { this.context.router.history.push('/notifications'); - } + }; handleHotkeyGoToLocal = () => { this.context.router.history.push('/public/local'); - } + }; handleHotkeyGoToFederated = () => { this.context.router.history.push('/public'); - } + }; handleHotkeyGoToDirect = () => { this.context.router.history.push('/conversations'); - } + }; handleHotkeyGoToStart = () => { this.context.router.history.push('/getting-started'); - } + }; handleHotkeyGoToFavourites = () => { this.context.router.history.push('/favourites'); - } + }; handleHotkeyGoToPinned = () => { this.context.router.history.push('/pinned'); - } + }; handleHotkeyGoToProfile = () => { this.context.router.history.push(`/@${this.props.username}`); - } + }; handleHotkeyGoToBlocked = () => { this.context.router.history.push('/blocks'); - } + }; handleHotkeyGoToMuted = () => { this.context.router.history.push('/mutes'); - } + }; handleHotkeyGoToRequests = () => { this.context.router.history.push('/follow_requests'); - } + }; render () { const { draggingOver } = this.state; @@ -585,3 +585,5 @@ class UI extends React.PureComponent { } } + +export default connect(mapStateToProps)(injectIntl(withRouter(UI))); diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js index 6046578de..1cf07f645 100644 --- a/app/javascript/mastodon/features/ui/util/async-components.js +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -90,6 +90,10 @@ export function FavouritedStatuses () { return import(/* webpackChunkName: "features/favourited_statuses" */'../../favourited_statuses'); } +export function FollowedTags () { + return import(/* webpackChunkName: "features/followed_tags" */'../../followed_tags'); +} + export function BookmarkedStatuses () { return import(/* webpackChunkName: "features/bookmarked_statuses" */'../../bookmarked_statuses'); } diff --git a/app/javascript/mastodon/features/ui/util/react_router_helpers.js b/app/javascript/mastodon/features/ui/util/react_router_helpers.jsx index 205dd6f10..21b352878 100644 --- a/app/javascript/mastodon/features/ui/util/react_router_helpers.js +++ b/app/javascript/mastodon/features/ui/util/react_router_helpers.jsx @@ -80,17 +80,17 @@ export class WrappedRoute extends React.Component { {Component => <Component params={match.params} multiColumn={multiColumn} {...componentParams}>{content}</Component>} </BundleContainer> ); - } + }; renderLoading = () => { const { multiColumn } = this.props; return <ColumnLoading multiColumn={multiColumn} />; - } + }; renderError = (props) => { return <BundleColumnError {...props} errorType='network' />; - } + }; render () { const { component: Component, content, ...rest } = this.props; diff --git a/app/javascript/mastodon/features/ui/util/reduced_motion.js b/app/javascript/mastodon/features/ui/util/reduced_motion.jsx index 95519042b..1123b80ed 100644 --- a/app/javascript/mastodon/features/ui/util/reduced_motion.js +++ b/app/javascript/mastodon/features/ui/util/reduced_motion.jsx @@ -17,7 +17,7 @@ class ReducedMotion extends React.Component { defaultStyle: PropTypes.object, style: PropTypes.object, children: PropTypes.func, - } + }; render() { diff --git a/app/javascript/mastodon/features/video/index.js b/app/javascript/mastodon/features/video/index.jsx index 1c35ca9d1..e2637e0be 100644 --- a/app/javascript/mastodon/features/video/index.js +++ b/app/javascript/mastodon/features/video/index.jsx @@ -94,7 +94,6 @@ export const fileNameFromURL = str => { return pathname.slice(index + 1); }; -export default @injectIntl class Video extends React.PureComponent { static propTypes = { @@ -102,6 +101,7 @@ class Video extends React.PureComponent { frameRate: PropTypes.string, src: PropTypes.string.isRequired, alt: PropTypes.string, + lang: PropTypes.string, width: PropTypes.number, height: PropTypes.number, sensitive: PropTypes.bool, @@ -148,7 +148,7 @@ class Video extends React.PureComponent { if (this.player) { this._setDimensions(); } - } + }; _setDimensions () { const width = this.player.offsetWidth; @@ -168,26 +168,26 @@ class Video extends React.PureComponent { if (this.video) { this.setState({ volume: this.video.volume, muted: this.video.muted }); } - } + }; setSeekRef = c => { this.seek = c; - } + }; setVolumeRef = c => { this.volume = c; - } + }; handleClickRoot = e => e.stopPropagation(); handlePlay = () => { this.setState({ paused: false }); this._updateTime(); - } + }; handlePause = () => { this.setState({ paused: true }); - } + }; _updateTime () { requestAnimationFrame(() => { @@ -206,7 +206,7 @@ class Video extends React.PureComponent { currentTime: this.video.currentTime, duration:this.video.duration, }); - } + }; handleVolumeMouseDown = e => { document.addEventListener('mousemove', this.handleMouseVolSlide, true); @@ -218,14 +218,14 @@ class Video extends React.PureComponent { e.preventDefault(); e.stopPropagation(); - } + }; handleVolumeMouseUp = () => { document.removeEventListener('mousemove', this.handleMouseVolSlide, true); document.removeEventListener('mouseup', this.handleVolumeMouseUp, true); document.removeEventListener('touchmove', this.handleMouseVolSlide, true); document.removeEventListener('touchend', this.handleVolumeMouseUp, true); - } + }; handleMouseVolSlide = throttle(e => { const { x } = getPointerPosition(this.volume, e); @@ -249,7 +249,7 @@ class Video extends React.PureComponent { e.preventDefault(); e.stopPropagation(); - } + }; handleMouseUp = () => { document.removeEventListener('mousemove', this.handleMouseMove, true); @@ -259,7 +259,7 @@ class Video extends React.PureComponent { this.setState({ dragging: false }); this.video.play(); - } + }; handleMouseMove = throttle(e => { const { x } = getPointerPosition(this.seek, e); @@ -291,7 +291,7 @@ class Video extends React.PureComponent { e.stopPropagation(); this.togglePlay(); } - } + }; handleKeyDown = e => { const frameTime = 1 / this.getFrameRate(); @@ -345,7 +345,7 @@ class Video extends React.PureComponent { exitFullscreen(); } } - } + }; togglePlay = () => { if (this.state.paused) { @@ -353,7 +353,7 @@ class Video extends React.PureComponent { } else { this.setState({ paused: true }, () => this.video.pause()); } - } + }; toggleFullscreen = () => { if (isFullscreen()) { @@ -361,7 +361,7 @@ class Video extends React.PureComponent { } else { requestFullscreen(this.player); } - } + }; componentDidMount () { document.addEventListener('fullscreenchange', this.handleFullscreenChange, true); @@ -434,19 +434,19 @@ class Video extends React.PureComponent { this.setState({ paused: true }); } - }, 150, { trailing: true }) + }, 150, { trailing: true }); handleFullscreenChange = () => { this.setState({ fullscreen: isFullscreen() }); - } + }; handleMouseEnter = () => { this.setState({ hovered: true }); - } + }; handleMouseLeave = () => { this.setState({ hovered: false }); - } + }; toggleMute = () => { const muted = !this.video.muted; @@ -454,7 +454,7 @@ class Video extends React.PureComponent { this.setState({ muted }, () => { this.video.muted = muted; }); - } + }; toggleReveal = () => { if (this.props.onToggleVisibility) { @@ -462,7 +462,7 @@ class Video extends React.PureComponent { } else { this.setState({ revealed: !this.state.revealed }); } - } + }; handleLoadedData = () => { const { currentTime, volume, muted, autoPlay } = this.props; @@ -482,7 +482,7 @@ class Video extends React.PureComponent { if (autoPlay) { this.video.play(); } - } + }; handleProgress = () => { const lastTimeRange = this.video.buffered.length - 1; @@ -490,11 +490,11 @@ class Video extends React.PureComponent { if (lastTimeRange > -1) { this.setState({ buffer: Math.ceil(this.video.buffered.end(lastTimeRange) / this.video.duration * 100) }); } - } + }; handleVolumeChange = () => { this.setState({ volume: this.video.volume, muted: this.video.muted }); - } + }; handleOpenVideo = () => { this.video.pause(); @@ -505,12 +505,12 @@ class Video extends React.PureComponent { defaultVolume: this.state.volume, componentIndex: this.props.componentIndex, }); - } + }; handleCloseVideo = () => { this.video.pause(); this.props.onCloseVideo(); - } + }; getFrameRate () { if (this.props.frameRate && isNaN(this.props.frameRate)) { @@ -524,7 +524,7 @@ class Video extends React.PureComponent { } render () { - const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, detailed, sensitive, editable, blurhash, autoFocus } = this.props; + const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, lang, detailed, sensitive, editable, blurhash, autoFocus } = this.props; const { containerWidth, currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state; const progress = Math.min((currentTime / duration) * 100, 100); const playerStyle = {}; @@ -582,9 +582,10 @@ class Video extends React.PureComponent { poster={preview} preload={preload} role='button' - tabIndex='0' + tabIndex={0} aria-label={alt} title={alt} + lang={lang} width={width} height={height} volume={volume} @@ -610,7 +611,7 @@ class Video extends React.PureComponent { <span className={classNames('video-player__seek__handle', { active: dragging })} - tabIndex='0' + tabIndex={0} style={{ left: `${progress}%` }} onKeyDown={this.handleVideoKeyDown} /> @@ -626,7 +627,7 @@ class Video extends React.PureComponent { <span className={classNames('video-player__volume__handle')} - tabIndex='0' + tabIndex={0} style={{ left: `${volume * 100}%` }} /> </div> @@ -653,3 +654,5 @@ class Video extends React.PureComponent { } } + +export default injectIntl(Video); |