From bc4fa6b198557a7f3989eb0865e2c77ac7451d29 Mon Sep 17 00:00:00 2001 From: kibigo! Date: Sun, 3 Dec 2017 23:26:40 -0800 Subject: Rename themes -> flavours ? ? --- app/javascript/flavours/glitch/actions/accounts.js | 661 +++ app/javascript/flavours/glitch/actions/alerts.js | 24 + app/javascript/flavours/glitch/actions/blocks.js | 82 + app/javascript/flavours/glitch/actions/bundles.js | 25 + app/javascript/flavours/glitch/actions/cards.js | 52 + app/javascript/flavours/glitch/actions/columns.js | 40 + app/javascript/flavours/glitch/actions/compose.js | 398 ++ .../flavours/glitch/actions/domain_blocks.js | 117 + app/javascript/flavours/glitch/actions/emojis.js | 14 + .../flavours/glitch/actions/favourites.js | 83 + .../flavours/glitch/actions/height_cache.js | 17 + .../flavours/glitch/actions/interactions.js | 313 ++ .../flavours/glitch/actions/local_settings.js | 24 + app/javascript/flavours/glitch/actions/modal.js | 16 + app/javascript/flavours/glitch/actions/mutes.js | 103 + .../flavours/glitch/actions/notifications.js | 265 ++ .../flavours/glitch/actions/onboarding.js | 14 + .../flavours/glitch/actions/pin_statuses.js | 40 + .../flavours/glitch/actions/push_notifications.js | 52 + app/javascript/flavours/glitch/actions/reports.js | 80 + app/javascript/flavours/glitch/actions/search.js | 73 + app/javascript/flavours/glitch/actions/settings.js | 31 + app/javascript/flavours/glitch/actions/statuses.js | 217 + app/javascript/flavours/glitch/actions/store.js | 17 + .../flavours/glitch/actions/streaming.js | 54 + .../flavours/glitch/actions/timelines.js | 208 + .../flavours/glitch/components/account.js | 116 + .../flavours/glitch/components/attachment_list.js | 33 + .../glitch/components/autosuggest_emoji.js | 42 + .../glitch/components/autosuggest_textarea.js | 222 + .../flavours/glitch/components/avatar.js | 72 + .../flavours/glitch/components/avatar_overlay.js | 30 + .../flavours/glitch/components/button.js | 64 + .../flavours/glitch/components/collapsable.js | 22 + .../flavours/glitch/components/column.js | 54 + .../glitch/components/column_back_button.js | 29 + .../glitch/components/column_back_button_slim.js | 31 + .../flavours/glitch/components/column_header.js | 213 + .../flavours/glitch/components/display_name.js | 20 + .../flavours/glitch/components/dropdown_menu.js | 211 + .../glitch/components/extended_video_player.js | 54 + .../flavours/glitch/components/icon_button.js | 137 + .../components/intersection_observer_article.js | 130 + .../flavours/glitch/components/load_more.js | 26 + .../glitch/components/loading_indicator.js | 11 + .../flavours/glitch/components/media_gallery.js | 255 ++ .../glitch/components/missing_indicator.js | 12 + .../components/notification_purge_buttons.js | 58 + .../flavours/glitch/components/permalink.js | 34 + .../glitch/components/relative_timestamp.js | 147 + .../flavours/glitch/components/scrollable_list.js | 198 + .../flavours/glitch/components/setting_text.js | 34 + .../flavours/glitch/components/status.js | 442 ++ .../glitch/components/status_action_bar.js | 185 + .../flavours/glitch/components/status_content.js | 245 + .../flavours/glitch/components/status_header.js | 120 + .../flavours/glitch/components/status_list.js | 72 + .../flavours/glitch/components/status_prepend.js | 83 + .../glitch/components/status_visibility_icon.js | 48 + .../glitch/containers/account_container.js | 72 + .../flavours/glitch/containers/card_container.js | 18 + .../glitch/containers/compose_container.js | 38 + .../glitch/containers/dropdown_menu_container.js | 16 + .../intersection_observer_article_container.js | 17 + .../flavours/glitch/containers/mastodon.js | 70 + .../glitch/containers/media_gallery_container.js | 34 + .../notification_purge_buttons_container.js | 49 + .../flavours/glitch/containers/status_container.js | 150 + .../glitch/containers/timeline_container.js | 48 + .../flavours/glitch/containers/video_container.js | 26 + .../features/account/components/action_bar.js | 145 + .../glitch/features/account/components/header.js | 99 + .../account_gallery/components/media_item.js | 39 + .../glitch/features/account_gallery/index.js | 111 + .../features/account_timeline/components/header.js | 95 + .../containers/header_container.js | 104 + .../glitch/features/account_timeline/index.js | 77 + .../flavours/glitch/features/blocks/index.js | 70 + .../components/column_settings.js | 35 + .../containers/column_settings_container.js | 17 + .../glitch/features/community_timeline/index.js | 107 + .../compose/components/advanced_options.js | 62 + .../compose/components/advanced_options_toggle.js | 35 + .../features/compose/components/attach_options.js | 131 + .../compose/components/autosuggest_account.js | 24 + .../compose/components/character_counter.js | 25 + .../features/compose/components/compose_form.js | 286 ++ .../glitch/features/compose/components/dropdown.js | 77 + .../compose/components/emoji_picker_dropdown.js | 376 ++ .../features/compose/components/navigation_bar.js | 38 + .../compose/components/privacy_dropdown.js | 200 + .../features/compose/components/reply_indicator.js | 63 + .../glitch/features/compose/components/search.js | 129 + .../features/compose/components/search_results.js | 65 + .../compose/components/text_icon_button.js | 29 + .../glitch/features/compose/components/upload.js | 96 + .../features/compose/components/upload_button.js | 77 + .../features/compose/components/upload_form.js | 29 + .../features/compose/components/upload_progress.js | 42 + .../glitch/features/compose/components/warning.js | 26 + .../containers/advanced_options_container.js | 20 + .../containers/autosuggest_account_container.js | 15 + .../compose/containers/compose_form_container.js | 71 + .../containers/emoji_picker_dropdown_container.js | 82 + .../compose/containers/navigation_container.js | 11 + .../containers/privacy_dropdown_container.js | 24 + .../containers/reply_indicator_container.js | 24 + .../compose/containers/search_container.js | 35 + .../compose/containers/search_results_container.js | 8 + .../containers/sensitive_button_container.js | 71 + .../compose/containers/spoiler_button_container.js | 25 + .../compose/containers/upload_button_container.js | 18 + .../compose/containers/upload_container.js | 21 + .../compose/containers/upload_form_container.js | 8 + .../containers/upload_progress_container.js | 9 + .../compose/containers/warning_container.js | 24 + .../flavours/glitch/features/compose/index.js | 126 + .../containers/column_settings_container.js | 17 + .../glitch/features/direct_timeline/index.js | 107 + .../glitch/features/favourited_statuses/index.js | 94 + .../flavours/glitch/features/favourites/index.js | 60 + .../components/account_authorize.js | 49 + .../containers/account_authorize_container.js | 26 + .../glitch/features/follow_requests/index.js | 71 + .../flavours/glitch/features/followers/index.js | 93 + .../flavours/glitch/features/following/index.js | 93 + .../glitch/features/generic_not_found/index.js | 11 + .../glitch/features/getting_started/index.js | 145 + .../glitch/features/hashtag_timeline/index.js | 118 + .../home_timeline/components/column_settings.js | 46 + .../containers/column_settings_container.js | 21 + .../glitch/features/home_timeline/index.js | 90 + .../glitch/features/local_settings/index.js | 68 + .../features/local_settings/navigation/index.js | 74 + .../local_settings/navigation/item/index.js | 69 + .../local_settings/navigation/item/style.scss | 27 + .../features/local_settings/navigation/style.scss | 10 + .../glitch/features/local_settings/page/index.js | 212 + .../features/local_settings/page/item/index.js | 90 + .../features/local_settings/page/item/style.scss | 7 + .../glitch/features/local_settings/page/style.scss | 9 + .../glitch/features/local_settings/style.scss | 34 + .../flavours/glitch/features/mutes/index.js | 70 + .../components/clear_column_button.js | 17 + .../notifications/components/column_settings.js | 86 + .../features/notifications/components/follow.js | 97 + .../notifications/components/notification.js | 88 + .../features/notifications/components/overlay.js | 57 + .../notifications/components/setting_toggle.js | 34 + .../containers/column_settings_container.js | 44 + .../containers/notification_container.js | 27 + .../notifications/containers/overlay_container.js | 18 + .../glitch/features/notifications/index.js | 193 + .../glitch/features/pinned_statuses/index.js | 59 + .../containers/column_settings_container.js | 17 + .../glitch/features/public_timeline/index.js | 107 + .../flavours/glitch/features/reblogs/index.js | 60 + .../features/report/components/status_check_box.js | 37 + .../containers/status_check_box_container.js | 19 + .../glitch/features/standalone/compose/index.js | 20 + .../features/standalone/hashtag_timeline/index.js | 70 + .../features/standalone/public_timeline/index.js | 76 + .../features/status/components/action_bar.js | 129 + .../glitch/features/status/components/card.js | 125 + .../features/status/components/detailed_status.js | 128 + .../features/status/containers/card_container.js | 8 + .../flavours/glitch/features/status/index.js | 343 ++ .../glitch/features/ui/components/actions_modal.js | 74 + .../glitch/features/ui/components/boost_modal.js | 84 + .../glitch/features/ui/components/bundle.js | 102 + .../features/ui/components/bundle_column_error.js | 44 + .../features/ui/components/bundle_modal_error.js | 53 + .../glitch/features/ui/components/column.js | 74 + .../glitch/features/ui/components/column_header.js | 35 + .../glitch/features/ui/components/column_link.js | 39 + .../features/ui/components/column_loading.js | 30 + .../features/ui/components/column_subheading.js | 16 + .../glitch/features/ui/components/columns_area.js | 174 + .../features/ui/components/confirmation_modal.js | 53 + .../glitch/features/ui/components/doodle_modal.js | 614 +++ .../features/ui/components/drawer_loading.js | 11 + .../glitch/features/ui/components/embed_modal.js | 84 + .../glitch/features/ui/components/image_loader.js | 152 + .../glitch/features/ui/components/media_modal.js | 126 + .../glitch/features/ui/components/modal_loading.js | 20 + .../glitch/features/ui/components/modal_root.js | 131 + .../glitch/features/ui/components/mute_modal.js | 105 + .../features/ui/components/onboarding_modal.js | 323 ++ .../glitch/features/ui/components/report_modal.js | 105 + .../glitch/features/ui/components/tabs_bar.js | 84 + .../glitch/features/ui/components/upload_area.js | 52 + .../glitch/features/ui/components/video_modal.js | 33 + .../features/ui/containers/bundle_container.js | 19 + .../ui/containers/columns_area_container.js | 8 + .../ui/containers/loading_bar_container.js | 8 + .../features/ui/containers/modal_container.js | 16 + .../ui/containers/notifications_container.js | 18 + .../ui/containers/status_list_container.js | 73 + .../flavours/glitch/features/ui/index.js | 442 ++ .../flavours/glitch/features/video/index.js | 288 ++ .../flavours/glitch/middleware/errors.js | 31 + .../flavours/glitch/middleware/loading_bar.js | 25 + .../flavours/glitch/middleware/sounds.js | 46 + app/javascript/flavours/glitch/packs/about.js | 22 + app/javascript/flavours/glitch/packs/common.js | 1 + app/javascript/flavours/glitch/packs/home.js | 7 + app/javascript/flavours/glitch/packs/public.js | 75 + app/javascript/flavours/glitch/packs/share.js | 22 + .../flavours/glitch/reducers/accounts.js | 135 + .../flavours/glitch/reducers/accounts_counters.js | 138 + app/javascript/flavours/glitch/reducers/alerts.js | 25 + app/javascript/flavours/glitch/reducers/cards.js | 14 + app/javascript/flavours/glitch/reducers/compose.js | 307 ++ .../flavours/glitch/reducers/contexts.js | 61 + .../flavours/glitch/reducers/custom_emojis.js | 16 + .../flavours/glitch/reducers/height_cache.js | 23 + app/javascript/flavours/glitch/reducers/index.js | 54 + .../flavours/glitch/reducers/local_settings.js | 45 + .../flavours/glitch/reducers/media_attachments.js | 15 + app/javascript/flavours/glitch/reducers/meta.js | 16 + app/javascript/flavours/glitch/reducers/modal.js | 17 + app/javascript/flavours/glitch/reducers/mutes.js | 29 + .../flavours/glitch/reducers/notifications.js | 191 + .../flavours/glitch/reducers/push_notifications.js | 51 + .../flavours/glitch/reducers/relationships.js | 46 + app/javascript/flavours/glitch/reducers/reports.js | 60 + app/javascript/flavours/glitch/reducers/search.js | 42 + .../flavours/glitch/reducers/settings.js | 119 + .../flavours/glitch/reducers/status_lists.js | 75 + .../flavours/glitch/reducers/statuses.js | 148 + .../flavours/glitch/reducers/timelines.js | 149 + .../flavours/glitch/reducers/user_lists.js | 80 + app/javascript/flavours/glitch/selectors/index.js | 87 + .../flavours/glitch/service_worker/entry.js | 10 + .../service_worker/web_push_notifications.js | 159 + .../flavours/glitch/store/configureStore.js | 15 + app/javascript/flavours/glitch/styles/_mixins.scss | 51 + app/javascript/flavours/glitch/styles/about.scss | 822 ++++ .../flavours/glitch/styles/accounts.scss | 589 +++ app/javascript/flavours/glitch/styles/admin.scss | 349 ++ app/javascript/flavours/glitch/styles/basics.scss | 122 + app/javascript/flavours/glitch/styles/boost.scss | 28 + .../flavours/glitch/styles/compact_header.scss | 34 + .../flavours/glitch/styles/components.scss | 4832 ++++++++++++++++++++ .../flavours/glitch/styles/containers.scss | 116 + app/javascript/flavours/glitch/styles/doodle.scss | 86 + .../flavours/glitch/styles/emoji_picker.scss | 199 + app/javascript/flavours/glitch/styles/footer.scss | 30 + app/javascript/flavours/glitch/styles/forms.scss | 540 +++ app/javascript/flavours/glitch/styles/index.scss | 22 + .../flavours/glitch/styles/landing_strip.scss | 36 + app/javascript/flavours/glitch/styles/lists.scss | 19 + .../flavours/glitch/styles/reset copy.scss | 91 + app/javascript/flavours/glitch/styles/reset.scss | 91 + app/javascript/flavours/glitch/styles/rtl.scss | 254 + .../flavours/glitch/styles/stream_entries.scss | 335 ++ app/javascript/flavours/glitch/styles/tables.scss | 76 + .../flavours/glitch/styles/variables.scss | 35 + app/javascript/flavours/glitch/theme.yml | 34 + app/javascript/flavours/glitch/util/api.js | 26 + .../flavours/glitch/util/async-components.js | 115 + .../flavours/glitch/util/base_polyfills.js | 18 + .../flavours/glitch/util/bio_metadata.js | 331 ++ app/javascript/flavours/glitch/util/counter.js | 9 + .../flavours/glitch/util/emoji/emoji_compressed.js | 93 + .../flavours/glitch/util/emoji/emoji_map.json | 1 + .../glitch/util/emoji/emoji_mart_data_light.js | 41 + .../glitch/util/emoji/emoji_mart_search_light.js | 157 + .../flavours/glitch/util/emoji/emoji_picker.js | 7 + .../util/emoji/emoji_unicode_mapping_light.js | 35 + .../flavours/glitch/util/emoji/emoji_utils.js | 258 ++ app/javascript/flavours/glitch/util/emoji/index.js | 95 + .../glitch/util/emoji/unicode_to_filename.js | 26 + .../glitch/util/emoji/unicode_to_unified_name.js | 17 + .../flavours/glitch/util/extra_polyfills.js | 5 + app/javascript/flavours/glitch/util/fullscreen.js | 46 + .../flavours/glitch/util/get_rect_from_entry.js | 21 + .../flavours/glitch/util/initial_state.js | 21 + .../glitch/util/intersection_observer_wrapper.js | 57 + app/javascript/flavours/glitch/util/is_mobile.js | 34 + app/javascript/flavours/glitch/util/link_header.js | 33 + .../flavours/glitch/util/load_polyfills.js | 39 + app/javascript/flavours/glitch/util/main.js | 39 + .../flavours/glitch/util/optional_motion.js | 5 + app/javascript/flavours/glitch/util/performance.js | 31 + .../flavours/glitch/util/react_router_helpers.js | 64 + app/javascript/flavours/glitch/util/ready.js | 7 + .../flavours/glitch/util/reduced_motion.js | 44 + app/javascript/flavours/glitch/util/rtl.js | 31 + .../flavours/glitch/util/schedule_idle_task.js | 29 + app/javascript/flavours/glitch/util/scroll.js | 30 + app/javascript/flavours/glitch/util/stream.js | 73 + app/javascript/flavours/glitch/util/url_regex.js | 196 + app/javascript/flavours/glitch/util/uuid.js | 3 + .../flavours/glitch/util/web_push_subscription.js | 105 + app/javascript/flavours/vanilla/theme.yml | 33 + 296 files changed, 30691 insertions(+) create mode 100644 app/javascript/flavours/glitch/actions/accounts.js create mode 100644 app/javascript/flavours/glitch/actions/alerts.js create mode 100644 app/javascript/flavours/glitch/actions/blocks.js create mode 100644 app/javascript/flavours/glitch/actions/bundles.js create mode 100644 app/javascript/flavours/glitch/actions/cards.js create mode 100644 app/javascript/flavours/glitch/actions/columns.js create mode 100644 app/javascript/flavours/glitch/actions/compose.js create mode 100644 app/javascript/flavours/glitch/actions/domain_blocks.js create mode 100644 app/javascript/flavours/glitch/actions/emojis.js create mode 100644 app/javascript/flavours/glitch/actions/favourites.js create mode 100644 app/javascript/flavours/glitch/actions/height_cache.js create mode 100644 app/javascript/flavours/glitch/actions/interactions.js create mode 100644 app/javascript/flavours/glitch/actions/local_settings.js create mode 100644 app/javascript/flavours/glitch/actions/modal.js create mode 100644 app/javascript/flavours/glitch/actions/mutes.js create mode 100644 app/javascript/flavours/glitch/actions/notifications.js create mode 100644 app/javascript/flavours/glitch/actions/onboarding.js create mode 100644 app/javascript/flavours/glitch/actions/pin_statuses.js create mode 100644 app/javascript/flavours/glitch/actions/push_notifications.js create mode 100644 app/javascript/flavours/glitch/actions/reports.js create mode 100644 app/javascript/flavours/glitch/actions/search.js create mode 100644 app/javascript/flavours/glitch/actions/settings.js create mode 100644 app/javascript/flavours/glitch/actions/statuses.js create mode 100644 app/javascript/flavours/glitch/actions/store.js create mode 100644 app/javascript/flavours/glitch/actions/streaming.js create mode 100644 app/javascript/flavours/glitch/actions/timelines.js create mode 100644 app/javascript/flavours/glitch/components/account.js create mode 100644 app/javascript/flavours/glitch/components/attachment_list.js create mode 100644 app/javascript/flavours/glitch/components/autosuggest_emoji.js create mode 100644 app/javascript/flavours/glitch/components/autosuggest_textarea.js create mode 100644 app/javascript/flavours/glitch/components/avatar.js create mode 100644 app/javascript/flavours/glitch/components/avatar_overlay.js create mode 100644 app/javascript/flavours/glitch/components/button.js create mode 100644 app/javascript/flavours/glitch/components/collapsable.js create mode 100644 app/javascript/flavours/glitch/components/column.js create mode 100644 app/javascript/flavours/glitch/components/column_back_button.js create mode 100644 app/javascript/flavours/glitch/components/column_back_button_slim.js create mode 100644 app/javascript/flavours/glitch/components/column_header.js create mode 100644 app/javascript/flavours/glitch/components/display_name.js create mode 100644 app/javascript/flavours/glitch/components/dropdown_menu.js create mode 100644 app/javascript/flavours/glitch/components/extended_video_player.js create mode 100644 app/javascript/flavours/glitch/components/icon_button.js create mode 100644 app/javascript/flavours/glitch/components/intersection_observer_article.js create mode 100644 app/javascript/flavours/glitch/components/load_more.js create mode 100644 app/javascript/flavours/glitch/components/loading_indicator.js create mode 100644 app/javascript/flavours/glitch/components/media_gallery.js create mode 100644 app/javascript/flavours/glitch/components/missing_indicator.js create mode 100644 app/javascript/flavours/glitch/components/notification_purge_buttons.js create mode 100644 app/javascript/flavours/glitch/components/permalink.js create mode 100644 app/javascript/flavours/glitch/components/relative_timestamp.js create mode 100644 app/javascript/flavours/glitch/components/scrollable_list.js create mode 100644 app/javascript/flavours/glitch/components/setting_text.js create mode 100644 app/javascript/flavours/glitch/components/status.js create mode 100644 app/javascript/flavours/glitch/components/status_action_bar.js create mode 100644 app/javascript/flavours/glitch/components/status_content.js create mode 100644 app/javascript/flavours/glitch/components/status_header.js create mode 100644 app/javascript/flavours/glitch/components/status_list.js create mode 100644 app/javascript/flavours/glitch/components/status_prepend.js create mode 100644 app/javascript/flavours/glitch/components/status_visibility_icon.js create mode 100644 app/javascript/flavours/glitch/containers/account_container.js create mode 100644 app/javascript/flavours/glitch/containers/card_container.js create mode 100644 app/javascript/flavours/glitch/containers/compose_container.js create mode 100644 app/javascript/flavours/glitch/containers/dropdown_menu_container.js create mode 100644 app/javascript/flavours/glitch/containers/intersection_observer_article_container.js create mode 100644 app/javascript/flavours/glitch/containers/mastodon.js create mode 100644 app/javascript/flavours/glitch/containers/media_gallery_container.js create mode 100644 app/javascript/flavours/glitch/containers/notification_purge_buttons_container.js create mode 100644 app/javascript/flavours/glitch/containers/status_container.js create mode 100644 app/javascript/flavours/glitch/containers/timeline_container.js create mode 100644 app/javascript/flavours/glitch/containers/video_container.js create mode 100644 app/javascript/flavours/glitch/features/account/components/action_bar.js create mode 100644 app/javascript/flavours/glitch/features/account/components/header.js create mode 100644 app/javascript/flavours/glitch/features/account_gallery/components/media_item.js create mode 100644 app/javascript/flavours/glitch/features/account_gallery/index.js create mode 100644 app/javascript/flavours/glitch/features/account_timeline/components/header.js create mode 100644 app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js create mode 100644 app/javascript/flavours/glitch/features/account_timeline/index.js create mode 100644 app/javascript/flavours/glitch/features/blocks/index.js create mode 100644 app/javascript/flavours/glitch/features/community_timeline/components/column_settings.js create mode 100644 app/javascript/flavours/glitch/features/community_timeline/containers/column_settings_container.js create mode 100644 app/javascript/flavours/glitch/features/community_timeline/index.js create mode 100644 app/javascript/flavours/glitch/features/compose/components/advanced_options.js create mode 100644 app/javascript/flavours/glitch/features/compose/components/advanced_options_toggle.js create mode 100644 app/javascript/flavours/glitch/features/compose/components/attach_options.js create mode 100644 app/javascript/flavours/glitch/features/compose/components/autosuggest_account.js create mode 100644 app/javascript/flavours/glitch/features/compose/components/character_counter.js create mode 100644 app/javascript/flavours/glitch/features/compose/components/compose_form.js create mode 100644 app/javascript/flavours/glitch/features/compose/components/dropdown.js create mode 100644 app/javascript/flavours/glitch/features/compose/components/emoji_picker_dropdown.js create mode 100644 app/javascript/flavours/glitch/features/compose/components/navigation_bar.js create mode 100644 app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js create mode 100644 app/javascript/flavours/glitch/features/compose/components/reply_indicator.js create mode 100644 app/javascript/flavours/glitch/features/compose/components/search.js create mode 100644 app/javascript/flavours/glitch/features/compose/components/search_results.js create mode 100644 app/javascript/flavours/glitch/features/compose/components/text_icon_button.js create mode 100644 app/javascript/flavours/glitch/features/compose/components/upload.js create mode 100644 app/javascript/flavours/glitch/features/compose/components/upload_button.js create mode 100644 app/javascript/flavours/glitch/features/compose/components/upload_form.js create mode 100644 app/javascript/flavours/glitch/features/compose/components/upload_progress.js create mode 100644 app/javascript/flavours/glitch/features/compose/components/warning.js create mode 100644 app/javascript/flavours/glitch/features/compose/containers/advanced_options_container.js create mode 100644 app/javascript/flavours/glitch/features/compose/containers/autosuggest_account_container.js create mode 100644 app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js create mode 100644 app/javascript/flavours/glitch/features/compose/containers/emoji_picker_dropdown_container.js create mode 100644 app/javascript/flavours/glitch/features/compose/containers/navigation_container.js create mode 100644 app/javascript/flavours/glitch/features/compose/containers/privacy_dropdown_container.js create mode 100644 app/javascript/flavours/glitch/features/compose/containers/reply_indicator_container.js create mode 100644 app/javascript/flavours/glitch/features/compose/containers/search_container.js create mode 100644 app/javascript/flavours/glitch/features/compose/containers/search_results_container.js create mode 100644 app/javascript/flavours/glitch/features/compose/containers/sensitive_button_container.js create mode 100644 app/javascript/flavours/glitch/features/compose/containers/spoiler_button_container.js create mode 100644 app/javascript/flavours/glitch/features/compose/containers/upload_button_container.js create mode 100644 app/javascript/flavours/glitch/features/compose/containers/upload_container.js create mode 100644 app/javascript/flavours/glitch/features/compose/containers/upload_form_container.js create mode 100644 app/javascript/flavours/glitch/features/compose/containers/upload_progress_container.js create mode 100644 app/javascript/flavours/glitch/features/compose/containers/warning_container.js create mode 100644 app/javascript/flavours/glitch/features/compose/index.js create mode 100644 app/javascript/flavours/glitch/features/direct_timeline/containers/column_settings_container.js create mode 100644 app/javascript/flavours/glitch/features/direct_timeline/index.js create mode 100644 app/javascript/flavours/glitch/features/favourited_statuses/index.js create mode 100644 app/javascript/flavours/glitch/features/favourites/index.js create mode 100644 app/javascript/flavours/glitch/features/follow_requests/components/account_authorize.js create mode 100644 app/javascript/flavours/glitch/features/follow_requests/containers/account_authorize_container.js create mode 100644 app/javascript/flavours/glitch/features/follow_requests/index.js create mode 100644 app/javascript/flavours/glitch/features/followers/index.js create mode 100644 app/javascript/flavours/glitch/features/following/index.js create mode 100644 app/javascript/flavours/glitch/features/generic_not_found/index.js create mode 100644 app/javascript/flavours/glitch/features/getting_started/index.js create mode 100644 app/javascript/flavours/glitch/features/hashtag_timeline/index.js create mode 100644 app/javascript/flavours/glitch/features/home_timeline/components/column_settings.js create mode 100644 app/javascript/flavours/glitch/features/home_timeline/containers/column_settings_container.js create mode 100644 app/javascript/flavours/glitch/features/home_timeline/index.js create mode 100644 app/javascript/flavours/glitch/features/local_settings/index.js create mode 100644 app/javascript/flavours/glitch/features/local_settings/navigation/index.js create mode 100644 app/javascript/flavours/glitch/features/local_settings/navigation/item/index.js create mode 100644 app/javascript/flavours/glitch/features/local_settings/navigation/item/style.scss create mode 100644 app/javascript/flavours/glitch/features/local_settings/navigation/style.scss create mode 100644 app/javascript/flavours/glitch/features/local_settings/page/index.js create mode 100644 app/javascript/flavours/glitch/features/local_settings/page/item/index.js create mode 100644 app/javascript/flavours/glitch/features/local_settings/page/item/style.scss create mode 100644 app/javascript/flavours/glitch/features/local_settings/page/style.scss create mode 100644 app/javascript/flavours/glitch/features/local_settings/style.scss create mode 100644 app/javascript/flavours/glitch/features/mutes/index.js create mode 100644 app/javascript/flavours/glitch/features/notifications/components/clear_column_button.js create mode 100644 app/javascript/flavours/glitch/features/notifications/components/column_settings.js create mode 100644 app/javascript/flavours/glitch/features/notifications/components/follow.js create mode 100644 app/javascript/flavours/glitch/features/notifications/components/notification.js create mode 100644 app/javascript/flavours/glitch/features/notifications/components/overlay.js create mode 100644 app/javascript/flavours/glitch/features/notifications/components/setting_toggle.js create mode 100644 app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js create mode 100644 app/javascript/flavours/glitch/features/notifications/containers/notification_container.js create mode 100644 app/javascript/flavours/glitch/features/notifications/containers/overlay_container.js create mode 100644 app/javascript/flavours/glitch/features/notifications/index.js create mode 100644 app/javascript/flavours/glitch/features/pinned_statuses/index.js create mode 100644 app/javascript/flavours/glitch/features/public_timeline/containers/column_settings_container.js create mode 100644 app/javascript/flavours/glitch/features/public_timeline/index.js create mode 100644 app/javascript/flavours/glitch/features/reblogs/index.js create mode 100644 app/javascript/flavours/glitch/features/report/components/status_check_box.js create mode 100644 app/javascript/flavours/glitch/features/report/containers/status_check_box_container.js create mode 100644 app/javascript/flavours/glitch/features/standalone/compose/index.js create mode 100644 app/javascript/flavours/glitch/features/standalone/hashtag_timeline/index.js create mode 100644 app/javascript/flavours/glitch/features/standalone/public_timeline/index.js create mode 100644 app/javascript/flavours/glitch/features/status/components/action_bar.js create mode 100644 app/javascript/flavours/glitch/features/status/components/card.js create mode 100644 app/javascript/flavours/glitch/features/status/components/detailed_status.js create mode 100644 app/javascript/flavours/glitch/features/status/containers/card_container.js create mode 100644 app/javascript/flavours/glitch/features/status/index.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/actions_modal.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/boost_modal.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/bundle.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/bundle_column_error.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/bundle_modal_error.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/column.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/column_header.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/column_link.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/column_loading.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/column_subheading.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/columns_area.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/confirmation_modal.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/doodle_modal.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/drawer_loading.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/embed_modal.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/image_loader.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/media_modal.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/modal_loading.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/modal_root.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/mute_modal.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/report_modal.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/tabs_bar.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/upload_area.js create mode 100644 app/javascript/flavours/glitch/features/ui/components/video_modal.js create mode 100644 app/javascript/flavours/glitch/features/ui/containers/bundle_container.js create mode 100644 app/javascript/flavours/glitch/features/ui/containers/columns_area_container.js create mode 100644 app/javascript/flavours/glitch/features/ui/containers/loading_bar_container.js create mode 100644 app/javascript/flavours/glitch/features/ui/containers/modal_container.js create mode 100644 app/javascript/flavours/glitch/features/ui/containers/notifications_container.js create mode 100644 app/javascript/flavours/glitch/features/ui/containers/status_list_container.js create mode 100644 app/javascript/flavours/glitch/features/ui/index.js create mode 100644 app/javascript/flavours/glitch/features/video/index.js create mode 100644 app/javascript/flavours/glitch/middleware/errors.js create mode 100644 app/javascript/flavours/glitch/middleware/loading_bar.js create mode 100644 app/javascript/flavours/glitch/middleware/sounds.js create mode 100644 app/javascript/flavours/glitch/packs/about.js create mode 100644 app/javascript/flavours/glitch/packs/common.js create mode 100644 app/javascript/flavours/glitch/packs/home.js create mode 100644 app/javascript/flavours/glitch/packs/public.js create mode 100644 app/javascript/flavours/glitch/packs/share.js create mode 100644 app/javascript/flavours/glitch/reducers/accounts.js create mode 100644 app/javascript/flavours/glitch/reducers/accounts_counters.js create mode 100644 app/javascript/flavours/glitch/reducers/alerts.js create mode 100644 app/javascript/flavours/glitch/reducers/cards.js create mode 100644 app/javascript/flavours/glitch/reducers/compose.js create mode 100644 app/javascript/flavours/glitch/reducers/contexts.js create mode 100644 app/javascript/flavours/glitch/reducers/custom_emojis.js create mode 100644 app/javascript/flavours/glitch/reducers/height_cache.js create mode 100644 app/javascript/flavours/glitch/reducers/index.js create mode 100644 app/javascript/flavours/glitch/reducers/local_settings.js create mode 100644 app/javascript/flavours/glitch/reducers/media_attachments.js create mode 100644 app/javascript/flavours/glitch/reducers/meta.js create mode 100644 app/javascript/flavours/glitch/reducers/modal.js create mode 100644 app/javascript/flavours/glitch/reducers/mutes.js create mode 100644 app/javascript/flavours/glitch/reducers/notifications.js create mode 100644 app/javascript/flavours/glitch/reducers/push_notifications.js create mode 100644 app/javascript/flavours/glitch/reducers/relationships.js create mode 100644 app/javascript/flavours/glitch/reducers/reports.js create mode 100644 app/javascript/flavours/glitch/reducers/search.js create mode 100644 app/javascript/flavours/glitch/reducers/settings.js create mode 100644 app/javascript/flavours/glitch/reducers/status_lists.js create mode 100644 app/javascript/flavours/glitch/reducers/statuses.js create mode 100644 app/javascript/flavours/glitch/reducers/timelines.js create mode 100644 app/javascript/flavours/glitch/reducers/user_lists.js create mode 100644 app/javascript/flavours/glitch/selectors/index.js create mode 100644 app/javascript/flavours/glitch/service_worker/entry.js create mode 100644 app/javascript/flavours/glitch/service_worker/web_push_notifications.js create mode 100644 app/javascript/flavours/glitch/store/configureStore.js create mode 100644 app/javascript/flavours/glitch/styles/_mixins.scss create mode 100644 app/javascript/flavours/glitch/styles/about.scss create mode 100644 app/javascript/flavours/glitch/styles/accounts.scss create mode 100644 app/javascript/flavours/glitch/styles/admin.scss create mode 100644 app/javascript/flavours/glitch/styles/basics.scss create mode 100644 app/javascript/flavours/glitch/styles/boost.scss create mode 100644 app/javascript/flavours/glitch/styles/compact_header.scss create mode 100644 app/javascript/flavours/glitch/styles/components.scss create mode 100644 app/javascript/flavours/glitch/styles/containers.scss create mode 100644 app/javascript/flavours/glitch/styles/doodle.scss create mode 100644 app/javascript/flavours/glitch/styles/emoji_picker.scss create mode 100644 app/javascript/flavours/glitch/styles/footer.scss create mode 100644 app/javascript/flavours/glitch/styles/forms.scss create mode 100644 app/javascript/flavours/glitch/styles/index.scss create mode 100644 app/javascript/flavours/glitch/styles/landing_strip.scss create mode 100644 app/javascript/flavours/glitch/styles/lists.scss create mode 100644 app/javascript/flavours/glitch/styles/reset copy.scss create mode 100644 app/javascript/flavours/glitch/styles/reset.scss create mode 100644 app/javascript/flavours/glitch/styles/rtl.scss create mode 100644 app/javascript/flavours/glitch/styles/stream_entries.scss create mode 100644 app/javascript/flavours/glitch/styles/tables.scss create mode 100644 app/javascript/flavours/glitch/styles/variables.scss create mode 100644 app/javascript/flavours/glitch/theme.yml create mode 100644 app/javascript/flavours/glitch/util/api.js create mode 100644 app/javascript/flavours/glitch/util/async-components.js create mode 100644 app/javascript/flavours/glitch/util/base_polyfills.js create mode 100644 app/javascript/flavours/glitch/util/bio_metadata.js create mode 100644 app/javascript/flavours/glitch/util/counter.js create mode 100644 app/javascript/flavours/glitch/util/emoji/emoji_compressed.js create mode 100644 app/javascript/flavours/glitch/util/emoji/emoji_map.json create mode 100644 app/javascript/flavours/glitch/util/emoji/emoji_mart_data_light.js create mode 100644 app/javascript/flavours/glitch/util/emoji/emoji_mart_search_light.js create mode 100644 app/javascript/flavours/glitch/util/emoji/emoji_picker.js create mode 100644 app/javascript/flavours/glitch/util/emoji/emoji_unicode_mapping_light.js create mode 100644 app/javascript/flavours/glitch/util/emoji/emoji_utils.js create mode 100644 app/javascript/flavours/glitch/util/emoji/index.js create mode 100644 app/javascript/flavours/glitch/util/emoji/unicode_to_filename.js create mode 100644 app/javascript/flavours/glitch/util/emoji/unicode_to_unified_name.js create mode 100644 app/javascript/flavours/glitch/util/extra_polyfills.js create mode 100644 app/javascript/flavours/glitch/util/fullscreen.js create mode 100644 app/javascript/flavours/glitch/util/get_rect_from_entry.js create mode 100644 app/javascript/flavours/glitch/util/initial_state.js create mode 100644 app/javascript/flavours/glitch/util/intersection_observer_wrapper.js create mode 100644 app/javascript/flavours/glitch/util/is_mobile.js create mode 100644 app/javascript/flavours/glitch/util/link_header.js create mode 100644 app/javascript/flavours/glitch/util/load_polyfills.js create mode 100644 app/javascript/flavours/glitch/util/main.js create mode 100644 app/javascript/flavours/glitch/util/optional_motion.js create mode 100644 app/javascript/flavours/glitch/util/performance.js create mode 100644 app/javascript/flavours/glitch/util/react_router_helpers.js create mode 100644 app/javascript/flavours/glitch/util/ready.js create mode 100644 app/javascript/flavours/glitch/util/reduced_motion.js create mode 100644 app/javascript/flavours/glitch/util/rtl.js create mode 100644 app/javascript/flavours/glitch/util/schedule_idle_task.js create mode 100644 app/javascript/flavours/glitch/util/scroll.js create mode 100644 app/javascript/flavours/glitch/util/stream.js create mode 100644 app/javascript/flavours/glitch/util/url_regex.js create mode 100644 app/javascript/flavours/glitch/util/uuid.js create mode 100644 app/javascript/flavours/glitch/util/web_push_subscription.js create mode 100644 app/javascript/flavours/vanilla/theme.yml (limited to 'app/javascript/flavours') diff --git a/app/javascript/flavours/glitch/actions/accounts.js b/app/javascript/flavours/glitch/actions/accounts.js new file mode 100644 index 000000000..8ab92f9e7 --- /dev/null +++ b/app/javascript/flavours/glitch/actions/accounts.js @@ -0,0 +1,661 @@ +import api, { getLinks } from 'flavours/glitch/util/api'; + +export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST'; +export const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS'; +export const ACCOUNT_FETCH_FAIL = 'ACCOUNT_FETCH_FAIL'; + +export const ACCOUNT_FOLLOW_REQUEST = 'ACCOUNT_FOLLOW_REQUEST'; +export const ACCOUNT_FOLLOW_SUCCESS = 'ACCOUNT_FOLLOW_SUCCESS'; +export const ACCOUNT_FOLLOW_FAIL = 'ACCOUNT_FOLLOW_FAIL'; + +export const ACCOUNT_UNFOLLOW_REQUEST = 'ACCOUNT_UNFOLLOW_REQUEST'; +export const ACCOUNT_UNFOLLOW_SUCCESS = 'ACCOUNT_UNFOLLOW_SUCCESS'; +export const ACCOUNT_UNFOLLOW_FAIL = 'ACCOUNT_UNFOLLOW_FAIL'; + +export const ACCOUNT_BLOCK_REQUEST = 'ACCOUNT_BLOCK_REQUEST'; +export const ACCOUNT_BLOCK_SUCCESS = 'ACCOUNT_BLOCK_SUCCESS'; +export const ACCOUNT_BLOCK_FAIL = 'ACCOUNT_BLOCK_FAIL'; + +export const ACCOUNT_UNBLOCK_REQUEST = 'ACCOUNT_UNBLOCK_REQUEST'; +export const ACCOUNT_UNBLOCK_SUCCESS = 'ACCOUNT_UNBLOCK_SUCCESS'; +export const ACCOUNT_UNBLOCK_FAIL = 'ACCOUNT_UNBLOCK_FAIL'; + +export const ACCOUNT_MUTE_REQUEST = 'ACCOUNT_MUTE_REQUEST'; +export const ACCOUNT_MUTE_SUCCESS = 'ACCOUNT_MUTE_SUCCESS'; +export const ACCOUNT_MUTE_FAIL = 'ACCOUNT_MUTE_FAIL'; + +export const ACCOUNT_UNMUTE_REQUEST = 'ACCOUNT_UNMUTE_REQUEST'; +export const ACCOUNT_UNMUTE_SUCCESS = 'ACCOUNT_UNMUTE_SUCCESS'; +export const ACCOUNT_UNMUTE_FAIL = 'ACCOUNT_UNMUTE_FAIL'; + +export const FOLLOWERS_FETCH_REQUEST = 'FOLLOWERS_FETCH_REQUEST'; +export const FOLLOWERS_FETCH_SUCCESS = 'FOLLOWERS_FETCH_SUCCESS'; +export const FOLLOWERS_FETCH_FAIL = 'FOLLOWERS_FETCH_FAIL'; + +export const FOLLOWERS_EXPAND_REQUEST = 'FOLLOWERS_EXPAND_REQUEST'; +export const FOLLOWERS_EXPAND_SUCCESS = 'FOLLOWERS_EXPAND_SUCCESS'; +export const FOLLOWERS_EXPAND_FAIL = 'FOLLOWERS_EXPAND_FAIL'; + +export const FOLLOWING_FETCH_REQUEST = 'FOLLOWING_FETCH_REQUEST'; +export const FOLLOWING_FETCH_SUCCESS = 'FOLLOWING_FETCH_SUCCESS'; +export const FOLLOWING_FETCH_FAIL = 'FOLLOWING_FETCH_FAIL'; + +export const FOLLOWING_EXPAND_REQUEST = 'FOLLOWING_EXPAND_REQUEST'; +export const FOLLOWING_EXPAND_SUCCESS = 'FOLLOWING_EXPAND_SUCCESS'; +export const FOLLOWING_EXPAND_FAIL = 'FOLLOWING_EXPAND_FAIL'; + +export const RELATIONSHIPS_FETCH_REQUEST = 'RELATIONSHIPS_FETCH_REQUEST'; +export const RELATIONSHIPS_FETCH_SUCCESS = 'RELATIONSHIPS_FETCH_SUCCESS'; +export const RELATIONSHIPS_FETCH_FAIL = 'RELATIONSHIPS_FETCH_FAIL'; + +export const FOLLOW_REQUESTS_FETCH_REQUEST = 'FOLLOW_REQUESTS_FETCH_REQUEST'; +export const FOLLOW_REQUESTS_FETCH_SUCCESS = 'FOLLOW_REQUESTS_FETCH_SUCCESS'; +export const FOLLOW_REQUESTS_FETCH_FAIL = 'FOLLOW_REQUESTS_FETCH_FAIL'; + +export const FOLLOW_REQUESTS_EXPAND_REQUEST = 'FOLLOW_REQUESTS_EXPAND_REQUEST'; +export const FOLLOW_REQUESTS_EXPAND_SUCCESS = 'FOLLOW_REQUESTS_EXPAND_SUCCESS'; +export const FOLLOW_REQUESTS_EXPAND_FAIL = 'FOLLOW_REQUESTS_EXPAND_FAIL'; + +export const FOLLOW_REQUEST_AUTHORIZE_REQUEST = 'FOLLOW_REQUEST_AUTHORIZE_REQUEST'; +export const FOLLOW_REQUEST_AUTHORIZE_SUCCESS = 'FOLLOW_REQUEST_AUTHORIZE_SUCCESS'; +export const FOLLOW_REQUEST_AUTHORIZE_FAIL = 'FOLLOW_REQUEST_AUTHORIZE_FAIL'; + +export const FOLLOW_REQUEST_REJECT_REQUEST = 'FOLLOW_REQUEST_REJECT_REQUEST'; +export const FOLLOW_REQUEST_REJECT_SUCCESS = 'FOLLOW_REQUEST_REJECT_SUCCESS'; +export const FOLLOW_REQUEST_REJECT_FAIL = 'FOLLOW_REQUEST_REJECT_FAIL'; + +export function fetchAccount(id) { + return (dispatch, getState) => { + dispatch(fetchRelationships([id])); + + if (getState().getIn(['accounts', id], null) !== null) { + return; + } + + dispatch(fetchAccountRequest(id)); + + api(getState).get(`/api/v1/accounts/${id}`).then(response => { + dispatch(fetchAccountSuccess(response.data)); + }).catch(error => { + dispatch(fetchAccountFail(id, error)); + }); + }; +}; + +export function fetchAccountRequest(id) { + return { + type: ACCOUNT_FETCH_REQUEST, + id, + }; +}; + +export function fetchAccountSuccess(account) { + return { + type: ACCOUNT_FETCH_SUCCESS, + account, + }; +}; + +export function fetchAccountFail(id, error) { + return { + type: ACCOUNT_FETCH_FAIL, + id, + error, + skipAlert: true, + }; +}; + +export function followAccount(id, reblogs = true) { + return (dispatch, getState) => { + const alreadyFollowing = getState().getIn(['relationships', id, 'following']); + dispatch(followAccountRequest(id)); + + api(getState).post(`/api/v1/accounts/${id}/follow`, { reblogs }).then(response => { + dispatch(followAccountSuccess(response.data, alreadyFollowing)); + }).catch(error => { + dispatch(followAccountFail(error)); + }); + }; +}; + +export function unfollowAccount(id) { + return (dispatch, getState) => { + dispatch(unfollowAccountRequest(id)); + + api(getState).post(`/api/v1/accounts/${id}/unfollow`).then(response => { + dispatch(unfollowAccountSuccess(response.data, getState().get('statuses'))); + }).catch(error => { + dispatch(unfollowAccountFail(error)); + }); + }; +}; + +export function followAccountRequest(id) { + return { + type: ACCOUNT_FOLLOW_REQUEST, + id, + }; +}; + +export function followAccountSuccess(relationship, alreadyFollowing) { + return { + type: ACCOUNT_FOLLOW_SUCCESS, + relationship, + alreadyFollowing, + }; +}; + +export function followAccountFail(error) { + return { + type: ACCOUNT_FOLLOW_FAIL, + error, + }; +}; + +export function unfollowAccountRequest(id) { + return { + type: ACCOUNT_UNFOLLOW_REQUEST, + id, + }; +}; + +export function unfollowAccountSuccess(relationship, statuses) { + return { + type: ACCOUNT_UNFOLLOW_SUCCESS, + relationship, + statuses, + }; +}; + +export function unfollowAccountFail(error) { + return { + type: ACCOUNT_UNFOLLOW_FAIL, + error, + }; +}; + +export function blockAccount(id) { + return (dispatch, getState) => { + dispatch(blockAccountRequest(id)); + + api(getState).post(`/api/v1/accounts/${id}/block`).then(response => { + // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers + dispatch(blockAccountSuccess(response.data, getState().get('statuses'))); + }).catch(error => { + dispatch(blockAccountFail(id, error)); + }); + }; +}; + +export function unblockAccount(id) { + return (dispatch, getState) => { + dispatch(unblockAccountRequest(id)); + + api(getState).post(`/api/v1/accounts/${id}/unblock`).then(response => { + dispatch(unblockAccountSuccess(response.data)); + }).catch(error => { + dispatch(unblockAccountFail(id, error)); + }); + }; +}; + +export function blockAccountRequest(id) { + return { + type: ACCOUNT_BLOCK_REQUEST, + id, + }; +}; + +export function blockAccountSuccess(relationship, statuses) { + return { + type: ACCOUNT_BLOCK_SUCCESS, + relationship, + statuses, + }; +}; + +export function blockAccountFail(error) { + return { + type: ACCOUNT_BLOCK_FAIL, + error, + }; +}; + +export function unblockAccountRequest(id) { + return { + type: ACCOUNT_UNBLOCK_REQUEST, + id, + }; +}; + +export function unblockAccountSuccess(relationship) { + return { + type: ACCOUNT_UNBLOCK_SUCCESS, + relationship, + }; +}; + +export function unblockAccountFail(error) { + return { + type: ACCOUNT_UNBLOCK_FAIL, + error, + }; +}; + + +export function muteAccount(id, notifications) { + return (dispatch, getState) => { + dispatch(muteAccountRequest(id)); + + api(getState).post(`/api/v1/accounts/${id}/mute`, { notifications }).then(response => { + // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers + dispatch(muteAccountSuccess(response.data, getState().get('statuses'))); + }).catch(error => { + dispatch(muteAccountFail(id, error)); + }); + }; +}; + +export function unmuteAccount(id) { + return (dispatch, getState) => { + dispatch(unmuteAccountRequest(id)); + + api(getState).post(`/api/v1/accounts/${id}/unmute`).then(response => { + dispatch(unmuteAccountSuccess(response.data)); + }).catch(error => { + dispatch(unmuteAccountFail(id, error)); + }); + }; +}; + +export function muteAccountRequest(id) { + return { + type: ACCOUNT_MUTE_REQUEST, + id, + }; +}; + +export function muteAccountSuccess(relationship, statuses) { + return { + type: ACCOUNT_MUTE_SUCCESS, + relationship, + statuses, + }; +}; + +export function muteAccountFail(error) { + return { + type: ACCOUNT_MUTE_FAIL, + error, + }; +}; + +export function unmuteAccountRequest(id) { + return { + type: ACCOUNT_UNMUTE_REQUEST, + id, + }; +}; + +export function unmuteAccountSuccess(relationship) { + return { + type: ACCOUNT_UNMUTE_SUCCESS, + relationship, + }; +}; + +export function unmuteAccountFail(error) { + return { + type: ACCOUNT_UNMUTE_FAIL, + error, + }; +}; + + +export function fetchFollowers(id) { + return (dispatch, getState) => { + dispatch(fetchFollowersRequest(id)); + + api(getState).get(`/api/v1/accounts/${id}/followers`).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + + dispatch(fetchFollowersSuccess(id, response.data, next ? next.uri : null)); + dispatch(fetchRelationships(response.data.map(item => item.id))); + }).catch(error => { + dispatch(fetchFollowersFail(id, error)); + }); + }; +}; + +export function fetchFollowersRequest(id) { + return { + type: FOLLOWERS_FETCH_REQUEST, + id, + }; +}; + +export function fetchFollowersSuccess(id, accounts, next) { + return { + type: FOLLOWERS_FETCH_SUCCESS, + id, + accounts, + next, + }; +}; + +export function fetchFollowersFail(id, error) { + return { + type: FOLLOWERS_FETCH_FAIL, + id, + error, + }; +}; + +export function expandFollowers(id) { + return (dispatch, getState) => { + const url = getState().getIn(['user_lists', 'followers', id, 'next']); + + if (url === null) { + return; + } + + dispatch(expandFollowersRequest(id)); + + api(getState).get(url).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + + dispatch(expandFollowersSuccess(id, response.data, next ? next.uri : null)); + dispatch(fetchRelationships(response.data.map(item => item.id))); + }).catch(error => { + dispatch(expandFollowersFail(id, error)); + }); + }; +}; + +export function expandFollowersRequest(id) { + return { + type: FOLLOWERS_EXPAND_REQUEST, + id, + }; +}; + +export function expandFollowersSuccess(id, accounts, next) { + return { + type: FOLLOWERS_EXPAND_SUCCESS, + id, + accounts, + next, + }; +}; + +export function expandFollowersFail(id, error) { + return { + type: FOLLOWERS_EXPAND_FAIL, + id, + error, + }; +}; + +export function fetchFollowing(id) { + return (dispatch, getState) => { + dispatch(fetchFollowingRequest(id)); + + api(getState).get(`/api/v1/accounts/${id}/following`).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + + dispatch(fetchFollowingSuccess(id, response.data, next ? next.uri : null)); + dispatch(fetchRelationships(response.data.map(item => item.id))); + }).catch(error => { + dispatch(fetchFollowingFail(id, error)); + }); + }; +}; + +export function fetchFollowingRequest(id) { + return { + type: FOLLOWING_FETCH_REQUEST, + id, + }; +}; + +export function fetchFollowingSuccess(id, accounts, next) { + return { + type: FOLLOWING_FETCH_SUCCESS, + id, + accounts, + next, + }; +}; + +export function fetchFollowingFail(id, error) { + return { + type: FOLLOWING_FETCH_FAIL, + id, + error, + }; +}; + +export function expandFollowing(id) { + return (dispatch, getState) => { + const url = getState().getIn(['user_lists', 'following', id, 'next']); + + if (url === null) { + return; + } + + dispatch(expandFollowingRequest(id)); + + api(getState).get(url).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + + dispatch(expandFollowingSuccess(id, response.data, next ? next.uri : null)); + dispatch(fetchRelationships(response.data.map(item => item.id))); + }).catch(error => { + dispatch(expandFollowingFail(id, error)); + }); + }; +}; + +export function expandFollowingRequest(id) { + return { + type: FOLLOWING_EXPAND_REQUEST, + id, + }; +}; + +export function expandFollowingSuccess(id, accounts, next) { + return { + type: FOLLOWING_EXPAND_SUCCESS, + id, + accounts, + next, + }; +}; + +export function expandFollowingFail(id, error) { + return { + type: FOLLOWING_EXPAND_FAIL, + id, + error, + }; +}; + +export function fetchRelationships(accountIds) { + return (dispatch, getState) => { + const loadedRelationships = getState().get('relationships'); + const newAccountIds = accountIds.filter(id => loadedRelationships.get(id, null) === null); + + if (newAccountIds.length === 0) { + return; + } + + dispatch(fetchRelationshipsRequest(newAccountIds)); + + api(getState).get(`/api/v1/accounts/relationships?${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then(response => { + dispatch(fetchRelationshipsSuccess(response.data)); + }).catch(error => { + dispatch(fetchRelationshipsFail(error)); + }); + }; +}; + +export function fetchRelationshipsRequest(ids) { + return { + type: RELATIONSHIPS_FETCH_REQUEST, + ids, + skipLoading: true, + }; +}; + +export function fetchRelationshipsSuccess(relationships) { + return { + type: RELATIONSHIPS_FETCH_SUCCESS, + relationships, + skipLoading: true, + }; +}; + +export function fetchRelationshipsFail(error) { + return { + type: RELATIONSHIPS_FETCH_FAIL, + error, + skipLoading: true, + }; +}; + +export function fetchFollowRequests() { + return (dispatch, getState) => { + dispatch(fetchFollowRequestsRequest()); + + api(getState).get('/api/v1/follow_requests').then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(fetchFollowRequestsSuccess(response.data, next ? next.uri : null)); + }).catch(error => dispatch(fetchFollowRequestsFail(error))); + }; +}; + +export function fetchFollowRequestsRequest() { + return { + type: FOLLOW_REQUESTS_FETCH_REQUEST, + }; +}; + +export function fetchFollowRequestsSuccess(accounts, next) { + return { + type: FOLLOW_REQUESTS_FETCH_SUCCESS, + accounts, + next, + }; +}; + +export function fetchFollowRequestsFail(error) { + return { + type: FOLLOW_REQUESTS_FETCH_FAIL, + error, + }; +}; + +export function expandFollowRequests() { + return (dispatch, getState) => { + const url = getState().getIn(['user_lists', 'follow_requests', 'next']); + + if (url === null) { + return; + } + + dispatch(expandFollowRequestsRequest()); + + api(getState).get(url).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(expandFollowRequestsSuccess(response.data, next ? next.uri : null)); + }).catch(error => dispatch(expandFollowRequestsFail(error))); + }; +}; + +export function expandFollowRequestsRequest() { + return { + type: FOLLOW_REQUESTS_EXPAND_REQUEST, + }; +}; + +export function expandFollowRequestsSuccess(accounts, next) { + return { + type: FOLLOW_REQUESTS_EXPAND_SUCCESS, + accounts, + next, + }; +}; + +export function expandFollowRequestsFail(error) { + return { + type: FOLLOW_REQUESTS_EXPAND_FAIL, + error, + }; +}; + +export function authorizeFollowRequest(id) { + return (dispatch, getState) => { + dispatch(authorizeFollowRequestRequest(id)); + + api(getState) + .post(`/api/v1/follow_requests/${id}/authorize`) + .then(() => dispatch(authorizeFollowRequestSuccess(id))) + .catch(error => dispatch(authorizeFollowRequestFail(id, error))); + }; +}; + +export function authorizeFollowRequestRequest(id) { + return { + type: FOLLOW_REQUEST_AUTHORIZE_REQUEST, + id, + }; +}; + +export function authorizeFollowRequestSuccess(id) { + return { + type: FOLLOW_REQUEST_AUTHORIZE_SUCCESS, + id, + }; +}; + +export function authorizeFollowRequestFail(id, error) { + return { + type: FOLLOW_REQUEST_AUTHORIZE_FAIL, + id, + error, + }; +}; + + +export function rejectFollowRequest(id) { + return (dispatch, getState) => { + dispatch(rejectFollowRequestRequest(id)); + + api(getState) + .post(`/api/v1/follow_requests/${id}/reject`) + .then(() => dispatch(rejectFollowRequestSuccess(id))) + .catch(error => dispatch(rejectFollowRequestFail(id, error))); + }; +}; + +export function rejectFollowRequestRequest(id) { + return { + type: FOLLOW_REQUEST_REJECT_REQUEST, + id, + }; +}; + +export function rejectFollowRequestSuccess(id) { + return { + type: FOLLOW_REQUEST_REJECT_SUCCESS, + id, + }; +}; + +export function rejectFollowRequestFail(id, error) { + return { + type: FOLLOW_REQUEST_REJECT_FAIL, + id, + error, + }; +}; diff --git a/app/javascript/flavours/glitch/actions/alerts.js b/app/javascript/flavours/glitch/actions/alerts.js new file mode 100644 index 000000000..f37fdeeb6 --- /dev/null +++ b/app/javascript/flavours/glitch/actions/alerts.js @@ -0,0 +1,24 @@ +export const ALERT_SHOW = 'ALERT_SHOW'; +export const ALERT_DISMISS = 'ALERT_DISMISS'; +export const ALERT_CLEAR = 'ALERT_CLEAR'; + +export function dismissAlert(alert) { + return { + type: ALERT_DISMISS, + alert, + }; +}; + +export function clearAlert() { + return { + type: ALERT_CLEAR, + }; +}; + +export function showAlert(title, message) { + return { + type: ALERT_SHOW, + title, + message, + }; +}; diff --git a/app/javascript/flavours/glitch/actions/blocks.js b/app/javascript/flavours/glitch/actions/blocks.js new file mode 100644 index 000000000..fe44ca19a --- /dev/null +++ b/app/javascript/flavours/glitch/actions/blocks.js @@ -0,0 +1,82 @@ +import api, { getLinks } from 'flavours/glitch/util/api'; +import { fetchRelationships } from './accounts'; + +export const BLOCKS_FETCH_REQUEST = 'BLOCKS_FETCH_REQUEST'; +export const BLOCKS_FETCH_SUCCESS = 'BLOCKS_FETCH_SUCCESS'; +export const BLOCKS_FETCH_FAIL = 'BLOCKS_FETCH_FAIL'; + +export const BLOCKS_EXPAND_REQUEST = 'BLOCKS_EXPAND_REQUEST'; +export const BLOCKS_EXPAND_SUCCESS = 'BLOCKS_EXPAND_SUCCESS'; +export const BLOCKS_EXPAND_FAIL = 'BLOCKS_EXPAND_FAIL'; + +export function fetchBlocks() { + return (dispatch, getState) => { + dispatch(fetchBlocksRequest()); + + api(getState).get('/api/v1/blocks').then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(fetchBlocksSuccess(response.data, next ? next.uri : null)); + dispatch(fetchRelationships(response.data.map(item => item.id))); + }).catch(error => dispatch(fetchBlocksFail(error))); + }; +}; + +export function fetchBlocksRequest() { + return { + type: BLOCKS_FETCH_REQUEST, + }; +}; + +export function fetchBlocksSuccess(accounts, next) { + return { + type: BLOCKS_FETCH_SUCCESS, + accounts, + next, + }; +}; + +export function fetchBlocksFail(error) { + return { + type: BLOCKS_FETCH_FAIL, + error, + }; +}; + +export function expandBlocks() { + return (dispatch, getState) => { + const url = getState().getIn(['user_lists', 'blocks', 'next']); + + if (url === null) { + return; + } + + dispatch(expandBlocksRequest()); + + api(getState).get(url).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(expandBlocksSuccess(response.data, next ? next.uri : null)); + dispatch(fetchRelationships(response.data.map(item => item.id))); + }).catch(error => dispatch(expandBlocksFail(error))); + }; +}; + +export function expandBlocksRequest() { + return { + type: BLOCKS_EXPAND_REQUEST, + }; +}; + +export function expandBlocksSuccess(accounts, next) { + return { + type: BLOCKS_EXPAND_SUCCESS, + accounts, + next, + }; +}; + +export function expandBlocksFail(error) { + return { + type: BLOCKS_EXPAND_FAIL, + error, + }; +}; diff --git a/app/javascript/flavours/glitch/actions/bundles.js b/app/javascript/flavours/glitch/actions/bundles.js new file mode 100644 index 000000000..ecc9c8f7d --- /dev/null +++ b/app/javascript/flavours/glitch/actions/bundles.js @@ -0,0 +1,25 @@ +export const BUNDLE_FETCH_REQUEST = 'BUNDLE_FETCH_REQUEST'; +export const BUNDLE_FETCH_SUCCESS = 'BUNDLE_FETCH_SUCCESS'; +export const BUNDLE_FETCH_FAIL = 'BUNDLE_FETCH_FAIL'; + +export function fetchBundleRequest(skipLoading) { + return { + type: BUNDLE_FETCH_REQUEST, + skipLoading, + }; +} + +export function fetchBundleSuccess(skipLoading) { + return { + type: BUNDLE_FETCH_SUCCESS, + skipLoading, + }; +} + +export function fetchBundleFail(error, skipLoading) { + return { + type: BUNDLE_FETCH_FAIL, + error, + skipLoading, + }; +} diff --git a/app/javascript/flavours/glitch/actions/cards.js b/app/javascript/flavours/glitch/actions/cards.js new file mode 100644 index 000000000..c897daf58 --- /dev/null +++ b/app/javascript/flavours/glitch/actions/cards.js @@ -0,0 +1,52 @@ +import api from 'flavours/glitch/util/api'; + +export const STATUS_CARD_FETCH_REQUEST = 'STATUS_CARD_FETCH_REQUEST'; +export const STATUS_CARD_FETCH_SUCCESS = 'STATUS_CARD_FETCH_SUCCESS'; +export const STATUS_CARD_FETCH_FAIL = 'STATUS_CARD_FETCH_FAIL'; + +export function fetchStatusCard(id) { + return (dispatch, getState) => { + if (getState().getIn(['cards', id], null) !== null) { + return; + } + + dispatch(fetchStatusCardRequest(id)); + + api(getState).get(`/api/v1/statuses/${id}/card`).then(response => { + if (!response.data.url) { + return; + } + + dispatch(fetchStatusCardSuccess(id, response.data)); + }).catch(error => { + dispatch(fetchStatusCardFail(id, error)); + }); + }; +}; + +export function fetchStatusCardRequest(id) { + return { + type: STATUS_CARD_FETCH_REQUEST, + id, + skipLoading: true, + }; +}; + +export function fetchStatusCardSuccess(id, card) { + return { + type: STATUS_CARD_FETCH_SUCCESS, + id, + card, + skipLoading: true, + }; +}; + +export function fetchStatusCardFail(id, error) { + return { + type: STATUS_CARD_FETCH_FAIL, + id, + error, + skipLoading: true, + skipAlert: true, + }; +}; diff --git a/app/javascript/flavours/glitch/actions/columns.js b/app/javascript/flavours/glitch/actions/columns.js new file mode 100644 index 000000000..bcb0cdf98 --- /dev/null +++ b/app/javascript/flavours/glitch/actions/columns.js @@ -0,0 +1,40 @@ +import { saveSettings } from './settings'; + +export const COLUMN_ADD = 'COLUMN_ADD'; +export const COLUMN_REMOVE = 'COLUMN_REMOVE'; +export const COLUMN_MOVE = 'COLUMN_MOVE'; + +export function addColumn(id, params) { + return dispatch => { + dispatch({ + type: COLUMN_ADD, + id, + params, + }); + + dispatch(saveSettings()); + }; +}; + +export function removeColumn(uuid) { + return dispatch => { + dispatch({ + type: COLUMN_REMOVE, + uuid, + }); + + dispatch(saveSettings()); + }; +}; + +export function moveColumn(uuid, direction) { + return dispatch => { + dispatch({ + type: COLUMN_MOVE, + uuid, + direction, + }); + + dispatch(saveSettings()); + }; +}; diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js new file mode 100644 index 000000000..32746f27b --- /dev/null +++ b/app/javascript/flavours/glitch/actions/compose.js @@ -0,0 +1,398 @@ +import api from 'flavours/glitch/util/api'; +import { throttle } from 'lodash'; +import { search as emojiSearch } from 'flavours/glitch/util/emoji/emoji_mart_search_light'; +import { useEmoji } from './emojis'; + +import { + updateTimeline, + refreshHomeTimeline, + refreshCommunityTimeline, + refreshPublicTimeline, + refreshDirectTimeline, +} from './timelines'; + +export const COMPOSE_CHANGE = 'COMPOSE_CHANGE'; +export const COMPOSE_SUBMIT_REQUEST = 'COMPOSE_SUBMIT_REQUEST'; +export const COMPOSE_SUBMIT_SUCCESS = 'COMPOSE_SUBMIT_SUCCESS'; +export const COMPOSE_SUBMIT_FAIL = 'COMPOSE_SUBMIT_FAIL'; +export const COMPOSE_REPLY = 'COMPOSE_REPLY'; +export const COMPOSE_REPLY_CANCEL = 'COMPOSE_REPLY_CANCEL'; +export const COMPOSE_MENTION = 'COMPOSE_MENTION'; +export const COMPOSE_RESET = 'COMPOSE_RESET'; +export const COMPOSE_UPLOAD_REQUEST = 'COMPOSE_UPLOAD_REQUEST'; +export const COMPOSE_UPLOAD_SUCCESS = 'COMPOSE_UPLOAD_SUCCESS'; +export const COMPOSE_UPLOAD_FAIL = 'COMPOSE_UPLOAD_FAIL'; +export const COMPOSE_UPLOAD_PROGRESS = 'COMPOSE_UPLOAD_PROGRESS'; +export const COMPOSE_UPLOAD_UNDO = 'COMPOSE_UPLOAD_UNDO'; + +export const COMPOSE_SUGGESTIONS_CLEAR = 'COMPOSE_SUGGESTIONS_CLEAR'; +export const COMPOSE_SUGGESTIONS_READY = 'COMPOSE_SUGGESTIONS_READY'; +export const COMPOSE_SUGGESTION_SELECT = 'COMPOSE_SUGGESTION_SELECT'; + +export const COMPOSE_MOUNT = 'COMPOSE_MOUNT'; +export const COMPOSE_UNMOUNT = 'COMPOSE_UNMOUNT'; + +export const COMPOSE_ADVANCED_OPTIONS_CHANGE = 'COMPOSE_ADVANCED_OPTIONS_CHANGE'; +export const COMPOSE_SENSITIVITY_CHANGE = 'COMPOSE_SENSITIVITY_CHANGE'; +export const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_CHANGE'; +export const COMPOSE_SPOILER_TEXT_CHANGE = 'COMPOSE_SPOILER_TEXT_CHANGE'; +export const COMPOSE_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_CHANGE'; +export const COMPOSE_LISTABILITY_CHANGE = 'COMPOSE_LISTABILITY_CHANGE'; +export const COMPOSE_COMPOSING_CHANGE = 'COMPOSE_COMPOSING_CHANGE'; + +export const COMPOSE_EMOJI_INSERT = 'COMPOSE_EMOJI_INSERT'; + +export const COMPOSE_UPLOAD_CHANGE_REQUEST = 'COMPOSE_UPLOAD_UPDATE_REQUEST'; +export const COMPOSE_UPLOAD_CHANGE_SUCCESS = 'COMPOSE_UPLOAD_UPDATE_SUCCESS'; +export const COMPOSE_UPLOAD_CHANGE_FAIL = 'COMPOSE_UPLOAD_UPDATE_FAIL'; + +export const COMPOSE_DOODLE_SET = 'COMPOSE_DOODLE_SET'; + +export function changeCompose(text) { + return { + type: COMPOSE_CHANGE, + text: text, + }; +}; + +export function replyCompose(status, router) { + return (dispatch, getState) => { + dispatch({ + type: COMPOSE_REPLY, + status: status, + }); + + if (!getState().getIn(['compose', 'mounted'])) { + router.push('/statuses/new'); + } + }; +}; + +export function cancelReplyCompose() { + return { + type: COMPOSE_REPLY_CANCEL, + }; +}; + +export function resetCompose() { + return { + type: COMPOSE_RESET, + }; +}; + +export function mentionCompose(account, router) { + return (dispatch, getState) => { + dispatch({ + type: COMPOSE_MENTION, + account: account, + }); + + if (!getState().getIn(['compose', 'mounted'])) { + router.push('/statuses/new'); + } + }; +}; + +export function submitCompose() { + return function (dispatch, getState) { + let status = getState().getIn(['compose', 'text'], ''); + + if (!status || !status.length) { + return; + } + + dispatch(submitComposeRequest()); + if (getState().getIn(['compose', 'advanced_options', 'do_not_federate'])) { + status = status + ' 👁️'; + } + api(getState).post('/api/v1/statuses', { + status, + in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null), + media_ids: getState().getIn(['compose', 'media_attachments']).map(item => item.get('id')), + sensitive: getState().getIn(['compose', 'sensitive']), + spoiler_text: getState().getIn(['compose', 'spoiler_text'], ''), + visibility: getState().getIn(['compose', 'privacy']), + }, { + headers: { + 'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']), + }, + }).then(function (response) { + dispatch(submitComposeSuccess({ ...response.data })); + + // To make the app more responsive, immediately get the status into the columns + + const insertOrRefresh = (timelineId, refreshAction) => { + if (getState().getIn(['timelines', timelineId, 'online'])) { + dispatch(updateTimeline(timelineId, { ...response.data })); + } else if (getState().getIn(['timelines', timelineId, 'loaded'])) { + dispatch(refreshAction()); + } + }; + + insertOrRefresh('home', refreshHomeTimeline); + + if (response.data.in_reply_to_id === null && response.data.visibility === 'public') { + insertOrRefresh('community', refreshCommunityTimeline); + insertOrRefresh('public', refreshPublicTimeline); + } else if (response.data.visibility === 'direct') { + insertOrRefresh('direct', refreshDirectTimeline); + } + }).catch(function (error) { + dispatch(submitComposeFail(error)); + }); + }; +}; + +export function submitComposeRequest() { + return { + type: COMPOSE_SUBMIT_REQUEST, + }; +}; + +export function submitComposeSuccess(status) { + return { + type: COMPOSE_SUBMIT_SUCCESS, + status: status, + }; +}; + +export function submitComposeFail(error) { + return { + type: COMPOSE_SUBMIT_FAIL, + error: error, + }; +}; + +export function doodleSet(options) { + return { + type: COMPOSE_DOODLE_SET, + options: options, + }; +}; + +export function uploadCompose(files) { + return function (dispatch, getState) { + if (getState().getIn(['compose', 'media_attachments']).size > 3) { + return; + } + + dispatch(uploadComposeRequest()); + + let data = new FormData(); + data.append('file', files[0]); + + api(getState).post('/api/v1/media', data, { + onUploadProgress: function (e) { + dispatch(uploadComposeProgress(e.loaded, e.total)); + }, + }).then(function (response) { + dispatch(uploadComposeSuccess(response.data)); + }).catch(function (error) { + dispatch(uploadComposeFail(error)); + }); + }; +}; + +export function changeUploadCompose(id, description) { + return (dispatch, getState) => { + dispatch(changeUploadComposeRequest()); + + api(getState).put(`/api/v1/media/${id}`, { description }).then(response => { + dispatch(changeUploadComposeSuccess(response.data)); + }).catch(error => { + dispatch(changeUploadComposeFail(id, error)); + }); + }; +}; + +export function changeUploadComposeRequest() { + return { + type: COMPOSE_UPLOAD_CHANGE_REQUEST, + skipLoading: true, + }; +}; +export function changeUploadComposeSuccess(media) { + return { + type: COMPOSE_UPLOAD_CHANGE_SUCCESS, + media: media, + skipLoading: true, + }; +}; + +export function changeUploadComposeFail(error) { + return { + type: COMPOSE_UPLOAD_CHANGE_FAIL, + error: error, + skipLoading: true, + }; +}; + +export function uploadComposeRequest() { + return { + type: COMPOSE_UPLOAD_REQUEST, + skipLoading: true, + }; +}; + +export function uploadComposeProgress(loaded, total) { + return { + type: COMPOSE_UPLOAD_PROGRESS, + loaded: loaded, + total: total, + }; +}; + +export function uploadComposeSuccess(media) { + return { + type: COMPOSE_UPLOAD_SUCCESS, + media: media, + skipLoading: true, + }; +}; + +export function uploadComposeFail(error) { + return { + type: COMPOSE_UPLOAD_FAIL, + error: error, + skipLoading: true, + }; +}; + +export function undoUploadCompose(media_id) { + return { + type: COMPOSE_UPLOAD_UNDO, + media_id: media_id, + }; +}; + +export function clearComposeSuggestions() { + return { + type: COMPOSE_SUGGESTIONS_CLEAR, + }; +}; + +const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) => { + api(getState).get('/api/v1/accounts/search', { + params: { + q: token.slice(1), + resolve: false, + limit: 4, + }, + }).then(response => { + dispatch(readyComposeSuggestionsAccounts(token, response.data)); + }); +}, 200, { leading: true, trailing: true }); + +const fetchComposeSuggestionsEmojis = (dispatch, getState, token) => { + const results = emojiSearch(token.replace(':', ''), { maxResults: 5 }); + dispatch(readyComposeSuggestionsEmojis(token, results)); +}; + +export function fetchComposeSuggestions(token) { + return (dispatch, getState) => { + if (token[0] === ':') { + fetchComposeSuggestionsEmojis(dispatch, getState, token); + } else { + fetchComposeSuggestionsAccounts(dispatch, getState, token); + } + }; +}; + +export function readyComposeSuggestionsEmojis(token, emojis) { + return { + type: COMPOSE_SUGGESTIONS_READY, + token, + emojis, + }; +}; + +export function readyComposeSuggestionsAccounts(token, accounts) { + return { + type: COMPOSE_SUGGESTIONS_READY, + token, + accounts, + }; +}; + +export function selectComposeSuggestion(position, token, suggestion) { + return (dispatch, getState) => { + let completion, startPosition; + + if (typeof suggestion === 'object' && suggestion.id) { + completion = suggestion.native || suggestion.colons; + startPosition = position - 1; + + dispatch(useEmoji(suggestion)); + } else { + completion = getState().getIn(['accounts', suggestion, 'acct']); + startPosition = position; + } + + dispatch({ + type: COMPOSE_SUGGESTION_SELECT, + position: startPosition, + token, + completion, + }); + }; +}; + +export function mountCompose() { + return { + type: COMPOSE_MOUNT, + }; +}; + +export function unmountCompose() { + return { + type: COMPOSE_UNMOUNT, + }; +}; + +export function toggleComposeAdvancedOption(option) { + return { + type: COMPOSE_ADVANCED_OPTIONS_CHANGE, + option: option, + }; +} + +export function changeComposeSensitivity() { + return { + type: COMPOSE_SENSITIVITY_CHANGE, + }; +}; + +export function changeComposeSpoilerness() { + return { + type: COMPOSE_SPOILERNESS_CHANGE, + }; +}; + +export function changeComposeSpoilerText(text) { + return { + type: COMPOSE_SPOILER_TEXT_CHANGE, + text, + }; +}; + +export function changeComposeVisibility(value) { + return { + type: COMPOSE_VISIBILITY_CHANGE, + value, + }; +}; + +export function insertEmojiCompose(position, emoji) { + return { + type: COMPOSE_EMOJI_INSERT, + position, + emoji, + }; +}; + +export function changeComposing(value) { + return { + type: COMPOSE_COMPOSING_CHANGE, + value, + }; +} diff --git a/app/javascript/flavours/glitch/actions/domain_blocks.js b/app/javascript/flavours/glitch/actions/domain_blocks.js new file mode 100644 index 000000000..8506df91c --- /dev/null +++ b/app/javascript/flavours/glitch/actions/domain_blocks.js @@ -0,0 +1,117 @@ +import api, { getLinks } from 'flavours/glitch/util/api'; + +export const DOMAIN_BLOCK_REQUEST = 'DOMAIN_BLOCK_REQUEST'; +export const DOMAIN_BLOCK_SUCCESS = 'DOMAIN_BLOCK_SUCCESS'; +export const DOMAIN_BLOCK_FAIL = 'DOMAIN_BLOCK_FAIL'; + +export const DOMAIN_UNBLOCK_REQUEST = 'DOMAIN_UNBLOCK_REQUEST'; +export const DOMAIN_UNBLOCK_SUCCESS = 'DOMAIN_UNBLOCK_SUCCESS'; +export const DOMAIN_UNBLOCK_FAIL = 'DOMAIN_UNBLOCK_FAIL'; + +export const DOMAIN_BLOCKS_FETCH_REQUEST = 'DOMAIN_BLOCKS_FETCH_REQUEST'; +export const DOMAIN_BLOCKS_FETCH_SUCCESS = 'DOMAIN_BLOCKS_FETCH_SUCCESS'; +export const DOMAIN_BLOCKS_FETCH_FAIL = 'DOMAIN_BLOCKS_FETCH_FAIL'; + +export function blockDomain(domain, accountId) { + return (dispatch, getState) => { + dispatch(blockDomainRequest(domain)); + + api(getState).post('/api/v1/domain_blocks', { domain }).then(() => { + dispatch(blockDomainSuccess(domain, accountId)); + }).catch(err => { + dispatch(blockDomainFail(domain, err)); + }); + }; +}; + +export function blockDomainRequest(domain) { + return { + type: DOMAIN_BLOCK_REQUEST, + domain, + }; +}; + +export function blockDomainSuccess(domain, accountId) { + return { + type: DOMAIN_BLOCK_SUCCESS, + domain, + accountId, + }; +}; + +export function blockDomainFail(domain, error) { + return { + type: DOMAIN_BLOCK_FAIL, + domain, + error, + }; +}; + +export function unblockDomain(domain, accountId) { + return (dispatch, getState) => { + dispatch(unblockDomainRequest(domain)); + + api(getState).delete('/api/v1/domain_blocks', { params: { domain } }).then(() => { + dispatch(unblockDomainSuccess(domain, accountId)); + }).catch(err => { + dispatch(unblockDomainFail(domain, err)); + }); + }; +}; + +export function unblockDomainRequest(domain) { + return { + type: DOMAIN_UNBLOCK_REQUEST, + domain, + }; +}; + +export function unblockDomainSuccess(domain, accountId) { + return { + type: DOMAIN_UNBLOCK_SUCCESS, + domain, + accountId, + }; +}; + +export function unblockDomainFail(domain, error) { + return { + type: DOMAIN_UNBLOCK_FAIL, + domain, + error, + }; +}; + +export function fetchDomainBlocks() { + return (dispatch, getState) => { + dispatch(fetchDomainBlocksRequest()); + + api(getState).get().then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(fetchDomainBlocksSuccess(response.data, next ? next.uri : null)); + }).catch(err => { + dispatch(fetchDomainBlocksFail(err)); + }); + }; +}; + +export function fetchDomainBlocksRequest() { + return { + type: DOMAIN_BLOCKS_FETCH_REQUEST, + }; +}; + +export function fetchDomainBlocksSuccess(domains, next) { + return { + type: DOMAIN_BLOCKS_FETCH_SUCCESS, + domains, + next, + }; +}; + +export function fetchDomainBlocksFail(error) { + return { + type: DOMAIN_BLOCKS_FETCH_FAIL, + error, + }; +}; diff --git a/app/javascript/flavours/glitch/actions/emojis.js b/app/javascript/flavours/glitch/actions/emojis.js new file mode 100644 index 000000000..7cd9d4b7b --- /dev/null +++ b/app/javascript/flavours/glitch/actions/emojis.js @@ -0,0 +1,14 @@ +import { saveSettings } from './settings'; + +export const EMOJI_USE = 'EMOJI_USE'; + +export function useEmoji(emoji) { + return dispatch => { + dispatch({ + type: EMOJI_USE, + emoji, + }); + + dispatch(saveSettings()); + }; +}; diff --git a/app/javascript/flavours/glitch/actions/favourites.js b/app/javascript/flavours/glitch/actions/favourites.js new file mode 100644 index 000000000..decdcee4f --- /dev/null +++ b/app/javascript/flavours/glitch/actions/favourites.js @@ -0,0 +1,83 @@ +import api, { getLinks } from 'flavours/glitch/util/api'; + +export const FAVOURITED_STATUSES_FETCH_REQUEST = 'FAVOURITED_STATUSES_FETCH_REQUEST'; +export const FAVOURITED_STATUSES_FETCH_SUCCESS = 'FAVOURITED_STATUSES_FETCH_SUCCESS'; +export const FAVOURITED_STATUSES_FETCH_FAIL = 'FAVOURITED_STATUSES_FETCH_FAIL'; + +export const FAVOURITED_STATUSES_EXPAND_REQUEST = 'FAVOURITED_STATUSES_EXPAND_REQUEST'; +export const FAVOURITED_STATUSES_EXPAND_SUCCESS = 'FAVOURITED_STATUSES_EXPAND_SUCCESS'; +export const FAVOURITED_STATUSES_EXPAND_FAIL = 'FAVOURITED_STATUSES_EXPAND_FAIL'; + +export function fetchFavouritedStatuses() { + return (dispatch, getState) => { + dispatch(fetchFavouritedStatusesRequest()); + + api(getState).get('/api/v1/favourites').then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(fetchFavouritedStatusesSuccess(response.data, next ? next.uri : null)); + }).catch(error => { + dispatch(fetchFavouritedStatusesFail(error)); + }); + }; +}; + +export function fetchFavouritedStatusesRequest() { + return { + type: FAVOURITED_STATUSES_FETCH_REQUEST, + }; +}; + +export function fetchFavouritedStatusesSuccess(statuses, next) { + return { + type: FAVOURITED_STATUSES_FETCH_SUCCESS, + statuses, + next, + }; +}; + +export function fetchFavouritedStatusesFail(error) { + return { + type: FAVOURITED_STATUSES_FETCH_FAIL, + error, + }; +}; + +export function expandFavouritedStatuses() { + return (dispatch, getState) => { + const url = getState().getIn(['status_lists', 'favourites', 'next'], null); + + if (url === null) { + return; + } + + dispatch(expandFavouritedStatusesRequest()); + + api(getState).get(url).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(expandFavouritedStatusesSuccess(response.data, next ? next.uri : null)); + }).catch(error => { + dispatch(expandFavouritedStatusesFail(error)); + }); + }; +}; + +export function expandFavouritedStatusesRequest() { + return { + type: FAVOURITED_STATUSES_EXPAND_REQUEST, + }; +}; + +export function expandFavouritedStatusesSuccess(statuses, next) { + return { + type: FAVOURITED_STATUSES_EXPAND_SUCCESS, + statuses, + next, + }; +}; + +export function expandFavouritedStatusesFail(error) { + return { + type: FAVOURITED_STATUSES_EXPAND_FAIL, + error, + }; +}; diff --git a/app/javascript/flavours/glitch/actions/height_cache.js b/app/javascript/flavours/glitch/actions/height_cache.js new file mode 100644 index 000000000..4c752993f --- /dev/null +++ b/app/javascript/flavours/glitch/actions/height_cache.js @@ -0,0 +1,17 @@ +export const HEIGHT_CACHE_SET = 'HEIGHT_CACHE_SET'; +export const HEIGHT_CACHE_CLEAR = 'HEIGHT_CACHE_CLEAR'; + +export function setHeight (key, id, height) { + return { + type: HEIGHT_CACHE_SET, + key, + id, + height, + }; +}; + +export function clearHeight () { + return { + type: HEIGHT_CACHE_CLEAR, + }; +}; diff --git a/app/javascript/flavours/glitch/actions/interactions.js b/app/javascript/flavours/glitch/actions/interactions.js new file mode 100644 index 000000000..ceeb2773b --- /dev/null +++ b/app/javascript/flavours/glitch/actions/interactions.js @@ -0,0 +1,313 @@ +import api from 'flavours/glitch/util/api'; + +export const REBLOG_REQUEST = 'REBLOG_REQUEST'; +export const REBLOG_SUCCESS = 'REBLOG_SUCCESS'; +export const REBLOG_FAIL = 'REBLOG_FAIL'; + +export const FAVOURITE_REQUEST = 'FAVOURITE_REQUEST'; +export const FAVOURITE_SUCCESS = 'FAVOURITE_SUCCESS'; +export const FAVOURITE_FAIL = 'FAVOURITE_FAIL'; + +export const UNREBLOG_REQUEST = 'UNREBLOG_REQUEST'; +export const UNREBLOG_SUCCESS = 'UNREBLOG_SUCCESS'; +export const UNREBLOG_FAIL = 'UNREBLOG_FAIL'; + +export const UNFAVOURITE_REQUEST = 'UNFAVOURITE_REQUEST'; +export const UNFAVOURITE_SUCCESS = 'UNFAVOURITE_SUCCESS'; +export const UNFAVOURITE_FAIL = 'UNFAVOURITE_FAIL'; + +export const REBLOGS_FETCH_REQUEST = 'REBLOGS_FETCH_REQUEST'; +export const REBLOGS_FETCH_SUCCESS = 'REBLOGS_FETCH_SUCCESS'; +export const REBLOGS_FETCH_FAIL = 'REBLOGS_FETCH_FAIL'; + +export const FAVOURITES_FETCH_REQUEST = 'FAVOURITES_FETCH_REQUEST'; +export const FAVOURITES_FETCH_SUCCESS = 'FAVOURITES_FETCH_SUCCESS'; +export const FAVOURITES_FETCH_FAIL = 'FAVOURITES_FETCH_FAIL'; + +export const PIN_REQUEST = 'PIN_REQUEST'; +export const PIN_SUCCESS = 'PIN_SUCCESS'; +export const PIN_FAIL = 'PIN_FAIL'; + +export const UNPIN_REQUEST = 'UNPIN_REQUEST'; +export const UNPIN_SUCCESS = 'UNPIN_SUCCESS'; +export const UNPIN_FAIL = 'UNPIN_FAIL'; + +export function reblog(status) { + return function (dispatch, getState) { + dispatch(reblogRequest(status)); + + api(getState).post(`/api/v1/statuses/${status.get('id')}/reblog`).then(function (response) { + // The reblog API method returns a new status wrapped around the original. In this case we are only + // interested in how the original is modified, hence passing it skipping the wrapper + dispatch(reblogSuccess(status, response.data.reblog)); + }).catch(function (error) { + dispatch(reblogFail(status, error)); + }); + }; +}; + +export function unreblog(status) { + return (dispatch, getState) => { + dispatch(unreblogRequest(status)); + + api(getState).post(`/api/v1/statuses/${status.get('id')}/unreblog`).then(response => { + dispatch(unreblogSuccess(status, response.data)); + }).catch(error => { + dispatch(unreblogFail(status, error)); + }); + }; +}; + +export function reblogRequest(status) { + return { + type: REBLOG_REQUEST, + status: status, + }; +}; + +export function reblogSuccess(status, response) { + return { + type: REBLOG_SUCCESS, + status: status, + response: response, + }; +}; + +export function reblogFail(status, error) { + return { + type: REBLOG_FAIL, + status: status, + error: error, + }; +}; + +export function unreblogRequest(status) { + return { + type: UNREBLOG_REQUEST, + status: status, + }; +}; + +export function unreblogSuccess(status, response) { + return { + type: UNREBLOG_SUCCESS, + status: status, + response: response, + }; +}; + +export function unreblogFail(status, error) { + return { + type: UNREBLOG_FAIL, + status: status, + error: error, + }; +}; + +export function favourite(status) { + return function (dispatch, getState) { + dispatch(favouriteRequest(status)); + + api(getState).post(`/api/v1/statuses/${status.get('id')}/favourite`).then(function (response) { + dispatch(favouriteSuccess(status, response.data)); + }).catch(function (error) { + dispatch(favouriteFail(status, error)); + }); + }; +}; + +export function unfavourite(status) { + return (dispatch, getState) => { + dispatch(unfavouriteRequest(status)); + + api(getState).post(`/api/v1/statuses/${status.get('id')}/unfavourite`).then(response => { + dispatch(unfavouriteSuccess(status, response.data)); + }).catch(error => { + dispatch(unfavouriteFail(status, error)); + }); + }; +}; + +export function favouriteRequest(status) { + return { + type: FAVOURITE_REQUEST, + status: status, + }; +}; + +export function favouriteSuccess(status, response) { + return { + type: FAVOURITE_SUCCESS, + status: status, + response: response, + }; +}; + +export function favouriteFail(status, error) { + return { + type: FAVOURITE_FAIL, + status: status, + error: error, + }; +}; + +export function unfavouriteRequest(status) { + return { + type: UNFAVOURITE_REQUEST, + status: status, + }; +}; + +export function unfavouriteSuccess(status, response) { + return { + type: UNFAVOURITE_SUCCESS, + status: status, + response: response, + }; +}; + +export function unfavouriteFail(status, error) { + return { + type: UNFAVOURITE_FAIL, + status: status, + error: error, + }; +}; + +export function fetchReblogs(id) { + return (dispatch, getState) => { + dispatch(fetchReblogsRequest(id)); + + api(getState).get(`/api/v1/statuses/${id}/reblogged_by`).then(response => { + dispatch(fetchReblogsSuccess(id, response.data)); + }).catch(error => { + dispatch(fetchReblogsFail(id, error)); + }); + }; +}; + +export function fetchReblogsRequest(id) { + return { + type: REBLOGS_FETCH_REQUEST, + id, + }; +}; + +export function fetchReblogsSuccess(id, accounts) { + return { + type: REBLOGS_FETCH_SUCCESS, + id, + accounts, + }; +}; + +export function fetchReblogsFail(id, error) { + return { + type: REBLOGS_FETCH_FAIL, + error, + }; +}; + +export function fetchFavourites(id) { + return (dispatch, getState) => { + dispatch(fetchFavouritesRequest(id)); + + api(getState).get(`/api/v1/statuses/${id}/favourited_by`).then(response => { + dispatch(fetchFavouritesSuccess(id, response.data)); + }).catch(error => { + dispatch(fetchFavouritesFail(id, error)); + }); + }; +}; + +export function fetchFavouritesRequest(id) { + return { + type: FAVOURITES_FETCH_REQUEST, + id, + }; +}; + +export function fetchFavouritesSuccess(id, accounts) { + return { + type: FAVOURITES_FETCH_SUCCESS, + id, + accounts, + }; +}; + +export function fetchFavouritesFail(id, error) { + return { + type: FAVOURITES_FETCH_FAIL, + error, + }; +}; + +export function pin(status) { + return (dispatch, getState) => { + dispatch(pinRequest(status)); + + api(getState).post(`/api/v1/statuses/${status.get('id')}/pin`).then(response => { + dispatch(pinSuccess(status, response.data)); + }).catch(error => { + dispatch(pinFail(status, error)); + }); + }; +}; + +export function pinRequest(status) { + return { + type: PIN_REQUEST, + status, + }; +}; + +export function pinSuccess(status, response) { + return { + type: PIN_SUCCESS, + status, + response, + }; +}; + +export function pinFail(status, error) { + return { + type: PIN_FAIL, + status, + error, + }; +}; + +export function unpin (status) { + return (dispatch, getState) => { + dispatch(unpinRequest(status)); + + api(getState).post(`/api/v1/statuses/${status.get('id')}/unpin`).then(response => { + dispatch(unpinSuccess(status, response.data)); + }).catch(error => { + dispatch(unpinFail(status, error)); + }); + }; +}; + +export function unpinRequest(status) { + return { + type: UNPIN_REQUEST, + status, + }; +}; + +export function unpinSuccess(status, response) { + return { + type: UNPIN_SUCCESS, + status, + response, + }; +}; + +export function unpinFail(status, error) { + return { + type: UNPIN_FAIL, + status, + error, + }; +}; diff --git a/app/javascript/flavours/glitch/actions/local_settings.js b/app/javascript/flavours/glitch/actions/local_settings.js new file mode 100644 index 000000000..28660a4e8 --- /dev/null +++ b/app/javascript/flavours/glitch/actions/local_settings.js @@ -0,0 +1,24 @@ +export const LOCAL_SETTING_CHANGE = 'LOCAL_SETTING_CHANGE'; + +export function changeLocalSetting(key, value) { + return dispatch => { + dispatch({ + type: LOCAL_SETTING_CHANGE, + key, + value, + }); + + dispatch(saveLocalSettings()); + }; +}; + +// __TODO :__ +// Right now `saveLocalSettings()` doesn't keep track of which user +// is currently signed in, but it might be better to give each user +// their *own* local settings. +export function saveLocalSettings() { + return (_, getState) => { + const localSettings = getState().get('local_settings').toJS(); + localStorage.setItem('mastodon-settings', JSON.stringify(localSettings)); + }; +}; diff --git a/app/javascript/flavours/glitch/actions/modal.js b/app/javascript/flavours/glitch/actions/modal.js new file mode 100644 index 000000000..80e15c28e --- /dev/null +++ b/app/javascript/flavours/glitch/actions/modal.js @@ -0,0 +1,16 @@ +export const MODAL_OPEN = 'MODAL_OPEN'; +export const MODAL_CLOSE = 'MODAL_CLOSE'; + +export function openModal(type, props) { + return { + type: MODAL_OPEN, + modalType: type, + modalProps: props, + }; +}; + +export function closeModal() { + return { + type: MODAL_CLOSE, + }; +}; diff --git a/app/javascript/flavours/glitch/actions/mutes.js b/app/javascript/flavours/glitch/actions/mutes.js new file mode 100644 index 000000000..e06130533 --- /dev/null +++ b/app/javascript/flavours/glitch/actions/mutes.js @@ -0,0 +1,103 @@ +import api, { getLinks } from 'flavours/glitch/util/api'; +import { fetchRelationships } from './accounts'; +import { openModal } from 'flavours/glitch/actions/modal'; + +export const MUTES_FETCH_REQUEST = 'MUTES_FETCH_REQUEST'; +export const MUTES_FETCH_SUCCESS = 'MUTES_FETCH_SUCCESS'; +export const MUTES_FETCH_FAIL = 'MUTES_FETCH_FAIL'; + +export const MUTES_EXPAND_REQUEST = 'MUTES_EXPAND_REQUEST'; +export const MUTES_EXPAND_SUCCESS = 'MUTES_EXPAND_SUCCESS'; +export const MUTES_EXPAND_FAIL = 'MUTES_EXPAND_FAIL'; + +export const MUTES_INIT_MODAL = 'MUTES_INIT_MODAL'; +export const MUTES_TOGGLE_HIDE_NOTIFICATIONS = 'MUTES_TOGGLE_HIDE_NOTIFICATIONS'; + +export function fetchMutes() { + return (dispatch, getState) => { + dispatch(fetchMutesRequest()); + + api(getState).get('/api/v1/mutes').then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(fetchMutesSuccess(response.data, next ? next.uri : null)); + dispatch(fetchRelationships(response.data.map(item => item.id))); + }).catch(error => dispatch(fetchMutesFail(error))); + }; +}; + +export function fetchMutesRequest() { + return { + type: MUTES_FETCH_REQUEST, + }; +}; + +export function fetchMutesSuccess(accounts, next) { + return { + type: MUTES_FETCH_SUCCESS, + accounts, + next, + }; +}; + +export function fetchMutesFail(error) { + return { + type: MUTES_FETCH_FAIL, + error, + }; +}; + +export function expandMutes() { + return (dispatch, getState) => { + const url = getState().getIn(['user_lists', 'mutes', 'next']); + + if (url === null) { + return; + } + + dispatch(expandMutesRequest()); + + api(getState).get(url).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(expandMutesSuccess(response.data, next ? next.uri : null)); + dispatch(fetchRelationships(response.data.map(item => item.id))); + }).catch(error => dispatch(expandMutesFail(error))); + }; +}; + +export function expandMutesRequest() { + return { + type: MUTES_EXPAND_REQUEST, + }; +}; + +export function expandMutesSuccess(accounts, next) { + return { + type: MUTES_EXPAND_SUCCESS, + accounts, + next, + }; +}; + +export function expandMutesFail(error) { + return { + type: MUTES_EXPAND_FAIL, + error, + }; +}; + +export function initMuteModal(account) { + return dispatch => { + dispatch({ + type: MUTES_INIT_MODAL, + account, + }); + + dispatch(openModal('MUTE')); + }; +} + +export function toggleHideNotifications() { + return dispatch => { + dispatch({ type: MUTES_TOGGLE_HIDE_NOTIFICATIONS }); + }; +} diff --git a/app/javascript/flavours/glitch/actions/notifications.js b/app/javascript/flavours/glitch/actions/notifications.js new file mode 100644 index 000000000..9b9ebf86d --- /dev/null +++ b/app/javascript/flavours/glitch/actions/notifications.js @@ -0,0 +1,265 @@ +import api, { getLinks } from 'flavours/glitch/util/api'; +import { List as ImmutableList } from 'immutable'; +import IntlMessageFormat from 'intl-messageformat'; +import { fetchRelationships } from './accounts'; +import { defineMessages } from 'react-intl'; + +export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE'; + +// tracking the notif cleaning request +export const NOTIFICATIONS_DELETE_MARKED_REQUEST = 'NOTIFICATIONS_DELETE_MARKED_REQUEST'; +export const NOTIFICATIONS_DELETE_MARKED_SUCCESS = 'NOTIFICATIONS_DELETE_MARKED_SUCCESS'; +export const NOTIFICATIONS_DELETE_MARKED_FAIL = 'NOTIFICATIONS_DELETE_MARKED_FAIL'; +export const NOTIFICATIONS_MARK_ALL_FOR_DELETE = 'NOTIFICATIONS_MARK_ALL_FOR_DELETE'; +export const NOTIFICATIONS_ENTER_CLEARING_MODE = 'NOTIFICATIONS_ENTER_CLEARING_MODE'; // arg: yes +// Unmark notifications (when the cleaning mode is left) +export const NOTIFICATIONS_UNMARK_ALL_FOR_DELETE = 'NOTIFICATIONS_UNMARK_ALL_FOR_DELETE'; +// Mark one for delete +export const NOTIFICATION_MARK_FOR_DELETE = 'NOTIFICATION_MARK_FOR_DELETE'; + +export const NOTIFICATIONS_REFRESH_REQUEST = 'NOTIFICATIONS_REFRESH_REQUEST'; +export const NOTIFICATIONS_REFRESH_SUCCESS = 'NOTIFICATIONS_REFRESH_SUCCESS'; +export const NOTIFICATIONS_REFRESH_FAIL = 'NOTIFICATIONS_REFRESH_FAIL'; + +export const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST'; +export const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS'; +export const NOTIFICATIONS_EXPAND_FAIL = 'NOTIFICATIONS_EXPAND_FAIL'; + +export const NOTIFICATIONS_CLEAR = 'NOTIFICATIONS_CLEAR'; +export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP'; + +defineMessages({ + mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' }, +}); + +const fetchRelatedRelationships = (dispatch, notifications) => { + const accountIds = notifications.filter(item => item.type === 'follow').map(item => item.account.id); + + if (accountIds > 0) { + dispatch(fetchRelationships(accountIds)); + } +}; + +const unescapeHTML = (html) => { + const wrapper = document.createElement('div'); + html = html.replace(/
|
|\n/, ' '); + wrapper.innerHTML = html; + return wrapper.textContent; +}; + +export function updateNotifications(notification, intlMessages, intlLocale) { + return (dispatch, getState) => { + const showAlert = getState().getIn(['settings', 'notifications', 'alerts', notification.type], true); + const playSound = getState().getIn(['settings', 'notifications', 'sounds', notification.type], true); + + dispatch({ + type: NOTIFICATIONS_UPDATE, + notification, + account: notification.account, + status: notification.status, + meta: playSound ? { sound: 'boop' } : undefined, + }); + + fetchRelatedRelationships(dispatch, [notification]); + + // Desktop notifications + if (typeof window.Notification !== 'undefined' && showAlert) { + const title = new IntlMessageFormat(intlMessages[`notification.${notification.type}`], intlLocale).format({ name: notification.account.display_name.length > 0 ? notification.account.display_name : notification.account.username }); + const body = (notification.status && notification.status.spoiler_text.length > 0) ? notification.status.spoiler_text : unescapeHTML(notification.status ? notification.status.content : ''); + + const notify = new Notification(title, { body, icon: notification.account.avatar, tag: notification.id }); + notify.addEventListener('click', () => { + window.focus(); + notify.close(); + }); + } + }; +}; + +const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS(); + +export function refreshNotifications() { + return (dispatch, getState) => { + const params = {}; + const ids = getState().getIn(['notifications', 'items']); + + let skipLoading = false; + + if (ids.size > 0) { + params.since_id = ids.first().get('id'); + } + + if (getState().getIn(['notifications', 'loaded'])) { + skipLoading = true; + } + + params.exclude_types = excludeTypesFromSettings(getState()); + + dispatch(refreshNotificationsRequest(skipLoading)); + + api(getState).get('/api/v1/notifications', { params }).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + + dispatch(refreshNotificationsSuccess(response.data, skipLoading, next ? next.uri : null)); + fetchRelatedRelationships(dispatch, response.data); + }).catch(error => { + dispatch(refreshNotificationsFail(error, skipLoading)); + }); + }; +}; + +export function refreshNotificationsRequest(skipLoading) { + return { + type: NOTIFICATIONS_REFRESH_REQUEST, + skipLoading, + }; +}; + +export function refreshNotificationsSuccess(notifications, skipLoading, next) { + return { + type: NOTIFICATIONS_REFRESH_SUCCESS, + notifications, + accounts: notifications.map(item => item.account), + statuses: notifications.map(item => item.status).filter(status => !!status), + skipLoading, + next, + }; +}; + +export function refreshNotificationsFail(error, skipLoading) { + return { + type: NOTIFICATIONS_REFRESH_FAIL, + error, + skipLoading, + }; +}; + +export function expandNotifications() { + return (dispatch, getState) => { + const items = getState().getIn(['notifications', 'items'], ImmutableList()); + + if (getState().getIn(['notifications', 'isLoading']) || items.size === 0) { + return; + } + + const params = { + max_id: items.last().get('id'), + limit: 20, + exclude_types: excludeTypesFromSettings(getState()), + }; + + dispatch(expandNotificationsRequest()); + + api(getState).get('/api/v1/notifications', { params }).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null)); + fetchRelatedRelationships(dispatch, response.data); + }).catch(error => { + dispatch(expandNotificationsFail(error)); + }); + }; +}; + +export function expandNotificationsRequest() { + return { + type: NOTIFICATIONS_EXPAND_REQUEST, + }; +}; + +export function expandNotificationsSuccess(notifications, next) { + return { + type: NOTIFICATIONS_EXPAND_SUCCESS, + notifications, + accounts: notifications.map(item => item.account), + statuses: notifications.map(item => item.status).filter(status => !!status), + next, + }; +}; + +export function expandNotificationsFail(error) { + return { + type: NOTIFICATIONS_EXPAND_FAIL, + error, + }; +}; + +export function clearNotifications() { + return (dispatch, getState) => { + dispatch({ + type: NOTIFICATIONS_CLEAR, + }); + + api(getState).post('/api/v1/notifications/clear'); + }; +}; + +export function scrollTopNotifications(top) { + return { + type: NOTIFICATIONS_SCROLL_TOP, + top, + }; +}; + +export function deleteMarkedNotifications() { + return (dispatch, getState) => { + dispatch(deleteMarkedNotificationsRequest()); + + let ids = []; + getState().getIn(['notifications', 'items']).forEach((n) => { + if (n.get('markedForDelete')) { + ids.push(n.get('id')); + } + }); + + if (ids.length === 0) { + return; + } + + api(getState).delete(`/api/v1/notifications/destroy_multiple?ids[]=${ids.join('&ids[]=')}`).then(() => { + dispatch(deleteMarkedNotificationsSuccess()); + }).catch(error => { + console.error(error); + dispatch(deleteMarkedNotificationsFail(error)); + }); + }; +}; + +export function enterNotificationClearingMode(yes) { + return { + type: NOTIFICATIONS_ENTER_CLEARING_MODE, + yes: yes, + }; +}; + +export function markAllNotifications(yes) { + return { + type: NOTIFICATIONS_MARK_ALL_FOR_DELETE, + yes: yes, // true, false or null. null = invert + }; +}; + +export function deleteMarkedNotificationsRequest() { + return { + type: NOTIFICATIONS_DELETE_MARKED_REQUEST, + }; +}; + +export function deleteMarkedNotificationsFail() { + return { + type: NOTIFICATIONS_DELETE_MARKED_FAIL, + }; +}; + +export function markNotificationForDelete(id, yes) { + return { + type: NOTIFICATION_MARK_FOR_DELETE, + id: id, + yes: yes, + }; +}; + +export function deleteMarkedNotificationsSuccess() { + return { + type: NOTIFICATIONS_DELETE_MARKED_SUCCESS, + }; +}; diff --git a/app/javascript/flavours/glitch/actions/onboarding.js b/app/javascript/flavours/glitch/actions/onboarding.js new file mode 100644 index 000000000..a161c50ef --- /dev/null +++ b/app/javascript/flavours/glitch/actions/onboarding.js @@ -0,0 +1,14 @@ +import { openModal } from './modal'; +import { changeSetting, saveSettings } from './settings'; + +export function showOnboardingOnce() { + return (dispatch, getState) => { + const alreadySeen = getState().getIn(['settings', 'onboarded']); + + if (!alreadySeen) { + dispatch(openModal('ONBOARDING')); + dispatch(changeSetting(['onboarded'], true)); + dispatch(saveSettings()); + } + }; +}; diff --git a/app/javascript/flavours/glitch/actions/pin_statuses.js b/app/javascript/flavours/glitch/actions/pin_statuses.js new file mode 100644 index 000000000..d3d1a154f --- /dev/null +++ b/app/javascript/flavours/glitch/actions/pin_statuses.js @@ -0,0 +1,40 @@ +import api from 'flavours/glitch/util/api'; + +export const PINNED_STATUSES_FETCH_REQUEST = 'PINNED_STATUSES_FETCH_REQUEST'; +export const PINNED_STATUSES_FETCH_SUCCESS = 'PINNED_STATUSES_FETCH_SUCCESS'; +export const PINNED_STATUSES_FETCH_FAIL = 'PINNED_STATUSES_FETCH_FAIL'; + +import { me } from 'flavours/glitch/util/initial_state'; + +export function fetchPinnedStatuses() { + return (dispatch, getState) => { + dispatch(fetchPinnedStatusesRequest()); + + api(getState).get(`/api/v1/accounts/${me}/statuses`, { params: { pinned: true } }).then(response => { + dispatch(fetchPinnedStatusesSuccess(response.data, null)); + }).catch(error => { + dispatch(fetchPinnedStatusesFail(error)); + }); + }; +}; + +export function fetchPinnedStatusesRequest() { + return { + type: PINNED_STATUSES_FETCH_REQUEST, + }; +}; + +export function fetchPinnedStatusesSuccess(statuses, next) { + return { + type: PINNED_STATUSES_FETCH_SUCCESS, + statuses, + next, + }; +}; + +export function fetchPinnedStatusesFail(error) { + return { + type: PINNED_STATUSES_FETCH_FAIL, + error, + }; +}; diff --git a/app/javascript/flavours/glitch/actions/push_notifications.js b/app/javascript/flavours/glitch/actions/push_notifications.js new file mode 100644 index 000000000..55661d2b0 --- /dev/null +++ b/app/javascript/flavours/glitch/actions/push_notifications.js @@ -0,0 +1,52 @@ +import axios from 'axios'; + +export const SET_BROWSER_SUPPORT = 'PUSH_NOTIFICATIONS_SET_BROWSER_SUPPORT'; +export const SET_SUBSCRIPTION = 'PUSH_NOTIFICATIONS_SET_SUBSCRIPTION'; +export const CLEAR_SUBSCRIPTION = 'PUSH_NOTIFICATIONS_CLEAR_SUBSCRIPTION'; +export const ALERTS_CHANGE = 'PUSH_NOTIFICATIONS_ALERTS_CHANGE'; + +export function setBrowserSupport (value) { + return { + type: SET_BROWSER_SUPPORT, + value, + }; +} + +export function setSubscription (subscription) { + return { + type: SET_SUBSCRIPTION, + subscription, + }; +} + +export function clearSubscription () { + return { + type: CLEAR_SUBSCRIPTION, + }; +} + +export function changeAlerts(key, value) { + return dispatch => { + dispatch({ + type: ALERTS_CHANGE, + key, + value, + }); + + dispatch(saveSettings()); + }; +} + +export function saveSettings() { + return (_, getState) => { + const state = getState().get('push_notifications'); + const subscription = state.get('subscription'); + const alerts = state.get('alerts'); + + axios.put(`/api/web/push_subscriptions/${subscription.get('id')}`, { + data: { + alerts, + }, + }); + }; +} diff --git a/app/javascript/flavours/glitch/actions/reports.js b/app/javascript/flavours/glitch/actions/reports.js new file mode 100644 index 000000000..ad4fd18a9 --- /dev/null +++ b/app/javascript/flavours/glitch/actions/reports.js @@ -0,0 +1,80 @@ +import api from 'flavours/glitch/util/api'; +import { openModal, closeModal } from './modal'; + +export const REPORT_INIT = 'REPORT_INIT'; +export const REPORT_CANCEL = 'REPORT_CANCEL'; + +export const REPORT_SUBMIT_REQUEST = 'REPORT_SUBMIT_REQUEST'; +export const REPORT_SUBMIT_SUCCESS = 'REPORT_SUBMIT_SUCCESS'; +export const REPORT_SUBMIT_FAIL = 'REPORT_SUBMIT_FAIL'; + +export const REPORT_STATUS_TOGGLE = 'REPORT_STATUS_TOGGLE'; +export const REPORT_COMMENT_CHANGE = 'REPORT_COMMENT_CHANGE'; + +export function initReport(account, status) { + return dispatch => { + dispatch({ + type: REPORT_INIT, + account, + status, + }); + + dispatch(openModal('REPORT')); + }; +}; + +export function cancelReport() { + return { + type: REPORT_CANCEL, + }; +}; + +export function toggleStatusReport(statusId, checked) { + return { + type: REPORT_STATUS_TOGGLE, + statusId, + checked, + }; +}; + +export function submitReport() { + return (dispatch, getState) => { + dispatch(submitReportRequest()); + + api(getState).post('/api/v1/reports', { + account_id: getState().getIn(['reports', 'new', 'account_id']), + status_ids: getState().getIn(['reports', 'new', 'status_ids']), + comment: getState().getIn(['reports', 'new', 'comment']), + }).then(response => { + dispatch(closeModal()); + dispatch(submitReportSuccess(response.data)); + }).catch(error => dispatch(submitReportFail(error))); + }; +}; + +export function submitReportRequest() { + return { + type: REPORT_SUBMIT_REQUEST, + }; +}; + +export function submitReportSuccess(report) { + return { + type: REPORT_SUBMIT_SUCCESS, + report, + }; +}; + +export function submitReportFail(error) { + return { + type: REPORT_SUBMIT_FAIL, + error, + }; +}; + +export function changeReportComment(comment) { + return { + type: REPORT_COMMENT_CHANGE, + comment, + }; +}; diff --git a/app/javascript/flavours/glitch/actions/search.js b/app/javascript/flavours/glitch/actions/search.js new file mode 100644 index 000000000..e86bd848e --- /dev/null +++ b/app/javascript/flavours/glitch/actions/search.js @@ -0,0 +1,73 @@ +import api from 'flavours/glitch/util/api'; + +export const SEARCH_CHANGE = 'SEARCH_CHANGE'; +export const SEARCH_CLEAR = 'SEARCH_CLEAR'; +export const SEARCH_SHOW = 'SEARCH_SHOW'; + +export const SEARCH_FETCH_REQUEST = 'SEARCH_FETCH_REQUEST'; +export const SEARCH_FETCH_SUCCESS = 'SEARCH_FETCH_SUCCESS'; +export const SEARCH_FETCH_FAIL = 'SEARCH_FETCH_FAIL'; + +export function changeSearch(value) { + return { + type: SEARCH_CHANGE, + value, + }; +}; + +export function clearSearch() { + return { + type: SEARCH_CLEAR, + }; +}; + +export function submitSearch() { + return (dispatch, getState) => { + const value = getState().getIn(['search', 'value']); + + if (value.length === 0) { + return; + } + + dispatch(fetchSearchRequest()); + + api(getState).get('/api/v1/search', { + params: { + q: value, + resolve: true, + }, + }).then(response => { + dispatch(fetchSearchSuccess(response.data)); + }).catch(error => { + dispatch(fetchSearchFail(error)); + }); + }; +}; + +export function fetchSearchRequest() { + return { + type: SEARCH_FETCH_REQUEST, + }; +}; + +export function fetchSearchSuccess(results) { + return { + type: SEARCH_FETCH_SUCCESS, + results, + accounts: results.accounts, + statuses: results.statuses, + }; +}; + +export function fetchSearchFail(error) { + return { + type: SEARCH_FETCH_FAIL, + error, + }; +}; + +export function showSearch() { + return { + type: SEARCH_SHOW, + }; +}; diff --git a/app/javascript/flavours/glitch/actions/settings.js b/app/javascript/flavours/glitch/actions/settings.js new file mode 100644 index 000000000..79adca18c --- /dev/null +++ b/app/javascript/flavours/glitch/actions/settings.js @@ -0,0 +1,31 @@ +import axios from 'axios'; +import { debounce } from 'lodash'; + +export const SETTING_CHANGE = 'SETTING_CHANGE'; +export const SETTING_SAVE = 'SETTING_SAVE'; + +export function changeSetting(key, value) { + return dispatch => { + dispatch({ + type: SETTING_CHANGE, + key, + value, + }); + + dispatch(saveSettings()); + }; +}; + +const debouncedSave = debounce((dispatch, getState) => { + if (getState().getIn(['settings', 'saved'])) { + return; + } + + const data = getState().get('settings').filter((_, key) => key !== 'saved').toJS(); + + axios.put('/api/web/settings', { data }).then(() => dispatch({ type: SETTING_SAVE })); +}, 5000, { trailing: true }); + +export function saveSettings() { + return (dispatch, getState) => debouncedSave(dispatch, getState); +}; diff --git a/app/javascript/flavours/glitch/actions/statuses.js b/app/javascript/flavours/glitch/actions/statuses.js new file mode 100644 index 000000000..8b49083ac --- /dev/null +++ b/app/javascript/flavours/glitch/actions/statuses.js @@ -0,0 +1,217 @@ +import api from 'flavours/glitch/util/api'; + +import { deleteFromTimelines } from './timelines'; +import { fetchStatusCard } from './cards'; + +export const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST'; +export const STATUS_FETCH_SUCCESS = 'STATUS_FETCH_SUCCESS'; +export const STATUS_FETCH_FAIL = 'STATUS_FETCH_FAIL'; + +export const STATUS_DELETE_REQUEST = 'STATUS_DELETE_REQUEST'; +export const STATUS_DELETE_SUCCESS = 'STATUS_DELETE_SUCCESS'; +export const STATUS_DELETE_FAIL = 'STATUS_DELETE_FAIL'; + +export const CONTEXT_FETCH_REQUEST = 'CONTEXT_FETCH_REQUEST'; +export const CONTEXT_FETCH_SUCCESS = 'CONTEXT_FETCH_SUCCESS'; +export const CONTEXT_FETCH_FAIL = 'CONTEXT_FETCH_FAIL'; + +export const STATUS_MUTE_REQUEST = 'STATUS_MUTE_REQUEST'; +export const STATUS_MUTE_SUCCESS = 'STATUS_MUTE_SUCCESS'; +export const STATUS_MUTE_FAIL = 'STATUS_MUTE_FAIL'; + +export const STATUS_UNMUTE_REQUEST = 'STATUS_UNMUTE_REQUEST'; +export const STATUS_UNMUTE_SUCCESS = 'STATUS_UNMUTE_SUCCESS'; +export const STATUS_UNMUTE_FAIL = 'STATUS_UNMUTE_FAIL'; + +export function fetchStatusRequest(id, skipLoading) { + return { + type: STATUS_FETCH_REQUEST, + id, + skipLoading, + }; +}; + +export function fetchStatus(id) { + return (dispatch, getState) => { + const skipLoading = getState().getIn(['statuses', id], null) !== null; + + dispatch(fetchContext(id)); + dispatch(fetchStatusCard(id)); + + if (skipLoading) { + return; + } + + dispatch(fetchStatusRequest(id, skipLoading)); + + api(getState).get(`/api/v1/statuses/${id}`).then(response => { + dispatch(fetchStatusSuccess(response.data, skipLoading)); + }).catch(error => { + dispatch(fetchStatusFail(id, error, skipLoading)); + }); + }; +}; + +export function fetchStatusSuccess(status, skipLoading) { + return { + type: STATUS_FETCH_SUCCESS, + status, + skipLoading, + }; +}; + +export function fetchStatusFail(id, error, skipLoading) { + return { + type: STATUS_FETCH_FAIL, + id, + error, + skipLoading, + skipAlert: true, + }; +}; + +export function deleteStatus(id) { + return (dispatch, getState) => { + dispatch(deleteStatusRequest(id)); + + api(getState).delete(`/api/v1/statuses/${id}`).then(() => { + dispatch(deleteStatusSuccess(id)); + dispatch(deleteFromTimelines(id)); + }).catch(error => { + dispatch(deleteStatusFail(id, error)); + }); + }; +}; + +export function deleteStatusRequest(id) { + return { + type: STATUS_DELETE_REQUEST, + id: id, + }; +}; + +export function deleteStatusSuccess(id) { + return { + type: STATUS_DELETE_SUCCESS, + id: id, + }; +}; + +export function deleteStatusFail(id, error) { + return { + type: STATUS_DELETE_FAIL, + id: id, + error: error, + }; +}; + +export function fetchContext(id) { + return (dispatch, getState) => { + dispatch(fetchContextRequest(id)); + + api(getState).get(`/api/v1/statuses/${id}/context`).then(response => { + dispatch(fetchContextSuccess(id, response.data.ancestors, response.data.descendants)); + + }).catch(error => { + if (error.response && error.response.status === 404) { + dispatch(deleteFromTimelines(id)); + } + + dispatch(fetchContextFail(id, error)); + }); + }; +}; + +export function fetchContextRequest(id) { + return { + type: CONTEXT_FETCH_REQUEST, + id, + }; +}; + +export function fetchContextSuccess(id, ancestors, descendants) { + return { + type: CONTEXT_FETCH_SUCCESS, + id, + ancestors, + descendants, + statuses: ancestors.concat(descendants), + }; +}; + +export function fetchContextFail(id, error) { + return { + type: CONTEXT_FETCH_FAIL, + id, + error, + skipAlert: true, + }; +}; + +export function muteStatus(id) { + return (dispatch, getState) => { + dispatch(muteStatusRequest(id)); + + api(getState).post(`/api/v1/statuses/${id}/mute`).then(() => { + dispatch(muteStatusSuccess(id)); + }).catch(error => { + dispatch(muteStatusFail(id, error)); + }); + }; +}; + +export function muteStatusRequest(id) { + return { + type: STATUS_MUTE_REQUEST, + id, + }; +}; + +export function muteStatusSuccess(id) { + return { + type: STATUS_MUTE_SUCCESS, + id, + }; +}; + +export function muteStatusFail(id, error) { + return { + type: STATUS_MUTE_FAIL, + id, + error, + }; +}; + +export function unmuteStatus(id) { + return (dispatch, getState) => { + dispatch(unmuteStatusRequest(id)); + + api(getState).post(`/api/v1/statuses/${id}/unmute`).then(() => { + dispatch(unmuteStatusSuccess(id)); + }).catch(error => { + dispatch(unmuteStatusFail(id, error)); + }); + }; +}; + +export function unmuteStatusRequest(id) { + return { + type: STATUS_UNMUTE_REQUEST, + id, + }; +}; + +export function unmuteStatusSuccess(id) { + return { + type: STATUS_UNMUTE_SUCCESS, + id, + }; +}; + +export function unmuteStatusFail(id, error) { + return { + type: STATUS_UNMUTE_FAIL, + id, + error, + }; +}; diff --git a/app/javascript/flavours/glitch/actions/store.js b/app/javascript/flavours/glitch/actions/store.js new file mode 100644 index 000000000..a1db0fdd5 --- /dev/null +++ b/app/javascript/flavours/glitch/actions/store.js @@ -0,0 +1,17 @@ +import { Iterable, fromJS } from 'immutable'; + +export const STORE_HYDRATE = 'STORE_HYDRATE'; +export const STORE_HYDRATE_LAZY = 'STORE_HYDRATE_LAZY'; + +const convertState = rawState => + fromJS(rawState, (k, v) => + Iterable.isIndexed(v) ? v.toList() : v.toMap()); + +export function hydrateStore(rawState) { + const state = convertState(rawState); + + return { + type: STORE_HYDRATE, + state, + }; +}; diff --git a/app/javascript/flavours/glitch/actions/streaming.js b/app/javascript/flavours/glitch/actions/streaming.js new file mode 100644 index 000000000..595eefa41 --- /dev/null +++ b/app/javascript/flavours/glitch/actions/streaming.js @@ -0,0 +1,54 @@ +import { connectStream } from 'flavours/glitch/util/stream'; +import { + updateTimeline, + deleteFromTimelines, + refreshHomeTimeline, + connectTimeline, + disconnectTimeline, +} from './timelines'; +import { updateNotifications, refreshNotifications } from './notifications'; +import { getLocale } from 'mastodon/locales'; + +const { messages } = getLocale(); + +export function connectTimelineStream (timelineId, path, pollingRefresh = null) { + + return connectStream (path, pollingRefresh, (dispatch, getState) => { + const locale = getState().getIn(['meta', 'locale']); + return { + onConnect() { + dispatch(connectTimeline(timelineId)); + }, + + onDisconnect() { + dispatch(disconnectTimeline(timelineId)); + }, + + onReceive (data) { + switch(data.event) { + case 'update': + dispatch(updateTimeline(timelineId, JSON.parse(data.payload))); + break; + case 'delete': + dispatch(deleteFromTimelines(data.payload)); + break; + case 'notification': + dispatch(updateNotifications(JSON.parse(data.payload), messages, locale)); + break; + } + }, + }; + }); +} + +function refreshHomeTimelineAndNotification (dispatch) { + dispatch(refreshHomeTimeline()); + dispatch(refreshNotifications()); +} + +export const connectUserStream = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification); +export const connectCommunityStream = () => connectTimelineStream('community', 'public:local'); +export const connectMediaStream = () => connectTimelineStream('community', 'public:local'); +export const connectPublicStream = () => connectTimelineStream('public', 'public'); +export const connectHashtagStream = (tag) => connectTimelineStream(`hashtag:${tag}`, `hashtag&tag=${tag}`); +export const connectDirectStream = () => connectTimelineStream('direct', 'direct'); diff --git a/app/javascript/flavours/glitch/actions/timelines.js b/app/javascript/flavours/glitch/actions/timelines.js new file mode 100644 index 000000000..3fabf3885 --- /dev/null +++ b/app/javascript/flavours/glitch/actions/timelines.js @@ -0,0 +1,208 @@ +import api, { getLinks } from 'flavours/glitch/util/api'; +import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; + +export const TIMELINE_UPDATE = 'TIMELINE_UPDATE'; +export const TIMELINE_DELETE = 'TIMELINE_DELETE'; + +export const TIMELINE_REFRESH_REQUEST = 'TIMELINE_REFRESH_REQUEST'; +export const TIMELINE_REFRESH_SUCCESS = 'TIMELINE_REFRESH_SUCCESS'; +export const TIMELINE_REFRESH_FAIL = 'TIMELINE_REFRESH_FAIL'; + +export const TIMELINE_EXPAND_REQUEST = 'TIMELINE_EXPAND_REQUEST'; +export const TIMELINE_EXPAND_SUCCESS = 'TIMELINE_EXPAND_SUCCESS'; +export const TIMELINE_EXPAND_FAIL = 'TIMELINE_EXPAND_FAIL'; + +export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP'; + +export const TIMELINE_CONNECT = 'TIMELINE_CONNECT'; +export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT'; + +export const TIMELINE_CONTEXT_UPDATE = 'CONTEXT_UPDATE'; + +export function refreshTimelineSuccess(timeline, statuses, skipLoading, next) { + return { + type: TIMELINE_REFRESH_SUCCESS, + timeline, + statuses, + skipLoading, + next, + }; +}; + +export function updateTimeline(timeline, status) { + return (dispatch, getState) => { + const references = status.reblog ? getState().get('statuses').filter((item, itemId) => (itemId === status.reblog.id || item.get('reblog') === status.reblog.id)).map((_, itemId) => itemId) : []; + const parents = []; + + if (status.in_reply_to_id) { + let parent = getState().getIn(['statuses', status.in_reply_to_id]); + + while (parent && parent.get('in_reply_to_id')) { + parents.push(parent.get('id')); + parent = getState().getIn(['statuses', parent.get('in_reply_to_id')]); + } + } + + dispatch({ + type: TIMELINE_UPDATE, + timeline, + status, + references, + }); + + if (parents.length > 0) { + dispatch({ + type: TIMELINE_CONTEXT_UPDATE, + status, + references: parents, + }); + } + }; +}; + +export function deleteFromTimelines(id) { + return (dispatch, getState) => { + const accountId = getState().getIn(['statuses', id, 'account']); + const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => [status.get('id'), status.get('account')]); + const reblogOf = getState().getIn(['statuses', id, 'reblog'], null); + + dispatch({ + type: TIMELINE_DELETE, + id, + accountId, + references, + reblogOf, + }); + }; +}; + +export function refreshTimelineRequest(timeline, skipLoading) { + return { + type: TIMELINE_REFRESH_REQUEST, + timeline, + skipLoading, + }; +}; + +export function refreshTimeline(timelineId, path, params = {}) { + return function (dispatch, getState) { + const timeline = getState().getIn(['timelines', timelineId], ImmutableMap()); + + if (timeline.get('isLoading') || timeline.get('online')) { + return; + } + + const ids = timeline.get('items', ImmutableList()); + const newestId = ids.size > 0 ? ids.first() : null; + + let skipLoading = timeline.get('loaded'); + + if (newestId !== null) { + params.since_id = newestId; + } + + dispatch(refreshTimelineRequest(timelineId, skipLoading)); + + api(getState).get(path, { params }).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(refreshTimelineSuccess(timelineId, response.data, skipLoading, next ? next.uri : null)); + }).catch(error => { + dispatch(refreshTimelineFail(timelineId, error, skipLoading)); + }); + }; +}; + +export const refreshHomeTimeline = () => refreshTimeline('home', '/api/v1/timelines/home'); +export const refreshPublicTimeline = () => refreshTimeline('public', '/api/v1/timelines/public'); +export const refreshCommunityTimeline = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true }); +export const refreshDirectTimeline = () => refreshTimeline('direct', '/api/v1/timelines/direct'); +export const refreshAccountTimeline = accountId => refreshTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`); +export const refreshAccountMediaTimeline = accountId => refreshTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true }); +export const refreshHashtagTimeline = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`); + +export function refreshTimelineFail(timeline, error, skipLoading) { + return { + type: TIMELINE_REFRESH_FAIL, + timeline, + error, + skipLoading, + skipAlert: error.response && error.response.status === 404, + }; +}; + +export function expandTimeline(timelineId, path, params = {}) { + return (dispatch, getState) => { + const timeline = getState().getIn(['timelines', timelineId], ImmutableMap()); + const ids = timeline.get('items', ImmutableList()); + + if (timeline.get('isLoading') || ids.size === 0) { + return; + } + + params.max_id = ids.last(); + params.limit = 10; + + dispatch(expandTimelineRequest(timelineId)); + + api(getState).get(path, { params }).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null)); + }).catch(error => { + dispatch(expandTimelineFail(timelineId, error)); + }); + }; +}; + +export const expandHomeTimeline = () => expandTimeline('home', '/api/v1/timelines/home'); +export const expandPublicTimeline = () => expandTimeline('public', '/api/v1/timelines/public'); +export const expandCommunityTimeline = () => expandTimeline('community', '/api/v1/timelines/public', { local: true }); +export const expandDirectTimeline = () => expandTimeline('direct', '/api/v1/timelines/direct'); +export const expandAccountTimeline = accountId => expandTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`); +export const expandAccountMediaTimeline = accountId => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true }); +export const expandHashtagTimeline = hashtag => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`); + +export function expandTimelineRequest(timeline) { + return { + type: TIMELINE_EXPAND_REQUEST, + timeline, + }; +}; + +export function expandTimelineSuccess(timeline, statuses, next) { + return { + type: TIMELINE_EXPAND_SUCCESS, + timeline, + statuses, + next, + }; +}; + +export function expandTimelineFail(timeline, error) { + return { + type: TIMELINE_EXPAND_FAIL, + timeline, + error, + }; +}; + +export function scrollTopTimeline(timeline, top) { + return { + type: TIMELINE_SCROLL_TOP, + timeline, + top, + }; +}; + +export function connectTimeline(timeline) { + return { + type: TIMELINE_CONNECT, + timeline, + }; +}; + +export function disconnectTimeline(timeline) { + return { + type: TIMELINE_DISCONNECT, + timeline, + }; +}; diff --git a/app/javascript/flavours/glitch/components/account.js b/app/javascript/flavours/glitch/components/account.js new file mode 100644 index 000000000..c8dacb0ab --- /dev/null +++ b/app/javascript/flavours/glitch/components/account.js @@ -0,0 +1,116 @@ +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; +import Avatar from './avatar'; +import DisplayName from './display_name'; +import Permalink from './permalink'; +import IconButton from './icon_button'; +import { defineMessages, injectIntl } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import { me } from 'flavours/glitch/util/initial_state'; + +const messages = defineMessages({ + follow: { id: 'account.follow', defaultMessage: 'Follow' }, + unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, + requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' }, + unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, + unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' }, + mute_notifications: { id: 'account.mute_notifications', defaultMessage: 'You are not currently muting notifications from @{name}. Click to mute notifications' }, + unmute_notifications: { id: 'account.unmute_notifications', defaultMessage: 'You are currently muting notifications from @{name}. Click to unmute notifications' }, +}); + +@injectIntl +export default class Account extends ImmutablePureComponent { + + static propTypes = { + account: ImmutablePropTypes.map.isRequired, + onFollow: PropTypes.func.isRequired, + onBlock: PropTypes.func.isRequired, + onMute: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + hidden: PropTypes.bool, + }; + + handleFollow = () => { + this.props.onFollow(this.props.account); + } + + handleBlock = () => { + this.props.onBlock(this.props.account); + } + + handleMute = () => { + this.props.onMute(this.props.account); + } + + handleMuteNotifications = () => { + this.props.onMuteNotifications(this.props.account, true); + } + + handleUnmuteNotifications = () => { + this.props.onMuteNotifications(this.props.account, false); + } + + render () { + const { account, intl, hidden } = this.props; + + if (!account) { + return
; + } + + if (hidden) { + return ( +
+ {account.get('display_name')} + {account.get('username')} +
+ ); + } + + let buttons; + + if (account.get('id') !== me && account.get('relationship', null) !== null) { + const following = account.getIn(['relationship', 'following']); + const requested = account.getIn(['relationship', 'requested']); + const blocking = account.getIn(['relationship', 'blocking']); + const muting = account.getIn(['relationship', 'muting']); + + if (requested) { + buttons = ; + } else if (blocking) { + buttons = ; + } else if (muting) { + let hidingNotificationsButton; + if (muting.get('notifications')) { + hidingNotificationsButton = ; + } else { + hidingNotificationsButton = ; + } + buttons = ( +
+ + {hidingNotificationsButton} +
+ ); + } else { + buttons = ; + } + } + + return ( +
+
+ +
+ +
+ +
+ {buttons} +
+
+
+ ); + } + +} diff --git a/app/javascript/flavours/glitch/components/attachment_list.js b/app/javascript/flavours/glitch/components/attachment_list.js new file mode 100644 index 000000000..b3d00b335 --- /dev/null +++ b/app/javascript/flavours/glitch/components/attachment_list.js @@ -0,0 +1,33 @@ +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import ImmutablePureComponent from 'react-immutable-pure-component'; + +const filename = url => url.split('/').pop().split('#')[0].split('?')[0]; + +export default class AttachmentList extends ImmutablePureComponent { + + static propTypes = { + media: ImmutablePropTypes.list.isRequired, + }; + + render () { + const { media } = this.props; + + return ( +
+
+ +
+ + +
+ ); + } + +} diff --git a/app/javascript/flavours/glitch/components/autosuggest_emoji.js b/app/javascript/flavours/glitch/components/autosuggest_emoji.js new file mode 100644 index 000000000..79e113d9c --- /dev/null +++ b/app/javascript/flavours/glitch/components/autosuggest_emoji.js @@ -0,0 +1,42 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import unicodeMapping from 'flavours/glitch/util/emoji/emoji_unicode_mapping_light'; + +const assetHost = process.env.CDN_HOST || ''; + +export default class AutosuggestEmoji extends React.PureComponent { + + static propTypes = { + emoji: PropTypes.object.isRequired, + }; + + render () { + const { emoji } = this.props; + let url; + + if (emoji.custom) { + url = emoji.imageUrl; + } else { + const mapping = unicodeMapping[emoji.native] || unicodeMapping[emoji.native.replace(/\uFE0F$/, '')]; + + if (!mapping) { + return null; + } + + url = `${assetHost}/emoji/${mapping.filename}.svg`; + } + + return ( +
+ {emoji.native + + {emoji.colons} +
+ ); + } + +} diff --git a/app/javascript/flavours/glitch/components/autosuggest_textarea.js b/app/javascript/flavours/glitch/components/autosuggest_textarea.js new file mode 100644 index 000000000..551528e5a --- /dev/null +++ b/app/javascript/flavours/glitch/components/autosuggest_textarea.js @@ -0,0 +1,222 @@ +import React from 'react'; +import AutosuggestAccountContainer from 'flavours/glitch/features/compose/containers/autosuggest_account_container'; +import AutosuggestEmoji from './autosuggest_emoji'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; +import { isRtl } from 'flavours/glitch/util/rtl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import Textarea from 'react-textarea-autosize'; +import classNames from 'classnames'; + +const textAtCursorMatchesToken = (str, caretPosition) => { + let word; + + let left = str.slice(0, caretPosition).search(/[^\s\u200B]+$/); + let right = str.slice(caretPosition).search(/[\s\u200B]/); + + if (right < 0) { + word = str.slice(left); + } else { + word = str.slice(left, right + caretPosition); + } + + if (!word || word.trim().length < 3 || ['@', ':'].indexOf(word[0]) === -1) { + return [null, null]; + } + + word = word.trim().toLowerCase(); + + if (word.length > 0) { + return [left + 1, word]; + } else { + return [null, null]; + } +}; + +export default class AutosuggestTextarea extends ImmutablePureComponent { + + static propTypes = { + value: PropTypes.string, + suggestions: ImmutablePropTypes.list, + disabled: PropTypes.bool, + placeholder: PropTypes.string, + onSuggestionSelected: PropTypes.func.isRequired, + onSuggestionsClearRequested: PropTypes.func.isRequired, + onSuggestionsFetchRequested: PropTypes.func.isRequired, + onChange: PropTypes.func.isRequired, + onKeyUp: PropTypes.func, + onKeyDown: PropTypes.func, + onPaste: PropTypes.func.isRequired, + autoFocus: PropTypes.bool, + }; + + static defaultProps = { + autoFocus: true, + }; + + state = { + suggestionsHidden: false, + selectedSuggestion: 0, + lastToken: null, + tokenStart: 0, + }; + + onChange = (e) => { + const [ tokenStart, token ] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart); + + if (token !== null && this.state.lastToken !== token) { + this.setState({ lastToken: token, selectedSuggestion: 0, tokenStart }); + this.props.onSuggestionsFetchRequested(token); + } else if (token === null) { + this.setState({ lastToken: null }); + this.props.onSuggestionsClearRequested(); + } + + this.props.onChange(e); + } + + onKeyDown = (e) => { + const { suggestions, disabled } = this.props; + const { selectedSuggestion, suggestionsHidden } = this.state; + + if (disabled) { + e.preventDefault(); + return; + } + + switch(e.key) { + case 'Escape': + if (!suggestionsHidden) { + e.preventDefault(); + this.setState({ suggestionsHidden: true }); + } + + break; + case 'ArrowDown': + if (suggestions.size > 0 && !suggestionsHidden) { + e.preventDefault(); + this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) }); + } + + break; + case 'ArrowUp': + if (suggestions.size > 0 && !suggestionsHidden) { + e.preventDefault(); + this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) }); + } + + break; + case 'Enter': + case 'Tab': + // Select suggestion + if (this.state.lastToken !== null && suggestions.size > 0 && !suggestionsHidden) { + e.preventDefault(); + e.stopPropagation(); + this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion)); + } + + break; + } + + if (e.defaultPrevented || !this.props.onKeyDown) { + return; + } + + this.props.onKeyDown(e); + } + + onKeyUp = e => { + if (e.key === 'Escape' && this.state.suggestionsHidden) { + document.querySelector('.ui').parentElement.focus(); + } + + if (this.props.onKeyUp) { + this.props.onKeyUp(e); + } + } + + onBlur = () => { + this.setState({ suggestionsHidden: true }); + } + + onSuggestionClick = (e) => { + const suggestion = this.props.suggestions.get(e.currentTarget.getAttribute('data-index')); + e.preventDefault(); + this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion); + this.textarea.focus(); + } + + componentWillReceiveProps (nextProps) { + if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden) { + this.setState({ suggestionsHidden: false }); + } + } + + setTextarea = (c) => { + this.textarea = c; + } + + onPaste = (e) => { + if (e.clipboardData && e.clipboardData.files.length === 1) { + this.props.onPaste(e.clipboardData.files); + e.preventDefault(); + } + } + + renderSuggestion = (suggestion, i) => { + const { selectedSuggestion } = this.state; + let inner, key; + + if (typeof suggestion === 'object') { + inner = ; + key = suggestion.id; + } else { + inner = ; + key = suggestion; + } + + return ( +
+ {inner} +
+ ); + } + + render () { + const { value, suggestions, disabled, placeholder, autoFocus } = this.props; + const { suggestionsHidden } = this.state; + const style = { direction: 'ltr' }; + + if (isRtl(value)) { + style.direction = 'rtl'; + } + + return ( +
+