summaryrefslogtreecommitdiffstats
path: root/webapp/components
diff options
context:
space:
mode:
Diffstat (limited to 'webapp/components')
-rw-r--r--webapp/components/about_build_modal.jsx213
-rw-r--r--webapp/components/access_history_modal/access_history_modal.jsx109
-rw-r--r--webapp/components/access_history_modal/index.js24
-rw-r--r--webapp/components/activity_log_modal/activity_log_modal.jsx338
-rw-r--r--webapp/components/activity_log_modal/index.js25
-rw-r--r--webapp/components/add_users_to_team/add_users_to_team.jsx264
-rw-r--r--webapp/components/add_users_to_team/index.js24
-rw-r--r--webapp/components/admin_console/admin_console.jsx68
-rw-r--r--webapp/components/admin_console/admin_navbar_dropdown.jsx223
-rw-r--r--webapp/components/admin_console/admin_settings.jsx147
-rw-r--r--webapp/components/admin_console/admin_sidebar.jsx702
-rw-r--r--webapp/components/admin_console/admin_sidebar_category.jsx86
-rw-r--r--webapp/components/admin_console/admin_sidebar_header.jsx69
-rw-r--r--webapp/components/admin_console/admin_sidebar_section.jsx102
-rw-r--r--webapp/components/admin_console/audits/audits.jsx101
-rw-r--r--webapp/components/admin_console/audits/index.js27
-rw-r--r--webapp/components/admin_console/banner.jsx39
-rw-r--r--webapp/components/admin_console/boolean_setting.jsx99
-rw-r--r--webapp/components/admin_console/brand_image_setting.jsx213
-rw-r--r--webapp/components/admin_console/client_versions_settings.jsx169
-rw-r--r--webapp/components/admin_console/cluster_settings.jsx273
-rw-r--r--webapp/components/admin_console/cluster_table.jsx172
-rw-r--r--webapp/components/admin_console/cluster_table_container.jsx72
-rw-r--r--webapp/components/admin_console/color_setting.jsx119
-rw-r--r--webapp/components/admin_console/compliance_reports/compliance_reports.jsx394
-rw-r--r--webapp/components/admin_console/compliance_reports/index.js46
-rw-r--r--webapp/components/admin_console/compliance_settings.jsx126
-rw-r--r--webapp/components/admin_console/configuration_settings.jsx356
-rw-r--r--webapp/components/admin_console/connection_security_dropdown_setting.jsx191
-rw-r--r--webapp/components/admin_console/connection_settings.jsx87
-rw-r--r--webapp/components/admin_console/custom_brand_settings.jsx156
-rw-r--r--webapp/components/admin_console/custom_emoji_settings.jsx121
-rw-r--r--webapp/components/admin_console/custom_integrations_settings.jsx197
-rw-r--r--webapp/components/admin_console/database_settings.jsx238
-rw-r--r--webapp/components/admin_console/developer_settings.jsx106
-rw-r--r--webapp/components/admin_console/dropdown_setting.jsx66
-rw-r--r--webapp/components/admin_console/elasticsearch_settings.jsx333
-rw-r--r--webapp/components/admin_console/elasticsearch_status/index.js28
-rw-r--r--webapp/components/admin_console/elasticsearch_status/status.jsx361
-rw-r--r--webapp/components/admin_console/email_authentication_settings.jsx102
-rw-r--r--webapp/components/admin_console/email_connection_test.jsx130
-rw-r--r--webapp/components/admin_console/email_settings.jsx395
-rw-r--r--webapp/components/admin_console/external_service_settings.jsx66
-rw-r--r--webapp/components/admin_console/file_upload_setting.jsx126
-rw-r--r--webapp/components/admin_console/generated_setting.jsx106
-rw-r--r--webapp/components/admin_console/gitlab_settings.jsx179
-rw-r--r--webapp/components/admin_console/index.js27
-rw-r--r--webapp/components/admin_console/ldap_settings.jsx504
-rw-r--r--webapp/components/admin_console/legal_and_support_settings.jsx159
-rw-r--r--webapp/components/admin_console/license_settings.jsx286
-rw-r--r--webapp/components/admin_console/link_previews_settings.jsx64
-rw-r--r--webapp/components/admin_console/localization_settings.jsx134
-rw-r--r--webapp/components/admin_console/log_settings.jsx282
-rw-r--r--webapp/components/admin_console/manage_roles_modal/index.js25
-rw-r--r--webapp/components/admin_console/manage_roles_modal/manage_roles_modal.jsx349
-rw-r--r--webapp/components/admin_console/manage_teams_modal/manage_teams_dropdown.jsx148
-rw-r--r--webapp/components/admin_console/manage_teams_modal/manage_teams_modal.jsx218
-rw-r--r--webapp/components/admin_console/manage_teams_modal/remove_from_team_button.jsx54
-rw-r--r--webapp/components/admin_console/manage_tokens_modal/index.js27
-rw-r--r--webapp/components/admin_console/manage_tokens_modal/manage_tokens_modal.jsx181
-rw-r--r--webapp/components/admin_console/metrics_settings.jsx94
-rw-r--r--webapp/components/admin_console/mfa_settings.jsx97
-rw-r--r--webapp/components/admin_console/multiselect_settings.jsx81
-rw-r--r--webapp/components/admin_console/native_app_link_settings.jsx102
-rw-r--r--webapp/components/admin_console/oauth_settings.jsx430
-rw-r--r--webapp/components/admin_console/password_settings.jsx281
-rw-r--r--webapp/components/admin_console/plugin_settings/index.js27
-rw-r--r--webapp/components/admin_console/plugin_settings/plugin_settings.jsx293
-rw-r--r--webapp/components/admin_console/policy_settings.jsx413
-rw-r--r--webapp/components/admin_console/post_edit_setting.jsx101
-rw-r--r--webapp/components/admin_console/privacy_settings.jsx83
-rw-r--r--webapp/components/admin_console/public_link_settings.jsx84
-rw-r--r--webapp/components/admin_console/push_settings.jsx233
-rw-r--r--webapp/components/admin_console/radio_setting.jsx65
-rw-r--r--webapp/components/admin_console/rate_settings.jsx179
-rw-r--r--webapp/components/admin_console/remove_file_setting.jsx62
-rw-r--r--webapp/components/admin_console/request_button/request_button.jsx262
-rw-r--r--webapp/components/admin_console/reset_password_modal.jsx165
-rw-r--r--webapp/components/admin_console/revoke_token_button/index.js24
-rw-r--r--webapp/components/admin_console/revoke_token_button/revoke_token_button.jsx56
-rw-r--r--webapp/components/admin_console/saml_settings.jsx584
-rw-r--r--webapp/components/admin_console/save_button.jsx64
-rw-r--r--webapp/components/admin_console/server_logs/index.js27
-rw-r--r--webapp/components/admin_console/server_logs/logs.jsx123
-rw-r--r--webapp/components/admin_console/session_settings.jsx127
-rw-r--r--webapp/components/admin_console/setting.jsx35
-rw-r--r--webapp/components/admin_console/settings_group.jsx44
-rw-r--r--webapp/components/admin_console/signup_settings.jsx117
-rw-r--r--webapp/components/admin_console/storage_settings.jsx340
-rw-r--r--webapp/components/admin_console/system_users/index.js31
-rw-r--r--webapp/components/admin_console/system_users/system_users.jsx372
-rw-r--r--webapp/components/admin_console/system_users/system_users_dropdown.jsx529
-rw-r--r--webapp/components/admin_console/system_users/system_users_list.jsx295
-rw-r--r--webapp/components/admin_console/text_setting.jsx88
-rw-r--r--webapp/components/admin_console/users_and_teams_settings.jsx218
-rw-r--r--webapp/components/admin_console/webrtc_settings.jsx218
-rw-r--r--webapp/components/admin_console/webserver_mode_dropdown_setting.jsx101
-rw-r--r--webapp/components/analytics/doughnut_chart.jsx90
-rw-r--r--webapp/components/analytics/line_chart.jsx121
-rw-r--r--webapp/components/analytics/statistic_count.jsx35
-rw-r--r--webapp/components/analytics/system_analytics.jsx492
-rw-r--r--webapp/components/analytics/table_chart.jsx62
-rw-r--r--webapp/components/analytics/team_analytics/index.js36
-rw-r--r--webapp/components/analytics/team_analytics/team_analytics.jsx369
-rw-r--r--webapp/components/announcement_bar/announcement_bar.jsx322
-rw-r--r--webapp/components/announcement_bar/index.js16
-rw-r--r--webapp/components/at_mention/at_mention.jsx100
-rw-r--r--webapp/components/at_mention/index.jsx17
-rw-r--r--webapp/components/audio_video_preview.jsx116
-rw-r--r--webapp/components/audit_table.jsx644
-rw-r--r--webapp/components/authorize.jsx156
-rw-r--r--webapp/components/autosize_textarea.jsx102
-rw-r--r--webapp/components/backstage/backstage_controller.jsx82
-rw-r--r--webapp/components/backstage/components/backstage_category.jsx75
-rw-r--r--webapp/components/backstage/components/backstage_header.jsx41
-rw-r--r--webapp/components/backstage/components/backstage_list.jsx114
-rw-r--r--webapp/components/backstage/components/backstage_navbar.jsx43
-rw-r--r--webapp/components/backstage/components/backstage_section.jsx80
-rw-r--r--webapp/components/backstage/components/backstage_sidebar.jsx149
-rw-r--r--webapp/components/bootstrap_span.jsx22
-rw-r--r--webapp/components/change_url_modal.jsx248
-rw-r--r--webapp/components/channel_header.jsx952
-rw-r--r--webapp/components/channel_info_modal.jsx156
-rw-r--r--webapp/components/channel_invite_button.jsx73
-rw-r--r--webapp/components/channel_invite_modal/channel_invite_modal.jsx195
-rw-r--r--webapp/components/channel_invite_modal/index.js26
-rw-r--r--webapp/components/channel_members_dropdown/channel_members_dropdown.jsx268
-rw-r--r--webapp/components/channel_members_dropdown/index.js24
-rw-r--r--webapp/components/channel_members_modal.jsx95
-rw-r--r--webapp/components/channel_notifications_modal.jsx651
-rw-r--r--webapp/components/channel_select.jsx116
-rw-r--r--webapp/components/channel_view.jsx120
-rw-r--r--webapp/components/claim/claim_controller.jsx61
-rw-r--r--webapp/components/claim/components/email_to_ldap.jsx273
-rw-r--r--webapp/components/claim/components/email_to_oauth.jsx178
-rw-r--r--webapp/components/claim/components/ldap_to_email.jsx251
-rw-r--r--webapp/components/claim/components/oauth_to_email.jsx147
-rw-r--r--webapp/components/code_preview.jsx142
-rw-r--r--webapp/components/common/comment_icon.jsx61
-rw-r--r--webapp/components/confirm_modal.jsx164
-rw-r--r--webapp/components/create_comment/create_comment.jsx629
-rw-r--r--webapp/components/create_comment/index.js16
-rw-r--r--webapp/components/create_post.jsx814
-rw-r--r--webapp/components/create_team/components/display_name.jsx131
-rw-r--r--webapp/components/create_team/components/team_url.jsx250
-rw-r--r--webapp/components/create_team/create_team_controller.jsx96
-rw-r--r--webapp/components/delete_channel_modal.jsx107
-rw-r--r--webapp/components/delete_modal_trigger.jsx72
-rw-r--r--webapp/components/delete_post_modal.jsx165
-rw-r--r--webapp/components/do_verify_email.jsx81
-rw-r--r--webapp/components/dot_menu/dot_menu.jsx241
-rw-r--r--webapp/components/dot_menu/dot_menu_edit.jsx58
-rw-r--r--webapp/components/dot_menu/dot_menu_flag.jsx83
-rw-r--r--webapp/components/dot_menu/dot_menu_item.jsx119
-rw-r--r--webapp/components/dot_menu/index.js27
-rw-r--r--webapp/components/edit_channel_header_modal.jsx231
-rw-r--r--webapp/components/edit_channel_purpose_modal.jsx188
-rw-r--r--webapp/components/edit_post_modal.jsx312
-rw-r--r--webapp/components/emoji/components/add_emoji.jsx316
-rw-r--r--webapp/components/emoji/components/delete_emoji_modal.jsx50
-rw-r--r--webapp/components/emoji/components/emoji_list.jsx228
-rw-r--r--webapp/components/emoji/components/emoji_list_item.jsx103
-rw-r--r--webapp/components/emoji_picker/components/emoji_picker_category.jsx43
-rw-r--r--webapp/components/emoji_picker/components/emoji_picker_item.jsx88
-rw-r--r--webapp/components/emoji_picker/components/emoji_picker_preview.jsx74
-rw-r--r--webapp/components/emoji_picker/emoji_picker.jsx520
-rw-r--r--webapp/components/emoji_picker/emoji_picker_container.jsx48
-rw-r--r--webapp/components/emoji_picker/emoji_picker_overlay.jsx73
-rw-r--r--webapp/components/error_page.jsx212
-rw-r--r--webapp/components/file_attachment.jsx205
-rw-r--r--webapp/components/file_attachment_list/file_attachment_list.jsx106
-rw-r--r--webapp/components/file_attachment_list/index.js40
-rw-r--r--webapp/components/file_info_preview.jsx75
-rw-r--r--webapp/components/file_preview.jsx126
-rw-r--r--webapp/components/file_upload.jsx430
-rw-r--r--webapp/components/file_upload_overlay.jsx50
-rw-r--r--webapp/components/filtered_user_list.jsx245
-rw-r--r--webapp/components/form_error.jsx84
-rw-r--r--webapp/components/get_android_app/get_android_app.jsx73
-rw-r--r--webapp/components/get_ios_app/get_ios_app.jsx66
-rw-r--r--webapp/components/get_link_modal.jsx139
-rw-r--r--webapp/components/get_post_link_modal.jsx61
-rw-r--r--webapp/components/get_public_link_modal.jsx76
-rw-r--r--webapp/components/get_team_invite_link_modal.jsx59
-rw-r--r--webapp/components/header_footer_template.jsx110
-rw-r--r--webapp/components/help/components/attaching.jsx84
-rw-r--r--webapp/components/help/components/commands.jsx77
-rw-r--r--webapp/components/help/components/composing.jsx77
-rw-r--r--webapp/components/help/components/formatting.jsx133
-rw-r--r--webapp/components/help/components/mentioning.jsx79
-rw-r--r--webapp/components/help/components/messaging.jsx78
-rw-r--r--webapp/components/help/help_controller.jsx29
-rw-r--r--webapp/components/integrations/components/abstract_incoming_webhook.jsx253
-rw-r--r--webapp/components/integrations/components/abstract_outgoing_webhook.jsx483
-rw-r--r--webapp/components/integrations/components/add_command/add_command.jsx615
-rw-r--r--webapp/components/integrations/components/add_command/index.js25
-rw-r--r--webapp/components/integrations/components/add_incoming_webhook/add_incoming_webhook.jsx68
-rw-r--r--webapp/components/integrations/components/add_incoming_webhook/index.js25
-rw-r--r--webapp/components/integrations/components/add_oauth_app/add_oauth_app.jsx441
-rw-r--r--webapp/components/integrations/components/add_oauth_app/index.js25
-rw-r--r--webapp/components/integrations/components/add_outgoing_webhook/add_outgoing_webhook.jsx69
-rw-r--r--webapp/components/integrations/components/add_outgoing_webhook/index.js25
-rw-r--r--webapp/components/integrations/components/commands_container/commands_container.jsx78
-rw-r--r--webapp/components/integrations/components/commands_container/index.js29
-rw-r--r--webapp/components/integrations/components/confirm_integration/confirm_integration.jsx258
-rw-r--r--webapp/components/integrations/components/confirm_integration/index.js16
-rw-r--r--webapp/components/integrations/components/delete_integration.jsx51
-rw-r--r--webapp/components/integrations/components/edit_command/edit_command.jsx727
-rw-r--r--webapp/components/integrations/components/edit_command/index.js31
-rw-r--r--webapp/components/integrations/components/edit_incoming_webhook/edit_incoming_webhook.jsx112
-rw-r--r--webapp/components/integrations/components/edit_incoming_webhook/index.js30
-rw-r--r--webapp/components/integrations/components/edit_outgoing_webhook/edit_outgoing_webhook.jsx169
-rw-r--r--webapp/components/integrations/components/edit_outgoing_webhook/index.js30
-rw-r--r--webapp/components/integrations/components/installed_command.jsx176
-rw-r--r--webapp/components/integrations/components/installed_commands/index.js25
-rw-r--r--webapp/components/integrations/components/installed_commands/installed_commands.jsx160
-rw-r--r--webapp/components/integrations/components/installed_incoming_webhook.jsx147
-rw-r--r--webapp/components/integrations/components/installed_incoming_webhooks.jsx178
-rw-r--r--webapp/components/integrations/components/installed_oauth_app.jsx270
-rw-r--r--webapp/components/integrations/components/installed_oauth_apps/index.js31
-rw-r--r--webapp/components/integrations/components/installed_oauth_apps/installed_oauth_apps.jsx165
-rw-r--r--webapp/components/integrations/components/installed_outgoing_webhook.jsx244
-rw-r--r--webapp/components/integrations/components/installed_outgoing_webhooks.jsx182
-rw-r--r--webapp/components/integrations/components/integration_option.jsx41
-rw-r--r--webapp/components/integrations/components/integrations.jsx175
-rw-r--r--webapp/components/invite_member_modal.jsx538
-rw-r--r--webapp/components/leave_team_modal.jsx131
-rw-r--r--webapp/components/loading_screen.jsx51
-rw-r--r--webapp/components/logged_in.jsx156
-rw-r--r--webapp/components/login/components/login_mfa.jsx95
-rw-r--r--webapp/components/login/login_controller.jsx643
-rw-r--r--webapp/components/markdown_image.jsx67
-rw-r--r--webapp/components/member_list_channel/index.js24
-rw-r--r--webapp/components/member_list_channel/member_list_channel.jsx173
-rw-r--r--webapp/components/member_list_team/index.js24
-rw-r--r--webapp/components/member_list_team/member_list_team.jsx168
-rw-r--r--webapp/components/message_wrapper.jsx42
-rw-r--r--webapp/components/mfa/components/confirm.jsx79
-rw-r--r--webapp/components/mfa/components/setup.jsx156
-rw-r--r--webapp/components/mfa/mfa_controller.jsx89
-rw-r--r--webapp/components/modals/leave_private_channel_modal.jsx120
-rw-r--r--webapp/components/more_channels/index.js24
-rw-r--r--webapp/components/more_channels/more_channels.jsx206
-rw-r--r--webapp/components/more_direct_channels/index.js25
-rw-r--r--webapp/components/more_direct_channels/more_direct_channels.jsx345
-rw-r--r--webapp/components/msg_typing.jsx99
-rw-r--r--webapp/components/multiselect/multiselect.jsx271
-rw-r--r--webapp/components/multiselect/multiselect_list.jsx171
-rw-r--r--webapp/components/navbar.jsx936
-rw-r--r--webapp/components/needs_team/index.js25
-rw-r--r--webapp/components/needs_team/needs_team.jsx242
-rw-r--r--webapp/components/new_channel_flow.jsx247
-rw-r--r--webapp/components/new_channel_modal/index.js21
-rw-r--r--webapp/components/new_channel_modal/new_channel_modal.jsx395
-rw-r--r--webapp/components/notify_counts.jsx46
-rw-r--r--webapp/components/password_reset_form.jsx131
-rw-r--r--webapp/components/password_reset_send_link.jsx156
-rw-r--r--webapp/components/pdf_preview.jsx178
-rw-r--r--webapp/components/permalink_view.jsx105
-rw-r--r--webapp/components/popover_list_members/index.js24
-rw-r--r--webapp/components/popover_list_members/popover_list_members.jsx294
-rw-r--r--webapp/components/post_deleted_modal.jsx71
-rw-r--r--webapp/components/post_view/commented_on_files_message/commented_on_files_message.jsx61
-rw-r--r--webapp/components/post_view/commented_on_files_message/index.js36
-rw-r--r--webapp/components/post_view/date_separator.jsx32
-rw-r--r--webapp/components/post_view/failed_post_options/failed_post_options.jsx94
-rw-r--r--webapp/components/post_view/failed_post_options/index.js24
-rw-r--r--webapp/components/post_view/floating_timestamp.jsx53
-rw-r--r--webapp/components/post_view/index.js54
-rw-r--r--webapp/components/post_view/new_message_indicator.jsx72
-rw-r--r--webapp/components/post_view/post/index.js34
-rw-r--r--webapp/components/post_view/post/post.jsx300
-rw-r--r--webapp/components/post_view/post_attachment.jsx359
-rw-r--r--webapp/components/post_view/post_attachment_list.jsx41
-rw-r--r--webapp/components/post_view/post_attachment_opengraph/index.js28
-rw-r--r--webapp/components/post_view/post_attachment_opengraph/post_attachment_opengraph.jsx328
-rw-r--r--webapp/components/post_view/post_body/index.js30
-rw-r--r--webapp/components/post_view/post_body/post_body.jsx200
-rw-r--r--webapp/components/post_view/post_body_additional_content.jsx273
-rw-r--r--webapp/components/post_view/post_flag_icon.jsx95
-rw-r--r--webapp/components/post_view/post_header/index.js18
-rw-r--r--webapp/components/post_view/post_header/post_header.jsx159
-rw-r--r--webapp/components/post_view/post_image.jsx105
-rw-r--r--webapp/components/post_view/post_info/index.js31
-rw-r--r--webapp/components/post_view/post_info/post_info.jsx275
-rw-r--r--webapp/components/post_view/post_list.jsx582
-rw-r--r--webapp/components/post_view/post_message_view/index.js41
-rw-r--r--webapp/components/post_view/post_message_view/post_message_view.jsx215
-rw-r--r--webapp/components/post_view/post_message_view/system_message_helpers.jsx232
-rw-r--r--webapp/components/post_view/post_time.jsx96
-rw-r--r--webapp/components/post_view/reaction/index.js52
-rw-r--r--webapp/components/post_view/reaction/reaction.jsx254
-rw-r--r--webapp/components/post_view/reaction_list/index.js33
-rw-r--r--webapp/components/post_view/reaction_list/reaction_list.jsx88
-rw-r--r--webapp/components/post_view/scroll_to_bottom_arrows.jsx37
-rw-r--r--webapp/components/profile_picture.jsx115
-rw-r--r--webapp/components/profile_popover.jsx301
-rw-r--r--webapp/components/quick_switch_modal/index.js15
-rw-r--r--webapp/components/quick_switch_modal/quick_switch_modal.jsx330
-rw-r--r--webapp/components/removed_from_channel_modal.jsx145
-rw-r--r--webapp/components/rename_channel_modal.jsx344
-rw-r--r--webapp/components/reset_status_modal/index.js34
-rw-r--r--webapp/components/reset_status_modal/reset_status_modal.jsx142
-rw-r--r--webapp/components/rhs_comment.jsx476
-rw-r--r--webapp/components/rhs_header_post.jsx217
-rw-r--r--webapp/components/rhs_root_post.jsx450
-rw-r--r--webapp/components/rhs_thread/index.js37
-rw-r--r--webapp/components/rhs_thread/rhs_thread.jsx473
-rw-r--r--webapp/components/root.jsx147
-rw-r--r--webapp/components/search_bar.jsx406
-rw-r--r--webapp/components/search_results.jsx367
-rw-r--r--webapp/components/search_results_header.jsx153
-rw-r--r--webapp/components/search_results_item.jsx353
-rw-r--r--webapp/components/searchable_channel_list.jsx218
-rw-r--r--webapp/components/searchable_user_list/searchable_user_list.jsx250
-rw-r--r--webapp/components/searchable_user_list/searchable_user_list_container.jsx74
-rw-r--r--webapp/components/select_team/components/select_team_item.jsx80
-rw-r--r--webapp/components/select_team/index.js24
-rw-r--r--webapp/components/select_team/select_team.jsx242
-rw-r--r--webapp/components/setting_item_max.jsx175
-rw-r--r--webapp/components/setting_item_min.jsx73
-rw-r--r--webapp/components/setting_picture.jsx222
-rw-r--r--webapp/components/setting_upload.jsx129
-rw-r--r--webapp/components/settings_sidebar.jsx69
-rw-r--r--webapp/components/shortcuts_modal.jsx396
-rw-r--r--webapp/components/should_verify_email.jsx117
-rw-r--r--webapp/components/sidebar.jsx994
-rw-r--r--webapp/components/sidebar_header.jsx132
-rw-r--r--webapp/components/sidebar_header_dropdown.jsx643
-rw-r--r--webapp/components/sidebar_header_dropdown_button.jsx49
-rw-r--r--webapp/components/sidebar_right/index.js17
-rw-r--r--webapp/components/sidebar_right/sidebar_right.jsx264
-rw-r--r--webapp/components/sidebar_right_menu.jsx554
-rw-r--r--webapp/components/signup/components/signup_email.jsx520
-rw-r--r--webapp/components/signup/components/signup_ldap.jsx268
-rw-r--r--webapp/components/signup/signup_controller.jsx375
-rw-r--r--webapp/components/spinner_button.jsx46
-rw-r--r--webapp/components/status_dropdown/index.jsx33
-rw-r--r--webapp/components/status_dropdown/status_dropdown.jsx158
-rw-r--r--webapp/components/status_icon.jsx51
-rw-r--r--webapp/components/suggestion/at_mention_provider.jsx165
-rw-r--r--webapp/components/suggestion/channel_mention_provider.jsx143
-rw-r--r--webapp/components/suggestion/command_provider.jsx41
-rw-r--r--webapp/components/suggestion/emoticon_provider.jsx131
-rw-r--r--webapp/components/suggestion/provider.jsx38
-rw-r--r--webapp/components/suggestion/search_channel_provider.jsx91
-rw-r--r--webapp/components/suggestion/search_suggestion_list.jsx98
-rw-r--r--webapp/components/suggestion/search_user_provider.jsx91
-rw-r--r--webapp/components/suggestion/suggestion.jsx30
-rw-r--r--webapp/components/suggestion/suggestion_box.jsx441
-rw-r--r--webapp/components/suggestion/suggestion_list.jsx177
-rw-r--r--webapp/components/suggestion/switch_channel_provider.jsx283
-rw-r--r--webapp/components/suggestion/switch_team_provider.jsx96
-rw-r--r--webapp/components/team_general_tab.jsx602
-rw-r--r--webapp/components/team_import_tab.jsx227
-rw-r--r--webapp/components/team_members_dropdown/index.js28
-rw-r--r--webapp/components/team_members_dropdown/team_members_dropdown.jsx398
-rw-r--r--webapp/components/team_members_modal.jsx85
-rw-r--r--webapp/components/team_settings.jsx81
-rw-r--r--webapp/components/team_settings_modal.jsx133
-rw-r--r--webapp/components/team_sidebar/components/team_button.jsx127
-rw-r--r--webapp/components/team_sidebar/index.js24
-rw-r--r--webapp/components/team_sidebar/team_sidebar_controller.jsx189
-rw-r--r--webapp/components/textbox.jsx318
-rw-r--r--webapp/components/toggle_modal_button.jsx82
-rw-r--r--webapp/components/tutorial/tutorial_intro_screens.jsx331
-rw-r--r--webapp/components/tutorial/tutorial_tip.jsx247
-rw-r--r--webapp/components/tutorial/tutorial_view.jsx49
-rw-r--r--webapp/components/unread_channel_indicator.jsx41
-rw-r--r--webapp/components/user_list.jsx84
-rw-r--r--webapp/components/user_list_row.jsx110
-rw-r--r--webapp/components/user_profile.jsx121
-rw-r--r--webapp/components/user_settings/custom_theme_chooser.jsx476
-rw-r--r--webapp/components/user_settings/desktop_notification_settings.jsx464
-rw-r--r--webapp/components/user_settings/email_notification_setting.jsx262
-rw-r--r--webapp/components/user_settings/import_theme_modal.jsx218
-rw-r--r--webapp/components/user_settings/manage_languages.jsx135
-rw-r--r--webapp/components/user_settings/premade_theme_chooser.jsx72
-rw-r--r--webapp/components/user_settings/user_settings.jsx124
-rw-r--r--webapp/components/user_settings/user_settings_advanced.jsx587
-rw-r--r--webapp/components/user_settings/user_settings_display.jsx673
-rw-r--r--webapp/components/user_settings/user_settings_general/index.js24
-rw-r--r--webapp/components/user_settings/user_settings_general/user_settings_general.jsx1245
-rw-r--r--webapp/components/user_settings/user_settings_modal.jsx309
-rw-r--r--webapp/components/user_settings/user_settings_notifications.jsx911
-rw-r--r--webapp/components/user_settings/user_settings_security/index.js34
-rw-r--r--webapp/components/user_settings/user_settings_security/user_settings_security.jsx1469
-rw-r--r--webapp/components/user_settings/user_settings_theme.jsx362
-rw-r--r--webapp/components/view_image.jsx385
-rw-r--r--webapp/components/view_image_popover_bar.jsx93
-rw-r--r--webapp/components/webrtc/components/webrtc_header.jsx102
-rw-r--r--webapp/components/webrtc/components/webrtc_notification.jsx319
-rw-r--r--webapp/components/webrtc/components/webrtc_sidebar.jsx136
-rw-r--r--webapp/components/webrtc/webrtc_controller.jsx1244
-rw-r--r--webapp/components/youtube_video/index.js16
-rw-r--r--webapp/components/youtube_video/youtube_video.jsx245
396 files changed, 0 insertions, 73792 deletions
diff --git a/webapp/components/about_build_modal.jsx b/webapp/components/about_build_modal.jsx
deleted file mode 100644
index 4621cfc89..000000000
--- a/webapp/components/about_build_modal.jsx
+++ /dev/null
@@ -1,213 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {Modal} from 'react-bootstrap';
-
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import Constants from 'utils/constants.jsx';
-
-export default class AboutBuildModal extends React.Component {
- constructor(props) {
- super(props);
- this.doHide = this.doHide.bind(this);
- }
-
- doHide() {
- this.props.onModalDismissed();
- }
-
- render() {
- const config = global.window.mm_config;
- const license = global.window.mm_license;
- const mattermostLogo = Constants.MATTERMOST_ICON_SVG;
-
- let title = (
- <FormattedMessage
- id='about.teamEditiont0'
- defaultMessage='Team Edition'
- />
- );
-
- let subTitle = (
- <FormattedMessage
- id='about.teamEditionSt'
- defaultMessage='All your team communication in one place, instantly searchable and accessible anywhere.'
- />
- );
-
- let learnMore = (
- <div>
- <FormattedMessage
- id='about.teamEditionLearn'
- defaultMessage='Join the Mattermost community at '
- />
- <a
- target='_blank'
- rel='noopener noreferrer'
- href='http://www.mattermost.org/'
- >
- {'mattermost.org'}
- </a>
- </div>
- );
-
- let licensee;
- if (config.BuildEnterpriseReady === 'true') {
- title = (
- <FormattedMessage
- id='about.teamEditiont1'
- defaultMessage='Enterprise Edition'
- />
- );
-
- subTitle = (
- <FormattedMessage
- id='about.enterpriseEditionSt'
- defaultMessage='Modern communication from behind your firewall.'
- />
- );
-
- learnMore = (
- <div>
- <FormattedMessage
- id='about.enterpriseEditionLearn'
- defaultMessage='Learn more about Enterprise Edition at '
- />
- <a
- target='_blank'
- rel='noopener noreferrer'
- href='http://about.mattermost.com/'
- >
- {'about.mattermost.com'}
- </a>
- </div>
- );
-
- if (license.IsLicensed === 'true') {
- title = (
- <FormattedMessage
- id='about.enterpriseEditione1'
- defaultMessage='Enterprise Edition'
- />
- );
- licensee = (
- <div className='form-group'>
- <FormattedMessage
- id='about.licensed'
- defaultMessage='Licensed to:'
- />
- &nbsp;{license.Company}
- </div>
- );
- }
- }
-
- let version = '\u00a0' + config.Version;
- if (config.BuildNumber !== config.Version) {
- version += '\u00a0 (' + config.BuildNumber + ')';
- }
-
- return (
- <Modal
- dialogClassName='about-modal'
- show={this.props.show}
- onHide={this.doHide}
- >
- <Modal.Header closeButton={true}>
- <Modal.Title>
- <FormattedMessage
- id='about.title'
- defaultMessage='About Mattermost'
- />
- </Modal.Title>
- </Modal.Header>
- <Modal.Body>
- <div className='about-modal__content'>
- <div className='about-modal__logo'>
- <span
- className='icon'
- dangerouslySetInnerHTML={{__html: mattermostLogo}}
- />
- </div>
- <div>
- <h3 className='about-modal__title'>{'Mattermost'} {title}</h3>
- <p className='about-modal__subtitle padding-bottom'>{subTitle}</p>
- <div className='form-group less'>
- <div>
- <FormattedMessage
- id='about.version'
- defaultMessage='Version:'
- />
- <span id='versionString'>{version}</span>
- </div>
- <div>
- <FormattedMessage
- id='about.database'
- defaultMessage='Database:'
- />
- {'\u00a0' + config.SQLDriverName}
- </div>
- </div>
- {licensee}
- </div>
- </div>
- <div className='about-modal__footer'>
- {learnMore}
- <div className='form-group about-modal__copyright'>
- <FormattedMessage
- id='about.copyright'
- defaultMessage='Copyright 2015 - {currentYear} Mattermost, Inc. All rights reserved'
- values={{
- currentYear: new Date().getFullYear()
- }}
- />
- </div>
- </div>
- <div className='about-modal__notice form-group padding-top x2'>
- <p>
- <FormattedHTMLMessage
- id='about.notice'
- defaultMessage='Mattermost is made possible by the open source software used in our <a href="https://about.mattermost.com/platform-notice-txt/" target="_blank">platform</a>, <a href="https://about.mattermost.com/desktop-notice-txt/" target="_blank">desktop</a> and <a href="https://about.mattermost.com/mobile-notice-txt/" target="_blank">mobile</a> apps.'
- />
- </p>
- </div>
- <div className='about-modal__hash'>
- <p>
- <FormattedMessage
- id='about.hash'
- defaultMessage='Build Hash:'
- />
- &nbsp;{config.BuildHash}
- <br/>
- <FormattedMessage
- id='about.hashee'
- defaultMessage='EE Build Hash:'
- />
- &nbsp;{config.BuildHashEnterprise}
- </p>
- <p>
- <FormattedMessage
- id='about.date'
- defaultMessage='Build Date:'
- />
- &nbsp;{config.BuildDate}
- </p>
- </div>
- </Modal.Body>
- </Modal>
- );
- }
-}
-
-AboutBuildModal.defaultProps = {
- show: false
-};
-
-AboutBuildModal.propTypes = {
- show: PropTypes.bool.isRequired,
- onModalDismissed: PropTypes.func.isRequired
-};
diff --git a/webapp/components/access_history_modal/access_history_modal.jsx b/webapp/components/access_history_modal/access_history_modal.jsx
deleted file mode 100644
index fdd18669b..000000000
--- a/webapp/components/access_history_modal/access_history_modal.jsx
+++ /dev/null
@@ -1,109 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import LoadingScreen from 'components/loading_screen.jsx';
-import AuditTable from 'components/audit_table.jsx';
-
-import UserStore from 'stores/user_store.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-
-import $ from 'jquery';
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import {Modal} from 'react-bootstrap';
-import {FormattedMessage} from 'react-intl';
-
-export default class AccessHistoryModal extends React.Component {
- static propTypes = {
- onHide: PropTypes.func.isRequired,
- actions: PropTypes.shape({
- getUserAudits: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.onAuditChange = this.onAuditChange.bind(this);
- this.onShow = this.onShow.bind(this);
- this.onHide = this.onHide.bind(this);
-
- const state = this.getStateFromStoresForAudits();
- state.moreInfo = [];
- state.show = true;
-
- this.state = state;
- }
-
- getStateFromStoresForAudits() {
- return {
- audits: UserStore.getAudits()
- };
- }
-
- onShow() {
- this.props.actions.getUserAudits(UserStore.getCurrentId(), 0, 200);
- if (!Utils.isMobile()) {
- $('.modal-body').perfectScrollbar();
- }
- }
-
- onHide() {
- this.setState({show: false});
- }
-
- componentDidMount() {
- UserStore.addAuditsChangeListener(this.onAuditChange);
- this.onShow();
- }
-
- componentWillUnmount() {
- UserStore.removeAuditsChangeListener(this.onAuditChange);
- }
-
- onAuditChange() {
- const newState = this.getStateFromStoresForAudits();
- if (!Utils.areObjectsEqual(newState.audits, this.state.audits)) {
- this.setState(newState);
- }
- }
-
- render() {
- let content;
- if (this.state.audits.length === 0) {
- content = (<LoadingScreen/>);
- } else {
- content = (
- <AuditTable
- audits={this.state.audits}
- showIp={true}
- showSession={true}
- />
- );
- }
-
- return (
- <Modal
- dialogClassName='modal--scroll'
- show={this.state.show}
- onHide={this.onHide}
- onExited={this.props.onHide}
- bsSize='large'
- >
- <Modal.Header closeButton={true}>
- <Modal.Title>
- <FormattedMessage
- id='access_history.title'
- defaultMessage='Access History'
- />
- </Modal.Title>
- </Modal.Header>
- <Modal.Body ref='modalBody'>
- {content}
- </Modal.Body>
- </Modal>
- );
- }
-}
diff --git a/webapp/components/access_history_modal/index.js b/webapp/components/access_history_modal/index.js
deleted file mode 100644
index 4842ca730..000000000
--- a/webapp/components/access_history_modal/index.js
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getUserAudits} from 'mattermost-redux/actions/users';
-
-import AccessHistoryModal from './access_history_modal.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getUserAudits
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(AccessHistoryModal);
diff --git a/webapp/components/activity_log_modal/activity_log_modal.jsx b/webapp/components/activity_log_modal/activity_log_modal.jsx
deleted file mode 100644
index 854a2f0cf..000000000
--- a/webapp/components/activity_log_modal/activity_log_modal.jsx
+++ /dev/null
@@ -1,338 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import LoadingScreen from 'components/loading_screen.jsx';
-
-import UserStore from 'stores/user_store.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-
-import $ from 'jquery';
-import PropTypes from 'prop-types';
-import React from 'react';
-import {Modal} from 'react-bootstrap';
-import {FormattedMessage, FormattedTime, FormattedDate} from 'react-intl';
-import {General} from 'mattermost-redux/constants';
-
-export default class ActivityLogModal extends React.Component {
- static propTypes = {
- onHide: PropTypes.func.isRequired,
- actions: PropTypes.shape({
- getSessions: PropTypes.func.isRequired,
- revokeSession: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.submitRevoke = this.submitRevoke.bind(this);
- this.onListenerChange = this.onListenerChange.bind(this);
- this.handleMoreInfo = this.handleMoreInfo.bind(this);
- this.onHide = this.onHide.bind(this);
- this.onShow = this.onShow.bind(this);
-
- const state = this.getStateFromStores();
- state.moreInfo = [];
- state.show = true;
-
- this.state = state;
- }
-
- getStateFromStores() {
- return {
- sessions: UserStore.getSessions(),
- clientError: null
- };
- }
-
- submitRevoke(altId, e) {
- e.preventDefault();
- var modalContent = $(e.target).closest('.modal-content');
- modalContent.addClass('animation--highlight');
- setTimeout(() => {
- modalContent.removeClass('animation--highlight');
- }, 1500);
- this.props.actions.revokeSession(UserStore.getCurrentId(), altId).then(() => {
- this.props.actions.getSessions(UserStore.getCurrentId());
- });
- }
-
- onShow() {
- this.props.actions.getSessions(UserStore.getCurrentId());
- if (!Utils.isMobile()) {
- $('.modal-body').perfectScrollbar();
- }
- }
-
- onHide() {
- this.setState({show: false});
- }
-
- componentDidMount() {
- UserStore.addSessionsChangeListener(this.onListenerChange);
- this.onShow();
- }
-
- componentWillUnmount() {
- UserStore.removeSessionsChangeListener(this.onListenerChange);
- }
-
- onListenerChange() {
- const newState = this.getStateFromStores();
- if (!Utils.areObjectsEqual(newState.sessions, this.state.sessions)) {
- this.setState(newState);
- }
- }
-
- handleMoreInfo(index) {
- const newMoreInfo = this.state.moreInfo;
- newMoreInfo[index] = true;
- this.setState({moreInfo: newMoreInfo});
- }
-
- isMobileSession = (session) => {
- return session.device_id && (session.device_id.includes('apple') || session.device_id.includes('android'));
- };
-
- mobileSessionInfo = (session) => {
- let deviceTypeId;
- let deviceTypeMessage;
- let devicePicture;
-
- if (session.device_id.includes('apple')) {
- devicePicture = 'fa fa-apple';
- deviceTypeId = 'activity_log_modal.iphoneNativeClassicApp';
- deviceTypeMessage = 'iPhone Native Classic App';
-
- if (session.device_id.includes(General.PUSH_NOTIFY_APPLE_REACT_NATIVE)) {
- deviceTypeId = 'activity_log_modal.iphoneNativeApp';
- deviceTypeMessage = 'iPhone Native App';
- }
- } else if (session.device_id.includes('android')) {
- devicePicture = 'fa fa-android';
- deviceTypeId = 'activity_log_modal.androidNativeClassicApp';
- deviceTypeMessage = 'Android Native Classic App';
-
- if (session.device_id.includes(General.PUSH_NOTIFY_ANDROID_REACT_NATIVE)) {
- deviceTypeId = 'activity_log_modal.androidNativeApp';
- deviceTypeMessage = 'Android Native App';
- }
- }
-
- return {
- devicePicture,
- devicePlatform: (
- <FormattedMessage
- id={deviceTypeId}
- defaultMessage={deviceTypeMessage}
- />
- )
- };
- };
-
- render() {
- const activityList = [];
-
- for (let i = 0; i < this.state.sessions.length; i++) {
- const currentSession = this.state.sessions[i];
- const lastAccessTime = new Date(currentSession.last_activity_at);
- const firstAccessTime = new Date(currentSession.create_at);
- let devicePlatform = currentSession.props.platform;
- let devicePicture = '';
-
- if (currentSession.props.type === 'UserAccessToken') {
- continue;
- }
-
- if (currentSession.props.platform === 'Windows') {
- devicePicture = 'fa fa-windows';
- } else if (this.isMobileSession(currentSession)) {
- const sessionInfo = this.mobileSessionInfo(currentSession);
-
- devicePicture = sessionInfo.devicePicture;
- devicePlatform = sessionInfo.devicePlatform;
- } else if (currentSession.props.platform === 'Macintosh' ||
- currentSession.props.platform === 'iPhone') {
- devicePicture = 'fa fa-apple';
- } else if (currentSession.props.platform === 'Linux') {
- if (currentSession.props.os.indexOf('Android') >= 0) {
- devicePlatform = (
- <FormattedMessage
- id='activity_log_modal.android'
- defaultMessage='Android'
- />
- );
- devicePicture = 'fa fa-android';
- } else {
- devicePicture = 'fa fa-linux';
- }
- } else if (currentSession.props.os.indexOf('Linux') !== -1) {
- devicePicture = 'fa fa-linux';
- }
-
- if (currentSession.props.browser.indexOf('Desktop App') !== -1) {
- devicePlatform = (
- <FormattedMessage
- id='activity_log_modal.desktop'
- defaultMessage='Native Desktop App'
- />
- );
- }
-
- let moreInfo;
- if (this.state.moreInfo[i]) {
- moreInfo = (
- <div>
- <div>
- <FormattedMessage
- id='activity_log.firstTime'
- defaultMessage='First time active: {date}, {time}'
- values={{
- date: (
- <FormattedDate
- value={firstAccessTime}
- day='2-digit'
- month='long'
- year='numeric'
- />
- ),
- time: (
- <FormattedTime
- value={firstAccessTime}
- hour='2-digit'
- minute='2-digit'
- />
- )
- }}
- />
- </div>
- <div>
- <FormattedMessage
- id='activity_log.os'
- defaultMessage='OS: {os}'
- values={{
- os: currentSession.props.os
- }}
- />
- </div>
- <div>
- <FormattedMessage
- id='activity_log.browser'
- defaultMessage='Browser: {browser}'
- values={{
- browser: currentSession.props.browser
- }}
- />
- </div>
- <div>
- <FormattedMessage
- id='activity_log.sessionId'
- defaultMessage='Session ID: {id}'
- values={{
- id: currentSession.id
- }}
- />
- </div>
- </div>
- );
- } else {
- moreInfo = (
- <a
- className='theme'
- href='#'
- onClick={this.handleMoreInfo.bind(this, i)}
- >
- <FormattedMessage
- id='activity_log.moreInfo'
- defaultMessage='More info'
- />
- </a>
- );
- }
-
- activityList[i] = (
- <div
- key={'activityLogEntryKey' + i}
- className='activity-log__table'
- >
- <div className='activity-log__report'>
- <div className='report__platform'><i className={devicePicture}/>{devicePlatform}</div>
- <div className='report__info'>
- <div>
- <FormattedMessage
- id='activity_log.lastActivity'
- defaultMessage='Last activity: {date}, {time}'
- values={{
- date: (
- <FormattedDate
- value={lastAccessTime}
- day='2-digit'
- month='long'
- year='numeric'
- />
- ),
- time: (
- <FormattedTime
- value={lastAccessTime}
- hour='2-digit'
- minute='2-digit'
- />
- )
- }}
- />
- </div>
- {moreInfo}
- </div>
- </div>
- <div className='activity-log__action'>
- <button
- onClick={this.submitRevoke.bind(this, currentSession.id)}
- className='btn btn-primary'
- >
- <FormattedMessage
- id='activity_log.logout'
- defaultMessage='Logout'
- />
- </button>
- </div>
- </div>
- );
- }
-
- let content;
- if (this.state.sessions.loading) {
- content = <LoadingScreen/>;
- } else {
- content = <form role='form'>{activityList}</form>;
- }
-
- return (
- <Modal
- dialogClassName='modal--scroll'
- show={this.state.show}
- onHide={this.onHide}
- onExited={this.props.onHide}
- bsSize='large'
- >
- <Modal.Header closeButton={true}>
- <Modal.Title>
- <FormattedMessage
- id='activity_log.activeSessions'
- defaultMessage='Active Sessions'
- />
- </Modal.Title>
- </Modal.Header>
- <Modal.Body ref='modalBody'>
- <p className='session-help-text'>
- <FormattedMessage
- id='activity_log.sessionsDescription'
- defaultMessage="Sessions are created when you log in to a new browser on a device. Sessions let you use Mattermost without having to log in again for a time period specified by the System Admin. If you want to log out sooner, use the 'Logout' button below to end a session."
- />
- </p>
- {content}
- </Modal.Body>
- </Modal>
- );
- }
-}
diff --git a/webapp/components/activity_log_modal/index.js b/webapp/components/activity_log_modal/index.js
deleted file mode 100644
index 1c4890c65..000000000
--- a/webapp/components/activity_log_modal/index.js
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {revokeSession, getSessions} from 'mattermost-redux/actions/users';
-
-import ActivityLogModal from './activity_log_modal.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getSessions,
- revokeSession
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(ActivityLogModal);
diff --git a/webapp/components/add_users_to_team/add_users_to_team.jsx b/webapp/components/add_users_to_team/add_users_to_team.jsx
deleted file mode 100644
index b86176c34..000000000
--- a/webapp/components/add_users_to_team/add_users_to_team.jsx
+++ /dev/null
@@ -1,264 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import MultiSelect from 'components/multiselect/multiselect.jsx';
-import ProfilePicture from 'components/profile_picture.jsx';
-
-import {addUsersToTeam} from 'actions/team_actions.jsx';
-import {searchUsersNotInTeam} from 'actions/user_actions.jsx';
-
-import UserStore from 'stores/user_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-
-import Constants from 'utils/constants.jsx';
-import {displayEntireNameForUser} from 'utils/utils.jsx';
-import {Client4} from 'mattermost-redux/client';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import {Modal} from 'react-bootstrap';
-import {FormattedMessage} from 'react-intl';
-import {browserHistory} from 'react-router/es6';
-
-import store from 'stores/redux_store.jsx';
-import {searchProfilesNotInCurrentTeam} from 'mattermost-redux/selectors/entities/users';
-
-const USERS_PER_PAGE = 50;
-const MAX_SELECTABLE_VALUES = 20;
-
-export default class AddUsersToTeam extends React.Component {
- static propTypes = {
- onModalDismissed: PropTypes.func,
- actions: PropTypes.shape({
- getProfilesNotInTeam: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.handleHide = this.handleHide.bind(this);
- this.handleExit = this.handleExit.bind(this);
- this.handleSubmit = this.handleSubmit.bind(this);
- this.handleDelete = this.handleDelete.bind(this);
- this.onChange = this.onChange.bind(this);
- this.search = this.search.bind(this);
- this.addValue = this.addValue.bind(this);
- this.handlePageChange = this.handlePageChange.bind(this);
-
- this.searchTimeoutId = 0;
-
- this.state = {
- users: Object.assign([], UserStore.getProfileListNotInTeam(TeamStore.getCurrentId(), true)),
- values: [],
- show: true,
- search: false
- };
- }
-
- componentDidMount() {
- UserStore.addChangeListener(this.onChange);
- UserStore.addNotInTeamChangeListener(this.onChange);
- UserStore.addStatusesChangeListener(this.onChange);
-
- this.props.actions.getProfilesNotInTeam(TeamStore.getCurrentId(), 0, USERS_PER_PAGE * 2);
- }
-
- componentWillUnmount() {
- UserStore.removeChangeListener(this.onChange);
- UserStore.removeNotInTeamChangeListener(this.onChange);
- UserStore.removeStatusesChangeListener(this.onChange);
- }
-
- handleHide() {
- this.setState({show: false});
- }
-
- handleExit() {
- if (this.exitToChannel) {
- browserHistory.push(this.exitToChannel);
- }
-
- if (this.props.onModalDismissed) {
- this.props.onModalDismissed();
- }
- }
-
- handleSubmit(e) {
- if (e) {
- e.preventDefault();
- }
-
- const userIds = this.state.values.map((v) => v.id);
- if (userIds.length === 0) {
- return;
- }
-
- addUsersToTeam(TeamStore.getCurrentId(), userIds);
-
- this.handleHide();
- }
-
- addValue(value) {
- const values = Object.assign([], this.state.values);
- if (values.indexOf(value) === -1) {
- values.push(value);
- }
-
- this.setState({values});
- }
-
- onChange() {
- let users;
- if (this.term) {
- users = Object.assign([], searchProfilesNotInCurrentTeam(store.getState(), this.term, true));
- } else {
- users = Object.assign([], UserStore.getProfileListNotInTeam(TeamStore.getCurrentId(), true));
- }
-
- for (let i = 0; i < users.length; i++) {
- const user = Object.assign({}, users[i]);
- user.value = user.id;
- user.label = '@' + user.username;
- users[i] = user;
- }
-
- this.setState({
- users
- });
- }
-
- handlePageChange(page, prevPage) {
- if (page > prevPage) {
- this.props.actions.getProfilesNotInTeam(TeamStore.getCurrentId(), page + 1, USERS_PER_PAGE);
- }
- }
-
- search(term) {
- clearTimeout(this.searchTimeoutId);
- this.term = term;
-
- if (term === '') {
- this.onChange();
- return;
- }
-
- this.searchTimeoutId = setTimeout(
- () => {
- searchUsersNotInTeam(term, TeamStore.getCurrentId(), {});
- },
- Constants.SEARCH_TIMEOUT_MILLISECONDS
- );
- }
-
- handleDelete(values) {
- this.setState({values});
- }
-
- renderOption(option, isSelected, onAdd) {
- var rowSelected = '';
- if (isSelected) {
- rowSelected = 'more-modal__row--selected';
- }
-
- return (
- <div
- key={option.id}
- ref={isSelected ? 'selected' : option.id}
- className={'more-modal__row clickable ' + rowSelected}
- onClick={() => onAdd(option)}
- >
- <ProfilePicture
- src={Client4.getProfilePictureUrl(option.id, option.last_picture_update)}
- width='32'
- height='32'
- />
- <div
- className='more-modal__details'
- >
- <div className='more-modal__name'>
- {displayEntireNameForUser(option)}
- </div>
- <div className='more-modal__description'>
- {option.email}
- </div>
- </div>
- <div className='more-modal__actions'>
- <div className='more-modal__actions--round'>
- <i className='fa fa-plus'/>
- </div>
- </div>
- </div>
- );
- }
-
- renderValue(user) {
- return user.username;
- }
-
- render() {
- const numRemainingText = (
- <FormattedMessage
- id='multiselect.numPeopleRemaining'
- defaultMessage='Use ↑↓ to browse, ↵ to select. You can add {num, number} more {num, plural, one {person} other {people}}. '
- values={{
- num: MAX_SELECTABLE_VALUES - this.state.values.length
- }}
- />
- );
-
- const buttonSubmitText = (
- <FormattedMessage
- id='multiselect.add'
- defaultMessage='Add'
- />
- );
-
- let users = [];
- if (this.state.users) {
- users = this.state.users.filter((user) => user.delete_at === 0);
- }
-
- return (
- <Modal
- dialogClassName={'more-modal more-direct-channels'}
- show={this.state.show}
- onHide={this.handleHide}
- onExited={this.handleExit}
- >
- <Modal.Header closeButton={true}>
- <Modal.Title>
- <FormattedMessage
- id='add_users_to_team.title'
- defaultMessage='Add New Members To {teamName} Team'
- values={{
- teamName: (
- <strong>{TeamStore.getCurrent().display_name}</strong>
- )
- }}
- />
- </Modal.Title>
- </Modal.Header>
- <Modal.Body>
- <MultiSelect
- key='addUsersToTeamKey'
- options={users}
- optionRenderer={this.renderOption}
- values={this.state.values}
- valueRenderer={this.renderValue}
- perPage={USERS_PER_PAGE}
- handlePageChange={this.handlePageChange}
- handleInput={this.search}
- handleDelete={this.handleDelete}
- handleAdd={this.addValue}
- handleSubmit={this.handleSubmit}
- maxValues={MAX_SELECTABLE_VALUES}
- numRemainingText={numRemainingText}
- buttonSubmitText={buttonSubmitText}
- />
- </Modal.Body>
- </Modal>
- );
- }
-}
diff --git a/webapp/components/add_users_to_team/index.js b/webapp/components/add_users_to_team/index.js
deleted file mode 100644
index d38aeb4e5..000000000
--- a/webapp/components/add_users_to_team/index.js
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getProfilesNotInTeam} from 'mattermost-redux/actions/users';
-
-import AddUsersToTeam from './add_users_to_team.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getProfilesNotInTeam
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(AddUsersToTeam);
diff --git a/webapp/components/admin_console/admin_console.jsx b/webapp/components/admin_console/admin_console.jsx
deleted file mode 100644
index 17670d6ab..000000000
--- a/webapp/components/admin_console/admin_console.jsx
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import 'bootstrap';
-
-import AnnouncementBar from 'components/announcement_bar';
-import AdminSidebar from './admin_sidebar.jsx';
-
-import {reloadIfServerVersionChanged} from 'actions/global_actions.jsx';
-
-export default class AdminConsole extends React.Component {
- static propTypes = {
-
- /*
- * Children components to render
- */
- children: PropTypes.node.isRequired,
-
- /*
- * Object representing the config file
- */
- config: PropTypes.object.isRequired,
-
- actions: PropTypes.shape({
-
- /*
- * Function to get the config file
- */
- getConfig: PropTypes.func.isRequired
- }).isRequired
- }
-
- componentWillMount() {
- this.props.actions.getConfig();
- reloadIfServerVersionChanged();
- }
-
- render() {
- const config = this.props.config;
- if (Object.keys(config).length === 0) {
- return <div/>;
- }
- if (config && Object.keys(config).length === 0 && config.constructor === 'Object') {
- return (
- <div className='admin-console__wrapper'>
- <AnnouncementBar/>
- <div className='admin-console'/>
- </div>
- );
- }
-
- // not every page in the system console will need the config, but the vast majority will
- const children = React.cloneElement(this.props.children, {
- config
- });
- return (
- <div className='admin-console__wrapper'>
- <AnnouncementBar/>
- <div className='admin-console'>
- <AdminSidebar/>
- {children}
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/admin_console/admin_navbar_dropdown.jsx b/webapp/components/admin_console/admin_navbar_dropdown.jsx
deleted file mode 100644
index 6ef4906f5..000000000
--- a/webapp/components/admin_console/admin_navbar_dropdown.jsx
+++ /dev/null
@@ -1,223 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import ReactDOM from 'react-dom';
-
-import TeamStore from 'stores/team_store.jsx';
-import Constants from 'utils/constants.jsx';
-import AboutBuildModal from 'components/about_build_modal.jsx';
-import {sortTeamsByDisplayName} from 'utils/team_utils.jsx';
-import * as GlobalActions from 'actions/global_actions.jsx';
-
-import {FormattedMessage} from 'react-intl';
-
-import {Link} from 'react-router/es6';
-
-import React from 'react';
-
-import * as Utils from 'utils/utils.jsx';
-
-export default class AdminNavbarDropdown extends React.Component {
- constructor(props) {
- super(props);
- this.blockToggle = false;
- this.onTeamChange = this.onTeamChange.bind(this);
- this.handleAboutModal = this.handleAboutModal.bind(this);
- this.aboutModalDismissed = this.aboutModalDismissed.bind(this);
-
- this.state = {
- teams: TeamStore.getAll(),
- teamMembers: TeamStore.getMyTeamMembers(),
- showAboutModal: false
- };
- }
-
- componentDidMount() {
- $(ReactDOM.findDOMNode(this.refs.dropdown)).on('hide.bs.dropdown', () => {
- this.blockToggle = true;
- setTimeout(() => {
- this.blockToggle = false;
- }, 100);
- });
-
- TeamStore.addChangeListener(this.onTeamChange);
- }
-
- componentWillUnmount() {
- $(ReactDOM.findDOMNode(this.refs.dropdown)).off('hide.bs.dropdown');
- TeamStore.removeChangeListener(this.onTeamChange);
- }
-
- handleAboutModal(e) {
- e.preventDefault();
-
- this.setState({showAboutModal: true});
- }
-
- aboutModalDismissed() {
- this.setState({showAboutModal: false});
- }
-
- onTeamChange() {
- this.setState({
- teams: TeamStore.getAll(),
- teamMembers: TeamStore.getMyTeamMembers()
- });
- }
-
- render() {
- var teamsArray = []; // Array of team objects
- var teams = []; // Array of team components
- let switchTeams;
-
- if (this.state.teamMembers && this.state.teamMembers.length > 0) {
- for (const index in this.state.teamMembers) {
- if (this.state.teamMembers.hasOwnProperty(index)) {
- const teamMember = this.state.teamMembers[index];
- const team = this.state.teams[teamMember.team_id];
- teamsArray.push(team);
- }
- }
-
- // Sort teams alphabetically with display_name
- teamsArray = teamsArray.sort(sortTeamsByDisplayName);
-
- for (const team of teamsArray) {
- teams.push(
- <li key={'team_' + team.name}>
- <Link
- id={'swithTo' + Utils.createSafeId(team.name)}
- to={'/' + team.name + '/channels/town-square'}
- >
- <FormattedMessage
- id='navbar_dropdown.switchTo'
- defaultMessage='Switch to '
- />
- {team.display_name}
- </Link>
- </li>
- );
- }
-
- teams.push(
- <li
- key='teamDiv'
- className='divider'
- />
- );
- } else {
- switchTeams = (
- <li>
- <Link
- to={'/select_team'}
- >
- <i className='fa fa-exchange'/>
- <FormattedMessage
- id='admin.nav.switch'
- defaultMessage='Team Selection'
- />
- </Link>
- </li>
- );
- }
-
- return (
- <ul className='nav navbar-nav navbar-right admin-navbar-dropdown'>
- <li
- ref='dropdown'
- className='dropdown'
- >
- <a
- href='#'
- id='adminNavbarDropdownButton'
- className='dropdown-toggle admin-navbar-dropdown__toggle'
- data-toggle='dropdown'
- role='button'
- aria-expanded='false'
- >
- <span
- className='dropdown__icon admin-navbar-dropdown__icon'
- dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}}
- />
- </a>
- <ul
- className='dropdown-menu'
- role='menu'
- >
- {teams}
- {switchTeams}
- <li
- key='teamDiv'
- className='divider'
- />
- <li>
- <Link
- to='https://about.mattermost.com/administrators-guide/'
- rel='noopener noreferrer'
- target='_blank'
- >
- <FormattedMessage
- id='admin.nav.administratorsGuide'
- defaultMessage='Administrator Guide'
- />
- </Link>
- </li>
- <li>
- <Link
- to='https://about.mattermost.com/troubleshooting-forum/'
- rel='noopener noreferrer'
- target='_blank'
- >
- <FormattedMessage
- id='admin.nav.troubleshootingForum'
- defaultMessage='Troubleshooting Forum'
- />
- </Link>
- </li>
- <li>
- <Link
- to='https://about.mattermost.com/commercial-support/'
- rel='noopener noreferrer'
- target='_blank'
- >
- <FormattedMessage
- id='admin.nav.commercialSupport'
- defaultMessage='Commercial Support'
- />
- </Link>
- </li>
- <li>
- <a
- href='#'
- onClick={this.handleAboutModal}
- >
- <FormattedMessage
- id='navbar_dropdown.about'
- defaultMessage='About Mattermost'
- />
- </a>
- </li>
- <li className='divider'/>
- <li>
- <a
- href='#'
- id='logout'
- onClick={() => GlobalActions.emitUserLoggedOutEvent()}
- >
- <FormattedMessage
- id='admin.nav.logout'
- defaultMessage='Logout'
- />
- </a>
- </li>
- <AboutBuildModal
- show={this.state.showAboutModal}
- onModalDismissed={this.aboutModalDismissed}
- />
- </ul>
- </li>
- </ul>
- );
- }
-}
diff --git a/webapp/components/admin_console/admin_settings.jsx b/webapp/components/admin_console/admin_settings.jsx
deleted file mode 100644
index 2411fbdb8..000000000
--- a/webapp/components/admin_console/admin_settings.jsx
+++ /dev/null
@@ -1,147 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import FormError from 'components/form_error.jsx';
-import SaveButton from 'components/admin_console/save_button.jsx';
-
-import {saveConfig} from 'actions/admin_actions.jsx';
-
-export default class AdminSettings extends React.Component {
- static propTypes = {
-
- /*
- * Object representing the config file
- */
- config: PropTypes.object
- }
-
- constructor(props) {
- super(props);
-
- this.handleChange = this.handleChange.bind(this);
- this.handleSubmit = this.handleSubmit.bind(this);
- this.doSubmit = this.doSubmit.bind(this);
-
- this.state = Object.assign(this.getStateFromConfig(props.config), {
- saveNeeded: false,
- saving: false,
- serverError: null
- });
- }
-
- handleChange(id, value) {
- this.setState({
- saveNeeded: true,
- [id]: value
- });
- }
-
- handleSubmit(e) {
- e.preventDefault();
-
- this.doSubmit();
- }
-
- doSubmit(callback) {
- this.setState({
- saving: true,
- serverError: null
- });
-
- // clone config so that we aren't modifying data in the stores
- let config = JSON.parse(JSON.stringify(this.props.config));
- config = this.getConfigFromState(config);
-
- saveConfig(
- config,
- (savedConfig) => {
- this.setState(this.getStateFromConfig(savedConfig));
-
- this.setState({
- saveNeeded: false,
- saving: false
- });
-
- if (callback) {
- callback();
- }
-
- if (this.handleSaved) {
- this.handleSaved(config);
- }
- },
- (err) => {
- this.setState({
- saving: false,
- serverError: err.message
- });
-
- if (callback) {
- callback();
- }
-
- if (this.handleSaved) {
- this.handleSaved(config);
- }
- }
- );
- }
-
- parseInt(str, defaultValue) {
- const n = parseInt(str, 10);
-
- if (isNaN(n)) {
- if (defaultValue) {
- return defaultValue;
- }
- return 0;
- }
-
- return n;
- }
-
- parseIntNonZero(str, defaultValue) {
- const n = parseInt(str, 10);
-
- if (isNaN(n) || n < 1) {
- if (defaultValue) {
- return defaultValue;
- }
- return 1;
- }
-
- return n;
- }
-
- render() {
- return (
- <div className='wrapper--fixed'>
- <h3 className='admin-console-header'>
- {this.renderTitle()}
- </h3>
- <form
- className='form-horizontal'
- role='form'
- onSubmit={this.handleSubmit}
- >
- {this.renderSettings()}
- <div className='form-group'>
- <FormError error={this.state.serverError}/>
- </div>
- <div className='form-group'>
- <div className='col-sm-12'>
- <SaveButton
- saving={this.state.saving}
- disabled={!this.state.saveNeeded || (this.canSave && !this.canSave())}
- onClick={this.handleSubmit}
- />
- </div>
- </div>
- </form>
- </div>
- );
- }
-}
diff --git a/webapp/components/admin_console/admin_sidebar.jsx b/webapp/components/admin_console/admin_sidebar.jsx
deleted file mode 100644
index 4918cdac0..000000000
--- a/webapp/components/admin_console/admin_sidebar.jsx
+++ /dev/null
@@ -1,702 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import PropTypes from 'prop-types';
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-
-import * as Utils from 'utils/utils.jsx';
-
-import AdminSidebarCategory from './admin_sidebar_category.jsx';
-import AdminSidebarHeader from './admin_sidebar_header.jsx';
-import AdminSidebarSection from './admin_sidebar_section.jsx';
-
-export default class AdminSidebar extends React.Component {
- static get contextTypes() {
- return {
- router: PropTypes.object.isRequired
- };
- }
-
- constructor(props) {
- super(props);
-
- this.updateTitle = this.updateTitle.bind(this);
- }
-
- componentDidMount() {
- this.updateTitle();
-
- if (!Utils.isMobile()) {
- $('.admin-sidebar .nav-pills__container').perfectScrollbar({
- suppressScrollX: true
- });
- }
- }
-
- componentDidUpdate() {
- if (!Utils.isMobile()) {
- $('.admin-sidebar .nav-pills__container').perfectScrollbar({
- suppressScrollX: true
- });
- }
- }
-
- updateTitle() {
- let currentSiteName = '';
- if (global.window.mm_config.SiteName != null) {
- currentSiteName = global.window.mm_config.SiteName;
- }
-
- document.title = Utils.localizeMessage('sidebar_right_menu.console', 'System Console') + ' - ' + currentSiteName;
- }
-
- render() {
- let oauthSettings = null;
- let ldapSettings = null;
- let samlSettings = null;
- let clusterSettings = null;
- let metricsSettings = null;
- let complianceSettings = null;
- let mfaSettings = null;
- let pluginSettings = null;
-
- let license = null;
- let audits = null;
- let policy = null;
-
- if (window.mm_config.BuildEnterpriseReady === 'true') {
- license = (
- <AdminSidebarSection
- name='license'
- title={
- <FormattedMessage
- id='admin.sidebar.license'
- defaultMessage='Edition and License'
- />
- }
- />
- );
- }
-
- if (window.mm_license.IsLicensed === 'true') {
- if (global.window.mm_license.LDAP === 'true') {
- ldapSettings = (
- <AdminSidebarSection
- name='ldap'
- title={
- <FormattedMessage
- id='admin.sidebar.ldap'
- defaultMessage='AD/LDAP'
- />
- }
- />
- );
- }
-
- if (global.window.mm_license.Cluster === 'true') {
- clusterSettings = (
- <AdminSidebarSection
- name='cluster'
- title={
- <FormattedMessage
- id='admin.sidebar.cluster'
- defaultMessage='High Availability'
- />
- }
- />
- );
- }
-
- if (global.window.mm_license.Metrics === 'true') {
- metricsSettings = (
- <AdminSidebarSection
- name='metrics'
- title={
- <FormattedMessage
- id='admin.sidebar.metrics'
- defaultMessage='Performance Monitoring'
- />
- }
- />
- );
- }
-
- if (global.window.mm_license.SAML === 'true') {
- samlSettings = (
- <AdminSidebarSection
- name='saml'
- title={
- <FormattedMessage
- id='admin.sidebar.saml'
- defaultMessage='SAML 2.0'
- />
- }
- />
- );
- }
-
- if (global.window.mm_license.Compliance === 'true') {
- complianceSettings = (
- <AdminSidebarSection
- name='compliance'
- title={
- <FormattedMessage
- id='admin.sidebar.compliance'
- defaultMessage='Compliance'
- />
- }
- />
- );
- }
-
- if (global.window.mm_license.MFA === 'true') {
- mfaSettings = (
- <AdminSidebarSection
- name='mfa'
- title={
- <FormattedMessage
- id='admin.sidebar.mfa'
- defaultMessage='MFA'
- />
- }
- />
- );
- }
-
- oauthSettings = (
- <AdminSidebarSection
- name='oauth'
- title={
- <FormattedMessage
- id='admin.sidebar.oauth'
- defaultMessage='OAuth 2.0'
- />
- }
- />
- );
-
- policy = (
- <AdminSidebarSection
- name='policy'
- title={
- <FormattedMessage
- id='admin.sidebar.policy'
- defaultMessage='Policy'
- />
- }
- />
- );
- } else {
- oauthSettings = (
- <AdminSidebarSection
- name='gitlab'
- title={
- <FormattedMessage
- id='admin.sidebar.gitlab'
- defaultMessage='GitLab'
- />
- }
- />
- );
- }
-
- if (window.mm_license.IsLicensed === 'true') {
- audits = (
- <AdminSidebarSection
- name='audits'
- title={
- <FormattedMessage
- id='admin.sidebar.audits'
- defaultMessage='Complaince and Auditing'
- />
- }
- />
- );
- }
-
- let customBranding = null;
-
- if (window.mm_license.IsLicensed === 'true') {
- customBranding = (
- <AdminSidebarSection
- name='custom_brand'
- title={
- <FormattedMessage
- id='admin.sidebar.customBrand'
- defaultMessage='Custom Branding'
- />
- }
- />
- );
- }
-
- let otherCategory = null;
- if (license || audits) {
- otherCategory = (
- <AdminSidebarCategory
- parentLink='/admin_console'
- icon='fa-wrench'
- title={
- <FormattedMessage
- id='admin.sidebar.other'
- defaultMessage='OTHER'
- />
- }
- >
- {license}
- {audits}
- </AdminSidebarCategory>
- );
- }
-
- const webrtcSettings = (
- <AdminSidebarSection
- name='webrtc'
- title={
- <FormattedMessage
- id='admin.sidebar.webrtc'
- defaultMessage='WebRTC (Beta)'
- />
- }
- />
- );
-
- let elasticSearchSettings = null;
- if (window.mm_license.IsLicensed === 'true') {
- elasticSearchSettings = (
- <AdminSidebarSection
- name='elasticsearch'
- title={
- <FormattedMessage
- id='admin.sidebar.elasticsearch'
- defaultMessage='Elasticsearch (Beta)'
- />
- }
- />
- );
- }
-
- if (window.mm_config.PluginsEnabled === 'true' && window.mm_license.IsLicensed === 'true') {
- pluginSettings = (
- <AdminSidebarSection
- name='plugins'
- title={
- <FormattedMessage
- id='admin.sidebar.plugins'
- defaultMessage='Plugins (experimental)'
- />
- }
- />
- );
- }
-
- const SHOW_CLIENT_VERSIONS = false;
- let clientVersions = null;
- if (SHOW_CLIENT_VERSIONS) {
- clientVersions = (
- <AdminSidebarSection
- name='client_versions'
- title={
- <FormattedMessage
- id='admin.sidebar.client_versions'
- defaultMessage='Client Versions'
- />
- }
- />
- );
- }
-
- return (
- <div className='admin-sidebar'>
- <AdminSidebarHeader/>
- <div className='nav-pills__container'>
- <ul className='nav nav-pills nav-stacked'>
- <AdminSidebarCategory
- parentLink='/admin_console'
- icon='fa-bar-chart'
- title={
- <FormattedMessage
- id='admin.sidebar.reports'
- defaultMessage='REPORTING'
- />
- }
- >
- <AdminSidebarSection
- name='system_analytics'
- title={
- <FormattedMessage
- id='admin.sidebar.view_statistics'
- defaultMessage='Site Statistics'
- />
- }
- />
- <AdminSidebarSection
- name='team_analytics'
- title={
- <FormattedMessage
- id='admin.sidebar.statistics'
- defaultMessage='Team Statistics'
- />
- }
- />
- <AdminSidebarSection
- name='users'
- title={
- <FormattedMessage
- id='admin.sidebar.users'
- defaultMessage='Users'
- />
- }
- />
- <AdminSidebarSection
- name='logs'
- title={
- <FormattedMessage
- id='admin.sidebar.logs'
- defaultMessage='Logs'
- />
- }
- />
- </AdminSidebarCategory>
- <AdminSidebarCategory
- sectionClass='sections--settings'
- parentLink='/admin_console'
- icon='fa-gear'
- title={
- <FormattedMessage
- id='admin.sidebar.settings'
- defaultMessage='SETTINGS'
- />
- }
- >
- <AdminSidebarSection
- name='general'
- type='text'
- title={
- <FormattedMessage
- id='admin.sidebar.general'
- defaultMessage='General'
- />
- }
- >
- <AdminSidebarSection
- name='configuration'
- title={
- <FormattedMessage
- id='admin.sidebar.configuration'
- defaultMessage='Configuration'
- />
- }
- />
- <AdminSidebarSection
- name='localization'
- title={
- <FormattedMessage
- id='admin.sidebar.localization'
- defaultMessage='Localization'
- />
- }
- />
- <AdminSidebarSection
- name='users_and_teams'
- title={
- <FormattedMessage
- id='admin.sidebar.usersAndTeams'
- defaultMessage='Users and Teams'
- />
- }
- />
- {policy}
- <AdminSidebarSection
- name='privacy'
- title={
- <FormattedMessage
- id='admin.sidebar.privacy'
- defaultMessage='Privacy'
- />
- }
- />
- {complianceSettings}
- <AdminSidebarSection
- name='logging'
- title={
- <FormattedMessage
- id='admin.sidebar.logging'
- defaultMessage='Logging'
- />
- }
- />
- </AdminSidebarSection>
- <AdminSidebarSection
- name='authentication'
- type='text'
- title={
- <FormattedMessage
- id='admin.sidebar.authentication'
- defaultMessage='Authentication'
- />
- }
- >
- <AdminSidebarSection
- name='authentication_email'
- title={
- <FormattedMessage
- id='admin.sidebar.email'
- defaultMessage='Email'
- />
- }
- />
- {oauthSettings}
- {ldapSettings}
- {samlSettings}
- {mfaSettings}
- </AdminSidebarSection>
- <AdminSidebarSection
- name='security'
- type='text'
- title={
- <FormattedMessage
- id='admin.sidebar.security'
- defaultMessage='Security'
- />
- }
- >
- <AdminSidebarSection
- name='sign_up'
- title={
- <FormattedMessage
- id='admin.sidebar.signUp'
- defaultMessage='Sign Up'
- />
- }
- />
- <AdminSidebarSection
- name='password'
- title={
- <FormattedMessage
- id='admin.sidebar.password'
- defaultMessage='Password'
- />
- }
- />
- <AdminSidebarSection
- name='public_links'
- title={
- <FormattedMessage
- id='admin.sidebar.publicLinks'
- defaultMessage='Public Links'
- />
- }
- />
- <AdminSidebarSection
- name='sessions'
- title={
- <FormattedMessage
- id='admin.sidebar.sessions'
- defaultMessage='Sessions'
- />
- }
- />
- <AdminSidebarSection
- name='connections'
- title={
- <FormattedMessage
- id='admin.sidebar.connections'
- defaultMessage='Connections'
- />
- }
- />
- {clientVersions}
- </AdminSidebarSection>
- <AdminSidebarSection
- name='notifications'
- type='text'
- title={
- <FormattedMessage
- id='admin.sidebar.notifications'
- defaultMessage='Notifications'
- />
- }
- >
- <AdminSidebarSection
- name='notifications_email'
- title={
- <FormattedMessage
- id='admin.sidebar.email'
- defaultMessage='Email'
- />
- }
- />
- <AdminSidebarSection
- name='push'
- title={
- <FormattedMessage
- id='admin.sidebar.push'
- defaultMessage='Mobile Push'
- />
- }
- />
- </AdminSidebarSection>
- <AdminSidebarSection
- name='integrations'
- type='text'
- title={
- <FormattedMessage
- id='admin.sidebar.integrations'
- defaultMessage='Integrations'
- />
- }
- >
- <AdminSidebarSection
- name='custom'
- title={
- <FormattedMessage
- id='admin.sidebar.customIntegrations'
- defaultMessage='Custom Integrations'
- />
- }
- />
- <AdminSidebarSection
- name='jira'
- title={
- <FormattedMessage
- id='admin.sidebar.jira'
- defaultMessage='JIRA (Beta)'
- />
- }
- />
- {webrtcSettings}
- <AdminSidebarSection
- name='external'
- title={
- <FormattedMessage
- id='admin.sidebar.external'
- defaultMessage='External Services'
- />
- }
- />
- {pluginSettings}
- </AdminSidebarSection>
- <AdminSidebarSection
- name='files'
- type='text'
- title={
- <FormattedMessage
- id='admin.sidebar.files'
- defaultMessage='Files'
- />
- }
- >
- <AdminSidebarSection
- key='storage'
- name='storage'
- title={
- <FormattedMessage
- id='admin.sidebar.storage'
- defaultMessage='Storage'
- />
- }
- />
- </AdminSidebarSection>
- <AdminSidebarSection
- name='customization'
- type='text'
- title={
- <FormattedMessage
- id='admin.sidebar.customization'
- defaultMessage='Customization'
- />
- }
- >
- {customBranding}
- <AdminSidebarSection
- name='emoji'
- title={
- <FormattedMessage
- id='admin.sidebar.emoji'
- defaultMessage='Emoji'
- />
-
- }
- />
- <AdminSidebarSection
- name='link_previews'
- title={
- <FormattedMessage
- id='admin.sidebar.linkPreviews'
- defaultMessage='Link Previews'
- />
-
- }
- />
- <AdminSidebarSection
- name='legal_and_support'
- title={
- <FormattedMessage
- id='admin.sidebar.legalAndSupport'
- defaultMessage='Legal and Support'
- />
- }
- />
- <AdminSidebarSection
- name='native_app_links'
- title={
- <FormattedMessage
- id='admin.sidebar.nativeAppLinks'
- defaultMessage='Mattermost App Links'
- />
-
- }
- />
- </AdminSidebarSection>
- <AdminSidebarSection
- name='advanced'
- type='text'
- title={
- <FormattedMessage
- id='admin.sidebar.advanced'
- defaultMessage='Advanced'
- />
- }
- >
- <AdminSidebarSection
- name='rate'
- title={
- <FormattedMessage
- id='admin.sidebar.rateLimiting'
- defaultMessage='Rate Limiting'
- />
- }
- />
- <AdminSidebarSection
- name='database'
- title={
- <FormattedMessage
- id='admin.sidebar.database'
- defaultMessage='Database'
- />
- }
- />
- {elasticSearchSettings}
- <AdminSidebarSection
- name='developer'
- title={
- <FormattedMessage
- id='admin.sidebar.developer'
- defaultMessage='Developer'
- />
- }
- />
- {clusterSettings}
- {metricsSettings}
- </AdminSidebarSection>
- </AdminSidebarCategory>
- {otherCategory}
- </ul>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/admin_console/admin_sidebar_category.jsx b/webapp/components/admin_console/admin_sidebar_category.jsx
deleted file mode 100644
index 5db68e876..000000000
--- a/webapp/components/admin_console/admin_sidebar_category.jsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import {Link} from 'react-router/es6';
-
-export default class AdminSidebarCategory extends React.Component {
- static get propTypes() {
- return {
- name: PropTypes.string,
- title: PropTypes.node.isRequired,
- icon: PropTypes.string.isRequired,
- sectionClass: PropTypes.string,
- parentLink: PropTypes.string,
- children: PropTypes.node,
- action: PropTypes.node
- };
- }
-
- static get defaultProps() {
- return {
- parentLink: ''
- };
- }
-
- static get contextTypes() {
- return {
- router: PropTypes.object.isRequired
- };
- }
-
- render() {
- let link = this.props.parentLink;
- let title = (
- <div className='category-title category-title--active'>
- <i className={'category-icon fa ' + this.props.icon}/>
- <span className='category-title__text'>
- {this.props.title}
- </span>
- {this.props.action}
- </div>
- );
-
- if (this.props.name) {
- link += '/' + name;
- title = (
- <Link
- to={link}
- className='category-title'
- activeClassName='category-title category-title--active'
- >
- {title}
- </Link>
- );
- }
-
- let clonedChildren = null;
- if (this.props.children && this.context.router.isActive(link)) {
- clonedChildren = (
- <ul className={'sections ' + this.props.sectionClass}>
- {
- React.Children.map(this.props.children, (child) => {
- if (child === null) {
- return null;
- }
-
- return React.cloneElement(child, {
- parentLink: link
- });
- })
- }
- </ul>
- );
- }
-
- return (
- <li className='sidebar-category'>
- {title}
- {clonedChildren}
- </li>
- );
- }
-}
diff --git a/webapp/components/admin_console/admin_sidebar_header.jsx b/webapp/components/admin_console/admin_sidebar_header.jsx
deleted file mode 100644
index 1c64eb6d1..000000000
--- a/webapp/components/admin_console/admin_sidebar_header.jsx
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import AdminNavbarDropdown from './admin_navbar_dropdown.jsx';
-import UserStore from 'stores/user_store.jsx';
-import {Client4} from 'mattermost-redux/client';
-
-import {FormattedMessage} from 'react-intl';
-
-import React from 'react';
-
-export default class SidebarHeader extends React.Component {
- constructor(props) {
- super(props);
-
- this.state = {};
- }
-
- toggleDropdown = (e) => {
- e.preventDefault();
-
- if (this.refs.dropdown.blockToggle) {
- this.refs.dropdown.blockToggle = false;
- return;
- }
-
- $('.team__header').find('.dropdown-toggle').dropdown('toggle');
- }
-
- render() {
- var me = UserStore.getCurrentUser();
- var profilePicture = null;
-
- if (!me) {
- return null;
- }
-
- if (me.last_picture_update) {
- profilePicture = (
- <img
- className='user__picture'
- src={Client4.getProfilePictureUrl(me.id, me.last_picture_update)}
- />
- );
- }
-
- return (
- <div className='team__header theme'>
- <a
- href='#'
- onClick={this.toggleDropdown}
- >
- {profilePicture}
- <div className='header__info'>
- <div className='team__name'>
- <FormattedMessage
- id='admin.sidebarHeader.systemConsole'
- defaultMessage='System Console'
- />
- </div>
- <div className='user__name'>{'@' + me.username}</div>
- </div>
- </a>
- <AdminNavbarDropdown ref='dropdown'/>
- </div>
- );
- }
-}
diff --git a/webapp/components/admin_console/admin_sidebar_section.jsx b/webapp/components/admin_console/admin_sidebar_section.jsx
deleted file mode 100644
index 2a8ecab71..000000000
--- a/webapp/components/admin_console/admin_sidebar_section.jsx
+++ /dev/null
@@ -1,102 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import {Link} from 'react-router/es6';
-import * as Utils from 'utils/utils.jsx';
-
-export default class AdminSidebarSection extends React.Component {
- static get propTypes() {
- return {
- name: PropTypes.string.isRequired,
- title: PropTypes.node.isRequired,
- type: PropTypes.string,
- parentLink: PropTypes.string,
- subsection: PropTypes.bool,
- children: PropTypes.node,
- action: PropTypes.node,
- onlyActiveOnIndex: PropTypes.bool
- };
- }
-
- static get defaultProps() {
- return {
- parentLink: '',
- subsection: false,
- children: [],
- onlyActiveOnIndex: true
- };
- }
-
- getLink() {
- return this.props.parentLink + '/' + this.props.name;
- }
-
- render() {
- const link = this.getLink();
-
- let clonedChildren = null;
- if (this.props.children) {
- clonedChildren = (
- <ul className='nav nav__sub-menu subsections'>
- {
- React.Children.map(this.props.children, (child) => {
- if (child === null) {
- return null;
- }
-
- return React.cloneElement(child, {
- parentLink: link,
- subsection: true
- });
- })
- }
- </ul>
- );
- }
-
- let className = 'sidebar-section';
- if (this.props.subsection) {
- className += ' sidebar-subsection';
- }
-
- let sidebarItem = (
- <Link
- id={Utils.createSafeId(this.props.name)}
- className={`${className}-title`}
- activeClassName={`${className}-title ${className}-title--active`}
- onlyActiveOnIndex={this.props.onlyActiveOnIndex}
- onClick={this.handleClick}
- to={link}
- >
- <span className={`${className}-title__text`}>
- {this.props.title}
- </span>
- {this.props.action}
- </Link>
- );
-
- if (this.props.type === 'text') {
- sidebarItem = (
- <div
- className={`${className}-title`}
- >
- <span className={`${className}-title__text`}>
- {this.props.title}
- </span>
- {this.props.action}
- </div>
- );
- }
-
- return (
- <li className={className}>
- {sidebarItem}
- {clonedChildren}
- </li>
- );
- }
-}
diff --git a/webapp/components/admin_console/audits/audits.jsx b/webapp/components/admin_console/audits/audits.jsx
deleted file mode 100644
index 0811c216f..000000000
--- a/webapp/components/admin_console/audits/audits.jsx
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import LoadingScreen from 'components/loading_screen.jsx';
-import AuditTable from 'components/audit_table.jsx';
-import ComplianceReports from 'components/admin_console/compliance_reports';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {FormattedMessage} from 'react-intl';
-
-export default class Audits extends React.PureComponent {
- static propTypes = {
-
- /*
- * Array of audits to render
- */
- audits: PropTypes.arrayOf(PropTypes.object).isRequired,
-
- actions: PropTypes.shape({
-
- /*
- * Function to fetch audits
- */
- getAudits: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.state = {
- loadingAudits: true
- };
- }
-
- componentDidMount() {
- this.props.actions.getAudits().then(
- () => this.setState({loadingAudits: false})
- );
- }
-
- reload = () => {
- this.setState({loadingAudits: true});
- this.props.actions.getAudits().then(
- () => this.setState({loadingAudits: false})
- );
- }
-
- render() {
- let content = null;
-
- if (global.window.mm_license.IsLicensed !== 'true') {
- return <div/>;
- }
-
- if (this.state.loadingAudits) {
- content = <LoadingScreen/>;
- } else {
- content = (
- <div style={{margin: '10px'}}>
- <AuditTable
- audits={this.props.audits}
- showUserId={true}
- showIp={true}
- showSession={true}
- />
- </div>
- );
- }
-
- return (
- <div>
- <ComplianceReports/>
-
- <div className='panel audit-panel'>
- <h3 className='admin-console-header'>
- <FormattedMessage
- id='admin.audits.title'
- defaultMessage='User Activity Logs'
- />
- <button
- type='submit'
- className='btn btn-link pull-right'
- onClick={this.reload}
- >
- <i className='fa fa-refresh'/>
- <FormattedMessage
- id='admin.audits.reload'
- defaultMessage='Reload User Activity Logs'
- />
- </button>
- </h3>
- <div className='audit-panel__table'>
- {content}
- </div>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/admin_console/audits/index.js b/webapp/components/admin_console/audits/index.js
deleted file mode 100644
index a48e33538..000000000
--- a/webapp/components/admin_console/audits/index.js
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getAudits} from 'mattermost-redux/actions/admin';
-
-import * as Selectors from 'mattermost-redux/selectors/entities/admin';
-
-import Audits from './audits.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps,
- audits: Object.values(Selectors.getAudits(state))
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getAudits
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(Audits);
diff --git a/webapp/components/admin_console/banner.jsx b/webapp/components/admin_console/banner.jsx
deleted file mode 100644
index 6395ef4a1..000000000
--- a/webapp/components/admin_console/banner.jsx
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {FormattedMessage} from 'react-intl';
-
-export default function Banner(props) {
- let title = (
- <FormattedMessage
- id='admin.banner.heading'
- defaultMessage='Note:'
- />
- );
-
- if (props.title) {
- title = props.title;
- }
-
- return (
- <div className='banner'>
- <div className='banner__content'>
- <h4 className='banner__heading'>
- {title}
- </h4>
- <p>
- {props.description}
- </p>
- </div>
- </div>
- );
-}
-
-Banner.defaultProps = {
-};
-Banner.propTypes = {
- title: PropTypes.node,
- description: PropTypes.node.isRequired
-};
diff --git a/webapp/components/admin_console/boolean_setting.jsx b/webapp/components/admin_console/boolean_setting.jsx
deleted file mode 100644
index 45c23c869..000000000
--- a/webapp/components/admin_console/boolean_setting.jsx
+++ /dev/null
@@ -1,99 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import Setting from './setting.jsx';
-
-import {FormattedMessage} from 'react-intl';
-
-import * as Utils from 'utils/utils.jsx';
-
-export default class BooleanSetting extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleChange = this.handleChange.bind(this);
- }
-
- handleChange(e) {
- this.props.onChange(this.props.id, e.target.value === 'true');
- }
-
- render() {
- let helpText;
- if (this.props.disabled && this.props.disabledText) {
- helpText = (
- <div>
- <span className='admin-console__disabled-text'>
- {this.props.disabledText}
- </span>
- {this.props.helpText}
- </div>
- );
- } else {
- helpText = this.props.helpText;
- }
-
- return (
- <Setting
- label={this.props.label}
- helpText={helpText}
- >
- <label className='radio-inline'>
- <input
- type='radio'
- value='true'
- id={Utils.createSafeId(this.props.id) + 'true'}
- name={this.props.id}
- checked={this.props.value}
- onChange={this.handleChange}
- disabled={this.props.disabled}
- />
- {this.props.trueText}
- </label>
- <label className='radio-inline'>
- <input
- type='radio'
- value='false'
- id={Utils.createSafeId(this.props.id) + 'false'}
- name={this.props.id}
- checked={!this.props.value}
- onChange={this.handleChange}
- disabled={this.props.disabled}
- />
- {this.props.falseText}
- </label>
- </Setting>
- );
- }
-}
-BooleanSetting.defaultProps = {
- trueText: (
- <FormattedMessage
- id='admin.true'
- defaultMessage='true'
- />
- ),
- falseText: (
- <FormattedMessage
- id='admin.false'
- defaultMessage='false'
- />
- ),
- disabled: false
-};
-
-BooleanSetting.propTypes = {
- id: PropTypes.string.isRequired,
- label: PropTypes.node.isRequired,
- value: PropTypes.bool.isRequired,
- onChange: PropTypes.func.isRequired,
- trueText: PropTypes.node,
- falseText: PropTypes.node,
- disabled: PropTypes.bool.isRequired,
- disabledText: PropTypes.node,
- helpText: PropTypes.node.isRequired
-};
diff --git a/webapp/components/admin_console/brand_image_setting.jsx b/webapp/components/admin_console/brand_image_setting.jsx
deleted file mode 100644
index d2eae3f6e..000000000
--- a/webapp/components/admin_console/brand_image_setting.jsx
+++ /dev/null
@@ -1,213 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import {Client4} from 'mattermost-redux/client';
-import * as Utils from 'utils/utils.jsx';
-import {uploadBrandImage} from 'actions/admin_actions.jsx';
-
-import FormError from 'components/form_error.jsx';
-import {FormattedHTMLMessage, FormattedMessage} from 'react-intl';
-
-const HTTP_STATUS_OK = 200;
-
-export default class BrandImageSetting extends React.PureComponent {
- static propTypes = {
-
- /*
- * Set to disable the setting
- */
- disabled: PropTypes.bool.isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.handleImageChange = this.handleImageChange.bind(this);
- this.handleImageSubmit = this.handleImageSubmit.bind(this);
-
- this.state = {
- brandImage: null,
- brandImageExists: false,
- brandImageTimestamp: Date.now(),
- uploading: false,
- uploadCompleted: false,
- error: ''
- };
- }
-
- componentWillMount() {
- fetch(Client4.getBrandImageUrl(this.state.brandImageTimestamp)).then(
- (resp) => {
- if (resp.status === HTTP_STATUS_OK) {
- this.setState({brandImageExists: true});
- } else {
- this.setState({brandImageExists: false});
- }
- }
- );
- }
-
- componentDidUpdate() {
- if (this.refs.image) {
- const reader = new FileReader();
-
- const img = this.refs.image;
- reader.onload = (e) => {
- $(img).attr('src', e.target.result);
- };
-
- reader.readAsDataURL(this.state.brandImage);
- }
- }
-
- handleImageChange() {
- const element = $(this.refs.fileInput);
-
- if (element.prop('files').length > 0) {
- this.setState({
- brandImage: element.prop('files')[0]
- });
- }
- }
-
- handleImageSubmit(e) {
- e.preventDefault();
-
- if (!this.state.brandImage) {
- return;
- }
-
- if (this.state.uploading) {
- return;
- }
-
- $(this.refs.upload).button('loading');
-
- this.setState({
- uploading: true,
- error: ''
- });
-
- uploadBrandImage(
- this.state.brandImage,
- () => {
- $(this.refs.upload).button('complete');
-
- this.setState({
- brandImageExists: true,
- brandImage: null,
- brandImageTimestamp: Date.now(),
- uploading: false
- });
- },
- (err) => {
- $(this.refs.upload).button('reset');
-
- this.setState({
- uploading: false,
- error: err.message
- });
- }
- );
- }
-
- render() {
- let btnPrimaryClass = 'btn';
- if (this.state.brandImage) {
- btnPrimaryClass += ' btn-primary';
- }
-
- let letbtnDefaultClass = 'btn';
- if (!this.props.disabled) {
- letbtnDefaultClass += ' btn-default';
- }
-
- let img = null;
- if (this.state.brandImage) {
- img = (
- <img
- ref='image'
- className='brand-img'
- src=''
- />
- );
- } else if (this.state.brandImageExists) {
- img = (
- <img
- className='brand-img'
- src={Client4.getBrandImageUrl(this.state.brandImageTimestamp)}
- />
- );
- } else {
- img = (
- <p>
- <FormattedMessage
- id='admin.team.noBrandImage'
- defaultMessage='No brand image uploaded'
- />
- </p>
- );
- }
-
- return (
- <div className='form-group'>
- <label className='control-label col-sm-4'>
- <FormattedMessage
- id='admin.team.brandImageTitle'
- defaultMessage='Custom Brand Image:'
- />
- </label>
- <div className='col-sm-8'>
- {img}
- </div>
- <div className='col-sm-4'/>
- <div className='col-sm-8'>
- <div className='file__upload'>
- <button
- className={letbtnDefaultClass}
- disabled={this.props.disabled}
- >
- <FormattedMessage
- id='admin.team.chooseImage'
- defaultMessage='Choose New Image'
- />
- </button>
- <input
- ref='fileInput'
- type='file'
- accept='.jpg,.png,.bmp'
- disabled={this.props.disabled}
- onChange={this.handleImageChange}
- />
- </div>
- <button
- className={btnPrimaryClass}
- disabled={this.props.disabled || !this.state.brandImage}
- onClick={this.handleImageSubmit}
- id='upload-button'
- ref='upload'
- data-loading-text={'<span class=\'fa fa-refresh fa-rotate\'></span> ' + Utils.localizeMessage('admin.team.uploading', 'Uploading..')}
- data-complete-text={'<span class=\'fa fa-check\'></span> ' + Utils.localizeMessage('admin.team.uploaded', 'Uploaded!')}
- >
- <FormattedMessage
- id='admin.team.upload'
- defaultMessage='Upload'
- />
- </button>
- <br/>
- <FormError error={this.state.error}/>
- <p className='help-text no-margin'>
- <FormattedHTMLMessage
- id='admin.team.uploadDesc'
- defaultMessage='Customize your user experience by adding a custom image to your login screen. See examples at <a href="http://docs.mattermost.com/administration/config-settings.html#custom-branding" target="_blank">docs.mattermost.com/administration/config-settings.html#custom-branding</a>.'
- />
- </p>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/admin_console/client_versions_settings.jsx b/webapp/components/admin_console/client_versions_settings.jsx
deleted file mode 100644
index 0c9a5f58a..000000000
--- a/webapp/components/admin_console/client_versions_settings.jsx
+++ /dev/null
@@ -1,169 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import * as Utils from 'utils/utils.jsx';
-
-import AdminSettings from './admin_settings.jsx';
-import {FormattedMessage} from 'react-intl';
-import SettingsGroup from './settings_group.jsx';
-import TextSetting from './text_setting.jsx';
-
-export default class ClientVersionsSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
- }
-
- getConfigFromState(config) {
- config.ClientRequirements.AndroidLatestVersion = this.state.androidLatestVersion;
- config.ClientRequirements.AndroidMinVersion = this.state.androidMinVersion;
- config.ClientRequirements.DesktopLatestVersion = this.state.desktopLatestVersion;
- config.ClientRequirements.DesktopMinVersion = this.state.desktopMinVersion;
- config.ClientRequirements.IosLatestVersion = this.state.iosLatestVersion;
- config.ClientRequirements.IosMinVersion = this.state.iosMinVersion;
-
- return config;
- }
-
- getStateFromConfig(config) {
- return {
- androidLatestVersion: config.ClientRequirements.AndroidLatestVersion,
- androidMinVersion: config.ClientRequirements.AndroidMinVersion,
- desktopLatestVersion: config.ClientRequirements.DesktopLatestVersion,
- desktopMinVersion: config.ClientRequirements.DesktopMinVersion,
- iosLatestVersion: config.ClientRequirements.IosLatestVersion,
- iosMinVersion: config.ClientRequirements.IosMinVersion
- };
- }
-
- renderTitle() {
- return (
- <h3>
- <FormattedMessage
- id='admin.security.client_versions'
- defaultMessage='Client Versions'
- />
- </h3>
- );
- }
-
- renderSettings() {
- return (
- <SettingsGroup>
- <TextSetting
- id='androidLatestVersion'
- label={
- <FormattedMessage
- id='admin.client_versions.androidLatestVersion'
- defaultMessage='Latest Android Version'
- />
- }
- placeholder={Utils.localizeMessage('admin.client_versions.androidLatestVersion', 'X.X.X')}
- helpText={
- <FormattedMessage
- id='admin.client_versions.androidLatestVersionHelp'
- defaultMessage='The latest released Android version'
- />
- }
- value={this.state.androidLatestVersion}
- onChange={this.handleChange}
- />
- <TextSetting
- id='androidMinVersion'
- label={
- <FormattedMessage
- id='admin.client_versions.androidMinVersion'
- defaultMessage='Minimum Android Version'
- />
- }
- placeholder={Utils.localizeMessage('admin.client_versions.androidMinVersion', 'X.X.X')}
- helpText={
- <FormattedMessage
- id='admin.client_versions.androidMinVersionHelp'
- defaultMessage='The minimum compliant Android version'
- />
- }
- value={this.state.androidMinVersion}
- onChange={this.handleChange}
- />
- <TextSetting
- id='desktopLatestVersion'
- label={
- <FormattedMessage
- id='admin.client_versions.desktopLatestVersion'
- defaultMessage='Latest Desktop Version'
- />
- }
- placeholder={Utils.localizeMessage('admin.client_versions.desktopLatestVersion', 'X.X.X')}
- helpText={
- <FormattedMessage
- id='admin.client_versions.desktopLatestVersionHelp'
- defaultMessage='The latest released Desktop version'
- />
- }
- value={this.state.desktopLatestVersion}
- onChange={this.handleChange}
- />
- <TextSetting
- id='desktopMinVersion'
- label={
- <FormattedMessage
- id='admin.client_versions.desktopMinVersion'
- defaultMessage='Minimum Destop Version'
- />
- }
- placeholder={Utils.localizeMessage('admin.client_versions.desktopMinVersion', 'X.X.X')}
- helpText={
- <FormattedMessage
- id='admin.client_versions.desktopMinVersionHelp'
- defaultMessage='The minimum compliant Desktop version'
- />
- }
- value={this.state.desktopMinVersion}
- onChange={this.handleChange}
- />
- <TextSetting
- id='iosLatestVersion'
- label={
- <FormattedMessage
- id='admin.client_versions.iosLatestVersion'
- defaultMessage='Latest IOS Version'
- />
- }
- placeholder={Utils.localizeMessage('admin.client_versions.iosLatestVersion', 'X.X.X')}
- helpText={
- <FormattedMessage
- id='admin.client_versions.iosLatestVersionHelp'
- defaultMessage='The latest released IOS version'
- />
- }
- value={this.state.iosLatestVersion}
- onChange={this.handleChange}
- />
- <TextSetting
- id='iosMinVersion'
- label={
- <FormattedMessage
- id='admin.client_versions.iosMinVersion'
- defaultMessage='Minimum IOS Version'
- />
- }
- placeholder={Utils.localizeMessage('admin.client_versions.iosMinVersion', 'X.X.X')}
- helpText={
- <FormattedMessage
- id='admin.client_versions.iosMinVersionHelp'
- defaultMessage='The minimum compliant IOS version'
- />
- }
- value={this.state.iosMinVersion}
- onChange={this.handleChange}
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/cluster_settings.jsx b/webapp/components/admin_console/cluster_settings.jsx
deleted file mode 100644
index 36f86f0ef..000000000
--- a/webapp/components/admin_console/cluster_settings.jsx
+++ /dev/null
@@ -1,273 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import AdminSettings from './admin_settings.jsx';
-import BooleanSetting from './boolean_setting.jsx';
-import TextSetting from './text_setting.jsx';
-
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-import SettingsGroup from './settings_group.jsx';
-import ClusterTableContainer from './cluster_table_container.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-
-import {Client4} from 'mattermost-redux/client';
-
-export default class ClusterSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
- this.renderSettings = this.renderSettings.bind(this);
- this.overrideHandleChange = this.overrideHandleChange.bind(this);
- }
-
- getConfigFromState(config) {
- config.ClusterSettings.Enable = this.state.Enable;
- config.ClusterSettings.ClusterName = this.state.ClusterName;
- config.ClusterSettings.OverrideHostname = this.state.OverrideHostname;
- config.ClusterSettings.UseIpAddress = this.state.UseIpAddress;
- config.ClusterSettings.UseExperimentalGossip = this.state.UseExperimentalGossip;
- config.ClusterSettings.ReadOnlyConfig = this.state.ReadOnlyConfig;
- config.ClusterSettings.GossipPort = this.parseIntNonZero(this.state.GossipPort, 8074);
- config.ClusterSettings.StreamingPort = this.parseIntNonZero(this.state.StreamingPort, 8075);
- return config;
- }
-
- getStateFromConfig(config) {
- const settings = config.ClusterSettings;
-
- return {
- Enable: settings.Enable,
- ClusterName: settings.ClusterName,
- OverrideHostname: settings.OverrideHostname,
- UseIpAddress: settings.UseIpAddress,
- UseExperimentalGossip: settings.UseExperimentalGossip,
- ReadOnlyConfig: settings.ReadOnlyConfig,
- GossipPort: settings.GossipPort,
- StreamingPort: settings.StreamingPort,
- showWarning: false
- };
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.advance.cluster'
- defaultMessage='High Availability'
- />
- );
- }
-
- overrideHandleChange(id, value) {
- this.setState({
- showWarning: true
- });
-
- this.handleChange(id, value);
- }
-
- renderSettings() {
- const licenseEnabled = global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.Cluster === 'true';
- if (!licenseEnabled) {
- return null;
- }
-
- var configLoadedFromCluster = null;
-
- if (Client4.clusterId) {
- configLoadedFromCluster = (
- <div
- style={{marginBottom: '10px'}}
- className='alert alert-warning'
- >
- <i className='fa fa-warning'/>
- <FormattedHTMLMessage
- id='admin.cluster.loadedFrom'
- defaultMessage='This configuration file was loaded from Node ID {clusterId}. Please see the Troubleshooting Guide in our <a href="http://docs.mattermost.com/deployment/cluster.html" target="_blank">documentation</a> if you are accessing the System Console through a load balancer and experiencing issues.'
- values={{
- clusterId: Client4.clusterId
- }}
- />
- </div>
- );
- }
-
- var warning = null;
- if (this.state.showWarning) {
- warning = (
- <div
- style={{marginBottom: '10px'}}
- className='alert alert-warning'
- >
- <i className='fa fa-warning'/>
- <FormattedHTMLMessage
- id='admin.cluster.should_not_change'
- defaultMessage='WARNING: These settings may not sync with the other servers in the cluster. High Availability inter-node communication will not start until you modify the config.json to be identical on all servers and restart Mattermost. Please see the <a href="http://docs.mattermost.com/deployment/cluster.html" target="_blank">documentation</a> on how to add or remove a server from the cluster. If you are accessing the System Console through a load balancer and experiencing issues, please see the Troubleshooting Guide in our <a href="http://docs.mattermost.com/deployment/cluster.html" target="_blank">documentation</a>.'
- />
- </div>
- );
- }
-
- var clusterTableContainer = null;
- if (this.state.Enable) {
- clusterTableContainer = (<ClusterTableContainer/>);
- }
-
- return (
- <SettingsGroup>
- {configLoadedFromCluster}
- {clusterTableContainer}
- <div className='banner'>
- <FormattedMessage
- id='admin.cluster.noteDescription'
- defaultMessage='Changing properties in this section will require a server restart before taking effect. When High Availability mode is enabled, the System Console is set to read-only and can only be changed from the configuration file unless ReadOnlyConfig is disabled in the configuration file.'
- />
- </div>
- {warning}
- <BooleanSetting
- id='Enable'
- label={
- <FormattedMessage
- id='admin.cluster.enableTitle'
- defaultMessage='Enable High Availability Mode:'
- />
- }
- helpText={
- <FormattedHTMLMessage
- id='admin.cluster.enableDescription'
- defaultMessage='When true, Mattermost will run in High Availability mode. Please see <a href="http://docs.mattermost.com/deployment/cluster.html" target="_blank">documentation</a> to learn more about configuring High Availability for Mattermost.'
- />
- }
- value={this.state.Enable}
- onChange={this.overrideHandleChange}
- />
- <TextSetting
- id='ClusterName'
- label={
- <FormattedMessage
- id='admin.cluster.ClusterName'
- defaultMessage='Cluster Name:'
- />
- }
- placeholder={Utils.localizeMessage('admin.cluster.ClusterNameEx', 'Ex "Production" or "Staging"')}
- helpText={
- <FormattedMessage
- id='admin.cluster.ClusterNameDesc'
- defaultMessage='The cluster to join by name. Only nodes with the same cluster name will join together. This is to support Blue-Green deployments or staging pointing to the same database.'
- />
- }
- value={this.state.ClusterName}
- onChange={this.overrideHandleChange}
- />
- <TextSetting
- id='OverrideHostname'
- label={
- <FormattedMessage
- id='admin.cluster.OverrideHostname'
- defaultMessage='Override Hostname:'
- />
- }
- placeholder={Utils.localizeMessage('admin.cluster.OverrideHostnameEx', 'Ex "app-server-01"')}
- helpText={
- <FormattedMessage
- id='admin.cluster.OverrideHostnameDesc'
- defaultMessage='The default value of <blank> will attempt to get the Hostname from the OS or use the IP Address. You can override the hostname of this server with this property. It is not recommended to override the Hostname unless needed. This property can also be set to a specific IP Address if needed.'
- />
- }
- value={this.state.OverrideHostname}
- onChange={this.overrideHandleChange}
- />
- <BooleanSetting
- id='UseIpAddress'
- label={
- <FormattedMessage
- id='admin.cluster.UseIpAddress'
- defaultMessage='Use IP Address:'
- />
- }
- helpText={
- <FormattedHTMLMessage
- id='admin.cluster.UseIpAddressDesc'
- defaultMessage='When true, the cluster will attempt to communicate via IP Address vs using the hostname.'
- />
- }
- value={this.state.UseIpAddress}
- onChange={this.overrideHandleChange}
- />
- <BooleanSetting
- id='UseExperimentalGossip'
- label={
- <FormattedMessage
- id='admin.cluster.UseExperimentalGossip'
- defaultMessage='Use Experimental Gossip:'
- />
- }
- helpText={
- <FormattedHTMLMessage
- id='admin.cluster.UseExperimentalGossipDesc'
- defaultMessage='When true, the server will attempt to communicate via the gossip protocol over the gossip port. When false the server will attempt to communicate over the streaming port. When false the gossip port and protocol are still used to determine cluster health.'
- />
- }
- value={this.state.UseExperimentalGossip}
- onChange={this.overrideHandleChange}
- />
- <BooleanSetting
- id='ReadOnlyConfig'
- label={
- <FormattedMessage
- id='admin.cluster.ReadOnlyConfig'
- defaultMessage='Read Only Config:'
- />
- }
- helpText={
- <FormattedHTMLMessage
- id='admin.cluster.ReadOnlyConfigDesc'
- defaultMessage='When true, the server will reject changes made to the configuration file from the system console. When running in production it is recommened to set this to true.'
- />
- }
- value={this.state.ReadOnlyConfig}
- onChange={this.overrideHandleChange}
- />
- <TextSetting
- id='GossipPort'
- label={
- <FormattedMessage
- id='admin.cluster.GossipPort'
- defaultMessage='Gossip Port:'
- />
- }
- placeholder={Utils.localizeMessage('admin.cluster.GossipPortEx', 'Ex "8074"')}
- helpText={
- <FormattedMessage
- id='admin.cluster.GossipPortDesc'
- defaultMessage='The port used for the gossip protocol. Both UDP and TCP should abe allowed on this port.'
- />
- }
- value={this.state.GossipPort}
- onChange={this.overrideHandleChange}
- />
- <TextSetting
- id='StreamingPort'
- label={
- <FormattedMessage
- id='admin.cluster.StreamingPort'
- defaultMessage='Streaming Port:'
- />
- }
- placeholder={Utils.localizeMessage('admin.cluster.StreamingPortEx', 'Ex "8075"')}
- helpText={
- <FormattedMessage
- id='admin.cluster.StreamingPortDesc'
- defaultMessage='The port used for streaming data between servers.'
- />
- }
- value={this.state.StreamingPort}
- onChange={this.overrideHandleChange}
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/cluster_table.jsx b/webapp/components/admin_console/cluster_table.jsx
deleted file mode 100644
index e7157635d..000000000
--- a/webapp/components/admin_console/cluster_table.jsx
+++ /dev/null
@@ -1,172 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import {FormattedMessage} from 'react-intl';
-import * as Utils from 'utils/utils.jsx';
-
-import statusGreen from 'images/status_green.png';
-import statusYellow from 'images/status_yellow.png';
-
-export default class ClusterTable extends React.Component {
- static propTypes = {
- clusterInfos: PropTypes.array.isRequired,
- reload: PropTypes.func.isRequired
- }
-
- render() {
- var versionMismatch = (
- <img
- className='cluster-status'
- src={statusGreen}
- />
- );
-
- var configMismatch = (
- <img
- className='cluster-status'
- src={statusGreen}
- />
- );
-
- var version = '';
- var configHash = '';
- var singleItem = false;
-
- if (this.props.clusterInfos.length) {
- version = this.props.clusterInfos[0].version;
- configHash = this.props.clusterInfos[0].config_hash;
- singleItem = this.props.clusterInfos.length === 1;
- }
-
- this.props.clusterInfos.map((clusterInfo) => {
- if (clusterInfo.version !== version) {
- versionMismatch = (
- <img
- className='cluster-status'
- src={statusYellow}
- />
- );
- }
-
- if (clusterInfo.config_hash !== configHash) {
- configMismatch = (
- <img
- className='cluster-status'
- src={statusYellow}
- />
- );
- }
-
- return null;
- });
-
- var items = this.props.clusterInfos.map((clusterInfo) => {
- var status = null;
-
- if (clusterInfo.hostname === '') {
- clusterInfo.hostname = Utils.localizeMessage('admin.cluster.unknown', 'unknown');
- }
-
- if (clusterInfo.version === '') {
- clusterInfo.version = Utils.localizeMessage('admin.cluster.unknown', 'unknown');
- }
-
- if (clusterInfo.config_hash === '') {
- clusterInfo.config_hash = Utils.localizeMessage('admin.cluster.unknown', 'unknown');
- }
-
- if (singleItem) {
- status = (
- <img
- className='cluster-status'
- src={statusYellow}
- />
- );
- } else {
- status = (
- <img
- className='cluster-status'
- src={statusGreen}
- />
- );
- }
-
- return (
- <tr key={clusterInfo.ipaddress}>
- <td style={{whiteSpace: 'nowrap'}}>{status}</td>
- <td style={{whiteSpace: 'nowrap'}}>{clusterInfo.hostname}</td>
- <td style={{whiteSpace: 'nowrap'}}>{versionMismatch} {clusterInfo.version}</td>
- <td style={{whiteSpace: 'nowrap'}}><div className='config-hash'>{configMismatch} {clusterInfo.config_hash}</div></td>
- <td style={{whiteSpace: 'nowrap'}}>{clusterInfo.ipaddress}</td>
- </tr>
- );
- });
-
- return (
- <div
- className='cluster-panel__table'
- style={{
- margin: '10px',
- marginBottom: '30px'
- }}
- >
- <div className='text-right'>
- <button
- type='submit'
- className='btn btn-link'
- onClick={this.props.reload}
- >
- <i className='fa fa-refresh'/>
- <FormattedMessage
- id='admin.cluster.status_table.reload'
- defaultMessage=' Reload Cluster Status'
- />
- </button>
- </div>
- <table className='table'>
- <thead>
- <tr>
- <th>
- <FormattedMessage
- id='admin.cluster.status_table.status'
- defaultMessage='Status'
- />
- </th>
- <th>
- <FormattedMessage
- id='admin.cluster.status_table.hostname'
- defaultMessage='Hostname'
- />
- </th>
- <th>
- <FormattedMessage
- id='admin.cluster.status_table.version'
- defaultMessage='Version'
- />
- </th>
- <th>
- <FormattedMessage
- id='admin.cluster.status_table.config_hash'
- defaultMessage='Config File MD5'
- />
- </th>
- <th>
- <FormattedMessage
- id='admin.cluster.status_table.url'
- defaultMessage='Gossip Address'
- />
- </th>
- </tr>
- </thead>
- <tbody>
- {items}
- </tbody>
- </table>
- </div>
- );
- }
-}
diff --git a/webapp/components/admin_console/cluster_table_container.jsx b/webapp/components/admin_console/cluster_table_container.jsx
deleted file mode 100644
index 7a67bb842..000000000
--- a/webapp/components/admin_console/cluster_table_container.jsx
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import ClusterTable from './cluster_table.jsx';
-import LoadingScreen from '../loading_screen.jsx';
-
-import {getClusterStatus} from 'actions/admin_actions.jsx';
-
-export default class ClusterTableContainer extends React.Component {
- constructor(props) {
- super(props);
-
- this.load = this.load.bind(this);
- this.reload = this.reload.bind(this);
-
- this.interval = null;
-
- this.state = {
- clusterInfos: null
- };
- }
-
- load() {
- getClusterStatus(
- (data) => {
- this.setState({
- clusterInfos: data
- });
- },
- null
- );
- }
-
- componentWillMount() {
- this.load();
-
- // reload the cluster status every 15 seconds
- this.interval = setInterval(this.load, 15000);
- }
-
- componentWillUnmount() {
- if (this.interval) {
- clearInterval(this.interval);
- }
- }
-
- reload(e) {
- if (e) {
- e.preventDefault();
- }
-
- this.setState({
- clusterInfos: null
- });
-
- this.load();
- }
-
- render() {
- if (this.state.clusterInfos == null) {
- return (<LoadingScreen/>);
- }
-
- return (
- <ClusterTable
- clusterInfos={this.state.clusterInfos}
- reload={this.reload}
- />
- );
- }
-}
diff --git a/webapp/components/admin_console/color_setting.jsx b/webapp/components/admin_console/color_setting.jsx
deleted file mode 100644
index 483b585ee..000000000
--- a/webapp/components/admin_console/color_setting.jsx
+++ /dev/null
@@ -1,119 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import Setting from './setting.jsx';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {ChromePicker} from 'react-color';
-
-export default class ColorSetting extends React.PureComponent {
- static propTypes = {
-
- /*
- * The unique identifer for the admin console setting
- */
- id: PropTypes.string.isRequired,
-
- /*
- * The text/jsx display name for the setting
- */
- label: PropTypes.node.isRequired,
-
- /*
- * The text/jsx help text to display underneath the setting
- */
- helpText: PropTypes.node,
-
- /*
- * The hex color value
- */
- value: PropTypes.string.isRequired,
-
- /*
- * Function called when the input changes
- */
- onChange: PropTypes.func,
-
- /*
- * Set to disable the setting
- */
- disabled: PropTypes.bool
- }
-
- constructor(props) {
- super(props);
-
- this.state = {
- showPicker: false
- };
- }
-
- componentDidMount() {
- document.addEventListener('click', this.closePicker);
- }
-
- componentWillUnmount() {
- document.removeEventListener('click', this.closePicker);
- }
-
- handleChange = (color) => {
- this.props.onChange(this.props.id, color.hex);
- }
-
- togglePicker = () => {
- if (this.props.disabled) {
- this.setState({showPicker: false});
- }
- this.setState({showPicker: !this.state.showPicker});
- }
-
- closePicker = (e) => {
- if (!e.target.closest('.picker-' + this.props.id)) {
- this.setState({showPicker: false});
- }
- }
-
- onTextInput = (e) => {
- this.props.onChange(this.props.id, e.target.value);
- }
-
- render() {
- let picker;
- if (this.state.showPicker) {
- picker = (
- <div className={'color-picker__popover picker-' + this.props.id}>
- <ChromePicker
- color={this.props.value}
- onChange={this.handleChange}
- />
- </div>
- );
- }
-
- return (
- <Setting
- label={this.props.label}
- helpText={this.props.helpText}
- inputId={this.props.id}
- >
- <div className='input-group color-picker colorpicker-element'>
- <input
- type='text'
- className='form-control'
- value={this.props.value}
- onChange={this.onTextInput}
- disabled={this.props.disabled}
- />
- <span
- className={'input-group-addon picker-' + this.props.id}
- onClick={this.togglePicker}
- >
- <i style={{backgroundColor: this.props.value}}/>
- </span>
- {picker}
- </div>
- </Setting>
- );
- }
-}
diff --git a/webapp/components/admin_console/compliance_reports/compliance_reports.jsx b/webapp/components/admin_console/compliance_reports/compliance_reports.jsx
deleted file mode 100644
index af361bace..000000000
--- a/webapp/components/admin_console/compliance_reports/compliance_reports.jsx
+++ /dev/null
@@ -1,394 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import LoadingScreen from 'components/loading_screen.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-import UserStore from 'stores/user_store.jsx';
-import {Client4} from 'mattermost-redux/client';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {FormattedMessage, FormattedDate, FormattedTime} from 'react-intl';
-
-export default class ComplianceReports extends React.PureComponent {
- static propTypes = {
-
- /*
- * Set if compliance reports are enabled in the config
- */
- enabled: PropTypes.bool.isRequired,
-
- /*
- * Array of reports to render
- */
- reports: PropTypes.arrayOf(PropTypes.object).isRequired,
-
- /*
- * Error message to display
- */
- serverError: PropTypes.string,
-
- actions: PropTypes.shape({
-
- /*
- * Function to get compliance reports
- */
- getComplianceReports: PropTypes.func.isRequired,
-
- /*
- * Function to save compliance reports
- */
- createComplianceReport: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.state = {
- loadingReports: true
- };
- }
-
- componentDidMount() {
- if (global.window.mm_license.IsLicensed !== 'true' || !this.props.enabled) {
- return;
- }
-
- this.props.actions.getComplianceReports().then(
- () => this.setState({loadingReports: false})
- );
- }
-
- reload = () => {
- this.setState({loadingReports: true});
-
- this.props.actions.getComplianceReports().then(
- () => this.setState({loadingReports: false})
- );
- }
-
- runReport = (e) => {
- e.preventDefault();
-
- this.setState({runningReport: true});
-
- const job = {};
- job.desc = this.refs.desc.value;
- job.emails = this.refs.emails.value;
- job.keywords = this.refs.keywords.value;
- job.start_at = Date.parse(this.refs.from.value);
- job.end_at = Date.parse(this.refs.to.value);
-
- this.props.actions.createComplianceReport(job).then(
- (data) => {
- if (data) {
- this.refs.emails.value = '';
- this.refs.keywords.value = '';
- this.refs.desc.value = '';
- this.refs.from.value = '';
- this.refs.to.value = '';
- }
- this.setState({runningReport: false});
- }
- );
- }
-
- getDateTime(millis) {
- const date = new Date(millis);
- return (
- <span style={{whiteSpace: 'nowrap'}}>
- <FormattedDate
- value={date}
- day='2-digit'
- month='short'
- year='numeric'
- />
- {' - '}
- <FormattedTime
- value={date}
- hour='2-digit'
- minute='2-digit'
- />
- </span>
- );
- }
-
- render() {
- if (global.window.mm_license.IsLicensed !== 'true' || !this.props.enabled) {
- return <div/>;
- }
-
- let content = null;
- if (this.state.loadingReports) {
- content = <LoadingScreen/>;
- } else {
- var list = [];
-
- for (var i = 0; i < this.props.reports.length; i++) {
- const report = this.props.reports[i];
-
- let params = '';
- if (report.type === 'adhoc') {
- params = (
- <span>
- <FormattedMessage
- id='admin.compliance_reports.from'
- defaultMessage='From:'
- />{' '}{this.getDateTime(report.start_at)}
- <br/>
- <FormattedMessage
- id='admin.compliance_reports.to'
- defaultMessage='To:'
- />{' '}{this.getDateTime(report.end_at)}
- <br/>
- <FormattedMessage
- id='admin.compliance_reports.emails'
- defaultMessage='Emails:'
- />{' '}{report.emails}
- <br/>
- <FormattedMessage
- id='admin.compliance_reports.keywords'
- defaultMessage='Keywords:'
- />{' '}{report.keywords}
- </span>);
- }
-
- let download = '';
- if (report.status === 'finished') {
- download = (
- <a href={`${Client4.getBaseRoute()}/compliance/reports/${report.id}/download`}>
- <FormattedMessage
- id='admin.compliance_table.download'
- defaultMessage='Download'
- />
- </a>
- );
- }
-
- let status = report.status;
- if (report.status === 'finished') {
- status = (
- <span style={{color: 'green'}}>{report.status}</span>
- );
- }
-
- if (report.status === 'failed') {
- status = (
- <span style={{color: 'red'}}>{report.status}</span>
- );
- }
-
- let user = report.user_id;
- const profile = UserStore.getProfile(report.user_id);
- if (profile) {
- user = profile.email;
- }
-
- list[i] = (
- <tr key={report.id}>
- <td style={{whiteSpace: 'nowrap'}}>{download}</td>
- <td>{this.getDateTime(report.create_at)}</td>
- <td>{status}</td>
- <td>{report.count}</td>
- <td>{report.type}</td>
- <td style={{whiteSpace: 'nowrap'}}>{report.desc}</td>
- <td>{user}</td>
- <td style={{whiteSpace: 'nowrap'}}>{params}</td>
- </tr>
- );
- }
-
- content = (
- <div style={{margin: '10px'}}>
- <table className='table'>
- <thead>
- <tr>
- <th/>
- <th>
- <FormattedMessage
- id='admin.compliance_table.timestamp'
- defaultMessage='Timestamp'
- />
- </th>
- <th>
- <FormattedMessage
- id='admin.compliance_table.status'
- defaultMessage='Status'
- />
- </th>
- <th>
- <FormattedMessage
- id='admin.compliance_table.records'
- defaultMessage='Records'
- />
- </th>
- <th>
- <FormattedMessage
- id='admin.compliance_table.type'
- defaultMessage='Type'
- />
- </th>
- <th>
- <FormattedMessage
- id='admin.compliance_table.desc'
- defaultMessage='Description'
- />
- </th>
- <th>
- <FormattedMessage
- id='admin.compliance_table.userId'
- defaultMessage='Requested By'
- />
- </th>
- <th>
- <FormattedMessage
- id='admin.compliance_table.params'
- defaultMessage='Params'
- />
- </th>
- </tr>
- </thead>
- <tbody>
- {list}
- </tbody>
- </table>
- </div>
- );
- }
-
- let serverError = '';
- if (this.props.serverError) {
- serverError = (
- <div
- className='form-group has-error'
- style={{marginTop: '10px'}}
- >
- <label className='control-label'>{this.props.serverError}</label>
- </div>
- );
- }
-
- return (
- <div className='panel compliance-panel'>
- <h3>
- <FormattedMessage
- id='admin.compliance_reports.title'
- defaultMessage='Compliance Reports'
- />
- </h3>
- <div className='row'>
- <div className='col-sm-6 col-md-4 form-group'>
- <label>
- <FormattedMessage
- id='admin.compliance_reports.desc'
- defaultMessage='Job Name:'
- />
- </label>
- <input
- type='text'
- className='form-control'
- id='desc'
- ref='desc'
- placeholder={Utils.localizeMessage('admin.compliance_reports.desc_placeholder', 'E.g. "Audit 445 for HR"')}
- />
- </div>
- <div className='col-sm-3 col-md-2 form-group'>
- <label>
- <FormattedMessage
- id='admin.compliance_reports.from'
- defaultMessage='From:'
- />
- </label>
- <input
- type='text'
- className='form-control'
- id='from'
- ref='from'
- placeholder={Utils.localizeMessage('admin.compliance_reports.from_placeholder', 'E.g. "2016-03-11"')}
- />
- </div>
- <div className='col-sm-3 col-md-2 form-group'>
- <label>
- <FormattedMessage
- id='admin.compliance_reports.to'
- defaultMessage='To:'
- />
- </label>
- <input
- type='text'
- className='form-control'
- id='to'
- ref='to'
- placeholder={Utils.localizeMessage('admin.compliance_reports.to_placeholder', 'E.g. "2016-03-15"')}
- />
- </div>
- </div>
- <div className='row'>
- <div className='col-sm-6 col-md-4 form-group'>
- <label>
- <FormattedMessage
- id='admin.compliance_reports.emails'
- defaultMessage='Emails:'
- />
- </label>
- <input
- type='text'
- className='form-control'
- id='emails'
- ref='emails'
- placeholder={Utils.localizeMessage('admin.compliance_reports.emails_placeholder', 'E.g. "bill@example.com, bob@example.com"')}
- />
- </div>
- <div className='col-sm-6 col-md-4 form-group'>
- <label>
- <FormattedMessage
- id='admin.compliance_reports.keywords'
- defaultMessage='Keywords:'
- />
- </label>
- <input
- type='text'
- className='form-control'
- id='keywords'
- ref='keywords'
- placeholder={Utils.localizeMessage('admin.compliance_reports.keywords_placeholder', 'E.g. "shorting stock"')}
- />
- </div>
- </div>
- <div className='clearfix'>
- <button
- id='run-button'
- type='submit'
- className='btn btn-primary'
- onClick={this.runReport}
- >
- <FormattedMessage
- id='admin.compliance_reports.run'
- defaultMessage='Run Compliance Report'
- />
- </button>
- </div>
- {serverError}
- <div className='text-right'>
- <button
- type='submit'
- className='btn btn-link'
- disabled={this.state.runningReport}
- onClick={this.reload}
- >
- <i className='fa fa-refresh'/>
- <FormattedMessage
- id='admin.compliance_reports.reload'
- defaultMessage='Reload Completed Compliance Reports'
- />
- </button>
- </div>
- <div className='compliance-panel__table'>
- {content}
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/admin_console/compliance_reports/index.js b/webapp/components/admin_console/compliance_reports/index.js
deleted file mode 100644
index 1cbf669e5..000000000
--- a/webapp/components/admin_console/compliance_reports/index.js
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getComplianceReports, createComplianceReport} from 'mattermost-redux/actions/admin';
-
-import {getComplianceReports as selectComplianceReports, getConfig} from 'mattermost-redux/selectors/entities/admin';
-
-import ComplianceReports from './compliance_reports.jsx';
-
-function mapStateToProps(state, ownProps) {
- let enabled = false;
- const config = getConfig(state);
- if (config && config.ComplianceSettings) {
- enabled = config.ComplianceSettings.Enable;
- }
-
- let serverError;
- const error = state.requests.admin.createCompliance.error;
- if (error) {
- serverError = error.message;
- }
-
- const reports = Object.values(selectComplianceReports(state)).sort((a, b) => {
- return b.create_at - a.create_at;
- });
-
- return {
- ...ownProps,
- enabled,
- reports,
- serverError
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getComplianceReports,
- createComplianceReport
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(ComplianceReports);
diff --git a/webapp/components/admin_console/compliance_settings.jsx b/webapp/components/admin_console/compliance_settings.jsx
deleted file mode 100644
index 5521c6e39..000000000
--- a/webapp/components/admin_console/compliance_settings.jsx
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import * as Utils from 'utils/utils.jsx';
-
-import AdminSettings from './admin_settings.jsx';
-import BooleanSetting from './boolean_setting.jsx';
-import {FormattedHTMLMessage, FormattedMessage} from 'react-intl';
-import SettingsGroup from './settings_group.jsx';
-import TextSetting from './text_setting.jsx';
-
-export default class ComplianceSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
- }
-
- getConfigFromState(config) {
- config.ComplianceSettings.Enable = this.state.enable;
- config.ComplianceSettings.Directory = this.state.directory;
- config.ComplianceSettings.EnableDaily = this.state.enableDaily;
-
- return config;
- }
-
- getStateFromConfig(config) {
- return {
- enable: config.ComplianceSettings.Enable,
- directory: config.ComplianceSettings.Directory,
- enableDaily: config.ComplianceSettings.EnableDaily
- };
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.compliance.title'
- defaultMessage='Compliance Settings'
- />
- );
- }
-
- renderSettings() {
- const licenseEnabled = global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.Compliance === 'true';
-
- let bannerContent;
- if (!licenseEnabled) {
- bannerContent = (
- <div className='banner warning'>
- <div className='banner__content'>
- <FormattedHTMLMessage
- id='admin.compliance.noLicense'
- defaultMessage='<h4 class="banner__heading">Note:</h4><p>Compliance is an enterprise feature. Your current license does not support Compliance. Click <a href="http://mattermost.com"target="_blank">here</a> for information and pricing on enterprise licenses.</p>'
- />
- </div>
- </div>
- );
- }
-
- return (
- <SettingsGroup>
- {bannerContent}
- <BooleanSetting
- id='enable'
- label={
- <FormattedMessage
- id='admin.compliance.enableTitle'
- defaultMessage='Enable Compliance Reporting:'
- />
- }
- helpText={
- <FormattedHTMLMessage
- id='admin.compliance.enableDesc'
- defaultMessage='When true, Mattermost allows compliance reporting from the <strong>Compliance and Auditing</strong> tab. See <a href="https://docs.mattermost.com/administration/compliance.html" target="_blank">documentation</a> to learn more.'
- />
- }
- value={this.state.enable}
- onChange={this.handleChange}
- disabled={!licenseEnabled}
- />
- <TextSetting
- id='directory'
- label={
- <FormattedMessage
- id='admin.compliance.directoryTitle'
- defaultMessage='Compliance Report Directory:'
- />
- }
- placeholder={Utils.localizeMessage('admin.sql.maxOpenExample', 'Ex "10"')}
- helpText={
- <FormattedMessage
- id='admin.compliance.directoryDescription'
- defaultMessage='Directory to which compliance reports are written. If blank, will be set to ./data/.'
- />
- }
- value={this.state.directory}
- onChange={this.handleChange}
- disabled={!licenseEnabled || !this.state.enable}
- />
- <BooleanSetting
- id='enableDaily'
- label={
- <FormattedMessage
- id='admin.compliance.enableDailyTitle'
- defaultMessage='Enable Daily Report:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.compliance.enableDailyDesc'
- defaultMessage='When true, Mattermost will generate a daily compliance report.'
- />
- }
- value={this.state.enableDaily}
- onChange={this.handleChange}
- disabled={!licenseEnabled || !this.state.enable}
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/configuration_settings.jsx b/webapp/components/admin_console/configuration_settings.jsx
deleted file mode 100644
index 6ac68a3bb..000000000
--- a/webapp/components/admin_console/configuration_settings.jsx
+++ /dev/null
@@ -1,356 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-
-import ErrorStore from 'stores/error_store.jsx';
-
-import {ErrorBarTypes} from 'utils/constants.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import {invalidateAllCaches, reloadConfig} from 'actions/admin_actions.jsx';
-import AdminSettings from './admin_settings.jsx';
-import BooleanSetting from './boolean_setting.jsx';
-import {ConnectionSecurityDropdownSettingWebserver} from './connection_security_dropdown_setting.jsx';
-import SettingsGroup from './settings_group.jsx';
-import RequestButton from './request_button/request_button';
-import TextSetting from './text_setting.jsx';
-import WebserverModeDropdownSetting from './webserver_mode_dropdown_setting.jsx';
-
-export default class ConfigurationSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.handleSaved = this.handleSaved.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
- }
-
- componentWillReceiveProps(nextProps) {
- // special case for this page since we don't update AdminSettings components when the
- // stored config changes, but we want this page to update when you reload the config
- this.setState(this.getStateFromConfig(nextProps.config));
- }
-
- getConfigFromState(config) {
- config.ServiceSettings.SiteURL = this.state.siteURL;
- config.ServiceSettings.ListenAddress = this.state.listenAddress;
- config.ServiceSettings.WebserverMode = this.state.webserverMode;
- config.ServiceSettings.ConnectionSecurity = this.state.connectionSecurity;
- config.ServiceSettings.TLSCertFile = this.state.TLSCertFile;
- config.ServiceSettings.TLSKeyFile = this.state.TLSKeyFile;
- config.ServiceSettings.UseLetsEncrypt = this.state.useLetsEncrypt;
- config.ServiceSettings.LetsEncryptCertificateCacheFile = this.state.letsEncryptCertificateCacheFile;
- config.ServiceSettings.Forward80To443 = this.state.forward80To443;
- config.ServiceSettings.ReadTimeout = this.parseIntNonZero(this.state.readTimeout);
- config.ServiceSettings.WriteTimeout = this.parseIntNonZero(this.state.writeTimeout);
- config.ServiceSettings.EnableAPIv3 = this.state.enableAPIv3;
-
- return config;
- }
-
- getStateFromConfig(config) {
- return {
- siteURL: config.ServiceSettings.SiteURL,
- listenAddress: config.ServiceSettings.ListenAddress,
- webserverMode: config.ServiceSettings.WebserverMode,
- connectionSecurity: config.ServiceSettings.ConnectionSecurity,
- TLSCertFile: config.ServiceSettings.TLSCertFile,
- TLSKeyFile: config.ServiceSettings.TLSKeyFile,
- useLetsEncrypt: config.ServiceSettings.UseLetsEncrypt,
- letsEncryptCertificateCacheFile: config.ServiceSettings.LetsEncryptCertificateCacheFile,
- forward80To443: config.ServiceSettings.Forward80To443,
- readTimeout: config.ServiceSettings.ReadTimeout,
- writeTimeout: config.ServiceSettings.WriteTimeout,
- enableAPIv3: config.ServiceSettings.EnableAPIv3
- };
- }
-
- handleSaved(newConfig) {
- if (newConfig.ServiceSettings.SiteURL) {
- ErrorStore.clearError(ErrorBarTypes.SITE_URL);
- }
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.general.configuration'
- defaultMessage='Configuration'
- />
- );
- }
-
- renderSettings() {
- const reloadConfigurationHelpText = (
- <FormattedMessage
- id='admin.reload.reloadDescription'
- defaultMessage='Deployments using multiple databases can switch from one master database to another without restarting the Mattermost server by updating "config.json" to the new desired configuration and using the {featureName} feature to load the new settings while the server is running. The administrator should then use the {recycleDatabaseConnections} feature to recycle the database connections based on the new settings.'
- values={{
- featureName: (
- <b>
- <FormattedMessage
- id='admin.reload.reloadDescription.featureName'
- defaultMessage='Reload Configuration from Disk'
- />
- </b>
- ),
- recycleDatabaseConnections: (
- <a href='../advanced/database'>
- <b>
- <FormattedMessage
- id='admin.reload.reloadDescription.recycleDatabaseConnections'
- defaultMessage='Database > Recycle Database Connections'
- />
- </b>
- </a>
- )
- }}
- />
- );
-
- let reloadConfigButton = <div/>;
- if (global.window.mm_license.IsLicensed === 'true') {
- reloadConfigButton = (
- <RequestButton
- requestAction={reloadConfig}
- helpText={reloadConfigurationHelpText}
- buttonText={
- <FormattedMessage
- id='admin.reload.button'
- defaultMessage='Reload Configuration From Disk'
- />
- }
- showSuccessMessage={false}
- errorMessage={{
- id: 'admin.reload.reloadFail',
- defaultMessage: 'Reload unsuccessful: {error}'
- }}
- />
- );
- }
-
- return (
- <SettingsGroup>
- <div className='banner'>
- <div className='banner__content'>
- <FormattedMessage
- id='admin.rate.noteDescription'
- defaultMessage='Changing properties other than Site URL in this section will require a server restart before taking effect.'
- />
- </div>
- </div>
- <TextSetting
- id='siteURL'
- label={
- <FormattedMessage
- id='admin.service.siteURL'
- defaultMessage='Site URL:'
- />
- }
- placeholder={Utils.localizeMessage('admin.service.siteURLExample', 'Ex "https://mattermost.example.com:1234"')}
- helpText={
- <FormattedMessage
- id='admin.service.siteURLDescription'
- defaultMessage='The URL that users will use to access Mattermost. Standard ports, such as 80 and 443, can be omitted, but non-standard ports are required. For example: http://mattermost.example.com:8065. This setting is required.'
- />
- }
- value={this.state.siteURL}
- onChange={this.handleChange}
- />
- <TextSetting
- id='listenAddress'
- label={
- <FormattedMessage
- id='admin.service.listenAddress'
- defaultMessage='Listen Address:'
- />
- }
- placeholder={Utils.localizeMessage('admin.service.listenExample', 'Ex ":8065"')}
- helpText={
- <FormattedMessage
- id='admin.service.listenDescription'
- defaultMessage='The address and port to which to bind and listen. Specifying ":8065" will bind to all network interfaces. Specifying "127.0.0.1:8065" will only bind to the network interface having that IP address. If you choose a port of a lower level (called "system ports" or "well-known ports", in the range of 0-1023), you must have permissions to bind to that port. On Linux you can use: "sudo setcap cap_net_bind_service=+ep ./bin/platform" to allow Mattermost to bind to well-known ports.'
- />
- }
- value={this.state.listenAddress}
- onChange={this.handleChange}
- />
- <ConnectionSecurityDropdownSettingWebserver
- value={this.state.connectionSecurity}
- onChange={this.handleChange}
- disabled={false}
- />
- <TextSetting
- id='TLSCertFile'
- label={
- <FormattedMessage
- id='admin.service.tlsCertFile'
- defaultMessage='TLS Certificate File:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.service.tlsCertFileDescription'
- defaultMessage='The certificate file to use.'
- />
- }
- disabled={this.state.useLetsEncrypt}
- value={this.state.TLSCertFile}
- onChange={this.handleChange}
- />
- <TextSetting
- id='TLSKeyFile'
- label={
- <FormattedMessage
- id='admin.service.tlsKeyFile'
- defaultMessage='TLS Key File:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.service.tlsKeyFileDescription'
- defaultMessage='The private key file to use.'
- />
- }
- disabled={this.state.useLetsEncrypt}
- value={this.state.TLSKeyFile}
- onChange={this.handleChange}
- />
- <BooleanSetting
- id='useLetsEncrypt'
- label={
- <FormattedMessage
- id='admin.service.useLetsEncrypt'
- defaultMessage="Use Let's Encrypt:"
- />
- }
- helpText={
- <FormattedMessage
- id='admin.service.useLetsEncryptDescription'
- defaultMessage="Enable the automatic retreval of certificates from the Let's Encrypt. The certificate will be retrieved when a client attempts to connect from a new domain. This will work with multiple domains."
- />
- }
- value={this.state.useLetsEncrypt}
- onChange={this.handleChange}
- />
- <TextSetting
- id='letsEncryptCertificateCacheFile'
- label={
- <FormattedMessage
- id='admin.service.letsEncryptCertificateCacheFile'
- defaultMessage="Let's Encrypt Certificate Cache File:"
- />
- }
- helpText={
- <FormattedMessage
- id='admin.service.letsEncryptCertificateCacheFileDescription'
- defaultMessage="Certificates retrieved and other data about the Let's Encrypt service will be stored in this file."
- />
- }
- disabled={!this.state.useLetsEncrypt}
- value={this.state.letsEncryptCertificateCacheFile}
- onChange={this.handleChange}
- />
- <BooleanSetting
- id='forward80To443'
- label={
- <FormattedMessage
- id='admin.service.forward80To443'
- defaultMessage='Forward port 80 to 443:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.service.forward80To443Description'
- defaultMessage='Forwards all insecure traffic from port 80 to secure port 443'
- />
- }
- value={this.state.forward80To443}
- onChange={this.handleChange}
- />
- <TextSetting
- id='readTimeout'
- label={
- <FormattedMessage
- id='admin.service.readTimeout'
- defaultMessage='Read Timeout:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.service.readTimeoutDescription'
- defaultMessage='Maximum time allowed from when the connection is accepted to when the request body is fully read.'
- />
- }
- value={this.state.readTimeout}
- onChange={this.handleChange}
- />
- <TextSetting
- id='writeTimeout'
- label={
- <FormattedMessage
- id='admin.service.writeTimeout'
- defaultMessage='Write Timeout:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.service.writeTimeoutDescription'
- defaultMessage='If using HTTP (insecure), this is the maximum time allowed from the end of reading the request headers until the response is written. If using HTTPS, it is the total time from when the connection is accepted until the response is written.'
- />
- }
- value={this.state.writeTimeout}
- onChange={this.handleChange}
- />
- <BooleanSetting
- id='enableAPIv3'
- label={
- <FormattedMessage
- id='admin.service.enableAPIv3'
- defaultMessage='Allow use of API v3 endpoints:'
- />
- }
- helpText={
- <FormattedHTMLMessage
- id='admin.service.enableAPIv3Description'
- defaultMessage='Set to false to disable all version 3 endpoints of the REST API. Integrations that rely on API v3 will fail and can then be identified for migration to API v4. API v3 is deprecated and will be removed in the near future. See <a href="https://api.mattermost.com" target="_blank">https://api.mattermost.com</a> for details.'
- />
- }
- value={this.state.enableAPIv3}
- onChange={this.handleChange}
- />
- <WebserverModeDropdownSetting
- value={this.state.webserverMode}
- onChange={this.handleChange}
- disabled={false}
- />
- {reloadConfigButton}
- <RequestButton
- requestAction={invalidateAllCaches}
- helpText={
- <FormattedMessage
- id='admin.purge.purgeDescription'
- defaultMessage='This will purge all the in-memory caches for things like sessions, accounts, channels, etc. Deployments using High Availability will attempt to purge all the servers in the cluster. Purging the caches may adversely impact performance.'
- />
- }
- buttonText={
- <FormattedMessage
- id='admin.purge.button'
- defaultMessage='Purge All Caches'
- />
- }
- showSuccessMessage={false}
- includeDetailedError={true}
- errorMessage={{
- id: 'admin.purge.purgeFail',
- defaultMessage: 'Purging unsuccessful: {error}'
- }}
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/connection_security_dropdown_setting.jsx b/webapp/components/admin_console/connection_security_dropdown_setting.jsx
deleted file mode 100644
index b7b283be1..000000000
--- a/webapp/components/admin_console/connection_security_dropdown_setting.jsx
+++ /dev/null
@@ -1,191 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import * as Utils from 'utils/utils.jsx';
-
-import DropdownSetting from './dropdown_setting.jsx';
-import {FormattedMessage} from 'react-intl';
-
-const SECTION_NONE = (
- <tr>
- <td>
- <FormattedMessage
- id='admin.connectionSecurityNone'
- defaultMessage='None'
- />
- </td>
- <td>
- <FormattedMessage
- id='admin.connectionSecurityNoneDescription'
- defaultMessage='Mattermost will connect over an insecure connection.'
- />
- </td>
- </tr>
-);
-
-const SECTION_TLS = (
- <tr>
- <td>
- <FormattedMessage
- id='admin.connectionSecurityTls'
- defaultMessage='TLS'
- />
- </td>
- <td>
- <FormattedMessage
- id='admin.connectionSecurityTlsDescription'
- defaultMessage='Encrypts the communication between Mattermost and your server.'
- />
- </td>
- </tr>
-);
-
-const SECTION_STARTTLS = (
- <tr>
- <td>
- <FormattedMessage
- id='admin.connectionSecurityStart'
- defaultMessage='STARTTLS'
- />
- </td>
- <td>
- <FormattedMessage
- id='admin.connectionSecurityStartDescription'
- defaultMessage='Takes an existing insecure connection and attempts to upgrade it to a secure connection using TLS.'
- />
- </td>
- </tr>
-);
-
-const CONNECTION_SECURITY_HELP_TEXT_EMAIL = (
- <table
- className='table table-bordered table-margin--none'
- cellPadding='5'
- >
- <tbody>
- {SECTION_NONE}
- {SECTION_TLS}
- {SECTION_STARTTLS}
- </tbody>
- </table>
-);
-
-const CONNECTION_SECURITY_HELP_TEXT_LDAP = (
- <table
- className='table table-bordered table-margin--none'
- cellPadding='5'
- >
- <tbody>
- {SECTION_NONE}
- {SECTION_TLS}
- {SECTION_STARTTLS}
- </tbody>
- </table>
-);
-
-const CONNECTION_SECURITY_HELP_TEXT_WEBSERVER = (
- <table
- className='table table-bordered table-margin--none'
- cellPadding='5'
- >
- <tbody>
- {SECTION_NONE}
- {SECTION_TLS}
- </tbody>
- </table>
-);
-
-export function ConnectionSecurityDropdownSettingEmail(props) {
- return (
- <DropdownSetting
- id='connectionSecurity'
- values={[
- {value: '', text: Utils.localizeMessage('admin.connectionSecurityNone', 'None')},
- {value: 'TLS', text: Utils.localizeMessage('admin.connectionSecurityTls', 'TLS (Recommended)')},
- {value: 'STARTTLS', text: Utils.localizeMessage('admin.connectionSecurityStart')}
- ]}
- label={
- <FormattedMessage
- id='admin.connectionSecurityTitle'
- defaultMessage='Connection Security:'
- />
- }
- value={props.value}
- onChange={props.onChange}
- disabled={props.disabled}
- helpText={CONNECTION_SECURITY_HELP_TEXT_EMAIL}
- />
- );
-}
-ConnectionSecurityDropdownSettingEmail.defaultProps = {
-};
-
-ConnectionSecurityDropdownSettingEmail.propTypes = {
- value: PropTypes.string.isRequired,
- onChange: PropTypes.func.isRequired,
- disabled: PropTypes.bool.isRequired
-};
-
-export function ConnectionSecurityDropdownSettingLdap(props) {
- return (
- <DropdownSetting
- id='connectionSecurity'
- values={[
- {value: '', text: Utils.localizeMessage('admin.connectionSecurityNone', 'None')},
- {value: 'TLS', text: Utils.localizeMessage('admin.connectionSecurityTls', 'TLS (Recommended)')},
- {value: 'STARTTLS', text: Utils.localizeMessage('admin.connectionSecurityStart')}
- ]}
- label={
- <FormattedMessage
- id='admin.connectionSecurityTitle'
- defaultMessage='Connection Security:'
- />
- }
- value={props.value}
- onChange={props.onChange}
- disabled={props.disabled}
- helpText={CONNECTION_SECURITY_HELP_TEXT_LDAP}
- />
- );
-}
-ConnectionSecurityDropdownSettingLdap.defaultProps = {
-};
-
-ConnectionSecurityDropdownSettingLdap.propTypes = {
- value: PropTypes.string.isRequired,
- onChange: PropTypes.func.isRequired,
- disabled: PropTypes.bool.isRequired
-};
-
-export function ConnectionSecurityDropdownSettingWebserver(props) {
- return (
- <DropdownSetting
- id='connectionSecurity'
- values={[
- {value: '', text: Utils.localizeMessage('admin.connectionSecurityNone', 'None')},
- {value: 'TLS', text: Utils.localizeMessage('admin.connectionSecurityTls', 'TLS (Recommended)')}
- ]}
- label={
- <FormattedMessage
- id='admin.connectionSecurityTitle'
- defaultMessage='Connection Security:'
- />
- }
- value={props.value}
- onChange={props.onChange}
- disabled={props.disabled}
- helpText={CONNECTION_SECURITY_HELP_TEXT_WEBSERVER}
- />
- );
-}
-ConnectionSecurityDropdownSettingWebserver.defaultProps = {
-};
-
-ConnectionSecurityDropdownSettingWebserver.propTypes = {
- value: PropTypes.string.isRequired,
- onChange: PropTypes.func.isRequired,
- disabled: PropTypes.bool.isRequired
-};
diff --git a/webapp/components/admin_console/connection_settings.jsx b/webapp/components/admin_console/connection_settings.jsx
deleted file mode 100644
index 78a0b89ed..000000000
--- a/webapp/components/admin_console/connection_settings.jsx
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import * as Utils from 'utils/utils.jsx';
-
-import AdminSettings from './admin_settings.jsx';
-import BooleanSetting from './boolean_setting.jsx';
-import {FormattedMessage} from 'react-intl';
-import SettingsGroup from './settings_group.jsx';
-import TextSetting from './text_setting.jsx';
-
-export default class ConnectionSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
- }
-
- getConfigFromState(config) {
- config.ServiceSettings.AllowCorsFrom = this.state.allowCorsFrom;
- config.ServiceSettings.EnableInsecureOutgoingConnections = this.state.enableInsecureOutgoingConnections;
-
- return config;
- }
-
- getStateFromConfig(config) {
- return {
- allowCorsFrom: config.ServiceSettings.AllowCorsFrom,
- enableInsecureOutgoingConnections: config.ServiceSettings.EnableInsecureOutgoingConnections
- };
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.security.connection'
- defaultMessage='Connections'
- />
- );
- }
-
- renderSettings() {
- return (
- <SettingsGroup>
- <TextSetting
- id='allowCorsFrom'
- label={
- <FormattedMessage
- id='admin.service.corsTitle'
- defaultMessage='Enable cross-origin requests from:'
- />
- }
- placeholder={Utils.localizeMessage('admin.service.corsEx', 'http://example.com')}
- helpText={
- <FormattedMessage
- id='admin.service.corsDescription'
- defaultMessage='Enable HTTP Cross origin request from a specific domain. Use "*" if you want to allow CORS from any domain or leave it blank to disable it.'
- />
- }
- value={this.state.allowCorsFrom}
- onChange={this.handleChange}
- />
- <BooleanSetting
- id='enableInsecureOutgoingConnections'
- label={
- <FormattedMessage
- id='admin.service.insecureTlsTitle'
- defaultMessage='Enable Insecure Outgoing Connections: '
- />
- }
- helpText={
- <FormattedMessage
- id='admin.service.insecureTlsDesc'
- defaultMessage='When true, any outgoing HTTPS requests will accept unverified, self-signed certificates. For example, outgoing webhooks to a server with a self-signed TLS certificate, using any domain, will be allowed. Note that this makes these connections susceptible to man-in-the-middle attacks.'
- />
- }
- value={this.state.enableInsecureOutgoingConnections}
- onChange={this.handleChange}
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/custom_brand_settings.jsx b/webapp/components/admin_console/custom_brand_settings.jsx
deleted file mode 100644
index 4ca3dee0c..000000000
--- a/webapp/components/admin_console/custom_brand_settings.jsx
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import * as Utils from 'utils/utils.jsx';
-
-import AdminSettings from './admin_settings.jsx';
-import BooleanSetting from './boolean_setting.jsx';
-import BrandImageSetting from './brand_image_setting.jsx';
-import {FormattedMessage} from 'react-intl';
-import SettingsGroup from './settings_group.jsx';
-import TextSetting from './text_setting.jsx';
-import Constants from 'utils/constants.jsx';
-
-export default class CustomBrandSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
- }
-
- getConfigFromState(config) {
- config.TeamSettings.SiteName = this.state.siteName;
- if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.CustomBrand === 'true') {
- config.TeamSettings.customDescriptionText = this.state.customDescriptionText;
- config.TeamSettings.EnableCustomBrand = this.state.enableCustomBrand;
- config.TeamSettings.CustomBrandText = this.state.customBrandText;
- }
-
- return config;
- }
-
- getStateFromConfig(config) {
- return {
- siteName: config.TeamSettings.SiteName,
- enableCustomBrand: config.TeamSettings.EnableCustomBrand,
- customBrandText: config.TeamSettings.CustomBrandText,
- customDescriptionText: config.TeamSettings.CustomDescriptionText
- };
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.customization.customBrand'
- defaultMessage='Custom Branding'
- />
- );
- }
-
- renderSettings() {
- const enterpriseSettings = [];
- if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.CustomBrand === 'true') {
- enterpriseSettings.push(
- <TextSetting
- key='customDescriptionText'
- id='customDescriptionText'
- label={
- <FormattedMessage
- id='admin.team.brandDescriptionTitle'
- defaultMessage='Site Description: '
- />
- }
- helpText={
- <FormattedMessage
- id='admin.team.brandDescriptionHelp'
- defaultMessage='Description of service shown in login screens and UI. When not specified, "All team communication in one place, searchable and accessible anywhere" is displayed.'
- />
- }
- value={this.state.customDescriptionText}
- placeholder={Utils.localizeMessage('web.root.signup_info', 'All team communication in one place, searchable and accessible anywhere')}
- onChange={this.handleChange}
- />
- );
-
- enterpriseSettings.push(
- <BooleanSetting
- key='enableCustomBrand'
- id='enableCustomBrand'
- label={
- <FormattedMessage
- id='admin.team.brandTitle'
- defaultMessage='Enable Custom Branding: '
- />
- }
- helpText={
- <FormattedMessage
- id='admin.team.brandDesc'
- defaultMessage='Enable custom branding to show an image of your choice, uploaded below, and some help text, written below, on the login page.'
- />
- }
- value={this.state.enableCustomBrand}
- onChange={this.handleChange}
- />
- );
-
- enterpriseSettings.push(
- <BrandImageSetting
- key='customBrandImage'
- disabled={!this.state.enableCustomBrand}
- />
- );
-
- enterpriseSettings.push(
- <TextSetting
- key='customBrandText'
- id='customBrandText'
- type='textarea'
- label={
- <FormattedMessage
- id='admin.team.brandTextTitle'
- defaultMessage='Custom Brand Text:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.team.brandTextDescription'
- defaultMessage='Text that will appear below your custom brand image on your login screen. Supports Markdown-formatted text. Maximum 500 characters allowed.'
- />
- }
- value={this.state.customBrandText}
- onChange={this.handleChange}
- disabled={!this.state.enableCustomBrand}
- />
- );
- }
-
- return (
- <SettingsGroup>
- <TextSetting
- id='siteName'
- label={
- <FormattedMessage
- id='admin.team.siteNameTitle'
- defaultMessage='Site Name:'
- />
- }
- maxLength={Constants.MAX_SITENAME_LENGTH}
- placeholder={Utils.localizeMessage('admin.team.siteNameExample', 'Ex "Mattermost"')}
- helpText={
- <FormattedMessage
- id='admin.team.siteNameDescription'
- defaultMessage='Name of service shown in login screens and UI.'
- />
- }
- value={this.state.siteName}
- onChange={this.handleChange}
- />
- {enterpriseSettings}
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/custom_emoji_settings.jsx b/webapp/components/admin_console/custom_emoji_settings.jsx
deleted file mode 100644
index 329900888..000000000
--- a/webapp/components/admin_console/custom_emoji_settings.jsx
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import * as Utils from 'utils/utils.jsx';
-
-import AdminSettings from './admin_settings.jsx';
-import BooleanSetting from './boolean_setting.jsx';
-import DropdownSetting from './dropdown_setting.jsx';
-import {FormattedMessage} from 'react-intl';
-import SettingsGroup from './settings_group.jsx';
-
-export default class CustomEmojiSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
- }
-
- getConfigFromState(config) {
- config.ServiceSettings.EnableCustomEmoji = this.state.enableCustomEmoji;
- config.ServiceSettings.EnableEmojiPicker = this.state.enableEmojiPicker;
-
- if (global.window.mm_license.IsLicensed === 'true') {
- config.ServiceSettings.RestrictCustomEmojiCreation = this.state.restrictCustomEmojiCreation;
- }
-
- return config;
- }
-
- getStateFromConfig(config) {
- return {
- enableCustomEmoji: config.ServiceSettings.EnableCustomEmoji,
- enableEmojiPicker: config.ServiceSettings.EnableEmojiPicker,
- restrictCustomEmojiCreation: config.ServiceSettings.RestrictCustomEmojiCreation
- };
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.customization.emoji'
- defaultMessage='Emoji'
- />
- );
- }
-
- renderSettings() {
- let restrictSetting = null;
- if (global.window.mm_license.IsLicensed === 'true') {
- restrictSetting = (
- <DropdownSetting
- id='restrictCustomEmojiCreation'
- values={[
- {value: 'all', text: Utils.localizeMessage('admin.customization.restrictCustomEmojiCreationAll', 'Allow everyone to create custom emoji')},
- {value: 'admin', text: Utils.localizeMessage('admin.customization.restrictCustomEmojiCreationAdmin', 'Allow System and Team Admins to create custom emoji')},
- {value: 'system_admin', text: Utils.localizeMessage('admin.customization.restrictCustomEmojiCreationSystemAdmin', 'Only allow System Admins to create custom emoji')}
- ]}
- label={
- <FormattedMessage
- id='admin.customization.restrictCustomEmojiCreationTitle'
- defaultMessage='Restrict Custom Emoji Creation:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.customization.restrictCustomEmojiCreationDesc'
- defaultMessage='Restrict the creation of custom emoji to certain users.'
- />
- }
- value={this.state.restrictCustomEmojiCreation}
- onChange={this.handleChange}
- disabled={!this.state.enableCustomEmoji}
- />
- );
- }
-
- return (
- <SettingsGroup>
- <BooleanSetting
- id='enableEmojiPicker'
- label={
- <FormattedMessage
- id='admin.customization.enableEmojiPickerTitle'
- defaultMessage='Enable Emoji Picker:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.customization.enableEmojiPickerDesc'
- defaultMessage='The emoji picker allows users to select emoji to add as reactions or use in messages. Enabling the emoji picker with a large number of custom emoji may slow down performance.'
- />
- }
- value={this.state.enableEmojiPicker}
- onChange={this.handleChange}
- />
- <BooleanSetting
- id='enableCustomEmoji'
- label={
- <FormattedMessage
- id='admin.customization.enableCustomEmojiTitle'
- defaultMessage='Enable Custom Emoji:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.customization.enableCustomEmojiDesc'
- defaultMessage='Enable users to create custom emoji for use in messages. When enabled, Custom Emoji settings can be accessed by switching to a team and clicking the three dots above the channel sidebar, and selecting "Custom Emoji".'
- />
- }
- value={this.state.enableCustomEmoji}
- onChange={this.handleChange}
- />
- {restrictSetting}
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/custom_integrations_settings.jsx b/webapp/components/admin_console/custom_integrations_settings.jsx
deleted file mode 100644
index e6bcb4b32..000000000
--- a/webapp/components/admin_console/custom_integrations_settings.jsx
+++ /dev/null
@@ -1,197 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import AdminSettings from './admin_settings.jsx';
-import BooleanSetting from './boolean_setting.jsx';
-import {FormattedHTMLMessage, FormattedMessage} from 'react-intl';
-import SettingsGroup from './settings_group.jsx';
-
-export default class WebhookSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
- }
-
- getConfigFromState(config) {
- config.ServiceSettings.EnableIncomingWebhooks = this.state.enableIncomingWebhooks;
- config.ServiceSettings.EnableOutgoingWebhooks = this.state.enableOutgoingWebhooks;
- config.ServiceSettings.EnableCommands = this.state.enableCommands;
- config.ServiceSettings.EnableOnlyAdminIntegrations = this.state.enableOnlyAdminIntegrations;
- config.ServiceSettings.EnablePostUsernameOverride = this.state.enablePostUsernameOverride;
- config.ServiceSettings.EnablePostIconOverride = this.state.enablePostIconOverride;
- config.ServiceSettings.EnableOAuthServiceProvider = this.state.enableOAuthServiceProvider;
- config.ServiceSettings.EnableUserAccessTokens = this.state.enableUserAccessTokens;
-
- return config;
- }
-
- getStateFromConfig(config) {
- return {
- enableIncomingWebhooks: config.ServiceSettings.EnableIncomingWebhooks,
- enableOutgoingWebhooks: config.ServiceSettings.EnableOutgoingWebhooks,
- enableCommands: config.ServiceSettings.EnableCommands,
- enableOnlyAdminIntegrations: config.ServiceSettings.EnableOnlyAdminIntegrations,
- enablePostUsernameOverride: config.ServiceSettings.EnablePostUsernameOverride,
- enablePostIconOverride: config.ServiceSettings.EnablePostIconOverride,
- enableOAuthServiceProvider: config.ServiceSettings.EnableOAuthServiceProvider,
- enableUserAccessTokens: config.ServiceSettings.EnableUserAccessTokens
- };
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.integrations.custom'
- defaultMessage='Custom Integrations'
- />
- );
- }
-
- renderSettings() {
- return (
- <SettingsGroup>
- <BooleanSetting
- id='enableIncomingWebhooks'
- label={
- <FormattedMessage
- id='admin.service.webhooksTitle'
- defaultMessage='Enable Incoming Webhooks: '
- />
- }
- helpText={
- <FormattedHTMLMessage
- id='admin.service.webhooksDescription'
- defaultMessage='When true, incoming webhooks will be allowed. To help combat phishing attacks, all posts from webhooks will be labelled by a BOT tag. See <a href="http://docs.mattermost.com/developer/webhooks-incoming.html" target="_blank">documentation</a> to learn more.'
- />
- }
- value={this.state.enableIncomingWebhooks}
- onChange={this.handleChange}
- />
- <BooleanSetting
- id='enableOutgoingWebhooks'
- label={
- <FormattedMessage
- id='admin.service.outWebhooksTitle'
- defaultMessage='Enable Outgoing Webhooks: '
- />
- }
- helpText={
- <FormattedHTMLMessage
- id='admin.service.outWebhooksDesc'
- defaultMessage='When true, outgoing webhooks will be allowed. See <a href="http://docs.mattermost.com/developer/webhooks-outgoing.html" target="_blank">documentation</a> to learn more.'
- />
- }
- value={this.state.enableOutgoingWebhooks}
- onChange={this.handleChange}
- />
- <BooleanSetting
- id='enableCommands'
- label={
- <FormattedMessage
- id='admin.service.cmdsTitle'
- defaultMessage='Enable Custom Slash Commands: '
- />
- }
- helpText={
- <FormattedHTMLMessage
- id='admin.service.cmdsDesc'
- defaultMessage='When true, custom slash commands will be allowed. See <a href="http://docs.mattermost.com/developer/slash-commands.html" target="_blank">documentation</a> to learn more.'
- />
- }
- value={this.state.enableCommands}
- onChange={this.handleChange}
- />
- <BooleanSetting
- id='enableOAuthServiceProvider'
- label={
- <FormattedMessage
- id='admin.oauth.providerTitle'
- defaultMessage='Enable OAuth 2.0 Service Provider: '
- />
- }
- helpText={
- <FormattedHTMLMessage
- id='admin.oauth.providerDescription'
- defaultMessage='When true, Mattermost can act as an OAuth 2.0 service provider allowing Mattermost to authorize API requests from external applications. See <a href="https://docs.mattermost.com/developer/oauth-2-0-applications.html" target="_blank">documentation</a> to learn more.'
- />
- }
- value={this.state.enableOAuthServiceProvider}
- onChange={this.handleChange}
- />
- <BooleanSetting
- id='enableOnlyAdminIntegrations'
- label={
- <FormattedMessage
- id='admin.service.integrationAdmin'
- defaultMessage='Restrict managing integrations to Admins:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.service.integrationAdminDesc'
- defaultMessage='When true, webhooks and slash commands can only be created, edited and viewed by Team and System Admins, and OAuth 2.0 applications by System Admins. Integrations are available to all users after they have been created by the Admin.'
- />
- }
- value={this.state.enableOnlyAdminIntegrations}
- onChange={this.handleChange}
- />
- <BooleanSetting
- id='enablePostUsernameOverride'
- label={
- <FormattedMessage
- id='admin.service.overrideTitle'
- defaultMessage='Enable integrations to override usernames:'
- />
- }
- helpText={
- <FormattedHTMLMessage
- id='admin.service.overrideDescription'
- defaultMessage='When true, webhooks, slash commands and other integrations, such as <a href="https://docs.mattermost.com/integrations/zapier.html" target="_blank">Zapier</a>, will be allowed to change the username they are posting as. Note: Combined with allowing integrations to override profile picture icons, users may be able to perform phishing attacks by attempting to impersonate other users.'
- />
- }
- value={this.state.enablePostUsernameOverride}
- onChange={this.handleChange}
- />
- <BooleanSetting
- id='enablePostIconOverride'
- label={
- <FormattedMessage
- id='admin.service.iconTitle'
- defaultMessage='Enable integrations to override profile picture icons:'
- />
- }
- helpText={
- <FormattedHTMLMessage
- id='admin.service.iconDescription'
- defaultMessage='When true, webhooks, slash commands and other integrations, such as <a href="https://docs.mattermost.com/integrations/zapier.html" target="_blank">Zapier</a>, will be allowed to change the profile picture they post with. Note: Combined with allowing integrations to override usernames, users may be able to perform phishing attacks by attempting to impersonate other users.'
- />
- }
- value={this.state.enablePostIconOverride}
- onChange={this.handleChange}
- />
- <BooleanSetting
- id='enableUserAccessTokens'
- label={
- <FormattedMessage
- id='admin.service.userAccessTokensTitle'
- defaultMessage='Enable User Access Tokens: '
- />
- }
- helpText={
- <FormattedHTMLMessage
- id='admin.service.userAccessTokensDescription'
- defaultMessage='When true, users can create <a href="https://about.mattermost.com/default-user-access-tokens" target="_blank">user access tokens</a> for integrations in <strong>Account Settings > Security</strong>. They can be used to authenticate against the API and give full access to the account.<br/><br/>To manage who can create personal access tokens or to search users by token ID, go to the <strong>System Console > Users</strong> page.'
- />
- }
- value={this.state.enableUserAccessTokens}
- onChange={this.handleChange}
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/database_settings.jsx b/webapp/components/admin_console/database_settings.jsx
deleted file mode 100644
index 9f008483c..000000000
--- a/webapp/components/admin_console/database_settings.jsx
+++ /dev/null
@@ -1,238 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import {recycleDatabaseConnection} from 'actions/admin_actions.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import AdminSettings from './admin_settings.jsx';
-import BooleanSetting from './boolean_setting.jsx';
-import {FormattedMessage} from 'react-intl';
-import GeneratedSetting from './generated_setting.jsx';
-import SettingsGroup from './settings_group.jsx';
-import TextSetting from './text_setting.jsx';
-import RequestButton from './request_button/request_button.jsx';
-
-export default class DatabaseSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
- }
-
- getConfigFromState(config) {
- // driverName and dataSource are read-only from the UI
-
- config.SqlSettings.MaxIdleConns = this.parseIntNonZero(this.state.maxIdleConns);
- config.SqlSettings.MaxOpenConns = this.parseIntNonZero(this.state.maxOpenConns);
- config.SqlSettings.AtRestEncryptKey = this.state.atRestEncryptKey;
- config.SqlSettings.Trace = this.state.trace;
- config.SqlSettings.QueryTimeout = this.parseIntNonZero(this.state.queryTimeout);
-
- return config;
- }
-
- getStateFromConfig(config) {
- return {
- driverName: config.SqlSettings.DriverName,
- dataSource: config.SqlSettings.DataSource,
- maxIdleConns: config.SqlSettings.MaxIdleConns,
- maxOpenConns: config.SqlSettings.MaxOpenConns,
- atRestEncryptKey: config.SqlSettings.AtRestEncryptKey,
- trace: config.SqlSettings.Trace,
- queryTimeout: config.SqlSettings.QueryTimeout
- };
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.database.title'
- defaultMessage='Database Settings'
- />
- );
- }
-
- renderSettings() {
- const dataSource = '**********' + this.state.dataSource.substring(this.state.dataSource.indexOf('@'));
-
- let recycleDbButton = <div/>;
- if (global.window.mm_license.IsLicensed === 'true') {
- recycleDbButton = (
- <RequestButton
- requestAction={recycleDatabaseConnection}
- helpText={
- <FormattedMessage
- id='admin.recycle.recycleDescription'
- defaultMessage='Deployments using multiple databases can switch from one master database to another without restarting the Mattermost server by updating "config.json" to the new desired configuration and using the {reloadConfiguration} feature to load the new settings while the server is running. The administrator should then use {featureName} feature to recycle the database connections based on the new settings.'
- values={{
- featureName: (
- <b>
- <FormattedMessage
- id='admin.recycle.recycleDescription.featureName'
- defaultMessage='Recycle Database Connections'
- />
- </b>
- ),
- reloadConfiguration: (
- <a href='../general/configuration'>
- <b>
- <FormattedMessage
- id='admin.recycle.recycleDescription.reloadConfiguration'
- defaultMessage='Configuration > Reload Configuration from Disk'
- />
- </b>
- </a>
- )
- }}
- />
- }
- buttonText={
- <FormattedMessage
- id='admin.recycle.button'
- defaultMessage='Recycle Database Connections'
- />
- }
- showSuccessMessage={false}
- errorMessage={{
- id: 'admin.recycle.reloadFail',
- defaultMessage: 'Recycling unsuccessful: {error}'
- }}
- includeDetailedError={true}
- />
- );
- }
-
- return (
- <SettingsGroup>
- <div className='banner'>
- <FormattedMessage
- id='admin.sql.noteDescription'
- defaultMessage='Changing properties in this section will require a server restart before taking effect.'
- />
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='DriverName'
- >
- <FormattedMessage
- id='admin.sql.driverName'
- defaultMessage='Driver Name:'
- />
- </label>
- <div className='col-sm-8'>
- <p className='help-text'>{this.state.driverName}</p>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='DataSource'
- >
- <FormattedMessage
- id='admin.sql.dataSource'
- defaultMessage='Data Source:'
- />
- </label>
- <div className='col-sm-8'>
- <p className='help-text'>{dataSource}</p>
- </div>
- </div>
- <TextSetting
- id='maxIdleConns'
- label={
- <FormattedMessage
- id='admin.sql.maxConnectionsTitle'
- defaultMessage='Maximum Idle Connections:'
- />
- }
- placeholder={Utils.localizeMessage('admin.sql.maxConnectionsExample', 'Ex "10"')}
- helpText={
- <FormattedMessage
- id='admin.sql.maxConnectionsDescription'
- defaultMessage='Maximum number of idle connections held open to the database.'
- />
- }
- value={this.state.maxIdleConns}
- onChange={this.handleChange}
- />
- <TextSetting
- id='maxOpenConns'
- label={
- <FormattedMessage
- id='admin.sql.maxOpenTitle'
- defaultMessage='Maximum Open Connections:'
- />
- }
- placeholder={Utils.localizeMessage('admin.sql.maxOpenExample', 'Ex "10"')}
- helpText={
- <FormattedMessage
- id='admin.sql.maxOpenDescription'
- defaultMessage='Maximum number of open connections held open to the database.'
- />
- }
- value={this.state.maxOpenConns}
- onChange={this.handleChange}
- />
- <TextSetting
- id='queryTimeout'
- label={
- <FormattedMessage
- id='admin.sql.queryTimeoutTitle'
- defaultMessage='Query Timeout:'
- />
- }
- placeholder={Utils.localizeMessage('admin.sql.queryTimeoutExample', 'Ex "30"')}
- helpText={
- <FormattedMessage
- id='admin.sql.queryTimeoutDescription'
- defaultMessage='The number of seconds to wait for a response from the database after opening a connection and sending the query. Errors that you see in the UI or in the logs as a result of a query timeout can vary depending on the type of query.'
- />
- }
- value={this.state.queryTimeout}
- onChange={this.handleChange}
- />
- <GeneratedSetting
- id='atRestEncryptKey'
- label={
- <FormattedMessage
- id='admin.sql.keyTitle'
- defaultMessage='At Rest Encrypt Key:'
- />
- }
- placeholder={Utils.localizeMessage('admin.sql.keyExample', 'Ex "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6"')}
- helpText={
- <FormattedMessage
- id='admin.sql.keyDescription'
- defaultMessage='32-character salt available to encrypt and decrypt sensitive fields in database.'
- />
- }
- value={this.state.atRestEncryptKey}
- onChange={this.handleChange}
- />
- <BooleanSetting
- id='trace'
- label={
- <FormattedMessage
- id='admin.sql.traceTitle'
- defaultMessage='Trace: '
- />
- }
- helpText={
- <FormattedMessage
- id='admin.sql.traceDescription'
- defaultMessage='(Development Mode) When true, executing SQL statements are written to the log.'
- />
- }
- value={this.state.trace}
- onChange={this.handleChange}
- />
- {recycleDbButton}
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/developer_settings.jsx b/webapp/components/admin_console/developer_settings.jsx
deleted file mode 100644
index 6a8f49dbd..000000000
--- a/webapp/components/admin_console/developer_settings.jsx
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import * as Utils from 'utils/utils.jsx';
-
-import AdminSettings from './admin_settings.jsx';
-import BooleanSetting from './boolean_setting.jsx';
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-import SettingsGroup from './settings_group.jsx';
-import TextSetting from './text_setting.jsx';
-
-export default class DeveloperSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
- }
-
- getConfigFromState(config) {
- config.ServiceSettings.EnableTesting = this.state.enableTesting;
- config.ServiceSettings.EnableDeveloper = this.state.enableDeveloper;
- config.ServiceSettings.AllowedUntrustedInternalConnections = this.state.allowedUntrustedInternalConnections;
-
- return config;
- }
-
- getStateFromConfig(config) {
- return {
- enableTesting: config.ServiceSettings.EnableTesting,
- enableDeveloper: config.ServiceSettings.EnableDeveloper,
- allowedUntrustedInternalConnections: config.ServiceSettings.AllowedUntrustedInternalConnections
- };
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.developer.title'
- defaultMessage='Developer Settings'
- />
- );
- }
-
- renderSettings() {
- return (
- <SettingsGroup>
- <BooleanSetting
- id='enableTesting'
- label={
- <FormattedMessage
- id='admin.service.testingTitle'
- defaultMessage='Enable Testing Commands: '
- />
- }
- helpText={
- <FormattedMessage
- id='admin.service.testingDescription'
- defaultMessage='When true, /test slash command is enabled to load test accounts, data and text formatting. Changing this requires a server restart before taking effect.'
- />
- }
- value={this.state.enableTesting}
- onChange={this.handleChange}
- />
- <BooleanSetting
- id='enableDeveloper'
- label={
- <FormattedMessage
- id='admin.service.developerTitle'
- defaultMessage='Enable Developer Mode: '
- />
- }
- helpText={
- <FormattedMessage
- id='admin.service.developerDesc'
- defaultMessage='When true, JavaScript errors are shown in a purple bar at the top of the user interface. Not recommended for use in production. '
- />
- }
- value={this.state.enableDeveloper}
- onChange={this.handleChange}
- />
- <TextSetting
- id='allowedUntrustedInternalConnections'
- label={
- <FormattedMessage
- id='admin.service.internalConnectionsTitle'
- defaultMessage='Allow untrusted internal connections to: '
- />
- }
- placeholder={Utils.localizeMessage('admin.service.internalConnectionsEx', 'webhooks.internal.example.com 127.0.0.1 10.0.16.0/28')}
- helpText={
- <FormattedHTMLMessage
- id='admin.service.internalConnectionsDesc'
- defaultMessage='In testing environments, such as when developing integrations locally on a development machine, use this setting to specify domains, IP addresses, or CIDR notations to allow internal connections. <b>Not recommended for use in production</b>, since this can allow a user to extract confidential data from your server or internal network.<br /><br />By default, user-supplied URLs such as those used for Open Graph metadata, webhooks, or slash commands will not be allowed to connect to reserved IP addresses including loopback or link-local addresses used for internal networks. Push notification, OAuth 2.0 and WebRTC server URLs are trusted and not affected by this setting.'
- />
- }
- value={this.state.allowedUntrustedInternalConnections}
- onChange={this.handleChange}
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/dropdown_setting.jsx b/webapp/components/admin_console/dropdown_setting.jsx
deleted file mode 100644
index 05179a4b9..000000000
--- a/webapp/components/admin_console/dropdown_setting.jsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import Setting from './setting.jsx';
-
-export default class DropdownSetting extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleChange = this.handleChange.bind(this);
- }
-
- handleChange(e) {
- this.props.onChange(this.props.id, e.target.value);
- }
-
- render() {
- const options = [];
- for (const {value, text} of this.props.values) {
- options.push(
- <option
- value={value}
- key={value}
- >
- {text}
- </option>
- );
- }
-
- return (
- <Setting
- label={this.props.label}
- inputId={this.props.id}
- helpText={this.props.helpText}
- >
- <select
- className='form-control'
- id={this.props.id}
- value={this.props.value}
- onChange={this.handleChange}
- disabled={this.props.disabled}
- >
- {options}
- </select>
- </Setting>
- );
- }
-}
-
-DropdownSetting.defaultProps = {
- isDisabled: false
-};
-
-DropdownSetting.propTypes = {
- id: PropTypes.string.isRequired,
- values: PropTypes.array.isRequired,
- label: PropTypes.node.isRequired,
- value: PropTypes.string.isRequired,
- onChange: PropTypes.func.isRequired,
- disabled: PropTypes.bool,
- helpText: PropTypes.node
-};
diff --git a/webapp/components/admin_console/elasticsearch_settings.jsx b/webapp/components/admin_console/elasticsearch_settings.jsx
deleted file mode 100644
index b739241ef..000000000
--- a/webapp/components/admin_console/elasticsearch_settings.jsx
+++ /dev/null
@@ -1,333 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import * as Utils from 'utils/utils.jsx';
-
-import AdminSettings from './admin_settings.jsx';
-import {elasticsearchTest, elasticsearchPurgeIndexes} from 'actions/admin_actions.jsx';
-import BooleanSetting from './boolean_setting.jsx';
-import {FormattedMessage} from 'react-intl';
-import SettingsGroup from './settings_group.jsx';
-import TextSetting from './text_setting.jsx';
-import RequestButton from './request_button/request_button.jsx';
-import ElasticsearchStatus from './elasticsearch_status';
-
-export default class ElasticsearchSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.doTestConfig = this.doTestConfig.bind(this);
- this.handleChange = this.handleChange.bind(this);
- this.handleSaved = this.handleSaved.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
- }
-
- getConfigFromState(config) {
- config.ElasticsearchSettings.ConnectionUrl = this.state.connectionUrl;
- config.ElasticsearchSettings.Username = this.state.username;
- config.ElasticsearchSettings.Password = this.state.password;
- config.ElasticsearchSettings.Sniff = this.state.sniff;
- config.ElasticsearchSettings.EnableIndexing = this.state.enableIndexing;
- config.ElasticsearchSettings.EnableSearching = this.state.enableSearching;
-
- return config;
- }
-
- getStateFromConfig(config) {
- return {
- connectionUrl: config.ElasticsearchSettings.ConnectionUrl,
- username: config.ElasticsearchSettings.Username,
- password: config.ElasticsearchSettings.Password,
- sniff: config.ElasticsearchSettings.Sniff,
- enableIndexing: config.ElasticsearchSettings.EnableIndexing,
- enableSearching: config.ElasticsearchSettings.EnableSearching,
- configTested: true,
- canSave: true,
- canPurgeAndIndex: config.ElasticsearchSettings.EnableIndexing
- };
- }
-
- handleChange(id, value) {
- if (id === 'enableIndexing') {
- if (value === false) {
- this.setState({
- enableSearching: false
- });
- } else {
- this.setState({
- canSave: false,
- configTested: false
- });
- }
- }
-
- if (id === 'connectionUrl' || id === 'username' || id === 'password' || id === 'sniff') {
- this.setState({
- configTested: false,
- canSave: false
- });
- }
-
- if (id !== 'enableSearching') {
- this.setState({
- canPurgeAndIndex: false
- });
- }
-
- super.handleChange(id, value);
- }
-
- handleSaved() {
- this.setState({
- canPurgeAndIndex: this.state.enableIndexing
- });
- }
-
- canSave() {
- return this.state.canSave;
- }
-
- doTestConfig(success, error) {
- const config = JSON.parse(JSON.stringify(this.props.config));
- this.getConfigFromState(config);
-
- elasticsearchTest(
- config,
- () => {
- this.setState({
- configTested: true,
- canSave: true
- });
- success();
- this.doSubmit();
- },
- (err) => {
- this.setState({
- configTested: false,
- canSave: false
- });
- error(err);
- }
- );
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.elasticsearch.title'
- defaultMessage='Elasticsearch (Beta)'
- />
- );
- }
-
- renderSettings() {
- return (
- <SettingsGroup>
- <div className='banner'>
- <div className='banner__content'>
- <FormattedMessage
- id='admin.elasticsearch.noteDescription'
- defaultMessage='Changing properties in this section will require a server restart before taking effect.'
- />
- </div>
- </div>
- <BooleanSetting
- id='enableIndexing'
- label={
- <FormattedMessage
- id='admin.elasticsearch.enableIndexingTitle'
- defaultMessage='Enable Elasticsearch Indexing:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.elasticsearch.enableIndexingDescription'
- defaultMessage='When true, indexing of new posts occurs automatically. Search queries will use database search until "Enable Elasticsearch for search queries" is enabled. {documentationLink}'
- values={{
- documentationLink: (
- <a
- href='https://about.mattermost.com/default-elasticsearch-documentation/'
- rel='noopener noreferrer'
- target='_blank'
- >
- <FormattedMessage
- id='admin.elasticsearch.enableIndexingDescription.documentationLinkText'
- defaultMessage='Learn more about Elasticsearch in our documentation.'
- />
- </a>
- )
- }}
- />
- }
- value={this.state.enableIndexing}
- onChange={this.handleChange}
- />
- <TextSetting
- id='connectionUrl'
- label={
- <FormattedMessage
- id='admin.elasticsearch.connectionUrlTitle'
- defaultMessage='Server Connection Address:'
- />
- }
- placeholder={Utils.localizeMessage('admin.elasticsearch.connectionUrlExample', 'E.g.: "https://elasticsearch.example.org:9200"')}
- helpText={
- <FormattedMessage
- id='admin.elasticsearch.connectionUrlDescription'
- defaultMessage='The address of the Elasticsearch server. {documentationLink}'
- values={{
- documentationLink: (
- <a
- href='https://about.mattermost.com/default-elasticsearch-server-setup/'
- rel='noopener noreferrer'
- target='_blank'
- >
- <FormattedMessage
- id='admin.elasticsearch.connectionUrlExample.documentationLinkText'
- defaultMessage='Please see documentation with server setup instructions.'
- />
- </a>
- )
- }}
- />
- }
- value={this.state.connectionUrl}
- disabled={!this.state.enableIndexing}
- onChange={this.handleChange}
- />
- <TextSetting
- id='username'
- label={
- <FormattedMessage
- id='admin.elasticsearch.usernameTitle'
- defaultMessage='Server Username:'
- />
- }
- placeholder={Utils.localizeMessage('admin.elasticsearch.usernameExample', 'E.g.: "elastic"')}
- helpText={
- <FormattedMessage
- id='admin.elasticsearch.usernameDescription'
- defaultMessage='(Optional) The username to authenticate to the Elasticsearch server.'
- />
- }
- value={this.state.username}
- disabled={!this.state.enableIndexing}
- onChange={this.handleChange}
- />
- <TextSetting
- id='password'
- label={
- <FormattedMessage
- id='admin.elasticsearch.passwordTitle'
- defaultMessage='Server Password:'
- />
- }
- placeholder={Utils.localizeMessage('admin.elasticsearch.password', 'E.g.: "yourpassword"')}
- helpText={
- <FormattedMessage
- id='admin.elasticsearch.passwordDescription'
- defaultMessage='(Optional) The password to authenticate to the Elasticsearch server.'
- />
- }
- value={this.state.password}
- disabled={!this.state.enableIndexing}
- onChange={this.handleChange}
- />
- <BooleanSetting
- id='sniff'
- label={
- <FormattedMessage
- id='admin.elasticsearch.sniffTitle'
- defaultMessage='Enable Cluster Sniffing:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.elasticsearch.sniffDescription'
- defaultMessage='When true, sniffing finds and connects to all data nodes in your cluster automatically.'
- />
- }
- value={this.state.sniff}
- disabled={!this.state.enableIndexing}
- onChange={this.handleChange}
- />
- <RequestButton
- requestAction={this.doTestConfig}
- helpText={
- <FormattedMessage
- id='admin.elasticsearch.testHelpText'
- defaultMessage='Tests if the Mattermost server can connect to the Elasticsearch server specified. Testing the connection only saves the configuration if the test is successful. See log file for more detailed error messages.'
- />
- }
- buttonText={
- <FormattedMessage
- id='admin.elasticsearch.elasticsearch_test_button'
- defaultMessage='Test Connection'
- />
- }
- successMessage={{
- id: 'admin.elasticsearch.testConfigSuccess',
- defaultMessage: 'Test successful. Configuration saved.'
- }}
- disabled={!this.state.enableIndexing}
- />
- <ElasticsearchStatus
- isConfigured={this.state.canPurgeAndIndex}
- />
- <RequestButton
- requestAction={elasticsearchPurgeIndexes}
- helpText={
- <FormattedMessage
- id='admin.elasticsearch.purgeIndexesHelpText'
- defaultMessage='Purging will entirely remove the index on the Elasticsearch server. Search results may be incomplete until a bulk index of the existing post database is rebuilt.'
- />
- }
- buttonText={
- <FormattedMessage
- id='admin.elasticsearch.purgeIndexesButton'
- defaultMessage='Purge Index'
- />
- }
- successMessage={{
- id: 'admin.elasticsearch.purgeIndexesButton.success',
- defaultMessage: 'Indexes purged successfully.'
- }}
- errorMessage={{
- id: 'admin.elasticsearch.purgeIndexesButton.error',
- defaultMessage: 'Failed to purge indexes: {error}'
- }}
- disabled={!this.state.canPurgeAndIndex}
- label={(
- <FormattedMessage
- id='admin.elasticsearch.purgeIndexesButton.label'
- defaultMessage='Purge Indexes:'
- />
- )}
- />
- <BooleanSetting
- id='enableSearching'
- label={
- <FormattedMessage
- id='admin.elasticsearch.enableSearchingTitle'
- defaultMessage='Enable Elasticsearch for search queries:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.elasticsearch.enableSearchingDescription'
- defaultMessage='Requires a successful connection to the Elasticsearch server. When true, Elasticsearch will be used for all search queries using the latest index. Search results may be incomplete until a bulk index of the existing post database is finished. When false, database search is used.'
- />
- }
- value={this.state.enableSearching}
- disabled={!this.state.enableIndexing || !this.state.configTested}
- onChange={this.handleChange}
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/elasticsearch_status/index.js b/webapp/components/admin_console/elasticsearch_status/index.js
deleted file mode 100644
index 6446195d2..000000000
--- a/webapp/components/admin_console/elasticsearch_status/index.js
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getJobsByType} from 'mattermost-redux/actions/jobs';
-import {JobTypes} from 'utils/constants.jsx';
-
-import * as Selectors from 'mattermost-redux/selectors/entities/jobs';
-
-import Status from './status.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps,
- jobs: Selectors.makeGetJobsByType(JobTypes.ELASTICSEARCH_POST_INDEXING)(state)
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getJobsByType
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(Status);
diff --git a/webapp/components/admin_console/elasticsearch_status/status.jsx b/webapp/components/admin_console/elasticsearch_status/status.jsx
deleted file mode 100644
index 0a32d39c8..000000000
--- a/webapp/components/admin_console/elasticsearch_status/status.jsx
+++ /dev/null
@@ -1,361 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {FormattedMessage} from 'react-intl';
-
-import {createJob, cancelJob} from 'actions/job_actions.jsx';
-import {JobTypes, JobStatuses} from 'utils/constants.jsx';
-import RequestButton from '../request_button/request_button.jsx';
-
-export default class Status extends React.PureComponent {
- static propTypes = {
-
- /**
- * Array of jobs
- */
- jobs: PropTypes.arrayOf(PropTypes.object).isRequired,
-
- /**
- * Whether Elasticsearch is properly configured.
- */
- isConfigured: PropTypes.bool.isRequired,
-
- actions: PropTypes.shape({
-
- /**
- * Function to fetch jobs
- */
- getJobsByType: PropTypes.func.isRequired
- }).isRequired
- };
-
- constructor(props) {
- super(props);
-
- this.interval = null;
-
- this.state = {
- loading: true,
- cancelInProgress: false
- };
- }
-
- componentWillMount() {
- // reload the cluster status every 15 seconds
- this.interval = setInterval(this.reload, 15000);
- }
-
- componentDidMount() {
- this.props.actions.getJobsByType(JobTypes.ELASTICSEARCH_POST_INDEXING).then(
- () => this.setState({loading: false})
- );
- }
-
- componentWillUnmount() {
- if (this.interval) {
- clearInterval(this.interval);
- }
- }
-
- reload = () => {
- this.props.actions.getJobsByType(JobTypes.ELASTICSEARCH_POST_INDEXING).then(
- () => {
- this.setState({
- loading: false,
- cancelInProgress: false
- });
- }
- );
- };
-
- createIndexJob = (success, error) => {
- const job = {
- type: JobTypes.ELASTICSEARCH_POST_INDEXING
- };
-
- createJob(
- job,
- () => {
- this.reload();
- success();
- },
- error
- );
- };
-
- cancelIndexJob = (e) => {
- e.preventDefault();
-
- const chosenJob = this.getChosenJob();
- if (!chosenJob) {
- return;
- }
-
- this.setState({
- cancelInProgress: true
- });
-
- cancelJob(
- chosenJob.id,
- () => {
- this.reload();
- },
- () => {
- this.reload();
- }
- );
- };
-
- getChosenJob = () => {
- let chosenJob = null;
-
- if (this.props.jobs.length > 0) {
- for (let i = 0; i < this.props.jobs.length; i++) {
- const job = this.props.jobs[i];
- if (job.status === JobStatuses.CANCEL_REQUESTED || job.status === JobStatuses.IN_PROGRESS) {
- chosenJob = job;
- } else {
- break;
- }
- }
-
- if (!chosenJob) {
- for (let i = 0; i < this.props.jobs.length; i++) {
- const job = this.props.jobs[i];
- if (job.status !== JobStatuses.PENDING && chosenJob) {
- continue;
- } else {
- chosenJob = job;
- break;
- }
- }
- }
- }
-
- return chosenJob;
- };
-
- render() {
- const chosenJob = this.getChosenJob();
-
- let indexButtonDisabled = !this.props.isConfigured;
- let buttonText = (
- <FormattedMessage
- id='admin.elasticsearch.indexButton.ready'
- defaultMessage='Build Index'
- />
- );
- let cancelButton = null;
- let indexButtonHelp = (
- <FormattedMessage
- id='admin.elasticsearch.indexHelpText.buildIndex'
- defaultMessage='All posts in the database will be indexed from oldest to newest. Elasticsearch is available during indexing but search results may be incomplete until the indexing job is complete.'
- />
- );
-
- if (this.state.loading) {
- indexButtonDisabled = true;
- } else if (chosenJob) {
- if (chosenJob.status === JobStatuses.PENDING || chosenJob.status === JobStatuses.IN_PROGRESS || chosenJob.status === JobStatuses.CANCEL_REQUESTED) {
- indexButtonDisabled = true;
- buttonText = (
- <span>
- <span className='fa fa-refresh icon--rotate'/>
- <FormattedMessage
- id='admin.elasticsearch.indexButton.inProgress'
- defaultMessage='Indexing in progress'
- />
- </span>
- );
- }
-
- if (chosenJob.status === JobStatuses.PENDING || chosenJob.status === JobStatuses.IN_PROGRESS || chosenJob.status === JobStatuses.CANCEL_REQUESTED) {
- indexButtonHelp = (
- <FormattedMessage
- id='admin.elasticsearch.indexHelpText.cancelIndexing'
- defaultMessage='Cancelling stops the indexing job and removes it from the queue. Posts that have already been indexed will not be deleted.'
- />
- );
- }
-
- if (!this.state.cancelInProgress && (chosenJob.status === JobStatuses.PENDING || chosenJob.status === JobStatuses.IN_PROGRESS)) {
- cancelButton = (
- <a
- href='#'
- className='btn btn-link'
- onClick={this.cancelIndexJob}
- >
- <FormattedMessage
- id='admin.elasticsearchStatus.cancelButton'
- defaultMessage='Cancel'
- />
- </a>
- );
- }
- }
-
- const indexButton = (
- <RequestButton
- requestAction={this.createIndexJob}
- helpText={indexButtonHelp}
- buttonText={buttonText}
- disabled={indexButtonDisabled}
- showSuccessMessage={false}
- errorMessage={{
- id: 'admin.elasticsearch.bulkIndexButton.error',
- defaultMessage: 'Failed to schedule Bulk Index Job: {error}'
- }}
- alternativeActionElement={cancelButton}
- label={(
- <FormattedMessage
- id='admin.elasticsearchStatus.bulkIndexLabel'
- defaultMessage='Bulk Indexing:'
- />
- )}
- />
- );
-
- let status = null;
- let statusHelp = null;
- let statusClass = null;
- if (!this.props.isConfigured) {
- status = (
- <FormattedMessage
- id='admin.elasticsearchStatus.statusIndexingDisabled'
- defaultMessage='Indexing disabled.'
- />
- );
- } else if (this.state.loading) {
- status = (
- <FormattedMessage
- id='admin.elasticsearchStatus.statusLoading'
- defaultMessage='Loading...'
- />
- );
- statusClass = 'status-icon-unknown';
- } else if (chosenJob) {
- if (chosenJob.status === JobStatuses.PENDING) {
- status = (
- <FormattedMessage
- id='admin.elasticsearchStatus.statusPending'
- defaultMessage='Job pending.'
- />
- );
- statusHelp = (
- <FormattedMessage
- id='admin.elasticsearchStatus.statusPending.help'
- defaultMessage='Elasticsearch index job is queued on the job server. If Elasticsearch is enabled, search results may be incomplete until the job is finished.'
- />
- );
- statusClass = 'status-icon-warning';
- } else if (chosenJob.status === JobStatuses.IN_PROGRESS) {
- status = (
- <FormattedMessage
- id='admin.elasticsearchStatus.statusInProgress'
- defaultMessage='Job in progress. {percent}% complete.'
- values={{
- percent: chosenJob.progress
- }}
- />
- );
- statusHelp = (
- <FormattedMessage
- id='admin.elasticsearchStatus.statusInProgress.help'
- defaultMessage='Indexing is in progress on the job server. If Elasticsearch is enabled, search results may be incomplete until the job is finished.'
- />
- );
- statusClass = 'status-icon-warning';
- } else if (chosenJob.status === JobStatuses.SUCCESS) {
- status = (
- <FormattedMessage
- id='admin.elasticsearchStatus.statusSuccess'
- defaultMessage='Indexing complete.'
- />
- );
- statusHelp = (
- <FormattedMessage
- id='admin.elasticsearchStatus.statusSuccess.help'
- defaultMessage='Indexing is complete and new posts are being automatically indexed.'
- />
- );
- statusClass = 'status-icon-success';
- } else if (chosenJob.status === JobStatuses.ERROR) {
- status = (
- <FormattedMessage
- id='admin.elasticsearchStatus.statusError'
- defaultMessage='Indexing error.'
- />
- );
- statusHelp = (
- <FormattedMessage
- id='admin.elasticsearchStatus.statusError.help'
- defaultMessage='Mattermost encountered an error building the Elasticsearch index: {error}'
- values={{
- error: chosenJob.data ? (chosenJob.data.error || '') : ''
- }}
- />
- );
- statusClass = 'status-icon-error';
- } else if (chosenJob.status === JobStatuses.CANCEL_REQUESTED) {
- status = (
- <FormattedMessage
- id='admin.elasticsearchStatus.statusRequestCancel'
- defaultMessage='Canceling Job...'
- />
- );
- statusClass = 'status-icon-warning';
- } else if (chosenJob.status === JobStatuses.CANCELED) {
- status = (
- <FormattedMessage
- id='admin.elasticsearchStatus.statusCancelled'
- defaultMessage='Indexing job cancelled.'
- />
- );
- statusClass = 'status-icon-error';
- }
- } else {
- status = (
- <FormattedMessage
- id='admin.elasticsearchStatus.statusNoJobs'
- defaultMessage='No indexing jobs queued.'
- />
- );
- statusClass = 'status-icon-unknown';
- }
-
- if (statusHelp !== null) {
- statusHelp = (
- <div className='col-sm-offset-4 col-sm-8'>
- <div className='help-text'>
- {statusHelp}
- </div>
- </div>
- );
- }
-
- statusClass = 'fa fa-circle margin--right ' + statusClass;
-
- return (
- <div>
- {indexButton}
- <div className='form-group'>
- <div className='col-sm-offset-4 col-sm-8'>
- <div className='help-text no-margin'>
- <FormattedMessage
- id='admin.elasticsearchStatus.status'
- defaultMessage='Status: '
- />
- <i
- className={statusClass}
- />
- {status}
- </div>
- </div>
- {statusHelp}
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/admin_console/email_authentication_settings.jsx b/webapp/components/admin_console/email_authentication_settings.jsx
deleted file mode 100644
index 9cc3ab3d7..000000000
--- a/webapp/components/admin_console/email_authentication_settings.jsx
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import AdminSettings from './admin_settings.jsx';
-import BooleanSetting from './boolean_setting.jsx';
-import {FormattedMessage} from 'react-intl';
-import SettingsGroup from './settings_group.jsx';
-
-export default class EmailAuthenticationSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
- }
-
- getConfigFromState(config) {
- config.EmailSettings.EnableSignUpWithEmail = this.state.enableSignUpWithEmail;
- config.EmailSettings.EnableSignInWithEmail = this.state.enableSignInWithEmail;
- config.EmailSettings.EnableSignInWithUsername = this.state.enableSignInWithUsername;
-
- return config;
- }
-
- getStateFromConfig(config) {
- return {
- enableSignUpWithEmail: config.EmailSettings.EnableSignUpWithEmail,
- enableSignInWithEmail: config.EmailSettings.EnableSignInWithEmail,
- enableSignInWithUsername: config.EmailSettings.EnableSignInWithUsername
- };
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.authentication.email'
- defaultMessage='Email Authentication'
- />
- );
- }
-
- renderSettings() {
- return (
- <SettingsGroup>
- <BooleanSetting
- id='enableSignUpWithEmail'
- label={
- <FormattedMessage
- id='admin.email.allowSignupTitle'
- defaultMessage='Enable account creation with email: '
- />
- }
- helpText={
- <FormattedMessage
- id='admin.email.allowSignupDescription'
- defaultMessage='When true, Mattermost allows account creation using email and password. This value should be false only when you want to limit sign up to a single sign-on service like AD/LDAP, SAML or GitLab.'
- />
- }
- value={this.state.enableSignUpWithEmail}
- onChange={this.handleChange}
- />
- <BooleanSetting
- id='enableSignInWithEmail'
- label={
- <FormattedMessage
- id='admin.email.allowEmailSignInTitle'
- defaultMessage='Enable sign-in with email: '
- />
- }
- helpText={
- <FormattedMessage
- id='admin.email.allowEmailSignInDescription'
- defaultMessage='When true, Mattermost allows users to sign in using their email and password.'
- />
- }
- value={this.state.enableSignInWithEmail}
- onChange={this.handleChange}
- />
- <BooleanSetting
- id='enableSignInWithUsername'
- label={
- <FormattedMessage
- id='admin.email.allowUsernameSignInTitle'
- defaultMessage='Enable sign-in with username: '
- />
- }
- helpText={
- <FormattedMessage
- id='admin.email.allowUsernameSignInDescription'
- defaultMessage='When true, users with email login can sign in using their username and password. This setting does not affect AD/LDAP login.'
- />
- }
- value={this.state.enableSignInWithUsername}
- onChange={this.handleChange}
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/email_connection_test.jsx b/webapp/components/admin_console/email_connection_test.jsx
deleted file mode 100644
index 17edbf23e..000000000
--- a/webapp/components/admin_console/email_connection_test.jsx
+++ /dev/null
@@ -1,130 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import * as Utils from 'utils/utils.jsx';
-
-import {FormattedMessage} from 'react-intl';
-
-import {testEmail} from 'actions/admin_actions.jsx';
-
-export default class EmailConnectionTestButton extends React.Component {
- static get propTypes() {
- return {
- config: PropTypes.object.isRequired,
- getConfigFromState: PropTypes.func.isRequired,
- disabled: PropTypes.bool.isRequired
- };
- }
-
- constructor(props) {
- super(props);
-
- this.handleTestConnection = this.handleTestConnection.bind(this);
-
- this.state = {
- testing: false,
- success: false,
- fail: null
- };
- }
-
- handleTestConnection(e) {
- e.preventDefault();
-
- this.setState({
- testing: true,
- success: false,
- fail: null
- });
-
- const config = JSON.parse(JSON.stringify(this.props.config));
- this.props.getConfigFromState(config);
-
- testEmail(
- config,
- () => {
- this.setState({
- testing: false,
- success: true
- });
- },
- (err) => {
- let fail = err.message;
- if (err.detailed_error) {
- fail += ' - ' + err.detailed_error;
- }
-
- this.setState({
- testing: false,
- fail
- });
- }
- );
- }
-
- render() {
- let testMessage = null;
- if (this.state.success) {
- testMessage = (
- <div className='alert alert-success'>
- <i className='fa fa-check'/>
- <FormattedMessage
- id='admin.email.emailSuccess'
- defaultMessage='No errors were reported while sending an email. Please check your inbox to make sure.'
- />
- </div>
- );
- } else if (this.state.fail) {
- testMessage = (
- <div className='alert alert-warning'>
- <i className='fa fa-warning'/>
- <FormattedMessage
- id='admin.email.emailFail'
- defaultMessage='Connection unsuccessful: {error}'
- values={{
- error: this.state.fail
- }}
- />
- </div>
- );
- }
-
- let contents = null;
- if (this.state.testing) {
- contents = (
- <span>
- <span className='fa fa-refresh icon--rotate'/>
- {Utils.localizeMessage('admin.email.testing', 'Testing...')}
- </span>
- );
- } else {
- contents = (
- <FormattedMessage
- id='admin.email.connectionSecurityTest'
- defaultMessage='Test Connection'
- />
- );
- }
-
- return (
- <div className='form-group email-connection-test'>
- <div className='col-sm-offset-4 col-sm-8'>
- <div className='help-text'>
- <button
- className='btn btn-default'
- onClick={this.handleTestConnection}
- disabled={this.props.disabled}
- >
- {contents}
- </button>
- {testMessage}
- </div>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/admin_console/email_settings.jsx b/webapp/components/admin_console/email_settings.jsx
deleted file mode 100644
index e630402bc..000000000
--- a/webapp/components/admin_console/email_settings.jsx
+++ /dev/null
@@ -1,395 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import ErrorStore from 'stores/error_store.jsx';
-
-import {ErrorBarTypes} from 'utils/constants.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import AdminSettings from './admin_settings.jsx';
-import BooleanSetting from './boolean_setting.jsx';
-import {ConnectionSecurityDropdownSettingEmail} from './connection_security_dropdown_setting.jsx';
-import DropdownSetting from './dropdown_setting.jsx';
-import EmailConnectionTest from './email_connection_test.jsx';
-import {FormattedHTMLMessage, FormattedMessage} from 'react-intl';
-import SettingsGroup from './settings_group.jsx';
-import TextSetting from './text_setting.jsx';
-
-const EMAIL_NOTIFICATION_CONTENTS_FULL = 'full';
-const EMAIL_NOTIFICATION_CONTENTS_GENERIC = 'generic';
-
-export default class EmailSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
- this.handleSaved = this.handleSaved.bind(this);
- this.renderSettings = this.renderSettings.bind(this);
- }
-
- getConfigFromState(config) {
- config.EmailSettings.SendEmailNotifications = this.state.sendEmailNotifications;
- config.EmailSettings.FeedbackName = this.state.feedbackName;
- config.EmailSettings.FeedbackEmail = this.state.feedbackEmail;
- config.EmailSettings.FeedbackOrganization = this.state.feedbackOrganization;
- config.EmailSettings.EnableSMTPAuth = this.state.enableSMTPAuth;
- config.EmailSettings.SMTPUsername = this.state.smtpUsername;
- config.EmailSettings.SMTPPassword = this.state.smtpPassword;
- config.EmailSettings.SMTPServer = this.state.smtpServer;
- config.EmailSettings.SMTPPort = this.state.smtpPort;
- config.EmailSettings.ConnectionSecurity = this.state.connectionSecurity;
- config.EmailSettings.EnableEmailBatching = this.state.enableEmailBatching;
- config.ServiceSettings.EnableSecurityFixAlert = this.state.enableSecurityFixAlert;
- config.EmailSettings.SkipServerCertificateVerification = this.state.skipServerCertificateVerification;
- config.EmailSettings.EmailNotificationContentsType = this.state.emailNotificationContentsType;
-
- return config;
- }
-
- handleSaved(newConfig) {
- if (newConfig.EmailSettings.SendEmailNotifications) {
- ErrorStore.clearError(ErrorBarTypes.PREVIEW_MODE);
- }
- }
-
- getStateFromConfig(config) {
- return {
- sendEmailNotifications: config.EmailSettings.SendEmailNotifications,
- feedbackName: config.EmailSettings.FeedbackName,
- feedbackEmail: config.EmailSettings.FeedbackEmail,
- feedbackOrganization: config.EmailSettings.FeedbackOrganization,
- enableSMTPAuth: config.EmailSettings.EnableSMTPAuth,
- smtpUsername: config.EmailSettings.SMTPUsername,
- smtpPassword: config.EmailSettings.SMTPPassword,
- smtpServer: config.EmailSettings.SMTPServer,
- smtpPort: config.EmailSettings.SMTPPort,
- connectionSecurity: config.EmailSettings.ConnectionSecurity,
- enableEmailBatching: config.EmailSettings.EnableEmailBatching,
- skipServerCertificateVerification: config.EmailSettings.SkipServerCertificateVerification,
- enableSecurityFixAlert: config.ServiceSettings.EnableSecurityFixAlert,
- emailNotificationContentsType: config.EmailSettings.EmailNotificationContentsType
- };
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.notifications.email'
- defaultMessage='Email'
- />
- );
- }
-
- renderSettings() {
- let enableEmailBatchingDisabledText = null;
-
- if (this.props.config.ClusterSettings.Enable) {
- enableEmailBatchingDisabledText = (
- <span
- key='admin.email.enableEmailBatching.clusterEnabled'
- className='help-text'
- >
- <FormattedHTMLMessage
- id='admin.email.enableEmailBatching.clusterEnabled'
- defaultMessage='Email batching cannot be enabled unless the SiteURL is configured in <b>Configuration > SiteURL</b>.'
- />
- </span>
- );
- } else if (!this.props.config.ServiceSettings.SiteURL) {
- enableEmailBatchingDisabledText = (
- <span
- key='admin.email.enableEmailBatching.siteURL'
- className='help-text'
- >
- <FormattedHTMLMessage
- id='admin.email.enableEmailBatching.siteURL'
- defaultMessage='Email batching cannot be enabled unless the SiteURL is configured in <b>Configuration > SiteURL</b>.'
- />
- </span>
- );
- }
-
- let emailNotificationContentsTypeDropdown = null;
- let emailNotificationContentsHelpText = null;
- if (window.mm_license.EmailNotificationContents === 'true') {
- const emailNotificationContentsTypes = [];
- emailNotificationContentsTypes.push({value: EMAIL_NOTIFICATION_CONTENTS_FULL, text: Utils.localizeMessage('admin.email.notification.contents.full', 'Send full message contents')});
- emailNotificationContentsTypes.push({value: EMAIL_NOTIFICATION_CONTENTS_GENERIC, text: Utils.localizeMessage('admin.email.notification.contents.generic', 'Send generic description with only sender name')});
-
- if (this.state.emailNotificationContentsType === EMAIL_NOTIFICATION_CONTENTS_FULL) {
- emailNotificationContentsHelpText = (
- <FormattedHTMLMessage
- key='admin.email.notification.contents.full.description'
- id='admin.email.notification.contents.full.description'
- defaultMessage='Sender name and channel are included in email notifications.</br>Typically used for compliance reasons if Mattermost contains confidential information and policy dictates it cannot be stored in email.'
- />
- );
- } else if (this.state.emailNotificationContentsType === EMAIL_NOTIFICATION_CONTENTS_GENERIC) {
- emailNotificationContentsHelpText = (
- <FormattedHTMLMessage
- key='admin.email.notification.contents.generic.description'
- id='admin.email.notification.contents.generic.description'
- defaultMessage='Only the name of the person who sent the message, with no information about channel name or message contents are included in email notifications.</br>Typically used for compliance reasons if Mattermost contains confidential information and policy dictates it cannot be stored in email.'
- />
- );
- }
-
- emailNotificationContentsTypeDropdown = (
- <DropdownSetting
- id='emailNotificationContentsType'
- values={emailNotificationContentsTypes}
- label={
- <FormattedMessage
- id='admin.email.notification.contents.title'
- defaultMessage='Email Notification Contents: '
- />
- }
- value={this.state.emailNotificationContentsType}
- onChange={this.handleChange}
- helpText={emailNotificationContentsHelpText}
- />
- );
- }
-
- return (
- <SettingsGroup>
- <BooleanSetting
- id='sendEmailNotifications'
- label={
- <FormattedMessage
- id='admin.email.notificationsTitle'
- defaultMessage='Enable Email Notifications: '
- />
- }
- helpText={
- <FormattedHTMLMessage
- id='admin.email.notificationsDescription'
- defaultMessage='Typically set to true in production. When true, Mattermost attempts to send email notifications. Developers may set this field to false to skip email setup for faster development.<br />Setting this to true removes the Preview Mode banner (requires logging out and logging back in after setting is changed).'
- />
- }
- value={this.state.sendEmailNotifications}
- onChange={this.handleChange}
- />
- <BooleanSetting
- id='enableEmailBatching'
- label={
- <FormattedMessage
- id='admin.email.enableEmailBatchingTitle'
- defaultMessage='Enable Email Batching: '
- />
- }
- helpText={[
- <FormattedHTMLMessage
- key='admin.email.enableEmailBatchingDesc'
- id='admin.email.enableEmailBatchingDesc'
- defaultMessage='When true, users will have email notifications for multiple direct messages and mentions combined into a single email. Batching will occur at a default interval of 15 minutes, configurable in Account Settings > Notifications.'
- />,
- enableEmailBatchingDisabledText
- ]}
- value={this.state.enableEmailBatching && !this.props.config.ClusterSettings.Enable && Boolean(this.props.config.ServiceSettings.SiteURL)}
- onChange={this.handleChange}
- disabled={!this.state.sendEmailNotifications || this.props.config.ClusterSettings.Enable || !this.props.config.ServiceSettings.SiteURL}
- />
- {emailNotificationContentsTypeDropdown}
- <TextSetting
- id='feedbackName'
- label={
- <FormattedMessage
- id='admin.email.notificationDisplayTitle'
- defaultMessage='Notification Display Name:'
- />
- }
- placeholder={Utils.localizeMessage('admin.email.notificationDisplayExample', 'Ex: "Mattermost Notification", "System", "No-Reply"')}
- helpText={
- <FormattedMessage
- id='admin.email.notificationDisplayDescription'
- defaultMessage='Display name on email account used when sending notification emails from Mattermost.'
- />
- }
- value={this.state.feedbackName}
- onChange={this.handleChange}
- disabled={!this.state.sendEmailNotifications}
- />
- <TextSetting
- id='feedbackEmail'
- label={
- <FormattedMessage
- id='admin.email.notificationEmailTitle'
- defaultMessage='Notification From Address:'
- />
- }
- placeholder={Utils.localizeMessage('admin.email.notificationEmailExample', 'Ex: "mattermost@yourcompany.com", "admin@yourcompany.com"')}
- helpText={
- <FormattedMessage
- id='admin.email.notificationEmailDescription'
- defaultMessage='Email address displayed on email account used when sending notification emails from Mattermost.'
- />
- }
- value={this.state.feedbackEmail}
- onChange={this.handleChange}
- disabled={!this.state.sendEmailNotifications}
- />
- <TextSetting
- id='feedbackOrganization'
- label={
- <FormattedMessage
- id='admin.email.notificationOrganization'
- defaultMessage='Notification Footer Mailing Address:'
- />
- }
- placeholder={Utils.localizeMessage('admin.email.notificationOrganizationExample', 'Ex: "© ABC Corporation, 565 Knight Way, Palo Alto, California, 94305, USA"')}
- helpText={
- <FormattedMessage
- id='admin.email.notificationOrganizationDescription'
- defaultMessage='Organization name and address displayed on email notifications from Mattermost, such as "© ABC Corporation, 565 Knight Way, Palo Alto, California, 94305, USA". If the field is left empty, the organization name and address will not be displayed.'
- />
- }
- value={this.state.feedbackOrganization}
- onChange={this.handleChange}
- disabled={!this.state.sendEmailNotifications}
- />
- <TextSetting
- id='smtpServer'
- label={
- <FormattedMessage
- id='admin.email.smtpServerTitle'
- defaultMessage='SMTP Server:'
- />
- }
- placeholder={Utils.localizeMessage('admin.email.smtpServerExample', 'Ex: "smtp.yourcompany.com", "email-smtp.us-east-1.amazonaws.com"')}
- helpText={
- <FormattedMessage
- id='admin.email.smtpServerDescription'
- defaultMessage='Location of SMTP email server.'
- />
- }
- value={this.state.smtpServer}
- onChange={this.handleChange}
- disabled={!this.state.sendEmailNotifications}
- />
- <TextSetting
- id='smtpPort'
- label={
- <FormattedMessage
- id='admin.email.smtpPortTitle'
- defaultMessage='SMTP Server Port:'
- />
- }
- placeholder={Utils.localizeMessage('admin.email.smtpPortExample', 'Ex: "25", "465", "587"')}
- helpText={
- <FormattedMessage
- id='admin.email.smtpPortDescription'
- defaultMessage='Port of SMTP email server.'
- />
- }
- value={this.state.smtpPort}
- onChange={this.handleChange}
- disabled={!this.state.sendEmailNotifications}
- />
- <BooleanSetting
- id='enableSMTPAuth'
- label={
- <FormattedMessage
- id='admin.email.enableSMTPAuthTitle'
- defaultMessage='Enable SMTP Authentication: '
- />
- }
- helpText={[
- <FormattedHTMLMessage
- key='admin.email.enableSMTPAuthDesc'
- id='admin.email.enableSMTPAuthDesc'
- defaultMessage='When true, SMTP Authentication is enabled.'
- />
- ]}
- value={this.state.enableSMTPAuth}
- onChange={this.handleChange}
- disabled={!this.state.sendEmailNotifications}
- />
- <TextSetting
- id='smtpUsername'
- label={
- <FormattedMessage
- id='admin.email.smtpUsernameTitle'
- defaultMessage='SMTP Server Username:'
- />
- }
- placeholder={Utils.localizeMessage('admin.email.smtpUsernameExample', 'Ex: "admin@yourcompany.com", "AKIADTOVBGERKLCBV"')}
- helpText={
- <FormattedMessage
- id='admin.email.smtpUsernameDescription'
- defaultMessage=' Obtain this credential from administrator setting up your email server.'
- />
- }
- value={this.state.smtpUsername}
- onChange={this.handleChange}
- disabled={!this.state.sendEmailNotifications || !this.state.enableSMTPAuth}
- />
- <TextSetting
- id='smtpPassword'
- label={
- <FormattedMessage
- id='admin.email.smtpPasswordTitle'
- defaultMessage='SMTP Server Password:'
- />
- }
- placeholder={Utils.localizeMessage('admin.email.smtpPasswordExample', 'Ex: "yourpassword", "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"')}
- helpText={
- <FormattedMessage
- id='admin.email.smtpPasswordDescription'
- defaultMessage=' Obtain this credential from administrator setting up your email server.'
- />
- }
- value={this.state.smtpPassword}
- onChange={this.handleChange}
- disabled={!this.state.sendEmailNotifications || !this.state.enableSMTPAuth}
- />
- <ConnectionSecurityDropdownSettingEmail
- value={this.state.connectionSecurity}
- onChange={this.handleChange}
- disabled={!this.state.sendEmailNotifications}
- />
- <EmailConnectionTest
- config={this.props.config}
- getConfigFromState={this.getConfigFromState}
- disabled={!this.state.sendEmailNotifications}
- />
- <BooleanSetting
- id='skipServerCertificateVerification'
- label={
- <FormattedMessage
- id='admin.email.skipServerCertificateVerification.title'
- defaultMessage='Skip Server Certificate Verification: '
- />
- }
- helpText={
- <FormattedMessage
- id='admin.email.skipServerCertificateVerification.description'
- defaultMessage='When true, Mattermost will not verify the email server certificate.'
- />
- }
- value={this.state.skipServerCertificateVerification}
- onChange={this.handleChange}
- />
- <BooleanSetting
- id='enableSecurityFixAlert'
- label={
- <FormattedMessage
- id='admin.service.securityTitle'
- defaultMessage='Enable Security Alerts: '
- />
- }
- helpText={
- <FormattedMessage
- id='admin.service.securityDesc'
- defaultMessage='When true, System Administrators are notified by email if a relevant security fix alert has been announced in the last 12 hours. Requires email to be enabled.'
- />
- }
- value={this.state.enableSecurityFixAlert}
- onChange={this.handleChange}
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/external_service_settings.jsx b/webapp/components/admin_console/external_service_settings.jsx
deleted file mode 100644
index 6359470a8..000000000
--- a/webapp/components/admin_console/external_service_settings.jsx
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import * as Utils from 'utils/utils.jsx';
-
-import AdminSettings from './admin_settings.jsx';
-import {FormattedHTMLMessage, FormattedMessage} from 'react-intl';
-import SettingsGroup from './settings_group.jsx';
-import TextSetting from './text_setting.jsx';
-
-export default class ExternalServiceSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
- }
-
- getConfigFromState(config) {
- config.ServiceSettings.GoogleDeveloperKey = this.state.googleDeveloperKey;
- return config;
- }
-
- getStateFromConfig(config) {
- return {
- googleDeveloperKey: config.ServiceSettings.GoogleDeveloperKey
- };
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.integrations.external'
- defaultMessage='External Services'
- />
- );
- }
-
- renderSettings() {
- return (
- <SettingsGroup>
- <TextSetting
- id='googleDeveloperKey'
- label={
- <FormattedMessage
- id='admin.service.googleTitle'
- defaultMessage='Google API Key:'
- />
- }
- placeholder={Utils.localizeMessage('admin.service.googleExample', 'Ex "7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QV"')}
- helpText={
- <FormattedHTMLMessage
- id='admin.service.googleDescription'
- defaultMessage='Set this key to enable the display of titles for embedded YouTube video previews. Without the key, YouTube previews will still be created based on hyperlinks appearing in messages or comments but they will not show the video title. View a <a href="https://www.youtube.com/watch?v=Im69kzhpR3I" target="_blank">Google Developers Tutorial</a> for instructions on how to obtain a key.'
- />
- }
- value={this.state.googleDeveloperKey}
- onChange={this.handleChange}
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/file_upload_setting.jsx b/webapp/components/admin_console/file_upload_setting.jsx
deleted file mode 100644
index c2bc9869e..000000000
--- a/webapp/components/admin_console/file_upload_setting.jsx
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import PropTypes from 'prop-types';
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-
-import Setting from './setting.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-
-export default class FileUploadSetting extends Setting {
- static get propTypes() {
- return {
- id: PropTypes.string.isRequired,
- label: PropTypes.node.isRequired,
- helpText: PropTypes.node,
- uploadingText: PropTypes.node,
- onSubmit: PropTypes.func.isRequired,
- disabled: PropTypes.bool,
- fileType: PropTypes.string.isRequired,
- error: PropTypes.string
- };
- }
-
- constructor(props) {
- super(props);
-
- this.handleChange = this.handleChange.bind(this);
- this.handleSubmit = this.handleSubmit.bind(this);
-
- this.state = {
- fileName: null,
- serverError: props.error
- };
- }
-
- handleChange() {
- const files = this.refs.fileInput.files;
- if (files && files.length > 0) {
- this.setState({fileSelected: true, fileName: files[0].name});
- }
- }
-
- handleSubmit(e) {
- e.preventDefault();
-
- $(this.refs.upload_button).button('loading');
- this.props.onSubmit(this.props.id, this.refs.fileInput.files[0], (error) => {
- $(this.refs.upload_button).button('reset');
- if (error) {
- Utils.clearFileInput(this.refs.fileInput);
- }
- });
- }
-
- render() {
- let serverError;
- if (this.state.serverError) {
- serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
- }
-
- let btnClass = 'btn';
- if (this.state.fileSelected) {
- btnClass = 'btn btn-primary';
- }
-
- let fileName;
- if (this.state.fileName) {
- fileName = this.state.fileName;
- } else {
- fileName = (
- <FormattedMessage
- id='admin.file_upload.noFile'
- defaultMessage='No file uploaded'
- />
- );
- }
-
- return (
- <Setting
- label={this.props.label}
- helpText={this.props.helpText}
- inputId={this.props.id}
- >
- <div>
- <div className='file__upload'>
- <button
- className='btn btn-default'
- disabled={this.props.disabled}
- >
- <FormattedMessage
- id='admin.file_upload.chooseFile'
- defaultMessage='Choose File'
- />
- </button>
- <input
- ref='fileInput'
- type='file'
- disabled={this.props.disabled}
- accept={this.props.fileType}
- onChange={this.handleChange}
- />
- </div>
- <button
- className={btnClass}
- disabled={!this.state.fileSelected}
- onClick={this.handleSubmit}
- ref='upload_button'
- data-loading-text={`<span class='glyphicon glyphicon-refresh glyphicon-refresh-animate'></span> ${this.props.uploadingText}`}
- >
- <FormattedMessage
- id='admin.file_upload.uploadFile'
- defaultMessage='Upload'
- />
- </button>
- <div className='help-text no-margin'>
- {fileName}
- </div>
- {serverError}
- </div>
- </Setting>
- );
- }
-}
diff --git a/webapp/components/admin_console/generated_setting.jsx b/webapp/components/admin_console/generated_setting.jsx
deleted file mode 100644
index 2fed2f42f..000000000
--- a/webapp/components/admin_console/generated_setting.jsx
+++ /dev/null
@@ -1,106 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import crypto from 'crypto';
-
-import {FormattedMessage} from 'react-intl';
-
-export default class GeneratedSetting extends React.Component {
- static get propTypes() {
- return {
- id: PropTypes.string.isRequired,
- label: PropTypes.node.isRequired,
- placeholder: PropTypes.string,
- value: PropTypes.string.isRequired,
- onChange: PropTypes.func.isRequired,
- disabled: PropTypes.bool.isRequired,
- disabledText: PropTypes.node,
- helpText: PropTypes.node.isRequired,
- regenerateText: PropTypes.node,
- regenerateHelpText: PropTypes.node
- };
- }
-
- static get defaultProps() {
- return {
- disabled: false,
- regenerateText: (
- <FormattedMessage
- id='admin.regenerate'
- defaultMessage='Regenerate'
- />
- )
- };
- }
-
- constructor(props) {
- super(props);
-
- this.regenerate = this.regenerate.bind(this);
- }
-
- regenerate(e) {
- e.preventDefault();
-
- this.props.onChange(this.props.id, crypto.randomBytes(256).toString('base64').substring(0, 32));
- }
-
- render() {
- let disabledText = null;
- if (this.props.disabled && this.props.disabledText) {
- disabledText = (
- <div className='admin-console__disabled-text'>
- {this.props.disabledText}
- </div>
- );
- }
-
- let regenerateHelpText = null;
- if (this.props.regenerateHelpText) {
- regenerateHelpText = (
- <div className='help-text'>
- {this.props.regenerateHelpText}
- </div>
- );
- }
-
- return (
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor={this.props.id}
- >
- {this.props.label}
- </label>
- <div className='col-sm-8'>
- <input
- type='text'
- className='form-control'
- id={this.props.id}
- placeholder={this.props.placeholder}
- value={this.props.value}
- disabled={true}
- />
- {disabledText}
- <div className='help-text'>
- {this.props.helpText}
- </div>
- <div className='help-text'>
- <button
- className='btn btn-default'
- onClick={this.regenerate}
- disabled={this.props.disabled}
- >
- {this.props.regenerateText}
- </button>
- </div>
- {regenerateHelpText}
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/admin_console/gitlab_settings.jsx b/webapp/components/admin_console/gitlab_settings.jsx
deleted file mode 100644
index d08597b7d..000000000
--- a/webapp/components/admin_console/gitlab_settings.jsx
+++ /dev/null
@@ -1,179 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import * as Utils from 'utils/utils.jsx';
-
-import AdminSettings from './admin_settings.jsx';
-import BooleanSetting from './boolean_setting.jsx';
-import {FormattedHTMLMessage, FormattedMessage} from 'react-intl';
-import SettingsGroup from './settings_group.jsx';
-import TextSetting from './text_setting.jsx';
-
-export default class GitLabSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
- }
-
- getConfigFromState(config) {
- config.GitLabSettings.Enable = this.state.enable;
- config.GitLabSettings.Id = this.state.id;
- config.GitLabSettings.Secret = this.state.secret;
- config.GitLabSettings.UserApiEndpoint = this.state.userApiEndpoint;
- config.GitLabSettings.AuthEndpoint = this.state.authEndpoint;
- config.GitLabSettings.TokenEndpoint = this.state.tokenEndpoint;
-
- return config;
- }
-
- getStateFromConfig(config) {
- return {
- enable: config.GitLabSettings.Enable,
- id: config.GitLabSettings.Id,
- secret: config.GitLabSettings.Secret,
- userApiEndpoint: config.GitLabSettings.UserApiEndpoint,
- authEndpoint: config.GitLabSettings.AuthEndpoint,
- tokenEndpoint: config.GitLabSettings.TokenEndpoint
- };
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.authentication.gitlab'
- defaultMessage='GitLab'
- />
- );
- }
-
- renderSettings() {
- return (
- <SettingsGroup>
- <BooleanSetting
- id='enable'
- label={
- <FormattedMessage
- id='admin.gitlab.enableTitle'
- defaultMessage='Enable authentication with GitLab: '
- />
- }
- helpText={
- <div>
- <FormattedMessage
- id='admin.gitlab.enableDescription'
- defaultMessage='When true, Mattermost allows team creation and account signup using GitLab OAuth.'
- />
- <br/>
- <FormattedHTMLMessage
- id='admin.gitlab.EnableHtmlDesc'
- defaultMessage='<ol><li>Log in to your GitLab account and go to Profile Settings -> Applications.</li><li>Enter Redirect URIs "<your-mattermost-url>/login/gitlab/complete" (example: http://localhost:8065/login/gitlab/complete) and "<your-mattermost-url>/signup/gitlab/complete". </li><li>Then use "Application Secret Key" and "Application ID" fields from GitLab to complete the options below.</li><li>Complete the Endpoint URLs below. </li></ol>'
- />
- </div>
- }
- value={this.state.enable}
- onChange={this.handleChange}
- />
- <TextSetting
- id='id'
- label={
- <FormattedMessage
- id='admin.gitlab.clientIdTitle'
- defaultMessage='Application ID:'
- />
- }
- placeholder={Utils.localizeMessage('admin.gitlab.clientIdExample', 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"')}
- helpText={
- <FormattedMessage
- id='admin.gitlab.clientIdDescription'
- defaultMessage='Obtain this value via the instructions above for logging into GitLab'
- />
- }
- value={this.state.id}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <TextSetting
- id='secret'
- label={
- <FormattedMessage
- id='admin.gitlab.clientSecretTitle'
- defaultMessage='Application Secret Key:'
- />
- }
- placeholder={Utils.localizeMessage('admin.gitlab.clientSecretExample', 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"')}
- helpText={
- <FormattedMessage
- id='admin.gitab.clientSecretDescription'
- defaultMessage='Obtain this value via the instructions above for logging into GitLab.'
- />
- }
- value={this.state.secret}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <TextSetting
- id='userApiEndpoint'
- label={
- <FormattedMessage
- id='admin.gitlab.userTitle'
- defaultMessage='User API Endpoint:'
- />
- }
- placeholder={Utils.localizeMessage('admin.gitlab.userExample', 'Ex "https://<your-gitlab-url>/api/v3/user"')}
- helpText={
- <FormattedMessage
- id='admin.gitlab.userDescription'
- defaultMessage='Enter https://<your-gitlab-url>/api/v3/user. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'
- />
- }
- value={this.state.userApiEndpoint}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <TextSetting
- id='authEndpoint'
- label={
- <FormattedMessage
- id='admin.gitlab.authTitle'
- defaultMessage='Auth Endpoint:'
- />
- }
- placeholder={Utils.localizeMessage('admin.gitlab.authExample', 'Ex "https://<your-gitlab-url>/oauth/authorize"')}
- helpText={
- <FormattedMessage
- id='admin.gitlab.authDescription'
- defaultMessage='Enter https://<your-gitlab-url>/oauth/authorize (example https://example.com:3000/oauth/authorize). Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'
- />
- }
- value={this.state.authEndpoint}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <TextSetting
- id='tokenEndpoint'
- label={
- <FormattedMessage
- id='admin.gitlab.tokenTitle'
- defaultMessage='Token Endpoint:'
- />
- }
- placeholder={Utils.localizeMessage('admin.gitlab.tokenExample', 'Ex "https://<your-gitlab-url>/oauth/token"')}
- helpText={
- <FormattedMessage
- id='admin.gitlab.tokenDescription'
- defaultMessage='Enter https://<your-gitlab-url>/oauth/token. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'
- />
- }
- value={this.state.tokenEndpoint}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/index.js b/webapp/components/admin_console/index.js
deleted file mode 100644
index 4b333e65c..000000000
--- a/webapp/components/admin_console/index.js
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getConfig} from 'mattermost-redux/actions/admin';
-
-import * as Selectors from 'mattermost-redux/selectors/entities/admin';
-
-import AdminConsole from './admin_console.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps,
- config: Selectors.getConfig(state)
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getConfig
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(AdminConsole);
diff --git a/webapp/components/admin_console/ldap_settings.jsx b/webapp/components/admin_console/ldap_settings.jsx
deleted file mode 100644
index 9ffbe3b0e..000000000
--- a/webapp/components/admin_console/ldap_settings.jsx
+++ /dev/null
@@ -1,504 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import AdminSettings from './admin_settings.jsx';
-import BooleanSetting from './boolean_setting.jsx';
-import {ConnectionSecurityDropdownSettingLdap} from './connection_security_dropdown_setting.jsx';
-import SettingsGroup from './settings_group.jsx';
-import TextSetting from './text_setting.jsx';
-
-import {ldapSyncNow, ldapTest} from 'actions/admin_actions.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-import RequestButton from './request_button/request_button.jsx';
-
-export default class LdapSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
- }
-
- getConfigFromState(config) {
- config.LdapSettings.Enable = this.state.enable;
- config.LdapSettings.LdapServer = this.state.ldapServer;
- config.LdapSettings.LdapPort = this.parseIntNonZero(this.state.ldapPort);
- config.LdapSettings.ConnectionSecurity = this.state.connectionSecurity;
- config.LdapSettings.BaseDN = this.state.baseDN;
- config.LdapSettings.BindUsername = this.state.bindUsername;
- config.LdapSettings.BindPassword = this.state.bindPassword;
- config.LdapSettings.UserFilter = this.state.userFilter;
- config.LdapSettings.FirstNameAttribute = this.state.firstNameAttribute;
- config.LdapSettings.LastNameAttribute = this.state.lastNameAttribute;
- config.LdapSettings.NicknameAttribute = this.state.nicknameAttribute;
- config.LdapSettings.EmailAttribute = this.state.emailAttribute;
- config.LdapSettings.UsernameAttribute = this.state.usernameAttribute;
- config.LdapSettings.PositionAttribute = this.state.positionAttribute;
- config.LdapSettings.IdAttribute = this.state.idAttribute;
- config.LdapSettings.SyncIntervalMinutes = this.parseIntNonZero(this.state.syncIntervalMinutes);
- config.LdapSettings.SkipCertificateVerification = this.state.skipCertificateVerification;
- config.LdapSettings.QueryTimeout = this.parseIntNonZero(this.state.queryTimeout);
- config.LdapSettings.MaxPageSize = this.parseInt(this.state.maxPageSize);
- config.LdapSettings.LoginFieldName = this.state.loginFieldName;
-
- return config;
- }
-
- getStateFromConfig(config) {
- return {
- enable: config.LdapSettings.Enable,
- ldapServer: config.LdapSettings.LdapServer,
- ldapPort: config.LdapSettings.LdapPort,
- connectionSecurity: config.LdapSettings.ConnectionSecurity,
- baseDN: config.LdapSettings.BaseDN,
- bindUsername: config.LdapSettings.BindUsername,
- bindPassword: config.LdapSettings.BindPassword,
- userFilter: config.LdapSettings.UserFilter,
- firstNameAttribute: config.LdapSettings.FirstNameAttribute,
- lastNameAttribute: config.LdapSettings.LastNameAttribute,
- nicknameAttribute: config.LdapSettings.NicknameAttribute,
- emailAttribute: config.LdapSettings.EmailAttribute,
- usernameAttribute: config.LdapSettings.UsernameAttribute,
- positionAttribute: config.LdapSettings.PositionAttribute,
- idAttribute: config.LdapSettings.IdAttribute,
- syncIntervalMinutes: config.LdapSettings.SyncIntervalMinutes,
- skipCertificateVerification: config.LdapSettings.SkipCertificateVerification,
- queryTimeout: config.LdapSettings.QueryTimeout,
- maxPageSize: config.LdapSettings.MaxPageSize,
- loginFieldName: config.LdapSettings.LoginFieldName
- };
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.authentication.ldap'
- defaultMessage='AD/LDAP'
- />
- );
- }
-
- renderSettings() {
- const licenseEnabled = global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.LDAP === 'true';
- if (!licenseEnabled) {
- return null;
- }
-
- return (
- <SettingsGroup>
- <BooleanSetting
- id='enable'
- label={
- <FormattedMessage
- id='admin.ldap.enableTitle'
- defaultMessage='Enable sign-in with AD/LDAP:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.ldap.enableDesc'
- defaultMessage='When true, Mattermost allows login using AD/LDAP'
- />
- }
- value={this.state.enable}
- onChange={this.handleChange}
- />
- <TextSetting
- id='ldapServer'
- label={
- <FormattedMessage
- id='admin.ldap.serverTitle'
- defaultMessage='AD/LDAP Server:'
- />
- }
- placeholder={Utils.localizeMessage('admin.ldap.serverEx', 'Ex "10.0.0.23"')}
- helpText={
- <FormattedMessage
- id='admin.ldap.serverDesc'
- defaultMessage='The domain or IP address of AD/LDAP server.'
- />
- }
- value={this.state.ldapServer}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <TextSetting
- id='ldapPort'
- label={
- <FormattedMessage
- id='admin.ldap.portTitle'
- defaultMessage='AD/LDAP Port:'
- />
- }
- placeholder={Utils.localizeMessage('admin.ldap.portEx', 'Ex "389"')}
- helpText={
- <FormattedMessage
- id='admin.ldap.portDesc'
- defaultMessage='The port Mattermost will use to connect to the AD/LDAP server. Default is 389.'
- />
- }
- value={this.state.ldapPort}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <ConnectionSecurityDropdownSettingLdap
- value={this.state.connectionSecurity}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <BooleanSetting
- id='skipCertificateVerification'
- label={
- <FormattedMessage
- id='admin.ldap.skipCertificateVerification'
- defaultMessage='Skip Certificate Verification:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.ldap.skipCertificateVerificationDesc'
- defaultMessage='Skips the certificate verification step for TLS or STARTTLS connections. Not recommended for production environments where TLS is required. For testing only.'
- />
- }
- value={this.state.skipCertificateVerification}
- onChange={this.handleChange}
- />
- <TextSetting
- id='baseDN'
- label={
- <FormattedMessage
- id='admin.ldap.baseTitle'
- defaultMessage='BaseDN:'
- />
- }
- placeholder={Utils.localizeMessage('admin.ldap.baseEx', 'Ex "ou=Unit Name,dc=corp,dc=example,dc=com"')}
- helpText={
- <FormattedMessage
- id='admin.ldap.baseDesc'
- defaultMessage='The Base DN is the Distinguished Name of the location where Mattermost should start its search for users in the AD/LDAP tree.'
- />
- }
- value={this.state.baseDN}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <TextSetting
- id='bindUsername'
- label={
- <FormattedMessage
- id='admin.ldap.bindUserTitle'
- defaultMessage='Bind Username:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.ldap.bindUserDesc'
- defaultMessage='The username used to perform the AD/LDAP search. This should typically be an account created specifically for use with Mattermost. It should have access limited to read the portion of the AD/LDAP tree specified in the BaseDN field.'
- />
- }
- value={this.state.bindUsername}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <TextSetting
- id='bindPassword'
- label={
- <FormattedMessage
- id='admin.ldap.bindPwdTitle'
- defaultMessage='Bind Password:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.ldap.bindPwdDesc'
- defaultMessage='Password of the user given in "Bind Username".'
- />
- }
- value={this.state.bindPassword}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <TextSetting
- id='userFilter'
- label={
- <FormattedMessage
- id='admin.ldap.userFilterTitle'
- defaultMessage='User Filter:'
- />
- }
- placeholder={Utils.localizeMessage('admin.ldap.userFilterEx', 'Ex. "(objectClass=user)"')}
- helpText={
- <FormattedMessage
- id='admin.ldap.userFilterDisc'
- defaultMessage='(Optional) Enter an AD/LDAP Filter to use when searching for user objects. Only the users selected by the query will be able to access Mattermost. For Active Directory, the query to filter out disabled users is (&(objectCategory=Person)(!(UserAccountControl:1.2.840.113556.1.4.803:=2))).'
- />
- }
- value={this.state.userFilter}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <TextSetting
- id='firstNameAttribute'
- label={
- <FormattedMessage
- id='admin.ldap.firstnameAttrTitle'
- defaultMessage='First Name Attribute'
- />
- }
- placeholder={Utils.localizeMessage('admin.ldap.firstnameAttrEx', 'Ex "givenName"')}
- helpText={
- <FormattedMessage
- id='admin.ldap.firstnameAttrDesc'
- defaultMessage='(Optional) The attribute in the AD/LDAP server that will be used to populate the first name of users in Mattermost. When set, users will not be able to edit their first name, since it is synchronized with the LDAP server. When left blank, users can set their own first name in Account Settings.'
- />
- }
- value={this.state.firstNameAttribute}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <TextSetting
- id='lastNameAttribute'
- label={
- <FormattedMessage
- id='admin.ldap.lastnameAttrTitle'
- defaultMessage='Last Name Attribute:'
- />
- }
- placeholder={Utils.localizeMessage('admin.ldap.lastnameAttrEx', 'Ex "sn"')}
- helpText={
- <FormattedMessage
- id='admin.ldap.lastnameAttrDesc'
- defaultMessage='(Optional) The attribute in the AD/LDAP server that will be used to populate the last name of users in Mattermost. When set, users will not be able to edit their last name, since it is synchronized with the LDAP server. When left blank, users can set their own last name in Account Settings.'
- />
- }
- value={this.state.lastNameAttribute}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <TextSetting
- id='nicknameAttribute'
- label={
- <FormattedMessage
- id='admin.ldap.nicknameAttrTitle'
- defaultMessage='Nickname Attribute:'
- />
- }
- placeholder={Utils.localizeMessage('admin.ldap.nicknameAttrEx', 'Ex "nickname"')}
- helpText={
- <FormattedMessage
- id='admin.ldap.nicknameAttrDesc'
- defaultMessage='(Optional) The attribute in the AD/LDAP server that will be used to populate the nickname of users in Mattermost. When set, users will not be able to edit their nickname, since it is synchronized with the LDAP server. When left blank, users can set their own nickname in Account Settings.'
- />
- }
- value={this.state.nicknameAttribute}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <TextSetting
- id='positionAttribute'
- label={
- <FormattedMessage
- id='admin.ldap.positionAttrTitle'
- defaultMessage='Position Attribute:'
- />
- }
- placeholder={Utils.localizeMessage('admin.ldap.positionAttrEx', 'E.g.: "title"')}
- helpText={
- <FormattedMessage
- id='admin.ldap.positionAttrDesc'
- defaultMessage='(Optional) The attribute in the AD/LDAP server that will be used to populate the position field in Mattermost.'
- />
- }
- value={this.state.positionAttribute}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <TextSetting
- id='emailAttribute'
- label={
- <FormattedMessage
- id='admin.ldap.emailAttrTitle'
- defaultMessage='Email Attribute:'
- />
- }
- placeholder={Utils.localizeMessage('admin.ldap.emailAttrEx', 'Ex "mail" or "userPrincipalName"')}
- helpText={
- <FormattedMessage
- id='admin.ldap.emailAttrDesc'
- defaultMessage='The attribute in the AD/LDAP server that will be used to populate the email addresses of users in Mattermost.'
- />
- }
- value={this.state.emailAttribute}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <TextSetting
- id='usernameAttribute'
- label={
- <FormattedMessage
- id='admin.ldap.usernameAttrTitle'
- defaultMessage='Username Attribute:'
- />
- }
- placeholder={Utils.localizeMessage('admin.ldap.usernameAttrEx', 'Ex "sAMAccountName"')}
- helpText={
- <FormattedMessage
- id='admin.ldap.uernameAttrDesc'
- defaultMessage='The attribute in the AD/LDAP server that will be used to populate the username field in Mattermost. This may be the same as the ID Attribute.'
- />
- }
- value={this.state.usernameAttribute}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <TextSetting
- id='idAttribute'
- label={
- <FormattedMessage
- id='admin.ldap.idAttrTitle'
- defaultMessage='ID Attribute: '
- />
- }
- placeholder={Utils.localizeMessage('admin.ldap.idAttrEx', 'Ex "sAMAccountName"')}
- helpText={
- <FormattedMessage
- id='admin.ldap.idAttrDesc'
- defaultMessage='The attribute in the AD/LDAP server that will be used as a unique identifier in Mattermost. It should be an AD/LDAP attribute with a value that does not change, such as username or uid. If a user’s ID Attribute changes, it will create a new Mattermost account unassociated with their old one. This is the value used to log in to Mattermost in the "AD/LDAP Username" field on the sign in page. Normally this attribute is the same as the "Username Attribute" field above. If your team typically uses domain\\username to sign in to other services with AD/LDAP, you may choose to put domain\\username in this field to maintain consistency between sites.'
- />
- }
- value={this.state.idAttribute}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <TextSetting
- id='loginFieldName'
- label={
- <FormattedMessage
- id='admin.ldap.loginNameTitle'
- defaultMessage='Sign-in Field Default Text:'
- />
- }
- placeholder={Utils.localizeMessage('admin.ldap.loginNameEx', 'Ex "AD/LDAP Username"')}
- helpText={
- <FormattedMessage
- id='admin.ldap.loginNameDesc'
- defaultMessage='The placeholder text that appears in the login field on the login page. Defaults to "AD/LDAP Username".'
- />
- }
- value={this.state.loginFieldName}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <TextSetting
- id='syncIntervalMinutes'
- label={
- <FormattedMessage
- id='admin.ldap.syncIntervalTitle'
- defaultMessage='Synchronization Interval (minutes):'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.ldap.syncIntervalHelpText'
- defaultMessage='AD/LDAP Synchronization updates Mattermost user information to reflect updates on the AD/LDAP server. For example, when a user’s name changes on the AD/LDAP server, the change updates in Mattermost when synchronization is performed. Accounts removed from or disabled in the AD/LDAP server have their Mattermost accounts set to "Inactive" and have their account sessions revoked. Mattermost performs synchronization on the interval entered. For example, if 60 is entered, Mattermost synchronizes every 60 minutes.'
- />
- }
- value={this.state.syncIntervalMinutes}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <TextSetting
- id='maxPageSize'
- label={
- <FormattedMessage
- id='admin.ldap.maxPageSizeTitle'
- defaultMessage='Maximum Page Size:'
- />
- }
- placeholder={Utils.localizeMessage('admin.ldap.maxPageSizeEx', 'Ex "2000"')}
- helpText={
- <FormattedMessage
- id='admin.ldap.maxPageSizeHelpText'
- defaultMessage='The maximum number of users the Mattermost server will request from the AD/LDAP server at one time. 0 is unlimited.'
- />
- }
- value={this.state.maxPageSize}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <TextSetting
- id='queryTimeout'
- label={
- <FormattedMessage
- id='admin.ldap.queryTitle'
- defaultMessage='Query Timeout (seconds):'
- />
- }
- placeholder={Utils.localizeMessage('admin.ldap.queryEx', 'Ex "60"')}
- helpText={
- <FormattedMessage
- id='admin.ldap.queryDesc'
- defaultMessage='The timeout value for queries to the AD/LDAP server. Increase if you are getting timeout errors caused by a slow AD/LDAP server.'
- />
- }
- value={this.state.queryTimeout}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <RequestButton
- requestAction={ldapSyncNow}
- helpText={
- <FormattedMessage
- id='admin.ldap.syncNowHelpText'
- defaultMessage='Initiates an AD/LDAP synchronization immediately.'
- />
- }
- buttonText={
- <FormattedMessage
- id='admin.ldap.sync_button'
- defaultMessage='AD/LDAP Synchronize Now'
- />
- }
- disabled={!this.state.enable}
- showSuccessMessage={false}
- errorMessage={{
- id: 'admin.ldap.syncFailure',
- defaultMessage: 'Sync Failure: {error}'
- }}
- includeDetailedError={true}
- />
- <RequestButton
- requestAction={ldapTest}
- helpText={
- <FormattedMessage
- id='admin.ldap.testHelpText'
- defaultMessage='Tests if the Mattermost server can connect to the AD/LDAP server specified. See log file for more detailed error messages.'
- />
- }
- buttonText={
- <FormattedMessage
- id='admin.ldap.ldap_test_button'
- defaultMessage='AD/LDAP Test'
- />
- }
- disabled={!this.state.enable}
- saveNeeded={this.state.saveNeeded}
- saveConfigAction={this.doSubmit}
- errorMessage={{
- id: 'admin.ldap.testFailure',
- defaultMessage: 'AD/LDAP Test Failure: {error}'
- }}
- successMessage={{
- id: 'admin.ldap.testSuccess',
- defaultMessage: 'AD/LDAP Test Successful'
- }}
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/legal_and_support_settings.jsx b/webapp/components/admin_console/legal_and_support_settings.jsx
deleted file mode 100644
index 6b64e0c07..000000000
--- a/webapp/components/admin_console/legal_and_support_settings.jsx
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import AdminSettings from './admin_settings.jsx';
-import {FormattedMessage} from 'react-intl';
-import SettingsGroup from './settings_group.jsx';
-import TextSetting from './text_setting.jsx';
-
-export default class LegalAndSupportSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
- }
-
- getConfigFromState(config) {
- config.SupportSettings.TermsOfServiceLink = this.state.termsOfServiceLink;
- config.SupportSettings.PrivacyPolicyLink = this.state.privacyPolicyLink;
- config.SupportSettings.AboutLink = this.state.aboutLink;
- config.SupportSettings.HelpLink = this.state.helpLink;
- config.SupportSettings.ReportAProblemLink = this.state.reportAProblemLink;
- config.SupportSettings.SupportEmail = this.state.supportEmail;
-
- return config;
- }
-
- getStateFromConfig(config) {
- return {
- termsOfServiceLink: config.SupportSettings.TermsOfServiceLink,
- privacyPolicyLink: config.SupportSettings.PrivacyPolicyLink,
- aboutLink: config.SupportSettings.AboutLink,
- helpLink: config.SupportSettings.HelpLink,
- reportAProblemLink: config.SupportSettings.ReportAProblemLink,
- supportEmail: config.SupportSettings.SupportEmail
- };
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.customization.support'
- defaultMessage='Legal and Support'
- />
- );
- }
-
- renderSettings() {
- return (
- <SettingsGroup>
- <TextSetting
- id='termsOfServiceLink'
- label={
- <FormattedMessage
- id='admin.support.termsTitle'
- defaultMessage='Terms of Service link:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.support.termsDesc'
- defaultMessage='Link to the terms under which users may use your online service. By default, this includes the "Mattermost Conditions of Use (End Users)" explaining the terms under which Mattermost software is provided to end users. If you change the default link to add your own terms for using the service you provide, your new terms must include a link to the default terms so end users are aware of the Mattermost Conditions of Use (End User) for Mattermost software.'
- />
- }
- value={this.state.termsOfServiceLink}
- onChange={this.handleChange}
- />
- <TextSetting
- id='privacyPolicyLink'
- label={
- <FormattedMessage
- id='admin.support.privacyTitle'
- defaultMessage='Privacy Policy link:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.support.privacyDesc'
- defaultMessage='The URL for the Privacy link on the login and sign-up pages. If this field is empty, the Privacy link is hidden from users.'
- />
- }
- value={this.state.privacyPolicyLink}
- onChange={this.handleChange}
- />
- <TextSetting
- id='aboutLink'
- label={
- <FormattedMessage
- id='admin.support.aboutTitle'
- defaultMessage='About link:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.support.aboutDesc'
- defaultMessage='The URL for the About link on the Mattermost login and sign-up pages. If this field is empty, the About link is hidden from users.'
- />
- }
- value={this.state.aboutLink}
- onChange={this.handleChange}
- />
- <TextSetting
- id='helpLink'
- label={
- <FormattedMessage
- id='admin.support.helpTitle'
- defaultMessage='Help link:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.support.helpDesc'
- defaultMessage='The URL for the Help link on the Mattermost login page, sign-up pages, and Main Menu. If this field is empty, the Help link is hidden from users.'
- />
- }
- value={this.state.helpLink}
- onChange={this.handleChange}
- />
- <TextSetting
- id='reportAProblemLink'
- label={
- <FormattedMessage
- id='admin.support.problemTitle'
- defaultMessage='Report a Problem link:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.support.problemDesc'
- defaultMessage='The URL for the Report a Problem link in the Main Menu. If this field is empty, the link is removed from the Main Menu.'
- />
- }
- value={this.state.reportAProblemLink}
- onChange={this.handleChange}
- />
- <TextSetting
- id='supportEmail'
- label={
- <FormattedMessage
- id='admin.support.emailTitle'
- defaultMessage='Support Email:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.support.emailHelp'
- defaultMessage='Email address displayed on email notifications and during tutorial for end users to ask support questions.'
- />
- }
- value={this.state.supportEmail}
- onChange={this.handleChange}
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/license_settings.jsx b/webapp/components/admin_console/license_settings.jsx
deleted file mode 100644
index f04a0c351..000000000
--- a/webapp/components/admin_console/license_settings.jsx
+++ /dev/null
@@ -1,286 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import ReactDOM from 'react-dom';
-import * as Utils from 'utils/utils.jsx';
-
-import ErrorStore from 'stores/error_store.jsx';
-import {uploadLicenseFile, removeLicenseFile} from 'actions/admin_actions.jsx';
-
-import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-
-const holders = defineMessages({
- removing: {
- id: 'admin.license.removing',
- defaultMessage: 'Removing License...'
- },
- uploading: {
- id: 'admin.license.uploading',
- defaultMessage: 'Uploading License...'
- }
-});
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-class LicenseSettings extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleChange = this.handleChange.bind(this);
- this.handleSubmit = this.handleSubmit.bind(this);
- this.handleRemove = this.handleRemove.bind(this);
-
- this.state = {
- fileSelected: false,
- fileName: null,
- serverError: null
- };
- }
-
- handleChange() {
- const element = $(ReactDOM.findDOMNode(this.refs.fileInput));
- if (element.prop('files').length > 0) {
- this.setState({fileSelected: true, fileName: element.prop('files')[0].name});
- }
- }
-
- handleSubmit(e) {
- e.preventDefault();
-
- const element = $(ReactDOM.findDOMNode(this.refs.fileInput));
- if (element.prop('files').length === 0) {
- return;
- }
- const file = element.prop('files')[0];
-
- $('#upload-button').button('loading');
-
- uploadLicenseFile(
- file,
- () => {
- Utils.clearFileInput(element[0]);
- $('#upload-button').button('reset');
- this.setState({fileSelected: false, fileName: null, serverError: null});
- window.location.reload(true);
- },
- (error) => {
- Utils.clearFileInput(element[0]);
- $('#upload-button').button('reset');
- this.setState({fileSelected: false, fileName: null, serverError: error.message});
- }
- );
- }
-
- handleRemove(e) {
- e.preventDefault();
-
- $('#remove-button').button('loading');
-
- removeLicenseFile(
- () => {
- $('#remove-button').button('reset');
- this.setState({fileSelected: false, fileName: null, serverError: null});
- ErrorStore.clearLastError(true);
- window.location.reload(true);
- },
- (error) => {
- $('#remove-button').button('reset');
- this.setState({fileSelected: false, fileName: null, serverError: error.message});
- }
- );
- }
-
- render() {
- var serverError = '';
- if (this.state.serverError) {
- serverError = <div className='col-sm-12'><div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div></div>;
- }
-
- var btnClass = 'btn';
- if (this.state.fileSelected) {
- btnClass = 'btn btn-primary';
- }
-
- let edition;
- let licenseType;
- let licenseKey;
-
- const issued = Utils.displayDate(parseInt(global.window.mm_license.IssuedAt, 10)) + ' ' + Utils.displayTime(parseInt(global.window.mm_license.IssuedAt, 10), true);
- const startsAt = Utils.displayDate(parseInt(global.window.mm_license.StartsAt, 10));
- const expiresAt = Utils.displayDate(parseInt(global.window.mm_license.ExpiresAt, 10));
-
- if (global.window.mm_license.IsLicensed === 'true') {
- // Note: DO NOT LOCALISE THESE STRINGS. Legally we can not since the license is in English.
- edition = 'Mattermost Enterprise Edition. Enterprise features on this server have been unlocked with a license key and a valid subscription.';
- licenseType = (
- <div>
- <p>
- {'This software is offered under a commercial license.\n\nSee ENTERPRISE-EDITION-LICENSE.txt in your root install directory for details. See NOTICE.txt for information about open source software used in this system.\n\nYour subscription details are as follows:'}
- </p>
- {`Name: ${global.window.mm_license.Name}`}<br/>
- {`Company or organization name: ${global.window.mm_license.Company}`}<br/>
- {`Number of users: ${global.window.mm_license.Users}`}<br/>
- {`License issued: ${issued}`}<br/>
- {`Start date of license: ${startsAt}`}<br/>
- {`Expiry date of license: ${expiresAt}`}<br/>
- <br/>
- {'See also '}<a href='https://about.mattermost.com/enterprise-edition-terms/'>{'Enterprise Edition Terms of Service'}</a>{' and '}<a href='https://about.mattermost.com/privacy/'>{'Privacy Policy.'}</a>
- </div>
- );
-
- licenseKey = (
- <div className='col-sm-8'>
- <button
- className='btn btn-danger'
- onClick={this.handleRemove}
- id='remove-button'
- data-loading-text={'<span class=\'fa fa-refresh icon--rotate\'></span> ' + this.props.intl.formatMessage(holders.removing)}
- >
- <FormattedMessage
- id='admin.license.keyRemove'
- defaultMessage='Remove Enterprise License and Downgrade Server'
- />
- </button>
- <br/>
- <br/>
- <p className='help-text'>
- {'If you migrate servers you may need to remove your license key to install it elsewhere. You can remove the key here, which will revert functionality to that of Team Edition.'}
- </p>
- </div>
- );
- } else {
- // Note: DO NOT LOCALISE THESE STRINGS. Legally we can not since the license is in English.
- edition = (
- <p>
- {'Mattermost Enterprise Edition. Unlock enterprise features in this software through the purchase of a subscription from '}
- <a
- target='_blank'
- rel='noopener noreferrer'
- href='https://mattermost.com/'
- >
- {'https://mattermost.com/'}
- </a>
- </p>
- );
-
- licenseType = 'This software is offered under a commercial license.\n\nSee ENTERPRISE-EDITION-LICENSE.txt in your root install directory for details. See NOTICE.txt for information about open source software used in this system.';
-
- let fileName;
- if (this.state.fileName) {
- fileName = this.state.fileName;
- } else {
- fileName = (
- <FormattedMessage
- id='admin.license.noFile'
- defaultMessage='No file uploaded'
- />
- );
- }
-
- licenseKey = (
- <div className='col-sm-8'>
- <div className='file__upload'>
- <button className='btn btn-primary'>
- <FormattedMessage
- id='admin.license.choose'
- defaultMessage='Choose File'
- />
- </button>
- <input
- ref='fileInput'
- type='file'
- accept='.mattermost-license'
- onChange={this.handleChange}
- />
- </div>
- <button
- className={btnClass}
- disabled={!this.state.fileSelected}
- onClick={this.handleSubmit}
- id='upload-button'
- data-loading-text={'<span class=\'fa fa-refresh icon--rotate\'></span> ' + this.props.intl.formatMessage(holders.uploading)}
- >
- <FormattedMessage
- id='admin.license.upload'
- defaultMessage='Upload'
- />
- </button>
- <div className='help-text no-margin'>
- {fileName}
- </div>
- <br/>
- {serverError}
- <p className='help-text no-margin'>
- <FormattedHTMLMessage
- id='admin.license.uploadDesc'
- defaultMessage='Upload a license key for Mattermost Enterprise Edition to upgrade this server. <a href="http://mattermost.com" target="_blank">Visit us online</a> to learn more about the benefits of Enterprise Edition or to purchase a key.'
- />
- </p>
- </div>
- );
- }
-
- return (
- <div className='wrapper--fixed'>
- <h3 className='admin-console-header'>
- <FormattedMessage
- id='admin.license.title'
- defaultMessage='Edition and License'
- />
- </h3>
- <form
- className='form-horizontal'
- role='form'
- >
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- >
- <FormattedMessage
- id='admin.license.edition'
- defaultMessage='Edition: '
- />
- </label>
- <div className='col-sm-8'>
- {edition}
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- >
- <FormattedMessage
- id='admin.license.type'
- defaultMessage='License: '
- />
- </label>
- <div className='col-sm-8'>
- {licenseType}
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- >
- <FormattedMessage
- id='admin.license.key'
- defaultMessage='License Key: '
- />
- </label>
- {licenseKey}
- </div>
- </form>
- </div>
- );
- }
-}
-
-LicenseSettings.propTypes = {
- intl: intlShape.isRequired,
- config: PropTypes.object
-};
-
-export default injectIntl(LicenseSettings);
diff --git a/webapp/components/admin_console/link_previews_settings.jsx b/webapp/components/admin_console/link_previews_settings.jsx
deleted file mode 100644
index b120d75d2..000000000
--- a/webapp/components/admin_console/link_previews_settings.jsx
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import AdminSettings from './admin_settings.jsx';
-import BooleanSetting from './boolean_setting.jsx';
-import {FormattedMessage} from 'react-intl';
-import SettingsGroup from './settings_group.jsx';
-
-export default class LinkPreviewsSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
- }
-
- getConfigFromState(config) {
- config.ServiceSettings.EnableLinkPreviews = this.state.enableLinkPreviews;
-
- return config;
- }
-
- getStateFromConfig(config) {
- return {
- enableLinkPreviews: config.ServiceSettings.EnableLinkPreviews
- };
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.customization.linkPreviews'
- defaultMessage='Link Previews'
- />
- );
- }
-
- renderSettings() {
- return (
- <SettingsGroup>
- <BooleanSetting
- id='enableLinkPreviews'
- label={
- <FormattedMessage
- id='admin.customization.enableLinkPreviewsTitle'
- defaultMessage='Enable Link Previews:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.customization.enableLinkPreviewsDesc'
- defaultMessage='Enable users to display a preview of website content below the message, if available. When true, website previews can be enabled from Account Settings > Advanced > Preview pre-release features.'
- />
- }
- value={this.state.enableLinkPreviews}
- onChange={this.handleChange}
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/localization_settings.jsx b/webapp/components/admin_console/localization_settings.jsx
deleted file mode 100644
index 78b63b163..000000000
--- a/webapp/components/admin_console/localization_settings.jsx
+++ /dev/null
@@ -1,134 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import * as I18n from 'i18n/i18n.jsx';
-
-import AdminSettings from './admin_settings.jsx';
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-import SettingsGroup from './settings_group.jsx';
-import DropdownSetting from './dropdown_setting.jsx';
-import MultiSelectSetting from './multiselect_settings.jsx';
-
-export default class LocalizationSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
- this.canSave = this.canSave.bind(this);
-
- const locales = I18n.getAllLanguages();
-
- this.state = Object.assign(this.state, {
- hasErrors: false,
- languages: Object.keys(locales).map((l) => {
- return {value: locales[l].value, text: locales[l].name, order: locales[l].order};
- }).sort((a, b) => a.order - b.order)
- });
- }
-
- canSave() {
- return this.state.availableLocales.join(',').indexOf(this.state.defaultClientLocale) !== -1 || this.state.availableLocales.length === 0;
- }
-
- getConfigFromState(config) {
- config.LocalizationSettings.DefaultServerLocale = this.state.defaultServerLocale;
- config.LocalizationSettings.DefaultClientLocale = this.state.defaultClientLocale;
- config.LocalizationSettings.AvailableLocales = this.state.availableLocales.join(',');
-
- return config;
- }
-
- getStateFromConfig(config) {
- return {
- defaultServerLocale: config.LocalizationSettings.DefaultServerLocale,
- defaultClientLocale: config.LocalizationSettings.DefaultClientLocale,
- availableLocales: config.LocalizationSettings.AvailableLocales ? config.LocalizationSettings.AvailableLocales.split(',') : []
- };
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.general.localization'
- defaultMessage='Localization'
- />
- );
- }
-
- renderSettings() {
- return (
- <SettingsGroup>
- <DropdownSetting
- id='defaultServerLocale'
- values={this.state.languages}
- label={
- <FormattedMessage
- id='admin.general.localization.serverLocaleTitle'
- defaultMessage='Default Server Language:'
- />
- }
- value={this.state.defaultServerLocale}
- onChange={this.handleChange}
- helpText={
- <FormattedMessage
- id='admin.general.localization.serverLocaleDescription'
- defaultMessage='Default language for system messages and logs. Changing this will require a server restart before taking effect.'
- />
- }
- />
- <DropdownSetting
- id='defaultClientLocale'
- values={this.state.languages}
- label={
- <FormattedMessage
- id='admin.general.localization.clientLocaleTitle'
- defaultMessage='Default Client Language:'
- />
- }
- value={this.state.defaultClientLocale}
- onChange={this.handleChange}
- helpText={
- <FormattedMessage
- id='admin.general.localization.clientLocaleDescription'
- defaultMessage="Default language for newly created users and pages where the user hasn't logged in."
- />
- }
- />
- <MultiSelectSetting
- id='availableLocales'
- values={this.state.languages}
- label={
- <FormattedMessage
- id='admin.general.localization.availableLocalesTitle'
- defaultMessage='Available Languages:'
- />
- }
- selected={this.state.availableLocales}
- onChange={this.handleChange}
- helpText={
- <FormattedHTMLMessage
- id='admin.general.localization.availableLocalesDescription'
- defaultMessage='Set which languages are available for users in Account Settings (leave this field blank to have all supported languages available). If you’re manually adding new languages, the <strong>Default Client Language</strong> must be added before saving this setting.<br /><br />Would like to help with translations? Join the <a href="http://translate.mattermost.com/" target="_blank">Mattermost Translation Server</a> to contribute.'
- />
- }
- noResultText={
- <FormattedMessage
- id='admin.general.localization.availableLocalesNoResults'
- defaultMessage='No results found'
- />
- }
- notPresent={
- <FormattedMessage
- id='admin.general.localization.availableLocalesNotPresent'
- defaultMessage='The default client language must be included in the available list'
- />
- }
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/log_settings.jsx b/webapp/components/admin_console/log_settings.jsx
deleted file mode 100644
index 8e1e4891e..000000000
--- a/webapp/components/admin_console/log_settings.jsx
+++ /dev/null
@@ -1,282 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import * as Utils from 'utils/utils.jsx';
-
-import AdminSettings from './admin_settings.jsx';
-import BooleanSetting from './boolean_setting.jsx';
-import DropdownSetting from './dropdown_setting.jsx';
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-import SettingsGroup from './settings_group.jsx';
-import TextSetting from './text_setting.jsx';
-
-export default class LogSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
- }
-
- getConfigFromState(config) {
- config.LogSettings.EnableConsole = this.state.enableConsole;
- config.LogSettings.ConsoleLevel = this.state.consoleLevel;
- config.LogSettings.EnableFile = this.state.enableFile;
- config.LogSettings.FileLevel = this.state.fileLevel;
- config.LogSettings.FileLocation = this.state.fileLocation;
- config.LogSettings.FileFormat = this.state.fileFormat;
- config.LogSettings.EnableWebhookDebugging = this.state.enableWebhookDebugging;
- config.LogSettings.EnableDiagnostics = this.state.enableDiagnostics;
-
- return config;
- }
-
- getStateFromConfig(config) {
- return {
- enableConsole: config.LogSettings.EnableConsole,
- consoleLevel: config.LogSettings.ConsoleLevel,
- enableFile: config.LogSettings.EnableFile,
- fileLevel: config.LogSettings.FileLevel,
- fileLocation: config.LogSettings.FileLocation,
- fileFormat: config.LogSettings.FileFormat,
- enableWebhookDebugging: config.LogSettings.EnableWebhookDebugging,
- enableDiagnostics: config.LogSettings.EnableDiagnostics
- };
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.general.log'
- defaultMessage='Logging'
- />
- );
- }
-
- renderSettings() {
- const logLevels = [
- {value: 'DEBUG', text: 'DEBUG'},
- {value: 'INFO', text: 'INFO'},
- {value: 'ERROR', text: 'ERROR'}
- ];
-
- return (
- <SettingsGroup>
- <BooleanSetting
- id='enableConsole'
- label={
- <FormattedMessage
- id='admin.log.consoleTitle'
- defaultMessage='Output logs to console: '
- />
- }
- helpText={
- <FormattedMessage
- id='admin.log.consoleDescription'
- defaultMessage='Typically set to false in production. Developers may set this field to true to output log messages to console based on the console level option. If true, server writes messages to the standard output stream (stdout).'
- />
- }
- value={this.state.enableConsole}
- onChange={this.handleChange}
- />
- <DropdownSetting
- id='consoleLevel'
- values={logLevels}
- label={
- <FormattedMessage
- id='admin.log.levelTitle'
- defaultMessage='Console Log Level:'
- />
- }
- value={this.state.consoleLevel}
- onChange={this.handleChange}
- disabled={!this.state.enableConsole}
- helpText={
- <FormattedMessage
- id='admin.log.levelDescription'
- defaultMessage='This setting determines the level of detail at which log events are written to the console. ERROR: Outputs only error messages. INFO: Outputs error messages and information around startup and initialization. DEBUG: Prints high detail for developers working on debugging issues.'
- />
- }
- />
- <BooleanSetting
- id='enableFile'
- label={
- <FormattedMessage
- id='admin.log.fileTitle'
- defaultMessage='Output logs to file: '
- />
- }
- helpText={
- <FormattedMessage
- id='admin.log.fileDescription'
- defaultMessage='Typically set to true in production. When true, logged events are written to the mattermost.log file in the directory specified in the File Log Directory field. The logs are rotated at 10,000 lines and archived to a file in the same directory, and given a name with a datestamp and serial number. For example, mattermost.2017-03-31.001.'
- />
- }
- value={this.state.enableFile}
- onChange={this.handleChange}
- />
- <DropdownSetting
- id='fileLevel'
- values={logLevels}
- label={
- <FormattedMessage
- id='admin.log.fileLevelTitle'
- defaultMessage='File Log Level:'
- />
- }
- value={this.state.fileLevel}
- onChange={this.handleChange}
- disabled={!this.state.enableFile}
- helpText={
- <FormattedMessage
- id='admin.log.fileLevelDescription'
- defaultMessage='This setting determines the level of detail at which log events are written to the log file. ERROR: Outputs only error messages. INFO: Outputs error messages and information around startup and initialization. DEBUG: Prints high detail for developers working on debugging issues.'
- />
- }
- />
- <TextSetting
- id='fileLocation'
- label={
- <FormattedMessage
- id='admin.log.locationTitle'
- defaultMessage='File Log Directory:'
- />
- }
- placeholder={Utils.localizeMessage('admin.log.locationPlaceholder', 'Enter your file location')}
- helpText={
- <FormattedMessage
- id='admin.log.locationDescription'
- defaultMessage='The location of the log files. If blank, they are stored in the ./logs directory. The path that you set must exist and Mattermost must have write permissions in it.'
- />
- }
- value={this.state.fileLocation}
- onChange={this.handleChange}
- disabled={!this.state.enableFile}
- />
- <TextSetting
- id='fileFormat'
- label={
- <FormattedMessage
- id='admin.log.formatTitle'
- defaultMessage='File Log Format:'
- />
- }
- placeholder={Utils.localizeMessage('admin.log.formatPlaceholder', 'Enter your file format')}
- helpText={this.renderFileFormatHelpText()}
- value={this.state.fileFormat}
- onChange={this.handleChange}
- disabled={!this.state.enableFile}
- />
- <BooleanSetting
- id='enableWebhookDebugging'
- label={
- <FormattedMessage
- id='admin.log.enableWebhookDebugging'
- defaultMessage='Enable Webhook Debugging:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.log.enableWebhookDebuggingDescription'
- defaultMessage='You can set this to false to disable the debug logging of all incoming webhook request bodies.'
- />
- }
- value={this.state.enableWebhookDebugging}
- onChange={this.handleChange}
- />
- <BooleanSetting
- id='enableDiagnostics'
- label={
- <FormattedMessage
- id='admin.log.enableDiagnostics'
- defaultMessage='Enable Diagnostics and Error Reporting:'
- />
- }
- helpText={
- <FormattedHTMLMessage
- id='admin.log.enableDiagnosticsDescription'
- defaultMessage='Enable this feature to improve the quality and performance of Mattermost by sending error reporting and diagnostic information to Mattermost, Inc. Read our <a href="https://about.mattermost.com/default-privacy-policy/" target="_blank">privacy policy</a> to learn more.'
- />
- }
- value={this.state.enableDiagnostics}
- onChange={this.handleChange}
- />
- </SettingsGroup>
- );
- }
-
- renderFileFormatHelpText() {
- return (
- <div>
- <FormattedMessage
- id='admin.log.formatDescription'
- defaultMessage='Format of log message output. If blank will be set to "[%D %T] [%L] %M", where:'
- />
- <table
- className='table table-bordered'
- cellPadding='5'
- >
- <tbody>
- <tr>
- <td>{'%T'}</td>
- <td>
- <FormattedMessage
- id='admin.log.formatTime'
- defaultMessage='Time (15:04:05 MST)'
- />
- </td>
- </tr>
- <tr>
- <td>{'%D'}</td>
- <td>
- <FormattedMessage
- id='admin.log.formatDateLong'
- defaultMessage='Date (2006/01/02)'
- />
- </td>
- </tr>
- <tr>
- <td>{'%d'}</td>
- <td>
- <FormattedMessage
- id='admin.log.formatDateShort'
- defaultMessage='Date (01/02/06)'
- />
- </td>
- </tr>
- <tr>
- <td>{'%L'}</td>
- <td>
- <FormattedMessage
- id='admin.log.formatLevel'
- defaultMessage='Level (DEBG, INFO, EROR)'
- />
- </td>
- </tr>
- <tr>
- <td>{'%S'}</td>
- <td>
- <FormattedMessage
- id='admin.log.formatSource'
- defaultMessage='Source'
- />
- </td>
- </tr>
- <tr>
- <td>{'%M'}</td>
- <td>
- <FormattedMessage
- id='admin.log.formatMessage'
- defaultMessage='Message'
- />
- </td>
- </tr>
- </tbody>
- </table>
- </div>
- );
- }
-}
diff --git a/webapp/components/admin_console/manage_roles_modal/index.js b/webapp/components/admin_console/manage_roles_modal/index.js
deleted file mode 100644
index 1ca243621..000000000
--- a/webapp/components/admin_console/manage_roles_modal/index.js
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {updateUserRoles} from 'mattermost-redux/actions/users';
-
-import ManageRolesModal from './manage_roles_modal.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps,
- userAccessTokensEnabled: state.entities.admin.config.ServiceSettings.EnableUserAccessTokens
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- updateUserRoles
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(ManageRolesModal);
diff --git a/webapp/components/admin_console/manage_roles_modal/manage_roles_modal.jsx b/webapp/components/admin_console/manage_roles_modal/manage_roles_modal.jsx
deleted file mode 100644
index 2358f0241..000000000
--- a/webapp/components/admin_console/manage_roles_modal/manage_roles_modal.jsx
+++ /dev/null
@@ -1,349 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import * as UserUtils from 'mattermost-redux/utils/user_utils';
-import {Client4} from 'mattermost-redux/client';
-import {General} from 'mattermost-redux/constants';
-
-import {trackEvent} from 'actions/diagnostics_actions.jsx';
-
-import React from 'react';
-import {Modal} from 'react-bootstrap';
-import PropTypes from 'prop-types';
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-
-function getStateFromProps(props) {
- const roles = props.user && props.user.roles ? props.user.roles : '';
-
- return {
- error: null,
- hasPostAllRole: UserUtils.hasPostAllRole(roles),
- hasPostAllPublicRole: UserUtils.hasPostAllPublicRole(roles),
- hasUserAccessTokenRole: UserUtils.hasUserAccessTokenRole(roles),
- isSystemAdmin: UserUtils.isSystemAdmin(roles)
- };
-}
-
-export default class ManageRolesModal extends React.PureComponent {
- static propTypes = {
-
- /**
- * Set to render the modal
- */
- show: PropTypes.bool.isRequired,
-
- /**
- * The user the roles are being managed for
- */
- user: PropTypes.object,
-
- /**
- * Set if user access tokens are enabled
- */
- userAccessTokensEnabled: PropTypes.bool.isRequired,
-
- /**
- * Function called when modal is dismissed
- */
- onModalDismissed: PropTypes.func.isRequired,
-
- actions: PropTypes.shape({
-
- /**
- * Function to update a user's roles
- */
- updateUserRoles: PropTypes.func.isRequired
- }).isRequired
- };
-
- constructor(props) {
- super(props);
- this.state = getStateFromProps(props);
- }
-
- componentWillReceiveProps(nextProps) {
- const user = this.props.user || {};
- const nextUser = nextProps.user || {};
- if (user.id !== nextUser.id) {
- this.setState(getStateFromProps(nextProps));
- }
- }
-
- handleError = (error) => {
- this.setState({
- error
- });
- }
-
- handleSystemAdminChange = (e) => {
- if (e.target.name === 'systemadmin') {
- this.setState({isSystemAdmin: true});
- } else if (e.target.name === 'systemmember') {
- this.setState({isSystemAdmin: false});
- }
- };
-
- handleUserAccessTokenChange = (e) => {
- this.setState({
- hasUserAccessTokenRole: e.target.checked
- });
- };
-
- handlePostAllChange = (e) => {
- this.setState({
- hasPostAllRole: e.target.checked
- });
- };
-
- handlePostAllPublicChange = (e) => {
- this.setState({
- hasPostAllPublicRole: e.target.checked
- });
- };
-
- trackRoleChanges = (roles, oldRoles) => {
- if (UserUtils.hasUserAccessTokenRole(roles) && !UserUtils.hasUserAccessTokenRole(oldRoles)) {
- trackEvent('actions', 'add_roles', {role: General.SYSTEM_USER_ACCESS_TOKEN_ROLE});
- } else if (!UserUtils.hasUserAccessTokenRole(roles) && UserUtils.hasUserAccessTokenRole(oldRoles)) {
- trackEvent('actions', 'remove_roles', {role: General.SYSTEM_USER_ACCESS_TOKEN_ROLE});
- }
-
- if (UserUtils.hasPostAllRole(roles) && !UserUtils.hasPostAllRole(oldRoles)) {
- trackEvent('actions', 'add_roles', {role: General.SYSTEM_POST_ALL_ROLE});
- } else if (!UserUtils.hasPostAllRole(roles) && UserUtils.hasPostAllRole(oldRoles)) {
- trackEvent('actions', 'remove_roles', {role: General.SYSTEM_POST_ALL_ROLE});
- }
-
- if (UserUtils.hasPostAllPublicRole(roles) && !UserUtils.hasPostAllPublicRole(oldRoles)) {
- trackEvent('actions', 'add_roles', {role: General.SYSTEM_POST_ALL_PUBLIC_ROLE});
- } else if (!UserUtils.hasPostAllPublicRole(roles) && UserUtils.hasPostAllPublicRole(oldRoles)) {
- trackEvent('actions', 'remove_roles', {role: General.SYSTEM_POST_ALL_PUBLIC_ROLE});
- }
- }
-
- handleSave = async () => {
- this.setState({error: null});
-
- let roles = General.SYSTEM_USER_ROLE;
-
- if (this.state.isSystemAdmin) {
- roles += ' ' + General.SYSTEM_ADMIN_ROLE;
- } else if (this.state.hasUserAccessTokenRole) {
- roles += ' ' + General.SYSTEM_USER_ACCESS_TOKEN_ROLE;
- if (this.state.hasPostAllRole) {
- roles += ' ' + General.SYSTEM_POST_ALL_ROLE;
- } else if (this.state.hasPostAllPublicRole) {
- roles += ' ' + General.SYSTEM_POST_ALL_PUBLIC_ROLE;
- }
- }
-
- const data = await this.props.actions.updateUserRoles(this.props.user.id, roles);
-
- this.trackRoleChanges(roles, this.props.user.roles);
-
- if (data) {
- this.props.onModalDismissed();
- } else {
- this.handleError(
- <FormattedMessage
- id='admin.manage_roles.saveError'
- defaultMessage='Unable to save roles.'
- />
- );
- }
- }
-
- renderContents = () => {
- const {user} = this.props;
-
- if (user == null) {
- return <div/>;
- }
-
- let name = UserUtils.getFullName(user);
- if (name) {
- name += ` (@${user.username})`;
- } else {
- name = `@${user.username}`;
- }
-
- let additionalRoles;
- if (this.state.hasUserAccessTokenRole || this.state.isSystemAdmin) {
- additionalRoles = (
- <div>
- <p>
- <FormattedHTMLMessage
- id='admin.manage_roles.additionalRoles'
- defaultMessage='Select additional permissions for the account. <a href="https://about.mattermost.com/default-permissions" target="_blank">Read more about roles and permissions</a>.'
- />
- </p>
- <div className='checkbox'>
- <label>
- <input
- type='checkbox'
- ref='postall'
- checked={this.state.hasPostAllRole || this.state.isSystemAdmin}
- disabled={this.state.isSystemAdmin}
- onChange={this.handlePostAllChange}
- />
- <strong>
- <FormattedMessage
- id='admin.manage_roles.postAllRoleTitle'
- defaultMessage='post:all'
- />
- </strong>
- <FormattedMessage
- id='admin.manage_roles.postAllRole'
- defaultMessage='Access to post to all Mattermost channels including direct messages.'
- />
- </label>
- </div>
- <div className='checkbox'>
- <label>
- <input
- type='checkbox'
- ref='postallpublic'
- checked={this.state.hasPostAllPublicRole || this.state.hasPostAllRole || this.state.isSystemAdmin}
- disabled={this.state.hasPostAllRole || this.state.isSystemAdmin}
- onChange={this.handlePostAllPublicChange}
- />
- <strong>
- <FormattedMessage
- id='admin.manage_roles.postAllPublicRoleTitle'
- defaultMessage='post:channels'
- />
- </strong>
- <FormattedMessage
- id='admin.manage_roles.postAllPublicRole'
- defaultMessage='Access to post to all Mattermost public channels.'
- />
- </label>
- </div>
- </div>
- );
- }
-
- let userAccessTokenContent;
- if (this.props.userAccessTokensEnabled) {
- userAccessTokenContent = (
- <div>
- <div className='checkbox'>
- <label>
- <input
- type='checkbox'
- ref='postall'
- checked={this.state.hasUserAccessTokenRole || this.state.isSystemAdmin}
- disabled={this.state.isSystemAdmin}
- onChange={this.handleUserAccessTokenChange}
- />
- <FormattedHTMLMessage
- id='admin.manage_roles.allowUserAccessTokens'
- defaultMessage='Allow this account to generate <a href="https://about.mattermost.com/default-user-access-tokens" target="_blank">user access tokens</a>.'
- />
- </label>
- </div>
- <div className='member-row--padded'>
- {additionalRoles}
- </div>
- </div>
- );
- }
-
- return (
- <div>
- <div className='manage-teams__user'>
- <img
- className='manage-teams__profile-picture'
- src={Client4.getProfilePictureUrl(user.id, user.last_picture_update)}
- />
- <div className='manage-teams__info'>
- <div className='manage-teams__name'>
- {name}
- </div>
- <div className='manage-teams__email'>
- {user.email}
- </div>
- </div>
- </div>
- <div>
- <div className='manage-row--inner'>
- <div className='radio-inline'>
- <label>
- <input
- name='systemadmin'
- type='radio'
- checked={this.state.isSystemAdmin}
- onChange={this.handleSystemAdminChange}
- />
- <FormattedMessage
- id='admin.manage_roles.systemAdmin'
- defaultMessage='System Admin'
- />
- </label>
- </div>
- <div className='radio-inline'>
- <label>
- <input
- name='systemmember'
- type='radio'
- checked={!this.state.isSystemAdmin}
- onChange={this.handleSystemAdminChange}
- />
- <FormattedMessage
- id='admin.manage_roles.systemMember'
- defaultMessage='Member'
- />
- </label>
- </div>
- </div>
- {userAccessTokenContent}
- </div>
- </div>
- );
- }
-
- render() {
- return (
- <Modal
- show={this.props.show}
- onHide={this.props.onModalDismissed}
- dialogClassName='manage-teams'
- >
- <Modal.Header closeButton={true}>
- <Modal.Title>
- <FormattedMessage
- id='admin.manage_roles.manageRolesTitle'
- defaultMessage='Manage Roles'
- />
- </Modal.Title>
- </Modal.Header>
- <Modal.Body>
- {this.renderContents()}
- {this.state.error}
- </Modal.Body>
- <Modal.Footer>
- <button
- type='button'
- className='btn btn-link'
- onClick={this.props.onModalDismissed}
- >
- <FormattedMessage
- id='admin.manage_roles.cancel'
- defaultMessage='Cancel'
- />
- </button>
- <button
- type='button'
- className='btn btn-primary'
- onClick={this.handleSave}
- >
- <FormattedMessage
- id='admin.manage_roles.save'
- defaultMessage='Save'
- />
- </button>
- </Modal.Footer>
- </Modal>
- );
- }
-}
diff --git a/webapp/components/admin_console/manage_teams_modal/manage_teams_dropdown.jsx b/webapp/components/admin_console/manage_teams_modal/manage_teams_dropdown.jsx
deleted file mode 100644
index 4ee3c11cd..000000000
--- a/webapp/components/admin_console/manage_teams_modal/manage_teams_dropdown.jsx
+++ /dev/null
@@ -1,148 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import {Dropdown, MenuItem} from 'react-bootstrap';
-import {FormattedMessage} from 'react-intl';
-
-import {updateTeamMemberRoles, removeUserFromTeam} from 'actions/team_actions.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-
-export default class ManageTeamsDropdown extends React.Component {
- static propTypes = {
- user: PropTypes.object.isRequired,
- teamMember: PropTypes.object.isRequired,
- onError: PropTypes.func.isRequired,
- onMemberChange: PropTypes.func.isRequired,
- onMemberRemove: PropTypes.func.isRequired
- };
-
- constructor(props) {
- super(props);
-
- this.toggleDropdown = this.toggleDropdown.bind(this);
-
- this.makeTeamAdmin = this.makeTeamAdmin.bind(this);
- this.makeMember = this.makeMember.bind(this);
- this.removeFromTeam = this.removeFromTeam.bind(this);
-
- this.handleMemberChange = this.handleMemberChange.bind(this);
- this.handleMemberRemove = this.handleMemberRemove.bind(this);
-
- this.state = {
- show: false
- };
- }
-
- toggleDropdown() {
- this.setState((prevState) => {
- return {show: !prevState.show};
- });
- }
-
- makeTeamAdmin() {
- updateTeamMemberRoles(
- this.props.teamMember.team_id,
- this.props.user.id,
- 'team_user team_admin',
- this.handleMemberChange,
- this.props.onError
- );
- }
-
- makeMember() {
- updateTeamMemberRoles(
- this.props.teamMember.team_id,
- this.props.user.id,
- 'team_user',
- this.handleMemberChange,
- this.props.onError
- );
- }
-
- removeFromTeam() {
- removeUserFromTeam(
- this.props.teamMember.team_id,
- this.props.user.id,
- this.handleMemberRemove,
- this.props.onError
- );
- }
-
- handleMemberChange() {
- this.props.onMemberChange(this.props.teamMember.team_id);
- }
-
- handleMemberRemove() {
- this.props.onMemberRemove(this.props.teamMember.team_id);
- }
-
- render() {
- const isTeamAdmin = Utils.isAdmin(this.props.teamMember.roles);
-
- let title;
- if (isTeamAdmin) {
- title = Utils.localizeMessage('admin.user_item.teamAdmin', 'Team Admin');
- } else {
- title = Utils.localizeMessage('admin.user_item.teamMember', 'Team Member');
- }
-
- let makeTeamAdmin = null;
- if (!isTeamAdmin) {
- makeTeamAdmin = (
- <MenuItem
- id='makeTeamAdmin'
- onSelect={this.makeTeamAdmin}
- >
- <FormattedMessage
- id='admin.user_item.makeTeamAdmin'
- defaultMessage='Make Team Admin'
- />
- </MenuItem>
- );
- }
-
- let makeMember = null;
- if (isTeamAdmin) {
- makeMember = (
- <MenuItem
- id='makeMember'
- onSelect={this.makeMember}
- >
- <FormattedMessage
- id='admin.user_item.makeMember'
- defaultMessage='Make Member'
- />
- </MenuItem>
- );
- }
-
- return (
- <Dropdown
- id={`manage-teams-${this.props.user.id}-${this.props.teamMember.team_id}`}
- open={this.state.show}
- onToggle={this.toggleDropdown}
- >
- <Dropdown.Toggle useAnchor={true}>
- {title}
- </Dropdown.Toggle>
- <Dropdown.Menu>
- {makeTeamAdmin}
- {makeMember}
- <MenuItem
- id='removeFromTeam'
- onSelect={this.removeFromTeam}
- >
- <FormattedMessage
- id='team_members_dropdown.leave_team'
- defaultMessage='Remove from Team'
- />
- </MenuItem>
- </Dropdown.Menu>
- </Dropdown>
- );
- }
-}
diff --git a/webapp/components/admin_console/manage_teams_modal/manage_teams_modal.jsx b/webapp/components/admin_console/manage_teams_modal/manage_teams_modal.jsx
deleted file mode 100644
index 21f9d762d..000000000
--- a/webapp/components/admin_console/manage_teams_modal/manage_teams_modal.jsx
+++ /dev/null
@@ -1,218 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import {Modal} from 'react-bootstrap';
-import {FormattedMessage} from 'react-intl';
-import PropTypes from 'prop-types';
-
-import * as TeamActions from 'actions/team_actions.jsx';
-
-import {Client4} from 'mattermost-redux/client';
-
-import LoadingScreen from 'components/loading_screen.jsx';
-
-import {sortTeamsByDisplayName} from 'utils/team_utils.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import ManageTeamsDropdown from './manage_teams_dropdown.jsx';
-import RemoveFromTeamButton from './remove_from_team_button.jsx';
-
-export default class ManageTeamsModal extends React.Component {
- static propTypes = {
- onModalDismissed: PropTypes.func.isRequired,
- show: PropTypes.bool.isRequired,
- user: PropTypes.object
- };
-
- constructor(props) {
- super(props);
-
- this.state = {
- error: null,
- teams: null,
- teamMembers: null
- };
- }
-
- componentDidMount() {
- if (this.props.user) {
- this.loadTeamsAndTeamMembers();
- }
- }
-
- componentWillReceiveProps(nextProps) {
- const userId = this.props.user ? this.props.user.id : '';
- const nextUserId = nextProps.user ? nextProps.user.id : '';
-
- if (userId !== nextUserId) {
- this.setState({
- teams: null,
- teamMembers: null
- });
-
- if (nextProps.user) {
- this.loadTeamsAndTeamMembers(nextProps.user);
- }
- }
- }
-
- loadTeamsAndTeamMembers = (user = this.props.user) => {
- TeamActions.getTeamsForUser(user.id, (teams) => {
- this.setState({
- teams: teams.sort(sortTeamsByDisplayName)
- });
- });
-
- TeamActions.getTeamMembersForUser(user.id, (teamMembers) => {
- this.setState({
- teamMembers
- });
- });
- }
-
- handleError = (error) => {
- this.setState({
- error
- });
- }
-
- handleMemberChange = () => {
- TeamActions.getTeamMembersForUser(this.props.user.id, (teamMembers) => {
- this.setState({
- teamMembers
- });
- });
- }
-
- handleMemberRemove = (teamId) => {
- this.setState({
- teams: this.state.teams.filter((team) => team.id !== teamId),
- teamMembers: this.state.teamMembers.filter((teamMember) => teamMember.team_id !== teamId)
- });
- }
-
- renderContents = () => {
- const {user} = this.props;
- const {teams, teamMembers} = this.state;
-
- if (!user) {
- return <LoadingScreen/>;
- }
-
- const isSystemAdmin = Utils.isAdmin(user.roles);
-
- let name = Utils.getFullName(user);
- if (name) {
- name += ` (@${user.username})`;
- } else {
- name = `@${user.username}`;
- }
-
- let teamList;
- if (teams && teamMembers) {
- teamList = teams.map((team) => {
- const teamMember = teamMembers.find((member) => member.team_id === team.id);
- if (!teamMember) {
- return null;
- }
-
- let action;
- if (isSystemAdmin) {
- action = (
- <RemoveFromTeamButton
- user={user}
- team={team}
- onError={this.handleError}
- onMemberRemove={this.handleMemberRemove}
- />
- );
- } else {
- action = (
- <ManageTeamsDropdown
- user={user}
- team={team}
- teamMember={teamMember}
- onError={this.handleError}
- onMemberChange={this.handleMemberChange}
- onMemberRemove={this.handleMemberRemove}
- />
- );
- }
-
- return (
- <div
- key={team.id}
- className='manage-teams__team'
- >
- <div className='manage-teams__team-name'>
- {team.display_name}
- </div>
- <div className='manage-teams__team-actions'>
- {action}
- </div>
- </div>
- );
- });
- } else {
- teamList = <LoadingScreen/>;
- }
-
- let systemAdminIndicator = null;
- if (isSystemAdmin) {
- systemAdminIndicator = (
- <div className='manage-teams__system-admin'>
- <FormattedMessage
- id='admin.user_item.sysAdmin'
- defaultMessage='System Admin'
- />
- </div>
- );
- }
-
- return (
- <div>
- <div className='manage-teams__user'>
- <img
- className='manage-teams__profile-picture'
- src={Client4.getProfilePictureUrl(user.id, user.last_picture_update)}
- />
- <div className='manage-teams__info'>
- <div className='manage-teams__name'>
- {name}
- </div>
- <div className='manage-teams__email'>
- {user.email}
- </div>
- </div>
- {systemAdminIndicator}
- </div>
- <div className='manage-teams__teams'>
- {teamList}
- </div>
- </div>
- );
- }
-
- render() {
- return (
- <Modal
- show={this.props.show}
- onHide={this.props.onModalDismissed}
- dialogClassName='manage-teams'
- >
- <Modal.Header closeButton={true}>
- <Modal.Title>
- <FormattedMessage
- id='admin.user_item.manageTeams'
- defaultMessage='Manage Teams'
- />
- </Modal.Title>
- </Modal.Header>
- <Modal.Body>
- {this.renderContents()}
- </Modal.Body>
- </Modal>
- );
- }
-}
diff --git a/webapp/components/admin_console/manage_teams_modal/remove_from_team_button.jsx b/webapp/components/admin_console/manage_teams_modal/remove_from_team_button.jsx
deleted file mode 100644
index 69579d46f..000000000
--- a/webapp/components/admin_console/manage_teams_modal/remove_from_team_button.jsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-
-import {removeUserFromTeam} from 'actions/team_actions.jsx';
-
-export default class RemoveFromTeamButton extends React.PureComponent {
- static propTypes = {
- onError: PropTypes.func.isRequired,
- onMemberRemove: PropTypes.func.isRequired,
- team: PropTypes.object.isRequired,
- user: PropTypes.object.isRequired
- };
-
- constructor(props) {
- super(props);
-
- this.handleClick = this.handleClick.bind(this);
- this.handleMemberRemove = this.handleMemberRemove.bind(this);
- }
-
- handleClick(e) {
- e.preventDefault();
-
- removeUserFromTeam(
- this.props.team.id,
- this.props.user.id,
- this.handleMemberRemove,
- this.props.onError
- );
- }
-
- handleMemberRemove() {
- this.props.onMemberRemove(this.props.team.id);
- }
-
- render() {
- return (
- <button
- className='btn btn-danger'
- onClick={this.handleClick}
- >
- <FormattedMessage
- id='team_members_dropdown.leave_team'
- defaultMessage='Remove from Team'
- />
- </button>
- );
- }
-}
diff --git a/webapp/components/admin_console/manage_tokens_modal/index.js b/webapp/components/admin_console/manage_tokens_modal/index.js
deleted file mode 100644
index 9f7a31141..000000000
--- a/webapp/components/admin_console/manage_tokens_modal/index.js
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getUserAccessTokensForUser} from 'mattermost-redux/actions/users';
-
-import ManageTokensModal from './manage_tokens_modal.jsx';
-
-function mapStateToProps(state, ownProps) {
- const userId = ownProps.user ? ownProps.user.id : '';
-
- return {
- ...ownProps,
- userAccessTokens: state.entities.admin.userAccessTokens[userId]
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getUserAccessTokensForUser
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(ManageTokensModal);
diff --git a/webapp/components/admin_console/manage_tokens_modal/manage_tokens_modal.jsx b/webapp/components/admin_console/manage_tokens_modal/manage_tokens_modal.jsx
deleted file mode 100644
index fdef870e5..000000000
--- a/webapp/components/admin_console/manage_tokens_modal/manage_tokens_modal.jsx
+++ /dev/null
@@ -1,181 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import LoadingScreen from 'components/loading_screen.jsx';
-import RevokeTokenButton from 'components/admin_console/revoke_token_button';
-
-import {Client4} from 'mattermost-redux/client';
-import * as UserUtils from 'mattermost-redux/utils/user_utils';
-
-import React from 'react';
-import {Modal} from 'react-bootstrap';
-import PropTypes from 'prop-types';
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-
-export default class ManageTokensModal extends React.PureComponent {
- static propTypes = {
-
- /**
- * Set to render the modal
- */
- show: PropTypes.bool.isRequired,
-
- /**
- * The user the roles are being managed for
- */
- user: PropTypes.object,
-
- /**
- * The personal access tokens for a user, object with token ids as keys
- */
- userAccessTokens: PropTypes.object,
-
- /**
- * Function called when modal is dismissed
- */
- onModalDismissed: PropTypes.func.isRequired,
-
- actions: PropTypes.shape({
-
- /**
- * Function to get a user's access tokens
- */
- getUserAccessTokensForUser: PropTypes.func.isRequired
- }).isRequired
- };
-
- constructor(props) {
- super(props);
- this.state = {error: null};
- }
-
- componentWillReceiveProps(nextProps) {
- const userId = this.props.user ? this.props.user.id : null;
- const nextUserId = nextProps.user ? nextProps.user.id : null;
- if (nextUserId && nextUserId !== userId) {
- this.props.actions.getUserAccessTokensForUser(nextUserId, 0, 200);
- }
- }
-
- handleError = (error) => {
- this.setState({
- error
- });
- }
-
- renderContents = () => {
- const {user, userAccessTokens} = this.props;
-
- if (!user) {
- return <LoadingScreen/>;
- }
-
- let name = UserUtils.getFullName(user);
- if (name) {
- name += ` (@${user.username})`;
- } else {
- name = `@${user.username}`;
- }
-
- let tokenList;
- if (userAccessTokens) {
- const userAccessTokensList = Object.values(userAccessTokens);
-
- if (userAccessTokensList.length === 0) {
- tokenList = (
- <div className='manage-row__empty'>
- <FormattedMessage
- id='admin.manage_tokens.userAccessTokensNone'
- defaultMessage='No personal access tokens.'
- />
- </div>
- );
- } else {
- tokenList = userAccessTokensList.map((token) => {
- return (
- <div
- key={token.id}
- className='manage-teams__team'
- >
- <div className='manage-teams__team-name'>
- <div className='whitespace--nowrap overflow--ellipsis'>
- <FormattedMessage
- id='admin.manage_tokens.userAccessTokensNameLabel'
- defaultMessage='Token Description: '
- />
- {token.description}
- </div>
- <div className='whitespace--nowrap overflow--ellipsis'>
- <FormattedMessage
- id='admin.manage_tokens.userAccessTokensIdLabel'
- defaultMessage='Token ID: '
- />
- {token.id}
- </div>
- </div>
- <div className='manage-teams__team-actions'>
- <RevokeTokenButton
- tokenId={token.id}
- onError={this.handleError}
- />
- </div>
- </div>
- );
- });
- }
- } else {
- tokenList = <LoadingScreen/>;
- }
-
- return (
- <div>
- <div className='manage-teams__user'>
- <img
- className='manage-teams__profile-picture'
- src={Client4.getProfilePictureUrl(user.id, user.last_picture_update)}
- />
- <div className='manage-teams__info'>
- <div className='manage-teams__name'>
- {name}
- </div>
- <div className='manage-teams__email'>
- {user.email}
- </div>
- </div>
- </div>
- <div className='padding-top x2'>
- <FormattedHTMLMessage
- id='admin.manage_tokens.userAccessTokensDescription'
- defaultMessage='Personal access tokens function similar to session tokens and can be used by integrations to <a href="https://about.mattermost.com/default-api-authentication" target="_blank">interact with this Mattermost server</a>. Learn more about <a href="https://about.mattermost.com/default-user-access-tokens" target="_blank">personal access tokens</a>.'
- />
- </div>
- <div className='manage-teams__teams'>
- {tokenList}
- </div>
- </div>
- );
- }
-
- render() {
- return (
- <Modal
- show={this.props.show}
- onHide={this.props.onModalDismissed}
- dialogClassName='manage-teams'
- >
- <Modal.Header closeButton={true}>
- <Modal.Title>
- <FormattedMessage
- id='admin.manage_tokens.manageTokensTitle'
- defaultMessage='Manage Personal Access Tokens'
- />
- </Modal.Title>
- </Modal.Header>
- <Modal.Body>
- {this.renderContents()}
- {this.state.error}
- </Modal.Body>
- </Modal>
- );
- }
-}
diff --git a/webapp/components/admin_console/metrics_settings.jsx b/webapp/components/admin_console/metrics_settings.jsx
deleted file mode 100644
index 9eab4073b..000000000
--- a/webapp/components/admin_console/metrics_settings.jsx
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import AdminSettings from './admin_settings.jsx';
-import BooleanSetting from './boolean_setting.jsx';
-import TextSetting from './text_setting.jsx';
-
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-import SettingsGroup from './settings_group.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-
-export default class MetricsSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
- this.renderSettings = this.renderSettings.bind(this);
- }
-
- getConfigFromState(config) {
- config.MetricsSettings.Enable = this.state.enable;
- config.MetricsSettings.ListenAddress = this.state.listenAddress;
-
- return config;
- }
-
- getStateFromConfig(config) {
- const settings = config.MetricsSettings;
-
- return {
- enable: settings.Enable,
- listenAddress: settings.ListenAddress
- };
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.advance.metrics'
- defaultMessage='Performance Monitoring'
- />
- );
- }
-
- renderSettings() {
- const licenseEnabled = global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.Metrics === 'true';
- if (!licenseEnabled) {
- return null;
- }
-
- return (
- <SettingsGroup>
- <BooleanSetting
- id='enable'
- label={
- <FormattedMessage
- id='admin.metrics.enableTitle'
- defaultMessage='Enable Performance Monitoring:'
- />
- }
- helpText={
- <FormattedHTMLMessage
- id='admin.metrics.enableDescription'
- defaultMessage='When true, Mattermost will enable performance monitoring collection and profiling. Please see <a href="http://docs.mattermost.com/deployment/metrics.html" target="_blank">documentation</a> to learn more about configuring performance monitoring for Mattermost.'
- />
- }
- value={this.state.enable}
- onChange={this.handleChange}
- />
- <TextSetting
- id='listenAddress'
- label={
- <FormattedMessage
- id='admin.metrics.listenAddressTitle'
- defaultMessage='Listen Address:'
- />
- }
- placeholder={Utils.localizeMessage('admin.metrics.listenAddressEx', 'Ex ":8067"')}
- helpText={
- <FormattedMessage
- id='admin.metrics.listenAddressDesc'
- defaultMessage='The address the server will listen on to expose performance metrics.'
- />
- }
- value={this.state.listenAddress}
- onChange={this.handleChange}
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/mfa_settings.jsx b/webapp/components/admin_console/mfa_settings.jsx
deleted file mode 100644
index 9d7a64d05..000000000
--- a/webapp/components/admin_console/mfa_settings.jsx
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import AdminSettings from './admin_settings.jsx';
-import SettingsGroup from './settings_group.jsx';
-import BooleanSetting from './boolean_setting.jsx';
-
-import React from 'react';
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-
-export default class MfaSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
-
- this.state = Object.assign(this.state, {
- enableMultifactorAuthentication: props.config.ServiceSettings.EnableMultifactorAuthentication,
- enforceMultifactorAuthentication: props.config.ServiceSettings.EnforceMultifactorAuthentication
- });
- }
-
- getConfigFromState(config) {
- config.ServiceSettings.EnableMultifactorAuthentication = this.state.enableMultifactorAuthentication;
- config.ServiceSettings.EnforceMultifactorAuthentication = this.state.enableMultifactorAuthentication && this.state.enforceMultifactorAuthentication;
-
- return config;
- }
-
- getStateFromConfig(config) {
- return {
- enableMultifactorAuthentication: config.ServiceSettings.EnableMultifactorAuthentication,
- enforceMultifactorAuthentication: config.ServiceSettings.EnableMultifactorAuthentication && config.ServiceSettings.EnforceMultifactorAuthentication
- };
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.mfa.title'
- defaultMessage='Multi-factor Authentication'
- />
- );
- }
-
- renderSettings() {
- return (
- <SettingsGroup>
- <div className='banner'>
- <div className='banner__content'>
- <FormattedHTMLMessage
- id='admin.mfa.bannerDesc'
- defaultMessage="<a href='https://docs.mattermost.com/deployment/auth.html' target='_blank'>Multi-factor authentication</a> is available for accounts with AD/LDAP or email login. If other login methods are used, MFA should be configured with the authentication provider."
- />
- </div>
- </div>
- <BooleanSetting
- id='enableMultifactorAuthentication'
- label={
- <FormattedMessage
- id='admin.service.mfaTitle'
- defaultMessage='Enable Multi-factor Authentication:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.service.mfaDesc'
- defaultMessage='When true, users with AD/LDAP or email login can add multi-factor authentication to their account using Google Authenticator.'
- />
- }
- value={this.state.enableMultifactorAuthentication}
- onChange={this.handleChange}
- />
- <BooleanSetting
- id='enforceMultifactorAuthentication'
- label={
- <FormattedMessage
- id='admin.service.enforceMfaTitle'
- defaultMessage='Enforce Multi-factor Authentication:'
- />
- }
- helpText={
- <FormattedHTMLMessage
- id='admin.service.enforceMfaDesc'
- defaultMessage="When true, <a href='https://docs.mattermost.com/deployment/auth.html' target='_blank'>multi-factor authentication</a> is required for login. New users will be required to configure MFA on signup. Logged in users without MFA configured are redirected to the MFA setup page until configuration is complete.<br/><br/>If your system has users with login methods other than AD/LDAP and email, MFA must be enforced with the authentication provider outside of Mattermost."
- />
- }
- disabled={!this.state.enableMultifactorAuthentication}
- value={this.state.enforceMultifactorAuthentication}
- onChange={this.handleChange}
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/multiselect_settings.jsx b/webapp/components/admin_console/multiselect_settings.jsx
deleted file mode 100644
index 8ae8e1349..000000000
--- a/webapp/components/admin_console/multiselect_settings.jsx
+++ /dev/null
@@ -1,81 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-import React from 'react';
-import ReactSelect from 'react-select';
-
-import Setting from './setting.jsx';
-import FormError from 'components/form_error.jsx';
-
-export default class MultiSelectSetting extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleChange = this.handleChange.bind(this);
- this.state = {error: false};
- }
-
- handleChange(newValue) {
- const values = newValue.map((n) => {
- return n.value;
- });
-
- if (this.props.selected.length > 0 && this.props.mustBePresent && values.join(',').indexOf(this.props.mustBePresent) === -1) {
- this.setState({error: this.props.notPresent});
- } else {
- this.props.onChange(this.props.id, values);
- this.setState({error: false});
- }
- }
-
- componentWillReceiveProps(newProps) {
- if (newProps.selected.length > 0 && newProps.mustBePresent && newProps.selected.join(',').indexOf(newProps.mustBePresent) === -1) {
- this.setState({error: this.props.notPresent});
- } else {
- this.setState({error: false});
- }
- }
-
- render() {
- return (
- <Setting
- label={this.props.label}
- inputId={this.props.id}
- helpText={this.props.helpText}
- >
- <ReactSelect
- id={this.props.id}
- multi={true}
- labelKey='text'
- options={this.props.values}
- joinValues={true}
- clearable={false}
- disabled={this.props.disabled}
- noResultsText={this.props.noResultText}
- onChange={this.handleChange}
- value={this.props.selected}
- />
- <FormError error={this.state.error}/>
- </Setting>
- );
- }
-}
-
-MultiSelectSetting.defaultProps = {
- disabled: false
-};
-
-MultiSelectSetting.propTypes = {
- id: PropTypes.string.isRequired,
- values: PropTypes.array.isRequired,
- label: PropTypes.node.isRequired,
- selected: PropTypes.array.isRequired,
- mustBePresent: PropTypes.string,
- onChange: PropTypes.func.isRequired,
- disabled: PropTypes.bool,
- helpText: PropTypes.node,
- noResultText: PropTypes.node,
- errorText: PropTypes.node,
- notPresent: PropTypes.node
-};
diff --git a/webapp/components/admin_console/native_app_link_settings.jsx b/webapp/components/admin_console/native_app_link_settings.jsx
deleted file mode 100644
index 88c078476..000000000
--- a/webapp/components/admin_console/native_app_link_settings.jsx
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import AdminSettings from './admin_settings.jsx';
-import {FormattedMessage} from 'react-intl';
-import SettingsGroup from './settings_group.jsx';
-import TextSetting from './text_setting.jsx';
-
-export default class NativeAppLinkSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
- }
-
- getConfigFromState(config) {
- config.NativeAppSettings.AppDownloadLink = this.state.appDownloadLink;
- config.NativeAppSettings.AndroidAppDownloadLink = this.state.androidAppDownloadLink;
- config.NativeAppSettings.IosAppDownloadLink = this.state.iosAppDownloadLink;
-
- return config;
- }
-
- getStateFromConfig(config) {
- return {
- appDownloadLink: config.NativeAppSettings.AppDownloadLink,
- androidAppDownloadLink: config.NativeAppSettings.AndroidAppDownloadLink,
- iosAppDownloadLink: config.NativeAppSettings.IosAppDownloadLink
- };
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.customization.nativeAppLinks'
- defaultMessage='Mattermost App Links'
- />
- );
- }
-
- renderSettings() {
- return (
- <SettingsGroup>
- <TextSetting
- id='appDownloadLink'
- label={
- <FormattedMessage
- id='admin.customization.appDownloadLinkTitle'
- defaultMessage='Mattermost Apps Download Page Link:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.customization.appDownloadLinkDesc'
- defaultMessage='Add a link to a download page for the Mattermost apps. When a link is present, an option to "Download Mattermost Apps" will be added in the Main Menu so users can find the download page. Leave this field blank to hide the option from the Main Menu.'
- />
- }
- value={this.state.appDownloadLink}
- onChange={this.handleChange}
- />
- <TextSetting
- id='androidAppDownloadLink'
- label={
- <FormattedMessage
- id='admin.customization.androidAppDownloadLinkTitle'
- defaultMessage='Android App Download Link:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.customization.androidAppDownloadLinkDesc'
- defaultMessage='Add a link to download the Android app. Users who access the site on a mobile web browser will be prompted with a page giving them the option to download the app. Leave this field blank to prevent the page from appearing.'
- />
- }
- value={this.state.androidAppDownloadLink}
- onChange={this.handleChange}
- />
- <TextSetting
- id='iosAppDownloadLink'
- label={
- <FormattedMessage
- id='admin.customization.iosAppDownloadLinkTitle'
- defaultMessage='iOS App Download Link:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.customization.iosAppDownloadLinkDesc'
- defaultMessage='Add a link to download the iOS app. Users who access the site on a mobile web browser will be prompted with a page giving them the option to download the app. Leave this field blank to prevent the page from appearing.'
- />
- }
- value={this.state.iosAppDownloadLink}
- onChange={this.handleChange}
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/oauth_settings.jsx b/webapp/components/admin_console/oauth_settings.jsx
deleted file mode 100644
index abb4dc762..000000000
--- a/webapp/components/admin_console/oauth_settings.jsx
+++ /dev/null
@@ -1,430 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-
-import AdminSettings from './admin_settings.jsx';
-import DropdownSetting from './dropdown_setting.jsx';
-import SettingsGroup from './settings_group.jsx';
-import TextSetting from './text_setting.jsx';
-
-import React from 'react';
-import {FormattedHTMLMessage, FormattedMessage} from 'react-intl';
-
-export default class OAuthSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
- this.getStateFromConfig = this.getStateFromConfig.bind(this);
- this.renderSettings = this.renderSettings.bind(this);
- this.renderOffice365 = this.renderOffice365.bind(this);
- this.renderGoogle = this.renderGoogle.bind(this);
- this.renderGitLab = this.renderGitLab.bind(this);
- this.changeType = this.changeType.bind(this);
- }
-
- getConfigFromState(config) {
- config.GitLabSettings.Enable = false;
- config.GoogleSettings.Enable = false;
- config.Office365Settings.Enable = false;
-
- if (this.state.oauthType === Constants.GITLAB_SERVICE) {
- config.GitLabSettings.Enable = true;
- config.GitLabSettings.Id = this.state.id;
- config.GitLabSettings.Secret = this.state.secret;
- config.GitLabSettings.UserApiEndpoint = this.state.userApiEndpoint;
- config.GitLabSettings.AuthEndpoint = this.state.authEndpoint;
- config.GitLabSettings.TokenEndpoint = this.state.tokenEndpoint;
- }
-
- if (this.state.oauthType === Constants.GOOGLE_SERVICE) {
- config.GoogleSettings.Enable = true;
- config.GoogleSettings.Id = this.state.id;
- config.GoogleSettings.Secret = this.state.secret;
- config.GoogleSettings.UserApiEndpoint = 'https://www.googleapis.com/plus/v1/people/me';
- config.GoogleSettings.AuthEndpoint = 'https://accounts.google.com/o/oauth2/v2/auth';
- config.GoogleSettings.TokenEndpoint = 'https://www.googleapis.com/oauth2/v4/token';
- config.GoogleSettings.Scope = 'profile email';
- }
-
- if (this.state.oauthType === Constants.OFFICE365_SERVICE) {
- config.Office365Settings.Enable = true;
- config.Office365Settings.Id = this.state.id;
- config.Office365Settings.Secret = this.state.secret;
- config.Office365Settings.UserApiEndpoint = 'https://graph.microsoft.com/v1.0/me';
- config.Office365Settings.AuthEndpoint = 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize';
- config.Office365Settings.TokenEndpoint = 'https://login.microsoftonline.com/common/oauth2/v2.0/token';
- config.Office365Settings.Scope = 'User.Read';
- }
-
- return config;
- }
-
- getStateFromConfig(config) {
- this.config = config;
-
- let oauthType = 'off';
- let settings = {};
- if (config.GitLabSettings.Enable) {
- oauthType = Constants.GITLAB_SERVICE;
- settings = config.GitLabSettings;
- } else if (config.GoogleSettings.Enable) {
- oauthType = Constants.GOOGLE_SERVICE;
- settings = config.GoogleSettings;
- } else if (config.Office365Settings.Enable) {
- oauthType = Constants.OFFICE365_SERVICE;
- settings = config.Office365Settings;
- }
-
- return {
- oauthType,
- id: settings.Id,
- secret: settings.Secret,
- userApiEndpoint: settings.UserApiEndpoint,
- authEndpoint: settings.AuthEndpoint,
- tokenEndpoint: settings.TokenEndpoint
- };
- }
-
- changeType(id, value) {
- let settings = {};
- if (value === Constants.GITLAB_SERVICE) {
- settings = this.config.GitLabSettings;
- } else if (value === Constants.GOOGLE_SERVICE) {
- settings = this.config.GoogleSettings;
- } else if (value === Constants.OFFICE365_SERVICE) {
- settings = this.config.Office365Settings;
- }
-
- this.setState({
- id: settings.Id,
- secret: settings.Secret,
- userApiEndpoint: settings.UserApiEndpoint,
- authEndpoint: settings.AuthEndpoint,
- tokenEndpoint: settings.TokenEndpoint
- });
-
- this.handleChange(id, value);
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.authentication.oauth'
- defaultMessage='OAuth 2.0'
- />
- );
- }
-
- renderGoogle() {
- return (
- <div>
- <TextSetting
- id='id'
- label={
- <FormattedMessage
- id='admin.google.clientIdTitle'
- defaultMessage='Client ID:'
- />
- }
- placeholder={Utils.localizeMessage('admin.google.clientIdExample', 'Ex "7602141235235-url0fhs1mayfasbmop5qlfns8dh4.apps.googleusercontent.com"')}
- helpText={
- <FormattedMessage
- id='admin.google.clientIdDescription'
- defaultMessage='The Client ID you received when registering your application with Google.'
- />
- }
- value={this.state.id}
- onChange={this.handleChange}
- />
- <TextSetting
- id='secret'
- label={
- <FormattedMessage
- id='admin.google.clientSecretTitle'
- defaultMessage='Client Secret:'
- />
- }
- placeholder={Utils.localizeMessage('admin.google.clientSecretExample', 'Ex "H8sz0Az-dDs2p15-7QzD231"')}
- helpText={
- <FormattedMessage
- id='admin.google.clientSecretDescription'
- defaultMessage='The Client Secret you received when registering your application with Google.'
- />
- }
- value={this.state.secret}
- onChange={this.handleChange}
- />
- <TextSetting
- id='userApiEndpoint'
- label={
- <FormattedMessage
- id='admin.google.userTitle'
- defaultMessage='User API Endpoint:'
- />
- }
- value='https://www.googleapis.com/plus/v1/people/me'
- disabled={true}
- />
- <TextSetting
- id='authEndpoint'
- label={
- <FormattedMessage
- id='admin.google.authTitle'
- defaultMessage='Auth Endpoint:'
- />
- }
- value='https://accounts.google.com/o/oauth2/v2/auth'
- disabled={true}
- />
- <TextSetting
- id='tokenEndpoint'
- label={
- <FormattedMessage
- id='admin.google.tokenTitle'
- defaultMessage='Token Endpoint:'
- />
- }
- value='https://www.googleapis.com/oauth2/v4/token'
- disabled={true}
- />
- </div>
- );
- }
-
- renderOffice365() {
- return (
- <div>
- <TextSetting
- id='id'
- label={
- <FormattedMessage
- id='admin.office365.clientIdTitle'
- defaultMessage='Application ID:'
- />
- }
- placeholder={Utils.localizeMessage('admin.office365.clientIdExample', 'Ex "adf3sfa2-ag3f-sn4n-ids0-sh1hdax192qq"')}
- helpText={
- <FormattedMessage
- id='admin.office365.clientIdDescription'
- defaultMessage='The Application/Client ID you received when registering your application with Microsoft.'
- />
- }
- value={this.state.id}
- onChange={this.handleChange}
- />
- <TextSetting
- id='secret'
- label={
- <FormattedMessage
- id='admin.office365.clientSecretTitle'
- defaultMessage='Application Secret Password:'
- />
- }
- placeholder={Utils.localizeMessage('admin.office365.clientSecretExample', 'Ex "shAieM47sNBfgl20f8ci294"')}
- helpText={
- <FormattedMessage
- id='admin.office365.clientSecretDescription'
- defaultMessage='The Application Secret Password you generated when registering your application with Microsoft.'
- />
- }
- value={this.state.secret}
- onChange={this.handleChange}
- />
- <TextSetting
- id='userApiEndpoint'
- label={
- <FormattedMessage
- id='admin.office365.userTitle'
- defaultMessage='User API Endpoint:'
- />
- }
- value='https://graph.microsoft.com/v1.0/me'
- disabled={true}
- />
- <TextSetting
- id='authEndpoint'
- label={
- <FormattedMessage
- id='admin.office365.authTitle'
- defaultMessage='Auth Endpoint:'
- />
- }
- value='https://login.microsoftonline.com/common/oauth2/v2.0/authorize'
- disabled={true}
- />
- <TextSetting
- id='tokenEndpoint'
- label={
- <FormattedMessage
- id='admin.office365.tokenTitle'
- defaultMessage='Token Endpoint:'
- />
- }
- value='https://login.microsoftonline.com/common/oauth2/v2.0/token'
- disabled={true}
- />
- </div>
- );
- }
-
- renderGitLab() {
- return (
- <div>
- <TextSetting
- id='id'
- label={
- <FormattedMessage
- id='admin.gitlab.clientIdTitle'
- defaultMessage='Application ID:'
- />
- }
- placeholder={Utils.localizeMessage('admin.gitlab.clientIdExample', 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"')}
- helpText={
- <FormattedMessage
- id='admin.gitlab.clientIdDescription'
- defaultMessage='Obtain this value via the instructions above for logging into GitLab'
- />
- }
- value={this.state.id}
- onChange={this.handleChange}
- />
- <TextSetting
- id='secret'
- label={
- <FormattedMessage
- id='admin.gitlab.clientSecretTitle'
- defaultMessage='Application Secret Key:'
- />
- }
- placeholder={Utils.localizeMessage('admin.gitlab.clientSecretExample', 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"')}
- helpText={
- <FormattedMessage
- id='admin.gitab.clientSecretDescription'
- defaultMessage='Obtain this value via the instructions above for logging into GitLab.'
- />
- }
- value={this.state.secret}
- onChange={this.handleChange}
- />
- <TextSetting
- id='userApiEndpoint'
- label={
- <FormattedMessage
- id='admin.gitlab.userTitle'
- defaultMessage='User API Endpoint:'
- />
- }
- placeholder={Utils.localizeMessage('admin.gitlab.userExample', 'Ex "https://<your-gitlab-url>/api/v3/user"')}
- helpText={
- <FormattedMessage
- id='admin.gitlab.userDescription'
- defaultMessage='Enter https://<your-gitlab-url>/api/v3/user. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'
- />
- }
- value={this.state.userApiEndpoint}
- onChange={this.handleChange}
- />
- <TextSetting
- id='authEndpoint'
- label={
- <FormattedMessage
- id='admin.gitlab.authTitle'
- defaultMessage='Auth Endpoint:'
- />
- }
- placeholder={Utils.localizeMessage('admin.gitlab.authExample', 'Ex "https://<your-gitlab-url>/oauth/authorize"')}
- helpText={
- <FormattedMessage
- id='admin.gitlab.authDescription'
- defaultMessage='Enter https://<your-gitlab-url>/oauth/authorize (example https://example.com:3000/oauth/authorize). Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'
- />
- }
- value={this.state.authEndpoint}
- onChange={this.handleChange}
- />
- <TextSetting
- id='tokenEndpoint'
- label={
- <FormattedMessage
- id='admin.gitlab.tokenTitle'
- defaultMessage='Token Endpoint:'
- />
- }
- placeholder={Utils.localizeMessage('admin.gitlab.tokenExample', 'Ex "https://<your-gitlab-url>/oauth/token"')}
- helpText={
- <FormattedMessage
- id='admin.gitlab.tokenDescription'
- defaultMessage='Enter https://<your-gitlab-url>/oauth/token. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'
- />
- }
- value={this.state.tokenEndpoint}
- onChange={this.handleChange}
- />
- </div>
- );
- }
-
- renderSettings() {
- let contents;
- let helpText;
- if (this.state.oauthType === Constants.GITLAB_SERVICE) {
- contents = this.renderGitLab();
- helpText = (
- <FormattedHTMLMessage
- id='admin.gitlab.EnableHtmlDesc'
- defaultMessage='<ol><li>Log in to your GitLab account and go to Profile Settings -> Applications.</li><li>Enter Redirect URIs "<your-mattermost-url>/login/gitlab/complete" (example: http://localhost:8065/login/gitlab/complete) and "<your-mattermost-url>/signup/gitlab/complete". </li><li>Then use "Application Secret Key" and "Application ID" fields from GitLab to complete the options below.</li><li>Complete the Endpoint URLs below. </li></ol>'
- />
- );
- } else if (this.state.oauthType === Constants.GOOGLE_SERVICE) {
- contents = this.renderGoogle();
- helpText = (
- <FormattedHTMLMessage
- id='admin.google.EnableHtmlDesc'
- defaultMessage='<ol><li><a target="_blank" href="https://accounts.google.com/login">Log in</a> to your Google account.</li><li>Go to <a target="_blank" href="https://console.developers.google.com">https://console.developers.google.com</a>, click <strong>Credentials</strong> in the left hand sidebar and enter "Mattermost - your-company-name" as the <strong>Project Name</strong>, then click <strong>Create</strong>.</li><li>Click the <strong>OAuth consent screen</strong> header and enter "Mattermost" as the <strong>Product name shown to users</strong>, then click <strong>Save</strong>.</li><li>Under the <strong>Credentials</strong> header, click <strong>Create credentials</strong>, choose <strong>OAuth client ID</strong> and select <strong>Web Application</strong>.</li><li>Under <strong>Restrictions</strong> and <strong>Authorized redirect URIs</strong> enter <strong>your-mattermost-url/signup/google/complete</strong> (example: http://localhost:8065/signup/google/complete). Click <strong>Create</strong>.</li><li>Paste the <strong>Client ID</strong> and <strong>Client Secret</strong> to the fields below, then click <strong>Save</strong>.</li><li>Finally, go to <a target="_blank" href="https://console.developers.google.com/apis/api/plus/overview">Google+ API</a> and click <strong>Enable</strong>. This might take a few minutes to propagate through Google`s systems.</li></ol>'
- />
- );
- } else if (this.state.oauthType === Constants.OFFICE365_SERVICE) {
- contents = this.renderOffice365();
- helpText = (
- <FormattedHTMLMessage
- id='admin.office365.EnableHtmlDesc'
- defaultMessage='<ol><li><a target="_blank" href="https://login.microsoftonline.com/">Log in</a> to your Microsoft or Office 365 account. Make sure it`s the account on the same <a target="_blank" href="https://msdn.microsoft.com/en-us/library/azure/jj573650.aspx#Anchor_0">tenant</a> that you would like users to log in with.</li><li>Go to <a target="_blank" href="https://apps.dev.microsoft.com">https://apps.dev.microsoft.com</a>, click <strong>Go to app list</strong> > <strong>Add an app</strong> and use "Mattermost - your-company-name" as the <strong>Application Name</strong>.</li><li>Under <strong>Application Secrets</strong>, click <strong>Generate New Password</strong> and paste it to the <strong>Application Secret Password</strong> field below.</li><li>Under <strong>Platforms</strong>, click <strong>Add Platform</strong>, choose <strong>Web</strong> and enter <strong>your-mattermost-url/signup/office365/complete</strong> (example: http://localhost:8065/signup/office365/complete) under <strong>Redirect URIs</strong>. Also uncheck <strong>Allow Implicit Flow</strong>.</li><li>Finally, click <strong>Save</strong> and then paste the <strong>Application ID</strong> below.</li></ol>'
- />
- );
- }
-
- const oauthTypes = [];
- oauthTypes.push({value: 'off', text: Utils.localizeMessage('admin.oauth.off', 'Do not allow sign-in via an OAuth 2.0 provider.')});
- oauthTypes.push({value: Constants.GITLAB_SERVICE, text: Utils.localizeMessage('admin.oauth.gitlab', 'GitLab')});
- if (global.window.mm_license.IsLicensed === 'true') {
- if (global.window.mm_license.GoogleOAuth === 'true') {
- oauthTypes.push({value: Constants.GOOGLE_SERVICE, text: Utils.localizeMessage('admin.oauth.google', 'Google Apps')});
- }
- if (global.window.mm_license.Office365OAuth === 'true') {
- oauthTypes.push({value: Constants.OFFICE365_SERVICE, text: Utils.localizeMessage('admin.oauth.office365', 'Office 365 (Beta)')});
- }
- }
-
- return (
- <SettingsGroup>
- <DropdownSetting
- id='oauthType'
- values={oauthTypes}
- label={
- <FormattedMessage
- id='admin.oauth.select'
- defaultMessage='Select OAuth 2.0 Service Provider:'
- />
- }
- helpText={helpText}
- value={this.state.oauthType}
- onChange={this.changeType}
- />
- {contents}
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/password_settings.jsx b/webapp/components/admin_console/password_settings.jsx
deleted file mode 100644
index 6f39269c0..000000000
--- a/webapp/components/admin_console/password_settings.jsx
+++ /dev/null
@@ -1,281 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import AdminSettings from './admin_settings.jsx';
-import {FormattedMessage} from 'react-intl';
-import SettingsGroup from './settings_group.jsx';
-import TextSetting from './text_setting.jsx';
-import Setting from './setting.jsx';
-import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-
-export default class PasswordSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
-
- this.getSampleErrorMsg = this.getSampleErrorMsg.bind(this);
-
- this.handlePasswordLengthChange = this.handlePasswordLengthChange.bind(this);
- this.handleCheckboxChange = this.handleCheckboxChange.bind(this);
-
- this.state = Object.assign(this.state, {
- passwordMinimumLength: props.config.PasswordSettings.MinimumLength,
- passwordLowercase: props.config.PasswordSettings.Lowercase,
- passwordNumber: props.config.PasswordSettings.Number,
- passwordUppercase: props.config.PasswordSettings.Uppercase,
- passwordSymbol: props.config.PasswordSettings.Symbol,
- maximumLoginAttempts: props.config.ServiceSettings.MaximumLoginAttempts
- });
-
- // Update sample message from config settings
- this.sampleErrorMsg = null;
- if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.PasswordRequirements === 'true') {
- let sampleErrorMsgId = 'user.settings.security.passwordError';
- if (props.config.PasswordSettings.Lowercase) {
- sampleErrorMsgId += 'Lowercase';
- }
- if (props.config.PasswordSettings.Uppercase) {
- sampleErrorMsgId += 'Uppercase';
- }
- if (props.config.PasswordSettings.Number) {
- sampleErrorMsgId += 'Number';
- }
- if (props.config.PasswordSettings.Symbol) {
- sampleErrorMsgId += 'Symbol';
- }
- this.sampleErrorMsg = (
- <FormattedMessage
- id={sampleErrorMsgId}
- default='Your password must contain between {min} and {max} characters.'
- values={{
- min: (this.state.passwordMinimumLength || Constants.MIN_PASSWORD_LENGTH),
- max: Constants.MAX_PASSWORD_LENGTH
- }}
- />
- );
- }
- }
-
- getConfigFromState(config) {
- if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.PasswordRequirements === 'true') {
- config.PasswordSettings.MinimumLength = this.parseIntNonZero(this.state.passwordMinimumLength, Constants.MIN_PASSWORD_LENGTH);
- config.PasswordSettings.Lowercase = this.refs.lowercase.checked;
- config.PasswordSettings.Uppercase = this.refs.uppercase.checked;
- config.PasswordSettings.Number = this.refs.number.checked;
- config.PasswordSettings.Symbol = this.refs.symbol.checked;
- }
-
- config.ServiceSettings.MaximumLoginAttempts = this.parseIntNonZero(this.state.maximumLoginAttempts);
-
- return config;
- }
-
- getStateFromConfig(config) {
- return {
- passwordMinimumLength: config.PasswordSettings.MinimumLength,
- passwordLowercase: config.PasswordSettings.Lowercase,
- passwordNumber: config.PasswordSettings.Number,
- passwordUppercase: config.PasswordSettings.Uppercase,
- passwordSymbol: config.PasswordSettings.Symbol,
- maximumLoginAttempts: config.ServiceSettings.MaximumLoginAttempts
- };
- }
-
- getSampleErrorMsg(minLength) {
- if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.PasswordRequirements === 'true') {
- if (this.props.config.PasswordSettings.MinimumLength > Constants.MAX_PASSWORD_LENGTH || this.props.config.PasswordSettings.MinimumLength < Constants.MIN_PASSWORD_LENGTH) {
- return (
- <FormattedMessage
- id='user.settings.security.passwordMinLength'
- default='Invalid minimum length, cannot show preview.'
- />
- );
- }
- let sampleErrorMsgId = 'user.settings.security.passwordError';
- if (this.refs.lowercase.checked) {
- sampleErrorMsgId += 'Lowercase';
- }
- if (this.refs.uppercase.checked) {
- sampleErrorMsgId += 'Uppercase';
- }
- if (this.refs.number.checked) {
- sampleErrorMsgId += 'Number';
- }
- if (this.refs.symbol.checked) {
- sampleErrorMsgId += 'Symbol';
- }
- return (
- <FormattedMessage
- id={sampleErrorMsgId}
- default='Your password must contain between {min} and {max} characters.'
- values={{
- min: (minLength || Constants.MIN_PASSWORD_LENGTH),
- max: Constants.MAX_PASSWORD_LENGTH
- }}
- />
- );
- }
-
- return null;
- }
-
- handlePasswordLengthChange(id, value) {
- this.sampleErrorMsg = this.getSampleErrorMsg(value);
- this.handleChange(id, value);
- }
-
- handleCheckboxChange(id, value) {
- this.sampleErrorMsg = this.getSampleErrorMsg(this.state.passwordMinimumLength);
- this.handleChange(id, value);
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.security.password'
- defaultMessage='Password'
- />
- );
- }
-
- renderSettings() {
- let passwordSettings = null;
- if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.PasswordRequirements === 'true') {
- passwordSettings = (
- <div>
- <TextSetting
- id='passwordMinimumLength'
- label={
- <FormattedMessage
- id='admin.password.minimumLength'
- defaultMessage='Minimum Password Length:'
- />
- }
- placeholder={Utils.localizeMessage('admin.password.minimumLengthExample', 'Ex "5"')}
- helpText={
- <FormattedMessage
- id='admin.password.minimumLengthDescription'
- defaultMessage='Minimum number of characters required for a valid password. Must be a whole number greater than or equal to {min} and less than or equal to {max}.'
- values={{
- min: Constants.MIN_PASSWORD_LENGTH,
- max: Constants.MAX_PASSWORD_LENGTH
- }}
- />
- }
- value={this.state.passwordMinimumLength}
- onChange={this.handlePasswordLengthChange}
- />
- <Setting
- label={
- <FormattedMessage
- id='passwordRequirements'
- defaultMessage='Password Requirements:'
- />
- }
- >
- <div>
- <label className='checkbox-inline'>
- <input
- type='checkbox'
- ref='lowercase'
- defaultChecked={this.state.passwordLowercase}
- name='admin.password.lowercase'
- onChange={this.handleCheckboxChange}
- />
- <FormattedMessage
- id='admin.password.lowercase'
- defaultMessage='At least one lowercase letter'
- />
- </label>
- </div>
- <div>
- <label className='checkbox-inline'>
- <input
- type='checkbox'
- ref='uppercase'
- defaultChecked={this.state.passwordUppercase}
- name='admin.password.uppercase'
- onChange={this.handleCheckboxChange}
- />
- <FormattedMessage
- id='admin.password.uppercase'
- defaultMessage='At least one uppercase letter'
- />
- </label>
- </div>
- <div>
- <label className='checkbox-inline'>
- <input
- type='checkbox'
- ref='number'
- defaultChecked={this.state.passwordNumber}
- name='admin.password.number'
- onChange={this.handleCheckboxChange}
- />
- <FormattedMessage
- id='admin.password.number'
- defaultMessage='At least one number'
- />
- </label>
- </div>
- <div>
- <label className='checkbox-inline'>
- <input
- type='checkbox'
- ref='symbol'
- defaultChecked={this.state.passwordSymbol}
- name='admin.password.symbol'
- onChange={this.handleCheckboxChange}
- />
- <FormattedMessage
- id='admin.password.symbol'
- defaultMessage='At least one symbol (e.g. "~!@#$%^&*()")'
- />
- </label>
- </div>
- <div>
- <br/>
- <label>
- <FormattedMessage
- id='admin.password.preview'
- defaultMessage='Error message preview:'
- />
- </label>
- <br/>
- {this.sampleErrorMsg}
- </div>
- </Setting>
- </div>
- );
- }
-
- return (
- <SettingsGroup>
- {passwordSettings}
- <TextSetting
- id='maximumLoginAttempts'
- label={
- <FormattedMessage
- id='admin.service.attemptTitle'
- defaultMessage='Maximum Login Attempts:'
- />
- }
- placeholder={Utils.localizeMessage('admin.service.attemptExample', 'Ex "10"')}
- helpText={
- <FormattedMessage
- id='admin.service.attemptDescription'
- defaultMessage='Login attempts allowed before user is locked out and required to reset password via email.'
- />
- }
- value={this.state.maximumLoginAttempts}
- onChange={this.handleChange}
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/plugin_settings/index.js b/webapp/components/admin_console/plugin_settings/index.js
deleted file mode 100644
index 469d4ee2e..000000000
--- a/webapp/components/admin_console/plugin_settings/index.js
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {uploadPlugin, removePlugin, getPlugins} from 'mattermost-redux/actions/admin';
-
-import PluginSettings from './plugin_settings.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps,
- plugins: state.entities.admin.plugins
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- uploadPlugin,
- removePlugin,
- getPlugins
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(PluginSettings);
diff --git a/webapp/components/admin_console/plugin_settings/plugin_settings.jsx b/webapp/components/admin_console/plugin_settings/plugin_settings.jsx
deleted file mode 100644
index 286e05c06..000000000
--- a/webapp/components/admin_console/plugin_settings/plugin_settings.jsx
+++ /dev/null
@@ -1,293 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import LoadingScreen from 'components/loading_screen.jsx';
-import Banner from 'components/admin_console/banner.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-
-export default class PluginSettings extends React.Component {
- static propTypes = {
-
- /*
- * The config
- */
- config: PropTypes.object.isRequired,
-
- /*
- * Plugins object with ids as keys and manifests as values
- */
- plugins: PropTypes.object.isRequired,
-
- actions: PropTypes.shape({
-
- /*
- * Function to upload a plugin
- */
- uploadPlugin: PropTypes.func.isRequired,
-
- /*
- * Function to remove a plugin
- */
- removePlugin: PropTypes.func.isRequired,
-
- /*
- * Function to get installed plugins
- */
- getPlugins: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.state = {
- loading: true,
- fileSelected: false,
- fileName: null,
- serverError: null
- };
- }
-
- componentDidMount() {
- this.props.actions.getPlugins().then(
- () => this.setState({loading: false})
- );
- }
-
- handleChange = () => {
- const element = this.refs.fileInput;
- if (element.files.length > 0) {
- this.setState({fileSelected: true, fileName: element.files[0].name});
- }
- }
-
- handleSubmit = async (e) => {
- e.preventDefault();
-
- const element = this.refs.fileInput;
- if (element.files.length === 0) {
- return;
- }
- const file = element.files[0];
-
- this.setState({uploading: true});
-
- const {error} = await this.props.actions.uploadPlugin(file);
- this.setState({fileSelected: false, fileName: null, uploading: false});
- Utils.clearFileInput(element);
-
- if (error) {
- if (error.server_error_id === 'app.plugin.activate.app_error') {
- this.setState({serverError: Utils.localizeMessage('admin.plugin.error.activate', 'Unable to upload the plugin. It may conflict with another plugin on your server.')});
- } else if (error.server_error_id === 'app.plugin.extract.app_error') {
- this.setState({serverError: Utils.localizeMessage('admin.plugin.error.extract', 'Encountered an error when extracting the plugin. Review your plugin file content and try again.')});
- } else {
- this.setState({serverError: error.message});
- }
- }
- }
-
- handleRemove = async (pluginId) => {
- this.setState({removing: pluginId});
-
- const {error} = await this.props.actions.removePlugin(pluginId);
- this.setState({removing: null});
-
- if (error) {
- this.setState({serverError: error.message});
- }
- }
-
- render() {
- let serverError = '';
- if (this.state.serverError) {
- serverError = <div className='col-sm-12'><div className='form-group has-error half'><label className='control-label'>{this.state.serverError}</label></div></div>;
- }
-
- let btnClass = 'btn';
- if (this.state.fileSelected) {
- btnClass = 'btn btn-primary';
- }
-
- let fileName;
- if (this.state.fileName) {
- fileName = this.state.fileName;
- }
-
- let uploadButtonText;
- if (this.state.uploading) {
- uploadButtonText = (
- <FormattedMessage
- id='admin.plugin.uploading'
- defaultMessage='Uploading...'
- />
- );
- } else {
- uploadButtonText = (
- <FormattedMessage
- id='admin.plugin.upload'
- defaultMessage='Upload'
- />
- );
- }
-
- let activePluginsList;
- let activePluginsContainer;
- const plugins = Object.values(this.props.plugins);
- if (this.state.loading) {
- activePluginsList = <LoadingScreen/>;
- } else if (plugins.length === 0) {
- activePluginsContainer = (
- <FormattedMessage
- id='admin.plugin.no_plugins'
- defaultMessage='No active plugins.'
- />
- );
- } else {
- activePluginsList = plugins.map(
- (p) => {
- let removeButtonText;
- if (this.state.removing === p.id) {
- removeButtonText = (
- <FormattedMessage
- id='admin.plugin.removing'
- defaultMessage='Removing...'
- />
- );
- } else {
- removeButtonText = (
- <FormattedMessage
- id='admin.plugin.remove'
- defaultMessage='Remove'
- />
- );
- }
-
- return (
- <div key={p.id}>
- <div>
- <strong>
- <FormattedMessage
- id='admin.plugin.id'
- defaultMessage='ID:'
- />
- </strong>
- {' ' + p.id}
- </div>
- <div className='padding-top'>
- <strong>
- <FormattedMessage
- id='admin.plugin.desc'
- defaultMessage='Description:'
- />
- </strong>
- {' ' + p.description}
- </div>
- <div className='padding-top'>
- <a
- disabled={this.state.removing === p.id}
- onClick={() => this.handleRemove(p.id)}
- >
- {removeButtonText}
- </a>
- </div>
- <hr/>
- </div>
- );
- }
- );
-
- activePluginsContainer = (
- <div className='alert alert-transparent'>
- {activePluginsList}
- </div>
- );
- }
-
- return (
- <div className='wrapper--fixed'>
- <h3 className='admin-console-header'>
- <FormattedMessage
- id='admin.plugin.title'
- defaultMessage='Plugins (experimental)'
- />
- </h3>
- <Banner
- title={<div/>}
- description={
- <FormattedHTMLMessage
- id='admin.plugin.banner'
- defaultMessage='Plugins are experimental stage and are not yet recommended for use in production environments. <br/><br/> Webapp plugins will require users to refresh their browsers or desktop apps before the plugin will take effect. Similarly when a plugin is removed, users will continue to see the plugin until they refresh their browser or app.'
- />
- }
- />
- <form
- className='form-horizontal'
- role='form'
- >
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- >
- <FormattedMessage
- id='admin.plugin.uploadTitle'
- defaultMessage='Upload Plugin: '
- />
- </label>
- <div className='col-sm-8'>
- <div className='file__upload'>
- <button className='btn btn-primary'>
- <FormattedMessage
- id='admin.plugin.choose'
- defaultMessage='Choose File'
- />
- </button>
- <input
- ref='fileInput'
- type='file'
- accept='.gz'
- onChange={this.handleChange}
- />
- </div>
- <button
- className={btnClass}
- disabled={!this.state.fileSelected}
- onClick={this.handleSubmit}
- >
- {uploadButtonText}
- </button>
- <div className='help-text no-margin'>
- {fileName}
- </div>
- {serverError}
- <p className='help-text'>
- <FormattedHTMLMessage
- id='admin.plugin.uploadDesc'
- defaultMessage='Upload a plugin for your Mattermost server. Adding or removing a webapp plugin requires users to refresh their browser or Desktop App before taking effect. See <a href="https://about.mattermost.com/default-plugins">documentation</a> to learn more.'
- />
- </p>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- >
- <FormattedMessage
- id='admin.plugin.activeTitle'
- defaultMessage='Active Plugins: '
- />
- </label>
- <div className='col-sm-8 padding-top'>
- {activePluginsContainer}
- </div>
- </div>
- </form>
- </div>
- );
- }
-}
diff --git a/webapp/components/admin_console/policy_settings.jsx b/webapp/components/admin_console/policy_settings.jsx
deleted file mode 100644
index f689efd82..000000000
--- a/webapp/components/admin_console/policy_settings.jsx
+++ /dev/null
@@ -1,413 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import AdminSettings from './admin_settings.jsx';
-import SettingsGroup from './settings_group.jsx';
-import DropdownSetting from './dropdown_setting.jsx';
-import RadioSetting from './radio_setting.jsx';
-import PostEditSetting from './post_edit_setting.jsx';
-import BooleanSetting from './boolean_setting.jsx';
-import TextSetting from './text_setting.jsx';
-import ColorSetting from './color_setting.jsx';
-
-import Constants from 'utils/constants.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-
-export default class PolicySettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
- }
-
- getConfigFromState(config) {
- config.ServiceSettings.RestrictPostDelete = this.state.restrictPostDelete;
- config.ServiceSettings.AllowEditPost = this.state.allowEditPost;
- config.ServiceSettings.PostEditTimeLimit = this.parseIntNonZero(this.state.postEditTimeLimit, Constants.DEFAULT_POST_EDIT_TIME_LIMIT);
- config.TeamSettings.RestrictTeamInvite = this.state.restrictTeamInvite;
- config.TeamSettings.RestrictPublicChannelCreation = this.state.restrictPublicChannelCreation;
- config.TeamSettings.RestrictPrivateChannelCreation = this.state.restrictPrivateChannelCreation;
- config.TeamSettings.RestrictPublicChannelManagement = this.state.restrictPublicChannelManagement;
- config.TeamSettings.RestrictPrivateChannelManagement = this.state.restrictPrivateChannelManagement;
- config.TeamSettings.RestrictPublicChannelDeletion = this.state.restrictPublicChannelDeletion;
- config.TeamSettings.RestrictPrivateChannelDeletion = this.state.restrictPrivateChannelDeletion;
- config.TeamSettings.RestrictPrivateChannelManageMembers = this.state.restrictPrivateChannelManageMembers;
- config.AnnouncementSettings.EnableBanner = this.state.enableBanner;
- config.AnnouncementSettings.BannerText = this.state.bannerText;
- config.AnnouncementSettings.BannerColor = this.state.bannerColor;
- config.AnnouncementSettings.BannerTextColor = this.state.bannerTextColor;
- config.AnnouncementSettings.AllowBannerDismissal = this.state.allowBannerDismissal;
-
- return config;
- }
-
- getStateFromConfig(config) {
- return {
- restrictPostDelete: config.ServiceSettings.RestrictPostDelete,
- allowEditPost: config.ServiceSettings.AllowEditPost,
- postEditTimeLimit: config.ServiceSettings.PostEditTimeLimit,
- restrictTeamInvite: config.TeamSettings.RestrictTeamInvite,
- restrictPublicChannelCreation: config.TeamSettings.RestrictPublicChannelCreation,
- restrictPrivateChannelCreation: config.TeamSettings.RestrictPrivateChannelCreation,
- restrictPublicChannelManagement: config.TeamSettings.RestrictPublicChannelManagement,
- restrictPrivateChannelManagement: config.TeamSettings.RestrictPrivateChannelManagement,
- restrictPublicChannelDeletion: config.TeamSettings.RestrictPublicChannelDeletion,
- restrictPrivateChannelDeletion: config.TeamSettings.RestrictPrivateChannelDeletion,
- restrictPrivateChannelManageMembers: config.TeamSettings.RestrictPrivateChannelManageMembers,
- enableBanner: config.AnnouncementSettings.EnableBanner,
- bannerText: config.AnnouncementSettings.BannerText,
- bannerColor: config.AnnouncementSettings.BannerColor,
- bannerTextColor: config.AnnouncementSettings.BannerTextColor,
- allowBannerDismissal: config.AnnouncementSettings.AllowBannerDismissal
- };
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.general.policy'
- defaultMessage='Policy'
- />
- );
- }
-
- renderSettings() {
- return (
- <SettingsGroup>
- <DropdownSetting
- id='restrictTeamInvite'
- values={[
- {value: Constants.PERMISSIONS_ALL, text: Utils.localizeMessage('admin.general.policy.permissionsAll', 'All team members')},
- {value: Constants.PERMISSIONS_TEAM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsAdmin', 'Team and System Admins')},
- {value: Constants.PERMISSIONS_SYSTEM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsSystemAdmin', 'System Admins')}
- ]}
- label={
- <FormattedMessage
- id='admin.general.policy.teamInviteTitle'
- defaultMessage='Enable sending team invites from:'
- />
- }
- value={this.state.restrictTeamInvite}
- onChange={this.handleChange}
- helpText={
- <FormattedHTMLMessage
- id='admin.general.policy.teamInviteDescription'
- defaultMessage='Set policy on who can invite others to a team using <b>Send Email Invite</b> to invite new users by email, or the <b>Get Team Invite Link</b> and <b>Add Members to Team</b> options from the Main Menu. If <b>Get Team Invite Link</b> is used to share a link, you can expire the invite code from <b>Team Settings</b> > <b>Invite Code</b> after the desired users join the team.'
- />
- }
- />
- <DropdownSetting
- id='restrictPublicChannelCreation'
- values={[
- {value: Constants.PERMISSIONS_ALL, text: Utils.localizeMessage('admin.general.policy.permissionsAll', 'All team members')},
- {value: Constants.PERMISSIONS_TEAM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsAdmin', 'Team and System Admins')},
- {value: Constants.PERMISSIONS_SYSTEM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsSystemAdmin', 'System Admins')}
- ]}
- label={
- <FormattedMessage
- id='admin.general.policy.restrictPublicChannelCreationTitle'
- defaultMessage='Enable public channel creation for:'
- />
- }
- value={this.state.restrictPublicChannelCreation}
- onChange={this.handleChange}
- helpText={
- <FormattedMessage
- id='admin.general.policy.restrictPublicChannelCreationDescription'
- defaultMessage='Set policy on who can create public channels.'
- />
- }
- />
- <DropdownSetting
- id='restrictPublicChannelManagement'
- values={[
- {value: Constants.PERMISSIONS_ALL, text: Utils.localizeMessage('admin.general.policy.permissionsAllChannel', 'All channel members')},
- {value: Constants.PERMISSIONS_CHANNEL_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsChannelAdmin', 'Channel, Team and System Admins')},
- {value: Constants.PERMISSIONS_TEAM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsAdmin', 'Team and System Admins')},
- {value: Constants.PERMISSIONS_SYSTEM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsSystemAdmin', 'System Admins')}
- ]}
- label={
- <FormattedMessage
- id='admin.general.policy.restrictPublicChannelManagementTitle'
- defaultMessage='Enable public channel renaming for:'
- />
- }
- value={this.state.restrictPublicChannelManagement}
- onChange={this.handleChange}
- helpText={
- <FormattedMessage
- id='admin.general.policy.restrictPublicChannelManagementDescription'
- defaultMessage='Set policy on who can rename and set the header or purpose for public channels.'
- />
- }
- />
- <DropdownSetting
- id='restrictPublicChannelDeletion'
- values={[
- {value: Constants.PERMISSIONS_ALL, text: Utils.localizeMessage('admin.general.policy.permissionsAllChannel', 'All channel members')},
- {value: Constants.PERMISSIONS_CHANNEL_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsChannelAdmin', 'Channel, Team and System Admins')},
- {value: Constants.PERMISSIONS_TEAM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsAdmin', 'Team and System Admins')},
- {value: Constants.PERMISSIONS_SYSTEM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsSystemAdmin', 'System Admins')}
- ]}
- label={
- <FormattedMessage
- id='admin.general.policy.restrictPublicChannelDeletionTitle'
- defaultMessage='Enable public channel deletion for:'
- />
- }
- value={this.state.restrictPublicChannelDeletion}
- onChange={this.handleChange}
- helpText={
- <FormattedMessage
- id='admin.general.policy.restrictPublicChannelDeletionDescription'
- defaultMessage='Set policy on who can delete public channels. Deleted channels can be recovered from the database using a {commandLineToolLink}.'
- values={{
- commandLineToolLink: (
- <a
- href='https://docs.mattermost.com/administration/command-line-tools.html'
- target='_blank'
- rel='noopener noreferrer'
- >
- <FormattedMessage
- id='admin.general.policy.restrictPublicChannelDeletionCommandLineToolLink'
- defaultMessage='command line tool'
- />
- </a>
- )
- }}
- />
- }
- />
- <DropdownSetting
- id='restrictPrivateChannelCreation'
- values={[
- {value: Constants.PERMISSIONS_ALL, text: Utils.localizeMessage('admin.general.policy.permissionsAll', 'All team members')},
- {value: Constants.PERMISSIONS_TEAM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsAdmin', 'Team and System Admins')},
- {value: Constants.PERMISSIONS_SYSTEM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsSystemAdmin', 'System Admins')}
- ]}
- label={
- <FormattedMessage
- id='admin.general.policy.restrictPrivateChannelCreationTitle'
- defaultMessage='Enable private channel creation for:'
- />
- }
- value={this.state.restrictPrivateChannelCreation}
- onChange={this.handleChange}
- helpText={
- <FormattedMessage
- id='admin.general.policy.restrictPrivateChannelCreationDescription'
- defaultMessage='Set policy on who can create private channels.'
- />
- }
- />
- <DropdownSetting
- id='restrictPrivateChannelManagement'
- values={[
- {value: Constants.PERMISSIONS_ALL, text: Utils.localizeMessage('admin.general.policy.permissionsAllChannel', 'All channel members')},
- {value: Constants.PERMISSIONS_CHANNEL_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsChannelAdmin', 'Channel, Team and System Admins')},
- {value: Constants.PERMISSIONS_TEAM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsAdmin', 'Team and System Admins')},
- {value: Constants.PERMISSIONS_SYSTEM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsSystemAdmin', 'System Admins')}
- ]}
- label={
- <FormattedMessage
- id='admin.general.policy.restrictPrivateChannelManagementTitle'
- defaultMessage='Enable private channel renaming for:'
- />
- }
- value={this.state.restrictPrivateChannelManagement}
- onChange={this.handleChange}
- helpText={
- <FormattedMessage
- id='admin.general.policy.restrictPrivateChannelManagementDescription'
- defaultMessage='Set policy on who can rename and set the header or purpose for private channels.'
- />
- }
- />
- <DropdownSetting
- id='restrictPrivateChannelManageMembers'
- values={[
- {value: Constants.PERMISSIONS_ALL, text: Utils.localizeMessage('admin.general.policy.permissionsAllChannel', 'All channel members')},
- {value: Constants.PERMISSIONS_CHANNEL_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsChannelAdmin', 'Channel, Team and System Admins')},
- {value: Constants.PERMISSIONS_TEAM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsAdmin', 'Team and System Admins')},
- {value: Constants.PERMISSIONS_SYSTEM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsSystemAdmin', 'System Admins')}
- ]}
- label={
- <FormattedMessage
- id='admin.general.policy.restrictPrivateChannelManageMembersTitle'
- defaultMessage='Enable managing of private group members for:'
- />
- }
- value={this.state.restrictPrivateChannelManageMembers}
- onChange={this.handleChange}
- helpText={
- <FormattedMessage
- id='admin.general.policy.restrictPrivateChannelManageMembersDescription'
- defaultMessage='Set policy on who can add and remove members from private groups.'
- />
- }
- />
- <DropdownSetting
- id='restrictPrivateChannelDeletion'
- values={[
- {value: Constants.PERMISSIONS_ALL, text: Utils.localizeMessage('admin.general.policy.permissionsAllChannel', 'All channel members')},
- {value: Constants.PERMISSIONS_CHANNEL_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsChannelAdmin', 'Channel, Team and System Admins')},
- {value: Constants.PERMISSIONS_TEAM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsAdmin', 'Team and System Admins')},
- {value: Constants.PERMISSIONS_SYSTEM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsSystemAdmin', 'System Admins')}
- ]}
- label={
- <FormattedMessage
- id='admin.general.policy.restrictPrivateChannelDeletionTitle'
- defaultMessage='Enable private channel deletion for:'
- />
- }
- value={this.state.restrictPrivateChannelDeletion}
- onChange={this.handleChange}
- helpText={
- <FormattedMessage
- id='admin.general.policy.restrictPrivateChannelDeletionDescription'
- defaultMessage='Set policy on who can delete private channels. Deleted channels can be recovered from the database using a {commandLineToolLink}.'
- values={{
- commandLineToolLink: (
- <a
- href='https://docs.mattermost.com/administration/command-line-tools.html'
- target='_blank'
- rel='noopener noreferrer'
- >
- <FormattedMessage
- id='admin.general.policy.restrictPrivateChannelDeletionCommandLineToolLink'
- defaultMessage='command line tool'
- />
- </a>
- )
- }}
- />
- }
- />
- <RadioSetting
- id='restrictPostDelete'
- values={[
- {value: Constants.PERMISSIONS_DELETE_POST_ALL, text: Utils.localizeMessage('admin.general.policy.permissionsDeletePostAll', 'Message authors can delete their own messages, and Administrators can delete any message')},
- {value: Constants.PERMISSIONS_DELETE_POST_TEAM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsDeletePostAdmin', 'Team Admins and System Admins')},
- {value: Constants.PERMISSIONS_DELETE_POST_SYSTEM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsDeletePostSystemAdmin', 'System Admins')}
- ]}
- label={
- <FormattedMessage
- id='admin.general.policy.restrictPostDeleteTitle'
- defaultMessage='Allow which users to delete messages:'
- />
- }
- value={this.state.restrictPostDelete}
- onChange={this.handleChange}
- helpText={
- <FormattedHTMLMessage
- id='admin.general.policy.restrictPostDeleteDescription'
- defaultMessage='Set policy on who has permission to delete messages.'
- />
- }
- />
- <PostEditSetting
- id='allowEditPost'
- timeLimitId='postEditTimeLimit'
- label={
- <FormattedMessage
- id='admin.general.policy.allowEditPostTitle'
- defaultMessage='Allow users to edit their messages:'
- />
- }
- value={this.state.allowEditPost}
- timeLimitValue={this.state.postEditTimeLimit}
- onChange={this.handleChange}
- helpText={
- <FormattedHTMLMessage
- id='admin.general.policy.allowEditPostDescription'
- defaultMessage='Set policy on the length of time authors have to edit their messages after posting.'
- />
- }
- />
- <BooleanSetting
- id='enableBanner'
- label={
- <FormattedMessage
- id='admin.general.policy.enableBannerTitle'
- defaultMessage='Enable Announcement Banner:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.general.policy.enableBannerDesc'
- defaultMessage='Enable an announcement banner across all teams.'
- />
- }
- value={this.state.enableBanner}
- onChange={this.handleChange}
- />
- <TextSetting
- id='bannerText'
- label={
- <FormattedMessage
- id='admin.general.policy.bannerTextTitle'
- defaultMessage='Banner Text:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.general.policy.bannerTextDesc'
- defaultMessage='Text that will appear in the announcement banner.'
- />
- }
- value={this.state.bannerText}
- onChange={this.handleChange}
- disabled={!this.state.enableBanner}
- />
- <ColorSetting
- id='bannerColor'
- label={
- <FormattedMessage
- id='admin.general.policy.bannerColorTitle'
- defaultMessage='Banner Color:'
- />
- }
- value={this.state.bannerColor}
- onChange={this.handleChange}
- disabled={!this.state.enableBanner}
- />
- <ColorSetting
- id='bannerTextColor'
- label={
- <FormattedMessage
- id='admin.general.policy.bannerTextColorTitle'
- defaultMessage='Banner Text Color:'
- />
- }
- value={this.state.bannerTextColor}
- onChange={this.handleChange}
- disabled={!this.state.enableBanner}
- />
- <BooleanSetting
- id='allowBannerDismissal'
- label={
- <FormattedMessage
- id='admin.general.policy.allowBannerDismissalTitle'
- defaultMessage='Allow Banner Dismissal:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.general.policy.allowBannerDismissalDesc'
- defaultMessage='When true, users can dismiss the banner until its next update. When false, the banner is permanently visible until it is turned off by the System Admin.'
- />
- }
- value={this.state.allowBannerDismissal}
- onChange={this.handleChange}
- disabled={!this.state.enableBanner}
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/post_edit_setting.jsx b/webapp/components/admin_console/post_edit_setting.jsx
deleted file mode 100644
index 08fafc14b..000000000
--- a/webapp/components/admin_console/post_edit_setting.jsx
+++ /dev/null
@@ -1,101 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import Setting from './setting.jsx';
-
-import Constants from 'utils/constants.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-export default class PostEditSetting extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleChange = this.handleChange.bind(this);
- this.handleTimeLimitChange = this.handleTimeLimitChange.bind(this);
- }
-
- handleChange(e) {
- this.props.onChange(this.props.id, e.target.value);
- }
-
- handleTimeLimitChange(e) {
- this.props.onChange(this.props.timeLimitId, e.target.value);
- }
-
- render() {
- return (
- <Setting
- label={this.props.label}
- inputId={this.props.id}
- helpText={this.props.helpText}
- >
- <div className='radio'>
- <label>
- <input
- type='radio'
- value={Constants.ALLOW_EDIT_POST_ALWAYS}
- name={this.props.id}
- checked={this.props.value === Constants.ALLOW_EDIT_POST_ALWAYS}
- onChange={this.handleChange}
- disabled={this.props.disabled}
- />
- {Utils.localizeMessage('admin.general.policy.allowEditPostAlways', 'Any time')}
- </label>
- </div>
- <div className='radio'>
- <label>
- <input
- type='radio'
- value={Constants.ALLOW_EDIT_POST_NEVER}
- name={this.props.id}
- checked={this.props.value === Constants.ALLOW_EDIT_POST_NEVER}
- onChange={this.handleChange}
- disabled={this.props.disabled}
- />
- {Utils.localizeMessage('admin.general.policy.allowEditPostNever', 'Never')}
- </label>
- </div>
- <div className='radio form-inline'>
- <label>
- <input
- type='radio'
- value={Constants.ALLOW_EDIT_POST_TIME_LIMIT}
- name={this.props.id}
- checked={this.props.value === Constants.ALLOW_EDIT_POST_TIME_LIMIT}
- onChange={this.handleChange}
- disabled={this.props.disabled}
- />
- <input
- type='text'
- value={this.props.timeLimitValue}
- className='form-control'
- name={this.props.timeLimitId}
- onChange={this.handleTimeLimitChange}
- disabled={this.props.disabled || this.props.value !== Constants.ALLOW_EDIT_POST_TIME_LIMIT}
- />
- <span> {Utils.localizeMessage('admin.general.policy.allowEditPostTimeLimit', 'seconds after posting')}</span>
- </label>
- </div>
- </Setting>
- );
- }
-}
-
-PostEditSetting.defaultProps = {
- isDisabled: false
-};
-
-PostEditSetting.propTypes = {
- id: PropTypes.string.isRequired,
- timeLimitId: PropTypes.string.isRequired,
- label: PropTypes.node.isRequired,
- value: PropTypes.string.isRequired,
- timeLimitValue: PropTypes.number.isRequired,
- onChange: PropTypes.func.isRequired,
- disabled: PropTypes.bool,
- helpText: PropTypes.node
-};
diff --git a/webapp/components/admin_console/privacy_settings.jsx b/webapp/components/admin_console/privacy_settings.jsx
deleted file mode 100644
index 92fcb3e88..000000000
--- a/webapp/components/admin_console/privacy_settings.jsx
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import AdminSettings from './admin_settings.jsx';
-import BooleanSetting from './boolean_setting.jsx';
-import {FormattedMessage} from 'react-intl';
-import SettingsGroup from './settings_group.jsx';
-
-export default class PrivacySettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
- }
-
- getConfigFromState(config) {
- config.PrivacySettings.ShowEmailAddress = this.state.showEmailAddress;
- config.PrivacySettings.ShowFullName = this.state.showFullName;
-
- return config;
- }
-
- getStateFromConfig(config) {
- return {
- showEmailAddress: config.PrivacySettings.ShowEmailAddress,
- showFullName: config.PrivacySettings.ShowFullName
- };
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.general.privacy'
- defaultMessage='Privacy'
- />
- );
- }
-
- renderSettings() {
- return (
- <SettingsGroup>
- <BooleanSetting
- id='showEmailAddress'
- label={
- <FormattedMessage
- id='admin.privacy.showEmailTitle'
- defaultMessage='Show Email Address: '
- />
- }
- helpText={
- <FormattedMessage
- id='admin.privacy.showEmailDescription'
- defaultMessage='When false, hides the email address of members from everyone except System Administrators.'
- />
- }
- value={this.state.showEmailAddress}
- onChange={this.handleChange}
- />
- <BooleanSetting
- id='showFullName'
- label={
- <FormattedMessage
- id='admin.privacy.showFullNameTitle'
- defaultMessage='Show Full Name: '
- />
- }
- helpText={
- <FormattedMessage
- id='admin.privacy.showFullNameDescription'
- defaultMessage='When false, hides the full name of members from everyone except System Administrators. Username is shown in place of full name.'
- />
- }
- value={this.state.showFullName}
- onChange={this.handleChange}
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/public_link_settings.jsx b/webapp/components/admin_console/public_link_settings.jsx
deleted file mode 100644
index a10574353..000000000
--- a/webapp/components/admin_console/public_link_settings.jsx
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import AdminSettings from './admin_settings.jsx';
-import BooleanSetting from './boolean_setting.jsx';
-import {FormattedMessage} from 'react-intl';
-import GeneratedSetting from './generated_setting.jsx';
-import SettingsGroup from './settings_group.jsx';
-
-export default class PublicLinkSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
- }
-
- getConfigFromState(config) {
- config.FileSettings.EnablePublicLink = this.state.enablePublicLink;
- config.FileSettings.PublicLinkSalt = this.state.publicLinkSalt;
-
- return config;
- }
-
- getStateFromConfig(config) {
- return {
- enablePublicLink: config.FileSettings.EnablePublicLink,
- publicLinkSalt: config.FileSettings.PublicLinkSalt
- };
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.security.public_links'
- defaultMessage='Public Links'
- />
- );
- }
-
- renderSettings() {
- return (
- <SettingsGroup>
- <BooleanSetting
- id='enablePublicLink'
- label={
- <FormattedMessage
- id='admin.image.shareTitle'
- defaultMessage='Enable Public File Links: '
- />
- }
- helpText={
- <FormattedMessage
- id='admin.image.shareDescription'
- defaultMessage='Allow users to share public links to files and images.'
- />
- }
- value={this.state.enablePublicLink}
- onChange={this.handleChange}
- />
- <GeneratedSetting
- id='publicLinkSalt'
- label={
- <FormattedMessage
- id='admin.image.publicLinkTitle'
- defaultMessage='Public Link Salt:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.image.publicLinkDescription'
- defaultMessage='32-character salt added to signing of public image links. Randomly generated on install. Click "Regenerate" to create new salt.'
- />
- }
- value={this.state.publicLinkSalt}
- onChange={this.handleChange}
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/push_settings.jsx b/webapp/components/admin_console/push_settings.jsx
deleted file mode 100644
index b5f788c86..000000000
--- a/webapp/components/admin_console/push_settings.jsx
+++ /dev/null
@@ -1,233 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import Constants from 'utils/constants.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import AdminSettings from './admin_settings.jsx';
-import DropdownSetting from './dropdown_setting.jsx';
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-import SettingsGroup from './settings_group.jsx';
-import TextSetting from './text_setting.jsx';
-
-const PUSH_NOTIFICATIONS_OFF = 'off';
-const PUSH_NOTIFICATIONS_MHPNS = 'mhpns';
-const PUSH_NOTIFICATIONS_MTPNS = 'mtpns';
-const PUSH_NOTIFICATIONS_CUSTOM = 'custom';
-
-export default class PushSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.canSave = this.canSave.bind(this);
-
- this.handleAgreeChange = this.handleAgreeChange.bind(this);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
- }
-
- canSave() {
- return this.state.pushNotificationServerType !== PUSH_NOTIFICATIONS_MHPNS || this.state.agree;
- }
-
- handleAgreeChange(e) {
- this.setState({
- agree: e.target.checked
- });
- }
-
- handleChange(id, value) {
- if (id === 'pushNotificationServerType') {
- this.setState({
- agree: false
- });
-
- if (value === PUSH_NOTIFICATIONS_MHPNS) {
- this.setState({
- pushNotificationServer: Constants.MHPNS
- });
- } else if (value === PUSH_NOTIFICATIONS_MTPNS) {
- this.setState({
- pushNotificationServer: Constants.MTPNS
- });
- }
- }
-
- super.handleChange(id, value);
- }
-
- getConfigFromState(config) {
- config.EmailSettings.SendPushNotifications = this.state.pushNotificationServerType !== PUSH_NOTIFICATIONS_OFF;
- config.EmailSettings.PushNotificationServer = this.state.pushNotificationServer.trim();
- config.EmailSettings.PushNotificationContents = this.state.pushNotificationContents;
-
- return config;
- }
-
- getStateFromConfig(config) {
- let pushNotificationServerType = PUSH_NOTIFICATIONS_CUSTOM;
- let agree = false;
- if (!config.EmailSettings.SendPushNotifications) {
- pushNotificationServerType = PUSH_NOTIFICATIONS_OFF;
- } else if (config.EmailSettings.PushNotificationServer === Constants.MHPNS &&
- global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.MHPNS === 'true') {
- pushNotificationServerType = PUSH_NOTIFICATIONS_MHPNS;
- agree = true;
- } else if (config.EmailSettings.PushNotificationServer === Constants.MTPNS) {
- pushNotificationServerType = PUSH_NOTIFICATIONS_MTPNS;
- }
-
- let pushNotificationServer = config.EmailSettings.PushNotificationServer;
- if (pushNotificationServerType === PUSH_NOTIFICATIONS_MTPNS) {
- pushNotificationServer = Constants.MTPNS;
- } else if (pushNotificationServerType === PUSH_NOTIFICATIONS_MHPNS) {
- pushNotificationServer = Constants.MHPNS;
- }
-
- return {
- pushNotificationServerType,
- pushNotificationServer,
- pushNotificationContents: config.EmailSettings.PushNotificationContents,
- agree
- };
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.notifications.title'
- defaultMessage='Notification Settings'
- />
- );
- }
-
- renderSettings() {
- const pushNotificationServerTypes = [];
- pushNotificationServerTypes.push({value: PUSH_NOTIFICATIONS_OFF, text: Utils.localizeMessage('admin.email.pushOff', 'Do not send push notifications')});
- if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.MHPNS === 'true') {
- pushNotificationServerTypes.push({value: PUSH_NOTIFICATIONS_MHPNS, text: Utils.localizeMessage('admin.email.mhpns', 'Use encrypted, production-quality HPNS connection to iOS and Android apps')});
- }
- pushNotificationServerTypes.push({value: PUSH_NOTIFICATIONS_MTPNS, text: Utils.localizeMessage('admin.email.mtpns', 'Use iOS and Android apps on iTunes and Google Play with TPNS')});
- pushNotificationServerTypes.push({value: PUSH_NOTIFICATIONS_CUSTOM, text: Utils.localizeMessage('admin.email.selfPush', 'Manually enter Push Notification Service location')});
-
- let sendHelpText = null;
- let pushServerHelpText = null;
- if (this.state.pushNotificationServerType === PUSH_NOTIFICATIONS_OFF) {
- sendHelpText = (
- <FormattedHTMLMessage
- id='admin.email.pushOffHelp'
- defaultMessage='Please see <a href="https://about.mattermost.com/default-mobile-push-notifications/" target="_blank">documentation on push notifications</a> to learn more about setup options.'
- />
- );
- } else if (this.state.pushNotificationServerType === PUSH_NOTIFICATIONS_MHPNS) {
- pushServerHelpText = (
- <FormattedHTMLMessage
- id='admin.email.mhpnsHelp'
- defaultMessage='Download <a href="https://about.mattermost.com/mattermost-ios-app/" target="_blank">Mattermost iOS app</a> from iTunes. Download <a href="https://about.mattermost.com/mattermost-android-app/" target="_blank">Mattermost Android app</a> from Google Play. Learn more about the <a href="https://about.mattermost.com/default-hpns/" target="_blank">Mattermost Hosted Push Notification Service</a>.'
- />
- );
- } else if (this.state.pushNotificationServerType === PUSH_NOTIFICATIONS_MTPNS) {
- pushServerHelpText = (
- <FormattedHTMLMessage
- id='admin.email.mtpnsHelp'
- defaultMessage='Download <a href="https://about.mattermost.com/mattermost-ios-app/" target="_blank">Mattermost iOS app</a> from iTunes. Download <a href="https://about.mattermost.com/mattermost-android-app/" target="_blank">Mattermost Android app</a> from Google Play. Learn more about the <a href="https://about.mattermost.com/default-tpns/" target="_blank">Mattermost Test Push Notification Service</a>.'
- />
- );
- } else {
- pushServerHelpText = (
- <FormattedHTMLMessage
- id='admin.email.easHelp'
- defaultMessage='Learn more about compiling and deploying your own mobile apps from an <a href="https://about.mattermost.com/default-enterprise-app-store" target="_blank">Enterprise App Store</a>.'
- />
- );
- }
-
- let tosCheckbox;
- if (this.state.pushNotificationServerType === PUSH_NOTIFICATIONS_MHPNS) {
- tosCheckbox = (
- <div className='form-group'>
- <div className='col-sm-4'/>
- <div className='col-sm-8'>
- <input
- type='checkbox'
- ref='agree'
- checked={this.state.agree}
- onChange={this.handleAgreeChange}
- />
- <FormattedHTMLMessage
- id='admin.email.agreeHPNS'
- defaultMessage=' I understand and accept the Mattermost Hosted Push Notification Service <a href="https://about.mattermost.com/hpns-terms/" target="_blank">Terms of Service</a> and <a href="https://about.mattermost.com/hpns-privacy/" target="_blank">Privacy Policy</a>.'
- />
- </div>
- </div>
- );
- }
-
- return (
- <SettingsGroup
- header={
- <FormattedMessage
- id='admin.notifications.push'
- defaultMessage='Mobile Push'
- />
- }
- >
- <DropdownSetting
- id='pushNotificationServerType'
- values={pushNotificationServerTypes}
- label={
- <FormattedMessage
- id='admin.email.pushTitle'
- defaultMessage='Enable Push Notifications: '
- />
- }
- value={this.state.pushNotificationServerType}
- onChange={this.handleChange}
- helpText={sendHelpText}
- />
- {tosCheckbox}
- <TextSetting
- id='pushNotificationServer'
- label={
- <FormattedMessage
- id='admin.email.pushServerTitle'
- defaultMessage='Push Notification Server:'
- />
- }
- placeholder={Utils.localizeMessage('admin.email.pushServerEx', 'E.g.: "http://push-test.mattermost.com"')}
- helpText={pushServerHelpText}
- value={this.state.pushNotificationServer}
- onChange={this.handleChange}
- disabled={this.state.pushNotificationServerType !== PUSH_NOTIFICATIONS_CUSTOM}
- />
- <DropdownSetting
- id='pushNotificationContents'
- values={[
- {value: 'generic_no_channel', text: Utils.localizeMessage('admin.email.genericNoChannelPushNotification', '"Send generic description with only sender name')},
- {value: 'generic', text: Utils.localizeMessage('admin.email.genericPushNotification', 'Send generic description with sender and channel names')},
- {value: 'full', text: Utils.localizeMessage('admin.email.fullPushNotification', 'Send full message snippet')}
- ]}
- label={
- <FormattedMessage
- id='admin.email.pushContentTitle'
- defaultMessage='Push Notification Contents:'
- />
- }
- value={this.state.pushNotificationContents}
- onChange={this.handleChange}
- disabled={this.state.pushNotificationServerType === PUSH_NOTIFICATIONS_OFF}
- helpText={
- <FormattedHTMLMessage
- id='admin.email.pushContentDesc'
- defaultMessage='"Send generic description with only sender name" includes only the name of the person who sent the message in push notifications, with no information about channel name or message contents.<br /><br />"Send generic description with sender and channel names" includes the name of the person who sent the message and the channel it was sent in, but not the message text.<br /><br />"Send full message snippet" includes a message excerpt in push notifications, which may contain confidential information sent in messages. If your Push Notification Service is outside your firewall, it is *highly recommended* this option only be used with an "https" protocol to encrypt the connection.'
- />
- }
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/radio_setting.jsx b/webapp/components/admin_console/radio_setting.jsx
deleted file mode 100644
index 7a6c2e459..000000000
--- a/webapp/components/admin_console/radio_setting.jsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import Setting from './setting.jsx';
-
-export default class RadioSetting extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleChange = this.handleChange.bind(this);
- }
-
- handleChange(e) {
- this.props.onChange(this.props.id, e.target.value);
- }
-
- render() {
- const options = [];
- for (const {value, text} of this.props.values) {
- options.push(
- <div className='radio'>
- <label>
- <input
- type='radio'
- value={value}
- name={this.props.id}
- checked={value === this.props.value}
- onChange={this.handleChange}
- disabled={this.props.disabled}
- />
- {text}
- </label>
- </div>
- );
- }
-
- return (
- <Setting
- label={this.props.label}
- inputId={this.props.id}
- helpText={this.props.helpText}
- >
- {options}
- </Setting>
- );
- }
-}
-
-RadioSetting.defaultProps = {
- isDisabled: false
-};
-
-RadioSetting.propTypes = {
- id: PropTypes.string.isRequired,
- values: PropTypes.array.isRequired,
- label: PropTypes.node.isRequired,
- value: PropTypes.string.isRequired,
- onChange: PropTypes.func.isRequired,
- disabled: PropTypes.bool,
- helpText: PropTypes.node
-};
diff --git a/webapp/components/admin_console/rate_settings.jsx b/webapp/components/admin_console/rate_settings.jsx
deleted file mode 100644
index a0cf14f75..000000000
--- a/webapp/components/admin_console/rate_settings.jsx
+++ /dev/null
@@ -1,179 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import * as Utils from 'utils/utils.jsx';
-
-import AdminSettings from './admin_settings.jsx';
-import BooleanSetting from './boolean_setting.jsx';
-import {FormattedMessage} from 'react-intl';
-import SettingsGroup from './settings_group.jsx';
-import TextSetting from './text_setting.jsx';
-
-export default class RateSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
- }
-
- getConfigFromState(config) {
- config.RateLimitSettings.Enable = this.state.enable;
- config.RateLimitSettings.PerSec = this.parseIntNonZero(this.state.perSec);
- config.RateLimitSettings.MaxBurst = this.parseIntNonZero(this.state.maxBurst);
- config.RateLimitSettings.MemoryStoreSize = this.parseIntNonZero(this.state.memoryStoreSize);
- config.RateLimitSettings.VaryByRemoteAddr = this.state.varyByRemoteAddr;
- config.RateLimitSettings.VaryByHeader = this.state.varyByHeader;
-
- return config;
- }
-
- getStateFromConfig(config) {
- return {
- enable: config.RateLimitSettings.Enable,
- perSec: config.RateLimitSettings.PerSec,
- maxBurst: config.RateLimitSettings.MaxBurst,
- memoryStoreSize: config.RateLimitSettings.MemoryStoreSize,
- varyByRemoteAddr: config.RateLimitSettings.VaryByRemoteAddr,
- varyByHeader: config.RateLimitSettings.VaryByHeader
- };
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.rate.title'
- defaultMessage='Rate Limit Settings'
- />
- );
- }
-
- renderSettings() {
- return (
- <SettingsGroup>
- <div className='banner'>
- <div className='banner__content'>
- <FormattedMessage
- id='admin.rate.noteDescription'
- defaultMessage='Changing properties in this section will require a server restart before taking effect.'
- />
- </div>
- </div>
- <BooleanSetting
- id='enable'
- label={
- <FormattedMessage
- id='admin.rate.enableLimiterTitle'
- defaultMessage='Enable Rate Limiting: '
- />
- }
- helpText={
- <FormattedMessage
- id='admin.rate.enableLimiterDescription'
- defaultMessage='When true, APIs are throttled at rates specified below.'
- />
- }
- value={this.state.enable}
- onChange={this.handleChange}
- />
- <TextSetting
- id='perSec'
- label={
- <FormattedMessage
- id='admin.rate.queriesTitle'
- defaultMessage='Maximum Queries per Second:'
- />
- }
- placeholder={Utils.localizeMessage('admin.rate.queriesExample', 'Ex "10"')}
- helpText={
- <FormattedMessage
- id='admin.rate.queriesDescription'
- defaultMessage='Throttles API at this number of requests per second.'
- />
- }
- value={this.state.perSec}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <TextSetting
- id='maxBurst'
- label={
- <FormattedMessage
- id='admin.rate.maxBurst'
- defaultMessage='Maximum Burst Size:'
- />
- }
- placeholder={Utils.localizeMessage('admin.rate.maxBurstExample', 'Ex "100"')}
- helpText={
- <FormattedMessage
- id='admin.rate.maxBurstDescription'
- defaultMessage='Maximum number of requests allowed beyond the per second query limit.'
- />
- }
- value={this.state.maxBurst}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <TextSetting
- id='memoryStoreSize'
- label={
- <FormattedMessage
- id='admin.rate.memoryTitle'
- defaultMessage='Memory Store Size:'
- />
- }
- placeholder={Utils.localizeMessage('admin.rate.memoryExample', 'Ex "10000"')}
- helpText={
- <FormattedMessage
- id='admin.rate.memoryDescription'
- defaultMessage='Maximum number of users sessions connected to the system as determined by "Vary rate limit by remote address" and "Vary rate limit by HTTP header".'
- />
- }
- value={this.state.memoryStoreSize}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <BooleanSetting
- id='varyByRemoteAddr'
- label={
- <FormattedMessage
- id='admin.rate.remoteTitle'
- defaultMessage='Vary rate limit by remote address: '
- />
- }
- helpText={
- <FormattedMessage
- id='admin.rate.remoteDescription'
- defaultMessage='When true, rate limit API access by IP address.'
- />
- }
- value={this.state.varyByRemoteAddr}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <TextSetting
- id='varyByHeader'
- label={
- <FormattedMessage
- id='admin.rate.httpHeaderTitle'
- defaultMessage='Vary rate limit by HTTP header:'
- />
- }
- placeholder={Utils.localizeMessage('admin.rate.httpHeaderExample', 'Ex "X-Real-IP", "X-Forwarded-For"')}
- helpText={
- <FormattedMessage
- id='admin.rate.httpHeaderDescription'
- defaultMessage='When filled in, vary rate limiting by HTTP header field specified (e.g. when configuring NGINX set to "X-Real-IP", when configuring AmazonELB set to "X-Forwarded-For").'
- />
- }
- value={this.state.varyByHeader}
- onChange={this.handleChange}
- disabled={!this.state.enable || this.state.varyByRemoteAddr}
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/remove_file_setting.jsx b/webapp/components/admin_console/remove_file_setting.jsx
deleted file mode 100644
index ff453d9fc..000000000
--- a/webapp/components/admin_console/remove_file_setting.jsx
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import Setting from './setting.jsx';
-
-export default class RemoveFileSetting extends Setting {
- static get propTypes() {
- return {
- id: PropTypes.string.isRequired,
- label: PropTypes.node.isRequired,
- helpText: PropTypes.node,
- removeButtonText: PropTypes.node.isRequired,
- removingText: PropTypes.node,
- fileName: PropTypes.string.isRequired,
- onSubmit: PropTypes.func.isRequired,
- disabled: PropTypes.bool
- };
- }
-
- constructor(props) {
- super(props);
- this.handleRemove = this.handleRemove.bind(this);
- }
-
- handleRemove(e) {
- e.preventDefault();
-
- $(this.refs.remove_button).button('loading');
- this.props.onSubmit(this.props.id, () => {
- $(this.refs.remove_button).button('reset');
- });
- }
-
- render() {
- return (
- <Setting
- label={this.props.label}
- helpText={this.props.helpText}
- inputId={this.props.id}
- >
- <div>
- <div className='help-text remove-filename'>
- {this.props.fileName}
- </div>
- <button
- className='btn btn-danger'
- onClick={this.handleRemove}
- ref='remove_button'
- disabled={this.props.disabled}
- data-loading-text={`<span class='glyphicon glyphicon-refresh glyphicon-refresh-animate'></span> ${this.props.removingText}`}
- >
- {this.props.removeButtonText}
- </button>
- </div>
- </Setting>
- );
- }
-}
diff --git a/webapp/components/admin_console/request_button/request_button.jsx b/webapp/components/admin_console/request_button/request_button.jsx
deleted file mode 100644
index e78d0443d..000000000
--- a/webapp/components/admin_console/request_button/request_button.jsx
+++ /dev/null
@@ -1,262 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import {FormattedMessage} from 'react-intl';
-import PropTypes from 'prop-types';
-
-import * as Utils from 'utils/utils.jsx';
-
-/**
- * A button which, when clicked, performs an action and displays
- * its outcome as either success, or failure accompanied by the
- * `message` property of the `err` object.
- */
-export default class RequestButton extends React.Component {
- static propTypes = {
-
- /**
- * The action to be called to carry out the request.
- */
- requestAction: PropTypes.func.isRequired,
-
- /**
- * A component that displays help text for the request button.
- *
- * Typically, this will be a <FormattedMessage/>.
- */
- helpText: PropTypes.element.isRequired,
-
- /**
- * A component to be displayed on the button.
- *
- * Typically, this will be a <FormattedMessage/>
- */
- buttonText: PropTypes.element.isRequired,
-
- /**
- * The element to display as the field label.
- *
- * Typically, this will be a <FormattedMessage/>
- */
- label: PropTypes.element,
-
- /**
- * True if the button form control should be disabled, otherwise false.
- */
- disabled: PropTypes.bool,
-
- /**
- * True if the config needs to be saved before running the request, otherwise false.
- *
- * If set to true, the action provided in the `saveConfigAction` property will be
- * called before the action provided in the `requestAction` property, with the later
- * only being called if the former is successful.
- */
- saveNeeded: PropTypes.bool,
-
- /**
- * Action to be called to save the config, if saveNeeded is set to true.
- */
- saveConfigAction: PropTypes.func,
-
- /**
- * True if the success message should be show when the request completes successfully,
- * otherwise false.
- */
- showSuccessMessage: PropTypes.bool,
-
- /**
- * The message to show when the request completes successfully.
- */
- successMessage: PropTypes.shape({
-
- /**
- * The i18n string ID for the success message.
- */
- id: PropTypes.string.isRequired,
-
- /**
- * The i18n default value for the success message.
- */
- defaultMessage: PropTypes.string.isRequired
- }),
-
- /**
- * The message to show when the request returns an error.
- */
- errorMessage: PropTypes.shape({
-
- /**
- * The i18n string ID for the error message.
- */
- id: PropTypes.string.isRequired,
-
- /**
- * The i18n default value for the error message.
- *
- * The placeholder {error} may be used to include the error message returned
- * by the server in response to the failed request.
- */
- defaultMessage: PropTypes.string.isRequired
- }),
-
- /**
- * True if the {error} placeholder for the `errorMessage` property should include both
- * the `message` and `detailed_error` properties of the error returned from the server,
- * otherwise false to include only the `message` property.
- */
- includeDetailedError: PropTypes.bool,
-
- /**
- * An element to display adjacent to the request button.
- */
- alternativeActionElement: PropTypes.element
- }
-
- static defaultProps = {
- disabled: false,
- saveNeeded: false,
- showSuccessMessage: true,
- includeDetailedError: false,
- successMessage: {
- id: 'admin.requestButton.requestSuccess',
- defaultMessage: 'Test Successful'
- },
- errorMessage: {
- id: 'admin.requestButton.requestFailure',
- defaultMessage: 'Test Failure: {error}'
- }
- }
-
- constructor(props) {
- super(props);
-
- this.handleRequest = this.handleRequest.bind(this);
-
- this.state = {
- busy: false,
- fail: null,
- success: false
- };
- }
-
- handleRequest(e) {
- e.preventDefault();
-
- this.setState({
- busy: true,
- fail: null,
- success: false
- });
-
- const doRequest = () => { //eslint-disable-line func-style
- this.props.requestAction(
- () => {
- this.setState({
- busy: false,
- success: true
- });
- },
- (err) => {
- let errMsg = err.message;
- if (this.props.includeDetailedError) {
- errMsg += ' - ' + err.detailed_error;
- }
-
- this.setState({
- busy: false,
- fail: errMsg
- });
- }
- );
- };
-
- if (this.props.saveNeeded) {
- this.props.saveConfigAction(doRequest);
- } else {
- doRequest();
- }
- }
-
- render() {
- let message = null;
- if (this.state.fail) {
- message = (
- <div>
- <div className='alert alert-warning'>
- <i className='fa fa-warning'/>
- <FormattedMessage
- id={this.props.errorMessage.id}
- defaultMessage={this.props.errorMessage.defaultMessage}
- values={{
- error: this.state.fail
- }}
- />
- </div>
- </div>
- );
- } else if (this.state.success && this.props.showSuccessMessage) {
- message = (
- <div>
- <div className='alert alert-success'>
- <i className='fa fa-success'/>
- <FormattedMessage
- id={this.props.successMessage.id}
- defaultMessage={this.props.successMessage.defaultMessage}
- />
- </div>
- </div>
- );
- }
-
- let contents = null;
- if (this.state.busy) {
- contents = (
- <span>
- <span className='fa fa-refresh icon--rotate'/>
- {Utils.localizeMessage('admin.requestButton.loading', ' Loading...')}
- </span>
- );
- } else {
- contents = this.props.buttonText;
- }
-
- let widgetClassNames = 'col-sm-8';
- let label = null;
- if (this.props.label) {
- label = (
- <label
- className='control-label col-sm-4'
- >
- {this.props.label}
- </label>
- );
- } else {
- widgetClassNames = 'col-sm-offset-4 ' + widgetClassNames;
- }
-
- return (
- <div className='form-group'>
- {label}
- <div className={widgetClassNames}>
- <div>
- <button
- className='btn btn-default'
- onClick={this.handleRequest}
- disabled={this.props.disabled}
- >
- {contents}
- </button>
- {this.props.alternativeActionElement}
- {message}
- </div>
- <div className='help-text'>
- {this.props.helpText}
- </div>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/admin_console/reset_password_modal.jsx b/webapp/components/admin_console/reset_password_modal.jsx
deleted file mode 100644
index 0a38adda1..000000000
--- a/webapp/components/admin_console/reset_password_modal.jsx
+++ /dev/null
@@ -1,165 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import * as Utils from 'utils/utils.jsx';
-import {Modal} from 'react-bootstrap';
-
-import {FormattedMessage} from 'react-intl';
-
-import {adminResetPassword} from 'actions/admin_actions.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class ResetPasswordModal extends React.Component {
- static propTypes = {
- user: PropTypes.object,
- show: PropTypes.bool.isRequired,
- onModalSubmit: PropTypes.func,
- onModalDismissed: PropTypes.func
- };
-
- static defaultProps = {
- show: false
- };
-
- constructor(props) {
- super(props);
-
- this.doSubmit = this.doSubmit.bind(this);
- this.doCancel = this.doCancel.bind(this);
-
- this.state = {
- serverError: null
- };
- }
-
- doSubmit(e) {
- e.preventDefault();
- const password = this.refs.password.value;
-
- const passwordErr = Utils.isValidPassword(password);
- if (passwordErr) {
- this.setState({serverError: passwordErr});
- return;
- }
- this.setState({serverError: null});
-
- adminResetPassword(
- this.props.user.id,
- password,
- () => {
- this.props.onModalSubmit(this.props.user);
- },
- (err) => {
- this.setState({serverError: err.message});
- }
- );
- }
-
- doCancel() {
- this.setState({serverError: null});
- this.props.onModalDismissed();
- }
-
- render() {
- const user = this.props.user;
- if (user == null) {
- return <div/>;
- }
-
- let urlClass = 'input-group input-group--limit';
- let serverError = null;
-
- if (this.state.serverError) {
- urlClass += ' has-error';
- serverError = <div className='has-error'><p className='input__help error'>{this.state.serverError}</p></div>;
- }
-
- let title;
- if (user.auth_service) {
- title = (
- <FormattedMessage
- id='admin.reset_password.titleSwitch'
- defaultMessage='Switch Account to Email/Password'
- />
- );
- } else {
- title = (
- <FormattedMessage
- id='admin.reset_password.titleReset'
- defaultMessage='Reset Password'
- />
- );
- }
-
- return (
- <Modal
- show={this.props.show}
- onHide={this.doCancel}
- >
- <Modal.Header closeButton={true}>
- <Modal.Title>
- {title}
- </Modal.Title>
- </Modal.Header>
- <form
- role='form'
- className='form-horizontal'
- >
- <Modal.Body>
- <div className='form-group'>
- <div className='col-sm-10'>
- <div className={urlClass}>
- <span
- data-toggle='tooltip'
- title='New Password'
- className='input-group-addon'
- >
- <FormattedMessage
- id='admin.reset_password.newPassword'
- defaultMessage='New Password'
- />
- </span>
- <input
- type='password'
- ref='password'
- className='form-control'
- maxLength='22'
- autoFocus={true}
- tabIndex='1'
- />
- </div>
- {serverError}
- </div>
- </div>
- </Modal.Body>
- <Modal.Footer>
- <button
- type='button'
- className='btn btn-default'
- onClick={this.doCancel}
- >
- <FormattedMessage
- id='admin.reset_password.close'
- defaultMessage='Close'
- />
- </button>
- <button
- onClick={this.doSubmit}
- type='submit'
- className='btn btn-primary'
- tabIndex='2'
- >
- <FormattedMessage
- id='admin.reset_password.select'
- defaultMessage='Select'
- />
- </button>
- </Modal.Footer>
- </form>
- </Modal>
- );
- }
-}
diff --git a/webapp/components/admin_console/revoke_token_button/index.js b/webapp/components/admin_console/revoke_token_button/index.js
deleted file mode 100644
index 6fada1bcc..000000000
--- a/webapp/components/admin_console/revoke_token_button/index.js
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {revokeUserAccessToken} from 'mattermost-redux/actions/users';
-
-import RevokeTokenButton from './revoke_token_button.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- revokeUserAccessToken
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(RevokeTokenButton);
diff --git a/webapp/components/admin_console/revoke_token_button/revoke_token_button.jsx b/webapp/components/admin_console/revoke_token_button/revoke_token_button.jsx
deleted file mode 100644
index 4829a0cde..000000000
--- a/webapp/components/admin_console/revoke_token_button/revoke_token_button.jsx
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {FormattedMessage} from 'react-intl';
-
-import {trackEvent} from 'actions/diagnostics_actions.jsx';
-
-export default class RevokeTokenButton extends React.PureComponent {
- static propTypes = {
-
- /*
- * Token id to revoke
- */
- tokenId: PropTypes.string.isRequired,
-
- /*
- * Function to call on error
- */
- onError: PropTypes.func.isRequired,
-
- actions: PropTypes.shape({
-
- /**
- * Function to revoke a user access token
- */
- revokeUserAccessToken: PropTypes.func.isRequired
- }).isRequired
- };
-
- handleClick = async (e) => {
- e.preventDefault();
-
- const {error} = await this.props.actions.revokeUserAccessToken(this.props.tokenId);
- trackEvent('system_console', 'revoke_user_access_token');
-
- if (error) {
- this.props.onError(error.message);
- }
- }
-
- render() {
- return (
- <button
- className='btn btn-danger'
- onClick={this.handleClick}
- >
- <FormattedMessage
- id='admin.revoke_token_button.delete'
- defaultMessage='Delete'
- />
- </button>
- );
- }
-}
diff --git a/webapp/components/admin_console/saml_settings.jsx b/webapp/components/admin_console/saml_settings.jsx
deleted file mode 100644
index 98c02e571..000000000
--- a/webapp/components/admin_console/saml_settings.jsx
+++ /dev/null
@@ -1,584 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import AdminSettings from './admin_settings.jsx';
-import BooleanSetting from './boolean_setting.jsx';
-import TextSetting from './text_setting.jsx';
-import FileUploadSetting from './file_upload_setting.jsx';
-import RemoveFileSetting from './remove_file_setting.jsx';
-
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-import SettingsGroup from './settings_group.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-
-import * as AdminActions from 'actions/admin_actions.jsx';
-
-export default class SamlSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
- this.uploadCertificate = this.uploadCertificate.bind(this);
- this.removeCertificate = this.removeCertificate.bind(this);
- }
-
- getConfigFromState(config) {
- config.SamlSettings.Enable = this.state.enable;
- config.SamlSettings.Verify = this.state.verify;
- config.SamlSettings.Encrypt = this.state.encrypt;
- config.SamlSettings.IdpUrl = this.state.idpUrl;
- config.SamlSettings.IdpDescriptorUrl = this.state.idpDescriptorUrl;
- config.SamlSettings.AssertionConsumerServiceURL = this.state.assertionConsumerServiceURL;
- config.SamlSettings.IdpCertificateFile = this.state.idpCertificateFile;
- config.SamlSettings.PublicCertificateFile = this.state.publicCertificateFile;
- config.SamlSettings.PrivateKeyFile = this.state.privateKeyFile;
- config.SamlSettings.FirstNameAttribute = this.state.firstNameAttribute;
- config.SamlSettings.LastNameAttribute = this.state.lastNameAttribute;
- config.SamlSettings.EmailAttribute = this.state.emailAttribute;
- config.SamlSettings.UsernameAttribute = this.state.usernameAttribute;
- config.SamlSettings.NicknameAttribute = this.state.nicknameAttribute;
- config.SamlSettings.PositionAttribute = this.state.positionAttribute;
- config.SamlSettings.LocaleAttribute = this.state.localeAttribute;
- config.SamlSettings.LoginButtonText = this.state.loginButtonText;
-
- return config;
- }
-
- getStateFromConfig(config) {
- const settings = config.SamlSettings;
-
- return {
- enable: settings.Enable,
- verify: settings.Verify,
- encrypt: settings.Encrypt,
- idpUrl: settings.IdpUrl,
- idpDescriptorUrl: settings.IdpDescriptorUrl,
- assertionConsumerServiceURL: settings.AssertionConsumerServiceURL,
- idpCertificateFile: settings.IdpCertificateFile,
- publicCertificateFile: settings.PublicCertificateFile,
- privateKeyFile: settings.PrivateKeyFile,
- firstNameAttribute: settings.FirstNameAttribute,
- lastNameAttribute: settings.LastNameAttribute,
- emailAttribute: settings.EmailAttribute,
- usernameAttribute: settings.UsernameAttribute,
- nicknameAttribute: settings.NicknameAttribute,
- positionAttribute: settings.PositionAttribute,
- localeAttribute: settings.LocaleAttribute,
- loginButtonText: settings.LoginButtonText
- };
- }
-
- componentWillMount() {
- AdminActions.samlCertificateStatus(
- (data) => {
- const files = {};
- if (!data.idp_certificate_file) {
- files.idpCertificateFile = '';
- }
-
- if (!data.public_certificate_file) {
- files.publicCertificateFile = '';
- }
-
- if (!data.private_key_file) {
- files.privateKeyFile = '';
- }
- this.setState(files);
- }
- );
- }
-
- uploadCertificate(id, file, callback) {
- const complete = () => {
- const fileName = file.name;
- this.handleChange(id, fileName);
- this.setState({[id]: fileName, [`${id}Error`]: null});
- if (callback && typeof callback === 'function') {
- callback();
- }
- };
-
- function fail(error) {
- if (callback && typeof callback === 'function') {
- callback(error.message);
- }
- }
-
- if (id === 'idpCertificateFile') {
- AdminActions.uploadIdpSamlCertificate(file, complete, fail);
- } else if (id === 'publicCertificateFile') {
- AdminActions.uploadPublicSamlCertificate(file, complete, fail);
- } else if (id === 'privateKeyFile') {
- AdminActions.uploadPrivateSamlCertificate(file, complete, fail);
- }
- }
-
- removeCertificate(id, callback) {
- const complete = () => {
- this.handleChange(id, '');
- this.setState({[id]: null, [`${id}Error`]: null});
- };
-
- const fail = (error) => {
- if (callback && typeof callback === 'function') {
- callback();
- }
- this.setState({[id]: null, [`${id}Error`]: error.message});
- };
-
- if (id === 'idpCertificateFile') {
- AdminActions.removeIdpSamlCertificate(complete, fail);
- } else if (id === 'publicCertificateFile') {
- AdminActions.removePublicSamlCertificate(complete, fail);
- } else if (id === 'privateKeyFile') {
- AdminActions.removePrivateSamlCertificate(complete, fail);
- }
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.authentication.saml'
- defaultMessage='SAML 2.0'
- />
- );
- }
-
- renderSettings() {
- const licenseEnabled = global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.SAML === 'true';
- if (!licenseEnabled) {
- return null;
- }
-
- let idpCert;
- let privKey;
- let pubCert;
-
- if (this.state.idpCertificateFile) {
- idpCert = (
- <RemoveFileSetting
- id='idpCertificateFile'
- label={
- <FormattedMessage
- id='admin.saml.idpCertificateFileTitle'
- defaultMessage='Identity Provider Public Certificate:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.saml.idpCertificateFileRemoveDesc'
- defaultMessage='Remove the public authentication certificate issued by your Identity Provider.'
- />
- }
- removeButtonText={Utils.localizeMessage('admin.saml.remove.idp_certificate', 'Remove Identity Provider Certificate')}
- removingText={Utils.localizeMessage('admin.saml.removing.certificate', 'Removing Certificate...')}
- fileName={this.state.idpCertificateFile}
- onSubmit={this.removeCertificate}
- disabled={!this.state.enable}
- />
- );
- } else {
- idpCert = (
- <FileUploadSetting
- id='idpCertificateFile'
- label={
- <FormattedMessage
- id='admin.saml.idpCertificateFileTitle'
- defaultMessage='Identity Provider Public Certificate:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.saml.idpCertificateFileDesc'
- defaultMessage='The public authentication certificate issued by your Identity Provider.'
- />
- }
- uploadingText={Utils.localizeMessage('admin.saml.uploading.certificate', 'Uploading Certificate...')}
- disabled={!this.state.enable}
- fileType='.crt,.cer'
- onSubmit={this.uploadCertificate}
- error={this.state.idpCertificateFileError}
- />
- );
- }
-
- if (this.state.privateKeyFile) {
- privKey = (
- <RemoveFileSetting
- id='privateKeyFile'
- label={
- <FormattedMessage
- id='admin.saml.privateKeyFileTitle'
- defaultMessage='Service Provider Private Key:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.saml.privateKeyFileFileRemoveDesc'
- defaultMessage='Remove the private key used to decrypt SAML Assertions from the Identity Provider.'
- />
- }
- removeButtonText={Utils.localizeMessage('admin.saml.remove.privKey', 'Remove Service Provider Private Key')}
- removingText={Utils.localizeMessage('admin.saml.removing.privKey', 'Removing Private Key...')}
- fileName={this.state.privateKeyFile}
- onSubmit={this.removeCertificate}
- disabled={!this.state.enable || !this.state.encrypt}
- />
- );
- } else {
- privKey = (
- <FileUploadSetting
- id='privateKeyFile'
- label={
- <FormattedMessage
- id='admin.saml.privateKeyFileTitle'
- defaultMessage='Service Provider Private Key:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.saml.privateKeyFileFileDesc'
- defaultMessage='The private key used to decrypt SAML Assertions from the Identity Provider.'
- />
- }
- uploadingText={Utils.localizeMessage('admin.saml.uploading.privateKey', 'Uploading Private Key...')}
- disabled={!this.state.enable || !this.state.encrypt}
- fileType='.key'
- onSubmit={this.uploadCertificate}
- error={this.state.privateKeyFileError}
- />
- );
- }
-
- if (this.state.publicCertificateFile) {
- pubCert = (
- <RemoveFileSetting
- id='publicCertificateFile'
- label={
- <FormattedMessage
- id='admin.saml.publicCertificateFileTitle'
- defaultMessage='Service Provider Public Certificate:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.saml.publicCertificateFileRemoveDesc'
- defaultMessage='Remove the certificate used to generate the signature on a SAML request to the Identity Provider for a service provider initiated SAML login, when Mattermost is the Service Provider.'
- />
- }
- removeButtonText={Utils.localizeMessage('admin.saml.remove.sp_certificate', 'Remove Service Provider Certificate')}
- removingText={Utils.localizeMessage('admin.saml.removing.certificate', 'Removing Certificate...')}
- fileName={this.state.publicCertificateFile}
- onSubmit={this.removeCertificate}
- disabled={!this.state.enable || !this.state.encrypt}
- />
- );
- } else {
- pubCert = (
- <FileUploadSetting
- id='publicCertificateFile'
- label={
- <FormattedMessage
- id='admin.saml.publicCertificateFileTitle'
- defaultMessage='Service Provider Public Certificate:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.saml.publicCertificateFileDesc'
- defaultMessage='The certificate used to generate the signature on a SAML request to the Identity Provider for a service provider initiated SAML login, when Mattermost is the Service Provider.'
- />
- }
- uploadingText={Utils.localizeMessage('admin.saml.uploading.certificate', 'Uploading Certificate...')}
- disabled={!this.state.enable || !this.state.encrypt}
- fileType='.crt,.cer'
- onSubmit={this.uploadCertificate}
- error={this.state.publicCertificateFileError}
- />
- );
- }
-
- return (
- <SettingsGroup>
- <div className='banner'>
- <div className='banner__content'>
- <FormattedHTMLMessage
- id='admin.saml.bannerDesc'
- defaultMessage='User attributes in SAML server, including user deactivation or removal, are updated in Mattermost during user login. Learn more at: <a href=\"https://docs.mattermost.com/deployment/sso-saml.html\">https://docs.mattermost.com/deployment/sso-saml.html</a>'
- />
- </div>
- </div>
- <BooleanSetting
- id='enable'
- label={
- <FormattedMessage
- id='admin.saml.enableTitle'
- defaultMessage='Enable Login With SAML 2.0:'
- />
- }
- helpText={
- <FormattedHTMLMessage
- id='admin.saml.enableDescription'
- defaultMessage='When true, Mattermost allows login using SAML 2.0. Please see <a href="http://docs.mattermost.com/deployment/sso-saml.html" target="_blank">documentation</a> to learn more about configuring SAML for Mattermost.'
- />
- }
- value={this.state.enable}
- onChange={this.handleChange}
- />
- <TextSetting
- id='idpUrl'
- label={
- <FormattedMessage
- id='admin.saml.idpUrlTitle'
- defaultMessage='SAML SSO URL:'
- />
- }
- placeholder={Utils.localizeMessage('admin.saml.idpUrlEx', 'Ex "https://idp.example.org/SAML2/SSO/Login"')}
- helpText={
- <FormattedMessage
- id='admin.saml.idpUrlDesc'
- defaultMessage='The URL where Mattermost sends a SAML request to start login sequence.'
- />
- }
- value={this.state.idpUrl}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <TextSetting
- id='idpDescriptorUrl'
- label={
- <FormattedMessage
- id='admin.saml.idpDescriptorUrlTitle'
- defaultMessage='Identity Provider Issuer URL:'
- />
- }
- placeholder={Utils.localizeMessage('admin.saml.idpDescriptorUrlEx', 'Ex "https://idp.example.org/SAML2/issuer"')}
- helpText={
- <FormattedMessage
- id='admin.saml.idpDescriptorUrlDesc'
- defaultMessage='The issuer URL for the Identity Provider you use for SAML requests.'
- />
- }
- value={this.state.idpDescriptorUrl}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- {idpCert}
- <BooleanSetting
- id='verify'
- label={
- <FormattedMessage
- id='admin.saml.verifyTitle'
- defaultMessage='Verify Signature:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.saml.verifyDescription'
- defaultMessage='When false, Mattermost will not verify that the signature sent from a SAML Response matches the Service Provider Login URL. Not recommended for production environments. For testing only.'
- />
- }
- value={this.state.verify}
- disabled={!this.state.enable}
- onChange={this.handleChange}
- />
- <TextSetting
- id='assertionConsumerServiceURL'
- label={
- <FormattedMessage
- id='admin.saml.assertionConsumerServiceURLTitle'
- defaultMessage='Service Provider Login URL:'
- />
- }
- placeholder={Utils.localizeMessage('admin.saml.assertionConsumerServiceURLEx', 'Ex "https://<your-mattermost-url>/login/sso/saml"')}
- helpText={
- <FormattedMessage
- id='admin.saml.assertionConsumerServiceURLDesc'
- defaultMessage='Enter https://<your-mattermost-url>/login/sso/saml. Make sure you use HTTP or HTTPS in your URL depending on your server configuration. This field is also known as the Assertion Consumer Service URL.'
- />
- }
- value={this.state.assertionConsumerServiceURL}
- onChange={this.handleChange}
- disabled={!this.state.enable || !this.state.verify}
- />
- <BooleanSetting
- id='encrypt'
- label={
- <FormattedMessage
- id='admin.saml.encryptTitle'
- defaultMessage='Enable Encryption:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.saml.encryptDescription'
- defaultMessage='When false, Mattermost will not decrypt SAML Assertions encrypted with your Service Provider Public Certificate. Not recommended for production environments. For testing only.'
- />
- }
- value={this.state.encrypt}
- disabled={!this.state.enable}
- onChange={this.handleChange}
- />
- {privKey}
- {pubCert}
- <TextSetting
- id='emailAttribute'
- label={
- <FormattedMessage
- id='admin.saml.emailAttrTitle'
- defaultMessage='Email Attribute:'
- />
- }
- placeholder={Utils.localizeMessage('admin.saml.emailAttrEx', 'Ex "Email" or "PrimaryEmail"')}
- helpText={
- <FormattedMessage
- id='admin.saml.emailAttrDesc'
- defaultMessage='The attribute in the SAML Assertion that will be used to populate the email addresses of users in Mattermost.'
- />
- }
- value={this.state.emailAttribute}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <TextSetting
- id='usernameAttribute'
- label={
- <FormattedMessage
- id='admin.saml.usernameAttrTitle'
- defaultMessage='Username Attribute:'
- />
- }
- placeholder={Utils.localizeMessage('admin.saml.usernameAttrEx', 'Ex "Username"')}
- helpText={
- <FormattedMessage
- id='admin.saml.usernameAttrDesc'
- defaultMessage='The attribute in the SAML Assertion that will be used to populate the username field in Mattermost.'
- />
- }
- value={this.state.usernameAttribute}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <TextSetting
- id='firstNameAttribute'
- label={
- <FormattedMessage
- id='admin.saml.firstnameAttrTitle'
- defaultMessage='First Name Attribute:'
- />
- }
- placeholder={Utils.localizeMessage('admin.saml.firstnameAttrEx', 'Ex "FirstName"')}
- helpText={
- <FormattedMessage
- id='admin.saml.firstnameAttrDesc'
- defaultMessage='(Optional) The attribute in the SAML Assertion that will be used to populate the first name of users in Mattermost.'
- />
- }
- value={this.state.firstNameAttribute}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <TextSetting
- id='lastNameAttribute'
- label={
- <FormattedMessage
- id='admin.saml.lastnameAttrTitle'
- defaultMessage='Last Name Attribute:'
- />
- }
- placeholder={Utils.localizeMessage('admin.saml.lastnameAttrEx', 'Ex "LastName"')}
- helpText={
- <FormattedMessage
- id='admin.saml.lastnameAttrDesc'
- defaultMessage='(Optional) The attribute in the SAML Assertion that will be used to populate the last name of users in Mattermost.'
- />
- }
- value={this.state.lastNameAttribute}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <TextSetting
- id='nicknameAttribute'
- label={
- <FormattedMessage
- id='admin.saml.nicknameAttrTitle'
- defaultMessage='Nickname Attribute:'
- />
- }
- placeholder={Utils.localizeMessage('admin.saml.nicknameAttrEx', 'Ex "Nickname"')}
- helpText={
- <FormattedMessage
- id='admin.saml.nicknameAttrDesc'
- defaultMessage='(Optional) The attribute in the SAML Assertion that will be used to populate the nickname of users in Mattermost.'
- />
- }
- value={this.state.nicknameAttribute}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <TextSetting
- id='positionAttribute'
- label={
- <FormattedMessage
- id='admin.saml.positionAttrTitle'
- defaultMessage='Position Attribute:'
- />
- }
- placeholder={Utils.localizeMessage('admin.saml.positionAttrEx', 'E.g.: "Role"')}
- helpText={
- <FormattedMessage
- id='admin.saml.positionAttrDesc'
- defaultMessage='(Optional) The attribute in the SAML Assertion that will be used to populate the position of users in Mattermost.'
- />
- }
- value={this.state.positionAttribute}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <TextSetting
- id='localeAttribute'
- label={
- <FormattedMessage
- id='admin.saml.localeAttrTitle'
- defaultMessage='Preferred Language Attribute:'
- />
- }
- placeholder={Utils.localizeMessage('admin.saml.localeAttrEx', 'Ex "Locale" or "PrimaryLanguage"')}
- helpText={
- <FormattedMessage
- id='admin.saml.localeAttrDesc'
- defaultMessage='(Optional) The attribute in the SAML Assertion that will be used to populate the language of users in Mattermost.'
- />
- }
- value={this.state.localeAttribute}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- <TextSetting
- id='loginButtonText'
- label={
- <FormattedMessage
- id='admin.saml.loginButtonTextTitle'
- defaultMessage='Login Button Text:'
- />
- }
- placeholder={Utils.localizeMessage('admin.saml.loginButtonTextEx', 'Ex "With OKTA"')}
- helpText={
- <FormattedMessage
- id='admin.saml.loginButtonTextDesc'
- defaultMessage='(Optional) The text that appears in the login button on the login page. Defaults to "With SAML".'
- />
- }
- value={this.state.loginButtonText}
- onChange={this.handleChange}
- disabled={!this.state.enable}
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/save_button.jsx b/webapp/components/admin_console/save_button.jsx
deleted file mode 100644
index 4d2b562da..000000000
--- a/webapp/components/admin_console/save_button.jsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import {FormattedMessage} from 'react-intl';
-
-export default class SaveButton extends React.Component {
- static get propTypes() {
- return {
- saving: PropTypes.bool.isRequired,
- disabled: PropTypes.bool
- };
- }
-
- static get defaultProps() {
- return {
- disabled: false
- };
- }
-
- render() {
- const {saving, disabled, ...props} = this.props; // eslint-disable-line no-use-before-define
-
- let contents;
- if (saving) {
- contents = (
- <span>
- <span className='icon fa fa-refresh icon--rotate'/>
- <FormattedMessage
- id='admin.saving'
- defaultMessage='Saving Config...'
- />
- </span>
- );
- } else {
- contents = (
- <FormattedMessage
- id='admin.save'
- defaultMessage='Save'
- />
- );
- }
-
- let className = 'save-button btn';
- if (!disabled) {
- className += ' btn-primary';
- }
-
- return (
- <button
- type='submit'
- id='saveSetting'
- className={className}
- disabled={disabled}
- {...props}
- >
- {contents}
- </button>
- );
- }
-}
diff --git a/webapp/components/admin_console/server_logs/index.js b/webapp/components/admin_console/server_logs/index.js
deleted file mode 100644
index 3adacaf1a..000000000
--- a/webapp/components/admin_console/server_logs/index.js
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getLogs} from 'mattermost-redux/actions/admin';
-
-import * as Selectors from 'mattermost-redux/selectors/entities/admin';
-
-import Logs from './logs.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps,
- logs: Selectors.getLogs(state)
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getLogs
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(Logs);
diff --git a/webapp/components/admin_console/server_logs/logs.jsx b/webapp/components/admin_console/server_logs/logs.jsx
deleted file mode 100644
index b0d8b38ac..000000000
--- a/webapp/components/admin_console/server_logs/logs.jsx
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import LoadingScreen from 'components/loading_screen.jsx';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {FormattedMessage} from 'react-intl';
-
-export default class Logs extends React.PureComponent {
- static propTypes = {
-
- /*
- * Array of logs to render
- */
- logs: PropTypes.arrayOf(PropTypes.string).isRequired,
-
- actions: PropTypes.shape({
-
- /*
- * Function to fetch logs
- */
- getLogs: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.state = {
- loadingLogs: true
- };
- }
-
- componentDidMount() {
- this.refs.logPanel.focus();
-
- this.props.actions.getLogs().then(
- () => this.setState({loadingLogs: false})
- );
- }
-
- componentDidUpdate() {
- // Scroll Down to get the latest logs
- var node = this.refs.logPanel;
- node.scrollTop = node.scrollHeight;
- node.focus();
- }
-
- reload = () => {
- this.setState({loadingLogs: true});
- this.props.actions.getLogs().then(
- () => this.setState({loadingLogs: false})
- );
- }
-
- render() {
- let content = null;
-
- if (this.state.loadingLogs) {
- content = <LoadingScreen/>;
- } else {
- content = [];
-
- for (let i = 0; i < this.props.logs.length; i++) {
- const style = {
- whiteSpace: 'nowrap',
- fontFamily: 'monospace'
- };
-
- if (this.props.logs[i].indexOf('[EROR]') > 0) {
- style.color = 'red';
- }
-
- content.push(<br key={'br_' + i}/>);
- content.push(
- <span
- key={'log_' + i}
- style={style}
- >
- {this.props.logs[i]}
- </span>
- );
- }
- }
-
- return (
- <div className='panel'>
- <h3 className='admin-console-header'>
- <FormattedMessage
- id='admin.logs.title'
- defaultMessage='Server Logs'
- />
- </h3>
- <div className='banner'>
- <div className='banner__content'>
- <FormattedMessage
- id='admin.logs.bannerDesc'
- defaultMessage='To look up users by User ID or Token ID, go to Reporting > Users and paste the ID into the search filter.'
- />
- </div>
- </div>
- <button
- type='submit'
- className='btn btn-primary'
- onClick={this.reload}
- >
- <FormattedMessage
- id='admin.logs.reload'
- defaultMessage='Reload'
- />
- </button>
- <div
- tabIndex='-1'
- ref='logPanel'
- className='log__panel'
- >
- {content}
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/admin_console/session_settings.jsx b/webapp/components/admin_console/session_settings.jsx
deleted file mode 100644
index a36126789..000000000
--- a/webapp/components/admin_console/session_settings.jsx
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import * as Utils from 'utils/utils.jsx';
-
-import AdminSettings from './admin_settings.jsx';
-import {FormattedMessage} from 'react-intl';
-import SettingsGroup from './settings_group.jsx';
-import TextSetting from './text_setting.jsx';
-
-export default class SessionSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
- }
-
- getConfigFromState(config) {
- config.ServiceSettings.SessionLengthWebInDays = this.parseIntNonZero(this.state.sessionLengthWebInDays);
- config.ServiceSettings.SessionLengthMobileInDays = this.parseIntNonZero(this.state.sessionLengthMobileInDays);
- config.ServiceSettings.SessionLengthSSOInDays = this.parseIntNonZero(this.state.sessionLengthSSOInDays);
- config.ServiceSettings.SessionCacheInMinutes = this.parseIntNonZero(this.state.sessionCacheInMinutes);
-
- return config;
- }
-
- getStateFromConfig(config) {
- return {
- sessionLengthWebInDays: config.ServiceSettings.SessionLengthWebInDays,
- sessionLengthMobileInDays: config.ServiceSettings.SessionLengthMobileInDays,
- sessionLengthSSOInDays: config.ServiceSettings.SessionLengthSSOInDays,
- sessionCacheInMinutes: config.ServiceSettings.SessionCacheInMinutes
- };
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.security.session'
- defaultMessage='Sessions'
- />
- );
- }
-
- renderSettings() {
- return (
- <SettingsGroup>
- <TextSetting
- id='sessionLengthWebInDays'
- label={
- <FormattedMessage
- id='admin.service.webSessionDays'
- defaultMessage='Session length AD/LDAP and email (days):'
- />
- }
- placeholder={Utils.localizeMessage('admin.service.sessionDaysEx', 'Ex "30"')}
- helpText={
- <FormattedMessage
- id='admin.service.webSessionDaysDesc'
- defaultMessage='The number of days from the last time a user entered their credentials to the expiry of the users session. After changing this setting, the new session length will take effect after the next time the user enters their credentials.'
- />
- }
- value={this.state.sessionLengthWebInDays}
- onChange={this.handleChange}
- />
- <TextSetting
- id='sessionLengthMobileInDays'
- label={
- <FormattedMessage
- id='admin.service.mobileSessionDays'
- defaultMessage='Session length mobile (days):'
- />
- }
- placeholder={Utils.localizeMessage('admin.service.sessionDaysEx', 'Ex "30"')}
- helpText={
- <FormattedMessage
- id='admin.service.mobileSessionDaysDesc'
- defaultMessage='The number of days from the last time a user entered their credentials to the expiry of the users session. After changing this setting, the new session length will take effect after the next time the user enters their credentials.'
- />
- }
- value={this.state.sessionLengthMobileInDays}
- onChange={this.handleChange}
- />
- <TextSetting
- id='sessionLengthSSOInDays'
- label={
- <FormattedMessage
- id='admin.service.ssoSessionDays'
- defaultMessage='Session length SSO (days):'
- />
- }
- placeholder={Utils.localizeMessage('admin.service.sessionDaysEx', 'Ex "30"')}
- helpText={
- <FormattedMessage
- id='admin.service.ssoSessionDaysDesc'
- defaultMessage='The number of days from the last time a user entered their credentials to the expiry of the users session. If the authentication method is SAML or GitLab, the user may automatically be logged back in to Mattermost if they are already logged in to SAML or GitLab. After changing this setting, the setting will take effect after the next time the user enters their credentials. '
- />
- }
- value={this.state.sessionLengthSSOInDays}
- onChange={this.handleChange}
- />
- <TextSetting
- id='sessionCacheInMinutes'
- label={
- <FormattedMessage
- id='admin.service.sessionCache'
- defaultMessage='Session Cache (minutes):'
- />
- }
- placeholder={Utils.localizeMessage('admin.service.sessionDaysEx', 'Ex "30"')}
- helpText={
- <FormattedMessage
- id='admin.service.sessionCacheDesc'
- defaultMessage='The number of minutes to cache a session in memory.'
- />
- }
- value={this.state.sessionCacheInMinutes}
- onChange={this.handleChange}
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/setting.jsx b/webapp/components/admin_console/setting.jsx
deleted file mode 100644
index 9ef6554ac..000000000
--- a/webapp/components/admin_console/setting.jsx
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React, {PureComponent} from 'react';
-import PropTypes from 'prop-types';
-
-export default class Settings extends PureComponent {
- static propTypes = {
- inputId: PropTypes.string,
- label: PropTypes.node.isRequired,
- children: PropTypes.node.isRequired,
- helpText: PropTypes.node
- };
-
- render() {
- const {children, helpText, inputId, label} = this.props;
-
- return (
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor={inputId}
- >
- {label}
- </label>
- <div className='col-sm-8'>
- {children}
- <div className='help-text'>
- {helpText}
- </div>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/admin_console/settings_group.jsx b/webapp/components/admin_console/settings_group.jsx
deleted file mode 100644
index 79f8dac97..000000000
--- a/webapp/components/admin_console/settings_group.jsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-export default class SettingsGroup extends React.Component {
- static get propTypes() {
- return {
- show: PropTypes.bool.isRequired,
- header: PropTypes.node,
- children: PropTypes.node
- };
- }
-
- static get defaultProps() {
- return {
- show: true
- };
- }
-
- render() {
- if (!this.props.show) {
- return null;
- }
-
- let header = null;
- if (this.props.header) {
- header = (
- <h4>
- {this.props.header}
- </h4>
- );
- }
-
- return (
- <div className='admin-settings__group'>
- {header}
- {this.props.children}
- </div>
- );
- }
-}
diff --git a/webapp/components/admin_console/signup_settings.jsx b/webapp/components/admin_console/signup_settings.jsx
deleted file mode 100644
index b3ae6fe60..000000000
--- a/webapp/components/admin_console/signup_settings.jsx
+++ /dev/null
@@ -1,117 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import AdminSettings from './admin_settings.jsx';
-import BooleanSetting from './boolean_setting.jsx';
-import {FormattedMessage} from 'react-intl';
-import GeneratedSetting from './generated_setting.jsx';
-import SettingsGroup from './settings_group.jsx';
-
-export default class SignupSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
- }
-
- getConfigFromState(config) {
- config.EmailSettings.RequireEmailVerification = this.state.requireEmailVerification;
- config.EmailSettings.InviteSalt = this.state.inviteSalt;
- config.TeamSettings.EnableOpenServer = this.state.enableOpenServer;
-
- return config;
- }
-
- getStateFromConfig(config) {
- return {
- requireEmailVerification: config.EmailSettings.RequireEmailVerification,
- inviteSalt: config.EmailSettings.InviteSalt,
- enableOpenServer: config.TeamSettings.EnableOpenServer
- };
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.security.signup'
- defaultMessage='Signup'
- />
- );
- }
-
- renderSettings() {
- return (
- <SettingsGroup>
- <BooleanSetting
- id='requireEmailVerification'
- label={
- <FormattedMessage
- id='admin.email.requireVerificationTitle'
- defaultMessage='Require Email Verification: '
- />
- }
- helpText={
- <FormattedMessage
- id='admin.email.requireVerificationDescription'
- defaultMessage='Typically set to true in production. When true, Mattermost requires email verification after account creation prior to allowing login. Developers may set this field to false so skip sending verification emails for faster development.'
- />
- }
- value={this.state.requireEmailVerification}
- onChange={this.handleChange}
- disabled={this.state.sendEmailNotifications}
- disabledText={
- <FormattedMessage
- id='admin.security.requireEmailVerification.disabled'
- defaultMessage='Email verification cannot be changed while sending emails is disabled.'
- />
- }
- />
- <GeneratedSetting
- id='inviteSalt'
- label={
- <FormattedMessage
- id='admin.email.inviteSaltTitle'
- defaultMessage='Email Invite Salt:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.email.inviteSaltDescription'
- defaultMessage='32-character salt added to signing of email invites. Randomly generated on install. Click "Regenerate" to create new salt.'
- />
- }
- value={this.state.inviteSalt}
- onChange={this.handleChange}
- disabled={this.state.sendEmailNotifications}
- disabledText={
- <FormattedMessage
- id='admin.security.inviteSalt.disabled'
- defaultMessage='Invite salt cannot be changed while sending emails is disabled.'
- />
- }
- />
- <BooleanSetting
- id='enableOpenServer'
- label={
- <FormattedMessage
- id='admin.team.openServerTitle'
- defaultMessage='Enable Open Server: '
- />
- }
- helpText={
- <FormattedMessage
- id='admin.team.openServerDescription'
- defaultMessage='When true, anyone can signup for a user account on this server without the need to be invited.'
- />
- }
- value={this.state.enableOpenServer}
- onChange={this.handleChange}
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/storage_settings.jsx b/webapp/components/admin_console/storage_settings.jsx
deleted file mode 100644
index 4b20a8b93..000000000
--- a/webapp/components/admin_console/storage_settings.jsx
+++ /dev/null
@@ -1,340 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import * as Utils from 'utils/utils.jsx';
-
-import AdminSettings from './admin_settings.jsx';
-import DropdownSetting from './dropdown_setting.jsx';
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-import SettingsGroup from './settings_group.jsx';
-import TextSetting from './text_setting.jsx';
-import BooleanSetting from './boolean_setting.jsx';
-
-const DRIVER_LOCAL = 'local';
-const DRIVER_S3 = 'amazons3';
-
-export default class StorageSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
- }
-
- getConfigFromState(config) {
- config.FileSettings.EnableFileAttachments = this.state.enableFileAttachments;
- config.FileSettings.EnableMobileUpload = this.state.enableMobileUpload;
- config.FileSettings.EnableMobileDownload = this.state.enableMobileDownload;
- config.FileSettings.MaxFileSize = this.parseInt(this.state.maxFileSize) * 1024 * 1024;
- config.FileSettings.DriverName = this.state.driverName;
- config.FileSettings.Directory = this.state.directory;
- config.FileSettings.AmazonS3AccessKeyId = this.state.amazonS3AccessKeyId;
- config.FileSettings.AmazonS3SecretAccessKey = this.state.amazonS3SecretAccessKey;
- config.FileSettings.AmazonS3Bucket = this.state.amazonS3Bucket;
- config.FileSettings.AmazonS3Endpoint = this.state.amazonS3Endpoint;
- config.FileSettings.AmazonS3SSL = this.state.amazonS3SSL;
- config.FileSettings.AmazonS3SSE = this.state.amazonS3SSE;
- config.FileSettings.AmazonS3Trace = this.state.amazonS3Trace;
-
- return config;
- }
-
- getStateFromConfig(config) {
- return {
- enableFileAttachments: config.FileSettings.EnableFileAttachments,
- enableMobileUpload: config.FileSettings.EnableMobileUpload,
- enableMobileDownload: config.FileSettings.EnableMobileDownload,
- maxFileSize: config.FileSettings.MaxFileSize / 1024 / 1024,
- driverName: config.FileSettings.DriverName,
- directory: config.FileSettings.Directory,
- amazonS3AccessKeyId: config.FileSettings.AmazonS3AccessKeyId,
- amazonS3SecretAccessKey: config.FileSettings.AmazonS3SecretAccessKey,
- amazonS3Bucket: config.FileSettings.AmazonS3Bucket,
- amazonS3Endpoint: config.FileSettings.AmazonS3Endpoint,
- amazonS3SSL: config.FileSettings.AmazonS3SSL,
- amazonS3SSE: config.FileSettings.AmazonS3SSE,
- amazonS3Trace: config.FileSettings.AmazonS3Trace
- };
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.files.storage'
- defaultMessage='Storage'
- />
- );
- }
-
- renderSettings() {
- let amazonSSEComp;
- const mobileUploadDownloadSettings = [];
- if (window.mm_license.IsLicensed === 'true' && window.mm_license.Compliance === 'true') {
- mobileUploadDownloadSettings.push(
- <BooleanSetting
- key='enableMobileUpload'
- id='enableMobileUpload'
- label={
- <FormattedMessage
- id='admin.file.enableMobileUploadTitle'
- defaultMessage='Allow File Uploads on Mobile:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.file.enableMobileUploadDesc'
- defaultMessage='When false, disables file uploads on mobile apps. If Allow File Sharing is set to true, users can still upload files from a mobile web browser.'
- />
- }
- value={this.state.enableMobileUpload}
- onChange={this.handleChange}
- disabled={!this.state.enableFileAttachments}
- />
- );
-
- mobileUploadDownloadSettings.push(
- <BooleanSetting
- key='enableMobileDownload'
- id='enableMobileDownload'
- label={
- <FormattedMessage
- id='admin.file.enableMobileDownloadTitle'
- defaultMessage='Allow File Downloads on Mobile:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.file.enableMobileDownloadDesc'
- defaultMessage='When false, disables file downloads on mobile apps. Users can still download files from a mobile web browser.'
- />
- }
- value={this.state.enableMobileDownload}
- onChange={this.handleChange}
- disabled={!this.state.enableFileAttachments}
- />
- );
-
- amazonSSEComp =
- (
- <BooleanSetting
- id='amazonS3SSE'
- label={
- <FormattedMessage
- id='admin.image.amazonS3SSETitle'
- defaultMessage='Enable Server-Side Encryption for Amazon S3:'
- />
- }
- helpText={
- <FormattedHTMLMessage
- id='admin.image.amazonS3SSEDescription'
- defaultMessage='When true, encrypt files in Amazon S3 using server-side encryption with Amazon S3-managed keys. See <a href="https://about.mattermost.com/default-server-side-encryption" target="_blank">documentation</a> to learn more.'
- />
- }
- value={this.state.amazonS3SSE}
- onChange={this.handleChange}
- disabled={this.state.driverName !== DRIVER_S3}
- />
- );
- }
-
- return (
- <SettingsGroup>
- <DropdownSetting
- id='driverName'
- values={[
- {value: DRIVER_LOCAL, text: Utils.localizeMessage('admin.image.storeLocal', 'Local File System')},
- {value: DRIVER_S3, text: Utils.localizeMessage('admin.image.storeAmazonS3', 'Amazon S3')}
- ]}
- label={
- <FormattedMessage
- id='admin.image.storeTitle'
- defaultMessage='File Storage System:'
- />
- }
- helpText={
- <FormattedHTMLMessage
- id='admin.image.storeDescription'
- defaultMessage='Storage system where files and image attachments are saved.<br /><br />
- Selecting "Amazon S3" enables fields to enter your Amazon credentials and bucket details.<br /><br />
- Selecting "Local File System" enables the field to specify a local file directory.'
- />
- }
- value={this.state.driverName}
- onChange={this.handleChange}
- />
- <TextSetting
- id='directory'
- label={
- <FormattedMessage
- id='admin.image.localTitle'
- defaultMessage='Local Storage Directory:'
- />
- }
- placeholder={Utils.localizeMessage('admin.image.localExample', 'Ex "./data/"')}
- helpText={
- <FormattedMessage
- id='admin.image.localDescription'
- defaultMessage='Directory to which files and images are written. If blank, defaults to ./data/.'
- />
- }
- value={this.state.directory}
- onChange={this.handleChange}
- disabled={this.state.driverName !== DRIVER_LOCAL}
- />
- <TextSetting
- id='amazonS3AccessKeyId'
- label={
- <FormattedMessage
- id='admin.image.amazonS3IdTitle'
- defaultMessage='Amazon S3 Access Key ID:'
- />
- }
- placeholder={Utils.localizeMessage('admin.image.amazonS3IdExample', 'Ex "AKIADTOVBGERKLCBV"')}
- helpText={
- <FormattedMessage
- id='admin.image.amazonS3IdDescription'
- defaultMessage='Obtain this credential from your Amazon EC2 administrator.'
- />
- }
- value={this.state.amazonS3AccessKeyId}
- onChange={this.handleChange}
- disabled={this.state.driverName !== DRIVER_S3}
- />
- <TextSetting
- id='amazonS3SecretAccessKey'
- label={
- <FormattedMessage
- id='admin.image.amazonS3SecretTitle'
- defaultMessage='Amazon S3 Secret Access Key:'
- />
- }
- placeholder={Utils.localizeMessage('admin.image.amazonS3SecretExample', 'Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"')}
- helpText={
- <FormattedMessage
- id='admin.image.amazonS3SecretDescription'
- defaultMessage='Obtain this credential from your Amazon EC2 administrator.'
- />
- }
- value={this.state.amazonS3SecretAccessKey}
- onChange={this.handleChange}
- disabled={this.state.driverName !== DRIVER_S3}
- />
- <TextSetting
- id='amazonS3Bucket'
- label={
- <FormattedMessage
- id='admin.image.amazonS3BucketTitle'
- defaultMessage='Amazon S3 Bucket:'
- />
- }
- placeholder={Utils.localizeMessage('admin.image.amazonS3BucketExample', 'Ex "mattermost-media"')}
- helpText={
- <FormattedMessage
- id='admin.image.amazonS3BucketDescription'
- defaultMessage='Name you selected for your S3 bucket in AWS.'
- />
- }
- value={this.state.amazonS3Bucket}
- onChange={this.handleChange}
- disabled={this.state.driverName !== DRIVER_S3}
- />
- <TextSetting
- id='amazonS3Endpoint'
- label={
- <FormattedMessage
- id='admin.image.amazonS3EndpointTitle'
- defaultMessage='Amazon S3 Endpoint:'
- />
- }
- placeholder={Utils.localizeMessage('admin.image.amazonS3EndpointExample', 'Ex "s3.amazonaws.com"')}
- helpText={
- <FormattedMessage
- id='admin.image.amazonS3EndpointDescription'
- defaultMessage='Hostname of your S3 Compatible Storage provider. Defaults to `s3.amazonaws.com`.'
- />
- }
- value={this.state.amazonS3Endpoint}
- onChange={this.handleChange}
- disabled={this.state.driverName !== DRIVER_S3}
- />
- <BooleanSetting
- id='amazonS3SSL'
- label={
- <FormattedMessage
- id='admin.image.amazonS3SSLTitle'
- defaultMessage='Enable Secure Amazon S3 Connections:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.image.amazonS3SSLDescription'
- defaultMessage='When false, allow insecure connections to Amazon S3. Defaults to secure connections only.'
- />
- }
- value={this.state.amazonS3SSL}
- onChange={this.handleChange}
- disabled={this.state.driverName !== DRIVER_S3}
- />
- {amazonSSEComp}
- <BooleanSetting
- id='amazonS3Trace'
- label={
- <FormattedMessage
- id='admin.image.amazonS3TraceTitle'
- defaultMessage='Enable Amazon S3 Debugging:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.image.amazonS3TraceDescription'
- defaultMessage='(Development Mode) When true, log additional debugging information to the system logs.'
- />
- }
- value={this.state.amazonS3Trace}
- onChange={this.handleChange}
- disabled={this.state.driverName !== DRIVER_S3}
- />
- <BooleanSetting
- id='enableFileAttachments'
- label={
- <FormattedMessage
- id='admin.file.enableFileAttachments'
- defaultMessage='Allow File Sharing:'
- />
- }
- helpText={
- <FormattedMessage
- id='admin.file.enableFileAttachmentsDesc'
- defaultMessage='When false, disables file sharing on the server. All file and image uploads on messages are forbidden across clients and devices, including mobile.'
- />
- }
- value={this.state.enableFileAttachments}
- onChange={this.handleChange}
- />
- {mobileUploadDownloadSettings}
- <TextSetting
- id='maxFileSize'
- label={
- <FormattedMessage
- id='admin.image.maxFileSizeTitle'
- defaultMessage='Maximum File Size:'
- />
- }
- placeholder={Utils.localizeMessage('admin.image.maxFileSizeExample', '50')}
- helpText={
- <FormattedMessage
- id='admin.image.maxFileSizeDescription'
- defaultMessage='Maximum file size for message attachments in megabytes. Caution: Verify server memory can support your setting choice. Large file sizes increase the risk of server crashes and failed uploads due to network interruptions.'
- />
- }
- value={this.state.maxFileSize}
- onChange={this.handleChange}
- disabled={!this.state.enableFileAttachments}
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/system_users/index.js b/webapp/components/admin_console/system_users/index.js
deleted file mode 100644
index 261a11d7e..000000000
--- a/webapp/components/admin_console/system_users/index.js
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getTeams, getTeamStats} from 'mattermost-redux/actions/teams';
-import {getUser, getUserAccessToken} from 'mattermost-redux/actions/users';
-
-import {getTeamsList} from 'mattermost-redux/selectors/entities/teams';
-
-import SystemUsers from './system_users.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- teams: getTeamsList(state),
- ...ownProps
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getTeams,
- getTeamStats,
- getUser,
- getUserAccessToken
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(SystemUsers);
diff --git a/webapp/components/admin_console/system_users/system_users.jsx b/webapp/components/admin_console/system_users/system_users.jsx
deleted file mode 100644
index db8400217..000000000
--- a/webapp/components/admin_console/system_users/system_users.jsx
+++ /dev/null
@@ -1,372 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {FormattedMessage} from 'react-intl';
-
-import {
- loadProfiles,
- loadProfilesAndTeamMembers,
- loadProfilesWithoutTeam,
- searchUsers
-} from 'actions/user_actions.jsx';
-
-import AnalyticsStore from 'stores/analytics_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-
-import {reloadIfServerVersionChanged} from 'actions/global_actions.jsx';
-import {getStandardAnalytics} from 'actions/admin_actions.jsx';
-import {Constants, StatTypes, UserSearchOptions} from 'utils/constants.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import SystemUsersList from './system_users_list.jsx';
-
-import store from 'stores/redux_store.jsx';
-import {searchProfiles, searchProfilesInTeam} from 'mattermost-redux/selectors/entities/users';
-
-const ALL_USERS = '';
-const NO_TEAM = 'no_team';
-
-const USER_ID_LENGTH = 26;
-const USERS_PER_PAGE = 50;
-
-export default class SystemUsers extends React.Component {
- static propTypes = {
-
- /*
- * Array of team objects
- */
- teams: PropTypes.arrayOf(PropTypes.object).isRequired,
-
- actions: PropTypes.shape({
-
- /*
- * Function to get teams
- */
- getTeams: PropTypes.func.isRequired,
-
- /*
- * Function to get statistics for a team
- */
- getTeamStats: PropTypes.func.isRequired,
-
- /*
- * Function to get a user
- */
- getUser: PropTypes.func.isRequired,
-
- /*
- * Function to get a user access token
- */
- getUserAccessToken: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.updateTotalUsersFromStore = this.updateTotalUsersFromStore.bind(this);
- this.updateUsersFromStore = this.updateUsersFromStore.bind(this);
-
- this.loadDataForTeam = this.loadDataForTeam.bind(this);
- this.loadComplete = this.loadComplete.bind(this);
-
- this.handleTeamChange = this.handleTeamChange.bind(this);
- this.handleTermChange = this.handleTermChange.bind(this);
- this.nextPage = this.nextPage.bind(this);
-
- this.doSearch = this.doSearch.bind(this);
- this.search = this.search.bind(this);
- this.getUserById = this.getUserById.bind(this);
-
- this.renderFilterRow = this.renderFilterRow.bind(this);
-
- this.state = {
- totalUsers: AnalyticsStore.getAllSystem()[StatTypes.TOTAL_USERS],
- users: UserStore.getProfileList(),
-
- teamId: ALL_USERS,
- term: '',
- loading: true,
- searching: false
- };
- }
-
- componentDidMount() {
- AnalyticsStore.addChangeListener(this.updateTotalUsersFromStore);
- TeamStore.addStatsChangeListener(this.updateTotalUsersFromStore);
-
- UserStore.addChangeListener(this.updateUsersFromStore);
- UserStore.addInTeamChangeListener(this.updateUsersFromStore);
- UserStore.addWithoutTeamChangeListener(this.updateUsersFromStore);
-
- this.loadDataForTeam(this.state.teamId);
- this.props.actions.getTeams(0, 1000).then(reloadIfServerVersionChanged);
- }
-
- componentWillUpdate(nextProps, nextState) {
- const nextTeamId = nextState.teamId;
-
- if (this.state.teamId !== nextTeamId) {
- this.updateTotalUsersFromStore(nextTeamId);
- this.updateUsersFromStore(nextTeamId, nextState.term);
-
- this.loadDataForTeam(nextTeamId);
- }
- }
-
- componentWillUnmount() {
- AnalyticsStore.removeChangeListener(this.updateTotalUsersFromStore);
- TeamStore.removeStatsChangeListener(this.updateTotalUsersFromStore);
-
- UserStore.removeChangeListener(this.updateUsersFromStore);
- UserStore.removeInTeamChangeListener(this.updateUsersFromStore);
- UserStore.removeWithoutTeamChangeListener(this.updateUsersFromStore);
- }
-
- updateTotalUsersFromStore(teamId = this.state.teamId) {
- if (teamId === ALL_USERS) {
- this.setState({
- totalUsers: AnalyticsStore.getAllSystem()[StatTypes.TOTAL_USERS]
- });
- } else if (teamId === NO_TEAM) {
- this.setState({
- totalUsers: 0
- });
- } else {
- this.setState({
- totalUsers: TeamStore.getStats(teamId).total_member_count
- });
- }
- }
-
- updateUsersFromStore(teamId = this.state.teamId, term = this.state.term) {
- if (term) {
- let users;
- if (teamId) {
- users = searchProfilesInTeam(store.getState(), teamId, term);
- } else {
- users = searchProfiles(store.getState(), term);
- }
-
- if (users.length === 0 && UserStore.hasProfile(term)) {
- users = [UserStore.getProfile(term)];
- }
-
- this.setState({users});
- return;
- }
-
- if (teamId === ALL_USERS) {
- this.setState({users: UserStore.getProfileList(false, true)});
- } else if (teamId === NO_TEAM) {
- this.setState({users: UserStore.getProfileListWithoutTeam()});
- } else {
- this.setState({users: UserStore.getProfileListInTeam(this.state.teamId)});
- }
- }
-
- loadDataForTeam(teamId) {
- if (this.state.term) {
- this.search(this.state.term, teamId);
- return;
- }
-
- if (teamId === ALL_USERS) {
- loadProfiles(0, Constants.PROFILE_CHUNK_SIZE, this.loadComplete);
- getStandardAnalytics();
- } else if (teamId === NO_TEAM) {
- loadProfilesWithoutTeam(0, Constants.PROFILE_CHUNK_SIZE, this.loadComplete);
- } else {
- loadProfilesAndTeamMembers(0, Constants.PROFILE_CHUNK_SIZE, teamId, this.loadComplete);
- this.props.actions.getTeamStats(teamId);
- }
- }
-
- loadComplete() {
- this.setState({loading: false});
- }
-
- handleTeamChange(e) {
- this.setState({teamId: e.target.value});
- }
-
- handleTermChange(term) {
- this.setState({term});
- }
-
- nextPage(page) {
- // Paging isn't supported while searching
-
- if (this.state.teamId === ALL_USERS) {
- loadProfiles(page, USERS_PER_PAGE, this.loadComplete);
- } else if (this.state.teamId === NO_TEAM) {
- loadProfilesWithoutTeam(page + 1, USERS_PER_PAGE, this.loadComplete);
- } else {
- loadProfilesAndTeamMembers(page + 1, USERS_PER_PAGE, this.state.teamId, this.loadComplete);
- }
- }
-
- search(term, teamId = this.state.teamId) {
- if (term === '') {
- this.updateUsersFromStore(teamId, term);
-
- this.setState({
- loading: false
- });
-
- this.searchTimeoutId = '';
- return;
- }
-
- this.doSearch(teamId, term);
- }
-
- doSearch(teamId, term, now = false) {
- clearTimeout(this.searchTimeoutId);
- this.term = term;
-
- this.setState({loading: true});
-
- const options = {
- [UserSearchOptions.ALLOW_INACTIVE]: true
- };
- if (teamId === NO_TEAM) {
- options[UserSearchOptions.WITHOUT_TEAM] = true;
- }
-
- this.searchTimeoutId = setTimeout(
- () => {
- searchUsers(
- term,
- teamId,
- options,
- (users) => {
- if (users.length === 0 && term.length === USER_ID_LENGTH) {
- // This term didn't match any users name, but it does look like it might be a user's ID
- this.getUserByTokenOrId(term);
- } else {
- this.setState({loading: false});
- }
- },
- () => {
- this.setState({loading: false});
- }
- );
- },
- now ? 0 : Constants.SEARCH_TIMEOUT_MILLISECONDS
- );
- }
-
- getUserById(id) {
- if (UserStore.hasProfile(id)) {
- this.setState({loading: false});
- return;
- }
-
- this.props.actions.getUser(id).then(
- () => {
- this.setState({
- loading: false
- });
- }
- );
- }
-
- getUserByTokenOrId = async (id) => {
- if (global.window.mm_config.EnableUserAccessTokens === 'true') {
- const {data} = await this.props.actions.getUserAccessToken(id);
-
- if (data) {
- this.term = data.user_id;
- this.setState({term: data.user_id});
- this.updateUsersFromStore(this.state.teamId, data.user_id);
- this.getUserById(data.user_id);
- return;
- }
- }
-
- this.getUserById(id);
- }
-
- renderFilterRow(doSearch) {
- const teams = this.props.teams.map((team) => {
- return (
- <option
- key={team.id}
- value={team.id}
- >
- {team.display_name}
- </option>
- );
- });
-
- return (
- <div className='system-users__filter-row'>
- <div className='system-users__filter'>
- <input
- id='searchUsers'
- ref='filter'
- className='form-control filter-textbox'
- placeholder={Utils.localizeMessage('filtered_user_list.search', 'Search users')}
- onInput={doSearch}
- />
- </div>
- <label>
- <span className='system-users__team-filter-label'>
- <FormattedMessage
- id='filtered_user_list.show'
- defaultMessage='Filter:'
- />
- </span>
- <select
- className='form-control system-users__team-filter'
- onChange={this.handleTeamChange}
- value={this.state.teamId}
- >
- <option value={ALL_USERS}>{Utils.localizeMessage('admin.system_users.allUsers', 'All Users')}</option>
- <option value={NO_TEAM}>{Utils.localizeMessage('admin.system_users.noTeams', 'No Teams')}</option>
- {teams}
- </select>
- </label>
- </div>
- );
- }
-
- render() {
- let users = null;
- if (!this.state.loading) {
- users = this.state.users;
- }
-
- return (
- <div className='wrapper--fixed'>
- <h3 className='admin-console-header'>
- <FormattedMessage
- id='admin.system_users.title'
- defaultMessage='{siteName} Users'
- values={{
- siteName: global.mm_config.SiteName
- }}
- />
- </h3>
- <div className='more-modal__list member-list-holder'>
- <SystemUsersList
- renderFilterRow={this.renderFilterRow}
- search={this.search}
- nextPage={this.nextPage}
- users={users}
- usersPerPage={USERS_PER_PAGE}
- total={this.state.totalUsers}
- teams={this.props.teams}
- teamId={this.state.teamId}
- term={this.state.term}
- onTermChange={this.handleTermChange}
- />
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/admin_console/system_users/system_users_dropdown.jsx b/webapp/components/admin_console/system_users/system_users_dropdown.jsx
deleted file mode 100644
index 79ccc8b31..000000000
--- a/webapp/components/admin_console/system_users/system_users_dropdown.jsx
+++ /dev/null
@@ -1,529 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ConfirmModal from 'components/confirm_modal.jsx';
-
-import TeamStore from 'stores/team_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-
-import Constants from 'utils/constants.jsx';
-import * as Utils from 'utils/utils.jsx';
-import {updateActive} from 'actions/user_actions.jsx';
-import {adminResetMfa} from 'actions/admin_actions.jsx';
-import * as UserUtils from 'mattermost-redux/utils/user_utils';
-
-import {FormattedMessage} from 'react-intl';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class SystemUsersDropdown extends React.Component {
- static propTypes = {
-
- /*
- * User to manage with dropdown
- */
- user: PropTypes.object.isRequired,
-
- /*
- * Function to open password reset, takes user as an argument
- */
- doPasswordReset: PropTypes.func.isRequired,
-
- /*
- * Function to open manage teams, takes user as an argument
- */
- doManageTeams: PropTypes.func.isRequired,
-
- /*
- * Function to open manage roles, takes user as an argument
- */
- doManageRoles: PropTypes.func.isRequired,
-
- /*
- * Function to open manage tokens, takes user as an argument
- */
- doManageTokens: PropTypes.func.isRequired
- };
-
- constructor(props) {
- super(props);
-
- this.state = {
- serverError: null,
- showDemoteModal: false,
- showDeactivateMemberModal: false,
- user: null,
- role: null
- };
- }
-
- handleMakeActive = (e) => {
- e.preventDefault();
- updateActive(this.props.user.id, true, null,
- (err) => {
- this.setState({serverError: err.message});
- }
- );
- }
-
- handleManageTeams = (e) => {
- e.preventDefault();
-
- this.props.doManageTeams(this.props.user);
- }
-
- handleManageRoles = (e) => {
- e.preventDefault();
-
- this.props.doManageRoles(this.props.user);
- }
-
- handleManageTokens = (e) => {
- e.preventDefault();
-
- this.props.doManageTokens(this.props.user);
- }
-
- handleResetPassword = (e) => {
- e.preventDefault();
- this.props.doPasswordReset(this.props.user);
- }
-
- handleResetMfa = (e) => {
- e.preventDefault();
-
- adminResetMfa(this.props.user.id,
- null,
- (err) => {
- this.setState({serverError: err.message});
- }
- );
- }
-
- handleDemoteSystemAdmin = (user, role) => {
- this.setState({
- serverError: this.state.serverError,
- showDemoteModal: true,
- user,
- role
- });
- }
-
- handleDemoteCancel = () => {
- this.setState({
- serverError: null,
- showDemoteModal: false,
- user: null,
- role: null
- });
- }
-
- handleDemoteSubmit = () => {
- if (this.state.role === 'member') {
- this.doMakeMember();
- }
-
- const teamUrl = TeamStore.getCurrentTeamUrl();
- if (teamUrl) {
- // the channel is added to the URL cause endless loading not being fully fixed
- window.location.href = teamUrl + '/channels/town-square';
- } else {
- window.location.href = '/';
- }
- }
-
- handleShowDeactivateMemberModal = (e) => {
- e.preventDefault();
-
- this.setState({showDeactivateMemberModal: true});
- }
-
- handleDeactivateMember = () => {
- updateActive(this.props.user.id, false, null,
- (err) => {
- this.setState({serverError: err.message});
- }
- );
-
- this.setState({showDeactivateMemberModal: false});
- }
-
- handleDeactivateCancel = () => {
- this.setState({showDeactivateMemberModal: false});
- }
-
- renderDeactivateMemberModal = () => {
- const title = (
- <FormattedMessage
- id='deactivate_member_modal.title'
- defaultMessage='Deactivate {username}'
- values={{
- username: this.props.user.username
- }}
- />
- );
-
- const message = (
- <FormattedMessage
- id='deactivate_member_modal.desc'
- defaultMessage='This action deactivates {username}. They will be logged out and not have access to any teams or channels on this system. Are you sure you want to deactivate {username}?'
- values={{
- username: this.props.user.username
- }}
- />
- );
-
- const confirmButtonClass = 'btn btn-danger';
- const deactivateMemberButton = (
- <FormattedMessage
- id='deactivate_member_modal.deactivate'
- defaultMessage='Deactivate'
- />
- );
-
- return (
- <ConfirmModal
- show={this.state.showDeactivateMemberModal}
- title={title}
- message={message}
- confirmButtonClass={confirmButtonClass}
- confirmButtonText={deactivateMemberButton}
- onConfirm={this.handleDeactivateMember}
- onCancel={this.handleDeactivateCancel}
- />
- );
- }
-
- renderAccessToken = () => {
- const userAccessTokensEnabled = global.window.mm_config.EnableUserAccessTokens === 'true';
- if (!userAccessTokensEnabled) {
- return null;
- }
-
- const user = this.props.user;
- const hasPostAllRole = UserUtils.hasPostAllRole(user.roles);
- const hasPostAllPublicRole = UserUtils.hasPostAllPublicRole(user.roles);
- const hasUserAccessTokenRole = UserUtils.hasUserAccessTokenRole(user.roles);
- const isSystemAdmin = UserUtils.isSystemAdmin(user.roles);
-
- let messageId = '';
- if (hasUserAccessTokenRole || isSystemAdmin) {
- if (hasPostAllRole) {
- messageId = 'admin.user_item.userAccessTokenPostAll';
- } else if (hasPostAllPublicRole) {
- messageId = 'admin.user_item.userAccessTokenPostAllPublic';
- } else {
- messageId = 'admin.user_item.userAccessTokenYes';
- }
- }
-
- if (!messageId) {
- return null;
- }
-
- return (
- <div className='light margin-top half'>
- <FormattedMessage
- key='admin.user_item.userAccessToken'
- id={messageId}
- />
- </div>
- );
- }
-
- render() {
- let serverError = null;
- if (this.state.serverError) {
- serverError = (
- <div className='has-error'>
- <label className='has-error control-label'>{this.state.serverError}</label>
- </div>
- );
- }
-
- const user = this.props.user;
- if (!user) {
- return <div/>;
- }
- let currentRoles = (
- <FormattedMessage
- id='admin.user_item.member'
- defaultMessage='Member'
- />
- );
-
- if (user.roles.length > 0 && Utils.isSystemAdmin(user.roles)) {
- currentRoles = (
- <FormattedMessage
- id='team_members_dropdown.systemAdmin'
- defaultMessage='System Admin'
- />
- );
- }
-
- const me = UserStore.getCurrentUser();
- let showMakeActive = false;
- let showMakeNotActive = !Utils.isSystemAdmin(user.roles);
- let showManageTeams = true;
- const mfaEnabled = global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.MFA === 'true' && global.window.mm_config.EnableMultifactorAuthentication === 'true';
- const showMfaReset = mfaEnabled && user.mfa_active;
-
- if (user.delete_at > 0) {
- currentRoles = (
- <FormattedMessage
- id='admin.user_item.inactive'
- defaultMessage='Inactive'
- />
- );
- showMakeActive = true;
- showMakeNotActive = false;
- showManageTeams = false;
- }
-
- let disableActivationToggle = false;
- if (user.auth_service === Constants.LDAP_SERVICE) {
- disableActivationToggle = true;
- }
-
- let menuClass = '';
- if (disableActivationToggle) {
- menuClass = 'disabled';
- }
-
- let makeActive = null;
- if (showMakeActive) {
- makeActive = (
- <li
- role='presentation'
- className={menuClass}
- >
- <a
- id='activate'
- role='menuitem'
- href='#'
- onClick={this.handleMakeActive}
- >
- <FormattedMessage
- id='admin.user_item.makeActive'
- defaultMessage='Activate'
- />
- </a>
- </li>
- );
- }
-
- let makeNotActive = null;
- if (showMakeNotActive) {
- makeNotActive = (
- <li
- role='presentation'
- className={menuClass}
- >
- <a
- id='deactivate'
- role='menuitem'
- href='#'
- onClick={this.handleShowDeactivateMemberModal}
- >
- <FormattedMessage
- id='admin.user_item.makeInactive'
- defaultMessage='Deactivate'
- />
- </a>
- </li>
- );
- }
-
- let manageTeams = null;
- if (showManageTeams) {
- manageTeams = (
- <li role='presentation'>
- <a
- id='manageTeams'
- role='menuitem'
- href='#'
- onClick={this.handleManageTeams}
- >
- <FormattedMessage
- id='admin.user_item.manageTeams'
- defaultMessage='Manage Teams'
- />
- </a>
- </li>
- );
- }
-
- let mfaReset = null;
- if (showMfaReset) {
- mfaReset = (
- <li role='presentation'>
- <a
- id='removeMFA'
- role='menuitem'
- href='#'
- onClick={this.handleResetMfa}
- >
- <FormattedMessage
- id='admin.user_item.resetMfa'
- defaultMessage='Remove MFA'
- />
- </a>
- </li>
- );
- }
-
- let passwordReset;
- if (user.auth_service) {
- passwordReset = (
- <li role='presentation'>
- <a
- id='switchEmailPassword'
- role='menuitem'
- href='#'
- onClick={this.handleResetPassword}
- >
- <FormattedMessage
- id='admin.user_item.switchToEmail'
- defaultMessage='Switch to Email/Password'
- />
- </a>
- </li>
- );
- } else {
- passwordReset = (
- <li role='presentation'>
- <a
- id='resetPassword'
- role='menuitem'
- href='#'
- onClick={this.handleResetPassword}
- >
- <FormattedMessage
- id='admin.user_item.resetPwd'
- defaultMessage='Reset Password'
- />
- </a>
- </li>
- );
- }
-
- let manageTokens;
- if (global.window.mm_config.EnableUserAccessTokens === 'true') {
- manageTokens = (
- <li role='presentation'>
- <a
- id='manageTokens'
- role='menuitem'
- href='#'
- onClick={this.handleManageTokens}
- >
- <FormattedMessage
- id='admin.user_item.manageTokens'
- defaultMessage='Manage Tokens'
- />
- </a>
- </li>
- );
- }
-
- let makeDemoteModal = null;
- if (this.props.user.id === me.id) {
- const title = (
- <FormattedMessage
- id='admin.user_item.confirmDemoteRoleTitle'
- defaultMessage='Confirm demotion from System Admin role'
- />
- );
-
- const message = (
- <div>
- <FormattedMessage
- id='admin.user_item.confirmDemoteDescription'
- defaultMessage="If you demote yourself from the System Admin role and there is not another user with System Admin privileges, you'll need to re-assign a System Admin by accessing the Mattermost server through a terminal and running the following command."
- />
- <br/>
- <br/>
- <FormattedMessage
- id='admin.user_item.confirmDemotionCmd'
- defaultMessage='platform roles system_admin {username}'
- values={{
- username: me.username
- }}
- />
- {serverError}
- </div>
- );
-
- const confirmButton = (
- <FormattedMessage
- id='admin.user_item.confirmDemotion'
- defaultMessage='Confirm Demotion'
- />
- );
-
- makeDemoteModal = (
- <ConfirmModal
- show={this.state.showDemoteModal}
- title={title}
- message={message}
- confirmButtonText={confirmButton}
- onConfirm={this.handleDemoteSubmit}
- onCancel={this.handleDemoteCancel}
- />
- );
- }
-
- const deactivateMemberModal = this.renderDeactivateMemberModal();
-
- let displayedName = Utils.getDisplayName(user);
- if (displayedName !== user.username) {
- displayedName += ' (@' + user.username + ')';
- }
-
- return (
- <div className='dropdown member-drop text-right'>
- <a
- id='memberDropdown'
- href='#'
- className='dropdown-toggle theme'
- type='button'
- data-toggle='dropdown'
- aria-expanded='true'
- >
- <span>{currentRoles} </span>
- <span className='caret'/>
- </a>
- {this.renderAccessToken()}
- <ul
- className='dropdown-menu member-menu'
- role='menu'
- >
- {makeActive}
- {makeNotActive}
- <li role='presentation'>
- <a
- id='manageRoles'
- role='menuitem'
- href='#'
- onClick={this.handleManageRoles}
- >
- <FormattedMessage
- id='admin.user_item.manageRoles'
- defaultMessage='Manage Roles'
- />
- </a>
- </li>
- {manageTeams}
- {manageTokens}
- {mfaReset}
- {passwordReset}
- </ul>
- {makeDemoteModal}
- {deactivateMemberModal}
- {serverError}
- </div>
- );
- }
-}
diff --git a/webapp/components/admin_console/system_users/system_users_list.jsx b/webapp/components/admin_console/system_users/system_users_list.jsx
deleted file mode 100644
index 8a7f30e1b..000000000
--- a/webapp/components/admin_console/system_users/system_users_list.jsx
+++ /dev/null
@@ -1,295 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-
-import ManageTeamsModal from 'components/admin_console/manage_teams_modal/manage_teams_modal.jsx';
-import ManageRolesModal from 'components/admin_console/manage_roles_modal';
-import ManageTokensModal from 'components/admin_console/manage_tokens_modal';
-import ResetPasswordModal from 'components/admin_console/reset_password_modal.jsx';
-import SearchableUserList from 'components/searchable_user_list/searchable_user_list.jsx';
-
-import store from 'stores/redux_store.jsx';
-const dispatch = store.dispatch;
-const getState = store.getState;
-
-import {getUser} from 'mattermost-redux/actions/users';
-import {Constants} from 'utils/constants.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import SystemUsersDropdown from './system_users_dropdown.jsx';
-
-export default class SystemUsersList extends React.Component {
- static propTypes = {
- users: PropTypes.arrayOf(PropTypes.object),
- usersPerPage: PropTypes.number,
- total: PropTypes.number,
- nextPage: PropTypes.func,
- search: PropTypes.func.isRequired,
- focusOnMount: PropTypes.bool,
- renderFilterRow: PropTypes.func,
-
- teamId: PropTypes.string.isRequired,
- term: PropTypes.string.isRequired,
- onTermChange: PropTypes.func.isRequired
- };
-
- constructor(props) {
- super(props);
-
- this.state = {
- page: 0,
-
- showManageTeamsModal: false,
- showManageRolesModal: false,
- showManageTokensModal: false,
- showPasswordModal: false,
- user: null
- };
- }
-
- componentWillReceiveProps(nextProps) {
- if (nextProps.teamId !== this.props.teamId) {
- this.setState({page: 0});
- }
- }
-
- nextPage = () => {
- this.setState({page: this.state.page + 1});
-
- this.props.nextPage(this.state.page + 1);
- }
-
- previousPage = () => {
- this.setState({page: this.state.page - 1});
- }
-
- search = (term) => {
- this.props.search(term);
-
- if (term !== '') {
- this.setState({page: 0});
- }
- }
-
- doManageTeams = (user) => {
- this.setState({
- showManageTeamsModal: true,
- user
- });
- }
-
- doManageRoles = (user) => {
- this.setState({
- showManageRolesModal: true,
- user
- });
- }
-
- doManageTokens = (user) => {
- this.setState({
- showManageTokensModal: true,
- user
- });
- }
-
- doManageTeamsDismiss = () => {
- this.setState({
- showManageTeamsModal: false,
- user: null
- });
- }
-
- doManageRolesDismiss = () => {
- this.setState({
- showManageRolesModal: false,
- user: null
- });
- }
-
- doManageTokensDismiss = () => {
- this.setState({
- showManageTokensModal: false,
- user: null
- });
- }
-
- doPasswordReset = (user) => {
- this.setState({
- showPasswordModal: true,
- user
- });
- }
-
- doPasswordResetDismiss = () => {
- this.setState({
- showPasswordModal: false,
- user: null
- });
- }
-
- doPasswordResetSubmit = (user) => {
- getUser(user.id)(dispatch, getState);
-
- this.setState({
- showPasswordModal: false,
- user: null
- });
- }
-
- getInfoForUser(user) {
- const info = [];
-
- if (user.auth_service) {
- let service;
- if (user.auth_service === Constants.LDAP_SERVICE || user.auth_service === Constants.SAML_SERVICE) {
- service = user.auth_service.toUpperCase();
- } else {
- service = Utils.toTitleCase(user.auth_service);
- }
-
- info.push(
- <FormattedHTMLMessage
- key='admin.user_item.authServiceNotEmail'
- id='admin.user_item.authServiceNotEmail'
- defaultMessage='<strong>Sign-in Method:</strong> {service}'
- values={{
- service
- }}
- />
- );
- } else {
- info.push(
- <FormattedHTMLMessage
- key='admin.user_item.authServiceEmail'
- id='admin.user_item.authServiceEmail'
- defaultMessage='<strong>Sign-in Method:</strong> Email'
- />
- );
- }
-
- const mfaEnabled = global.window.mm_license.IsLicensed === 'true' &&
- global.window.mm_license.MFA === 'true' &&
- global.window.mm_config.EnableMultifactorAuthentication === 'true';
- if (mfaEnabled) {
- info.push(', ');
-
- if (user.mfa_active) {
- info.push(
- <FormattedHTMLMessage
- key='admin.user_item.mfaYes'
- id='admin.user_item.mfaYes'
- defaultMessage='<strong>MFA</strong>: Yes'
- />
- );
- } else {
- info.push(
- <FormattedHTMLMessage
- key='admin.user_item.mfaNo'
- id='admin.user_item.mfaNo'
- defaultMessage='<strong>MFA</strong>: No'
- />
- );
- }
- }
-
- return info;
- }
-
- renderCount(count, total, startCount, endCount, isSearch) {
- if (total) {
- if (isSearch) {
- return (
- <FormattedMessage
- id='system_users_list.countSearch'
- defaultMessage='{count, number} {count, plural, one {user} other {users}} of {total, number} total'
- values={{
- count,
- total
- }}
- />
- );
- } else if (startCount !== 0 || endCount !== total) {
- return (
- <FormattedMessage
- id='system_users_list.countPage'
- defaultMessage='{startCount, number} - {endCount, number} {count, plural, one {user} other {users}} of {total, number} total'
- values={{
- count,
- startCount: startCount + 1,
- endCount,
- total
- }}
- />
- );
- }
-
- return (
- <FormattedMessage
- id='system_users_list.count'
- defaultMessage='{count, number} {count, plural, one {user} other {users}}'
- values={{
- count
- }}
- />
- );
- }
-
- return null;
- }
-
- render() {
- const extraInfo = {};
- if (this.props.users) {
- for (const user of this.props.users) {
- extraInfo[user.id] = this.getInfoForUser(user);
- }
- }
-
- return (
- <div>
- <SearchableUserList
- {...this.props}
- renderCount={this.renderCount}
- extraInfo={extraInfo}
- actions={[SystemUsersDropdown]}
- actionProps={{
- doPasswordReset: this.doPasswordReset,
- doManageTeams: this.doManageTeams,
- doManageRoles: this.doManageRoles,
- doManageTokens: this.doManageTokens
- }}
- nextPage={this.nextPage}
- previousPage={this.previousPage}
- search={this.search}
- page={this.state.page}
- term={this.props.term}
- onTermChange={this.props.onTermChange}
- />
- <ManageTeamsModal
- user={this.state.user}
- show={this.state.showManageTeamsModal}
- onModalDismissed={this.doManageTeamsDismiss}
- />
- <ManageRolesModal
- user={this.state.user}
- show={this.state.showManageRolesModal}
- onModalDismissed={this.doManageRolesDismiss}
- />
- <ManageTokensModal
- user={this.state.user}
- show={this.state.showManageTokensModal}
- onModalDismissed={this.doManageTokensDismiss}
- />
- <ResetPasswordModal
- user={this.state.user}
- show={this.state.showPasswordModal}
- onModalSubmit={this.doPasswordResetSubmit}
- onModalDismissed={this.doPasswordResetDismiss}
- />
- </div>
- );
- }
-}
diff --git a/webapp/components/admin_console/text_setting.jsx b/webapp/components/admin_console/text_setting.jsx
deleted file mode 100644
index 5830828d2..000000000
--- a/webapp/components/admin_console/text_setting.jsx
+++ /dev/null
@@ -1,88 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import Setting from './setting.jsx';
-
-export default class TextSetting extends React.Component {
- static get propTypes() {
- return {
- id: PropTypes.string.isRequired,
- label: PropTypes.node.isRequired,
- placeholder: PropTypes.string,
- helpText: PropTypes.node,
- value: PropTypes.oneOfType([
- PropTypes.string,
- PropTypes.number
- ]).isRequired,
- maxLength: PropTypes.number,
- onChange: PropTypes.func,
- disabled: PropTypes.bool,
- type: PropTypes.oneOf([
- 'input',
- 'textarea'
- ])
- };
- }
-
- static get defaultProps() {
- return {
- type: 'input',
- maxLength: null
- };
- }
-
- constructor(props) {
- super(props);
-
- this.handleChange = this.handleChange.bind(this);
- }
-
- handleChange(e) {
- this.props.onChange(this.props.id, e.target.value);
- }
-
- render() {
- let input = null;
- if (this.props.type === 'input') {
- input = (
- <input
- id={this.props.id}
- className='form-control'
- type='text'
- placeholder={this.props.placeholder}
- value={this.props.value}
- maxLength={this.props.maxLength}
- onChange={this.handleChange}
- disabled={this.props.disabled}
- />
- );
- } else if (this.props.type === 'textarea') {
- input = (
- <textarea
- id={this.props.id}
- className='form-control'
- rows='5'
- placeholder={this.props.placeholder}
- value={this.props.value}
- maxLength={this.props.maxLength}
- onChange={this.handleChange}
- disabled={this.props.disabled}
- />
- );
- }
-
- return (
- <Setting
- label={this.props.label}
- helpText={this.props.helpText}
- inputId={this.props.id}
- >
- {input}
- </Setting>
- );
- }
-}
diff --git a/webapp/components/admin_console/users_and_teams_settings.jsx b/webapp/components/admin_console/users_and_teams_settings.jsx
deleted file mode 100644
index bfd67b638..000000000
--- a/webapp/components/admin_console/users_and_teams_settings.jsx
+++ /dev/null
@@ -1,218 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-
-import AdminSettings from './admin_settings.jsx';
-import BooleanSetting from './boolean_setting.jsx';
-import DropdownSetting from './dropdown_setting.jsx';
-import {FormattedHTMLMessage, FormattedMessage} from 'react-intl';
-import SettingsGroup from './settings_group.jsx';
-import TextSetting from './text_setting.jsx';
-
-const RESTRICT_DIRECT_MESSAGE_ANY = 'any';
-const RESTRICT_DIRECT_MESSAGE_TEAM = 'team';
-
-export default class UsersAndTeamsSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
-
- this.renderSettings = this.renderSettings.bind(this);
- }
-
- getConfigFromState(config) {
- config.TeamSettings.EnableUserCreation = this.state.enableUserCreation;
- config.TeamSettings.EnableTeamCreation = this.state.enableTeamCreation;
- config.TeamSettings.MaxUsersPerTeam = this.parseIntNonZero(this.state.maxUsersPerTeam, Constants.DEFAULT_MAX_USERS_PER_TEAM);
- config.TeamSettings.RestrictCreationToDomains = this.state.restrictCreationToDomains;
- config.TeamSettings.RestrictDirectMessage = this.state.restrictDirectMessage;
- config.TeamSettings.TeammateNameDisplay = this.state.teammateNameDisplay;
- config.TeamSettings.MaxChannelsPerTeam = this.parseIntNonZero(this.state.maxChannelsPerTeam, Constants.DEFAULT_MAX_CHANNELS_PER_TEAM);
- config.TeamSettings.MaxNotificationsPerChannel = this.parseIntNonZero(this.state.maxNotificationsPerChannel, Constants.DEFAULT_MAX_NOTIFICATIONS_PER_CHANNEL);
-
- return config;
- }
-
- getStateFromConfig(config) {
- return {
- enableUserCreation: config.TeamSettings.EnableUserCreation,
- enableTeamCreation: config.TeamSettings.EnableTeamCreation,
- maxUsersPerTeam: config.TeamSettings.MaxUsersPerTeam,
- restrictCreationToDomains: config.TeamSettings.RestrictCreationToDomains,
- restrictDirectMessage: config.TeamSettings.RestrictDirectMessage,
- teammateNameDisplay: config.TeamSettings.TeammateNameDisplay,
- maxChannelsPerTeam: config.TeamSettings.MaxChannelsPerTeam,
- maxNotificationsPerChannel: config.TeamSettings.MaxNotificationsPerChannel
- };
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.general.usersAndTeams'
- defaultMessage='Users and Teams'
- />
- );
- }
-
- renderSettings() {
- return (
- <SettingsGroup>
- <BooleanSetting
- id='enableUserCreation'
- label={
- <FormattedMessage
- id='admin.team.userCreationTitle'
- defaultMessage='Enable Account Creation: '
- />
- }
- helpText={
- <FormattedMessage
- id='admin.team.userCreationDescription'
- defaultMessage='When false, the ability to create accounts is disabled. The create account button displays error when pressed.'
- />
- }
- value={this.state.enableUserCreation}
- onChange={this.handleChange}
- />
- <BooleanSetting
- id='enableTeamCreation'
- label={
- <FormattedMessage
- id='admin.team.teamCreationTitle'
- defaultMessage='Enable Team Creation: '
- />
- }
- helpText={
- <FormattedMessage
- id='admin.team.teamCreationDescription'
- defaultMessage='When false, only System Administrators can create teams.'
- />
- }
- value={this.state.enableTeamCreation}
- onChange={this.handleChange}
- />
- <TextSetting
- id='maxUsersPerTeam'
- label={
- <FormattedMessage
- id='admin.team.maxUsersTitle'
- defaultMessage='Max Users Per Team:'
- />
- }
- placeholder={Utils.localizeMessage('admin.team.maxUsersExample', 'Ex "25"')}
- helpText={
- <FormattedMessage
- id='admin.team.maxUsersDescription'
- defaultMessage='Maximum total number of users per team, including both active and inactive users.'
- />
- }
- value={this.state.maxUsersPerTeam}
- onChange={this.handleChange}
- />
- <TextSetting
- id='maxChannelsPerTeam'
- label={
- <FormattedMessage
- id='admin.team.maxChannelsTitle'
- defaultMessage='Max Channels Per Team:'
- />
- }
- placeholder={Utils.localizeMessage('admin.team.maxChannelsExample', 'Ex "100"')}
- helpText={
- <FormattedMessage
- id='admin.team.maxChannelsDescription'
- defaultMessage='Maximum total number of channels per team, including both active and deleted channels.'
- />
- }
- value={this.state.maxChannelsPerTeam}
- onChange={this.handleChange}
- />
- <TextSetting
- id='maxNotificationsPerChannel'
- label={
- <FormattedMessage
- id='admin.team.maxNotificationsPerChannelTitle'
- defaultMessage='Max Notifications Per Channel:'
- />
- }
- placeholder={Utils.localizeMessage('admin.team.maxNotificationsPerChannelExample', 'Ex "1000"')}
- helpText={
- <FormattedMessage
- id='admin.team.maxNotificationsPerChannelDescription'
- defaultMessage='Maximum total number of users in a channel before users typing messages, @all, @here, and @channel no longer send notifications because of performance.'
- />
- }
- value={this.state.maxNotificationsPerChannel}
- onChange={this.handleChange}
- />
- <TextSetting
- id='restrictCreationToDomains'
- label={
- <FormattedMessage
- id='admin.team.restrictTitle'
- defaultMessage='Restrict account creation to specified email domains:'
- />
- }
- placeholder={Utils.localizeMessage('admin.team.restrictExample', 'Ex "corp.mattermost.com, mattermost.org"')}
- helpText={
- <FormattedMessage
- id='admin.team.restrictDescription'
- defaultMessage='Teams and user accounts can only be created from a specific domain (e.g. "mattermost.org") or list of comma-separated domains (e.g. "corp.mattermost.com, mattermost.org").'
- />
- }
- value={this.state.restrictCreationToDomains}
- onChange={this.handleChange}
- />
- <DropdownSetting
- id='restrictDirectMessage'
- values={[
- {value: RESTRICT_DIRECT_MESSAGE_ANY, text: Utils.localizeMessage('admin.team.restrict_direct_message_any', 'Any user on the Mattermost server')},
- {value: RESTRICT_DIRECT_MESSAGE_TEAM, text: Utils.localizeMessage('admin.team.restrict_direct_message_team', 'Any member of the team')}
- ]}
- label={
- <FormattedMessage
- id='admin.team.restrictDirectMessage'
- defaultMessage='Enable users to open Direct Message channels with:'
- />
- }
- helpText={
- <FormattedHTMLMessage
- id='admin.team.restrictDirectMessageDesc'
- defaultMessage='"Any user on the Mattermost server" enables users to open a Direct Message channel with any user on the server, even if they are not on any teams together. "Any member of the team" limits the ability to open Direct Message channels to only users who are in the same team.'
- />
- }
- value={this.state.restrictDirectMessage}
- onChange={this.handleChange}
- />
- <DropdownSetting
- id='teammateNameDisplay'
- values={[
- {value: Constants.TEAMMATE_NAME_DISPLAY.SHOW_USERNAME, text: Utils.localizeMessage('admin.team.showUsername', 'Show username (default)')},
- {value: Constants.TEAMMATE_NAME_DISPLAY.SHOW_NICKNAME_FULLNAME, text: Utils.localizeMessage('admin.team.showNickname', 'Show nickname if one exists, otherwise show first and last name')},
- {value: Constants.TEAMMATE_NAME_DISPLAY.SHOW_FULLNAME, text: Utils.localizeMessage('admin.team.showFullname', 'Show first and last name')}
- ]}
- label={
- <FormattedMessage
- id='admin.team.teammateNameDisplay'
- defaultMessage='Teammate Name Display:'
- />
- }
- helpText={
- <FormattedHTMLMessage
- id='admin.team.teammateNameDisplayDesc'
- defaultMessage="Set how to display users' names in posts and the Direct Messages list."
- />
- }
- value={this.state.teammateNameDisplay}
- onChange={this.handleChange}
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/webrtc_settings.jsx b/webapp/components/admin_console/webrtc_settings.jsx
deleted file mode 100644
index d9453936c..000000000
--- a/webapp/components/admin_console/webrtc_settings.jsx
+++ /dev/null
@@ -1,218 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import * as Utils from 'utils/utils.jsx';
-
-import AdminSettings from './admin_settings.jsx';
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-import SettingsGroup from './settings_group.jsx';
-import BooleanSetting from './boolean_setting.jsx';
-import TextSetting from './text_setting.jsx';
-
-export default class WebrtcSettings extends AdminSettings {
- constructor(props) {
- super(props);
-
- this.getConfigFromState = this.getConfigFromState.bind(this);
- this.renderSettings = this.renderSettings.bind(this);
- }
-
- getConfigFromState(config) {
- config.WebrtcSettings.Enable = this.state.enableWebrtc;
- config.WebrtcSettings.GatewayWebsocketUrl = this.state.gatewayWebsocketUrl;
- config.WebrtcSettings.GatewayAdminUrl = this.state.gatewayAdminUrl;
- config.WebrtcSettings.GatewayAdminSecret = this.state.gatewayAdminSecret;
- config.WebrtcSettings.StunURI = this.state.stunURI;
- config.WebrtcSettings.TurnURI = this.state.turnURI;
- config.WebrtcSettings.TurnUsername = this.state.turnUsername;
- config.WebrtcSettings.TurnSharedKey = this.state.turnSharedKey;
-
- return config;
- }
-
- getStateFromConfig(config) {
- const settings = config.WebrtcSettings;
-
- return {
- hasErrors: false,
- enableWebrtc: settings.Enable,
- gatewayWebsocketUrl: settings.GatewayWebsocketUrl,
- gatewayAdminUrl: settings.GatewayAdminUrl,
- gatewayAdminSecret: settings.GatewayAdminSecret,
- stunURI: settings.StunURI,
- turnURI: settings.TurnURI,
- turnUsername: settings.TurnUsername,
- turnSharedKey: settings.TurnSharedKey
- };
- }
-
- renderTitle() {
- return (
- <FormattedMessage
- id='admin.integrations.webrtc'
- defaultMessage='Mattermost WebRTC (Beta)'
- />
- );
- }
-
- renderSettings() {
- return (
- <SettingsGroup>
- <BooleanSetting
- id='enableWebrtc'
- label={
- <FormattedMessage
- id='admin.webrtc.enableTitle'
- defaultMessage='Enable Mattermost WebRTC: '
- />
- }
- helpText={
- <FormattedHTMLMessage
- id='admin.webrtc.enableDescription'
- defaultMessage='When true, Mattermost allows making <strong>one-on-one</strong> video calls. WebRTC calls are available on Chrome, Firefox and Mattermost Desktop Apps.'
- />
- }
- value={this.state.enableWebrtc}
- onChange={this.handleChange}
- />
- <TextSetting
- id='gatewayWebsocketUrl'
- label={
- <FormattedMessage
- id='admin.webrtc.gatewayWebsocketUrlTitle'
- defaultMessage='Gateway WebSocket URL:'
- />
- }
- placeholder={Utils.localizeMessage('admin.webrtc.gatewayWebsocketUrlExample', 'Ex "wss://webrtc.mattermost.com:8189"')}
- helpText={
- <FormattedMessage
- id='admin.webrtc.gatewayWebsocketUrlDescription'
- defaultMessage='Enter wss://<mattermost-webrtc-gateway-url>:<port>. Make sure you use WS or WSS in your URL depending on your server configuration.
- This is the WebSocket used to signal and establish communication between the peers.'
- />
- }
- value={this.state.gatewayWebsocketUrl}
- onChange={this.handleChange}
- disabled={!this.state.enableWebrtc}
- />
- <TextSetting
- id='gatewayAdminUrl'
- label={
- <FormattedMessage
- id='admin.webrtc.gatewayAdminUrlTitle'
- defaultMessage='Gateway Admin URL:'
- />
- }
- placeholder={Utils.localizeMessage('admin.webrtc.gatewayAdminUrlExample', 'Ex "https://webrtc.mattermost.com:7089/admin"')}
- helpText={
- <FormattedMessage
- id='admin.webrtc.gatewayAdminUrlDescription'
- defaultMessage='Enter https://<mattermost-webrtc-gateway-url>:<port>/admin. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.
- Mattermost WebRTC uses this URL to obtain valid tokens for each peer to establish the connection.'
- />
- }
- value={this.state.gatewayAdminUrl}
- onChange={this.handleChange}
- disabled={!this.state.enableWebrtc}
- />
- <TextSetting
- id='gatewayAdminSecret'
- label={
- <FormattedMessage
- id='admin.webrtc.gatewayAdminSecretTitle'
- defaultMessage='Gateway Admin Secret:'
- />
- }
- placeholder={Utils.localizeMessage('admin.webrtc.gatewayAdminSecretExample', 'Ex "PVRzWNN1Tg6szn7IQWvhpAvLByScWxdy"')}
- helpText={
- <FormattedMessage
- id='admin.webrtc.gatewayAdminSecretDescription'
- defaultMessage='Enter your admin secret password to access the Gateway Admin URL.'
- />
- }
- value={this.state.gatewayAdminSecret}
- onChange={this.handleChange}
- disabled={!this.state.enableWebrtc}
- />
- <TextSetting
- id='stunURI'
- label={
- <FormattedMessage
- id='admin.webrtc.stunUriTitle'
- defaultMessage='STUN URI:'
- />
- }
- placeholder={Utils.localizeMessage('admin.webrtc.stunUriExample', 'Ex "stun:webrtc.mattermost.com:5349"')}
- helpText={
- <FormattedMessage
- id='admin.webrtc.stunUriDescription'
- defaultMessage='Enter your STUN URI as stun:<your-stun-url>:<port>. STUN is a standardized network protocol to allow an end host to assist devices to access its public IP address if it is located behind a NAT.'
- />
- }
- value={this.state.stunURI}
- onChange={this.handleChange}
- disabled={!this.state.enableWebrtc}
- />
- <TextSetting
- id='turnURI'
- label={
- <FormattedMessage
- id='admin.webrtc.turnUriTitle'
- defaultMessage='TURN URI:'
- />
- }
- placeholder={Utils.localizeMessage('admin.webrtc.turnUriExample', 'Ex "turn:webrtc.mattermost.com:5349"')}
- helpText={
- <FormattedMessage
- id='admin.webrtc.turnUriDescription'
- defaultMessage='Enter your TURN URI as turn:<your-turn-url>:<port>. TURN is a standardized network protocol to allow an end host to assist devices to establish a connection by using a relay public IP address if it is located behind a symmetric NAT.'
- />
- }
- value={this.state.turnURI}
- onChange={this.handleChange}
- disabled={!this.state.enableWebrtc}
- />
- <TextSetting
- id='turnUsername'
- label={
- <FormattedMessage
- id='admin.webrtc.turnUsernameTitle'
- defaultMessage='TURN Username:'
- />
- }
- placeholder={Utils.localizeMessage('admin.webrtc.turnUsernameExample', 'Ex "myusername"')}
- helpText={
- <FormattedMessage
- id='admin.webrtc.turnUsernameDescription'
- defaultMessage='Enter your TURN Server Username.'
- />
- }
- value={this.state.turnUsername}
- onChange={this.handleChange}
- disabled={!this.state.enableWebrtc || !this.state.turnURI}
- />
- <TextSetting
- id='turnSharedKey'
- label={
- <FormattedMessage
- id='admin.webrtc.turnSharedKeyTitle'
- defaultMessage='TURN Shared Key:'
- />
- }
- placeholder={Utils.localizeMessage('admin.webrtc.turnSharedKeyExample', 'Ex "bXdkOWQxc3d0Ynk3emY5ZmsxZ3NtazRjaWg="')}
- helpText={
- <FormattedMessage
- id='admin.webrtc.turnSharedKeyDescription'
- defaultMessage='Enter your TURN Server Shared Key. This is used to created dynamic passwords to establish the connection. Each password is valid for a short period of time.'
- />
- }
- value={this.state.turnSharedKey}
- onChange={this.handleChange}
- disabled={!this.state.enableWebrtc || !this.state.turnURI}
- />
- </SettingsGroup>
- );
- }
-}
diff --git a/webapp/components/admin_console/webserver_mode_dropdown_setting.jsx b/webapp/components/admin_console/webserver_mode_dropdown_setting.jsx
deleted file mode 100644
index e9ddee677..000000000
--- a/webapp/components/admin_console/webserver_mode_dropdown_setting.jsx
+++ /dev/null
@@ -1,101 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import * as Utils from 'utils/utils.jsx';
-
-import DropdownSetting from './dropdown_setting.jsx';
-import {FormattedMessage} from 'react-intl';
-
-const WEBSERVER_MODE_HELP_TEXT = (
- <div>
- <table
- className='table table-bordered table-margin--none'
- cellPadding='5'
- >
- <tbody>
- <tr>
- <td>
- <FormattedMessage
- id='admin.webserverModeGzip'
- defaultMessage='gzip'
- />
- </td>
- <td>
- <FormattedMessage
- id='admin.webserverModeGzipDescription'
- defaultMessage='The Mattermost server will serve static files compressed with gzip.'
- />
- </td>
- </tr>
- <tr>
- <td>
- <FormattedMessage
- id='admin.webserverModeUncompressed'
- defaultMessage='Uncompressed'
- />
- </td>
- <td>
- <FormattedMessage
- id='admin.webserverModeUncompressedDescription'
- defaultMessage='The Mattermost server will serve static files uncompressed.'
- />
- </td>
- </tr>
- <tr>
- <td>
- <FormattedMessage
- id='admin.webserverModeDisabled'
- defaultMessage='Disabled'
- />
- </td>
- <td>
- <FormattedMessage
- id='admin.webserverModeDisabledDescription'
- defaultMessage='The Mattermost server will not serve static files.'
- />
- </td>
- </tr>
- </tbody>
- </table>
- <p className='help-text'>
- <FormattedMessage
- id='admin.webserverModeHelpText'
- defaultMessage='gzip compression applies to static content files. It is recommended to enable gzip to improve performance unless your environment has specific restrictions, such as a web proxy that distributes gzip files poorly.'
- />
- </p>
- </div>
-);
-
-export default function WebserverModeDropdownSetting(props) {
- return (
- <DropdownSetting
- id='webserverMode'
- values={[
- {value: 'gzip', text: Utils.localizeMessage('admin.webserverModeGzip', 'gzip')},
- {value: 'uncompressed', text: Utils.localizeMessage('admin.webserverModeUncompressed', 'Uncompressed')},
- {value: 'disabled', text: Utils.localizeMessage('admin.webserverModeDisabled', 'Disabled')}
- ]}
- label={
- <FormattedMessage
- id='admin.webserverModeTitle'
- defaultMessage='Webserver Mode:'
- />
- }
- value={props.value}
- onChange={props.onChange}
- disabled={props.disabled}
- helpText={WEBSERVER_MODE_HELP_TEXT}
- />
- );
-}
-WebserverModeDropdownSetting.defaultProps = {
-};
-
-WebserverModeDropdownSetting.propTypes = {
- value: PropTypes.string.isRequired,
- onChange: PropTypes.func.isRequired,
- disabled: PropTypes.bool.isRequired
-};
diff --git a/webapp/components/analytics/doughnut_chart.jsx b/webapp/components/analytics/doughnut_chart.jsx
deleted file mode 100644
index 598922acd..000000000
--- a/webapp/components/analytics/doughnut_chart.jsx
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {FormattedMessage} from 'react-intl';
-
-import * as Utils from 'utils/utils.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import ReactDOM from 'react-dom';
-import Chart from 'chart.js';
-
-export default class DoughnutChart extends React.Component {
- constructor(props) {
- super(props);
-
- this.initChart = this.initChart.bind(this);
- this.chart = null;
- }
-
- componentDidMount() {
- this.initChart();
- }
-
- componentDidUpdate(prevProps) {
- if (!Utils.areObjectsEqual(prevProps.data, this.props.data) || !Utils.areObjectsEqual(prevProps.options, this.props.options)) {
- this.initChart(true);
- }
- }
-
- componentWillUnmount() {
- if (this.chart && this.refs.canvas) {
- this.chart.destroy();
- }
- }
-
- initChart(update) {
- if (!this.refs.canvas) {
- return;
- }
- var el = ReactDOM.findDOMNode(this.refs.canvas);
- var ctx = el.getContext('2d');
- this.chart = new Chart(ctx, {type: 'doughnut', data: this.props.data, options: this.props.options || {}}); //eslint-disable-line new-cap
- if (update) {
- this.chart.update();
- }
- }
-
- render() {
- let content;
- if (this.props.data == null) {
- content = (
- <FormattedMessage
- id='analytics.chart.loading'
- defaultMessage='Loading...'
- />
- );
- } else {
- content = (
- <canvas
- ref='canvas'
- width={this.props.width}
- height={this.props.height}
- />
- );
- }
-
- return (
- <div className='col-sm-6'>
- <div className='total-count'>
- <div className='title'>
- {this.props.title}
- </div>
- <div className='content'>
- {content}
- </div>
- </div>
- </div>
- );
- }
-}
-
-DoughnutChart.propTypes = {
- title: PropTypes.node,
- width: PropTypes.string,
- height: PropTypes.string,
- data: PropTypes.object,
- options: PropTypes.object
-};
diff --git a/webapp/components/analytics/line_chart.jsx b/webapp/components/analytics/line_chart.jsx
deleted file mode 100644
index 67a1162fc..000000000
--- a/webapp/components/analytics/line_chart.jsx
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {FormattedMessage} from 'react-intl';
-
-import * as Utils from 'utils/utils.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import ReactDOM from 'react-dom';
-import Chart from 'chart.js';
-
-export default class LineChart extends React.Component {
- constructor(props) {
- super(props);
-
- this.initChart = this.initChart.bind(this);
- this.chart = null;
- }
-
- componentDidMount() {
- this.initChart();
- }
-
- componentWillUpdate(nextProps) {
- const willHaveData = nextProps.data && nextProps.data.labels.length > 0;
- const hasChart = Boolean(this.chart);
-
- if (!willHaveData && hasChart) {
- // Clean up the rendered chart before we render and destroy its context
- this.chart.destroy();
- this.chart = null;
- }
- }
-
- componentDidUpdate(prevProps) {
- if (Utils.areObjectsEqual(prevProps.data, this.props.data) && Utils.areObjectsEqual(prevProps.options, this.props.options)) {
- return;
- }
-
- const hasData = this.props.data && this.props.data.labels.length > 0;
- const hasChart = Boolean(this.chart);
-
- if (hasData) {
- // Update the rendered chart or initialize it as necessary
- this.initChart(hasChart);
- }
- }
-
- componentWillUnmount() {
- if (this.chart) {
- this.chart.destroy();
- }
- }
-
- initChart(update) {
- if (!this.refs.canvas) {
- return;
- }
-
- var el = ReactDOM.findDOMNode(this.refs.canvas);
- var ctx = el.getContext('2d');
- this.chart = new Chart(ctx, {type: 'line', data: this.props.data, options: this.props.options || {}}); // eslint-disable-line new-cap
-
- if (update) {
- this.chart.update();
- }
- }
-
- render() {
- let content;
- if (this.props.data == null) {
- content = (
- <FormattedMessage
- id='analytics.chart.loading'
- defaultMessage='Loading...'
- />
- );
- } else if (this.props.data.labels.length === 0) {
- content = (
- <h5>
- <FormattedMessage
- id='analytics.chart.meaningful'
- defaultMessage='Not enough data for a meaningful representation.'
- />
- </h5>
- );
- } else {
- content = (
- <canvas
- ref='canvas'
- width={this.props.width}
- height={this.props.height}
- />
- );
- }
-
- return (
- <div className='col-sm-12'>
- <div className='total-count by-day'>
- <div className='title'>
- {this.props.title}
- </div>
- <div className='content'>
- {content}
- </div>
- </div>
- </div>
- );
- }
-}
-
-LineChart.propTypes = {
- title: PropTypes.node.isRequired,
- width: PropTypes.string.isRequired,
- height: PropTypes.string.isRequired,
- data: PropTypes.object,
- options: PropTypes.object
-};
-
diff --git a/webapp/components/analytics/statistic_count.jsx b/webapp/components/analytics/statistic_count.jsx
deleted file mode 100644
index 4b505bb3f..000000000
--- a/webapp/components/analytics/statistic_count.jsx
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {FormattedMessage} from 'react-intl';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default function StatisticCount(props) {
- const loading = (
- <FormattedMessage
- id='analytics.chart.loading'
- defaultMessage='Loading...'
- />
- );
-
- return (
- <div className='col-md-3 col-sm-6'>
- <div className='total-count'>
- <div className='title'>
- {props.title}
- <i className={'fa ' + props.icon}/>
- </div>
- <div className='content'>{props.count == null ? loading : props.count}</div>
- </div>
- </div>
- );
-}
-
-StatisticCount.propTypes = {
- title: PropTypes.node.isRequired,
- icon: PropTypes.string.isRequired,
- count: PropTypes.number
-};
diff --git a/webapp/components/analytics/system_analytics.jsx b/webapp/components/analytics/system_analytics.jsx
deleted file mode 100644
index 9aa4c26c8..000000000
--- a/webapp/components/analytics/system_analytics.jsx
+++ /dev/null
@@ -1,492 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import LineChart from './line_chart.jsx';
-import DoughnutChart from './doughnut_chart.jsx';
-import StatisticCount from './statistic_count.jsx';
-
-import AnalyticsStore from 'stores/analytics_store.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-import * as AdminActions from 'actions/admin_actions.jsx';
-import Constants from 'utils/constants.jsx';
-const StatTypes = Constants.StatTypes;
-
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-
-import React from 'react';
-
-export default class SystemAnalytics extends React.Component {
- constructor(props) {
- super(props);
-
- this.onChange = this.onChange.bind(this);
-
- this.state = {stats: AnalyticsStore.getAllSystem()};
- }
-
- componentDidMount() {
- AnalyticsStore.addChangeListener(this.onChange);
-
- AdminActions.getStandardAnalytics();
- AdminActions.getPostsPerDayAnalytics();
- AdminActions.getUsersPerDayAnalytics();
-
- if (global.window.mm_license.IsLicensed === 'true') {
- AdminActions.getAdvancedAnalytics();
- }
- }
-
- componentWillUnmount() {
- AnalyticsStore.removeChangeListener(this.onChange);
- }
-
- shouldComponentUpdate(nextProps, nextState) {
- if (!Utils.areObjectsEqual(nextState.stats, this.state.stats)) {
- return true;
- }
-
- return false;
- }
-
- onChange() {
- this.setState({stats: AnalyticsStore.getAllSystem()});
- }
-
- render() {
- const stats = this.state.stats;
- const isLicensed = global.window.mm_license.IsLicensed === 'true';
- const skippedIntensiveQueries = stats[StatTypes.TOTAL_POSTS] === -1;
- const postCountsDay = formatPostsPerDayData(stats[StatTypes.POST_PER_DAY]);
- const userCountsWithPostsDay = formatUsersWithPostsPerDayData(stats[StatTypes.USERS_WITH_POSTS_PER_DAY]);
-
- let banner;
- let postCount;
- let postTotalGraph;
- let activeUserGraph;
- if (skippedIntensiveQueries) {
- banner = (
- <div className='banner'>
- <div className='banner__content'>
- <FormattedHTMLMessage
- id='analytics.system.skippedIntensiveQueries'
- defaultMessage="To maximize performance, some statistics are disabled. You can re-enable them in config.json. See: <a href='https://docs.mattermost.com/administration/statistics.html' target='_blank'>https://docs.mattermost.com/administration/statistics.html</a>"
- />
- </div>
- </div>
- );
- } else {
- postCount = (
- <StatisticCount
- title={
- <FormattedMessage
- id='analytics.system.totalPosts'
- defaultMessage='Total Posts'
- />
- }
- icon='fa-comment'
- count={stats[StatTypes.TOTAL_POSTS]}
- />
- );
-
- postTotalGraph = (
- <div className='row'>
- <LineChart
- title={
- <FormattedMessage
- id='analytics.system.totalPosts'
- defaultMessage='Total Posts'
- />
- }
- data={postCountsDay}
- options={{
- legend: {
- display: false
- }
- }}
- width='740'
- height='225'
- />
- </div>
- );
-
- activeUserGraph = (
- <div className='row'>
- <LineChart
- title={
- <FormattedMessage
- id='analytics.system.activeUsers'
- defaultMessage='Active Users With Posts'
- />
- }
- data={userCountsWithPostsDay}
- options={{
- legend: {
- display: false
- }
- }}
- width='740'
- height='225'
- />
- </div>
- );
- }
-
- let advancedStats;
- let advancedGraphs;
- let sessionCount;
- let commandCount;
- let incomingCount;
- let outgoingCount;
- if (global.window.mm_license.IsLicensed === 'true') {
- sessionCount = (
- <StatisticCount
- title={
- <FormattedMessage
- id='analytics.system.totalSessions'
- defaultMessage='Total Sessions'
- />
- }
- icon='fa-signal'
- count={stats[StatTypes.TOTAL_SESSIONS]}
- />
- );
-
- commandCount = (
- <StatisticCount
- title={
- <FormattedMessage
- id='analytics.system.totalCommands'
- defaultMessage='Total Commands'
- />
- }
- icon='fa-terminal'
- count={stats[StatTypes.TOTAL_COMMANDS]}
- />
- );
-
- incomingCount = (
- <StatisticCount
- title={
- <FormattedMessage
- id='analytics.system.totalIncomingWebhooks'
- defaultMessage='Incoming Webhooks'
- />
- }
- icon='fa-arrow-down'
- count={stats[StatTypes.TOTAL_IHOOKS]}
- />
- );
-
- outgoingCount = (
- <StatisticCount
- title={
- <FormattedMessage
- id='analytics.system.totalOutgoingWebhooks'
- defaultMessage='Outgoing Webhooks'
- />
- }
- icon='fa-arrow-up'
- count={stats[StatTypes.TOTAL_OHOOKS]}
- />
- );
-
- advancedStats = (
- <div>
- <StatisticCount
- title={
- <FormattedMessage
- id='analytics.system.totalWebsockets'
- defaultMessage='WebSocket Conns'
- />
- }
- icon='fa-user'
- count={stats[StatTypes.TOTAL_WEBSOCKET_CONNECTIONS]}
- />
- <StatisticCount
- title={
- <FormattedMessage
- id='analytics.system.totalMasterDbConnections'
- defaultMessage='Master DB Conns'
- />
- }
- icon='fa-terminal'
- count={stats[StatTypes.TOTAL_MASTER_DB_CONNECTIONS]}
- />
- <StatisticCount
- title={
- <FormattedMessage
- id='analytics.system.totalReadDbConnections'
- defaultMessage='Replica DB Conns'
- />
- }
- icon='fa-terminal'
- count={stats[StatTypes.TOTAL_READ_DB_CONNECTIONS]}
- />
- </div>
- );
-
- const channelTypeData = formatChannelDoughtnutData(stats[StatTypes.TOTAL_PUBLIC_CHANNELS], stats[StatTypes.TOTAL_PRIVATE_GROUPS]);
- const postTypeData = formatPostDoughtnutData(stats[StatTypes.TOTAL_FILE_POSTS], stats[StatTypes.TOTAL_HASHTAG_POSTS], stats[StatTypes.TOTAL_POSTS]);
-
- let postTypeGraph;
- if (stats[StatTypes.TOTAL_POSTS] !== -1) {
- postTypeGraph = (
- <DoughnutChart
- title={
- <FormattedMessage
- id='analytics.system.postTypes'
- defaultMessage='Posts, Files and Hashtags'
- />
- }
- data={postTypeData}
- width='300'
- height='225'
- />
- );
- }
-
- advancedGraphs = (
- <div className='row'>
- <DoughnutChart
- title={
- <FormattedMessage
- id='analytics.system.channelTypes'
- defaultMessage='Channel Types'
- />
- }
- data={channelTypeData}
- width='300'
- height='225'
- />
- {postTypeGraph}
- </div>
- );
- }
-
- const userCount = (
- <StatisticCount
- title={
- <FormattedMessage
- id='analytics.system.totalUsers'
- defaultMessage='Total Users'
- />
- }
- icon='fa-user'
- count={stats[StatTypes.TOTAL_USERS]}
- />
- );
-
- const teamCount = (
- <StatisticCount
- title={
- <FormattedMessage
- id='analytics.system.totalTeams'
- defaultMessage='Total Teams'
- />
- }
- icon='fa-users'
- count={stats[StatTypes.TOTAL_TEAMS]}
- />
- );
-
- const channelCount = (
- <StatisticCount
- title={
- <FormattedMessage
- id='analytics.system.totalChannels'
- defaultMessage='Total Channels'
- />
- }
- icon='fa-globe'
- count={stats[StatTypes.TOTAL_PUBLIC_CHANNELS] + stats[StatTypes.TOTAL_PRIVATE_GROUPS]}
- />
- );
-
- const dailyActiveUsers = (
- <StatisticCount
- title={
- <FormattedMessage
- id='analytics.system.dailyActiveUsers'
- defaultMessage='Daily Active Users'
- />
- }
- icon='fa-users'
- count={stats[StatTypes.DAILY_ACTIVE_USERS]}
- />
- );
-
- const monthlyActiveUsers = (
- <StatisticCount
- title={
- <FormattedMessage
- id='analytics.system.monthlyActiveUsers'
- defaultMessage='Monthly Active Users'
- />
- }
- icon='fa-users'
- count={stats[StatTypes.MONTHLY_ACTIVE_USERS]}
- />
- );
-
- let firstRow;
- let secondRow;
- if (isLicensed && skippedIntensiveQueries) {
- firstRow = (
- <div>
- {userCount}
- {teamCount}
- {channelCount}
- {sessionCount}
- </div>
- );
-
- secondRow = (
- <div>
- {commandCount}
- {incomingCount}
- {outgoingCount}
- </div>
- );
- } else if (isLicensed && !skippedIntensiveQueries) {
- firstRow = (
- <div>
- {userCount}
- {teamCount}
- {channelCount}
- {postCount}
- </div>
- );
-
- secondRow = (
- <div>
- {sessionCount}
- {commandCount}
- {incomingCount}
- {outgoingCount}
- </div>
- );
- } else if (!isLicensed) {
- firstRow = (
- <div>
- {userCount}
- {teamCount}
- {channelCount}
- {postCount}
- </div>
- );
- }
-
- const thirdRow = (
- <div>
- {dailyActiveUsers}
- {monthlyActiveUsers}
- </div>
- );
-
- return (
- <div className='wrapper--fixed team_statistics'>
- <h3 className='admin-console-header'>
- <FormattedMessage
- id='analytics.system.title'
- defaultMessage='System Statistics'
- />
- </h3>
- {banner}
- <div className='row'>
- {firstRow}
- {secondRow}
- {thirdRow}
- {advancedStats}
- </div>
- {advancedGraphs}
- {postTotalGraph}
- {activeUserGraph}
- </div>
- );
- }
-}
-
-export function formatChannelDoughtnutData(totalPublic, totalPrivate) {
- const channelTypeData = {
- labels: [
- Utils.localizeMessage('analytics.system.publicChannels', 'Public Channels'),
- Utils.localizeMessage('analytics.system.privateGroups', 'Private Channels')
- ],
- datasets: [{
- data: [totalPublic, totalPrivate],
- backgroundColor: ['#46BFBD', '#FDB45C'],
- hoverBackgroundColor: ['#5AD3D1', '#FFC870']
- }]
- };
-
- return channelTypeData;
-}
-
-export function formatPostDoughtnutData(filePosts, hashtagPosts, totalPosts) {
- const postTypeData = {
- labels: [
- Utils.localizeMessage('analytics.system.totalFilePosts', 'Posts with Files'),
- Utils.localizeMessage('analytics.system.totalHashtagPosts', 'Posts with Hashtags'),
- Utils.localizeMessage('analytics.system.textPosts', 'Posts with Text-only')
- ],
- datasets: [{
- data: [filePosts, hashtagPosts, (totalPosts - filePosts - hashtagPosts)],
- backgroundColor: ['#46BFBD', '#F7464A', '#FDB45C'],
- hoverBackgroundColor: ['#5AD3D1', '#FF5A5E', '#FFC870']
- }]
- };
-
- return postTypeData;
-}
-
-export function formatPostsPerDayData(data) {
- var chartData = {
- labels: [],
- datasets: [{
- fillColor: 'rgba(151,187,205,0.2)',
- borderColor: 'rgba(151,187,205,1)',
- pointBackgroundColor: 'rgba(151,187,205,1)',
- pointBorderColor: '#fff',
- pointHoverBackgroundColor: '#fff',
- pointHoverBorderColor: 'rgba(151,187,205,1)',
- data: []
- }]
- };
-
- for (var index in data) {
- if (data[index]) {
- var row = data[index];
- chartData.labels.push(row.name);
- chartData.datasets[0].data.push(row.value);
- }
- }
-
- return chartData;
-}
-
-export function formatUsersWithPostsPerDayData(data) {
- var chartData = {
- labels: [],
- datasets: [{
- label: '',
- fillColor: 'rgba(151,187,205,0.2)',
- borderColor: 'rgba(151,187,205,1)',
- pointBackgroundColor: 'rgba(151,187,205,1)',
- pointBorderColor: '#fff',
- pointHoverBackgroundColor: '#fff',
- pointHoverBorderColor: 'rgba(151,187,205,1)',
- data: []
- }]
- };
-
- for (var index in data) {
- if (data[index]) {
- var row = data[index];
- chartData.labels.push(row.name);
- chartData.datasets[0].data.push(row.value);
- }
- }
-
- return chartData;
-}
diff --git a/webapp/components/analytics/table_chart.jsx b/webapp/components/analytics/table_chart.jsx
deleted file mode 100644
index 5836e09a7..000000000
--- a/webapp/components/analytics/table_chart.jsx
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import Constants from 'utils/constants.jsx';
-
-import {Tooltip, OverlayTrigger} from 'react-bootstrap';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default function TableChart(props) {
- return (
- <div className='col-sm-6'>
- <div className='total-count recent-active-users'>
- <div className='title'>
- {props.title}
- </div>
- <div className='content'>
- <table>
- <tbody>
- {
- props.data.map((item) => {
- const tooltip = (
- <Tooltip id={'tip-table-entry-' + item.name}>
- {item.tip}
- </Tooltip>
- );
-
- return (
- <tr key={'table-entry-' + item.name}>
- <td>
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={Constants.OVERLAY_TIME_DELAY}
- placement='top'
- overlay={tooltip}
- >
- <time>
- {item.name}
- </time>
- </OverlayTrigger>
- </td>
- <td>
- {item.value}
- </td>
- </tr>
- );
- })
- }
- </tbody>
- </table>
- </div>
- </div>
- </div>
- );
-}
-
-TableChart.propTypes = {
- title: PropTypes.node,
- data: PropTypes.array
-};
diff --git a/webapp/components/analytics/team_analytics/index.js b/webapp/components/analytics/team_analytics/index.js
deleted file mode 100644
index fe53a1559..000000000
--- a/webapp/components/analytics/team_analytics/index.js
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getTeams} from 'mattermost-redux/actions/teams';
-import {getProfilesInTeam} from 'mattermost-redux/actions/users';
-
-import {getTeamsList} from 'mattermost-redux/selectors/entities/teams';
-import BrowserStore from 'stores/browser_store.jsx';
-
-import TeamAnalytics from './team_analytics.jsx';
-
-const LAST_ANALYTICS_TEAM = 'last_analytics_team';
-
-function mapStateToProps(state, ownProps) {
- const teams = getTeamsList(state);
- const teamId = BrowserStore.getGlobalItem(LAST_ANALYTICS_TEAM, teams.length > 0 ? teams[0].id : '');
-
- return {
- initialTeam: state.entities.teams.teams[teamId],
- teams,
- ...ownProps
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getTeams,
- getProfilesInTeam
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(TeamAnalytics);
diff --git a/webapp/components/analytics/team_analytics/team_analytics.jsx b/webapp/components/analytics/team_analytics/team_analytics.jsx
deleted file mode 100644
index f2f12d938..000000000
--- a/webapp/components/analytics/team_analytics/team_analytics.jsx
+++ /dev/null
@@ -1,369 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {FormattedDate, FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-
-import Banner from 'components/admin_console/banner.jsx';
-import LoadingScreen from 'components/loading_screen.jsx';
-
-import AnalyticsStore from 'stores/analytics_store.jsx';
-import BrowserStore from 'stores/browser_store.jsx';
-
-import * as AdminActions from 'actions/admin_actions.jsx';
-import {StatTypes} from 'utils/constants.jsx';
-import {General} from 'mattermost-redux/constants';
-
-import LineChart from 'components/analytics/line_chart.jsx';
-import StatisticCount from 'components/analytics/statistic_count.jsx';
-import TableChart from 'components/analytics/table_chart.jsx';
-import {formatPostsPerDayData, formatUsersWithPostsPerDayData} from 'components/analytics/system_analytics.jsx';
-
-const LAST_ANALYTICS_TEAM = 'last_analytics_team';
-
-export default class TeamAnalytics extends React.Component {
- static propTypes = {
-
- /*
- * Array of team objects
- */
- teams: PropTypes.arrayOf(PropTypes.object).isRequired,
-
- /*
- * Initial team to load analytics for
- */
- initialTeam: PropTypes.object,
-
- actions: PropTypes.shape({
-
- /*
- * Function to get teams
- */
- getTeams: PropTypes.func.isRequired,
-
- /*
- * Function to get users in a team
- */
- getProfilesInTeam: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- const teamId = props.initialTeam ? props.initialTeam.id : '';
-
- this.state = {
- team: props.initialTeam,
- stats: AnalyticsStore.getAllTeam(teamId),
- recentlyActiveUsers: [],
- newUsers: []
- };
- }
-
- componentDidMount() {
- AnalyticsStore.addChangeListener(this.onChange);
-
- if (this.state.team) {
- this.getData(this.state.team.id);
- }
-
- this.props.actions.getTeams(0, 1000);
- }
-
- componentWillUpdate(nextProps, nextState) {
- if (nextState.team && nextState.team !== this.state.team) {
- this.getData(nextState.team.id);
- }
- }
-
- getData = async (id) => {
- AdminActions.getStandardAnalytics(id);
- AdminActions.getPostsPerDayAnalytics(id);
- AdminActions.getUsersPerDayAnalytics(id);
- const recentlyActiveUsers = await this.props.actions.getProfilesInTeam(id, 0, General.PROFILE_CHUNK_SIZE, 'last_activity_at');
- const newUsers = await this.props.actions.getProfilesInTeam(id, 0, General.PROFILE_CHUNK_SIZE, 'create_at');
-
- this.setState({
- recentlyActiveUsers,
- newUsers
- });
- }
-
- componentWillUnmount() {
- AnalyticsStore.removeChangeListener(this.onChange);
- }
-
- onChange = () => {
- const teamId = this.state.team ? this.state.team.id : '';
- this.setState({
- stats: AnalyticsStore.getAllTeam(teamId)
- });
- }
-
- handleTeamChange = (e) => {
- const teamId = e.target.value;
-
- let team;
- this.props.teams.forEach((t) => {
- if (t.id === teamId) {
- team = t;
- }
- });
-
- this.setState({
- team
- });
-
- BrowserStore.setGlobalItem(LAST_ANALYTICS_TEAM, teamId);
- }
-
- render() {
- if (this.props.teams.length === 0 || !this.state.team || !this.state.stats) {
- return <LoadingScreen/>;
- }
-
- if (this.state.team == null) {
- return (
- <Banner
- description={
- <FormattedMessage
- id='analytics.team.noTeams'
- defaultMessage='There are no teams on this server for which to view statistics.'
- />
- }
- />
- );
- }
-
- const stats = this.state.stats;
- const postCountsDay = formatPostsPerDayData(stats[StatTypes.POST_PER_DAY]);
- const userCountsWithPostsDay = formatUsersWithPostsPerDayData(stats[StatTypes.USERS_WITH_POSTS_PER_DAY]);
-
- let banner;
- let totalPostsCount;
- let postTotalGraph;
- let userActiveGraph;
- if (stats[StatTypes.TOTAL_POSTS] === -1) {
- banner = (
- <div className='banner'>
- <div className='banner__content'>
- <FormattedHTMLMessage
- id='analytics.system.skippedIntensiveQueries'
- defaultMessage="Some statistics have been omitted because they put too much load on the system to calculate. See <a href='https://docs.mattermost.com/administration/statistics.html' target='_blank'>https://docs.mattermost.com/administration/statistics.html</a> for more details."
- />
- </div>
- </div>
- );
- } else {
- totalPostsCount = (
- <StatisticCount
- title={
- <FormattedMessage
- id='analytics.team.totalPosts'
- defaultMessage='Total Posts'
- />
- }
- icon='fa-comment'
- count={stats[StatTypes.TOTAL_POSTS]}
- />
- );
-
- postTotalGraph = (
- <div className='row'>
- <LineChart
- key={this.state.team.id}
- title={
- <FormattedMessage
- id='analytics.team.totalPosts'
- defaultMessage='Total Posts'
- />
- }
- data={postCountsDay}
- options={{
- legend: {
- display: false
- }
- }}
- width='740'
- height='225'
- />
- </div>
- );
-
- userActiveGraph = (
- <div className='row'>
- <LineChart
- key={this.state.team.id}
- title={
- <FormattedMessage
- id='analytics.team.activeUsers'
- defaultMessage='Active Users With Posts'
- />
- }
- data={userCountsWithPostsDay}
- options={{
- legend: {
- display: false
- }
- }}
- width='740'
- height='225'
- />
- </div>
- );
- }
-
- const recentActiveUsers = formatRecentUsersData(this.state.recentlyActiveUsers);
- const newlyCreatedUsers = formatNewUsersData(this.state.newUsers);
-
- const teams = this.props.teams.map((team) => {
- return (
- <option
- key={team.id}
- value={team.id}
- >
- {team.display_name}
- </option>
- );
- });
-
- return (
- <div className='wrapper--fixed team_statistics'>
- <div className='admin-console-header team-statistics__header-row'>
- <div className='team-statistics__header'>
- <h3>
- <FormattedMessage
- id='analytics.team.title'
- defaultMessage='Team Statistics for {team}'
- values={{
- team: this.state.team.display_name
- }}
- />
- </h3>
- </div>
- <div className='team-statistics__team-filter'>
- <select
- className='form-control team-statistics__team-filter__dropdown'
- onChange={this.handleTeamChange}
- value={this.state.team.id}
- >
- {teams}
- </select>
- </div>
- </div>
- {banner}
- <div className='row'>
- <StatisticCount
- title={
- <FormattedMessage
- id='analytics.team.totalUsers'
- defaultMessage='Total Users'
- />
- }
- icon='fa-user'
- count={stats[StatTypes.TOTAL_USERS]}
- />
- <StatisticCount
- title={
- <FormattedMessage
- id='analytics.team.publicChannels'
- defaultMessage='Public Channels'
- />
- }
- icon='fa-users'
- count={stats[StatTypes.TOTAL_PUBLIC_CHANNELS]}
- />
- <StatisticCount
- title={
- <FormattedMessage
- id='analytics.team.privateGroups'
- defaultMessage='Private Channels'
- />
- }
- icon='fa-globe'
- count={stats[StatTypes.TOTAL_PRIVATE_GROUPS]}
- />
- {totalPostsCount}
- </div>
- {postTotalGraph}
- {userActiveGraph}
- <div className='row'>
- <TableChart
- title={
- <FormattedMessage
- id='analytics.team.recentUsers'
- defaultMessage='Recent Active Users'
- />
- }
- data={recentActiveUsers}
- />
- <TableChart
- title={
- <FormattedMessage
- id='analytics.team.newlyCreated'
- defaultMessage='Newly Created Users'
- />
- }
- data={newlyCreatedUsers}
- />
- </div>
- </div>
- );
- }
-}
-
-export function formatRecentUsersData(data) {
- if (data == null) {
- return [];
- }
-
- const formattedData = data.map((user) => {
- const item = {};
- item.name = user.username;
- item.value = (
- <FormattedDate
- value={user.last_activity_at}
- day='numeric'
- month='long'
- year='numeric'
- hour12={true}
- hour='2-digit'
- minute='2-digit'
- />
- );
- item.tip = user.email;
-
- return item;
- });
-
- return formattedData;
-}
-
-export function formatNewUsersData(data) {
- if (data == null) {
- return [];
- }
-
- const formattedData = data.map((user) => {
- const item = {};
- item.name = user.username;
- item.value = (
- <FormattedDate
- value={user.create_at}
- day='numeric'
- month='long'
- year='numeric'
- hour12={true}
- hour='2-digit'
- minute='2-digit'
- />
- );
- item.tip = user.email;
-
- return item;
- });
-
- return formattedData;
-}
diff --git a/webapp/components/announcement_bar/announcement_bar.jsx b/webapp/components/announcement_bar/announcement_bar.jsx
deleted file mode 100644
index cd0cd0cb9..000000000
--- a/webapp/components/announcement_bar/announcement_bar.jsx
+++ /dev/null
@@ -1,322 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-import {Link} from 'react-router';
-
-import AnalyticsStore from 'stores/analytics_store.jsx';
-import ErrorStore from 'stores/error_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-
-import * as AdminActions from 'actions/admin_actions.jsx';
-import {ErrorBarTypes, StatTypes} from 'utils/constants.jsx';
-import {isLicenseExpiring, isLicenseExpired, isLicensePastGracePeriod, displayExpiryDate} from 'utils/license_utils.jsx';
-import * as Utils from 'utils/utils.jsx';
-import * as TextFormatting from 'utils/text_formatting.jsx';
-
-const RENEWAL_LINK = 'https://licensing.mattermost.com/renew';
-
-const BAR_DEVELOPER_TYPE = 'developer';
-const BAR_CRITICAL_TYPE = 'critical';
-const BAR_ANNOUNCEMENT_TYPE = 'announcement';
-
-const ANNOUNCEMENT_KEY = 'announcement--';
-
-export default class AnnouncementBar extends React.PureComponent {
- static propTypes = {
-
- /*
- * Set if the user is logged in
- */
- isLoggedIn: React.PropTypes.bool.isRequired
- }
-
- constructor() {
- super();
-
- this.onErrorChange = this.onErrorChange.bind(this);
- this.onAnalyticsChange = this.onAnalyticsChange.bind(this);
- this.handleClose = this.handleClose.bind(this);
-
- ErrorStore.clearLastError();
-
- this.setInitialError();
-
- this.state = this.getState();
- }
-
- setInitialError() {
- let isSystemAdmin = false;
- const user = UserStore.getCurrentUser();
- if (user) {
- isSystemAdmin = Utils.isSystemAdmin(user.roles);
- }
-
- const errorIgnored = ErrorStore.getIgnoreNotification();
-
- if (!errorIgnored) {
- if (isSystemAdmin && global.mm_config.SiteURL === '') {
- ErrorStore.storeLastError({notification: true, message: ErrorBarTypes.SITE_URL});
- return;
- } else if (global.mm_config.SendEmailNotifications === 'false') {
- ErrorStore.storeLastError({notification: true, message: ErrorBarTypes.PREVIEW_MODE});
- return;
- }
- }
-
- if (isLicensePastGracePeriod()) {
- if (isSystemAdmin) {
- ErrorStore.storeLastError({notification: true, message: ErrorBarTypes.LICENSE_EXPIRED, type: BAR_CRITICAL_TYPE});
- } else {
- ErrorStore.storeLastError({notification: true, message: ErrorBarTypes.LICENSE_PAST_GRACE, type: BAR_CRITICAL_TYPE});
- }
- } else if (isLicenseExpired() && isSystemAdmin) {
- ErrorStore.storeLastError({notification: true, message: ErrorBarTypes.LICENSE_EXPIRED, type: BAR_CRITICAL_TYPE});
- } else if (isLicenseExpiring() && isSystemAdmin) {
- ErrorStore.storeLastError({notification: true, message: ErrorBarTypes.LICENSE_EXPIRING, type: BAR_CRITICAL_TYPE});
- }
- }
-
- getState() {
- const error = ErrorStore.getLastError();
- if (error) {
- return {message: error.message, color: null, textColor: null, type: error.type, allowDismissal: true};
- }
-
- const bannerText = global.window.mm_config.BannerText || '';
- const allowDismissal = global.window.mm_config.AllowBannerDismissal === 'true';
- const bannerDismissed = localStorage.getItem(ANNOUNCEMENT_KEY + global.window.mm_config.BannerText);
-
- if (global.window.mm_config.EnableBanner === 'true' &&
- bannerText.length > 0 &&
- (!bannerDismissed || !allowDismissal)) {
- // Remove old local storage items
- Utils.removePrefixFromLocalStorage(ANNOUNCEMENT_KEY);
- return {
- message: bannerText,
- color: global.window.mm_config.BannerColor,
- textColor: global.window.mm_config.BannerTextColor,
- type: BAR_ANNOUNCEMENT_TYPE,
- allowDismissal
- };
- }
-
- return {message: null, color: null, colorText: null, textColor: null, type: null, allowDismissal: true};
- }
-
- isValidState(s) {
- if (!s) {
- return false;
- }
-
- if (!s.message) {
- return false;
- }
-
- if (s.message === ErrorBarTypes.LICENSE_EXPIRING && !this.state.totalUsers) {
- return false;
- }
-
- return true;
- }
-
- componentDidMount() {
- if (this.props.isLoggedIn && !this.state.allowDismissal) {
- document.body.classList.add('error-bar--fixed');
- }
-
- ErrorStore.addChangeListener(this.onErrorChange);
- AnalyticsStore.addChangeListener(this.onAnalyticsChange);
- }
-
- componentWillUnmount() {
- document.body.classList.remove('error-bar--fixed');
- ErrorStore.removeChangeListener(this.onErrorChange);
- AnalyticsStore.removeChangeListener(this.onAnalyticsChange);
- }
-
- componentDidUpdate(prevProps, prevState) {
- if (!this.props.isLoggedIn) {
- return;
- }
-
- if (!prevState.allowDismissal && this.state.allowDismissal) {
- document.body.classList.remove('error-bar--fixed');
- } else if (prevState.allowDismissal && !this.state.allowDismissal) {
- document.body.classList.add('error-bar--fixed');
- }
- }
-
- onErrorChange() {
- const newState = this.getState();
- if (newState.message === ErrorBarTypes.LICENSE_EXPIRING && !this.state.totalUsers) {
- AdminActions.getStandardAnalytics();
- }
- this.setState(newState);
- }
-
- onAnalyticsChange() {
- const stats = AnalyticsStore.getAllSystem();
- this.setState({totalUsers: stats[StatTypes.TOTAL_USERS]});
- }
-
- handleClose(e) {
- if (e) {
- e.preventDefault();
- }
-
- if (this.state.type === BAR_ANNOUNCEMENT_TYPE) {
- localStorage.setItem(ANNOUNCEMENT_KEY + this.state.message, true);
- }
-
- if (ErrorStore.getLastError() && ErrorStore.getLastError().notification) {
- ErrorStore.clearNotificationError();
- } else {
- ErrorStore.clearLastError();
- }
-
- this.setState(this.getState());
- }
-
- render() {
- if (!this.isValidState(this.state)) {
- return <div/>;
- }
-
- if (!this.props.isLoggedIn && this.state.type === BAR_ANNOUNCEMENT_TYPE) {
- return <div/>;
- }
-
- let errClass = 'error-bar';
- let dismissClass = ' error-bar--fixed';
- const barStyle = {};
- const linkStyle = {};
- if (this.state.color && this.state.textColor) {
- barStyle.backgroundColor = this.state.color;
- barStyle.color = this.state.textColor;
- linkStyle.color = this.state.textColor;
- } else if (this.state.type === BAR_DEVELOPER_TYPE) {
- errClass = 'error-bar error-bar-developer';
- } else if (this.state.type === BAR_CRITICAL_TYPE) {
- errClass = 'error-bar error-bar-critical';
- }
-
- let closeButton;
- if (this.state.allowDismissal) {
- dismissClass = '';
- closeButton = (
- <a
- href='#'
- className='error-bar__close'
- style={linkStyle}
- onClick={this.handleClose}
- >
- {'×'}
- </a>
- );
- }
-
- const renewalLink = RENEWAL_LINK + '?id=' + global.window.mm_license.Id + '&user_count=' + this.state.totalUsers;
-
- let message = this.state.message;
- if (this.state.type === BAR_ANNOUNCEMENT_TYPE) {
- message = (
- <span
- dangerouslySetInnerHTML={{__html: TextFormatting.formatText(message, {singleline: true, mentionHighlight: false})}}
- />
- );
- } else if (message === ErrorBarTypes.PREVIEW_MODE) {
- message = (
- <FormattedMessage
- id={ErrorBarTypes.PREVIEW_MODE}
- defaultMessage='Preview Mode: Email notifications have not been configured'
- />
- );
- } else if (message === ErrorBarTypes.LICENSE_EXPIRING) {
- message = (
- <FormattedHTMLMessage
- id={ErrorBarTypes.LICENSE_EXPIRING}
- defaultMessage='Enterprise license expires on {date}. <a href="{link}" target="_blank">Please renew</a>.'
- values={{
- date: displayExpiryDate(),
- link: renewalLink
- }}
- />
- );
- } else if (message === ErrorBarTypes.LICENSE_EXPIRED) {
- message = (
- <FormattedHTMLMessage
- id={ErrorBarTypes.LICENSE_EXPIRED}
- defaultMessage='Enterprise license is expired and some features may be disabled. <a href="{link}" target="_blank">Please renew</a>.'
- values={{
- link: renewalLink
- }}
- />
- );
- } else if (message === ErrorBarTypes.LICENSE_PAST_GRACE) {
- message = (
- <FormattedMessage
- id={ErrorBarTypes.LICENSE_PAST_GRACE}
- defaultMessage='Enterprise license is expired and some features may be disabled. Please contact your System Administrator for details.'
- />
- );
- } else if (message === ErrorBarTypes.WEBSOCKET_PORT_ERROR) {
- message = (
- <FormattedHTMLMessage
- id={ErrorBarTypes.WEBSOCKET_PORT_ERROR}
- defaultMessage='Please check connection, Mattermost unreachable. If issue persists, ask administrator to <a href="https://about.mattermost.com/default-websocket-port-help" target="_blank">check WebSocket port</a>.'
- />
- );
- } else if (message === ErrorBarTypes.SITE_URL) {
- let id;
- let defaultMessage;
- if (global.mm_config.EnableSignUpWithGitLab === 'true') {
- id = 'error_bar.site_url_gitlab';
- defaultMessage = 'Please configure your {docsLink} in the System Console or in gitlab.rb if you\'re using GitLab Mattermost.';
- } else {
- id = 'error_bar.site_url';
- defaultMessage = 'Please configure your {docsLink} in the System Console.';
- }
-
- message = (
- <FormattedMessage
- id={id}
- defaultMessage={defaultMessage}
- values={{
- docsLink: (
- <a
- href='https://docs.mattermost.com/administration/config-settings.html#site-url'
- rel='noopener noreferrer'
- target='_blank'
- >
- <FormattedMessage
- id='error_bar.site_url.docsLink'
- defaultMessage='Site URL'
- />
- </a>
- ),
- link: (
- <Link to='/admin_console/general/configuration'>
- <FormattedMessage
- id='error_bar.site_url.link'
- defaultMessage='the System Console'
- />
- </Link>
- )
- }}
- />
- );
- }
-
- return (
- <div
- className={errClass + dismissClass}
- style={barStyle}
- >
- <span>{message}</span>
- {closeButton}
- </div>
- );
- }
-}
diff --git a/webapp/components/announcement_bar/index.js b/webapp/components/announcement_bar/index.js
deleted file mode 100644
index 8fe4f56b4..000000000
--- a/webapp/components/announcement_bar/index.js
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
-
-import AnnouncementBar from './announcement_bar.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps,
- isLoggedIn: Boolean(getCurrentUserId(state))
- };
-}
-
-export default connect(mapStateToProps)(AnnouncementBar);
diff --git a/webapp/components/at_mention/at_mention.jsx b/webapp/components/at_mention/at_mention.jsx
deleted file mode 100644
index 668222cc2..000000000
--- a/webapp/components/at_mention/at_mention.jsx
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ProfilePopover from 'components/profile_popover.jsx';
-import Pluggable from 'plugins/pluggable';
-import {Client4} from 'mattermost-redux/client';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {OverlayTrigger} from 'react-bootstrap';
-
-export default class AtMention extends React.PureComponent {
- static propTypes = {
- mentionName: PropTypes.string.isRequired,
- usersByUsername: PropTypes.object.isRequired,
- isRHS: PropTypes.bool,
- hasMention: PropTypes.bool
- };
-
- static defaultProps = {
- isRHS: false,
- hasMention: false
- }
-
- constructor(props) {
- super(props);
-
- this.hideProfilePopover = this.hideProfilePopover.bind(this);
-
- this.state = {
- user: this.getUserFromMentionName(props)
- };
- }
-
- componentWillReceiveProps(nextProps) {
- if (nextProps.mentionName !== this.props.mentionName || nextProps.usersByUsername !== this.props.usersByUsername) {
- this.setState({
- user: this.getUserFromMentionName(nextProps)
- });
- }
- }
-
- hideProfilePopover() {
- this.refs.overlay.hide();
- }
-
- getUserFromMentionName(props) {
- const usersByUsername = props.usersByUsername;
- let mentionName = props.mentionName;
-
- while (mentionName.length > 0) {
- if (usersByUsername[mentionName]) {
- return usersByUsername[mentionName];
- }
-
- // Repeatedly trim off trailing punctuation in case this is at the end of a sentence
- if ((/[._-]$/).test(mentionName)) {
- mentionName = mentionName.substring(0, mentionName.length - 1);
- } else {
- break;
- }
- }
-
- return '';
- }
-
- render() {
- if (!this.state.user) {
- return <span>{'@' + this.props.mentionName}</span>;
- }
-
- const user = this.state.user;
- const suffix = this.props.mentionName.substring(user.username.length);
-
- return (
- <span>
- <OverlayTrigger
- ref='overlay'
- trigger='click'
- placement='right'
- rootClose={true}
- overlay={
- <Pluggable>
- <ProfilePopover
- user={user}
- src={Client4.getProfilePictureUrl(user.id, user.last_picture_update)}
- hide={this.hideProfilePopover}
- isRHS={this.props.isRHS}
- hasMention={this.props.hasMention}
- />
- </Pluggable>
- }
- >
- <a className='mention-link'>{'@' + user.username}</a>
- </OverlayTrigger>
- {suffix}
- </span>
- );
- }
-}
diff --git a/webapp/components/at_mention/index.jsx b/webapp/components/at_mention/index.jsx
deleted file mode 100644
index 37c886bbd..000000000
--- a/webapp/components/at_mention/index.jsx
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-
-import {getUsersByUsername} from 'mattermost-redux/selectors/entities/users';
-
-import AtMention from './at_mention.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps,
- usersByUsername: getUsersByUsername(state)
- };
-}
-
-export default connect(mapStateToProps)(AtMention);
diff --git a/webapp/components/audio_video_preview.jsx b/webapp/components/audio_video_preview.jsx
deleted file mode 100644
index 968f8433f..000000000
--- a/webapp/components/audio_video_preview.jsx
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import ReactDOM from 'react-dom';
-import Constants from 'utils/constants.jsx';
-import FileInfoPreview from './file_info_preview.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class AudioVideoPreview extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleFileInfoChanged = this.handleFileInfoChanged.bind(this);
- this.handleLoadError = this.handleLoadError.bind(this);
-
- this.stop = this.stop.bind(this);
-
- this.state = {
- canPlay: true
- };
- }
-
- componentWillMount() {
- this.handleFileInfoChanged(this.props.fileInfo);
- }
-
- componentDidMount() {
- if (this.refs.source) {
- $(ReactDOM.findDOMNode(this.refs.source)).one('error', this.handleLoadError);
- }
- }
-
- componentWillReceiveProps(nextProps) {
- if (this.props.fileUrl !== nextProps.fileUrl) {
- this.handleFileInfoChanged(nextProps.fileInfo);
- }
- }
-
- handleFileInfoChanged(fileInfo) {
- let video = ReactDOM.findDOMNode(this.refs.video);
- if (!video) {
- video = document.createElement('video');
- }
-
- const canPlayType = video.canPlayType(fileInfo.mime_type);
-
- this.setState({
- canPlay: canPlayType === 'probably' || canPlayType === 'maybe'
- });
- }
-
- componentDidUpdate() {
- if (this.refs.source) {
- $(ReactDOM.findDOMNode(this.refs.source)).one('error', this.handleLoadError);
- }
- }
-
- handleLoadError() {
- this.setState({
- canPlay: false
- });
- }
-
- stop() {
- if (this.refs.video) {
- const video = ReactDOM.findDOMNode(this.refs.video);
- video.pause();
- video.currentTime = 0;
- }
- }
-
- render() {
- if (!this.state.canPlay) {
- return (
- <FileInfoPreview
- fileInfo={this.props.fileInfo}
- fileUrl={this.props.fileUrl}
- />
- );
- }
-
- let width = Constants.WEB_VIDEO_WIDTH;
- let height = Constants.WEB_VIDEO_HEIGHT;
- if (Utils.isMobile()) {
- width = Constants.MOBILE_VIDEO_WIDTH;
- height = Constants.MOBILE_VIDEO_HEIGHT;
- }
-
- // add a key to the video to prevent React from using an old video source while a new one is loading
- return (
- <video
- key={this.props.fileInfo.id}
- ref='video'
- data-setup='{}'
- controls='controls'
- width={width}
- height={height}
- >
- <source
- ref='source'
- src={this.props.fileUrl}
- />
- </video>
- );
- }
-}
-
-AudioVideoPreview.propTypes = {
- fileInfo: PropTypes.object.isRequired,
- fileUrl: PropTypes.string.isRequired
-};
diff --git a/webapp/components/audit_table.jsx b/webapp/components/audit_table.jsx
deleted file mode 100644
index 1f005e7e5..000000000
--- a/webapp/components/audit_table.jsx
+++ /dev/null
@@ -1,644 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import UserStore from 'stores/user_store.jsx';
-import ChannelStore from 'stores/channel_store.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedDate, FormattedTime} from 'react-intl';
-
-const holders = defineMessages({
- sessionRevoked: {
- id: 'audit_table.sessionRevoked',
- defaultMessage: 'The session with id {sessionId} was revoked'
- },
- channelCreated: {
- id: 'audit_table.channelCreated',
- defaultMessage: 'Created the {channelName} channel'
- },
- establishedDM: {
- id: 'audit_table.establishedDM',
- defaultMessage: 'Established a direct message channel with {username}'
- },
- nameUpdated: {
- id: 'audit_table.nameUpdated',
- defaultMessage: 'Updated the {channelName} channel name'
- },
- headerUpdated: {
- id: 'audit_table.headerUpdated',
- defaultMessage: 'Updated the {channelName} channel header'
- },
- channelDeleted: {
- id: 'audit_table.channelDeleted',
- defaultMessage: 'Deleted the channel with the URL {url}'
- },
- userAdded: {
- id: 'audit_table.userAdded',
- defaultMessage: 'Added {username} to the {channelName} channel'
- },
- userRemoved: {
- id: 'audit_table.userRemoved',
- defaultMessage: 'Removed {username} to the {channelName} channel'
- },
- attemptedRegisterApp: {
- id: 'audit_table.attemptedRegisterApp',
- defaultMessage: 'Attempted to register a new OAuth Application with ID {id}'
- },
- attemptedAllowOAuthAccess: {
- id: 'audit_table.attemptedAllowOAuthAccess',
- defaultMessage: 'Attempted to allow a new OAuth service access'
- },
- successfullOAuthAccess: {
- id: 'audit_table.successfullOAuthAccess',
- defaultMessage: 'Successfully gave a new OAuth service access'
- },
- failedOAuthAccess: {
- id: 'audit_table.failedOAuthAccess',
- defaultMessage: 'Failed to allow a new OAuth service access - the redirect URI did not match the previously registered callback'
- },
- attemptedOAuthToken: {
- id: 'audit_table.attemptedOAuthToken',
- defaultMessage: 'Attempted to get an OAuth access token'
- },
- successfullOAuthToken: {
- id: 'audit_table.successfullOAuthToken',
- defaultMessage: 'Successfully added a new OAuth service'
- },
- oauthTokenFailed: {
- id: 'audit_table.oauthTokenFailed',
- defaultMessage: 'Failed to get an OAuth access token - {token}'
- },
- attemptedLogin: {
- id: 'audit_table.attemptedLogin',
- defaultMessage: 'Attempted to login'
- },
- successfullLogin: {
- id: 'audit_table.successfullLogin',
- defaultMessage: 'Successfully logged in'
- },
- failedLogin: {
- id: 'audit_table.failedLogin',
- defaultMessage: 'FAILED login attempt'
- },
- updatePicture: {
- id: 'audit_table.updatePicture',
- defaultMessage: 'Updated your profile picture'
- },
- updateGeneral: {
- id: 'audit_table.updateGeneral',
- defaultMessage: 'Updated the general settings of your account'
- },
- attemptedPassword: {
- id: 'audit_table.attemptedPassword',
- defaultMessage: 'Attempted to change password'
- },
- successfullPassword: {
- id: 'audit_table.successfullPassword',
- defaultMessage: 'Successfully changed password'
- },
- failedPassword: {
- id: 'audit_table.failedPassword',
- defaultMessage: 'Failed to change password - tried to update user password who was logged in through OAuth'
- },
- updatedRol: {
- id: 'audit_table.updatedRol',
- defaultMessage: 'Updated user role(s) to '
- },
- member: {
- id: 'audit_table.member',
- defaultMessage: 'member'
- },
- accountActive: {
- id: 'audit_table.accountActive',
- defaultMessage: 'Account activated'
- },
- accountInactive: {
- id: 'audit_table.accountInactive',
- defaultMessage: 'Account deactivated'
- },
- by: {
- id: 'audit_table.by',
- defaultMessage: ' by {username}'
- },
- byAdmin: {
- id: 'audit_table.byAdmin',
- defaultMessage: ' by an admin'
- },
- sentEmail: {
- id: 'audit_table.sentEmail',
- defaultMessage: 'Sent an email to {email} to reset your password'
- },
- attemptedReset: {
- id: 'audit_table.attemptedReset',
- defaultMessage: 'Attempted to reset password'
- },
- successfullReset: {
- id: 'audit_table.successfullReset',
- defaultMessage: 'Successfully reset password'
- },
- updateGlobalNotifications: {
- id: 'audit_table.updateGlobalNotifications',
- defaultMessage: 'Updated your global notification settings'
- },
- attemptedWebhookCreate: {
- id: 'audit_table.attemptedWebhookCreate',
- defaultMessage: 'Attempted to create a webhook'
- },
- succcessfullWebhookCreate: {
- id: 'audit_table.successfullWebhookCreate',
- defaultMessage: 'Successfully created a webhook'
- },
- failedWebhookCreate: {
- id: 'audit_table.failedWebhookCreate',
- defaultMessage: 'Failed to create a webhook - bad channel permissions'
- },
- attemptedWebhookDelete: {
- id: 'audit_table.attemptedWebhookDelete',
- defaultMessage: 'Attempted to delete a webhook'
- },
- successfullWebhookDelete: {
- id: 'audit_table.successfullWebhookDelete',
- defaultMessage: 'Successfully deleted a webhook'
- },
- failedWebhookDelete: {
- id: 'audit_table.failedWebhookDelete',
- defaultMessage: 'Failed to delete a webhook - inappropriate conditions'
- },
- logout: {
- id: 'audit_table.logout',
- defaultMessage: 'Logged out of your account'
- },
- verified: {
- id: 'audit_table.verified',
- defaultMessage: 'Sucessfully verified your email address'
- },
- revokedAll: {
- id: 'audit_table.revokedAll',
- defaultMessage: 'Revoked all current sessions for the team'
- },
- loginAttempt: {
- id: 'audit_table.loginAttempt',
- defaultMessage: ' (Login attempt)'
- },
- loginFailure: {
- id: 'audit_table.loginFailure',
- defaultMessage: ' (Login failure)'
- },
- attemptedLicenseAdd: {
- id: 'audit_table.attemptedLicenseAdd',
- defaultMessage: 'Attempted to add new license'
- },
- successfullLicenseAdd: {
- id: 'audit_table.successfullLicenseAdd',
- defaultMessage: 'Successfully added new license'
- },
- failedExpiredLicenseAdd: {
- id: 'audit_table.failedExpiredLicenseAdd',
- defaultMessage: 'Failed to add a new license as it has either expired or not yet been started'
- },
- failedInvalidLicenseAdd: {
- id: 'audit_table.failedInvalidLicenseAdd',
- defaultMessage: 'Failed to add an invalid license'
- },
- licenseRemoved: {
- id: 'audit_table.licenseRemoved',
- defaultMessage: 'Successfully removed a license'
- }
-});
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-function AuditTable(props) {
- var accessList = [];
-
- const {formatMessage} = props.intl;
- for (var i = 0; i < props.audits.length; i++) {
- const audit = props.audits[i];
- const auditInfo = formatAuditInfo(audit, formatMessage);
-
- let uContent;
- if (props.showUserId) {
- var profile = UserStore.getProfile(auditInfo.userId);
- if (profile) {
- uContent = <td className='word-break--all'>{profile.email}</td>;
- } else {
- uContent = <td className='word-break--all'>{auditInfo.userId}</td>;
- }
- }
-
- let iContent;
- if (props.showIp) {
- iContent = <td className='whitespace--nowrap word-break--all'>{auditInfo.ip}</td>;
- }
-
- let sContent;
- if (props.showSession) {
- sContent = <td className='whitespace--nowrap word-break--all'>{auditInfo.sessionId}</td>;
- }
-
- const descStyle = {};
- if (auditInfo.desc.toLowerCase().indexOf('fail') !== -1) {
- descStyle.color = 'red';
- }
-
- accessList[i] = (
- <tr key={audit.id}>
- <td className='whitespace--nowrap word-break--all'>{auditInfo.timestamp}</td>
- {uContent}
- <td
- className='word-break--all'
- style={descStyle}
- >
- {auditInfo.desc}
- </td>
- {iContent}
- {sContent}
- </tr>
- );
- }
-
- let userIdContent;
- if (props.showUserId) {
- userIdContent = (
- <th>
- <FormattedMessage
- id='audit_table.userId'
- defaultMessage='User ID'
- />
- </th>
- );
- }
-
- let ipContent;
- if (props.showIp) {
- ipContent = (
- <th>
- <FormattedMessage
- id='audit_table.ip'
- defaultMessage='IP Address'
- />
- </th>
- );
- }
-
- let sessionContent;
- if (props.showSession) {
- sessionContent = (
- <th>
- <FormattedMessage
- id='audit_table.session'
- defaultMessage='Session ID'
- />
- </th>
- );
- }
-
- return (
- <table className='table'>
- <thead>
- <tr>
- <th>
- <FormattedMessage
- id='audit_table.timestamp'
- defaultMessage='Timestamp'
- />
- </th>
- {userIdContent}
- <th>
- <FormattedMessage
- id='audit_table.action'
- defaultMessage='Action'
- />
- </th>
- {ipContent}
- {sessionContent}
- </tr>
- </thead>
- <tbody>
- {accessList}
- </tbody>
- </table>
- );
-}
-
-AuditTable.propTypes = {
- intl: intlShape.isRequired,
- audits: PropTypes.array.isRequired,
- showUserId: PropTypes.bool,
- showIp: PropTypes.bool,
- showSession: PropTypes.bool
-};
-
-export default injectIntl(AuditTable);
-
-export function formatAuditInfo(audit, formatMessage) {
- const actionURL = audit.action.replace(/\/api\/v[1-9]/, '');
- let auditDesc = '';
-
- if (actionURL.indexOf('/channels') === 0) {
- const channelInfo = audit.extra_info.split(' ');
- const channelNameField = channelInfo[0].split('=');
-
- let channelURL = '';
- let channelObj;
- let channelName = '';
- if (channelNameField.indexOf('name') >= 0) {
- channelURL = channelNameField[channelNameField.indexOf('name') + 1];
- channelObj = ChannelStore.getByName(channelURL);
- if (channelObj) {
- channelName = channelObj.display_name;
- } else {
- channelName = channelURL;
- }
- }
-
- switch (actionURL) {
- case '/channels/create':
- auditDesc = formatMessage(holders.channelCreated, {channelName});
- break;
- case '/channels/create_direct':
- auditDesc = formatMessage(holders.establishedDM, {username: Utils.getDirectTeammate(channelObj.id).username});
- break;
- case '/channels/update':
- auditDesc = formatMessage(holders.nameUpdated, {channelName});
- break;
- case '/channels/update_desc': // support the old path
- case '/channels/update_header':
- auditDesc = formatMessage(holders.headerUpdated, {channelName});
- break;
- default: {
- let userIdField = [];
- let userId = '';
- let username = '';
-
- if (channelInfo[1]) {
- userIdField = channelInfo[1].split('=');
-
- if (userIdField.indexOf('user_id') >= 0) {
- userId = userIdField[userIdField.indexOf('user_id') + 1];
- var profile = UserStore.getProfile(userId);
- if (profile) {
- username = profile.username;
- }
- }
- }
-
- if (/\/channels\/[A-Za-z0-9]+\/delete/.test(actionURL)) {
- auditDesc = formatMessage(holders.channelDeleted, {url: channelURL});
- } else if (/\/channels\/[A-Za-z0-9]+\/add/.test(actionURL)) {
- auditDesc = formatMessage(holders.userAdded, {username, channelName});
- } else if (/\/channels\/[A-Za-z0-9]+\/remove/.test(actionURL)) {
- auditDesc = formatMessage(holders.userRemoved, {username, channelName});
- }
-
- break;
- }
- }
- } else if (actionURL.indexOf('/oauth') === 0) {
- const oauthInfo = audit.extra_info.split(' ');
-
- switch (actionURL) {
- case '/oauth/register': {
- const clientIdField = oauthInfo[0].split('=');
-
- if (clientIdField[0] === 'client_id') {
- auditDesc = formatMessage(holders.attemptedRegisterApp, {id: clientIdField[1]});
- }
-
- break;
- }
- case '/oauth/allow':
- if (oauthInfo[0] === 'attempt') {
- auditDesc = formatMessage(holders.attemptedAllowOAuthAccess);
- } else if (oauthInfo[0] === 'success') {
- auditDesc = formatMessage(holders.successfullOAuthAccess);
- } else if (oauthInfo[0] === 'fail - redirect_uri did not match registered callback') {
- auditDesc = formatMessage(holders.failedOAuthAccess);
- }
-
- break;
- case '/oauth/access_token':
- if (oauthInfo[0] === 'attempt') {
- auditDesc = formatMessage(holders.attemptedOAuthToken);
- } else if (oauthInfo[0] === 'success') {
- auditDesc = formatMessage(holders.successfullOAuthToken);
- } else {
- const oauthTokenFailure = oauthInfo[0].split('-');
-
- if (oauthTokenFailure[0].trim() === 'fail' && oauthTokenFailure[1]) {
- auditDesc = formatMessage(oauthTokenFailure, {token: oauthTokenFailure[1].trim()});
- }
- }
-
- break;
- default:
- break;
- }
- } else if (actionURL.indexOf('/users') === 0) {
- const userInfo = audit.extra_info.split(' ');
-
- switch (actionURL) {
- case '/users/login':
- if (userInfo[0] === 'attempt') {
- auditDesc = formatMessage(holders.attemptedLogin);
- } else if (userInfo[0] === 'success') {
- auditDesc = formatMessage(holders.successfullLogin);
- } else if (userInfo[0]) {
- auditDesc = formatMessage(holders.failedLogin);
- }
-
- break;
- case '/users/revoke_session':
- auditDesc = formatMessage(holders.sessionRevoked, {sessionId: userInfo[0].split('=')[1]});
- break;
- case '/users/newimage':
- auditDesc = formatMessage(holders.updatePicture);
- break;
- case '/users/update':
- auditDesc = formatMessage(holders.updateGeneral);
- break;
- case '/users/newpassword':
- if (userInfo[0] === 'attempted') {
- auditDesc = formatMessage(holders.attemptedPassword);
- } else if (userInfo[0] === 'completed') {
- auditDesc = formatMessage(holders.successfullPassword);
- } else if (userInfo[0] === 'failed - tried to update user password who was logged in through oauth') {
- auditDesc = formatMessage(holders.failedPassword);
- }
-
- break;
- case '/users/update_roles': {
- const userRoles = userInfo[0].split('=')[1];
-
- auditDesc = formatMessage(holders.updatedRol);
- if (userRoles.trim()) {
- auditDesc += userRoles;
- } else {
- auditDesc += formatMessage(holders.member);
- }
-
- break;
- }
- case '/users/update_active': {
- const updateType = userInfo[0].split('=')[0];
- const updateField = userInfo[0].split('=')[1];
-
- /* Either describes account activation/deactivation or a revoked session as part of an account deactivation */
- if (updateType === 'active') {
- if (updateField === 'true') {
- auditDesc = formatMessage(holders.accountActive);
- } else if (updateField === 'false') {
- auditDesc = formatMessage(holders.accountInactive);
- }
-
- const actingUserInfo = userInfo[1].split('=');
- if (actingUserInfo[0] === 'session_user') {
- const actingUser = UserStore.getProfile(actingUserInfo[1]);
- const user = UserStore.getCurrentUser();
- if (user && actingUser && (Utils.isSystemAdmin(user.roles))) {
- auditDesc += formatMessage(holders.by, {username: actingUser.username});
- } else if (user && actingUser) {
- auditDesc += formatMessage(holders.byAdmin);
- }
- }
- } else if (updateType === 'session_id') {
- auditDesc = formatMessage(holders.sessionRevoked, {sessionId: updateField});
- }
-
- break;
- }
- case '/users/send_password_reset':
- auditDesc = formatMessage(holders.sentEmail, {email: userInfo[0].split('=')[1]});
- break;
- case '/users/reset_password':
- if (userInfo[0] === 'attempt') {
- auditDesc = formatMessage(holders.attemptedReset);
- } else if (userInfo[0] === 'success') {
- auditDesc = formatMessage(holders.successfullReset);
- }
-
- break;
- case '/users/update_notify':
- auditDesc = formatMessage(holders.updateGlobalNotifications);
- break;
- default:
- break;
- }
- } else if (actionURL.indexOf('/hooks') === 0) {
- const webhookInfo = audit.extra_info;
-
- switch (actionURL) {
- case '/hooks/incoming/create':
- if (webhookInfo === 'attempt') {
- auditDesc = formatMessage(holders.attemptedWebhookCreate);
- } else if (webhookInfo === 'success') {
- auditDesc = formatMessage(holders.succcessfullWebhookCreate);
- } else if (webhookInfo === 'fail - bad channel permissions') {
- auditDesc = formatMessage(holders.failedWebhookCreate);
- }
-
- break;
- case '/hooks/incoming/delete':
- if (webhookInfo === 'attempt') {
- auditDesc = formatMessage(holders.attemptedWebhookDelete);
- } else if (webhookInfo === 'success') {
- auditDesc = formatMessage(holders.successfullWebhookDelete);
- } else if (webhookInfo === 'fail - inappropriate conditions') {
- auditDesc = formatMessage(holders.failedWebhookDelete);
- }
-
- break;
- default:
- break;
- }
- } else if (actionURL.indexOf('/license') === 0) {
- const licenseInfo = audit.extra_info;
-
- switch (actionURL) {
- case '/license/add':
- if (licenseInfo === 'attempt') {
- auditDesc = formatMessage(holders.attemptedLicenseAdd);
- } else if (licenseInfo === 'success') {
- auditDesc = formatMessage(holders.successfullLicenseAdd);
- } else if (licenseInfo === 'failed - expired or non-started license') {
- auditDesc = formatMessage(holders.failedExpiredLicenseAdd);
- } else if (licenseInfo === 'failed - invalid license') {
- auditDesc = formatMessage(holders.failedInvalidLicenseAdd);
- }
-
- break;
- case '/license/remove':
- auditDesc = formatMessage(holders.licenseRemoved);
- break;
- default:
- break;
- }
- } else if (actionURL.indexOf('/admin/download_compliance_report') === 0) {
- auditDesc = Utils.toTitleCase(audit.extra_info);
- } else {
- switch (actionURL) {
- case '/logout':
- auditDesc = formatMessage(holders.logout);
- break;
- case '/verify_email':
- auditDesc = formatMessage(holders.verified);
- break;
- default:
- break;
- }
- }
-
- /* If all else fails... */
- if (!auditDesc) {
- /* Currently not called anywhere */
- if (audit.extra_info.indexOf('revoked_all=') >= 0) {
- auditDesc = formatMessage(holders.revokedAll);
- } else {
- let actionDesc = '';
- if (actionURL && actionURL.lastIndexOf('/') !== -1) {
- actionDesc = actionURL.substring(actionURL.lastIndexOf('/') + 1).replace('_', ' ');
- actionDesc = Utils.toTitleCase(actionDesc);
- }
-
- let extraInfoDesc = '';
- if (audit.extra_info) {
- extraInfoDesc = audit.extra_info;
-
- if (extraInfoDesc.indexOf('=') !== -1) {
- extraInfoDesc = extraInfoDesc.substring(extraInfoDesc.indexOf('=') + 1);
- }
- }
- auditDesc = actionDesc + ' ' + extraInfoDesc;
- }
- }
-
- const date = new Date(audit.create_at);
- const auditInfo = {};
- auditInfo.timestamp = (
- <div>
- <div>
- <FormattedDate
- value={date}
- day='2-digit'
- month='short'
- year='numeric'
- />
- </div>
- <div>
- <FormattedTime
- value={date}
- hour='2-digit'
- minute='2-digit'
- />
- </div>
- </div>
- );
- auditInfo.userId = audit.user_id;
- auditInfo.desc = auditDesc;
- auditInfo.ip = audit.ip_address;
- auditInfo.sessionId = audit.session_id;
-
- return auditInfo;
-}
diff --git a/webapp/components/authorize.jsx b/webapp/components/authorize.jsx
deleted file mode 100644
index ec8bc5a06..000000000
--- a/webapp/components/authorize.jsx
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import FormError from 'components/form_error.jsx';
-
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import icon50 from 'images/icon50x50.png';
-
-import {getOAuthAppInfo, allowOAuth2} from 'actions/admin_actions.jsx';
-
-export default class Authorize extends React.Component {
- static get propTypes() {
- return {
- location: PropTypes.object.isRequired,
- params: PropTypes.object.isRequired
- };
- }
-
- constructor(props) {
- super(props);
-
- this.handleAllow = this.handleAllow.bind(this);
- this.handleDeny = this.handleDeny.bind(this);
-
- this.state = {};
- }
-
- componentWillMount() {
- getOAuthAppInfo(
- this.props.location.query.client_id,
- (app) => {
- this.setState({app});
- }
- );
- }
-
- componentDidMount() {
- // if we get to this point remove the antiClickjack blocker
- const blocker = document.getElementById('antiClickjack');
- if (blocker) {
- blocker.parentNode.removeChild(blocker);
- }
- }
-
- handleAllow() {
- const params = this.props.location.query;
-
- allowOAuth2(params,
- (data) => {
- if (data.redirect) {
- window.location.href = data.redirect;
- }
- },
- (err) => {
- this.setState({error: err.message});
- }
- );
- }
-
- handleDeny() {
- window.location.replace(this.props.location.query.redirect_uri + '?error=access_denied');
- }
-
- render() {
- const app = this.state.app;
- if (!app) {
- return null;
- }
-
- let icon;
- if (app.icon_url) {
- icon = app.icon_url;
- } else {
- icon = icon50;
- }
-
- let error;
- if (this.state.error) {
- error = (
- <div className='prompt__error form-group'>
- <FormError error={this.state.error}/>
- </div>
- );
- }
-
- return (
- <div className='container-fluid'>
- <div className='prompt'>
- <div className='prompt__heading'>
- <div className='prompt__app-icon'>
- <img
- src={icon}
- width='50'
- height='50'
- alt=''
- />
- </div>
- <div className='text'>
- <FormattedHTMLMessage
- id='authorize.title'
- defaultMessage='<strong>{appName}</strong> would like to connect to your <strong>Mattermost</strong> user account'
- values={{
- appName: app.name
- }}
- />
- </div>
- </div>
- <p>
- <FormattedHTMLMessage
- id='authorize.app'
- defaultMessage='The app <strong>{appName}</strong> would like the ability to access and modify your basic information.'
- values={{
- appName: app.name
- }}
- />
- </p>
- <h2 className='prompt__allow'>
- <FormattedHTMLMessage
- id='authorize.access'
- defaultMessage='Allow <strong>{appName}</strong> access?'
- values={{
- appName: app.name
- }}
- />
- </h2>
- <div className='prompt__buttons'>
- <button
- type='submit'
- className='btn btn-link authorize-btn'
- onClick={this.handleDeny}
- >
- <FormattedMessage
- id='authorize.deny'
- defaultMessage='Deny'
- />
- </button>
- <button
- type='submit'
- className='btn btn-primary authorize-btn'
- onClick={this.handleAllow}
- >
- <FormattedMessage
- id='authorize.allow'
- defaultMessage='Allow'
- />
- </button>
- </div>
- {error}
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/autosize_textarea.jsx b/webapp/components/autosize_textarea.jsx
deleted file mode 100644
index 17d07b59a..000000000
--- a/webapp/components/autosize_textarea.jsx
+++ /dev/null
@@ -1,102 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-export default class AutosizeTextarea extends React.Component {
- static propTypes = {
- value: PropTypes.string,
- placeholder: PropTypes.string,
- onHeightChange: PropTypes.func
- }
-
- constructor(props) {
- super(props);
-
- this.height = 0;
- }
-
- get value() {
- return this.refs.textarea.value;
- }
-
- set value(value) {
- this.refs.textarea.value = value;
- }
-
- componentDidUpdate() {
- this.recalculateSize();
- }
-
- recalculateSize() {
- const height = this.refs.reference.scrollHeight;
-
- if (height > 0 && height !== this.height) {
- const textarea = this.refs.textarea;
-
- const style = getComputedStyle(textarea);
- const borderWidth = parseInt(style.borderTopWidth, 10) + parseInt(style.borderBottomWidth, 10);
-
- // Directly change the height to avoid circular rerenders
- textarea.style.height = String(height + borderWidth) + 'px';
-
- this.height = height;
-
- if (this.props.onHeightChange) {
- this.props.onHeightChange(height, parseInt(style.maxHeight, 10));
- }
- }
- }
-
- getDOMNode() {
- return this.refs.textarea;
- }
-
- render() {
- const props = {...this.props};
-
- Reflect.deleteProperty(props, 'onHeightChange');
- Reflect.deleteProperty(props, 'providers');
- Reflect.deleteProperty(props, 'channelId');
-
- const {
- value,
- placeholder,
- id,
- ...otherProps
- } = props;
-
- const heightProps = {};
- if (this.height <= 0) {
- // Set an initial number of rows so that the textarea doesn't appear too large when its first rendered
- heightProps.rows = 1;
- } else {
- heightProps.height = this.height;
- }
-
- return (
- <div>
- <textarea
- ref='textarea'
- id={id + '-textarea'}
- {...heightProps}
- {...props}
- />
- <div style={{height: 0, overflow: 'hidden'}}>
- <textarea
- ref='reference'
- id={id + '-reference'}
- style={{height: 'auto', width: '100%'}}
- disabled={true}
- value={value}
- placeholder={placeholder}
- rows='1'
- {...otherProps}
- />
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/backstage/backstage_controller.jsx b/webapp/components/backstage/backstage_controller.jsx
deleted file mode 100644
index 795fb0e95..000000000
--- a/webapp/components/backstage/backstage_controller.jsx
+++ /dev/null
@@ -1,82 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import TeamStore from 'stores/team_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-
-import BackstageSidebar from './components/backstage_sidebar.jsx';
-import BackstageNavbar from './components/backstage_navbar.jsx';
-import AnnouncementBar from 'components/announcement_bar';
-
-export default class BackstageController extends React.Component {
- static get propTypes() {
- return {
- user: PropTypes.object,
- children: PropTypes.node.isRequired
- };
- }
-
- constructor(props) {
- super(props);
-
- this.onTeamChange = this.onTeamChange.bind(this);
-
- const team = TeamStore.getCurrent();
-
- this.state = {
- team,
- isAdmin: UserStore.isSystemAdminForCurrentUser(this.props.user) ||
- TeamStore.isTeamAdminForCurrentTeam(team)
- };
- }
-
- componentDidMount() {
- TeamStore.addChangeListener(this.onTeamChange);
- }
-
- componentWillUnmount() {
- TeamStore.removeChangeListener(this.onTeamChange);
- }
-
- onTeamChange() {
- const team = TeamStore.getCurrent();
-
- this.state = {
- team,
- isAdmin: UserStore.isSystemAdminForCurrentUser(this.props.user) ||
- TeamStore.isTeamAdminForCurrentTeam(team)
- };
- }
-
- render() {
- return (
- <div className='backstage'>
- <AnnouncementBar/>
- <BackstageNavbar team={this.state.team}/>
- <div className='backstage-body'>
- <BackstageSidebar
- team={this.state.team}
- user={this.props.user}
- />
- {
- React.Children.map(this.props.children, (child) => {
- if (!child) {
- return child;
- }
-
- return React.cloneElement(child, {
- team: this.state.team,
- user: this.props.user,
- isAdmin: this.state.isAdmin
- });
- })
- }
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/backstage/components/backstage_category.jsx b/webapp/components/backstage/components/backstage_category.jsx
deleted file mode 100644
index ee98e6b56..000000000
--- a/webapp/components/backstage/components/backstage_category.jsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import {Link} from 'react-router/es6';
-
-export default class BackstageCategory extends React.Component {
- static get propTypes() {
- return {
- name: PropTypes.string.isRequired,
- title: PropTypes.node.isRequired,
- icon: PropTypes.string.isRequired,
- parentLink: PropTypes.string,
- children: PropTypes.arrayOf(PropTypes.element)
- };
- }
-
- static get defaultProps() {
- return {
- parentLink: '',
- children: []
- };
- }
-
- static get contextTypes() {
- return {
- router: PropTypes.object.isRequired
- };
- }
-
- render() {
- const {name, title, icon, parentLink, children} = this.props;
-
- const link = parentLink + '/' + name;
-
- let clonedChildren = null;
- if (children.length > 0 && this.context.router.isActive(link)) {
- clonedChildren = (
- <ul className='sections'>
- {
- React.Children.map(children, (child) => {
- if (!child) {
- return child;
- }
-
- return React.cloneElement(child, {
- parentLink: link
- });
- })
- }
- </ul>
- );
- }
-
- return (
- <li className='backstage-sidebar__category'>
- <Link
- to={link}
- className='category-title'
- activeClassName='category-title--active'
- onlyActiveOnIndex={true}
- >
- <i className={'fa ' + icon}/>
- <span className='category-title__text'>
- {title}
- </span>
- </Link>
- {clonedChildren}
- </li>
- );
- }
-}
diff --git a/webapp/components/backstage/components/backstage_header.jsx b/webapp/components/backstage/components/backstage_header.jsx
deleted file mode 100644
index 68d5590a6..000000000
--- a/webapp/components/backstage/components/backstage_header.jsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-export default class BackstageHeader extends React.Component {
- static get propTypes() {
- return {
- children: PropTypes.node
- };
- }
-
- render() {
- const children = [];
-
- React.Children.forEach(this.props.children, (child, index) => {
- if (index !== 0) {
- children.push(
- <span
- key={'divider' + index}
- className='backstage-header__divider'
- >
- <i className='fa fa-angle-right'/>
- </span>
- );
- }
-
- children.push(child);
- });
-
- return (
- <div className='backstage-header'>
- <h1>
- {children}
- </h1>
- </div>
- );
- }
-}
diff --git a/webapp/components/backstage/components/backstage_list.jsx b/webapp/components/backstage/components/backstage_list.jsx
deleted file mode 100644
index d0a2426ec..000000000
--- a/webapp/components/backstage/components/backstage_list.jsx
+++ /dev/null
@@ -1,114 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import * as Utils from 'utils/utils.jsx';
-
-import {Link} from 'react-router';
-import LoadingScreen from 'components/loading_screen.jsx';
-
-export default class BackstageList extends React.Component {
- static propTypes = {
- children: PropTypes.node,
- header: PropTypes.node.isRequired,
- addLink: PropTypes.string,
- addText: PropTypes.node,
- emptyText: PropTypes.node,
- helpText: PropTypes.node,
- loading: PropTypes.bool.isRequired,
- searchPlaceholder: PropTypes.string
- }
-
- static defaultProps = {
- searchPlaceholder: Utils.localizeMessage('backstage_list.search', 'Search')
- }
-
- constructor(props) {
- super(props);
-
- this.updateFilter = this.updateFilter.bind(this);
-
- this.state = {
- filter: ''
- };
- }
-
- updateFilter(e) {
- this.setState({
- filter: e.target.value
- });
- }
-
- render() {
- const filter = this.state.filter.toLowerCase();
-
- let children;
- if (this.props.loading) {
- children = <LoadingScreen/>;
- } else {
- children = React.Children.map(this.props.children, (child) => {
- return React.cloneElement(child, {filter});
- });
-
- if (children.length === 0 && this.props.emptyText) {
- children = (
- <span className='backstage-list__item backstage-list__empty'>
- {this.props.emptyText}
- </span>
- );
- }
- }
-
- let addLink = null;
- if (this.props.addLink && this.props.addText) {
- addLink = (
- <Link
- className='add-link'
- to={this.props.addLink}
- >
- <button
- type='button'
- className='btn btn-primary'
- >
- <span>
- {this.props.addText}
- </span>
- </button>
- </Link>
- );
- }
-
- return (
- <div className='backstage-content'>
- <div className='backstage-header'>
- <h1>
- {this.props.header}
- </h1>
- {addLink}
- </div>
- <div className='backstage-filters'>
- <div className='backstage-filter__search'>
- <i className='fa fa-search'/>
- <input
- type='search'
- className='form-control'
- placeholder={this.props.searchPlaceholder}
- value={this.state.filter}
- onChange={this.updateFilter}
- style={{flexGrow: 0, flexShrink: 0}}
- />
- </div>
- </div>
- <span className='backstage-list__help'>
- {this.props.helpText}
- </span>
- <div className='backstage-list'>
- {children}
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/backstage/components/backstage_navbar.jsx b/webapp/components/backstage/components/backstage_navbar.jsx
deleted file mode 100644
index c3a9feec8..000000000
--- a/webapp/components/backstage/components/backstage_navbar.jsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import {FormattedMessage} from 'react-intl';
-import {Link} from 'react-router/es6';
-
-export default class BackstageNavbar extends React.Component {
- static get propTypes() {
- return {
- team: PropTypes.object.isRequired
- };
- }
-
- render() {
- if (!this.props.team) {
- return null;
- }
-
- return (
- <div className='backstage-navbar'>
- <Link
- className='backstage-navbar__back'
- to={`/${this.props.team.name}/channels/town-square`}
- >
- <i className='fa fa-angle-left'/>
- <span>
- <FormattedMessage
- id='backstage_navbar.backToMattermost'
- defaultMessage='Back to {siteName}'
- values={{
- siteName: global.window.mm_config.SiteName
- }}
- />
- </span>
- </Link>
- </div>
- );
- }
-}
diff --git a/webapp/components/backstage/components/backstage_section.jsx b/webapp/components/backstage/components/backstage_section.jsx
deleted file mode 100644
index 6da53a6e6..000000000
--- a/webapp/components/backstage/components/backstage_section.jsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import {Link} from 'react-router/es6';
-
-export default class BackstageSection extends React.Component {
- static get propTypes() {
- return {
- name: PropTypes.string.isRequired,
- title: PropTypes.node.isRequired,
- parentLink: PropTypes.string,
- subsection: PropTypes.bool,
- children: PropTypes.arrayOf(PropTypes.element)
- };
- }
-
- static get defaultProps() {
- return {
- parentLink: '',
- subsection: false,
- children: []
- };
- }
-
- static get contextTypes() {
- return {
- router: PropTypes.object.isRequired
- };
- }
-
- getLink() {
- return this.props.parentLink + '/' + this.props.name;
- }
-
- render() {
- const {title, subsection, children} = this.props;
-
- const link = this.getLink();
-
- let clonedChildren = null;
- if (children.length > 0) {
- clonedChildren = (
- <ul className='subsections'>
- {
- React.Children.map(children, (child) => {
- return React.cloneElement(child, {
- parentLink: link,
- subsection: true
- });
- })
- }
- </ul>
- );
- }
-
- let className = 'section';
- if (subsection) {
- className = 'subsection';
- }
-
- return (
- <li className={className}>
- <Link
- className={`${className}-title`}
- activeClassName={`${className}-title--active`}
- to={link}
- >
- <span className={`${className}-title__text`}>
- {title}
- </span>
- </Link>
- {clonedChildren}
- </li>
- );
- }
-}
diff --git a/webapp/components/backstage/components/backstage_sidebar.jsx b/webapp/components/backstage/components/backstage_sidebar.jsx
deleted file mode 100644
index 00cd6b63e..000000000
--- a/webapp/components/backstage/components/backstage_sidebar.jsx
+++ /dev/null
@@ -1,149 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import TeamStore from 'stores/team_store.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import BackstageCategory from './backstage_category.jsx';
-import BackstageSection from './backstage_section.jsx';
-import {FormattedMessage} from 'react-intl';
-
-export default class BackstageSidebar extends React.Component {
- static get propTypes() {
- return {
- team: PropTypes.object.isRequired,
- user: PropTypes.object.isRequired
- };
- }
-
- renderCustomEmoji() {
- if (window.mm_config.EnableCustomEmoji !== 'true' || !Utils.canCreateCustomEmoji(this.props.user)) {
- return null;
- }
-
- return (
- <BackstageCategory
- name='emoji'
- parentLink={'/' + this.props.team.name}
- icon='fa-smile-o'
- title={
- <FormattedMessage
- id='backstage_sidebar.emoji'
- defaultMessage='Custom Emoji'
- />
- }
- />
- );
- }
-
- renderIntegrations() {
- const config = window.mm_config;
- const isSystemAdmin = Utils.isSystemAdmin(this.props.user.roles);
- if (config.EnableIncomingWebhooks !== 'true' &&
- config.EnableOutgoingWebhooks !== 'true' &&
- config.EnableCommands !== 'true' &&
- config.EnableOAuthServiceProvider !== 'true') {
- return null;
- }
-
- if (config.EnableOnlyAdminIntegrations !== 'false' &&
- !isSystemAdmin &&
- !TeamStore.isTeamAdmin(this.props.user.id, this.props.team.id)) {
- return null;
- }
-
- let incomingWebhooks = null;
- if (config.EnableIncomingWebhooks === 'true') {
- incomingWebhooks = (
- <BackstageSection
- name='incoming_webhooks'
- title={(
- <FormattedMessage
- id='backstage_sidebar.integrations.incoming_webhooks'
- defaultMessage='Incoming Webhooks'
- />
- )}
- />
- );
- }
-
- let outgoingWebhooks = null;
- if (config.EnableOutgoingWebhooks === 'true') {
- outgoingWebhooks = (
- <BackstageSection
- name='outgoing_webhooks'
- title={(
- <FormattedMessage
- id='backstage_sidebar.integrations.outgoing_webhooks'
- defaultMessage='Outgoing Webhooks'
- />
- )}
- />
- );
- }
-
- let commands = null;
- if (config.EnableCommands === 'true') {
- commands = (
- <BackstageSection
- name='commands'
- title={(
- <FormattedMessage
- id='backstage_sidebar.integrations.commands'
- defaultMessage='Slash Commands'
- />
- )}
- />
- );
- }
-
- let oauthApps = null;
- if (config.EnableOAuthServiceProvider === 'true' && (isSystemAdmin || config.EnableOnlyAdminIntegrations !== 'true')) {
- oauthApps = (
- <BackstageSection
- name='oauth2-apps'
- title={
- <FormattedMessage
- id='backstage_sidebar.integrations.oauthApps'
- defaultMessage='OAuth 2.0 Applications'
- />
- }
- />
- );
- }
-
- return (
- <BackstageCategory
- name='integrations'
- parentLink={'/' + this.props.team.name}
- icon='fa-link'
- title={
- <FormattedMessage
- id='backstage_sidebar.integrations'
- defaultMessage='Integrations'
- />
- }
- >
- {incomingWebhooks}
- {outgoingWebhooks}
- {commands}
- {oauthApps}
- </BackstageCategory>
- );
- }
-
- render() {
- return (
- <div className='backstage-sidebar'>
- <ul>
- {this.renderCustomEmoji()}
- {this.renderIntegrations()}
- </ul>
- </div>
- );
- }
-}
diff --git a/webapp/components/bootstrap_span.jsx b/webapp/components/bootstrap_span.jsx
deleted file mode 100644
index fb425594b..000000000
--- a/webapp/components/bootstrap_span.jsx
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-class BootstrapSpan extends React.PureComponent {
-
- static propTypes = {
- children: PropTypes.element
- }
-
- render() {
- const {children, ...props} = this.props;
- delete props.bsRole;
- delete props.bsClass;
-
- return <span {...props}>{children}</span>;
- }
-}
-
-export default BootstrapSpan;
diff --git a/webapp/components/change_url_modal.jsx b/webapp/components/change_url_modal.jsx
deleted file mode 100644
index 88304a4a7..000000000
--- a/webapp/components/change_url_modal.jsx
+++ /dev/null
@@ -1,248 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ReactDOM from 'react-dom';
-import Constants from 'utils/constants.jsx';
-import {Modal, Tooltip, OverlayTrigger} from 'react-bootstrap';
-import TeamStore from 'stores/team_store.jsx';
-import * as URL from 'utils/url.jsx';
-
-import {FormattedMessage} from 'react-intl';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class ChangeUrlModal extends React.Component {
- constructor(props) {
- super(props);
-
- this.onURLChanged = this.onURLChanged.bind(this);
- this.doSubmit = this.doSubmit.bind(this);
- this.doCancel = this.doCancel.bind(this);
-
- this.state = {
- currentURL: props.currentURL,
- urlError: '',
- userEdit: false
- };
- }
-
- componentWillReceiveProps(nextProps) {
- // This check prevents the url being deleted when we re-render
- // because of user status check
- if (!this.state.userEdit) {
- this.setState({
- currentURL: nextProps.currentURL
- });
- }
- }
-
- componentDidUpdate(prevProps) {
- if (this.props.show === true && prevProps.show === false) {
- ReactDOM.findDOMNode(this.refs.urlinput).select();
- }
- }
-
- onURLChanged(e) {
- const url = e.target.value.trim();
- this.setState({currentURL: url.replace(/[^A-Za-z0-9-_]/g, '').toLowerCase(), userEdit: true});
- }
-
- getURLError(url) {
- let error = []; //eslint-disable-line prefer-const
- if (url.length < 2) {
- error.push(
- <span key='error1'>
- <FormattedMessage
- id='change_url.longer'
- defaultMessage='URL must be two or more characters.'
- />
- <br/>
- </span>
- );
- }
- if (url.charAt(0) === '-' || url.charAt(0) === '_') {
- error.push(
- <span key='error2'>
- <FormattedMessage
- id='change_url.startWithLetter'
- defaultMessage='URL must start with a letter or number.'
- />
- <br/>
- </span>
- );
- }
- if (url.length > 1 && (url.charAt(url.length - 1) === '-' || url.charAt(url.length - 1) === '_')) {
- error.push(
- <span key='error3'>
- <FormattedMessage
- id='change_url.endWithLetter'
- defaultMessage='URL must end with a letter or number.'
- />
- <br/>
- </span>);
- }
- if (url.indexOf('__') > -1) {
- error.push(
- <span key='error4'>
- <FormattedMessage
- id='change_url.noUnderscore'
- defaultMessage='URL can not contain two underscores in a row.'
- />
- <br/>
- </span>);
- }
-
- // In case of error we don't detect
- if (error.length === 0) {
- error.push(
- <span key='errorlast'>
- <FormattedMessage
- id='change_url.invalidUrl'
- defaultMessage='Invalid URL'
- />
- <br/>
- </span>);
- }
- return error;
- }
-
- doSubmit(e) {
- e.preventDefault();
-
- const url = ReactDOM.findDOMNode(this.refs.urlinput).value;
- const cleanedURL = URL.cleanUpUrlable(url);
- if (cleanedURL !== url || url.length < 2 || url.indexOf('__') > -1) {
- this.setState({urlError: this.getURLError(url)});
- return;
- }
- this.setState({urlError: '', userEdit: false});
- this.props.onModalSubmit(url);
- }
-
- doCancel() {
- this.setState({urlError: '', userEdit: false});
- this.props.onModalDismissed();
- }
-
- render() {
- let urlClass = 'input-group input-group--limit';
- let error = null;
-
- if (this.state.urlError) {
- urlClass += ' has-error';
- }
-
- if (this.props.serverError || this.state.urlError) {
- error = (
- <div className='form-group has-error'>
- <p className='input__help error'>
- {this.state.urlError || this.props.serverError}
- </p>
- </div>
- );
- }
-
- const fullUrl = TeamStore.getCurrentTeamUrl() + '/channels';
- const shortURL = URL.getShortenedURL(fullUrl);
- const urlTooltip = (
- <Tooltip id='urlTooltip'>{fullUrl}</Tooltip>
- );
-
- return (
- <Modal
- show={this.props.show}
- onHide={this.doCancel}
- onExited={this.props.onModalExited}
- >
- <Modal.Header closeButton={true}>
- <Modal.Title>{this.props.title}</Modal.Title>
- </Modal.Header>
- <form
- role='form'
- className='form-horizontal'
- >
- <Modal.Body>
- <div className='modal-intro'>
- <FormattedMessage
- id='channel_flow.changeUrlDescription'
- defaultMessage='Some characters are now allowed in URLs and may be removed.'
- />
- </div>
- <div className='form-group'>
- <label className='col-sm-2 form__label control-label'>
- <FormattedMessage
- id='change_url.urlLabel'
- defaultMessage='Channel URL'
- />
- </label>
- <div className='col-sm-10'>
- <div className={urlClass}>
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={Constants.OVERLAY_TIME_DELAY}
- placement='top'
- overlay={urlTooltip}
- >
- <span className='input-group-addon'>{shortURL}</span>
- </OverlayTrigger>
- <input
- type='text'
- ref='urlinput'
- className='form-control'
- maxLength={Constants.MAX_CHANNELNAME_LENGTH}
- onChange={this.onURLChanged}
- value={this.state.currentURL}
- autoFocus={true}
- tabIndex='1'
- />
- </div>
- {error}
- </div>
- </div>
- </Modal.Body>
- <Modal.Footer>
- <button
- type='button'
- className='btn btn-default'
- onClick={this.doCancel}
- >
- <FormattedMessage
- id='change_url.close'
- defaultMessage='Close'
- />
- </button>
- <button
- onClick={this.doSubmit}
- type='submit'
- className='btn btn-primary'
- tabIndex='2'
- >
- {this.props.submitButtonText}
- </button>
- </Modal.Footer>
- </form>
- </Modal>
- );
- }
-}
-
-ChangeUrlModal.defaultProps = {
- show: false,
- title: 'Change URL',
- submitButtonText: 'Save',
- currentURL: '',
- serverError: null
-};
-
-ChangeUrlModal.propTypes = {
- show: PropTypes.bool.isRequired,
- title: PropTypes.node,
- submitButtonText: PropTypes.node,
- currentURL: PropTypes.string,
- serverError: PropTypes.node,
- onModalSubmit: PropTypes.func.isRequired,
- onModalExited: PropTypes.func,
- onModalDismissed: PropTypes.func.isRequired
-};
diff --git a/webapp/components/channel_header.jsx b/webapp/components/channel_header.jsx
deleted file mode 100644
index 29498eef2..000000000
--- a/webapp/components/channel_header.jsx
+++ /dev/null
@@ -1,952 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import 'bootstrap';
-import NavbarSearchBox from './search_bar.jsx';
-import MessageWrapper from './message_wrapper.jsx';
-import PopoverListMembers from 'components/popover_list_members';
-import EditChannelHeaderModal from './edit_channel_header_modal.jsx';
-import EditChannelPurposeModal from './edit_channel_purpose_modal.jsx';
-import ChannelInfoModal from './channel_info_modal.jsx';
-import ChannelInviteModal from 'components/channel_invite_modal';
-import ChannelMembersModal from './channel_members_modal.jsx';
-import ChannelNotificationsModal from './channel_notifications_modal.jsx';
-import DeleteChannelModal from './delete_channel_modal.jsx';
-import RenameChannelModal from './rename_channel_modal.jsx';
-import ToggleModalButton from './toggle_modal_button.jsx';
-
-import ChannelStore from 'stores/channel_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-import SearchStore from 'stores/search_store.jsx';
-import PreferenceStore from 'stores/preference_store.jsx';
-import WebrtcStore from 'stores/webrtc_store.jsx';
-
-import * as GlobalActions from 'actions/global_actions.jsx';
-import * as WebrtcActions from 'actions/webrtc_actions.jsx';
-import * as ChannelActions from 'actions/channel_actions.jsx';
-import * as Utils from 'utils/utils.jsx';
-import * as ChannelUtils from 'utils/channel_utils.jsx';
-import {getSiteURL} from 'utils/url.jsx';
-import * as TextFormatting from 'utils/text_formatting.jsx';
-import {getFlaggedPosts, getPinnedPosts} from 'actions/post_actions.jsx';
-
-import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
-
-import {Constants, Preferences, UserStatuses, ActionTypes} from 'utils/constants.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-import {Tooltip, OverlayTrigger, Popover} from 'react-bootstrap';
-
-const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES;
-
-export default class ChannelHeader extends React.Component {
- constructor(props) {
- super(props);
-
- this.onListenerChange = this.onListenerChange.bind(this);
- this.handleLeave = this.handleLeave.bind(this);
- this.searchMentions = this.searchMentions.bind(this);
- this.showRenameChannelModal = this.showRenameChannelModal.bind(this);
- this.hideRenameChannelModal = this.hideRenameChannelModal.bind(this);
- this.handleShortcut = this.handleShortcut.bind(this);
- this.getFlagged = this.getFlagged.bind(this);
- this.getPinnedPosts = this.getPinnedPosts.bind(this);
- this.initWebrtc = this.initWebrtc.bind(this);
- this.onBusy = this.onBusy.bind(this);
- this.openDirectMessageModal = this.openDirectMessageModal.bind(this);
-
- const state = this.getStateFromStores();
- state.showEditChannelHeaderModal = false;
- state.showEditChannelPurposeModal = false;
- state.showMembersModal = false;
- state.showRenameChannelModal = false;
- this.state = state;
- }
-
- getStateFromStores() {
- const channel = ChannelStore.get(this.props.channelId);
- const stats = ChannelStore.getStats(this.props.channelId);
- const users = UserStore.getProfileListInChannel(this.props.channelId, false, true);
-
- let otherUserId = null;
- if (channel && channel.type === 'D') {
- otherUserId = Utils.getUserIdFromChannelName(channel);
- }
-
- return {
- channel,
- memberChannel: ChannelStore.getMyMember(this.props.channelId),
- users,
- userCount: stats.member_count,
- currentUser: UserStore.getCurrentUser(),
- otherUserId,
- enableFormatting: PreferenceStore.getBool(Preferences.CATEGORY_ADVANCED_SETTINGS, 'formatting', true),
- isBusy: WebrtcStore.isBusy(),
- isFavorite: channel && ChannelUtils.isFavoriteChannel(channel),
- pinsOpen: SearchStore.getIsPinnedPosts()
- };
- }
-
- validState() {
- if (!this.state.channel ||
- !this.state.memberChannel ||
- !this.state.users ||
- !this.state.userCount ||
- !this.state.currentUser) {
- return false;
- }
- return true;
- }
-
- componentDidMount() {
- ChannelStore.addChangeListener(this.onListenerChange);
- ChannelStore.addStatsChangeListener(this.onListenerChange);
- SearchStore.addSearchChangeListener(this.onListenerChange);
- PreferenceStore.addChangeListener(this.onListenerChange);
- UserStore.addChangeListener(this.onListenerChange);
- UserStore.addInChannelChangeListener(this.onListenerChange);
- UserStore.addStatusesChangeListener(this.onListenerChange);
- WebrtcStore.addChangedListener(this.onListenerChange);
- WebrtcStore.addBusyListener(this.onBusy);
- $('.sidebar--left .dropdown-menu').perfectScrollbar();
- document.addEventListener('keydown', this.handleShortcut);
- }
-
- componentWillUnmount() {
- ChannelStore.removeChangeListener(this.onListenerChange);
- ChannelStore.removeStatsChangeListener(this.onListenerChange);
- SearchStore.removeSearchChangeListener(this.onListenerChange);
- PreferenceStore.removeChangeListener(this.onListenerChange);
- UserStore.removeChangeListener(this.onListenerChange);
- UserStore.removeInChannelChangeListener(this.onListenerChange);
- UserStore.removeStatusesChangeListener(this.onListenerChange);
- WebrtcStore.removeChangedListener(this.onListenerChange);
- WebrtcStore.removeBusyListener(this.onBusy);
- document.removeEventListener('keydown', this.handleShortcut);
- }
-
- shouldComponentUpdate(nextProps) {
- return Boolean(nextProps.channelId);
- }
-
- onListenerChange() {
- this.setState(this.getStateFromStores());
- }
-
- handleLeave() {
- if (this.state.channel.type === Constants.PRIVATE_CHANNEL) {
- GlobalActions.showLeavePrivateChannelModal(this.state.channel);
- } else {
- ChannelActions.leaveChannel(this.state.channel.id);
- }
- }
-
- toggleFavorite = (e) => {
- e.preventDefault();
-
- if (this.state.isFavorite) {
- ChannelActions.unmarkFavorite(this.state.channel.id);
- } else {
- ChannelActions.markFavorite(this.state.channel.id);
- }
- };
-
- searchMentions(e) {
- e.preventDefault();
- const user = this.state.currentUser;
- if (SearchStore.isMentionSearch) {
- // Close
- GlobalActions.toggleSideBarAction(false);
- } else {
- GlobalActions.emitSearchMentionsEvent(user);
- }
- }
-
- getPinnedPosts(e) {
- e.preventDefault();
- if (SearchStore.isPinnedPosts) {
- GlobalActions.toggleSideBarAction(false);
- } else {
- getPinnedPosts(this.props.channelId);
- }
- }
-
- getFlagged(e) {
- e.preventDefault();
- if (SearchStore.isFlaggedPosts) {
- GlobalActions.toggleSideBarAction(false);
- } else {
- getFlaggedPosts();
- }
- }
-
- handleShortcut(e) {
- if (Utils.cmdOrCtrlPressed(e) && e.shiftKey) {
- if (e.keyCode === Constants.KeyCodes.M) {
- e.preventDefault();
- this.searchMentions(e);
- }
- }
- }
-
- showRenameChannelModal(e) {
- e.preventDefault();
-
- this.setState({
- showRenameChannelModal: true
- });
- }
-
- hideRenameChannelModal() {
- this.setState({
- showRenameChannelModal: false
- });
- }
-
- initWebrtc(contactId, isOnline) {
- if (isOnline && !this.state.isBusy) {
- GlobalActions.emitCloseRightHandSide();
- WebrtcActions.initWebrtc(contactId, true);
- }
- }
-
- onBusy(isBusy) {
- this.setState({isBusy});
- }
-
- openDirectMessageModal() {
- AppDispatcher.handleViewAction({
- type: ActionTypes.TOGGLE_DM_MODAL,
- value: true,
- startingUsers: UserStore.getProfileListInChannel(this.props.channelId, true, false)
- });
- }
-
- render() {
- const flagIcon = Constants.FLAG_ICON_SVG;
- const pinIcon = Constants.PIN_ICON_SVG;
- const mentionsIcon = Constants.MENTIONS_ICON_SVG;
-
- if (!this.validState()) {
- // Use an empty div to make sure the header's height stays constant
- return (
- <div className='channel-header'/>
- );
- }
-
- const channel = this.state.channel;
- const recentMentionsTooltip = (
- <Tooltip id='recentMentionsTooltip'>
- <FormattedMessage
- id='channel_header.recentMentions'
- defaultMessage='Recent Mentions'
- />
- </Tooltip>
- );
-
- const pinnedPostTooltip = (
- <Tooltip id='pinnedPostTooltip'>
- <FormattedMessage
- id='channel_header.pinnedPosts'
- defaultMessage='Pinned Posts'
- />
- </Tooltip>
- );
-
- const flaggedTooltip = (
- <Tooltip
- id='flaggedTooltip'
- className='text-nowrap'
- >
- <FormattedMessage
- id='channel_header.flagged'
- defaultMessage='Flagged Posts'
- />
- </Tooltip>
- );
-
- const popoverContent = (
- <Popover
- id='header-popover'
- bStyle='info'
- bSize='large'
- placement='bottom'
- className='description'
- onMouseOver={() => this.refs.headerOverlay.show()}
- onMouseOut={() => this.refs.headerOverlay.hide()}
- >
- <MessageWrapper
- message={channel.header}
- />
- </Popover>
- );
- let channelTitle = channel.display_name;
- const isChannelAdmin = ChannelStore.isChannelAdminForCurrentChannel();
- const isTeamAdmin = TeamStore.isTeamAdminForCurrentTeam();
- const isSystemAdmin = UserStore.isSystemAdminForCurrentUser();
- const isDirect = (this.state.channel.type === Constants.DM_CHANNEL);
- const isGroup = (this.state.channel.type === Constants.GM_CHANNEL);
- let webrtc;
-
- if (isDirect) {
- const userMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
- const otherUserId = this.state.otherUserId;
-
- const teammateId = Utils.getUserIdFromChannelName(channel);
- channelTitle = Utils.displayUsername(teammateId);
-
- const webrtcEnabled = global.mm_config.EnableWebrtc === 'true' && userMedia && Utils.isFeatureEnabled(PreReleaseFeatures.WEBRTC_PREVIEW);
-
- if (webrtcEnabled) {
- const isOffline = UserStore.getStatus(otherUserId) === UserStatuses.OFFLINE;
- const busy = this.state.isBusy;
- let circleClass = '';
- let webrtcMessage;
-
- if (isOffline || busy) {
- circleClass = 'offline';
- webrtcMessage = (
- <FormattedMessage
- id='channel_header.webrtc.offline'
- defaultMessage='The user is offline'
- />
- );
-
- if (busy) {
- webrtcMessage = (
- <FormattedMessage
- id='channel_header.webrtc.unavailable'
- defaultMessage='New call unavailable until your existing call ends'
- />
- );
- }
- } else {
- webrtcMessage = (
- <FormattedMessage
- id='channel_header.webrtc.call'
- defaultMessage='Start Video Call'
- />
- );
- }
-
- const webrtcTooltip = (
- <Tooltip id='webrtcTooltip'>{webrtcMessage}</Tooltip>
- );
-
- webrtc = (
- <div className='webrtc__header channel-header__icon'>
- <a
- href='#'
- onClick={() => this.initWebrtc(otherUserId, !isOffline)}
- disabled={isOffline}
- >
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={Constants.WEBRTC_TIME_DELAY}
- placement='bottom'
- overlay={webrtcTooltip}
- >
- <div
- id='webrtc-btn'
- className={'webrtc__button ' + circleClass}
- >
- <span dangerouslySetInnerHTML={{__html: Constants.VIDEO_ICON}}/>
- </div>
- </OverlayTrigger>
- </a>
- </div>
- );
- }
- }
-
- if (isGroup) {
- channelTitle = ChannelUtils.buildGroupChannelName(channel.id);
- }
-
- let popoverListMembers;
- if (!isDirect) {
- popoverListMembers = (
- <PopoverListMembers
- channel={channel}
- members={this.state.users}
- memberCount={this.state.userCount}
- />
- );
- }
-
- const dropdownContents = [];
- if (isDirect) {
- dropdownContents.push(
- <li
- key='edit_header_direct'
- role='presentation'
- >
- <ToggleModalButton
- id='channelEditHeaderDirect'
- role='menuitem'
- dialogType={EditChannelHeaderModal}
- dialogProps={{channel}}
- >
- <FormattedMessage
- id='channel_header.channelHeader'
- defaultMessage='Edit Channel Header'
- />
- </ToggleModalButton>
- </li>
- );
- } else if (isGroup) {
- dropdownContents.push(
- <li
- key='edit_header_direct'
- role='presentation'
- >
- <ToggleModalButton
- id='channelEditHeaderGroup'
- role='menuitem'
- dialogType={EditChannelHeaderModal}
- dialogProps={{channel}}
- >
- <FormattedMessage
- id='channel_header.channelHeader'
- defaultMessage='Edit Channel Header'
- />
- </ToggleModalButton>
- </li>
- );
-
- dropdownContents.push(
- <li
- key='notification_preferences'
- role='presentation'
- >
- <ToggleModalButton
- id='channelnotificationPreferencesGroup'
- role='menuitem'
- dialogType={ChannelNotificationsModal}
- dialogProps={{
- channel,
- channelMember: this.state.memberChannel,
- currentUser: this.state.currentUser
- }}
- >
- <FormattedMessage
- id='channel_header.notificationPreferences'
- defaultMessage='Notification Preferences'
- />
- </ToggleModalButton>
- </li>
- );
-
- dropdownContents.push(
- <li
- key='add_members'
- role='presentation'
- >
- <a
- id='channelAddMembersGroup'
- role='menuitem'
- href='#'
- onClick={this.openDirectMessageModal}
- >
- <FormattedMessage
- id='channel_header.addMembers'
- defaultMessage='Add Members'
- />
- </a>
- </li>
- );
- } else {
- dropdownContents.push(
- <li
- key='view_info'
- role='presentation'
- >
- <ToggleModalButton
- id='channelViewInfo'
- role='menuitem'
- dialogType={ChannelInfoModal}
- dialogProps={{channel}}
- >
- <FormattedMessage
- id='channel_header.viewInfo'
- defaultMessage='View Info'
- />
- </ToggleModalButton>
- </li>
- );
-
- if (ChannelStore.isDefault(channel)) {
- dropdownContents.push(
- <li
- key='manage_members'
- role='presentation'
- >
- <a
- id='channelManageMembers'
- role='menuitem'
- href='#'
- onClick={() => this.setState({showMembersModal: true})}
- >
- <FormattedMessage
- id='channel_header.viewMembers'
- defaultMessage='View Members'
- />
- </a>
- </li>
- );
- }
-
- dropdownContents.push(
- <li
- key='notification_preferences'
- role='presentation'
- >
- <ToggleModalButton
- id='channelNotificationPreferences'
- role='menuitem'
- dialogType={ChannelNotificationsModal}
- dialogProps={{
- channel,
- channelMember: this.state.memberChannel,
- currentUser: this.state.currentUser
- }}
- >
- <FormattedMessage
- id='channel_header.notificationPreferences'
- defaultMessage='Notification Preferences'
- />
- </ToggleModalButton>
- </li>
- );
-
- if (!ChannelStore.isDefault(channel)) {
- dropdownContents.push(
- <li
- key='divider-1'
- className='divider'
- />
- );
-
- if (ChannelUtils.canManageMembers(channel, isChannelAdmin, isTeamAdmin, isSystemAdmin)) {
- dropdownContents.push(
- <li
- key='add_members'
- role='presentation'
- >
- <ToggleModalButton
- id='channelAddMembers'
- ref='channelInviteModalButton'
- role='menuitem'
- dialogType={ChannelInviteModal}
- dialogProps={{channel, currentUser: this.state.currentUser}}
- >
- <FormattedMessage
- id='channel_header.addMembers'
- defaultMessage='Add Members'
- />
- </ToggleModalButton>
- </li>
- );
-
- dropdownContents.push(
- <li
- key='manage_members'
- role='presentation'
- >
- <a
- id='channelManageMembers'
- role='menuitem'
- href='#'
- onClick={() => this.setState({showMembersModal: true})}
- >
- <FormattedMessage
- id='channel_header.manageMembers'
- defaultMessage='Manage Members'
- />
- </a>
- </li>
- );
- } else {
- dropdownContents.push(
- <li
- key='view_members'
- role='presentation'
- >
- <a
- id='channelViewMembers'
- role='menuitem'
- href='#'
- onClick={() => this.setState({showMembersModal: true})}
- >
- <FormattedMessage
- id='channel_header.viewMembers'
- defaultMessage='View Members'
- />
- </a>
- </li>
- );
- }
- }
-
- if (ChannelUtils.showManagementOptions(channel, isChannelAdmin, isTeamAdmin, isSystemAdmin)) {
- dropdownContents.push(
- <li
- key='divider-2'
- className='divider'
- />
- );
-
- dropdownContents.push(
- <li
- key='set_channel_header'
- role='presentation'
- >
- <ToggleModalButton
- id='channelEditHeader'
- role='menuitem'
- dialogType={EditChannelHeaderModal}
- dialogProps={{channel}}
- >
- <FormattedMessage
- id='channel_header.setHeader'
- defaultMessage='Edit Channel Header'
- />
- </ToggleModalButton>
- </li>
- );
-
- dropdownContents.push(
- <li
- key='set_channel_purpose'
- role='presentation'
- >
- <a
- id='channelEditPurpose'
- role='menuitem'
- href='#'
- onClick={() => this.setState({showEditChannelPurposeModal: true})}
- >
- <FormattedMessage
- id='channel_header.setPurpose'
- defaultMessage='Edit Channel Purpose'
- />
- </a>
- </li>
- );
-
- dropdownContents.push(
- <li
- key='rename_channel'
- role='presentation'
- >
- <a
- id='channelRename'
- role='menuitem'
- href='#'
- onClick={this.showRenameChannelModal}
- >
- <FormattedMessage
- id='channel_header.rename'
- defaultMessage='Rename Channel'
- />
- </a>
- </li>
- );
- }
-
- if (ChannelUtils.showDeleteOptionForCurrentUser(channel, isChannelAdmin, isTeamAdmin, isSystemAdmin)) {
- dropdownContents.push(
- <li
- key='delete_channel'
- role='presentation'
- >
- <ToggleModalButton
- id='channelDelete'
- role='menuitem'
- dialogType={DeleteChannelModal}
- dialogProps={{channel}}
- >
- <FormattedMessage
- id='channel_header.delete'
- defaultMessage='Delete Channel'
- />
- </ToggleModalButton>
- </li>
- );
- }
-
- if (!ChannelStore.isDefault(channel)) {
- dropdownContents.push(
- <li
- key='divider-3'
- className='divider'
- />
- );
-
- dropdownContents.push(
- <li
- key='leave_channel'
- role='presentation'
- >
- <a
- id='channelLeave'
- role='menuitem'
- href='#'
- onClick={this.handleLeave}
- >
- <FormattedMessage
- id='channel_header.leave'
- defaultMessage='Leave Channel'
- />
- </a>
- </li>
- );
- }
- }
-
- let headerTextContainer;
- if (channel.header) {
- let headerTextElement;
- if (this.state.enableFormatting) {
- headerTextElement = (
- <div
- onClick={Utils.handleFormattedTextClick}
- className='channel-header__description'
- dangerouslySetInnerHTML={{__html: TextFormatting.formatText(channel.header, {singleline: true, mentionHighlight: false, siteURL: getSiteURL()})}}
- />
- );
- } else {
- headerTextElement = (
- <div
- onClick={Utils.handleFormattedTextClick}
- className='channel-header__description'
- >
- {channel.header}
- </div>
- );
- }
-
- headerTextContainer = (
- <OverlayTrigger
- trigger={'click'}
- placement='bottom'
- rootClose={true}
- overlay={popoverContent}
- ref='headerOverlay'
- >
- {headerTextElement}
- </OverlayTrigger>
- );
- } else {
- headerTextContainer = (
- <a
- href='#'
- className='channel-header__description light'
- onClick={() => this.setState({showEditChannelHeaderModal: true})}
- >
- <FormattedMessage
- id='channel_header.addChannelHeader'
- defaultMessage='Add a channel description'
- />
- </a>
- );
- }
-
- let editHeaderModal;
- if (this.state.showEditChannelHeaderModal) {
- editHeaderModal = (
- <EditChannelHeaderModal
- onHide={() => this.setState({showEditChannelHeaderModal: false})}
- channel={channel}
- />
- );
- }
-
- let toggleFavoriteTooltip;
- if (this.state.isFavorite) {
- toggleFavoriteTooltip = (
- <Tooltip id='favoriteTooltip'>
- <FormattedMessage
- id='channelHeader.removeFromFavorites'
- defaultMessage='Remove from Favorites'
- />
- </Tooltip>
- );
- } else {
- toggleFavoriteTooltip = (
- <Tooltip id='favoriteTooltip'>
- <FormattedMessage
- id='channelHeader.addToFavorites'
- defaultMessage='Add to Favorites'
- />
- </Tooltip>
- );
- }
-
- const toggleFavorite = (
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={Constants.OVERLAY_TIME_DELAY}
- placement='bottom'
- overlay={toggleFavoriteTooltip}
- >
- <a
- id='toggleFavorite'
- href='#'
- onClick={this.toggleFavorite}
- className={'channel-header__favorites ' + (this.state.isFavorite ? 'active' : 'inactive')}
- >
- <i className={'icon fa ' + (this.state.isFavorite ? 'fa-star' : 'fa-star-o')}/>
- </a>
- </OverlayTrigger>
- );
-
- let channelMembersModal;
- if (this.state.showMembersModal) {
- channelMembersModal = (
- <ChannelMembersModal
- onModalDismissed={() => this.setState({showMembersModal: false})}
- showInviteModal={() => this.refs.channelInviteModalButton.show()}
- channel={channel}
- />
- );
- }
-
- let editPurposeModal;
- if (this.state.showEditChannelPurposeModal) {
- editPurposeModal = (
- <EditChannelPurposeModal
- onModalDismissed={() => this.setState({showEditChannelPurposeModal: false})}
- channel={channel}
- />
- );
- }
-
- let pinnedIconClass = 'channel-header__icon';
- if (this.state.pinsOpen) {
- pinnedIconClass += ' active';
- }
-
- return (
- <div
- id='channel-header'
- className='channel-header alt'
- >
- <div className='flex-parent'>
- <div className='flex-child'>
- <div className='channel-header__info'>
- {toggleFavorite}
- <div className='channel-header__title dropdown'>
- <a
- id='channelHeaderDropdown'
- href='#'
- className='dropdown-toggle theme'
- type='button'
- data-toggle='dropdown'
- aria-expanded='true'
- >
- <strong className='heading'>{channelTitle} </strong>
- <span className='fa fa-angle-down header-dropdown__icon'/>
- </a>
- <ul
- className='dropdown-menu'
- role='menu'
- aria-labelledby='channel_header_dropdown'
- >
- {dropdownContents}
- </ul>
- </div>
- {headerTextContainer}
- </div>
- </div>
- <div className='flex-child'>
- {webrtc}
- </div>
- <div className='flex-child'>
- {popoverListMembers}
- </div>
- <div className='flex-child'>
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={Constants.OVERLAY_TIME_DELAY}
- placement='bottom'
- overlay={pinnedPostTooltip}
- >
- <div
- className={pinnedIconClass}
- onClick={this.getPinnedPosts}
- >
- <span
- className='icon icon__pin'
- dangerouslySetInnerHTML={{__html: pinIcon}}
- aria-hidden='true'
- />
- </div>
- </OverlayTrigger>
- </div>
- <div className='flex-child search-bar__container'>
- <NavbarSearchBox
- showMentionFlagBtns={false}
- isFocus={Utils.isMobile()}
- />
- </div>
- <div className='flex-child'>
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={Constants.OVERLAY_TIME_DELAY}
- placement='bottom'
- overlay={recentMentionsTooltip}
- >
- <div
- className='channel-header__icon icon--hidden'
- onClick={this.searchMentions}
- >
- <span
- className='icon icon__mentions'
- dangerouslySetInnerHTML={{__html: mentionsIcon}}
- aria-hidden='true'
- />
- </div>
- </OverlayTrigger>
- </div>
- <div className='flex-child'>
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={Constants.OVERLAY_TIME_DELAY}
- placement='bottom'
- overlay={flaggedTooltip}
- >
- <div
- className='channel-header__icon icon--hidden'
- onClick={this.getFlagged}
-
- >
- <span
- className='icon icon__flag'
- dangerouslySetInnerHTML={{__html: flagIcon}}
- />
- </div>
- </OverlayTrigger>
- </div>
- </div>
- {editHeaderModal}
- {editPurposeModal}
- {channelMembersModal}
- <RenameChannelModal
- show={this.state.showRenameChannelModal}
- onHide={this.hideRenameChannelModal}
- channel={channel}
- />
- </div>
- );
- }
-}
-
-ChannelHeader.propTypes = {
- channelId: PropTypes.string.isRequired
-};
diff --git a/webapp/components/channel_info_modal.jsx b/webapp/components/channel_info_modal.jsx
deleted file mode 100644
index 186dfc5cf..000000000
--- a/webapp/components/channel_info_modal.jsx
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-
-import {FormattedMessage} from 'react-intl';
-import {Modal} from 'react-bootstrap';
-import TeamStore from 'stores/team_store.jsx';
-import * as TextFormatting from 'utils/text_formatting.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class ChannelInfoModal extends React.Component {
- constructor(props) {
- super(props);
-
- this.onHide = this.onHide.bind(this);
-
- this.state = {show: true};
- }
-
- onHide() {
- this.setState({show: false});
- }
-
- render() {
- let channel = this.props.channel;
- let channelIcon;
- const globeIcon = Constants.GLOBE_ICON_SVG;
- const lockIcon = Constants.LOCK_ICON_SVG;
-
- if (!channel) {
- const notFound = Utils.localizeMessage('channel_info.notFound', 'No Channel Found');
-
- channel = {
- display_name: notFound,
- name: notFound,
- purpose: notFound,
- header: notFound,
- id: notFound
- };
- }
-
- if (channel.type === 'O') {
- channelIcon = (
- <span
- className='icon icon__globe icon--body'
- dangerouslySetInnerHTML={{__html: globeIcon}}
- />
- );
- } else if (channel.type === 'P') {
- channelIcon = (
- <span
- className='icon icon__globe icon--body'
- dangerouslySetInnerHTML={{__html: lockIcon}}
- />
- );
- }
-
- const channelURL = TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name;
-
- let channelPurpose;
- if (channel.purpose) {
- channelPurpose = channel.purpose;
- } else if (channel.name === Constants.DEFAULT_CHANNEL) {
- channelPurpose = (
- <FormattedMessage
- id='default_channel.purpose'
- defaultMessage='Post messages here that you want everyone to see. Everyone automatically becomes a permanent member of this channel when they join the team.'
- />
- );
- }
-
- let channelPurposeElement;
- if (channelPurpose) {
- channelPurposeElement = (
- <div className='form-group'>
- <div className='info__label'>
- <FormattedMessage
- id='channel_info.purpose'
- defaultMessage='Purpose:'
- />
- </div>
- <div className='info__value'>{channelPurpose}</div>
- </div>
- );
- }
-
- let channelHeader = null;
- if (channel.header) {
- channelHeader = (
- <div className='form-group'>
- <div className='info__label'>
- <FormattedMessage
- id='channel_info.header'
- defaultMessage='Header:'
- />
- </div>
- <div
- className='info__value'
- dangerouslySetInnerHTML={{__html: TextFormatting.formatText(channel.header, {singleline: false, mentionHighlight: false})}}
- />
- </div>
- );
- }
-
- return (
- <Modal
- dialogClassName='about-modal'
- show={this.state.show}
- onHide={this.onHide}
- onExited={this.props.onHide}
- >
- <Modal.Header closeButton={true}>
- <Modal.Title>
- <FormattedMessage
- id='channel_info.about'
- defaultMessage='About'
- />
- <strong>{channelIcon}{channel.display_name}</strong>
- </Modal.Title>
- </Modal.Header>
- <Modal.Body ref='modalBody'>
- {channelPurposeElement}
- {channelHeader}
- <div className='form-group'>
- <div className='info__label'>
- <FormattedMessage
- id='channel_info.url'
- defaultMessage='URL:'
- />
- </div>
- <div className='info__value'>{channelURL}</div>
- </div>
- <div className='about-modal__hash form-group padding-top x2'>
- <p>
- <FormattedMessage
- id='channel_info.id'
- defaultMessage='ID: '
- />
- {channel.id}
- </p>
- </div>
- </Modal.Body>
- </Modal>
- );
- }
-}
-
-ChannelInfoModal.propTypes = {
- onHide: PropTypes.func.isRequired,
- channel: PropTypes.object.isRequired
-};
diff --git a/webapp/components/channel_invite_button.jsx b/webapp/components/channel_invite_button.jsx
deleted file mode 100644
index dbdf68246..000000000
--- a/webapp/components/channel_invite_button.jsx
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import SpinnerButton from 'components/spinner_button.jsx';
-
-import {addUserToChannel} from 'actions/channel_actions.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-
-export default class ChannelInviteButton extends React.Component {
- static get propTypes() {
- return {
- user: PropTypes.object.isRequired,
- channel: PropTypes.object.isRequired,
- onInviteError: PropTypes.func.isRequired
- };
- }
-
- constructor(props) {
- super(props);
-
- this.handleClick = this.handleClick.bind(this);
-
- this.state = {
- addingUser: false
- };
- }
-
- handleClick() {
- if (this.state.addingUser) {
- return;
- }
-
- this.setState({
- addingUser: true
- });
-
- addUserToChannel(
- this.props.channel.id,
- this.props.user.id,
- () => {
- this.props.onInviteError(null);
- },
- (err) => {
- this.setState({
- addingUser: false
- });
-
- this.props.onInviteError(err);
- }
- );
- }
-
- render() {
- return (
- <SpinnerButton
- id='addMembers'
- className='btn btn-sm btn-primary'
- onClick={this.handleClick}
- spinning={this.state.addingUser}
- >
- <i className='fa fa-envelope fa-margin--right'/>
- <FormattedMessage
- id='channel_invite.add'
- defaultMessage=' Add'
- />
- </SpinnerButton>
- );
- }
-}
diff --git a/webapp/components/channel_invite_modal/channel_invite_modal.jsx b/webapp/components/channel_invite_modal/channel_invite_modal.jsx
deleted file mode 100644
index 8b09a7496..000000000
--- a/webapp/components/channel_invite_modal/channel_invite_modal.jsx
+++ /dev/null
@@ -1,195 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ChannelInviteButton from 'components/channel_invite_button.jsx';
-import SearchableUserList from 'components/searchable_user_list/searchable_user_list_container.jsx';
-import LoadingScreen from 'components/loading_screen.jsx';
-
-import ChannelStore from 'stores/channel_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-
-import {searchUsers} from 'actions/user_actions.jsx';
-
-import * as UserAgent from 'utils/user_agent.jsx';
-import Constants from 'utils/constants.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import {Modal} from 'react-bootstrap';
-import {FormattedMessage} from 'react-intl';
-
-import store from 'stores/redux_store.jsx';
-import {searchProfilesNotInCurrentChannel} from 'mattermost-redux/selectors/entities/users';
-
-const USERS_PER_PAGE = 50;
-
-export default class ChannelInviteModal extends React.Component {
- static propTypes = {
- onHide: PropTypes.func.isRequired,
- channel: PropTypes.object.isRequired,
- actions: PropTypes.shape({
- getProfilesNotInChannel: PropTypes.func.isRequired,
- getTeamStats: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.onChange = this.onChange.bind(this);
- this.onStatusChange = this.onStatusChange.bind(this);
- this.onHide = this.onHide.bind(this);
- this.handleInviteError = this.handleInviteError.bind(this);
- this.nextPage = this.nextPage.bind(this);
- this.search = this.search.bind(this);
-
- this.term = '';
- this.searchTimeoutId = 0;
-
- const channelStats = ChannelStore.getStats(props.channel.id);
- const teamStats = TeamStore.getCurrentStats();
-
- this.state = {
- users: UserStore.getProfileListNotInChannel(props.channel.id, true),
- total: teamStats.active_member_count - channelStats.member_count,
- show: true,
- statusChange: false
- };
- }
-
- componentDidMount() {
- TeamStore.addStatsChangeListener(this.onChange);
- ChannelStore.addStatsChangeListener(this.onChange);
- UserStore.addNotInChannelChangeListener(this.onChange);
- UserStore.addStatusesChangeListener(this.onStatusChange);
-
- this.props.actions.getProfilesNotInChannel(TeamStore.getCurrentId(), this.props.channel.id, 0);
- this.props.actions.getTeamStats(TeamStore.getCurrentId());
- }
-
- componentWillUnmount() {
- TeamStore.removeStatsChangeListener(this.onChange);
- ChannelStore.removeStatsChangeListener(this.onChange);
- UserStore.removeNotInChannelChangeListener(this.onChange);
- UserStore.removeStatusesChangeListener(this.onStatusChange);
- }
-
- onChange() {
- let users;
- if (this.term) {
- users = searchProfilesNotInCurrentChannel(store.getState(), this.term, true);
- } else {
- users = UserStore.getProfileListNotInChannel(this.props.channel.id, true);
- }
-
- const channelStats = ChannelStore.getStats(this.props.channel.id);
- const teamStats = TeamStore.getCurrentStats();
-
- this.setState({
- users,
- total: teamStats.active_member_count - channelStats.member_count
- });
- }
-
- onStatusChange() {
- // Initiate a render to pick up on new statuses
- this.setState({
- statusChange: !this.state.statusChange
- });
- }
-
- onHide() {
- this.setState({show: false});
- }
-
- handleInviteError(err) {
- if (err) {
- this.setState({
- inviteError: err.message
- });
- } else {
- this.setState({
- inviteError: null
- });
- }
- }
-
- nextPage(page) {
- this.props.actions.getProfilesNotInChannel(TeamStore.getCurrentId(), this.props.channel.id, (page + 1) * USERS_PER_PAGE, USERS_PER_PAGE);
- }
-
- search(term) {
- clearTimeout(this.searchTimeoutId);
- this.term = term;
-
- if (term === '') {
- this.onChange();
- return;
- }
-
- this.searchTimeoutId = setTimeout(
- () => {
- searchUsers(term, TeamStore.getCurrentId(), {not_in_channel_id: this.props.channel.id});
- },
- Constants.SEARCH_TIMEOUT_MILLISECONDS
- );
- }
-
- render() {
- let inviteError = null;
- if (this.state.inviteError) {
- inviteError = (<label className='has-error control-label'>{this.state.inviteError}</label>);
- }
-
- let users = [];
- if (this.state.users) {
- users = this.state.users.filter((user) => user.delete_at === 0);
- }
-
- let content;
- if (this.state.loading) {
- content = (<LoadingScreen/>);
- } else {
- content = (
- <SearchableUserList
- users={users}
- usersPerPage={USERS_PER_PAGE}
- total={this.state.total}
- nextPage={this.nextPage}
- search={this.search}
- actions={[ChannelInviteButton]}
- focusOnMount={!UserAgent.isMobile()}
- actionProps={{
- channel: this.props.channel,
- onInviteError: this.handleInviteError
- }}
- />
- );
- }
-
- return (
- <Modal
- dialogClassName='more-modal'
- show={this.state.show}
- onHide={this.onHide}
- onExited={this.props.onHide}
- >
- <Modal.Header closeButton={true}>
- <Modal.Title>
- <FormattedMessage
- id='channel_invite.addNewMembers'
- defaultMessage='Add New Members to '
- />
- <span className='name'>{this.props.channel.display_name}</span>
- </Modal.Title>
- </Modal.Header>
- <Modal.Body>
- {inviteError}
- {content}
- </Modal.Body>
- </Modal>
- );
- }
-}
diff --git a/webapp/components/channel_invite_modal/index.js b/webapp/components/channel_invite_modal/index.js
deleted file mode 100644
index a89a94a4c..000000000
--- a/webapp/components/channel_invite_modal/index.js
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getProfilesNotInChannel} from 'mattermost-redux/actions/users';
-import {getTeamStats} from 'mattermost-redux/actions/teams';
-
-import ChannelInviteModal from './channel_invite_modal.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getProfilesNotInChannel,
- getTeamStats
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(ChannelInviteModal);
diff --git a/webapp/components/channel_members_dropdown/channel_members_dropdown.jsx b/webapp/components/channel_members_dropdown/channel_members_dropdown.jsx
deleted file mode 100644
index fbad6cde1..000000000
--- a/webapp/components/channel_members_dropdown/channel_members_dropdown.jsx
+++ /dev/null
@@ -1,268 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ChannelStore from 'stores/channel_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-
-import {removeUserFromChannel, makeUserChannelAdmin, makeUserChannelMember} from 'actions/channel_actions.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-import {canManageMembers} from 'utils/channel_utils.jsx';
-import {Constants} from 'utils/constants.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-
-export default class ChannelMembersDropdown extends React.Component {
- static propTypes = {
- channel: PropTypes.object.isRequired,
- user: PropTypes.object.isRequired,
- teamMember: PropTypes.object.isRequired,
- channelMember: PropTypes.object.isRequired,
- actions: PropTypes.shape({
- getChannelStats: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.handleRemoveFromChannel = this.handleRemoveFromChannel.bind(this);
- this.handleMakeChannelMember = this.handleMakeChannelMember.bind(this);
- this.handleMakeChannelAdmin = this.handleMakeChannelAdmin.bind(this);
-
- this.state = {
- serverError: null,
- user: null,
- role: null
- };
- }
-
- handleRemoveFromChannel() {
- removeUserFromChannel(
- this.props.channel.id,
- this.props.user.id,
- () => {
- this.props.actions.getChannelStats(this.props.channel.id);
- },
- (err) => {
- this.setState({serverError: err.message});
- }
- );
- }
-
- handleMakeChannelMember() {
- makeUserChannelMember(
- this.props.channel.id,
- this.props.user.id,
- () => {
- this.props.actions.getChannelStats(this.props.channel.id);
- },
- (err) => {
- this.setState({serverError: err.message});
- }
- );
- }
-
- handleMakeChannelAdmin() {
- makeUserChannelAdmin(
- this.props.channel.id,
- this.props.user.id,
- () => {
- this.props.actions.getChannelStats(this.props.channel.id);
- },
- (err) => {
- this.setState({serverError: err.message});
- }
- );
- }
-
- // Checks if the current user has the power to change the roles of this member.
- canChangeMemberRoles() {
- if (UserStore.isSystemAdminForCurrentUser()) {
- return true;
- } else if (TeamStore.isTeamAdminForCurrentTeam()) {
- return true;
- } else if (ChannelStore.isChannelAdminForCurrentChannel()) {
- return true;
- }
-
- return false;
- }
-
- // Checks if the current user has the power to remove this member from the channel.
- canRemoveMember() {
- return canManageMembers(this.props.channel, ChannelStore.isChannelAdminForCurrentChannel(), TeamStore.isTeamAdminForCurrentTeam(), UserStore.isSystemAdminForCurrentUser());
- }
-
- render() {
- const supportsChannelAdmin = global.mm_license.IsLicensed === 'true';
- const isChannelAdmin = supportsChannelAdmin && Utils.isChannelAdmin(this.props.channelMember.roles);
-
- let serverError = null;
- if (this.state.serverError) {
- serverError = (
- <div className='has-error'>
- <label className='has-error control-label'>{this.state.serverError}</label>
- </div>
- );
- }
-
- if (this.props.user.id === UserStore.getCurrentId()) {
- return null;
- }
-
- if (this.canChangeMemberRoles()) {
- let role = (
- <FormattedMessage
- id='channel_members_dropdown.channel_member'
- defaultMessage='Channel Member'
- />
- );
-
- if (isChannelAdmin) {
- role = (
- <FormattedMessage
- id='channel_members_dropdown.channel_admin'
- defaultMessage='Channel Admin'
- />
- );
- }
-
- let removeFromChannel = null;
- if (this.canRemoveMember() && this.props.channel.name !== Constants.DEFAULT_CHANNEL) {
- removeFromChannel = (
- <li role='presentation'>
- <a
- id='removeFromChannel'
- role='menuitem'
- href='#'
- onClick={this.handleRemoveFromChannel}
- >
- <FormattedMessage
- id='channel_members_dropdown.remove_from_channel'
- defaultMessage='Remove From Channel'
- />
- </a>
- </li>
- );
- }
-
- let makeChannelMember = null;
- if (isChannelAdmin) {
- makeChannelMember = (
- <li role='presentation'>
- <a
- id='makeChannelMember'
- role='menuitem'
- href='#'
- onClick={this.handleMakeChannelMember}
- >
- <FormattedMessage
- id='channel_members_dropdown.make_channel_member'
- defaultMessage='Make Channel Member'
- />
- </a>
- </li>
- );
- }
-
- let makeChannelAdmin = null;
- if (supportsChannelAdmin && !isChannelAdmin) {
- makeChannelAdmin = (
- <li role='presentation'>
- <a
- id='makeChannelAdmin'
- role='menuitem'
- href='#'
- onClick={this.handleMakeChannelAdmin}
- >
- <FormattedMessage
- id='channel_members_dropdown.make_channel_admin'
- defaultMessage='Make Channel Admin'
- />
- </a>
- </li>
- );
- }
-
- if ((makeChannelMember || makeChannelAdmin) && removeFromChannel) {
- return (
- <div className='dropdown member-drop'>
- <a
- id='channelMemberDropdown'
- href='#'
- className='dropdown-toggle theme'
- type='button'
- data-toggle='dropdown'
- aria-expanded='true'
- >
- <span>{role} </span>
- <span className='fa fa-chevron-down'/>
- </a>
- <ul
- className='dropdown-menu member-menu'
- role='menu'
- >
- {makeChannelMember}
- {makeChannelAdmin}
- {removeFromChannel}
- </ul>
- {serverError}
- </div>
- );
- }
- }
-
- if (this.canRemoveMember() && this.props.channel.name !== Constants.DEFAULT_CHANNEL) {
- return (
- <button
- id='removeMember'
- type='button'
- className='btn btn-danger btn-message'
- onClick={this.handleRemoveFromChannel}
- >
- <FormattedMessage
- id='channel_members_dropdown.remove_member'
- defaultMessage='Remove Member'
- />
- </button>
- );
- }
-
- if (isChannelAdmin) {
- if (this.props.channel.name === Constants.DEFAULT_CHANNEL) {
- return (
- <div/>
- );
- }
-
- return (
- <div>
- <FormattedMessage
- id='channel_members_dropdown.channel_admin'
- defaultMessage='Channel Admin'
- />
- </div>
- );
- }
-
- if (this.props.channel.name === Constants.DEFAULT_CHANNEL) {
- return (
- <div/>
- );
- }
-
- return (
- <div>
- <FormattedMessage
- id='channel_members_dropdown.channel_member'
- defaultMessage='Channel Member'
- />
- </div>
- );
- }
-}
diff --git a/webapp/components/channel_members_dropdown/index.js b/webapp/components/channel_members_dropdown/index.js
deleted file mode 100644
index 11a626e46..000000000
--- a/webapp/components/channel_members_dropdown/index.js
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getChannelStats} from 'mattermost-redux/actions/channels';
-
-import ChannelMembersDropdown from './channel_members_dropdown.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getChannelStats
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(ChannelMembersDropdown);
diff --git a/webapp/components/channel_members_modal.jsx b/webapp/components/channel_members_modal.jsx
deleted file mode 100644
index f991b7599..000000000
--- a/webapp/components/channel_members_modal.jsx
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import MemberListChannel from 'components/member_list_channel';
-
-import TeamStore from 'stores/team_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-import ChannelStore from 'stores/channel_store.jsx';
-
-import {canManageMembers} from 'utils/channel_utils.jsx';
-import {Constants} from 'utils/constants.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import {Modal} from 'react-bootstrap';
-import {FormattedMessage} from 'react-intl';
-
-export default class ChannelMembersModal extends React.Component {
- constructor(props) {
- super(props);
-
- this.onHide = this.onHide.bind(this);
-
- this.state = {
- channel: this.props.channel,
- show: true
- };
- }
-
- onHide() {
- this.setState({show: false});
- }
-
- render() {
- const isSystemAdmin = UserStore.isSystemAdminForCurrentUser();
- const isTeamAdmin = TeamStore.isTeamAdminForCurrentTeam();
- const isChannelAdmin = ChannelStore.isChannelAdminForCurrentChannel();
-
- let addMembersButton = null;
- if (canManageMembers(this.state.channel, isChannelAdmin, isTeamAdmin, isSystemAdmin) && this.state.channel.name !== Constants.DEFAULT_CHANNEL) {
- addMembersButton = (
- <a
- id='showInviteModal'
- className='btn btn-md btn-primary'
- href='#'
- onClick={() => {
- this.props.showInviteModal();
- this.onHide();
- }}
- >
- <FormattedMessage
- id='channel_members_modal.addNew'
- defaultMessage=' Add New Members'
- />
- </a>
- );
- }
-
- return (
- <div>
- <Modal
- dialogClassName='more-modal more-modal--action'
- show={this.state.show}
- onHide={this.onHide}
- onExited={this.props.onModalDismissed}
- >
- <Modal.Header closeButton={true}>
- <Modal.Title>
- <span className='name'>{this.props.channel.display_name}</span>
- <FormattedMessage
- id='channel_members_modal.members'
- defaultMessage=' Members'
- />
- </Modal.Title>
- {addMembersButton}
- </Modal.Header>
- <Modal.Body
- ref='modalBody'
- >
- <MemberListChannel
- channel={this.props.channel}
- />
- </Modal.Body>
- </Modal>
- </div>
- );
- }
-}
-
-ChannelMembersModal.propTypes = {
- onModalDismissed: PropTypes.func.isRequired,
- showInviteModal: PropTypes.func.isRequired,
- channel: PropTypes.object.isRequired
-};
diff --git a/webapp/components/channel_notifications_modal.jsx b/webapp/components/channel_notifications_modal.jsx
deleted file mode 100644
index e924b3d45..000000000
--- a/webapp/components/channel_notifications_modal.jsx
+++ /dev/null
@@ -1,651 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import SettingItemMin from 'components/setting_item_min.jsx';
-import SettingItemMax from 'components/setting_item_max.jsx';
-
-import $ from 'jquery';
-import PropTypes from 'prop-types';
-import React from 'react';
-import {Modal} from 'react-bootstrap';
-import {FormattedMessage} from 'react-intl';
-
-import {updateChannelNotifyProps} from 'actions/channel_actions.jsx';
-
-export default class ChannelNotificationsModal extends React.Component {
- constructor(props) {
- super(props);
-
- this.updateSection = this.updateSection.bind(this);
- this.onHide = this.onHide.bind(this);
-
- this.handleSubmitDesktopNotifyLevel = this.handleSubmitDesktopNotifyLevel.bind(this);
- this.handleUpdateDesktopNotifyLevel = this.handleUpdateDesktopNotifyLevel.bind(this);
- this.createDesktopNotifyLevelSection = this.createDesktopNotifyLevelSection.bind(this);
-
- this.handleSubmitMarkUnreadLevel = this.handleSubmitMarkUnreadLevel.bind(this);
- this.handleUpdateMarkUnreadLevel = this.handleUpdateMarkUnreadLevel.bind(this);
- this.createMarkUnreadLevelSection = this.createMarkUnreadLevelSection.bind(this);
-
- this.handleSubmitPushNotificationLevel = this.handleSubmitPushNotificationLevel.bind(this);
- this.handleUpdatePushNotificationLevel = this.handleUpdatePushNotificationLevel.bind(this);
- this.createPushNotificationLevelSection = this.createPushNotificationLevelSection.bind(this);
-
- this.state = {
- activeSection: '',
- show: true,
- notifyLevel: props.channelMember.notify_props.desktop,
- unreadLevel: props.channelMember.notify_props.mark_unread,
- pushLevel: props.channelMember.notify_props.push || 'default'
- };
- }
-
- updateSection(section) {
- if ($('.section-max').length) {
- $('.settings-modal .modal-body').scrollTop(0).perfectScrollbar('update');
- }
- this.setState({activeSection: section});
- }
-
- onHide() {
- this.setState({show: false});
- }
-
- handleSubmitDesktopNotifyLevel() {
- const channelId = this.props.channel.id;
- const notifyLevel = this.state.notifyLevel;
- const currentUserId = this.props.currentUser.id;
-
- if (this.props.channelMember.notify_props.desktop === notifyLevel) {
- this.updateSection('');
- return;
- }
-
- const options = {desktop: notifyLevel};
- const data = {
- channel_id: channelId,
- user_id: currentUserId
- };
-
- updateChannelNotifyProps(data, options,
- () => {
- this.updateSection('');
- },
- (err) => {
- this.setState({serverError: err.message});
- }
- );
- }
-
- handleUpdateDesktopNotifyLevel(notifyLevel) {
- this.setState({notifyLevel});
- }
-
- createDesktopNotifyLevelSection(serverError) {
- // Get glabal user setting for notifications
- const globalNotifyLevel = this.props.currentUser.notify_props ? this.props.currentUser.notify_props.desktop : 'all';
- let globalNotifyLevelName;
- if (globalNotifyLevel === 'all') {
- globalNotifyLevelName = (
- <FormattedMessage
- id='channel_notifications.allActivity'
- defaultMessage='For all activity'
- />
- );
- } else if (globalNotifyLevel === 'mention') {
- globalNotifyLevelName = (
- <FormattedMessage
- id='channel_notifications.onlyMentions'
- defaultMessage='Only for mentions'
- />
- );
- } else {
- globalNotifyLevelName = (
- <FormattedMessage
- id='channel_notifications.never'
- defaultMessage='Never'
- />
- );
- }
-
- const sendDesktop = (
- <FormattedMessage
- id='channel_notifications.sendDesktop'
- defaultMessage='Send desktop notifications'
- />
- );
-
- const notificationLevel = this.state.notifyLevel;
-
- if (this.state.activeSection === 'desktop') {
- const notifyActive = [false, false, false, false];
- if (notificationLevel === 'default') {
- notifyActive[0] = true;
- } else if (notificationLevel === 'all') {
- notifyActive[1] = true;
- } else if (notificationLevel === 'mention') {
- notifyActive[2] = true;
- } else {
- notifyActive[3] = true;
- }
-
- var inputs = [];
-
- inputs.push(
- <div key='channel-notification-level-radio'>
- <div className='radio'>
- <label>
- <input
- id='channelNotificationGlobalDefault'
- type='radio'
- name='desktopNotificationLevel'
- checked={notifyActive[0]}
- onChange={this.handleUpdateDesktopNotifyLevel.bind(this, 'default')}
- />
- <FormattedMessage
- id='channel_notifications.globalDefault'
- defaultMessage='Global default ({notifyLevel})'
- values={{
- notifyLevel: (globalNotifyLevelName)
- }}
- />
- </label>
- <br/>
- </div>
- <div className='radio'>
- <label>
- <input
- id='channelNotificationAllActivity'
- type='radio'
- name='desktopNotificationLevel'
- checked={notifyActive[1]}
- onChange={this.handleUpdateDesktopNotifyLevel.bind(this, 'all')}
- />
- <FormattedMessage id='channel_notifications.allActivity'/>
- </label>
- <br/>
- </div>
- <div className='radio'>
- <label>
- <input
- id='channelNotificationMentions'
- type='radio'
- name='desktopNotificationLevel'
- checked={notifyActive[2]}
- onChange={this.handleUpdateDesktopNotifyLevel.bind(this, 'mention')}
- />
- <FormattedMessage id='channel_notifications.onlyMentions'/>
- </label>
- <br/>
- </div>
- <div className='radio'>
- <label>
- <input
- id='channelNotificationNever'
- type='radio'
- name='desktopNotificationLevel'
- checked={notifyActive[3]}
- onChange={this.handleUpdateDesktopNotifyLevel.bind(this, 'none')}
- />
- <FormattedMessage id='channel_notifications.never'/>
- </label>
- </div>
- </div>
- );
-
- const handleUpdateSection = function updateSection(e) {
- this.updateSection('');
- this.setState({
- notifyLevel: this.props.channelMember.notify_props.desktop
- });
- e.preventDefault();
- }.bind(this);
-
- const extraInfo = (
- <span>
- <FormattedMessage
- id='channel_notifications.override'
- defaultMessage='Selecting an option other than "Default" will override the global notification settings. Desktop notifications are available on Firefox, Safari, and Chrome.'
- />
- </span>
- );
-
- return (
- <SettingItemMax
- title={sendDesktop}
- inputs={inputs}
- submit={this.handleSubmitDesktopNotifyLevel}
- server_error={serverError}
- updateSection={handleUpdateSection}
- extraInfo={extraInfo}
- />
- );
- }
-
- var describe;
- if (notificationLevel === 'default') {
- describe = (
- <FormattedMessage
- id='channel_notifications.globalDefault'
- values={{
- notifyLevel: (globalNotifyLevelName)
- }}
- />
- );
- } else if (notificationLevel === 'mention') {
- describe = (<FormattedMessage id='channel_notifications.onlyMentions'/>);
- } else if (notificationLevel === 'all') {
- describe = (<FormattedMessage id='channel_notifications.allActivity'/>);
- } else {
- describe = (<FormattedMessage id='channel_notifications.never'/>);
- }
-
- return (
- <SettingItemMin
- title={sendDesktop}
- describe={describe}
- updateSection={() => {
- this.updateSection('desktop');
- }}
- />
- );
- }
-
- handleSubmitMarkUnreadLevel() {
- const channelId = this.props.channel.id;
- const markUnreadLevel = this.state.unreadLevel;
-
- if (this.props.channelMember.notify_props.mark_unread === markUnreadLevel) {
- this.updateSection('');
- return;
- }
-
- const options = {mark_unread: markUnreadLevel};
- const data = {
- channel_id: channelId,
- user_id: this.props.currentUser.id
- };
-
- updateChannelNotifyProps(data, options,
- () => {
- this.updateSection('');
- },
- (err) => {
- this.setState({serverError: err.message});
- }
- );
- }
-
- handleUpdateMarkUnreadLevel(unreadLevel) {
- this.setState({unreadLevel});
- }
-
- createMarkUnreadLevelSection(serverError) {
- let content;
-
- const markUnread = (
- <FormattedMessage
- id='channel_notifications.markUnread'
- defaultMessage='Mark Channel Unread'
- />
- );
- if (this.state.activeSection === 'markUnreadLevel') {
- const inputs = [(
- <div key='channel-notification-unread-radio'>
- <div className='radio'>
- <label>
- <input
- id='channelUnreadAll'
- type='radio'
- name='markUnreadLevel'
- checked={this.state.unreadLevel === 'all'}
- onChange={this.handleUpdateMarkUnreadLevel.bind(this, 'all')}
- />
- <FormattedMessage
- id='channel_notifications.allUnread'
- defaultMessage='For all unread messages'
- />
- </label>
- <br/>
- </div>
- <div className='radio'>
- <label>
- <input
- id='channelUnreadMentions'
- type='radio'
- name='markUnreadLevel'
- checked={this.state.unreadLevel === 'mention'}
- onChange={this.handleUpdateMarkUnreadLevel.bind(this, 'mention')}
- />
- <FormattedMessage id='channel_notifications.onlyMentions'/>
- </label>
- <br/>
- </div>
- </div>
- )];
-
- const handleUpdateSection = function handleUpdateSection(e) {
- this.updateSection('');
- this.setState({
- unreadLevel: this.props.channelMember.notify_props.mark_unread
- });
- e.preventDefault();
- }.bind(this);
-
- const extraInfo = (
- <span>
- <FormattedMessage
- id='channel_notifications.unreadInfo'
- defaultMessage='The channel name is bolded in the sidebar when there are unread messages. Selecting "Only for mentions" will bold the channel only when you are mentioned.'
- />
- </span>
- );
-
- content = (
- <SettingItemMax
- title={markUnread}
- inputs={inputs}
- submit={this.handleSubmitMarkUnreadLevel}
- server_error={serverError}
- updateSection={handleUpdateSection}
- extraInfo={extraInfo}
- />
- );
- } else {
- let describe;
-
- if (!this.state.unreadLevel || this.state.unreadLevel === 'all') {
- describe = (
- <FormattedMessage
- id='channel_notifications.allUnread'
- defaultMessage='For all unread messages'
- />
- );
- } else {
- describe = (<FormattedMessage id='channel_notifications.onlyMentions'/>);
- }
-
- const handleUpdateSection = function handleUpdateSection(e) {
- this.updateSection('markUnreadLevel');
- this.setState({
- unreadLevel: this.props.channelMember.notify_props.mark_unread
- });
- e.preventDefault();
- }.bind(this);
-
- content = (
- <SettingItemMin
- title={markUnread}
- describe={describe}
- updateSection={handleUpdateSection}
- />
- );
- }
-
- return content;
- }
-
- handleSubmitPushNotificationLevel() {
- const channelId = this.props.channel.id;
- const notifyLevel = this.state.pushLevel;
- const currentUserId = this.props.currentUser.id;
-
- if (this.props.channelMember.notify_props.push === notifyLevel) {
- this.updateSection('');
- return;
- }
-
- const options = {push: notifyLevel};
- const data = {
- channel_id: channelId,
- user_id: currentUserId
- };
-
- updateChannelNotifyProps(data, options,
- () => {
- this.updateSection('');
- },
- (err) => {
- this.setState({serverError: err.message});
- }
- );
- }
-
- handleUpdatePushNotificationLevel(pushLevel) {
- this.setState({pushLevel});
- }
-
- createPushNotificationLevelSection(serverError) {
- if (global.mm_config.SendPushNotifications === 'false') {
- return null;
- }
-
- // Get glabal user setting for notifications
- const globalNotifyLevel = this.props.currentUser.notify_props ? this.props.currentUser.notify_props.push : 'all';
- let globalNotifyLevelName;
- if (globalNotifyLevel === 'all') {
- globalNotifyLevelName = (
- <FormattedMessage
- id='channel_notifications.allActivity'
- defaultMessage='For all activity'
- />
- );
- } else if (globalNotifyLevel === 'mention') {
- globalNotifyLevelName = (
- <FormattedMessage
- id='channel_notifications.onlyMentions'
- defaultMessage='Only for mentions'
- />
- );
- } else {
- globalNotifyLevelName = (
- <FormattedMessage
- id='channel_notifications.never'
- defaultMessage='Never'
- />
- );
- }
-
- const sendPushNotifications = (
- <FormattedMessage
- id='channel_notifications.push'
- defaultMessage='Send mobile push notifications'
- />
- );
-
- const notificationLevel = this.state.pushLevel;
-
- let content;
- if (this.state.activeSection === 'push') {
- const notifyActive = [false, false, false, false];
- if (notificationLevel === 'default') {
- notifyActive[0] = true;
- } else if (notificationLevel === 'all') {
- notifyActive[1] = true;
- } else if (notificationLevel === 'mention') {
- notifyActive[2] = true;
- } else {
- notifyActive[3] = true;
- }
-
- const inputs = [];
-
- inputs.push(
- <div key='channel-notification-level-radio'>
- <div className='radio'>
- <label>
- <input
- id='channelPushNotificationGlobalDefault'
- type='radio'
- name='pushNotificationLevel'
- checked={notifyActive[0]}
- onChange={this.handleUpdatePushNotificationLevel.bind(this, 'default')}
- />
- <FormattedMessage
- id='channel_notifications.globalDefault'
- defaultMessage='Global default ({notifyLevel})'
- values={{
- notifyLevel: (globalNotifyLevelName)
- }}
- />
- </label>
- <br/>
- </div>
- <div className='radio'>
- <label>
- <input
- id='channelPushNotificationAllActivity'
- type='radio'
- name='pushNotificationLevel'
- checked={notifyActive[1]}
- onChange={this.handleUpdatePushNotificationLevel.bind(this, 'all')}
- />
- <FormattedMessage id='channel_notifications.allActivity'/>
- </label>
- <br/>
- </div>
- <div className='radio'>
- <label>
- <input
- id='channelPushNotificationMentions'
- type='radio'
- name='pushNotificationLevel'
- checked={notifyActive[2]}
- onChange={this.handleUpdatePushNotificationLevel.bind(this, 'mention')}
- />
- <FormattedMessage id='channel_notifications.onlyMentions'/>
- </label>
- <br/>
- </div>
- <div className='radio'>
- <label>
- <input
- id='channelPushNotificationNever'
- type='radio'
- name='pushNotificationLevel'
- checked={notifyActive[3]}
- onChange={this.handleUpdatePushNotificationLevel.bind(this, 'none')}
- />
- <FormattedMessage id='channel_notifications.never'/>
- </label>
- </div>
- </div>
- );
-
- const handleUpdateSection = function updateSection(e) {
- this.updateSection('');
- this.setState({
- pushLevel: this.props.channelMember.notify_props.push || 'default'
- });
- e.preventDefault();
- }.bind(this);
-
- const extraInfo = (
- <span>
- <FormattedMessage
- id='channel_notifications.overridePush'
- defaultMessage='Selecting an option other than "Global default" will override the global notification settings for mobile push notifications in account settings. Push notifications must be enabled by the System Admin.'
- />
- </span>
- );
-
- content = (
- <SettingItemMax
- title={sendPushNotifications}
- inputs={inputs}
- submit={this.handleSubmitPushNotificationLevel}
- server_error={serverError}
- updateSection={handleUpdateSection}
- extraInfo={extraInfo}
- />
- );
- } else {
- let describe;
- if (notificationLevel === 'default') {
- describe = (
- <FormattedMessage
- id='channel_notifications.globalDefault'
- values={{
- notifyLevel: (globalNotifyLevelName)
- }}
- />
- );
- } else if (notificationLevel === 'mention') {
- describe = (<FormattedMessage id='channel_notifications.onlyMentions'/>);
- } else if (notificationLevel === 'all') {
- describe = (<FormattedMessage id='channel_notifications.allActivity'/>);
- } else {
- describe = (<FormattedMessage id='channel_notifications.never'/>);
- }
-
- content = (
- <SettingItemMin
- title={sendPushNotifications}
- describe={describe}
- updateSection={() => {
- this.updateSection('push');
- }}
- />
- );
- }
-
- return (
- <div>
- <div className='divider-light'/>
- {content}
- </div>
- );
- }
-
- render() {
- let serverError = null;
- if (this.state.serverError) {
- serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
- }
-
- return (
- <Modal
- show={this.state.show}
- dialogClassName='settings-modal settings-modal--tabless'
- onHide={this.onHide}
- onExited={this.props.onHide}
- >
- <Modal.Header closeButton={true}>
- <Modal.Title>
- <FormattedMessage
- id='channel_notifications.preferences'
- defaultMessage='Notification Preferences for '
- />
- <span className='name'>{this.props.channel.display_name}</span>
- </Modal.Title>
- </Modal.Header>
- <Modal.Body>
- <div className='settings-table'>
- <div className='settings-content'>
- <div
- ref='wrapper'
- className='user-settings'
- >
- <br/>
- <div className='divider-dark first'/>
- {this.createDesktopNotifyLevelSection(serverError)}
- {this.createPushNotificationLevelSection(serverError)}
- <div className='divider-light'/>
- {this.createMarkUnreadLevelSection(serverError)}
- <div className='divider-dark'/>
- </div>
- </div>
- </div>
- {serverError}
- </Modal.Body>
- </Modal>
- );
- }
-}
-
-ChannelNotificationsModal.propTypes = {
- onHide: PropTypes.func.isRequired,
- channel: PropTypes.object.isRequired,
- channelMember: PropTypes.object.isRequired,
- currentUser: PropTypes.object.isRequired
-};
diff --git a/webapp/components/channel_select.jsx b/webapp/components/channel_select.jsx
deleted file mode 100644
index bad8dffca..000000000
--- a/webapp/components/channel_select.jsx
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import Constants from 'utils/constants.jsx';
-import ChannelStore from 'stores/channel_store.jsx';
-import * as Utils from 'utils/utils.jsx';
-import {sortChannelsByDisplayName} from 'utils/channel_utils.jsx';
-
-export default class ChannelSelect extends React.Component {
- static get propTypes() {
- return {
- onChange: PropTypes.func,
- value: PropTypes.string,
- selectOpen: PropTypes.bool.isRequired,
- selectPrivate: PropTypes.bool.isRequired,
- selectDm: PropTypes.bool.isRequired
- };
- }
-
- static get defaultProps() {
- return {
- selectOpen: false,
- selectPrivate: false,
- selectDm: false
- };
- }
-
- constructor(props) {
- super(props);
-
- this.handleChannelChange = this.handleChannelChange.bind(this);
- this.filterChannels = this.filterChannels.bind(this);
-
- this.state = {
- channels: ChannelStore.getAll().filter(this.filterChannels).sort(sortChannelsByDisplayName)
- };
- }
-
- componentDidMount() {
- ChannelStore.addChangeListener(this.handleChannelChange);
- }
-
- componentWillUnmount() {
- ChannelStore.removeChangeListener(this.handleChannelChange);
- }
-
- handleChannelChange() {
- this.setState({
- channels: ChannelStore.getAll().
- filter(this.filterChannels).sort(sortChannelsByDisplayName)
- });
- }
-
- filterChannels(channel) {
- if (channel.display_name) {
- return true;
- }
-
- return false;
- }
-
- render() {
- const options = [
- <option
- key=''
- value=''
- >
- {Utils.localizeMessage('channel_select.placeholder', '--- Select a channel ---')}
- </option>
- ];
-
- this.state.channels.forEach((channel) => {
- if (channel.type === Constants.OPEN_CHANNEL && this.props.selectOpen) {
- options.push(
- <option
- key={channel.id}
- value={channel.id}
- >
- {channel.display_name}
- </option>
- );
- } else if (channel.type === Constants.PRIVATE_CHANNEL && this.props.selectPrivate) {
- options.push(
- <option
- key={channel.id}
- value={channel.id}
- >
- {channel.display_name}
- </option>
- );
- } else if (channel.type === Constants.DM_CHANNEL && this.props.selectDm) {
- options.push(
- <option
- key={channel.id}
- value={channel.id}
- >
- {channel.display_name}
- </option>
- );
- }
- });
-
- return (
- <select
- className='form-control'
- value={this.props.value}
- onChange={this.props.onChange}
- >
- {options}
- </select>
- );
- }
-}
diff --git a/webapp/components/channel_view.jsx b/webapp/components/channel_view.jsx
deleted file mode 100644
index 4a5ac8969..000000000
--- a/webapp/components/channel_view.jsx
+++ /dev/null
@@ -1,120 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import Constants from 'utils/constants.jsx';
-import * as UserAgent from 'utils/user_agent.jsx';
-import ChannelHeader from 'components/channel_header.jsx';
-import FileUploadOverlay from 'components/file_upload_overlay.jsx';
-import CreatePost from 'components/create_post.jsx';
-import PostView from 'components/post_view';
-import TutorialView from 'components/tutorial/tutorial_view.jsx';
-const TutorialSteps = Constants.TutorialSteps;
-const Preferences = Constants.Preferences;
-
-import ChannelStore from 'stores/channel_store.jsx';
-import PreferenceStore from 'stores/preference_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-
-export default class ChannelView extends React.Component {
- constructor(props) {
- super(props);
-
- this.getStateFromStores = this.getStateFromStores.bind(this);
- this.isStateValid = this.isStateValid.bind(this);
- this.updateState = this.updateState.bind(this);
-
- this.state = this.getStateFromStores(props);
- }
-
- getStateFromStores() {
- return {
- channelId: ChannelStore.getCurrentId(),
- tutorialStep: PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999)
- };
- }
-
- isStateValid() {
- return this.state.channelId !== '';
- }
-
- updateState() {
- this.setState(this.getStateFromStores(this.props));
- }
-
- componentDidMount() {
- ChannelStore.addChangeListener(this.updateState);
-
- $('body').addClass('app__body');
-
- // IE Detection
- if (UserAgent.isInternetExplorer() || UserAgent.isEdge()) {
- $('body').addClass('browser--ie');
- }
- }
-
- componentWillUnmount() {
- ChannelStore.removeChangeListener(this.updateState);
-
- $('body').removeClass('app__body');
- }
-
- componentWillReceiveProps(nextProps) {
- this.setState(this.getStateFromStores(nextProps));
- }
-
- shouldComponentUpdate(nextProps, nextState) {
- if (!Utils.areObjectsEqual(nextProps.params, this.props.params)) {
- return true;
- }
-
- if (nextState.channelId !== this.state.channelId) {
- return true;
- }
-
- return false;
- }
-
- getChannelView = () => {
- return this.refs.channelView;
- }
-
- render() {
- if (this.state.tutorialStep <= TutorialSteps.INTRO_SCREENS) {
- return (<TutorialView/>);
- }
-
- return (
- <div
- ref='channelView'
- id='app-content'
- className='app__content'
- >
- <FileUploadOverlay overlayType='center'/>
- <ChannelHeader
- channelId={this.state.channelId}
- />
- <PostView
- channelId={this.state.channelId}
- />
- <div
- className='post-create__container'
- id='post-create'
- >
- <CreatePost getChannelView={this.getChannelView}/>
- </div>
- </div>
- );
- }
-}
-ChannelView.defaultProps = {
-};
-
-ChannelView.propTypes = {
- params: PropTypes.object.isRequired
-};
diff --git a/webapp/components/claim/claim_controller.jsx b/webapp/components/claim/claim_controller.jsx
deleted file mode 100644
index ccf4fb64b..000000000
--- a/webapp/components/claim/claim_controller.jsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-import {Link} from 'react-router/es6';
-
-import logoImage from 'images/logo.png';
-
-export default class ClaimController extends React.Component {
- constructor(props) {
- super(props);
-
- this.state = {};
- }
- componentWillMount() {
- this.setState({
- email: this.props.location.query.email,
- newType: this.props.location.query.new_type,
- oldType: this.props.location.query.old_type
- });
- }
- render() {
- return (
- <div>
- <div className='signup-header'>
- <Link to='/'>
- <span className='fa fa-chevron-left'/>
- <FormattedMessage
- id='web.header.back'
- />
- </Link>
- </div>
- <div className='col-sm-12'>
- <div className='signup-team__container'>
- <img
- className='signup-team-logo'
- src={logoImage}
- />
- <div id='claim'>
- {React.cloneElement(this.props.children, {
- currentType: this.state.oldType,
- newType: this.state.newType,
- email: this.state.email
- })}
- </div>
- </div>
- </div>
- </div>
- );
- }
-}
-
-ClaimController.defaultProps = {
-};
-ClaimController.propTypes = {
- location: PropTypes.object.isRequired,
- children: PropTypes.node
-};
diff --git a/webapp/components/claim/components/email_to_ldap.jsx b/webapp/components/claim/components/email_to_ldap.jsx
deleted file mode 100644
index 2054231ce..000000000
--- a/webapp/components/claim/components/email_to_ldap.jsx
+++ /dev/null
@@ -1,273 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import LoginMfa from 'components/login/components/login_mfa.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-
-import {checkMfa} from 'actions/user_actions.jsx';
-import {emailToLdap} from 'actions/admin_actions.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-
-export default class EmailToLDAP extends React.Component {
- constructor(props) {
- super(props);
-
- this.submit = this.submit.bind(this);
- this.preSubmit = this.preSubmit.bind(this);
-
- this.state = {
- passwordError: '',
- ldapError: '',
- ldapPasswordError: '',
- serverError: '',
- showMfa: false
- };
- }
-
- preSubmit(e) {
- e.preventDefault();
-
- var state = {
- passwordError: '',
- ldapError: '',
- ldapPasswordError: '',
- serverError: ''
- };
-
- const password = this.refs.emailpassword.value;
- if (!password) {
- state.passwordError = Utils.localizeMessage('claim.email_to_ldap.pwdError', 'Please enter your password.');
- this.setState(state);
- return;
- }
-
- const ldapId = this.refs.ldapid.value.trim();
- if (!ldapId) {
- state.ldapError = Utils.localizeMessage('claim.email_to_ldap.ldapIdError', 'Please enter your AD/LDAP ID.');
- this.setState(state);
- return;
- }
-
- const ldapPassword = this.refs.ldappassword.value;
- if (!ldapPassword) {
- state.ldapPasswordError = Utils.localizeMessage('claim.email_to_ldap.ldapPasswordError', 'Please enter your AD/LDAP password.');
- this.setState(state);
- return;
- }
-
- state.password = password;
- state.ldapId = ldapId;
- state.ldapPassword = ldapPassword;
- this.setState(state);
-
- checkMfa(
- this.props.email,
- (requiresMfa) => {
- if (requiresMfa) {
- this.setState({showMfa: true});
- } else {
- this.submit(this.props.email, password, '', ldapId, ldapPassword);
- }
- },
- (err) => {
- this.setState({error: err.message});
- }
- );
- }
-
- submit(loginId, password, token, ldapId, ldapPassword) {
- emailToLdap(
- loginId,
- password,
- token,
- ldapId || this.state.ldapId,
- ldapPassword || this.state.ldapPassword,
- (data) => {
- if (data.follow_link) {
- window.location.href = data.follow_link;
- }
- },
- (err) => {
- switch (err.id) {
- case 'ent.ldap.do_login.user_not_registered.app_error':
- case 'ent.ldap.do_login.user_filtered.app_error':
- case 'ent.ldap.do_login.matched_to_many_users.app_error':
- this.setState({ldapError: err.message, showMfa: false});
- break;
- case 'ent.ldap.do_login.invalid_password.app_error':
- this.setState({ldapPasswordError: err.message, showMfa: false});
- break;
- case 'api.user.check_user_password.invalid.app_error':
- this.setState({passwordError: err.message, showMfa: false});
- break;
- default:
- this.setState({serverError: err.message, showMfa: false});
- }
- }
- );
- }
-
- render() {
- let serverError = null;
- let formClass = 'form-group';
- if (this.state.serverError) {
- serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
- formClass += ' has-error';
- }
-
- let passwordError = null;
- let passwordClass = 'form-group';
- if (this.state.passwordError) {
- passwordError = <div className='form-group has-error'><label className='control-label'>{this.state.passwordError}</label></div>;
- passwordClass += ' has-error';
- }
-
- let ldapError = null;
- let ldapClass = 'form-group';
- if (this.state.ldapError) {
- ldapError = <div className='form-group has-error'><label className='control-label'>{this.state.ldapError}</label></div>;
- ldapClass += ' has-error';
- }
-
- let ldapPasswordError = null;
- let ldapPasswordClass = 'form-group';
- if (this.state.ldapPasswordError) {
- ldapPasswordError = <div className='form-group has-error'><label className='control-label'>{this.state.ldapPasswordError}</label></div>;
- ldapPasswordClass += ' has-error';
- }
-
- let loginPlaceholder;
- if (global.window.mm_config.LdapLoginFieldName) {
- loginPlaceholder = global.window.mm_config.LdapLoginFieldName;
- } else {
- loginPlaceholder = Utils.localizeMessage('claim.email_to_ldap.ldapId', 'AD/LDAP ID');
- }
-
- let passwordPlaceholder;
- if (global.window.mm_config.LdapPasswordFieldName) {
- passwordPlaceholder = global.window.mm_config.LdapPasswordFieldName;
- } else {
- passwordPlaceholder = Utils.localizeMessage('claim.email_to_ldap.ldapPwd', 'AD/LDAP Password');
- }
-
- let content;
- if (this.state.showMfa) {
- content = (
- <LoginMfa
- loginId={this.props.email}
- password={this.state.password}
- submit={this.submit}
- />
- );
- } else {
- content = (
- <form
- onSubmit={this.preSubmit}
- className={formClass}
- >
- <p>
- <FormattedMessage
- id='claim.email_to_ldap.ssoType'
- defaultMessage='Upon claiming your account, you will only be able to login with AD/LDAP'
- />
- </p>
- <p>
- <FormattedMessage
- id='claim.email_to_ldap.ssoNote'
- defaultMessage='You must already have a valid AD/LDAP account'
- />
- </p>
- <p>
- <FormattedMessage
- id='claim.email_to_ldap.enterPwd'
- defaultMessage='Enter the password for your {site} email account'
- values={{
- site: global.window.mm_config.SiteName
- }}
- />
- </p>
- <input
- type='text'
- style={{display: 'none'}}
- name='fakeusernameremembered'
- />
- <div className={passwordClass}>
- <input
- type='password'
- className='form-control'
- name='emailPassword'
- ref='emailpassword'
- autoComplete='off'
- placeholder={Utils.localizeMessage('claim.email_to_ldap.pwd', 'Password')}
- spellCheck='false'
- />
- </div>
- {passwordError}
- <p>
- <FormattedMessage
- id='claim.email_to_ldap.enterLdapPwd'
- defaultMessage='Enter the ID and password for your AD/LDAP account'
- />
- </p>
- <div className={ldapClass}>
- <input
- type='text'
- className='form-control'
- name='ldapId'
- ref='ldapid'
- autoComplete='off'
- placeholder={loginPlaceholder}
- spellCheck='false'
- />
- </div>
- {ldapError}
- <div className={ldapPasswordClass}>
- <input
- type='password'
- className='form-control'
- name='ldapPassword'
- ref='ldappassword'
- autoComplete='off'
- placeholder={passwordPlaceholder}
- spellCheck='false'
- />
- </div>
- {ldapPasswordError}
- <button
- type='submit'
- className='btn btn-primary'
- >
- <FormattedMessage
- id='claim.email_to_ldap.switchTo'
- defaultMessage='Switch account to AD/LDAP'
- />
- </button>
- {serverError}
- </form>
- );
- }
-
- return (
- <div>
- <h3>
- <FormattedMessage
- id='claim.email_to_ldap.title'
- defaultMessage='Switch Email/Password Account to AD/LDAP'
- />
- </h3>
- {content}
- </div>
- );
- }
-}
-
-EmailToLDAP.defaultProps = {
-};
-EmailToLDAP.propTypes = {
- email: PropTypes.string
-};
diff --git a/webapp/components/claim/components/email_to_oauth.jsx b/webapp/components/claim/components/email_to_oauth.jsx
deleted file mode 100644
index 27504e6f3..000000000
--- a/webapp/components/claim/components/email_to_oauth.jsx
+++ /dev/null
@@ -1,178 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import LoginMfa from 'components/login/components/login_mfa.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-
-import {checkMfa} from 'actions/user_actions.jsx';
-import {emailToOAuth} from 'actions/admin_actions.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import ReactDOM from 'react-dom';
-import {FormattedMessage} from 'react-intl';
-
-export default class EmailToOAuth extends React.Component {
- constructor(props) {
- super(props);
-
- this.submit = this.submit.bind(this);
- this.preSubmit = this.preSubmit.bind(this);
-
- this.state = {showMfa: false, password: ''};
- }
-
- preSubmit(e) {
- e.preventDefault();
- var state = {};
-
- var password = ReactDOM.findDOMNode(this.refs.password).value;
- if (!password) {
- state.error = Utils.localizeMessage('claim.email_to_oauth.pwdError', 'Please enter your password.');
- this.setState(state);
- return;
- }
-
- this.setState({password});
-
- state.error = null;
- this.setState(state);
-
- checkMfa(
- this.props.email,
- (requiresMfa) => {
- if (requiresMfa) {
- this.setState({showMfa: true});
- } else {
- this.submit(this.props.email, password, '');
- }
- },
- (err) => {
- this.setState({error: err.message});
- }
- );
- }
-
- submit(loginId, password, token) {
- emailToOAuth(
- loginId,
- password,
- token,
- this.props.newType,
- (data) => {
- if (data.follow_link) {
- window.location.href = data.follow_link;
- }
- },
- (err) => {
- this.setState({error: err.message, showMfa: false});
- }
- );
- }
-
- render() {
- var error = null;
- if (this.state.error) {
- error = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>;
- }
-
- var formClass = 'form-group';
- if (error) {
- formClass += ' has-error';
- }
-
- const type = (this.props.newType === Constants.SAML_SERVICE ? Constants.SAML_SERVICE.toUpperCase() : Utils.toTitleCase(this.props.newType));
- const uiType = `${type} SSO`;
-
- let content;
- if (this.state.showMfa) {
- content = (
- <LoginMfa
- loginId={this.props.email}
- password={this.state.password}
- submit={this.submit}
- />
- );
- } else {
- content = (
- <form onSubmit={this.preSubmit}>
- <p>
- <FormattedMessage
- id='claim.email_to_oauth.ssoType'
- defaultMessage='Upon claiming your account, you will only be able to login with {type} SSO'
- values={{
- type
- }}
- />
- </p>
- <p>
- <FormattedMessage
- id='claim.email_to_oauth.ssoNote'
- defaultMessage='You must already have a valid {type} account'
- values={{
- type
- }}
- />
- </p>
- <p>
- <FormattedMessage
- id='claim.email_to_oauth.enterPwd'
- defaultMessage='Enter the password for your {site} account'
- values={{
- site: global.window.mm_config.SiteName
- }}
- />
- </p>
- <div className={formClass}>
- <input
- type='password'
- className='form-control'
- name='password'
- ref='password'
- placeholder={Utils.localizeMessage('claim.email_to_oauth.pwd', 'Password')}
- spellCheck='false'
- />
- </div>
- {error}
- <button
- type='submit'
- className='btn btn-primary'
- >
- <FormattedMessage
- id='claim.email_to_oauth.switchTo'
- defaultMessage='Switch account to {uiType}'
- values={{
- uiType
- }}
- />
- </button>
- </form>
- );
- }
-
- return (
- <div>
- <h3>
- <FormattedMessage
- id='claim.email_to_oauth.title'
- defaultMessage='Switch Email/Password Account to {uiType}'
- values={{
- uiType
- }}
- />
- </h3>
- {content}
- </div>
- );
- }
-}
-
-EmailToOAuth.defaultProps = {
-};
-EmailToOAuth.propTypes = {
- newType: PropTypes.string,
- email: PropTypes.string
-};
diff --git a/webapp/components/claim/components/ldap_to_email.jsx b/webapp/components/claim/components/ldap_to_email.jsx
deleted file mode 100644
index d5f6fbbb2..000000000
--- a/webapp/components/claim/components/ldap_to_email.jsx
+++ /dev/null
@@ -1,251 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import LoginMfa from 'components/login/components/login_mfa.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-
-import {checkMfa, switchFromLdapToEmail} from 'actions/user_actions.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-
-export default class LDAPToEmail extends React.Component {
- constructor(props) {
- super(props);
-
- this.submit = this.submit.bind(this);
- this.preSubmit = this.preSubmit.bind(this);
-
- this.state = {
- passwordError: '',
- confirmError: '',
- ldapPasswordError: '',
- serverError: ''
- };
- }
-
- preSubmit(e) {
- e.preventDefault();
-
- var state = {
- passwordError: '',
- confirmError: '',
- ldapPasswordError: '',
- serverError: ''
- };
-
- const ldapPassword = this.refs.ldappassword.value;
- if (!ldapPassword) {
- state.ldapPasswordError = Utils.localizeMessage('claim.ldap_to_email.ldapPasswordError', 'Please enter your AD/LDAP password.');
- this.setState(state);
- return;
- }
-
- const password = this.refs.password.value;
- if (!password) {
- state.passwordError = Utils.localizeMessage('claim.ldap_to_email.pwdError', 'Please enter your password.');
- this.setState(state);
- return;
- }
-
- const passwordErr = Utils.isValidPassword(password);
- if (passwordErr !== '') {
- this.setState({
- passwordError: passwordErr
- });
- return;
- }
-
- const confirmPassword = this.refs.passwordconfirm.value;
- if (!confirmPassword || password !== confirmPassword) {
- state.confirmError = Utils.localizeMessage('claim.ldap_to_email.pwdNotMatch', 'Passwords do not match.');
- this.setState(state);
- return;
- }
-
- state.password = password;
- state.ldapPassword = ldapPassword;
- this.setState(state);
-
- checkMfa(
- this.props.email,
- (requiresMfa) => {
- if (requiresMfa) {
- this.setState({showMfa: true});
- } else {
- this.submit(this.props.email, password, '', ldapPassword);
- }
- },
- (err) => {
- this.setState({error: err.message});
- }
- );
- }
-
- submit(loginId, password, token, ldapPassword) {
- switchFromLdapToEmail(
- this.props.email,
- password,
- token,
- ldapPassword || this.state.ldapPassword,
- null,
- (err) => {
- if (err.id.startsWith('model.user.is_valid.pwd')) {
- this.setState({passwordError: err.message, showMfa: false});
- } else {
- switch (err.id) {
- case 'ent.ldap.do_login.invalid_password.app_error':
- this.setState({ldapPasswordError: err.message, showMfa: false});
- break;
- default:
- this.setState({serverError: err.message, showMfa: false});
- }
- }
- }
- );
- }
-
- render() {
- let serverError = null;
- let formClass = 'form-group';
- if (this.state.serverError) {
- serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
- formClass += ' has-error';
- }
-
- let passwordError = null;
- let passwordClass = 'form-group';
- if (this.state.passwordError) {
- passwordError = <div className='form-group has-error'><label className='control-label'>{this.state.passwordError}</label></div>;
- passwordClass += ' has-error';
- }
-
- let ldapPasswordError = null;
- let ldapPasswordClass = 'form-group';
- if (this.state.ldapPasswordError) {
- ldapPasswordError = <div className='form-group has-error'><label className='control-label'>{this.state.ldapPasswordError}</label></div>;
- ldapPasswordClass += ' has-error';
- }
-
- let confirmError = null;
- let confimClass = 'form-group';
- if (this.state.confirmError) {
- confirmError = <div className='form-group has-error'><label className='control-label'>{this.state.confirmError}</label></div>;
- confimClass += ' has-error';
- }
-
- let passwordPlaceholder;
- if (global.window.mm_config.LdapPasswordFieldName) {
- passwordPlaceholder = global.window.mm_config.LdapPasswordFieldName;
- } else {
- passwordPlaceholder = Utils.localizeMessage('claim.ldap_to_email.ldapPwd', 'AD/LDAP Password');
- }
-
- let content;
- if (this.state.showMfa) {
- content = (
- <LoginMfa
- loginId={this.props.email}
- password={this.state.password}
- submit={this.submit}
- />
- );
- } else {
- content = (
- <form
- onSubmit={this.preSubmit}
- className={formClass}
- >
- <p>
- <FormattedMessage
- id='claim.ldap_to_email.email'
- defaultMessage='After switching your authentication method, you will use {email} to login. Your AD/LDAP credentials will no longer allow access to Mattermost.'
- values={{
- email: this.props.email
- }}
- />
- </p>
- <p>
- <FormattedMessage
- id='claim.ldap_to_email.enterLdapPwd'
- defaultMessage='{ldapPassword}:'
- values={{
- ldapPassword: passwordPlaceholder
- }}
- />
- </p>
- <div className={ldapPasswordClass}>
- <input
- type='password'
- className='form-control'
- name='ldapPassword'
- ref='ldappassword'
- placeholder={passwordPlaceholder}
- spellCheck='false'
- />
- </div>
- {ldapPasswordError}
- <p>
- <FormattedMessage
- id='claim.ldap_to_email.enterPwd'
- defaultMessage='New email login password:'
- />
- </p>
- <div className={passwordClass}>
- <input
- type='password'
- className='form-control'
- name='password'
- ref='password'
- placeholder={Utils.localizeMessage('claim.ldap_to_email.pwd', 'Password')}
- spellCheck='false'
- />
- </div>
- {passwordError}
- <div className={confimClass}>
- <input
- type='password'
- className='form-control'
- name='passwordconfirm'
- ref='passwordconfirm'
- placeholder={Utils.localizeMessage('claim.ldap_to_email.confirm', 'Confirm Password')}
- spellCheck='false'
- />
- </div>
- {confirmError}
- <button
- type='submit'
- className='btn btn-primary'
- >
- <FormattedMessage
- id='claim.ldap_to_email.switchTo'
- defaultMessage='Switch account to email/password'
- />
- </button>
- {serverError}
- </form>
- );
- }
-
- return (
- <div>
- <h3>
- <FormattedMessage
- id='claim.ldap_to_email.title'
- defaultMessage='Switch AD/LDAP Account to Email/Password'
- />
- </h3>
- {content}
- </div>
- );
- }
-}
-
-LDAPToEmail.defaultProps = {
-};
-LDAPToEmail.propTypes = {
- email: PropTypes.string
-};
diff --git a/webapp/components/claim/components/oauth_to_email.jsx b/webapp/components/claim/components/oauth_to_email.jsx
deleted file mode 100644
index 9944b3306..000000000
--- a/webapp/components/claim/components/oauth_to_email.jsx
+++ /dev/null
@@ -1,147 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import ReactDOM from 'react-dom';
-import {FormattedMessage} from 'react-intl';
-
-import {oauthToEmail} from 'actions/admin_actions.jsx';
-
-export default class OAuthToEmail extends React.Component {
- constructor(props) {
- super(props);
-
- this.submit = this.submit.bind(this);
-
- this.state = {};
- }
-
- submit(e) {
- e.preventDefault();
- const state = {};
-
- const password = ReactDOM.findDOMNode(this.refs.password).value;
- if (!password) {
- state.error = Utils.localizeMessage('claim.oauth_to_email.enterPwd', 'Please enter a password.');
- this.setState(state);
- return;
- }
-
- const passwordErr = Utils.isValidPassword(password);
- if (passwordErr !== '') {
- this.setState({
- error: passwordErr
- });
- return;
- }
-
- const confirmPassword = ReactDOM.findDOMNode(this.refs.passwordconfirm).value;
- if (!confirmPassword || password !== confirmPassword) {
- state.error = Utils.localizeMessage('claim.oauth_to_email.pwdNotMatch', 'Password do not match.');
- this.setState(state);
- return;
- }
-
- state.error = null;
- this.setState(state);
-
- oauthToEmail(
- this.props.currentType,
- this.props.email,
- password,
- null,
- (err) => {
- this.setState({error: err.message});
- }
- );
- }
- render() {
- var error = null;
- if (this.state.error) {
- error = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>;
- }
-
- var formClass = 'form-group';
- if (error) {
- formClass += ' has-error';
- }
-
- const uiType = `${(this.props.currentType === Constants.SAML_SERVICE ? Constants.SAML_SERVICE.toUpperCase() : Utils.toTitleCase(this.props.currentType))} SSO`;
-
- return (
- <div>
- <h3>
- <FormattedMessage
- id='claim.oauth_to_email.title'
- defaultMessage='Switch {type} Account to Email'
- values={{
- type: uiType
- }}
- />
- </h3>
- <form onSubmit={this.submit}>
- <p>
- <FormattedMessage
- id='claim.oauth_to_email.description'
- defaultMessage='Upon changing your account type, you will only be able to login with your email and password.'
- />
- </p>
- <p>
- <FormattedMessage
- id='claim.oauth_to_email.enterNewPwd'
- defaultMessage='Enter a new password for your {site} email account'
- values={{
- site: global.window.mm_config.SiteName
- }}
- />
- </p>
- <div className={formClass}>
- <input
- type='password'
- className='form-control'
- name='password'
- ref='password'
- placeholder={Utils.localizeMessage('claim.oauth_to_email.newPwd', 'New Password')}
- spellCheck='false'
- />
- </div>
- <div className={formClass}>
- <input
- type='password'
- className='form-control'
- name='passwordconfirm'
- ref='passwordconfirm'
- placeholder={Utils.localizeMessage('claim.oauth_to_email.confirm', 'Confirm Password')}
- spellCheck='false'
- />
- </div>
- {error}
- <button
- type='submit'
- className='btn btn-primary'
- >
- <FormattedMessage
- id='claim.oauth_to_email.switchTo'
- defaultMessage='Switch {type} to email and password'
- values={{
- type: uiType
- }}
- />
- </button>
- </form>
- </div>
- );
- }
-}
-
-OAuthToEmail.defaultProps = {
-};
-OAuthToEmail.propTypes = {
- currentType: PropTypes.string,
- email: PropTypes.string
-};
diff --git a/webapp/components/code_preview.jsx b/webapp/components/code_preview.jsx
deleted file mode 100644
index fc2d7314e..000000000
--- a/webapp/components/code_preview.jsx
+++ /dev/null
@@ -1,142 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import * as SyntaxHighlighting from 'utils/syntax_highlighting.jsx';
-import Constants from 'utils/constants.jsx';
-
-import FileInfoPreview from './file_info_preview.jsx';
-
-import loadingGif from 'images/load.gif';
-
-export default class CodePreview extends React.Component {
- constructor(props) {
- super(props);
-
- this.updateStateFromProps = this.updateStateFromProps.bind(this);
- this.handleReceivedError = this.handleReceivedError.bind(this);
- this.handleReceivedCode = this.handleReceivedCode.bind(this);
-
- this.state = {
- code: '',
- lang: '',
- loading: true,
- success: true
- };
- }
-
- componentDidMount() {
- this.updateStateFromProps(this.props);
- }
-
- componentWillReceiveProps(nextProps) {
- if (this.props.fileUrl !== nextProps.fileUrl) {
- this.updateStateFromProps(nextProps);
- }
- }
-
- updateStateFromProps(props) {
- const usedLanguage = SyntaxHighlighting.getLanguageFromFileExtension(props.fileInfo.extension);
-
- if (!usedLanguage || props.fileInfo.size > Constants.CODE_PREVIEW_MAX_FILE_SIZE) {
- this.setState({code: '', lang: '', loading: false, success: false});
- return;
- }
-
- this.setState({code: '', lang: usedLanguage, loading: true});
-
- $.ajax({
- async: true,
- url: props.fileUrl,
- type: 'GET',
- dataType: 'text',
- error: this.handleReceivedError,
- success: this.handleReceivedCode
- });
- }
-
- handleReceivedCode(data) {
- let code = data;
- if (data.nodeName === '#document') {
- code = new XMLSerializer().serializeToString(data);
- }
- this.setState({
- code,
- loading: false,
- success: true
- });
- }
-
- handleReceivedError() {
- this.setState({loading: false, success: false});
- }
-
- static supports(fileInfo) {
- return Boolean(SyntaxHighlighting.getLanguageFromFileExtension(fileInfo.extension));
- }
-
- render() {
- if (this.state.loading) {
- return (
- <div className='view-image__loading'>
- <img
- className='loader-image'
- src={loadingGif}
- />
- </div>
- );
- }
-
- if (!this.state.success) {
- return (
- <FileInfoPreview
- fileInfo={this.props.fileInfo}
- fileUrl={this.props.fileUrl}
- />
- );
- }
-
- // add line numbers when viewing a code file preview
- const lines = this.state.code.match(/\r\n|\r|\n|$/g).length;
- let strlines = '';
- for (let i = 1; i <= lines; i++) {
- if (strlines) {
- strlines += '\n' + i;
- } else {
- strlines += i;
- }
- }
-
- const language = SyntaxHighlighting.getLanguageName(this.state.lang);
-
- const highlighted = SyntaxHighlighting.highlight(this.state.lang, this.state.code);
-
- return (
- <div className='post-code'>
- <span className='post-code__language'>
- {`${this.props.fileInfo.name} - ${language}`}
- </span>
- <div className='post-code__container'>
- <code className='hljs'>
- <table>
- <tbody>
- <tr>
- <td className='post-code__lineno'>{strlines}</td>
- <td dangerouslySetInnerHTML={{__html: highlighted}}/>
- </tr>
- </tbody>
- </table>
- </code>
- </div>
- </div>
- );
- }
-}
-
-CodePreview.propTypes = {
- fileInfo: PropTypes.object.isRequired,
- fileUrl: PropTypes.string.isRequired
-};
diff --git a/webapp/components/common/comment_icon.jsx b/webapp/components/common/comment_icon.jsx
deleted file mode 100644
index e3781a4be..000000000
--- a/webapp/components/common/comment_icon.jsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import Constants from 'utils/constants.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-export default function CommentIcon(props) {
- let commentCountSpan = '';
- let iconStyle = 'comment-icon__container';
- if (props.commentCount > 0) {
- iconStyle += ' icon--show';
- commentCountSpan = (
- <span className='comment-count'>
- {props.commentCount}
- </span>
- );
- } else if (props.searchStyle !== '') {
- iconStyle = iconStyle + ' ' + props.searchStyle;
- }
-
- let selectorId = props.idPrefix;
- if (props.idCount > -1) {
- selectorId += props.idCount;
- }
-
- const id = Utils.createSafeId(props.idPrefix + '_' + props.id);
-
- return (
- <a
- id={id}
- href='#'
- className={iconStyle + ' ' + selectorId}
- onClick={props.handleCommentClick}
- >
- <span
- className='comment-icon'
- dangerouslySetInnerHTML={{__html: Constants.REPLY_ICON}}
- />
- {commentCountSpan}
- </a>
- );
-}
-
-CommentIcon.propTypes = {
- idPrefix: PropTypes.string.isRequired,
- idCount: PropTypes.number,
- handleCommentClick: PropTypes.func.isRequired,
- searchStyle: PropTypes.string,
- commentCount: PropTypes.number,
- id: PropTypes.string
-};
-
-CommentIcon.defaultProps = {
- idCount: -1,
- searchStyle: '',
- commentCount: 0,
- id: ''
-};
diff --git a/webapp/components/confirm_modal.jsx b/webapp/components/confirm_modal.jsx
deleted file mode 100644
index 72f341efb..000000000
--- a/webapp/components/confirm_modal.jsx
+++ /dev/null
@@ -1,164 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import {Modal} from 'react-bootstrap';
-import PropTypes from 'prop-types';
-import {FormattedMessage} from 'react-intl';
-
-export default class ConfirmModal extends React.Component {
- static propTypes = {
-
- /*
- * Set to show modal
- */
- show: PropTypes.bool.isRequired,
-
- /*
- * Title to use for the modal
- */
- title: PropTypes.node,
-
- /*
- * Message to display in the body of the modal
- */
- message: PropTypes.node,
-
- /*
- * The CSS class to apply to the confirm button
- */
- confirmButtonClass: PropTypes.string,
-
- /*
- * Text/jsx element on the confirm button
- */
- confirmButtonText: PropTypes.node,
-
- /*
- * Text/jsx element on the cancel button
- */
- cancelButtonText: PropTypes.node,
-
- /*
- * Set to show checkbox
- */
- showCheckbox: PropTypes.bool,
-
- /*
- * Text/jsx element to display with the checkbox
- */
- checkboxText: PropTypes.node,
-
- /*
- * Function called when the confirm button or ENTER is pressed. Passes `true` if the checkbox is checked
- */
- onConfirm: PropTypes.func.isRequired,
-
- /*
- * Function called when the cancel button is pressed or the modal is hidden. Passes `true` if the checkbox is checked
- */
- onCancel: PropTypes.func.isRequired
- }
-
- static defaultProps = {
- title: '',
- message: '',
- confirmButtonClass: 'btn btn-primary',
- confirmButtonText: ''
- }
-
- componentDidMount() {
- if (this.props.show) {
- document.addEventListener('keypress', this.handleKeypress);
- }
- }
-
- componentWillUnmount() {
- document.removeEventListener('keypress', this.handleKeypress);
- }
-
- componentWillReceiveProps(nextProps) {
- if (this.props.show && !nextProps.show) {
- document.removeEventListener('keypress', this.handleKeypress);
- } else if (!this.props.show && nextProps.show) {
- document.addEventListener('keypress', this.handleKeypress);
- }
- }
-
- handleKeypress = (e) => {
- if (e.key === 'Enter' && this.props.show) {
- this.handleConfirm();
- }
- }
-
- handleConfirm = () => {
- const checked = this.refs.checkbox ? this.refs.checkbox.checked : false;
- this.props.onConfirm(checked);
- }
-
- handleCancel = () => {
- const checked = this.refs.checkbox ? this.refs.checkbox.checked : false;
- this.props.onCancel(checked);
- }
-
- render() {
- let checkbox;
- if (this.props.showCheckbox) {
- checkbox = (
- <div className='checkbox text-right margin-bottom--none'>
- <label>
- <input
- ref='checkbox'
- type='checkbox'
- />
- {this.props.checkboxText}
- </label>
- </div>
- );
- }
-
- let cancelText;
- if (this.props.cancelButtonText) {
- cancelText = this.props.cancelButtonText;
- } else {
- cancelText = (
- <FormattedMessage
- id='confirm_modal.cancel'
- defaultMessage='Cancel'
- />
- );
- }
-
- return (
- <Modal
- className='modal-confirm'
- show={this.props.show}
- onHide={this.props.onCancel}
- >
- <Modal.Header closeButton={false}>
- <Modal.Title>{this.props.title}</Modal.Title>
- </Modal.Header>
- <Modal.Body>
- {this.props.message}
- {checkbox}
- </Modal.Body>
- <Modal.Footer>
- <button
- type='button'
- className='btn btn-default'
- onClick={this.handleCancel}
- >
- {cancelText}
- </button>
- <button
- type='button'
- className={this.props.confirmButtonClass}
- onClick={this.handleConfirm}
- >
- {this.props.confirmButtonText}
- </button>
- </Modal.Footer>
- </Modal>
- );
- }
-}
diff --git a/webapp/components/create_comment/create_comment.jsx b/webapp/components/create_comment/create_comment.jsx
deleted file mode 100644
index 18648d0e9..000000000
--- a/webapp/components/create_comment/create_comment.jsx
+++ /dev/null
@@ -1,629 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import ReactDOM from 'react-dom';
-import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
-import * as ChannelActions from 'actions/channel_actions.jsx';
-import EmojiStore from 'stores/emoji_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-import PostDeletedModal from 'components/post_deleted_modal.jsx';
-import PostStore from 'stores/post_store.jsx';
-import PreferenceStore from 'stores/preference_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-import MessageHistoryStore from 'stores/message_history_store.jsx';
-import Textbox from 'components/textbox.jsx';
-import MsgTyping from 'components/msg_typing.jsx';
-import FileUpload from 'components/file_upload.jsx';
-import FilePreview from 'components/file_preview.jsx';
-import EmojiPickerOverlay from 'components/emoji_picker/emoji_picker_overlay.jsx';
-import * as EmojiPicker from 'components/emoji_picker/emoji_picker.jsx';
-import * as Utils from 'utils/utils.jsx';
-import * as UserAgent from 'utils/user_agent.jsx';
-import * as GlobalActions from 'actions/global_actions.jsx';
-import * as PostActions from 'actions/post_actions.jsx';
-
-import Constants from 'utils/constants.jsx';
-
-import {FormattedMessage} from 'react-intl';
-import {browserHistory} from 'react-router/es6';
-
-const ActionTypes = Constants.ActionTypes;
-const KeyCodes = Constants.KeyCodes;
-
-import {REACTION_PATTERN} from 'components/create_post.jsx';
-import PropTypes from 'prop-types';
-import React from 'react';
-
-export default class CreateComment extends React.Component {
- static propTypes = {
- channelId: PropTypes.string.isRequired,
- rootId: PropTypes.string.isRequired,
- latestPostId: PropTypes.string,
- getSidebarBody: PropTypes.func,
- createPostErrorId: PropTypes.string
- }
-
- constructor(props) {
- super(props);
-
- this.lastTime = 0;
-
- PostStore.clearCommentDraftUploads();
- MessageHistoryStore.resetHistoryIndex('comment');
-
- const draft = PostStore.getCommentDraft(this.props.rootId);
- const enableAddButton = this.handleEnableAddButton(draft.message, draft.fileInfos);
- this.state = {
- message: draft.message,
- uploadsInProgress: draft.uploadsInProgress,
- fileInfos: draft.fileInfos,
- submitting: false,
- ctrlSend: PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter'),
- showPostDeletedModal: false,
- enableAddButton,
- showEmojiPicker: false
- };
-
- this.lastBlurAt = 0;
- }
-
- toggleEmojiPicker = () => {
- this.setState({showEmojiPicker: !this.state.showEmojiPicker});
- }
-
- hideEmojiPicker = () => {
- this.setState({showEmojiPicker: false});
- }
-
- handleEmojiClick = (emoji) => {
- const emojiAlias = emoji.name || emoji.aliases[0];
-
- if (!emojiAlias) {
- //Oops.. There went something wrong
- return;
- }
-
- if (this.state.message === '') {
- this.setState({message: ':' + emojiAlias + ': '});
- } else {
- // Check whether there is already a blank at the end of the current message
- let newMessage;
- if ((/\s+$/).test(this.state.message)) {
- newMessage = this.state.message + ':' + emojiAlias + ': ';
- } else {
- newMessage = this.state.message + ' :' + emojiAlias + ': ';
- }
-
- this.setState({message: newMessage});
- }
-
- this.setState({showEmojiPicker: false});
-
- this.focusTextbox();
- }
-
- componentDidMount() {
- PreferenceStore.addChangeListener(this.onPreferenceChange);
-
- this.focusTextbox();
- }
-
- componentWillUnmount() {
- PreferenceStore.removeChangeListener(this.onPreferenceChange);
- }
-
- onPreferenceChange = () => {
- this.setState({
- ctrlSend: PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter')
- });
- }
-
- componentDidUpdate(prevProps, prevState) {
- if (prevState.uploadsInProgress < this.state.uploadsInProgress) {
- $('.post-right__scroll').scrollTop($('.post-right__scroll')[0].scrollHeight);
- }
-
- if (prevProps.rootId !== this.props.rootId) {
- this.focusTextbox();
- }
- }
-
- handlePostError = (postError) => {
- this.setState({postError});
- }
-
- handleSubmit = (e) => {
- e.preventDefault();
-
- if (this.state.uploadsInProgress.length > 0 || this.state.submitting) {
- return;
- }
-
- const message = this.state.message;
-
- if (this.state.postError) {
- this.setState({errorClass: 'animation--highlight'});
- setTimeout(() => {
- this.setState({errorClass: null});
- }, Constants.ANIMATION_TIMEOUT);
- return;
- }
-
- MessageHistoryStore.storeMessageInHistory(message);
- if (message.trim().length === 0 && this.state.fileInfos.length === 0) {
- return;
- }
-
- const isReaction = REACTION_PATTERN.exec(message);
- if (isReaction && EmojiStore.has(isReaction[2])) {
- this.handleSubmitReaction(isReaction);
- } else if (message.indexOf('/') === 0) {
- this.handleSubmitCommand(message);
- } else {
- this.handleSubmitPost(message);
- }
-
- this.setState({
- message: '',
- submitting: false,
- postError: null,
- fileInfos: [],
- serverError: null,
- enableAddButton: false
- });
-
- const fasterThanHumanWillClick = 150;
- const forceFocus = (Date.now() - this.lastBlurAt < fasterThanHumanWillClick);
- this.focusTextbox(forceFocus);
- }
-
- handleSubmitCommand = (message) => {
- PostStore.storeCommentDraft(this.props.rootId, null);
- this.setState({
- message: '',
- postError: null,
- fileInfos: [],
- enableAddButton: false
- });
-
- const args = {};
- args.channel_id = this.props.channelId;
- args.team_id = TeamStore.getCurrentId();
- args.root_id = this.props.rootId;
- args.parent_id = this.props.rootId;
- ChannelActions.executeCommand(
- message,
- args,
- (data) => {
- this.setState({submitting: false});
- if (data.goto_location && data.goto_location.length > 0) {
- if (data.goto_location.startsWith('/') || data.goto_location.includes(window.location.hostname)) {
- browserHistory.push(data.goto_location);
- } else {
- window.open(data.goto_location);
- }
- }
- },
- (err) => {
- if (err.sendMessage) {
- this.handleSubmitPost(message);
- } else {
- const state = {};
- state.serverError = err.message;
- state.submitting = false;
- this.setState(state);
- }
- }
- );
- }
-
- handleSubmitPost = (message) => {
- const userId = UserStore.getCurrentId();
- const time = Utils.getTimestamp();
-
- const post = {};
- post.file_ids = [];
- post.message = message;
- post.channel_id = this.props.channelId;
- post.root_id = this.props.rootId;
- post.parent_id = this.props.rootId;
- post.pending_post_id = `${userId}:${time}`;
- post.user_id = userId;
- post.create_at = time;
-
- GlobalActions.emitUserCommentedEvent(post);
-
- PostActions.createPost(post, this.state.fileInfos);
-
- this.setState({
- message: '',
- submitting: false,
- postError: null,
- fileInfos: [],
- serverError: null,
- enableAddButton: false
- });
-
- const fasterThanHumanWillClick = 150;
- const forceFocus = (Date.now() - this.state.lastBlurAt < fasterThanHumanWillClick);
- this.focusTextbox(forceFocus);
- }
-
- handleSubmitReaction = (isReaction) => {
- const action = isReaction[1];
-
- const emojiName = isReaction[2];
- const postId = this.props.latestPostId;
-
- if (action === '+') {
- PostActions.addReaction(this.props.channelId, postId, emojiName);
- } else if (action === '-') {
- PostActions.removeReaction(this.props.channelId, postId, emojiName);
- }
-
- PostStore.storeCommentDraft(this.props.rootId, null);
- }
-
- commentMsgKeyPress = (e) => {
- if (!UserAgent.isMobile() && ((this.state.ctrlSend && e.ctrlKey) || !this.state.ctrlSend)) {
- if (e.which === KeyCodes.ENTER && !e.shiftKey && !e.altKey) {
- e.preventDefault();
- ReactDOM.findDOMNode(this.refs.textbox).blur();
- this.handleSubmit(e);
- }
- }
-
- GlobalActions.emitLocalUserTypingEvent(this.props.channelId, this.props.rootId);
- }
-
- handleChange = (e) => {
- const message = e.target.value;
-
- const draft = PostStore.getCommentDraft(this.props.rootId);
- draft.message = message;
- PostStore.storeCommentDraft(this.props.rootId, draft);
-
- $('.post-right__scroll').parent().scrollTop($('.post-right__scroll')[0].scrollHeight);
-
- const enableAddButton = this.handleEnableAddButton(message, this.state.fileInfos);
-
- this.setState({
- message,
- enableAddButton
- });
- }
-
- handleKeyDown = (e) => {
- if (this.state.ctrlSend && e.keyCode === KeyCodes.ENTER && e.ctrlKey === true) {
- this.commentMsgKeyPress(e);
- return;
- }
-
- if (!e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey && e.keyCode === KeyCodes.UP && this.state.message === '') {
- e.preventDefault();
-
- const lastPost = PostStore.getCurrentUsersLatestPost(this.props.channelId, this.props.rootId);
- if (!lastPost) {
- return;
- }
-
- AppDispatcher.handleViewAction({
- type: ActionTypes.RECEIVED_EDIT_POST,
- refocusId: '#reply_textbox',
- title: Utils.localizeMessage('create_comment.commentTitle', 'Comment'),
- message: lastPost.message,
- postId: lastPost.id,
- channelId: lastPost.channel_id,
- comments: PostStore.getCommentCount(lastPost)
- });
- }
-
- if ((e.ctrlKey || e.metaKey) && !e.altKey && !e.shiftKey && (e.keyCode === Constants.KeyCodes.UP || e.keyCode === Constants.KeyCodes.DOWN)) {
- const lastMessage = MessageHistoryStore.nextMessageInHistory(e.keyCode, this.state.message, 'comment');
- if (lastMessage !== null) {
- e.preventDefault();
- this.setState({
- message: lastMessage,
- enableAddButton: true
- });
- }
- }
- }
-
- handleFileUploadChange = () => {
- this.focusTextbox();
- }
-
- handleUploadStart = (clientIds) => {
- const draft = PostStore.getCommentDraft(this.props.rootId);
-
- draft.uploadsInProgress = draft.uploadsInProgress.concat(clientIds);
- PostStore.storeCommentDraft(this.props.rootId, draft);
-
- this.setState({uploadsInProgress: draft.uploadsInProgress});
-
- // this is a bit redundant with the code that sets focus when the file input is clicked,
- // but this also resets the focus after a drag and drop
- this.focusTextbox();
- }
-
- handleFileUploadComplete = (fileInfos, clientIds) => {
- const draft = PostStore.getCommentDraft(this.props.rootId);
-
- // remove each finished file from uploads
- for (let i = 0; i < clientIds.length; i++) {
- const index = draft.uploadsInProgress.indexOf(clientIds[i]);
-
- if (index !== -1) {
- draft.uploadsInProgress.splice(index, 1);
- }
- }
-
- draft.fileInfos = draft.fileInfos.concat(fileInfos);
- PostStore.storeCommentDraft(this.props.rootId, draft);
-
- // Focus on preview if needed/possible - if user has switched teams since starting the file upload,
- // the preview will be undefined and the switch will fail
- if (typeof this.refs.preview != 'undefined' && this.refs.preview) {
- this.refs.preview.refs.container.scrollIntoView();
- }
-
- const enableAddButton = this.handleEnableAddButton(draft.message, draft.fileInfos);
-
- this.setState({
- uploadsInProgress: draft.uploadsInProgress,
- fileInfos: draft.fileInfos,
- enableAddButton
- });
- }
-
- handleUploadError = (err, clientId) => {
- if (clientId === -1) {
- this.setState({serverError: err});
- } else {
- const draft = PostStore.getCommentDraft(this.props.rootId);
-
- const index = draft.uploadsInProgress.indexOf(clientId);
- if (index !== -1) {
- draft.uploadsInProgress.splice(index, 1);
- }
-
- PostStore.storeCommentDraft(this.props.rootId, draft);
-
- this.setState({uploadsInProgress: draft.uploadsInProgress, serverError: err});
- }
- }
-
- removePreview = (id) => {
- const fileInfos = this.state.fileInfos;
- const uploadsInProgress = this.state.uploadsInProgress;
-
- // Clear previous errors
- this.handleUploadError(null);
-
- // id can either be the id of an uploaded file or the client id of an in progress upload
- let index = fileInfos.findIndex((info) => info.id === id);
- if (index === -1) {
- index = uploadsInProgress.indexOf(id);
-
- if (index !== -1) {
- uploadsInProgress.splice(index, 1);
- this.refs.fileUpload.getWrappedInstance().cancelUpload(id);
- }
- } else {
- fileInfos.splice(index, 1);
- }
-
- const draft = PostStore.getCommentDraft(this.props.rootId);
- draft.fileInfos = fileInfos;
- draft.uploadsInProgress = uploadsInProgress;
- PostStore.storeCommentDraft(this.props.rootId, draft);
-
- this.setState({fileInfos, uploadsInProgress});
-
- this.handleFileUploadChange();
- }
-
- componentWillReceiveProps(newProps) {
- if (newProps.rootId !== this.props.rootId) {
- const draft = PostStore.getCommentDraft(newProps.rootId);
- const enableAddButton = this.handleEnableAddButton(draft.message, draft.fileInfos);
- this.setState({
- message: draft.message,
- uploadsInProgress: draft.uploadsInProgress,
- fileInfos: draft.fileInfos,
- enableAddButton
- });
- }
-
- if (newProps.createPostErrorId === 'api.post.create_post.root_id.app_error' && newProps.createPostErrorId !== this.props.createPostErrorId) {
- this.showPostDeletedModal();
- }
- }
-
- getFileCount = () => {
- return this.state.fileInfos.length + this.state.uploadsInProgress.length;
- }
-
- getFileUploadTarget = () => {
- return this.refs.textbox;
- }
-
- getCreateCommentControls = () => {
- return this.refs.createCommentControls;
- }
-
- focusTextbox = (keepFocus = false) => {
- if (keepFocus || !UserAgent.isMobile()) {
- this.refs.textbox.focus();
- }
- }
-
- showPostDeletedModal = () => {
- this.setState({
- showPostDeletedModal: true
- });
- }
-
- hidePostDeletedModal = () => {
- this.setState({
- showPostDeletedModal: false
- });
- }
-
- handleBlur = () => {
- this.lastBlurAt = Date.now();
- }
-
- handleEnableAddButton = (message, fileInfos) => {
- return message.trim().length !== 0 || fileInfos.length !== 0;
- }
-
- render() {
- let serverError = null;
- if (this.state.serverError) {
- serverError = (
- <div className='form-group has-error'>
- <label className='control-label'>{this.state.serverError}</label>
- </div>
- );
- }
-
- let postError = null;
- if (this.state.postError) {
- const postErrorClass = 'post-error' + (this.state.errorClass ? (' ' + this.state.errorClass) : '');
- postError = <label className={postErrorClass}>{this.state.postError}</label>;
- }
-
- let preview = null;
- if (this.state.fileInfos.length > 0 || this.state.uploadsInProgress.length > 0) {
- preview = (
- <FilePreview
- fileInfos={this.state.fileInfos}
- onRemove={this.removePreview}
- uploadsInProgress={this.state.uploadsInProgress}
- ref='preview'
- />
- );
- }
-
- let uploadsInProgressText = null;
- if (this.state.uploadsInProgress.length > 0) {
- uploadsInProgressText = (
- <span className='pull-right post-right-comments-upload-in-progress'>
- {this.state.uploadsInProgress.length === 1 ? (
- <FormattedMessage
- id='create_comment.file'
- defaultMessage='File uploading'
- />
- ) : (
- <FormattedMessage
- id='create_comment.files'
- defaultMessage='Files uploading'
- />
- )}
- </span>
- );
- }
-
- let addButtonClass = 'btn btn-primary comment-btn pull-right';
- if (!this.state.enableAddButton) {
- addButtonClass += ' disabled';
- }
-
- const fileUpload = (
- <FileUpload
- ref='fileUpload'
- getFileCount={this.getFileCount}
- getTarget={this.getFileUploadTarget}
- onFileUploadChange={this.handleFileUploadChange}
- onUploadStart={this.handleUploadStart}
- onFileUpload={this.handleFileUploadComplete}
- onUploadError={this.handleUploadError}
- postType='comment'
- channelId={this.props.channelId}
- />
- );
-
- let emojiPicker = null;
- if (window.mm_config.EnableEmojiPicker === 'true') {
- emojiPicker = (
- <span className='emoji-picker__container'>
- <EmojiPickerOverlay
- show={this.state.showEmojiPicker}
- container={this.props.getSidebarBody}
- target={this.getCreateCommentControls}
- onHide={this.hideEmojiPicker}
- onEmojiClick={this.handleEmojiClick}
- rightOffset={15}
- topOffset={55}
- />
- <span
- className='icon icon--emoji emoji-rhs'
- dangerouslySetInnerHTML={{__html: Constants.EMOJI_ICON_SVG}}
- onClick={this.toggleEmojiPicker}
- onMouseOver={EmojiPicker.beginPreloading}
- />
- </span>
- );
- }
-
- return (
- <form onSubmit={this.handleSubmit}>
- <div className='post-create'>
- <div
- id={this.props.rootId}
- className='post-create-body comment-create-body'
- >
- <div className='post-body__cell'>
- <Textbox
- onChange={this.handleChange}
- onKeyPress={this.commentMsgKeyPress}
- onKeyDown={this.handleKeyDown}
- handlePostError={this.handlePostError}
- value={this.state.message}
- onBlur={this.handleBlur}
- createMessage={Utils.localizeMessage('create_comment.addComment', 'Add a comment...')}
- emojiEnabled={window.mm_config.EnableEmojiPicker === 'true'}
- initialText=''
- channelId={this.props.channelId}
- isRHS={true}
- popoverMentionKeyClick={true}
- id='reply_textbox'
- ref='textbox'
- />
- <span
- ref='createCommentControls'
- className='post-body__actions'
- >
- {fileUpload}
- {emojiPicker}
- </span>
- </div>
- </div>
- <MsgTyping
- channelId={this.props.channelId}
- parentId={this.props.rootId}
- />
- <div className='post-create-footer'>
- <input
- type='button'
- className={addButtonClass}
- value={Utils.localizeMessage('create_comment.comment', 'Add Comment')}
- onClick={this.handleSubmit}
- />
- {uploadsInProgressText}
- {postError}
- {preview}
- {serverError}
- </div>
- </div>
- <PostDeletedModal
- show={this.state.showPostDeletedModal}
- onHide={this.hidePostDeletedModal}
- />
- </form>
- );
- }
-}
diff --git a/webapp/components/create_comment/index.js b/webapp/components/create_comment/index.js
deleted file mode 100644
index 50017461a..000000000
--- a/webapp/components/create_comment/index.js
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-
-import CreateComment from './create_comment.jsx';
-
-function mapStateToProps(state, ownProps) {
- const err = state.requests.posts.createPost.error || {};
- return {
- ...ownProps,
- createPostErrorId: err.server_error_id
- };
-}
-
-export default connect(mapStateToProps)(CreateComment);
diff --git a/webapp/components/create_post.jsx b/webapp/components/create_post.jsx
deleted file mode 100644
index 997ba8065..000000000
--- a/webapp/components/create_post.jsx
+++ /dev/null
@@ -1,814 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ReactDOM from 'react-dom';
-import MsgTyping from './msg_typing.jsx';
-import Textbox from './textbox.jsx';
-import FileUpload from './file_upload.jsx';
-import FilePreview from './file_preview.jsx';
-import PostDeletedModal from './post_deleted_modal.jsx';
-import TutorialTip from './tutorial/tutorial_tip.jsx';
-import EmojiPickerOverlay from 'components/emoji_picker/emoji_picker_overlay.jsx';
-import * as EmojiPicker from 'components/emoji_picker/emoji_picker.jsx';
-
-import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
-import * as GlobalActions from 'actions/global_actions.jsx';
-import * as Utils from 'utils/utils.jsx';
-import * as PostUtils from 'utils/post_utils.jsx';
-import * as UserAgent from 'utils/user_agent.jsx';
-import * as ChannelActions from 'actions/channel_actions.jsx';
-import * as PostActions from 'actions/post_actions.jsx';
-
-import ChannelStore from 'stores/channel_store.jsx';
-import EmojiStore from 'stores/emoji_store.jsx';
-import PostStore from 'stores/post_store.jsx';
-import MessageHistoryStore from 'stores/message_history_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-import PreferenceStore from 'stores/preference_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-import ConfirmModal from './confirm_modal.jsx';
-
-import Constants from 'utils/constants.jsx';
-import * as FileUtils from 'utils/file_utils';
-
-import {FormattedHTMLMessage, FormattedMessage} from 'react-intl';
-import {browserHistory} from 'react-router/es6';
-
-const Preferences = Constants.Preferences;
-const TutorialSteps = Constants.TutorialSteps;
-const ActionTypes = Constants.ActionTypes;
-const KeyCodes = Constants.KeyCodes;
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-export const REACTION_PATTERN = /^(\+|-):([^:\s]+):\s*$/;
-
-export default class CreatePost extends React.Component {
- constructor(props) {
- super(props);
-
- this.lastTime = 0;
-
- this.doSubmit = this.doSubmit.bind(this);
- this.handleSubmit = this.handleSubmit.bind(this);
- this.postMsgKeyPress = this.postMsgKeyPress.bind(this);
- this.handleChange = this.handleChange.bind(this);
- this.handleFileUploadChange = this.handleFileUploadChange.bind(this);
- this.handleUploadStart = this.handleUploadStart.bind(this);
- this.handleFileUploadComplete = this.handleFileUploadComplete.bind(this);
- this.handleUploadError = this.handleUploadError.bind(this);
- this.removePreview = this.removePreview.bind(this);
- this.onChange = this.onChange.bind(this);
- this.onPreferenceChange = this.onPreferenceChange.bind(this);
- this.getFileCount = this.getFileCount.bind(this);
- this.getFileUploadTarget = this.getFileUploadTarget.bind(this);
- this.getCreatePostControls = this.getCreatePostControls.bind(this);
- this.handleKeyDown = this.handleKeyDown.bind(this);
- this.handleBlur = this.handleBlur.bind(this);
- this.sendMessage = this.sendMessage.bind(this);
- this.focusTextbox = this.focusTextbox.bind(this);
- this.showPostDeletedModal = this.showPostDeletedModal.bind(this);
- this.hidePostDeletedModal = this.hidePostDeletedModal.bind(this);
- this.showShortcuts = this.showShortcuts.bind(this);
- this.handleEmojiClick = this.handleEmojiClick.bind(this);
- this.handlePostError = this.handlePostError.bind(this);
- this.hideNotifyAllModal = this.hideNotifyAllModal.bind(this);
- this.showNotifyAllModal = this.showNotifyAllModal.bind(this);
- this.handleNotifyModalCancel = this.handleNotifyModalCancel.bind(this);
- this.handleNotifyAllConfirmation = this.handleNotifyAllConfirmation.bind(this);
-
- PostStore.clearDraftUploads();
-
- const channel = ChannelStore.getCurrent();
- const channelId = channel.id;
- const draft = PostStore.getDraft(channelId);
- const stats = ChannelStore.getCurrentStats();
- const members = stats.member_count - 1;
-
- this.state = {
- channelId,
- channel,
- message: draft.message,
- uploadsInProgress: draft.uploadsInProgress,
- fileInfos: draft.fileInfos,
- submitting: false,
- ctrlSend: PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter'),
- fullWidthTextBox: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_FULL_SCREEN,
- showTutorialTip: false,
- showPostDeletedModal: false,
- enableSendButton: false,
- showEmojiPicker: false,
- showConfirmModal: false,
- totalMembers: members
- };
-
- this.lastBlurAt = 0;
- }
-
- handlePostError(postError) {
- this.setState({postError});
- }
-
- toggleEmojiPicker = () => {
- this.setState({showEmojiPicker: !this.state.showEmojiPicker});
- }
-
- hideEmojiPicker = () => {
- this.setState({showEmojiPicker: false});
- }
-
- doSubmit(e) {
- if (e) {
- e.preventDefault();
- }
-
- if (this.state.uploadsInProgress.length > 0 || this.state.submitting) {
- return;
- }
-
- const post = {};
- post.file_ids = [];
- post.message = this.state.message;
-
- if (post.message.trim().length === 0 && this.state.fileInfos.length === 0) {
- return;
- }
-
- if (this.state.postError) {
- this.setState({errorClass: 'animation--highlight'});
- setTimeout(() => {
- this.setState({errorClass: null});
- }, Constants.ANIMATION_TIMEOUT);
- return;
- }
-
- MessageHistoryStore.storeMessageInHistory(this.state.message);
-
- this.setState({submitting: true, serverError: null});
-
- const isReaction = REACTION_PATTERN.exec(post.message);
- if (post.message.indexOf('/') === 0) {
- PostStore.storeDraft(this.state.channelId, null);
- this.setState({message: '', postError: null, fileInfos: [], enableSendButton: false});
-
- const args = {};
- args.channel_id = this.state.channelId;
- args.team_id = TeamStore.getCurrentId();
- ChannelActions.executeCommand(
- post.message,
- args,
- (data) => {
- this.setState({submitting: false});
-
- if (post.message.trim() === '/logout') {
- GlobalActions.clientLogout(data.goto_location);
- return;
- }
-
- if (data.goto_location && data.goto_location.length > 0) {
- if (data.goto_location.startsWith('/') || data.goto_location.includes(window.location.hostname)) {
- browserHistory.push(data.goto_location);
- } else {
- window.open(data.goto_location);
- }
- }
- },
- (err) => {
- if (err.sendMessage) {
- this.sendMessage(post);
- } else {
- this.setState({
- serverError: err.message,
- submitting: false,
- message: post.message
- });
- }
- }
- );
- } else if (isReaction && EmojiStore.has(isReaction[2])) {
- this.sendReaction(isReaction);
- } else {
- this.sendMessage(post);
- }
-
- this.setState({
- message: '',
- submitting: false,
- postError: null,
- fileInfos: [],
- serverError: null,
- enableSendButton: false
- });
-
- const fasterThanHumanWillClick = 150;
- const forceFocus = (Date.now() - this.lastBlurAt < fasterThanHumanWillClick);
-
- this.focusTextbox(forceFocus);
- }
-
- handleNotifyAllConfirmation(e) {
- this.hideNotifyAllModal();
- this.doSubmit(e);
- }
-
- hideNotifyAllModal() {
- this.setState({showConfirmModal: false});
- }
-
- showNotifyAllModal() {
- this.setState({showConfirmModal: true});
- }
-
- handleSubmit(e) {
- const stats = ChannelStore.getCurrentStats();
- const members = stats.member_count - 1;
- const updateChannel = ChannelStore.getCurrent();
-
- if ((PostUtils.containsAtMention(this.state.message, '@all') || PostUtils.containsAtMention(this.state.message, '@channel')) && members >= Constants.NOTIFY_ALL_MEMBERS) {
- this.setState({totalMembers: members});
- this.showNotifyAllModal();
- return;
- }
-
- if (this.state.message.trimRight() === '/header') {
- GlobalActions.showChannelHeaderUpdateModal(updateChannel);
- this.setState({message: ''});
- return;
- }
-
- const isDirectOrGroup = ((updateChannel.type === Constants.DM_CHANNEL) || (updateChannel.type === Constants.GM_CHANNEL));
- if (!isDirectOrGroup && this.state.message.trimRight() === '/purpose') {
- GlobalActions.showChannelPurposeUpdateModal(updateChannel);
- this.setState({message: ''});
- return;
- }
-
- if (!isDirectOrGroup && this.state.message.trimRight() === '/rename') {
- GlobalActions.showChannelNameUpdateModal(updateChannel);
- this.setState({message: ''});
- return;
- }
-
- this.doSubmit(e);
- }
-
- handleNotifyModalCancel() {
- this.setState({showConfirmModal: false});
- }
-
- sendMessage(post) {
- post.channel_id = this.state.channelId;
-
- const time = Utils.getTimestamp();
- const userId = UserStore.getCurrentId();
- post.pending_post_id = `${userId}:${time}`;
- post.user_id = userId;
- post.create_at = time;
- post.parent_id = this.state.parentId;
-
- GlobalActions.emitUserPostedEvent(post);
-
- PostActions.createPost(post, this.state.fileInfos,
- () => GlobalActions.postListScrollChange(true),
- (err) => {
- if (err.id === 'api.post.create_post.root_id.app_error') {
- // this should never actually happen since you can't reply from this textbox
- this.showPostDeletedModal();
- } else {
- this.forceUpdate();
- }
-
- this.setState({
- submitting: false
- });
- }
- );
- }
-
- sendReaction(isReaction) {
- const action = isReaction[1];
-
- const emojiName = isReaction[2];
- const postId = PostStore.getLatestPostId(this.state.channelId);
-
- if (postId && action === '+') {
- PostActions.addReaction(this.state.channelId, postId, emojiName);
- } else if (postId && action === '-') {
- PostActions.removeReaction(this.state.channelId, postId, emojiName);
- }
-
- PostStore.storeDraft(this.state.channelId, null);
- }
-
- focusTextbox(keepFocus = false) {
- if (keepFocus || !UserAgent.isMobile()) {
- this.refs.textbox.focus();
- }
- }
-
- postMsgKeyPress(e) {
- if (!UserAgent.isMobile() && ((this.state.ctrlSend && e.ctrlKey) || !this.state.ctrlSend)) {
- if (e.which === KeyCodes.ENTER && !e.shiftKey && !e.altKey) {
- e.preventDefault();
- ReactDOM.findDOMNode(this.refs.textbox).blur();
- this.handleSubmit(e);
- }
- }
-
- GlobalActions.emitLocalUserTypingEvent(this.state.channelId, '');
- }
-
- handleChange(e) {
- const message = e.target.value;
- const enableSendButton = this.handleEnableSendButton(message, this.state.fileInfos);
-
- this.setState({
- message,
- enableSendButton
- });
-
- const draft = PostStore.getDraft(this.state.channelId);
- draft.message = message;
- PostStore.storeDraft(this.state.channelId, draft);
- }
-
- handleFileUploadChange() {
- this.focusTextbox(true);
- }
-
- handleUploadStart(clientIds, channelId) {
- const draft = PostStore.getDraft(channelId);
-
- draft.uploadsInProgress = draft.uploadsInProgress.concat(clientIds);
- PostStore.storeDraft(channelId, draft);
-
- this.setState({uploadsInProgress: draft.uploadsInProgress});
-
- // this is a bit redundant with the code that sets focus when the file input is clicked,
- // but this also resets the focus after a drag and drop
- this.focusTextbox();
- }
-
- handleFileUploadComplete(fileInfos, clientIds, channelId) {
- const draft = PostStore.getDraft(channelId);
-
- // remove each finished file from uploads
- for (let i = 0; i < clientIds.length; i++) {
- const index = draft.uploadsInProgress.indexOf(clientIds[i]);
-
- if (index !== -1) {
- draft.uploadsInProgress.splice(index, 1);
- }
- }
-
- draft.fileInfos = draft.fileInfos.concat(fileInfos);
- PostStore.storeDraft(channelId, draft);
-
- if (channelId === this.state.channelId) {
- this.setState({
- uploadsInProgress: draft.uploadsInProgress,
- fileInfos: draft.fileInfos,
- enableSendButton: true
- });
- }
- }
-
- handleUploadError(err, clientId, channelId) {
- let message = err;
- if (message && typeof message !== 'string') {
- // err is an AppError from the server
- message = err.message;
- }
-
- if (clientId !== -1) {
- const draft = PostStore.getDraft(channelId);
-
- const index = draft.uploadsInProgress.indexOf(clientId);
- if (index !== -1) {
- draft.uploadsInProgress.splice(index, 1);
- }
-
- PostStore.storeDraft(channelId, draft);
-
- if (channelId === this.state.channelId) {
- this.setState({uploadsInProgress: draft.uploadsInProgress});
- }
- }
-
- this.setState({serverError: message});
- }
-
- removePreview(id) {
- const fileInfos = Object.assign([], this.state.fileInfos);
- const uploadsInProgress = this.state.uploadsInProgress;
-
- // Clear previous errors
- this.handleUploadError(null);
-
- // id can either be the id of an uploaded file or the client id of an in progress upload
- let index = fileInfos.findIndex((info) => info.id === id);
- if (index === -1) {
- index = uploadsInProgress.indexOf(id);
-
- if (index !== -1) {
- uploadsInProgress.splice(index, 1);
- this.refs.fileUpload.getWrappedInstance().cancelUpload(id);
- }
- } else {
- fileInfos.splice(index, 1);
- }
-
- const draft = PostStore.getDraft(this.state.channelId);
- draft.fileInfos = fileInfos;
- draft.uploadsInProgress = uploadsInProgress;
- PostStore.storeDraft(this.state.channelId, draft);
- const enableSendButton = this.handleEnableSendButton(this.state.message, fileInfos);
-
- this.setState({fileInfos, uploadsInProgress, enableSendButton});
-
- this.handleFileUploadChange();
- }
-
- componentWillMount() {
- const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999);
- const enableSendButton = this.handleEnableSendButton(this.state.message, this.state.fileInfos);
-
- // wait to load these since they may have changed since the component was constructed (particularly in the case of skipping the tutorial)
- this.setState({
- ctrlSend: PreferenceStore.getBool(Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter'),
- fullWidthTextBox: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_FULL_SCREEN,
- showTutorialTip: tutorialStep === TutorialSteps.POST_POPOVER,
- enableSendButton
- });
- }
-
- componentDidMount() {
- ChannelStore.addChangeListener(this.onChange);
- PreferenceStore.addChangeListener(this.onPreferenceChange);
-
- this.focusTextbox();
- document.addEventListener('keydown', this.showShortcuts);
- }
-
- componentDidUpdate(prevProps, prevState) {
- if (prevState.channelId !== this.state.channelId) {
- this.focusTextbox();
- }
- }
-
- componentWillUnmount() {
- ChannelStore.removeChangeListener(this.onChange);
- PreferenceStore.removeChangeListener(this.onPreferenceChange);
- document.removeEventListener('keydown', this.showShortcuts);
- }
-
- showShortcuts(e) {
- if ((e.ctrlKey || e.metaKey) && e.keyCode === Constants.KeyCodes.FORWARD_SLASH) {
- e.preventDefault();
-
- GlobalActions.showShortcutsModal();
- }
- }
-
- onChange() {
- const channelId = ChannelStore.getCurrentId();
- if (this.state.channelId !== channelId) {
- const draft = PostStore.getDraft(channelId);
-
- this.setState({channelId, message: draft.message, submitting: false, serverError: null, postError: null, fileInfos: draft.fileInfos, uploadsInProgress: draft.uploadsInProgress});
- }
- }
-
- onPreferenceChange() {
- const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999);
- this.setState({
- showTutorialTip: tutorialStep === TutorialSteps.POST_POPOVER,
- ctrlSend: PreferenceStore.getBool(Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter'),
- fullWidthTextBox: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_FULL_SCREEN
- });
- }
-
- getFileCount(channelId) {
- if (channelId === this.state.channelId) {
- return this.state.fileInfos.length + this.state.uploadsInProgress.length;
- }
-
- const draft = PostStore.getDraft(channelId);
- return draft.fileInfos.length + draft.uploadsInProgress.length;
- }
-
- getFileUploadTarget() {
- return this.refs.textbox;
- }
-
- getCreatePostControls() {
- return this.refs.createPostControls;
- }
-
- handleKeyDown(e) {
- if (this.state.ctrlSend && e.keyCode === KeyCodes.ENTER && e.ctrlKey === true) {
- this.postMsgKeyPress(e);
- return;
- }
-
- const latestReplyablePost = PostStore.getLatestReplyablePost(this.state.channelId);
- const latestReplyablePostId = latestReplyablePost == null ? '' : latestReplyablePost.id;
- const lastPostEl = document.getElementById(`commentIcon_${this.state.channelId}_${latestReplyablePostId}`);
-
- if (!e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey && e.keyCode === KeyCodes.UP && this.state.message === '') {
- e.preventDefault();
-
- const lastPost = PostStore.getCurrentUsersLatestPost(this.state.channelId);
- if (!lastPost) {
- return;
- }
-
- let type;
- if (lastPost.root_id && lastPost.root_id.length > 0) {
- type = Utils.localizeMessage('create_post.comment', 'Comment');
- } else {
- type = Utils.localizeMessage('create_post.post', 'Post');
- }
-
- AppDispatcher.handleViewAction({
- type: ActionTypes.RECEIVED_EDIT_POST,
- refocusId: '#post_textbox',
- title: type,
- message: lastPost.message,
- postId: lastPost.id,
- channelId: lastPost.channel_id,
- comments: PostStore.getCommentCount(lastPost)
- });
- } else if (!e.ctrlKey && !e.metaKey && !e.altKey && e.shiftKey && e.keyCode === KeyCodes.UP && this.state.message === '' && lastPostEl) {
- e.preventDefault();
- if (document.createEvent) {
- const evt = document.createEvent('MouseEvents');
- evt.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
- lastPostEl.dispatchEvent(evt);
- } else if (document.createEventObject) {
- const evObj = document.createEventObject();
- lastPostEl.fireEvent('onclick', evObj);
- }
- }
-
- if ((e.ctrlKey || e.metaKey) && !e.altKey && !e.shiftKey && (e.keyCode === Constants.KeyCodes.UP || e.keyCode === Constants.KeyCodes.DOWN)) {
- const lastMessage = MessageHistoryStore.nextMessageInHistory(e.keyCode, this.state.message, 'post');
- if (lastMessage !== null) {
- e.preventDefault();
- this.setState({
- message: lastMessage
- });
- }
- }
- }
-
- handleBlur() {
- this.lastBlurAt = Date.now();
- }
-
- showPostDeletedModal() {
- this.setState({
- showPostDeletedModal: true
- });
- }
-
- hidePostDeletedModal() {
- this.setState({
- showPostDeletedModal: false
- });
- }
-
- handleEmojiClick(emoji) {
- const emojiAlias = emoji.name || emoji.aliases[0];
-
- if (!emojiAlias) {
- //Oops.. There went something wrong
- return;
- }
-
- if (this.state.message === '') {
- this.setState({message: ':' + emojiAlias + ': '});
- } else {
- //check whether there is already a blank at the end of the current message
- const newMessage = (/\s+$/.test(this.state.message)) ?
- this.state.message + ':' + emojiAlias + ': ' : this.state.message + ' :' + emojiAlias + ': ';
-
- this.setState({message: newMessage});
- }
-
- this.setState({showEmojiPicker: false});
-
- this.focusTextbox();
- }
-
- createTutorialTip() {
- const screens = [];
-
- screens.push(
- <div>
- <FormattedHTMLMessage
- id='create_post.tutorialTip'
- defaultMessage='<h4>Sending Messages</h4><p>Type here to write a message and press <strong>Enter</strong> to post it.</p><p>Click the <strong>Attachment</strong> button to upload an image or a file.</p>'
- />
- </div>
- );
-
- return (
- <TutorialTip
- placement='top'
- screens={screens}
- overlayClass='tip-overlay--chat'
- diagnosticsTag='tutorial_tip_1_sending_messages'
- />
- );
- }
-
- handleEnableSendButton(message, fileInfos) {
- return message.trim().length !== 0 || fileInfos.length !== 0;
- }
-
- render() {
- const notifyAllTitle = (
- <FormattedMessage
- id='notify_all.title.confirm'
- defaultMessage='Confirm sending notifications to entire channel'
- />
- );
-
- const notifyAllConfirm = (
- <FormattedMessage
- id='notify_all.confirm'
- defaultMessage='Confirm'
- />
- );
-
- const notifyAllMessage = (
- <FormattedMessage
- id='notify_all.question'
- defaultMessage='By using @all or @channel you are about to send notifications to {totalMembers} people. Are you sure you want to do this?'
- values={{
- totalMembers: this.state.totalMembers
- }}
- />
- );
-
- let serverError = null;
- if (this.state.serverError) {
- serverError = (
- <div className='has-error'>
- <label className='control-label'>{this.state.serverError}</label>
- </div>
- );
- }
-
- let postError = null;
- if (this.state.postError) {
- const postErrorClass = 'post-error' + (this.state.errorClass ? (' ' + this.state.errorClass) : '');
- postError = <label className={postErrorClass}>{this.state.postError}</label>;
- }
-
- let preview = null;
- if (this.state.fileInfos.length > 0 || this.state.uploadsInProgress.length > 0) {
- preview = (
- <FilePreview
- fileInfos={this.state.fileInfos}
- onRemove={this.removePreview}
- uploadsInProgress={this.state.uploadsInProgress}
- />
- );
- }
-
- let postFooterClassName = 'post-create-footer';
- if (postError) {
- postFooterClassName += ' has-error';
- }
-
- let tutorialTip = null;
- if (this.state.showTutorialTip) {
- tutorialTip = this.createTutorialTip();
- }
-
- let centerClass = '';
- if (!this.state.fullWidthTextBox) {
- centerClass = 'center';
- }
-
- let sendButtonClass = 'send-button theme';
- if (!this.state.enableSendButton) {
- sendButtonClass += ' disabled';
- }
-
- let attachmentsDisabled = '';
- if (!FileUtils.canUploadFiles()) {
- attachmentsDisabled = ' post-create--attachment-disabled';
- }
-
- const fileUpload = (
- <FileUpload
- ref='fileUpload'
- getFileCount={this.getFileCount}
- getTarget={this.getFileUploadTarget}
- onFileUploadChange={this.handleFileUploadChange}
- onUploadStart={this.handleUploadStart}
- onFileUpload={this.handleFileUploadComplete}
- onUploadError={this.handleUploadError}
- postType='post'
- channelId=''
- />
- );
-
- let emojiPicker = null;
- if (window.mm_config.EnableEmojiPicker === 'true') {
- emojiPicker = (
- <span className='emoji-picker__container'>
- <EmojiPickerOverlay
- show={this.state.showEmojiPicker}
- container={this.props.getChannelView}
- target={this.getCreatePostControls}
- onHide={this.hideEmojiPicker}
- onEmojiClick={this.handleEmojiClick}
- rightOffset={15}
- topOffset={-7}
- />
- <span
- className='icon icon--emoji'
- dangerouslySetInnerHTML={{__html: Constants.EMOJI_ICON_SVG}}
- onClick={this.toggleEmojiPicker}
- onMouseOver={EmojiPicker.beginPreloading}
- />
- </span>
- );
- }
-
- return (
- <form
- id='create_post'
- ref='topDiv'
- role='form'
- className={centerClass}
- onSubmit={this.handleSubmit}
- >
- <div className={'post-create' + attachmentsDisabled}>
- <div className='post-create-body'>
- <div className='post-body__cell'>
- <Textbox
- onChange={this.handleChange}
- onKeyPress={this.postMsgKeyPress}
- onKeyDown={this.handleKeyDown}
- handlePostError={this.handlePostError}
- value={this.state.message}
- onBlur={this.handleBlur}
- emojiEnabled={window.mm_config.EnableEmojiPicker === 'true'}
- createMessage={Utils.localizeMessage('create_post.write', 'Write a message...')}
- channelId={this.state.channelId}
- popoverMentionKeyClick={true}
- id='post_textbox'
- ref='textbox'
- />
- <span
- ref='createPostControls'
- className='post-body__actions'
- >
- {fileUpload}
- {emojiPicker}
- <a
- className={sendButtonClass}
- onClick={this.handleSubmit}
- >
- <i className='fa fa-paper-plane'/>
- </a>
- </span>
- </div>
- {tutorialTip}
- </div>
- <div className={postFooterClassName}>
- <MsgTyping
- channelId={this.state.channelId}
- parentId=''
- />
- {postError}
- {preview}
- {serverError}
- </div>
- </div>
- <PostDeletedModal
- show={this.state.showPostDeletedModal}
- onHide={this.hidePostDeletedModal}
- />
- <ConfirmModal
- title={notifyAllTitle}
- message={notifyAllMessage}
- confirmButtonText={notifyAllConfirm}
- show={this.state.showConfirmModal}
- onConfirm={this.handleNotifyAllConfirmation}
- onCancel={this.handleNotifyModalCancel}
- />
- </form>
- );
- }
-}
-
-CreatePost.propTypes = {
- getChannelView: PropTypes.func
-};
diff --git a/webapp/components/create_team/components/display_name.jsx b/webapp/components/create_team/components/display_name.jsx
deleted file mode 100644
index 333c262d3..000000000
--- a/webapp/components/create_team/components/display_name.jsx
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {trackEvent} from 'actions/diagnostics_actions.jsx';
-
-import Constants from 'utils/constants.jsx';
-import {cleanUpUrlable} from 'utils/url.jsx';
-
-import logoImage from 'images/logo.png';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import ReactDOM from 'react-dom';
-import {FormattedMessage} from 'react-intl';
-
-export default class TeamSignupDisplayNamePage extends React.Component {
- constructor(props) {
- super(props);
-
- this.submitNext = this.submitNext.bind(this);
-
- this.state = {};
- }
-
- componentDidMount() {
- trackEvent('signup', 'signup_team_01_name');
- }
-
- submitNext(e) {
- e.preventDefault();
-
- var displayName = ReactDOM.findDOMNode(this.refs.name).value.trim();
- if (!displayName) {
- this.setState({nameError: (
- <FormattedMessage
- id='create_team.display_name.required'
- defaultMessage='This field is required'
- />)
- });
- return;
- } else if (displayName.length < Constants.MIN_TEAMNAME_LENGTH || displayName.length > Constants.MAX_TEAMNAME_LENGTH) {
- this.setState({nameError: (
- <FormattedMessage
- id='create_team.display_name.charLength'
- defaultMessage='Name must be {min} or more characters up to a maximum of {max}. You can add a longer team description later.'
- values={{
- min: Constants.MIN_TEAMNAME_LENGTH,
- max: Constants.MAX_TEAMNAME_LENGTH
- }}
- />)
- });
- return;
- }
-
- this.props.state.wizard = 'team_url';
- this.props.state.team.display_name = displayName;
- this.props.state.team.name = cleanUpUrlable(displayName);
- this.props.updateParent(this.props.state);
- }
-
- handleFocus(e) {
- e.preventDefault();
- e.currentTarget.select();
- }
-
- render() {
- var nameError = null;
- var nameDivClass = 'form-group';
- if (this.state.nameError) {
- nameError = <label className='control-label'>{this.state.nameError}</label>;
- nameDivClass += ' has-error';
- }
-
- return (
- <div>
- <form>
- <img
- className='signup-team-logo'
- src={logoImage}
- />
- <h2>
- <FormattedMessage
- id='create_team.display_name.teamName'
- defaultMessage='Team Name'
- />
- </h2>
- <div className={nameDivClass}>
- <div className='row'>
- <div className='col-sm-9'>
- <input
- type='text'
- ref='name'
- className='form-control'
- placeholder=''
- maxLength='128'
- defaultValue={this.props.state.team.display_name}
- autoFocus={true}
- onFocus={this.handleFocus}
- spellCheck='false'
- />
- </div>
- </div>
- {nameError}
- </div>
- <div>
- <FormattedMessage
- id='create_team.display_name.nameHelp'
- defaultMessage='Name your team in any language. Your team name shows in menus and headings.'
- />
- </div>
- <button
- type='submit'
- className='btn btn-primary margin--extra'
- onClick={this.submitNext}
- >
- <FormattedMessage
- id='create_team.display_name.next'
- defaultMessage='Next'
- /><i className='fa fa-chevron-right'/>
- </button>
- </form>
- </div>
- );
- }
-}
-
-TeamSignupDisplayNamePage.propTypes = {
- state: PropTypes.object,
- updateParent: PropTypes.func
-};
diff --git a/webapp/components/create_team/components/team_url.jsx b/webapp/components/create_team/components/team_url.jsx
deleted file mode 100644
index f42dc6e0b..000000000
--- a/webapp/components/create_team/components/team_url.jsx
+++ /dev/null
@@ -1,250 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {checkIfTeamExists, createTeam} from 'actions/team_actions.jsx';
-import {trackEvent} from 'actions/diagnostics_actions.jsx';
-import Constants from 'utils/constants.jsx';
-import * as URL from 'utils/url.jsx';
-
-import logoImage from 'images/logo.png';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import ReactDOM from 'react-dom';
-import {Button, Tooltip, OverlayTrigger} from 'react-bootstrap';
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-
-export default class TeamUrl extends React.Component {
- constructor(props) {
- super(props);
-
- this.submitBack = this.submitBack.bind(this);
- this.submitNext = this.submitNext.bind(this);
- this.handleFocus = this.handleFocus.bind(this);
-
- this.state = {
- nameError: '',
- isLoading: false
- };
- }
-
- componentDidMount() {
- trackEvent('signup', 'signup_team_02_url');
- }
-
- submitBack(e) {
- e.preventDefault();
- this.props.state.wizard = 'display_name';
- this.props.updateParent(this.props.state);
- }
-
- submitNext(e) {
- e.preventDefault();
-
- const name = ReactDOM.findDOMNode(this.refs.name).value.trim();
- const cleanedName = URL.cleanUpUrlable(name);
- const urlRegex = /^[a-z]+([a-z\-0-9]+|(__)?)[a-z0-9]+$/g;
-
- if (!name) {
- this.setState({nameError: (
- <FormattedMessage
- id='create_team.team_url.required'
- defaultMessage='This field is required'
- />)
- });
- return;
- }
-
- if (cleanedName.length < Constants.MIN_TEAMNAME_LENGTH || cleanedName.length > Constants.MAX_TEAMNAME_LENGTH) {
- this.setState({nameError: (
- <FormattedMessage
- id='create_team.team_url.charLength'
- defaultMessage='Name must be {min} or more characters up to a maximum of {max}'
- values={{
- min: Constants.MIN_TEAMNAME_LENGTH,
- max: Constants.MAX_TEAMNAME_LENGTH
- }}
- />)
- });
- return;
- }
-
- if (cleanedName !== name || !urlRegex.test(name)) {
- this.setState({nameError: (
- <FormattedMessage
- id='create_team.team_url.regex'
- defaultMessage="Use only lower case letters, numbers and dashes. Must start with a letter and can't end in a dash."
- />)
- });
- return;
- }
-
- for (let index = 0; index < Constants.RESERVED_TEAM_NAMES.length; index++) {
- if (cleanedName.indexOf(Constants.RESERVED_TEAM_NAMES[index]) === 0) {
- this.setState({nameError: (
- <FormattedHTMLMessage
- id='create_team.team_url.taken'
- defaultMessage='This URL <a href="https://docs.mattermost.com/help/getting-started/creating-teams.html#team-url" target="_blank">starts with a reserved word</a> or is unavailable. Please try another.'
- />)
- });
- return;
- }
- }
-
- this.setState({isLoading: true});
- var teamSignup = JSON.parse(JSON.stringify(this.props.state));
- teamSignup.team.type = 'O';
- teamSignup.team.name = name;
-
- checkIfTeamExists(name,
- (foundTeam) => {
- if (foundTeam) {
- this.setState({nameError: (
- <FormattedMessage
- id='create_team.team_url.unavailable'
- defaultMessage='This URL is taken or unavailable. Please try another.'
- />)
- });
- this.setState({isLoading: false});
- return;
- }
-
- createTeam(teamSignup.team,
- () => {
- trackEvent('signup', 'signup_team_03_complete');
- },
- (err) => {
- this.setState({nameError: err.message});
- this.setState({isLoading: false});
- }
- );
- },
- (err) => {
- this.setState({nameError: err.message});
- }
- );
- }
-
- handleFocus(e) {
- e.preventDefault();
- e.currentTarget.select();
- }
-
- render() {
- let nameError = null;
- let nameDivClass = 'form-group';
- if (this.state.nameError) {
- nameError = <label className='control-label'>{this.state.nameError}</label>;
- nameDivClass += ' has-error';
- }
-
- const title = `${URL.getSiteURL()}/`;
- const urlTooltip = (
- <Tooltip id='urlTooltip'>{title}</Tooltip>
- );
-
- let finishMessage = (
- <FormattedMessage
- id='create_team.team_url.finish'
- defaultMessage='Finish'
- />
- );
-
- if (this.state.isLoading) {
- finishMessage = (
- <FormattedMessage
- id='create_team.team_url.creatingTeam'
- defaultMessage='Creating team...'
- />
- );
- }
-
- return (
- <div>
- <form>
- <img
- className='signup-team-logo'
- src={logoImage}
- />
- <h2>
- <FormattedMessage
- id='create_team.team_url.teamUrl'
- defaultMessage='Team URL'
- />
- </h2>
- <div className={nameDivClass}>
- <div className='row'>
- <div className='col-sm-11'>
- <div className='input-group input-group--limit'>
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={Constants.OVERLAY_TIME_DELAY}
- placement='top'
- overlay={urlTooltip}
- >
- <span className='input-group-addon'>
- {title}
- </span>
- </OverlayTrigger>
- <input
- type='text'
- ref='name'
- className='form-control'
- placeholder=''
- maxLength='128'
- defaultValue={this.props.state.team.name}
- autoFocus={true}
- onFocus={this.handleFocus}
- spellCheck='false'
- />
- </div>
- </div>
- </div>
- {nameError}
- </div>
- <p>
- <FormattedMessage
- id='create_team.team_url.webAddress'
- defaultMessage='Choose the web address of your new team:'
- />
- </p>
- <ul className='color--light'>
- <FormattedHTMLMessage
- id='create_team.team_url.hint'
- defaultMessage="<li>Short and memorable is best</li>
- <li>Use lowercase letters, numbers and dashes</li>
- <li>Must start with a letter and can't end in a dash</li>"
- />
- </ul>
- <div className='margin--extra'>
- <Button
- type='submit'
- bsStyle='primary'
- disabled={this.state.isLoading}
- onClick={this.submitNext}
- >
- {finishMessage}
- </Button>
- </div>
- <div className='margin--extra'>
- <a
- href='#'
- onClick={this.submitBack}
- >
- <FormattedMessage
- id='create_team.team_url.back'
- defaultMessage='Back to previous step'
- />
- </a>
- </div>
- </form>
- </div>
- );
- }
-}
-
-TeamUrl.propTypes = {
- state: PropTypes.object,
- updateParent: PropTypes.func
-};
diff --git a/webapp/components/create_team/create_team_controller.jsx b/webapp/components/create_team/create_team_controller.jsx
deleted file mode 100644
index 1e2e7dde1..000000000
--- a/webapp/components/create_team/create_team_controller.jsx
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import AnnouncementBar from 'components/announcement_bar';
-import ChannelStore from 'stores/channel_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-
-import {FormattedMessage} from 'react-intl';
-import {browserHistory, Link} from 'react-router/es6';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class CreateTeamController extends React.Component {
- constructor(props) {
- super(props);
-
- this.submit = this.submit.bind(this);
- this.updateParent = this.updateParent.bind(this);
-
- const state = {};
- state.team = {};
- state.wizard = 'display_name';
- this.state = state;
- }
-
- submit() {
- // todo fill in
- }
-
- componentDidMount() {
- browserHistory.push('/create_team/display_name');
- }
-
- updateParent(state) {
- this.setState(state);
- browserHistory.push('/create_team/' + state.wizard);
- }
-
- render() {
- let description = null;
- if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.CustomBrand === 'true' && global.window.mm_config.EnableCustomBrand === 'true') {
- description = global.window.mm_config.CustomDescriptionText;
- } else {
- description = (
- <FormattedMessage
- id='web.root.signup_info'
- defaultMessage='All team communication in one place, searchable and accessible anywhere'
- />
- );
- }
-
- let url = '/select_team';
- const team = TeamStore.getCurrent();
- const channel = ChannelStore.getCurrent();
- if (team) {
- url = `/${team.name}`;
- if (channel) {
- url += `/channels/${channel.name}`;
- }
- }
-
- return (
- <div>
- <AnnouncementBar/>
- <div className='signup-header'>
- <Link to={url}>
- <span className='fa fa-chevron-left'/>
- <FormattedMessage
- id='web.header.back'
- />
- </Link>
- </div>
- <div className='col-sm-12'>
- <div className='signup-team__container'>
- <h1>{global.window.mm_config.SiteName}</h1>
- <h4 className='color--light'>
- {description}
- </h4>
- <div className='signup__content'>
- {React.cloneElement(this.props.children, {
- state: this.state,
- updateParent: this.updateParent
- })}
- </div>
- </div>
- </div>
- </div>
- );
- }
-}
-
-CreateTeamController.propTypes = {
- children: PropTypes.node
-};
diff --git a/webapp/components/delete_channel_modal.jsx b/webapp/components/delete_channel_modal.jsx
deleted file mode 100644
index 92589ce42..000000000
--- a/webapp/components/delete_channel_modal.jsx
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {Modal} from 'react-bootstrap';
-import TeamStore from 'stores/team_store.jsx';
-
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-
-import {browserHistory} from 'react-router/es6';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-import {deleteChannel} from 'actions/channel_actions.jsx';
-import Constants from 'utils/constants.jsx';
-
-export default class DeleteChannelModal extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleDelete = this.handleDelete.bind(this);
- this.handleKeyDown = this.handleKeyDown.bind(this);
- this.onHide = this.onHide.bind(this);
-
- this.state = {show: true};
- }
-
- handleDelete() {
- if (this.props.channel.id.length !== 26) {
- return;
- }
-
- browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/town-square');
- deleteChannel(this.props.channel.id);
- this.onHide();
- }
-
- onHide() {
- this.setState({show: false});
- }
-
- handleKeyDown(e) {
- if (e.keyCode === Constants.KeyCodes.ENTER) {
- this.handleDelete();
- }
- }
-
- render() {
- return (
- <Modal
- show={this.state.show}
- onHide={this.onHide}
- onExited={this.props.onHide}
- onKeyDown={this.handleKeyDown}
- >
- <Modal.Header closeButton={true}>
- <h4 className='modal-title'>
- <FormattedMessage
- id='delete_channel.confirm'
- defaultMessage='Confirm DELETE Channel'
- />
- </h4>
- </Modal.Header>
- <Modal.Body>
- <div className='alert alert-danger'>
- <FormattedHTMLMessage
- id='delete_channel.question'
- defaultMessage='This will delete the channel from the team and make its contents inaccessible for all users. <br /><br />Are you sure you wish to delete the <strong>{display_name}</strong> channel?'
- values={{
- display_name: this.props.channel.display_name
- }}
- />
- </div>
- </Modal.Body>
- <Modal.Footer>
- <button
- type='button'
- className='btn btn-default'
- onClick={this.onHide}
- >
- <FormattedMessage
- id='delete_channel.cancel'
- defaultMessage='Cancel'
- />
- </button>
- <button
- type='button'
- className='btn btn-danger'
- data-dismiss='modal'
- onClick={this.handleDelete}
- >
- <FormattedMessage
- id='delete_channel.del'
- defaultMessage='Delete'
- />
- </button>
- </Modal.Footer>
- </Modal>
- );
- }
-}
-
-DeleteChannelModal.propTypes = {
- onHide: PropTypes.func.isRequired,
- channel: PropTypes.object.isRequired
-};
diff --git a/webapp/components/delete_modal_trigger.jsx b/webapp/components/delete_modal_trigger.jsx
deleted file mode 100644
index 54a1d6604..000000000
--- a/webapp/components/delete_modal_trigger.jsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import ConfirmModal from './confirm_modal.jsx';
-import Constants from 'utils/constants.jsx';
-
-export default class DeleteModalTrigger extends React.Component {
- constructor(props) {
- super(props);
- if (this.constructor === DeleteModalTrigger) {
- throw new TypeError('Can not construct abstract class.');
- }
- this.handleConfirm = this.handleConfirm.bind(this);
- this.handleCancel = this.handleCancel.bind(this);
- this.handleOpenModal = this.handleOpenModal.bind(this);
- this.handleKeyDown = this.handleKeyDown.bind(this);
-
- this.state = {
- showDeleteModal: false
- };
- }
-
- handleOpenModal(e) {
- e.preventDefault();
-
- this.setState({
- showDeleteModal: true
- });
- }
-
- handleConfirm() {
- this.props.onDelete();
- }
-
- handleCancel() {
- this.setState({
- showDeleteModal: false
- });
- }
-
- handleKeyDown(e) {
- if (e.keyCode === Constants.KeyCodes.ENTER) {
- this.handleConfirm(e);
- }
- }
-
- render() {
- return (
- <span>
- <a
- href='#'
- onClick={this.handleOpenModal}
- >
- { this.triggerTitle }
- </a>
- <ConfirmModal
- show={this.state.showDeleteModal}
- title={this.modalTitle}
- message={this.modalMessage}
- confirmButtonText={this.modalConfirmButton}
- onConfirm={this.handleConfirm}
- onCancel={this.handleCancel}
- onKeyDown={this.handleKeyDown}
- />
- </span>
- );
- }
-}
-
-DeleteModalTrigger.propTypes = {
- onDelete: PropTypes.func.isRequired
-};
diff --git a/webapp/components/delete_post_modal.jsx b/webapp/components/delete_post_modal.jsx
deleted file mode 100644
index fc7e24f22..000000000
--- a/webapp/components/delete_post_modal.jsx
+++ /dev/null
@@ -1,165 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import ReactDOM from 'react-dom';
-import {Modal} from 'react-bootstrap';
-import ModalStore from 'stores/modal_store.jsx';
-import {deletePost} from 'actions/post_actions.jsx';
-import Constants from 'utils/constants.jsx';
-
-import {FormattedMessage} from 'react-intl';
-
-var ActionTypes = Constants.ActionTypes;
-
-import React from 'react';
-
-export default class DeletePostModal extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleDelete = this.handleDelete.bind(this);
- this.handleToggle = this.handleToggle.bind(this);
- this.handleHide = this.handleHide.bind(this);
-
- this.state = {
- show: false,
- post: null,
- commentCount: 0,
- error: ''
- };
- }
-
- componentDidMount() {
- ModalStore.addModalListener(ActionTypes.TOGGLE_DELETE_POST_MODAL, this.handleToggle);
- }
-
- componentWillUnmount() {
- ModalStore.removeModalListener(ActionTypes.TOGGLE_DELETE_POST_MODAL, this.handleToggle);
- }
-
- componentDidUpdate(prevProps, prevState) {
- if (this.state.show && !prevState.show) {
- setTimeout(() => {
- $(ReactDOM.findDOMNode(this.refs.deletePostBtn)).focus();
- }, 0);
- }
- }
-
- handleDelete() {
- deletePost(
- this.state.post.channel_id,
- this.state.post,
- () => {
- this.handleHide();
- },
- (err) => {
- this.setState({error: err.message});
- }
- );
- }
-
- handleToggle(value, args) {
- this.setState({
- show: value,
- post: args.post,
- commentCount: args.commentCount,
- error: ''
- });
- }
-
- handleHide() {
- this.setState({show: false});
- }
-
- render() {
- if (!this.state.post) {
- return null;
- }
-
- var error = null;
- if (this.state.error) {
- error = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>;
- }
-
- var commentWarning = '';
- if (this.state.commentCount > 0) {
- commentWarning = (
- <FormattedMessage
- id='delete_post.warning'
- defaultMessage='This post has {count, number} {count, plural, one {comment} other {comments}} on it.'
- values={{
- count: this.state.commentCount
- }}
- />
- );
- }
-
- const postTerm = this.state.post.root_id ? (
- <FormattedMessage
- id='delete_post.comment'
- defaultMessage='Comment'
- />
- ) : (
- <FormattedMessage
- id='delete_post.post'
- defaultMessage='Post'
- />
- );
-
- return (
- <Modal
- show={this.state.show}
- onHide={this.handleHide}
- >
- <Modal.Header closeButton={true}>
- <Modal.Title>
- <FormattedMessage
- id='delete_post.confirm'
- defaultMessage='Confirm {term} Delete'
- values={{
- term: (postTerm)
- }}
- />
- </Modal.Title>
- </Modal.Header>
- <Modal.Body>
- <FormattedMessage
- id='delete_post.question'
- defaultMessage='Are you sure you want to delete this {term}?'
- values={{
- term: (postTerm)
- }}
- />
- <br/>
- <br/>
- {commentWarning}
- {error}
- </Modal.Body>
- <Modal.Footer>
- <button
- type='button'
- className='btn btn-default'
- onClick={this.handleHide}
- >
- <FormattedMessage
- id='delete_post.cancel'
- defaultMessage='Cancel'
- />
- </button>
- <button
- ref='deletePostBtn'
- type='button'
- className='btn btn-danger'
- onClick={this.handleDelete}
- >
- <FormattedMessage
- id='delete_post.del'
- defaultMessage='Delete'
- />
- </button>
- </Modal.Footer>
- </Modal>
- );
- }
-}
diff --git a/webapp/components/do_verify_email.jsx b/webapp/components/do_verify_email.jsx
deleted file mode 100644
index a8cc4df1c..000000000
--- a/webapp/components/do_verify_email.jsx
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {FormattedMessage} from 'react-intl';
-import LoadingScreen from './loading_screen.jsx';
-
-import {browserHistory, Link} from 'react-router/es6';
-
-import {verifyEmail} from 'actions/user_actions.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class DoVerifyEmail extends React.Component {
- constructor(props) {
- super(props);
-
- this.state = {
- verifyStatus: 'pending',
- serverError: ''
- };
- }
- componentWillMount() {
- verifyEmail(
- this.props.location.query.token,
- () => {
- browserHistory.push('/login?extra=verified&email=' + encodeURIComponent(this.props.location.query.email));
- },
- (err) => {
- this.setState({verifyStatus: 'failure', serverError: err.message});
- }
- );
- }
- render() {
- if (this.state.verifyStatus !== 'failure') {
- return (<LoadingScreen/>);
- }
-
- return (
- <div>
- <div className='signup-header'>
- <Link to='/'>
- <span className='fa fa-chevron-left'/>
- <FormattedMessage
- id='web.header.back'
- />
- </Link>
- </div>
- <div className='col-sm-12'>
- <div className='signup-team__container'>
- <h3>
- <FormattedMessage
- id='email_verify.almost'
- defaultMessage='{siteName}: You are almost done'
- values={{
- siteName: global.window.mm_config.SiteName
- }}
- />
- </h3>
- <div>
- <p>
- <FormattedMessage id='email_verify.verifyFailed'/>
- </p>
- <p className='alert alert-danger'>
- <i className='fa fa-times'/>
- {this.state.serverError}
- </p>
- </div>
- </div>
- </div>
- </div>
- );
- }
-}
-
-DoVerifyEmail.defaultProps = {
-};
-DoVerifyEmail.propTypes = {
- location: PropTypes.object.isRequired
-};
diff --git a/webapp/components/dot_menu/dot_menu.jsx b/webapp/components/dot_menu/dot_menu.jsx
deleted file mode 100644
index 6a64981d0..000000000
--- a/webapp/components/dot_menu/dot_menu.jsx
+++ /dev/null
@@ -1,241 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import React, {Component} from 'react';
-import PropTypes from 'prop-types';
-
-import DotMenuFlag from './dot_menu_flag.jsx';
-import DotMenuItem from './dot_menu_item.jsx';
-import DotMenuEdit from './dot_menu_edit.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-import * as PostUtils from 'utils/post_utils.jsx';
-import Constants from 'utils/constants.jsx';
-import DelayedAction from 'utils/delayed_action.jsx';
-
-export default class DotMenu extends Component {
- static propTypes = {
- idPrefix: PropTypes.string.isRequired,
- idCount: PropTypes.number,
- post: PropTypes.object.isRequired,
- commentCount: PropTypes.number,
- isFlagged: PropTypes.bool,
- handleCommentClick: PropTypes.func,
- handleDropdownOpened: PropTypes.func,
-
- actions: PropTypes.shape({
-
- /*
- * Function flag the post
- */
- flagPost: PropTypes.func.isRequired,
-
- /*
- * Function to unflag the post
- */
- unflagPost: PropTypes.func.isRequired,
-
- /*
- * Function to pin the post
- */
- pinPost: PropTypes.func.isRequired,
-
- /*
- * Function to unpin the post
- */
- unpinPost: PropTypes.func.isRequired
- }).isRequired
- }
-
- static defaultProps = {
- idCount: -1,
- post: {},
- commentCount: 0,
- isFlagged: false
- }
-
- constructor(props) {
- super(props);
-
- this.editDisableAction = new DelayedAction(this.handleEditDisable);
-
- this.state = {
- canDelete: PostUtils.canDeletePost(props.post),
- canEdit: PostUtils.canEditPost(props.post, this.editDisableAction)
- };
- }
-
- componentDidMount() {
- $('#' + this.props.idPrefix + '_dropdown' + this.props.post.id).on('shown.bs.dropdown', this.handleDropdownOpened);
- $('#' + this.props.idPrefix + '_dropdown' + this.props.post.id).on('hidden.bs.dropdown', () => this.props.handleDropdownOpened(false));
- }
-
- componentWillReceiveProps(nextProps) {
- if (nextProps.post !== this.props.post) {
- this.state = {
- canDelete: PostUtils.canDeletePost(nextProps.post),
- canEdit: PostUtils.canEditPost(nextProps.post, this.editDisableAction)
- };
- }
- }
-
- componentWillUnmount() {
- this.editDisableAction.cancel();
- }
-
- handleDropdownOpened = () => {
- this.props.handleDropdownOpened(true);
-
- const position = $('#post-list').height() - $(this.refs.dropdownToggle).offset().top;
- const dropdown = $(this.refs.dropdown);
-
- if (position < dropdown.height()) {
- dropdown.addClass('bottom');
- }
- }
-
- handleEditDisable = () => {
- this.setState({canEdit: false});
- }
-
- render() {
- const isSystemMessage = PostUtils.isSystemMessage(this.props.post);
- const isMobile = Utils.isMobile();
-
- if (this.props.idPrefix === Constants.CENTER && (!isMobile && isSystemMessage && !this.state.canDelete && !this.state.canEdit)) {
- return null;
- }
-
- if (this.props.idPrefix === Constants.RHS && (this.props.post.state === Constants.POST_FAILED || this.props.post.state === Constants.POST_LOADING)) {
- return null;
- }
-
- let type = 'Post';
- if (this.props.post.root_id && this.props.post.root_id.length > 0) {
- type = 'Comment';
- }
-
- const idPrefix = this.props.idPrefix + 'DotMenu';
-
- let dotMenuFlag = null;
- if (isMobile) {
- dotMenuFlag = (
- <DotMenuFlag
- idPrefix={idPrefix + 'Flag'}
- idCount={this.props.idCount}
- postId={this.props.post.id}
- isFlagged={this.props.isFlagged}
- actions={{
- flagPost: this.props.actions.flagPost,
- unflagPost: this.props.actions.unflagPost
- }}
- />
- );
- }
-
- let dotMenuReply = null;
- let dotMenuPermalink = null;
- let dotMenuPin = null;
- if (!isSystemMessage) {
- if (this.props.idPrefix === Constants.CENTER) {
- dotMenuReply = (
- <DotMenuItem
- idPrefix={idPrefix + 'Reply'}
- idCount={this.props.idCount}
- handleOnClick={this.props.handleCommentClick}
- />
- );
- }
-
- dotMenuPermalink = (
- <DotMenuItem
- idPrefix={idPrefix + 'Permalink'}
- idCount={this.props.idCount}
- post={this.props.post}
- />
- );
-
- dotMenuPin = (
- <DotMenuItem
- idPrefix={idPrefix + 'Pin'}
- idCount={this.props.idCount}
- post={this.props.post}
- actions={{
- pinPost: this.props.actions.pinPost,
- unpinPost: this.props.actions.unpinPost
- }}
- />
- );
- }
-
- let dotMenuDelete = null;
- if (this.state.canDelete) {
- dotMenuDelete = (
- <DotMenuItem
- idPrefix={idPrefix + 'Delete'}
- idCount={this.props.idCount}
- post={this.props.post}
- commentCount={type === 'Post' ? this.props.commentCount : 0}
- />
- );
- }
-
- let dotMenuEdit = null;
- if (this.state.canEdit) {
- dotMenuEdit = (
- <DotMenuEdit
- idPrefix={idPrefix + 'Edit'}
- idCount={this.props.idCount}
- post={this.props.post}
- type={type}
- commentCount={type === 'Post' ? this.props.commentCount : 0}
- />
- );
- }
-
- let dotMenuId = null;
- if (this.props.idCount > -1) {
- dotMenuId = Utils.createSafeId(idPrefix + this.props.idCount);
- }
-
- if (this.props.idPrefix === Constants.RHS_ROOT) {
- dotMenuId = idPrefix;
- }
-
- return (
- <div
- id={dotMenuId}
- className='dropdown'
- ref='dotMenu'
- >
- <div
- id={this.props.idPrefix + '_dropdown' + this.props.post.id}
- >
- <a
- ref='dropdownToggle'
- href='#'
- className='dropdown-toggle post__dropdown theme'
- type='button'
- data-toggle='dropdown'
- aria-expanded='false'
- />
- <div className='dropdown-menu__content'>
- <ul
- ref='dropdown'
- className='dropdown-menu'
- role='menu'
- >
- {dotMenuReply}
- {dotMenuFlag}
- {dotMenuPermalink}
- {dotMenuPin}
- {dotMenuDelete}
- {dotMenuEdit}
- </ul>
- </div>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/dot_menu/dot_menu_edit.jsx b/webapp/components/dot_menu/dot_menu_edit.jsx
deleted file mode 100644
index 2ee0947e4..000000000
--- a/webapp/components/dot_menu/dot_menu_edit.jsx
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-import PropTypes from 'prop-types';
-
-import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-
-export default function DotMenuEdit(props) {
- let editId = null;
- if (props.idCount > -1) {
- editId = Utils.createSafeId(props.idPrefix + props.idCount);
- }
-
- if (props.idPrefix.indexOf(Constants.RHS_ROOT) === 0) {
- editId = props.idPrefix;
- }
-
- return (
- <li
- id={Utils.createSafeId(editId)}
- key={props.idPrefix}
- role='presentation'
- >
- <a
- href='#'
- role='menuitem'
- data-toggle='modal'
- data-target='#edit_post'
- data-refocusid={props.idPrefix.indexOf(Constants.CENTER) === 0 ? '#post_textbox' : '#reply_textbox'}
- data-title={props.idPrefix.indexOf(Constants.CENTER) === 0 ? props.type : Utils.localizeMessage('rhs_comment.comment', 'Comment')}
- data-message={props.post.message}
- data-postid={props.post.id}
- data-channelid={props.post.channel_id}
- data-comments={props.commentCount}
- >
- <FormattedMessage
- id='post_info.edit'
- defaultMessage='Edit'
- />
- </a>
- </li>
- );
-}
-
-DotMenuEdit.propTypes = {
- idPrefix: PropTypes.string.isRequired,
- idCount: PropTypes.number,
- post: PropTypes.object,
- type: PropTypes.string,
- commentCount: PropTypes.number
-};
-
-DotMenuEdit.defaultProps = {
- idCount: -1
-};
diff --git a/webapp/components/dot_menu/dot_menu_flag.jsx b/webapp/components/dot_menu/dot_menu_flag.jsx
deleted file mode 100644
index 11546ee79..000000000
--- a/webapp/components/dot_menu/dot_menu_flag.jsx
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-import PropTypes from 'prop-types';
-
-import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-
-function formatMessage(isFlagged) {
- return (
- <FormattedMessage
- id={isFlagged ? 'rhs_root.mobile.unflag' : 'rhs_root.mobile.flag'}
- defaultMessage={isFlagged ? 'Unflag' : 'Flag'}
- />
- );
-}
-
-export default function DotMenuFlag(props) {
- function onFlagPost(e) {
- e.preventDefault();
- props.actions.flagPost(props.postId);
- }
-
- function onUnflagPost(e) {
- e.preventDefault();
- props.actions.unflagPost(props.postId);
- }
-
- const flagFunc = props.isFlagged ? onUnflagPost : onFlagPost;
-
- let flagId = null;
- if (props.idCount > -1) {
- flagId = Utils.createSafeId(props.idPrefix + props.idCount);
- }
-
- if (props.idPrefix.indexOf(Constants.RHS_ROOT) === 0) {
- flagId = props.idPrefix;
- }
-
- return (
- <li
- key={props.idPrefix}
- role='presentation'
- >
- <a
- id={flagId}
- href='#'
- onClick={flagFunc}
- >
- {formatMessage(props.isFlagged)}
- </a>
- </li>
- );
-}
-
-DotMenuFlag.propTypes = {
- idCount: PropTypes.number,
- idPrefix: PropTypes.string.isRequired,
- postId: PropTypes.string.isRequired,
- isFlagged: PropTypes.bool.isRequired,
-
- actions: PropTypes.shape({
-
- /*
- * Function flag the post
- */
- flagPost: PropTypes.func.isRequired,
-
- /*
- * Function to unflag the post
- */
- unflagPost: PropTypes.func.isRequired
-
- }).isRequired
-};
-
-DotMenuFlag.defaultProps = {
- idCount: -1,
- postId: '',
- isFlagged: false
-};
diff --git a/webapp/components/dot_menu/dot_menu_item.jsx b/webapp/components/dot_menu/dot_menu_item.jsx
deleted file mode 100644
index 6411beafb..000000000
--- a/webapp/components/dot_menu/dot_menu_item.jsx
+++ /dev/null
@@ -1,119 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-import PropTypes from 'prop-types';
-
-import {showGetPostLinkModal, showDeletePostModal} from 'actions/global_actions.jsx';
-import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-
-export default function DotMenuItem(props) {
- function handlePermalink(e) {
- e.preventDefault();
- showGetPostLinkModal(props.post);
- }
-
- function handleUnpinPost(e) {
- e.preventDefault();
- props.actions.unpinPost(props.post.id);
- }
-
- function handlePinPost(e) {
- e.preventDefault();
- props.actions.pinPost(props.post.id);
- }
-
- function handleDeletePost(e) {
- e.preventDefault();
- showDeletePostModal(props.post, props.commentCount);
- }
-
- const attrib = {};
- attrib.idPrefix = props.idPrefix;
- attrib.class = '';
-
- switch (props.idPrefix.substring((props.idPrefix.indexOf('DotMenu') + 7))) {
- case 'Reply':
- attrib.class = 'link__reply theme';
- attrib.onClick = props.handleOnClick;
- attrib.formattedMessageId = 'post_info.reply';
- attrib.formattedDefaultMessage = 'Reply';
- break;
- case 'Permalink':
- attrib.onClick = handlePermalink;
- attrib.formattedMessageId = 'post_info.permalink';
- attrib.formattedDefaultMessage = 'Permalink';
- attrib.post = props.post;
- break;
- case 'Pin':
- attrib.onClick = props.post.is_pinned ? handleUnpinPost : handlePinPost;
- attrib.formattedMessageId = props.post.is_pinned ? 'post_info.unpin' : 'post_info.pin';
- attrib.formattedDefaultMessage = props.post.is_pinned ? 'Un-pin from channel' : 'Pin from channel';
- attrib.post = props.post;
- break;
- case 'Delete':
- attrib.onClick = handleDeletePost;
- attrib.formattedMessageId = 'post_info.del';
- attrib.formattedDefaultMessage = 'Delete';
- attrib.commentCount = props.commentCount;
- break;
- default:
- }
-
- let itemId = null;
- if (props.idCount > -1) {
- itemId = Utils.createSafeId(props.idPrefix + props.idCount);
- }
-
- if (attrib.idPrefix.indexOf(Constants.RHS_ROOT) === 0) {
- itemId = attrib.idPrefix;
- }
-
- return (
- <li
- id={Utils.createSafeId(itemId)}
- key={attrib.idPrefix}
- role='presentation'
- >
- <a
- href='#'
- role='menuitem'
- onClick={attrib.onClick}
- >
- <FormattedMessage
- id={attrib.formattedMessageId}
- defaultMessage={attrib.formattedDefaultMessage}
- />
- </a>
- </li>
- );
-}
-
-DotMenuItem.propTypes = {
- idPrefix: PropTypes.string.isRequired,
- idCount: PropTypes.number,
- post: PropTypes.object,
- handleOnClick: PropTypes.func,
- type: PropTypes.string,
- commentCount: PropTypes.number,
-
- actions: PropTypes.shape({
-
- /*
- * Function to pin the post
- */
- pinPost: PropTypes.func,
-
- /*
- * Function to unpin the post
- */
- unpinPost: PropTypes.func
- })
-};
-
-DotMenuItem.defaultProps = {
- idPrefix: '',
- idCount: -1
-};
diff --git a/webapp/components/dot_menu/index.js b/webapp/components/dot_menu/index.js
deleted file mode 100644
index 852fe6791..000000000
--- a/webapp/components/dot_menu/index.js
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {flagPost, unflagPost} from 'mattermost-redux/actions/posts';
-import {pinPost, unpinPost} from 'actions/post_actions.jsx';
-
-import DotMenu from './dot_menu.jsx';
-
-function mapStateToProps(state, ownProps) {
- return ownProps;
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- flagPost,
- unflagPost,
- pinPost,
- unpinPost
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(DotMenu);
-
diff --git a/webapp/components/edit_channel_header_modal.jsx b/webapp/components/edit_channel_header_modal.jsx
deleted file mode 100644
index 36ea06fce..000000000
--- a/webapp/components/edit_channel_header_modal.jsx
+++ /dev/null
@@ -1,231 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import Textbox from './textbox.jsx';
-
-import ReactDOM from 'react-dom';
-import Constants from 'utils/constants.jsx';
-import * as Utils from 'utils/utils.jsx';
-import PreferenceStore from 'stores/preference_store.jsx';
-
-import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl';
-import {updateChannelHeader} from 'actions/channel_actions.jsx';
-import * as UserAgent from 'utils/user_agent.jsx';
-
-const KeyCodes = Constants.KeyCodes;
-
-import {Modal} from 'react-bootstrap';
-
-const holders = defineMessages({
- error: {
- id: 'edit_channel_header_modal.error',
- defaultMessage: 'This channel header is too long, please enter a shorter one'
- }
-});
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-class EditChannelHeaderModal extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleChange = this.handleChange.bind(this);
- this.handleSave = this.handleSave.bind(this);
- this.handleKeyDown = this.handleKeyDown.bind(this);
- this.handleKeyPress = this.handleKeyPress.bind(this);
- this.onShow = this.onShow.bind(this);
- this.onHide = this.onHide.bind(this);
- this.handlePostError = this.handlePostError.bind(this);
- this.focusTextbox = this.focusTextbox.bind(this);
- this.onPreferenceChange = this.onPreferenceChange.bind(this);
-
- this.state = {
- header: props.channel.header,
- show: true,
- serverError: '',
- submitted: false,
- ctrlSend: PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter')
- };
- }
-
- componentWillMount() {
- this.setState({
- ctrlSend: PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter')
- });
- }
-
- componentDidMount() {
- PreferenceStore.addChangeListener(this.onPreferenceChange);
- this.onShow();
- this.focusTextbox();
- }
-
- componentWillUnmount() {
- PreferenceStore.removeChangeListener(this.onPreferenceChange);
- }
-
- handleChange(e) {
- this.setState({
- header: e.target.value
- });
- }
-
- onPreferenceChange() {
- this.setState({
- ctrlSend: PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter')
- });
- }
-
- handleSave() {
- this.setState({submitted: true});
-
- updateChannelHeader(
- this.props.channel.id,
- this.state.header,
- () => {
- this.setState({serverError: '', submitted: false});
- this.onHide();
- },
- (err) => {
- if (err.id === 'api.context.invalid_param.app_error') {
- this.setState({serverError: this.props.intl.formatMessage(holders.error)});
- }
- this.setState({submitted: false});
- }
- );
- }
-
- onShow() {
- this.submitted = false;
- }
-
- onHide() {
- this.setState({show: false});
- }
-
- focusTextbox() {
- if (!Utils.isMobile()) {
- this.refs.editChannelHeaderTextbox.focus();
- }
- }
-
- handleKeyDown(e) {
- if (this.state.ctrlSend && e.keyCode === KeyCodes.ENTER && e.ctrlKey === true) {
- this.handleKeyPress(e);
- }
- }
-
- handleKeyPress(e) {
- if (!UserAgent.isMobile() && ((this.state.ctrlSend && e.ctrlKey) || !this.state.ctrlSend)) {
- if (e.which === KeyCodes.ENTER && !e.shiftKey && !e.altKey) {
- e.preventDefault();
- ReactDOM.findDOMNode(this.refs.editChannelHeaderTextbox).blur();
- this.handleSave(e);
- }
- }
- }
-
- handlePostError(postError) {
- this.setState({serverError: postError});
- }
-
- render() {
- var serverError = null;
- if (this.state.serverError) {
- serverError = <div className='form-group has-error'><br/><label className='control-label'>{this.state.serverError}</label></div>;
- }
-
- let headerTitle = null;
- if (this.props.channel.type === Constants.DM_CHANNEL) {
- headerTitle = (
- <FormattedMessage
- id='edit_channel_header_modal.title_dm'
- defaultMessage='Edit Header'
- />
- );
- } else {
- headerTitle = (
- <FormattedMessage
- id='edit_channel_header_modal.title'
- defaultMessage='Edit Header for {channel}'
- values={{
- channel: this.props.channel.display_name
- }}
- />
- );
- }
-
- return (
- <Modal
- show={this.state.show}
- onHide={this.onHide}
- onExited={this.props.onHide}
- >
- <Modal.Header closeButton={true}>
- <Modal.Title>
- {headerTitle}
- </Modal.Title>
- </Modal.Header>
- <Modal.Body bsClass='modal-body edit-modal-body'>
- <div>
- <p>
- <FormattedMessage
- id='edit_channel_header_modal.description'
- defaultMessage='Edit the text appearing next to the channel name in the channel header.'
- />
- </p>
- <Textbox
- value={this.state.header}
- onChange={this.handleChange}
- onKeyPress={this.handleKeyPress}
- onKeyDown={this.handleKeyDown}
- supportsCommands={false}
- suggestionListStyle='bottom'
- createMessage={Utils.localizeMessage('edit_channel_header.editHeader', 'Edit the Channel Header...')}
- previewMessageLink={Utils.localizeMessage('edit_channel_header.previewHeader', 'Edit Header')}
- handlePostError={this.handlePostError}
- id='edit_textbox'
- ref='editChannelHeaderTextbox'
- characterLimit={1024}
- />
- <br/>
- {serverError}
- </div>
- </Modal.Body>
- <Modal.Footer>
- <button
- type='button'
- className='btn btn-default'
- onClick={this.onHide}
- >
- <FormattedMessage
- id='edit_channel_header_modal.cancel'
- defaultMessage='Cancel'
- />
- </button>
- <button
- disabled={this.state.submitted}
- type='button'
- className='btn btn-primary'
- onClick={this.handleSave}
- >
- <FormattedMessage
- id='edit_channel_header_modal.save'
- defaultMessage='Save'
- />
- </button>
- </Modal.Footer>
- </Modal>
- );
- }
-}
-
-EditChannelHeaderModal.propTypes = {
- intl: intlShape.isRequired,
- onHide: PropTypes.func.isRequired,
- channel: PropTypes.object.isRequired
-};
-
-export default injectIntl(EditChannelHeaderModal);
diff --git a/webapp/components/edit_channel_purpose_modal.jsx b/webapp/components/edit_channel_purpose_modal.jsx
deleted file mode 100644
index c00b30c49..000000000
--- a/webapp/components/edit_channel_purpose_modal.jsx
+++ /dev/null
@@ -1,188 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import PreferenceStore from 'stores/preference_store.jsx';
-
-import Constants from 'utils/constants.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import {Modal} from 'react-bootstrap';
-import {FormattedMessage} from 'react-intl';
-import {updateChannelPurpose} from 'actions/channel_actions.jsx';
-
-export default class EditChannelPurposeModal extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleHide = this.handleHide.bind(this);
- this.handleSave = this.handleSave.bind(this);
- this.handleKeyDown = this.handleKeyDown.bind(this);
- this.onPreferenceChange = this.onPreferenceChange.bind(this);
-
- this.ctrlSend = PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter');
-
- this.state = {
- serverError: '',
- show: true,
- submitted: false
- };
- }
-
- componentDidMount() {
- PreferenceStore.addChangeListener(this.onPreferenceChange);
- Utils.placeCaretAtEnd(this.refs.purpose);
- }
-
- componentWillUnmount() {
- PreferenceStore.removeChangeListener(this.onPreferenceChange);
- }
-
- handleHide() {
- this.setState({show: false});
- }
-
- onPreferenceChange() {
- this.ctrlSend = PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter');
- }
-
- handleKeyDown(e) {
- if (this.ctrlSend && e.keyCode === Constants.KeyCodes.ENTER && e.ctrlKey) {
- e.preventDefault();
- this.handleSave(e);
- } else if (!this.ctrlSend && e.keyCode === Constants.KeyCodes.ENTER && !e.shiftKey && !e.altKey) {
- e.preventDefault();
- this.handleSave(e);
- }
- }
-
- handleSave() {
- if (!this.props.channel) {
- return;
- }
-
- this.setState({submitted: true});
-
- updateChannelPurpose(
- this.props.channel.id,
- this.refs.purpose.value.trim(),
- () => {
- this.handleHide();
- },
- (err) => {
- if (err.id === 'api.context.invalid_param.app_error') {
- this.setState({serverError: Utils.localizeMessage('edit_channel_purpose_modal.error', 'This channel purpose is too long, please enter a shorter one')});
- } else {
- this.setState({serverError: err.message});
- }
- }
- );
- }
-
- render() {
- let serverError = null;
- if (this.state.serverError) {
- serverError = (
- <div className='form-group has-error'>
- <br/>
- <label className='control-label'>{this.state.serverError}</label>
- </div>
- );
- }
-
- let title = (
- <span>
- <FormattedMessage
- id='edit_channel_purpose_modal.title1'
- defaultMessage='Edit Purpose'
- />
- </span>
- );
- if (this.props.channel.display_name) {
- title = (
- <span>
- <FormattedMessage
- id='edit_channel_purpose_modal.title2'
- defaultMessage='Edit Purpose for '
- />
- <span className='name'>{this.props.channel.display_name}</span>
- </span>
- );
- }
-
- let channelPurposeModal = (
- <FormattedMessage
- id='edit_channel_purpose_modal.body'
- defaultMessage='Describe how this channel should be used. This text appears in the channel list in the "More..." menu and helps others decide whether to join.'
- />
- );
- if (this.props.channel.type === 'P') {
- channelPurposeModal = (
- <FormattedMessage
- id='edit_channel_private_purpose_modal.body'
- defaultMessage='This text appears in the \"View Info\" modal of the private channel.'
- />
- );
- }
-
- return (
- <Modal
- className='modal-edit-channel-purpose'
- ref='modal'
- show={this.state.show}
- onHide={this.handleHide}
- onExited={this.props.onModalDismissed}
- >
- <Modal.Header closeButton={true}>
- <Modal.Title>
- {title}
- </Modal.Title>
- </Modal.Header>
- <Modal.Body>
- <p>
- {channelPurposeModal}
- </p>
- <textarea
- ref='purpose'
- className='form-control no-resize'
- rows='6'
- maxLength='250'
- defaultValue={this.props.channel.purpose}
- onKeyDown={this.handleKeyDown}
- />
- {serverError}
- </Modal.Body>
- <Modal.Footer>
- <button
- type='button'
- className='btn btn-default'
- onClick={this.handleHide}
- >
- <FormattedMessage
- id='edit_channel_purpose_modal.cancel'
- defaultMessage='Cancel'
- />
- </button>
- <button
- type='button'
- className='btn btn-primary'
- disabled={this.state.submitted}
- onClick={this.handleSave}
- >
- <FormattedMessage
- id='edit_channel_purpose_modal.save'
- defaultMessage='Save'
- />
- </button>
- </Modal.Footer>
- </Modal>
- );
- }
-}
-
-EditChannelPurposeModal.propTypes = {
- channel: PropTypes.object,
- onModalDismissed: PropTypes.func.isRequired
-};
diff --git a/webapp/components/edit_post_modal.jsx b/webapp/components/edit_post_modal.jsx
deleted file mode 100644
index 683371d23..000000000
--- a/webapp/components/edit_post_modal.jsx
+++ /dev/null
@@ -1,312 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import Textbox from './textbox.jsx';
-
-import BrowserStore from 'stores/browser_store.jsx';
-import PostStore from 'stores/post_store.jsx';
-import MessageHistoryStore from 'stores/message_history_store.jsx';
-import PreferenceStore from 'stores/preference_store.jsx';
-
-import * as GlobalActions from 'actions/global_actions.jsx';
-import {updatePost} from 'actions/post_actions.jsx';
-
-import * as UserAgent from 'utils/user_agent.jsx';
-import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-const KeyCodes = Constants.KeyCodes;
-
-import $ from 'jquery';
-import React from 'react';
-import ReactDOM from 'react-dom';
-import {FormattedMessage} from 'react-intl';
-
-import store from 'stores/redux_store.jsx';
-const getState = store.getState;
-
-import * as Selectors from 'mattermost-redux/selectors/entities/posts';
-
-export default class EditPostModal extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleEdit = this.handleEdit.bind(this);
- this.handleEditKeyPress = this.handleEditKeyPress.bind(this);
- this.handleEditPostEvent = this.handleEditPostEvent.bind(this);
- this.handleKeyDown = this.handleKeyDown.bind(this);
- this.handleChange = this.handleChange.bind(this);
- this.onPreferenceChange = this.onPreferenceChange.bind(this);
- this.onModalHidden = this.onModalHidden.bind(this);
- this.onModalShow = this.onModalShow.bind(this);
- this.onModalShown = this.onModalShown.bind(this);
- this.onModalHide = this.onModalHide.bind(this);
- this.onModalKeyDown = this.onModalKeyDown.bind(this);
- this.handlePostError = this.handlePostError.bind(this);
-
- this.state = {
- editText: '',
- originalText: '',
- title: '',
- post_id: '',
- channel_id: '',
- comments: 0,
- refocusId: '',
- ctrlSend: PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter'),
- postError: ''
- };
- }
-
- handlePostError(postError) {
- if (this.state.postError !== postError) {
- this.setState({postError});
- }
- }
-
- handleEdit() {
- const updatedPost = {
- message: this.state.editText,
- id: this.state.post_id,
- channel_id: this.state.channel_id
- };
-
- if (this.state.postError) {
- this.setState({errorClass: 'animation--highlight'});
- setTimeout(() => {
- this.setState({errorClass: null});
- }, Constants.ANIMATION_TIMEOUT);
- return;
- }
-
- if (updatedPost.message === this.state.originalText) {
- // no changes so just close the modal
- $('#edit_post').modal('hide');
- return;
- }
-
- MessageHistoryStore.storeMessageInHistory(updatedPost.message);
-
- if (updatedPost.message.trim().length === 0) {
- var tempState = this.state;
- Reflect.deleteProperty(tempState, 'editText');
- BrowserStore.setItem('edit_state_transfer', tempState);
- $('#edit_post').modal('hide');
- GlobalActions.showDeletePostModal(Selectors.getPost(getState(), this.state.post_id), this.state.comments);
- return;
- }
-
- updatePost(
- updatedPost,
- () => {
- window.scrollTo(0, 0);
- }
- );
-
- $('#edit_post').modal('hide');
- }
-
- handleChange(e) {
- const message = e.target.value;
- this.setState({
- editText: message
- });
- }
-
- handleEditKeyPress(e) {
- if (!UserAgent.isMobile() && !this.state.ctrlSend && e.which === KeyCodes.ENTER && !e.shiftKey && !e.altKey) {
- e.preventDefault();
- ReactDOM.findDOMNode(this.refs.editbox).blur();
- this.handleEdit();
- } else if (this.state.ctrlSend && e.ctrlKey && e.which === KeyCodes.ENTER) {
- e.preventDefault();
- ReactDOM.findDOMNode(this.refs.editbox).blur();
- this.handleEdit();
- }
- }
-
- handleEditPostEvent(options) {
- const post = Selectors.getPost(getState(), options.postId);
- if (global.window.mm_license.IsLicensed === 'true') {
- if (global.window.mm_config.AllowEditPost === Constants.ALLOW_EDIT_POST_NEVER) {
- return;
- }
- if (global.window.mm_config.AllowEditPost === Constants.ALLOW_EDIT_POST_TIME_LIMIT) {
- if ((post.create_at + (global.window.mm_config.PostEditTimeLimit * 1000)) < Utils.getTimestamp()) {
- return;
- }
- }
- }
- this.setState({
- editText: options.message || '',
- originalText: options.message || '',
- title: options.title || '',
- post_id: options.postId || '',
- channel_id: options.channelId || '',
- comments: options.comments || 0,
- refocusId: options.refocusId || ''
- });
-
- $(ReactDOM.findDOMNode(this.refs.modal)).modal('show');
- }
-
- handleKeyDown(e) {
- if (this.state.ctrlSend && e.keyCode === KeyCodes.ENTER && e.ctrlKey === true) {
- this.handleEdit();
- }
- }
-
- onPreferenceChange() {
- this.setState({
- ctrlSend: PreferenceStore.getBool(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter')
- });
- }
-
- onModalHidden() {
- this.setState({editText: '', originalText: '', title: '', channel_id: '', post_id: '', comments: 0, refocusId: '', error: '', typing: false});
- }
-
- onModalShow(e) {
- var button = e.relatedTarget;
- if (!button) {
- return;
- }
- this.setState({
- editText: $(button).attr('data-message'),
- originalText: $(button).attr('data-message'),
- title: $(button).attr('data-title'),
- channel_id: $(button).attr('data-channelid'),
- post_id: $(button).attr('data-postid'),
- comments: $(button).attr('data-comments'),
- refocusId: $(button).attr('data-refocusid'),
- typing: false
- });
- }
-
- onModalShown() {
- this.refs.editbox.focus();
-
- this.refs.editbox.recalculateSize();
- }
-
- onModalHide() {
- this.refs.editbox.hidePreview();
-
- if (this.state.refocusId !== '') {
- setTimeout(() => {
- const element = $(this.state.refocusId).get(0);
- if (element) {
- element.focus();
- }
- });
- }
- }
-
- onModalKeyDown(e) {
- if (e.which === Constants.KeyCodes.ESCAPE) {
- e.stopPropagation();
- }
- }
-
- componentDidMount() {
- $(this.refs.modal).on('hidden.bs.modal', this.onModalHidden);
- $(this.refs.modal).on('show.bs.modal', this.onModalShow);
- $(this.refs.modal).on('shown.bs.modal', this.onModalShown);
- $(this.refs.modal).on('hide.bs.modal', this.onModalHide);
- $(this.refs.modal).on('keydown', this.onModalKeyDown);
- PostStore.addEditPostListener(this.handleEditPostEvent);
- PreferenceStore.addChangeListener(this.onPreferenceChange);
- }
-
- componentWillUnmount() {
- $(this.refs.modal).off('hidden.bs.modal', this.onModalHidden);
- $(this.refs.modal).off('show.bs.modal', this.onModalShow);
- $(this.refs.modal).off('shown.bs.modal', this.onModalShown);
- $(this.refs.modal).off('hide.bs.modal', this.onModalHide);
- $(this.refs.modal).off('keydown', this.onModalKeyDown);
- PostStore.removeEditPostListner(this.handleEditPostEvent);
- PreferenceStore.removeChangeListener(this.onPreferenceChange);
- }
-
- render() {
- const errorBoxClass = 'edit-post-footer' + (this.state.postError ? ' has-error' : '');
- let postError = null;
- if (this.state.postError) {
- const postErrorClass = 'post-error' + (this.state.errorClass ? (' ' + this.state.errorClass) : '');
- postError = (<label className={postErrorClass}>{this.state.postError}</label>);
- }
-
- return (
- <div
- className='modal fade edit-modal'
- ref='modal'
- id='edit_post'
- role='dialog'
- tabIndex='-1'
- aria-hidden='true'
- >
- <div className='modal-dialog modal-push-down modal-xl'>
- <div className='modal-content'>
- <div className='modal-header'>
- <button
- type='button'
- className='close'
- data-dismiss='modal'
- aria-label='Close'
- >
- <span aria-hidden='true'>{'×'}</span>
- </button>
- <h4 className='modal-title'>
- <FormattedMessage
- id='edit_post.edit'
- defaultMessage='Edit {title}'
- values={{
- title: this.state.title
- }}
- />
- </h4>
- </div>
- <div className='edit-modal-body modal-body'>
- <Textbox
- onChange={this.handleChange}
- onKeyPress={this.handleEditKeyPress}
- onKeyDown={this.handleKeyDown}
- handlePostError={this.handlePostError}
- value={this.state.editText}
- channelId={this.state.channel_id}
- createMessage={Utils.localizeMessage('edit_post.editPost', 'Edit the post...')}
- supportsCommands={false}
- suggestionListStyle='bottom'
- id='edit_textbox'
- ref='editbox'
- />
- <div className={errorBoxClass}>
- {postError}
- </div>
- </div>
- <div className='modal-footer'>
- <button
- type='button'
- className='btn btn-default'
- data-dismiss='modal'
- >
- <FormattedMessage
- id='edit_post.cancel'
- defaultMessage='Cancel'
- />
- </button>
- <button
- type='button'
- className='btn btn-primary'
- onClick={this.handleEdit}
- >
- <FormattedMessage
- id='edit_post.save'
- defaultMessage='Save'
- />
- </button>
- </div>
- </div>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/emoji/components/add_emoji.jsx b/webapp/components/emoji/components/add_emoji.jsx
deleted file mode 100644
index 936e15d6b..000000000
--- a/webapp/components/emoji/components/add_emoji.jsx
+++ /dev/null
@@ -1,316 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import * as EmojiActions from 'actions/emoji_actions.jsx';
-import EmojiStore from 'stores/emoji_store.jsx';
-
-import BackstageHeader from 'components/backstage/components/backstage_header.jsx';
-import {FormattedMessage} from 'react-intl';
-import FormError from 'components/form_error.jsx';
-import {Link} from 'react-router';
-import SpinnerButton from 'components/spinner_button.jsx';
-
-export default class AddEmoji extends React.Component {
- static propTypes = {
- team: PropTypes.object,
- user: PropTypes.object
- };
-
- static contextTypes = {
- router: PropTypes.object.isRequired
- };
-
- constructor(props) {
- super(props);
-
- this.handleSubmit = this.handleSubmit.bind(this);
-
- this.updateName = this.updateName.bind(this);
- this.updateImage = this.updateImage.bind(this);
-
- this.state = {
- name: '',
- image: null,
- imageUrl: '',
- saving: false,
- error: null
- };
- }
-
- handleSubmit(e) {
- e.preventDefault();
-
- if (this.state.saving) {
- return;
- }
-
- this.setState({
- saving: true,
- error: null
- });
-
- const emoji = {
- creator_id: this.props.user.id,
- name: this.state.name.trim().toLowerCase()
- };
-
- // trim surrounding colons if the user accidentally included them in the name
- if (emoji.name.startsWith(':') && emoji.name.endsWith(':')) {
- emoji.name = emoji.name.substring(1, emoji.name.length - 1);
- }
-
- if (!emoji.name) {
- this.setState({
- saving: false,
- error: (
- <FormattedMessage
- id='add_emoji.nameRequired'
- defaultMessage='A name is required for the emoji'
- />
- )
- });
-
- return;
- } else if (/[^a-z0-9_-]/.test(emoji.name)) {
- this.setState({
- saving: false,
- error: (
- <FormattedMessage
- id='add_emoji.nameInvalid'
- defaultMessage="An emoji's name can only contain lowercase letters, numbers, and the symbols '-' and '_'."
- />
- )
- });
-
- return;
- } else if (EmojiStore.hasSystemEmoji(emoji.name)) {
- this.setState({
- saving: false,
- error: (
- <FormattedMessage
- id='add_emoji.nameTaken'
- defaultMessage='This name is already in use by a system emoji. Please choose another name.'
- />
- )
- });
-
- return;
- }
-
- if (!this.state.image) {
- this.setState({
- saving: false,
- error: (
- <FormattedMessage
- id='add_emoji.imageRequired'
- defaultMessage='An image is required for the emoji'
- />
- )
- });
-
- return;
- }
-
- EmojiActions.addEmoji(
- emoji,
- this.state.image,
- () => {
- // for some reason, browserHistory.push doesn't trigger a state change even though the url changes
- this.context.router.push('/' + this.props.team.name + '/emoji');
- },
- (err) => {
- this.setState({
- saving: false,
- error: err.message
- });
- }
- );
- }
-
- updateName(e) {
- this.setState({
- name: e.target.value
- });
- }
-
- updateImage(e) {
- if (e.target.files.length === 0) {
- this.setState({
- image: null,
- imageUrl: ''
- });
-
- return;
- }
-
- const image = e.target.files[0];
-
- const reader = new FileReader();
- reader.onload = () => {
- this.setState({
- image,
- imageUrl: reader.result
- });
- };
- reader.readAsDataURL(image);
- }
-
- render() {
- let filename = null;
- if (this.state.image) {
- filename = (
- <span className='add-emoji__filename'>
- {this.state.image.name}
- </span>
- );
- }
-
- let preview = null;
- if (this.state.imageUrl) {
- preview = (
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='preview'
- >
- <FormattedMessage
- id='add_emoji.preview'
- defaultMessage='Preview'
- />
- </label>
- <div className='col-md-5 col-sm-8 add-emoji__preview'>
- <FormattedMessage
- id='add_emoji.preview.sentence'
- defaultMessage='This is a sentence with {image} in it.'
- values={{
- image: (
- <span
- className='emoticon'
- style={{backgroundImage: 'url(' + this.state.imageUrl + ')'}}
- />
- )
- }}
- />
- </div>
- </div>
- );
- }
-
- return (
- <div className='backstage-content row'>
- <BackstageHeader>
- <Link to={'/' + this.props.team.name + '/emoji'}>
- <FormattedMessage
- id='emoji_list.header'
- defaultMessage='Custom Emoji'
- />
- </Link>
- <FormattedMessage
- id='add_emoji.header'
- defaultMessage='Add'
- />
- </BackstageHeader>
- <div className='backstage-form'>
- <form
- className='form-horizontal'
- onSubmit={this.handleSubmit}
- >
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='name'
- >
- <FormattedMessage
- id='add_emoji.name'
- defaultMessage='Name'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='name'
- type='text'
- maxLength='64'
- className='form-control'
- value={this.state.name}
- onChange={this.updateName}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_emoji.name.help'
- defaultMessage="Choose a name for your emoji made of up to 64 characters consisting of lowercase letters, numbers, and the symbols '-' and '_'."
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='image'
- >
- <FormattedMessage
- id='add_emoji.image'
- defaultMessage='Image'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <div>
- <div className='add-emoji__upload'>
- <button className='btn btn-primary'>
- <FormattedMessage
- id='add_emoji.image.button'
- defaultMessage='Select'
- />
- </button>
- <input
- type='file'
- accept='.jpg,.png,.gif'
- multiple={false}
- onChange={this.updateImage}
- />
- </div>
- {filename}
- <div className='form__help'>
- <FormattedMessage
- id='add_emoji.image.help'
- defaultMessage='Choose the image for your emoji. The image can be a gif, png, or jpeg file with a max size of 64 KB and dimensions up to 128 by 128 pixels.'
- />
- </div>
- </div>
- </div>
- </div>
- {preview}
- <div className='backstage-form__footer'>
- <FormError
- type='backstage'
- error={this.state.error}
- />
- <Link
- className='btn btn-sm'
- to={'/' + this.props.team.name + '/emoji'}
- >
- <FormattedMessage
- id='add_emoji.cancel'
- defaultMessage='Cancel'
- />
- </Link>
- <SpinnerButton
- className='btn btn-primary'
- type='submit'
- spinning={this.state.saving}
- onClick={this.handleSubmit}
- >
- <FormattedMessage
- id='add_emoji.save'
- defaultMessage='Save'
- />
- </SpinnerButton>
- </div>
- </form>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/emoji/components/delete_emoji_modal.jsx b/webapp/components/emoji/components/delete_emoji_modal.jsx
deleted file mode 100644
index 8b8ad7725..000000000
--- a/webapp/components/emoji/components/delete_emoji_modal.jsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-
-import DeleteModalTrigger from '../../delete_modal_trigger.jsx';
-
-export default class DeleteEmoji extends DeleteModalTrigger {
- get triggerTitle() {
- return (
- <FormattedMessage
- id='emoji_list.delete'
- defaultMessage='Delete'
- />
- );
- }
-
- get modalTitle() {
- return (
- <FormattedMessage
- id='emoji_list.delete.confirm.title'
- defaultMessage='Delete Custom Emoji'
- />
- );
- }
-
- get modalMessage() {
- return (
- <div className='alert alert-warning'>
- <i className='fa fa-warning fa-margin--right'/>
- <FormattedMessage
- id='emoji_list.delete.confirm.msg'
- defaultMessage='This action permanently deletes the custom emoji. Are you sure you want to delete it?'
- />
- </div>
- );
- }
-
- get modalConfirmButton() {
- return (
- <FormattedMessage
- id='emoji_list.delete.confirm.button'
- defaultMessage='Delete'
- />
- );
- }
-}
-
-DeleteEmoji.propTypes = {
- onDelete: PropTypes.func.isRequired
-};
diff --git a/webapp/components/emoji/components/emoji_list.jsx b/webapp/components/emoji/components/emoji_list.jsx
deleted file mode 100644
index 94d82caaa..000000000
--- a/webapp/components/emoji/components/emoji_list.jsx
+++ /dev/null
@@ -1,228 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import EmojiListItem from './emoji_list_item.jsx';
-import LoadingScreen from 'components/loading_screen.jsx';
-
-import EmojiStore from 'stores/emoji_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-
-import * as EmojiActions from 'actions/emoji_actions.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {Link} from 'react-router';
-import {FormattedMessage} from 'react-intl';
-
-export default class EmojiList extends React.Component {
- static get propTypes() {
- return {
- team: PropTypes.object,
- user: PropTypes.object
- };
- }
-
- constructor(props) {
- super(props);
-
- this.updateTitle = this.updateTitle.bind(this);
-
- this.handleEmojiChange = this.handleEmojiChange.bind(this);
- this.handleUserChange = this.handleUserChange.bind(this);
- this.deleteEmoji = this.deleteEmoji.bind(this);
- this.updateFilter = this.updateFilter.bind(this);
-
- this.state = {
- emojis: EmojiStore.getCustomEmojiMap(),
- loading: true,
- filter: '',
- users: UserStore.getProfiles()
- };
- }
-
- componentDidMount() {
- EmojiStore.addChangeListener(this.handleEmojiChange);
- UserStore.addChangeListener(this.handleUserChange);
-
- if (window.mm_config.EnableCustomEmoji === 'true') {
- EmojiActions.loadEmoji().then(() => this.setState({loading: false}));
- }
-
- this.updateTitle();
- }
-
- updateTitle() {
- let currentSiteName = '';
- if (global.window.mm_config.SiteName != null) {
- currentSiteName = global.window.mm_config.SiteName;
- }
-
- document.title = Utils.localizeMessage('custom_emoji.header', 'Custom Emoji') + ' - ' + this.props.team.display_name + ' ' + currentSiteName;
- }
-
- componentWillUnmount() {
- EmojiStore.removeChangeListener(this.handleEmojiChange);
- UserStore.removeChangeListener(this.handleUserChange);
- }
-
- handleEmojiChange() {
- this.setState({
- emojis: EmojiStore.getCustomEmojiMap()
- });
- }
-
- handleUserChange() {
- this.setState({users: UserStore.getProfiles()});
- }
-
- updateFilter(e) {
- this.setState({
- filter: e.target.value
- });
- }
-
- deleteEmoji(emoji) {
- EmojiActions.deleteEmoji(emoji.id);
- }
-
- render() {
- const filter = this.state.filter.toLowerCase();
- const isSystemAdmin = Utils.isSystemAdmin(this.props.user.roles);
-
- const emojis = [];
- if (this.state.loading) {
- emojis.push(
- <tr
- key='loading'
- className='backstage-list__item backstage-list__empty'
- >
- <td colSpan='4'>
- <LoadingScreen key='loading'/>
- </td>
- </tr>
- );
- } else if (this.state.emojis.size === 0) {
- emojis.push(
- <tr
- key='empty'
- className='backstage-list__item backstage-list__empty'
- >
- <td colSpan='4'>
- <FormattedMessage
- id='emoji_list.empty'
- defaultMessage='No custom emoji found'
- />
- </td>
- </tr>
- );
- } else {
- for (const [, emoji] of this.state.emojis) {
- let onDelete = null;
- if (isSystemAdmin || this.props.user.id === emoji.creator_id) {
- onDelete = this.deleteEmoji;
- }
-
- emojis.push(
- <EmojiListItem
- key={emoji.id}
- emoji={emoji}
- onDelete={onDelete}
- filter={filter}
- creator={this.state.users[emoji.creator_id] || {}}
- />
- );
- }
- }
-
- return (
- <div className='backstage-content emoji-list'>
- <div className='backstage-header'>
- <h1>
- <FormattedMessage
- id='emoji_list.header'
- defaultMessage='Custom Emoji'
- />
- </h1>
- <Link
- className='add-link'
- to={'/' + this.props.team.name + '/emoji/add'}
- >
- <button
- type='button'
- className='btn btn-primary'
- >
- <FormattedMessage
- id='emoji_list.add'
- defaultMessage='Add Custom Emoji'
- />
- </button>
- </Link>
- </div>
- <div className='backstage-filters'>
- <div className='backstage-filter__search'>
- <i className='fa fa-search'/>
- <input
- type='search'
- className='form-control'
- placeholder={Utils.localizeMessage('emoji_list.search', 'Search Custom Emoji')}
- value={this.state.filter}
- onChange={this.updateFilter}
- style={{flexGrow: 0, flexShrink: 0}}
- />
- </div>
- </div>
- <span className='backstage-list__help'>
- <p>
- <FormattedMessage
- id='emoji_list.help'
- defaultMessage="Custom emoji are available to everyone on your server. Type ':' in a message box to bring up the emoji selection menu. Other users may need to refresh the page before new emojis appear."
- />
- </p>
- <p>
- <FormattedMessage
- id='emoji_list.help2'
- defaultMessage="Tip: If you add #, ##, or ### as the first character on a new line containing emoji, you can use larger sized emoji. To try it out, send a message such as: '# :smile:'."
- />
- </p>
- </span>
- <div className='backstage-list'>
- <table className='emoji-list__table'>
- <thead>
- <tr className='backstage-list__item emoji-list__table-header'>
- <th className='emoji-list__name'>
- <FormattedMessage
- id='emoji_list.name'
- defaultMessage='Name'
- />
- </th>
- <th className='emoji-list__image'>
- <FormattedMessage
- id='emoji_list.image'
- defaultMessage='Image'
- />
- </th>
- <th className='emoji-list__creator'>
- <FormattedMessage
- id='emoji_list.creator'
- defaultMessage='Creator'
- />
- </th>
- <th className='emoji-list_actions'>
- <FormattedMessage
- id='emoji_list.actions'
- defaultMessage='Actions'
- />
- </th>
- </tr>
- </thead>
- <tbody>
- {emojis}
- </tbody>
- </table>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/emoji/components/emoji_list_item.jsx b/webapp/components/emoji/components/emoji_list_item.jsx
deleted file mode 100644
index cb3a8aceb..000000000
--- a/webapp/components/emoji/components/emoji_list_item.jsx
+++ /dev/null
@@ -1,103 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import EmojiStore from 'stores/emoji_store.jsx';
-import DeleteEmoji from './delete_emoji_modal.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-
-import {FormattedMessage} from 'react-intl';
-
-export default class EmojiListItem extends React.Component {
- static get propTypes() {
- return {
- emoji: PropTypes.object.isRequired,
- onDelete: PropTypes.func.isRequired,
- filter: PropTypes.string,
- creator: PropTypes.object.isRequired
- };
- }
-
- constructor(props) {
- super(props);
-
- this.handleDelete = this.handleDelete.bind(this);
- }
-
- handleDelete() {
- this.props.onDelete(this.props.emoji);
- }
-
- matchesFilter(emoji, creator, filter) {
- if (!filter) {
- return true;
- }
-
- if (emoji.name.toLowerCase().indexOf(filter) !== -1) {
- return true;
- }
-
- if (creator && creator.username && creator.username.toLowerCase().indexOf(filter) !== -1) {
- return true;
- }
-
- return false;
- }
-
- render() {
- const emoji = this.props.emoji;
- const creator = this.props.creator;
- const filter = this.props.filter ? this.props.filter.toLowerCase() : '';
-
- if (!this.matchesFilter(emoji, creator, filter)) {
- return null;
- }
-
- let creatorName;
- if (creator) {
- creatorName = Utils.displayUsernameForUser(creator);
-
- if (creatorName !== creator.username) {
- creatorName += ' (@' + creator.username + ')';
- }
- } else {
- creatorName = (
- <FormattedMessage
- id='emoji_list.somebody'
- defaultMessage='Somebody on another team'
- />
- );
- }
-
- let deleteButton = null;
- if (this.props.onDelete) {
- deleteButton = (
- <DeleteEmoji onDelete={this.handleDelete}/>
- );
- }
-
- return (
- <tr className='backstage-list__item'>
- <td className='emoji-list__name'>
- {':' + emoji.name + ':'}
- </td>
- <td className='emoji-list__image'>
- <span
- className='emoticon'
- style={{backgroundImage: 'url(' + EmojiStore.getEmojiImageUrl(emoji) + ')'}}
- />
- </td>
- <td className='emoji-list__creator'>
- {creatorName}
- </td>
- <td className='emoji-list-item_actions'>
- {deleteButton}
- </td>
- </tr>
- );
- }
-}
diff --git a/webapp/components/emoji_picker/components/emoji_picker_category.jsx b/webapp/components/emoji_picker/components/emoji_picker_category.jsx
deleted file mode 100644
index 66146106b..000000000
--- a/webapp/components/emoji_picker/components/emoji_picker_category.jsx
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import PropTypes from 'prop-types';
-import React from 'react';
-
-export default class EmojiPickerCategory extends React.Component {
- static propTypes = {
- category: PropTypes.string.isRequired,
- icon: PropTypes.node.isRequired,
- onCategoryClick: PropTypes.func.isRequired,
- selected: PropTypes.bool.isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.handleClick = this.handleClick.bind(this);
- }
-
- handleClick(e) {
- e.preventDefault();
-
- this.props.onCategoryClick(this.props.category);
- }
-
- render() {
- let className = 'emoji-picker__category';
- if (this.props.selected) {
- className += ' emoji-picker__category--selected';
- }
-
- return (
- <a
- className={className}
- href='#'
- onClick={this.handleClick}
- >
- {this.props.icon}
- </a>
- );
- }
-}
diff --git a/webapp/components/emoji_picker/components/emoji_picker_item.jsx b/webapp/components/emoji_picker/components/emoji_picker_item.jsx
deleted file mode 100644
index af72331be..000000000
--- a/webapp/components/emoji_picker/components/emoji_picker_item.jsx
+++ /dev/null
@@ -1,88 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import EmojiStore from 'stores/emoji_store.jsx';
-
-export default class EmojiPickerItem extends React.PureComponent {
- static propTypes = {
- emoji: PropTypes.object.isRequired,
- onItemOver: PropTypes.func.isRequired,
- onItemOut: PropTypes.func.isRequired,
- onItemClick: PropTypes.func.isRequired,
- onItemUnmount: PropTypes.func.isRequired,
- category: PropTypes.string.isRequired,
- isLoaded: PropTypes.bool.isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.handleMouseOver = this.handleMouseOver.bind(this);
- this.handleMouseOut = this.handleMouseOut.bind(this);
- this.handleClick = this.handleClick.bind(this);
- }
-
- componentWillUnmount() {
- this.props.onItemUnmount(this.props.emoji);
- }
-
- handleMouseOver() {
- this.props.onItemOver(this.props.emoji);
- }
-
- handleMouseOut() {
- this.props.onItemOut();
- }
-
- handleClick() {
- this.props.onItemClick(this.props.emoji);
- }
-
- render() {
- let item = null;
-
- if (this.props.emoji.category) {
- let className;
- if (this.props.isLoaded) {
- className = 'emojisprite';
- } else {
- className = 'emojisprite-loading';
- }
-
- className += ' emoji-category-' + this.props.emoji.category;
- className += ' emoji-' + this.props.emoji.filename;
-
- item = (
- <div>
- <img
- src='/static/images/img_trans.gif'
- className={className}
- onMouseOver={this.handleMouseOver}
- onMouseOut={this.handleMouseOut}
- onClick={this.handleClick}
- />
- </div>
- );
- } else {
- item = (
- <span
- onMouseOver={this.handleMouseOver}
- onMouseOut={this.handleMouseOut}
- onClick={this.handleClick}
- className='emoji-picker__item-wrapper'
- >
- <img
- className='emoji-picker__item emoticon'
- src={EmojiStore.getEmojiImageUrl(this.props.emoji)}
- />
- </span>
- );
- }
-
- return item;
- }
-}
diff --git a/webapp/components/emoji_picker/components/emoji_picker_preview.jsx b/webapp/components/emoji_picker/components/emoji_picker_preview.jsx
deleted file mode 100644
index 991bf4a2d..000000000
--- a/webapp/components/emoji_picker/components/emoji_picker_preview.jsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import EmojiStore from 'stores/emoji_store.jsx';
-
-import {FormattedMessage} from 'react-intl';
-
-export default class EmojiPickerPreview extends React.Component {
- static propTypes = {
- emoji: PropTypes.object
- }
-
- render() {
- const emoji = this.props.emoji;
-
- if (emoji) {
- let name;
- let aliases;
- let previewImage;
-
- if (emoji.aliases && emoji.category) {
- // This is a system emoji which only has a list of aliases
- name = emoji.aliases[0];
- aliases = emoji.aliases;
-
- previewImage = (
- <span className='sprite-preview'>
- <img
- src='/static/images/img_trans.gif'
- className={'emojisprite-preview emoji-category-' + emoji.category + ' emoji-' + emoji.filename}
- />
- </span>
- );
- } else {
- // This is a custom emoji that matches the model on the server
- name = emoji.name;
- aliases = [emoji.name];
- previewImage = (
- <img
- className='emoji-picker__preview-image'
- src={EmojiStore.getEmojiImageUrl(emoji)}
- />
- );
- }
-
- return (
- <div className='emoji-picker__preview'>
- <div className='emoji-picker__preview-image-box'>
- {previewImage}
- </div>
- <div className='emoji-picker__preview-image-label-box'>
- <span className='emoji-picker__preview-name'>{name}</span>
- <span className='emoji-picker__preview-aliases'>
- {':' + aliases[0] + ':'}
- </span>
- </div>
- </div>
- );
- }
-
- return (
- <div className='emoji-picker__preview emoji-picker__preview-placeholder'>
- <FormattedMessage
- id='emoji_picker.emojiPicker'
- defaultMessage='Emoji Picker'
- />
- </div>
- );
- }
-}
diff --git a/webapp/components/emoji_picker/emoji_picker.jsx b/webapp/components/emoji_picker/emoji_picker.jsx
deleted file mode 100644
index 56acdec2d..000000000
--- a/webapp/components/emoji_picker/emoji_picker.jsx
+++ /dev/null
@@ -1,520 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import * as Emoji from 'utils/emoji.jsx';
-import EmojiStore from 'stores/emoji_store.jsx';
-import PureRenderMixin from 'react-addons-pure-render-mixin';
-import * as Utils from 'utils/utils.jsx';
-import {FormattedMessage} from 'react-intl';
-
-import EmojiPickerCategory from './components/emoji_picker_category.jsx';
-import EmojiPickerItem from './components/emoji_picker_item.jsx';
-import EmojiPickerPreview from './components/emoji_picker_preview.jsx';
-
-import PeopleSpriteSheet from 'images/emoji-sheets/people.png';
-import NatureSpriteSheet from 'images/emoji-sheets/nature.png';
-import FoodsSpriteSheet from 'images/emoji-sheets/foods.png';
-import ActivitySpriteSheet from 'images/emoji-sheets/activity.png';
-import PlacesSpriteSheet from 'images/emoji-sheets/places.png';
-import ObjectsSpriteSheet from 'images/emoji-sheets/objects.png';
-import SymbolsSpriteSheet from 'images/emoji-sheets/symbols.png';
-import FlagsSpriteSheet from 'images/emoji-sheets/flags.png';
-
-// This should include all the categories available in Emoji.CategoryNames
-const CATEGORIES = [
- 'recent',
- 'people',
- 'nature',
- 'foods',
- 'activity',
- 'places',
- 'objects',
- 'symbols',
- 'flags',
- 'custom'
-];
-
-export default class EmojiPicker extends React.Component {
- static propTypes = {
- style: PropTypes.object,
- rightOffset: PropTypes.number,
- topOffset: PropTypes.number,
- placement: PropTypes.oneOf(['top', 'bottom', 'left']),
- customEmojis: PropTypes.object,
- onEmojiClick: PropTypes.func.isRequired
- }
-
- static defaultProps = {
- rightOffset: 0,
- topOffset: 0
- };
-
- constructor(props) {
- super(props);
-
- // All props are primitives or treated as immutable
- this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
-
- this.handlePreload = this.handlePreload.bind(this);
- this.handleCategoryClick = this.handleCategoryClick.bind(this);
- this.handleFilterChange = this.handleFilterChange.bind(this);
- this.handleItemOver = this.handleItemOver.bind(this);
- this.handleItemOut = this.handleItemOut.bind(this);
- this.handleItemClick = this.handleItemClick.bind(this);
- this.handleScroll = this.handleScroll.bind(this);
- this.handleItemUnmount = this.handleItemUnmount.bind(this);
- this.renderCategory = this.renderCategory.bind(this);
-
- this.state = {
- category: 'recent',
- filter: '',
- selected: null,
- preloaded: []
- };
- }
-
- componentDidMount() {
- // Delay taking focus because this briefly renders offscreen when using an Overlay
- // so focusing it immediately on mount can cause weird scrolling
- requestAnimationFrame(() => {
- this.searchInput.focus();
- });
- beginPreloading();
- subscribeToPreloads(this.handlePreload);
- this.handlePreload();
- }
-
- componentWillUnmount() {
- unsubscribeFromPreloads(this.handlePreload);
- }
-
- handlePreload() {
- const preloaded = [];
- for (const category of CATEGORIES) {
- if (didPreloadCategory(category)) {
- preloaded.push(category);
- }
- }
- this.setState({preloaded});
- }
-
- handleCategoryClick(category) {
- const items = this.refs.items;
-
- if (category === CATEGORIES[0]) {
- // First category includes the search box so just scroll to the top
- items.scrollTop = 0;
- } else {
- const cat = this.refs[category];
- items.scrollTop = cat.offsetTop;
- }
- }
-
- handleFilterChange(e) {
- this.setState({filter: e.target.value});
- }
-
- handleItemOver(emoji) {
- clearTimeout(this.timeouthandler);
- this.setState({
- selected: emoji
- });
- }
-
- handleItemOut() {
- this.timeouthandler = setTimeout(() => this.setState({
- selected: null
- }), 500);
- }
-
- handleItemUnmount(emoji) {
- // Prevent emoji preview from showing emoji which is not present anymore (due to filter)
- if (this.state.selected === emoji) {
- this.setState({
- selected: null
- });
- }
- }
-
- handleItemClick(emoji) {
- this.props.onEmojiClick(emoji);
- }
-
- handleScroll() {
- const items = this.refs.items;
- const contentTop = items.scrollTop;
- const itemsPaddingTop = getComputedStyle(items).paddingTop;
- const contentTopPadding = parseInt(itemsPaddingTop, 10);
- const scrollPct = (contentTop / (items.scrollHeight - items.clientHeight)) * 100.0;
-
- if (scrollPct > 99.0) {
- this.setState({category: 'custom'});
- return;
- }
-
- for (const category of CATEGORIES) {
- const header = this.refs[category];
- const headerStyle = getComputedStyle(header);
- const headerBottomMargin = parseInt(headerStyle.marginBottom, 10);
- const headerBottomPadding = parseInt(headerStyle.paddingBottom, 10);
- const headerBottomSpace = headerBottomMargin + headerBottomPadding;
- const headerBottom = header.offsetTop + header.offsetHeight + headerBottomSpace;
-
- // If category is the first one visible, highlight it in the bar at the top
- if (headerBottom - contentTopPadding >= contentTop) {
- if (this.state.category !== category) {
- this.setState({category: String(category)});
- }
-
- break;
- }
- }
- }
-
- renderCategory(category, isLoaded, filter) {
- let emojis;
- if (category === 'recent') {
- const recentEmojis = [...EmojiStore.getRecentEmojis()];
-
- // Reverse so most recently added is first
- recentEmojis.reverse();
-
- emojis = recentEmojis.filter((name) => {
- return EmojiStore.has(name);
- }).map((name) => {
- return EmojiStore.get(name);
- });
- } else {
- const indices = Emoji.EmojiIndicesByCategory.get(category) || [];
-
- emojis = indices.map((index) => Emoji.Emojis[index]);
-
- if (category === 'custom') {
- emojis = emojis.concat([...EmojiStore.getCustomEmojiMap().values()]);
- }
- }
-
- // Apply filter
- emojis = emojis.filter((emoji) => {
- if (emoji.name) {
- return emoji.name.indexOf(filter) !== -1;
- }
-
- for (const alias of emoji.aliases) {
- if (alias.indexOf(filter) !== -1) {
- return true;
- }
- }
-
- return false;
- });
-
- const items = emojis.map((emoji) => {
- const name = emoji.name || emoji.aliases[0];
- let key;
- if (category === 'recent') {
- key = 'system_recent_' + name;
- } else if (category === 'custom' && emoji.name) {
- key = 'custom_' + name;
- } else {
- key = 'system_' + name;
- }
-
- return (
- <EmojiPickerItem
- key={key}
- emoji={emoji}
- category={category}
- isLoaded={isLoaded}
- onItemOver={this.handleItemOver}
- onItemOut={this.handleItemOut}
- onItemClick={this.handleItemClick}
- onItemUnmount={this.handleItemUnmount}
- />
- );
- });
-
- // Only render the header if there's any visible items
- let header = null;
- if (items.length > 0) {
- header = (
- <div className='emoji-picker__category-header'>
- <FormattedMessage id={'emoji_picker.' + category}/>
- </div>
- );
- }
-
- return (
- <div
- key={'category_' + category}
- id={'emojipickercat-' + category}
- ref={category}
- >
- {header}
- <div className='emoji-picker-items__container'>
- {items}
- </div>
- </div>
- );
- }
-
- render() {
- const items = [];
-
- for (const category of CATEGORIES) {
- if (category === 'custom') {
- items.push(this.renderCategory('custom', true, this.state.filter, this.props.customEmojis));
- } else {
- items.push(this.renderCategory(category, category === 'recent' || this.state.preloaded.indexOf(category) >= 0, this.state.filter));
- }
- }
-
- let pickerStyle;
- if (this.props.style && !(this.props.style.left === 0 || this.props.style.top === 0)) {
- if (this.props.placement === 'top' || this.props.placement === 'bottom') {
- // Only take the top/bottom position passed by React Bootstrap since we want to be right-aligned
- pickerStyle = {
- top: this.props.style.top,
- bottom: this.props.style.bottom,
- right: this.props.rightOffset
- };
- } else {
- pickerStyle = {...this.props.style};
- }
- }
-
- if (pickerStyle && pickerStyle.top) {
- pickerStyle.top += this.props.topOffset;
- }
-
- return (
- <div
- className='emoji-picker'
- style={pickerStyle}
- >
- <div className='emoji-picker__categories'>
- <EmojiPickerCategory
- category='recent'
- icon={
- <i
- className='fa fa-clock-o'
- title={Utils.localizeMessage('emoji_picker.recent', 'Recently Used')}
- />
- }
- onCategoryClick={this.handleCategoryClick}
- selected={this.state.category === 'recent'}
- />
- <EmojiPickerCategory
- category='people'
- icon={
- <i
- className='fa fa-smile-o'
- title={Utils.localizeMessage('emoji_picker.people', 'People')}
- />
- }
- onCategoryClick={this.handleCategoryClick}
- selected={this.state.category === 'people'}
- />
- <EmojiPickerCategory
- category='nature'
- icon={
- <i
- className='fa fa-leaf'
- title={Utils.localizeMessage('emoji_picker.nature', 'Nature')}
- />
- }
- onCategoryClick={this.handleCategoryClick}
- selected={this.state.category === 'nature'}
- />
- <EmojiPickerCategory
- category='foods'
- icon={
- <i
- className='fa fa-cutlery'
- title={Utils.localizeMessage('emoji_picker.foods', 'Foods')}
- />
- }
- onCategoryClick={this.handleCategoryClick}
- selected={this.state.category === 'foods'}
- />
- <EmojiPickerCategory
- category='activity'
- icon={
- <i
- className='fa fa-futbol-o'
- title={Utils.localizeMessage('emoji_picker.activity', 'Activity')}
- />
- }
- onCategoryClick={this.handleCategoryClick}
- selected={this.state.category === 'activity'}
- />
- <EmojiPickerCategory
- category='places'
- icon={
- <i
- className='fa fa-plane'
- title={Utils.localizeMessage('emoji_picker.places', 'Places')}
- />
- }
- onCategoryClick={this.handleCategoryClick}
- selected={this.state.category === 'places'}
- />
- <EmojiPickerCategory
- category='objects'
- icon={
- <i
- className='fa fa-lightbulb-o'
- title={Utils.localizeMessage('emoji_picker.objects', 'Objects')}
- />
- }
- onCategoryClick={this.handleCategoryClick}
- selected={this.state.category === 'objects'}
- />
- <EmojiPickerCategory
- category='symbols'
- icon={
- <i
- className='fa fa-heart-o'
- title={Utils.localizeMessage('emoji_picker.symbols', 'Symbols')}
- />
- }
- onCategoryClick={this.handleCategoryClick}
- selected={this.state.category === 'symbols'}
- />
- <EmojiPickerCategory
- category='flags'
- icon={
- <i
- className='fa fa-flag-o'
- title={Utils.localizeMessage('emoji_picker.flags', 'Flags')}
- />
- }
- onCategoryClick={this.handleCategoryClick}
- selected={this.state.category === 'flags'}
- />
- <EmojiPickerCategory
- category='custom'
- icon={
- <i
- className='fa fa-at'
- title={Utils.localizeMessage('emoji_picker.custom', 'Custom')}
- />
- }
- onCategoryClick={this.handleCategoryClick}
- selected={this.state.category === 'custom'}
- />
- </div>
- <div className='emoji-picker__search-container'>
- <span className='fa fa-search emoji-picker__search-icon'/>
- <input
- ref={(input) => {
- this.searchInput = input;
- }}
- className='emoji-picker__search'
- type='text'
- onChange={this.handleFilterChange}
- placeholder={Utils.localizeMessage('emoji_picker.search', 'search')}
- />
- </div>
- <div
- ref='items'
- id='emojipickeritems'
- className='emoji-picker__items'
- onScroll={this.handleScroll}
- >
- {items}
- </div>
- <EmojiPickerPreview
- emoji={this.state.selected}
- />
- </div>
- );
- }
-}
-
-var preloads = {
- people: {
- src: PeopleSpriteSheet,
- didPreload: false
- },
- nature: {
- src: NatureSpriteSheet,
- didPreload: false
- },
- foods: {
- src: FoodsSpriteSheet,
- didPreload: false
- },
- activity: {
- src: ActivitySpriteSheet,
- didPreload: false
- },
- places: {
- src: PlacesSpriteSheet,
- didPreload: false
- },
- objects: {
- src: ObjectsSpriteSheet,
- didPreload: false
- },
- symbols: {
- src: SymbolsSpriteSheet,
- didPreload: false
- },
- flags: {
- src: FlagsSpriteSheet,
- didPreload: false
- }
-};
-
-var didBeginPreloading = false;
-
-var preloadCallback = null;
-
-export function beginPreloading() {
- if (didBeginPreloading) {
- return;
- }
- didBeginPreloading = true;
- preloadNextCategory();
-}
-
-function preloadNextCategory() {
- let sheet = null;
- for (const category of CATEGORIES) {
- const preload = preloads[category];
- if (preload && !preload.didPreload) {
- sheet = preload;
- break;
- }
- }
- if (sheet) {
- const img = new Image();
- img.onload = () => {
- sheet.didPreload = true;
- if (preloadCallback) {
- preloadCallback();
- }
- preloadNextCategory();
- };
- img.src = sheet.src;
- }
-}
-
-export function didPreloadCategory(category) {
- const preload = preloads[category];
- return preload && preload.didPreload;
-}
-
-function subscribeToPreloads(callback) {
- preloadCallback = callback;
-}
-
-function unsubscribeFromPreloads(callback) {
- if (callback === preloadCallback) {
- preloadCallback = null;
- }
-}
diff --git a/webapp/components/emoji_picker/emoji_picker_container.jsx b/webapp/components/emoji_picker/emoji_picker_container.jsx
deleted file mode 100644
index ab24afcc4..000000000
--- a/webapp/components/emoji_picker/emoji_picker_container.jsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import EmojiStore from 'stores/emoji_store.jsx';
-
-import EmojiPicker from './emoji_picker.jsx';
-
-export default class EmojiPickerContainer extends React.Component {
- static propTypes = {
- onEmojiClick: PropTypes.func.isRequred
- }
-
- constructor(props) {
- super(props);
- this.handleEmojiChange = this.handleEmojiChange.bind(this);
-
- this.state = {
- customEmojis: EmojiStore.getCustomEmojiMap().values() ? EmojiStore.getCustomEmojiMap().values() : []
- };
- }
-
- componentDidMount() {
- EmojiStore.addChangeListener(this.handleEmojiChange);
- }
-
- componentWillUnount() {
- EmojiStore.removeChangeListener(this.handleEmojiChange);
- }
-
- handleEmojiChange() {
- this.setState({
- customEmojis: EmojiStore.getCustomEmojiMap().values()
- });
- }
-
- render() {
- return (
- <EmojiPicker
- customEmojis={EmojiStore.getCustomEmojiMap().values()}
- onEmojiClick={this.props.onEmojiClick}
- />
- );
- }
-}
diff --git a/webapp/components/emoji_picker/emoji_picker_overlay.jsx b/webapp/components/emoji_picker/emoji_picker_overlay.jsx
deleted file mode 100644
index 7174e004c..000000000
--- a/webapp/components/emoji_picker/emoji_picker_overlay.jsx
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {Overlay} from 'react-bootstrap';
-
-import EmojiPicker from './emoji_picker.jsx';
-
-export default class EmojiPickerOverlay extends React.PureComponent {
- static propTypes = {
- show: PropTypes.bool.isRequired,
- container: PropTypes.func,
- target: PropTypes.func.isRequired,
- onEmojiClick: PropTypes.func.isRequired,
- onHide: PropTypes.func.isRequired,
- rightOffset: PropTypes.number,
- topOffset: PropTypes.number,
- spaceRequiredAbove: PropTypes.number,
- spaceRequiredBelow: PropTypes.number
- }
-
- // Reasonable defaults calculated from from the center channel
- static defaultProps = {
- spaceRequiredAbove: 422,
- spaceRequiredBelow: 436
- }
-
- constructor(props) {
- super(props);
-
- this.state = {
- placement: 'top'
- };
- }
-
- componentWillUpdate(nextProps) {
- if (nextProps.show && !this.props.show) {
- const targetBounds = nextProps.target().getBoundingClientRect();
-
- let placement;
- if (targetBounds.top > nextProps.spaceRequiredAbove) {
- placement = 'top';
- } else if (window.innerHeight - targetBounds.bottom > nextProps.spaceRequiredBelow) {
- placement = 'bottom';
- } else {
- placement = 'left';
- }
-
- this.setState({placement});
- }
- }
-
- render() {
- return (
- <Overlay
- show={this.props.show}
- placement={this.state.placement}
- rootClose={true}
- container={this.props.container}
- onHide={this.props.onHide}
- target={this.props.target}
- animation={false}
- >
- <EmojiPicker
- onEmojiClick={this.props.onEmojiClick}
- rightOffset={this.props.rightOffset}
- topOffset={this.props.topOffset}
- />
- </Overlay>
- );
- }
-}
diff --git a/webapp/components/error_page.jsx b/webapp/components/error_page.jsx
deleted file mode 100644
index 4e3e73188..000000000
--- a/webapp/components/error_page.jsx
+++ /dev/null
@@ -1,212 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import PropTypes from 'prop-types';
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-import {Link} from 'react-router/es6';
-
-import {ErrorPageTypes} from 'utils/constants.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-export default class ErrorPage extends React.Component {
- static propTypes = {
- location: PropTypes.object.isRequired
- };
-
- componentDidMount() {
- $('body').attr('class', 'sticky error');
- }
-
- componentWillUnmount() {
- $('body').attr('class', '');
- }
-
- renderTitle = () => {
- switch (this.props.location.query.type) {
- case ErrorPageTypes.LOCAL_STORAGE:
- return (
- <FormattedMessage
- id='error.local_storage.title'
- defaultMessage='Cannot Load Mattermost'
- />
- );
- case ErrorPageTypes.PERMALINK_NOT_FOUND:
- return (
- <FormattedMessage
- id='permalink.error.title'
- defaultMessage='Message Not Found'
- />
- );
- case ErrorPageTypes.PAGE_NOT_FOUND:
- return (
- <FormattedMessage
- id='error.not_found.title'
- defaultMessage='Message Not Found'
- />
- );
- }
-
- if (this.props.location.query.title) {
- return this.props.location.query.title;
- }
-
- return Utils.localizeMessage('error.generic.title', 'Error');
- }
-
- renderMessage = () => {
- switch (this.props.location.query.type) {
- case ErrorPageTypes.LOCAL_STORAGE:
- return (
- <div>
- <FormattedMessage
- id='error.local_storage.message'
- defaultMessage='Mattermost was unable to load because a setting in your browser prevents the use of its local storage features. To allow Mattermost to load, try the following actions:'
- />
- <ul>
- <li>
- <FormattedMessage
- id='error.local_storage.help1'
- defaultMessage='Enable cookies'
- />
- </li>
- <li>
- <FormattedMessage
- id='error.local_storage.help2'
- defaultMessage='Turn off private browsing'
- />
- </li>
- <li>
- <FormattedMessage
- id='error.local_storage.help3'
- defaultMessage='Use a supported browser (IE 11, Chrome 43+, Firefox 38+, Safari 9, Edge)'
- />
- </li>
- </ul>
- </div>
- );
- case ErrorPageTypes.PERMALINK_NOT_FOUND:
- return (
- <p>
- <FormattedMessage
- id='permalink.error.access'
- defaultMessage='Permalink belongs to a deleted message or to a channel to which you do not have access.'
- />
- </p>
- );
- case ErrorPageTypes.OAUTH_MISSING_CODE:
- return (
- <div>
- <p>
- <FormattedMessage
- id='error.oauth_missing_code'
- defaultMessage='The service provider {service} did not provide an authorization code in the redirect URL.'
- values={{
- service: this.props.location.query.service
- }}
- />
- </p>
- <p>
- <FormattedMessage
- id='error.oauth_missing_code.google'
- defaultMessage='For {link} make sure your administrator enabled the Google+ API.'
- values={{
- link: this.renderLink('https://docs.mattermost.com/deployment/sso-google.html', 'error.oauth_missing_code.google.link', 'Google Apps')
- }}
- />
- </p>
- <p>
- <FormattedMessage
- id='error.oauth_missing_code.office365'
- defaultMessage='For {link} make sure the administrator of your Microsoft organization has enabled the Mattermost app.'
- values={{
- link: this.renderLink('https://docs.mattermost.com/deployment/sso-office.html', 'error.oauth_missing_code.office365.link', 'Office 365')
- }}
- />
- </p>
- <p>
- <FormattedMessage
- id='error.oauth_missing_code.gitlab'
- defaultMessage='For {link} please make sure you followed the setup instructions.'
- values={{
- link: this.renderLink('https://docs.mattermost.com/deployment/sso-gitlab.html', 'error.oauth_missing_code.gitlab.link', 'GitLab')
- }}
- />
- </p>
- <p>
- <FormattedMessage
- id='error.oauth_missing_code.forum'
- defaultMessage="If you reviewed the above and are still having trouble with configuration, you may post in our {link} where we'll be happy to help with issues during setup."
- values={{
- link: this.renderLink('https://forum.mattermost.org/c/trouble-shoot', 'error.oauth_missing_code.forum.link', 'Troubleshooting forum')
- }}
- />
- </p>
- </div>
- );
- case ErrorPageTypes.PAGE_NOT_FOUND:
- return (
- <p>
- <FormattedMessage
- id='error.not_found.message'
- defaultMessage='The page you were trying to reach does not exist'
- />
- </p>
- );
- }
-
- if (this.props.location.query.message) {
- return <p>{this.props.location.query.message}</p>;
- }
-
- return (
- <p>
- <FormattedMessage
- id='error.generic.message'
- defaultMessage='An error has occurred.'
- />
- </p>
- );
- }
-
- renderLink = (url, id, defaultMessage) => {
- return (
- <a
- href={url}
- rel='noopener noreferrer'
- target='_blank'
- >
- <FormattedMessage
- id={id}
- defaultMessage={defaultMessage}
- />
- </a>
- );
- }
-
- render() {
- const title = this.renderTitle();
- const message = this.renderMessage();
-
- return (
- <div className='container-fluid'>
- <div className='error__container'>
- <div className='error__icon'>
- <i className='fa fa-exclamation-triangle'/>
- </div>
- <h2>
- {title}
- </h2>
- {message}
- <Link to='/'>
- <FormattedMessage
- id='error.generic.link'
- defaultMessage='Back to Mattermost'
- />
- </Link>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/file_attachment.jsx b/webapp/components/file_attachment.jsx
deleted file mode 100644
index 5642d33c5..000000000
--- a/webapp/components/file_attachment.jsx
+++ /dev/null
@@ -1,205 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import PropTypes from 'prop-types';
-import React from 'react';
-import {Tooltip, OverlayTrigger} from 'react-bootstrap';
-
-import Constants from 'utils/constants.jsx';
-import * as FileUtils from 'utils/file_utils';
-import * as Utils from 'utils/utils.jsx';
-
-import {getFileUrl, getFileThumbnailUrl} from 'mattermost-redux/utils/file_utils';
-
-export default class FileAttachment extends React.Component {
- constructor(props) {
- super(props);
-
- this.loadFiles = this.loadFiles.bind(this);
- this.onAttachmentClick = this.onAttachmentClick.bind(this);
-
- this.state = {
- loaded: Utils.getFileType(props.fileInfo.extension) !== 'image'
- };
- }
-
- componentDidMount() {
- this.loadFiles();
- }
-
- componentWillReceiveProps(nextProps) {
- if (nextProps.fileInfo.id !== this.props.fileInfo.id) {
- this.setState({
- loaded: Utils.getFileType(nextProps.fileInfo.extension) !== 'image'
- });
- }
- }
-
- componentDidUpdate(prevProps) {
- if (!this.state.loaded && this.props.fileInfo.id !== prevProps.fileInfo.id) {
- this.loadFiles();
- }
- }
-
- loadFiles() {
- const fileInfo = this.props.fileInfo;
- const fileType = Utils.getFileType(fileInfo.extension);
-
- if (fileType === 'image') {
- const thumbnailUrl = getFileThumbnailUrl(fileInfo.id);
-
- Utils.loadImage(thumbnailUrl, this.handleImageLoaded);
- }
- }
-
- handleImageLoaded = () => {
- this.setState({
- loaded: true
- });
- }
-
- onAttachmentClick(e) {
- e.preventDefault();
- this.props.handleImageClick(this.props.index);
- }
-
- render() {
- const fileInfo = this.props.fileInfo;
- const fileName = fileInfo.name;
- const fileUrl = getFileUrl(fileInfo.id);
-
- let thumbnail;
- if (this.state.loaded) {
- const type = Utils.getFileType(fileInfo.extension);
-
- if (type === 'image') {
- let className = 'post-image';
-
- if (fileInfo.width < Constants.THUMBNAIL_WIDTH && fileInfo.height < Constants.THUMBNAIL_HEIGHT) {
- className += ' small';
- } else {
- className += ' normal';
- }
-
- thumbnail = (
- <div
- className={className}
- style={{
- backgroundImage: `url(${getFileThumbnailUrl(fileInfo.id)})`
- }}
- />
- );
- } else {
- thumbnail = <div className={'file-icon ' + Utils.getIconClassName(type)}/>;
- }
- } else {
- thumbnail = <div className='post-image__load'/>;
- }
-
- let trimmedFilename;
- if (fileName.length > 35) {
- trimmedFilename = fileName.substring(0, Math.min(35, fileName.length)) + '...';
- } else {
- trimmedFilename = fileName;
- }
-
- const canDownloadFiles = FileUtils.canDownloadFiles();
-
- let filenameOverlay;
- if (this.props.compactDisplay) {
- filenameOverlay = (
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={1000}
- placement='top'
- overlay={<Tooltip id='file-name__tooltip'>{fileName}</Tooltip>}
- >
- <a
- href='#'
- onClick={this.onAttachmentClick}
- className='post-image__name'
- rel='noopener noreferrer'
- >
- <span
- className='icon'
- dangerouslySetInnerHTML={{__html: Constants.ATTACHMENT_ICON_SVG}}
- />
- {trimmedFilename}
- </a>
- </OverlayTrigger>
- );
- } else if (canDownloadFiles) {
- filenameOverlay = (
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={1000}
- placement='top'
- overlay={<Tooltip id='file-name__tooltip'>{Utils.localizeMessage('file_attachment.download', 'Download') + ' "' + fileName + '"'}</Tooltip>}
- >
- <a
- href={fileUrl}
- download={fileName}
- className='post-image__name'
- target='_blank'
- rel='noopener noreferrer'
- >
- {trimmedFilename}
- </a>
- </OverlayTrigger>
- );
- } else {
- filenameOverlay = (
- <span className='post-image__name'>
- {trimmedFilename}
- </span>
- );
- }
-
- let downloadButton = null;
- if (canDownloadFiles) {
- downloadButton = (
- <a
- href={fileUrl}
- download={fileName}
- className='post-image__download'
- target='_blank'
- rel='noopener noreferrer'
- >
- <span className='fa fa-download'/>
- </a>
- );
- }
-
- return (
- <div className='post-image__column'>
- <a
- className='post-image__thumbnail'
- href='#'
- onClick={this.onAttachmentClick}
- >
- {thumbnail}
- </a>
- <div className='post-image__details'>
- {filenameOverlay}
- <div>
- {downloadButton}
- <span className='post-image__type'>{fileInfo.extension.toUpperCase()}</span>
- <span className='post-image__size'>{Utils.fileSizeToString(fileInfo.size)}</span>
- </div>
- </div>
- </div>
- );
- }
-}
-
-FileAttachment.propTypes = {
- fileInfo: PropTypes.object.isRequired,
-
- // the index of this attachment preview in the parent FileAttachmentList
- index: PropTypes.number.isRequired,
-
- // handler for when the thumbnail is clicked passed the index above
- handleImageClick: PropTypes.func,
-
- compactDisplay: PropTypes.bool
-};
diff --git a/webapp/components/file_attachment_list/file_attachment_list.jsx b/webapp/components/file_attachment_list/file_attachment_list.jsx
deleted file mode 100644
index a497a0004..000000000
--- a/webapp/components/file_attachment_list/file_attachment_list.jsx
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ViewImageModal from 'components/view_image.jsx';
-import FileAttachment from 'components/file_attachment.jsx';
-import Constants from 'utils/constants.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class FileAttachmentList extends React.Component {
- static propTypes = {
-
- /*
- * The post the files are attached to
- */
- post: PropTypes.object.isRequired,
-
- /*
- * The number of files attached to the post
- */
- fileCount: PropTypes.number.isRequired,
-
- /*
- * Array of metadata for each file attached to the post
- */
- fileInfos: PropTypes.arrayOf(PropTypes.object),
-
- /*
- * Set to render compactly
- */
- compactDisplay: PropTypes.bool,
-
- actions: PropTypes.shape({
-
- /*
- * Function to get file metadata for a post
- */
- getMissingFilesForPost: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.handleImageClick = this.handleImageClick.bind(this);
-
- this.state = {showPreviewModal: false, startImgIndex: 0};
- }
-
- componentDidMount() {
- if (this.props.post.file_ids || this.props.post.filenames) {
- this.props.actions.getMissingFilesForPost(this.props.post.id);
- }
- }
-
- handleImageClick(indexClicked) {
- this.setState({showPreviewModal: true, startImgIndex: indexClicked});
- }
-
- render() {
- const postFiles = [];
- let fileInfos = [];
- if (this.props.fileInfos && this.props.fileInfos.length > 0) {
- fileInfos = this.props.fileInfos.sort((a, b) => a.create_at - b.create_at);
- for (let i = 0; i < Math.min(fileInfos.length, Constants.MAX_DISPLAY_FILES); i++) {
- const fileInfo = fileInfos[i];
-
- postFiles.push(
- <FileAttachment
- key={fileInfo.id}
- fileInfo={fileInfos[i]}
- index={i}
- handleImageClick={this.handleImageClick}
- compactDisplay={this.props.compactDisplay}
- />
- );
- }
- } else if (this.props.fileCount > 0) {
- for (let i = 0; i < Math.min(this.props.fileCount, Constants.MAX_DISPLAY_FILES); i++) {
- // Add a placeholder to avoid pop-in once we get the file infos for this post
- postFiles.push(
- <div
- key={`fileCount-${i}`}
- className='post-image__column post-image__column--placeholder'
- />
- );
- }
- }
-
- return (
- <div>
- <div className='post-image__columns clearfix'>
- {postFiles}
- </div>
- <ViewImageModal
- show={this.state.showPreviewModal}
- onModalDismissed={() => this.setState({showPreviewModal: false})}
- startId={this.state.startImgIndex}
- fileInfos={fileInfos}
- />
- </div>
- );
- }
-}
diff --git a/webapp/components/file_attachment_list/index.js b/webapp/components/file_attachment_list/index.js
deleted file mode 100644
index 1e901114b..000000000
--- a/webapp/components/file_attachment_list/index.js
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getMissingFilesForPost} from 'mattermost-redux/actions/files';
-import {makeGetFilesForPost} from 'mattermost-redux/selectors/entities/files';
-
-import FileAttachmentList from './file_attachment_list.jsx';
-
-function makeMapStateToProps() {
- const selectFilesForPost = makeGetFilesForPost();
- return function mapStateToProps(state, ownProps) {
- const postId = ownProps.post ? ownProps.post.id : '';
- const fileInfos = selectFilesForPost(state, postId);
-
- let fileCount = 0;
- if (ownProps.post.file_ids) {
- fileCount = ownProps.post.file_ids.length;
- } else if (ownProps.post.filenames) {
- fileCount = ownProps.post.filenames.length;
- }
-
- return {
- ...ownProps,
- fileInfos,
- fileCount
- };
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getMissingFilesForPost
- }, dispatch)
- };
-}
-
-export default connect(makeMapStateToProps, mapDispatchToProps)(FileAttachmentList);
diff --git a/webapp/components/file_info_preview.jsx b/webapp/components/file_info_preview.jsx
deleted file mode 100644
index 6032defb6..000000000
--- a/webapp/components/file_info_preview.jsx
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import * as FileUtils from 'utils/file_utils';
-import * as Utils from 'utils/utils.jsx';
-
-export default class FileInfoPreview extends React.Component {
- shouldComponentUpdate(nextProps) {
- if (nextProps.fileUrl !== this.props.fileUrl) {
- return true;
- }
-
- if (!Utils.areObjectsEqual(nextProps.fileInfo, this.props.fileInfo)) {
- return true;
- }
-
- return false;
- }
-
- render() {
- const fileInfo = this.props.fileInfo;
- const fileUrl = this.props.fileUrl;
-
- // non-image files include a section providing details about the file
- const infoParts = [];
-
- if (fileInfo.extension !== '') {
- infoParts.push(Utils.localizeMessage('file_info_preview.type', 'File type ') + fileInfo.extension.toUpperCase());
- }
-
- infoParts.push(Utils.localizeMessage('file_info_preview.size', 'Size ') + Utils.fileSizeToString(fileInfo.size));
-
- const infoString = infoParts.join(', ');
-
- let preview = null;
- if (FileUtils.canDownloadFiles()) {
- preview = (
- <a
- className='file-details__preview'
- href={fileUrl}
- target='_blank'
- rel='noopener noreferrer'
- >
- <span className='file-details__preview-helper'/>
- <img src={Utils.getFileIconPath(fileInfo)}/>
- </a>
- );
- } else {
- preview = (
- <span className='file-details__preview'>
- <span className='file-details__preview-helper'/>
- <img src={Utils.getFileIconPath(fileInfo)}/>
- </span>
- );
- }
-
- return (
- <div className='file-details__container'>
- {preview}
- <div className='file-details'>
- <div className='file-details__name'>{fileInfo.name}</div>
- <div className='file-details__info'>{infoString}</div>
- </div>
- </div>
- );
- }
-}
-
-FileInfoPreview.propTypes = {
- fileInfo: PropTypes.object.isRequired,
- fileUrl: PropTypes.string.isRequired
-};
diff --git a/webapp/components/file_preview.jsx b/webapp/components/file_preview.jsx
deleted file mode 100644
index a53134f7e..000000000
--- a/webapp/components/file_preview.jsx
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import loadingGif from 'images/load.gif';
-
-import Constants from 'utils/constants.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import {getFileUrl, getFileThumbnailUrl} from 'mattermost-redux/utils/file_utils';
-
-export default class FilePreview extends React.Component {
- static propTypes = {
- onRemove: PropTypes.func.isRequired,
- fileInfos: PropTypes.arrayOf(PropTypes.object).isRequired,
- uploadsInProgress: PropTypes.array
- };
-
- static defaultProps = {
- fileInfos: [],
- uploadsInProgress: []
- };
-
- constructor(props) {
- super(props);
-
- this.handleRemove = this.handleRemove.bind(this);
- }
-
- componentDidUpdate() {
- if (this.props.uploadsInProgress.length > 0) {
- this.refs[this.props.uploadsInProgress[0]].scrollIntoView();
- }
- }
-
- handleRemove(id) {
- this.props.onRemove(id);
- }
-
- render() {
- var previews = [];
- const fileInfos = this.props.fileInfos.sort((a, b) => a.create_at - b.create_at);
- fileInfos.forEach((info) => {
- const type = Utils.getFileType(info.extension);
-
- let className = 'file-preview';
- let previewImage;
- if (type === 'svg') {
- previewImage = (
- <img
- className='post-image normal'
- src={getFileUrl(info.id)}
- />
- );
- } else if (type === 'image') {
- let imageClassName = 'post-image';
-
- if (info.width < Constants.THUMBNAIL_WIDTH && info.height < Constants.THUMBNAIL_HEIGHT) {
- imageClassName += ' small';
- } else {
- imageClassName += ' normal';
- }
-
- previewImage = (
- <div
- className={imageClassName}
- style={{
- backgroundImage: `url(${getFileThumbnailUrl(info.id)})`
- }}
- />
- );
- } else {
- className += ' custom-file';
- previewImage = <div className={'file-icon ' + Utils.getIconClassName(type)}/>;
- }
-
- previews.push(
- <div
- key={info.id}
- className={className}
- >
- {previewImage}
- <a
- className='file-preview__remove'
- onClick={this.handleRemove.bind(this, info.id)}
- >
- <i className='fa fa-remove'/>
- </a>
- </div>
- );
- });
-
- this.props.uploadsInProgress.forEach((clientId) => {
- previews.push(
- <div
- ref={clientId}
- key={clientId}
- className='file-preview'
- data-client-id={clientId}
- >
- <img
- className='spinner'
- src={loadingGif}
- />
- <a
- className='file-preview__remove'
- onClick={this.handleRemove.bind(this, clientId)}
- >
- <i className='fa fa-remove'/>
- </a>
- </div>
- );
- });
-
- return (
- <div
- className='file-preview__container'
- ref='container'
- >
- {previews}
- </div>
- );
- }
-}
diff --git a/webapp/components/file_upload.jsx b/webapp/components/file_upload.jsx
deleted file mode 100644
index 479dfa145..000000000
--- a/webapp/components/file_upload.jsx
+++ /dev/null
@@ -1,430 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import 'jquery-dragster/jquery.dragster.js';
-import ReactDOM from 'react-dom';
-import Constants from 'utils/constants.jsx';
-import ChannelStore from 'stores/channel_store.jsx';
-import DelayedAction from 'utils/delayed_action.jsx';
-import * as UserAgent from 'utils/user_agent.jsx';
-import * as FileUtils from 'utils/file_utils';
-import * as Utils from 'utils/utils.jsx';
-
-import {intlShape, injectIntl, defineMessages} from 'react-intl';
-
-import {uploadFile} from 'actions/file_actions.jsx';
-
-const holders = defineMessages({
- limited: {
- id: 'file_upload.limited',
- defaultMessage: 'Uploads limited to {count, number} files maximum. Please use additional posts for more files.'
- },
- filesAbove: {
- id: 'file_upload.filesAbove',
- defaultMessage: 'Files above {max}MB could not be uploaded: {filenames}'
- },
- fileAbove: {
- id: 'file_upload.fileAbove',
- defaultMessage: 'File above {max}MB could not be uploaded: {filename}'
- },
- pasted: {
- id: 'file_upload.pasted',
- defaultMessage: 'Image Pasted at '
- }
-});
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-const OverlayTimeout = 500;
-
-class FileUpload extends React.Component {
- constructor(props) {
- super(props);
-
- this.uploadFiles = this.uploadFiles.bind(this);
- this.handleChange = this.handleChange.bind(this);
- this.handleDrop = this.handleDrop.bind(this);
- this.registerDragEvents = this.registerDragEvents.bind(this);
- this.cancelUpload = this.cancelUpload.bind(this);
- this.pasteUpload = this.pasteUpload.bind(this);
- this.keyUpload = this.keyUpload.bind(this);
- this.handleMaxUploadReached = this.handleMaxUploadReached.bind(this);
-
- this.state = {
- requests: {}
- };
- }
-
- fileUploadSuccess(channelId, data) {
- this.props.onFileUpload(data.file_infos, data.client_ids, channelId);
-
- const requests = Object.assign({}, this.state.requests);
- for (var j = 0; j < data.client_ids.length; j++) {
- Reflect.deleteProperty(requests, data.client_ids[j]);
- }
- this.setState({requests});
- }
-
- fileUploadFail(clientId, channelId, err) {
- this.props.onUploadError(err, clientId, channelId);
- }
-
- uploadFiles(files) {
- const sortedFiles = Utils.sortFilesByName(files);
-
- // clear any existing errors
- this.props.onUploadError(null);
-
- const channelId = this.props.channelId || ChannelStore.getCurrentId();
-
- const uploadsRemaining = Constants.MAX_UPLOAD_FILES - this.props.getFileCount(channelId);
- let numUploads = 0;
-
- // keep track of how many files have been too large
- const tooLargeFiles = [];
-
- for (let i = 0; i < sortedFiles.length && numUploads < uploadsRemaining; i++) {
- if (sortedFiles[i].size > global.mm_config.MaxFileSize) {
- tooLargeFiles.push(sortedFiles[i]);
- continue;
- }
-
- // generate a unique id that can be used by other components to refer back to this upload
- const clientId = Utils.generateId();
-
- const request = uploadFile(
- sortedFiles[i],
- sortedFiles[i].name,
- channelId,
- clientId,
- this.fileUploadSuccess.bind(this, channelId),
- this.fileUploadFail.bind(this, clientId, channelId)
- );
-
- const requests = this.state.requests;
- requests[clientId] = request;
- this.setState({requests});
-
- this.props.onUploadStart([clientId], channelId);
-
- numUploads += 1;
- }
-
- const {formatMessage} = this.props.intl;
- if (sortedFiles.length > uploadsRemaining) {
- this.props.onUploadError(formatMessage(holders.limited, {count: Constants.MAX_UPLOAD_FILES}));
- } else if (tooLargeFiles.length > 1) {
- var tooLargeFilenames = tooLargeFiles.map((file) => file.name).join(', ');
-
- this.props.onUploadError(formatMessage(holders.filesAbove, {max: (global.mm_config.MaxFileSize / 1048576), filenames: tooLargeFilenames}));
- } else if (tooLargeFiles.length > 0) {
- this.props.onUploadError(formatMessage(holders.fileAbove, {max: (global.mm_config.MaxFileSize / 1048576), filename: tooLargeFiles[0].name}));
- }
- }
-
- handleChange(e) {
- if (e.target.files.length > 0) {
- this.uploadFiles(e.target.files);
-
- Utils.clearFileInput(e.target);
- }
-
- this.props.onFileUploadChange();
- }
-
- handleDrop(e) {
- if (!FileUtils.canUploadFiles()) {
- this.props.onUploadError(Utils.localizeMessage('file_upload.disabled', 'File attachments are disabled.'));
- return;
- }
-
- this.props.onUploadError(null);
-
- var files = e.originalEvent.dataTransfer.files;
-
- if (typeof files !== 'string' && files.length) {
- this.uploadFiles(files);
- }
- }
-
- componentDidMount() {
- if (this.props.postType === 'post') {
- this.registerDragEvents('.row.main', '.center-file-overlay');
- } else if (this.props.postType === 'comment') {
- this.registerDragEvents('.post-right__container', '.right-file-overlay');
- }
-
- document.addEventListener('paste', this.pasteUpload);
- document.addEventListener('keydown', this.keyUpload);
- }
-
- registerDragEvents(containerSelector, overlaySelector) {
- const self = this;
-
- const overlay = $(overlaySelector);
-
- const dragTimeout = new DelayedAction(() => {
- if (!overlay.hasClass('hidden')) {
- overlay.addClass('hidden');
- }
- });
-
- let dragsterActions = {};
- if (FileUtils.canUploadFiles()) {
- dragsterActions = {
- enter(dragsterEvent, e) {
- var files = e.originalEvent.dataTransfer;
-
- if (Utils.isFileTransfer(files)) {
- $(overlaySelector).removeClass('hidden');
- }
- },
- leave(dragsterEvent, e) {
- var files = e.originalEvent.dataTransfer;
-
- if (Utils.isFileTransfer(files) && !overlay.hasClass('hidden')) {
- overlay.addClass('hidden');
- }
-
- dragTimeout.cancel();
- },
- over() {
- dragTimeout.fireAfter(OverlayTimeout);
- },
- drop(dragsterEvent, e) {
- if (!overlay.hasClass('hidden')) {
- overlay.addClass('hidden');
- }
-
- dragTimeout.cancel();
-
- self.handleDrop(e);
- }
- };
- } else {
- dragsterActions = {
- drop(dragsterEvent, e) {
- self.handleDrop(e);
- }
- };
- }
-
- $(containerSelector).dragster(dragsterActions);
-
- this.props.onFileUploadChange();
- }
-
- componentWillUnmount() {
- let target;
- if (this.props.postType === 'post') {
- target = $('.row.main');
- } else {
- target = $('.post-right__container');
- }
-
- document.removeEventListener('paste', this.pasteUpload);
- document.removeEventListener('keydown', this.keyUpload);
-
- // jquery-dragster doesn't provide a function to unregister itself so do it manually
- target.off('dragenter dragleave dragover drop dragster:enter dragster:leave dragster:over dragster:drop');
- }
-
- pasteUpload(e) {
- const {formatMessage} = this.props.intl;
-
- if (!e.clipboardData || !e.clipboardData.items) {
- return;
- }
-
- const textarea = ReactDOM.findDOMNode(this.props.getTarget());
- if (!textarea || !textarea.contains(e.target)) {
- return;
- }
-
- this.props.onUploadError(null);
-
- const items = [];
- for (let i = 0; i < e.clipboardData.items.length; i++) {
- const item = e.clipboardData.items[i];
-
- if (item.kind !== 'file') {
- continue;
- }
-
- items.push(item);
- }
-
- // This looks redundant, but must be done this way due to
- // setState being an asynchronous call
- if (items && items.length > 0) {
- if (!FileUtils.canUploadFiles()) {
- this.props.onUploadError(Utils.localizeMessage('file_upload.disabled', 'File attachments are disabled.'));
- return;
- }
-
- var numToUpload = Math.min(Constants.MAX_UPLOAD_FILES - this.props.getFileCount(ChannelStore.getCurrentId()), items.length);
-
- if (items.length > numToUpload) {
- this.props.onUploadError(formatMessage(holders.limited, {count: Constants.MAX_UPLOAD_FILES}));
- }
-
- const channelId = this.props.channelId || ChannelStore.getCurrentId();
-
- for (var i = 0; i < items.length && i < numToUpload; i++) {
- var file = items[i].getAsFile();
- if (!file) {
- continue;
- }
-
- // generate a unique id that can be used by other components to refer back to this file upload
- var clientId = Utils.generateId();
-
- var d = new Date();
- var hour;
- if (d.getHours() < 10) {
- hour = '0' + d.getHours();
- } else {
- hour = String(d.getHours());
- }
- var min;
- if (d.getMinutes() < 10) {
- min = '0' + d.getMinutes();
- } else {
- min = String(d.getMinutes());
- }
-
- var ext = '';
- if (file.name) {
- if (file.name.includes('.')) {
- ext = file.name.substr(file.name.lastIndexOf('.'));
- }
- } else if (items[i].type.includes('/')) {
- ext = '.' + items[i].type.split('/')[1].toLowerCase();
- }
-
- const name = formatMessage(holders.pasted) + d.getFullYear() + '-' + (d.getMonth() + 1) + '-' + d.getDate() + ' ' + hour + '-' + min + ext;
-
- const request = uploadFile(
- file,
- name,
- channelId,
- clientId,
- this.fileUploadSuccess.bind(this, channelId),
- this.fileUploadFail.bind(this, clientId)
- );
-
- const requests = this.state.requests;
- requests[clientId] = request;
- this.setState({requests});
-
- this.props.onUploadStart([clientId], channelId);
- }
-
- if (numToUpload > 0) {
- this.props.onFileUploadChange();
- }
- }
- }
-
- keyUpload(e) {
- if (Utils.cmdOrCtrlPressed(e) && e.keyCode === Constants.KeyCodes.U) {
- e.preventDefault();
-
- if (!FileUtils.canUploadFiles()) {
- this.props.onUploadError(Utils.localizeMessage('file_upload.disabled', 'File attachments are disabled.'));
- return;
- }
-
- if ((this.props.postType === 'post' && document.activeElement.id === 'post_textbox') ||
- (this.props.postType === 'comment' && document.activeElement.id === 'reply_textbox')) {
- $(this.refs.fileInput).focus().trigger('click');
- }
- }
- }
-
- cancelUpload(clientId) {
- const requests = Object.assign({}, this.state.requests);
- const request = requests[clientId];
-
- if (request) {
- request.abort();
-
- Reflect.deleteProperty(requests, clientId);
- this.setState({requests});
- }
- }
-
- handleMaxUploadReached(e) {
- e.preventDefault();
-
- const {formatMessage} = this.props.intl;
-
- this.props.onUploadError(formatMessage(holders.limited, {count: Constants.MAX_UPLOAD_FILES}));
-
- return false;
- }
-
- render() {
- let multiple = true;
- if (UserAgent.isMobileApp()) {
- // iOS WebViews don't upload videos properly in multiple mode
- multiple = false;
- }
-
- let accept = '';
- if (UserAgent.isIosChrome()) {
- // iOS Chrome can't upload videos at all
- accept = 'image/*';
- }
-
- const channelId = this.props.channelId || ChannelStore.getCurrentId();
- const uploadsRemaining = Constants.MAX_UPLOAD_FILES - this.props.getFileCount(channelId);
-
- let fileDiv;
- if (FileUtils.canUploadFiles()) {
- fileDiv = (
- <div className='icon icon--attachment'>
- <span
- dangerouslySetInnerHTML={{__html: Constants.ATTACHMENT_ICON_SVG}}
- />
- <input
- ref='fileInput'
- type='file'
- onChange={this.handleChange}
- onClick={uploadsRemaining > 0 ? this.props.onClick : this.handleMaxUploadReached}
- multiple={multiple}
- accept={accept}
- />
- </div>
- );
- }
-
- return (
- <span
- ref='input'
- className={uploadsRemaining <= 0 ? ' btn-file__disabled' : ''}
- >
- {fileDiv}
- </span>
- );
- }
-}
-
-FileUpload.propTypes = {
- intl: intlShape.isRequired,
- onUploadError: PropTypes.func,
- getFileCount: PropTypes.func,
- getTarget: PropTypes.func.isRequired,
- onClick: PropTypes.func,
- onFileUpload: PropTypes.func,
- onUploadStart: PropTypes.func,
- onFileUploadChange: PropTypes.func,
- onTextDrop: PropTypes.func,
- channelId: PropTypes.string,
- postType: PropTypes.string
-};
-
-export default injectIntl(FileUpload, {withRef: true});
diff --git a/webapp/components/file_upload_overlay.jsx b/webapp/components/file_upload_overlay.jsx
deleted file mode 100644
index 865733fe6..000000000
--- a/webapp/components/file_upload_overlay.jsx
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {FormattedMessage} from 'react-intl';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-import fileOverlayImage from 'images/filesOverlay.png';
-import overlayLogoImage from 'images/logoWhite.png';
-
-export default function FileUploadOverlay(props) {
- var overlayClass = 'file-overlay hidden';
- if (props.overlayType === 'right') {
- overlayClass += ' right-file-overlay';
- } else if (props.overlayType === 'center') {
- overlayClass += ' center-file-overlay';
- }
-
- return (
- <div className={overlayClass}>
- <div className='overlay__indent'>
- <div className='overlay__circle'>
- <img
- className='overlay__files'
- src={fileOverlayImage}
- alt='Files'
- />
- <span><i className='fa fa-upload'/>
- <FormattedMessage
- id='upload_overlay.info'
- defaultMessage='Drop a file to upload it.'
- />
- </span>
- <img
- className='overlay__logo'
- src={overlayLogoImage}
- width='100'
- alt='Logo'
- />
- </div>
- </div>
- </div>
- );
-}
-
-FileUploadOverlay.propTypes = {
- overlayType: PropTypes.string
-};
diff --git a/webapp/components/filtered_user_list.jsx b/webapp/components/filtered_user_list.jsx
deleted file mode 100644
index 3a7190b2d..000000000
--- a/webapp/components/filtered_user_list.jsx
+++ /dev/null
@@ -1,245 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import ReactDOM from 'react-dom';
-import * as UserAgent from 'utils/user_agent.jsx';
-import UserList from './user_list.jsx';
-
-import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl';
-
-const holders = defineMessages({
- member: {
- id: 'filtered_user_list.member',
- defaultMessage: 'Member'
- },
- search: {
- id: 'filtered_user_list.search',
- defaultMessage: 'Search members'
- },
- anyTeam: {
- id: 'filtered_user_list.any_team',
- defaultMessage: 'All Users'
- },
- teamOnly: {
- id: 'filtered_user_list.team_only',
- defaultMessage: 'Members of this Team'
- }
-});
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-class FilteredUserList extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleFilterChange = this.handleFilterChange.bind(this);
- this.handleListChange = this.handleListChange.bind(this);
- this.filterUsers = this.filterUsers.bind(this);
-
- this.state = {
- filter: '',
- users: this.filterUsers(props.teamMembers, props.users),
- selected: 'team',
- teamMembers: props.teamMembers
- };
- }
-
- componentWillReceiveProps(nextProps) {
- // assume the user list is immutable
- if (this.props.users !== nextProps.users) {
- this.setState({
- users: this.filterUsers(nextProps.teamMembers, nextProps.users)
- });
- }
-
- if (this.props.teamMembers !== nextProps.teamMembers) {
- this.setState({
- users: this.filterUsers(nextProps.teamMembers, nextProps.users)
- });
- }
- }
-
- componentDidMount() {
- // only focus the search box on desktop so that we don't cause the keyboard to open on mobile
- if (!UserAgent.isMobile()) {
- ReactDOM.findDOMNode(this.refs.filter).focus();
- }
- }
-
- componentDidUpdate(prevProps, prevState) {
- if (prevState.filter !== this.state.filter) {
- $(ReactDOM.findDOMNode(this.refs.userList)).scrollTop(0);
- }
- }
-
- filterUsers(teamMembers, users) {
- if (!teamMembers || teamMembers.length === 0) {
- return users;
- }
-
- var filteredUsers = users.filter((user) => {
- for (const index in teamMembers) {
- if (teamMembers.hasOwnProperty(index) && teamMembers[index].user_id === user.id) {
- if (teamMembers[index].delete_at > 0) {
- return false;
- }
-
- return true;
- }
- }
-
- return false;
- });
-
- return filteredUsers;
- }
-
- handleFilterChange(e) {
- this.setState({
- filter: e.target.value
- });
- }
-
- handleListChange(e) {
- var users = this.props.users;
-
- if (e.target.value === 'team') {
- users = this.filterUsers(this.props.teamMembers, this.props.users);
- }
-
- this.setState({
- selected: e.target.value,
- users
- });
- }
-
- render() {
- const {formatMessage} = this.props.intl;
-
- let users = this.state.users;
-
- if (this.state.filter && this.state.filter.length > 0) {
- const filter = this.state.filter.toLowerCase();
-
- users = users.filter((user) => {
- return user.username.toLowerCase().indexOf(filter) !== -1 ||
- (user.first_name && user.first_name.toLowerCase().indexOf(filter) !== -1) ||
- (user.last_name && user.last_name.toLowerCase().indexOf(filter) !== -1) ||
- (user.email && user.email.toLowerCase().indexOf(filter) !== -1) ||
- (user.nickname && user.nickname.toLowerCase().indexOf(filter) !== -1);
- });
- }
-
- let count;
- if (users.length === this.state.users.length) {
- count = (
- <FormattedMessage
- id='filtered_user_list.count'
- defaultMessage='{count, number} {count, plural, one {member} other {members}}'
- values={{
- count: users.length
- }}
- />
- );
- } else {
- count = (
- <FormattedMessage
- id='filtered_user_list.countTotal'
- defaultMessage='{count, number} {count, plural, one {member} other {members}} of {total, number} total'
- values={{
- count: users.length,
- total: this.state.users.length
- }}
- />
- );
- }
-
- let teamToggle;
-
- let teamMembers = this.props.teamMembers;
- if (this.props.showTeamToggle) {
- teamMembers = [];
-
- teamToggle = (
- <div className='member-select__container'>
- <select
- className='form-control'
- id='restrictList'
- ref='restrictList'
- defaultValue='team'
- onChange={this.handleListChange}
- >
- <option value='any'>{formatMessage(holders.anyTeam)}</option>
- <option value='team'>{formatMessage(holders.teamOnly)}</option>
- </select>
- <span
- className='member-show'
- >
- <FormattedMessage
- id='filtered_user_list.show'
- defaultMessage='Filter:'
- />
- </span>
- </div>
- );
- }
-
- return (
- <div
- className='filtered-user-list'
- style={this.props.style}
- >
- <div className='filter-row'>
- <div className='col-sm-6'>
- <input
- ref='filter'
- className='form-control filter-textbox'
- placeholder={formatMessage(holders.search)}
- onInput={this.handleFilterChange}
- />
- </div>
- <div className='col-sm-6'>
- {teamToggle}
- </div>
- <div className='col-sm-12'>
- <span className='member-count pull-left'>{count}</span>
- </div>
- </div>
- <div
- ref='userList'
- className='more-modal__list'
- >
- <UserList
- users={users}
- teamMembers={teamMembers}
- actions={this.props.actions}
- actionProps={this.props.actionProps}
- />
- </div>
- </div>
- );
- }
-}
-
-FilteredUserList.defaultProps = {
- users: [],
- teamMembers: [],
- actions: [],
- actionProps: {},
- showTeamToggle: false
-};
-
-FilteredUserList.propTypes = {
- intl: intlShape.isRequired,
- users: PropTypes.arrayOf(PropTypes.object),
- teamMembers: PropTypes.arrayOf(PropTypes.object),
- actions: PropTypes.arrayOf(PropTypes.func),
- actionProps: PropTypes.object,
- showTeamToggle: PropTypes.bool,
- style: PropTypes.object
-};
-
-export default injectIntl(FilteredUserList);
diff --git a/webapp/components/form_error.jsx b/webapp/components/form_error.jsx
deleted file mode 100644
index e8c3d1e98..000000000
--- a/webapp/components/form_error.jsx
+++ /dev/null
@@ -1,84 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-export default class FormError extends React.Component {
- static get propTypes() {
- // accepts either a single error or an array of errors
- return {
- type: PropTypes.node,
- error: PropTypes.node,
- margin: PropTypes.bool,
- errors: PropTypes.arrayOf(PropTypes.node)
- };
- }
-
- static get defaultProps() {
- return {
- error: null,
- errors: []
- };
- }
-
- render() {
- if (!this.props.error && this.props.errors.length === 0) {
- return null;
- }
-
- // look for the first truthy error to display
- let message = this.props.error;
-
- if (!message) {
- for (const error of this.props.errors) {
- if (error) {
- message = error;
- }
- }
- }
-
- if (!message) {
- return null;
- }
-
- if (this.props.type === 'modal') {
- return (
- <div className='form-group'>
- <label className='col-sm-12 has-error'>
- {message}
- </label>
- </div>
- );
- }
-
- if (this.props.type === 'backstage') {
- return (
- <div className='pull-left has-error'>
- <label className='control-label'>
- {message}
- </label>
- </div>
- );
- }
-
- if (this.props.margin) {
- return (
- <div className='form-group has-error'>
- <label className='control-label'>
- {message}
- </label>
- </div>
- );
- }
-
- return (
- <div className='col-sm-12 has-error'>
- <label className='control-label'>
- {message}
- </label>
- </div>
- );
- }
-}
diff --git a/webapp/components/get_android_app/get_android_app.jsx b/webapp/components/get_android_app/get_android_app.jsx
deleted file mode 100644
index cf82b1179..000000000
--- a/webapp/components/get_android_app/get_android_app.jsx
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import {FormattedMessage} from 'react-intl';
-import {Link} from 'react-router';
-
-import MattermostIcon from 'images/favicon/android-chrome-192x192.png';
-import Nexus6Mockup from 'images/nexus-6p-mockup.png';
-
-export default function GetAndroidApp() {
- return (
- <div className='get-app get-android-app'>
- <h1 className='get-app__header'>
- <FormattedMessage
- id='get_app.androidHeader'
- defaultMessage='Mattermost works best if you switch to our Android app'
- />
- </h1>
- <hr/>
- <div>
- <img
- className='get-android-app__icon'
- src={MattermostIcon}
- />
- <div className='get-android-app__app-info'>
- <span className='get-android-app__app-name'>
- <FormattedMessage
- id='get_app.androidAppName'
- defaultMessage='Mattermost for Android'
- />
- </span>
- <span className='get-android-app__app-creator'>
- <FormattedMessage
- id='get_app.mattermostInc'
- defaultMessage='Mattermost, Inc'
- />
- </span>
- </div>
- </div>
- <a
- className='btn btn-primary get-android-app__continue'
- href={global.window.mm_config.AndroidAppDownloadLink}
- >
- <FormattedMessage
- id='get_app.continue'
- defaultMessage='Continue'
- />
- </a>
- <img
- className='get-app__screenshot'
- src={Nexus6Mockup}
- />
- <span className='get-app__continue-with-browser'>
- <FormattedMessage
- id='get_app.continueWithBrowser'
- defaultMessage='Or {link}'
- values={{
- link: (
- <Link to='/switch_team'>
- <FormattedMessage
- id='get_app.continueWithBrowserLink'
- defaultMessage='continue with browser'
- />
- </Link>
- )
- }}
- />
- </span>
- </div>
- );
-}
diff --git a/webapp/components/get_ios_app/get_ios_app.jsx b/webapp/components/get_ios_app/get_ios_app.jsx
deleted file mode 100644
index 3d371ef11..000000000
--- a/webapp/components/get_ios_app/get_ios_app.jsx
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import {FormattedMessage} from 'react-intl';
-import {Link} from 'react-router';
-
-import AppStoreButton from 'images/app-store-button.png';
-import IPhone6Mockup from 'images/iphone-6-mockup.png';
-
-export default function GetIosApp() {
- return (
- <div className='get-app get-ios-app'>
- <h1 className='get-app__header'>
- <FormattedMessage
- id='get_app.iosHeader'
- defaultMessage='Mattermost works best if you switch to our iPhone app'
- />
- </h1>
- <hr/>
- <a
- className='get-ios-app__app-store-link'
- href={global.window.mm_config.IosAppDownloadLink}
- rel='noopener noreferrer'
- >
- <img src={AppStoreButton}/>
- </a>
- <img
- className='get-app__screenshot'
- src={IPhone6Mockup}
- />
- <h2 className='get-ios-app__already-have-it'>
- <FormattedMessage
- id='get_app.alreadyHaveIt'
- defaultMessage='Already have it?'
- />
- </h2>
- <a
- className='btn btn-primary get-ios-app__open-mattermost'
- href='mattermost://'
- >
- <FormattedMessage
- id='get_app.openMattermost'
- defaultMessage='Open Mattermost'
- />
- </a>
- <span className='get-app__continue-with-browser'>
- <FormattedMessage
- id='get_app.continueWithBrowser'
- defaultMessage='Or {link}'
- values={{
- link: (
- <Link to='/switch_team'>
- <FormattedMessage
- id='get_app.continueWithBrowserLink'
- defaultMessage='continue with browser'
- />
- </Link>
- )
- }}
- />
- </span>
- </div>
- );
-}
diff --git a/webapp/components/get_link_modal.jsx b/webapp/components/get_link_modal.jsx
deleted file mode 100644
index f9866c500..000000000
--- a/webapp/components/get_link_modal.jsx
+++ /dev/null
@@ -1,139 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import {FormattedMessage} from 'react-intl';
-import {Modal} from 'react-bootstrap';
-
-export default class GetLinkModal extends React.Component {
- constructor(props) {
- super(props);
-
- this.onHide = this.onHide.bind(this);
-
- this.copyLink = this.copyLink.bind(this);
-
- this.state = {
- copiedLink: false
- };
- }
-
- onHide() {
- this.setState({copiedLink: false});
-
- this.props.onHide();
- }
-
- copyLink() {
- const textarea = this.refs.textarea;
- textarea.focus();
- textarea.setSelectionRange(0, this.props.link.length);
-
- try {
- if (document.execCommand('copy')) {
- this.setState({copiedLink: true});
- } else {
- this.setState({copiedLink: false});
- }
- } catch (err) {
- this.setState({copiedLink: false});
- }
- }
-
- render() {
- let helpText = null;
- if (this.props.helpText) {
- helpText = (
- <p>
- {this.props.helpText}
- <br/>
- <br/>
- </p>
- );
- }
-
- let copyLink = null;
- if (document.queryCommandSupported('copy')) {
- copyLink = (
- <button
- data-copy-btn='true'
- type='button'
- className='btn btn-primary pull-left'
- onClick={this.copyLink}
- >
- <FormattedMessage
- id='get_link.copy'
- defaultMessage='Copy Link'
- />
- </button>
- );
- }
-
- const linkText = (
- <textarea
- className='form-control no-resize min-height'
- ref='textarea'
- value={this.props.link}
- onClick={this.copyLink}
- readOnly={true}
- />
- );
-
- let copyLinkConfirm = null;
- if (this.state.copiedLink) {
- copyLinkConfirm = (
- <p className='alert alert-success alert--confirm'>
- <i className='fa fa-check'/>
- <FormattedMessage
- id='get_link.clipboard'
- defaultMessage=' Link copied'
- />
- </p>
- );
- }
-
- return (
- <Modal
- show={this.props.show}
- onHide={this.onHide}
- >
- <Modal.Header closeButton={true}>
- <h4 className='modal-title'>{this.props.title}</h4>
- </Modal.Header>
- <Modal.Body>
- {helpText}
- {linkText}
- </Modal.Body>
- <Modal.Footer>
- <button
- type='button'
- className='btn btn-default'
- onClick={this.onHide}
- >
- <FormattedMessage
- id='get_link.close'
- defaultMessage='Close'
- />
- </button>
- {copyLink}
- {copyLinkConfirm}
- </Modal.Footer>
- </Modal>
- );
- }
-}
-
-GetLinkModal.propTypes = {
- show: PropTypes.bool.isRequired,
- onHide: PropTypes.func.isRequired,
- title: PropTypes.string.isRequired,
- helpText: PropTypes.string,
- link: PropTypes.string.isRequired
-};
-
-GetLinkModal.defaultProps = {
- helpText: null
-};
diff --git a/webapp/components/get_post_link_modal.jsx b/webapp/components/get_post_link_modal.jsx
deleted file mode 100644
index 4f89f1c9d..000000000
--- a/webapp/components/get_post_link_modal.jsx
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import GetLinkModal from './get_link_modal.jsx';
-import ModalStore from 'stores/modal_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-
-import React from 'react';
-import PureRenderMixin from 'react-addons-pure-render-mixin';
-
-export default class GetPostLinkModal extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleToggle = this.handleToggle.bind(this);
- this.hide = this.hide.bind(this);
-
- this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
-
- this.state = {
- show: false,
- post: {}
- };
- }
-
- componentDidMount() {
- ModalStore.addModalListener(Constants.ActionTypes.TOGGLE_GET_POST_LINK_MODAL, this.handleToggle);
- }
-
- componentWillUnmount() {
- ModalStore.removeModalListener(Constants.ActionTypes.TOGGLE_GET_POST_LINK_MODAL, this.handleToggle);
- }
-
- handleToggle(value, args) {
- this.setState({
- show: value,
- post: args.post
- });
- }
-
- hide() {
- this.setState({
- show: false
- });
- }
-
- render() {
- return (
- <GetLinkModal
- show={this.state.show}
- onHide={this.hide}
- title={Utils.localizeMessage('get_post_link_modal.title', 'Copy Permalink')}
- helpText={Utils.localizeMessage('get_post_link_modal.help', 'The link below allows authorized users to see your post.')}
- link={TeamStore.getCurrentTeamUrl() + '/pl/' + this.state.post.id}
- />
- );
- }
-}
diff --git a/webapp/components/get_public_link_modal.jsx b/webapp/components/get_public_link_modal.jsx
deleted file mode 100644
index 90e2271bb..000000000
--- a/webapp/components/get_public_link_modal.jsx
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import {getPublicLink} from 'actions/file_actions.jsx';
-import Constants from 'utils/constants.jsx';
-import ModalStore from 'stores/modal_store.jsx';
-import PureRenderMixin from 'react-addons-pure-render-mixin';
-import * as Utils from 'utils/utils.jsx';
-
-import GetLinkModal from './get_link_modal.jsx';
-
-export default class GetPublicLinkModal extends React.Component {
- constructor(props) {
- super(props);
-
- this.handlePublicLink = this.handlePublicLink.bind(this);
- this.handleToggle = this.handleToggle.bind(this);
- this.hide = this.hide.bind(this);
-
- this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
-
- this.state = {
- show: false,
- fileId: '',
- link: ''
- };
- }
-
- componentDidMount() {
- ModalStore.addModalListener(Constants.ActionTypes.TOGGLE_GET_PUBLIC_LINK_MODAL, this.handleToggle);
- }
-
- componentDidUpdate(prevProps, prevState) {
- if (this.state.show && !prevState.show) {
- getPublicLink(this.state.fileId, this.handlePublicLink);
- }
- }
-
- componentWillUnmount() {
- ModalStore.removeModalListener(Constants.ActionTypes.TOGGLE_GET_PUBLIC_LINK_MODAL, this.handleToggle);
- }
-
- handlePublicLink(link) {
- this.setState({
- link
- });
- }
-
- handleToggle(value, args) {
- this.setState({
- show: value,
- fileId: args.fileId,
- link: ''
- });
- }
-
- hide() {
- this.setState({
- show: false
- });
- }
-
- render() {
- return (
- <GetLinkModal
- show={this.state.show}
- onHide={this.hide}
- title={Utils.localizeMessage('get_public_link_modal.title', 'Copy Public Link')}
- helpText={Utils.localizeMessage('get_public_link_modal.help', 'The link below allows anyone to see this file without being registered on this server.')}
- link={this.state.link}
- />
- );
- }
-}
diff --git a/webapp/components/get_team_invite_link_modal.jsx b/webapp/components/get_team_invite_link_modal.jsx
deleted file mode 100644
index 78ccb368e..000000000
--- a/webapp/components/get_team_invite_link_modal.jsx
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import GetLinkModal from './get_link_modal.jsx';
-import ModalStore from 'stores/modal_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-
-import React from 'react';
-import PureRenderMixin from 'react-addons-pure-render-mixin';
-
-export default class GetTeamInviteLinkModal extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleToggle = this.handleToggle.bind(this);
-
- this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
-
- this.state = {
- show: false
- };
- }
-
- componentDidMount() {
- ModalStore.addModalListener(Constants.ActionTypes.TOGGLE_GET_TEAM_INVITE_LINK_MODAL, this.handleToggle);
- }
-
- componentWillUnmount() {
- ModalStore.removeModalListener(Constants.ActionTypes.TOGGLE_GET_TEAM_INVITE_LINK_MODAL, this.handleToggle);
- }
-
- handleToggle(value) {
- this.setState({
- show: value
- });
- }
-
- render() {
- let helpText;
- if (global.window.mm_config.EnableUserCreation === 'true') {
- helpText = Utils.localizeMessage('get_team_invite_link_modal.help', 'Send teammates the link below for them to sign-up to this team site. The Team Invite Link can be shared with multiple teammates as it does not change unless it\'s regenerated in Team Settings by a Team Admin.');
- } else {
- helpText = Utils.localizeMessage('get_team_invite_link_modal.helpDisabled', 'User creation has been disabled for your team. Please ask your team administrator for details.');
- }
-
- return (
- <GetLinkModal
- show={this.state.show}
- onHide={() => this.setState({show: false})}
- title={Utils.localizeMessage('get_team_invite_link_modal.title', 'Team Invite Link')}
- helpText={helpText}
- link={TeamStore.getCurrentInviteLink()}
- />
- );
- }
-}
diff --git a/webapp/components/header_footer_template.jsx b/webapp/components/header_footer_template.jsx
deleted file mode 100644
index 9f6d40631..000000000
--- a/webapp/components/header_footer_template.jsx
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import {FormattedMessage} from 'react-intl';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class NotLoggedIn extends React.Component {
- componentDidMount() {
- $('body').addClass('sticky');
- $('#root').addClass('container-fluid');
- }
- componentWillUnmount() {
- $('body').removeClass('sticky');
- $('#root').removeClass('container-fluid');
- }
- render() {
- const content = [];
-
- if (global.window.mm_config.HelpLink) {
- content.push(
- <a
- key='help_link'
- id='help_link'
- className='pull-right footer-link'
- target='_blank'
- rel='noopener noreferrer'
- href={global.window.mm_config.HelpLink}
- >
- <FormattedMessage id='web.footer.help'/>
- </a>
- );
- }
-
- content.push(
- <a
- key='terms_link'
- id='terms_link'
- className='pull-right footer-link'
- target='_blank'
- rel='noopener noreferrer'
- href={global.window.mm_config.TermsOfServiceLink}
- >
- <FormattedMessage id='web.footer.terms'/>
- </a>
- );
-
- if (global.window.mm_config.PrivacyPolicyLink) {
- content.push(
- <a
- key='privacy_link'
- id='privacy_link'
- className='pull-right footer-link'
- target='_blank'
- rel='noopener noreferrer'
- href={global.window.mm_config.PrivacyPolicyLink}
- >
- <FormattedMessage id='web.footer.privacy'/>
- </a>
- );
- }
-
- if (global.window.mm_config.AboutLink) {
- content.push(
- <a
- key='about_link'
- id='about_link'
- className='pull-right footer-link'
- target='_blank'
- rel='noopener noreferrer'
- href={global.window.mm_config.AboutLink}
- >
- <FormattedMessage id='web.footer.about'/>
- </a>
- );
- }
-
- return (
- <div className='inner-wrap'>
- <div className='row content'>
- {this.props.children}
- <div className='footer-push'/>
- </div>
- <div className='row footer'>
- <div className='footer-pane col-xs-12'>
- <div className='col-xs-12'>
- <span className='pull-right footer-site-name'>{'Mattermost'}</span>
- </div>
- <div className='col-xs-12'>
- <span className='pull-right footer-link copyright'>
- {`© 2015-${new Date().getFullYear()} Mattermost, Inc.`}
- </span>
- {content}
- </div>
- </div>
- </div>
- </div>
- );
- }
-}
-
-NotLoggedIn.defaultProps = {
-};
-
-NotLoggedIn.propTypes = {
- children: PropTypes.object
-};
diff --git a/webapp/components/help/components/attaching.jsx b/webapp/components/help/components/attaching.jsx
deleted file mode 100644
index 3013d744d..000000000
--- a/webapp/components/help/components/attaching.jsx
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {localizeMessage} from 'utils/utils.jsx';
-import {formatText} from 'utils/text_formatting.jsx';
-import {FormattedMessage} from 'react-intl';
-import {Link} from 'react-router/es6';
-
-import React from 'react';
-
-export default function HelpAttaching() {
- const message = [];
- message.push(localizeMessage('help.attaching.title', '# Attaching Files\n_____'));
- message.push(localizeMessage('help.attaching.methods', '## Attachment Methods\nAttach a file by drag and drop or by clicking the attachment icon in the message input box.'));
- message.push(localizeMessage('help.attaching.dragdrop', '#### Drag and Drop\nUpload a file or selection of files by dragging the files from your computer into the RHS or center pane. Dragging and dropping attaches the files to the message input box, then you can optionally type a message and press **ENTER** to post.'));
- message.push(localizeMessage('help.attaching.icon', '#### Attachment Icon\nAlternatively, upload files by clicking the grey paperclip icon inside the message input box. This opens up your system file viewer where you can navigate to the desired files and then click **Open** to upload the files to the message input box. Optionally type a message and then press **ENTER** to post.'));
- message.push(localizeMessage('help.attaching.pasting', '#### Pasting Images\nOn Chrome and Edge browsers, it is also possible to upload files by pasting them from the clipboard. This is not yet supported on other browsers.'));
- message.push(localizeMessage('help.attaching.previewer', '## File Previewer\nMattermost has a built in file previewer that is used to view media, download files and share public links. Click the thumbnail of an attached file to open it in the file previewer.'));
- message.push('\n');
- message.push(localizeMessage('help.attaching.publicLinks', '#### Sharing Public Links\nPublic links allow you to share file attachments with people outside your Mattermost team. Open the file previewer by clicking on the thumbnail of an attachment, then click **Get Public Link**. This opens a dialog box with a link to copy. When the link is shared and opened by another user, the file will automatically download.'));
- message.push(localizeMessage('help.attaching.publicLinks2', 'If **Get Public Link** is not visible in the file previewer and you prefer the feature enabled, you can request that your System Admin enable the feature from the System Console under **Security** > **Public Links**.'));
- message.push('\n');
- message.push(localizeMessage('help.attaching.downloading', '#### Downloading Files\nDownload an attached file by clicking the download icon next to the file thumbnail or by opening the file previewer and clicking **Download**.'));
- message.push(localizeMessage('help.attaching.supported', '#### Supported Media Types\nIf you are trying to preview a media type that is not supported, the file previewer will open a standard media attachment icon. Supported media formats depend heavily on your browser and operating system, but the following formats are supported by Mattermost on most browsers:'));
- message.push(localizeMessage('help.attaching.supportedList', '- Images: BMP, GIF, JPG, JPEG, PNG\n- Video: MP4\n- Audio: MP3, M4A\n- Documents: PDF'));
- message.push(localizeMessage('help.attaching.notSupported', 'Document preview (Word, Excel, PPT) is not yet supported.'));
- message.push(localizeMessage('help.attaching.limitations', '## File Size Limitations\nMattermost supports a maximum of five attached files per post, each with a maximum file size of 50Mb.'));
-
- return (
- <div>
- <span
- dangerouslySetInnerHTML={{__html: formatText(message.join('\n\n'))}}
- />
- <p className='links'>
- <FormattedMessage
- id='help.learnMore'
- defaultMessage='Learn more about:'
- />
- </p>
- <ul>
- <li>
- <Link to='/help/messaging'>
- <FormattedMessage
- id='help.link.messaging'
- defaultMessage='Basic Messaging'
- />
- </Link>
- </li>
- <li>
- <Link to='/help/composing'>
- <FormattedMessage
- id='help.link.composing'
- defaultMessage='Composing Messages and Replies'
- />
- </Link>
- </li>
- <li>
- <Link to='/help/mentioning'>
- <FormattedMessage
- id='help.link.mentioning'
- defaultMessage='Mentioning Teammates'
- />
- </Link>
- </li>
- <li>
- <Link to='/help/formatting'>
- <FormattedMessage
- id='help.link.formatting'
- defaultMessage='Formatting Messages using Markdown'
- />
- </Link>
- </li>
- <li>
- <Link to='/help/commands'>
- <FormattedMessage
- id='help.link.commands'
- defaultMessage='Executing Commands'
- />
- </Link>
- </li>
- </ul>
- </div>
- );
-}
diff --git a/webapp/components/help/components/commands.jsx b/webapp/components/help/components/commands.jsx
deleted file mode 100644
index 41dc736b5..000000000
--- a/webapp/components/help/components/commands.jsx
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {localizeMessage} from 'utils/utils.jsx';
-import {formatText} from 'utils/text_formatting.jsx';
-import {FormattedMessage} from 'react-intl';
-import {Link} from 'react-router/es6';
-
-import React from 'react';
-
-export default function HelpCommands() {
- const message = [];
- message.push(localizeMessage('help.commands.title', '# Executing Commands\n___'));
- message.push(localizeMessage('help.commands.intro', 'Slash commands perform operations in Mattermost by typing into the text input box. Enter a `/` followed by a command and some arguments to perform actions.\n\nBuilt-in slash commands come with all Mattermost installations and custom slash commands are configurable to interact with external applications. Learn about configuring custom slash commands on the [developer slash command documentation page](http://docs.mattermost.com/developer/slash-commands.html).'));
- message.push(localizeMessage('help.commands.builtin', '## Built-in Commands\nThe following slash commands are available on all Mattermost installations:'));
- message.push('![commands](https://docs.mattermost.com/_images/slashCommandsTable1.PNG)');
- message.push(localizeMessage('help.commands.builtin2', 'Begin by typing `/` and a list of slash command options appears above the text input box. The autocomplete suggestions help by providing a format example in black text and a short description of the slash command in grey text.'));
- message.push('![autocomplete](https://docs.mattermost.com/_images/slashCommandsAutocomplete.PNG)');
- message.push(localizeMessage('help.commands.custom', '## Custom Commands\nCustom slash commands integrate with external applications. For example, a team might configure a custom slash command to check internal health records with `/patient joe smith` or check the weekly weather forecast in a city with `/weather toronto week`. Check with your System Admin or open the autocomplete list by typing `/` to determine if your team configured any custom slash commands.'));
- message.push(localizeMessage('help.commands.custom2', 'Custom slash commands are disabled by default and can be enabled by the System Admin in the **System Console** > **Integrations** > **Webhooks and Commands**. Learn about configuring custom slash commands on the [developer slash command documentation page](http://docs.mattermost.com/developer/slash-commands.html).'));
-
- return (
- <div>
- <span
- dangerouslySetInnerHTML={{__html: formatText(message.join('\n\n'))}}
- />
- <p className='links'>
- <FormattedMessage
- id='help.learnMore'
- defaultMessage='Learn more about:'
- />
- </p>
- <ul>
- <li>
- <Link to='/help/messaging'>
- <FormattedMessage
- id='help.link.messaging'
- defaultMessage='Basic Messaging'
- />
- </Link>
- </li>
- <li>
- <Link to='/help/composing'>
- <FormattedMessage
- id='help.link.composing'
- defaultMessage='Composing Messages and Replies'
- />
- </Link>
- </li>
- <li>
- <Link to='/help/mentioning'>
- <FormattedMessage
- id='help.link.mentioning'
- defaultMessage='Mentioning Teammates'
- />
- </Link>
- </li>
- <li>
- <Link to='/help/formatting'>
- <FormattedMessage
- id='help.link.formatting'
- defaultMessage='Formatting Messages using Markdown'
- />
- </Link>
- </li>
- <li>
- <Link to='/help/attaching'>
- <FormattedMessage
- id='help.link.attaching'
- defaultMessage='Attaching Files'
- />
- </Link>
- </li>
- </ul>
- </div>
- );
-}
diff --git a/webapp/components/help/components/composing.jsx b/webapp/components/help/components/composing.jsx
deleted file mode 100644
index 41b620d26..000000000
--- a/webapp/components/help/components/composing.jsx
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {localizeMessage} from 'utils/utils.jsx';
-import {formatText} from 'utils/text_formatting.jsx';
-import {FormattedMessage} from 'react-intl';
-import {Link} from 'react-router/es6';
-
-import React from 'react';
-
-export default function HelpComposing() {
- const message = [];
- message.push(localizeMessage('help.composing.title', '# Sending Messages\n_____'));
- message.push(localizeMessage('help.composing.types', '## Message Types\nReply to posts to keep conversations organized in threads.'));
- message.push(localizeMessage('help.composing.posts', '#### Posts\nPosts can be considered parent messages. They are the messages that often start a thread of replies. Posts are composed and sent from the text input box at the bottom of the center pane.'));
- message.push(localizeMessage('help.composing.replies', '#### Replies\nReply to a message by clicking the reply icon next to any message text. This action opens the right-hand-side (RHS) where you can see the message thread, then compose and send your reply. Replies are indented slightly in the center pane to indicate that they are child messages of a parent post.\n\nWhen composing a reply in the right-hand side, click the expand/collapse icon with two arrows at the top of the sidebar to make things easier to read.'));
- message.push(localizeMessage('help.composing.posting', '## Posting a Message\nWrite a message by typing into the text input box, then press ENTER to send it. Use SHIFT+ENTER to create a new line without sending a message. To send messages by pressing CTRL+ENTER go to **Main Menu > Account Settings > Send messages on CTRL+ENTER**.'));
- message.push(localizeMessage('help.composing.editing', '## Editing a Message\nEdit a message by clicking the **[...]** icon next to any message text that you’ve composed, then click **Edit**. After making modifications to the message text, press **ENTER** to save the modifications. Message edits do not trigger new @mention notifications, desktop notifications or notification sounds.'));
- message.push(localizeMessage('help.composing.deleting', '## Deleting a message\nDelete a message by clicking the **[...]** icon next to any message text that you’ve composed, then click **Delete**. System and Team Admins can delete any message on their system or team.'));
- message.push(localizeMessage('help.composing.linking', '## Linking to a message\nThe **Permalink** feature creates a link to any message. Sharing this link with other users in the channel lets them view the linked message in the Message Archives. Users who are not a member of the channel where the message was posted cannot view the permalink. Get the permalink to any message by clicking the **[...]** icon next to the message text > **Permalink** > **Copy Link**.'));
-
- return (
- <div>
- <span
- dangerouslySetInnerHTML={{__html: formatText(message.join('\n\n'))}}
- />
- <p className='links'>
- <FormattedMessage
- id='help.learnMore'
- defaultMessage='Learn more about:'
- />
- </p>
- <ul>
- <li>
- <Link to='/help/messaging'>
- <FormattedMessage
- id='help.link.messaging'
- defaultMessage='Basic Messaging'
- />
- </Link>
- </li>
- <li>
- <Link to='/help/mentioning'>
- <FormattedMessage
- id='help.link.mentioning'
- defaultMessage='Mentioning Teammates'
- />
- </Link>
- </li>
- <li>
- <Link to='/help/formatting'>
- <FormattedMessage
- id='help.link.formatting'
- defaultMessage='Formatting Messages using Markdown'
- />
- </Link>
- </li>
- <li>
- <Link to='/help/attaching'>
- <FormattedMessage
- id='help.link.attaching'
- defaultMessage='Attaching Files'
- />
- </Link>
- </li>
- <li>
- <Link to='/help/commands'>
- <FormattedMessage
- id='help.link.commands'
- defaultMessage='Executing Commands'
- />
- </Link>
- </li>
- </ul>
- </div>
- );
-}
diff --git a/webapp/components/help/components/formatting.jsx b/webapp/components/help/components/formatting.jsx
deleted file mode 100644
index 312df252a..000000000
--- a/webapp/components/help/components/formatting.jsx
+++ /dev/null
@@ -1,133 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {localizeMessage} from 'utils/utils.jsx';
-import {formatText} from 'utils/text_formatting.jsx';
-import {FormattedMessage} from 'react-intl';
-import {Link} from 'react-router/es6';
-
-import React from 'react';
-
-export default function HelpFormatting() {
- const message = [];
- message.push(localizeMessage('help.formatting.title', '# Formatting Text\n_____'));
- message.push(localizeMessage('help.formatting.intro', 'Markdown makes it easy to format messages. Type a message as you normally would, and use these rules to render it with special formatting.'));
- message.push(localizeMessage('help.formatting.style', '## Text Style\n\nYou can use either `_` or `*` around a word to make it italic. Use two to make it bold.\n\n* `_italics_` renders as _italics_\n* `**bold**` renders as **bold**\n* `**_bold-italic_**` renders as **_bold-italics_**\n* `~~strikethrough~~` renders as ~~strikethrough~~'));
- message.push(localizeMessage('help.formatting.code', '## Code Block\n\nCreate a code block by indenting each line by four spaces, or by placing ``` on the line above and below your code.'));
- message.push(localizeMessage('help.formatting.example', 'Example:') + '\n\n');
- message.push(' ```\n ' + localizeMessage('help.formatting.codeBlock', 'code block') + '\n ```');
- message.push(localizeMessage('help.formatting.renders', 'Renders as:'));
- message.push('```\n' + localizeMessage('help.formatting.codeBlock', 'code block') + '\n```');
- message.push(localizeMessage('help.formatting.syntax', '### Syntax Highlighting\n\nTo add syntax highlighting, type the language to be highlighted after the ``` at the beginning of the code block. Mattermost also offers four different code themes (GitHub, Solarized Dark, Solarized Light, Monokai) that can be changed in **Account Settings** > **Display** > **Theme** > **Custom Theme** > **Center Channel Styles**'));
- message.push(localizeMessage('help.formatting.supportedSyntax', 'Supported languages are:\n`as`, `applescript`, `osascript`, `scpt`, `bash`, `sh`, `zsh`, `clj`, `boot`, `cl2`, `cljc`, `cljs`, `cljs.hl`, `cljscm`, `cljx`, `hic`, `coffee`, `_coffee`, `cake`, `cjsx`, `cson`, `iced`, `cpp`, `c`, `cc`, `h`, `c++`, `h++`, `hpp`, `cs`, `csharp`, `css`, `d`, `di`, `dart`, `delphi`, `dpr`, `dfm`, `pas`, `pascal`, `freepascal`, `lazarus`, `lpr`, `lfm`, `diff`, `django`, `jinja`, `dockerfile`, `docker`, `erl`, `f90`, `f95`, `fsharp`, `fs`, `gcode`, `nc`, `go`, `groovy`, `handlebars`, `hbs`, `html.hbs`, `html.handlebars`, `hs`, `hx`, `java`, `jsp`, `js`, `jsx`, `json`, `jl`, `kt`, `ktm`, `kts`, `less`, `lisp`, `lua`, `mk`, `mak`, `md`, `mkdown`, `mkd`, `matlab`, `m`, `mm`, `objc`, `obj-c`, `ml`, `perl`, `pl`, `php`, `php3`, `php4`, `php5`, `php6`, `ps`, `ps1`, `pp`, `py`, `gyp`, `r`, `ruby`, `rb`, `gemspec`, `podspec`, `thor`, `irb`, `rs`, `scala`, `scm`, `sld`, `scss`, `st`, `sql`, `swift`, `tex`, `vbnet`, `vb`, `bas`, `vbs`, `v`, `veo`, `xml`, `html`, `xhtml`, `rss`, `atom`, `xsl`, `plist`, `yaml`'));
- message.push(localizeMessage('help.formatting.example', 'Example:') + '\n\n');
- message.push(' ```go\n' + localizeMessage('help.formatting.syntaxEx', ' package main\n import "fmt"\n func main() {\n fmt.Println("Hello, 世界")\n }') + '\n ```');
- message.push(localizeMessage('help.formatting.renders', 'Renders as:'));
- message.push(localizeMessage('help.formatting.githubTheme', '**GitHub Theme**'));
- message.push('![go syntax-highlighting](https://docs.mattermost.com/_images/syntax-highlighting-github.PNG)');
- message.push(localizeMessage('help.formatting.solirizedDarkTheme', '**Solarized Dark Theme**'));
- message.push('![go syntax-highlighting](https://docs.mattermost.com/_images/syntax-highlighting-sol-dark.PNG)');
- message.push(localizeMessage('help.formatting.solirizedLightTheme', '**Solarized Light Theme**'));
- message.push('![go syntax-highlighting](https://docs.mattermost.com/_images/syntax-highlighting-sol-light.PNG)');
- message.push(localizeMessage('help.formatting.monokaiTheme', '**Monokai Theme**'));
- message.push('![go syntax-highlighting](https://docs.mattermost.com/_images/syntax-highlighting-monokai.PNG)');
- message.push(localizeMessage('help.formatting.inline', '## In-line Code\n\nCreate in-line monospaced font by surrounding it with backticks.'));
- message.push('```\n`monospace`\n```');
- message.push(localizeMessage('help.formatting.renders', 'Renders as:') + ' `monospace`');
- message.push(localizeMessage('help.formatting.links', '## Links\n\nCreate labeled links by putting the desired text in square brackets and the associated link in normal brackets.'));
- message.push('`' + localizeMessage('help.formatting.linkEx', '[Check out Mattermost!](https://about.mattermost.com/)') + '`');
- message.push(localizeMessage('help.formatting.renders', 'Renders as:') + ' ' + localizeMessage('help.formatting.linkEx', '[Check out Mattermost!](https://about.mattermost.com/)'));
- message.push(localizeMessage('help.formatting.images', '## In-line Images\n\nCreate in-line images using an `!` followed by the alt text in square brackets and the link in normal brackets. Add hover text by placing it in quotes after the link.'));
- message.push('```\n' + localizeMessage('help.formatting.imagesExample', '![alt text](link "hover text")\n\nand\n\n[![Build Status](https://travis-ci.org/mattermost/platform.svg?branch=master)](https://travis-ci.org/mattermost/platform) [![Github](https://assets-cdn.github.com/favicon.ico)](https://github.com/mattermost/platform)') + '\n```');
- message.push(localizeMessage('help.formatting.renders', 'Renders as:'));
- message.push(localizeMessage('help.formatting.imagesExample', '![alt text](link "hover text")\n\nand\n\n[![Build Status](https://travis-ci.org/mattermost/platform.svg?branch=master)](https://travis-ci.org/mattermost/platform) [![Github](https://assets-cdn.github.com/favicon.ico)](https://github.com/mattermost/platform)'));
- message.push(localizeMessage('help.formatting.emojis', '## Emojis\n\nOpen the emoji autocomplete by typing `:`. A full list of emojis can be found [here](http://www.emoji-cheat-sheet.com/). It is also possible to create your own [Custom Emoji](http://docs.mattermost.com/help/settings/custom-emoji.html) if the emoji you want to use doesn\'t exist.'));
- message.push('```\n' + localizeMessage('help.formatting.emojiExample', ':smile: :+1: :sheep:') + '\n```');
- message.push(localizeMessage('help.formatting.renders', 'Renders as:'));
- message.push(localizeMessage('help.formatting.emojiExample', ':smile: :+1: :sheep:'));
- message.push(localizeMessage('help.formatting.lines', '## Lines\n\nCreate a line by using three `*`, `_`, or `-`.'));
- message.push('`***` ' + localizeMessage('help.formatting.renders', 'Renders as:') + '\n***');
- message.push(localizeMessage('help.formatting.quotes', '## Block quotes\n\nCreate block quotes using `>`.'));
- message.push(localizeMessage('help.formatting.quotesExample', '`> block quotes` renders as:'));
- message.push(localizeMessage('help.formatting.quotesRender', '> block quotes'));
- message.push(localizeMessage('help.formatting.lists', '## Lists\n\nCreate a list by using `*` or `-` as bullets. Indent a bullet point by adding two spaces in front of it.'));
- message.push('```\n' + localizeMessage('help.formatting.listExample', '* list item one\n* list item two\n * item two sub-point') + '\n```');
- message.push(localizeMessage('help.formatting.renders', 'Renders as:'));
- message.push(localizeMessage('help.formatting.listExample', '* list item one\n* list item two\n * item two sub-point'));
- message.push(localizeMessage('help.formatting.ordered', 'Make it an ordered list by using numbers instead:'));
- message.push('```\n' + localizeMessage('help.formatting.orderedExample', '1. Item one\n2. Item two') + '\n```');
- message.push(localizeMessage('help.formatting.renders', 'Renders as:'));
- message.push(localizeMessage('help.formatting.orderedExample', '1. Item one\n2. Item two'));
- message.push(localizeMessage('help.formatting.checklist', 'Make a task list by including square brackets:'));
- message.push('```\n' + localizeMessage('help.formatting.checklistExample', '- [ ] Item one\n- [ ] Item two\n- [x] Completed item') + '\n```');
- message.push(localizeMessage('help.formatting.renders', 'Renders as:'));
- message.push(localizeMessage('help.formatting.checklistExample', '- [ ] Item one\n- [ ] Item two\n- [x] Completed item'));
- message.push(localizeMessage('help.formatting.tables', '## Tables\n\nCreate a table by placing a dashed line under the header row and separating the columns with a pipe `|`. (The columns don’t need to line up exactly for it to work). Choose how to align table columns by including colons `:` within the header row.'));
- message.push('```\n' + localizeMessage('help.formatting.tableExample', '| Left-Aligned | Center Aligned | Right Aligned |\n| :------------ |:---------------:| -----:|\n| Left column 1 | this text | $100 |\n| Left column 2 | is | $10 |\n| Left column 3 | centered | $1 |') + '\n```');
- message.push(localizeMessage('help.formatting.renders', 'Renders as:'));
- message.push(localizeMessage('help.formatting.tableExample', '| Left-Aligned | Center Aligned | Right Aligned |\n| :------------ |:---------------:| -----:|\n| Left column 1 | this text | $100 |\n| Left column 2 | is | $10 |\n| Left column 3 | centered | $1 |'));
- message.push(localizeMessage('help.formatting.headings', '## Headings\n\nMake a heading by typing # and a space before your title. For smaller headings, use more #’s.'));
- message.push('```\n' + localizeMessage('help.formatting.headingsExample', '## Large Heading\n### Smaller Heading\n#### Even Smaller Heading') + '\n```');
- message.push(localizeMessage('help.formatting.renders', 'Renders as:'));
- message.push(localizeMessage('help.formatting.headingsExample', '## Large Heading\n### Smaller Heading\n#### Even Smaller Heading'));
- message.push(localizeMessage('help.formatting.headings2', 'Alternatively, you can underline the text using `===` or `---` to create headings.'));
- message.push('```\n' + localizeMessage('help.formatting.headings2Example', 'Large Heading\n-------------') + '\n```');
- message.push(localizeMessage('help.formatting.renders', 'Renders as:'));
- message.push(localizeMessage('help.formatting.headings2Example', 'Large Heading\n-------------'));
-
- return (
- <div>
- <span
- dangerouslySetInnerHTML={{__html: formatText(message.join('\n\n'))}}
- />
- <p className='links'>
- <FormattedMessage
- id='help.learnMore'
- defaultMessage='Learn more about:'
- />
- </p>
- <ul>
- <li>
- <Link to='/help/messaging'>
- <FormattedMessage
- id='help.link.messaging'
- defaultMessage='Basic Messaging'
- />
- </Link>
- </li>
- <li>
- <Link to='/help/composing'>
- <FormattedMessage
- id='help.link.composing'
- defaultMessage='Composing Messages and Replies'
- />
- </Link>
- </li>
- <li>
- <Link to='/help/mentioning'>
- <FormattedMessage
- id='help.link.mentioning'
- defaultMessage='Mentioning Teammates'
- />
- </Link>
- </li>
- <li>
- <Link to='/help/attaching'>
- <FormattedMessage
- id='help.link.attaching'
- defaultMessage='Attaching Files'
- />
- </Link>
- </li>
- <li>
- <Link to='/help/commands'>
- <FormattedMessage
- id='help.link.commands'
- defaultMessage='Executing Commands'
- />
- </Link>
- </li>
- </ul>
- </div>
- );
-}
diff --git a/webapp/components/help/components/mentioning.jsx b/webapp/components/help/components/mentioning.jsx
deleted file mode 100644
index fa3bf2d7d..000000000
--- a/webapp/components/help/components/mentioning.jsx
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {localizeMessage} from 'utils/utils.jsx';
-import {formatText} from 'utils/text_formatting.jsx';
-import {FormattedMessage} from 'react-intl';
-import {Link} from 'react-router/es6';
-
-import React from 'react';
-
-export default function HelpMentioning() {
- const message = [];
- message.push(localizeMessage('help.mentioning.title', '# Mentioning Teammates\n_____'));
- message.push(localizeMessage('help.mentioning.mentions', '## @Mentions\nUse @mentions to get the attention of specific team members.'));
- message.push(localizeMessage('help.mentioning.username', '#### @Username\nYou can mention a teammate by using the `@` symbol plus their username to send them a mention notification.\n\nType `@` to bring up a list of team members who can be mentioned. To filter the list, type the first few letters of any username, first name, last name, or nickname. The **Up** and **Down** arrow keys can then be used to scroll through entries in the list, and pressing **ENTER** will select which user to mention. Once selected, the username will automatically replace the full name or nickname.\nThe following example sends a special mention notification to **alice** that alerts her of the channel and message where she has been mentioned. If **alice** is away from Mattermost and has [email notifications](http://docs.mattermost.com/help/getting-started/configuring-notifications.html#email-notifications) turned on, then she will receive an email alert of her mention along with the message text.'));
- message.push('```\n' + localizeMessage('help.mentioning.usernameExample', '@alice how did your interview go with the new candidate?') + '\n```');
- message.push('\n\n ');
- message.push(localizeMessage('help.mentioning.usernameCont', 'If the user you mentioned does not belong to the channel, a System Message will be posted to let you know. This is a temporary message only seen by the person who triggered it. To add the mentioned user to the channel, go to the dropdown menu beside the channel name and select **Add Members**.'));
- message.push(localizeMessage('help.mentioning.channel', '#### @Channel\nYou can mention an entire channel by typing `@channel`. All members of the channel will receive a mention notification that behaves the same way as if the members had been mentioned personally.'));
- message.push('```\n' + localizeMessage('help.mentioning.channelExample', '@channel great work on interviews this week. I think we found some excellent potential candidates!') + '```\n');
- message.push(localizeMessage('help.mentioning.triggers', '## Words That Trigger Mentions\nIn addition to being notified by @username and @channel, you can customize words that trigger mention notifications in **Account Settings** > **Notifications** > **Words that trigger mentions**. By default, you will receive mention notifications on your first name, and you can add more words by typing them into the input box separated by commas. This is useful if you want to be notified of all posts on certain topics, for example, "interviewing" or "marketing".'));
- message.push(localizeMessage('help.mentioning.recent', '## Recent Mentions\nClick `@` next to the search box to query for your most recent @mentions and words that trigger mentions. Click **Jump** next to a search result in the RHS to jump the center pane to the channel and location of the message with the mention.'));
-
- return (
- <div>
- <span
- dangerouslySetInnerHTML={{__html: formatText(message.join('\n\n'))}}
- />
- <p className='links'>
- <FormattedMessage
- id='help.learnMore'
- defaultMessage='Learn more about:'
- />
- </p>
- <ul>
- <li>
- <Link to='/help/messaging'>
- <FormattedMessage
- id='help.link.messaging'
- defaultMessage='Basic Messaging'
- />
- </Link>
- </li>
- <li>
- <Link to='/help/composing'>
- <FormattedMessage
- id='help.link.composing'
- defaultMessage='Composing Messages and Replies'
- />
- </Link>
- </li>
- <li>
- <Link to='/help/formatting'>
- <FormattedMessage
- id='help.link.formatting'
- defaultMessage='Formatting Messages using Markdown'
- />
- </Link>
- </li>
- <li>
- <Link to='/help/attaching'>
- <FormattedMessage
- id='help.link.attaching'
- defaultMessage='Attaching Files'
- />
- </Link>
- </li>
- <li>
- <Link to='/help/commands'>
- <FormattedMessage
- id='help.link.commands'
- defaultMessage='Executing Commands'
- />
- </Link>
- </li>
- </ul>
- </div>
- );
-}
diff --git a/webapp/components/help/components/messaging.jsx b/webapp/components/help/components/messaging.jsx
deleted file mode 100644
index a36e8e772..000000000
--- a/webapp/components/help/components/messaging.jsx
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {localizeMessage} from 'utils/utils.jsx';
-import {formatText} from 'utils/text_formatting.jsx';
-import {FormattedMessage} from 'react-intl';
-import {Link} from 'react-router/es6';
-
-import React from 'react';
-
-export default function HelpMessaging() {
- const message = [];
- message.push(localizeMessage('help.messaging.title', '# Messaging Basics\n_____'));
- message.push(localizeMessage('help.messaging.write', '**Write messages** using the text input box at the bottom of Mattermost. Press ENTER to send a message. Use SHIFT+ENTER to create a new line without sending a message.'));
- message.push(localizeMessage('help.messaging.reply', '**Reply to messages** by clicking the reply arrow next to the message text.'));
- message.push('![reply arrow](https://docs.mattermost.com/_images/replyIcon.PNG)');
- message.push(localizeMessage('help.messaging.notify', '**Notify teammates** when they are needed by typing `@username`.'));
- message.push(localizeMessage('help.messaging.format', '**Format your messages** using Markdown that supports text styling, headings, links, emoticons, code blocks, block quotes, tables, lists and in-line images.'));
- message.push('![markdown](https://docs.mattermost.com/_images/messagesTable1.PNG)');
- message.push(localizeMessage('help.messaging.emoji', '**Quickly add emoji** by typing ":", which will open an emoji autocomplete. If the existing emoji don\'t cover what you want to express, you can also create your own [Custom Emoji](http://docs.mattermost.com/help/settings/custom-emoji.html).'));
- message.push(localizeMessage('help.messaging.attach', '**Attach files** by dragging and dropping into Mattermost or clicking the attachment icon in the text input box.'));
-
- return (
- <div>
- <span
- dangerouslySetInnerHTML={{__html: formatText(message.join('\n\n'))}}
- />
- <p className='links'>
- <FormattedMessage
- id='help.learnMore'
- defaultMessage='Learn more about:'
- />
- </p>
- <ul>
- <li>
- <Link to='/help/composing'>
- <FormattedMessage
- id='help.link.composing'
- defaultMessage='Composing Messages and Replies'
- />
- </Link>
- </li>
- <li>
- <Link to='/help/mentioning'>
- <FormattedMessage
- id='help.link.mentioning'
- defaultMessage='Mentioning Teammates'
- />
- </Link>
- </li>
- <li>
- <Link to='/help/formatting'>
- <FormattedMessage
- id='help.link.formatting'
- defaultMessage='Formatting Messages using Markdown'
- />
- </Link>
- </li>
- <li>
- <Link to='/help/attaching'>
- <FormattedMessage
- id='help.link.attaching'
- defaultMessage='Attaching Files'
- />
- </Link>
- </li>
- <li>
- <Link to='/help/commands'>
- <FormattedMessage
- id='help.link.commands'
- defaultMessage='Executing Commands'
- />
- </Link>
- </li>
- </ul>
- </div>
- );
-}
diff --git a/webapp/components/help/help_controller.jsx b/webapp/components/help/help_controller.jsx
deleted file mode 100644
index b58a93320..000000000
--- a/webapp/components/help/help_controller.jsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import ReactDOM from 'react-dom';
-
-export default class HelpController extends React.Component {
- static get propTypes() {
- return {
- children: PropTypes.node.isRequired
- };
- }
-
- componentWillUpdate() {
- ReactDOM.findDOMNode(this).scrollIntoView();
- }
-
- render() {
- return (
- <div className='help'>
- <div className='container col-sm-10 col-sm-offset-1'>
- {this.props.children}
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/integrations/components/abstract_incoming_webhook.jsx b/webapp/components/integrations/components/abstract_incoming_webhook.jsx
deleted file mode 100644
index 1253842a7..000000000
--- a/webapp/components/integrations/components/abstract_incoming_webhook.jsx
+++ /dev/null
@@ -1,253 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import BackstageHeader from 'components/backstage/components/backstage_header.jsx';
-import ChannelSelect from 'components/channel_select.jsx';
-import {FormattedMessage} from 'react-intl';
-import FormError from 'components/form_error.jsx';
-import SpinnerButton from 'components/spinner_button.jsx';
-import {Link} from 'react-router/es6';
-
-export default class AbstractIncomingWebhook extends React.Component {
- static propTypes = {
-
- /**
- * The current team
- */
- team: PropTypes.object.isRequired,
-
- /**
- * The header text to render, has id and defaultMessage
- */
- header: PropTypes.object.isRequired,
-
- /**
- * The footer text to render, has id and defaultMessage
- */
- footer: PropTypes.object.isRequired,
-
- /**
- * The server error text after a failed action
- */
- serverError: PropTypes.string.isRequired,
-
- /**
- * The hook used to set the initial state
- */
- initialHook: PropTypes.object,
-
- /**
- * The async function to run when the action button is pressed
- */
- action: PropTypes.func.isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.state = this.getStateFromHook(this.props.initialHook || {});
- }
-
- getStateFromHook = (hook) => {
- return {
- displayName: hook.display_name || '',
- description: hook.description || '',
- channelId: hook.channel_id || '',
- saving: false,
- serverError: '',
- clientError: null
- };
- }
-
- handleSubmit = (e) => {
- e.preventDefault();
-
- if (this.state.saving) {
- return;
- }
-
- this.setState({
- saving: true,
- serverError: '',
- clientError: ''
- });
-
- if (!this.state.channelId) {
- this.setState({
- saving: false,
- clientError: (
- <FormattedMessage
- id='add_incoming_webhook.channelRequired'
- defaultMessage='A valid channel is required'
- />
- )
- });
-
- return;
- }
-
- const hook = {
- channel_id: this.state.channelId,
- display_name: this.state.displayName,
- description: this.state.description
- };
-
- this.props.action(hook).then(() => this.setState({saving: false}));
- }
-
- updateDisplayName = (e) => {
- this.setState({
- displayName: e.target.value
- });
- }
-
- updateDescription = (e) => {
- this.setState({
- description: e.target.value
- });
- }
-
- updateChannelId = (e) => {
- this.setState({
- channelId: e.target.value
- });
- }
-
- render() {
- var headerToRender = this.props.header;
- var footerToRender = this.props.footer;
-
- return (
- <div className='backstage-content'>
- <BackstageHeader>
- <Link to={`/${this.props.team.name}/integrations/incoming_webhooks`}>
- <FormattedMessage
- id='installed_incoming_webhooks.header'
- defaultMessage='Incoming Webhooks'
- />
- </Link>
- <FormattedMessage
- id={headerToRender.id}
- defaultMessage={headerToRender.defaultMessage}
- />
- </BackstageHeader>
- <div className='backstage-form'>
- <form
- className='form-horizontal'
- onSubmit={this.handleSubmit}
- >
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='displayName'
- >
- <FormattedMessage
- id='add_incoming_webhook.displayName'
- defaultMessage='Display Name'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='displayName'
- type='text'
- maxLength='64'
- className='form-control'
- value={this.state.displayName}
- onChange={this.updateDisplayName}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_incoming_webhook.displayName.help'
- defaultMessage='Display name for your incoming webhook made of up to 64 characters.'
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='description'
- >
- <FormattedMessage
- id='add_incoming_webhook.description'
- defaultMessage='Description'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='description'
- type='text'
- maxLength='128'
- className='form-control'
- value={this.state.description}
- onChange={this.updateDescription}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_incoming_webhook.description.help'
- defaultMessage='Description for your incoming webhook.'
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='channelId'
- >
- <FormattedMessage
- id='add_incoming_webhook.channel'
- defaultMessage='Channel'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <ChannelSelect
- id='channelId'
- value={this.state.channelId}
- onChange={this.updateChannelId}
- selectOpen={true}
- selectPrivate={true}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_incoming_webhook.channel.help'
- defaultMessage='Public or private channel that receives the webhook payloads. You must belong to the private channel when setting up the webhook.'
- />
- </div>
- </div>
- </div>
- <div className='backstage-form__footer'>
- <FormError
- type='backstage'
- errors={[this.props.serverError, this.state.clientError]}
- />
- <Link
- className='btn btn-sm'
- to={`/${this.props.team.name}/integrations/incoming_webhooks`}
- >
- <FormattedMessage
- id='add_incoming_webhook.cancel'
- defaultMessage='Cancel'
- />
- </Link>
- <SpinnerButton
- className='btn btn-primary'
- type='submit'
- spinning={this.state.saving}
- onClick={this.handleSubmit}
- >
- <FormattedMessage
- id={footerToRender.id}
- defaultMessage={footerToRender.defaultMessage}
- />
- </SpinnerButton>
- </div>
- </form>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/integrations/components/abstract_outgoing_webhook.jsx b/webapp/components/integrations/components/abstract_outgoing_webhook.jsx
deleted file mode 100644
index 397423395..000000000
--- a/webapp/components/integrations/components/abstract_outgoing_webhook.jsx
+++ /dev/null
@@ -1,483 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import TeamStore from 'stores/team_store.jsx';
-
-import {localizeMessage} from 'utils/utils.jsx';
-
-import BackstageHeader from 'components/backstage/components/backstage_header.jsx';
-import ChannelSelect from 'components/channel_select.jsx';
-import {FormattedMessage} from 'react-intl';
-import FormError from 'components/form_error.jsx';
-import {Link} from 'react-router/es6';
-import SpinnerButton from 'components/spinner_button.jsx';
-
-export default class AbstractOutgoingWebhook extends React.Component {
- static propTypes = {
-
- /**
- * The current team
- */
- team: PropTypes.object.isRequired,
-
- /**
- * The header text to render, has id and defaultMessage
- */
- header: PropTypes.object.isRequired,
-
- /**
- * The footer text to render, has id and defaultMessage
- */
- footer: PropTypes.object.isRequired,
-
- /**
- * Any extra component/node to render
- */
- renderExtra: PropTypes.node.isRequired,
-
- /**
- * The server error text after a failed action
- */
- serverError: PropTypes.string.isRequired,
-
- /**
- * The hook used to set the initial state
- */
- initialHook: PropTypes.object,
-
- /**
- * The async function to run when the action button is pressed
- */
- action: PropTypes.func.isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.state = this.getStateFromHook(this.props.initialHook || {});
- }
-
- getStateFromHook = (hook) => {
- let triggerWords = '';
- if (hook.trigger_words) {
- let i = 0;
- for (i = 0; i < hook.trigger_words.length; i++) {
- triggerWords += hook.trigger_words[i] + '\n';
- }
- }
-
- let callbackUrls = '';
- if (hook.callback_urls) {
- let i = 0;
- for (i = 0; i < hook.callback_urls.length; i++) {
- callbackUrls += hook.callback_urls[i] + '\n';
- }
- }
-
- return {
- displayName: hook.display_name || '',
- description: hook.description || '',
- contentType: hook.content_type || 'application/x-www-form-urlencoded',
- channelId: hook.channel_id || '',
- triggerWords,
- triggerWhen: hook.trigger_when || 0,
- callbackUrls,
- saving: false,
- clientError: null
- };
- }
-
- handleSubmit = (e) => {
- e.preventDefault();
-
- if (this.state.saving) {
- return;
- }
-
- this.setState({
- saving: true,
- clientError: ''
- });
-
- const triggerWords = [];
- if (this.state.triggerWords) {
- for (let triggerWord of this.state.triggerWords.split('\n')) {
- triggerWord = triggerWord.trim();
-
- if (triggerWord.length > 0) {
- triggerWords.push(triggerWord);
- }
- }
- }
-
- if (!this.state.channelId && triggerWords.length === 0) {
- this.setState({
- saving: false,
- clientError: (
- <FormattedMessage
- id='add_outgoing_webhook.triggerWordsOrChannelRequired'
- defaultMessage='A valid channel or a list of trigger words is required'
- />
- )
- });
-
- return;
- }
-
- const callbackUrls = [];
- for (let callbackUrl of this.state.callbackUrls.split('\n')) {
- callbackUrl = callbackUrl.trim();
-
- if (callbackUrl.length > 0) {
- callbackUrls.push(callbackUrl);
- }
- }
-
- if (callbackUrls.length === 0) {
- this.setState({
- saving: false,
- clientError: (
- <FormattedMessage
- id='add_outgoing_webhook.callbackUrlsRequired'
- defaultMessage='One or more callback URLs are required'
- />
- )
- });
-
- return;
- }
-
- const hook = {
- team_id: TeamStore.getCurrentId(),
- channel_id: this.state.channelId,
- trigger_words: triggerWords,
- trigger_when: parseInt(this.state.triggerWhen, 10),
- callback_urls: callbackUrls,
- display_name: this.state.displayName,
- content_type: this.state.contentType,
- description: this.state.description
- };
-
- this.props.action(hook).then(() => this.setState({saving: false}));
- }
-
- updateDisplayName = (e) => {
- this.setState({
- displayName: e.target.value
- });
- }
-
- updateDescription = (e) => {
- this.setState({
- description: e.target.value
- });
- }
-
- updateContentType = (e) => {
- this.setState({
- contentType: e.target.value
- });
- }
-
- updateChannelId = (e) => {
- this.setState({
- channelId: e.target.value
- });
- }
-
- updateTriggerWords = (e) => {
- this.setState({
- triggerWords: e.target.value
- });
- }
-
- updateTriggerWhen = (e) => {
- this.setState({
- triggerWhen: e.target.value
- });
- }
-
- updateCallbackUrls = (e) => {
- this.setState({
- callbackUrls: e.target.value
- });
- }
-
- render() {
- const contentTypeOption1 = 'application/x-www-form-urlencoded';
- const contentTypeOption2 = 'application/json';
-
- var headerToRender = this.props.header;
- var footerToRender = this.props.footer;
- var renderExtra = this.props.renderExtra;
-
- return (
- <div className='backstage-content'>
- <BackstageHeader>
- <Link to={`/${this.props.team.name}/integrations/outgoing_webhooks`}>
- <FormattedMessage
- id='installed_outgoing_webhooks.header'
- defaultMessage='Outgoing Webhooks'
- />
- </Link>
- <FormattedMessage
- id={headerToRender.id}
- defaultMessage={headerToRender.defaultMessage}
- />
- </BackstageHeader>
- <div className='backstage-form'>
- <form
- className='form-horizontal'
- onSubmit={this.handleSubmit}
- >
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='displayName'
- >
- <FormattedMessage
- id='add_outgoing_webhook.displayName'
- defaultMessage='Display Name'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='displayName'
- type='text'
- maxLength='64'
- className='form-control'
- value={this.state.displayName}
- onChange={this.updateDisplayName}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_outgoing_webhook.displayName.help'
- defaultMessage='Display name for your incoming webhook made of up to 64 characters.'
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='description'
- >
- <FormattedMessage
- id='add_outgoing_webhook.description'
- defaultMessage='Description'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='description'
- type='text'
- maxLength='128'
- className='form-control'
- value={this.state.description}
- onChange={this.updateDescription}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_outgoing_webhook.description.help'
- defaultMessage='Description for your incoming webhook.'
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='contentType'
- >
- <FormattedMessage
- id='add_outgoing_webhook.content_Type'
- defaultMessage='Content Type'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <select
- className='form-control'
- value={this.state.contentType}
- onChange={this.updateContentType}
- >
- <option
- value={contentTypeOption1}
- >
- {contentTypeOption1}
- </option>
- <option
- value={contentTypeOption2}
- >
- {contentTypeOption2}
- </option>
- </select>
- <div className='form__help'>
- <FormattedMessage
- id='add_outgoing_webhook.contentType.help1'
- defaultMessage='Choose the content type by which the response will be sent.'
- />
- </div>
- <div className='form__help'>
- <FormattedMessage
- id='add_outgoing_webhook.contentType.help2'
- defaultMessage='If application/x-www-form-urlencoded is chosen, the server assumes you will be encoding the parameters in a URL format.'
- />
- </div>
- <div className='form__help'>
- <FormattedMessage
- id='add_outgoing_webhook.contentType.help3'
- defaultMessage='If application/json is chosen, the server assumes you will posting JSON data.'
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='channelId'
- >
- <FormattedMessage
- id='add_outgoing_webhook.channel'
- defaultMessage='Channel'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <ChannelSelect
- id='channelId'
- value={this.state.channelId}
- onChange={this.updateChannelId}
- selectOpen={true}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_outgoing_webhook.channel.help'
- defaultMessage='Public channel to receive webhook payloads. Optional if at least one Trigger Word is specified.'
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='triggerWords'
- >
- <FormattedMessage
- id='add_outgoing_webhook.triggerWords'
- defaultMessage='Trigger Words (One Per Line)'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <textarea
- id='triggerWords'
- rows='3'
- maxLength='1000'
- className='form-control'
- value={this.state.triggerWords}
- onChange={this.updateTriggerWords}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_outgoing_webhook.triggerWords.help'
- defaultMessage='Messages that start with one of the specified words will trigger the outgoing webhook. Optional if Channel is selected.'
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='triggerWords'
- >
- <FormattedMessage
- id='add_outgoing_webhook.triggerWordsTriggerWhen'
- defaultMessage='Trigger When'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <select
- className='form-control'
- value={this.state.triggerWhen}
- onChange={this.updateTriggerWhen}
- >
- <option
- value='0'
- >
- {localizeMessage('add_outgoing_webhook.triggerWordsTriggerWhenFullWord', 'First word matches a trigger word exactly')}
- </option>
- <option
- value='1'
- >
- {localizeMessage('add_outgoing_webhook.triggerWordsTriggerWhenStartsWith', 'First word starts with a trigger word')}
- </option>
- </select>
- <div className='form__help'>
- <FormattedMessage
- id='add_outgoing_webhook.triggerWordsTriggerWhen.help'
- defaultMessage='Choose when to trigger the outgoing webhook; if the first word of a message matches a Trigger Word exactly, or if it starts with a Trigger Word.'
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='callbackUrls'
- >
- <FormattedMessage
- id='add_outgoing_webhook.callbackUrls'
- defaultMessage='Callback URLs (One Per Line)'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <textarea
- id='callbackUrls'
- rows='3'
- maxLength='1000'
- className='form-control'
- value={this.state.callbackUrls}
- onChange={this.updateCallbackUrls}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_outgoing_webhook.callbackUrls.help'
- defaultMessage='The URL that messages will be sent to.'
- />
- </div>
- </div>
- </div>
- <div className='backstage-form__footer'>
- <FormError
- type='backstage'
- errors={[this.props.serverError, this.state.clientError]}
- />
- <Link
- className='btn btn-sm'
- to={`/${this.props.team.name}/integrations/outgoing_webhooks`}
- >
- <FormattedMessage
- id='add_outgoing_webhook.cancel'
- defaultMessage='Cancel'
- />
- </Link>
- <SpinnerButton
- className='btn btn-primary'
- type='submit'
- spinning={this.state.saving}
- onClick={this.handleSubmit}
- >
- <FormattedMessage
- id={footerToRender.id}
- defaultMessage={footerToRender.defaultMessage}
- />
- </SpinnerButton>
- {renderExtra}
- </div>
- </form>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/integrations/components/add_command/add_command.jsx b/webapp/components/integrations/components/add_command/add_command.jsx
deleted file mode 100644
index 28f6115f9..000000000
--- a/webapp/components/integrations/components/add_command/add_command.jsx
+++ /dev/null
@@ -1,615 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import * as Utils from 'utils/utils.jsx';
-
-import BackstageHeader from 'components/backstage/components/backstage_header.jsx';
-import {FormattedMessage} from 'react-intl';
-import FormError from 'components/form_error.jsx';
-import {browserHistory, Link} from 'react-router/es6';
-import SpinnerButton from 'components/spinner_button.jsx';
-import Constants from 'utils/constants.jsx';
-
-const REQUEST_POST = 'P';
-const REQUEST_GET = 'G';
-
-export default class AddCommand extends React.PureComponent {
- static propTypes = {
-
- /**
- * The team data
- */
- team: PropTypes.object,
-
- /**
- * The request state for addCommand action. Contains status and error
- */
- addCommandRequest: PropTypes.object.isRequired,
-
- actions: PropTypes.shape({
-
- /**
- * The function to call to add new command
- */
- addCommand: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.state = {
- displayName: '',
- description: '',
- trigger: '',
- url: '',
- method: REQUEST_POST,
- username: '',
- iconUrl: '',
- autocomplete: false,
- autocompleteHint: '',
- autocompleteDescription: '',
- saving: false,
- serverError: '',
- clientError: null
- };
- }
-
- handleSubmit = (e) => {
- e.preventDefault();
-
- if (this.state.saving) {
- return;
- }
-
- this.setState({
- saving: true,
- serverError: '',
- clientError: ''
- });
-
- let triggerWord = this.state.trigger.trim().toLowerCase();
- if (triggerWord.indexOf('/') === 0) {
- triggerWord = triggerWord.substr(1);
- }
-
- const command = {
- display_name: this.state.displayName,
- description: this.state.description,
- trigger: triggerWord,
- url: this.state.url.trim(),
- method: this.state.method,
- username: this.state.username,
- icon_url: this.state.iconUrl,
- auto_complete: this.state.autocomplete,
- team_id: this.props.team.id
- };
-
- if (command.auto_complete) {
- command.auto_complete_desc = this.state.autocompleteDescription;
- command.auto_complete_hint = this.state.autocompleteHint;
- }
-
- if (!command.trigger) {
- this.setState({
- saving: false,
- clientError: (
- <FormattedMessage
- id='add_command.triggerRequired'
- defaultMessage='A trigger word is required'
- />
- )
- });
-
- return;
- }
-
- if (command.trigger.indexOf('/') === 0) {
- this.setState({
- saving: false,
- clientError: (
- <FormattedMessage
- id='add_command.triggerInvalidSlash'
- defaultMessage='A trigger word cannot begin with a /'
- />
- )
- });
-
- return;
- }
-
- if (command.trigger.indexOf(' ') !== -1) {
- this.setState({
- saving: false,
- clientError: (
- <FormattedMessage
- id='add_command.triggerInvalidSpace'
- defaultMessage='A trigger word must not contain spaces'
- />
- )
- });
- return;
- }
-
- if (command.trigger.length < Constants.MIN_TRIGGER_LENGTH ||
- command.trigger.length > Constants.MAX_TRIGGER_LENGTH) {
- this.setState({
- saving: false,
- clientError: (
- <FormattedMessage
- id='add_command.triggerInvalidLength'
- defaultMessage='A trigger word must contain between {min} and {max} characters'
- values={{
- min: Constants.MIN_TRIGGER_LENGTH,
- max: Constants.MAX_TRIGGER_LENGTH
- }}
- />
- )
- });
-
- return;
- }
-
- if (!command.url) {
- this.setState({
- saving: false,
- clientError: (
- <FormattedMessage
- id='add_command.urlRequired'
- defaultMessage='A request URL is required'
- />
- )
- });
-
- return;
- }
-
- this.props.actions.addCommand(command).then(
- (data) => {
- if (data) {
- browserHistory.push(`/${this.props.team.name}/integrations/commands/confirm?type=commands&id=${data.id}`);
- } else {
- this.setState({
- saving: false,
- serverError: this.props.addCommandRequest.error.message
- });
- }
- }
- );
- }
-
- updateDisplayName = (e) => {
- this.setState({
- displayName: e.target.value
- });
- }
-
- updateDescription = (e) => {
- this.setState({
- description: e.target.value
- });
- }
-
- updateTrigger = (e) => {
- this.setState({
- trigger: e.target.value
- });
- }
-
- updateUrl = (e) => {
- this.setState({
- url: e.target.value
- });
- }
-
- updateMethod = (e) => {
- this.setState({
- method: e.target.value
- });
- }
-
- updateUsername = (e) => {
- this.setState({
- username: e.target.value
- });
- }
-
- updateIconUrl = (e) => {
- this.setState({
- iconUrl: e.target.value
- });
- }
-
- updateAutocomplete = (e) => {
- this.setState({
- autocomplete: e.target.checked
- });
- }
-
- updateAutocompleteHint = (e) => {
- this.setState({
- autocompleteHint: e.target.value
- });
- }
-
- updateAutocompleteDescription = (e) => {
- this.setState({
- autocompleteDescription: e.target.value
- });
- }
-
- render() {
- let autocompleteFields = null;
- if (this.state.autocomplete) {
- autocompleteFields = [(
- <div
- key='autocompleteHint'
- className='form-group'
- >
- <label
- className='control-label col-sm-4'
- htmlFor='autocompleteHint'
- >
- <FormattedMessage
- id='add_command.autocompleteHint'
- defaultMessage='Autocomplete Hint'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='autocompleteHint'
- type='text'
- maxLength='1024'
- className='form-control'
- value={this.state.autocompleteHint}
- onChange={this.updateAutocompleteHint}
- placeholder={Utils.localizeMessage('add_command.autocompleteHint.placeholder', 'Example: [Patient Name]')}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_command.autocompleteHint.help'
- defaultMessage='(Optional) Arguments associated with your slash command, displayed as help in the autocomplete list.'
- />
- </div>
- </div>
- </div>
- ),
- (
- <div
- key='autocompleteDescription'
- className='form-group'
- >
- <label
- className='control-label col-sm-4'
- htmlFor='autocompleteDescription'
- >
- <FormattedMessage
- id='add_command.autocompleteDescription'
- defaultMessage='Autocomplete Description'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='description'
- type='text'
- maxLength='128'
- className='form-control'
- value={this.state.autocompleteDescription}
- onChange={this.updateAutocompleteDescription}
- placeholder={Utils.localizeMessage('add_command.autocompleteDescription.placeholder', 'Example: "Returns search results for patient records"')}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_command.autocompleteDescription.help'
- defaultMessage='(Optional) Short description of slash command for the autocomplete list.'
- />
- </div>
- </div>
- </div>
- )];
- }
-
- return (
- <div className='backstage-content row'>
- <BackstageHeader>
- <Link to={'/' + this.props.team.name + '/integrations/commands'}>
- <FormattedMessage
- id='installed_command.header'
- defaultMessage='Slash Commands'
- />
- </Link>
- <FormattedMessage
- id='integrations.add'
- defaultMessage='Add'
- />
- </BackstageHeader>
- <div className='backstage-form'>
- <form
- className='form-horizontal'
- onSubmit={this.handleSubmit}
- >
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='displayName'
- >
- <FormattedMessage
- id='add_command.displayName'
- defaultMessage='Display Name'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='displayName'
- type='text'
- maxLength='64'
- className='form-control'
- value={this.state.displayName}
- onChange={this.updateDisplayName}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_command.displayName.help'
- defaultMessage='Display name for your slash command made of up to 64 characters.'
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='description'
- >
- <FormattedMessage
- id='add_command.description'
- defaultMessage='Description'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='description'
- type='text'
- maxLength='128'
- className='form-control'
- value={this.state.description}
- onChange={this.updateDescription}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_command.description.help'
- defaultMessage='Description for your incoming webhook.'
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='trigger'
- >
- <FormattedMessage
- id='add_command.trigger'
- defaultMessage='Command Trigger Word'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='trigger'
- type='text'
- maxLength={Constants.MAX_TRIGGER_LENGTH}
- className='form-control'
- value={this.state.trigger}
- onChange={this.updateTrigger}
- placeholder={Utils.localizeMessage('add_command.trigger.placeholder', 'Command trigger e.g. "hello" not including the slash')}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_command.trigger.help'
- defaultMessage='Trigger word must be unique, and cannot begin with a slash or contain any spaces.'
- />
- </div>
- <div className='form__help'>
- <FormattedMessage
- id='add_command.trigger.helpExamples'
- defaultMessage='Examples: client, employee, patient, weather'
- />
- </div>
- <div className='form__help'>
- <FormattedMessage
- id='add_command.trigger.helpReserved'
- defaultMessage='Reserved: {link}'
- values={{
- link: (
- <a
- href='https://docs.mattermost.com/help/messaging/executing-commands.html#built-in-commands'
- target='_blank'
- rel='noopener noreferrer'
- >
- <FormattedMessage
- id='add_command.trigger.helpReservedLinkText'
- defaultMessage='see list of built-in slash commands'
- />
- </a>
- )
- }}
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='url'
- >
- <FormattedMessage
- id='add_command.url'
- defaultMessage='Request URL'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='url'
- type='text'
- maxLength='1024'
- className='form-control'
- value={this.state.url}
- onChange={this.updateUrl}
- placeholder={Utils.localizeMessage('add_command.url.placeholder', 'Must start with http:// or https://')}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_command.url.help'
- defaultMessage='The callback URL to receive the HTTP POST or GET event request when the slash command is run.'
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='method'
- >
- <FormattedMessage
- id='add_command.method'
- defaultMessage='Request Method'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <select
- id='method'
- className='form-control'
- value={this.state.method}
- onChange={this.updateMethod}
- >
- <option value={REQUEST_POST}>
- {Utils.localizeMessage('add_command.method.post', 'POST')}
- </option>
- <option value={REQUEST_GET}>
- {Utils.localizeMessage('add_command.method.get', 'GET')}
- </option>
- </select>
- <div className='form__help'>
- <FormattedMessage
- id='add_command.method.help'
- defaultMessage='The type of command request issued to the Request URL.'
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='username'
- >
- <FormattedMessage
- id='add_command.username'
- defaultMessage='Response Username'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='username'
- type='text'
- maxLength='64'
- className='form-control'
- value={this.state.username}
- onChange={this.updateUsername}
- placeholder={Utils.localizeMessage('add_command.username.placeholder', 'Username')}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_command.username.help'
- defaultMessage='(Optional) Choose a username override for responses for this slash command. Usernames can consist of up to 22 characters consisting of lowercase letters, numbers and they symbols "-", "_", and "." .'
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='iconUrl'
- >
- <FormattedMessage
- id='add_command.iconUrl'
- defaultMessage='Response Icon'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='iconUrl'
- type='text'
- maxLength='1024'
- className='form-control'
- value={this.state.iconUrl}
- onChange={this.updateIconUrl}
- placeholder={Utils.localizeMessage('add_command.iconUrl.placeholder', 'https://www.example.com/myicon.png')}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_command.iconUrl.help'
- defaultMessage='(Optional) Choose a profile picture override for the post responses to this slash command. Enter the URL of a .png or .jpg file at least 128 pixels by 128 pixels.'
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='autocomplete'
- >
- <FormattedMessage
- id='add_command.autocomplete'
- defaultMessage='Autocomplete'
- />
- </label>
- <div className='col-md-5 col-sm-8 checkbox'>
- <input
- id='autocomplete'
- type='checkbox'
- checked={this.state.autocomplete}
- onChange={this.updateAutocomplete}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_command.autocomplete.help'
- defaultMessage='(Optional) Show slash command in autocomplete list.'
- />
- </div>
- </div>
- </div>
- {autocompleteFields}
- <div className='backstage-form__footer'>
- <FormError
- type='backstage'
- errors={[this.state.serverError, this.state.clientError]}
- />
- <Link
- className='btn btn-sm'
- to={'/' + this.props.team.name + '/integrations/commands'}
- >
- <FormattedMessage
- id='add_command.cancel'
- defaultMessage='Cancel'
- />
- </Link>
- <SpinnerButton
- className='btn btn-primary'
- type='submit'
- spinning={this.state.saving}
- onClick={this.handleSubmit}
- >
- <FormattedMessage
- id='add_command.save'
- defaultMessage='Save'
- />
- </SpinnerButton>
- </div>
- </form>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/integrations/components/add_command/index.js b/webapp/components/integrations/components/add_command/index.js
deleted file mode 100644
index 9ac7db220..000000000
--- a/webapp/components/integrations/components/add_command/index.js
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {addCommand} from 'mattermost-redux/actions/integrations';
-
-import AddCommand from './add_command.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps,
- addCommandRequest: state.requests.integrations.addCommand
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- addCommand
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(AddCommand);
diff --git a/webapp/components/integrations/components/add_incoming_webhook/add_incoming_webhook.jsx b/webapp/components/integrations/components/add_incoming_webhook/add_incoming_webhook.jsx
deleted file mode 100644
index 23f0fad6f..000000000
--- a/webapp/components/integrations/components/add_incoming_webhook/add_incoming_webhook.jsx
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import {browserHistory} from 'react-router/es6';
-import PropTypes from 'prop-types';
-
-import AbstractIncomingWebhook from 'components/integrations/components/abstract_incoming_webhook.jsx';
-
-const HEADER = {id: 'integrations.add', defaultMessage: 'Add'};
-const FOOTER = {id: 'add_incoming_webhook.save', defaultMessage: 'Save'};
-
-export default class AddIncomingWebhook extends React.PureComponent {
- static propTypes = {
-
- /**
- * The current team
- */
- team: PropTypes.object.isRequired,
-
- /**
- * The request state for createIncomingHook action. Contains status and error
- */
- createIncomingHookRequest: PropTypes.object.isRequired,
-
- actions: PropTypes.shape({
-
- /**
- * The function to call to add a new incoming webhook
- */
- createIncomingHook: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.state = {
- serverError: ''
- };
- }
-
- addIncomingHook = async (hook) => {
- this.setState({serverError: ''});
-
- const data = await this.props.actions.createIncomingHook(hook);
- if (data) {
- browserHistory.push(`/${this.props.team.name}/integrations/confirm?type=incoming_webhooks&id=${data.id}`);
- return;
- }
-
- if (this.props.createIncomingHookRequest.error) {
- this.setState({serverError: this.props.createIncomingHookRequest.error.message});
- }
- }
-
- render() {
- return (
- <AbstractIncomingWebhook
- team={this.props.team}
- header={HEADER}
- footer={FOOTER}
- action={this.addIncomingHook}
- serverError={this.state.serverError}
- />
- );
- }
-}
diff --git a/webapp/components/integrations/components/add_incoming_webhook/index.js b/webapp/components/integrations/components/add_incoming_webhook/index.js
deleted file mode 100644
index ed2b53ba8..000000000
--- a/webapp/components/integrations/components/add_incoming_webhook/index.js
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
- import {connect} from 'react-redux';
- import {bindActionCreators} from 'redux';
- import {createIncomingHook} from 'mattermost-redux/actions/integrations';
-
- import AddIncomingWebhook from './add_incoming_webhook.jsx';
-
- function mapStateToProps(state, ownProps) {
- return {
- ...ownProps,
- createIncomingHookRequest: state.requests.integrations.createIncomingHook
- };
- }
-
- function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- createIncomingHook
- }, dispatch)
- };
- }
-
- export default connect(mapStateToProps, mapDispatchToProps)(AddIncomingWebhook); \ No newline at end of file
diff --git a/webapp/components/integrations/components/add_oauth_app/add_oauth_app.jsx b/webapp/components/integrations/components/add_oauth_app/add_oauth_app.jsx
deleted file mode 100644
index cad3244e3..000000000
--- a/webapp/components/integrations/components/add_oauth_app/add_oauth_app.jsx
+++ /dev/null
@@ -1,441 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import BackstageHeader from 'components/backstage/components/backstage_header.jsx';
-import {FormattedMessage} from 'react-intl';
-import FormError from 'components/form_error.jsx';
-import {browserHistory, Link} from 'react-router/es6';
-import SpinnerButton from 'components/spinner_button.jsx';
-
-export default class AddOAuthApp extends React.PureComponent {
- static propTypes = {
-
- /**
- * The team data
- */
- team: PropTypes.object,
-
- /**
- * The request state for addOAuthApp action. Contains status and error
- */
- addOAuthAppRequest: PropTypes.object.isRequired,
-
- actions: PropTypes.shape({
-
- /**
- * The function to call to add new OAuthApp
- */
- addOAuthApp: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.image = new Image();
- this.image.onload = this.imageLoaded;
-
- this.state = {
- name: '',
- description: '',
- homepage: '',
- icon_url: '',
- callbackUrls: '',
- is_trusted: false,
- has_icon: false,
- saving: false,
- serverError: '',
- clientError: null
- };
- }
-
- imageLoaded = () => {
- this.setState({
- has_icon: true,
- icon_url: this.refs.icon_url.value
- });
- }
-
- handleSubmit = (e) => {
- e.preventDefault();
-
- if (this.state.saving) {
- return;
- }
-
- this.setState({
- saving: true,
- serverError: '',
- clientError: ''
- });
-
- if (!this.state.name) {
- this.setState({
- saving: false,
- clientError: (
- <FormattedMessage
- id='add_oauth_app.nameRequired'
- defaultMessage='Name for the OAuth 2.0 application is required.'
- />
- )
- });
-
- return;
- }
-
- if (!this.state.description) {
- this.setState({
- saving: false,
- clientError: (
- <FormattedMessage
- id='add_oauth_app.descriptionRequired'
- defaultMessage='Description for the OAuth 2.0 application is required.'
- />
- )
- });
-
- return;
- }
-
- if (!this.state.homepage) {
- this.setState({
- saving: false,
- clientError: (
- <FormattedMessage
- id='add_oauth_app.homepageRequired'
- defaultMessage='Homepage for the OAuth 2.0 application is required.'
- />
- )
- });
-
- return;
- }
-
- const callbackUrls = [];
- for (let callbackUrl of this.state.callbackUrls.split('\n')) {
- callbackUrl = callbackUrl.trim();
-
- if (callbackUrl.length > 0) {
- callbackUrls.push(callbackUrl);
- }
- }
-
- if (callbackUrls.length === 0) {
- this.setState({
- saving: false,
- clientError: (
- <FormattedMessage
- id='add_oauth_app.callbackUrlsRequired'
- defaultMessage='One or more callback URLs are required.'
- />
- )
- });
-
- return;
- }
-
- const app = {
- name: this.state.name,
- callback_urls: callbackUrls,
- homepage: this.state.homepage,
- description: this.state.description,
- is_trusted: this.state.is_trusted,
- icon_url: this.state.icon_url
- };
-
- this.props.actions.addOAuthApp(app).then(
- (data) => {
- const {error} = this.props.addOAuthAppRequest;
- if (error) {
- this.setState({
- saving: false,
- serverError: error.message
- });
- } else {
- browserHistory.
- push(`/${this.props.team.name}/integrations/confirm?type=oauth2-apps&id=${data.id}`);
- }
- }
- );
- }
-
- updateName = (e) => {
- this.setState({
- name: e.target.value
- });
- }
-
- updateTrusted = (e) => {
- this.setState({
- is_trusted: e.target.value === 'true'
- });
- }
-
- updateDescription = (e) => {
- this.setState({
- description: e.target.value
- });
- }
-
- updateHomepage = (e) => {
- this.setState({
- homepage: e.target.value
- });
- }
-
- updateIconUrl = (e) => {
- this.setState({
- has_icon: false,
- icon_url: ''
- });
- this.image.src = e.target.value;
- }
-
- updateCallbackUrls = (e) => {
- this.setState({
- callbackUrls: e.target.value
- });
- }
-
- render() {
- let icon;
- if (this.state.has_icon) {
- icon = (
- <div className='integration__icon'>
- <img src={this.state.icon_url}/>
- </div>
- );
- }
-
- return (
- <div className='backstage-content'>
- <BackstageHeader>
- <Link to={`/${this.props.team.name}/integrations/oauth2-apps`}>
- <FormattedMessage
- id='installed_oauth_apps.header'
- defaultMessage='Installed OAuth2 Apps'
- />
- </Link>
- <FormattedMessage
- id='add_oauth_app.header'
- defaultMessage='Add'
- />
- </BackstageHeader>
- <div className='backstage-form'>
- {icon}
- <form className='form-horizontal'>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='is_trusted'
- >
- <FormattedMessage
- id='installed_oauth_apps.trusted'
- defaultMessage='Is Trusted'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <label className='radio-inline'>
- <input
- type='radio'
- value='true'
- name='is_trusted'
- checked={this.state.is_trusted}
- onChange={this.updateTrusted}
- />
- <FormattedMessage
- id='installed_oauth_apps.trusted.yes'
- defaultMessage='Yes'
- />
- </label>
- <label className='radio-inline'>
- <input
- type='radio'
- value='false'
- name='is_trusted'
- checked={!this.state.is_trusted}
- onChange={this.updateTrusted}
- />
- <FormattedMessage
- id='installed_oauth_apps.trusted.no'
- defaultMessage='No'
- />
- </label>
- <div className='form__help'>
- <FormattedMessage
- id='add_oauth_app.trusted.help'
- defaultMessage="When true, the OAuth 2.0 application is considered trusted by the Mattermost server and doesn't require the user to accept authorization. When false, an additional window will appear, asking the user to accept or deny the authorization."
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='name'
- >
- <FormattedMessage
- id='installed_oauth_apps.name'
- defaultMessage='Display Name'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='name'
- type='text'
- maxLength='64'
- className='form-control'
- value={this.state.name}
- onChange={this.updateName}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_oauth_app.name.help'
- defaultMessage='Display name for your OAuth 2.0 application made of up to 64 characters.'
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='description'
- >
- <FormattedMessage
- id='installed_oauth_apps.description'
- defaultMessage='Description'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='description'
- type='text'
- maxLength='512'
- className='form-control'
- value={this.state.description}
- onChange={this.updateDescription}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_oauth_app.description.help'
- defaultMessage='Description for your OAuth 2.0 application.'
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='homepage'
- >
- <FormattedMessage
- id='installed_oauth_apps.homepage'
- defaultMessage='Homepage'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='homepage'
- type='url'
- maxLength='256'
- className='form-control'
- value={this.state.homepage}
- onChange={this.updateHomepage}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_oauth_app.homepage.help'
- defaultMessage='The URL for the homepage of the OAuth 2.0 application. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='icon_url'
- >
- <FormattedMessage
- id='installed_oauth_apps.iconUrl'
- defaultMessage='Icon URL'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='icon_url'
- ref='icon_url'
- type='url'
- maxLength='512'
- className='form-control'
- onChange={this.updateIconUrl}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_oauth_app.icon.help'
- defaultMessage='The URL for the homepage of the OAuth 2.0 application. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.'
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='callbackUrls'
- >
- <FormattedMessage
- id='installed_oauth_apps.callbackUrls'
- defaultMessage='Callback URLs (One Per Line)'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <textarea
- id='callbackUrls'
- rows='3'
- maxLength='1024'
- className='form-control'
- value={this.state.callbackUrls}
- onChange={this.updateCallbackUrls}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_oauth_app.callbackUrls.help'
- defaultMessage='The redirect URIs to which the service will redirect users after accepting or denying authorization of your application, and which will handle authorization codes or access tokens. Must be a valid URL and start with http:// or https://.'
- />
- </div>
- </div>
- </div>
- <div className='backstage-form__footer'>
- <FormError
- type='backstage'
- errors={[this.state.serverError, this.state.clientError]}
- />
- <Link
- className='btn btn-sm'
- to={`/${this.props.team.name}/integrations/oauth2-apps`}
- >
- <FormattedMessage
- id='installed_oauth_apps.cancel'
- defaultMessage='Cancel'
- />
- </Link>
- <SpinnerButton
- className='btn btn-primary'
- type='submit'
- spinning={this.state.saving}
- onClick={this.handleSubmit}
- >
- <FormattedMessage
- id='installed_oauth_apps.save'
- defaultMessage='Save'
- />
- </SpinnerButton>
- </div>
- </form>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/integrations/components/add_oauth_app/index.js b/webapp/components/integrations/components/add_oauth_app/index.js
deleted file mode 100644
index be3446c45..000000000
--- a/webapp/components/integrations/components/add_oauth_app/index.js
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {addOAuthApp} from 'mattermost-redux/actions/integrations';
-
-import AddOAuthApp from './add_oauth_app.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps,
- addOAuthAppRequest: state.requests.integrations.addOAuthApp
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- addOAuthApp
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(AddOAuthApp);
diff --git a/webapp/components/integrations/components/add_outgoing_webhook/add_outgoing_webhook.jsx b/webapp/components/integrations/components/add_outgoing_webhook/add_outgoing_webhook.jsx
deleted file mode 100644
index 41ab8a073..000000000
--- a/webapp/components/integrations/components/add_outgoing_webhook/add_outgoing_webhook.jsx
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import AbstractOutgoingWebhook from 'components/integrations/components/abstract_outgoing_webhook.jsx';
-
-import React from 'react';
-import {browserHistory} from 'react-router/es6';
-import PropTypes from 'prop-types';
-
-const HEADER = {id: 'integrations.add', defaultMessage: 'Add'};
-const FOOTER = {id: 'add_outgoing_webhook.save', defaultMessage: 'Save'};
-
-export default class AddOutgoingWebhook extends React.PureComponent {
- static propTypes = {
-
- /**
- * The current team
- */
- team: PropTypes.object.isRequired,
-
- /**
- * The request state for createOutgoingHook action. Contains status and error
- */
- createOutgoingHookRequest: PropTypes.object.isRequired,
-
- actions: PropTypes.shape({
-
- /**
- * The function to call to add a new outgoing webhook
- */
- createOutgoingHook: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.state = {
- serverError: ''
- };
- }
-
- addOutgoingHook = async (hook) => {
- this.setState({serverError: ''});
-
- const data = await this.props.actions.createOutgoingHook(hook);
- if (data) {
- browserHistory.push(`/${this.props.team.name}/integrations/confirm?type=outgoing_webhooks&id=${data.id}`);
- return;
- }
-
- if (this.props.createOutgoingHookRequest.error) {
- this.setState({serverError: this.props.createOutgoingHookRequest.error.message});
- }
- }
-
- render() {
- return (
- <AbstractOutgoingWebhook
- team={this.props.team}
- header={HEADER}
- footer={FOOTER}
- renderExtra={''}
- action={this.addOutgoingHook}
- serverError={this.state.serverError}
- />
- );
- }
-}
diff --git a/webapp/components/integrations/components/add_outgoing_webhook/index.js b/webapp/components/integrations/components/add_outgoing_webhook/index.js
deleted file mode 100644
index f930ac81f..000000000
--- a/webapp/components/integrations/components/add_outgoing_webhook/index.js
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {createOutgoingHook} from 'mattermost-redux/actions/integrations';
-
-import AddOutgoingWebhook from './add_outgoing_webhook.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps,
- createOutgoingHookRequest: state.requests.integrations.createOutgoingHook
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- createOutgoingHook
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(AddOutgoingWebhook);
diff --git a/webapp/components/integrations/components/commands_container/commands_container.jsx b/webapp/components/integrations/components/commands_container/commands_container.jsx
deleted file mode 100644
index 8f89ff631..000000000
--- a/webapp/components/integrations/components/commands_container/commands_container.jsx
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-export default class CommandsContainer extends React.PureComponent {
- static propTypes = {
-
- /**
- * The team data needed to pass into child components
- */
- team: PropTypes.object,
-
- /**
- * The user data needed to pass into child components
- */
- user: PropTypes.object,
-
- /**
- * The children prop needed to render child component
- */
- children: PropTypes.node.isRequired,
-
- /**
- * Set if user is admin
- */
- isAdmin: PropTypes.bool,
-
- /**
- * The users collection
- */
- users: PropTypes.object,
-
- /**
- * Installed slash commands to display
- */
- commands: PropTypes.array,
-
- actions: PropTypes.shape({
-
- /**
- * The function to call to fetch team commands
- */
- getCustomTeamCommands: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
- this.state = {
- loading: true
- };
- }
-
- componentDidMount() {
- if (window.mm_config.EnableCommands === 'true') {
- this.props.actions.getCustomTeamCommands(this.props.team.id).then(
- () => this.setState({loading: false})
- );
- }
- }
-
- render() {
- return (
- <div>
- {React.cloneElement(this.props.children, {
- loading: this.state.loading,
- commands: this.props.commands || [],
- users: this.props.users,
- team: this.props.team,
- user: this.props.user,
- isAdmin: this.props.isAdmin
- })}
- </div>
- );
- }
-}
diff --git a/webapp/components/integrations/components/commands_container/index.js b/webapp/components/integrations/components/commands_container/index.js
deleted file mode 100644
index ed0a4c138..000000000
--- a/webapp/components/integrations/components/commands_container/index.js
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getCustomTeamCommands} from 'mattermost-redux/actions/integrations';
-
-import {getCommands} from 'mattermost-redux/selectors/entities/integrations';
-import {getUsers} from 'mattermost-redux/selectors/entities/users';
-
-import CommandsContainer from './commands_container.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps,
- commands: Object.values(getCommands(state)),
- users: getUsers(state)
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getCustomTeamCommands
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(CommandsContainer); \ No newline at end of file
diff --git a/webapp/components/integrations/components/confirm_integration/confirm_integration.jsx b/webapp/components/integrations/components/confirm_integration/confirm_integration.jsx
deleted file mode 100644
index 05dd61efb..000000000
--- a/webapp/components/integrations/components/confirm_integration/confirm_integration.jsx
+++ /dev/null
@@ -1,258 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import BackstageHeader from 'components/backstage/components/backstage_header.jsx';
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-import {Link, browserHistory} from 'react-router/es6';
-
-import UserStore from 'stores/user_store.jsx';
-import IntegrationStore from 'stores/integration_store.jsx';
-
-import Constants from 'utils/constants.jsx';
-
-export default class ConfirmIntegration extends React.Component {
- static get propTypes() {
- return {
- team: PropTypes.object,
- location: PropTypes.object,
- commands: PropTypes.object,
- loading: PropTypes.bool
- };
- }
-
- constructor(props) {
- super(props);
-
- this.handleIntegrationChange = this.handleIntegrationChange.bind(this);
- this.handleKeyPress = this.handleKeyPress.bind(this);
-
- const userId = UserStore.getCurrentId();
-
- this.state = {
- type: this.props.location.query.type,
- id: this.props.location.query.id,
- oauthApps: IntegrationStore.getOAuthApps(userId),
- loading: !IntegrationStore.hasReceivedOAuthApps(userId)
- };
- }
-
- componentDidMount() {
- IntegrationStore.addChangeListener(this.handleIntegrationChange);
- window.addEventListener('keypress', this.handleKeyPress);
- }
-
- componentWillUnmount() {
- IntegrationStore.removeChangeListener(this.handleIntegrationChange);
- window.removeEventListener('keypress', this.handleKeyPress);
- }
-
- handleIntegrationChange() {
- const userId = UserStore.getCurrentId();
-
- this.setState({
- oauthApps: IntegrationStore.getOAuthApps(userId),
- loading: !IntegrationStore.hasReceivedOAuthApps(userId)
- });
- }
-
- handleKeyPress(e) {
- if (e.key === 'Enter') {
- browserHistory.push('/' + this.props.team.name + '/integrations/' + this.state.type);
- }
- }
-
- render() {
- let headerText = null;
- let helpText = null;
- let tokenText = null;
-
- if (this.props.loading === true) {
- return (<div/>);
- }
-
- if (this.state.type === Constants.Integrations.COMMAND) {
- headerText = (
- <FormattedMessage
- id={'installed_commands.header'}
- defaultMessage='Slash Commands'
- />
- );
- helpText = (
- <p>
- <FormattedHTMLMessage
- id='add_command.doneHelp'
- defaultMessage='Your slash command has been set up. The following token will be sent in the outgoing payload. Please use it to verify the request came from your Mattermost team (see <a href="https://docs.mattermost.com/developer/slash-commands.html">documentation</a> for further details).'
- />
- </p>
- );
- tokenText = (
- <p className='word-break--all'>
- <FormattedHTMLMessage
- id='add_command.token'
- defaultMessage='<b>Token</b>: {token}'
- values={{
- token: this.props.commands[this.state.id].token
- }}
- />
- </p>
- );
- } else if (this.state.type === Constants.Integrations.INCOMING_WEBHOOK) {
- headerText = (
- <FormattedMessage
- id={'installed_incoming_webhooks.header'}
- defaultMessage='Incoming Webhooks'
- />
- );
- helpText = (
- <p>
- <FormattedHTMLMessage
- id='add_incoming_webhook.doneHelp'
- defaultMessage='Your incoming webhook has been set up. Please send data to the following URL (see <a href=\"https://docs.mattermost.com/developer/webhooks-incoming.html\">documentation</a> for further details).'
- />
- </p>
- );
- tokenText = (
- <p className='word-break--all'>
- <FormattedHTMLMessage
- id='add_incoming_webhook.url'
- defaultMessage='<b>URL</b>: {url}'
- values={{
- url: window.location.origin + '/hooks/' + this.state.id
- }}
- />
- </p>
- );
- } else if (this.state.type === Constants.Integrations.OUTGOING_WEBHOOK) {
- headerText = (
- <FormattedMessage
- id={'installed_outgoing_webhooks.header'}
- defaultMessage='Outgoing Webhooks'
- />
- );
- helpText = (
- <p>
- <FormattedHTMLMessage
- id='add_outgoing_webhook.doneHelp'
- defaultMessage='Your outgoing webhook has been set up. The following token will be sent in the outgoing payload. Please use it to verify the request came from your Mattermost team (see <a href=\"https://docs.mattermost.com/developer/webhooks-outgoing.html\">documentation</a> for further details).'
- />
- </p>
- );
- tokenText = (
- <p className='word-break--all'>
- <FormattedHTMLMessage
- id='add_outgoing_webhook.token'
- defaultMessage='<b>Token</b>: {token}'
- values={{
- token: IntegrationStore.getOutgoingWebhook(this.props.team.id, this.state.id).token
- }}
- />
- </p>
- );
- } else if (this.state.type === Constants.Integrations.OAUTH_APP) {
- let oauthApp = {};
- for (var i = 0; i < this.state.oauthApps.length; i++) {
- if (this.state.oauthApps[i].id === this.state.id) {
- oauthApp = this.state.oauthApps[i];
- break;
- }
- }
-
- if (oauthApp) {
- headerText = (
- <FormattedMessage
- id={'installed_oauth_apps.header'}
- defaultMessage='OAuth 2.0 Applications'
- />
- );
-
- helpText = [];
- helpText.push(
- <p key='add_oauth_app.doneHelp'>
- <FormattedHTMLMessage
- id='add_oauth_app.doneHelp'
- defaultMessage='Your OAuth 2.0 application has been set up. Please use the following Client ID and Client Secret when requesting authorization for your application (see <a href="https://docs.mattermost.com/developer/oauth-2-0-applications.html">documentation</a> for further details).'
- />
- </p>
- );
- helpText.push(
- <p key='add_oauth_app.clientId'>
- <FormattedHTMLMessage
- id='add_oauth_app.clientId'
- defaultMessage='<b>Client ID:</b> {id}'
- values={{
- id: this.state.id
- }}
- /> <br/>
- <FormattedHTMLMessage
- id='add_oauth_app.clientSecret'
- defaultMessage='<b>Client Secret:</b> {secret}'
- values={{
- secret: oauthApp.client_secret
- }}
- />
- </p>
- );
-
- helpText.push(
- <p key='add_oauth_app.doneUrlHelp'>
- <FormattedHTMLMessage
- id='add_oauth_app.doneUrlHelp'
- defaultMessage='The following are your authorized redirect URL(s).'
- />
- </p>
- );
-
- tokenText = (
- <p className='word-break--all'>
- <FormattedHTMLMessage
- id='add_oauth_app.url'
- defaultMessage='<b>URL(s)</b>: {url}'
- values={{
- url: oauthApp.callback_urls
- }}
- />
- </p>
- );
- }
- }
-
- return (
- <div className='backstage-content row'>
- <BackstageHeader>
- <Link to={'/' + this.props.team.name + '/integrations/' + this.state.type}>
- {headerText}
- </Link>
- <FormattedMessage
- id='integrations.add'
- defaultMessage='Add'
- />
- </BackstageHeader>
- <div className='backstage-form backstage-form__confirmation'>
- <h4 className='backstage-form__title'>
- <FormattedMessage
- id='integrations.successful'
- defaultMessage='Setup Successful'
- />
- </h4>
- {helpText}
- {tokenText}
- <div className='backstage-form__footer'>
- <Link
- className='btn btn-primary'
- type='submit'
- to={'/' + this.props.team.name + '/integrations/' + this.state.type}
- >
- <FormattedMessage
- id='integrations.done'
- defaultMessage='Done'
- />
- </Link>
- </div>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/integrations/components/confirm_integration/index.js b/webapp/components/integrations/components/confirm_integration/index.js
deleted file mode 100644
index fe7984f2b..000000000
--- a/webapp/components/integrations/components/confirm_integration/index.js
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {getCommands} from 'mattermost-redux/selectors/entities/integrations';
-
-import ConfirmIntegration from './confirm_integration.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps,
- commands: getCommands(state)
- };
-}
-
-export default connect(mapStateToProps)(ConfirmIntegration); \ No newline at end of file
diff --git a/webapp/components/integrations/components/delete_integration.jsx b/webapp/components/integrations/components/delete_integration.jsx
deleted file mode 100644
index 592f415d6..000000000
--- a/webapp/components/integrations/components/delete_integration.jsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-
-import DeleteModalTrigger from '../../delete_modal_trigger.jsx';
-
-export default class DeleteIntegration extends DeleteModalTrigger {
- get triggerTitle() {
- return (
- <FormattedMessage
- id='installed_integrations.delete'
- defaultMessage='Delete'
- />
- );
- }
-
- get modalTitle() {
- return (
- <FormattedMessage
- id='integrations.delete.confirm.title'
- defaultMessage='Delete Integration'
- />
- );
- }
-
- get modalMessage() {
- return (
- <div className='alert alert-warning'>
- <i className='fa fa-warning fa-margin--right'/>
- <FormattedMessage
- id={this.props.messageId}
- defaultMessage='This action permanently deletes the integration and breaks any integrations using it. Are you sure you want to delete it?'
- />
- </div>
- );
- }
-
- get modalConfirmButton() {
- return (
- <FormattedMessage
- id='integrations.delete.confirm.button'
- defaultMessage='Delete'
- />
- );
- }
-}
-
-DeleteIntegration.propTypes = {
- messageId: PropTypes.string.isRequired,
- onDelete: PropTypes.func.isRequired
-};
diff --git a/webapp/components/integrations/components/edit_command/edit_command.jsx b/webapp/components/integrations/components/edit_command/edit_command.jsx
deleted file mode 100644
index 588047fb3..000000000
--- a/webapp/components/integrations/components/edit_command/edit_command.jsx
+++ /dev/null
@@ -1,727 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import {FormattedMessage} from 'react-intl';
-import {browserHistory, Link} from 'react-router/es6';
-import Constants from 'utils/constants.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import FormError from 'components/form_error.jsx';
-import SpinnerButton from 'components/spinner_button.jsx';
-import ConfirmModal from 'components/confirm_modal.jsx';
-import BackstageHeader from 'components/backstage/components/backstage_header.jsx';
-
-const REQUEST_POST = 'P';
-const REQUEST_GET = 'G';
-
-export default class EditCommand extends React.PureComponent {
- static propTypes = {
-
- /**
- * The current team
- */
- team: PropTypes.object.isRequired,
-
- /**
- * The id of the command to edit
- */
- commandId: PropTypes.string.isRequired,
-
- /**
- * Installed slash commands to display
- */
- commands: PropTypes.object,
-
- /**
- * The request state for editCommand action. Contains status and error
- */
- editCommandRequest: PropTypes.object.isRequired,
-
- actions: PropTypes.shape({
-
- /**
- * The function to call to fetch team commands
- */
- getCustomTeamCommands: PropTypes.func.isRequired,
-
- /**
- * The function to call to edit command
- */
- editCommand: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.originalCommand = null;
- this.newCommand = null;
-
- this.state = {
- displayName: '',
- description: '',
- trigger: '',
- url: '',
- method: REQUEST_POST,
- username: '',
- iconUrl: '',
- autocomplete: false,
- autocompleteHint: '',
- autocompleteDescription: '',
- saving: false,
- serverError: '',
- clientError: null,
- showConfirmModal: false,
- loading: true
- };
- }
-
- componentDidMount() {
- if (window.mm_config.EnableCommands === 'true') {
- this.props.actions.getCustomTeamCommands(this.props.team.id).then(
- () => {
- this.originalCommand = Object.values(this.props.commands).filter((command) => command.id === this.props.commandId)[0];
- this.setState({
- displayName: this.originalCommand.display_name,
- description: this.originalCommand.description,
- trigger: this.originalCommand.trigger,
- url: this.originalCommand.url,
- method: this.originalCommand.method,
- username: this.originalCommand.username,
- iconUrl: this.originalCommand.icon_url,
- autocomplete: this.originalCommand.auto_complete,
- autocompleteHint: this.originalCommand.auto_complete_hint,
- autocompleteDescription: this.originalCommand.auto_complete_desc,
- loading: false
- });
- }
- );
- }
- }
-
- handleConfirmModal = () => {
- this.setState({showConfirmModal: true});
- }
-
- confirmModalDismissed = () => {
- this.setState({showConfirmModal: false});
- }
-
- submitCommand = async () => {
- const data = await this.props.actions.editCommand(this.newCommand);
-
- if (data) {
- browserHistory.push(`/${this.props.team.name}/integrations/commands`);
- return;
- }
-
- if (this.props.editCommandRequest.error) {
- this.setState({
- saving: false,
- serverError: this.props.editCommandRequest.error.message
- });
- }
- }
-
- handleUpdate = async () => {
- this.setState({
- saving: true,
- serverError: '',
- clientError: ''
- });
-
- await this.submitCommand();
- }
-
- handleSubmit = async (e) => {
- e.preventDefault();
-
- if (this.state.saving) {
- return;
- }
-
- this.setState({
- saving: true,
- serverError: '',
- clientError: ''
- });
-
- let triggerWord = this.state.trigger.trim().toLowerCase();
- if (triggerWord.indexOf('/') === 0) {
- triggerWord = triggerWord.substr(1);
- }
-
- const command = {
- display_name: this.state.displayName,
- description: this.state.description,
- trigger: triggerWord,
- url: this.state.url.trim(),
- method: this.state.method,
- username: this.state.username,
- icon_url: this.state.iconUrl,
- auto_complete: this.state.autocomplete,
- team_id: this.props.team.id
- };
-
- if (this.originalCommand.id) {
- command.id = this.originalCommand.id;
- }
-
- if (command.auto_complete) {
- command.auto_complete_desc = this.state.autocompleteDescription;
- command.auto_complete_hint = this.state.autocompleteHint;
- }
-
- if (!command.trigger) {
- this.setState({
- saving: false,
- clientError: (
- <FormattedMessage
- id='add_command.triggerRequired'
- defaultMessage='A trigger word is required'
- />
- )
- });
-
- return;
- }
-
- if (command.trigger.indexOf('/') === 0) {
- this.setState({
- saving: false,
- clientError: (
- <FormattedMessage
- id='add_command.triggerInvalidSlash'
- defaultMessage='A trigger word cannot begin with a /'
- />
- )
- });
-
- return;
- }
-
- if (command.trigger.indexOf(' ') !== -1) {
- this.setState({
- saving: false,
- clientError: (
- <FormattedMessage
- id='add_command.triggerInvalidSpace'
- defaultMessage='A trigger word must not contain spaces'
- />
- )
- });
- return;
- }
-
- if (command.trigger.length < Constants.MIN_TRIGGER_LENGTH ||
- command.trigger.length > Constants.MAX_TRIGGER_LENGTH) {
- this.setState({
- saving: false,
- clientError: (
- <FormattedMessage
- id='add_command.triggerInvalidLength'
- defaultMessage='A trigger word must contain between {min} and {max} characters'
- values={{
- min: Constants.MIN_TRIGGER_LENGTH,
- max: Constants.MAX_TRIGGER_LENGTH
- }}
- />
- )
- });
-
- return;
- }
-
- if (!command.url) {
- this.setState({
- saving: false,
- clientError: (
- <FormattedMessage
- id='add_command.urlRequired'
- defaultMessage='A request URL is required'
- />
- )
- });
-
- return;
- }
-
- this.newCommand = command;
-
- if (this.originalCommand.url !== this.newCommand.url ||
- this.originalCommand.trigger !== this.newCommand.trigger ||
- this.originalCommand.method !== this.newCommand.method) {
- this.handleConfirmModal();
- this.setState({
- saving: false
- });
- } else {
- await this.submitCommand();
- }
- }
-
- updateDisplayName = (e) => {
- this.setState({
- displayName: e.target.value
- });
- }
-
- updateDescription = (e) => {
- this.setState({
- description: e.target.value
- });
- }
-
- updateTrigger = (e) => {
- this.setState({
- trigger: e.target.value
- });
- }
-
- updateUrl = (e) => {
- this.setState({
- url: e.target.value
- });
- }
-
- updateMethod = (e) => {
- this.setState({
- method: e.target.value
- });
- }
-
- updateUsername = (e) => {
- this.setState({
- username: e.target.value
- });
- }
-
- updateIconUrl = (e) => {
- this.setState({
- iconUrl: e.target.value
- });
- }
-
- updateAutocomplete = (e) => {
- this.setState({
- autocomplete: e.target.checked
- });
- }
-
- updateAutocompleteHint = (e) => {
- this.setState({
- autocompleteHint: e.target.value
- });
- }
-
- updateAutocompleteDescription = (e) => {
- this.setState({
- autocompleteDescription: e.target.value
- });
- }
-
- render() {
- const confirmButton = (
- <FormattedMessage
- id='update_command.update'
- defaultMessage='Update'
- />
- );
-
- const confirmTitle = (
- <FormattedMessage
- id='update_command.confirm'
- defaultMessage='Edit Slash Command'
- />
- );
-
- const confirmMessage = (
- <FormattedMessage
- id='update_command.question'
- defaultMessage='Your changes may break the existing slash command. Are you sure you would like to update it?'
- />
- );
-
- let autocompleteFields = null;
- if (this.state.autocomplete) {
- autocompleteFields = [(
- <div
- key='autocompleteHint'
- className='form-group'
- >
- <label
- className='control-label col-sm-4'
- htmlFor='autocompleteHint'
- >
- <FormattedMessage
- id='add_command.autocompleteHint'
- defaultMessage='Autocomplete Hint'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='autocompleteHint'
- type='text'
- maxLength='1024'
- className='form-control'
- value={this.state.autocompleteHint}
- onChange={this.updateAutocompleteHint}
- placeholder={Utils.localizeMessage('add_command.autocompleteHint.placeholder', 'Example: [Patient Name]')}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_command.autocompleteHint.help'
- defaultMessage='(Optional) Arguments associated with your slash command, displayed as help in the autocomplete list.'
- />
- </div>
- </div>
- </div>
- ),
- (
- <div
- key='autocompleteDescription'
- className='form-group'
- >
- <label
- className='control-label col-sm-4'
- htmlFor='autocompleteDescription'
- >
- <FormattedMessage
- id='add_command.autocompleteDescription'
- defaultMessage='Autocomplete Description'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='description'
- type='text'
- maxLength='128'
- className='form-control'
- value={this.state.autocompleteDescription}
- onChange={this.updateAutocompleteDescription}
- placeholder={Utils.localizeMessage('add_command.autocompleteDescription.placeholder', 'Example: "Returns search results for patient records"')}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_command.autocompleteDescription.help'
- defaultMessage='(Optional) Short description of slash command for the autocomplete list.'
- />
- </div>
- </div>
- </div>
- )];
- }
-
- return (
- <div className='backstage-content row'>
- <BackstageHeader>
- <Link to={'/' + this.props.team.name + '/integrations/commands'}>
- <FormattedMessage
- id='installed_command.header'
- defaultMessage='Slash Commands'
- />
- </Link>
- <FormattedMessage
- id='integrations.edit'
- defaultMessage='Edit'
- />
- </BackstageHeader>
- <div className='backstage-form'>
- <form
- className='form-horizontal'
- onSubmit={this.handleSubmit}
- >
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='displayName'
- >
- <FormattedMessage
- id='add_command.displayName'
- defaultMessage='Display Name'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='displayName'
- type='text'
- maxLength='64'
- className='form-control'
- value={this.state.displayName}
- onChange={this.updateDisplayName}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_command.displayName.help'
- defaultMessage='Display name for your slash command made of up to 64 characters.'
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='description'
- >
- <FormattedMessage
- id='add_command.description'
- defaultMessage='Description'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='description'
- type='text'
- maxLength='128'
- className='form-control'
- value={this.state.description}
- onChange={this.updateDescription}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_command.description.help'
- defaultMessage='Description for your incoming webhook.'
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='trigger'
- >
- <FormattedMessage
- id='add_command.trigger'
- defaultMessage='Command Trigger Word'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='trigger'
- type='text'
- maxLength={Constants.MAX_TRIGGER_LENGTH}
- className='form-control'
- value={this.state.trigger}
- onChange={this.updateTrigger}
- placeholder={Utils.localizeMessage('add_command.trigger.placeholder', 'Command trigger e.g. "hello" not including the slash')}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_command.trigger.help'
- defaultMessage='Trigger word must be unique, and cannot begin with a slash or contain any spaces.'
- />
- </div>
- <div className='form__help'>
- <FormattedMessage
- id='add_command.trigger.helpExamples'
- defaultMessage='Examples: client, employee, patient, weather'
- />
- </div>
- <div className='form__help'>
- <FormattedMessage
- id='add_command.trigger.helpReserved'
- defaultMessage='Reserved: {link}'
- values={{
- link: (
- <a
- href='https://docs.mattermost.com/help/messaging/executing-commands.html#built-in-commands'
- target='_blank'
- rel='noopener noreferrer'
- >
- <FormattedMessage
- id='add_command.trigger.helpReservedLinkText'
- defaultMessage='see list of built-in slash commands'
- />
- </a>
- )
- }}
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='url'
- >
- <FormattedMessage
- id='add_command.url'
- defaultMessage='Request URL'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='url'
- type='text'
- maxLength='1024'
- className='form-control'
- value={this.state.url}
- onChange={this.updateUrl}
- placeholder={Utils.localizeMessage('add_command.url.placeholder', 'Must start with http:// or https://')}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_command.url.help'
- defaultMessage='The callback URL to receive the HTTP POST or GET event request when the slash command is run.'
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='method'
- >
- <FormattedMessage
- id='add_command.method'
- defaultMessage='Request Method'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <select
- id='method'
- className='form-control'
- value={this.state.method}
- onChange={this.updateMethod}
- >
- <option value={REQUEST_POST}>
- {Utils.localizeMessage('add_command.method.post', 'POST')}
- </option>
- <option value={REQUEST_GET}>
- {Utils.localizeMessage('add_command.method.get', 'GET')}
- </option>
- </select>
- <div className='form__help'>
- <FormattedMessage
- id='add_command.method.help'
- defaultMessage='The type of command request issued to the Request URL.'
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='username'
- >
- <FormattedMessage
- id='add_command.username'
- defaultMessage='Response Username'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='username'
- type='text'
- maxLength='64'
- className='form-control'
- value={this.state.username}
- onChange={this.updateUsername}
- placeholder={Utils.localizeMessage('add_command.username.placeholder', 'Username')}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_command.username.help'
- defaultMessage='(Optional) Choose a username override for responses for this slash command. Usernames can consist of up to 22 characters consisting of lowercase letters, numbers and they symbols "-", "_", and "." .'
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='iconUrl'
- >
- <FormattedMessage
- id='add_command.iconUrl'
- defaultMessage='Response Icon'
- />
- </label>
- <div className='col-md-5 col-sm-8'>
- <input
- id='iconUrl'
- type='text'
- maxLength='1024'
- className='form-control'
- value={this.state.iconUrl}
- onChange={this.updateIconUrl}
- placeholder={Utils.localizeMessage('add_command.iconUrl.placeholder', 'https://www.example.com/myicon.png')}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_command.iconUrl.help'
- defaultMessage='(Optional) Choose a profile picture override for the post responses to this slash command. Enter the URL of a .png or .jpg file at least 128 pixels by 128 pixels.'
- />
- </div>
- </div>
- </div>
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
- htmlFor='autocomplete'
- >
- <FormattedMessage
- id='add_command.autocomplete'
- defaultMessage='Autocomplete'
- />
- </label>
- <div className='col-md-5 col-sm-8 checkbox'>
- <input
- id='autocomplete'
- type='checkbox'
- checked={this.state.autocomplete}
- onChange={this.updateAutocomplete}
- />
- <div className='form__help'>
- <FormattedMessage
- id='add_command.autocomplete.help'
- defaultMessage='(Optional) Show slash command in autocomplete list.'
- />
- </div>
- </div>
- </div>
- {autocompleteFields}
- <div className='backstage-form__footer'>
- <FormError
- type='backstage'
- errors={[this.state.serverError, this.state.clientError]}
- />
- <Link
- className='btn btn-sm'
- to={'/' + this.props.team.name + '/integrations/commands'}
- >
- <FormattedMessage
- id='add_command.cancel'
- defaultMessage='Cancel'
- />
- </Link>
- <SpinnerButton
- className='btn btn-primary'
- type='submit'
- spinning={this.state.saving}
- onClick={this.handleSubmit}
- disabled={this.state.loading}
- >
- <FormattedMessage
- id='edit_command.save'
- defaultMessage='Update'
- />
- </SpinnerButton>
- <ConfirmModal
- title={confirmTitle}
- message={confirmMessage}
- confirmButtonText={confirmButton}
- show={this.state.showConfirmModal}
- onConfirm={this.handleUpdate}
- onCancel={this.confirmModalDismissed}
- />
- </div>
- </form>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/integrations/components/edit_command/index.js b/webapp/components/integrations/components/edit_command/index.js
deleted file mode 100644
index 2a8257113..000000000
--- a/webapp/components/integrations/components/edit_command/index.js
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getCustomTeamCommands, editCommand} from 'mattermost-redux/actions/integrations';
-import {getCommands} from 'mattermost-redux/selectors/entities/integrations';
-
-import EditCommand from './edit_command.jsx';
-
-function mapStateToProps(state, ownProps) {
- const commandId = ownProps.location.query.id;
-
- return {
- ...ownProps,
- commandId,
- commands: getCommands(state),
- editCommandRequest: state.requests.integrations.editCommand
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getCustomTeamCommands,
- editCommand
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(EditCommand);
diff --git a/webapp/components/integrations/components/edit_incoming_webhook/edit_incoming_webhook.jsx b/webapp/components/integrations/components/edit_incoming_webhook/edit_incoming_webhook.jsx
deleted file mode 100644
index 35d8983a2..000000000
--- a/webapp/components/integrations/components/edit_incoming_webhook/edit_incoming_webhook.jsx
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {browserHistory} from 'react-router/es6';
-import LoadingScreen from 'components/loading_screen.jsx';
-
-import AbstractIncomingWebhook from 'components/integrations/components/abstract_incoming_webhook.jsx';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-const HEADER = {id: 'integrations.edit', defaultMessage: 'Edit'};
-const FOOTER = {id: 'update_incoming_webhook.update', defaultMessage: 'Update'};
-
-export default class EditIncomingWebhook extends React.PureComponent {
- static propTypes = {
-
- /**
- * The current team
- */
- team: PropTypes.object.isRequired,
-
- /**
- * The incoming webhook to edit
- */
- hook: PropTypes.object,
-
- /**
- * The id of the incoming webhook to edit
- */
- hookId: PropTypes.string.isRequired,
-
- /**
- * The request state for updateIncomingHook action. Contains status and error
- */
- updateIncomingHookRequest: PropTypes.object.isRequired,
-
- actions: PropTypes.shape({
-
- /**
- * The function to call to update an incoming webhook
- */
- updateIncomingHook: PropTypes.func.isRequired,
-
- /**
- * The function to call to get an incoming webhook
- */
- getIncomingHook: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.state = {
- showConfirmModal: false,
- serverError: ''
- };
- }
-
- componentDidMount() {
- if (window.mm_config.EnableIncomingWebhooks === 'true') {
- this.props.actions.getIncomingHook(this.props.hookId);
- }
- }
-
- editIncomingHook = async (hook) => {
- this.newHook = hook;
-
- if (this.props.hook.id) {
- hook.id = this.props.hook.id;
- }
-
- if (this.props.hook.token) {
- hook.token = this.props.hook.token;
- }
-
- await this.submitHook();
- }
-
- submitHook = async () => {
- this.setState({serverError: ''});
-
- const data = await this.props.actions.updateIncomingHook(this.newHook);
-
- if (data) {
- browserHistory.push(`/${this.props.team.name}/integrations/incoming_webhooks`);
- return;
- }
-
- if (this.props.updateIncomingHookRequest.error) {
- this.setState({serverError: this.props.updateIncomingHookRequest.error.message});
- }
- }
-
- render() {
- if (!this.props.hook) {
- return <LoadingScreen/>;
- }
-
- return (
- <AbstractIncomingWebhook
- team={this.props.team}
- header={HEADER}
- footer={FOOTER}
- action={this.editIncomingHook}
- serverError={this.state.serverError}
- initialHook={this.props.hook}
- />
- );
- }
-}
diff --git a/webapp/components/integrations/components/edit_incoming_webhook/index.js b/webapp/components/integrations/components/edit_incoming_webhook/index.js
deleted file mode 100644
index a391a98a6..000000000
--- a/webapp/components/integrations/components/edit_incoming_webhook/index.js
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {updateIncomingHook, getIncomingHook} from 'mattermost-redux/actions/integrations';
-
-import EditIncomingWebhook from './edit_incoming_webhook.jsx';
-
-function mapStateToProps(state, ownProps) {
- const hookId = ownProps.location.query.id;
-
- return {
- ...ownProps,
- hookId,
- hook: state.entities.integrations.incomingHooks[hookId],
- updateIncomingHookRequest: state.requests.integrations.createIncomingHook
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- updateIncomingHook,
- getIncomingHook
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(EditIncomingWebhook); \ No newline at end of file
diff --git a/webapp/components/integrations/components/edit_outgoing_webhook/edit_outgoing_webhook.jsx b/webapp/components/integrations/components/edit_outgoing_webhook/edit_outgoing_webhook.jsx
deleted file mode 100644
index 9b2dbff0a..000000000
--- a/webapp/components/integrations/components/edit_outgoing_webhook/edit_outgoing_webhook.jsx
+++ /dev/null
@@ -1,169 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import AbstractOutgoingWebhook from 'components/integrations/components/abstract_outgoing_webhook.jsx';
-import ConfirmModal from 'components/confirm_modal.jsx';
-import LoadingScreen from 'components/loading_screen.jsx';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {browserHistory} from 'react-router/es6';
-import {FormattedMessage} from 'react-intl';
-
-const HEADER = {id: 'integrations.edit', defaultMessage: 'Edit'};
-const FOOTER = {id: 'update_outgoing_webhook.update', defaultMessage: 'Update'};
-
-export default class EditOutgoingWebhook extends React.PureComponent {
- static propTypes = {
-
- /**
- * The current team
- */
- team: PropTypes.object.isRequired,
-
- /**
- * The outgoing webhook to edit
- */
- hook: PropTypes.object,
-
- /**
- * The id of the outgoing webhook to edit
- */
- hookId: PropTypes.string.isRequired,
-
- /**
- * The request state for updateOutgoingHook action. Contains status and error
- */
- updateOutgoingHookRequest: PropTypes.object.isRequired,
-
- actions: PropTypes.shape({
-
- /**
- * The function to call to update an outgoing webhook
- */
- updateOutgoingHook: PropTypes.func.isRequired,
-
- /**
- * The function to call to get an outgoing webhook
- */
- getOutgoingHook: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.state = {
- showConfirmModal: false,
- serverError: ''
- };
- }
-
- componentDidMount() {
- if (window.mm_config.EnableOutgoingWebhooks === 'true') {
- this.props.actions.getOutgoingHook(this.props.hookId);
- }
- }
-
- editOutgoingHook = async (hook) => {
- this.newHook = hook;
-
- if (this.props.hook.id) {
- hook.id = this.props.hook.id;
- }
-
- if (this.props.hook.token) {
- hook.token = this.props.hook.token;
- }
-
- const triggerWordsSame = (this.props.hook.trigger_words.length === hook.trigger_words.length) &&
- this.props.hook.trigger_words.every((v, i) => v === hook.trigger_words[i]);
-
- const callbackUrlsSame = (this.props.hook.callback_urls.length === hook.callback_urls.length) &&
- this.props.hook.callback_urls.every((v, i) => v === hook.callback_urls[i]);
-
- if (this.props.hook.content_type !== hook.content_type ||
- !triggerWordsSame || !callbackUrlsSame) {
- this.handleConfirmModal();
- } else {
- await this.submitHook();
- }
- }
-
- handleConfirmModal = () => {
- this.setState({showConfirmModal: true});
- }
-
- confirmModalDismissed = () => {
- this.setState({showConfirmModal: false});
- }
-
- submitHook = async () => {
- this.setState({serverError: ''});
-
- const data = await this.props.actions.updateOutgoingHook(this.newHook);
-
- if (data) {
- browserHistory.push(`/${this.props.team.name}/integrations/outgoing_webhooks`);
- return;
- }
-
- this.setState({showConfirmModal: false});
-
- if (this.props.updateOutgoingHookRequest.error) {
- this.setState({serverError: this.props.updateOutgoingHookRequest.error.message});
- }
- }
-
- renderExtra = () => {
- const confirmButton = (
- <FormattedMessage
- id='update_outgoing_webhook.update'
- defaultMessage='Update'
- />
- );
-
- const confirmTitle = (
- <FormattedMessage
- id='update_outgoing_webhook.confirm'
- defaultMessage='Edit Outgoing Webhook'
- />
- );
-
- const confirmMessage = (
- <FormattedMessage
- id='update_outgoing_webhook.question'
- defaultMessage='Your changes may break the existing outgoing webhook. Are you sure you would like to update it?'
- />
- );
-
- return (
- <ConfirmModal
- title={confirmTitle}
- message={confirmMessage}
- confirmButtonText={confirmButton}
- show={this.state.showConfirmModal}
- onConfirm={this.submitHook}
- onCancel={this.confirmModalDismissed}
- />
- );
- }
-
- render() {
- if (!this.props.hook) {
- return <LoadingScreen/>;
- }
-
- return (
- <AbstractOutgoingWebhook
- team={this.props.team}
- header={HEADER}
- footer={FOOTER}
- renderExtra={this.renderExtra()}
- action={this.editOutgoingHook}
- serverError={this.state.serverError}
- initialHook={this.props.hook}
- />
- );
- }
-}
diff --git a/webapp/components/integrations/components/edit_outgoing_webhook/index.js b/webapp/components/integrations/components/edit_outgoing_webhook/index.js
deleted file mode 100644
index a526ac76c..000000000
--- a/webapp/components/integrations/components/edit_outgoing_webhook/index.js
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {updateOutgoingHook, getOutgoingHook} from 'mattermost-redux/actions/integrations';
-
-import EditOutgoingWebhook from './edit_outgoing_webhook.jsx';
-
-function mapStateToProps(state, ownProps) {
- const hookId = ownProps.location.query.id;
-
- return {
- ...ownProps,
- hookId,
- hook: state.entities.integrations.outgoingHooks[hookId],
- updateOutgoingHookRequest: state.requests.integrations.createOutgoingHook
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- updateOutgoingHook,
- getOutgoingHook
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(EditOutgoingWebhook);
diff --git a/webapp/components/integrations/components/installed_command.jsx b/webapp/components/integrations/components/installed_command.jsx
deleted file mode 100644
index 1470712b0..000000000
--- a/webapp/components/integrations/components/installed_command.jsx
+++ /dev/null
@@ -1,176 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {Link} from 'react-router';
-import {FormattedMessage} from 'react-intl';
-
-import DeleteIntegration from './delete_integration.jsx';
-
-export default class InstalledCommand extends React.PureComponent {
- static propTypes = {
-
- /**
- * The team data
- */
- team: PropTypes.object.isRequired,
-
- /**
- * Installed slash command to display
- */
- command: PropTypes.object.isRequired,
-
- /**
- * The function to call when Regenerate Token link is clicked
- */
- onRegenToken: PropTypes.func.isRequired,
-
- /**
- * The function to call when Delete link is clicked
- */
- onDelete: PropTypes.func.isRequired,
-
- /**
- * Set to filter command, comes from BackstageList
- */
- filter: PropTypes.string,
-
- /**
- * The creator user data
- */
- creator: PropTypes.object.isRequired,
-
- /**
- * Set to show edit link
- */
- canChange: PropTypes.bool.isRequired
- }
-
- handleRegenToken = (e) => {
- e.preventDefault();
-
- this.props.onRegenToken(this.props.command);
- }
-
- handleDelete = () => {
- this.props.onDelete(this.props.command);
- }
-
- matchesFilter(command, filter) {
- if (!filter) {
- return true;
- }
-
- return command.display_name.toLowerCase().indexOf(filter) !== -1 ||
- command.description.toLowerCase().indexOf(filter) !== -1 ||
- command.trigger.toLowerCase().indexOf(filter) !== -1;
- }
-
- render() {
- const command = this.props.command;
- const filter = this.props.filter ? this.props.filter.toLowerCase() : '';
-
- if (!this.matchesFilter(command, filter)) {
- return null;
- }
-
- let name;
-
- if (command.display_name) {
- name = command.display_name;
- } else {
- name = (
- <FormattedMessage
- id='installed_commands.unnamed_command'
- defaultMessage='Unnamed Slash Command'
- />
- );
- }
-
- let description = null;
- if (command.description) {
- description = (
- <div className='item-details__row'>
- <span className='item-details__description'>
- {command.description}
- </span>
- </div>
- );
- }
-
- let trigger = '- /' + command.trigger;
- if (command.auto_complete && command.auto_complete_hint) {
- trigger += ' ' + command.auto_complete_hint;
- }
-
- let actions = null;
- if (this.props.canChange) {
- actions = (
- <div className='item-actions'>
- <a
- href='#'
- onClick={this.handleRegenToken}
- >
- <FormattedMessage
- id='installed_integrations.regenToken'
- defaultMessage='Regenerate Token'
- />
- </a>
- {' - '}
- <Link to={`/${this.props.team.name}/integrations/commands/edit?id=${command.id}`}>
- <FormattedMessage
- id='installed_integrations.edit'
- defaultMessage='Edit'
- />
- </Link>
- {' - '}
- <DeleteIntegration
- messageId='installed_commands.delete.confirm'
- onDelete={this.handleDelete}
- />
- </div>
- );
- }
-
- return (
- <div className='backstage-list__item'>
- <div className='item-details'>
- <div className='item-details__row'>
- <span className='item-details__name'>
- {name}
- </span>
- <span className='item-details__trigger'>
- {trigger}
- </span>
- </div>
- {description}
- <div className='item-details__row'>
- <span className='item-details__token'>
- <FormattedMessage
- id='installed_integrations.token'
- defaultMessage='Token: {token}'
- values={{
- token: command.token
- }}
- />
- </span>
- </div>
- <div className='item-details__row'>
- <span className='item-details__creation'>
- <FormattedMessage
- id='installed_integrations.creation'
- defaultMessage='Created by {creator} on {createAt, date, full}'
- values={{
- creator: this.props.creator.username,
- createAt: command.create_at
- }}
- />
- </span>
- </div>
- </div>
- {actions}
- </div>
- );
- }
-}
diff --git a/webapp/components/integrations/components/installed_commands/index.js b/webapp/components/integrations/components/installed_commands/index.js
deleted file mode 100644
index b8fb9b8bd..000000000
--- a/webapp/components/integrations/components/installed_commands/index.js
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {regenCommandToken, deleteCommand} from 'mattermost-redux/actions/integrations';
-
-import InstalledCommands from './installed_commands.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- regenCommandToken,
- deleteCommand
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(InstalledCommands); \ No newline at end of file
diff --git a/webapp/components/integrations/components/installed_commands/installed_commands.jsx b/webapp/components/integrations/components/installed_commands/installed_commands.jsx
deleted file mode 100644
index 7085afdc4..000000000
--- a/webapp/components/integrations/components/installed_commands/installed_commands.jsx
+++ /dev/null
@@ -1,160 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import BackstageList from 'components/backstage/components/backstage_list.jsx';
-import InstalledCommand from '../installed_command.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-
-export default class InstalledCommands extends React.PureComponent {
- static propTypes = {
-
- /**
- * The team object
- */
- team: PropTypes.object,
-
- /**
- * The user object
- */
- user: PropTypes.object,
-
- /**
- * The users collection
- */
- users: PropTypes.object,
-
- /**
- * Installed slash commands to display
- */
- commands: PropTypes.array,
-
- /**
- * Set whether to show the loading... animation or not
- */
- loading: PropTypes.bool,
-
- /**
- * Set to allow changes to installed slash commands
- */
- isAdmin: PropTypes.bool,
-
- actions: PropTypes.shape({
-
- /**
- * The function to call when Regenerate Token link is clicked
- */
- regenCommandToken: PropTypes.func.isRequired,
-
- /**
- * The function to call when Delete link is clicked
- */
- deleteCommand: PropTypes.func.isRequired
- }).isRequired
- }
-
- regenCommandToken = (command) => {
- this.props.actions.regenCommandToken(command.id);
- }
-
- deleteCommand = (command) => {
- this.props.actions.deleteCommand(command.id);
- }
-
- commandCompare(a, b) {
- let nameA = a.display_name;
- if (!nameA) {
- nameA = Utils.localizeMessage('installed_commands.unnamed_command', 'Unnamed Slash Command');
- }
-
- let nameB = b.display_name;
- if (!nameB) {
- nameB = Utils.localizeMessage('installed_commands.unnamed_command', 'Unnamed Slash Command');
- }
-
- return nameA.localeCompare(nameB);
- }
-
- render() {
- const commands = this.props.commands.sort(this.commandCompare).map((command) => {
- const canChange = this.props.isAdmin || this.props.user.id === command.creator_id;
-
- return (
- <InstalledCommand
- key={command.id}
- team={this.props.team}
- command={command}
- onRegenToken={this.regenCommandToken}
- onDelete={this.deleteCommand}
- creator={this.props.users[command.creator_id] || {}}
- canChange={canChange}
- />
- );
- });
-
- return (
- <BackstageList
- header={
- <FormattedMessage
- id='installed_commands.header'
- defaultMessage='Installed Slash Commands'
- />
- }
- addText={
- <FormattedMessage
- id='installed_commands.add'
- defaultMessage='Add Slash Command'
- />
- }
- addLink={'/' + this.props.team.name + '/integrations/commands/add'}
- emptyText={
- <FormattedMessage
- id='installed_commands.empty'
- defaultMessage='No slash commands found'
- />
- }
- helpText={
- <FormattedMessage
- id='installed_commands.help'
- defaultMessage='Use slash commands to connect external tools to Mattermost. {buildYourOwn} or visit the {appDirectory} to find self-hosted, third-party apps and integrations.'
- values={{
- buildYourOwn: (
- <a
- target='_blank'
- rel='noopener noreferrer'
- href='http://docs.mattermost.com/developer/slash-commands.html'
- >
- <FormattedMessage
- id='installed_commands.help.buildYourOwn'
- defaultMessage='Build your own'
- />
- </a>
- ),
- appDirectory: (
- <a
- target='_blank'
- rel='noopener noreferrer'
- href='https://about.mattermost.com/default-app-directory/'
- >
- <FormattedMessage
- id='installed_commands.help.appDirectory'
- defaultMessage='App Directory'
- />
- </a>
- )
- }}
- />
- }
- searchPlaceholder={Utils.localizeMessage('installed_commands.search', 'Search Slash Commands')}
- loading={this.props.loading}
- >
- {commands}
- </BackstageList>
- );
- }
-} \ No newline at end of file
diff --git a/webapp/components/integrations/components/installed_incoming_webhook.jsx b/webapp/components/integrations/components/installed_incoming_webhook.jsx
deleted file mode 100644
index 241205b3f..000000000
--- a/webapp/components/integrations/components/installed_incoming_webhook.jsx
+++ /dev/null
@@ -1,147 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import DeleteIntegration from './delete_integration.jsx';
-
-import ChannelStore from 'stores/channel_store.jsx';
-import {getSiteURL} from 'utils/url.jsx';
-
-import {FormattedMessage} from 'react-intl';
-import {Link} from 'react-router';
-
-export default class InstalledIncomingWebhook extends React.Component {
- static get propTypes() {
- return {
- incomingWebhook: PropTypes.object.isRequired,
- onDelete: PropTypes.func.isRequired,
- filter: PropTypes.string,
- creator: PropTypes.object.isRequired,
- canChange: PropTypes.bool.isRequired,
- team: PropTypes.object.isRequired
- };
- }
-
- constructor(props) {
- super(props);
-
- this.handleDelete = this.handleDelete.bind(this);
- }
-
- handleDelete() {
- this.props.onDelete(this.props.incomingWebhook);
- }
-
- matchesFilter(incomingWebhook, channel, filter) {
- if (!filter) {
- return true;
- }
-
- if (incomingWebhook.display_name.toLowerCase().indexOf(filter) !== -1 ||
- incomingWebhook.description.toLowerCase().indexOf(filter) !== -1) {
- return true;
- }
-
- if (incomingWebhook.channel_id) {
- if (channel && channel.name.toLowerCase().indexOf(filter) !== -1) {
- return true;
- }
- }
-
- return false;
- }
-
- render() {
- const incomingWebhook = this.props.incomingWebhook;
- const channel = ChannelStore.get(incomingWebhook.channel_id);
- const filter = this.props.filter ? this.props.filter.toLowerCase() : '';
-
- if (!this.matchesFilter(incomingWebhook, channel, filter)) {
- return null;
- }
-
- let displayName;
- if (incomingWebhook.display_name) {
- displayName = incomingWebhook.display_name;
- } else if (channel) {
- displayName = channel.display_name;
- } else {
- displayName = (
- <FormattedMessage
- id='installed_incoming_webhooks.unknown_channel'
- defaultMessage='A Private Webhook'
- />
- );
- }
-
- let description = null;
- if (incomingWebhook.description) {
- description = (
- <div className='item-details__row'>
- <span className='item-details__description'>
- {incomingWebhook.description}
- </span>
- </div>
- );
- }
-
- let actions = null;
- if (this.props.canChange) {
- actions = (
- <div className='item-actions'>
- <Link to={`/${this.props.team.name}/integrations/incoming_webhooks/edit?id=${incomingWebhook.id}`}>
- <FormattedMessage
- id='installed_integrations.edit'
- defaultMessage='Edit'
- />
- </Link>
- {' - '}
- <DeleteIntegration
- messageId='installed_incoming_webhooks.delete.confirm'
- onDelete={this.handleDelete}
- />
- </div>
- );
- }
-
- return (
- <div className='backstage-list__item'>
- <div className='item-details'>
- <div className='item-details__row'>
- <span className='item-details__name'>
- {displayName}
- </span>
- </div>
- {description}
- <div className='item-details__row'>
- <span className='item-details__url'>
- <FormattedMessage
- id='installed_integrations.url'
- defaultMessage='URL: {url}'
- values={{
- url: getSiteURL() + '/hooks/' + incomingWebhook.id
- }}
- />
- </span>
- </div>
- <div className='item-details__row'>
- <span className='item-details__creation'>
- <FormattedMessage
- id='installed_integrations.creation'
- defaultMessage='Created by {creator} on {createAt, date, full}'
- values={{
- creator: this.props.creator.username,
- createAt: incomingWebhook.create_at
- }}
- />
- </span>
- </div>
- </div>
- {actions}
- </div>
- );
- }
-}
diff --git a/webapp/components/integrations/components/installed_incoming_webhooks.jsx b/webapp/components/integrations/components/installed_incoming_webhooks.jsx
deleted file mode 100644
index 06fa217dc..000000000
--- a/webapp/components/integrations/components/installed_incoming_webhooks.jsx
+++ /dev/null
@@ -1,178 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import BackstageList from 'components/backstage/components/backstage_list.jsx';
-import InstalledIncomingWebhook from './installed_incoming_webhook.jsx';
-
-import ChannelStore from 'stores/channel_store.jsx';
-import IntegrationStore from 'stores/integration_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-
-import {loadIncomingHooksForTeam, deleteIncomingHook} from 'actions/integration_actions.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-
-export default class InstalledIncomingWebhooks extends React.Component {
- static get propTypes() {
- return {
- team: PropTypes.object,
- user: PropTypes.object,
- isAdmin: PropTypes.bool
- };
- }
-
- constructor(props) {
- super(props);
-
- this.handleIntegrationChange = this.handleIntegrationChange.bind(this);
- this.handleUserChange = this.handleUserChange.bind(this);
- this.deleteIncomingWebhook = this.deleteIncomingWebhook.bind(this);
-
- const teamId = TeamStore.getCurrentId();
-
- this.state = {
- incomingWebhooks: IntegrationStore.getIncomingWebhooks(teamId),
- loading: !IntegrationStore.hasReceivedIncomingWebhooks(teamId),
- users: UserStore.getProfiles()
- };
- }
-
- componentDidMount() {
- IntegrationStore.addChangeListener(this.handleIntegrationChange);
- UserStore.addChangeListener(this.handleUserChange);
-
- if (window.mm_config.EnableIncomingWebhooks === 'true') {
- loadIncomingHooksForTeam(TeamStore.getCurrentId(), () => this.setState({loading: false}));
- }
- }
-
- componentWillUnmount() {
- IntegrationStore.removeChangeListener(this.handleIntegrationChange);
- UserStore.removeChangeListener(this.handleUserChange);
- }
-
- handleIntegrationChange() {
- const teamId = TeamStore.getCurrentId();
-
- this.setState({
- incomingWebhooks: IntegrationStore.getIncomingWebhooks(teamId)
- });
- }
-
- handleUserChange() {
- this.setState({
- users: UserStore.getProfiles()
- });
- }
-
- deleteIncomingWebhook(incomingWebhook) {
- deleteIncomingHook(incomingWebhook.id);
- }
-
- incomingWebhookCompare(a, b) {
- let displayNameA = a.display_name;
- if (!displayNameA) {
- const channelA = ChannelStore.get(a.channel_id);
- if (channelA) {
- displayNameA = channelA.display_name;
- } else {
- displayNameA = Utils.localizeMessage('installed_incoming_webhooks.unknown_channel', 'A Private Webhook');
- }
- }
-
- let displayNameB = b.display_name;
- if (!displayNameB) {
- const channelB = ChannelStore.get(b.channel_id);
- if (channelB) {
- displayNameB = channelB.display_name;
- } else {
- displayNameB = Utils.localizeMessage('installed_incoming_webhooks.unknown_channel', 'A Private Webhook');
- }
- }
-
- return displayNameA.localeCompare(displayNameB);
- }
-
- render() {
- const incomingWebhooks = this.state.incomingWebhooks.sort(this.incomingWebhookCompare).map((incomingWebhook) => {
- const canChange = this.props.isAdmin || this.props.user.id === incomingWebhook.user_id;
-
- return (
- <InstalledIncomingWebhook
- key={incomingWebhook.id}
- incomingWebhook={incomingWebhook}
- onDelete={this.deleteIncomingWebhook}
- creator={this.state.users[incomingWebhook.user_id] || {}}
- canChange={canChange}
- team={this.props.team}
- />
- );
- });
-
- return (
- <BackstageList
- header={
- <FormattedMessage
- id='installed_incoming_webhooks.header'
- defaultMessage='Installed Incoming Webhooks'
- />
- }
- addText={
- <FormattedMessage
- id='installed_incoming_webhooks.add'
- defaultMessage='Add Incoming Webhook'
- />
- }
- addLink={'/' + this.props.team.name + '/integrations/incoming_webhooks/add'}
- emptyText={
- <FormattedMessage
- id='installed_incoming_webhooks.empty'
- defaultMessage='No incoming webhooks found'
- />
- }
- helpText={
- <FormattedMessage
- id='installed_incoming_webhooks.help'
- defaultMessage='Use incoming webhooks to connect external tools to Mattermost. {buildYourOwn} or visit the {appDirectory} to find self-hosted, third-party apps and integrations.'
- values={{
- buildYourOwn: (
- <a
- target='_blank'
- rel='noopener noreferrer'
- href='http://docs.mattermost.com/developer/webhooks-incoming.html'
- >
- <FormattedMessage
- id='installed_incoming_webhooks.help.buildYourOwn'
- defaultMessage='Build your own'
- />
- </a>
- ),
- appDirectory: (
- <a
- target='_blank'
- rel='noopener noreferrer'
- href='https://about.mattermost.com/default-app-directory/'
- >
- <FormattedMessage
- id='installed_incoming_webhooks.help.appDirectory'
- defaultMessage='App Directory'
- />
- </a>
- )
- }}
- />
- }
- searchPlaceholder={Utils.localizeMessage('installed_incoming_webhooks.search', 'Search Incoming Webhooks')}
- loading={this.state.loading}
- >
- {incomingWebhooks}
- </BackstageList>
- );
- }
-}
diff --git a/webapp/components/integrations/components/installed_oauth_app.jsx b/webapp/components/integrations/components/installed_oauth_app.jsx
deleted file mode 100644
index bcb7a7c96..000000000
--- a/webapp/components/integrations/components/installed_oauth_app.jsx
+++ /dev/null
@@ -1,270 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import * as Utils from 'utils/utils.jsx';
-
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-import FormError from 'components/form_error.jsx';
-import DeleteIntegration from './delete_integration.jsx';
-
-const FAKE_SECRET = '***************';
-
-export default class InstalledOAuthApp extends React.PureComponent {
- static propTypes = {
-
- /**
- * The oauthApp data
- */
- oauthApp: PropTypes.object.isRequired,
-
- /**
- * The request state for regenOAuthAppSecret action. Contains status and error
- */
- regenOAuthAppSecretRequest: PropTypes.object.isRequired,
-
- /**
- * The function to call when Regenerate Secret link is clicked
- */
- onRegenerateSecret: PropTypes.func.isRequired,
-
- /**
- * The function to call when Delete link is clicked
- */
- onDelete: PropTypes.func.isRequired,
-
- /**
- * Set to filter OAuthApp
- */
- filter: PropTypes.string
- }
-
- constructor(props) {
- super(props);
-
- this.state = {
- clientSecret: FAKE_SECRET
- };
- }
-
- handleShowClientSecret = (e) => {
- if (e && e.preventDefault) {
- e.preventDefault();
- }
- this.setState({clientSecret: this.props.oauthApp.client_secret});
- }
-
- handleHideClientSecret = (e) => {
- e.preventDefault();
- this.setState({clientSecret: FAKE_SECRET});
- }
-
- handleRegenerate = (e) => {
- e.preventDefault();
- this.props.onRegenerateSecret(this.props.oauthApp.id).then(
- () => {
- const {error} = this.props.regenOAuthAppSecretRequest;
- if (error) {
- this.setState({error: error.message});
- } else {
- this.setState({error: null});
- this.handleShowClientSecret();
- }
- }
- );
- }
-
- handleDelete = () => {
- this.props.onDelete(this.props.oauthApp);
- }
-
- matchesFilter = (oauthApp, filter) => {
- if (!filter) {
- return true;
- }
-
- return oauthApp.name.toLowerCase().indexOf(filter) !== -1;
- }
-
- render() {
- const oauthApp = this.props.oauthApp;
- let error;
-
- if (this.state.error) {
- error = (
- <FormError
- error={this.state.error}
- />
- );
- }
-
- if (!this.matchesFilter(oauthApp, this.props.filter)) {
- return null;
- }
-
- let name;
- if (oauthApp.name) {
- name = oauthApp.name;
- } else {
- name = (
- <FormattedMessage
- id='installed_integrations.unnamed_oauth_app'
- defaultMessage='Unnamed OAuth 2.0 Application'
- />
- );
- }
-
- let description;
- if (oauthApp.description) {
- description = (
- <div className='item-details__row'>
- <span className='item-details__description'>
- {oauthApp.description}
- </span>
- </div>
- );
- }
-
- const urls = (
- <div className='item-details__row'>
- <span className='item-details__url'>
- <FormattedMessage
- id='installed_integrations.callback_urls'
- defaultMessage='Callback URLs: {urls}'
- values={{
- urls: oauthApp.callback_urls.join(', ')
- }}
- />
- </span>
- </div>
- );
-
- let isTrusted;
- if (oauthApp.is_trusted) {
- isTrusted = Utils.localizeMessage('installed_oauth_apps.trusted.yes', 'Yes');
- } else {
- isTrusted = Utils.localizeMessage('installed_oauth_apps.trusted.no', 'No');
- }
-
- let showHide;
- if (this.state.clientSecret === FAKE_SECRET) {
- showHide = (
- <a
- href='#'
- onClick={this.handleShowClientSecret}
- >
- <FormattedMessage
- id='installed_integrations.showSecret'
- defaultMessage='Show Secret'
- />
- </a>
- );
- } else {
- showHide = (
- <a
- href='#'
- onClick={this.handleHideClientSecret}
- >
- <FormattedMessage
- id='installed_integrations.hideSecret'
- defaultMessage='Hide Secret'
- />
- </a>
- );
- }
-
- const regen = (
- <a
- href='#'
- onClick={this.handleRegenerate}
- >
- <FormattedMessage
- id='installed_integrations.regenSecret'
- defaultMessage='Regenerate Secret'
- />
- </a>
- );
-
- let icon;
- if (oauthApp.icon_url) {
- icon = (
- <div className='integration__icon integration-list__icon'>
- <img src={oauthApp.icon_url}/>
- </div>
- );
- }
-
- return (
- <div className='backstage-list__item'>
- {icon}
- <div className='item-details'>
- <div className='item-details__row'>
- <span className='item-details__name'>
- {name}
- </span>
- </div>
- {error}
- {description}
- <div className='item-details__row'>
- <span className='item-details__url'>
- <FormattedHTMLMessage
- id='installed_oauth_apps.is_trusted'
- defaultMessage='Is Trusted: <strong>{isTrusted}</strong>'
- values={{
- isTrusted
- }}
- />
- </span>
- </div>
- <div className='item-details__row'>
- <span className='item-details__token'>
- <FormattedHTMLMessage
- id='installed_integrations.client_id'
- defaultMessage='Client ID: <strong>{clientId}</strong>'
- values={{
- clientId: oauthApp.id
- }}
- />
- </span>
- </div>
- <div className='item-details__row'>
- <span className='item-details__token'>
- <FormattedHTMLMessage
- id='installed_integrations.client_secret'
- defaultMessage='Client Secret: <strong>{clientSecret}</strong>'
- values={{
- clientSecret: this.state.clientSecret
- }}
- />
- </span>
- </div>
- {urls}
- <div className='item-details__row'>
- <span className='item-details__creation'>
- <FormattedMessage
- id='installed_integrations.creation'
- defaultMessage='Created by {creator} on {createAt, date, full}'
- values={{
- creator: Utils.displayUsername(oauthApp.creator_id),
- createAt: oauthApp.create_at
- }}
- />
- </span>
- </div>
- </div>
- <div className='item-actions'>
- {showHide}
- {' - '}
- {regen}
- {' - '}
- <DeleteIntegration
- messageId='installed_oauth_apps.delete.confirm'
- onDelete={this.handleDelete}
- />
- </div>
- </div>
- );
- }
-} \ No newline at end of file
diff --git a/webapp/components/integrations/components/installed_oauth_apps/index.js b/webapp/components/integrations/components/installed_oauth_apps/index.js
deleted file mode 100644
index bfeed6d66..000000000
--- a/webapp/components/integrations/components/installed_oauth_apps/index.js
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import * as Actions from 'mattermost-redux/actions/integrations';
-import {getOAuthApps} from 'mattermost-redux/selectors/entities/integrations';
-import {isCurrentUserSystemAdmin} from 'mattermost-redux/selectors/entities/users';
-
-import InstalledOAuthApps from './installed_oauth_apps.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps,
- oauthApps: getOAuthApps(state),
- isSystemAdmin: isCurrentUserSystemAdmin(state),
- regenOAuthAppSecretRequest: state.requests.integrations.updateOAuthApp
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getOAuthApps: Actions.getOAuthApps,
- regenOAuthAppSecret: Actions.regenOAuthAppSecret,
- deleteOAuthApp: Actions.deleteOAuthApp
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(InstalledOAuthApps); \ No newline at end of file
diff --git a/webapp/components/integrations/components/installed_oauth_apps/installed_oauth_apps.jsx b/webapp/components/integrations/components/installed_oauth_apps/installed_oauth_apps.jsx
deleted file mode 100644
index 4cd27ab57..000000000
--- a/webapp/components/integrations/components/installed_oauth_apps/installed_oauth_apps.jsx
+++ /dev/null
@@ -1,165 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import {localizeMessage} from 'utils/utils.jsx';
-import BackstageList from 'components/backstage/components/backstage_list.jsx';
-import {FormattedMessage} from 'react-intl';
-import InstalledOAuthApp from '../installed_oauth_app.jsx';
-
-export default class InstalledOAuthApps extends React.PureComponent {
- static propTypes = {
-
- /**
- * The team data
- */
- team: PropTypes.object,
-
- /**
- * The oauthApps data
- */
- oauthApps: PropTypes.object,
-
- /**
- * Set if user is admin
- */
- isSystemAdmin: PropTypes.bool,
-
- /**
- * The request state for regenOAuthAppSecret action. Contains status and error
- */
- regenOAuthAppSecretRequest: PropTypes.object.isRequired,
-
- actions: PropTypes.shape({
-
- /**
- * The function to call to fetch OAuth apps
- */
- getOAuthApps: PropTypes.func.isRequired,
-
- /**
- * The function to call when Regenerate Secret link is clicked
- */
- regenOAuthAppSecret: PropTypes.func.isRequired,
-
- /**
- * The function to call when Delete link is clicked
- */
- deleteOAuthApp: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
- this.state = {
- loading: true
- };
- }
-
- componentDidMount() {
- if (window.mm_config.EnableOAuthServiceProvider === 'true') {
- this.props.actions.getOAuthApps().then(
- () => this.setState({loading: false})
- );
- }
- }
-
- deleteOAuthApp = (app) => {
- this.props.actions.deleteOAuthApp(app.id);
- }
-
- oauthAppCompare(a, b) {
- let nameA = a.name;
- if (!nameA) {
- nameA = localizeMessage('installed_integrations.unnamed_oauth_app', 'Unnamed OAuth 2.0 Application');
- }
-
- let nameB = b.name;
- if (!nameB) {
- nameB = localizeMessage('installed_integrations.unnamed_oauth_app', 'Unnamed OAuth 2.0 Application');
- }
-
- return nameA.localeCompare(nameB);
- }
-
- render() {
- const oauthApps = Object.values(this.props.oauthApps).sort(this.oauthAppCompare).map((app) => {
- return (
- <InstalledOAuthApp
- key={app.id}
- oauthApp={app}
- regenOAuthAppSecretRequest={this.props.regenOAuthAppSecretRequest}
- onRegenerateSecret={this.props.actions.regenOAuthAppSecret}
- onDelete={this.deleteOAuthApp}
- />
- );
- });
-
- const config = global.mm_config;
- const integrationsEnabled = (config.EnableOAuthServiceProvider === 'true' &&
- (this.props.isSystemAdmin || config.EnableOnlyAdminIntegrations !== 'true'));
- let props;
- if (integrationsEnabled) {
- props = {
- addLink: '/' + this.props.team.name + '/integrations/oauth2-apps/add',
- addText: localizeMessage('installed_oauth_apps.add', 'Add OAuth 2.0 Application')
- };
- }
-
- return (
- <BackstageList
- header={
- <FormattedMessage
- id='installed_oauth_apps.header'
- defaultMessage='OAuth 2.0 Applications'
- />
- }
- helpText={
- <FormattedMessage
- id='installed_oauth_apps.help'
- defaultMessage='Create {oauthApplications} to securely integrate bots and third-party apps with Mattermost. Visit the {appDirectory} to find available self-hosted apps.'
- values={{
- oauthApplications: (
- <a
- target='_blank'
- rel='noopener noreferrer'
- href='https://docs.mattermost.com/developer/oauth-2-0-applications.html'
- >
- <FormattedMessage
- id='installed_oauth_apps.help.oauthApplications'
- defaultMessage='OAuth 2.0 applications'
- />
- </a>
- ),
- appDirectory: (
- <a
- target='_blank'
- rel='noopener noreferrer'
- href='https://about.mattermost.com/default-app-directory/'
- >
- <FormattedMessage
- id='installed_oauth_apps.help.appDirectory'
- defaultMessage='App Directory'
- />
- </a>
- )
- }}
- />
- }
- emptyText={
- <FormattedMessage
- id='installed_oauth_apps.empty'
- defaultMessage='No OAuth 2.0 Applications found'
- />
- }
- searchPlaceholder={localizeMessage('installed_oauth_apps.search', 'Search OAuth 2.0 Applications')}
- loading={this.state.loading}
- {...props}
- >
- {oauthApps}
- </BackstageList>
- );
- }
-}
diff --git a/webapp/components/integrations/components/installed_outgoing_webhook.jsx b/webapp/components/integrations/components/installed_outgoing_webhook.jsx
deleted file mode 100644
index 70c606b97..000000000
--- a/webapp/components/integrations/components/installed_outgoing_webhook.jsx
+++ /dev/null
@@ -1,244 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import ChannelStore from 'stores/channel_store.jsx';
-
-import {FormattedMessage} from 'react-intl';
-import {Link} from 'react-router';
-
-import DeleteIntegration from './delete_integration.jsx';
-
-export default class InstalledOutgoingWebhook extends React.Component {
- static get propTypes() {
- return {
- outgoingWebhook: PropTypes.object.isRequired,
- onRegenToken: PropTypes.func.isRequired,
- onDelete: PropTypes.func.isRequired,
- filter: PropTypes.string,
- creator: PropTypes.object.isRequired,
- canChange: PropTypes.bool.isRequired,
- team: PropTypes.object.isRequired
- };
- }
-
- constructor(props) {
- super(props);
-
- this.handleRegenToken = this.handleRegenToken.bind(this);
- this.handleDelete = this.handleDelete.bind(this);
- }
-
- handleRegenToken(e) {
- e.preventDefault();
-
- this.props.onRegenToken(this.props.outgoingWebhook);
- }
-
- handleDelete() {
- this.props.onDelete(this.props.outgoingWebhook);
- }
-
- matchesFilter(outgoingWebhook, channel, filter) {
- if (!filter) {
- return true;
- }
-
- if (outgoingWebhook.display_name.toLowerCase().indexOf(filter) !== -1 ||
- outgoingWebhook.description.toLowerCase().indexOf(filter) !== -1) {
- return true;
- }
-
- for (const trigger of outgoingWebhook.trigger_words) {
- if (trigger.toLowerCase().indexOf(filter) !== -1) {
- return true;
- }
- }
-
- if (channel) {
- if (channel && channel.name.toLowerCase().indexOf(filter) !== -1) {
- return true;
- }
- }
-
- return false;
- }
-
- render() {
- const outgoingWebhook = this.props.outgoingWebhook;
- const channel = ChannelStore.get(outgoingWebhook.channel_id);
- const filter = this.props.filter ? this.props.filter.toLowerCase() : '';
- const triggerWordsFull = 0;
- const triggerWordsStartsWith = 1;
-
- if (!this.matchesFilter(outgoingWebhook, channel, filter)) {
- return null;
- }
-
- let displayName;
- if (outgoingWebhook.display_name) {
- displayName = outgoingWebhook.display_name;
- } else if (channel) {
- displayName = channel.display_name;
- } else {
- displayName = (
- <FormattedMessage
- id='installed_outgoing_webhooks.unknown_channel'
- defaultMessage='A Private Webhook'
- />
- );
- }
-
- let description = null;
- if (outgoingWebhook.description) {
- description = (
- <div className='item-details__row'>
- <span className='item-details__description'>
- {outgoingWebhook.description}
- </span>
- </div>
- );
- }
-
- let triggerWords = null;
- if (outgoingWebhook.trigger_words && outgoingWebhook.trigger_words.length > 0) {
- triggerWords = (
- <div className='item-details__row'>
- <span className='item-details__trigger-words'>
- <FormattedMessage
- id='installed_integrations.triggerWords'
- defaultMessage='Trigger Words: {triggerWords}'
- values={{
- triggerWords: outgoingWebhook.trigger_words.join(', ')
- }}
- />
- </span>
- </div>
- );
- }
-
- const urls = (
- <div className='item-details__row'>
- <span className='item-details__url'>
- <FormattedMessage
- id='installed_integrations.callback_urls'
- defaultMessage='Callback URLs: {urls}'
- values={{
- urls: outgoingWebhook.callback_urls.join(', ')
- }}
- />
- </span>
- </div>
- );
-
- let triggerWhen;
- if (outgoingWebhook.trigger_when === triggerWordsFull) {
- triggerWhen = (
- <FormattedMessage
- id='add_outgoing_webhook.triggerWordsTriggerWhenFullWord'
- defaultMessage='First word matches a trigger word exactly'
- />
- );
- } else if (outgoingWebhook.trigger_when === triggerWordsStartsWith) {
- triggerWhen = (
- <FormattedMessage
- id='add_outgoing_webhook.triggerWordsTriggerWhenStartsWith'
- defaultMessage='First word starts with a trigger word'
- />
- );
- }
-
- let actions = null;
- if (this.props.canChange) {
- actions = (
- <div className='item-actions'>
- <a
- href='#'
- onClick={this.handleRegenToken}
- >
- <FormattedMessage
- id='installed_integrations.regenToken'
- defaultMessage='Regen Token'
- />
- </a>
- {' - '}
- <Link to={`/${this.props.team.name}/integrations/outgoing_webhooks/edit?id=${outgoingWebhook.id}`}>
- <FormattedMessage
- id='installed_integrations.edit'
- defaultMessage='Edit'
- />
- </Link>
- {' - '}
- <DeleteIntegration
- messageId='installed_outgoing_webhooks.delete.confirm'
- onDelete={this.handleDelete}
- />
- </div>
- );
- }
-
- return (
- <div className='backstage-list__item'>
- <div className='item-details'>
- <div className='item-details__row'>
- <span className='item-details__name'>
- {displayName}
- </span>
- </div>
- {description}
- <div className='item-details__row'>
- <span className='item-details__content_type'>
- <FormattedMessage
- id='installed_integrations.content_type'
- defaultMessage='Content-Type: {contentType}'
- values={{
- contentType: outgoingWebhook.content_type || 'application/x-www-form-urlencoded'
- }}
- />
- </span>
- </div>
- {triggerWords}
- <div className='item-details__row'>
- <span className='item-details__trigger-when'>
- <FormattedMessage
- id='installed_integrations.triggerWhen'
- defaultMessage='Trigger When: {triggerWhen}'
- values={{
- triggerWhen
- }}
- />
- </span>
- </div>
- <div className='item-details__row'>
- <span className='item-details__token'>
- <FormattedMessage
- id='installed_integrations.token'
- defaultMessage='Token: {token}'
- values={{
- token: outgoingWebhook.token
- }}
- />
- </span>
- </div>
- <div className='item-details__row'>
- <span className='item-details__creation'>
- <FormattedMessage
- id='installed_integrations.creation'
- defaultMessage='Created by {creator} on {createAt, date, full}'
- values={{
- creator: this.props.creator.username,
- createAt: outgoingWebhook.create_at
- }}
- />
- </span>
- </div>
- {urls}
- </div>
- {actions}
- </div>
- );
- }
-}
diff --git a/webapp/components/integrations/components/installed_outgoing_webhooks.jsx b/webapp/components/integrations/components/installed_outgoing_webhooks.jsx
deleted file mode 100644
index 9b8baf3f4..000000000
--- a/webapp/components/integrations/components/installed_outgoing_webhooks.jsx
+++ /dev/null
@@ -1,182 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import BackstageList from 'components/backstage/components/backstage_list.jsx';
-import InstalledOutgoingWebhook from './installed_outgoing_webhook.jsx';
-
-import ChannelStore from 'stores/channel_store.jsx';
-import IntegrationStore from 'stores/integration_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-
-import {loadOutgoingHooksForTeam, regenOutgoingHookToken, deleteOutgoingHook} from 'actions/integration_actions.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-
-export default class InstalledOutgoingWebhooks extends React.Component {
- static get propTypes() {
- return {
- team: PropTypes.object,
- user: PropTypes.object,
- isAdmin: PropTypes.bool
- };
- }
-
- constructor(props) {
- super(props);
-
- this.handleIntegrationChange = this.handleIntegrationChange.bind(this);
- this.handleUserChange = this.handleUserChange.bind(this);
- this.regenOutgoingWebhookToken = this.regenOutgoingWebhookToken.bind(this);
- this.deleteOutgoingWebhook = this.deleteOutgoingWebhook.bind(this);
-
- const teamId = TeamStore.getCurrentId();
-
- this.state = {
- outgoingWebhooks: IntegrationStore.getOutgoingWebhooks(teamId),
- loading: !IntegrationStore.hasReceivedOutgoingWebhooks(teamId),
- users: UserStore.getProfiles()
- };
- }
-
- componentDidMount() {
- IntegrationStore.addChangeListener(this.handleIntegrationChange);
- UserStore.addChangeListener(this.handleUserChange);
-
- if (window.mm_config.EnableOutgoingWebhooks === 'true') {
- loadOutgoingHooksForTeam(TeamStore.getCurrentId(), () => this.setState({loading: false}));
- }
- }
-
- componentWillUnmount() {
- IntegrationStore.removeChangeListener(this.handleIntegrationChange);
- UserStore.removeChangeListener(this.handleUserChange);
- }
-
- handleIntegrationChange() {
- const teamId = TeamStore.getCurrentId();
-
- this.setState({
- outgoingWebhooks: IntegrationStore.getOutgoingWebhooks(teamId)
- });
- }
-
- handleUserChange() {
- this.setState({users: UserStore.getProfiles()});
- }
-
- regenOutgoingWebhookToken(outgoingWebhook) {
- regenOutgoingHookToken(outgoingWebhook.id);
- }
-
- deleteOutgoingWebhook(outgoingWebhook) {
- deleteOutgoingHook(outgoingWebhook.id);
- }
-
- outgoingWebhookCompare(a, b) {
- let displayNameA = a.display_name;
- if (!displayNameA) {
- const channelA = ChannelStore.get(a.channel_id);
- if (channelA) {
- displayNameA = channelA.display_name;
- } else {
- displayNameA = Utils.localizeMessage('installed_outgoing_webhooks.unknown_channel', 'A Private Webhook');
- }
- }
-
- let displayNameB = b.display_name;
- if (!displayNameB) {
- const channelB = ChannelStore.get(b.channel_id);
- if (channelB) {
- displayNameB = channelB.display_name;
- } else {
- displayNameB = Utils.localizeMessage('installed_outgoing_webhooks.unknown_channel', 'A Private Webhook');
- }
- }
-
- return displayNameA.localeCompare(displayNameB);
- }
-
- render() {
- const outgoingWebhooks = this.state.outgoingWebhooks.sort(this.outgoingWebhookCompare).map((outgoingWebhook) => {
- const canChange = this.props.isAdmin || this.props.user.id === outgoingWebhook.creator_id;
-
- return (
- <InstalledOutgoingWebhook
- key={outgoingWebhook.id}
- outgoingWebhook={outgoingWebhook}
- onRegenToken={this.regenOutgoingWebhookToken}
- onDelete={this.deleteOutgoingWebhook}
- creator={this.state.users[outgoingWebhook.creator_id] || {}}
- canChange={canChange}
- team={this.props.team}
- />
- );
- });
-
- return (
- <BackstageList
- header={
- <FormattedMessage
- id='installed_outgoing_webhooks.header'
- defaultMessage='Installed Outgoing Webhooks'
- />
- }
- addText={
- <FormattedMessage
- id='installed_outgoing_webhooks.add'
- defaultMessage='Add Outgoing Webhook'
- />
- }
- addLink={'/' + this.props.team.name + '/integrations/outgoing_webhooks/add'}
- emptyText={
- <FormattedMessage
- id='installed_outgoing_webhooks.empty'
- defaultMessage='No outgoing webhooks found'
- />
- }
- helpText={
- <FormattedMessage
- id='installed_outgoing_webhooks.help'
- defaultMessage='Use outgoing webhooks to connect external tools to Mattermost. {buildYourOwn} or visit the {appDirectory} to find self-hosted, third-party apps and integrations.'
- values={{
- buildYourOwn: (
- <a
- target='_blank'
- rel='noopener noreferrer'
- href='http://docs.mattermost.com/developer/webhooks-outgoing.html'
- >
- <FormattedMessage
- id='installed_outgoing_webhooks.help.buildYourOwn'
- defaultMessage='Build your own'
- />
- </a>
- ),
- appDirectory: (
- <a
- target='_blank'
- rel='noopener noreferrer'
- href='https://about.mattermost.com/default-app-directory/'
- >
- <FormattedMessage
- id='installed_outgoing_webhooks.help.appDirectory'
- defaultMessage='App Directory'
- />
- </a>
- )
- }}
- />
- }
- searchPlaceholder={Utils.localizeMessage('installed_outgoing_webhooks.search', 'Search Outgoing Webhooks')}
- loading={this.state.loading}
- >
- {outgoingWebhooks}
- </BackstageList>
- );
- }
-}
diff --git a/webapp/components/integrations/components/integration_option.jsx b/webapp/components/integrations/components/integration_option.jsx
deleted file mode 100644
index dc411cb07..000000000
--- a/webapp/components/integrations/components/integration_option.jsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import {Link} from 'react-router/es6';
-
-export default class IntegrationOption extends React.Component {
- static get propTypes() {
- return {
- image: PropTypes.string.isRequired,
- title: PropTypes.node.isRequired,
- description: PropTypes.node.isRequired,
- link: PropTypes.string.isRequired
- };
- }
-
- render() {
- const {image, title, description, link} = this.props;
-
- return (
- <Link
- to={link}
- className='integration-option'
- >
- <img
- className='integration-option__image'
- src={image}
- />
- <div className='integration-option__title'>
- {title}
- </div>
- <div className='integration-option__description'>
- {description}
- </div>
- </Link>
- );
- }
-}
diff --git a/webapp/components/integrations/components/integrations.jsx b/webapp/components/integrations/components/integrations.jsx
deleted file mode 100644
index 7de8810a0..000000000
--- a/webapp/components/integrations/components/integrations.jsx
+++ /dev/null
@@ -1,175 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import {FormattedMessage} from 'react-intl';
-import IntegrationOption from './integration_option.jsx';
-
-import IncomingWebhookIcon from 'images/incoming_webhook.jpg';
-import OutgoingWebhookIcon from 'images/outgoing_webhook.jpg';
-import SlashCommandIcon from 'images/slash_command_icon.jpg';
-import OAuthIcon from 'images/oauth_icon.png';
-
-import * as Utils from 'utils/utils.jsx';
-
-export default class Integrations extends React.Component {
- static get propTypes() {
- return {
- team: PropTypes.object,
- user: PropTypes.object
- };
- }
-
- constructor(props) {
- super(props);
-
- this.updateTitle = this.updateTitle.bind(this);
- }
-
- componentDidMount() {
- this.updateTitle();
- }
-
- updateTitle() {
- let currentSiteName = '';
- if (global.window.mm_config.SiteName != null) {
- currentSiteName = global.window.mm_config.SiteName;
- }
-
- document.title = Utils.localizeMessage('admin.sidebar.integrations', 'Integrations') + ' - ' + this.props.team.display_name + ' ' + currentSiteName;
- }
-
- render() {
- const options = [];
- const config = window.mm_config;
- const isSystemAdmin = Utils.isSystemAdmin(this.props.user.roles);
-
- if (config.EnableIncomingWebhooks === 'true') {
- options.push(
- <IntegrationOption
- key='incomingWebhook'
- image={IncomingWebhookIcon}
- title={
- <FormattedMessage
- id='integrations.incomingWebhook.title'
- defaultMessage='Incoming Webhook'
- />
- }
- description={
- <FormattedMessage
- id='integrations.incomingWebhook.description'
- defaultMessage='Incoming webhooks allow external integrations to send messages'
- />
- }
- link={'/' + this.props.team.name + '/integrations/incoming_webhooks'}
- />
- );
- }
-
- if (config.EnableOutgoingWebhooks === 'true') {
- options.push(
- <IntegrationOption
- key='outgoingWebhook'
- image={OutgoingWebhookIcon}
- title={
- <FormattedMessage
- id='integrations.outgoingWebhook.title'
- defaultMessage='Outgoing Webhook'
- />
- }
- description={
- <FormattedMessage
- id='integrations.outgoingWebhook.description'
- defaultMessage='Outgoing webhooks allow external integrations to receive and respond to messages'
- />
- }
- link={'/' + this.props.team.name + '/integrations/outgoing_webhooks'}
- />
- );
- }
-
- if (config.EnableCommands === 'true') {
- options.push(
- <IntegrationOption
- key='command'
- image={SlashCommandIcon}
- title={
- <FormattedMessage
- id='integrations.command.title'
- defaultMessage='Slash Command'
- />
- }
- description={
- <FormattedMessage
- id='integrations.command.description'
- defaultMessage='Slash commands send events to an external integration'
- />
- }
- link={'/' + this.props.team.name + '/integrations/commands'}
- />
- );
- }
-
- if (config.EnableOAuthServiceProvider === 'true' && (isSystemAdmin || config.EnableOnlyAdminIntegrations !== 'true')) {
- options.push(
- <IntegrationOption
- key='oauth2Apps'
- image={OAuthIcon}
- title={
- <FormattedMessage
- id='integrations.oauthApps.title'
- defaultMessage='OAuth 2.0 Applications'
- />
- }
- description={
- <FormattedMessage
- id='integrations.oauthApps.description'
- defaultMessage='Auth 2.0 allows external applications to make authorized requests to the Mattermost API.'
- />
- }
- link={'/' + this.props.team.name + '/integrations/oauth2-apps'}
- />
- );
- }
-
- return (
- <div className='backstage-content row'>
- <div className='backstage-header'>
- <h1>
- <FormattedMessage
- id='integrations.header'
- defaultMessage='Integrations'
- />
- </h1>
- </div>
- <div className='backstage-list__help'>
- <FormattedMessage
- id='integrations.help'
- defaultMessage='Visit the {appDirectory} to find self-hosted, third-party apps and integrations for Mattermost.'
- values={{
- appDirectory: (
- <a
- target='_blank'
- rel='noopener noreferrer'
- href='https://about.mattermost.com/default-app-directory/'
- >
- <FormattedMessage
- id='integrations.help.appDirectory'
- defaultMessage='App Directory'
- />
- </a>
- )
- }}
- />
- </div>
- <div>
- {options}
- </div>
- </div>
- );
- }
-}
-
diff --git a/webapp/components/invite_member_modal.jsx b/webapp/components/invite_member_modal.jsx
deleted file mode 100644
index 07aa9a1a3..000000000
--- a/webapp/components/invite_member_modal.jsx
+++ /dev/null
@@ -1,538 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ReactDOM from 'react-dom';
-import * as utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-const ActionTypes = Constants.ActionTypes;
-import * as GlobalActions from 'actions/global_actions.jsx';
-import ModalStore from 'stores/modal_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-import ChannelStore from 'stores/channel_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-import ConfirmModal from './confirm_modal.jsx';
-import {inviteMembers} from 'actions/team_actions.jsx';
-
-import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-
-import {Modal} from 'react-bootstrap';
-
-const holders = defineMessages({
- emailError: {
- id: 'invite_member.emailError',
- defaultMessage: 'Please enter a valid email address'
- },
- firstname: {
- id: 'invite_member.firstname',
- defaultMessage: 'First name'
- },
- lastname: {
- id: 'invite_member.lastname',
- defaultMessage: 'Last name'
- },
- modalTitle: {
- id: 'invite_member.modalTitle',
- defaultMessage: 'Discard Invitations?'
- },
- modalMessage: {
- id: 'invite_member.modalMessage',
- defaultMessage: 'You have unsent invitations, are you sure you want to discard them?'
- },
- modalButton: {
- id: 'invite_member.modalButton',
- defaultMessage: 'Yes, Discard'
- }
-});
-
-import React from 'react';
-
-class InviteMemberModal extends React.Component {
- constructor(props) {
- super(props);
-
- this.teamChange = this.teamChange.bind(this);
- this.handleToggle = this.handleToggle.bind(this);
- this.handleSubmit = this.handleSubmit.bind(this);
- this.handleHide = this.handleHide.bind(this);
- this.addInviteFields = this.addInviteFields.bind(this);
- this.clearFields = this.clearFields.bind(this);
- this.removeInviteFields = this.removeInviteFields.bind(this);
- this.showGetTeamInviteLinkModal = this.showGetTeamInviteLinkModal.bind(this);
- this.handleKeyDown = this.handleKeyDown.bind(this);
-
- const team = TeamStore.getCurrent();
-
- this.state = {
- show: false,
- inviteIds: [0],
- idCount: 0,
- emailErrors: {},
- firstNameErrors: {},
- lastNameErrors: {},
- emailEnabled: global.window.mm_config.SendEmailNotifications === 'true',
- userCreationEnabled: global.window.mm_config.EnableUserCreation === 'true',
- showConfirmModal: false,
- isSendingEmails: false,
- teamType: team ? team.type : null
- };
- }
-
- teamChange() {
- const team = TeamStore.getCurrent();
- const teamType = team ? team.type : null;
- this.setState({
- teamType
- });
- }
-
- componentDidMount() {
- ModalStore.addModalListener(ActionTypes.TOGGLE_INVITE_MEMBER_MODAL, this.handleToggle);
- TeamStore.addChangeListener(this.teamChange);
- }
-
- componentWillUnmount() {
- ModalStore.removeModalListener(ActionTypes.TOGGLE_INVITE_MEMBER_MODAL, this.handleToggle);
- TeamStore.removeChangeListener(this.teamChange);
- }
-
- handleToggle(value) {
- this.setState({
- show: value,
- serverError: null
- });
- }
-
- handleSubmit() {
- if (!this.state.emailEnabled) {
- return;
- }
-
- var inviteIds = this.state.inviteIds;
- var count = inviteIds.length;
- var invites = [];
- var emailErrors = this.state.emailErrors;
- var firstNameErrors = this.state.firstNameErrors;
- var lastNameErrors = this.state.lastNameErrors;
- var valid = true;
-
- for (var i = 0; i < count; i++) {
- var invite = {};
- var index = inviteIds[i];
- invite.email = ReactDOM.findDOMNode(this.refs['email' + index]).value.trim();
- invite.firstName = ReactDOM.findDOMNode(this.refs['first_name' + index]).value.trim();
- invite.lastName = ReactDOM.findDOMNode(this.refs['last_name' + index]).value.trim();
- if (invite.email !== '' || index === 0) {
- if (!invite.email || !utils.isEmail(invite.email)) {
- emailErrors[index] = this.props.intl.formatMessage(holders.emailError);
- valid = false;
- } else {
- emailErrors[index] = '';
- }
- invites.push(invite);
- }
- }
-
- this.setState({emailErrors, firstNameErrors, lastNameErrors});
-
- if (!valid || invites.length === 0) {
- return;
- }
-
- var data = {};
- data.invites = invites;
-
- this.setState({isSendingEmails: true});
-
- inviteMembers(
- data,
- () => {
- this.handleHide(false);
- this.setState({isSendingEmails: false});
- },
- (err) => {
- if (err.id === 'api.team.invite_members.already.app_error') {
- emailErrors[err.detailed_error] = err.message;
- this.setState({emailErrors});
- } else {
- this.setState({serverError: err.message});
- }
-
- this.setState({isSendingEmails: false});
- }
- );
- }
-
- handleHide(requireConfirm) {
- if (requireConfirm) {
- var notEmpty = false;
- for (var i = 0; i < this.state.inviteIds.length; i++) {
- var index = this.state.inviteIds[i];
- if (ReactDOM.findDOMNode(this.refs['email' + index]).value.trim() !== '') {
- notEmpty = true;
- break;
- }
- }
-
- if (notEmpty) {
- this.setState({
- showConfirmModal: true
- });
-
- return;
- }
- }
-
- this.clearFields();
-
- this.setState({
- show: false,
- showConfirmModal: false
- });
- }
-
- addInviteFields() {
- var count = this.state.idCount + 1;
- var inviteIds = this.state.inviteIds;
- inviteIds.push(count);
- this.setState({inviteIds, idCount: count});
- }
-
- clearFields() {
- var inviteIds = this.state.inviteIds;
-
- for (var i = 0; i < inviteIds.length; i++) {
- var index = inviteIds[i];
- ReactDOM.findDOMNode(this.refs['email' + index]).value = '';
- ReactDOM.findDOMNode(this.refs['first_name' + index]).value = '';
- ReactDOM.findDOMNode(this.refs['last_name' + index]).value = '';
- }
-
- this.setState({
- inviteIds: [0],
- idCount: 0,
- emailErrors: {},
- firstNameErrors: {},
- lastNameErrors: {}
- });
- }
-
- removeInviteFields(index) {
- var count = this.state.idCount;
- var inviteIds = this.state.inviteIds;
- var i = inviteIds.indexOf(index);
- if (i > -1) {
- inviteIds.splice(i, 1);
- }
- if (!inviteIds.length) {
- inviteIds.push(++count);
- }
- this.setState({inviteIds, idCount: count});
- }
-
- showGetTeamInviteLinkModal() {
- this.handleHide(false);
-
- GlobalActions.showGetTeamInviteLinkModal();
- }
-
- handleKeyDown(e) {
- if (e.keyCode === Constants.KeyCodes.ENTER) {
- e.preventDefault();
- this.handleSubmit();
- }
- }
-
- render() {
- var currentUser = UserStore.getCurrentUser();
- const {formatMessage} = this.props.intl;
-
- if (currentUser != null && this.state.teamType != null) {
- var inviteSections = [];
- var inviteIds = this.state.inviteIds;
- for (var i = 0; i < inviteIds.length; i++) {
- var index = inviteIds[i];
- var emailError = null;
- if (this.state.emailErrors[index]) {
- emailError = <label className='control-label'>{this.state.emailErrors[index]}</label>;
- }
- var firstNameError = null;
- if (this.state.firstNameErrors[index]) {
- firstNameError = <label className='control-label'>{this.state.firstNameErrors[index]}</label>;
- }
- var lastNameError = null;
- if (this.state.lastNameErrors[index]) {
- lastNameError = <label className='control-label'>{this.state.lastNameErrors[index]}</label>;
- }
-
- var removeButton = null;
- if (index) {
- removeButton = (
- <div>
- <button
- type='button'
- className='btn btn-link remove__member'
- onClick={this.removeInviteFields.bind(this, index)}
- >
- <span className='fa fa-trash'/>
- </button>
- </div>
- );
- }
- var emailClass = 'form-group invite';
- if (emailError) {
- emailClass += ' has-error';
- }
-
- var nameFields = null;
-
- var firstNameClass = 'form-group';
- if (firstNameError) {
- firstNameClass += ' has-error';
- }
- var lastNameClass = 'form-group';
- if (lastNameError) {
- lastNameClass += ' has-error';
- }
- nameFields = (
- <div className='row row--invite'>
- <div className='col-sm-6'>
- <div className={firstNameClass}>
- <input
- onKeyDown={this.handleKeyDown}
- type='text'
- className='form-control'
- ref={'first_name' + index}
- placeholder={formatMessage(holders.firstname)}
- maxLength='64'
- disabled={!this.state.emailEnabled || !this.state.userCreationEnabled}
- spellCheck='false'
- />
- {firstNameError}
- </div>
- </div>
- <div className='col-sm-6'>
- <div className={lastNameClass}>
- <input
- onKeyDown={this.handleKeyDown}
- type='text'
- className='form-control'
- ref={'last_name' + index}
- placeholder={formatMessage(holders.lastname)}
- maxLength='64'
- disabled={!this.state.emailEnabled || !this.state.userCreationEnabled}
- spellCheck='false'
- />
- {lastNameError}
- </div>
- </div>
- </div>
- );
-
- inviteSections[index] = (
- <div key={'key' + index}>
- {removeButton}
- <div className={emailClass}>
- <input
- onKeyUp={this.displayNameKeyUp}
- onKeyDown={this.handleKeyDown}
- type='text'
- ref={'email' + index}
- className='form-control'
- placeholder='email@domain.com'
- maxLength='64'
- disabled={!this.state.emailEnabled || !this.state.userCreationEnabled}
- spellCheck='false'
- />
- {emailError}
- </div>
- {nameFields}
- </div>
- );
- }
-
- var serverError = null;
- if (this.state.serverError) {
- serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
- }
-
- var content = null;
- var sendButton = null;
-
- var defaultChannelName = '';
- if (ChannelStore.getByName(Constants.DEFAULT_CHANNEL)) {
- defaultChannelName = ChannelStore.getByName(Constants.DEFAULT_CHANNEL).display_name;
- }
-
- if (this.state.emailEnabled && this.state.userCreationEnabled) {
- content = (
- <div>
- {serverError}
- <button
- type='button'
- className='btn btn-default'
- onClick={this.addInviteFields}
- >
- <FormattedMessage
- id='invite_member.addAnother'
- defaultMessage='Add another'
- />
- </button>
- <br/>
- <br/>
- <span>
- <FormattedHTMLMessage
- id='invite_member.autoJoin'
- defaultMessage='People invited automatically join the <strong>{channel}</strong> channel.'
- values={{
- channel: defaultChannelName
- }}
- />
- </span>
- </div>
- );
-
- var sendButtonLabel = (
- <FormattedMessage
- id='invite_member.send'
- defaultMessage='Send Invitation'
- />
- );
- if (this.state.isSendingEmails) {
- sendButtonLabel = (
- <span><i className='fa fa-spinner fa-spin'/>
- <FormattedMessage
- id='invite_member.sending'
- defaultMessage=' Sending'
- />
- </span>
- );
- } else if (this.state.inviteIds.length > 1) {
- sendButtonLabel = (
- <FormattedMessage
- id='invite_member.send2'
- defaultMessage='Send Invitations'
- />
- );
- }
-
- sendButton = (
- <button
- onClick={this.handleSubmit}
- type='button'
- className='btn btn-primary'
- disabled={this.state.isSendingEmails}
- >
- {sendButtonLabel}
- </button>
- );
- } else if (this.state.userCreationEnabled) {
- var teamInviteLink = null;
- if (currentUser && this.state.teamType === 'O') {
- var link = (
- <a
- href='#'
- onClick={this.showGetTeamInviteLinkModal}
- >
- <FormattedMessage
- id='invite_member.inviteLink'
- defaultMessage='Team Invite Link'
- />
- </a>
- );
-
- teamInviteLink = (
- <p>
- <FormattedMessage
- id='invite_member.teamInviteLink'
- defaultMessage='You can also invite people using the {link}.'
- values={{
- link
- }}
- />
- </p>
- );
- }
-
- content = (
- <div>
- <p>
- <FormattedMessage
- id='invite_member.content'
- defaultMessage='Email is currently disabled for your team, and email invitations cannot be sent. Contact your System Administrator to enable email and email invitations.'
- />
- </p>
- {teamInviteLink}
- </div>
- );
- } else {
- content = (
- <div>
- <p>
- <FormattedMessage
- id='invite_member.disabled'
- defaultMessage='User creation has been disabled for your team. Please ask your Team Administrator for details.'
- />
- </p>
- </div>
- );
- }
-
- return (
- <div>
- <Modal
- dialogClassName='modal-invite-member'
- show={this.state.show}
- onHide={this.handleHide.bind(this, true)}
- enforceFocus={!this.state.showConfirmModal}
- backdrop={this.state.isSendingEmails ? 'static' : true}
- >
- <Modal.Header closeButton={!this.state.isSendingEmails}>
- <Modal.Title>
- <FormattedMessage
- id='invite_member.newMember'
- defaultMessage='Send Email Invite'
- />
- </Modal.Title>
- </Modal.Header>
- <Modal.Body ref='modalBody'>
- <form role='form'>
- {inviteSections}
- </form>
- {content}
- </Modal.Body>
- <Modal.Footer>
- <button
- type='button'
- className='btn btn-default'
- onClick={this.handleHide.bind(this, true)}
- disabled={this.state.isSendingEmails}
- >
- <FormattedMessage
- id='invite_member.cancel'
- defaultMessage='Cancel'
- />
- </button>
- {sendButton}
- </Modal.Footer>
- </Modal>
- <ConfirmModal
- title={formatMessage(holders.modalTitle)}
- message={formatMessage(holders.modalMessage)}
- confirmButtonText={formatMessage(holders.modalButton)}
- show={this.state.showConfirmModal}
- onConfirm={this.handleHide.bind(this, false)}
- onCancel={() => this.setState({showConfirmModal: false})}
- />
- </div>
- );
- }
-
- return null;
- }
-}
-
-InviteMemberModal.propTypes = {
- intl: intlShape.isRequired
-};
-
-export default injectIntl(InviteMemberModal);
diff --git a/webapp/components/leave_team_modal.jsx b/webapp/components/leave_team_modal.jsx
deleted file mode 100644
index a38dd2da1..000000000
--- a/webapp/components/leave_team_modal.jsx
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {ActionTypes, WebrtcActionTypes} from 'utils/constants.jsx';
-import * as GlobalActions from 'actions/global_actions.jsx';
-import ModalStore from 'stores/modal_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-import WebrtcStore from 'stores/webrtc_store.jsx';
-
-import {intlShape, injectIntl, FormattedMessage} from 'react-intl';
-
-import {Modal} from 'react-bootstrap';
-
-import React from 'react';
-
-class LeaveTeamModal extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleToggle = this.handleToggle.bind(this);
- this.handleSubmit = this.handleSubmit.bind(this);
- this.handleHide = this.handleHide.bind(this);
- this.handleKeyPress = this.handleKeyPress.bind(this);
-
- this.state = {
- show: false
- };
- }
-
- componentDidMount() {
- ModalStore.addModalListener(ActionTypes.TOGGLE_LEAVE_TEAM_MODAL, this.handleToggle);
- document.addEventListener('keypress', this.handleKeyPress);
- }
-
- componentWillUnmount() {
- ModalStore.removeModalListener(ActionTypes.TOGGLE_LEAVE_TEAM_MODAL, this.handleToggle);
- document.removeEventListener('keypress', this.handleKeyPress);
- }
-
- handleKeyPress(e) {
- if (e.key === 'Enter' && this.state.show) {
- this.handleSubmit(e);
- }
- }
-
- handleToggle(value) {
- this.setState({
- show: value
- });
- }
-
- handleSubmit(e) {
- this.setState({
- show: false
- });
-
- if (WebrtcStore.isBusy()) {
- WebrtcStore.emitChanged({action: WebrtcActionTypes.IN_PROGRESS});
- e.preventDefault();
- return;
- }
-
- GlobalActions.emitLeaveTeam();
- GlobalActions.toggleSideBarRightMenuAction();
- }
-
- handleHide() {
- this.setState({
- show: false
- });
- }
-
- render() {
- var currentUser = UserStore.getCurrentUser();
-
- if (currentUser != null) {
- return (
- <Modal
- className='modal-confirm'
- show={this.state.show}
- onHide={this.handleHide}
- >
- <Modal.Header closeButton={false}>
- <Modal.Title>
- <FormattedMessage
- id='leave_team_modal.title'
- defaultMessage='Leave the team?'
- />
- </Modal.Title>
- </Modal.Header>
- <Modal.Body>
- <FormattedMessage
- id='leave_team_modal.desc'
- defaultMessage='You will be removed from all public and private channels. If the team is private you will not be able to rejoin the team. Are you sure?'
- />
- </Modal.Body>
- <Modal.Footer>
- <button
- type='button'
- className='btn btn-default'
- onClick={this.handleHide}
- >
- <FormattedMessage
- id='leave_team_modal.no'
- defaultMessage='No'
- />
- </button>
- <button
- type='button'
- className='btn btn-danger'
- onClick={this.handleSubmit}
- >
- <FormattedMessage
- id='leave_team_modal.yes'
- defaultMessage='Yes'
- />
- </button>
- </Modal.Footer>
- </Modal>
- );
- }
-
- return null;
- }
-}
-
-LeaveTeamModal.propTypes = {
- intl: intlShape.isRequired
-};
-
-export default injectIntl(LeaveTeamModal);
diff --git a/webapp/components/loading_screen.jsx b/webapp/components/loading_screen.jsx
deleted file mode 100644
index e4821c07b..000000000
--- a/webapp/components/loading_screen.jsx
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {FormattedMessage} from 'react-intl';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class LoadingScreen extends React.Component {
- constructor(props) {
- super(props);
- this.state = {};
- }
- render() {
- let message = (
- <FormattedMessage
- id='loading_screen.loading'
- defaultMessage='Loading'
- />
- );
-
- if (this.props.message) {
- message = this.props.message;
- }
-
- return (
- <div
- className='loading-screen'
- style={{position: this.props.position}}
- >
- <div className='loading__content'>
- <h3>
- {message}
- </h3>
- <div className='round round-1'/>
- <div className='round round-2'/>
- <div className='round round-3'/>
- </div>
- </div>
- );
- }
-}
-
-LoadingScreen.defaultProps = {
- position: 'relative'
-};
-LoadingScreen.propTypes = {
- position: PropTypes.oneOf(['absolute', 'fixed', 'relative', 'static', 'inherit']),
- message: PropTypes.node
-};
diff --git a/webapp/components/logged_in.jsx b/webapp/components/logged_in.jsx
deleted file mode 100644
index c81969145..000000000
--- a/webapp/components/logged_in.jsx
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import LoadingScreen from 'components/loading_screen.jsx';
-
-import UserStore from 'stores/user_store.jsx';
-
-import * as GlobalActions from 'actions/global_actions.jsx';
-import * as WebSocketActions from 'actions/websocket_actions.jsx';
-import {loadEmoji} from 'actions/emoji_actions.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-
-const BACKSPACE_CHAR = 8;
-
-import $ from 'jquery';
-import PropTypes from 'prop-types';
-import React from 'react';
-
-// import the EmojiStore so that it'll register to receive the results of the listEmojis call further down
-import 'stores/emoji_store.jsx';
-
-export default class LoggedIn extends React.Component {
- constructor(params) {
- super(params);
-
- this.onUserChanged = this.onUserChanged.bind(this);
-
- // Because current CSS requires the root tag to have specific stuff
- $('#root').attr('class', 'channel-view');
-
- // Device tracking setup
- var iOS = (/(iPad|iPhone|iPod)/g).test(navigator.userAgent);
- if (iOS) {
- $('body').addClass('ios');
- }
-
- this.state = {
- user: UserStore.getCurrentUser()
- };
-
- if (!this.state.user) {
- GlobalActions.emitUserLoggedOutEvent('/login');
- }
- }
-
- isValidState() {
- return this.state.user != null;
- }
-
- onUserChanged() {
- // Grab the current user
- const user = UserStore.getCurrentUser();
- if (!Utils.areObjectsEqual(this.state.user, user)) {
- this.setState({
- user
- });
- }
- }
-
- componentDidMount() {
- // Initialize websocket
- WebSocketActions.initialize();
-
- // Listen for user
- UserStore.addChangeListener(this.onUserChanged);
-
- // Listen for focussed tab/window state
- window.addEventListener('focus', this.onFocusListener);
- window.addEventListener('blur', this.onBlurListener);
-
- // ???
- $('body').on('mouseenter mouseleave', '.post', function mouseOver(ev) {
- if (ev.type === 'mouseenter') {
- $(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--after');
- $(this).parent('div').next('.date-separator, .new-separator').addClass('hovered--before');
- } else {
- $(this).parent('div').prev('.date-separator, .new-separator').removeClass('hovered--after');
- $(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--before');
- }
- });
-
- $('body').on('mouseenter mouseleave', '.search-item__container .post', function mouseOver(ev) {
- if (ev.type === 'mouseenter') {
- $(this).closest('.search-item__container').find('.date-separator').addClass('hovered--after');
- $(this).closest('.search-item__container').next('div').find('.date-separator').addClass('hovered--before');
- } else {
- $(this).closest('.search-item__container').find('.date-separator').removeClass('hovered--after');
- $(this).closest('.search-item__container').next('div').find('.date-separator').removeClass('hovered--before');
- }
- });
-
- $('body').on('mouseenter mouseleave', '.post.post--comment.same--root', function mouseOver(ev) {
- if (ev.type === 'mouseenter') {
- $(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--comment');
- $(this).parent('div').next('.date-separator, .new-separator').addClass('hovered--comment');
- } else {
- $(this).parent('div').prev('.date-separator, .new-separator').removeClass('hovered--comment');
- $(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--comment');
- }
- });
-
- // Prevent backspace from navigating back a page
- $(window).on('keydown.preventBackspace', (e) => {
- if (e.which === BACKSPACE_CHAR && !$(e.target).is('input, textarea')) {
- e.preventDefault();
- }
- });
-
- // Get custom emoji from the server
- if (window.mm_config.EnableCustomEmoji === 'true') {
- loadEmoji(false);
- }
- }
-
- componentWillUnmount() {
- $('#root').attr('class', '');
-
- WebSocketActions.close();
- UserStore.removeChangeListener(this.onUserChanged);
-
- $('body').off('click.userpopover');
- $('body').off('mouseenter mouseleave', '.post');
- $('body').off('mouseenter mouseleave', '.post.post--comment.same--root');
-
- $('.modal').off('show.bs.modal');
-
- $(window).off('keydown.preventBackspace');
-
- // Listen for focussed tab/window state
- window.removeEventListener('focus', this.onFocusListener);
- window.removeEventListener('blur', this.onBlurListener);
- }
-
- render() {
- if (!this.isValidState()) {
- return <LoadingScreen/>;
- }
-
- return React.cloneElement(this.props.children, {
- user: this.state.user
- });
- }
-
- onFocusListener() {
- GlobalActions.emitBrowserFocus(true);
- }
-
- onBlurListener() {
- GlobalActions.emitBrowserFocus(false);
- }
-}
-
-LoggedIn.propTypes = {
- children: PropTypes.object
-};
diff --git a/webapp/components/login/components/login_mfa.jsx b/webapp/components/login/components/login_mfa.jsx
deleted file mode 100644
index 3d8d1edc4..000000000
--- a/webapp/components/login/components/login_mfa.jsx
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import * as Utils from 'utils/utils.jsx';
-
-import {FormattedMessage} from 'react-intl';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class LoginMfa extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleSubmit = this.handleSubmit.bind(this);
-
- this.state = {
- serverError: ''
- };
- }
-
- handleSubmit(e) {
- e.preventDefault();
- const state = {};
-
- const token = this.refs.token.value.trim().replace(/\s/g, '');
- if (!token) {
- state.serverError = Utils.localizeMessage('login_mfa.tokenReq', 'Please enter an MFA token');
- this.setState(state);
- return;
- }
-
- state.serverError = '';
- this.setState(state);
-
- this.props.submit(this.props.loginId, this.props.password, token);
- }
-
- render() {
- let serverError;
- let errorClass = '';
- if (this.state.serverError) {
- serverError = <label className='control-label'>{this.state.serverError}</label>;
- errorClass = ' has-error';
- }
-
- return (
- <form onSubmit={this.handleSubmit}>
- <div className='signup__email-container'>
- <p>
- <FormattedMessage
- id='login_mfa.enterToken'
- defaultMessage="To complete the sign in process, please enter a token from your smartphone's authenticator"
- />
- </p>
- <div className={'form-group' + errorClass}>
- {serverError}
- </div>
- <div className={'form-group' + errorClass}>
- <input
- type='text'
- className='form-control'
- name='token'
- ref='token'
- placeholder={Utils.localizeMessage('login_mfa.token', 'MFA Token')}
- spellCheck='false'
- autoComplete='off'
- autoFocus={true}
- />
- </div>
- <div className='form-group'>
- <button
- type='submit'
- className='btn btn-primary'
- >
- <FormattedMessage
- id='login_mfa.submit'
- defaultMessage='Submit'
- />
- </button>
- </div>
- </div>
- </form>
- );
- }
-}
-LoginMfa.defaultProps = {
-};
-
-LoginMfa.propTypes = {
- loginId: PropTypes.string.isRequired,
- password: PropTypes.string.isRequired,
- submit: PropTypes.func.isRequired
-};
diff --git a/webapp/components/login/login_controller.jsx b/webapp/components/login/login_controller.jsx
deleted file mode 100644
index 3d0b5d24e..000000000
--- a/webapp/components/login/login_controller.jsx
+++ /dev/null
@@ -1,643 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import LoginMfa from './components/login_mfa.jsx';
-import AnnouncementBar from 'components/announcement_bar';
-import FormError from 'components/form_error.jsx';
-
-import * as GlobalActions from 'actions/global_actions.jsx';
-import {addUserToTeamFromInvite} from 'actions/team_actions.jsx';
-import {checkMfa, webLogin} from 'actions/user_actions.jsx';
-import BrowserStore from 'stores/browser_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-
-import {Client4} from 'mattermost-redux/client';
-import * as TextFormatting from 'utils/text_formatting.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-
-import {FormattedMessage} from 'react-intl';
-import {browserHistory, Link} from 'react-router/es6';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import logoImage from 'images/logo.png';
-
-export default class LoginController extends React.Component {
- static get propTypes() {
- return {
- location: PropTypes.object.isRequired,
- params: PropTypes.object.isRequired
- };
- }
-
- constructor(props) {
- super(props);
-
- this.preSubmit = this.preSubmit.bind(this);
- this.submit = this.submit.bind(this);
- this.finishSignin = this.finishSignin.bind(this);
-
- this.handleLoginIdChange = this.handleLoginIdChange.bind(this);
- this.handlePasswordChange = this.handlePasswordChange.bind(this);
-
- let loginId = '';
- if (this.props.location.query.extra === Constants.SIGNIN_VERIFIED && this.props.location.query.email) {
- loginId = this.props.location.query.email;
- }
-
- this.state = {
- ldapEnabled: global.window.mm_license.IsLicensed === 'true' && global.window.mm_config.EnableLdap === 'true',
- usernameSigninEnabled: global.window.mm_config.EnableSignInWithUsername === 'true',
- emailSigninEnabled: global.window.mm_config.EnableSignInWithEmail === 'true',
- samlEnabled: global.window.mm_license.IsLicensed === 'true' && global.window.mm_config.EnableSaml === 'true',
- loginId,
- password: '',
- showMfa: false,
- loading: false
- };
- }
-
- componentDidMount() {
- document.title = global.window.mm_config.SiteName;
- BrowserStore.removeGlobalItem('team');
- if (UserStore.getCurrentUser()) {
- GlobalActions.redirectUserToDefaultTeam();
- }
-
- if (this.props.location.query.extra === Constants.SIGNIN_VERIFIED && this.props.location.query.email) {
- this.refs.password.focus();
- }
- }
-
- preSubmit(e) {
- e.preventDefault();
-
- // password managers don't always call onInput handlers for form fields so it's possible
- // for the state to get out of sync with what the user sees in the browser
- let loginId = this.refs.loginId.value;
- if (loginId !== this.state.loginId) {
- this.setState({loginId});
- }
-
- const password = this.refs.password.value;
- if (password !== this.state.password) {
- this.setState({password});
- }
-
- // don't trim the password since we support spaces in passwords
- loginId = loginId.trim().toLowerCase();
-
- if (!loginId) {
- // it's slightly weird to be constructing the message ID, but it's a bit nicer than triply nested if statements
- let msgId = 'login.no';
- if (this.state.emailSigninEnabled) {
- msgId += 'Email';
- }
- if (this.state.usernameSigninEnabled) {
- msgId += 'Username';
- }
- if (this.state.ldapEnabled) {
- msgId += 'LdapUsername';
- }
-
- this.setState({
- serverError: (
- <FormattedMessage
- id={msgId}
- values={{
- ldapUsername: global.window.mm_config.LdapLoginFieldName || Utils.localizeMessage('login.ldapUsernameLower', 'AD/LDAP username')
- }}
- />
- )
- });
- return;
- }
-
- if (!password) {
- this.setState({
- serverError: (
- <FormattedMessage
- id='login.noPassword'
- defaultMessage='Please enter your password'
- />
- )
- });
- return;
- }
-
- checkMfa(
- loginId,
- (requiresMfa) => {
- if (requiresMfa) {
- this.setState({showMfa: true});
- } else {
- this.submit(loginId, password, '');
- }
- },
- (err) => {
- this.setState({serverError: err.message});
- }
- );
- }
-
- submit(loginId, password, token) {
- this.setState({serverError: null, loading: true});
-
- webLogin(
- loginId,
- password,
- token,
- () => {
- // check for query params brought over from signup_user_complete
- const hash = this.props.location.query.h;
- const data = this.props.location.query.d;
- const inviteId = this.props.location.query.id;
- if (inviteId || hash) {
- addUserToTeamFromInvite(
- data,
- hash,
- inviteId,
- (team) => {
- this.finishSignin(team);
- },
- () => {
- // there's not really a good way to deal with this, so just let the user log in like normal
- this.finishSignin();
- }
- );
-
- return;
- }
-
- this.finishSignin();
- },
- (err) => {
- if (err.id === 'api.user.login.not_verified.app_error') {
- browserHistory.push('/should_verify_email?&email=' + encodeURIComponent(loginId));
- } else if (err.id === 'store.sql_user.get_for_login.app_error' ||
- err.id === 'ent.ldap.do_login.user_not_registered.app_error') {
- this.setState({
- showMfa: false,
- loading: false,
- serverError: (
- <FormattedMessage
- id='login.userNotFound'
- defaultMessage="We couldn't find an account matching your login credentials."
- />
- )
- });
- } else if (err.id === 'api.user.check_user_password.invalid.app_error' || err.id === 'ent.ldap.do_login.invalid_password.app_error') {
- this.setState({
- showMfa: false,
- loading: false,
- serverError: (
- <FormattedMessage
- id='login.invalidPassword'
- defaultMessage='Your password is incorrect.'
- />
- )
- });
- } else {
- this.setState({showMfa: false, serverError: err.message, loading: false});
- }
- }
- );
- }
-
- finishSignin(team) {
- const query = this.props.location.query;
- GlobalActions.loadCurrentLocale();
- if (query.redirect_to && query.redirect_to.match(/^\/([^/]|$)/)) {
- browserHistory.push(query.redirect_to);
- } else if (team) {
- browserHistory.push(`/${team.name}`);
- } else {
- GlobalActions.redirectUserToDefaultTeam();
- }
- }
-
- handleLoginIdChange(e) {
- this.setState({
- loginId: e.target.value
- });
- }
-
- handlePasswordChange(e) {
- this.setState({
- password: e.target.value
- });
- }
-
- createCustomLogin() {
- if (global.window.mm_license.IsLicensed === 'true' &&
- global.window.mm_license.CustomBrand === 'true' &&
- global.window.mm_config.EnableCustomBrand === 'true') {
- const text = global.window.mm_config.CustomBrandText || '';
-
- return (
- <div>
- <img
- src={Client4.getBrandImageUrl(0)}
- />
- <p dangerouslySetInnerHTML={{__html: TextFormatting.formatText(text)}}/>
- </div>
- );
- }
-
- return null;
- }
-
- createLoginPlaceholder() {
- const ldapEnabled = this.state.ldapEnabled;
- const usernameSigninEnabled = this.state.usernameSigninEnabled;
- const emailSigninEnabled = this.state.emailSigninEnabled;
-
- const loginPlaceholders = [];
- if (emailSigninEnabled) {
- loginPlaceholders.push(Utils.localizeMessage('login.email', 'Email'));
- }
-
- if (usernameSigninEnabled) {
- loginPlaceholders.push(Utils.localizeMessage('login.username', 'Username'));
- }
-
- if (ldapEnabled) {
- if (global.window.mm_config.LdapLoginFieldName) {
- loginPlaceholders.push(global.window.mm_config.LdapLoginFieldName);
- } else {
- loginPlaceholders.push(Utils.localizeMessage('login.ldapUsername', 'AD/LDAP Username'));
- }
- }
-
- if (loginPlaceholders.length >= 2) {
- return loginPlaceholders.slice(0, loginPlaceholders.length - 1).join(', ') +
- Utils.localizeMessage('login.placeholderOr', ' or ') +
- loginPlaceholders[loginPlaceholders.length - 1];
- } else if (loginPlaceholders.length === 1) {
- return loginPlaceholders[0];
- }
-
- return '';
- }
-
- checkSignUpEnabled() {
- return global.window.mm_config.EnableSignUpWithEmail === 'true' ||
- global.window.mm_config.EnableSignUpWithGitLab === 'true' ||
- global.window.mm_config.EnableSignUpWithOffice365 === 'true' ||
- global.window.mm_config.EnableSignUpWithGoogle === 'true' ||
- global.window.mm_config.EnableLdap === 'true' ||
- global.window.mm_config.EnableSaml === 'true';
- }
-
- createLoginOptions() {
- const extraParam = this.props.location.query.extra;
- let extraBox = '';
- if (extraParam) {
- if (extraParam === Constants.SIGNIN_CHANGE) {
- extraBox = (
- <div className='alert alert-success'>
- <i className='fa fa-check'/>
- <FormattedMessage
- id='login.changed'
- defaultMessage=' Sign-in method changed successfully'
- />
- </div>
- );
- } else if (extraParam === Constants.SIGNIN_VERIFIED) {
- extraBox = (
- <div className='alert alert-success'>
- <i className='fa fa-check'/>
- <FormattedMessage
- id='login.verified'
- defaultMessage=' Email Verified'
- />
- </div>
- );
- } else if (extraParam === Constants.SESSION_EXPIRED) {
- extraBox = (
- <div className='alert alert-warning'>
- <i className='fa fa-exclamation-triangle'/>
- <FormattedMessage
- id='login.session_expired'
- defaultMessage=' Your session has expired. Please login again.'
- />
- </div>
- );
- } else if (extraParam === Constants.PASSWORD_CHANGE) {
- extraBox = (
- <div className='alert alert-success'>
- <i className='fa fa-check'/>
- <FormattedMessage
- id='login.passwordChanged'
- defaultMessage=' Password updated successfully'
- />
- </div>
- );
- }
- }
-
- const loginControls = [];
-
- const ldapEnabled = this.state.ldapEnabled;
- const gitlabSigninEnabled = global.window.mm_config.EnableSignUpWithGitLab === 'true';
- const googleSigninEnabled = global.window.mm_config.EnableSignUpWithGoogle === 'true';
- const office365SigninEnabled = global.window.mm_config.EnableSignUpWithOffice365 === 'true';
- const samlSigninEnabled = this.state.samlEnabled;
- const usernameSigninEnabled = this.state.usernameSigninEnabled;
- const emailSigninEnabled = this.state.emailSigninEnabled;
-
- if (emailSigninEnabled || usernameSigninEnabled || ldapEnabled) {
- let errorClass = '';
- if (this.state.serverError) {
- errorClass = ' has-error';
- }
-
- let loginButton =
- (<FormattedMessage
- id='login.signIn'
- defaultMessage='Sign in'
- />);
-
- if (this.state.loading) {
- loginButton =
- (<span>
- <span className='fa fa-refresh icon--rotate'/>
- <FormattedMessage
- id='login.signInLoading'
- defaultMessage='Signing in...'
- />
- </span>);
- }
-
- loginControls.push(
- <form
- key='loginBoxes'
- onSubmit={this.preSubmit}
- >
- <div className='signup__email-container'>
- <FormError
- error={this.state.serverError}
- margin={true}
- />
- <div className={'form-group' + errorClass}>
- <input
- className='form-control'
- ref='loginId'
- name='loginId'
- value={this.state.loginId}
- onChange={this.handleLoginIdChange}
- placeholder={this.createLoginPlaceholder()}
- spellCheck='false'
- autoCapitalize='off'
- />
- </div>
- <div className={'form-group' + errorClass}>
- <input
- type='password'
- className='form-control'
- ref='password'
- name='password'
- value={this.state.password}
- onChange={this.handlePasswordChange}
- placeholder={Utils.localizeMessage('login.password', 'Password')}
- spellCheck='false'
- />
- </div>
- <div className='form-group'>
- <button
- id='loginButton'
- type='submit'
- className='btn btn-primary'
- >
- { loginButton }
- </button>
- </div>
- </div>
- </form>
- );
- }
-
- if (global.window.mm_config.EnableOpenServer === 'true' && this.checkSignUpEnabled()) {
- loginControls.push(
- <div
- className='form-group'
- key='signup'
- >
- <span>
- <FormattedMessage
- id='login.noAccount'
- defaultMessage="Don't have an account? "
- />
- <Link
- id='signup'
- to={'/signup_user_complete' + this.props.location.search}
- className='signup-team-login'
- >
- <FormattedMessage
- id='login.create'
- defaultMessage='Create one now'
- />
- </Link>
- </span>
- </div>
- );
- }
-
- if (usernameSigninEnabled || emailSigninEnabled) {
- loginControls.push(
- <div
- key='forgotPassword'
- className='form-group'
- >
- <Link to={'/reset_password'}>
- <FormattedMessage
- id='login.forgot'
- defaultMessage='I forgot my password'
- />
- </Link>
- </div>
- );
- }
-
- if ((emailSigninEnabled || usernameSigninEnabled || ldapEnabled) && (gitlabSigninEnabled || googleSigninEnabled || samlSigninEnabled || office365SigninEnabled)) {
- loginControls.push(
- <div
- key='divider'
- className='or__container'
- >
- <FormattedMessage
- id='login.or'
- defaultMessage='or'
- />
- </div>
- );
-
- loginControls.push(
- <h5 key='oauthHeader'>
- <FormattedMessage
- id='login.signInWith'
- defaultMessage='Sign in with:'
- />
- </h5>
- );
- }
-
- if (gitlabSigninEnabled) {
- loginControls.push(
- <a
- className='btn btn-custom-login gitlab'
- key='gitlab'
- href={Client4.getOAuthRoute() + '/gitlab/login' + this.props.location.search}
- >
- <span>
- <span className='icon'/>
- <span>
- <FormattedMessage
- id='login.gitlab'
- defaultMessage='GitLab'
- />
- </span>
- </span>
- </a>
- );
- }
-
- if (googleSigninEnabled) {
- loginControls.push(
- <a
- className='btn btn-custom-login google'
- key='google'
- href={Client4.getOAuthRoute() + '/google/login' + this.props.location.search}
- >
- <span>
- <span className='icon'/>
- <span>
- <FormattedMessage
- id='login.google'
- defaultMessage='Google Apps'
- />
- </span>
- </span>
- </a>
- );
- }
-
- if (office365SigninEnabled) {
- loginControls.push(
- <a
- className='btn btn-custom-login office365'
- key='office365'
- href={Client4.getOAuthRoute() + '/office365/login' + this.props.location.search}
- >
- <span>
- <span className='icon'/>
- <span>
- <FormattedMessage
- id='login.office365'
- defaultMessage='Office 365'
- />
- </span>
- </span>
- </a>
- );
- }
-
- if (samlSigninEnabled) {
- loginControls.push(
- <a
- className='btn btn-custom-login saml'
- key='saml'
- href={'/login/sso/saml' + this.props.location.search}
- >
- <span>
- <span className='icon fa fa-lock fa--margin-top'/>
- <span>
- {global.window.mm_config.SamlLoginButtonText}
- </span>
- </span>
- </a>
- );
- }
-
- if (loginControls.length === 0) {
- loginControls.push(
- <FormError
- error={
- <FormattedMessage
- id='login.noMethods'
- defaultMessage='No sign-in methods are enabled. Please contact your System Administrator.'
- />
- }
- margin={true}
- />
- );
- }
-
- return (
- <div>
- {extraBox}
- {loginControls}
- </div>
- );
- }
-
- render() {
- let content;
- let customContent;
- let customClass;
- if (this.state.showMfa) {
- content = (
- <LoginMfa
- loginId={this.state.loginId}
- password={this.state.password}
- submit={this.submit}
- />
- );
- } else {
- content = this.createLoginOptions();
- customContent = this.createCustomLogin();
- if (customContent) {
- customClass = 'branded';
- }
- }
-
- let description = null;
- if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.CustomBrand === 'true' && global.window.mm_config.EnableCustomBrand === 'true') {
- description = global.window.mm_config.CustomDescriptionText;
- } else {
- description = (
- <FormattedMessage
- id='web.root.signup_info'
- defaultMessage='All team communication in one place, searchable and accessible anywhere'
- />
- );
- }
-
- return (
- <div>
- <AnnouncementBar/>
- <div className='col-sm-12'>
- <div className={'signup-team__container ' + customClass}>
- <div className='signup__markdown'>
- {customContent}
- </div>
- <img
- className='signup-team-logo'
- src={logoImage}
- />
- <div className='signup__content'>
- <h1>{global.window.mm_config.SiteName}</h1>
- <h4 className='color--light'>
- {description}
- </h4>
- {content}
- </div>
- </div>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/markdown_image.jsx b/webapp/components/markdown_image.jsx
deleted file mode 100644
index 2634ef3f6..000000000
--- a/webapp/components/markdown_image.jsx
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import {postListScrollChange} from 'actions/global_actions.jsx';
-
-const WAIT_FOR_HEIGHT_TIMEOUT = 100;
-
-export default class MarkdownImage extends React.PureComponent {
- static propTypes = {
-
- /*
- * The href of the image to be loaded
- */
- href: PropTypes.string
- }
-
- constructor(props) {
- super(props);
-
- this.heightTimeout = 0;
- }
-
- componentDidMount() {
- this.waitForHeight();
- }
-
- componentDidUpdate(prevProps) {
- if (this.props.href !== prevProps.href) {
- this.waitForHeight();
- }
- }
-
- componentWillUnmount() {
- this.stopWaitingForHeight();
- }
-
- waitForHeight = () => {
- if (this.refs.image.height) {
- setTimeout(postListScrollChange, 0);
-
- this.heightTimeout = 0;
- } else {
- this.heightTimeout = setTimeout(this.waitForHeight, WAIT_FOR_HEIGHT_TIMEOUT);
- }
- }
-
- stopWaitingForHeight = () => {
- if (this.heightTimeout !== 0) {
- clearTimeout(this.heightTimeout);
- this.heightTimeout = 0;
- }
- }
-
- render() {
- return (
- <img
- {...this.props}
- ref='image'
- onLoad={this.stopWaitingForHeight}
- onError={this.stopWaitingForHeight}
- />
- );
- }
-}
diff --git a/webapp/components/member_list_channel/index.js b/webapp/components/member_list_channel/index.js
deleted file mode 100644
index c0f70709e..000000000
--- a/webapp/components/member_list_channel/index.js
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getChannelStats} from 'mattermost-redux/actions/channels';
-
-import MemberListChannel from './member_list_channel.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getChannelStats
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(MemberListChannel);
diff --git a/webapp/components/member_list_channel/member_list_channel.jsx b/webapp/components/member_list_channel/member_list_channel.jsx
deleted file mode 100644
index 272e210ce..000000000
--- a/webapp/components/member_list_channel/member_list_channel.jsx
+++ /dev/null
@@ -1,173 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ChannelMembersDropdown from 'components/channel_members_dropdown';
-import SearchableUserList from 'components/searchable_user_list/searchable_user_list_container.jsx';
-
-import ChannelStore from 'stores/channel_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-
-import {searchUsers, loadProfilesAndTeamMembersAndChannelMembers, loadTeamMembersAndChannelMembersForProfilesList} from 'actions/user_actions.jsx';
-
-import Constants from 'utils/constants.jsx';
-
-import * as UserAgent from 'utils/user_agent.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-import store from 'stores/redux_store.jsx';
-import {searchProfilesInCurrentChannel} from 'mattermost-redux/selectors/entities/users';
-
-const USERS_PER_PAGE = 50;
-
-export default class MemberListChannel extends React.Component {
- static propTypes = {
- channel: PropTypes.object.isRequired,
- actions: PropTypes.shape({
- getChannelStats: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.onChange = this.onChange.bind(this);
- this.onStatsChange = this.onStatsChange.bind(this);
- this.search = this.search.bind(this);
- this.loadComplete = this.loadComplete.bind(this);
-
- this.searchTimeoutId = 0;
- this.term = '';
-
- const stats = ChannelStore.getCurrentStats();
-
- this.state = {
- users: UserStore.getProfileListInChannel(ChannelStore.getCurrentId(), false, true),
- teamMembers: Object.assign({}, TeamStore.getMembersInTeam()),
- channelMembers: Object.assign({}, ChannelStore.getMembersInChannel()),
- total: stats.member_count,
- loading: true
- };
- }
-
- componentDidMount() {
- UserStore.addInTeamChangeListener(this.onChange);
- UserStore.addStatusesChangeListener(this.onChange);
- TeamStore.addChangeListener(this.onChange);
- ChannelStore.addChangeListener(this.onChange);
- ChannelStore.addStatsChangeListener(this.onStatsChange);
-
- loadProfilesAndTeamMembersAndChannelMembers(0, Constants.PROFILE_CHUNK_SIZE, TeamStore.getCurrentId(), ChannelStore.getCurrentId(), this.loadComplete);
- this.props.actions.getChannelStats(ChannelStore.getCurrentId());
- }
-
- componentWillUnmount() {
- UserStore.removeInTeamChangeListener(this.onChange);
- UserStore.removeStatusesChangeListener(this.onChange);
- TeamStore.removeChangeListener(this.onChange);
- ChannelStore.removeChangeListener(this.onChange);
- ChannelStore.removeStatsChangeListener(this.onStatsChange);
- }
-
- loadComplete() {
- this.setState({loading: false});
- }
-
- onChange() {
- let users;
- if (this.term) {
- users = searchProfilesInCurrentChannel(store.getState(), this.term);
- } else {
- users = UserStore.getProfileListInChannel(ChannelStore.getCurrentId(), false, true);
- }
-
- this.setState({
- users,
- teamMembers: Object.assign({}, TeamStore.getMembersInTeam()),
- channelMembers: Object.assign({}, ChannelStore.getMembersInChannel())
- });
- }
-
- onStatsChange() {
- const stats = ChannelStore.getCurrentStats();
- this.setState({total: stats.member_count});
- }
-
- nextPage(page) {
- loadProfilesAndTeamMembersAndChannelMembers(page + 1, USERS_PER_PAGE);
- }
-
- search(term) {
- clearTimeout(this.searchTimeoutId);
- this.term = term;
-
- if (term === '') {
- this.setState({loading: false});
- this.searchTimeoutId = '';
- this.onChange();
- return;
- }
-
- const searchTimeoutId = setTimeout(
- () => {
- searchUsers(term, '', {in_channel_id: ChannelStore.getCurrentId()},
- (users) => {
- if (searchTimeoutId !== this.searchTimeoutId) {
- return;
- }
-
- this.setState({loading: true});
-
- loadTeamMembersAndChannelMembersForProfilesList(users, TeamStore.getCurrentId(), ChannelStore.getCurrentId(), this.loadComplete);
- }
- );
- },
- Constants.SEARCH_TIMEOUT_MILLISECONDS
- );
-
- this.searchTimeoutId = searchTimeoutId;
- }
-
- render() {
- const teamMembers = this.state.teamMembers;
- const channelMembers = this.state.channelMembers;
- const users = this.state.users;
- const actionUserProps = {};
-
- let usersToDisplay;
- if (this.state.loading) {
- usersToDisplay = null;
- } else {
- usersToDisplay = [];
-
- for (let i = 0; i < users.length; i++) {
- const user = users[i];
-
- if (teamMembers[user.id] && channelMembers[user.id] && user.delete_at === 0) {
- usersToDisplay.push(user);
- actionUserProps[user.id] = {
- channel: this.props.channel,
- teamMember: teamMembers[user.id],
- channelMember: channelMembers[user.id]
- };
- }
- }
- }
-
- return (
- <SearchableUserList
- users={usersToDisplay}
- usersPerPage={USERS_PER_PAGE}
- total={this.state.total}
- nextPage={this.nextPage}
- search={this.search}
- actions={[ChannelMembersDropdown]}
- actionUserProps={actionUserProps}
- focusOnMount={!UserAgent.isMobile()}
- />
- );
- }
-}
diff --git a/webapp/components/member_list_team/index.js b/webapp/components/member_list_team/index.js
deleted file mode 100644
index dc5b0b9f2..000000000
--- a/webapp/components/member_list_team/index.js
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getTeamStats} from 'mattermost-redux/actions/teams';
-
-import MemberListTeam from './member_list_team.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getTeamStats
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(MemberListTeam);
diff --git a/webapp/components/member_list_team/member_list_team.jsx b/webapp/components/member_list_team/member_list_team.jsx
deleted file mode 100644
index 60342ab83..000000000
--- a/webapp/components/member_list_team/member_list_team.jsx
+++ /dev/null
@@ -1,168 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import SearchableUserList from 'components/searchable_user_list/searchable_user_list_container.jsx';
-import TeamMembersDropdown from 'components/team_members_dropdown';
-
-import UserStore from 'stores/user_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-
-import {searchUsers, loadProfilesAndTeamMembers, loadTeamMembersForProfilesList} from 'actions/user_actions.jsx';
-
-import Constants from 'utils/constants.jsx';
-
-import * as UserAgent from 'utils/user_agent.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-import store from 'stores/redux_store.jsx';
-import {searchProfilesInCurrentTeam} from 'mattermost-redux/selectors/entities/users';
-
-const USERS_PER_PAGE = 50;
-
-export default class MemberListTeam extends React.Component {
- static propTypes = {
- isAdmin: PropTypes.bool,
- actions: PropTypes.shape({
- getTeamStats: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.onChange = this.onChange.bind(this);
- this.onStatsChange = this.onStatsChange.bind(this);
- this.search = this.search.bind(this);
- this.loadComplete = this.loadComplete.bind(this);
-
- this.searchTimeoutId = 0;
- this.term = '';
-
- const stats = TeamStore.getCurrentStats();
-
- this.state = {
- users: UserStore.getProfileListInTeam(),
- teamMembers: Object.assign([], TeamStore.getMembersInTeam()),
- total: stats.active_member_count,
- loading: true
- };
- }
-
- componentDidMount() {
- UserStore.addInTeamChangeListener(this.onChange);
- UserStore.addStatusesChangeListener(this.onChange);
- TeamStore.addChangeListener(this.onChange);
- TeamStore.addStatsChangeListener(this.onStatsChange);
-
- loadProfilesAndTeamMembers(0, Constants.PROFILE_CHUNK_SIZE, TeamStore.getCurrentId(), this.loadComplete);
- this.props.actions.getTeamStats(TeamStore.getCurrentId());
- }
-
- componentWillUnmount() {
- UserStore.removeInTeamChangeListener(this.onChange);
- UserStore.removeStatusesChangeListener(this.onChange);
- TeamStore.removeChangeListener(this.onChange);
- TeamStore.removeStatsChangeListener(this.onStatsChange);
- }
-
- loadComplete() {
- this.setState({loading: false});
- }
-
- onChange() {
- let users;
- if (this.term) {
- users = searchProfilesInCurrentTeam(store.getState(), this.term);
- } else {
- users = UserStore.getProfileListInTeam();
- }
-
- this.setState({users, teamMembers: Object.assign([], TeamStore.getMembersInTeam())});
- }
-
- onStatsChange() {
- const stats = TeamStore.getCurrentStats();
- this.setState({total: stats.active_member_count});
- }
-
- nextPage(page) {
- loadProfilesAndTeamMembers(page, USERS_PER_PAGE);
- }
-
- search(term) {
- clearTimeout(this.searchTimeoutId);
- this.term = term;
-
- if (term === '') {
- this.setState({loading: false});
- this.searchTimeoutId = '';
- this.onChange();
- return;
- }
-
- const searchTimeoutId = setTimeout(
- () => {
- searchUsers(
- term,
- TeamStore.getCurrentId(),
- {},
- (users) => {
- if (searchTimeoutId !== this.searchTimeoutId) {
- return;
- }
- this.setState({loading: true});
- loadTeamMembersForProfilesList(users, TeamStore.getCurrentId(), this.loadComplete);
- }
- );
- },
- Constants.SEARCH_TIMEOUT_MILLISECONDS
- );
-
- this.searchTimeoutId = searchTimeoutId;
- }
-
- render() {
- let teamMembersDropdown = null;
- if (this.props.isAdmin) {
- teamMembersDropdown = [TeamMembersDropdown];
- }
-
- const teamMembers = this.state.teamMembers;
- const users = this.state.users;
- const actionUserProps = {};
-
- let usersToDisplay;
- if (this.state.loading) {
- usersToDisplay = null;
- } else {
- usersToDisplay = [];
-
- for (let i = 0; i < users.length; i++) {
- const user = users[i];
-
- if (teamMembers[user.id] && user.delete_at === 0) {
- usersToDisplay.push(user);
- actionUserProps[user.id] = {
- teamMember: teamMembers[user.id]
- };
- }
- }
- }
-
- return (
- <SearchableUserList
- users={usersToDisplay}
- usersPerPage={USERS_PER_PAGE}
- total={this.state.total}
- nextPage={this.nextPage}
- search={this.search}
- actions={teamMembersDropdown}
- actionUserProps={actionUserProps}
- focusOnMount={!UserAgent.isMobile()}
- />
- );
- }
-}
diff --git a/webapp/components/message_wrapper.jsx b/webapp/components/message_wrapper.jsx
deleted file mode 100644
index 22f409783..000000000
--- a/webapp/components/message_wrapper.jsx
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import * as TextFormatting from 'utils/text_formatting.jsx';
-import * as Utils from 'utils/utils.jsx';
-import {getSiteURL} from 'utils/url.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class MessageWrapper extends React.Component {
- constructor(props) {
- super(props);
- this.state = {};
- }
-
- render() {
- if (this.props.message) {
- const options = Object.assign({}, this.props.options, {
- siteURL: getSiteURL()
- });
-
- return (
- <div
- onClick={Utils.handleFormattedTextClick}
- dangerouslySetInnerHTML={{__html: TextFormatting.formatText(this.props.message, options)}}
- />
- );
- }
-
- return <div/>;
- }
-}
-
-MessageWrapper.defaultProps = {
- message: ''
-};
-MessageWrapper.propTypes = {
- message: PropTypes.string,
- options: PropTypes.object
-};
diff --git a/webapp/components/mfa/components/confirm.jsx b/webapp/components/mfa/components/confirm.jsx
deleted file mode 100644
index 718632382..000000000
--- a/webapp/components/mfa/components/confirm.jsx
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import Constants from 'utils/constants.jsx';
-const KeyCodes = Constants.KeyCodes;
-
-import React from 'react';
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-import {browserHistory} from 'react-router/es6';
-
-import {loadMe} from 'actions/user_actions.jsx';
-
-export default class Confirm extends React.Component {
- constructor(props) {
- super(props);
-
- this.onKeyPress = this.onKeyPress.bind(this);
- }
-
- componentDidMount() {
- document.body.addEventListener('keydown', this.onKeyPress);
- }
-
- componentWillUnmount() {
- document.body.removeEventListener('keydown', this.onKeyPress);
- }
-
- submit(e) {
- e.preventDefault();
- loadMe().then(() => {
- browserHistory.push('/');
- });
- }
-
- onKeyPress(e) {
- if (e.which === KeyCodes.ENTER) {
- this.submit(e);
- }
- }
-
- render() {
- return (
- <div>
- <form
- onSubmit={this.submit}
- onKeyPress={this.onKeyPress}
- className='form-group'
- >
- <p>
- <FormattedHTMLMessage
- id='mfa.confirm.complete'
- defaultMessage='<strong>Set up complete!</strong>'
- />
- </p>
- <p>
- <FormattedMessage
- id='mfa.confirm.secure'
- defaultMessage='Your account is now secure. Next time you sign in, you will be asked to enter a code from the Google Authenticator app on your phone.'
- />
- </p>
- <button
- type='submit'
- className='btn btn-primary'
- >
- <FormattedMessage
- id='mfa.confirm.okay'
- defaultMessage='Okay'
- />
- </button>
- </form>
- </div>
- );
- }
-}
-
-Confirm.defaultProps = {
-};
-Confirm.propTypes = {
-};
diff --git a/webapp/components/mfa/components/setup.jsx b/webapp/components/mfa/components/setup.jsx
deleted file mode 100644
index caec1571a..000000000
--- a/webapp/components/mfa/components/setup.jsx
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {generateMfaSecret, activateMfa} from 'actions/user_actions.jsx';
-
-import UserStore from 'stores/user_store.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-
-import React from 'react';
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-import {browserHistory} from 'react-router/es6';
-
-export default class Setup extends React.Component {
- constructor(props) {
- super(props);
-
- this.submit = this.submit.bind(this);
-
- this.state = {secret: '', qrCode: ''};
- }
-
- componentDidMount() {
- const user = UserStore.getCurrentUser();
- if (!user || user.mfa_active) {
- browserHistory.push('/');
- return;
- }
-
- generateMfaSecret(
- (data) => this.setState({secret: data.secret, qrCode: data.qr_code}),
- (err) => this.setState({serverError: err.message})
- );
- }
-
- submit(e) {
- e.preventDefault();
- const code = this.refs.code.value.replace(/\s/g, '');
- if (!code || code.length === 0) {
- this.setState({error: Utils.localizeMessage('mfa.setup.codeError', 'Please enter the code from Google Authenticator.')});
- return;
- }
-
- this.setState({error: null});
-
- activateMfa(
- code,
- () => {
- browserHistory.push('/mfa/confirm');
- },
- (err) => {
- if (err.id === 'ent.mfa.activate.authenticate.app_error') {
- this.setState({error: Utils.localizeMessage('mfa.setup.badCode', 'Invalid code. If this issue persists, contact your System Administrator.')});
- return;
- }
- this.setState({error: err.message});
- }
- );
- }
-
- render() {
- let formClass = 'form-group';
- let errorContent;
- if (this.state.error) {
- errorContent = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>;
- formClass += ' has-error';
- }
-
- let mfaRequired;
- if (global.window.mm_config.EnforceMultifactorAuthentication === 'true') {
- mfaRequired = (
- <p>
- <FormattedHTMLMessage
- id='mfa.setup.required'
- defaultMessage='<strong>Multi-factor authentication is required on {siteName}.</strong>'
- values={{
- siteName: global.window.mm_config.SiteName
- }}
- />
- </p>
- );
- }
-
- return (
- <div>
- <form
- onSubmit={this.submit}
- className={formClass}
- >
- {mfaRequired}
- <p>
- <FormattedHTMLMessage
- id='mfa.setup.step1'
- defaultMessage="<strong>Step 1: </strong>On your phone, download Google Authenticator from <a target='_blank' href='https://itunes.apple.com/us/app/google-authenticator/id388497605?mt=8'>iTunes</a> or <a target='_blank' href='https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en'>Google Play</a>"
- />
- </p>
- <p>
- <FormattedHTMLMessage
- id='mfa.setup.step2'
- defaultMessage='<strong>Step 2: </strong>Use Google Authenticator to scan this QR code, or manually type in the secret key'
- />
- </p>
- <div className='form-group'>
- <div className='col-sm-12'>
- <img
- style={{maxHeight: 170}}
- src={'data:image/png;base64,' + this.state.qrCode}
- />
- </div>
- </div>
- <br/>
- <div className='form-group'>
- <p className='col-sm-12'>
- <FormattedMessage
- id='mfa.setup.secret'
- defaultMessage='Secret: {secret}'
- values={{
- secret: this.state.secret
- }}
- />
- </p>
- </div>
- <p>
- <FormattedHTMLMessage
- id='mfa.setup.step3'
- defaultMessage='<strong>Step 3: </strong>Enter the code generated by Google Authenticator'
- />
- </p>
- <p>
- <input
- ref='code'
- className='form-control'
- placeholder={Utils.localizeMessage('mfa.setup.code', 'MFA Code')}
- autoFocus={true}
- />
- </p>
- {errorContent}
- <button
- type='submit'
- className='btn btn-primary'
- >
- <FormattedMessage
- id='mfa.setup.save'
- defaultMessage='Save'
- />
- </button>
- </form>
- </div>
- );
- }
-}
-
-Setup.defaultProps = {
-};
-Setup.propTypes = {
-};
diff --git a/webapp/components/mfa/mfa_controller.jsx b/webapp/components/mfa/mfa_controller.jsx
deleted file mode 100644
index a3d098abd..000000000
--- a/webapp/components/mfa/mfa_controller.jsx
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {emitUserLoggedOutEvent} from 'actions/global_actions.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-import {browserHistory, Link} from 'react-router/es6';
-
-import logoImage from 'images/logo.png';
-
-export default class MFAController extends React.Component {
- componentDidMount() {
- if (window.mm_license.MFA !== 'true' || window.mm_config.EnableMultifactorAuthentication !== 'true') {
- browserHistory.push('/');
- }
- }
-
- render() {
- let backButton;
- if (window.mm_config.EnforceMultifactorAuthentication === 'true') {
- backButton = (
- <div className='signup-header'>
- <a
- href='#'
- onClick={(e) => {
- e.preventDefault();
- emitUserLoggedOutEvent('/login');
- }}
- >
- <span className='fa fa-chevron-left'/>
- <FormattedMessage
- id='web.header.logout'
- defaultMessage='Logout'
- />
- </a>
- </div>
- );
- } else {
- backButton = (
- <div className='signup-header'>
- <Link to='/'>
- <span className='fa fa-chevron-left'/>
- <FormattedMessage
- id='web.header.back'
- defaultMessage='Back'
- />
- </Link>
- </div>
- );
- }
-
- return (
- <div className='inner-wrap sticky'>
- <div className='content'>
- <div>
- {backButton}
- <div className='col-sm-12'>
- <div className='signup-team__container'>
- <h3>
- <FormattedMessage
- id='mfa.setupTitle'
- defaultMessage='Multi-factor Authentication Setup'
- />
- </h3>
- <img
- className='signup-team-logo'
- src={logoImage}
- />
- <div id='mfa'>
- {React.cloneElement(this.props.children, {})}
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- );
- }
-}
-
-MFAController.defaultProps = {
-};
-MFAController.propTypes = {
- location: PropTypes.object.isRequired,
- children: PropTypes.node
-};
diff --git a/webapp/components/modals/leave_private_channel_modal.jsx b/webapp/components/modals/leave_private_channel_modal.jsx
deleted file mode 100644
index 9b8e6df83..000000000
--- a/webapp/components/modals/leave_private_channel_modal.jsx
+++ /dev/null
@@ -1,120 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ConfirmModal from 'components/confirm_modal.jsx';
-
-import * as ChannelActions from 'actions/channel_actions.jsx';
-
-import ModalStore from 'stores/modal_store.jsx';
-import Constants from 'utils/constants.jsx';
-
-import {intlShape, injectIntl, FormattedMessage} from 'react-intl';
-
-import React from 'react';
-
-class LeavePrivateChannelModal extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleToggle = this.handleToggle.bind(this);
- this.handleSubmit = this.handleSubmit.bind(this);
- this.handleHide = this.handleHide.bind(this);
- this.handleKeyPress = this.handleKeyPress.bind(this);
-
- this.state = {
- show: false,
- channel: null
- };
- this.mounted = false;
- }
-
- componentDidMount() {
- this.mounted = true;
- ModalStore.addModalListener(Constants.ActionTypes.TOGGLE_LEAVE_PRIVATE_CHANNEL_MODAL, this.handleToggle);
- }
-
- componentWillUnmount() {
- this.mounted = false;
- ModalStore.removeModalListener(Constants.ActionTypes.TOGGLE_LEAVE_PRIVATE_CHANNEL_MODAL, this.handleToggle);
- }
-
- handleKeyPress(e) {
- if (e.key === 'Enter' && this.state.show) {
- this.handleSubmit();
- }
- }
-
- handleSubmit() {
- const channelId = this.state.channel.id;
- this.setState({
- show: false,
- channel: null
- });
- ChannelActions.leaveChannel(channelId);
- }
-
- handleToggle(value) {
- this.setState({
- channel: value,
- show: value !== null
- });
- }
-
- handleHide() {
- this.setState({
- show: false
- });
- }
-
- render() {
- let title = '';
- let message = '';
- if (this.state.channel) {
- title = (
- <FormattedMessage
- id='leave_private_channel_modal.title'
- defaultMessage='Leave Private Channel {channel}'
- values={{
- channel: <b>{this.state.channel.display_name}</b>
- }}
- />
- );
-
- message = (
- <FormattedMessage
- id='leave_private_channel_modal.message'
- defaultMessage='Are you sure you wish to leave the private channel {channel}? You must be re-invited in order to re-join this channel in the future.'
- values={{
- channel: <b>{this.state.channel.display_name}</b>
- }}
- />
- );
- }
-
- const buttonClass = 'btn btn-danger';
- const button = (
- <FormattedMessage
- id='leave_private_channel_modal.leave'
- defaultMessage='Yes, leave channel'
- />
- );
-
- return (
- <ConfirmModal
- show={this.state.show}
- title={title}
- message={message}
- confirmButtonClass={buttonClass}
- confirmButtonText={button}
- onConfirm={this.handleSubmit}
- onCancel={this.handleHide}
- />
- );
- }
-}
-
-LeavePrivateChannelModal.propTypes = {
- intl: intlShape.isRequired
-};
-
-export default injectIntl(LeavePrivateChannelModal);
diff --git a/webapp/components/more_channels/index.js b/webapp/components/more_channels/index.js
deleted file mode 100644
index b3ce839ef..000000000
--- a/webapp/components/more_channels/index.js
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getChannels} from 'mattermost-redux/actions/channels';
-
-import MoreChannels from './more_channels.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getChannels
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(MoreChannels);
diff --git a/webapp/components/more_channels/more_channels.jsx b/webapp/components/more_channels/more_channels.jsx
deleted file mode 100644
index ecc7aecf8..000000000
--- a/webapp/components/more_channels/more_channels.jsx
+++ /dev/null
@@ -1,206 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import SearchableChannelList from 'components/searchable_channel_list.jsx';
-
-import ChannelStore from 'stores/channel_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-
-import Constants from 'utils/constants.jsx';
-import {joinChannel, searchMoreChannels} from 'actions/channel_actions.jsx';
-import {showCreateOption} from 'utils/channel_utils.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import PureRenderMixin from 'react-addons-pure-render-mixin';
-import {Modal} from 'react-bootstrap';
-import {FormattedMessage} from 'react-intl';
-import {browserHistory} from 'react-router/es6';
-
-const CHANNELS_CHUNK_SIZE = 50;
-const CHANNELS_PER_PAGE = 50;
-const SEARCH_TIMEOUT_MILLISECONDS = 100;
-
-export default class MoreChannels extends React.Component {
- static propTypes = {
- onModalDismissed: PropTypes.func,
- handleNewChannel: PropTypes.func,
- actions: PropTypes.shape({
- getChannels: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.onChange = this.onChange.bind(this);
- this.handleJoin = this.handleJoin.bind(this);
- this.handleHide = this.handleHide.bind(this);
- this.handleExit = this.handleExit.bind(this);
- this.nextPage = this.nextPage.bind(this);
- this.search = this.search.bind(this);
-
- this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
-
- this.searchTimeoutId = 0;
-
- this.state = {
- show: true,
- search: false,
- channels: null,
- serverError: null
- };
- }
-
- componentDidMount() {
- ChannelStore.addChangeListener(this.onChange);
- this.props.actions.getChannels(TeamStore.getCurrentId(), 0, CHANNELS_CHUNK_SIZE * 2);
- }
-
- componentWillUnmount() {
- ChannelStore.removeChangeListener(this.onChange);
- }
-
- handleHide() {
- this.setState({show: false});
- }
-
- handleExit() {
- if (this.props.onModalDismissed) {
- this.props.onModalDismissed();
- }
- }
-
- onChange(force) {
- if (this.state.search && !force) {
- return;
- }
-
- this.setState({
- channels: ChannelStore.getMoreChannelsList(),
- serverError: null
- });
- }
-
- nextPage(page) {
- this.props.actions.getChannels(TeamStore.getCurrentId(), page + 1, CHANNELS_PER_PAGE);
- }
-
- handleJoin(channel, done) {
- joinChannel(
- channel,
- () => {
- browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + channel.name);
- if (done) {
- done();
- }
-
- this.handleHide();
- },
- (err) => {
- this.setState({serverError: err.message});
- if (done) {
- done();
- }
- }
- );
- }
-
- search(term) {
- clearTimeout(this.searchTimeoutId);
-
- if (term === '') {
- this.onChange(true);
- this.setState({search: false});
- this.searchTimeoutId = '';
- return;
- }
-
- const searchTimeoutId = setTimeout(
- () => {
- searchMoreChannels(
- term,
- (channels) => {
- if (searchTimeoutId !== this.searchTimeoutId) {
- return;
- }
- this.setState({search: true, channels});
- }
- );
- },
- SEARCH_TIMEOUT_MILLISECONDS
- );
-
- this.searchTimeoutId = searchTimeoutId;
- }
-
- render() {
- let serverError;
- if (this.state.serverError) {
- serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
- }
-
- let createNewChannelButton = (
- <button
- id='createNewChannel'
- type='button'
- className='btn btn-primary channel-create-btn'
- onClick={this.props.handleNewChannel}
- >
- <FormattedMessage
- id='more_channels.create'
- defaultMessage='Create New Channel'
- />
- </button>
- );
-
- let createChannelHelpText = (
- <p className='secondary-message'>
- <FormattedMessage
- id='more_channels.createClick'
- defaultMessage="Click 'Create New Channel' to make a new one"
- />
- </p>
- );
-
- const isTeamAdmin = TeamStore.isTeamAdminForCurrentTeam();
- const isSystemAdmin = UserStore.isSystemAdminForCurrentUser();
-
- if (!showCreateOption(Constants.OPEN_CHANNEL, isTeamAdmin, isSystemAdmin)) {
- createNewChannelButton = null;
- createChannelHelpText = null;
- }
-
- return (
- <Modal
- dialogClassName='more-modal more-modal--action'
- show={this.state.show}
- onHide={this.handleHide}
- onExited={this.handleExit}
- >
- <Modal.Header closeButton={true}>
- <Modal.Title>
- <FormattedMessage
- id='more_channels.title'
- defaultMessage='More Channels'
- />
- </Modal.Title>
- {createNewChannelButton}
- </Modal.Header>
- <Modal.Body>
- <SearchableChannelList
- channels={this.state.channels}
- channelsPerPage={CHANNELS_PER_PAGE}
- nextPage={this.nextPage}
- search={this.search}
- handleJoin={this.handleJoin}
- noResultsText={createChannelHelpText}
- />
- {serverError}
- </Modal.Body>
- </Modal>
- );
- }
-}
diff --git a/webapp/components/more_direct_channels/index.js b/webapp/components/more_direct_channels/index.js
deleted file mode 100644
index a56f45886..000000000
--- a/webapp/components/more_direct_channels/index.js
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getProfiles, getProfilesInTeam} from 'mattermost-redux/actions/users';
-
-import MoreDirectChannels from './more_direct_channels.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getProfiles,
- getProfilesInTeam
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(MoreDirectChannels);
diff --git a/webapp/components/more_direct_channels/more_direct_channels.jsx b/webapp/components/more_direct_channels/more_direct_channels.jsx
deleted file mode 100644
index ab338f68e..000000000
--- a/webapp/components/more_direct_channels/more_direct_channels.jsx
+++ /dev/null
@@ -1,345 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import MultiSelect from 'components/multiselect/multiselect.jsx';
-import ProfilePicture from 'components/profile_picture.jsx';
-
-import {searchUsers} from 'actions/user_actions.jsx';
-import {openDirectChannelToUser, openGroupChannelToUsers} from 'actions/channel_actions.jsx';
-
-import UserStore from 'stores/user_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-
-import Constants from 'utils/constants.jsx';
-import {displayEntireNameForUser} from 'utils/utils.jsx';
-import {Client4} from 'mattermost-redux/client';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import {Modal} from 'react-bootstrap';
-import {FormattedMessage} from 'react-intl';
-import {browserHistory} from 'react-router/es6';
-
-import store from 'stores/redux_store.jsx';
-import {searchProfiles, searchProfilesInCurrentTeam} from 'mattermost-redux/selectors/entities/users';
-
-const USERS_PER_PAGE = 50;
-const MAX_SELECTABLE_VALUES = Constants.MAX_USERS_IN_GM - 1;
-
-export default class MoreDirectChannels extends React.Component {
- static propTypes = {
- startingUsers: PropTypes.arrayOf(PropTypes.object),
- onModalDismissed: PropTypes.func,
- actions: PropTypes.shape({
- getProfiles: PropTypes.func.isRequired,
- getProfilesInTeam: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.handleHide = this.handleHide.bind(this);
- this.handleExit = this.handleExit.bind(this);
- this.handleSubmit = this.handleSubmit.bind(this);
- this.handleDelete = this.handleDelete.bind(this);
- this.handlePageChange = this.handlePageChange.bind(this);
- this.onChange = this.onChange.bind(this);
- this.search = this.search.bind(this);
- this.addValue = this.addValue.bind(this);
-
- this.searchTimeoutId = 0;
- this.term = '';
- this.listType = global.window.mm_config.RestrictDirectMessage;
-
- const values = [];
- if (props.startingUsers) {
- for (let i = 0; i < props.startingUsers.length; i++) {
- const user = Object.assign({}, props.startingUsers[i]);
- user.value = user.id;
- user.label = '@' + user.username;
- values.push(user);
- }
- }
-
- this.state = {
- users: null,
- values,
- show: true,
- search: false,
- loadingChannel: -1
- };
- }
-
- componentDidMount() {
- UserStore.addChangeListener(this.onChange);
- UserStore.addInTeamChangeListener(this.onChange);
- UserStore.addStatusesChangeListener(this.onChange);
-
- if (this.listType === 'any') {
- this.props.actions.getProfiles(0, USERS_PER_PAGE * 2);
- } else {
- this.props.actions.getProfilesInTeam(TeamStore.getCurrentId(), 0, USERS_PER_PAGE * 2);
- }
- }
-
- componentWillUnmount() {
- UserStore.removeChangeListener(this.onChange);
- UserStore.removeInTeamChangeListener(this.onChange);
- UserStore.removeStatusesChangeListener(this.onChange);
- }
-
- handleHide() {
- this.setState({show: false});
- }
-
- handleExit() {
- if (this.exitToChannel) {
- browserHistory.push(this.exitToChannel);
- }
-
- if (this.props.onModalDismissed) {
- this.props.onModalDismissed();
- }
- }
-
- handleSubmit(e) {
- if (e) {
- e.preventDefault();
- }
-
- if (this.state.loadingChannel !== -1) {
- return;
- }
-
- const userIds = this.state.values.map((v) => v.id);
- if (userIds.length === 0) {
- return;
- }
-
- this.setState({loadingChannel: 1});
-
- const success = (channel) => {
- // Due to how react-overlays Modal handles focus, we delay pushing
- // the new channel information until the modal is fully exited.
- // The channel information will be pushed in `handleExit`
- this.exitToChannel = TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + channel.name;
- this.setState({loadingChannel: -1});
- this.handleHide();
- };
-
- const error = () => {
- this.setState({loadingChannel: -1});
- };
-
- if (userIds.length === 1) {
- openDirectChannelToUser(userIds[0], success, error);
- } else {
- openGroupChannelToUsers(userIds, success, error);
- }
- }
-
- addValue(value) {
- const values = Object.assign([], this.state.values);
- if (values.indexOf(value) === -1) {
- values.push(value);
- }
-
- this.setState({values});
- }
-
- onChange() {
- let users;
- if (this.term) {
- if (this.listType === 'any') {
- users = Object.assign([], searchProfiles(store.getState(), this.term, true));
- } else {
- users = Object.assign([], searchProfilesInCurrentTeam(store.getState(), this.term, true));
- }
- } else if (this.listType === 'any') {
- users = Object.assign([], UserStore.getProfileList(true));
- } else {
- users = Object.assign([], UserStore.getProfileListInTeam(TeamStore.getCurrentId(), true));
- }
-
- for (let i = 0; i < users.length; i++) {
- const user = Object.assign({}, users[i]);
- user.value = user.id;
- user.label = '@' + user.username;
- users[i] = user;
- }
-
- this.setState({
- users
- });
- }
-
- handlePageChange(page, prevPage) {
- if (page > prevPage) {
- if (this.listType === 'any') {
- this.props.actions.getProfiles(page + 1, USERS_PER_PAGE);
- } else {
- this.props.actions.getProfilesInTeam(page + 1, USERS_PER_PAGE);
- }
- }
- }
-
- resetPaging = () => {
- if (this.refs.multiselect) {
- this.refs.multiselect.resetPaging();
- }
- }
-
- search(term) {
- clearTimeout(this.searchTimeoutId);
- this.term = term;
-
- if (term === '') {
- this.resetPaging();
- this.onChange();
- return;
- }
-
- let teamId;
- if (this.listType === 'any') {
- teamId = '';
- } else {
- teamId = TeamStore.getCurrentId();
- }
-
- this.searchTimeoutId = setTimeout(
- () => {
- searchUsers(term, teamId, {}, this.resetPaging);
- },
- Constants.SEARCH_TIMEOUT_MILLISECONDS
- );
- }
-
- handleDelete(values) {
- this.setState({values});
- }
-
- renderOption(option, isSelected, onAdd) {
- var rowSelected = '';
- if (isSelected) {
- rowSelected = 'more-modal__row--selected';
- }
-
- return (
- <div
- key={option.id}
- ref={isSelected ? 'selected' : option.id}
- className={'more-modal__row clickable ' + rowSelected}
- onClick={() => onAdd(option)}
- >
- <ProfilePicture
- src={Client4.getProfilePictureUrl(option.id, option.last_picture_update)}
- status={`${UserStore.getStatus(option.id)}`}
- width='32'
- height='32'
- />
- <div
- className='more-modal__details'
- >
- <div className='more-modal__name'>
- {displayEntireNameForUser(option)}
- </div>
- <div className='more-modal__description'>
- {option.email}
- </div>
- </div>
- <div className='more-modal__actions'>
- <div className='more-modal__actions--round'>
- <i className='fa fa-plus'/>
- </div>
- </div>
- </div>
- );
- }
-
- renderValue(user) {
- return user.username;
- }
-
- render() {
- let note;
- if (this.props.startingUsers) {
- if (this.state.values && this.state.values.length >= MAX_SELECTABLE_VALUES) {
- note = (
- <FormattedMessage
- id='more_direct_channels.new_convo_note.full'
- defaultMessage='You’ve reached the maximum number of people for this conversation. Consider creating a private channel instead.'
- />
- );
- } else {
- note = (
- <FormattedMessage
- id='more_direct_channels.new_convo_note'
- defaultMessage='This will start a new conversation. If you’re adding a lot of people, consider creating a private channel instead.'
- />
- );
- }
- }
-
- const buttonSubmitText = (
- <FormattedMessage
- id='multiselect.go'
- defaultMessage='Go'
- />
- );
-
- const numRemainingText = (
- <FormattedMessage
- id='multiselect.numPeopleRemaining'
- defaultMessage='Use ↑↓ to browse, ↵ to select. You can add {num, number} more {num, plural, one {person} other {people}}. '
- values={{
- num: MAX_SELECTABLE_VALUES - this.state.values.length
- }}
- />
- );
-
- let users = [];
- if (this.state.users) {
- users = this.state.users.filter((user) => user.delete_at === 0);
- }
-
- return (
- <Modal
- dialogClassName={'more-modal more-direct-channels'}
- show={this.state.show}
- onHide={this.handleHide}
- onExited={this.handleExit}
- >
- <Modal.Header closeButton={true}>
- <Modal.Title>
- <FormattedMessage
- id='more_direct_channels.title'
- defaultMessage='Direct Messages'
- />
- </Modal.Title>
- </Modal.Header>
- <Modal.Body>
- <MultiSelect
- key='moreDirectChannelsList'
- ref='multiselect'
- options={users}
- optionRenderer={this.renderOption}
- values={this.state.values}
- valueRenderer={this.renderValue}
- perPage={USERS_PER_PAGE}
- handlePageChange={this.handlePageChange}
- handleInput={this.search}
- handleDelete={this.handleDelete}
- handleAdd={this.addValue}
- handleSubmit={this.handleSubmit}
- noteText={note}
- maxValues={MAX_SELECTABLE_VALUES}
- numRemainingText={numRemainingText}
- buttonSubmitText={buttonSubmitText}
- />
- </Modal.Body>
- </Modal>
- );
- }
-}
diff --git a/webapp/components/msg_typing.jsx b/webapp/components/msg_typing.jsx
deleted file mode 100644
index ee278c5f6..000000000
--- a/webapp/components/msg_typing.jsx
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import UserTypingStore from 'stores/user_typing_store.jsx';
-
-import {FormattedMessage} from 'react-intl';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-class MsgTyping extends React.Component {
- constructor(props) {
- super(props);
-
- this.onTypingChange = this.onTypingChange.bind(this);
- this.updateTypingText = this.updateTypingText.bind(this);
- this.componentWillReceiveProps = this.componentWillReceiveProps.bind(this);
-
- this.state = {
- text: ''
- };
- }
-
- componentWillMount() {
- UserTypingStore.addChangeListener(this.onTypingChange);
- this.onTypingChange();
- }
-
- componentWillUnmount() {
- UserTypingStore.removeChangeListener(this.onTypingChange);
- }
-
- componentWillReceiveProps(nextProps) {
- if (this.props.channelId !== nextProps.channelId) {
- this.updateTypingText(UserTypingStore.getUsersTyping(nextProps.channelId, nextProps.parentId));
- }
- }
-
- onTypingChange() {
- this.updateTypingText(UserTypingStore.getUsersTyping(this.props.channelId, this.props.parentId));
- }
-
- updateTypingText(typingUsers) {
- let text = '';
- let users = {};
- let numUsers = 0;
- if (typingUsers) {
- users = Object.keys(typingUsers);
- numUsers = users.length;
- }
-
- switch (numUsers) {
- case 0:
- text = '';
- break;
- case 1:
- text = (
- <FormattedMessage
- id='msg_typing.isTyping'
- defaultMessage='{user} is typing...'
- values={{
- user: users[0]
- }}
- />
- );
- break;
- default: {
- const last = users.pop();
- text = (
- <FormattedMessage
- id='msg_typing.areTyping'
- defaultMessage='{users} and {last} are typing...'
- values={{
- users: (users.join(', ')),
- last
- }}
- />
- );
- break;
- }
- }
-
- this.setState({text});
- }
-
- render() {
- return (
- <span className='msg-typing'>{this.state.text}</span>
- );
- }
-}
-
-MsgTyping.propTypes = {
- channelId: PropTypes.string,
- parentId: PropTypes.string
-};
-
-export default MsgTyping;
diff --git a/webapp/components/multiselect/multiselect.jsx b/webapp/components/multiselect/multiselect.jsx
deleted file mode 100644
index 5f650cfa0..000000000
--- a/webapp/components/multiselect/multiselect.jsx
+++ /dev/null
@@ -1,271 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import MultiSelectList from './multiselect_list.jsx';
-
-import {localizeMessage} from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-const KeyCodes = Constants.KeyCodes;
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import ReactSelect from 'react-select';
-import {FormattedMessage} from 'react-intl';
-
-export default class MultiSelect extends React.Component {
- constructor(props) {
- super(props);
-
- this.selected = null;
-
- this.state = {
- page: 0
- };
- }
-
- componentDidMount() {
- document.addEventListener('keydown', this.handleEnterPress);
- this.refs.select.focus();
- }
-
- componentWillUnmount() {
- document.removeEventListener('keydown', this.handleEnterPress);
- }
-
- nextPage = () => {
- if (this.props.handlePageChange) {
- this.props.handlePageChange(this.state.page + 1, this.state.page);
- }
- this.refs.list.setSelected(0);
- this.setState({page: this.state.page + 1});
- }
-
- prevPage = () => {
- if (this.state.page === 0) {
- return;
- }
-
- if (this.props.handlePageChange) {
- this.props.handlePageChange(this.state.page - 1, this.state.page);
- }
- this.refs.list.setSelected(0);
- this.setState({page: this.state.page - 1});
- }
-
- resetPaging = () => {
- this.setState({page: 0});
- }
-
- onSelect = (selected) => {
- this.selected = selected;
- }
-
- onAdd = (value) => {
- if (this.props.maxValues && this.props.values.length >= this.props.maxValues) {
- return;
- }
-
- for (let i = 0; i < this.props.values.length; i++) {
- if (this.props.values[i].value === value.value) {
- return;
- }
- }
-
- this.props.handleAdd(value);
- this.selected = null;
- this.refs.select.handleInputChange({target: {value: ''}});
- this.onInput('');
- this.refs.select.focus();
- }
-
- onInput = (input) => {
- if (input === '') {
- this.refs.list.setSelected(-1);
- } else {
- this.refs.list.setSelected(0);
- }
- this.selected = null;
-
- this.props.handleInput(input);
- }
-
- handleEnterPress = (e) => {
- switch (e.keyCode) {
- case KeyCodes.ENTER:
- if (this.selected == null) {
- this.props.handleSubmit();
- return;
- }
- this.onAdd(this.selected);
- break;
- }
- }
-
- onChange = (values) => {
- if (values.length < this.props.values.length) {
- this.props.handleDelete(values);
- }
- }
-
- render() {
- const options = Object.assign([], this.props.options);
- const values = this.props.values;
-
- let numRemainingText;
- if (this.props.numRemainingText) {
- numRemainingText = this.props.numRemainingText;
- } else if (this.props.maxValues != null) {
- numRemainingText = (
- <FormattedMessage
- id='multiselect.numRemaining'
- defaultMessage='You can add {num, number} more. '
- values={{
- num: this.props.maxValues - this.props.values.length
- }}
- />
- );
- }
-
- let buttonSubmitText;
- if (this.props.buttonSubmitText) {
- buttonSubmitText = this.props.buttonSubmitText;
- } else if (this.props.maxValues != null) {
- buttonSubmitText = (
- <FormattedMessage
- id='multiselect.go'
- defaultMessage='Go'
- />
- );
- }
-
- let optionsToDisplay = [];
- let nextButton;
- let previousButton;
- let noteTextContainer;
-
- if (this.props.noteText) {
- noteTextContainer = (
- <div className='multi-select__note'>
- <div className='note__icon'><span className='fa fa-info'/></div>
- <div>{this.props.noteText}</div>
- </div>
- );
- }
-
- const valueMap = {};
- for (let i = 0; i < values.length; i++) {
- valueMap[values[i].id] = true;
- }
-
- for (let i = options.length - 1; i >= 0; i--) {
- if (valueMap[options[i].id]) {
- options.splice(i, 1);
- }
- }
-
- if (options && options.length > this.props.perPage) {
- const pageStart = this.state.page * this.props.perPage;
- const pageEnd = pageStart + this.props.perPage;
- optionsToDisplay = options.slice(pageStart, pageEnd);
-
- if (options.length > pageEnd) {
- nextButton = (
- <button
- className='btn btn-default filter-control filter-control__next'
- onClick={this.nextPage}
- >
- <FormattedMessage
- id='filtered_user_list.next'
- defaultMessage='Next'
- />
- </button>
- );
- }
-
- if (this.state.page > 0) {
- previousButton = (
- <button
- className='btn btn-default filter-control filter-control__prev'
- onClick={this.prevPage}
- >
- <FormattedMessage
- id='filtered_user_list.prev'
- defaultMessage='Previous'
- />
- </button>
- );
- }
- } else {
- optionsToDisplay = options;
- }
-
- return (
- <div className='filtered-user-list'>
- <div className='filter-row filter-row--full'>
- <div className='multi-select__container'>
- <ReactSelect
- ref='select'
- multi={true}
- options={this.props.options}
- joinValues={true}
- clearable={false}
- openOnFocus={true}
- onInputChange={this.onInput}
- onBlurResetsInput={false}
- onCloseResetsInput={false}
- onChange={this.onChange}
- value={this.props.values}
- valueRenderer={this.props.valueRenderer}
- menuRenderer={() => null}
- arrowRenderer={() => null}
- noResultsText={null}
- placeholder={localizeMessage('multiselect.placeholder', 'Search and add members')}
- />
- <button
- className='btn btn-primary btn-sm'
- onClick={this.props.handleSubmit}
- >
- {buttonSubmitText}
- </button>
- </div>
- <div className='multi-select__help'>
- {numRemainingText}
- {noteTextContainer}
- </div>
- </div>
- <MultiSelectList
- ref='list'
- options={optionsToDisplay}
- optionRenderer={this.props.optionRenderer}
- page={this.state.page}
- perPage={this.props.perPage}
- onPageChange={this.props.handlePageChange}
- onAdd={this.onAdd}
- onSelect={this.onSelect}
- />
- <div className='filter-controls'>
- {previousButton}
- {nextButton}
- </div>
- </div>
- );
- }
-}
-
-MultiSelect.propTypes = {
- options: PropTypes.arrayOf(PropTypes.object),
- optionRenderer: PropTypes.func,
- values: PropTypes.arrayOf(PropTypes.object),
- valueRenderer: PropTypes.func,
- handleInput: PropTypes.func,
- handleDelete: PropTypes.func,
- perPage: PropTypes.number,
- handlePageChange: PropTypes.func,
- handleAdd: PropTypes.func,
- handleSubmit: PropTypes.func,
- noteText: PropTypes.node,
- maxValues: PropTypes.number,
- numRemainingText: PropTypes.node,
- buttonSubmitText: PropTypes.node
-};
diff --git a/webapp/components/multiselect/multiselect_list.jsx b/webapp/components/multiselect/multiselect_list.jsx
deleted file mode 100644
index 4e5878790..000000000
--- a/webapp/components/multiselect/multiselect_list.jsx
+++ /dev/null
@@ -1,171 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {cmdOrCtrlPressed} from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-const KeyCodes = Constants.KeyCodes;
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-
-export default class MultiSelectList extends React.Component {
- constructor(props) {
- super(props);
-
- this.defaultOptionRenderer = this.defaultOptionRenderer.bind(this);
- this.handleArrowPress = this.handleArrowPress.bind(this);
- this.setSelected = this.setSelected.bind(this);
-
- this.toSelect = -1;
-
- this.state = {
- selected: -1
- };
- }
-
- componentDidMount() {
- document.addEventListener('keydown', this.handleArrowPress);
- }
-
- componentWillUnmount() {
- document.removeEventListener('keydown', this.handleArrowPress);
- }
-
- componentWillReceiveProps(nextProps) {
- this.setState({selected: this.toSelect});
-
- const options = nextProps.options;
-
- if (options && options.length > 0 && this.toSelect >= 0) {
- this.props.onSelect(options[this.toSelect]);
- }
- }
-
- componentDidUpdate() {
- if (this.refs.list && this.refs.selected) {
- const elemTop = this.refs.selected.getBoundingClientRect().top;
- const elemBottom = this.refs.selected.getBoundingClientRect().bottom;
- const listTop = this.refs.list.getBoundingClientRect().top;
- const listBottom = this.refs.list.getBoundingClientRect().bottom;
- if (elemBottom > listBottom) {
- this.refs.selected.scrollIntoView(false);
- } else if (elemTop < listTop) {
- this.refs.selected.scrollIntoView(true);
- }
- }
- }
-
- setSelected(selected) {
- this.toSelect = selected;
- }
-
- handleArrowPress(e) {
- if (cmdOrCtrlPressed(e) && e.shiftKey) {
- return;
- }
-
- const options = this.props.options;
- if (options.length === 0) {
- return;
- }
-
- let selected;
- switch (e.keyCode) {
- case KeyCodes.DOWN:
- if (this.state.selected === -1) {
- selected = 0;
- break;
- }
- selected = Math.min(this.state.selected + 1, options.length - 1);
- break;
- case KeyCodes.UP:
- if (this.state.selected === -1) {
- selected = 0;
- break;
- }
- selected = Math.max(this.state.selected - 1, 0);
- break;
- default:
- return;
- }
-
- e.preventDefault();
- this.setState({selected});
- this.props.onSelect(options[selected]);
- }
-
- defaultOptionRenderer(option, isSelected, onAdd) {
- var rowSelected = '';
- if (isSelected) {
- rowSelected = 'more-modal__row--selected';
- }
-
- return (
- <div
- ref={isSelected ? 'selected' : option.value}
- className={rowSelected}
- key={'multiselectoption' + option.value}
- onClick={() => onAdd(option)}
- >
- {option.label}
- </div>
- );
- }
-
- render() {
- const options = this.props.options;
-
- if (options == null || options.length === 0) {
- return (
- <div
- key='no-users-found'
- className='no-channel-message'
- >
- <p className='primary-message'>
- <FormattedMessage
- id='multiselect.list.notFound'
- defaultMessage='No items found'
- />
- </p>
- </div>
- );
- }
-
- let renderer;
- if (this.props.optionRenderer) {
- renderer = this.props.optionRenderer;
- } else {
- renderer = this.defaultOptionRenderer;
- }
-
- const optionControls = options.map((o, i) => renderer(o, this.state.selected === i, this.props.onAdd));
-
- return (
- <div className='more-modal__list'>
- <div
- ref='list'
- >
- {optionControls}
- </div>
- </div>
- );
- }
-}
-
-MultiSelectList.defaultProps = {
- options: [],
- perPage: 50,
- onAction: () => null
-};
-
-MultiSelectList.propTypes = {
- options: PropTypes.arrayOf(PropTypes.object),
- optionRenderer: PropTypes.func,
- page: PropTypes.number,
- perPage: PropTypes.number,
- onPageChange: PropTypes.func,
- onAdd: PropTypes.func,
- onSelect: PropTypes.func
-};
diff --git a/webapp/components/navbar.jsx b/webapp/components/navbar.jsx
deleted file mode 100644
index 43411c2cf..000000000
--- a/webapp/components/navbar.jsx
+++ /dev/null
@@ -1,936 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import EditChannelHeaderModal from './edit_channel_header_modal.jsx';
-import EditChannelPurposeModal from './edit_channel_purpose_modal.jsx';
-import MessageWrapper from './message_wrapper.jsx';
-import NotifyCounts from './notify_counts.jsx';
-import ChannelInfoModal from './channel_info_modal.jsx';
-import ChannelInviteModal from 'components/channel_invite_modal';
-import ChannelMembersModal from './channel_members_modal.jsx';
-import ChannelNotificationsModal from './channel_notifications_modal.jsx';
-import DeleteChannelModal from './delete_channel_modal.jsx';
-import RenameChannelModal from './rename_channel_modal.jsx';
-import ToggleModalButton from './toggle_modal_button.jsx';
-import StatusIcon from './status_icon.jsx';
-
-import UserStore from 'stores/user_store.jsx';
-import ChannelStore from 'stores/channel_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-import PreferenceStore from 'stores/preference_store.jsx';
-import SearchStore from 'stores/search_store.jsx';
-import ModalStore from 'stores/modal_store.jsx';
-
-import QuickSwitchModal from 'components/quick_switch_modal';
-
-import * as Utils from 'utils/utils.jsx';
-import * as ChannelUtils from 'utils/channel_utils.jsx';
-import * as ChannelActions from 'actions/channel_actions.jsx';
-import * as GlobalActions from 'actions/global_actions.jsx';
-import {getPinnedPosts} from 'actions/post_actions.jsx';
-
-import Constants from 'utils/constants.jsx';
-const ActionTypes = Constants.ActionTypes;
-
-import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
-
-import {FormattedMessage} from 'react-intl';
-
-import {Popover, OverlayTrigger} from 'react-bootstrap';
-
-import {Link} from 'react-router/es6';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class Navbar extends React.Component {
- constructor(props) {
- super(props);
-
- this.onChange = this.onChange.bind(this);
- this.handleLeave = this.handleLeave.bind(this);
- this.showSearch = this.showSearch.bind(this);
-
- this.showEditChannelHeaderModal = this.showEditChannelHeaderModal.bind(this);
- this.hideEditChannelHeaderModal = this.hideEditChannelHeaderModal.bind(this);
- this.showChannelPurposeModal = this.showChannelPurposeModal.bind(this);
- this.hideChannelPurposeModal = this.hideChannelPurposeModal.bind(this);
- this.showRenameChannelModal = this.showRenameChannelModal.bind(this);
- this.hideRenameChannelModal = this.hideRenameChannelModal.bind(this);
- this.isStateValid = this.isStateValid.bind(this);
-
- this.createCollapseButtons = this.createCollapseButtons.bind(this);
- this.createDropdown = this.createDropdown.bind(this);
-
- this.showMembersModal = this.showMembersModal.bind(this);
- this.hideMembersModal = this.hideMembersModal.bind(this);
-
- this.toggleQuickSwitchModal = this.toggleQuickSwitchModal.bind(this);
- this.hideQuickSwitchModal = this.hideQuickSwitchModal.bind(this);
- this.handleQuickSwitchKeyPress = this.handleQuickSwitchKeyPress.bind(this);
-
- this.openDirectMessageModal = this.openDirectMessageModal.bind(this);
- this.getPinnedPosts = this.getPinnedPosts.bind(this);
-
- const state = this.getStateFromStores();
- state.showEditChannelPurposeModal = false;
- state.showEditChannelHeaderModal = false;
- state.showMembersModal = false;
- state.showRenameChannelModal = false;
- state.showQuickSwitchModal = false;
- state.quickSwitchMode = 'channel';
- this.state = state;
- }
-
- getStateFromStores() {
- const channel = ChannelStore.getCurrent();
-
- return {
- channel,
- member: ChannelStore.getCurrentMember(),
- users: [],
- userCount: ChannelStore.getCurrentStats().member_count,
- currentUser: UserStore.getCurrentUser(),
- isFavorite: channel && ChannelUtils.isFavoriteChannel(channel)
- };
- }
-
- isStateValid() {
- return this.state.channel && this.state.member && this.state.users && this.state.currentUser;
- }
-
- componentDidMount() {
- ChannelStore.addChangeListener(this.onChange);
- ChannelStore.addStatsChangeListener(this.onChange);
- UserStore.addStatusesChangeListener(this.onChange);
- UserStore.addChangeListener(this.onChange);
- PreferenceStore.addChangeListener(this.onChange);
- ModalStore.addModalListener(ActionTypes.TOGGLE_QUICK_SWITCH_MODAL, this.toggleQuickSwitchModal);
- ModalStore.addModalListener(ActionTypes.TOGGLE_CHANNEL_HEADER_UPDATE_MODAL, this.showEditChannelHeaderModal);
- ModalStore.addModalListener(ActionTypes.TOGGLE_CHANNEL_PURPOSE_UPDATE_MODAL, this.showChannelPurposeModal);
- ModalStore.addModalListener(ActionTypes.TOGGLE_CHANNEL_NAME_UPDATE_MODAL, this.showRenameChannelModal);
- $('.inner-wrap').click(this.hideSidebars);
- document.addEventListener('keydown', this.handleQuickSwitchKeyPress);
- }
-
- componentWillUnmount() {
- ChannelStore.removeChangeListener(this.onChange);
- ChannelStore.removeStatsChangeListener(this.onChange);
- UserStore.removeStatusesChangeListener(this.onChange);
- UserStore.removeChangeListener(this.onChange);
- PreferenceStore.removeChangeListener(this.onChange);
- ModalStore.removeModalListener(ActionTypes.TOGGLE_QUICK_SWITCH_MODAL, this.toggleQuickSwitchModal);
- ModalStore.addModalListener(ActionTypes.TOGGLE_CHANNEL_HEADER_UPDATE_MODAL, this.hideEditChannelHeaderModal);
- ModalStore.addModalListener(ActionTypes.TOGGLE_CHANNEL_PURPOSE_UPDATE_MODAL, this.hideChannelPurposeModal);
- ModalStore.addModalListener(ActionTypes.TOGGLE_CHANNEL_NAME_UPDATE_MODAL, this.hideRenameChannelModal);
- document.removeEventListener('keydown', this.handleQuickSwitchKeyPress);
- }
-
- handleSubmit(e) {
- e.preventDefault();
- }
-
- handleLeave() {
- if (this.state.channel.type === Constants.PRIVATE_CHANNEL) {
- GlobalActions.showLeavePrivateChannelModal(this.state.channel);
- } else {
- ChannelActions.leaveChannel(this.state.channel.id);
- }
- }
-
- hideSidebars(e) {
- var windowWidth = $(window).outerWidth();
- if (windowWidth <= 768) {
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_SEARCH,
- results: null
- });
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_POST_SELECTED,
- postId: null
- });
-
- if (e.target.className !== 'navbar-toggle' && e.target.className !== 'icon-bar') {
- $('.app__body .inner-wrap').removeClass('move--right move--left move--left-small');
- $('.app__body .sidebar--left').removeClass('move--right');
- $('.multi-teams .team-sidebar').removeClass('move--right');
- $('.app__body .sidebar--right').removeClass('move--left');
- $('.app__body .sidebar--menu').removeClass('move--left');
- }
- }
- }
-
- toggleLeftSidebar() {
- $('.app__body .inner-wrap').toggleClass('move--right');
- $('.app__body .sidebar--left').toggleClass('move--right');
- $('.multi-teams .team-sidebar').toggleClass('move--right');
- }
-
- toggleRightSidebar() {
- $('.app__body .inner-wrap').toggleClass('move--left-small');
- $('.app__body .sidebar--menu').toggleClass('move--left');
- }
-
- showSearch() {
- AppDispatcher.handleServerAction({
- type: ActionTypes.SHOW_SEARCH
- });
- }
-
- onChange() {
- this.setState(this.getStateFromStores());
- }
-
- showEditChannelHeaderModal() {
- // this can't be done using a ToggleModalButton because we can't use one inside an OverlayTrigger
- if (this.refs.headerOverlay) {
- this.refs.headerOverlay.hide();
- }
-
- this.setState({
- showEditChannelHeaderModal: true
- });
- }
-
- hideEditChannelHeaderModal() {
- this.setState({
- showEditChannelHeaderModal: false
- });
- }
-
- showChannelPurposeModal() {
- this.setState({
- showEditChannelPurposeModal: true
- });
- }
-
- hideChannelPurposeModal() {
- this.setState({
- showEditChannelPurposeModal: false
- });
- }
-
- showRenameChannelModal() {
- this.setState({
- showRenameChannelModal: true
- });
- }
-
- hideRenameChannelModal() {
- this.setState({
- showRenameChannelModal: false
- });
- }
-
- showMembersModal(e) {
- e.preventDefault();
-
- this.setState({showMembersModal: true});
- }
-
- hideMembersModal() {
- this.setState({showMembersModal: false});
- }
-
- handleQuickSwitchKeyPress(e) {
- if (Utils.cmdOrCtrlPressed(e) && !e.shiftKey && e.keyCode === Constants.KeyCodes.K) {
- if (!e.altKey) {
- e.preventDefault();
- this.toggleQuickSwitchModal('channel');
- }
- }
- }
-
- toggleQuickSwitchModal(mode = 'channel') {
- if (this.state.showQuickSwitchModal) {
- this.setState({showQuickSwitchModal: false, quickSwitchMode: 'channel'});
- } else {
- this.setState({showQuickSwitchModal: true, quickSwitchMode: mode});
- }
- }
-
- hideQuickSwitchModal() {
- this.setState({
- showQuickSwitchModal: false,
- quickSwitchMode: 'channel'
- });
- }
-
- openDirectMessageModal() {
- AppDispatcher.handleViewAction({
- type: ActionTypes.TOGGLE_DM_MODAL,
- value: true,
- startingUsers: UserStore.getProfileListInChannel(this.state.channel.id, true)
- });
- }
-
- getPinnedPosts(e) {
- e.preventDefault();
- if (SearchStore.isPinnedPosts) {
- GlobalActions.toggleSideBarAction(false);
- } else {
- getPinnedPosts(this.state.channel.id);
- }
- }
-
- toggleFavorite = (e) => {
- e.preventDefault();
-
- if (this.state.isFavorite) {
- ChannelActions.unmarkFavorite(this.state.channel.id);
- } else {
- ChannelActions.markFavorite(this.state.channel.id);
- }
- };
-
- createDropdown(channel, channelTitle, isSystemAdmin, isTeamAdmin, isChannelAdmin, isDirect, isGroup, popoverContent) {
- const infoIcon = Constants.INFO_ICON_SVG;
-
- if (channel) {
- let viewInfoOption;
- let viewPinnedPostsOption;
- let addMembersOption;
- let manageMembersOption;
- let setChannelHeaderOption;
- let setChannelPurposeOption;
- let notificationPreferenceOption;
- let renameChannelOption;
- let deleteChannelOption;
- let leaveChannelOption;
-
- if (isDirect) {
- setChannelHeaderOption = (
- <li role='presentation'>
- <a
- role='menuitem'
- href='#'
- onClick={this.showEditChannelHeaderModal}
- >
- <FormattedMessage
- id='channel_header.channelHeader'
- defaultMessage='Set Channel Header...'
- />
- </a>
- </li>
- );
- } else if (isGroup) {
- setChannelHeaderOption = (
- <li role='presentation'>
- <a
- role='menuitem'
- href='#'
- onClick={this.showEditChannelHeaderModal}
- >
- <FormattedMessage
- id='channel_header.channelHeader'
- defaultMessage='Set Channel Header...'
- />
- </a>
- </li>
- );
-
- notificationPreferenceOption = (
- <li role='presentation'>
- <ToggleModalButton
- role='menuitem'
- dialogType={ChannelNotificationsModal}
- dialogProps={{
- channel,
- channelMember: this.state.member,
- currentUser: this.state.currentUser
- }}
- >
- <FormattedMessage
- id='navbar.preferences'
- defaultMessage='Notification Preferences'
- />
- </ToggleModalButton>
- </li>
- );
-
- addMembersOption = (
- <li
- role='presentation'
- >
- <a
- role='menuitem'
- href='#'
- onClick={this.openDirectMessageModal}
- >
- <FormattedMessage
- id='navbar.addMembers'
- defaultMessage='Add Members'
- />
- </a>
- </li>
- );
- } else {
- viewInfoOption = (
- <li role='presentation'>
- <ToggleModalButton
- role='menuitem'
- dialogType={ChannelInfoModal}
- dialogProps={{channel}}
- >
- <FormattedMessage
- id='navbar.viewInfo'
- defaultMessage='View Info'
- />
- </ToggleModalButton>
- </li>
- );
-
- viewPinnedPostsOption = (
- <li role='presentation'>
- <a
- role='menuitem'
- href='#'
- onClick={this.getPinnedPosts}
- >
- <FormattedMessage
- id='navbar.viewPinnedPosts'
- defaultMessage='View Pinned Posts'
- />
- </a>
- </li>
- );
-
- if (ChannelStore.isDefault(channel)) {
- manageMembersOption = (
- <li
- key='view_members'
- role='presentation'
- >
- <a
- role='menuitem'
- href='#'
- onClick={this.showMembersModal}
- >
- <FormattedMessage
- id='channel_header.viewMembers'
- defaultMessage='View Members'
- />
- </a>
- </li>
- );
- } else {
- addMembersOption = (
- <li role='presentation'>
- <ToggleModalButton
- ref='channelInviteModalButton'
- role='menuitem'
- dialogType={ChannelInviteModal}
- dialogProps={{channel, currentUser: this.state.currentUser}}
- >
- <FormattedMessage
- id='navbar.addMembers'
- defaultMessage='Add Members'
- />
- </ToggleModalButton>
- </li>
- );
-
- if (ChannelUtils.canManageMembers(channel, isChannelAdmin, isTeamAdmin, isSystemAdmin)) {
- manageMembersOption = (
- <li
- key='manage_members'
- role='presentation'
- >
- <a
- role='menuitem'
- href='#'
- onClick={this.showMembersModal}
- >
- <FormattedMessage
- id='channel_header.manageMembers'
- defaultMessage='Manage Members'
- />
- </a>
- </li>
- );
- } else {
- manageMembersOption = (
- <li
- key='view_members'
- role='presentation'
- >
- <a
- role='menuitem'
- href='#'
- onClick={this.showMembersModal}
- >
- <FormattedMessage
- id='channel_header.viewMembers'
- defaultMessage='View Members'
- />
- </a>
- </li>
- );
- }
- }
-
- notificationPreferenceOption = (
- <li role='presentation'>
- <ToggleModalButton
- role='menuitem'
- dialogType={ChannelNotificationsModal}
- dialogProps={{
- channel,
- channelMember: this.state.member,
- currentUser: this.state.currentUser
- }}
- >
- <FormattedMessage
- id='navbar.preferences'
- defaultMessage='Notification Preferences'
- />
- </ToggleModalButton>
- </li>
- );
-
- if (ChannelUtils.showManagementOptions(channel, isChannelAdmin, isTeamAdmin, isSystemAdmin)) {
- setChannelHeaderOption = (
- <li role='presentation'>
- <a
- role='menuitem'
- href='#'
- onClick={this.showEditChannelHeaderModal}
- >
- <FormattedMessage
- id='channel_header.setHeader'
- defaultMessage='Edit Channel Header'
- />
- </a>
- </li>
- );
-
- setChannelPurposeOption = (
- <li role='presentation'>
- <a
- role='menuitem'
- href='#'
- onClick={this.showChannelPurposeModal}
- >
- <FormattedMessage
- id='channel_header.setPurpose'
- defaultMessage='Edit Channel Purpose'
- />
- </a>
- </li>
- );
-
- renameChannelOption = (
- <li role='presentation'>
- <a
- role='menuitem'
- href='#'
- onClick={this.showRenameChannelModal}
- >
- <FormattedMessage
- id='channel_header.rename'
- defaultMessage='Rename Channel'
- />
- </a>
- </li>
- );
- }
-
- if (ChannelUtils.showDeleteOptionForCurrentUser(channel, isChannelAdmin, isTeamAdmin, isSystemAdmin)) {
- deleteChannelOption = (
- <li role='presentation'>
- <ToggleModalButton
- role='menuitem'
- dialogType={DeleteChannelModal}
- dialogProps={{channel}}
- >
- <FormattedMessage
- id='channel_header.delete'
- defaultMessage='Delete Channel'
- />
- </ToggleModalButton>
- </li>
- );
- }
-
- if (!ChannelStore.isDefault(channel)) {
- leaveChannelOption = (
- <li role='presentation'>
- <a
- role='menuitem'
- href='#'
- onClick={this.handleLeave}
- >
- <FormattedMessage
- id='channel_header.leave'
- defaultMessage='Leave Channel'
- />
- </a>
- </li>
- );
- }
- }
-
- const toggleFavoriteOption = (
- <li
- key='toggle_favorite'
- role='presentation'
- >
- <a
- role='menuitem'
- href='#'
- onClick={this.toggleFavorite}
- >
- {this.state.isFavorite ?
- <FormattedMessage
- id='channelHeader.removeFromFavorites'
- defaultMessage='Remove from Favorites'
- /> :
- <FormattedMessage
- id='channelHeader.addToFavorites'
- defaultMessage='Add to Favorites'
- />}
- </a>
- </li>
- );
-
- return (
- <div className='navbar-brand'>
- <div className='dropdown'>
- <OverlayTrigger
- ref='headerOverlay'
- trigger='click'
- placement='bottom'
- overlay={popoverContent}
- className='description'
- rootClose={true}
- >
- <div className='pull-right description navbar-right__icon info-popover'>
- <span
- className='icon icon__info'
- dangerouslySetInnerHTML={{__html: infoIcon}}
- aria-hidden='true'
- />
- </div>
- </OverlayTrigger>
- <a
- href='#'
- className='dropdown-toggle theme'
- type='button'
- data-toggle='dropdown'
- aria-expanded='true'
- >
- <span className='heading'><StatusIcon status={this.getTeammateStatus()}/>{channelTitle} </span>
- <span className='fa fa-chevron-down header-dropdown__icon'/>
- </a>
- <ul
- className='dropdown-menu'
- role='menu'
- >
- {viewInfoOption}
- {viewPinnedPostsOption}
- {notificationPreferenceOption}
- {addMembersOption}
- {manageMembersOption}
- {setChannelHeaderOption}
- {setChannelPurposeOption}
- {renameChannelOption}
- {deleteChannelOption}
- {leaveChannelOption}
- {toggleFavoriteOption}
- <div
- className='close visible-xs-block'
- onClick={() => this.refs.headerOverlay.hide()}
- >
- {'×'}
- </div>
- </ul>
- </div>
- </div>
- );
- }
-
- return (
- <div className='navbar-brand'>
- <Link
- to={TeamStore.getCurrentTeamUrl() + '/channels/town-square'}
- className='heading'
- >
- {channelTitle}
- </Link>
- </div>
- );
- }
-
- createCollapseButtons(currentId) {
- var buttons = [];
- const menuIcon = Constants.MENU_ICON_SVG;
-
- if (currentId == null) {
- buttons.push(
- <button
- key='navbar-toggle-collapse'
- type='button'
- className='navbar-toggle'
- data-toggle='collapse'
- data-target='#navbar-collapse-1'
- >
- <span className='sr-only'>
- <FormattedMessage
- id='navbar.toggle1'
- defaultMessage='Toggle sidebar'
- />
- </span>
- <span className='icon-bar'/>
- <span className='icon-bar'/>
- <span className='icon-bar'/>
- </button>
- );
- } else {
- buttons.push(
- <button
- key='navbar-toggle-sidebar'
- type='button'
- className='navbar-toggle'
- data-toggle='collapse'
- data-target='#sidebar-nav'
- onClick={this.toggleLeftSidebar}
- >
- <span className='sr-only'>
- <FormattedMessage
- id='navbar.toggle2'
- defaultMessage='Toggle sidebar'
- />
- </span>
- <span
- className='icon icon__menu icon--sidebarHeaderTextColor'
- dangerouslySetInnerHTML={{__html: menuIcon}}
- aria-hidden='true'
- />
- <NotifyCounts/>
- </button>
- );
-
- buttons.push(
- <button
- key='navbar-toggle-menu'
- type='button'
- className='navbar-toggle navbar-right__icon menu-toggle pull-right'
- data-toggle='collapse'
- data-target='#sidebar-nav'
- onClick={this.toggleRightSidebar}
- >
- <span dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}}/>
- </button>
- );
- }
-
- return buttons;
- }
-
- getTeammateStatus() {
- const channel = this.state.channel;
-
- // get status for direct message channels
- if (channel.type === 'D') {
- const currentUserId = this.state.currentUser.id;
- const teammate = this.state.users.find((user) => user.id !== currentUserId);
- if (teammate) {
- return UserStore.getStatus(teammate.id);
- }
- }
- return null;
- }
-
- render() {
- if (!this.isStateValid()) {
- return null;
- }
-
- var currentId = this.state.currentUser.id;
- var channel = this.state.channel;
- var channelTitle = this.props.teamDisplayName;
- var popoverContent;
- var isTeamAdmin = TeamStore.isTeamAdminForCurrentTeam();
- var isSystemAdmin = UserStore.isSystemAdminForCurrentUser();
- var isChannelAdmin = false;
- var isDirect = false;
- let isGroup = false;
-
- var editChannelHeaderModal = null;
- var editChannelPurposeModal = null;
- let renameChannelModal = null;
- let channelMembersModal = null;
- let quickSwitchModal = null;
-
- if (channel) {
- popoverContent = (
- <Popover
- bsStyle='info'
- placement='bottom'
- id='header-popover'
- >
- <MessageWrapper
- message={channel.header}
- options={{singleline: true, mentionHighlight: false}}
- />
- <div
- className='close visible-xs-block'
- onClick={() => this.refs.headerOverlay.hide()}
- >
- {'×'}
- </div>
- </Popover>
- );
-
- isChannelAdmin = ChannelStore.isChannelAdminForCurrentChannel();
-
- if (channel.type === 'O') {
- channelTitle = channel.display_name;
- } else if (channel.type === 'P') {
- channelTitle = channel.display_name;
- } else if (channel.type === 'D') {
- isDirect = true;
- const teammateId = Utils.getUserIdFromChannelName(channel);
- channelTitle = Utils.displayUsername(teammateId);
- } else if (channel.type === Constants.GM_CHANNEL) {
- isGroup = true;
- channelTitle = ChannelUtils.buildGroupChannelName(channel.id);
- }
-
- if (channel.header.length === 0) {
- const link = (
- <a
- href='#'
- onClick={this.showEditChannelHeaderModal}
- >
- <FormattedMessage
- id='navbar.click'
- defaultMessage='Click here'
- />
- </a>
- );
- popoverContent = (
- <Popover
- bsStyle='info'
- placement='bottom'
- id='header-popover'
- >
- <div>
- <FormattedMessage
- id='navbar.noHeader'
- defaultMessage='No channel header yet.{newline}{link} to add one.'
- values={{
- newline: (<br/>),
- link
- }}
- />
- </div>
- <div
- className='close visible-xs-block'
- onClick={() => this.refs.headerOverlay.hide()}
- >
- {'×'}
- </div>
- </Popover>
- );
- }
-
- if (this.state.showEditChannelHeaderModal) {
- editChannelHeaderModal = (
- <EditChannelHeaderModal
- onHide={() => this.setState({showEditChannelHeaderModal: false})}
- channel={channel}
- />
- );
- }
-
- if (this.state.showEditChannelPurposeModal) {
- editChannelPurposeModal = (
- <EditChannelPurposeModal
- onModalDismissed={this.hideChannelPurposeModal}
- channel={channel}
- />
- );
- }
-
- renameChannelModal = (
- <RenameChannelModal
- show={this.state.showRenameChannelModal}
- onHide={this.hideRenameChannelModal}
- channel={channel}
- />
- );
-
- if (this.state.showMembersModal) {
- channelMembersModal = (
- <ChannelMembersModal
- show={true}
- onModalDismissed={this.hideMembersModal}
- showInviteModal={() => this.refs.channelInviteModalButton.show()}
- channel={channel}
- isAdmin={isTeamAdmin || isSystemAdmin}
- />
- );
- }
-
- quickSwitchModal = (
- <QuickSwitchModal
- show={this.state.showQuickSwitchModal}
- onHide={this.hideQuickSwitchModal}
- initialMode={this.state.quickSwitchMode}
- />
- );
- }
-
- var collapseButtons = this.createCollapseButtons(currentId);
-
- const searchIcon = Constants.SEARCH_ICON_SVG;
- const searchButton = (
- <button
- type='button'
- className='navbar-toggle navbar-right__icon navbar-search pull-right'
- onClick={this.showSearch}
- >
- <span
- className='icon icon__search'
- dangerouslySetInnerHTML={{__html: searchIcon}}
- aria-hidden='true'
- />
- </button>
- );
-
- var channelMenuDropdown = this.createDropdown(channel, channelTitle, isSystemAdmin, isTeamAdmin, isChannelAdmin, isDirect, isGroup, popoverContent);
-
- return (
- <div>
- <nav
- className='navbar navbar-default navbar-fixed-top'
- role='navigation'
- >
- <div className='container-fluid theme'>
- <div className='navbar-header'>
- {collapseButtons}
- {searchButton}
- {channelMenuDropdown}
- </div>
- </div>
- </nav>
- {editChannelHeaderModal}
- {editChannelPurposeModal}
- {renameChannelModal}
- {channelMembersModal}
- {quickSwitchModal}
- </div>
- );
- }
-}
-
-Navbar.defaultProps = {
- teamDisplayName: ''
-};
-Navbar.propTypes = {
- teamDisplayName: PropTypes.string
-};
diff --git a/webapp/components/needs_team/index.js b/webapp/components/needs_team/index.js
deleted file mode 100644
index ba809fd10..000000000
--- a/webapp/components/needs_team/index.js
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {viewChannel, getMyChannelMembers} from 'mattermost-redux/actions/channels';
-
-import NeedsTeam from './needs_team.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- viewChannel,
- getMyChannelMembers
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(NeedsTeam);
diff --git a/webapp/components/needs_team/needs_team.jsx b/webapp/components/needs_team/needs_team.jsx
deleted file mode 100644
index 349b9ae67..000000000
--- a/webapp/components/needs_team/needs_team.jsx
+++ /dev/null
@@ -1,242 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import $ from 'jquery';
-
-import {browserHistory} from 'react-router/es6';
-import * as Utils from 'utils/utils.jsx';
-import TeamStore from 'stores/team_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-import PreferenceStore from 'stores/preference_store.jsx';
-import ChannelStore from 'stores/channel_store.jsx';
-import PostStore from 'stores/post_store.jsx';
-import {startPeriodicStatusUpdates, stopPeriodicStatusUpdates} from 'actions/status_actions.jsx';
-import {startPeriodicSync, stopPeriodicSync} from 'actions/websocket_actions.jsx';
-import {loadProfilesForSidebar} from 'actions/user_actions.jsx';
-
-import Constants from 'utils/constants.jsx';
-const TutorialSteps = Constants.TutorialSteps;
-const Preferences = Constants.Preferences;
-
-import AnnouncementBar from 'components/announcement_bar';
-import SidebarRight from 'components/sidebar_right';
-import SidebarRightMenu from 'components/sidebar_right_menu.jsx';
-import Navbar from 'components/navbar.jsx';
-import WebrtcSidebar from 'components/webrtc/components/webrtc_sidebar.jsx';
-
-import WebrtcNotification from 'components/webrtc/components/webrtc_notification.jsx';
-
-import store from 'stores/redux_store.jsx';
-import {getPost} from 'mattermost-redux/selectors/entities/posts';
-
-// Modals
-import UserSettingsModal from 'components/user_settings/user_settings_modal.jsx';
-import GetPostLinkModal from 'components/get_post_link_modal.jsx';
-import GetPublicLinkModal from 'components/get_public_link_modal.jsx';
-import GetTeamInviteLinkModal from 'components/get_team_invite_link_modal.jsx';
-import EditPostModal from 'components/edit_post_modal.jsx';
-import DeletePostModal from 'components/delete_post_modal.jsx';
-import TeamSettingsModal from 'components/team_settings_modal.jsx';
-import RemovedFromChannelModal from 'components/removed_from_channel_modal.jsx';
-import ImportThemeModal from 'components/user_settings/import_theme_modal.jsx';
-import InviteMemberModal from 'components/invite_member_modal.jsx';
-import LeaveTeamModal from 'components/leave_team_modal.jsx';
-import ResetStatusModal from 'components/reset_status_modal';
-import LeavePrivateChannelModal from 'components/modals/leave_private_channel_modal.jsx';
-import ShortcutsModal from 'components/shortcuts_modal.jsx';
-
-import iNoBounce from 'inobounce';
-import * as UserAgent from 'utils/user_agent.jsx';
-
-const UNREAD_CHECK_TIME_MILLISECONDS = 10000;
-
-export default class NeedsTeam extends React.Component {
- static propTypes = {
- children: PropTypes.oneOfType([
- PropTypes.arrayOf(PropTypes.element),
- PropTypes.element
- ]),
- navbar: PropTypes.element,
- sidebar: PropTypes.element,
- team_sidebar: PropTypes.element,
- center: PropTypes.element,
- params: PropTypes.object,
- user: PropTypes.object,
- actions: PropTypes.shape({
- viewChannel: PropTypes.func.isRequired,
- getMyChannelMembers: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(params) {
- super(params);
-
- this.onTeamChanged = this.onTeamChanged.bind(this);
- this.onPreferencesChanged = this.onPreferencesChanged.bind(this);
-
- this.blurTime = new Date().getTime();
-
- const team = TeamStore.getCurrent();
-
- this.state = {
- team,
- theme: PreferenceStore.getTheme(team.id)
- };
- }
-
- onTeamChanged() {
- const team = TeamStore.getCurrent();
-
- this.setState({
- team,
- theme: PreferenceStore.getTheme(team.id)
- });
- }
-
- onPreferencesChanged(category) {
- if (!category || category === Preferences.CATEGORY_THEME) {
- this.setState({
- theme: PreferenceStore.getTheme(this.state.team.id)
- });
- }
- }
-
- componentWillMount() {
- // Go to tutorial if we are first arriving
- const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999);
- if (tutorialStep <= TutorialSteps.INTRO_SCREENS) {
- browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/tutorial');
- }
- }
-
- componentDidMount() {
- TeamStore.addChangeListener(this.onTeamChanged);
- PreferenceStore.addChangeListener(this.onPreferencesChanged);
-
- startPeriodicStatusUpdates();
- startPeriodicSync();
-
- // Set up tracking for whether the window is active
- window.isActive = true;
- $(window).on('focus', async () => {
- ChannelStore.resetCounts([ChannelStore.getCurrentId()]);
- ChannelStore.emitChange();
- window.isActive = true;
-
- await this.props.actions.viewChannel(ChannelStore.getCurrentId());
- if (new Date().getTime() - this.blurTime > UNREAD_CHECK_TIME_MILLISECONDS) {
- this.props.actions.getMyChannelMembers(TeamStore.getCurrentId()).then(loadProfilesForSidebar);
- }
- });
-
- $(window).on('blur', () => {
- window.isActive = false;
- this.blurTime = new Date().getTime();
- if (UserStore.getCurrentUser()) {
- this.props.actions.viewChannel('');
- }
- });
-
- Utils.applyTheme(this.state.theme);
-
- if (UserAgent.isIosSafari()) {
- // Use iNoBounce to prevent scrolling past the boundaries of the page
- iNoBounce.enable();
- }
- }
-
- componentDidUpdate(prevProps, prevState) {
- if (!Utils.areObjectsEqual(prevState.theme, this.state.theme)) {
- Utils.applyTheme(this.state.theme);
- }
- }
-
- componentWillUnmount() {
- TeamStore.removeChangeListener(this.onTeamChanged);
- PreferenceStore.removeChangeListener(this.onPreferencesChanged);
- $(window).off('focus');
- $(window).off('blur');
-
- if (UserAgent.isIosSafari()) {
- iNoBounce.disable();
- }
- stopPeriodicStatusUpdates();
- stopPeriodicSync();
- }
-
- render() {
- let content = [];
- if (this.props.children) {
- content = this.props.children;
- } else {
- content.push(
- this.props.navbar
- );
- content.push(this.props.team_sidebar);
- content.push(
- this.props.sidebar
- );
- content.push(
- <div
- key='inner-wrap'
- className='inner-wrap channel__wrap'
- >
- <div className='row header'>
- <div id='navbar'>
- <Navbar/>
- </div>
- </div>
- <div className='row main'>
- {React.cloneElement(this.props.center, {
- user: this.props.user,
- team: this.state.team
- })}
- </div>
- </div>
- );
- }
-
- let channel = ChannelStore.getByName(this.props.params.channel);
- if (channel == null) {
- // the permalink view is not really tied to a particular channel but still needs it
- const postId = PostStore.getFocusedPostId();
- const post = getPost(store.getState(), postId);
-
- // the post take some time before being available on page load
- if (post != null) {
- channel = ChannelStore.get(post.channel_id);
- }
- }
-
- return (
- <div className='channel-view'>
- <AnnouncementBar/>
- <WebrtcNotification/>
- <div className='container-fluid'>
- <SidebarRight channel={channel}/>
- <SidebarRightMenu teamType={this.state.team.type}/>
- <WebrtcSidebar/>
- {content}
-
- <UserSettingsModal/>
- <GetPostLinkModal/>
- <GetPublicLinkModal/>
- <GetTeamInviteLinkModal/>
- <InviteMemberModal/>
- <LeaveTeamModal/>
- <ImportThemeModal/>
- <TeamSettingsModal/>
- <EditPostModal/>
- <DeletePostModal/>
- <RemovedFromChannelModal/>
- <ResetStatusModal/>
- <LeavePrivateChannelModal/>
- <ShortcutsModal isMac={Utils.isMac()}/>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/new_channel_flow.jsx b/webapp/components/new_channel_flow.jsx
deleted file mode 100644
index 1a5c77132..000000000
--- a/webapp/components/new_channel_flow.jsx
+++ /dev/null
@@ -1,247 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import * as Utils from 'utils/utils.jsx';
-import TeamStore from 'stores/team_store.jsx';
-import {cleanUpUrlable} from 'utils/url.jsx';
-
-import NewChannelModal from 'components/new_channel_modal';
-import ChangeURLModal from './change_url_modal.jsx';
-
-import {FormattedMessage} from 'react-intl';
-import {createChannel} from 'actions/channel_actions.jsx';
-import {browserHistory} from 'react-router/es6';
-
-const SHOW_NEW_CHANNEL = 1;
-const SHOW_EDIT_URL = 2;
-const SHOW_EDIT_URL_THEN_COMPLETE = 3;
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class NewChannelFlow extends React.Component {
- constructor(props) {
- super(props);
-
- this.doSubmit = this.doSubmit.bind(this);
- this.onModalExited = this.onModalExited.bind(this);
- this.typeSwitched = this.typeSwitched.bind(this);
- this.urlChangeRequested = this.urlChangeRequested.bind(this);
- this.urlChangeSubmitted = this.urlChangeSubmitted.bind(this);
- this.urlChangeDismissed = this.urlChangeDismissed.bind(this);
- this.channelDataChanged = this.channelDataChanged.bind(this);
-
- this.state = {
- serverError: '',
- channelType: 'O',
- flowState: SHOW_NEW_CHANNEL,
- channelDisplayName: '',
- channelName: '',
- channelPurpose: '',
- channelHeader: '',
- nameModified: false
- };
- }
- componentWillReceiveProps(nextProps) {
- // If we are being shown, grab channel type from props and clear
- if (nextProps.show === true && this.props.show === false) {
- this.setState({
- serverError: '',
- channelType: nextProps.channelType,
- flowState: SHOW_NEW_CHANNEL,
- channelDisplayName: '',
- channelName: '',
- channelPurpose: '',
- channelHeader: '',
- nameModified: false
- });
- }
- }
- doSubmit() {
- if (!this.state.channelDisplayName) {
- this.setState({serverError: Utils.localizeMessage('channel_flow.invalidName', 'Invalid Channel Name')});
- return;
- }
-
- if (this.state.channelName < 2) {
- this.setState({flowState: SHOW_EDIT_URL_THEN_COMPLETE});
- return;
- }
-
- const channel = {
- team_id: TeamStore.getCurrentId(),
- name: this.state.channelName,
- display_name: this.state.channelDisplayName,
- purpose: this.state.channelPurpose,
- header: this.state.channelHeader,
- type: this.state.channelType
- };
-
- createChannel(
- channel,
- (data) => {
- this.doOnModalExited = () => {
- browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + data.name);
- };
-
- this.props.onModalDismissed();
- },
- (err) => {
- if (err.id === 'model.channel.is_valid.2_or_more.app_error') {
- this.setState({
- flowState: SHOW_EDIT_URL_THEN_COMPLETE,
- serverError: (
- <FormattedMessage
- id='channel_flow.handleTooShort'
- defaultMessage='Channel URL must be 2 or more lowercase alphanumeric characters'
- />
- )
- });
- return;
- }
- if (err.id === 'store.sql_channel.update.exists.app_error') {
- this.setState({serverError: Utils.localizeMessage('channel_flow.alreadyExist', 'A channel with that URL already exists')});
- return;
- }
- this.setState({serverError: err.message});
- }
- );
- }
- onModalExited() {
- if (this.doOnModalExited) {
- this.doOnModalExited();
- }
- }
- typeSwitched() {
- if (this.state.channelType === 'P') {
- this.setState({channelType: 'O'});
- } else {
- this.setState({channelType: 'P'});
- }
- }
- urlChangeRequested() {
- this.setState({flowState: SHOW_EDIT_URL});
- }
- urlChangeSubmitted(newURL) {
- if (this.state.flowState === SHOW_EDIT_URL_THEN_COMPLETE) {
- this.setState({channelName: newURL, nameModified: true}, this.doSubmit);
- } else {
- this.setState({flowState: SHOW_NEW_CHANNEL, serverError: null, channelName: newURL, nameModified: true});
- }
- }
- urlChangeDismissed() {
- this.setState({flowState: SHOW_NEW_CHANNEL});
- }
- channelDataChanged(data) {
- this.setState({
- channelDisplayName: data.displayName,
- channelPurpose: data.purpose,
- channelHeader: data.header
- });
- if (!this.state.nameModified) {
- this.setState({channelName: cleanUpUrlable(data.displayName.trim())});
- }
- }
- render() {
- const channelData = {
- name: this.state.channelName,
- displayName: this.state.channelDisplayName,
- purpose: this.state.channelPurpose
- };
-
- let showChannelModal = false;
- let showGroupModal = false;
- let showChangeURLModal = false;
-
- let changeURLTitle = '';
- let changeURLSubmitButtonText = '';
-
- // Only listen to flow state if we are being shown
- if (this.props.show) {
- switch (this.state.flowState) {
- case SHOW_NEW_CHANNEL:
- if (this.state.channelType === 'O') {
- showChannelModal = true;
- } else {
- showGroupModal = true;
- }
- break;
- case SHOW_EDIT_URL:
- showChangeURLModal = true;
- changeURLTitle = (
- <FormattedMessage
- id='channel_flow.changeUrlTitle'
- defaultMessage='Change Channel URL'
- />
- );
- changeURLSubmitButtonText = changeURLTitle;
- break;
- case SHOW_EDIT_URL_THEN_COMPLETE:
- showChangeURLModal = true;
- changeURLTitle = (
- <FormattedMessage
- id='channel_flow.set_url_title'
- defaultMessage='Set Channel URL'
- />
- );
- changeURLSubmitButtonText = (
- <FormattedMessage
- id='channel_flow.create'
- defaultMessage='Create Channel'
- />
- );
- break;
- }
- }
- return (
- <span>
- <NewChannelModal
- show={showChannelModal}
- channelType={'O'}
- channelData={channelData}
- serverError={this.state.serverError}
- onSubmitChannel={this.doSubmit}
- onModalDismissed={this.props.onModalDismissed}
- onModalExited={this.onModalExited}
- onTypeSwitched={this.typeSwitched}
- onChangeURLPressed={this.urlChangeRequested}
- onDataChanged={this.channelDataChanged}
- />
- <NewChannelModal
- show={showGroupModal}
- channelType={'P'}
- channelData={channelData}
- serverError={this.state.serverError}
- onSubmitChannel={this.doSubmit}
- onModalExited={this.onModalExited}
- onModalDismissed={this.props.onModalDismissed}
- onTypeSwitched={this.typeSwitched}
- onChangeURLPressed={this.urlChangeRequested}
- onDataChanged={this.channelDataChanged}
- />
- <ChangeURLModal
- show={showChangeURLModal}
- title={changeURLTitle}
- submitButtonText={changeURLSubmitButtonText}
- currentURL={this.state.channelName}
- serverError={this.state.serverError}
- onModalSubmit={this.urlChangeSubmitted}
- onModalDismissed={this.urlChangeDismissed}
- onModalExited={this.onModalExited}
- />
- </span>
- );
- }
-}
-
-NewChannelFlow.defaultProps = {
- show: false,
- channelType: 'O'
-};
-
-NewChannelFlow.propTypes = {
- show: PropTypes.bool.isRequired,
- channelType: PropTypes.string.isRequired,
- onModalDismissed: PropTypes.func.isRequired
-};
diff --git a/webapp/components/new_channel_modal/index.js b/webapp/components/new_channel_modal/index.js
deleted file mode 100644
index 770084fbb..000000000
--- a/webapp/components/new_channel_modal/index.js
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {getBool} from 'mattermost-redux/selectors/entities/preferences';
-import {isCurrentUserSystemAdmin} from 'mattermost-redux/selectors/entities/users';
-import {isCurrentUserCurrentTeamAdmin} from 'mattermost-redux/selectors/entities/teams';
-import {Preferences} from 'mattermost-redux/constants';
-
-import NewChannelModal from './new_channel_modal.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps,
- ctrlSend: getBool(state, Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter'),
- isTeamAdmin: isCurrentUserCurrentTeamAdmin(state),
- isSystemAdmin: isCurrentUserSystemAdmin(state)
- };
-}
-
-export default connect(mapStateToProps)(NewChannelModal);
diff --git a/webapp/components/new_channel_modal/new_channel_modal.jsx b/webapp/components/new_channel_modal/new_channel_modal.jsx
deleted file mode 100644
index 721defe08..000000000
--- a/webapp/components/new_channel_modal/new_channel_modal.jsx
+++ /dev/null
@@ -1,395 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import ReactDOM from 'react-dom';
-
-import {getShortenedURL} from 'utils/url.jsx';
-import * as UserAgent from 'utils/user_agent.jsx';
-import * as Utils from 'utils/utils.jsx';
-import * as ChannelUtils from 'utils/channel_utils.jsx';
-import Constants from 'utils/constants.jsx';
-
-import {FormattedMessage} from 'react-intl';
-
-import {Modal} from 'react-bootstrap';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-export default class NewChannelModal extends React.PureComponent {
- static propTypes = {
-
- /**
- * Set whether to show the modal or not
- */
- show: PropTypes.bool.isRequired,
-
- /**
- * The type of channel to create, 'O' or 'P'
- */
- channelType: PropTypes.string.isRequired,
-
- /**
- * The data needed to create the channel
- */
- channelData: PropTypes.object.isRequired,
-
- /**
- * Set to force form submission on CTRL/CMD + ENTER instead of ENTER
- */
- ctrlSend: PropTypes.bool,
-
- /**
- * Set to show options available to team admins
- */
- isTeamAdmin: PropTypes.bool,
-
- /**
- * Set to show options available to system admins
- */
- isSystemAdmin: PropTypes.bool,
-
- /**
- * Server error from failed channel creation
- */
- serverError: PropTypes.node,
-
- /**
- * Function used to submit the channel
- */
- onSubmitChannel: PropTypes.func.isRequired,
-
- /**
- * Function to call when modal is dimissed
- */
- onModalDismissed: PropTypes.func.isRequired,
-
- /**
- * Function to call when modal has exited
- */
- onModalExited: PropTypes.func,
-
- /**
- * Function to call to switch channel type
- */
- onTypeSwitched: PropTypes.func.isRequired,
-
- /**
- * Function to call when edit URL button is pressed
- */
- onChangeURLPressed: PropTypes.func.isRequired,
-
- /**
- * Function to call when channel data is modified
- */
- onDataChanged: PropTypes.func.isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.handleSubmit = this.handleSubmit.bind(this);
- this.handleChange = this.handleChange.bind(this);
- this.onEnterKeyDown = this.onEnterKeyDown.bind(this);
-
- this.state = {
- displayNameError: ''
- };
- }
-
- componentWillReceiveProps(nextProps) {
- if (nextProps.show === true && this.props.show === false) {
- this.setState({
- displayNameError: ''
- });
-
- document.addEventListener('keydown', this.onEnterKeyDown);
- } else if (nextProps.show === false && this.props.show === true) {
- document.removeEventListener('keydown', this.onEnterKeyDown);
- }
- }
-
- componentDidMount() {
- // ???
- if (UserAgent.isInternetExplorer() || UserAgent.isEdge()) {
- $('body').addClass('browser--ie');
- }
- }
-
- onEnterKeyDown(e) {
- if (this.props.ctrlSend && e.keyCode === Constants.KeyCodes.ENTER && e.ctrlKey) {
- this.handleSubmit(e);
- } else if (!this.props.ctrlSend && e.keyCode === Constants.KeyCodes.ENTER && !e.shiftKey && !e.altKey) {
- this.handleSubmit(e);
- }
- }
-
- handleSubmit(e) {
- e.preventDefault();
-
- const displayName = ReactDOM.findDOMNode(this.refs.display_name).value.trim();
- if (displayName.length < Constants.MIN_CHANNELNAME_LENGTH) {
- this.setState({displayNameError: true});
- return;
- }
-
- this.props.onSubmitChannel();
- }
-
- handleChange() {
- const newData = {
- displayName: this.refs.display_name.value,
- header: this.refs.channel_header.value,
- purpose: this.refs.channel_purpose.value
- };
- this.props.onDataChanged(newData);
- }
-
- render() {
- var displayNameError = null;
- var serverError = null;
- var displayNameClass = 'form-group';
-
- if (this.state.displayNameError) {
- displayNameError = (
- <p className='input__help error'>
- <FormattedMessage
- id='channel_modal.displayNameError'
- defaultMessage='Channel name must be 2 or more characters'
- />
- {this.state.displayNameError}
- </p>
- );
- displayNameClass += ' has-error';
- }
-
- if (this.props.serverError) {
- serverError = <div className='form-group has-error'><div className='col-sm-12'><p className='input__help error'>{this.props.serverError}</p></div></div>;
- }
-
- let createPublicChannelLink = (
- <a
- href='#'
- onClick={this.props.onTypeSwitched}
- >
- <FormattedMessage
- id='channel_modal.publicChannel1'
- defaultMessage='Create a public channel'
- />
- </a>
- );
-
- let createPrivateChannelLink = (
- <a
- href='#'
- onClick={this.props.onTypeSwitched}
- >
- <FormattedMessage
- id='channel_modal.privateGroup2'
- defaultMessage='Create a private channel'
- />
- </a>
- );
-
- if (!ChannelUtils.showCreateOption(Constants.OPEN_CHANNEL, this.props.isTeamAdmin, this.props.isSystemAdmin)) {
- createPublicChannelLink = null;
- }
-
- if (!ChannelUtils.showCreateOption(Constants.PRIVATE_CHANNEL, this.props.isTeamAdmin, this.props.isSystemAdmin)) {
- createPrivateChannelLink = null;
- }
-
- var channelSwitchText = '';
- let inputPrefixId = '';
- switch (this.props.channelType) {
- case 'P':
- channelSwitchText = (
- <div className='modal-intro'>
- <FormattedMessage
- id='channel_modal.privateGroup1'
- defaultMessage='Create a new private channel with restricted membership. '
- />
- {createPublicChannelLink}
- </div>
- );
- inputPrefixId = 'newPrivateChannel';
- break;
- case 'O':
- channelSwitchText = (
- <div className='modal-intro'>
- <FormattedMessage
- id='channel_modal.publicChannel2'
- defaultMessage='Create a new public channel anyone can join. '
- />
- {createPrivateChannelLink}
- </div>
- );
- inputPrefixId = 'newPublicChannel';
- break;
- }
-
- const prettyTeamURL = getShortenedURL();
-
- return (
- <span>
- <Modal
- dialogClassName='new-channel__modal'
- show={this.props.show}
- bsSize='large'
- onHide={this.props.onModalDismissed}
- onExited={this.props.onModalExited}
- >
- <Modal.Header closeButton={true}>
- <Modal.Title>
- <FormattedMessage
- id='channel_modal.modalTitle'
- defaultMessage='New Channel'
- />
- </Modal.Title>
- </Modal.Header>
- <form
- role='form'
- className='form-horizontal'
- >
- <Modal.Body>
- <div>
- {channelSwitchText}
- </div>
- <div className={displayNameClass}>
- <label className='col-sm-3 form__label control-label'>
- <FormattedMessage
- id='channel_modal.name'
- defaultMessage='Name'
- />
- </label>
- <div className='col-sm-9'>
- <input
- id={inputPrefixId + 'Name'}
- onChange={this.handleChange}
- type='text'
- ref='display_name'
- className='form-control'
- placeholder={Utils.localizeMessage('channel_modal.nameEx', 'E.g.: "Bugs", "Marketing", "客户支持"')}
- maxLength={Constants.MAX_CHANNELNAME_LENGTH}
- value={this.props.channelData.displayName}
- autoFocus={true}
- tabIndex='1'
- />
- {displayNameError}
- <p className='input__help dark'>
- {'URL: ' + prettyTeamURL + this.props.channelData.name + ' ('}
- <a
- href='#'
- onClick={this.props.onChangeURLPressed}
- >
- <FormattedMessage
- id='channel_modal.edit'
- defaultMessage='Edit'
- />
- </a>
- {')'}
- </p>
- </div>
- </div>
- <div className='form-group'>
- <div className='col-sm-3'>
- <label className='form__label control-label'>
- <FormattedMessage
- id='channel_modal.purpose'
- defaultMessage='Purpose'
- />
- </label>
- <label className='form__label light'>
- <FormattedMessage
- id='channel_modal.optional'
- defaultMessage='(optional)'
- />
- </label>
- </div>
- <div className='col-sm-9'>
- <textarea
- id={inputPrefixId + 'Purpose'}
- className='form-control no-resize'
- ref='channel_purpose'
- rows='4'
- placeholder={Utils.localizeMessage('channel_modal.purposeEx', 'E.g.: "A channel to file bugs and improvements"')}
- maxLength='250'
- value={this.props.channelData.purpose}
- onChange={this.handleChange}
- tabIndex='2'
- />
- <p className='input__help'>
- <FormattedMessage
- id='channel_modal.descriptionHelp'
- defaultMessage='Describe how this channel should be used.'
- />
- </p>
- </div>
- </div>
- <div className='form-group less'>
- <div className='col-sm-3'>
- <label className='form__label control-label'>
- <FormattedMessage
- id='channel_modal.header'
- defaultMessage='Header'
- />
- </label>
- <label className='form__label light'>
- <FormattedMessage
- id='channel_modal.optional'
- defaultMessage='(optional)'
- />
- </label>
- </div>
- <div className='col-sm-9'>
- <textarea
- id={inputPrefixId + 'Header'}
- className='form-control no-resize'
- ref='channel_header'
- rows='4'
- placeholder={Utils.localizeMessage('channel_modal.headerEx', 'E.g.: "[Link Title](http://example.com)"')}
- maxLength='1024'
- value={this.props.channelData.header}
- onChange={this.handleChange}
- tabIndex='2'
- />
- <p className='input__help'>
- <FormattedMessage
- id='channel_modal.headerHelp'
- defaultMessage='Set text that will appear in the header of the channel beside the channel name. For example, include frequently used links by typing [Link Title](http://example.com).'
- />
- </p>
- {serverError}
- </div>
- </div>
- </Modal.Body>
- <Modal.Footer>
- <button
- type='button'
- className='btn btn-default'
- onClick={this.props.onModalDismissed}
- >
- <FormattedMessage
- id='channel_modal.cancel'
- defaultMessage='Cancel'
- />
- </button>
- <button
- onClick={this.handleSubmit}
- type='submit'
- className='btn btn-primary'
- tabIndex='3'
- >
- <FormattedMessage
- id='channel_modal.createNew'
- defaultMessage='Create New Channel'
- />
- </button>
- </Modal.Footer>
- </form>
- </Modal>
- </span>
- );
- }
-}
diff --git a/webapp/components/notify_counts.jsx b/webapp/components/notify_counts.jsx
deleted file mode 100644
index f05ecbf12..000000000
--- a/webapp/components/notify_counts.jsx
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import * as utils from 'utils/utils.jsx';
-import {getCountsStateFromStores} from 'utils/channel_utils.jsx';
-import ChannelStore from 'stores/channel_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-
-import React from 'react';
-
-export default class NotifyCounts extends React.Component {
- constructor(props) {
- super(props);
-
- this.onListenerChange = this.onListenerChange.bind(this);
-
- this.state = getCountsStateFromStores();
- this.mounted = false;
- }
- componentDidMount() {
- this.mounted = true;
- ChannelStore.addChangeListener(this.onListenerChange);
- TeamStore.addChangeListener(this.onListenerChange);
- }
- componentWillUnmount() {
- this.mounted = false;
- ChannelStore.removeChangeListener(this.onListenerChange);
- TeamStore.removeChangeListener(this.onListenerChange);
- }
- onListenerChange() {
- if (this.mounted) {
- var newState = getCountsStateFromStores();
- if (!utils.areObjectsEqual(newState, this.state)) {
- this.setState(newState);
- }
- }
- }
- render() {
- if (this.state.mentionCount) {
- return <span className='badge badge-notify'>{this.state.mentionCount}</span>;
- } else if (this.state.messageCount) {
- return <span className='badge badge-notify'>{'•'}</span>;
- }
- return null;
- }
-}
diff --git a/webapp/components/password_reset_form.jsx b/webapp/components/password_reset_form.jsx
deleted file mode 100644
index 5b77cb3e6..000000000
--- a/webapp/components/password_reset_form.jsx
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ReactDOM from 'react-dom';
-import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-
-import {FormattedMessage} from 'react-intl';
-
-import {resetPassword} from 'actions/user_actions.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-class PasswordResetForm extends React.Component {
- constructor(props) {
- super(props);
-
- this.handlePasswordReset = this.handlePasswordReset.bind(this);
-
- this.state = {};
- }
- handlePasswordReset(e) {
- e.preventDefault();
-
- const password = ReactDOM.findDOMNode(this.refs.password).value;
- if (!password || password.length < Constants.MIN_PASSWORD_LENGTH) {
- this.setState({
- error: (
- <FormattedMessage
- id='password_form.error'
- defaultMessage='Please enter at least {chars} characters.'
- values={{
- chars: Constants.MIN_PASSWORD_LENGTH
- }}
- />
- )
- });
- return;
- }
-
- this.setState({
- error: null
- });
-
- resetPassword(
- this.props.location.query.token,
- password,
- () => {
- this.setState({error: null});
- },
- (err) => {
- this.setState({error: err.message});
- }
- );
- }
- render() {
- var error = null;
- if (this.state.error) {
- error = (
- <div className='form-group has-error'>
- <label className='control-label'>
- {this.state.error}
- </label>
- </div>
- );
- }
-
- var formClass = 'form-group';
- if (error) {
- formClass += ' has-error';
- }
-
- return (
- <div className='col-sm-12'>
- <div className='signup-team__container'>
- <h3>
- <FormattedMessage
- id='password_form.title'
- defaultMessage='Password Reset'
- />
- </h3>
- <form onSubmit={this.handlePasswordReset}>
- <p>
- <FormattedMessage
- id='password_form.enter'
- defaultMessage='Enter a new password for your {siteName} account.'
- values={{
- siteName: global.window.mm_config.SiteName
- }}
- />
- </p>
- <div className={formClass}>
- <input
- type='password'
- className='form-control'
- name='password'
- ref='password'
- placeholder={Utils.localizeMessage(
- 'password_form.pwd',
- 'Password'
- )}
- spellCheck='false'
- />
- </div>
- {error}
- <button
- type='submit'
- className='btn btn-primary'
- >
- <FormattedMessage
- id='password_form.change'
- defaultMessage='Change my password'
- />
- </button>
- </form>
- </div>
- </div>
- );
- }
-}
-
-PasswordResetForm.defaultProps = {
-};
-PasswordResetForm.propTypes = {
- params: PropTypes.object.isRequired,
- location: PropTypes.object.isRequired
-};
-
-export default PasswordResetForm;
diff --git a/webapp/components/password_reset_send_link.jsx b/webapp/components/password_reset_send_link.jsx
deleted file mode 100644
index 84edf8e3d..000000000
--- a/webapp/components/password_reset_send_link.jsx
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import ReactDOM from 'react-dom';
-import * as Utils from 'utils/utils.jsx';
-import {sendPasswordResetEmail} from 'actions/user_actions.jsx';
-
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import {Link} from 'react-router/es6';
-
-class PasswordResetSendLink extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleSendLink = this.handleSendLink.bind(this);
-
- this.state = {
- error: '',
- updateText: ''
- };
- }
- handleSendLink(e) {
- e.preventDefault();
-
- var email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase();
- if (!email || !Utils.isEmail(email)) {
- this.setState({
- error: (
- <FormattedMessage
- id={'password_send.error'}
- defaultMessage={'Please enter a valid email address.'}
- />
- )
- });
- return;
- }
-
- // End of error checking clear error
- this.setState({
- error: ''
- });
-
- sendPasswordResetEmail(
- email,
- () => {
- this.setState({
- error: null,
- updateText: (
- <div className='reset-form alert alert-success'>
- <FormattedHTMLMessage
- id='password_send.link'
- defaultMessage='If the account exists, a password reset email will be sent to: <br/><b>{email}</b><br/><br/>'
- values={{
- email
- }}
- />
- <FormattedMessage
- id='password_send.checkInbox'
- defaultMessage='Please check your inbox.'
- />
- </div>
- )
- });
- $(ReactDOM.findDOMNode(this.refs.reset_form)).hide();
- },
- (err) => {
- this.setState({
- error: err.message,
- update_text: null
- });
- }
- );
- }
- render() {
- var error = null;
- if (this.state.error) {
- error = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>;
- }
-
- var formClass = 'form-group';
- if (error) {
- formClass += ' has-error';
- }
-
- return (
- <div>
- <div className='signup-header'>
- <Link to='/'>
- <span className='fa fa-chevron-left'/>
- <FormattedMessage
- id='web.header.back'
- />
- </Link>
- </div>
- <div className='col-sm-12'>
- <div className='signup-team__container'>
- <h3>
- <FormattedMessage
- id='password_send.title'
- defaultMessage='Password Reset'
- />
- </h3>
- {this.state.updateText}
- <form
- onSubmit={this.handleSendLink}
- ref='reset_form'
- >
- <p>
- <FormattedMessage
- id='password_send.description'
- defaultMessage='To reset your password, enter the email address you used to sign up'
- />
- </p>
- <div className={formClass}>
- <input
- type='email'
- className='form-control'
- name='email'
- ref='email'
- placeholder={Utils.localizeMessage(
- 'password_send.email',
- 'Email'
- )}
- spellCheck='false'
- />
- </div>
- {error}
- <button
- type='submit'
- className='btn btn-primary'
- >
- <FormattedMessage
- id='password_send.reset'
- defaultMessage='Reset my password'
- />
- </button>
- </form>
- </div>
- </div>
- </div>
- );
- }
-}
-
-PasswordResetSendLink.defaultProps = {
-};
-PasswordResetSendLink.propTypes = {
- params: PropTypes.object.isRequired
-};
-
-export default PasswordResetSendLink;
diff --git a/webapp/components/pdf_preview.jsx b/webapp/components/pdf_preview.jsx
deleted file mode 100644
index 09913afcf..000000000
--- a/webapp/components/pdf_preview.jsx
+++ /dev/null
@@ -1,178 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import FileInfoPreview from './file_info_preview.jsx';
-
-import loadingGif from 'images/load.gif';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import PDFJS from 'pdfjs-dist';
-import {FormattedMessage} from 'react-intl';
-
-const MAX_PDF_PAGES = 5;
-
-export default class PDFPreview extends React.Component {
- constructor(props) {
- super(props);
-
- this.updateStateFromProps = this.updateStateFromProps.bind(this);
- this.onDocumentLoad = this.onDocumentLoad.bind(this);
- this.onPageLoad = this.onPageLoad.bind(this);
- this.renderPDFPage = this.renderPDFPage.bind(this);
-
- this.pdfPagesRendered = {};
-
- this.state = {
- pdf: null,
- pdfPages: {},
- pdfPagesLoaded: {},
- numPages: 0,
- loading: true,
- success: false
- };
- }
-
- componentDidMount() {
- this.updateStateFromProps(this.props);
- }
-
- componentWillReceiveProps(nextProps) {
- if (this.props.fileUrl !== nextProps.fileUrl) {
- this.updateStateFromProps(nextProps);
- this.pdfPagesRendered = {};
- }
- }
-
- componentDidUpdate() {
- if (this.state.success) {
- for (let i = 0; i < this.state.numPages; i++) {
- this.renderPDFPage(i);
- }
- }
- }
-
- renderPDFPage(pageIndex) {
- if (this.pdfPagesRendered[pageIndex] || !this.state.pdfPagesLoaded[pageIndex]) {
- return;
- }
-
- const canvas = this.refs['pdfCanvas' + pageIndex];
- const context = canvas.getContext('2d');
- const viewport = this.state.pdfPages[pageIndex].getViewport(1);
-
- canvas.height = viewport.height;
- canvas.width = viewport.width;
-
- const renderContext = {
- canvasContext: context,
- viewport
- };
-
- this.state.pdfPages[pageIndex].render(renderContext);
- this.pdfPagesRendered[pageIndex] = true;
- }
-
- updateStateFromProps(props) {
- this.setState({
- pdf: null,
- pdfPages: {},
- pdfPagesLoaded: {},
- numPages: 0,
- loading: true,
- success: false
- });
-
- PDFJS.getDocument(props.fileUrl).then(this.onDocumentLoad);
- }
-
- onDocumentLoad(pdf) {
- const numPages = pdf.numPages <= MAX_PDF_PAGES ? pdf.numPages : MAX_PDF_PAGES;
- this.setState({pdf, numPages});
- for (let i = 1; i <= pdf.numPages; i++) {
- pdf.getPage(i).then(this.onPageLoad);
- }
- }
-
- onPageLoad(page) {
- const pdfPages = Object.assign({}, this.state.pdfPages);
- pdfPages[page.pageIndex] = page;
-
- const pdfPagesLoaded = Object.assign({}, this.state.pdfPagesLoaded);
- pdfPagesLoaded[page.pageIndex] = true;
-
- this.setState({pdfPages, pdfPagesLoaded});
-
- if (page.pageIndex === 0) {
- this.setState({success: true, loading: false});
- }
- }
-
- static supports(fileInfo) {
- return fileInfo.extension === 'pdf';
- }
-
- render() {
- if (this.state.loading) {
- return (
- <div className='view-image__loading'>
- <img
- className='loader-image'
- src={loadingGif}
- />
- </div>
- );
- }
-
- if (!this.state.success) {
- return (
- <FileInfoPreview
- fileInfo={this.props.fileInfo}
- fileUrl={this.props.fileUrl}
- />
- );
- }
-
- const pdfCanvases = [];
- for (let i = 0; i < this.state.numPages; i++) {
- pdfCanvases.push(
- <canvas
- ref={'pdfCanvas' + i}
- key={'previewpdfcanvas' + i}
- />
- );
-
- if (i < this.state.numPages - 1 && this.state.numPages > 1) {
- pdfCanvases.push(
- <div className='pdf-preview-spacer'/>
- );
- }
- }
-
- if (this.state.pdf.numPages > MAX_PDF_PAGES) {
- pdfCanvases.push(
- <a
- href={this.props.fileUrl}
- className='pdf-max-pages'
- >
- <FormattedMessage
- id='pdf_preview.max_pages'
- defaultMessage='Download to read more pages'
- />
- </a>
- );
- }
-
- return (
- <div className='post-code'>
- {pdfCanvases}
- </div>
- );
- }
-}
-
-PDFPreview.propTypes = {
- fileInfo: PropTypes.object.isRequired,
- fileUrl: PropTypes.string.isRequired
-};
diff --git a/webapp/components/permalink_view.jsx b/webapp/components/permalink_view.jsx
deleted file mode 100644
index 237ad8f44..000000000
--- a/webapp/components/permalink_view.jsx
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import PropTypes from 'prop-types';
-import React from 'react';
-
-import ChannelHeader from 'components/channel_header.jsx';
-import PostView from 'components/post_view';
-
-import ChannelStore from 'stores/channel_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-
-import {Link} from 'react-router/es6';
-import {FormattedMessage} from 'react-intl';
-
-export default class PermalinkView extends React.PureComponent {
- static propTypes = {
- params: PropTypes.object.isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.getStateFromStores = this.getStateFromStores.bind(this);
- this.isStateValid = this.isStateValid.bind(this);
- this.updateState = this.updateState.bind(this);
-
- this.state = this.getStateFromStores(props);
- }
-
- getStateFromStores(props) {
- const postId = props.params.postid;
- const channel = ChannelStore.getCurrent();
- const channelId = channel ? channel.id : '';
- const channelName = channel ? channel.name : '';
- const team = TeamStore.getCurrent();
- const teamName = team ? team.name : '';
- return {
- channelId,
- channelName,
- teamName,
- postId
- };
- }
-
- isStateValid() {
- return this.state.channelId !== '' && this.state.teamName;
- }
-
- updateState() {
- this.setState(this.getStateFromStores(this.props));
- }
-
- componentDidMount() {
- ChannelStore.addChangeListener(this.updateState);
- TeamStore.addChangeListener(this.updateState);
-
- $('body').addClass('app__body');
- }
-
- componentWillUnmount() {
- ChannelStore.removeChangeListener(this.updateState);
- TeamStore.removeChangeListener(this.updateState);
-
- $('body').removeClass('app__body');
- }
-
- componentWillReceiveProps(nextProps) {
- this.setState(this.getStateFromStores(nextProps));
- }
-
- render() {
- if (!this.isStateValid()) {
- return null;
- }
- return (
- <div
- id='app-content'
- className='app__content'
- >
- <ChannelHeader
- channelId={this.state.channelId}
- />
- <PostView
- channelId={this.state.channelId}
- focusedPostId={this.state.postId}
- />
- <div
- id='archive-link-home'
- >
- <Link
- to={'/' + this.state.teamName + '/channels/' + this.state.channelName}
- >
- <FormattedMessage
- id='center_panel.recent'
- defaultMessage='Click here to jump to recent messages. '
- />
- <i className='fa fa-arrow-down'/>
- </Link>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/popover_list_members/index.js b/webapp/components/popover_list_members/index.js
deleted file mode 100644
index 3e9087e0d..000000000
--- a/webapp/components/popover_list_members/index.js
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getProfilesInChannel} from 'mattermost-redux/actions/users';
-
-import PopoverListMembers from './popover_list_members.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getProfilesInChannel
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(PopoverListMembers);
diff --git a/webapp/components/popover_list_members/popover_list_members.jsx b/webapp/components/popover_list_members/popover_list_members.jsx
deleted file mode 100644
index 1b790e923..000000000
--- a/webapp/components/popover_list_members/popover_list_members.jsx
+++ /dev/null
@@ -1,294 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ProfilePicture from 'components/profile_picture.jsx';
-
-import TeamStore from 'stores/team_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-import ChannelStore from 'stores/channel_store.jsx';
-
-import TeamMembersModal from 'components/team_members_modal.jsx';
-import ChannelMembersModal from 'components/channel_members_modal.jsx';
-import ChannelInviteModal from 'components/channel_invite_modal';
-
-import {openDirectChannelToUser} from 'actions/channel_actions.jsx';
-
-import {Client4} from 'mattermost-redux/client';
-import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-import {canManageMembers} from 'utils/channel_utils.jsx';
-
-import $ from 'jquery';
-import PropTypes from 'prop-types';
-import React from 'react';
-import {Tooltip, OverlayTrigger, Popover, Overlay} from 'react-bootstrap';
-import {FormattedMessage} from 'react-intl';
-import {browserHistory} from 'react-router/es6';
-
-export default class PopoverListMembers extends React.Component {
- static propTypes = {
- channel: PropTypes.object.isRequired,
- members: PropTypes.array.isRequired,
- memberCount: PropTypes.number,
- actions: PropTypes.shape({
- getProfilesInChannel: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.showMembersModal = this.showMembersModal.bind(this);
-
- this.handleShowDirectChannel = this.handleShowDirectChannel.bind(this);
- this.closePopover = this.closePopover.bind(this);
-
- this.state = {
- showPopover: false,
- showTeamMembersModal: false,
- showChannelMembersModal: false,
- showChannelInviteModal: false
- };
- }
-
- componentDidUpdate() {
- $('.member-list__popover .popover-content .more-modal__body').perfectScrollbar();
- }
-
- handleShowDirectChannel(teammate, e) {
- e.preventDefault();
-
- openDirectChannelToUser(
- teammate.id,
- (channel, channelAlreadyExisted) => {
- browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + channel.name);
- if (channelAlreadyExisted) {
- this.closePopover();
- }
- },
- () => {
- this.closePopover();
- }
- );
- }
-
- closePopover() {
- this.setState({showPopover: false});
- }
-
- showMembersModal(e) {
- e.preventDefault();
-
- this.setState({
- showPopover: false,
- showChannelMembersModal: true
- });
- }
-
- render() {
- let popoverButton;
- const popoverHtml = [];
- const members = this.props.members;
- const teamMembers = UserStore.getProfilesUsernameMap();
- const currentUserId = UserStore.getCurrentId();
-
- const isSystemAdmin = UserStore.isSystemAdminForCurrentUser();
- const isTeamAdmin = TeamStore.isTeamAdminForCurrentTeam();
- const isChannelAdmin = ChannelStore.isChannelAdminForCurrentChannel();
- const membersIcon = Constants.MEMBERS_ICON_SVG;
-
- if (members && teamMembers) {
- members.sort((a, b) => {
- const aName = Utils.displayEntireName(a.id);
- const bName = Utils.displayEntireName(b.id);
-
- return aName.localeCompare(bName);
- });
-
- members.forEach((m, i) => {
- let messageIcon = '';
- if (currentUserId !== m.id && this.props.channel.type !== Constants.DM_CHANNEl) {
- messageIcon = Constants.MESSAGE_ICON_SVG;
- }
-
- let name = '';
- if (teamMembers[m.username]) {
- name = Utils.displayUsernameForUser(teamMembers[m.username]);
- }
-
- if (name) {
- popoverHtml.push(
- <div
- className='more-modal__row'
- onClick={(e) => this.handleShowDirectChannel(m, e)}
- key={'popover-member-' + i}
- >
- <ProfilePicture
- src={Client4.getProfilePictureUrl(m.id, m.last_picture_update)}
- width='40'
- height='40'
- />
- <div className='more-modal__details'>
- <div
- className='more-modal__name'
- >
- {name}
- </div>
- </div>
- <div
- className='more-modal__actions'
- >
- <span
- className='icon icon__message'
- dangerouslySetInnerHTML={{__html: messageIcon}}
- aria-hidden='true'
- />
- </div>
- </div>
- );
- }
- });
-
- if (this.props.channel.type !== Constants.GM_CHANNEL) {
- let membersName = (
- <FormattedMessage
- id='members_popover.manageMembers'
- defaultMessage='Manage Members'
- />
- );
-
- const manageMembers = canManageMembers(this.props.channel, isChannelAdmin, isTeamAdmin, isSystemAdmin);
- const isDefaultChannel = ChannelStore.isDefault(this.props.channel);
-
- if ((manageMembers === false && isDefaultChannel === false) || isDefaultChannel) {
- membersName = (
- <FormattedMessage
- id='members_popover.viewMembers'
- defaultMessage='View Members'
- />
- );
- }
-
- popoverButton = (
- <div
- className='more-modal__button'
- key={'popover-member-more'}
- >
- <button
- className='btn btn-link'
- onClick={this.showMembersModal}
- >
- {membersName}
- </button>
- </div>
- );
- }
- }
-
- const count = this.props.memberCount;
- let countText = '-';
- if (count > 0) {
- countText = count.toString();
- }
-
- const title = (
- <FormattedMessage
- id='members_popover.title'
- defaultMessage='Channel Members'
- />
- );
-
- let channelMembersModal;
- if (this.state.showChannelMembersModal) {
- channelMembersModal = (
- <ChannelMembersModal
- onModalDismissed={() => this.setState({showChannelMembersModal: false})}
- showInviteModal={() => this.setState({showChannelInviteModal: true})}
- channel={this.props.channel}
- />
- );
- }
-
- let teamMembersModal;
- if (this.state.showTeamMembersModal) {
- teamMembersModal = (
- <TeamMembersModal
- onHide={() => this.setState({showTeamMembersModal: false})}
- isAdmin={isTeamAdmin || isSystemAdmin}
- />
- );
- }
-
- let channelInviteModal;
- if (this.state.showChannelInviteModal) {
- channelInviteModal = (
- <ChannelInviteModal
- onHide={() => this.setState({showChannelInviteModal: false})}
- channel={this.props.channel}
- />
- );
- }
-
- const channelMembersTooltip = (
- <Tooltip id='channelMembersTooltip'>
- <FormattedMessage
- id='channel_header.channelMembers'
- defaultMessage='Members'
- />
- </Tooltip>
- );
-
- return (
- <div className={'channel-header__icon wide ' + (this.state.showPopover ? 'active' : '')}>
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={Constants.OVERLAY_TIME_DELAY}
- placement='bottom'
- overlay={channelMembersTooltip}
- >
- <div
- id='member_popover'
- className='member-popover__trigger'
- ref='member_popover_target'
- onClick={(e) => {
- this.setState({popoverTarget: e.target, showPopover: !this.state.showPopover});
- this.props.actions.getProfilesInChannel(this.props.channel.id, 0);
- }}
- >
- <span className='icon__text'>{countText}</span>
- <span
- className='icon icon__members'
- dangerouslySetInnerHTML={{__html: membersIcon}}
- aria-hidden='true'
- />
- </div>
- </OverlayTrigger>
- <Overlay
- rootClose={true}
- onHide={this.closePopover}
- show={this.state.showPopover}
- target={() => this.state.popoverTarget}
- placement='bottom'
- >
- <Popover
- ref='memebersPopover'
- className='member-list__popover'
- id='member-list-popover'
- >
- <div className='more-modal__header'>
- {title}
- </div>
- <div className='more-modal__body'>
- <div className='more-modal__list'>{popoverHtml}</div>
- </div>
- {popoverButton}
- </Popover>
- </Overlay>
- {channelMembersModal}
- {teamMembersModal}
- {channelInviteModal}
- </div>
- );
- }
-}
-
diff --git a/webapp/components/post_deleted_modal.jsx b/webapp/components/post_deleted_modal.jsx
deleted file mode 100644
index 5b5984140..000000000
--- a/webapp/components/post_deleted_modal.jsx
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {FormattedMessage} from 'react-intl';
-
-import {Modal} from 'react-bootstrap';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class PostDeletedModal extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleHide = this.handleHide.bind(this);
- }
-
- shouldComponentUpdate(nextProps) {
- return nextProps.show !== this.props.show;
- }
-
- handleHide(e) {
- e.preventDefault();
-
- this.props.onHide();
- }
-
- render() {
- return (
- <Modal
- show={this.props.show}
- onHide={this.handleHide}
- >
- <Modal.Header closeButton={true}>
- <Modal.Title>
- <FormattedMessage
- id='post_delete.notPosted'
- defaultMessage='Comment could not be posted'
- />
- </Modal.Title>
- </Modal.Header>
- <Modal.Body>
- <p>
- <FormattedMessage
- id='post_delete.someone'
- defaultMessage='Someone deleted the message on which you tried to post a comment.'
- />
- </p>
- </Modal.Body>
- <Modal.Footer>
- <button
- type='button'
- className='btn btn-primary'
- onClick={this.handleHide}
- >
- <FormattedMessage
- id='post_delete.okay'
- defaultMessage='Okay'
- />
- </button>
- </Modal.Footer>
- </Modal>
- );
- }
-}
-
-PostDeletedModal.propTypes = {
- show: PropTypes.bool.isRequired,
- onHide: PropTypes.func.isRequired
-};
diff --git a/webapp/components/post_view/commented_on_files_message/commented_on_files_message.jsx b/webapp/components/post_view/commented_on_files_message/commented_on_files_message.jsx
deleted file mode 100644
index 1755d68e4..000000000
--- a/webapp/components/post_view/commented_on_files_message/commented_on_files_message.jsx
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import PropTypes from 'prop-types';
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-
-export default class CommentedOnFilesMessage extends React.PureComponent {
- static propTypes = {
-
- /*
- * The id of the post that was commented on
- */
- parentPostId: PropTypes.string.isRequired,
-
- /*
- * An array of file metadata for the parent post
- */
- fileInfos: PropTypes.arrayOf(PropTypes.object),
-
- actions: PropTypes.shape({
-
- /*
- * Function to get file metadata for a post
- */
- getFilesForPost: PropTypes.func.isRequired
- }).isRequired
- }
-
- componentDidMount() {
- if (!this.props.fileInfos || this.props.fileInfos.length === 0) {
- this.props.actions.getFilesForPost(this.props.parentPostId);
- }
- }
-
- render() {
- if (!this.props.fileInfos || this.props.fileInfos.length === 0) {
- return null;
- }
-
- let plusMore = null;
- if (this.props.fileInfos.length > 1) {
- plusMore = (
- <FormattedMessage
- id='post_body.plusMore'
- defaultMessage=' plus {count, number} other {count, plural, one {file} other {files}}'
- values={{
- count: this.props.fileInfos.length - 1
- }}
- />
- );
- }
-
- return (
- <span>
- {this.props.fileInfos[0].name}
- {plusMore}
- </span>
- );
- }
-}
diff --git a/webapp/components/post_view/commented_on_files_message/index.js b/webapp/components/post_view/commented_on_files_message/index.js
deleted file mode 100644
index d55cff16b..000000000
--- a/webapp/components/post_view/commented_on_files_message/index.js
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getFilesForPost} from 'mattermost-redux/actions/files';
-
-import {makeGetFilesForPost} from 'mattermost-redux/selectors/entities/files';
-
-import CommentedOnFilesMessage from './commented_on_files_message.jsx';
-
-function makeMapStateToProps() {
- const selectFileInfosForPost = makeGetFilesForPost();
-
- return function mapStateToProps(state, ownProps) {
- let fileInfos;
- if (ownProps.parentPostId) {
- fileInfos = selectFileInfosForPost(state, ownProps.parentPostId);
- }
-
- return {
- ...ownProps,
- fileInfos
- };
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getFilesForPost
- }, dispatch)
- };
-}
-
-export default connect(makeMapStateToProps, mapDispatchToProps)(CommentedOnFilesMessage);
diff --git a/webapp/components/post_view/date_separator.jsx b/webapp/components/post_view/date_separator.jsx
deleted file mode 100644
index 3f5184dbf..000000000
--- a/webapp/components/post_view/date_separator.jsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import {FormattedDate} from 'react-intl';
-
-export default class DateSeparator extends React.PureComponent {
- static propTypes = {
-
- /*
- * The date to display in the separator
- */
- date: PropTypes.instanceOf(Date)
- }
-
- render() {
- return (
- <div
- className='date-separator'
- >
- <hr className='separator__hr'/>
- <div className='separator__text'>
- <FormattedDate
- value={this.props.date}
- weekday='short'
- month='short'
- day='2-digit'
- year='numeric'
- />
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/post_view/failed_post_options/failed_post_options.jsx b/webapp/components/post_view/failed_post_options/failed_post_options.jsx
deleted file mode 100644
index f28de343b..000000000
--- a/webapp/components/post_view/failed_post_options/failed_post_options.jsx
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {createPost} from 'actions/post_actions.jsx';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {FormattedMessage} from 'react-intl';
-
-export default class FailedPostOptions extends React.Component {
- static propTypes = {
-
- /*
- * The failed post
- */
- post: PropTypes.object.isRequired,
- actions: PropTypes.shape({
-
- /**
- * The function to delete the post
- */
- removePost: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.retryPost = this.retryPost.bind(this);
- this.cancelPost = this.cancelPost.bind(this);
-
- this.submitting = false;
-
- this.state = {};
- }
-
- retryPost(e) {
- e.preventDefault();
-
- if (this.submitting) {
- return;
- }
-
- this.submitting = true;
-
- const post = {...this.props.post};
- Reflect.deleteProperty(post, 'id');
- createPost(post,
- () => {
- this.submitting = false;
- },
- (err) => {
- if (err.id === 'api.post.create_post.root_id.app_error') {
- this.showPostDeletedModal();
- } else {
- this.forceUpdate();
- }
-
- this.submitting = false;
- }
- );
- }
-
- cancelPost(e) {
- e.preventDefault();
- this.props.actions.removePost(this.props.post);
- }
-
- render() {
- return (<span className='pending-post-actions'>
- <a
- className='post-retry'
- href='#'
- onClick={this.retryPost}
- >
- <FormattedMessage
- id='pending_post_actions.retry'
- defaultMessage='Retry'
- />
- </a>
- {' - '}
- <a
- className='post-cancel'
- href='#'
- onClick={this.cancelPost}
- >
- <FormattedMessage
- id='pending_post_actions.cancel'
- defaultMessage='Cancel'
- />
- </a>
- </span>);
- }
-}
diff --git a/webapp/components/post_view/failed_post_options/index.js b/webapp/components/post_view/failed_post_options/index.js
deleted file mode 100644
index bb8dde893..000000000
--- a/webapp/components/post_view/failed_post_options/index.js
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {removePost} from 'mattermost-redux/actions/posts';
-
-import FailedPostOptions from './failed_post_options.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- removePost
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(FailedPostOptions);
diff --git a/webapp/components/post_view/floating_timestamp.jsx b/webapp/components/post_view/floating_timestamp.jsx
deleted file mode 100644
index f0f6af60e..000000000
--- a/webapp/components/post_view/floating_timestamp.jsx
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {FormattedDate} from 'react-intl';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-export default class FloatingTimestamp extends React.PureComponent {
- static propTypes = {
- isScrolling: PropTypes.bool.isRequired,
- isMobile: PropTypes.bool,
- createAt: PropTypes.number,
- isRhsPost: PropTypes.bool
- }
-
- render() {
- if (!this.props.isMobile) {
- return <noscript/>;
- }
-
- if (this.props.createAt === 0) {
- return <noscript/>;
- }
-
- const dateString = (
- <FormattedDate
- value={this.props.createAt}
- weekday='short'
- day='2-digit'
- month='short'
- year='numeric'
- />
- );
-
- let className = 'post-list__timestamp';
- if (this.props.isScrolling) {
- className += ' scrolling';
- }
-
- if (this.props.isRhsPost) {
- className += ' rhs';
- }
-
- return (
- <div className={className}>
- <div>
- <span>{dateString}</span>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/post_view/index.js b/webapp/components/post_view/index.js
deleted file mode 100644
index ad0270cdd..000000000
--- a/webapp/components/post_view/index.js
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-
-import {makeGetPostsInChannel, makeGetPostsAroundPost} from 'mattermost-redux/selectors/entities/posts';
-import {get} from 'mattermost-redux/selectors/entities/preferences';
-import {getChannel} from 'mattermost-redux/selectors/entities/channels';
-import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
-import {getPosts, getPostsBefore, getPostsAfter, getPostThread} from 'mattermost-redux/actions/posts';
-import {increasePostVisibility} from 'actions/post_actions.jsx';
-import {Preferences} from 'utils/constants.jsx';
-
-import PostList from './post_list.jsx';
-
-function makeMapStateToProps() {
- const getPostsInChannel = makeGetPostsInChannel();
- const getPostsAroundPost = makeGetPostsAroundPost();
-
- return function mapStateToProps(state, ownProps) {
- let posts;
- if (ownProps.focusedPostId) {
- posts = getPostsAroundPost(state, ownProps.focusedPostId, ownProps.channelId);
- } else {
- posts = getPostsInChannel(state, ownProps.channelId);
- }
-
- return {
- channel: getChannel(state, ownProps.channelId),
- lastViewedAt: state.views.channel.lastChannelViewTime[ownProps.channelId],
- posts,
- postVisibility: state.views.channel.postVisibility[ownProps.channelId],
- loadingPosts: state.views.channel.loadingPosts[ownProps.channelId],
- focusedPostId: ownProps.focusedPostId,
- currentUserId: getCurrentUserId(state),
- fullWidth: get(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_FULL_SCREEN
- };
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getPosts,
- getPostsBefore,
- getPostsAfter,
- getPostThread,
- increasePostVisibility
- }, dispatch)
- };
-}
-
-export default connect(makeMapStateToProps, mapDispatchToProps)(PostList);
diff --git a/webapp/components/post_view/new_message_indicator.jsx b/webapp/components/post_view/new_message_indicator.jsx
deleted file mode 100644
index 537520c29..000000000
--- a/webapp/components/post_view/new_message_indicator.jsx
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import Constants from 'utils/constants.jsx';
-import {FormattedMessage} from 'react-intl';
-
-export default class NewMessageIndicator extends React.PureComponent {
- static propTypes = {
- onClick: PropTypes.func.isRequired,
- newMessages: PropTypes.number
- }
-
- constructor(props) {
- super(props);
- this.state = {
- visible: false,
- rendered: false
- };
- }
-
- componentWillReceiveProps(nextProps) {
- if (nextProps.newMessages > 0) {
- this.setState({rendered: true}, () => {
- this.setState({visible: true});
- });
- } else {
- this.setState({visible: false});
- }
- }
-
- render() {
- const unreadIcon = Constants.UNREAD_ICON_SVG;
- let className = 'new-messages__button';
- if (this.state.visible > 0) {
- className += ' visible';
- }
- if (!this.state.rendered) {
- className += ' disabled';
- }
- return (
- <div
- className={className}
- onTransitionEnd={this.setRendered.bind(this)}
- ref='indicator'
- >
- <div onClick={this.props.onClick}>
- <FormattedMessage
- id='posts_view.newMsgBelow'
- defaultMessage='New {count, plural, one {message} other {messages}}'
- values={{count: this.props.newMessages}}
- />
- <span
- className='icon icon__unread'
- dangerouslySetInnerHTML={{__html: unreadIcon}}
- />
- </div>
- </div>
- );
- }
-
- // Sync 'rendered' state with visibility param, only after transitions
- // have ended
- setRendered() {
- this.setState({rendered: this.state.visible});
- }
-}
-
-NewMessageIndicator.defaultProps = {
- newMessages: 0
-};
diff --git a/webapp/components/post_view/post/index.js b/webapp/components/post_view/post/index.js
deleted file mode 100644
index 2e7125f34..000000000
--- a/webapp/components/post_view/post/index.js
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-
-import {getCurrentUser, getUser, getStatusForUserId} from 'mattermost-redux/selectors/entities/users';
-import {get} from 'mattermost-redux/selectors/entities/preferences';
-import {getPost} from 'mattermost-redux/selectors/entities/posts';
-
-import {Preferences} from 'utils/constants.jsx';
-
-import Post from './post.jsx';
-
-function mapStateToProps(state, ownProps) {
- const detailedPost = ownProps.post || {};
-
- return {
- post: getPost(state, detailedPost.id),
- lastPostCount: ownProps.lastPostCount,
- user: getUser(state, detailedPost.user_id),
- status: getStatusForUserId(state, detailedPost.user_id),
- currentUser: getCurrentUser(state),
- isFirstReply: Boolean(detailedPost.isFirstReply && detailedPost.commentedOnPost),
- highlight: detailedPost.highlight,
- consecutivePostByUser: detailedPost.consecutivePostByUser,
- previousPostIsComment: detailedPost.previousPostIsComment,
- replyCount: detailedPost.replyCount,
- isCommentMention: detailedPost.isCommentMention,
- center: get(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT) === Preferences.CHANNEL_DISPLAY_MODE_CENTERED,
- compactDisplay: get(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT) === Preferences.MESSAGE_DISPLAY_COMPACT
- };
-}
-
-export default connect(mapStateToProps)(Post);
diff --git a/webapp/components/post_view/post/post.jsx b/webapp/components/post_view/post/post.jsx
deleted file mode 100644
index 25d23c690..000000000
--- a/webapp/components/post_view/post/post.jsx
+++ /dev/null
@@ -1,300 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import PostHeader from 'components/post_view/post_header';
-import PostBody from 'components/post_view/post_body';
-import ProfilePicture from 'components/profile_picture.jsx';
-
-import Constants from 'utils/constants.jsx';
-const ActionTypes = Constants.ActionTypes;
-import {Posts} from 'mattermost-redux/constants';
-
-import * as Utils from 'utils/utils.jsx';
-import * as PostUtils from 'utils/post_utils.jsx';
-import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-export default class Post extends React.PureComponent {
- static propTypes = {
-
- /**
- * The post to render
- */
- post: PropTypes.object.isRequired,
-
- /**
- * The user who created the post
- */
- user: PropTypes.object,
-
- /**
- * The status of the poster
- */
- status: PropTypes.string,
-
- /**
- * The logged in user
- */
- currentUser: PropTypes.object.isRequired,
-
- /**
- * Set to center the post
- */
- center: PropTypes.bool,
-
- /**
- * Set to render post compactly
- */
- compactDisplay: PropTypes.bool,
-
- /**
- * Set to render a preview of the parent post above this reply
- */
- isFirstReply: PropTypes.bool,
-
- /**
- * Set to highlight the background of the post
- */
- highlight: PropTypes.bool,
-
- /**
- * Set to render this post as if it was attached to the previous post
- */
- consecutivePostByUser: PropTypes.bool,
-
- /**
- * Set if the previous post is a comment
- */
- previousPostIsComment: PropTypes.bool,
-
- /**
- * Set to render this comment as a mention
- */
- isCommentMention: PropTypes.bool,
-
- /**
- * The number of replies in the same thread as this post
- */
- replyCount: PropTypes.number,
-
- /**
- * Set to mark the poster as in a webrtc call
- */
- isBusy: PropTypes.bool,
-
- /**
- * The post count used for selenium tests
- */
- lastPostCount: PropTypes.number,
-
- /**
- * Function to get the post list HTML element
- */
- getPostList: PropTypes.func.isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.state = {
- dropdownOpened: false
- };
- }
-
- handleCommentClick = (e) => {
- e.preventDefault();
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_POST_SELECTED,
- postId: Utils.getRootId(this.props.post)
- });
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_SEARCH,
- results: null
- });
- }
-
- handleDropdownOpened = (opened) => {
- this.setState({
- dropdownOpened: opened
- });
- }
-
- forceUpdateInfo = () => {
- this.refs.info.forceUpdate();
- this.refs.header.forceUpdate();
- }
-
- getClassName = (post, isSystemMessage, fromWebhook) => {
- let className = 'post';
-
- if (post.failed || post.state === Posts.POST_DELETED) {
- className += ' post--hide-controls';
- }
-
- if (this.props.highlight) {
- className += ' post--highlight';
- }
-
- let rootUser = '';
- if (this.props.isFirstReply) {
- rootUser = 'other--root';
- } else if (!post.root_id && !this.props.previousPostIsComment && this.props.consecutivePostByUser) {
- rootUser = 'same--root';
- } else if (post.root_id) {
- rootUser = 'same--root';
- } else {
- rootUser = 'other--root';
- }
-
- let currentUserCss = '';
- if (this.props.currentUser.id === post.user_id && !fromWebhook && !isSystemMessage) {
- currentUserCss = 'current--user';
- }
-
- let sameUserClass = '';
- if (this.props.consecutivePostByUser) {
- sameUserClass = 'same--user';
- }
-
- let postType = '';
- if (post.root_id && post.root_id.length > 0) {
- postType = 'post--comment';
- } else if (this.props.replyCount > 0) {
- postType = 'post--root';
- sameUserClass = '';
- rootUser = '';
- }
-
- if (isSystemMessage) {
- className += ' post--system';
- sameUserClass = '';
- currentUserCss = '';
- postType = '';
- rootUser = '';
- }
-
- if (this.props.compactDisplay) {
- className += ' post--compact';
- }
-
- if (this.state.dropdownOpened) {
- className += ' post--hovered';
- }
-
- if (post.is_pinned) {
- className += ' post--pinned';
- }
-
- return className + ' ' + sameUserClass + ' ' + rootUser + ' ' + postType + ' ' + currentUserCss;
- }
-
- render() {
- const post = this.props.post;
- const mattermostLogo = Constants.MATTERMOST_ICON_SVG;
-
- const isSystemMessage = PostUtils.isSystemMessage(post);
- const fromWebhook = post.props && post.props.from_webhook === 'true';
-
- let status = this.props.status;
- if (fromWebhook) {
- status = null;
- }
-
- let profilePic = (
- <ProfilePicture
- src={PostUtils.getProfilePicSrcForPost(post, this.props.user)}
- status={status}
- user={this.props.user}
- isBusy={this.props.isBusy}
- hasMention={true}
- />
- );
-
- if (fromWebhook) {
- profilePic = (
- <ProfilePicture
- src={PostUtils.getProfilePicSrcForPost(post, this.props.user)}
- />
- );
- } else if (PostUtils.isSystemMessage(post)) {
- profilePic = (
- <span
- className='icon'
- dangerouslySetInnerHTML={{__html: mattermostLogo}}
- />
- );
- }
-
- let centerClass = '';
- if (this.props.center) {
- centerClass = 'center';
- }
-
- if (this.props.compactDisplay) {
- if (fromWebhook) {
- profilePic = (
- <ProfilePicture
- src=''
- status={status}
- isBusy={this.props.isBusy}
- user={this.props.user}
- />
- );
- } else {
- profilePic = (
- <ProfilePicture
- src=''
- status={status}
- />
- );
- }
- }
-
- const profilePicContainer = (<div className='post__img'>{profilePic}</div>);
-
- return (
- <div
- ref={(div) => {
- this.domNode = div;
- }}
- >
- <div
- id={'post_' + post.id}
- className={this.getClassName(this.props.post, isSystemMessage, fromWebhook)}
- >
- <div className={'post__content ' + centerClass}>
- {profilePicContainer}
- <div>
- <PostHeader
- ref='header'
- post={post}
- handleCommentClick={this.handleCommentClick}
- handleDropdownOpened={this.handleDropdownOpened}
- user={this.props.user}
- currentUser={this.props.currentUser}
- compactDisplay={this.props.compactDisplay}
- status={this.props.status}
- isBusy={this.props.isBusy}
- lastPostCount={this.props.lastPostCount}
- replyCount={this.props.replyCount}
- consecutivePostByUser={this.props.consecutivePostByUser}
- getPostList={this.props.getPostList}
- />
- <PostBody
- post={post}
- handleCommentClick={this.handleCommentClick}
- compactDisplay={this.props.compactDisplay}
- lastPostCount={this.props.lastPostCount}
- isCommentMention={this.props.isCommentMention}
- />
- </div>
- </div>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/post_view/post_attachment.jsx b/webapp/components/post_view/post_attachment.jsx
deleted file mode 100644
index cc7aa509c..000000000
--- a/webapp/components/post_view/post_attachment.jsx
+++ /dev/null
@@ -1,359 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import * as TextFormatting from 'utils/text_formatting.jsx';
-import {localizeMessage} from 'utils/utils.jsx';
-
-import * as PostActions from 'actions/post_actions.jsx';
-
-import $ from 'jquery';
-import React from 'react';
-import PropTypes from 'prop-types';
-
-export default class PostAttachment extends React.PureComponent {
- static propTypes = {
-
- /**
- * The post id
- */
- postId: PropTypes.string.isRequired,
-
- /**
- * The attachment to render
- */
- attachment: PropTypes.object.isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.handleActionButtonClick = this.handleActionButtonClick.bind(this);
- this.getActionView = this.getActionView.bind(this);
- this.getFieldsTable = this.getFieldsTable.bind(this);
- this.getInitState = this.getInitState.bind(this);
- this.shouldCollapse = this.shouldCollapse.bind(this);
- this.toggleCollapseState = this.toggleCollapseState.bind(this);
- }
-
- componentDidMount() {
- $(this.refs.attachment).on('click', '.attachment-link-more', this.toggleCollapseState);
- }
-
- componentWillUnmount() {
- $(this.refs.attachment).off('click', '.attachment-link-more', this.toggleCollapseState);
- }
-
- componentWillMount() {
- this.setState(this.getInitState());
- }
-
- getInitState() {
- const shouldCollapse = this.shouldCollapse();
- const text = TextFormatting.formatText(this.props.attachment.text || '');
- const uncollapsedText = text + (shouldCollapse ? `<div><a class="attachment-link-more" href="#">${localizeMessage('post_attachment.collapse', 'Show less...')}</a></div>` : '');
- const collapsedText = shouldCollapse ? this.getCollapsedText() : text;
-
- return {
- shouldCollapse,
- collapsedText,
- uncollapsedText,
- text: shouldCollapse ? collapsedText : uncollapsedText,
- collapsed: shouldCollapse
- };
- }
-
- toggleCollapseState(e) {
- e.preventDefault();
- this.setState((prevState) => {
- return {
- text: prevState.collapsed ? prevState.uncollapsedText : prevState.collapsedText,
- collapsed: !prevState.collapsed
- };
- });
- }
-
- shouldCollapse() {
- const text = this.props.attachment.text || '';
- return (text.match(/\n/g) || []).length >= 5 || text.length > 700;
- }
-
- getCollapsedText() {
- let text = this.props.attachment.text || '';
- if ((text.match(/\n/g) || []).length >= 5) {
- text = text.split('\n').splice(0, 5).join('\n');
- }
- if (text.length > 300) {
- text = text.substr(0, 300);
- }
-
- return TextFormatting.formatText(text) + `<div><a class="attachment-link-more" href="#">${localizeMessage('post_attachment.more', 'Show more...')}</a></div>`;
- }
-
- getActionView() {
- const actions = this.props.attachment.actions;
- if (!actions || !actions.length) {
- return '';
- }
-
- const buttons = [];
-
- actions.forEach((action) => {
- if (!action.id || !action.name) {
- return;
- }
- buttons.push(
- <button
- key={action.id}
- onClick={() => this.handleActionButtonClick(action.id)}
- >
- {action.name}
- </button>
- );
- });
-
- return (
- <div
- className='attachment-actions'
- >
- {buttons}
- </div>
- );
- }
-
- handleActionButtonClick(actionId) {
- PostActions.doPostAction(this.props.postId, actionId);
- }
-
- getFieldsTable() {
- const fields = this.props.attachment.fields;
- if (!fields || !fields.length) {
- return '';
- }
-
- const fieldTables = [];
-
- let headerCols = [];
- let bodyCols = [];
- let rowPos = 0;
- let lastWasLong = false;
- let nrTables = 0;
-
- fields.forEach((field, i) => {
- if (rowPos === 2 || !(field.short === true) || lastWasLong) {
- fieldTables.push(
- <table
- className='attachment-fields'
- key={'attachment__table__' + nrTables}
- >
- <thead>
- <tr>
- {headerCols}
- </tr>
- </thead>
- <tbody>
- <tr>
- {bodyCols}
- </tr>
- </tbody>
- </table>
- );
- headerCols = [];
- bodyCols = [];
- rowPos = 0;
- nrTables += 1;
- lastWasLong = false;
- }
- headerCols.push(
- <th
- className='attachment-field__caption'
- key={'attachment__field-caption-' + i + '__' + nrTables}
- width='50%'
- >
- {field.title}
- </th>
- );
- bodyCols.push(
- <td
- className='attachment-field'
- key={'attachment__field-' + i + '__' + nrTables}
- dangerouslySetInnerHTML={{__html: TextFormatting.formatText(field.value || '')}}
- />
- );
- rowPos += 1;
- lastWasLong = !(field.short === true);
- });
- if (headerCols.length > 0) { // Flush last fields
- fieldTables.push(
- <table
- className='attachment-fields'
- key={'attachment__table__' + nrTables}
- >
- <thead>
- <tr>
- {headerCols}
- </tr>
- </thead>
- <tbody>
- <tr>
- {bodyCols}
- </tr>
- </tbody>
- </table>
- );
- }
- return (
- <div>
- {fieldTables}
- </div>
- );
- }
-
- render() {
- const data = this.props.attachment;
-
- let preText;
- if (data.pretext) {
- preText = (
- <div
- className='attachment__thumb-pretext'
- dangerouslySetInnerHTML={{__html: TextFormatting.formatText(data.pretext)}}
- />
- );
- }
-
- let author = [];
- if (data.author_name || data.author_icon) {
- if (data.author_icon) {
- author.push(
- <img
- className='attachment__author-icon'
- src={data.author_icon}
- key={'attachment__author-icon'}
- height='14'
- width='14'
- />
- );
- }
- if (data.author_name) {
- author.push(
- <span
- className='attachment__author-name'
- key={'attachment__author-name'}
- >
- {data.author_name}
- </span>
- );
- }
- }
- if (data.author_link) {
- author = (
- <a
- href={data.author_link}
- target='_blank'
- rel='noopener noreferrer'
- >
- {author}
- </a>
- );
- }
-
- let title;
- if (data.title) {
- if (data.title_link) {
- title = (
- <h1
- className='attachment__title'
- >
- <a
- className='attachment__title-link'
- href={data.title_link}
- target='_blank'
- rel='noopener noreferrer'
- >
- {data.title}
- </a>
- </h1>
- );
- } else {
- title = (
- <h1
- className='attachment__title'
- >
- {data.title}
- </h1>
- );
- }
- }
-
- let text;
- if (data.text) {
- text = (
- <div
- className='attachment__text'
- dangerouslySetInnerHTML={{__html: this.state.text}}
- />
- );
- }
-
- let image;
- if (data.image_url) {
- image = (
- <img
- className='attachment__image'
- src={data.image_url}
- />
- );
- }
-
- let thumb;
- if (data.thumb_url) {
- thumb = (
- <div
- className='attachment__thumb-container'
- >
- <img
- src={data.thumb_url}
- />
- </div>
- );
- }
-
- const fields = this.getFieldsTable();
- const actions = this.getActionView();
-
- let useBorderStyle;
- if (data.color && data.color[0] === '#') {
- useBorderStyle = {borderLeftColor: data.color};
- }
-
- return (
- <div
- className='attachment'
- ref='attachment'
- >
- {preText}
- <div className='attachment__content'>
- <div
- className={useBorderStyle ? 'clearfix attachment__container' : 'clearfix attachment__container attachment__container--' + data.color}
- style={useBorderStyle}
- >
- {author}
- {title}
- <div>
- <div
- className={thumb ? 'attachment__body' : 'attachment__body attachment__body--no_thumb'}
- >
- {text}
- {image}
- {fields}
- {actions}
- </div>
- {thumb}
- <div style={{clear: 'both'}}/>
- </div>
- </div>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/post_view/post_attachment_list.jsx b/webapp/components/post_view/post_attachment_list.jsx
deleted file mode 100644
index ce60a0155..000000000
--- a/webapp/components/post_view/post_attachment_list.jsx
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import PostAttachment from './post_attachment.jsx';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-export default class PostAttachmentList extends React.PureComponent {
- static propTypes = {
-
- /**
- * The post id
- */
- postId: PropTypes.string.isRequired,
-
- /**
- * Array of attachments to render
- */
- attachments: PropTypes.array.isRequired
- }
-
- render() {
- const content = [];
- this.props.attachments.forEach((attachment, i) => {
- content.push(
- <PostAttachment
- attachment={attachment}
- postId={this.props.postId}
- key={'att_' + i}
- />
- );
- });
-
- return (
- <div className='attachment_list'>
- {content}
- </div>
- );
- }
-}
diff --git a/webapp/components/post_view/post_attachment_opengraph/index.js b/webapp/components/post_view/post_attachment_opengraph/index.js
deleted file mode 100644
index 1f889f1d6..000000000
--- a/webapp/components/post_view/post_attachment_opengraph/index.js
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
-import {bindActionCreators} from 'redux';
-import {getOpenGraphMetadata} from 'mattermost-redux/actions/posts';
-import {getOpenGraphMetadataForUrl} from 'mattermost-redux/selectors/entities/posts';
-
-import PostAttachmentOpenGraph from './post_attachment_opengraph.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps,
- openGraphData: getOpenGraphMetadataForUrl(state, ownProps.link),
- currentUser: getCurrentUser(state),
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getOpenGraphMetadata
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(PostAttachmentOpenGraph);
diff --git a/webapp/components/post_view/post_attachment_opengraph/post_attachment_opengraph.jsx b/webapp/components/post_view/post_attachment_opengraph/post_attachment_opengraph.jsx
deleted file mode 100644
index 729084637..000000000
--- a/webapp/components/post_view/post_attachment_opengraph/post_attachment_opengraph.jsx
+++ /dev/null
@@ -1,328 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import {postListScrollChange} from 'actions/global_actions.jsx';
-import {updatePost} from 'actions/post_actions.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-import * as CommonUtils from 'utils/commons.jsx';
-import {PostTypes} from 'utils/constants.jsx';
-
-export default class PostAttachmentOpenGraph extends React.PureComponent {
- static propTypes = {
-
- /**
- * The link to display the open graph data for
- */
- link: PropTypes.string.isRequired,
-
- /**
- * The current user viewing the post
- */
- currentUser: PropTypes.object,
-
- /**
- * The post where this link is included
- */
- post: PropTypes.object,
-
- /**
- * The open graph data to render
- */
- openGraphData: PropTypes.object,
-
- /**
- * Set to collapse the preview
- */
- previewCollapsed: PropTypes.string,
- actions: PropTypes.shape({
-
- /**
- * The function to get open graph data for a link
- */
- getOpenGraphMetadata: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
- this.largeImageMinWidth = 150;
- this.imageDimentions = { // Image dimentions in pixels.
- height: 80,
- width: 80
- };
- this.textMaxLenght = 300;
- this.textEllipsis = '...';
- this.largeImageMinRatio = 16 / 9;
- this.smallImageContainerLeftPadding = 15;
-
- this.imageRatio = null;
-
- this.smallImageContainer = null;
- this.smallImageElement = null;
-
- this.IMAGE_LOADED = {
- LOADING: 'loading',
- YES: 'yes',
- ERROR: 'error'
- };
-
- this.fetchData = this.fetchData.bind(this);
- this.toggleImageVisibility = this.toggleImageVisibility.bind(this);
- this.onImageLoad = this.onImageLoad.bind(this);
- this.onImageError = this.onImageError.bind(this);
- this.handleRemovePreview = this.handleRemovePreview.bind(this);
- }
-
- componentWillMount() {
- const removePreview = this.isRemovePreview(this.props.post, this.props.currentUser);
-
- this.setState({
- imageLoaded: this.IMAGE_LOADED.LOADING,
- imageVisible: this.props.previewCollapsed.startsWith('false'),
- hasLargeImage: false,
- removePreview
- });
- this.fetchData(this.props.link);
- }
-
- componentWillReceiveProps(nextProps) {
- if (!Utils.areObjectsEqual(nextProps.post, this.props.post)) {
- const removePreview = this.isRemovePreview(nextProps.post, nextProps.currentUser);
- this.setState({
- removePreview
- });
- }
- if (nextProps.link !== this.props.link) {
- this.fetchData(nextProps.link);
- }
- if (nextProps.previewCollapsed !== this.props.previewCollapsed) {
- this.setState({
- imageVisible: nextProps.previewCollapsed.startsWith('false')
- });
- }
- }
-
- componentDidUpdate() {
- setTimeout(postListScrollChange, 0);
- }
-
- fetchData(url) {
- if (!this.props.openGraphData) {
- this.props.actions.getOpenGraphMetadata(url);
- }
- }
-
- getBestImageUrl(data) {
- if (Utils.isEmptyObject(data.images)) {
- return null;
- }
-
- const bestImage = CommonUtils.getNearestPoint(this.imageDimentions, data.images, 'width', 'height');
- return bestImage.secure_url || bestImage.url;
- }
-
- toggleImageVisibility() {
- this.setState({imageVisible: !this.state.imageVisible});
- }
-
- onImageLoad(image) {
- this.imageRatio = image.target.naturalWidth / image.target.naturalHeight;
- if (
- image.target.naturalWidth >= this.largeImageMinWidth &&
- this.imageRatio >= this.largeImageMinRatio &&
- !this.state.hasLargeImage
- ) {
- this.setState({
- hasLargeImage: true
- });
- }
- this.setState({
- imageLoaded: this.IMAGE_LOADED.YES
- });
- }
-
- onImageError() {
- this.setState({imageLoaded: this.IMAGE_LOADED.ERROR});
- }
-
- loadImage(src) {
- const img = new Image();
- img.onload = this.onImageLoad;
- img.onerror = this.onImageError;
- img.src = src;
- }
-
- imageToggleAnchoreTag(imageUrl) {
- if (imageUrl && this.state.hasLargeImage) {
- return (
- <a
- className={'post__embed-visibility'}
- data-expanded={this.state.imageVisible}
- aria-label='Toggle Embed Visibility'
- onClick={this.toggleImageVisibility}
- />
- );
- }
- return null;
- }
-
- wrapInSmallImageContainer(imageElement) {
- return (
- <div
- className='attachment__image__container--openraph'
- ref={(div) => {
- this.smallImageContainer = div;
- }}
- >
- {imageElement}
- </div>
- );
- }
-
- imageTag(imageUrl, renderingForLargeImage = false) {
- var element = null;
- if (
- imageUrl && renderingForLargeImage === this.state.hasLargeImage &&
- (!renderingForLargeImage || (renderingForLargeImage && this.state.imageVisible))
- ) {
- if (this.state.imageLoaded === this.IMAGE_LOADED.LOADING) {
- if (renderingForLargeImage) {
- element = <img className={'attachment__image attachment__image--openraph loading large_image'}/>;
- } else {
- element = this.wrapInSmallImageContainer(
- <img className={'attachment__image attachment__image--openraph loading '}/>
- );
- }
- } else if (this.state.imageLoaded === this.IMAGE_LOADED.YES) {
- if (renderingForLargeImage) {
- element = (
- <img
- className={'attachment__image attachment__image--openraph large_image'}
- src={imageUrl}
- />
- );
- } else {
- element = this.wrapInSmallImageContainer(
- <img
- className={'attachment__image attachment__image--openraph'}
- src={imageUrl}
- ref={(img) => {
- this.smallImageElement = img;
- }}
- />
- );
- }
- } else if (this.state.imageLoaded === this.IMAGE_LOADED.ERROR) {
- return null;
- }
- }
- return element;
- }
-
- truncateText(text, maxLength = this.textMaxLenght, ellipsis = this.textEllipsis) {
- if (text.length > maxLength) {
- return text.substring(0, maxLength - ellipsis.length) + ellipsis;
- }
- return text;
- }
-
- handleRemovePreview() {
- const props = Object.assign({}, this.props.post.props);
- props[PostTypes.REMOVE_LINK_PREVIEW] = 'true';
-
- const patchedPost = ({
- id: this.props.post.id,
- props
- });
-
- updatePost(patchedPost, () => {
- this.setState({removePreview: true});
- });
- }
-
- isRemovePreview(post, currentUser) {
- if (post && post.props && currentUser.id === post.user_id) {
- return post.props[PostTypes.REMOVE_LINK_PREVIEW] && post.props[PostTypes.REMOVE_LINK_PREVIEW] === 'true';
- }
-
- return false;
- }
-
- render() {
- const data = this.props.openGraphData;
- if (!data || Utils.isEmptyObject(data.description) || this.state.removePreview) {
- return null;
- }
-
- let removePreviewButton;
- if (this.props.currentUser.id === this.props.post.user_id) {
- removePreviewButton = (
- <button
- id='removePreviewButton'
- type='button'
- className='btn-close'
- aria-label='Close'
- onClick={this.handleRemovePreview}
- >
- <span aria-hidden='true'>{'×'}</span>
- </button>
- );
- }
-
- const imageUrl = this.getBestImageUrl(data);
- if (imageUrl) {
- this.loadImage(imageUrl);
- }
-
- return (
- <div
- className='attachment attachment--opengraph'
- ref='attachment'
- >
- <div className='attachment__content'>
- <div
- className={'clearfix attachment__container attachment__container--opengraph'}
- >
- <div
- className={'attachment__body__wrap attachment__body__wrap--opengraph'}
- >
- <span className='sitename'>{this.truncateText(data.site_name)}</span>
- {removePreviewButton}
- <h1
- className={'attachment__title attachment__title--opengraph' + (data.title ? '' : ' is-url')}
- >
- <a
- className='attachment__title-link attachment__title-link--opengraph'
- href={data.url || this.props.link}
- target='_blank'
- rel='noopener noreferrer'
- title={data.title || data.url || this.props.link}
- >
- {this.truncateText(data.title || data.url || this.props.link)}
- </a>
- </h1>
- <div >
- <div
- className={'attachment__body attachment__body--opengraph'}
- >
- <div>
- <div>
- {this.truncateText(data.description)} &nbsp;
- {this.imageToggleAnchoreTag(imageUrl)}
- </div>
- {this.imageTag(imageUrl, true)}
- </div>
- </div>
- </div>
- </div>
- {this.imageTag(imageUrl, false)}
- </div>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/post_view/post_body/index.js b/webapp/components/post_view/post_body/index.js
deleted file mode 100644
index 90f04e0f9..000000000
--- a/webapp/components/post_view/post_body/index.js
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-
-import {getUser} from 'mattermost-redux/selectors/entities/users';
-import {get} from 'mattermost-redux/selectors/entities/preferences';
-import {getPost} from 'mattermost-redux/selectors/entities/posts';
-
-import {Preferences} from 'utils/constants.jsx';
-
-import PostBody from './post_body.jsx';
-
-function mapStateToProps(state, ownProps) {
- let parentPost;
- let parentPostUser;
- if (ownProps.post.root_id) {
- parentPost = getPost(state, ownProps.post.root_id);
- parentPostUser = parentPost ? getUser(state, parentPost.user_id) : null;
- }
-
- return {
- ...ownProps,
- parentPost,
- parentPostUser,
- previewCollapsed: get(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.COLLAPSE_DISPLAY, 'false')
- };
-}
-
-export default connect(mapStateToProps)(PostBody);
diff --git a/webapp/components/post_view/post_body/post_body.jsx b/webapp/components/post_view/post_body/post_body.jsx
deleted file mode 100644
index 1eab74cf2..000000000
--- a/webapp/components/post_view/post_body/post_body.jsx
+++ /dev/null
@@ -1,200 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import PropTypes from 'prop-types';
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-
-import * as PostActions from 'actions/post_actions.jsx';
-
-import FileAttachmentListContainer from 'components/file_attachment_list';
-import CommentedOnFilesMessage from 'components/post_view/commented_on_files_message';
-import PostBodyAdditionalContent from 'components/post_view/post_body_additional_content.jsx';
-import FailedPostOptions from 'components/post_view/failed_post_options';
-import PostMessageView from 'components/post_view/post_message_view';
-import ReactionListContainer from 'components/post_view/reaction_list';
-
-import * as Utils from 'utils/utils.jsx';
-import * as PostUtils from 'utils/post_utils.jsx';
-
-import {Posts} from 'mattermost-redux/constants';
-
-export default class PostBody extends React.PureComponent {
- static propTypes = {
-
- /**
- * The post to render the body of
- */
- post: PropTypes.object.isRequired,
-
- /**
- * The parent post of the thread this post is in
- */
- parentPost: PropTypes.object,
-
- /**
- * The poster of the parent post, if exists
- */
- parentPostUser: PropTypes.object,
-
- /**
- * The function called when the comment icon is clicked
- */
- handleCommentClick: PropTypes.func.isRequired,
-
- /**
- * Set to render post body compactly
- */
- compactDisplay: PropTypes.bool,
-
- /**
- * Set to highlight comment as a mention
- */
- isCommentMention: PropTypes.bool,
-
- /**
- * Set to collapse image and video previews
- */
- previewCollapsed: PropTypes.string,
-
- /**
- * Post identifiers for selenium tests
- */
- lastPostCount: PropTypes.number
- }
-
- render() {
- const post = this.props.post;
- const parentPost = this.props.parentPost;
-
- let comment = '';
- let postClass = '';
-
- if (parentPost && !Utils.isPostEphemeral(post)) {
- const profile = this.props.parentPostUser;
-
- let apostrophe = '';
- let name = '...';
- if (profile != null) {
- let username = Utils.displayUsernameForUser(profile);
- if (parentPost.props &&
- parentPost.props.from_webhook &&
- parentPost.props.override_username &&
- global.window.mm_config.EnablePostUsernameOverride === 'true') {
- username = parentPost.props.override_username;
- }
-
- if (username.slice(-1) === 's') {
- apostrophe = '\'';
- } else {
- apostrophe = '\'s';
- }
- name = (
- <a
- className='theme'
- onClick={PostActions.searchForTerm.bind(null, username)}
- >
- {username}
- </a>
- );
- }
-
- let message = '';
- if (parentPost.message) {
- message = Utils.replaceHtmlEntities(parentPost.message);
- } else if (parentPost.file_ids && parentPost.file_ids.length > 0) {
- message = (
- <CommentedOnFilesMessage
- parentPostId={parentPost.id}
- />
- );
- }
-
- comment = (
- <div className='post__link'>
- <span>
- <FormattedMessage
- id='post_body.commentedOn'
- defaultMessage='Commented on {name}{apostrophe} message: '
- values={{
- name,
- apostrophe
- }}
- />
- <a
- className='theme'
- onClick={this.props.handleCommentClick}
- >
- {message}
- </a>
- </span>
- </div>
- );
- }
-
- let failedOptions;
- if (this.props.post.failed) {
- postClass += ' post--fail';
- failedOptions = <FailedPostOptions post={this.props.post}/>;
- }
-
- if (PostUtils.isEdited(this.props.post)) {
- postClass += ' post--edited';
- }
-
- let fileAttachmentHolder = null;
- if (((post.file_ids && post.file_ids.length > 0) || (post.filenames && post.filenames.length > 0)) && this.props.post.state !== Posts.POST_DELETED) {
- fileAttachmentHolder = (
- <FileAttachmentListContainer
- post={post}
- compactDisplay={this.props.compactDisplay}
- />
- );
- }
-
- const messageWrapper = (
- <div
- key={`${post.id}_message`}
- id={`${post.id}_message`}
- className={postClass}
- >
- {failedOptions}
- <PostMessageView
- lastPostCount={this.props.lastPostCount}
- post={this.props.post}
- compactDisplay={this.props.compactDisplay}
- hasMention={true}
- />
- </div>
- );
-
- let messageWithAdditionalContent;
- if (this.props.post.state === Posts.POST_DELETED) {
- messageWithAdditionalContent = messageWrapper;
- } else {
- messageWithAdditionalContent = (
- <PostBodyAdditionalContent
- post={this.props.post}
- message={messageWrapper}
- previewCollapsed={this.props.previewCollapsed}
- />
- );
- }
-
- let mentionHighlightClass = '';
- if (this.props.isCommentMention) {
- mentionHighlightClass = 'mention-comment';
- }
-
- return (
- <div>
- {comment}
- <div className={'post__body ' + mentionHighlightClass}>
- {messageWithAdditionalContent}
- {fileAttachmentHolder}
- <ReactionListContainer post={post}/>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/post_view/post_body_additional_content.jsx b/webapp/components/post_view/post_body_additional_content.jsx
deleted file mode 100644
index 88e8f2ba8..000000000
--- a/webapp/components/post_view/post_body_additional_content.jsx
+++ /dev/null
@@ -1,273 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import PostAttachmentList from './post_attachment_list.jsx';
-import PostAttachmentOpenGraph from './post_attachment_opengraph';
-import PostImage from './post_image.jsx';
-import YoutubeVideo from 'components/youtube_video';
-
-import Constants from 'utils/constants.jsx';
-import * as Utils from 'utils/utils.jsx';
-import BrowserStore from 'stores/browser_store.jsx';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-export default class PostBodyAdditionalContent extends React.PureComponent {
- static propTypes = {
-
- /**
- * The post to render the content of
- */
- post: PropTypes.object.isRequired,
-
- /**
- * The post's message
- */
- message: PropTypes.element.isRequired,
-
- /**
- * Set to collapse image and video previews
- */
- previewCollapsed: PropTypes.string
- }
-
- static defaultProps = {
- previewCollapsed: ''
- }
-
- constructor(props) {
- super(props);
-
- this.getSlackAttachment = this.getSlackAttachment.bind(this);
- this.generateToggleableEmbed = this.generateToggleableEmbed.bind(this);
- this.generateStaticEmbed = this.generateStaticEmbed.bind(this);
- this.toggleEmbedVisibility = this.toggleEmbedVisibility.bind(this);
- this.isLinkToggleable = this.isLinkToggleable.bind(this);
- this.handleLinkLoadError = this.handleLinkLoadError.bind(this);
- this.handleLinkLoaded = this.handleLinkLoaded.bind(this);
-
- this.state = {
- embedVisible: PostBodyAdditionalContent.isEmbedVisible(props),
- link: Utils.extractFirstLink(props.post.message),
- linkLoadError: false,
- linkLoaded: false
- };
- }
-
- componentDidMount() {
- // check the availability of the image rendered(if any) in the first render.
- this.preCheckImageLink();
- }
-
- componentWillReceiveProps(nextProps) {
- if (nextProps.previewCollapsed !== this.props.previewCollapsed || nextProps.post.message !== this.props.post.message) {
- this.setState({
- embedVisible: PostBodyAdditionalContent.isEmbedVisible(nextProps),
- link: Utils.extractFirstLink(nextProps.post.message)
- }, () => {
- // check the availability of the image link
- this.preCheckImageLink();
- });
- }
- }
-
- toggleEmbedVisibility() {
- // save the taggle info in the localstorage
- BrowserStore.setItem(`isVisible-${this.props.post.id}`, !this.state.embedVisible);
-
- this.setState((prevState) => {
- return {embedVisible: !prevState.embedVisible};
- });
- }
-
- getSlackAttachment() {
- let attachments = [];
- if (this.props.post.props && this.props.post.props.attachments) {
- attachments = this.props.post.props.attachments;
- }
-
- return (
- <PostAttachmentList
- attachments={attachments}
- postId={this.props.post.id}
- key={this.props.post.id}
- />
- );
- }
-
- // when image links are collapsed, check if the link is a valid image url and it is available
- preCheckImageLink() {
- // check only if embedVisible is false i.e the image are by default hidden/collapsed
- // if embedVisible is true, the image is rendered, during which image load error is captured
- if (!this.state.embedVisible && this.isLinkImage(this.state.link)) {
- const image = new Image();
- image.src = this.state.link;
-
- image.onload = () => {
- this.handleLinkLoaded();
- };
-
- image.onerror = () => {
- this.handleLinkLoadError();
- };
- }
- }
-
- isLinkImage(link) {
- const regex = /.+\/(.+\.(?:jpg|gif|bmp|png|jpeg))(?:\?.*)?$/i;
- const match = link.match(regex);
- if (match && match[1]) {
- return true;
- }
-
- return false;
- }
-
- isLinkToggleable() {
- const link = this.state.link;
- if (!link) {
- return false;
- }
-
- if (YoutubeVideo.isYoutubeLink(link)) {
- return true;
- }
-
- if (this.isLinkImage(link)) {
- return true;
- }
-
- return false;
- }
-
- handleLinkLoadError() {
- this.setState({
- linkLoadError: true
- });
- }
-
- handleLinkLoaded() {
- this.setState({
- linkLoaded: true
- });
- }
-
- generateToggleableEmbed() {
- const link = this.state.link;
- if (!link) {
- return null;
- }
-
- if (YoutubeVideo.isYoutubeLink(link)) {
- return (
- <YoutubeVideo
- channelId={this.props.post.channel_id}
- link={link}
- show={this.state.embedVisible}
- onLinkLoaded={this.handleLinkLoaded}
- />
- );
- }
-
- if (this.isLinkImage(link)) {
- return (
- <PostImage
- channelId={this.props.post.channel_id}
- link={link}
- onLinkLoadError={this.handleLinkLoadError}
- onLinkLoaded={this.handleLinkLoaded}
- />
- );
- }
-
- return null;
- }
-
- generateStaticEmbed() {
- if (this.props.post.props && this.props.post.props.attachments) {
- return this.getSlackAttachment();
- }
-
- const link = Utils.extractFirstLink(this.props.post.message);
- if (link && Utils.isFeatureEnabled(Constants.PRE_RELEASE_FEATURES.EMBED_PREVIEW) && global.window.mm_config.EnableLinkPreviews === 'true') {
- return (
- <PostAttachmentOpenGraph
- link={link}
- previewCollapsed={this.props.previewCollapsed}
- post={this.props.post}
- />
- );
- }
-
- return null;
- }
-
- render() {
- if (this.isLinkToggleable() && !this.state.linkLoadError) {
- // if message has only one line and starts with a link place toggle in this only line
- // else - place it in new line between message and embed
- const prependToggle = (/^\s*https?:\/\/.*$/).test(this.props.post.message);
-
- const toggle = (
- <a
- key='toggle'
- className={`post__embed-visibility ${prependToggle ? 'pull-left' : ''}`}
- data-expanded={this.state.embedVisible}
- aria-label='Toggle Embed Visibility'
- onClick={this.toggleEmbedVisibility}
- />
- );
- const message = (
- <div key='message'>
- {this.props.message}
- </div>
- );
-
- const contents = [message];
-
- if (this.state.linkLoaded || YoutubeVideo.isYoutubeLink(this.state.link)) {
- if (prependToggle) {
- contents.unshift(toggle);
- } else {
- contents.push(toggle);
- }
- }
-
- if (this.state.embedVisible) {
- contents.push(
- <div
- key='embed'
- className='post__embed-container'
- >
- {this.generateToggleableEmbed()}
- </div>
- );
- }
-
- return (
- <div>
- {contents}
- </div>
- );
- }
-
- const staticEmbed = this.generateStaticEmbed();
-
- if (staticEmbed) {
- return (
- <div>
- {this.props.message}
- {staticEmbed}
- </div>
- );
- }
-
- return this.props.message;
- }
-
- static isEmbedVisible(props) {
- // check first in localstorage, if not present, consider previewCollapsed from the parent component
- return BrowserStore.getItem(`isVisible-${props.post.id}`, props.previewCollapsed.startsWith('false'));
- }
-}
diff --git a/webapp/components/post_view/post_flag_icon.jsx b/webapp/components/post_view/post_flag_icon.jsx
deleted file mode 100644
index 02f8feb53..000000000
--- a/webapp/components/post_view/post_flag_icon.jsx
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {FormattedMessage} from 'react-intl';
-import {Tooltip, OverlayTrigger} from 'react-bootstrap';
-
-import {flagPost, unflagPost} from 'actions/post_actions.jsx';
-import Constants from 'utils/constants.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-function flagToolTip(isFlagged) {
- return (
- <Tooltip id='flagTooltip'>
- <FormattedMessage
- id={isFlagged ? 'flag_post.unflag' : 'flag_post.flag'}
- defaultMessage={isFlagged ? 'Unflag' : 'Flag for follow up'}
- />
- </Tooltip>
- );
-}
-
-function flagIcon(isFlagged) {
- let flagIconSvg = Constants.FLAG_ICON_SVG;
-
- if (isFlagged) {
- flagIconSvg = Constants.FLAG_FILLED_ICON_SVG;
- }
-
- return (
- <span
- className='icon'
- dangerouslySetInnerHTML={{__html: flagIconSvg}}
- />
- );
-}
-
-export default function PostFlagIcon(props) {
- function onFlagPost(e) {
- e.preventDefault();
- flagPost(props.postId);
- }
-
- function onUnflagPost(e) {
- e.preventDefault();
- unflagPost(props.postId);
- }
-
- const flagFunc = props.isFlagged ? onUnflagPost : onFlagPost;
- const flagVisible = props.isFlagged ? 'visible' : '';
-
- let flagIconId = null;
- if (props.idCount > -1) {
- flagIconId = Utils.createSafeId(props.idPrefix + props.idCount);
- }
-
- if (!props.isEphemeral) {
- return (
- <OverlayTrigger
- trigger={['hover', 'focus']}
- key={'flagtooltipkey' + flagVisible}
- delayShow={Constants.OVERLAY_TIME_DELAY}
- placement='top'
- overlay={flagToolTip(props.isFlagged)}
- >
- <a
- id={flagIconId}
- href='#'
- className={'flag-icon__container ' + flagVisible}
- onClick={flagFunc}
- >
- {flagIcon(props.isFlagged)}
- </a>
- </OverlayTrigger>
- );
- }
-
- return null;
-}
-
-PostFlagIcon.propTypes = {
- idPrefix: PropTypes.string.isRequired,
- idCount: PropTypes.number,
- postId: PropTypes.string.isRequired,
- isFlagged: PropTypes.bool.isRequired,
- isEphemeral: PropTypes.bool
-};
-
-PostFlagIcon.defaultProps = {
- idCount: -1,
- postId: '',
- isFlagged: false,
- isEphemeral: false
-};
diff --git a/webapp/components/post_view/post_header/index.js b/webapp/components/post_view/post_header/index.js
deleted file mode 100644
index d7aaef1d5..000000000
--- a/webapp/components/post_view/post_header/index.js
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-
-import {get} from 'mattermost-redux/selectors/entities/preferences';
-import {Preferences} from 'mattermost-redux/constants';
-
-import PostHeader from './post_header.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps,
- displayNameType: get(state, Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', 'false')
- };
-}
-
-export default connect(mapStateToProps)(PostHeader);
diff --git a/webapp/components/post_view/post_header/post_header.jsx b/webapp/components/post_view/post_header/post_header.jsx
deleted file mode 100644
index 0715f047c..000000000
--- a/webapp/components/post_view/post_header/post_header.jsx
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import UserProfile from 'components/user_profile.jsx';
-import PostInfo from 'components/post_view/post_info';
-import {FormattedMessage} from 'react-intl';
-
-import * as PostUtils from 'utils/post_utils.jsx';
-
-import Constants from 'utils/constants.jsx';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-export default class PostHeader extends React.PureComponent {
- static propTypes = {
-
- /*
- * The post to render the header for
- */
- post: PropTypes.object.isRequired,
-
- /*
- * The user who created the post
- */
- user: PropTypes.object,
-
- /*
- * Function called when the comment icon is clicked
- */
- handleCommentClick: PropTypes.func.isRequired,
-
- /*
- * Function called when the post options dropdown is opened
- */
- handleDropdownOpened: PropTypes.func.isRequired,
-
- /*
- * Set to render compactly
- */
- compactDisplay: PropTypes.bool,
-
- /*
- * Set to render the post as if it was part of the previous post
- */
- consecutivePostByUser: PropTypes.bool,
-
- /*
- * The method for displaying the post creator's name
- */
- displayNameType: PropTypes.string,
-
- /*
- * The status of the user who created the post
- */
- status: PropTypes.string,
-
- /*
- * Set if the post creator is currenlty in a WebRTC call
- */
- isBusy: PropTypes.bool,
-
- /*
- * The number of replies in the same thread as this post
- */
- replyCount: PropTypes.number,
-
- /*
- * Post identifiers for selenium tests
- */
- lastPostCount: PropTypes.number,
-
- /**
- * Function to get the post list HTML element
- */
- getPostList: PropTypes.func.isRequired
- }
-
- constructor(props) {
- super(props);
- this.state = {};
- }
-
- render() {
- const post = this.props.post;
- const isSystemMessage = PostUtils.isSystemMessage(post);
-
- let userProfile = (
- <UserProfile
- user={this.props.user}
- displayNameType={this.props.displayNameType}
- status={this.props.status}
- isBusy={this.props.isBusy}
- hasMention={true}
- />
- );
- let botIndicator;
- let colon;
-
- if (post.props && post.props.from_webhook) {
- if (post.props.override_username && global.window.mm_config.EnablePostUsernameOverride === 'true') {
- userProfile = (
- <UserProfile
- user={this.props.user}
- overwriteName={post.props.override_username}
- disablePopover={true}
- />
- );
- } else {
- userProfile = (
- <UserProfile
- user={this.props.user}
- displayNameType={this.props.displayNameType}
- disablePopover={true}
- />
- );
- }
-
- botIndicator = <div className='bot-indicator'>{Constants.BOT_NAME}</div>;
- } else if (isSystemMessage) {
- userProfile = (
- <UserProfile
- user={{}}
- overwriteName={
- <FormattedMessage
- id='post_info.system'
- defaultMessage='System'
- />
- }
- overwriteImage={Constants.SYSTEM_MESSAGE_PROFILE_IMAGE}
- disablePopover={true}
- />
- );
- }
-
- if (this.props.compactDisplay) {
- colon = (<strong className='colon'>{':'}</strong>);
- }
-
- return (
- <div className='post__header'>
- <div className='col col__name'>{userProfile}{colon}</div>
- {botIndicator}
- <div className='col'>
- <PostInfo
- post={post}
- handleCommentClick={this.props.handleCommentClick}
- handleDropdownOpened={this.props.handleDropdownOpened}
- compactDisplay={this.props.compactDisplay}
- lastPostCount={this.props.lastPostCount}
- replyCount={this.props.replyCount}
- consecutivePostByUser={this.props.consecutivePostByUser}
- getPostList={this.props.getPostList}
- />
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/post_view/post_image.jsx b/webapp/components/post_view/post_image.jsx
deleted file mode 100644
index 322742305..000000000
--- a/webapp/components/post_view/post_image.jsx
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import {postListScrollChange} from 'actions/global_actions.jsx';
-
-export default class PostImageEmbed extends React.PureComponent {
- static propTypes = {
-
- /**
- * The link to load the image from
- */
- link: PropTypes.string.isRequired,
-
- /**
- * Function to call when image is loaded
- */
- onLinkLoaded: PropTypes.func,
-
- /**
- * The function to call if image load fails
- */
- onLinkLoadError: PropTypes.func
- }
-
- constructor(props) {
- super(props);
-
- this.handleLoadComplete = this.handleLoadComplete.bind(this);
- this.handleLoadError = this.handleLoadError.bind(this);
-
- this.state = {
- loaded: false,
- errored: false
- };
- }
-
- componentWillMount() {
- this.loadImg(this.props.link);
- }
-
- componentWillReceiveProps(nextProps) {
- if (nextProps.link !== this.props.link) {
- this.setState({
- loaded: false,
- errored: false
- });
- }
- }
-
- componentDidUpdate(prevProps) {
- if (!this.state.loaded && prevProps.link !== this.props.link) {
- this.loadImg(this.props.link);
- }
- }
-
- loadImg(src) {
- const img = new Image();
- img.onload = this.handleLoadComplete;
- img.onerror = this.handleLoadError;
- img.src = src;
- }
-
- handleLoadComplete() {
- this.setState({
- loaded: true,
- errored: false
- });
-
- postListScrollChange();
-
- if (this.props.onLinkLoaded) {
- this.props.onLinkLoaded();
- }
- }
-
- handleLoadError() {
- this.setState({
- errored: true,
- loaded: true
- });
- if (this.props.onLinkLoadError) {
- this.props.onLinkLoadError();
- }
- }
-
- render() {
- if (this.state.errored || !this.state.loaded) {
- return null;
- }
-
- return (
- <div
- className='post__embed-container'
- >
- <img
- className='img-div'
- src={this.props.link}
- />
- </div>
- );
- }
-}
diff --git a/webapp/components/post_view/post_info/index.js b/webapp/components/post_view/post_info/index.js
deleted file mode 100644
index 041080da8..000000000
--- a/webapp/components/post_view/post_info/index.js
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {removePost, addReaction} from 'mattermost-redux/actions/posts';
-
-import {get, getBool} from 'mattermost-redux/selectors/entities/preferences';
-
-import {Preferences} from 'utils/constants.jsx';
-
-import PostInfo from './post_info.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps,
- useMilitaryTime: getBool(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.USE_MILITARY_TIME, false),
- isFlagged: get(state, Preferences.CATEGORY_FLAGGED_POST, ownProps.post.id, null) != null
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- removePost,
- addReaction
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(PostInfo);
diff --git a/webapp/components/post_view/post_info/post_info.jsx b/webapp/components/post_view/post_info/post_info.jsx
deleted file mode 100644
index cc3133764..000000000
--- a/webapp/components/post_view/post_info/post_info.jsx
+++ /dev/null
@@ -1,275 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import PostTime from 'components/post_view/post_time.jsx';
-import PostFlagIcon from 'components/post_view/post_flag_icon.jsx';
-import CommentIcon from 'components/common/comment_icon.jsx';
-import EmojiPickerOverlay from 'components/emoji_picker/emoji_picker_overlay.jsx';
-import DotMenu from 'components/dot_menu';
-
-import * as Utils from 'utils/utils.jsx';
-import * as PostUtils from 'utils/post_utils.jsx';
-import * as ReduxPostUtils from 'mattermost-redux/utils/post_utils';
-import {emitEmojiPosted} from 'actions/post_actions.jsx';
-import Constants from 'utils/constants.jsx';
-import {Posts} from 'mattermost-redux/constants';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {FormattedMessage} from 'react-intl';
-
-export default class PostInfo extends React.PureComponent {
- static propTypes = {
-
- /*
- * The post to render the info for
- */
- post: PropTypes.object.isRequired,
-
- /*
- * Function called when the comment icon is clicked
- */
- handleCommentClick: PropTypes.func.isRequired,
-
- /*
- * Funciton called when the post options dropdown is opened
- */
- handleDropdownOpened: PropTypes.func.isRequired,
-
- /*
- * Set to display in 24 hour format
- */
- useMilitaryTime: PropTypes.bool.isRequired,
-
- /*
- * Set to mark the post as flagged
- */
- isFlagged: PropTypes.bool,
-
- /*
- * The number of replies in the same thread as this post
- */
- replyCount: PropTypes.number,
-
- /*
- * Post identifiers for selenium tests
- */
- lastPostCount: PropTypes.number,
-
- /**
- * Set to render in compact view
- */
- compactDisplay: PropTypes.bool,
-
- /**
- * Function to get the post list HTML element
- */
- getPostList: PropTypes.func.isRequired,
-
- actions: PropTypes.shape({
-
- /*
- * Function to remove the post
- */
- removePost: PropTypes.func.isRequired,
-
- /*
- * Function to add a reaction to the post
- */
- addReaction: PropTypes.func.isRequired
- }).isRequired
- };
-
- constructor(props) {
- super(props);
-
- this.removePost = this.removePost.bind(this);
- this.reactEmojiClick = this.reactEmojiClick.bind(this);
-
- this.state = {
- showEmojiPicker: false,
- reactionPickerOffset: 21
- };
- }
-
- toggleEmojiPicker = () => {
- const showEmojiPicker = !this.state.showEmojiPicker;
-
- this.setState({showEmojiPicker});
- this.props.handleDropdownOpened(showEmojiPicker);
- };
-
- hideEmojiPicker = () => {
- this.setState({showEmojiPicker: false});
- this.props.handleDropdownOpened(false);
- };
-
- removePost() {
- this.props.actions.removePost(this.props.post);
- }
-
- createRemovePostButton() {
- return (
- <a
- href='#'
- className='post__remove theme'
- type='button'
- onClick={this.removePost}
- >
- {'×'}
- </a>
- );
- }
-
- reactEmojiClick(emoji) {
- const pickerOffset = 21;
- this.setState({showEmojiPicker: false, reactionPickerOffset: pickerOffset});
- const emojiName = emoji.name || emoji.aliases[0];
- this.props.actions.addReaction(this.props.post.id, emojiName);
- emitEmojiPosted(emojiName);
- this.props.handleDropdownOpened(false);
- }
-
- getDotMenu = () => {
- return this.refs.dotMenu;
- };
-
- render() {
- const post = this.props.post;
-
- let idCount = -1;
- if (this.props.lastPostCount >= 0 && this.props.lastPostCount < Constants.TEST_ID_COUNT) {
- idCount = this.props.lastPostCount;
- }
-
- const isEphemeral = Utils.isPostEphemeral(post);
- const isSystemMessage = PostUtils.isSystemMessage(post);
-
- let comments = null;
- let react = null;
- if (!isEphemeral && !post.failed && !isSystemMessage) {
- comments = (
- <CommentIcon
- idPrefix='commentIcon'
- idCount={idCount}
- handleCommentClick={this.props.handleCommentClick}
- commentCount={this.props.replyCount}
- id={post.channel_id + '_' + post.id}
- />
- );
-
- if (window.mm_config.EnableEmojiPicker === 'true') {
- react = (
- <span>
- <EmojiPickerOverlay
- show={this.state.showEmojiPicker}
- container={this.props.getPostList}
- target={this.getDotMenu}
- onHide={this.hideEmojiPicker}
- onEmojiClick={this.reactEmojiClick}
- rightOffset={7}
- />
- <a
- href='#'
- className='reacticon__container'
- onClick={this.toggleEmojiPicker}
- >
- <span
- className='icon icon--emoji'
- dangerouslySetInnerHTML={{__html: Constants.EMOJI_ICON_SVG}}
- />
- </a>
- </span>
-
- );
- }
- }
-
- let options;
- if (isEphemeral) {
- options = (
- <div className='col col__remove'>
- {this.createRemovePostButton()}
- </div>
- );
- } else if (!post.failed) {
- const dotMenu = (
- <DotMenu
- idPrefix={Constants.CENTER}
- idCount={idCount}
- post={this.props.post}
- commentCount={this.props.replyCount}
- isFlagged={this.props.isFlagged}
- handleCommentClick={this.props.handleCommentClick}
- handleDropdownOpened={this.props.handleDropdownOpened}
- />
- );
-
- if (PostUtils.shouldShowDotMenu(this.props.post)) {
- options = (
- <div
- ref='dotMenu'
- className='col col__reply'
- >
- {dotMenu}
- {react}
- {comments}
- </div>
- );
- }
- }
-
- let visibleMessage;
- if (isEphemeral && !this.props.compactDisplay && post.state !== Posts.POST_DELETED) {
- visibleMessage = (
- <span className='post__visibility'>
- <FormattedMessage
- id='post_info.message.visible'
- defaultMessage='(Only visible to you)'
- />
- </span>
- );
- }
-
- let pinnedBadge;
- if (post.is_pinned) {
- pinnedBadge = (
- <span className='post__pinned-badge'>
- <FormattedMessage
- id='post_info.pinned'
- defaultMessage='Pinned'
- />
- </span>
- );
- }
-
- // timestamp should not be a permalink if the post has been deleted, is ephemeral message, or is pending
- const isPermalink = !(isEphemeral ||
- Posts.POST_DELETED === this.props.post.state ||
- ReduxPostUtils.isPostPendingOrFailed(this.props.post));
-
- return (
- <div className='post__header--info'>
- <div className='col'>
- <PostTime
- isPermalink={isPermalink}
- eventTime={post.create_at}
- useMilitaryTime={this.props.useMilitaryTime}
- postId={post.id}
- />
- {pinnedBadge}
- {this.state.showEmojiPicker}
- <PostFlagIcon
- idPrefix={'centerPostFlag'}
- idCount={idCount}
- postId={post.id}
- isFlagged={this.props.isFlagged}
- isEphemeral={isEphemeral}
- />
- {visibleMessage}
- </div>
- {options}
- </div>
- );
- }
-}
diff --git a/webapp/components/post_view/post_list.jsx b/webapp/components/post_view/post_list.jsx
deleted file mode 100644
index c13de7096..000000000
--- a/webapp/components/post_view/post_list.jsx
+++ /dev/null
@@ -1,582 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import Post from './post';
-import LoadingScreen from 'components/loading_screen.jsx';
-import FloatingTimestamp from './floating_timestamp.jsx';
-import ScrollToBottomArrows from './scroll_to_bottom_arrows.jsx';
-import NewMessageIndicator from './new_message_indicator.jsx';
-
-import * as UserAgent from 'utils/user_agent.jsx';
-import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-import {createChannelIntroMessage} from 'utils/channel_intro_messages.jsx';
-import DelayedAction from 'utils/delayed_action.jsx';
-import EventTypes from 'utils/event_types.jsx';
-import GlobalEventEmitter from 'utils/global_event_emitter.jsx';
-
-import {FormattedDate, FormattedMessage} from 'react-intl';
-
-import React from 'react';
-import ReactDOM from 'react-dom';
-import PropTypes from 'prop-types';
-
-const CLOSE_TO_BOTTOM_SCROLL_MARGIN = 10;
-const POSTS_PER_PAGE = Constants.POST_CHUNK_SIZE / 2;
-
-export default class PostList extends React.PureComponent {
- static propTypes = {
-
- /**
- * Array of posts in the channel, ordered from oldest to newest
- */
- posts: PropTypes.array,
-
- /**
- * The number of posts that should be rendered
- */
- postVisibility: PropTypes.number,
-
- /**
- * The channel the posts are in
- */
- channel: PropTypes.object.isRequired,
-
- /**
- * The last time the channel was viewed, sets the new message separator
- */
- lastViewedAt: PropTypes.number,
-
- /**
- * Set if more posts are being loaded
- */
- loadingPosts: PropTypes.bool,
-
- /**
- * The user id of the logged in user
- */
- currentUserId: PropTypes.string,
-
- /**
- * Set to focus this post
- */
- focusedPostId: PropTypes.array,
-
- /**
- * Whether to display the channel intro at full width
- */
- fullWidth: PropTypes.bool,
-
- actions: PropTypes.shape({
-
- /**
- * Function to get posts in the channel
- */
- getPosts: PropTypes.func.isRequired,
-
- /**
- * Function to get posts in the channel older than the focused post
- */
- getPostsBefore: PropTypes.func.isRequired,
-
- /**
- * Function to get posts in the channel newer than the focused post
- */
- getPostsAfter: PropTypes.func.isRequired,
-
- /**
- * Function to get the post thread for the focused post
- */
- getPostThread: PropTypes.func.isRequired,
-
- /**
- * Function to increase the number of posts being rendered
- */
- increasePostVisibility: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.scrollStopAction = new DelayedAction(this.handleScrollStop);
-
- this.previousScrollTop = Number.MAX_SAFE_INTEGER;
- this.previousScrollHeight = 0;
- this.previousClientHeight = 0;
- this.atBottom = false;
-
- this.state = {
- atEnd: false,
- unViewedCount: 0,
- isScrolling: false,
- lastViewed: props.lastViewedAt
- };
- }
-
- componentDidMount() {
- this.loadPosts(this.props.channel.id, this.props.focusedPostId);
- GlobalEventEmitter.addListener(EventTypes.POST_LIST_SCROLL_CHANGE, this.handleResize);
-
- window.addEventListener('resize', () => this.handleResize());
- }
-
- componentWillUnmount() {
- GlobalEventEmitter.removeListener(EventTypes.POST_LIST_SCROLL_CHANGE, this.handleResize);
- window.removeEventListener('resize', () => this.handleResize());
- }
-
- componentWillReceiveProps(nextProps) {
- // Focusing on a new post so load posts around it
- if (nextProps.focusedPostId && this.props.focusedPostId !== nextProps.focusedPostId) {
- this.hasScrolledToFocusedPost = false;
- this.hasScrolledToNewMessageSeparator = false;
- this.setState({atEnd: false});
- this.loadPosts(nextProps.channel.id, nextProps.focusedPostId);
- return;
- }
-
- const channel = this.props.channel || {};
- const nextChannel = nextProps.channel || {};
-
- if (nextProps.focusedPostId == null) {
- // Channel changed so load posts for new channel
- if (channel.id !== nextChannel.id) {
- this.hasScrolled = false;
- this.hasScrolledToFocusedPost = false;
- this.hasScrolledToNewMessageSeparator = false;
- this.atBottom = false;
- this.setState({atEnd: false, lastViewed: nextProps.lastViewedAt});
-
- if (nextChannel.id) {
- this.loadPosts(nextChannel.id);
- }
- }
-
- const nextPosts = nextProps.posts || [];
- const posts = this.props.posts || [];
- const hasNewPosts = (posts.length === 0 && nextPosts.length > 0) || (posts.length > 0 && nextPosts.length > 0 && posts[0].id !== nextPosts[0].id);
-
- if (!this.checkBottom() && hasNewPosts) {
- this.setUnreadsBelow(nextPosts, nextProps.currentUserId);
- }
- }
- }
-
- componentWillUpdate() {
- if (this.refs.postlist) {
- this.previousScrollTop = this.refs.postlist.scrollTop;
- this.previousScrollHeight = this.refs.postlist.scrollHeight;
- this.previousClientHeight = this.refs.postlist.clientHeight;
- }
- }
-
- componentDidUpdate(prevProps, prevState) {
- // Do not update scrolling unless posts, visibility or intro message change
- if (this.props.posts === prevProps.posts && this.props.postVisibility === prevProps.postVisibility && this.state.atEnd === prevState.atEnd) {
- return;
- }
-
- const prevPosts = prevProps.posts;
- const posts = this.props.posts;
- const postList = this.refs.postlist;
-
- if (!postList) {
- return;
- }
-
- // Scroll to focused post on first load
- const focusedPost = this.refs[this.props.focusedPostId];
- if (focusedPost && this.props.posts) {
- if (!this.hasScrolledToFocusedPost) {
- const element = ReactDOM.findDOMNode(focusedPost);
- const rect = element.getBoundingClientRect();
- const listHeight = postList.clientHeight / 2;
- postList.scrollTop += rect.top - listHeight;
- } else if (this.previousScrollHeight !== postList.scrollHeight && posts[0].id === prevPosts[0].id) {
- postList.scrollTop = this.previousScrollTop + (postList.scrollHeight - this.previousScrollHeight);
- }
- return;
- }
-
- // Scroll to new message indicator or bottom on first load
- const messageSeparator = this.refs.newMessageSeparator;
- if (messageSeparator && !this.hasScrolledToNewMessageSeparator) {
- const element = ReactDOM.findDOMNode(messageSeparator);
- element.scrollIntoView();
- if (!this.checkBottom()) {
- this.setUnreadsBelow(posts, this.props.currentUserId);
- }
- return;
- } else if (postList && !this.hasScrolledToNewMessageSeparator) {
- postList.scrollTop = postList.scrollHeight;
- this.atBottom = true;
- return;
- }
-
- if (postList && prevPosts && posts && posts[0] && prevPosts[0]) {
- // A new message was posted, so scroll to bottom if user
- // was already scrolled close to bottom
- let doScrollToBottom = false;
- const postId = posts[0].id;
- const prevPostId = prevPosts[0].id;
- const pendingPostId = posts[0].pending_post_id;
- if (postId !== prevPostId && pendingPostId !== prevPostId) {
- // If already scrolled to bottom
- if (this.atBottom) {
- doScrollToBottom = true;
- }
-
- // If new post was ephemeral
- if (Utils.isPostEphemeral(posts[0])) {
- doScrollToBottom = true;
- }
- }
-
- if (doScrollToBottom) {
- this.atBottom = true;
- postList.scrollTop = postList.scrollHeight;
- return;
- }
-
- // New posts added at the top, maintain scroll position
- if (this.previousScrollHeight !== postList.scrollHeight && posts[0].id === prevPosts[0].id) {
- postList.scrollTop = this.previousScrollTop + (postList.scrollHeight - this.previousScrollHeight);
- }
- }
- }
-
- setUnreadsBelow = (posts, currentUserId) => {
- const unViewedCount = posts.reduce((count, post) => {
- if (post.create_at > this.state.lastViewed &&
- post.user_id !== currentUserId &&
- post.state !== Constants.POST_DELETED) {
- return count + 1;
- }
- return count;
- }, 0);
- this.setState({unViewedCount});
- }
-
- handleScrollStop = () => {
- this.setState({
- isScrolling: false
- });
- }
-
- checkBottom = () => {
- if (!this.refs.postlist) {
- return true;
- }
-
- // No scroll bar so we're at the bottom
- if (this.refs.postlist.scrollHeight <= this.refs.postlist.clientHeight) {
- return true;
- }
-
- return this.refs.postlist.clientHeight + this.refs.postlist.scrollTop >= this.refs.postlist.scrollHeight - CLOSE_TO_BOTTOM_SCROLL_MARGIN;
- }
-
- handleResize = (forceScrollToBottom) => {
- const postList = this.refs.postlist;
- const messageSeparator = this.refs.newMessageSeparator;
- const doScrollToBottom = this.atBottom || forceScrollToBottom;
-
- if (postList) {
- if (doScrollToBottom) {
- postList.scrollTop = postList.scrollHeight;
- } else if (!this.hasScrolled && messageSeparator) {
- const element = ReactDOM.findDOMNode(messageSeparator);
- element.scrollIntoView();
- }
-
- this.previousScrollHeight = postList.scrollHeight;
- this.previousScrollTop = postList.scrollTop;
- this.previousClientHeight = postList.clientHeight;
-
- this.atBottom = this.checkBottom();
- }
- }
-
- loadPosts = async (channelId, focusedPostId) => {
- let posts;
- if (focusedPostId) {
- const getPostThreadAsync = this.props.actions.getPostThread(focusedPostId, false);
- const getPostsBeforeAsync = this.props.actions.getPostsBefore(channelId, focusedPostId, 0, POSTS_PER_PAGE);
- const getPostsAfterAsync = this.props.actions.getPostsAfter(channelId, focusedPostId, 0, POSTS_PER_PAGE);
-
- posts = await getPostsBeforeAsync;
- await getPostsAfterAsync;
- await getPostThreadAsync;
-
- this.hasScrolledToFocusedPost = true;
- } else {
- posts = await this.props.actions.getPosts(channelId, 0, POSTS_PER_PAGE);
- this.hasScrolledToNewMessageSeparator = true;
- }
-
- if (posts && posts.order.length < POSTS_PER_PAGE) {
- this.setState({atEnd: true});
- }
- }
-
- loadMorePosts = (e) => {
- if (e) {
- e.preventDefault();
- }
-
- this.props.actions.increasePostVisibility(this.props.channel.id, this.props.focusedPostId).then((moreToLoad) => {
- this.setState({atEnd: !moreToLoad && this.props.posts.length < this.props.postVisibility});
- });
- }
-
- handleScroll = () => {
- // Only count as user scroll if we've already performed our first load scroll
- this.hasScrolled = this.hasScrolledToNewMessageSeparator || this.hasScrolledToFocusedPost;
- if (!this.refs.postlist) {
- return;
- }
-
- this.previousScrollTop = this.refs.postlist.scrollTop;
-
- if (this.refs.postlist.scrollHeight === this.previousScrollHeight) {
- this.atBottom = this.checkBottom();
- }
-
- this.updateFloatingTimestamp();
-
- if (!this.state.isScrolling) {
- this.setState({
- isScrolling: true
- });
- }
-
- if (this.checkBottom()) {
- this.setState({
- lastViewed: new Date().getTime(),
- unViewedCount: 0,
- isScrolling: false
- });
- }
-
- this.scrollStopAction.fireAfter(Constants.SCROLL_DELAY);
- }
-
- updateFloatingTimestamp = () => {
- // skip this in non-mobile view since that's when the timestamp is visible
- if (!Utils.isMobile()) {
- return;
- }
-
- if (this.props.posts) {
- // iterate through posts starting at the bottom since users are more likely to be viewing newer posts
- for (let i = 0; i < this.props.posts.length; i++) {
- const post = this.props.posts[i];
- const element = this.refs[post.id];
-
- if (!element || !element.domNode || element.domNode.offsetTop + element.domNode.clientHeight <= this.refs.postlist.scrollTop) {
- // this post is off the top of the screen so the last one is at the top of the screen
- let topPost;
-
- if (i > 0) {
- topPost = this.props.posts[i - 1];
- } else {
- // the first post we look at should always be on the screen, but handle that case anyway
- topPost = post;
- }
-
- if (!this.state.topPost || topPost.id !== this.state.topPost.id) {
- this.setState({
- topPost
- });
- }
-
- break;
- }
- }
- }
- }
-
- scrollToBottom = () => {
- if (this.refs.postlist) {
- this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight;
- }
- }
-
- createPosts = (posts) => {
- const postCtls = [];
- let previousPostDay = new Date(0);
- const currentUserId = this.props.currentUserId;
- const lastViewed = this.props.lastViewedAt || 0;
-
- let renderedLastViewed = false;
-
- for (let i = posts.length - 1; i >= 0; i--) {
- const post = posts[i];
-
- if (post == null) {
- continue;
- }
-
- const postCtl = (
- <Post
- ref={post.id}
- key={'post ' + (post.id || post.pending_post_id)}
- post={post}
- lastPostCount={(i >= 0 && i < Constants.TEST_ID_COUNT) ? i : -1}
- getPostList={this.getPostList}
- />
- );
-
- const currentPostDay = Utils.getDateForUnixTicks(post.create_at);
- if (currentPostDay.toDateString() !== previousPostDay.toDateString()) {
- postCtls.push(
- <div
- key={currentPostDay.toDateString()}
- className='date-separator'
- >
- <hr className='separator__hr'/>
- <div className='separator__text'>
- <FormattedDate
- value={currentPostDay}
- weekday='short'
- month='short'
- day='2-digit'
- year='numeric'
- />
- </div>
- </div>
- );
- }
-
- if (post.user_id !== currentUserId &&
- lastViewed !== 0 &&
- post.create_at > lastViewed &&
- !Utils.isPostEphemeral(post) &&
- !renderedLastViewed) {
- renderedLastViewed = true;
-
- // Temporary fix to solve ie11 rendering issue
- let newSeparatorId = '';
- if (!UserAgent.isInternetExplorer()) {
- newSeparatorId = 'new_message_' + post.id;
- }
- postCtls.push(
- <div
- id={newSeparatorId}
- key='unviewed'
- ref='newMessageSeparator'
- className='new-separator'
- >
- <hr
- className='separator__hr'
- />
- <div className='separator__text'>
- <FormattedMessage
- id='posts_view.newMsg'
- defaultMessage='New Messages'
- />
- </div>
- </div>
- );
- }
-
- postCtls.push(postCtl);
- previousPostDay = currentPostDay;
- }
-
- return postCtls;
- }
-
- getPostList = () => {
- return this.refs.postlist;
- }
-
- render() {
- const posts = this.props.posts;
- const channel = this.props.channel;
-
- if (posts == null || channel == null) {
- return (
- <div id='post-list'>
- <LoadingScreen
- position='absolute'
- key='loading'
- />
- </div>
- );
- }
-
- let topRow;
- if (this.state.atEnd) {
- topRow = createChannelIntroMessage(channel, this.props.fullWidth);
- } else if (this.props.postVisibility >= Constants.MAX_POST_VISIBILITY) {
- topRow = (
- <div className='post-list__loading post-list__loading-search'>
- <FormattedMessage
- id='posts_view.maxLoaded'
- defaultMessage='Looking for a specific message? Try searching for it'
- />
- </div>
- );
- } else {
- topRow = (
- <a
- ref='loadmoretop'
- className='more-messages-text theme'
- href='#'
- onClick={this.loadMorePosts}
- >
- <FormattedMessage
- id='posts_view.loadMore'
- defaultMessage='Load more messages'
- />
- </a>
- );
- }
-
- const topPostCreateAt = this.state.topPost ? this.state.topPost.create_at : 0;
-
- let postVisibility = this.props.postVisibility;
-
- // In focus mode there's an extra (Constants.POST_CHUNK_SIZE / 2) posts to show
- if (this.props.focusedPostId) {
- postVisibility += Constants.POST_CHUNK_SIZE / 2;
- }
-
- return (
- <div id='post-list'>
- <FloatingTimestamp
- isScrolling={this.state.isScrolling}
- isMobile={Utils.isMobile()}
- createAt={topPostCreateAt}
- />
- <ScrollToBottomArrows
- isScrolling={this.state.isScrolling}
- atBottom={this.atBottom}
- onClick={this.scrollToBottom}
- />
- <NewMessageIndicator
- newMessages={this.state.unViewedCount}
- onClick={this.scrollToBottom}
- />
- <div
- ref='postlist'
- className='post-list-holder-by-time'
- key={'postlist-' + channel.id}
- onScroll={this.handleScroll}
- >
- <div className='post-list__table'>
- <div
- ref='postlistcontent'
- className='post-list__content'
- >
- {topRow}
- {this.createPosts(posts.slice(0, postVisibility))}
- </div>
- </div>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/post_view/post_message_view/index.js b/webapp/components/post_view/post_message_view/index.js
deleted file mode 100644
index 84682eb89..000000000
--- a/webapp/components/post_view/post_message_view/index.js
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {getCustomEmojisByName} from 'mattermost-redux/selectors/entities/emojis';
-import {getBool} from 'mattermost-redux/selectors/entities/preferences';
-import {getCurrentUserMentionKeys, getUsersByUsername} from 'mattermost-redux/selectors/entities/users';
-
-import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams';
-
-import {Preferences} from 'mattermost-redux/constants';
-import {getSiteURL} from 'utils/url.jsx';
-
-import {EmojiMap} from 'stores/emoji_store.jsx';
-
-import PostMessageView from './post_message_view.jsx';
-
-function makeMapStateToProps() {
- let emojiMap;
- let oldCustomEmoji;
-
- return function mapStateToProps(state, ownProps) {
- const newCustomEmoji = getCustomEmojisByName(state);
- if (newCustomEmoji !== oldCustomEmoji) {
- emojiMap = new EmojiMap(newCustomEmoji);
- }
- oldCustomEmoji = newCustomEmoji;
-
- return {
- ...ownProps,
- emojis: emojiMap,
- enableFormatting: getBool(state, Preferences.CATEGORY_ADVANCED_SETTINGS, 'formatting', true),
- mentionKeys: getCurrentUserMentionKeys(state),
- usernameMap: getUsersByUsername(state),
- team: getCurrentTeam(state),
- siteUrl: getSiteURL()
- };
- };
-}
-
-export default connect(makeMapStateToProps)(PostMessageView);
diff --git a/webapp/components/post_view/post_message_view/post_message_view.jsx b/webapp/components/post_view/post_message_view/post_message_view.jsx
deleted file mode 100644
index 348748450..000000000
--- a/webapp/components/post_view/post_message_view/post_message_view.jsx
+++ /dev/null
@@ -1,215 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {Parser, ProcessNodeDefinitions} from 'html-to-react';
-import PropTypes from 'prop-types';
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-
-import AtMention from 'components/at_mention';
-import MarkdownImage from 'components/markdown_image';
-
-import store from 'stores/redux_store.jsx';
-
-import * as PostUtils from 'utils/post_utils.jsx';
-import * as TextFormatting from 'utils/text_formatting.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import {getChannelsNameMapInCurrentTeam} from 'mattermost-redux/selectors/entities/channels';
-import {Posts} from 'mattermost-redux/constants';
-
-import {renderSystemMessage} from './system_message_helpers.jsx';
-
-export default class PostMessageView extends React.PureComponent {
- static propTypes = {
-
- /*
- * The post to render the message for
- */
- post: PropTypes.object.isRequired,
-
- /*
- * Object using emoji names as keys with custom emojis as the values
- */
- emojis: PropTypes.object.isRequired,
-
- /*
- * The team the post was made in
- */
- team: PropTypes.object.isRequired,
-
- /*
- * Set to enable Markdown formatting
- */
- enableFormatting: PropTypes.bool,
-
- /*
- * An array of words that can be used to mention a user
- */
- mentionKeys: PropTypes.arrayOf(PropTypes.string),
-
- /*
- * The URL that the app is hosted on
- */
- siteUrl: PropTypes.string,
-
- /*
- * Options specific to text formatting
- */
- options: PropTypes.object,
-
- /*
- * Post identifiers for selenium tests
- */
- lastPostCount: PropTypes.number,
-
- /**
- * Set to render post body compactly
- */
- compactDisplay: PropTypes.bool,
-
- /**
- * Flags if the post_message_view is for the RHS (Reply).
- */
- isRHS: PropTypes.bool,
-
- /**
- * Flags if the post_message_view is for the RHS (Reply).
- */
- hasMention: PropTypes.bool
- };
-
- static defaultProps = {
- options: {},
- mentionKeys: [],
- isRHS: false,
- hasMention: false
- };
-
- renderDeletedPost() {
- return (
- <p>
- <FormattedMessage
- id='post_body.deleted'
- defaultMessage='(message deleted)'
- />
- </p>
- );
- }
-
- renderEditedIndicator() {
- if (!PostUtils.isEdited(this.props.post)) {
- return null;
- }
-
- return (
- <span className='post-edited-indicator'>
- <FormattedMessage
- id='post_message_view.edited'
- defaultMessage='(edited)'
- />
- </span>
- );
- }
-
- postMessageHtmlToComponent(html) {
- const parser = new Parser();
- const attrib = 'data-mention';
- const processNodeDefinitions = new ProcessNodeDefinitions(React);
-
- function isValidNode() {
- return true;
- }
-
- const processingInstructions = [
- {
- replaceChildren: true,
- shouldProcessNode: (node) => node.attribs && node.attribs[attrib],
- processNode: (node) => {
- const mentionName = node.attribs[attrib];
-
- return (
- <AtMention
- mentionName={mentionName}
- isRHS={this.props.isRHS}
- hasMention={this.props.hasMention}
- />
- );
- }
- },
- {
- shouldProcessNode: (node) => node.type === 'tag' && node.name === 'img',
- processNode: (node) => {
- const {
- class: className,
- ...attribs
- } = node.attribs;
-
- return (
- <MarkdownImage
- className={className}
- {...attribs}
- />
- );
- }
- },
- {
- shouldProcessNode: () => true,
- processNode: processNodeDefinitions.processDefaultNode
- }
- ];
-
- return parser.parseWithInstructions(html, isValidNode, processingInstructions);
- }
-
- render() {
- if (this.props.post.state === Posts.POST_DELETED) {
- return this.renderDeletedPost();
- }
-
- if (!this.props.enableFormatting) {
- return <span>{this.props.post.message}</span>;
- }
-
- const options = Object.assign({}, this.props.options, {
- emojis: this.props.emojis,
- siteURL: this.props.siteUrl,
- mentionKeys: this.props.mentionKeys,
- atMentions: true,
- channelNamesMap: getChannelsNameMapInCurrentTeam(store.getState()),
- team: this.props.team
- });
-
- const renderedSystemMessage = renderSystemMessage(this.props.post, options);
- if (renderedSystemMessage) {
- return <div>{renderedSystemMessage}</div>;
- }
-
- let postId = null;
- if (this.props.lastPostCount >= 0) {
- postId = Utils.createSafeId('lastPostMessageText' + this.props.lastPostCount);
- }
-
- let message = this.props.post.message;
- const isEphemeral = Utils.isPostEphemeral(this.props.post);
- if (this.props.compactDisplay && isEphemeral) {
- const visibleMessage = Utils.localizeMessage('post_info.message.visible.compact', ' (Only visible to you)');
- message = message.concat(visibleMessage);
- }
- const htmlFormattedText = TextFormatting.formatText(message, options);
- const postMessageComponent = this.postMessageHtmlToComponent(htmlFormattedText);
-
- return (
- <div>
- <span
- id={postId}
- className='post-message__text'
- onClick={Utils.handleFormattedTextClick}
- >
- {postMessageComponent}
- </span>
- {this.renderEditedIndicator()}
- </div>
- );
- }
-}
diff --git a/webapp/components/post_view/post_message_view/system_message_helpers.jsx b/webapp/components/post_view/post_message_view/system_message_helpers.jsx
deleted file mode 100644
index c134e1a7a..000000000
--- a/webapp/components/post_view/post_message_view/system_message_helpers.jsx
+++ /dev/null
@@ -1,232 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-
-import {PostTypes} from 'utils/constants.jsx';
-import {formatText} from 'utils/text_formatting.jsx';
-
-function renderUsername(value, options) {
- return renderFormattedText(value, {...options, markdown: false});
-}
-
-function renderFormattedText(value, options) {
- return <span dangerouslySetInnerHTML={{__html: formatText(value, options)}}/>;
-}
-
-function renderJoinChannelMessage(post, options) {
- const username = renderUsername(post.props.username, options);
-
- return (
- <FormattedMessage
- id='api.channel.join_channel.post_and_forget'
- defaultMessage='{username} has joined the channel.'
- values={{username}}
- />
- );
-}
-
-function renderLeaveChannelMessage(post, options) {
- const username = renderUsername(post.props.username, options);
-
- return (
- <FormattedMessage
- id='api.channel.leave.left'
- defaultMessage='{username} has left the channel.'
- values={{username}}
- />
- );
-}
-
-function renderAddToChannelMessage(post, options) {
- const username = renderUsername(post.props.username, options);
- const addedUsername = renderUsername(post.props.addedUsername, options);
-
- return (
- <FormattedMessage
- id='api.channel.add_member.added'
- defaultMessage='{addedUsername} added to the channel by {username}'
- values={{
- username,
- addedUsername
- }}
- />
- );
-}
-
-function renderRemoveFromChannelMessage(post, options) {
- const removedUsername = renderUsername(post.props.removedUsername, options);
-
- return (
- <FormattedMessage
- id='api.channel.remove_member.removed'
- defaultMessage='{removedUsername} was removed from the channel'
- values={{
- removedUsername
- }}
- />
- );
-}
-
-function renderHeaderChangeMessage(post, options) {
- if (!post.props.username) {
- return null;
- }
-
- const headerOptions = {
- ...options,
- singleline: true
- };
-
- const username = renderUsername(post.props.username, options);
- const oldHeader = post.props.old_header ? renderFormattedText(post.props.old_header, headerOptions) : null;
- const newHeader = post.props.new_header ? renderFormattedText(post.props.new_header, headerOptions) : null;
-
- if (post.props.new_header) {
- if (post.props.old_header) {
- return (
- <FormattedMessage
- id='api.channel.post_update_channel_header_message_and_forget.updated_from'
- defaultMessage='{username} updated the channel header from: {old} to: {new}'
- values={{
- username,
- old: oldHeader,
- new: newHeader
- }}
- />
- );
- }
-
- return (
- <FormattedMessage
- id='api.channel.post_update_channel_header_message_and_forget.updated_to'
- defaultMessage='{username} updated the channel header to: {new}'
- values={{
- username,
- new: newHeader
- }}
- />
- );
- } else if (post.props.old_header) {
- return (
- <FormattedMessage
- id='api.channel.post_update_channel_header_message_and_forget.removed'
- defaultMessage='{username} removed the channel header (was: {old})'
- values={{
- username,
- old: oldHeader
- }}
- />
- );
- }
-
- return null;
-}
-
-function renderDisplayNameChangeMessage(post, options) {
- if (!(post.props.username && post.props.old_displayname && post.props.new_displayname)) {
- return null;
- }
-
- const username = renderUsername(post.props.username, options);
- const oldDisplayName = post.props.old_displayname;
- const newDisplayName = post.props.new_displayname;
-
- return (
- <FormattedMessage
- id='api.channel.post_update_channel_displayname_message_and_forget.updated_from'
- defaultMessage='{username} updated the channel display name from: {old} to: {new}'
- values={{
- username,
- old: oldDisplayName,
- new: newDisplayName
- }}
- />
- );
-}
-
-function renderPurposeChangeMessage(post, options) {
- if (!post.props.username) {
- return null;
- }
-
- const username = renderUsername(post.props.username, options);
- const oldPurpose = post.props.old_purpose;
- const newPurpose = post.props.new_purpose;
-
- if (post.props.new_purpose) {
- if (post.props.old_purpose) {
- return (
- <FormattedMessage
- id='app.channel.post_update_channel_purpose_message.updated_from'
- defaultMessage='{username} updated the channel purpose from: {old} to: {new}'
- values={{
- username,
- old: oldPurpose,
- new: newPurpose
- }}
- />
- );
- }
-
- return (
- <FormattedMessage
- id='app.channel.post_update_channel_purpose_message.updated_to'
- defaultMessage='{username} updated the channel purpose to: {new}'
- values={{
- username,
- new: newPurpose
- }}
- />
- );
- } else if (post.props.old_purpose) {
- return (
- <FormattedMessage
- id='app.channel.post_update_channel_purpose_message.removed'
- defaultMessage='{username} removed the channel purpose (was: {old})'
- values={{
- username,
- old: oldPurpose
- }}
- />
- );
- }
-
- return null;
-}
-
-function renderChannelDeletedMessage(post, options) {
- if (!post.props.username) {
- return null;
- }
-
- const username = renderUsername(post.props.username, options);
-
- return (
- <FormattedMessage
- id='api.channel.delete_channel.archived'
- defaultMessage='{username} has archived the channel.'
- values={{username}}
- />
- );
-}
-
-const systemMessageRenderers = {
- [PostTypes.JOIN_CHANNEL]: renderJoinChannelMessage,
- [PostTypes.LEAVE_CHANNEL]: renderLeaveChannelMessage,
- [PostTypes.ADD_TO_CHANNEL]: renderAddToChannelMessage,
- [PostTypes.REMOVE_FROM_CHANNEL]: renderRemoveFromChannelMessage,
- [PostTypes.HEADER_CHANGE]: renderHeaderChangeMessage,
- [PostTypes.DISPLAYNAME_CHANGE]: renderDisplayNameChangeMessage,
- [PostTypes.PURPOSE_CHANGE]: renderPurposeChangeMessage,
- [PostTypes.CHANNEL_DELETED]: renderChannelDeletedMessage
-};
-
-export function renderSystemMessage(post, options) {
- if (!systemMessageRenderers[post.type]) {
- return null;
- }
-
- return systemMessageRenderers[post.type](post, options);
-}
diff --git a/webapp/components/post_view/post_time.jsx b/webapp/components/post_view/post_time.jsx
deleted file mode 100644
index 2619c6807..000000000
--- a/webapp/components/post_view/post_time.jsx
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import Constants from 'utils/constants.jsx';
-import {getDateForUnixTicks, isMobile, updateWindowDimensions} from 'utils/utils.jsx';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {Link} from 'react-router/es6';
-import TeamStore from 'stores/team_store.jsx';
-
-export default class PostTime extends React.PureComponent {
- static propTypes = {
-
- /*
- * If true, time will be rendered as a permalink to the post
- */
- isPermalink: PropTypes.bool.isRequired,
-
- /*
- * The time to display
- */
- eventTime: PropTypes.number.isRequired,
-
- /*
- * Set to display using 24 hour format
- */
- useMilitaryTime: PropTypes.bool,
-
- /*
- * The post id of posting being rendered
- */
- postId: PropTypes.string
- };
-
- static defaultProps = {
- eventTime: 0,
- useMilitaryTime: false
- };
-
- constructor(props) {
- super(props);
-
- this.state = {
- currentTeamDisplayName: TeamStore.getCurrent().name,
- width: '',
- height: ''
- };
- }
-
- componentDidMount() {
- this.intervalId = setInterval(() => {
- this.forceUpdate();
- }, Constants.TIME_SINCE_UPDATE_INTERVAL);
- window.addEventListener('resize', () => {
- updateWindowDimensions(this);
- });
- }
-
- componentWillUnmount() {
- clearInterval(this.intervalId);
- window.removeEventListener('resize', () => {
- updateWindowDimensions(this);
- });
- }
-
- renderTimeTag() {
- const date = getDateForUnixTicks(this.props.eventTime);
-
- return (
- <time
- className='post__time'
- dateTime={date.toISOString()}
- title={date}
- >
- {date.toLocaleString('en', {hour: '2-digit', minute: '2-digit', hour12: !this.props.useMilitaryTime})}
- </time>
- );
- }
-
- render() {
- if (isMobile() || !this.props.isPermalink) {
- return this.renderTimeTag();
- }
-
- return (
- <Link
- to={`/${this.state.currentTeamDisplayName}/pl/${this.props.postId}`}
- target='_blank'
- className='post__permalink'
- >
- {this.renderTimeTag()}
- </Link>
- );
- }
-}
diff --git a/webapp/components/post_view/reaction/index.js b/webapp/components/post_view/reaction/index.js
deleted file mode 100644
index 74edd1324..000000000
--- a/webapp/components/post_view/reaction/index.js
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getCurrentUserId, makeGetProfilesForReactions} from 'mattermost-redux/selectors/entities/users';
-import {getMissingProfilesByIds} from 'mattermost-redux/actions/users';
-import {addReaction, removeReaction} from 'mattermost-redux/actions/posts';
-import {getEmojiImageUrl} from 'mattermost-redux/utils/emoji_utils';
-import * as Emoji from 'utils/emoji.jsx';
-
-import Reaction from './reaction.jsx';
-
-function makeMapStateToProps() {
- const getProfilesForReactions = makeGetProfilesForReactions();
-
- return function mapStateToProps(state, ownProps) {
- const profiles = getProfilesForReactions(state, ownProps.reactions);
- let emoji;
- if (Emoji.EmojiIndicesByAlias.has(ownProps.emojiName)) {
- emoji = Emoji.Emojis[Emoji.EmojiIndicesByAlias.get(ownProps.emojiName)];
- } else {
- emoji = ownProps.emojis.get(ownProps.emojiName);
- }
-
- let emojiImageUrl = '';
- if (emoji) {
- emojiImageUrl = getEmojiImageUrl(emoji);
- }
-
- return {
- ...ownProps,
- profiles,
- otherUsersCount: ownProps.reactions.length - profiles.length,
- currentUserId: getCurrentUserId(state),
- reactionCount: ownProps.reactions.length,
- emojiImageUrl
- };
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- addReaction,
- removeReaction,
- getMissingProfilesByIds
- }, dispatch)
- };
-}
-
-export default connect(makeMapStateToProps, mapDispatchToProps)(Reaction);
diff --git a/webapp/components/post_view/reaction/reaction.jsx b/webapp/components/post_view/reaction/reaction.jsx
deleted file mode 100644
index 673f8fd7f..000000000
--- a/webapp/components/post_view/reaction/reaction.jsx
+++ /dev/null
@@ -1,254 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {OverlayTrigger, Tooltip} from 'react-bootstrap';
-import {FormattedMessage} from 'react-intl';
-
-import * as Utils from 'utils/utils.jsx';
-
-export default class Reaction extends React.PureComponent {
- static propTypes = {
-
- /*
- * The post to render the reaction for
- */
- post: PropTypes.object.isRequired,
-
- /*
- * The user id of the logged in user
- */
- currentUserId: PropTypes.string.isRequired,
-
- /*
- * The name of the emoji for the reaction
- */
- emojiName: PropTypes.string.isRequired,
-
- /*
- * The number of reactions to this post for this emoji
- */
- reactionCount: PropTypes.number.isRequired,
-
- /*
- * Array of users who reacted to this post
- */
- profiles: PropTypes.array.isRequired,
-
- /*
- * The number of users not in the profile list who have reacted with this emoji
- */
- otherUsersCount: PropTypes.number.isRequired,
-
- /*
- * Array of reactions by user
- */
- reactions: PropTypes.arrayOf(PropTypes.object).isRequired,
-
- /*
- * The URL of the emoji image
- */
- emojiImageUrl: PropTypes.string.isRequired,
-
- actions: PropTypes.shape({
-
- /*
- * Function to add a reaction to a post
- */
- addReaction: PropTypes.func.isRequired,
-
- /*
- * Function to get non-loaded profiles by id
- */
- getMissingProfilesByIds: PropTypes.func.isRequired,
-
- /*
- * Function to remove a reaction from a post
- */
- removeReaction: PropTypes.func.isRequired
- })
- }
-
- constructor(props) {
- super(props);
-
- this.addReaction = this.addReaction.bind(this);
- this.removeReaction = this.removeReaction.bind(this);
- }
-
- addReaction(e) {
- e.preventDefault();
- this.props.actions.addReaction(this.props.post.id, this.props.emojiName);
- }
-
- removeReaction(e) {
- e.preventDefault();
- this.props.actions.removeReaction(this.props.post.id, this.props.emojiName);
- }
-
- loadMissingProfiles = () => {
- const ids = this.props.reactions.map((reaction) => reaction.user_id);
- this.props.actions.getMissingProfilesByIds(ids);
- }
-
- render() {
- if (!this.props.emojiImageUrl) {
- return null;
- }
-
- let currentUserReacted = false;
- const users = [];
- const otherUsersCount = this.props.otherUsersCount;
- for (const user of this.props.profiles) {
- if (user.id === this.props.currentUserId) {
- currentUserReacted = true;
- } else {
- users.push(Utils.displayUsernameForUser(user));
- }
- }
-
- // Sort users in alphabetical order with "you" being first if the current user reacted
- users.sort();
- if (currentUserReacted) {
- users.unshift(Utils.localizeMessage('reaction.you', 'You'));
- }
-
- let names;
- if (otherUsersCount > 0) {
- if (users.length > 0) {
- names = (
- <FormattedMessage
- id='reaction.usersAndOthersReacted'
- defaultMessage='{users} and {otherUsers, number} other {otherUsers, plural, one {user} other {users}}'
- values={{
- users: users.join(', '),
- otherUsers: otherUsersCount
- }}
- />
- );
- } else {
- names = (
- <FormattedMessage
- id='reaction.othersReacted'
- defaultMessage='{otherUsers, number} {otherUsers, plural, one {user} other {users}}'
- values={{
- otherUsers: otherUsersCount
- }}
- />
- );
- }
- } else if (users.length > 1) {
- names = (
- <FormattedMessage
- id='reaction.usersReacted'
- defaultMessage='{users} and {lastUser}'
- values={{
- users: users.slice(0, -1).join(', '),
- lastUser: users[users.length - 1]
- }}
- />
- );
- } else {
- names = users[0];
- }
-
- let reactionVerb;
- if (users.length + otherUsersCount > 1) {
- if (currentUserReacted) {
- reactionVerb = (
- <FormattedMessage
- id='reaction.reactionVerb.youAndUsers'
- defaultMessage='reacted'
- />
- );
- } else {
- reactionVerb = (
- <FormattedMessage
- id='reaction.reactionVerb.users'
- defaultMessage='reacted'
- />
- );
- }
- } else if (currentUserReacted) {
- reactionVerb = (
- <FormattedMessage
- id='reaction.reactionVerb.you'
- defaultMessage='reacted'
- />
- );
- } else {
- reactionVerb = (
- <FormattedMessage
- id='reaction.reactionVerb.user'
- defaultMessage='reacted'
- />
- );
- }
-
- const tooltip = (
- <FormattedMessage
- id='reaction.reacted'
- defaultMessage='{users} {reactionVerb} with {emoji}'
- values={{
- users: <b>{names}</b>,
- reactionVerb,
- emoji: <b>{':' + this.props.emojiName + ':'}</b>
- }}
- />
- );
-
- let handleClick;
- let clickTooltip;
- let className = 'post-reaction';
- if (currentUserReacted) {
- handleClick = this.removeReaction;
- clickTooltip = (
- <FormattedMessage
- id='reaction.clickToRemove'
- defaultMessage='(click to remove)'
- />
- );
-
- className += ' post-reaction--current-user';
- } else {
- handleClick = this.addReaction;
- clickTooltip = (
- <FormattedMessage
- id='reaction.clickToAdd'
- defaultMessage='(click to add)'
- />
- );
- }
-
- return (
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={1000}
- placement='top'
- shouldUpdatePosition={true}
- overlay={
- <Tooltip id={`${this.props.post.id}-${this.props.emojiName}-reaction`}>
- {tooltip}
- <br/>
- {clickTooltip}
- </Tooltip>
- }
- onEnter={this.loadMissingProfiles}
- >
- <div
- className={className}
- onClick={handleClick}
- >
- <span
- className='post-reaction__emoji emoticon'
- style={{backgroundImage: 'url(' + this.props.emojiImageUrl + ')'}}
- />
- <span className='post-reaction__count'>
- {this.props.reactionCount}
- </span>
- </div>
- </OverlayTrigger>
- );
- }
-}
diff --git a/webapp/components/post_view/reaction_list/index.js b/webapp/components/post_view/reaction_list/index.js
deleted file mode 100644
index ee807ca88..000000000
--- a/webapp/components/post_view/reaction_list/index.js
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {makeGetReactionsForPost} from 'mattermost-redux/selectors/entities/posts';
-import {getCustomEmojisByName} from 'mattermost-redux/selectors/entities/emojis';
-
-import * as Actions from 'mattermost-redux/actions/posts';
-
-import ReactionList from './reaction_list.jsx';
-
-function makeMapStateToProps() {
- const getReactionsForPost = makeGetReactionsForPost();
-
- return function mapStateToProps(state, ownProps) {
- return {
- ...ownProps,
- reactions: getReactionsForPost(state, ownProps.post.id),
- emojis: getCustomEmojisByName(state)
- };
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getReactionsForPost: Actions.getReactionsForPost
- }, dispatch)
- };
-}
-
-export default connect(makeMapStateToProps, mapDispatchToProps)(ReactionList);
diff --git a/webapp/components/post_view/reaction_list/reaction_list.jsx b/webapp/components/post_view/reaction_list/reaction_list.jsx
deleted file mode 100644
index 4d2f3a5fc..000000000
--- a/webapp/components/post_view/reaction_list/reaction_list.jsx
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import {postListScrollChange} from 'actions/global_actions.jsx';
-
-import Reaction from 'components/post_view/reaction';
-
-export default class ReactionListView extends React.PureComponent {
- static propTypes = {
-
- /**
- * The post to render reactions for
- */
- post: PropTypes.object.isRequired,
-
- /**
- * The reactions to render
- */
- reactions: PropTypes.arrayOf(PropTypes.object),
-
- /**
- * The emojis for the different reactions
- */
- emojis: PropTypes.object.isRequired,
- actions: PropTypes.shape({
-
- /**
- * Function to get reactions for a post
- */
- getReactionsForPost: PropTypes.func.isRequired
- })
- }
-
- componentDidMount() {
- if (this.props.post.has_reactions) {
- this.props.actions.getReactionsForPost(this.props.post.id);
- }
- }
-
- componentDidUpdate(prevProps) {
- if (this.props.reactions !== prevProps.reactions) {
- postListScrollChange();
- }
- }
-
- render() {
- if (!this.props.post.has_reactions || (this.props.reactions && this.props.reactions.length === 0)) {
- return null;
- }
-
- const reactionsByName = new Map();
- const emojiNames = [];
-
- if (this.props.reactions) {
- for (const reaction of this.props.reactions) {
- const emojiName = reaction.emoji_name;
-
- if (reactionsByName.has(emojiName)) {
- reactionsByName.get(emojiName).push(reaction);
- } else {
- emojiNames.push(emojiName);
- reactionsByName.set(emojiName, [reaction]);
- }
- }
- }
-
- const children = emojiNames.map((emojiName) => {
- return (
- <Reaction
- key={emojiName}
- post={this.props.post}
- emojiName={emojiName}
- reactions={reactionsByName.get(emojiName) || []}
- emojis={this.props.emojis}
- />
- );
- });
-
- return (
- <div className='post-reaction-list'>
- {children}
- </div>
- );
- }
-}
diff --git a/webapp/components/post_view/scroll_to_bottom_arrows.jsx b/webapp/components/post_view/scroll_to_bottom_arrows.jsx
deleted file mode 100644
index 73f8e6527..000000000
--- a/webapp/components/post_view/scroll_to_bottom_arrows.jsx
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-
-import Constants from 'utils/constants.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default function ScrollToBottomArrows(props) {
- // only show on mobile
- if ($(window).width() > 768) {
- return <noscript/>;
- }
-
- let className = 'post-list__arrows';
- if (props.isScrolling && !props.atBottom) {
- className += ' scrolling';
- }
-
- return (
- <div
- className={className}
- onClick={props.onClick}
- >
- <span dangerouslySetInnerHTML={{__html: Constants.SCROLL_BOTTOM_ICON}}/>
- </div>
- );
-}
-
-ScrollToBottomArrows.propTypes = {
- isScrolling: PropTypes.bool.isRequired,
- atBottom: PropTypes.bool.isRequired,
- onClick: PropTypes.func.isRequired
-};
diff --git a/webapp/components/profile_picture.jsx b/webapp/components/profile_picture.jsx
deleted file mode 100644
index 90cea9d34..000000000
--- a/webapp/components/profile_picture.jsx
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ProfilePopover from './profile_popover.jsx';
-import Pluggable from 'plugins/pluggable';
-import * as Utils from 'utils/utils.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import StatusIcon from './status_icon.jsx';
-import {OverlayTrigger} from 'react-bootstrap';
-
-export default class ProfilePicture extends React.Component {
- constructor(props) {
- super(props);
-
- this.hideProfilePopover = this.hideProfilePopover.bind(this);
- }
- shouldComponentUpdate(nextProps) {
- if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) {
- return true;
- }
-
- if (nextProps.src !== this.props.src) {
- return true;
- }
-
- if (nextProps.status !== this.props.status) {
- return true;
- }
-
- if (nextProps.width !== this.props.width) {
- return true;
- }
-
- if (nextProps.height !== this.props.height) {
- return true;
- }
-
- if (nextProps.isBusy !== this.props.isBusy) {
- return true;
- }
-
- return false;
- }
-
- hideProfilePopover() {
- this.refs.overlay.hide();
- }
-
- render() {
- if (this.props.user) {
- return (
- <OverlayTrigger
- ref='overlay'
- trigger='click'
- placement='right'
- rootClose={true}
- overlay={
- <Pluggable>
- <ProfilePopover
- user={this.props.user}
- src={this.props.src}
- status={this.props.status}
- isBusy={this.props.isBusy}
- hide={this.hideProfilePopover}
- isRHS={this.props.isRHS}
- hasMention={this.props.hasMention}
- />
- </Pluggable>
- }
- >
- <span className='status-wrapper'>
- <img
- className='more-modal__image'
- width={this.props.width}
- height={this.props.width}
- src={this.props.src}
- />
- <StatusIcon status={this.props.status}/>
- </span>
- </OverlayTrigger>
- );
- }
- return (
- <span className='status-wrapper'>
- <img
- className='more-modal__image'
- width={this.props.width}
- height={this.props.width}
- src={this.props.src}
- />
- <StatusIcon status={this.props.status}/>
- </span>
- );
- }
-}
-
-ProfilePicture.defaultProps = {
- width: '36',
- height: '36',
- isRHS: false,
- hasMention: false
-};
-ProfilePicture.propTypes = {
- src: PropTypes.string.isRequired,
- status: PropTypes.string,
- width: PropTypes.string,
- height: PropTypes.string,
- user: PropTypes.object,
- isBusy: PropTypes.bool,
- isRHS: PropTypes.bool,
- hasMention: PropTypes.bool
-};
diff --git a/webapp/components/profile_popover.jsx b/webapp/components/profile_popover.jsx
deleted file mode 100644
index 63812428c..000000000
--- a/webapp/components/profile_popover.jsx
+++ /dev/null
@@ -1,301 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import * as Utils from 'utils/utils.jsx';
-import UserStore from 'stores/user_store.jsx';
-import WebrtcStore from 'stores/webrtc_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-import * as GlobalActions from 'actions/global_actions.jsx';
-import * as WebrtcActions from 'actions/webrtc_actions.jsx';
-import {openDirectChannelToUser} from 'actions/channel_actions.jsx';
-import Constants from 'utils/constants.jsx';
-const UserStatuses = Constants.UserStatuses;
-const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES;
-
-import {Popover, OverlayTrigger, Tooltip} from 'react-bootstrap';
-import {FormattedMessage} from 'react-intl';
-import {browserHistory} from 'react-router/es6';
-import PropTypes from 'prop-types';
-import React from 'react';
-
-export default class ProfilePopover extends React.Component {
- static getComponentName() {
- return 'ProfilePopover';
- }
-
- constructor(props) {
- super(props);
-
- this.initWebrtc = this.initWebrtc.bind(this);
- this.handleShowDirectChannel = this.handleShowDirectChannel.bind(this);
- this.handleMentionKeyClick = this.handleMentionKeyClick.bind(this);
- this.state = {
- currentUserId: UserStore.getCurrentId(),
- loadingDMChannel: -1
- };
- }
- shouldComponentUpdate(nextProps) {
- if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) {
- return true;
- }
-
- if (nextProps.src !== this.props.src) {
- return true;
- }
-
- if (nextProps.status !== this.props.status) {
- return true;
- }
-
- if (nextProps.isBusy !== this.props.isBusy) {
- return true;
- }
-
- // React-Bootstrap Forwarded Props from OverlayTrigger to Popover
- if (nextProps.arrowOffsetLeft !== this.props.arrowOffsetLeft) {
- return true;
- }
-
- if (nextProps.arrowOffsetTop !== this.props.arrowOffsetTop) {
- return true;
- }
-
- if (nextProps.positionLeft !== this.props.positionLeft) {
- return true;
- }
-
- if (nextProps.positionTop !== this.props.positionTop) {
- return true;
- }
-
- return false;
- }
-
- handleShowDirectChannel(e) {
- e.preventDefault();
-
- if (!this.props.user) {
- return;
- }
-
- const user = this.props.user;
-
- if (this.state.loadingDMChannel !== -1) {
- return;
- }
-
- this.setState({loadingDMChannel: user.id});
-
- openDirectChannelToUser(
- user.id,
- (channel) => {
- if (Utils.isMobile()) {
- GlobalActions.emitCloseRightHandSide();
- }
- this.setState({loadingDMChannel: -1});
- if (this.props.hide) {
- this.props.hide();
- }
- browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + channel.name);
- }
- );
- }
-
- initWebrtc() {
- if (this.props.status !== UserStatuses.OFFLINE && !WebrtcStore.isBusy()) {
- GlobalActions.emitCloseRightHandSide();
- WebrtcActions.initWebrtc(this.props.user.id, true);
- }
- }
-
- handleMentionKeyClick(e) {
- e.preventDefault();
-
- if (!this.props.user) {
- return;
- }
- if (this.props.hide) {
- this.props.hide();
- }
- GlobalActions.emitPopoverMentionKeyClick(this.props.isRHS, this.props.user.username);
- }
-
- render() {
- const popoverProps = Object.assign({}, this.props);
- delete popoverProps.user;
- delete popoverProps.src;
- delete popoverProps.status;
- delete popoverProps.isBusy;
- delete popoverProps.hide;
- delete popoverProps.isRHS;
- delete popoverProps.hasMention;
-
- let webrtc;
- const userMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
-
- const webrtcEnabled = global.mm_config.EnableWebrtc === 'true' && userMedia && Utils.isFeatureEnabled(PreReleaseFeatures.WEBRTC_PREVIEW);
-
- if (webrtcEnabled && this.props.user.id !== this.state.currentUserId) {
- const isOnline = this.props.status !== UserStatuses.OFFLINE;
- let webrtcMessage;
- if (isOnline && !this.props.isBusy) {
- webrtcMessage = (
- <FormattedMessage
- id='user_profile.webrtc.call'
- defaultMessage='Start Video Call'
- />
- );
- } else if (this.props.isBusy) {
- webrtcMessage = (
- <FormattedMessage
- id='user_profile.webrtc.unavailable'
- defaultMessage='New call unavailable until your existing call ends'
- />
- );
- } else {
- webrtcMessage = (
- <FormattedMessage
- id='user_profile.webrtc.offline'
- defaultMessage='The user is offline'
- />
- );
- }
-
- webrtc = (
- <div
- data-toggle='tooltip'
- key='makeCall'
- className='popover__row'
- >
- <a
- href='#'
- className='text-nowrap user-popover__email'
- onClick={() => this.initWebrtc()}
- disabled={!isOnline}
- >
- <i className='fa fa-video-camera'/>
- {webrtcMessage}
- </a>
- </div>
- );
- }
-
- var dataContent = [];
- dataContent.push(
- <img
- className='user-popover__image'
- src={this.props.src}
- height='128'
- width='128'
- key='user-popover-image'
- />
- );
-
- const fullname = Utils.getFullName(this.props.user);
- if (fullname) {
- dataContent.push(
- <OverlayTrigger
- delayShow={Constants.WEBRTC_TIME_DELAY}
- placement='top'
- overlay={<Tooltip id='fullNameTooltip'>{fullname}</Tooltip>}
- key='user-popover-fullname'
- >
- <div
- className='overflow--ellipsis text-nowrap padding-bottom'
- >
- {fullname}
- </div>
- </OverlayTrigger>
- );
- }
-
- if (this.props.user.position) {
- const position = this.props.user.position.substring(0, Constants.MAX_POSITION_LENGTH);
- dataContent.push(
- <OverlayTrigger
- delayShow={Constants.WEBRTC_TIME_DELAY}
- placement='top'
- overlay={<Tooltip id='positionTooltip'>{position}</Tooltip>}
- >
- <div
- className='overflow--ellipsis text-nowrap padding-bottom'
- >
- {position}
- </div>
- </OverlayTrigger>
- );
- }
-
- const email = this.props.user.email;
- if (global.window.mm_config.ShowEmailAddress === 'true' || UserStore.isSystemAdminForCurrentUser() || this.props.user === UserStore.getCurrentUser()) {
- dataContent.push(
- <div
- data-toggle='tooltip'
- title={email}
- key='user-popover-email'
- >
- <a
- href={'mailto:' + email}
- className='text-nowrap text-lowercase user-popover__email'
- >
- {email}
- </a>
- </div>
- );
- }
-
- if (this.props.user.id !== UserStore.getCurrentId()) {
- dataContent.push(
- <div
- data-toggle='tooltip'
- key='user-popover-dm'
- className='popover__row first'
- >
- <a
- href='#'
- className='text-nowrap text-lowercase user-popover__email'
- onClick={this.handleShowDirectChannel}
- >
- <i className='fa fa-paper-plane'/>
- <FormattedMessage
- id='user_profile.send.dm'
- defaultMessage='Send Message'
- />
- </a>
- </div>
- );
- dataContent.push(webrtc);
- }
-
- let title = `@${this.props.user.username}`;
- if (this.props.hasMention) {
- title = <a onClick={this.handleMentionKeyClick}>{title}</a>;
- }
-
- return (
- <Popover
- {...popoverProps}
- title={title}
- id='user-profile-popover'
- >
- {dataContent}
- </Popover>
- );
- }
-}
-
-ProfilePopover.defaultProps = {
- isRHS: false,
- hasMention: false
-};
-
-ProfilePopover.propTypes = Object.assign({
- src: PropTypes.string.isRequired,
- user: PropTypes.object.isRequired,
- status: PropTypes.string,
- isBusy: PropTypes.bool,
- hide: PropTypes.func,
- isRHS: PropTypes.bool,
- hasMention: PropTypes.bool
-}, Popover.propTypes);
-delete ProfilePopover.propTypes.id;
diff --git a/webapp/components/quick_switch_modal/index.js b/webapp/components/quick_switch_modal/index.js
deleted file mode 100644
index ad5d1dd7c..000000000
--- a/webapp/components/quick_switch_modal/index.js
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-
-import QuickSwitchModal from './quick_switch_modal.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps,
- showTeamSwitcher: false
- };
-}
-
-export default connect(mapStateToProps)(QuickSwitchModal);
diff --git a/webapp/components/quick_switch_modal/quick_switch_modal.jsx b/webapp/components/quick_switch_modal/quick_switch_modal.jsx
deleted file mode 100644
index 736b728f0..000000000
--- a/webapp/components/quick_switch_modal/quick_switch_modal.jsx
+++ /dev/null
@@ -1,330 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import SuggestionList from 'components/suggestion/suggestion_list.jsx';
-import SuggestionBox from 'components/suggestion/suggestion_box.jsx';
-import SwitchChannelProvider from 'components/suggestion/switch_channel_provider.jsx';
-import SwitchTeamProvider from 'components/suggestion/switch_team_provider.jsx';
-
-import {goToChannel, openDirectChannelToUser} from 'actions/channel_actions.jsx';
-
-import Constants from 'utils/constants.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {browserHistory} from 'react-router/es6';
-import {Modal} from 'react-bootstrap';
-import {FormattedMessage} from 'react-intl';
-
-// Redux actions
-import store from 'stores/redux_store.jsx';
-const getState = store.getState;
-
-import {getChannel} from 'mattermost-redux/selectors/entities/channels';
-
-const CHANNEL_MODE = 'channel';
-const TEAM_MODE = 'team';
-
-export default class QuickSwitchModal extends React.PureComponent {
- static propTypes = {
-
- /**
- * The mode to start in when showing the modal, either 'channel' or 'team'
- */
- initialMode: PropTypes.string.isRequired,
-
- /**
- * Set to show the modal
- */
- show: PropTypes.bool.isRequired,
-
- /**
- * The function called to hide the modal
- */
- onHide: PropTypes.func.isRequired,
-
- /**
- * Set to show team switcher
- */
- showTeamSwitcher: PropTypes.bool
- }
-
- static defaultProps = {
- initialMode: CHANNEL_MODE
- }
-
- constructor(props) {
- super(props);
-
- this.onChange = this.onChange.bind(this);
- this.onShow = this.onShow.bind(this);
- this.onHide = this.onHide.bind(this);
- this.onExited = this.onExited.bind(this);
- this.handleKeyDown = this.handleKeyDown.bind(this);
- this.handleSubmit = this.handleSubmit.bind(this);
- this.switchToChannel = this.switchToChannel.bind(this);
- this.switchMode = this.switchMode.bind(this);
- this.focusTextbox = this.focusTextbox.bind(this);
-
- this.enableChannelProvider = this.enableChannelProvider.bind(this);
- this.enableTeamProvider = this.enableTeamProvider.bind(this);
- this.channelProviders = [new SwitchChannelProvider()];
- this.teamProviders = [new SwitchTeamProvider()];
-
- this.state = {
- text: '',
- mode: props.initialMode
- };
- }
-
- componentDidUpdate(prevProps) {
- if (this.props.show && !prevProps.show) {
- this.focusTextbox();
- }
- }
-
- componentWillReceiveProps(nextProps) {
- if (!this.props.show && nextProps.show) {
- this.setState({mode: nextProps.initialMode, text: ''});
- }
- }
-
- focusTextbox() {
- if (this.refs.switchbox == null) {
- return;
- }
-
- const textbox = this.refs.switchbox.getTextbox();
- textbox.focus();
- Utils.placeCaretAtEnd(textbox);
- }
-
- onShow() {
- this.setState({
- text: ''
- });
- }
-
- onHide() {
- this.setState({
- text: ''
- });
- this.props.onHide();
- }
-
- onExited() {
- setTimeout(() => {
- document.querySelector('#post_textbox').focus();
- });
- }
-
- onChange(e) {
- this.setState({text: e.target.value});
- }
-
- handleKeyDown(e) {
- if (e.keyCode === Constants.KeyCodes.TAB) {
- e.preventDefault();
- this.switchMode();
- }
- }
-
- handleSubmit(selected) {
- let channel = null;
-
- if (!selected) {
- return;
- }
-
- if (this.state.mode === CHANNEL_MODE) {
- const selectedChannel = selected.channel;
- if (selectedChannel.type === Constants.DM_CHANNEL) {
- openDirectChannelToUser(
- selectedChannel.id,
- (ch) => {
- channel = ch;
- this.switchToChannel(channel);
- },
- () => {
- channel = null;
- this.switchToChannel(channel);
- }
- );
- } else if (selectedChannel.type === Constants.GM_CHANNEL) {
- channel = getChannel(getState(), selectedChannel.id);
- this.switchToChannel(channel);
- } else {
- this.switchToChannel(selectedChannel);
- }
- } else {
- browserHistory.push('/' + selected.name);
- this.onHide();
- }
- }
-
- switchToChannel(channel) {
- if (channel != null) {
- goToChannel(channel);
- this.onHide();
- }
- }
-
- enableChannelProvider() {
- this.channelProviders[0].disableDispatches = false;
- this.teamProviders[0].disableDispatches = true;
- }
-
- enableTeamProvider() {
- this.teamProviders[0].disableDispatches = false;
- this.channelProviders[0].disableDispatches = true;
- }
-
- switchMode() {
- if (this.state.mode === CHANNEL_MODE && this.props.showTeamSwitcher) {
- this.enableTeamProvider();
- this.setState({mode: TEAM_MODE});
- } else if (this.state.mode === TEAM_MODE) {
- this.enableChannelProvider();
- this.setState({mode: CHANNEL_MODE});
- }
- }
-
- render() {
- let providers = this.channelProviders;
- let header;
- let renderDividers = true;
-
- let channelShortcut = 'quick_switch_modal.channelsShortcut.windows';
- let defaultChannelShortcut = 'CTRL+K';
- if (Utils.isMac()) {
- channelShortcut = 'quick_switch_modal.channelsShortcut.mac';
- defaultChannelShortcut = 'CMD+K';
- }
-
- let teamShortcut = 'quick_switch_modal.teamsShortcut.windows';
- let defaultTeamShortcut = 'CTRL+ALT+K';
- if (Utils.isMac()) {
- teamShortcut = 'quick_switch_modal.teamsShortcut.mac';
- defaultTeamShortcut = 'CMD+ALT+K';
- }
-
- if (this.props.showTeamSwitcher) {
- let channelsActiveClass = '';
- let teamsActiveClass = '';
- if (this.state.mode === TEAM_MODE) {
- providers = this.teamProviders;
- renderDividers = false;
- teamsActiveClass = 'active';
- } else {
- channelsActiveClass = 'active';
- }
-
- header = (
- <div className='nav nav-tabs'>
- <li className={channelsActiveClass}>
- <a
- href='#'
- onClick={(e) => {
- e.preventDefault();
- this.enableChannelProvider();
- this.setState({mode: 'channel'});
- this.focusTextbox();
- }}
- >
- <FormattedMessage
- id='quick_switch_modal.channels'
- defaultMessage='Channels'
- />
- <span className='small'>
- <FormattedMessage
- id={channelShortcut}
- defaultMessage={defaultChannelShortcut}
- />
- </span>
- </a>
- </li>
- <li className={teamsActiveClass}>
- <a
- href='#'
- onClick={(e) => {
- e.preventDefault();
- this.enableTeamProvider();
- this.setState({mode: 'team'});
- this.focusTextbox();
- }}
- >
- <FormattedMessage
- id='quick_switch_modal.teams'
- defaultMessage='Teams'
- />
- <span className='small'>
- <FormattedMessage
- id={teamShortcut}
- defaultMessage={defaultTeamShortcut}
- />
- </span>
- </a>
- </li>
- </div>
- );
- }
-
- let help;
- if (Utils.isMobile()) {
- help = (
- <FormattedMessage
- id='quick_switch_modal.help_mobile'
- defaultMessage='Type to find a channel.'
- />
- );
- } else if (this.props.showTeamSwitcher) {
- help = (
- <FormattedMessage
- id='quick_switch_modal.help'
- defaultMessage='Start typing then use TAB to toggle channels/teams, ↑↓ to browse, ↵ to select, and ESC to dismiss.'
- />
- );
- } else {
- help = (
- <FormattedMessage
- id='quick_switch_modal.help_no_team'
- defaultMessage='Type to find a channel. Use ↑↓ to browse, ↵ to select, ESC to dismiss.'
- />
- );
- }
-
- return (
- <Modal
- dialogClassName='channel-switch-modal modal--overflow'
- ref='modal'
- show={this.props.show}
- onHide={this.onHide}
- onExited={this.onExited}
- >
- <Modal.Header closeButton={true}/>
- <Modal.Body>
- {header}
- <div className='modal__hint'>
- {help}
- </div>
- <SuggestionBox
- ref='switchbox'
- className='form-control focused'
- type='input'
- onChange={this.onChange}
- value={this.state.text}
- onKeyDown={this.handleKeyDown}
- onItemSelected={this.handleSubmit}
- listComponent={SuggestionList}
- maxLength='64'
- providers={providers}
- listStyle='bottom'
- completeOnTab={false}
- renderDividers={renderDividers}
- />
- </Modal.Body>
- </Modal>
- );
- }
-}
diff --git a/webapp/components/removed_from_channel_modal.jsx b/webapp/components/removed_from_channel_modal.jsx
deleted file mode 100644
index c82faf7e0..000000000
--- a/webapp/components/removed_from_channel_modal.jsx
+++ /dev/null
@@ -1,145 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import ReactDOM from 'react-dom';
-import ChannelStore from 'stores/channel_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-import BrowserStore from 'stores/browser_store.jsx';
-
-import {FormattedMessage} from 'react-intl';
-import {browserHistory} from 'react-router/es6';
-
-import React from 'react';
-
-export default class RemovedFromChannelModal extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleShow = this.handleShow.bind(this);
- this.handleClose = this.handleClose.bind(this);
-
- this.state = {
- channelName: '',
- remover: ''
- };
- }
-
- handleShow() {
- var newState = {};
- if (BrowserStore.getItem('channel-removed-state')) {
- newState = BrowserStore.getItem('channel-removed-state');
- BrowserStore.removeItem('channel-removed-state');
- }
-
- var townSquare = ChannelStore.getByName('town-square');
- setTimeout(
- () => {
- browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + townSquare.name);
- },
- 1);
-
- this.setState(newState);
- }
-
- handleClose() {
- this.setState({channelName: '', remover: ''});
- }
-
- componentDidMount() {
- $(ReactDOM.findDOMNode(this)).on('show.bs.modal', this.handleShow);
- $(ReactDOM.findDOMNode(this)).on('hidden.bs.modal', this.handleClose);
- }
-
- componentWillUnmount() {
- $(ReactDOM.findDOMNode(this)).off('show.bs.modal', this.handleShow);
- $(ReactDOM.findDOMNode(this)).off('hidden.bs.modal', this.handleClose);
- }
-
- render() {
- var currentUser = UserStore.getCurrentUser();
-
- var channelName = (
- <FormattedMessage
- id='removed_channel.channelName'
- defaultMessage='the channel'
- />
- );
- if (this.state.channelName) {
- channelName = this.state.channelName;
- }
-
- var remover = (
- <FormattedMessage
- id='removed_channel.someone'
- defaultMessage='Someone'
- />
- );
- if (this.state.remover) {
- remover = this.state.remover;
- }
-
- if (currentUser != null) {
- return (
- <div
- className='modal fade'
- ref='modal'
- id='removed_from_channel'
- tabIndex='-1'
- role='dialog'
- aria-hidden='true'
- >
- <div className='modal-dialog'>
- <div className='modal-content'>
- <div className='modal-header'>
- <button
- type='button'
- className='close'
- data-dismiss='modal'
- aria-label='Close'
- >
- <span aria-hidden='true'>
- {'×'}
- </span>
- </button>
- <h4 className='modal-title'>
- <FormattedMessage
- id='removed_channel.from'
- defaultMessage='Removed from '
- />
- <span className='name'>{channelName}</span></h4>
- </div>
- <div className='modal-body'>
- <p>
- <FormattedMessage
- id='removed_channel.remover'
- defaultMessage='{remover} removed you from {channel}'
- values={{
- remover,
- channel: (channelName)
- }}
- />
- </p>
- </div>
- <div className='modal-footer'>
- <button
- type='button'
- className='btn btn-primary'
- data-dismiss='modal'
- >
- <FormattedMessage
- id='removed_channel.okay'
- defaultMessage='Okay'
- />
- </button>
- </div>
- </div>
- </div>
- </div>
- );
- }
-
- return <div/>;
- }
-}
diff --git a/webapp/components/rename_channel_modal.jsx b/webapp/components/rename_channel_modal.jsx
deleted file mode 100644
index 6d7156b51..000000000
--- a/webapp/components/rename_channel_modal.jsx
+++ /dev/null
@@ -1,344 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ReactDOM from 'react-dom';
-import {browserHistory} from 'react-router/es6';
-import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-import {cleanUpUrlable, getShortenedURL} from 'utils/url.jsx';
-
-import TeamStore from 'stores/team_store.jsx';
-
-import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl';
-import {updateChannel} from 'actions/channel_actions.jsx';
-
-import {Modal, Tooltip, OverlayTrigger} from 'react-bootstrap';
-
-const holders = defineMessages({
- required: {
- id: 'rename_channel.required',
- defaultMessage: 'This field is required'
- },
- maxLength: {
- id: 'rename_channel.maxLength',
- defaultMessage: 'This field must be less than {maxLength, number} characters'
- },
- lowercase: {
- id: 'rename_channel.lowercase',
- defaultMessage: 'Must be lowercase alphanumeric characters'
- },
- url: {
- id: 'rename_channel.url',
- defaultMessage: 'URL'
- },
- defaultError: {
- id: 'rename_channel.defaultError',
- defaultMessage: ' - Cannot be changed for the default channel'
- },
- displayNameHolder: {
- id: 'rename_channel.displayNameHolder',
- defaultMessage: 'Enter display name'
- },
- handleHolder: {
- id: 'rename_channel.handleHolder',
- defaultMessage: 'lowercase alphanumeric characters'
- }
-});
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export class RenameChannelModal extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleShow = this.handleShow.bind(this);
- this.handleHide = this.handleHide.bind(this);
- this.handleSubmit = this.handleSubmit.bind(this);
- this.handleCancel = this.handleCancel.bind(this);
-
- this.onNameChange = this.onNameChange.bind(this);
- this.onDisplayNameChange = this.onDisplayNameChange.bind(this);
-
- this.state = {
- displayName: props.channel.display_name,
- channelName: props.channel.name,
- serverError: '',
- nameError: '',
- displayNameError: '',
- invalid: false
- };
- }
-
- componentWillReceiveProps(nextProps) {
- if (!Utils.areObjectsEqual(nextProps.channel, this.props.channel)) {
- this.setState({
- displayName: nextProps.channel.display_name,
- channelName: nextProps.channel.name
- });
- }
- }
-
- shouldComponentUpdate(nextProps, nextState) {
- if (!nextProps.show && !this.props.show) {
- return false;
- }
-
- if (!Utils.areObjectsEqual(nextState, this.state)) {
- return true;
- }
-
- if (!Utils.areObjectsEqual(nextProps, this.props)) {
- return true;
- }
-
- return false;
- }
-
- componentDidUpdate(prevProps) {
- if (!prevProps.show && this.props.show) {
- this.handleShow();
- }
- }
-
- handleShow() {
- const textbox = ReactDOM.findDOMNode(this.refs.displayName);
- textbox.focus();
- Utils.placeCaretAtEnd(textbox);
- }
-
- handleHide(e) {
- if (e) {
- e.preventDefault();
- }
-
- this.props.onHide();
-
- this.setState({
- serverError: '',
- nameError: '',
- displayNameError: '',
- invalid: false
- });
- }
-
- handleSubmit(e) {
- e.preventDefault();
-
- const channel = Object.assign({}, this.props.channel);
- const oldName = channel.name;
- const oldDisplayName = channel.display_name;
- const state = {serverError: ''};
- const {formatMessage} = this.props.intl;
-
- channel.display_name = this.state.displayName.trim();
- if (!channel.display_name) {
- state.displayNameError = formatMessage(holders.required);
- state.invalid = true;
- } else if (channel.display_name.length > Constants.MAX_CHANNELNAME_LENGTH) {
- state.displayNameError = formatMessage(holders.maxLength, {maxLength: Constants.MAX_CHANNELNAME_LENGTH});
- state.invalid = true;
- } else if (channel.display_name.length < Constants.MIN_CHANNELNAME_LENGTH) {
- state.displayNameError = (
- <FormattedMessage
- id='rename_channel.minLength'
- defaultMessage='Channel name must be {minLength, number} or more characters'
- values={{
- minLength: Constants.MIN_CHANNELNAME_LENGTH
- }}
- />
- );
- state.invalid = true;
- } else {
- state.displayNameError = '';
- }
-
- channel.name = this.state.channelName.trim();
- if (!channel.name) {
- state.nameError = formatMessage(holders.required);
- state.invalid = true;
- } else if (channel.name.length > Constants.MAX_CHANNELNAME_LENGTH) {
- state.nameError = formatMessage(holders.maxLength, {maxLength: Constants.MAX_CHANNELNAME_LENGTH});
- state.invalid = true;
- } else {
- const cleanedName = cleanUpUrlable(channel.name);
- if (cleanedName === channel.name) {
- state.nameError = '';
- } else {
- state.nameError = formatMessage(holders.lowercase);
- state.invalid = true;
- }
- }
-
- this.setState(state);
-
- if (state.invalid || (oldName === channel.name && oldDisplayName === channel.display_name)) {
- return;
- }
-
- updateChannel(channel,
- (data) => {
- this.handleHide();
- const team = TeamStore.get(data.team_id);
- browserHistory.push('/' + team.name + '/channels/' + data.name);
- },
- (err) => {
- this.setState({
- serverError: err.message,
- invalid: true
- });
- }
- );
- }
-
- handleCancel(e) {
- this.setState({
- displayName: this.props.channel.display_name,
- channelName: this.props.channel.name
- });
-
- this.handleHide(e);
- }
-
- onNameChange() {
- this.setState({channelName: ReactDOM.findDOMNode(this.refs.channelName).value});
- }
-
- onDisplayNameChange() {
- this.setState({displayName: ReactDOM.findDOMNode(this.refs.displayName).value});
- }
-
- render() {
- let displayNameError = null;
- let displayNameClass = 'form-group';
- if (this.state.displayNameError) {
- displayNameError = <label className='control-label'>{this.state.displayNameError}</label>;
- displayNameClass += ' has-error';
- }
-
- let nameError = null;
- let nameClass = 'form-group';
- if (this.state.nameError) {
- nameError = <label className='control-label'>{this.state.nameError}</label>;
- nameClass += ' has-error';
- }
-
- let serverError = null;
- if (this.state.serverError) {
- serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
- }
-
- const {formatMessage} = this.props.intl;
-
- let urlInputLabel = formatMessage(holders.url);
- const handleInputClass = 'form-control';
- let readOnlyHandleInput = false;
- if (this.state.channelName === Constants.DEFAULT_CHANNEL) {
- urlInputLabel += formatMessage(holders.defaultError);
- readOnlyHandleInput = true;
- }
-
- const fullUrl = TeamStore.getCurrentTeamUrl() + '/channels';
- const shortUrl = getShortenedURL(fullUrl, 35);
- const urlTooltip = (
- <Tooltip id='urlTooltip'>{fullUrl}</Tooltip>
- );
-
- return (
- <Modal
- show={this.props.show}
- onHide={this.handleCancel}
- >
- <Modal.Header closeButton={true}>
- <Modal.Title>
- <FormattedMessage
- id='rename_channel.title'
- defaultMessage='Rename Channel'
- />
- </Modal.Title>
- </Modal.Header>
- <form role='form'>
- <Modal.Body>
- <div className={displayNameClass}>
- <label className='control-label'>
- <FormattedMessage
- id='rename_channel.displayName'
- defaultMessage='Display Name'
- />
- </label>
- <input
- onChange={this.onDisplayNameChange}
- type='text'
- ref='displayName'
- id='display_name'
- className='form-control'
- placeholder={formatMessage(holders.displayNameHolder)}
- value={this.state.displayName}
- maxLength={Constants.MAX_CHANNELNAME_LENGTH}
- />
- {displayNameError}
- </div>
- <div className={nameClass}>
- <label className='control-label'>{urlInputLabel}</label>
-
- <div className='input-group input-group--limit'>
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={Constants.OVERLAY_TIME_DELAY}
- placement='top'
- overlay={urlTooltip}
- >
- <span className='input-group-addon'>{shortUrl}</span>
- </OverlayTrigger>
- <input
- onChange={this.onNameChange}
- type='text'
- className={handleInputClass}
- ref='channelName'
- placeholder={formatMessage(holders.handleHolder)}
- value={this.state.channelName}
- maxLength={Constants.MAX_CHANNELNAME_LENGTH}
- readOnly={readOnlyHandleInput}
- />
- </div>
- {nameError}
- </div>
- {serverError}
- </Modal.Body>
- <Modal.Footer>
- <button
- type='button'
- className='btn btn-default'
- onClick={this.handleCancel}
- >
- <FormattedMessage
- id='rename_channel.cancel'
- defaultMessage='Cancel'
- />
- </button>
- <button
- onClick={this.handleSubmit}
- type='submit'
- className='btn btn-primary'
- >
- <FormattedMessage
- id='rename_channel.save'
- defaultMessage='Save'
- />
- </button>
- </Modal.Footer>
- </form>
- </Modal>
- );
- }
-}
-
-RenameChannelModal.propTypes = {
- intl: intlShape.isRequired,
- show: PropTypes.bool.isRequired,
- onHide: PropTypes.func.isRequired,
- channel: PropTypes.object.isRequired
-};
-
-export default injectIntl(RenameChannelModal);
diff --git a/webapp/components/reset_status_modal/index.js b/webapp/components/reset_status_modal/index.js
deleted file mode 100644
index 34f08c7a5..000000000
--- a/webapp/components/reset_status_modal/index.js
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-
-import {Preferences} from 'mattermost-redux/constants';
-import {get} from 'mattermost-redux/selectors/entities/preferences';
-
-import {savePreferences} from 'mattermost-redux/actions/preferences';
-import {setStatus} from 'mattermost-redux/actions/users';
-import {autoResetStatus} from 'actions/user_actions.jsx';
-
-import ResetStatusModal from './reset_status_modal.jsx';
-
-function mapStateToProps(state, ownProps) {
- const {currentUserId} = state.entities.users;
- return {
- ...ownProps,
- autoResetPref: get(state, Preferences.CATEGORY_AUTO_RESET_MANUAL_STATUS, currentUserId, '')
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- autoResetStatus,
- setStatus,
- savePreferences
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(ResetStatusModal);
diff --git a/webapp/components/reset_status_modal/reset_status_modal.jsx b/webapp/components/reset_status_modal/reset_status_modal.jsx
deleted file mode 100644
index 2e9bb8382..000000000
--- a/webapp/components/reset_status_modal/reset_status_modal.jsx
+++ /dev/null
@@ -1,142 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ConfirmModal from 'components/confirm_modal.jsx';
-
-import {toTitleCase} from 'utils/utils.jsx';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {FormattedMessage} from 'react-intl';
-import {Preferences} from 'mattermost-redux/constants';
-
-export default class ResetStatusModal extends React.PureComponent {
- static propTypes = {
-
- /*
- * The user's preference for whether their status is automatically reset
- */
- autoResetPref: PropTypes.string,
- actions: PropTypes.shape({
-
- /*
- * Function to get and then reset the user's status if needed
- */
- autoResetStatus: PropTypes.func.isRequired,
-
- /*
- * Function to set the status for a user
- */
- setStatus: PropTypes.func.isRequired,
-
- /*
- * Function to save user preferences
- */
- savePreferences: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.state = {
- show: false,
- currentUserStatus: {}
- };
- }
-
- componentDidMount() {
- this.props.actions.autoResetStatus().then(
- (status) => {
- const statusIsManual = status.manual;
- const autoResetPrefNotSet = this.props.autoResetPref === '';
-
- this.setState({
- currentUserStatus: status, // Set in state until status refactor where we store 'manual' field in redux
- show: Boolean(statusIsManual && autoResetPrefNotSet)
- });
- }
- );
- }
-
- onConfirm = (checked) => {
- this.setState({show: false});
-
- const newStatus = {...this.state.currentUserStatus};
- newStatus.status = 'online';
- this.props.actions.setStatus(newStatus);
-
- if (checked) {
- const pref = {category: Preferences.CATEGORY_AUTO_RESET_MANUAL_STATUS, user_id: newStatus.user_id, name: newStatus.user_id, value: 'true'};
- this.props.actions.savePreferences(pref.user_id, [pref]);
- }
- }
-
- onCancel = (checked) => {
- this.setState({show: false});
-
- if (checked) {
- const status = {...this.state.currentUserStatus};
- const pref = {category: Preferences.CATEGORY_AUTO_RESET_MANUAL_STATUS, user_id: status.user_id, name: status.user_id, value: 'false'};
- this.props.actions.savePreferences(pref.user_id, [pref]);
- }
- }
-
- render() {
- const userStatus = toTitleCase(this.state.currentUserStatus.status || '');
- const manualStatusTitle = (
- <FormattedMessage
- id='modal.manaul_status.title'
- defaultMessage='Your status is set to "{status}"'
- values={{
- status: userStatus
- }}
- />
- );
-
- const manualStatusMessage = (
- <FormattedMessage
- id='modal.manaul_status.message'
- defaultMessage='Would you like to switch your status to "Online"?'
- />
- );
-
- const manualStatusButton = (
- <FormattedMessage
- id='modal.manaul_status.button'
- defaultMessage='Yes, set my status to "Online"'
- />
- );
-
- const manualStatusCancel = (
- <FormattedMessage
- id='modal.manaul_status.cancel'
- defaultMessage='No, keep it as "{status}"'
- values={{
- status: userStatus
- }}
- />
- );
-
- const manualStatusCheckbox = (
- <FormattedMessage
- id='modal.manaul_status.ask'
- defaultMessage='Do not ask me again'
- />
- );
-
- return (
- <ConfirmModal
- show={this.state.show}
- title={manualStatusTitle}
- message={manualStatusMessage}
- confirmButtonText={manualStatusButton}
- onConfirm={this.onConfirm}
- cancelButtonText={manualStatusCancel}
- onCancel={this.onCancel}
- showCheckbox={true}
- checkboxText={manualStatusCheckbox}
- />
- );
- }
-}
diff --git a/webapp/components/rhs_comment.jsx b/webapp/components/rhs_comment.jsx
deleted file mode 100644
index 6b27848bb..000000000
--- a/webapp/components/rhs_comment.jsx
+++ /dev/null
@@ -1,476 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import UserProfile from './user_profile.jsx';
-import FileAttachmentListContainer from 'components/file_attachment_list';
-import PostMessageContainer from 'components/post_view/post_message_view';
-import ProfilePicture from 'components/profile_picture.jsx';
-import ReactionListContainer from 'components/post_view/reaction_list';
-import PostFlagIcon from 'components/post_view/post_flag_icon.jsx';
-import FailedPostOptions from 'components/post_view/failed_post_options';
-import DotMenu from 'components/dot_menu';
-import EmojiPickerOverlay from 'components/emoji_picker/emoji_picker_overlay.jsx';
-
-import {addReaction, emitEmojiPosted} from 'actions/post_actions.jsx';
-
-import TeamStore from 'stores/team_store.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-import * as PostUtils from 'utils/post_utils.jsx';
-
-import Constants from 'utils/constants.jsx';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {Link} from 'react-router/es6';
-import {FormattedMessage} from 'react-intl';
-
-export default class RhsComment extends React.Component {
- static propTypes = {
- post: PropTypes.object,
- lastPostCount: PropTypes.number,
- user: PropTypes.object.isRequired,
- currentUser: PropTypes.object.isRequired,
- compactDisplay: PropTypes.bool,
- useMilitaryTime: PropTypes.bool.isRequired,
- isFlagged: PropTypes.bool,
- status: PropTypes.string,
- isBusy: PropTypes.bool,
- removePost: PropTypes.func.isRequired
- };
-
- constructor(props) {
- super(props);
-
- this.removePost = this.removePost.bind(this);
- this.reactEmojiClick = this.reactEmojiClick.bind(this);
- this.handleDropdownOpened = this.handleDropdownOpened.bind(this);
-
- this.state = {
- currentTeamDisplayName: TeamStore.getCurrent().name,
- width: '',
- height: '',
- showEmojiPicker: false,
- dropdownOpened: false
- };
- }
-
- componentDidMount() {
- window.addEventListener('resize', () => {
- Utils.updateWindowDimensions(this);
- });
- }
-
- componentWillUnmount() {
- window.removeEventListener('resize', () => {
- Utils.updateWindowDimensions(this);
- });
- }
-
- removePost() {
- this.props.removePost(this.props.post);
- }
-
- createRemovePostButton() {
- return (
- <a
- href='#'
- className='post__remove theme'
- type='button'
- onClick={this.removePost}
- >
- {'×'}
- </a>
- );
- }
-
- shouldComponentUpdate(nextProps, nextState) {
- if (nextProps.status !== this.props.status) {
- return true;
- }
-
- if (nextProps.isBusy !== this.props.isBusy) {
- return true;
- }
-
- if (nextProps.compactDisplay !== this.props.compactDisplay) {
- return true;
- }
-
- if (nextProps.useMilitaryTime !== this.props.useMilitaryTime) {
- return true;
- }
-
- if (nextProps.isFlagged !== this.props.isFlagged) {
- return true;
- }
-
- if (!Utils.areObjectsEqual(nextProps.post, this.props.post)) {
- return true;
- }
-
- if (!Utils.areObjectsEqual(nextProps.currentUser, this.props.currentUser)) {
- return true;
- }
-
- if (this.state.showEmojiPicker !== nextState.showEmojiPicker) {
- return true;
- }
-
- if (nextProps.lastPostCount !== this.props.lastPostCount) {
- return true;
- }
-
- if (this.state.dropdownOpened !== nextState.dropdownOpened) {
- return true;
- }
-
- return false;
- }
-
- timeTag(post, timeOptions) {
- return (
- <time
- className='post__time'
- dateTime={Utils.getDateForUnixTicks(post.create_at).toISOString()}
- >
- {Utils.getDateForUnixTicks(post.create_at).toLocaleString('en', timeOptions)}
- </time>
- );
- }
-
- renderTimeTag(post, timeOptions) {
- return Utils.isMobile() ?
- this.timeTag(post, timeOptions) :
- (
- <Link
- to={`/${this.state.currentTeamDisplayName}/pl/${post.id}`}
- target='_blank'
- className='post__permalink'
- >
- {this.timeTag(post, timeOptions)}
- </Link>
- );
- }
-
- toggleEmojiPicker = () => {
- const showEmojiPicker = !this.state.showEmojiPicker;
-
- this.setState({
- showEmojiPicker,
- dropdownOpened: showEmojiPicker
- });
- }
-
- reactEmojiClick(emoji) {
- this.setState({showEmojiPicker: false});
- const emojiName = emoji.name || emoji.aliases[0];
- addReaction(this.props.post.channel_id, this.props.post.id, emojiName);
- emitEmojiPosted(emojiName);
- this.handleDropdownOpened(false);
- }
-
- getClassName = (post, isSystemMessage) => {
- let className = 'post post--thread';
-
- if (this.props.currentUser.id === post.user_id) {
- className += ' current--user';
- }
-
- if (isSystemMessage) {
- className += ' post--system';
- }
-
- if (this.props.compactDisplay) {
- className += ' post--compact';
- }
-
- if (post.is_pinned) {
- className += ' post--pinned';
- }
-
- if (this.state.dropdownOpened) {
- className += ' post--hovered';
- }
-
- return className;
- }
-
- handleDropdownOpened(isOpened) {
- this.setState({
- dropdownOpened: isOpened
- });
- }
-
- render() {
- const post = this.props.post;
- const mattermostLogo = Constants.MATTERMOST_ICON_SVG;
-
- let idCount = -1;
- if (this.props.lastPostCount >= 0 && this.props.lastPostCount < Constants.TEST_ID_COUNT) {
- idCount = this.props.lastPostCount;
- }
-
- const isEphemeral = Utils.isPostEphemeral(post);
- const isSystemMessage = PostUtils.isSystemMessage(post);
-
- let status = this.props.status;
- if (post.props && post.props.from_webhook === 'true') {
- status = null;
- }
-
- let botIndicator;
- let userProfile = (
- <UserProfile
- user={this.props.user}
- status={status}
- isBusy={this.props.isBusy}
- isRHS={true}
- hasMention={true}
- />
- );
-
- let visibleMessage;
- if (post.props && post.props.from_webhook) {
- if (post.props.override_username && global.window.mm_config.EnablePostUsernameOverride === 'true') {
- userProfile = (
- <UserProfile
- user={this.props.user}
- overwriteName={post.props.override_username}
- disablePopover={true}
- />
- );
- } else {
- userProfile = (
- <UserProfile
- user={this.props.user}
- disablePopover={true}
- />
- );
- }
-
- botIndicator = <div className='col col__name bot-indicator'>{'BOT'}</div>;
- } else if (isSystemMessage) {
- userProfile = (
- <UserProfile
- user={{}}
- overwriteName={
- <FormattedMessage
- id='post_info.system'
- defaultMessage='System'
- />
- }
- overwriteImage={Constants.SYSTEM_MESSAGE_PROFILE_IMAGE}
- disablePopover={true}
- />
- );
-
- visibleMessage = (
- <span className='post__visibility'>
- <FormattedMessage
- id='post_info.message.visible'
- defaultMessage='(Only visible to you)'
- />
- </span>
- );
- }
-
- let failedPostOptions;
- let postClass = '';
-
- if (post.failed) {
- postClass += ' post-failed';
- failedPostOptions = <FailedPostOptions post={this.props.post}/>;
- }
-
- if (PostUtils.isEdited(this.props.post)) {
- postClass += ' post--edited';
- }
-
- let profilePic = (
- <ProfilePicture
- src={PostUtils.getProfilePicSrcForPost(post, this.props.user)}
- status={status}
- width='36'
- height='36'
- user={this.props.user}
- isBusy={this.props.isBusy}
- isRHS={true}
- hasMention={true}
- />
- );
-
- if (post.props && post.props.from_webhook) {
- profilePic = (
- <ProfilePicture
- src={PostUtils.getProfilePicSrcForPost(post, this.props.user)}
- width='36'
- height='36'
- />
- );
- }
-
- if (isSystemMessage) {
- profilePic = (
- <span
- className='icon'
- dangerouslySetInnerHTML={{__html: mattermostLogo}}
- />
- );
- }
-
- if (this.props.compactDisplay) {
- if (post.props && post.props.from_webhook) {
- profilePic = (
- <ProfilePicture
- src=''
- />
- );
- } else {
- profilePic = (
- <ProfilePicture
- src=''
- status={status}
- user={this.props.user}
- isBusy={this.props.isBusy}
- isRHS={true}
- hasMention={true}
- />
- );
- }
- }
-
- const profilePicContainer = (<div className='post__img'>{profilePic}</div>);
-
- let fileAttachment = null;
- if (post.file_ids && post.file_ids.length > 0) {
- fileAttachment = (
- <FileAttachmentListContainer
- post={post}
- compactDisplay={this.props.compactDisplay}
- />
- );
- }
-
- let react;
-
- if (!isEphemeral && !post.failed && !isSystemMessage && window.mm_config.EnableEmojiPicker === 'true') {
- react = (
- <span>
- <EmojiPickerOverlay
- show={this.state.showEmojiPicker}
- onHide={this.toggleEmojiPicker}
- target={() => this.refs.dotMenu}
- onEmojiClick={this.reactEmojiClick}
- rightOffset={15}
- spaceRequiredAbove={342}
- spaceRequiredBelow={342}
- />
- <a
- href='#'
- className='reacticon__container reaction'
- onClick={this.toggleEmojiPicker}
- ref={'rhs_reacticon_' + post.id}
- >
- <span
- className='icon icon--emoji'
- dangerouslySetInnerHTML={{__html: Constants.EMOJI_ICON_SVG}}
- />
- </a>
- </span>
-
- );
- }
-
- let options;
- if (isEphemeral) {
- options = (
- <div className='col col__remove'>
- {this.createRemovePostButton()}
- </div>
- );
- } else if (!isSystemMessage) {
- const dotMenu = (
- <DotMenu
- idPrefix={Constants.RHS}
- idCount={idCount}
- post={this.props.post}
- isFlagged={this.props.isFlagged}
- handleDropdownOpened={this.handleDropdownOpened}
- />
- );
-
- options = (
- <div
- ref='dotMenu'
- className='col col__reply'
- >
- {dotMenu}
- {react}
- </div>
- );
- }
-
- let pinnedBadge;
- if (post.is_pinned) {
- pinnedBadge = (
- <span className='post__pinned-badge'>
- <FormattedMessage
- id='post_info.pinned'
- defaultMessage='Pinned'
- />
- </span>
- );
- }
-
- const timeOptions = {
- hour: '2-digit',
- minute: '2-digit',
- hour12: !this.props.useMilitaryTime
- };
-
- return (
- <div
- ref={'post_body_' + post.id}
- className={this.getClassName(post, isSystemMessage)}
- >
- <div className='post__content'>
- {profilePicContainer}
- <div>
- <div className='post__header'>
- <div className='col col__name'>
- <strong>{userProfile}</strong>
- </div>
- {botIndicator}
- <div className='col'>
- {this.renderTimeTag(post, timeOptions)}
- {pinnedBadge}
- <PostFlagIcon
- idPrefix={'rhsCommentFlag'}
- idCount={idCount}
- postId={post.id}
- isFlagged={this.props.isFlagged}
- isEphemeral={isEphemeral}
- />
- {visibleMessage}
- </div>
- {options}
- </div>
- <div className='post__body' >
- <div className={postClass}>
- {failedPostOptions}
- <PostMessageContainer
- post={post}
- isRHS={true}
- hasMention={true}
- />
- </div>
- {fileAttachment}
- <ReactionListContainer post={post}/>
- </div>
- </div>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/rhs_header_post.jsx b/webapp/components/rhs_header_post.jsx
deleted file mode 100644
index 1608f90a2..000000000
--- a/webapp/components/rhs_header_post.jsx
+++ /dev/null
@@ -1,217 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
-import Constants from 'utils/constants.jsx';
-import {Tooltip, OverlayTrigger} from 'react-bootstrap';
-
-import * as GlobalActions from 'actions/global_actions.jsx';
-import {getFlaggedPosts, getPinnedPosts} from 'actions/post_actions.jsx';
-
-import {FormattedMessage} from 'react-intl';
-
-const ActionTypes = Constants.ActionTypes;
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class RhsHeaderPost extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleClose = this.handleClose.bind(this);
- this.toggleSize = this.toggleSize.bind(this);
- this.handleBack = this.handleBack.bind(this);
-
- this.state = {};
- }
-
- handleClose(e) {
- e.preventDefault();
- GlobalActions.emitCloseRightHandSide();
- this.props.shrink();
- }
-
- toggleSize(e) {
- e.preventDefault();
- this.props.toggleSize();
- }
-
- handleBack(e) {
- e.preventDefault();
-
- if (this.props.fromSearch || this.props.isWebrtc) {
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_SEARCH_TERM,
- term: this.props.fromSearch,
- do_search: true,
- is_mention_search: this.props.isMentionSearch
- });
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_POST_SELECTED,
- postId: null
- });
- } else if (this.props.fromFlaggedPosts) {
- getFlaggedPosts();
- } else if (this.props.fromPinnedPosts) {
- getPinnedPosts();
- }
- }
-
- render() {
- let back;
- const closeSidebarTooltip = (
- <Tooltip id='closeSidebarTooltip'>
- <FormattedMessage
- id='rhs_header.closeSidebarTooltip'
- defaultMessage='Close Sidebar'
- />
- </Tooltip>
- );
-
- let backToResultsTooltip;
- if (this.props.fromSearch) {
- backToResultsTooltip = (
- <Tooltip id='backToResultsTooltip'>
- <FormattedMessage
- id='rhs_header.backToResultsTooltip'
- defaultMessage='Back to Search Results'
- />
- </Tooltip>
- );
- } else if (this.props.fromFlaggedPosts) {
- backToResultsTooltip = (
- <Tooltip id='backToResultsTooltip'>
- <FormattedMessage
- id='rhs_header.backToFlaggedTooltip'
- defaultMessage='Back to Flagged Posts'
- />
- </Tooltip>
- );
- } else if (this.props.isWebrtc) {
- backToResultsTooltip = (
- <Tooltip id='backToResultsTooltip'>
- <FormattedMessage
- id='rhs_header.backToCallTooltip'
- defaultMessage='Back to Call'
- />
- </Tooltip>
- );
- } else if (this.props.fromPinnedPosts) {
- backToResultsTooltip = (
- <Tooltip id='backToResultsTooltip'>
- <FormattedMessage
- id='rhs_header.backToPinnedTooltip'
- defaultMessage='Back to Pinned Posts'
- />
- </Tooltip>
- );
- }
-
- const expandSidebarTooltip = (
- <Tooltip id='expandSidebarTooltip'>
- <FormattedMessage
- id='rhs_header.expandSidebarTooltip'
- defaultMessage='Expand Sidebar'
- />
- </Tooltip>
- );
-
- const shrinkSidebarTooltip = (
- <Tooltip id='shrinkSidebarTooltip'>
- <FormattedMessage
- id='rhs_header.shrinkSidebarTooltip'
- defaultMessage='Shrink Sidebar'
- />
- </Tooltip>
- );
-
- if (this.props.fromSearch || this.props.fromFlaggedPosts || this.props.isWebrtc || this.props.fromPinnedPosts) {
- back = (
- <a
- href='#'
- onClick={this.handleBack}
- className='sidebar--right__back'
- >
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={Constants.OVERLAY_TIME_DELAY}
- placement='top'
- overlay={backToResultsTooltip}
- >
- <i className='fa fa-angle-left'/>
- </OverlayTrigger>
- </a>
- );
- }
-
- return (
- <div className='sidebar--right__header'>
- <span className='sidebar--right__title'>
- {back}
- <FormattedMessage
- id='rhs_header.details'
- defaultMessage='Message Details'
- />
- </span>
- <div className='pull-right'>
- <button
- type='button'
- className='sidebar--right__expand'
- aria-label='Expand'
- onClick={this.toggleSize}
- >
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={Constants.OVERLAY_TIME_DELAY}
- placement='top'
- overlay={expandSidebarTooltip}
- >
- <i className='fa fa-expand'/>
- </OverlayTrigger>
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={Constants.OVERLAY_TIME_DELAY}
- placement='top'
- overlay={shrinkSidebarTooltip}
- >
- <i className='fa fa-compress'/>
- </OverlayTrigger>
- </button>
- <button
- type='button'
- className='sidebar--right__close'
- aria-label='Close'
- onClick={this.handleClose}
- >
-
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={Constants.OVERLAY_TIME_DELAY}
- placement='top'
- overlay={closeSidebarTooltip}
- >
- <i className='fa fa-sign-out'/>
- </OverlayTrigger>
- </button>
- </div>
- </div>
- );
- }
-}
-
-RhsHeaderPost.defaultProps = {
- isMentionSearch: false,
- fromSearch: ''
-};
-RhsHeaderPost.propTypes = {
- isMentionSearch: PropTypes.bool,
- isWebrtc: PropTypes.bool,
- fromSearch: PropTypes.string,
- fromFlaggedPosts: PropTypes.bool,
- fromPinnedPosts: PropTypes.bool,
- toggleSize: PropTypes.func,
- shrink: PropTypes.func
-};
diff --git a/webapp/components/rhs_root_post.jsx b/webapp/components/rhs_root_post.jsx
deleted file mode 100644
index 9c7fd7244..000000000
--- a/webapp/components/rhs_root_post.jsx
+++ /dev/null
@@ -1,450 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import UserProfile from './user_profile.jsx';
-import PostBodyAdditionalContent from 'components/post_view/post_body_additional_content.jsx';
-import PostMessageContainer from 'components/post_view/post_message_view';
-import FileAttachmentListContainer from 'components/file_attachment_list';
-import ProfilePicture from 'components/profile_picture.jsx';
-import ReactionListContainer from 'components/post_view/reaction_list';
-import PostFlagIcon from 'components/post_view/post_flag_icon.jsx';
-import DotMenu from 'components/dot_menu';
-import EmojiPickerOverlay from 'components/emoji_picker/emoji_picker_overlay.jsx';
-
-import ChannelStore from 'stores/channel_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-
-import {addReaction, emitEmojiPosted} from 'actions/post_actions.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-import * as PostUtils from 'utils/post_utils.jsx';
-
-import Constants from 'utils/constants.jsx';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {Link} from 'react-router/es6';
-import {FormattedMessage} from 'react-intl';
-
-export default class RhsRootPost extends React.Component {
- static propTypes = {
- post: PropTypes.object.isRequired,
- user: PropTypes.object.isRequired,
- currentUser: PropTypes.object.isRequired,
- compactDisplay: PropTypes.bool,
- useMilitaryTime: PropTypes.bool.isRequired,
- commentCount: PropTypes.number.isRequired,
- isFlagged: PropTypes.bool,
- status: PropTypes.string,
- previewCollapsed: PropTypes.string,
- isBusy: PropTypes.bool
- }
-
- static defaultProps = {
- commentCount: 0
- }
-
- constructor(props) {
- super(props);
-
- this.reactEmojiClick = this.reactEmojiClick.bind(this);
- this.handleDropdownOpened = this.handleDropdownOpened.bind(this);
-
- this.state = {
- currentTeamDisplayName: TeamStore.getCurrent().name,
- width: '',
- height: '',
- showEmojiPicker: false,
- testStateObj: true,
- dropdownOpened: false
- };
- }
-
- componentDidMount() {
- window.addEventListener('resize', () => {
- Utils.updateWindowDimensions(this);
- });
- }
-
- componentWillUnmount() {
- window.removeEventListener('resize', () => {
- Utils.updateWindowDimensions(this);
- });
- }
-
- shouldComponentUpdate(nextProps, nextState) {
- if (nextProps.status !== this.props.status) {
- return true;
- }
-
- if (nextProps.isBusy !== this.props.isBusy) {
- return true;
- }
-
- if (nextProps.compactDisplay !== this.props.compactDisplay) {
- return true;
- }
-
- if (nextProps.useMilitaryTime !== this.props.useMilitaryTime) {
- return true;
- }
-
- if (nextProps.isFlagged !== this.props.isFlagged) {
- return true;
- }
-
- if (nextProps.previewCollapsed !== this.props.previewCollapsed) {
- return true;
- }
-
- if (!Utils.areObjectsEqual(nextProps.post, this.props.post)) {
- return true;
- }
-
- if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) {
- return true;
- }
-
- if (!Utils.areObjectsEqual(nextProps.currentUser, this.props.currentUser)) {
- return true;
- }
-
- if (this.state.showEmojiPicker !== nextState.showEmojiPicker) {
- return true;
- }
-
- if (this.state.dropdownOpened !== nextState.dropdownOpened) {
- return true;
- }
-
- return false;
- }
-
- timeTag(post, timeOptions) {
- return (
- <time
- className='post__time'
- dateTime={Utils.getDateForUnixTicks(post.create_at).toISOString()}
- >
- {Utils.getDateForUnixTicks(post.create_at).toLocaleString('en', timeOptions)}
- </time>
- );
- }
-
- renderTimeTag(post, timeOptions) {
- return Utils.isMobile() ?
- this.timeTag(post, timeOptions) :
- (
- <Link
- to={`/${this.state.currentTeamDisplayName}/pl/${post.id}`}
- target='_blank'
- className='post__permalink'
- >
- {this.timeTag(post, timeOptions)}
- </Link>
- );
- }
-
- toggleEmojiPicker = () => {
- const showEmojiPicker = !this.state.showEmojiPicker;
-
- this.setState({
- showEmojiPicker,
- dropdownOpened: showEmojiPicker
- });
- }
-
- reactEmojiClick(emoji) {
- this.setState({showEmojiPicker: false});
- const emojiName = emoji.name || emoji.aliases[0];
- addReaction(this.props.post.channel_id, this.props.post.id, emojiName);
- emitEmojiPosted(emojiName);
- this.handleDropdownOpened(false);
- }
-
- getClassName = (post, isSystemMessage) => {
- let className = 'post post--root post--thread';
- if (UserStore.getCurrentId() === post.user_id) {
- className += ' current--user';
- }
-
- if (isSystemMessage) {
- className += ' post--system';
- }
-
- if (this.props.compactDisplay) {
- className += ' post--compact';
- }
-
- if (post.is_pinned) {
- className += ' post--pinned';
- }
-
- if (this.state.dropdownOpened) {
- className += ' post--hovered';
- }
-
- return className;
- }
-
- handleDropdownOpened(isOpened) {
- this.setState({
- dropdownOpened: isOpened
- });
- }
-
- render() {
- const post = this.props.post;
- const user = this.props.user;
- const mattermostLogo = Constants.MATTERMOST_ICON_SVG;
- var channel = ChannelStore.get(post.channel_id);
-
- const isEphemeral = Utils.isPostEphemeral(post);
- const isSystemMessage = PostUtils.isSystemMessage(post);
-
- var channelName;
- if (channel) {
- if (channel.type === 'D') {
- channelName = (
- <FormattedMessage
- id='rhs_root.direct'
- defaultMessage='Direct Message'
- />
- );
- } else {
- channelName = channel.display_name;
- }
- }
-
- let react;
-
- if (!isEphemeral && !post.failed && !isSystemMessage && window.mm_config.EnableEmojiPicker === 'true') {
- react = (
- <span>
- <EmojiPickerOverlay
- show={this.state.showEmojiPicker}
- onHide={this.toggleEmojiPicker}
- target={() => this.refs.dotMenu}
- onEmojiClick={this.reactEmojiClick}
- rightOffset={15}
- spaceRequiredAbove={342}
- spaceRequiredBelow={342}
- />
- <a
- href='#'
- className='reacticon__container reaction'
- onClick={this.toggleEmojiPicker}
- ref='rhs_root_reacticon'
- >
- <span
- className='icon icon--emoji'
- dangerouslySetInnerHTML={{__html: Constants.EMOJI_ICON_SVG}}
- />
- </a>
- </span>
-
- );
- }
-
- let fileAttachment = null;
- if (post.file_ids && post.file_ids.length > 0) {
- fileAttachment = (
- <FileAttachmentListContainer
- post={post}
- compactDisplay={this.props.compactDisplay}
- />
- );
- }
-
- let userProfile = (
- <UserProfile
- user={user}
- status={this.props.status}
- isBusy={this.props.isBusy}
- isRHS={true}
- hasMention={true}
- />
- );
- let botIndicator;
-
- if (post.props && post.props.from_webhook) {
- if (post.props.override_username && global.window.mm_config.EnablePostUsernameOverride === 'true') {
- userProfile = (
- <UserProfile
- user={user}
- overwriteName={post.props.override_username}
- disablePopover={true}
- />
- );
- } else {
- userProfile = (
- <UserProfile
- user={user}
- disablePopover={true}
- />
- );
- }
-
- botIndicator = <div className='col col__name bot-indicator'>{'BOT'}</div>;
- } else if (isSystemMessage) {
- userProfile = (
- <UserProfile
- user={{}}
- overwriteName={
- <FormattedMessage
- id='post_info.system'
- defaultMessage='System'
- />
- }
- overwriteImage={Constants.SYSTEM_MESSAGE_PROFILE_IMAGE}
- disablePopover={true}
- />
- );
- }
-
- let status = this.props.status;
- if (post.props && post.props.from_webhook === 'true') {
- status = null;
- }
-
- let profilePic = (
- <ProfilePicture
- src={PostUtils.getProfilePicSrcForPost(post, user)}
- status={status}
- width='36'
- height='36'
- user={this.props.user}
- isBusy={this.props.isBusy}
- isRHS={true}
- hasMention={true}
- />
- );
-
- if (post.props && post.props.from_webhook) {
- profilePic = (
- <ProfilePicture
- src={PostUtils.getProfilePicSrcForPost(post, user)}
- width='36'
- height='36'
- />
- );
- }
-
- if (isSystemMessage) {
- profilePic = (
- <span
- className='icon'
- dangerouslySetInnerHTML={{__html: mattermostLogo}}
- />
- );
- }
-
- if (this.props.compactDisplay) {
- if (post.props && post.props.from_webhook) {
- profilePic = (
- <ProfilePicture
- src=''
- />
- );
- } else {
- profilePic = (
- <ProfilePicture
- src=''
- status={status}
- user={this.props.user}
- isBusy={this.props.isBusy}
- isRHS={true}
- hasMention={true}
- />
- );
- }
- }
-
- let postClass = '';
- if (PostUtils.isEdited(this.props.post)) {
- postClass += ' post--edited';
- }
-
- const profilePicContainer = (<div className='post__img'>{profilePic}</div>);
-
- let pinnedBadge;
- if (post.is_pinned) {
- pinnedBadge = (
- <span className='post__pinned-badge'>
- <FormattedMessage
- id='post_info.pinned'
- defaultMessage='Pinned'
- />
- </span>
- );
- }
-
- const timeOptions = {
- hour: '2-digit',
- minute: '2-digit',
- hour12: !this.props.useMilitaryTime
- };
-
- const dotMenu = (
- <DotMenu
- idPrefix={Constants.RHS_ROOT}
- post={this.props.post}
- isFlagged={this.props.isFlagged}
- handleDropdownOpened={this.handleDropdownOpened}
- commentCount={this.props.commentCount}
- />
- );
-
- return (
- <div
- id='thread--root'
- className={this.getClassName(post, isSystemMessage)}
- >
- <div className='post-right-channel__name'>{channelName}</div>
- <div className='post__content'>
- {profilePicContainer}
- <div>
- <div className='post__header'>
- <div className='col__name'>{userProfile}</div>
- {botIndicator}
- <div className='col'>
- {this.renderTimeTag(post, timeOptions)}
- {pinnedBadge}
- <PostFlagIcon
- idPrefix={'rhsRootPostFlag'}
- postId={post.id}
- isFlagged={this.props.isFlagged}
- />
- </div>
- <div
- ref='dotMenu'
- className='col col__reply'
- >
- {dotMenu}
- {react}
- </div>
- </div>
- <div className='post__body'>
- <div className={postClass}>
- <PostBodyAdditionalContent
- post={post}
- message={
- <PostMessageContainer
- post={post}
- isRHS={true}
- hasMention={true}
- />
- }
- previewCollapsed={this.props.previewCollapsed}
- />
- </div>
- {fileAttachment}
- <ReactionListContainer post={post}/>
- </div>
- </div>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/rhs_thread/index.js b/webapp/components/rhs_thread/index.js
deleted file mode 100644
index ed7618427..000000000
--- a/webapp/components/rhs_thread/index.js
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getPost, makeGetPostsForThread} from 'mattermost-redux/selectors/entities/posts';
-import {removePost} from 'mattermost-redux/actions/posts';
-
-import RhsThread from './rhs_thread.jsx';
-
-function makeMapStateToProps() {
- const getPostsForThread = makeGetPostsForThread();
-
- return function mapStateToProps(state, ownProps) {
- const selected = getPost(state, state.views.rhs.selectedPostId);
- let posts = [];
- if (selected) {
- posts = getPostsForThread(state, {rootId: selected.id, channelId: selected.channel_id});
- }
-
- return {
- ...ownProps,
- selected,
- posts
- };
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- removePost
- }, dispatch)
- };
-}
-
-export default connect(makeMapStateToProps, mapDispatchToProps)(RhsThread);
diff --git a/webapp/components/rhs_thread/rhs_thread.jsx b/webapp/components/rhs_thread/rhs_thread.jsx
deleted file mode 100644
index 021daf3f7..000000000
--- a/webapp/components/rhs_thread/rhs_thread.jsx
+++ /dev/null
@@ -1,473 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import CreateComment from 'components/create_comment';
-import RhsHeaderPost from 'components/rhs_header_post.jsx';
-import RootPost from 'components/rhs_root_post.jsx';
-import Comment from 'components/rhs_comment.jsx';
-import FloatingTimestamp from 'components/post_view/floating_timestamp.jsx';
-import DateSeparator from 'components/post_view/date_separator.jsx';
-
-import UserStore from 'stores/user_store.jsx';
-import PreferenceStore from 'stores/preference_store.jsx';
-import WebrtcStore from 'stores/webrtc_store.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-import DelayedAction from 'utils/delayed_action.jsx';
-
-import Constants from 'utils/constants.jsx';
-const Preferences = Constants.Preferences;
-
-import $ from 'jquery';
-import PropTypes from 'prop-types';
-import React from 'react';
-import Scrollbars from 'react-custom-scrollbars';
-
-export function renderView(props) {
- return (
- <div
- {...props}
- className='scrollbar--view'
- />);
-}
-
-export function renderThumbHorizontal(props) {
- return (
- <div
- {...props}
- className='scrollbar--horizontal'
- />);
-}
-
-export function renderThumbVertical(props) {
- return (
- <div
- {...props}
- className='scrollbar--vertical'
- />);
-}
-
-export default class RhsThread extends React.Component {
- static propTypes = {
- posts: PropTypes.arrayOf(PropTypes.object).isRequired,
- selected: PropTypes.object.isRequired,
- fromSearch: PropTypes.string,
- fromFlaggedPosts: PropTypes.bool,
- fromPinnedPosts: PropTypes.bool,
- isWebrtc: PropTypes.bool,
- isMentionSearch: PropTypes.bool,
- currentUser: PropTypes.object.isRequired,
- useMilitaryTime: PropTypes.bool.isRequired,
- toggleSize: PropTypes.func,
- shrink: PropTypes.func,
- actions: PropTypes.shape({
- removePost: PropTypes.func.isRequired
- }).isRequired
- }
-
- static defaultProps = {
- fromSearch: '',
- isMentionSearch: false
- }
-
- constructor(props) {
- super(props);
-
- this.mounted = false;
-
- this.onUserChange = this.onUserChange.bind(this);
- this.forceUpdateInfo = this.forceUpdateInfo.bind(this);
- this.onPreferenceChange = this.onPreferenceChange.bind(this);
- this.onStatusChange = this.onStatusChange.bind(this);
- this.onBusy = this.onBusy.bind(this);
- this.handleResize = this.handleResize.bind(this);
- this.handleScroll = this.handleScroll.bind(this);
- this.handleScrollStop = this.handleScrollStop.bind(this);
- this.scrollStopAction = new DelayedAction(this.handleScrollStop);
-
- const openTime = (new Date()).getTime();
- const state = {};
- state.windowWidth = Utils.windowWidth();
- state.windowHeight = Utils.windowHeight();
- state.profiles = JSON.parse(JSON.stringify(UserStore.getProfiles()));
- state.compactDisplay = PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT) === Preferences.MESSAGE_DISPLAY_COMPACT;
- state.flaggedPosts = PreferenceStore.getCategory(Constants.Preferences.CATEGORY_FLAGGED_POST);
- state.statuses = Object.assign({}, UserStore.getStatuses());
- state.previewsCollapsed = PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.COLLAPSE_DISPLAY, 'false');
- state.isBusy = WebrtcStore.isBusy();
-
- this.state = {
- ...state,
- isScrolling: false,
- topRhsPostCreateAt: 0,
- openTime
- };
- }
-
- componentDidMount() {
- PreferenceStore.addChangeListener(this.onPreferenceChange);
- UserStore.addChangeListener(this.onUserChange);
- UserStore.addStatusesChangeListener(this.onStatusChange);
- WebrtcStore.addBusyListener(this.onBusy);
-
- this.scrollToBottom();
- window.addEventListener('resize', this.handleResize);
-
- this.mounted = true;
- }
-
- componentWillUnmount() {
- PreferenceStore.removeChangeListener(this.onPreferenceChange);
- UserStore.removeChangeListener(this.onUserChange);
- UserStore.removeStatusesChangeListener(this.onStatusChange);
- WebrtcStore.removeBusyListener(this.onBusy);
-
- window.removeEventListener('resize', this.handleResize);
-
- this.mounted = false;
- }
-
- componentDidUpdate(prevProps) {
- const prevPostsArray = prevProps.posts || [];
- const curPostsArray = this.props.posts || [];
-
- if (prevPostsArray.length >= curPostsArray.length) {
- return;
- }
-
- const curLastPost = curPostsArray[curPostsArray.length - 1];
-
- if (curLastPost.user_id === UserStore.getCurrentId()) {
- this.scrollToBottom();
- }
- }
-
- shouldComponentUpdate(nextProps, nextState) {
- if (!Utils.areObjectsEqual(nextState.statuses, this.state.statuses)) {
- return true;
- }
-
- if (!Utils.areObjectsEqual(nextState.postsArray, this.props.posts)) {
- return true;
- }
-
- if (!Utils.areObjectsEqual(nextState.selected, this.props.selected)) {
- return true;
- }
-
- if (nextState.compactDisplay !== this.state.compactDisplay) {
- return true;
- }
-
- if (nextProps.useMilitaryTime !== this.props.useMilitaryTime) {
- return true;
- }
-
- if (nextState.previewsCollapsed !== this.state.previewsCollapsed) {
- return true;
- }
-
- if (!Utils.areObjectsEqual(nextState.flaggedPosts, this.state.flaggedPosts)) {
- return true;
- }
-
- if (!Utils.areObjectsEqual(nextState.profiles, this.state.profiles)) {
- return true;
- }
-
- if (!Utils.areObjectsEqual(nextProps.currentUser, this.props.currentUser)) {
- return true;
- }
-
- if (nextState.isBusy !== this.state.isBusy) {
- return true;
- }
-
- if (nextState.isScrolling !== this.state.isScrolling) {
- return true;
- }
-
- if (nextState.topRhsPostCreateAt !== this.state.topRhsPostCreateAt) {
- return true;
- }
-
- return false;
- }
-
- forceUpdateInfo() {
- if (this.state.postList) {
- for (var postId in this.state.postList.posts) {
- if (this.refs[postId]) {
- this.refs[postId].forceUpdate();
- }
- }
- }
- }
-
- handleResize() {
- this.setState({
- windowWidth: Utils.windowWidth(),
- windowHeight: Utils.windowHeight()
- });
- }
-
- componentWillReceiveProps(nextProps) {
- if (!this.props.selected || !nextProps.selected) {
- return;
- }
-
- if (this.props.selected.id !== nextProps.selected.id) {
- this.setState({
- openTime: (new Date()).getTime()
- });
- }
- }
-
- onPreferenceChange(category) {
- let previewSuffix = '';
- if (category === Preferences.CATEGORY_DISPLAY_SETTINGS) {
- previewSuffix = '_' + Utils.generateId();
- }
-
- this.setState({
- compactDisplay: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT) === Preferences.MESSAGE_DISPLAY_COMPACT,
- flaggedPosts: PreferenceStore.getCategory(Constants.Preferences.CATEGORY_FLAGGED_POST),
- previewsCollapsed: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.COLLAPSE_DISPLAY, 'false') + previewSuffix
- });
- this.forceUpdateInfo();
- }
-
- onStatusChange() {
- this.setState({statuses: Object.assign({}, UserStore.getStatuses())});
- }
-
- onBusy(isBusy) {
- this.setState({isBusy});
- }
-
- filterPosts(posts, selected, openTime) {
- const postsArray = [];
-
- posts.forEach((cpost) => {
- // Do not show empherals created before sidebar has been opened
- if (cpost.type === 'system_ephemeral' && cpost.create_at < openTime) {
- return;
- }
-
- if (cpost.root_id === selected.id) {
- postsArray.unshift(cpost);
- }
- });
-
- return postsArray;
- }
-
- onUserChange() {
- const profiles = JSON.parse(JSON.stringify(UserStore.getProfiles()));
- this.setState({profiles});
- }
-
- scrollToBottom() {
- if ($('.post-right__scroll')[0]) {
- $('.post-right__scroll').parent().scrollTop($('.post-right__scroll')[0].scrollHeight);
- }
- }
-
- updateFloatingTimestamp() {
- // skip this in non-mobile view since that's when the timestamp is visible
- if (!Utils.isMobile()) {
- return;
- }
-
- if (this.props.posts) {
- const childNodes = this.refs.rhspostlist.childNodes;
- const viewPort = this.refs.rhspostlist.getBoundingClientRect();
- let topRhsPostCreateAt = 0;
- const offset = 100;
-
- // determine the top rhs comment assuming that childNodes and postsArray are of same length
- for (let i = 0; i < childNodes.length; i++) {
- if ((childNodes[i].offsetTop + viewPort.top) - offset > 0) {
- topRhsPostCreateAt = this.props.posts[i].create_at;
- break;
- }
- }
-
- if (topRhsPostCreateAt !== this.state.topRhsPostCreateAt) {
- this.setState({
- topRhsPostCreateAt
- });
- }
- }
- }
-
- handleScroll() {
- this.updateFloatingTimestamp();
-
- if (!this.state.isScrolling) {
- this.setState({
- isScrolling: true
- });
- }
-
- this.scrollStopAction.fireAfter(Constants.SCROLL_DELAY);
- }
-
- handleScrollStop() {
- this.setState({
- isScrolling: false
- });
- }
-
- getSidebarBody = () => {
- return this.refs.sidebarbody;
- }
-
- render() {
- if (this.props.posts == null || this.props.selected == null) {
- return (
- <div/>
- );
- }
-
- const postsArray = this.filterPosts(this.props.posts, this.props.selected, this.state.openTime);
- const selected = this.props.selected;
- const profiles = this.state.profiles || {};
-
- let profile;
- if (UserStore.getCurrentId() === selected.user_id) {
- profile = this.props.currentUser;
- } else {
- profile = profiles[selected.user_id];
- }
-
- let isRootFlagged = false;
- if (this.state.flaggedPosts) {
- isRootFlagged = this.state.flaggedPosts.get(selected.id) != null;
- }
-
- let rootStatus = 'offline';
- if (this.state.statuses) {
- rootStatus = this.state.statuses[selected.user_id] || 'offline';
- }
-
- const rootPostDay = Utils.getDateForUnixTicks(selected.create_at);
- let previousPostDay = rootPostDay;
-
- const commentsLists = [];
- const postsLength = postsArray.length;
- for (let i = 0; i < postsLength; i++) {
- const comPost = postsArray[i];
- let p;
- if (UserStore.getCurrentId() === comPost.user_id) {
- p = UserStore.getCurrentUser();
- } else {
- p = profiles[comPost.user_id];
- }
-
- let isFlagged = false;
- if (this.state.flaggedPosts) {
- isFlagged = this.state.flaggedPosts.get(comPost.id) != null;
- }
-
- let status = 'offline';
- if (this.state.statuses && p && p.id) {
- status = this.state.statuses[p.id] || 'offline';
- }
-
- const currentPostDay = Utils.getDateForUnixTicks(comPost.create_at);
- if (currentPostDay.toDateString() !== previousPostDay.toDateString()) {
- previousPostDay = currentPostDay;
- commentsLists.push(
- <DateSeparator
- date={currentPostDay}
- />);
- }
-
- const keyPrefix = comPost.id ? comPost.id : comPost.pending_post_id;
- const reverseCount = postsLength - i - 1;
- commentsLists.push(
- <div key={keyPrefix + 'commentKey'}>
- <Comment
- ref={comPost.id}
- post={comPost}
- lastPostCount={(reverseCount >= 0 && reverseCount < Constants.TEST_ID_COUNT) ? reverseCount : -1}
- user={p}
- currentUser={this.props.currentUser}
- compactDisplay={this.state.compactDisplay}
- useMilitaryTime={this.props.useMilitaryTime}
- isFlagged={isFlagged}
- status={status}
- isBusy={this.state.isBusy}
- removePost={this.props.actions.removePost}
- />
- </div>
- );
- }
-
- return (
- <div
- className='sidebar-right__body'
- ref='sidebarbody'
- >
- <FloatingTimestamp
- isScrolling={this.state.isScrolling}
- isMobile={Utils.isMobile()}
- createAt={this.state.topRhsPostCreateAt}
- isRhsPost={true}
- />
- <RhsHeaderPost
- fromFlaggedPosts={this.props.fromFlaggedPosts}
- fromSearch={this.props.fromSearch}
- fromPinnedPosts={this.props.fromPinnedPosts}
- isWebrtc={this.props.isWebrtc}
- isMentionSearch={this.props.isMentionSearch}
- toggleSize={this.props.toggleSize}
- shrink={this.props.shrink}
- />
- <Scrollbars
- autoHide={true}
- autoHideTimeout={500}
- autoHideDuration={500}
- renderThumbHorizontal={renderThumbHorizontal}
- renderThumbVertical={renderThumbVertical}
- renderView={renderView}
- onScroll={this.handleScroll}
- >
- <div className='post-right__scroll'>
- <DateSeparator
- date={rootPostDay}
- />
- <RootPost
- ref={selected.id}
- post={selected}
- commentCount={postsLength}
- user={profile}
- currentUser={this.props.currentUser}
- compactDisplay={this.state.compactDisplay}
- useMilitaryTime={this.props.useMilitaryTime}
- isFlagged={isRootFlagged}
- status={rootStatus}
- previewCollapsed={this.state.previewsCollapsed}
- isBusy={this.state.isBusy}
- />
- <div
- ref='rhspostlist'
- className='post-right-comments-container'
- >
- {commentsLists}
- </div>
- <div className='post-create__container'>
- <CreateComment
- channelId={selected.channel_id}
- rootId={selected.id}
- latestPostId={postsLength > 0 ? postsArray[postsLength - 1].id : selected.id}
- getSidebarBody={this.getSidebarBody}
- />
- </div>
- </div>
- </Scrollbars>
- </div>
- );
- }
-}
diff --git a/webapp/components/root.jsx b/webapp/components/root.jsx
deleted file mode 100644
index 2ed493b52..000000000
--- a/webapp/components/root.jsx
+++ /dev/null
@@ -1,147 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import * as GlobalActions from 'actions/global_actions.jsx';
-import LocalizationStore from 'stores/localization_store.jsx';
-import {Client4} from 'mattermost-redux/client';
-
-import {IntlProvider} from 'react-intl';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import FastClick from 'fastclick';
-import $ from 'jquery';
-
-import {browserHistory} from 'react-router/es6';
-import UserStore from 'stores/user_store.jsx';
-import BrowserStore from 'stores/browser_store.jsx';
-import Constants from 'utils/constants.jsx';
-
-export default class Root extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- locale: LocalizationStore.getLocale(),
- translations: LocalizationStore.getTranslations()
- };
-
- this.localizationChanged = this.localizationChanged.bind(this);
- this.redirectIfNecessary = this.redirectIfNecessary.bind(this);
-
- const segmentKey = Constants.DIAGNOSTICS_SEGMENT_KEY;
-
- // Ya....
- /*eslint-disable */
- if (segmentKey != null && segmentKey !== '' && window.mm_config.DiagnosticsEnabled === 'true') {
- !function(){var analytics=global.window.analytics=global.window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","group","track","ready","alias","page","once","off","on"];analytics.factory=function(t){return function(){var e=Array.prototype.slice.call(arguments);e.unshift(t);analytics.push(e);return analytics}};for(var t=0;t<analytics.methods.length;t++){var e=analytics.methods[t];analytics[e]=analytics.factory(e)}analytics.load=function(t){var e=document.createElement("script");e.type="text/javascript";e.async=!0;e.src=("https:"===document.location.protocol?"https://":"http://")+"cdn.segment.com/analytics.js/v1/"+t+"/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(e,n)};analytics.SNIPPET_VERSION="3.0.1";
- analytics.load(segmentKey);
-
- analytics.page('ApplicationLoaded', {
- path: '',
- referrer: '',
- search: '',
- title: '',
- url: '',
- },
- {
- context: {
- ip: '0.0.0.0'
- },
- anonymousId: '00000000000000000000000000'
- });
- }}();
- }
- /*eslint-enable */
-
- // Force logout of all tabs if one tab is logged out
- $(window).bind('storage', (e) => {
- // when one tab on a browser logs out, it sets __logout__ in localStorage to trigger other tabs to log out
- if (e.originalEvent.key === '__logout__' && e.originalEvent.storageArea === localStorage && e.originalEvent.newValue) {
- // make sure it isn't this tab that is sending the logout signal (only necessary for IE11)
- if (BrowserStore.isSignallingLogout(e.originalEvent.newValue)) {
- return;
- }
-
- console.log('detected logout from a different tab'); //eslint-disable-line no-console
- GlobalActions.emitUserLoggedOutEvent('/', false);
- }
-
- if (e.originalEvent.key === '__login__' && e.originalEvent.storageArea === localStorage && e.originalEvent.newValue) {
- // make sure it isn't this tab that is sending the logout signal (only necessary for IE11)
- if (BrowserStore.isSignallingLogin(e.originalEvent.newValue)) {
- return;
- }
-
- console.log('detected login from a different tab'); //eslint-disable-line no-console
- location.reload();
- }
- });
-
- // Fastclick
- FastClick.attach(document.body);
- }
-
- localizationChanged() {
- const locale = LocalizationStore.getLocale();
-
- Client4.setAcceptLanguage(locale);
- this.setState({locale, translations: LocalizationStore.getTranslations()});
- }
-
- redirectIfNecessary(props) {
- if (props.location.pathname === '/') {
- if (UserStore.getNoAccounts()) {
- browserHistory.push('/signup_user_complete');
- } else if (UserStore.getCurrentUser()) {
- GlobalActions.redirectUserToDefaultTeam();
- } else {
- browserHistory.push('/login' + window.location.search);
- }
- }
- }
-
- componentWillReceiveProps(newProps) {
- this.redirectIfNecessary(newProps);
- }
-
- componentWillMount() {
- // Redirect if Necessary
- this.redirectIfNecessary(this.props);
- }
-
- componentDidMount() {
- // Setup localization listener
- LocalizationStore.addChangeListener(this.localizationChanged);
-
- // Get our localizaiton
- GlobalActions.loadCurrentLocale();
- }
-
- componentWillUnmount() {
- LocalizationStore.removeChangeListener(this.localizationChanged);
- }
-
- render() {
- if (this.state.translations == null || this.props.children == null) {
- return <div/>;
- }
-
- return (
- <IntlProvider
- locale={this.state.locale}
- messages={this.state.translations}
- key={this.state.locale}
- >
- {this.props.children}
- </IntlProvider>
- );
- }
-}
-
-Root.defaultProps = {
-};
-
-Root.propTypes = {
- children: PropTypes.object
-};
diff --git a/webapp/components/search_bar.jsx b/webapp/components/search_bar.jsx
deleted file mode 100644
index 7915b82ab..000000000
--- a/webapp/components/search_bar.jsx
+++ /dev/null
@@ -1,406 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import * as GlobalActions from 'actions/global_actions.jsx';
-import SearchStore from 'stores/search_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
-import SuggestionBox from './suggestion/suggestion_box.jsx';
-import SearchChannelProvider from './suggestion/search_channel_provider.jsx';
-import SearchSuggestionList from './suggestion/search_suggestion_list.jsx';
-import SearchUserProvider from './suggestion/search_user_provider.jsx';
-import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-import {getFlaggedPosts, performSearch} from 'actions/post_actions.jsx';
-
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-
-const ActionTypes = Constants.ActionTypes;
-const KeyCodes = Constants.KeyCodes;
-
-import {Tooltip, OverlayTrigger, Popover} from 'react-bootstrap';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class SearchBar extends React.Component {
- constructor() {
- super();
- this.mounted = false;
-
- this.onListenerChange = this.onListenerChange.bind(this);
- this.handleChange = this.handleChange.bind(this);
- this.handleUserFocus = this.handleUserFocus.bind(this);
- this.handleClear = this.handleClear.bind(this);
- this.handleUserBlur = this.handleUserBlur.bind(this);
- this.handleSearch = this.handleSearch.bind(this);
- this.handleSearchOnSuccess = this.handleSearchOnSuccess.bind(this);
- this.handleSearchOnError = this.handleSearchOnError.bind(this);
- this.handleSubmit = this.handleSubmit.bind(this);
- this.searchMentions = this.searchMentions.bind(this);
- this.getFlagged = this.getFlagged.bind(this);
- this.handleKeyDown = this.handleKeyDown.bind(this);
-
- const state = this.getSearchTermStateFromStores();
- state.focused = false;
- state.isPristine = true;
- this.state = state;
-
- this.suggestionProviders = [new SearchChannelProvider(), new SearchUserProvider()];
- }
-
- getSearchTermStateFromStores() {
- var term = SearchStore.getSearchTerm() || '';
- return {
- searchTerm: term
- };
- }
-
- componentDidMount() {
- SearchStore.addSearchTermChangeListener(this.onListenerChange);
- this.mounted = true;
-
- if (Utils.isMobile()) {
- setTimeout(() => {
- document.querySelector('.app__body .sidebar--menu').classList.remove('visible');
- });
- }
- }
-
- componentWillUnmount() {
- SearchStore.removeSearchTermChangeListener(this.onListenerChange);
- this.mounted = false;
- }
-
- onListenerChange(doSearch, isMentionSearch) {
- if (this.mounted) {
- var newState = this.getSearchTermStateFromStores();
- if (!Utils.areObjectsEqual(newState, this.state)) {
- this.setState(newState);
- }
- if (doSearch) {
- this.handleSearch(newState.searchTerm, isMentionSearch);
- }
- }
- }
-
- handleClose(e) {
- e.preventDefault();
-
- if (Utils.isMobile()) {
- setTimeout(() => {
- document.querySelector('.app__body .sidebar--menu').classList.add('visible');
- });
- }
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_SEARCH,
- results: null
- });
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_SEARCH_TERM,
- term: null,
- do_search: false,
- is_mention_search: false
- });
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_POST_SELECTED,
- postId: null
- });
- }
-
- handleKeyDown(e) {
- if (e.which === KeyCodes.ESCAPE) {
- e.stopPropagation();
- e.preventDefault();
- }
- }
-
- handleChange(e) {
- var term = e.target.value;
- SearchStore.storeSearchTerm(term);
- SearchStore.emitSearchTermChange(false, false);
- this.setState({searchTerm: term});
- }
-
- handleUserBlur() {
- this.setState({focused: false});
- }
-
- handleClear() {
- this.setState({searchTerm: ''});
- }
-
- handleUserFocus() {
- this.setState({focused: true});
- }
-
- handleSearch(terms, isMentionSearch) {
- if (terms.length) {
- this.setState({
- isSearching: true,
- isPristine: false
- });
-
- performSearch(
- terms,
- isMentionSearch,
- () => {
- this.handleSearchOnSuccess();
- },
- () => {
- this.handleSearchOnError();
- }
- );
- }
- }
-
- handleSearchOnSuccess() {
- if (this.mounted) {
- this.setState({isSearching: false});
-
- if (Utils.isMobile() && this.search) {
- this.search.value = '';
- }
- }
- }
-
- handleSearchOnError() {
- if (this.mounted) {
- this.setState({isSearching: false});
- }
- }
-
- handleSubmit(e) {
- e.preventDefault();
- const terms = this.state.searchTerm.trim();
-
- if (terms.length === 0) {
- return;
- }
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_SEARCH_TERM,
- term: terms,
- do_search: true,
- is_mention_search: false
- });
-
- this.search.blur();
- }
-
- searchMentions(e) {
- e.preventDefault();
- const user = UserStore.getCurrentUser();
- if (SearchStore.isMentionSearch) {
- // Close
- GlobalActions.toggleSideBarAction(false);
- } else {
- GlobalActions.emitSearchMentionsEvent(user);
- }
- }
-
- getFlagged(e) {
- e.preventDefault();
- if (SearchStore.isFlaggedPosts) {
- GlobalActions.toggleSideBarAction(false);
- } else {
- getFlaggedPosts();
- }
- }
-
- renderHintPopover(helpClass) {
- if (!this.props.isCommentsPage && Utils.isMobile() && this.state.isPristine) {
- return false;
- }
-
- return (
- <Popover
- id='searchbar-help-popup'
- placement='bottom'
- className={helpClass}
- >
- <FormattedHTMLMessage
- id='search_bar.usage'
- defaultMessage='<h4>Search Options</h4><ul><li><span>Use </span><b>"quotation marks"</b><span> to search for phrases</span></li><li><span>Use </span><b>from:</b><span> to find posts from specific users and </span><b>in:</b><span> to find posts in specific channels</span></li></ul>'
- />
- </Popover>
- );
- }
-
- render() {
- const flagIcon = Constants.FLAG_ICON_SVG;
- const searchIcon = Constants.SEARCH_ICON_SVG;
- const mentionsIcon = Constants.MENTIONS_ICON_SVG;
-
- var isSearching = null;
- if (this.state.isSearching) {
- isSearching = <span className={'fa fa-spin fa-spinner'}/>;
- }
-
- let helpClass = 'search-help-popover';
- if (!this.state.searchTerm && this.state.focused) {
- helpClass += ' visible';
- }
-
- const recentMentionsTooltip = (
- <Tooltip id='recentMentionsTooltip'>
- <FormattedMessage
- id='channel_header.recentMentions'
- defaultMessage='Recent Mentions'
- />
- </Tooltip>
- );
-
- const flaggedTooltip = (
- <Tooltip
- id='flaggedTooltip'
- className='text-nowrap'
- >
- <FormattedMessage
- id='channel_header.flagged'
- defaultMessage='Flagged Posts'
- />
- </Tooltip>
- );
-
- let mentionBtn;
- let flagBtn;
- if (this.props.showMentionFlagBtns) {
- var mentionBtnClass = SearchStore.isMentionSearch ? 'active' : '';
-
- mentionBtn = (
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={Constants.OVERLAY_TIME_DELAY}
- placement='bottom'
- overlay={recentMentionsTooltip}
- >
- <div
- className={'channel-header__icon ' + mentionBtnClass}
- onClick={this.searchMentions}
- >
- <span
- className='icon icon__mentions'
- dangerouslySetInnerHTML={{__html: mentionsIcon}}
- aria-hidden='true'
- />
- </div>
- </OverlayTrigger>
- );
-
- var flagBtnClass = SearchStore.isFlaggedPosts ? 'active' : '';
-
- flagBtn = (
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={Constants.OVERLAY_TIME_DELAY}
- placement='bottom'
- overlay={flaggedTooltip}
- >
- <div
- className={'channel-header__icon ' + flagBtnClass}
- >
- <a
- href='#'
- type='button'
- onClick={this.getFlagged}
- >
- <span
- className='icon icon__flag'
- dangerouslySetInnerHTML={{__html: flagIcon}}
- />
- </a>
- </div>
- </OverlayTrigger>
- );
- }
-
- let clearClass = 'sidebar__search-clear';
- if (!this.state.isSearching && this.state.searchTerm && this.state.searchTerm.trim() !== '') {
- clearClass += ' visible';
- }
-
- let searchFormClass = 'search__form';
- if (this.state.focused) {
- searchFormClass += ' focused';
- }
-
- return (
- <div className='sidebar-right__table'>
- <div className='sidebar-collapse__container'>
- <div
- className='sidebar-collapse'
- onClick={this.handleClose}
- >
- <span className='fa fa-chevron-left'/>
- </div>
- </div>
- <div className='search-form__container'>
- <form
- role='form'
- className={searchFormClass}
- onSubmit={this.handleSubmit}
- style={{overflow: 'visible'}}
- autoComplete='off'
- >
- <span
- className='search__icon'
- dangerouslySetInnerHTML={{__html: searchIcon}}
- aria-hidden='true'
- />
- <SuggestionBox
- ref={(search) => {
- this.search = search;
- }}
- className='search-bar'
- placeholder={Utils.localizeMessage('search_bar.search', 'Search')}
- value={this.state.searchTerm}
- onFocus={this.handleUserFocus}
- onBlur={this.handleUserBlur}
- onChange={this.handleChange}
- onKeyDown={this.handleKeyDown}
- listComponent={SearchSuggestionList}
- providers={this.suggestionProviders}
- type='search'
- autoFocus={this.props.isFocus && this.state.searchTerm === ''}
- />
- <div
- className={clearClass}
- onClick={this.handleClear}
- >
- <span
- className='sidebar__search-clear-x'
- aria-hidden='true'
- >
- {'×'}
- </span>
- </div>
- {isSearching}
- {this.renderHintPopover(helpClass)}
- </form>
- </div>
- <div>
- {mentionBtn}
- </div>
- <div>
- {flagBtn}
- </div>
- </div>
- );
- }
-}
-
-SearchBar.defaultProps = {
- showMentionFlagBtns: true,
- isFocus: false
-};
-
-SearchBar.propTypes = {
- showMentionFlagBtns: PropTypes.bool,
- isCommentsPage: PropTypes.bool,
- isFocus: PropTypes.bool
-};
diff --git a/webapp/components/search_results.jsx b/webapp/components/search_results.jsx
deleted file mode 100644
index 1499c8abb..000000000
--- a/webapp/components/search_results.jsx
+++ /dev/null
@@ -1,367 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import SearchResultsHeader from './search_results_header.jsx';
-import SearchResultsItem from './search_results_item.jsx';
-
-import ChannelStore from 'stores/channel_store.jsx';
-import SearchStore from 'stores/search_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-import PreferenceStore from 'stores/preference_store.jsx';
-import WebrtcStore from 'stores/webrtc_store.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-const Preferences = Constants.Preferences;
-
-import $ from 'jquery';
-import PropTypes from 'prop-types';
-import React from 'react';
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-
-function getStateFromStores() {
- const results = JSON.parse(JSON.stringify(SearchStore.getSearchResults()));
-
- const channels = new Map();
-
- if (results && results.order) {
- const channelIds = results.order.map((postId) => results.posts[postId].channel_id);
- for (const id of channelIds) {
- if (channels.has(id)) {
- continue;
- }
-
- channels.set(id, ChannelStore.get(id));
- }
- }
-
- return {
- results,
- channels,
- searchTerm: SearchStore.getSearchTerm(),
- flaggedPosts: PreferenceStore.getCategory(Constants.Preferences.CATEGORY_FLAGGED_POST),
- loading: SearchStore.isLoading()
- };
-}
-
-export default class SearchResults extends React.Component {
- constructor(props) {
- super(props);
-
- this.mounted = false;
-
- this.onChange = this.onChange.bind(this);
- this.onUserChange = this.onUserChange.bind(this);
- this.onPreferenceChange = this.onPreferenceChange.bind(this);
- this.onBusy = this.onBusy.bind(this);
- this.resize = this.resize.bind(this);
- this.onPreferenceChange = this.onPreferenceChange.bind(this);
- this.onStatusChange = this.onStatusChange.bind(this);
- this.handleResize = this.handleResize.bind(this);
-
- const state = getStateFromStores();
- state.windowWidth = Utils.windowWidth();
- state.windowHeight = Utils.windowHeight();
- state.profiles = JSON.parse(JSON.stringify(UserStore.getProfiles()));
- state.compactDisplay = PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT) === Preferences.MESSAGE_DISPLAY_COMPACT;
- state.isBusy = WebrtcStore.isBusy();
- state.statuses = Object.assign({}, UserStore.getStatuses());
- this.state = state;
- }
-
- componentDidMount() {
- this.mounted = true;
-
- SearchStore.addSearchTermChangeListener(this.onSearchTermChange);
- SearchStore.addSearchChangeListener(this.onChange);
- ChannelStore.addChangeListener(this.onChange);
- PreferenceStore.addChangeListener(this.onPreferenceChange);
- UserStore.addChangeListener(this.onUserChange);
- UserStore.addStatusesChangeListener(this.onStatusChange);
- PreferenceStore.addChangeListener(this.onPreferenceChange);
- WebrtcStore.addBusyListener(this.onBusy);
-
- this.resize();
- window.addEventListener('resize', this.handleResize);
- if (!Utils.isMobile()) {
- $('.sidebar--right .search-items-container').perfectScrollbar();
- }
- }
-
- shouldComponentUpdate(nextProps, nextState) {
- if (!Utils.areObjectsEqual(nextState.statuses, this.state.statuses)) {
- return true;
- }
-
- if (!Utils.areObjectsEqual(this.props, nextProps)) {
- return true;
- }
-
- if (!Utils.areObjectsEqual(this.state, nextState)) {
- return true;
- }
-
- if (nextState.compactDisplay !== this.state.compactDisplay) {
- return true;
- }
-
- if (nextState.isBusy !== this.state.isBusy) {
- return true;
- }
-
- return false;
- }
-
- componentWillUnmount() {
- this.mounted = false;
-
- SearchStore.removeSearchTermChangeListener(this.onSearchTermChange);
- SearchStore.removeSearchChangeListener(this.onChange);
- ChannelStore.removeChangeListener(this.onChange);
- PreferenceStore.removeChangeListener(this.onPreferenceChange);
- UserStore.removeChangeListener(this.onUserChange);
- UserStore.removeStatusesChangeListener(this.onStatusChange);
- PreferenceStore.removeChangeListener(this.onPreferenceChange);
- WebrtcStore.removeBusyListener(this.onBusy);
-
- window.removeEventListener('resize', this.handleResize);
- }
-
- componentDidUpdate(prevProps, prevState) {
- if (this.state.searchTerm !== prevState.searchTerm) {
- this.resize();
- }
- }
-
- handleResize() {
- this.setState({
- windowWidth: Utils.windowWidth(),
- windowHeight: Utils.windowHeight()
- });
- }
-
- onPreferenceChange() {
- this.setState({
- compactDisplay: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT) === Preferences.MESSAGE_DISPLAY_COMPACT,
- flaggedPosts: PreferenceStore.getCategory(Constants.Preferences.CATEGORY_FLAGGED_POST)
- });
- }
-
- onSearchTermChange(doSearch) {
- if (this.mounted && doSearch) {
- this.setState({
- loading: true
- });
- }
- }
-
- onChange() {
- if (this.mounted) {
- this.setState(getStateFromStores());
- }
- }
-
- onUserChange() {
- this.setState({profiles: JSON.parse(JSON.stringify(UserStore.getProfiles()))});
- }
-
- onBusy(isBusy) {
- this.setState({isBusy});
- }
-
- onStatusChange() {
- this.setState({statuses: Object.assign({}, UserStore.getStatuses())});
- }
-
- resize() {
- $('#search-items-container').scrollTop(0);
- }
-
- render() {
- var results = this.state.results;
- var noResults = (!results || !results.order || !results.order.length);
- const searchTerm = this.state.searchTerm;
- const profiles = this.state.profiles || {};
- const flagIcon = Constants.FLAG_ICON_SVG;
-
- var ctls = null;
-
- if (this.state.loading) {
- ctls =
- (
- <div className='sidebar--right__subheader'>
- <div className='sidebar--right__loading'>
- <i className='fa fa-spinner fa-spin'/>
- <FormattedMessage
- id='search_header.loading'
- defaultMessage='Searching...'
- />
- </div>
- </div>
- );
- } else if (this.props.isFlaggedPosts && noResults) {
- ctls = (
- <div className='sidebar--right__subheader'>
- <ul>
- <li>
- <FormattedHTMLMessage
- id='search_results.usageFlag1'
- defaultMessage="You haven't flagged any messages yet."
- />
- </li>
- <li>
- <FormattedHTMLMessage
- id='search_results.usageFlag2'
- defaultMessage='You can add a flag to messages and comments by clicking the '
- />
- <span
- className='usage__icon'
- dangerouslySetInnerHTML={{__html: flagIcon}}
- />
- <FormattedHTMLMessage
- id='search_results.usageFlag3'
- defaultMessage=' icon next to the timestamp.'
- />
- </li>
- <li>
- <FormattedHTMLMessage
- id='search_results.usageFlag4'
- defaultMessage='Flags are a way to mark messages for follow up. Your flags are personal, and cannot be seen by other users.'
- />
- </li>
- </ul>
- </div>
- );
- } else if (this.props.isPinnedPosts && noResults) {
- ctls = (
- <div className='sidebar--right__subheader'>
- <ul>
- <li>
- <FormattedHTMLMessage
- id='search_results.usagePin1'
- defaultMessage='There are no pinned messages yet.'
- />
- </li>
- <li>
- <FormattedHTMLMessage
- id='search_results.usagePin2'
- defaultMessage='All members of this channel can pin important or useful messages.'
- />
- </li>
- <li>
- <FormattedHTMLMessage
- id='search_results.usagePin3'
- defaultMessage='Pinned messages are visible to all channel members.'
- />
- </li>
- <li>
- <FormattedHTMLMessage
- id='search_results.usagePin4'
- defaultMessage={'To pin a message: Go to the message that you want to pin and click [...] > "Pin to channel".'}
- />
- </li>
- </ul>
- </div>
- );
- } else if (!searchTerm && noResults) {
- ctls = (
- <div className='sidebar--right__subheader'>
- <FormattedHTMLMessage
- id='search_results.usage'
- defaultMessage='<ul><li>Use <b>"quotation marks"</b> to search for phrases</li><li>Use <b>from:</b> to find posts from specific users and <b>in:</b> to find posts in specific channels</li></ul>'
- />
- </div>
- );
- } else if (noResults) {
- ctls =
- (
- <div className='sidebar--right__subheader'>
- <h4>
- <FormattedMessage
- id='search_results.noResults'
- defaultMessage='No results found. Try again?'
- />
- </h4>
- <FormattedHTMLMessage
- id='search_results.because'
- defaultMessage='<ul>
- <li>If you&#39;re searching a partial phrase (ex. searching "rea", looking for "reach" or "reaction"), append a * to your search term.</li>
- <li>Two letter searches and common words like "this", "a" and "is" won&#39;t appear in search results, due to the excessive results returned.</li>
- </ul>'
- />
- </div>
- );
- } else {
- ctls = results.order.map(function searchResults(id, idx, arr) {
- const post = results.posts[id];
- let profile;
- if (UserStore.getCurrentId() === post.user_id) {
- profile = UserStore.getCurrentUser();
- } else {
- profile = profiles[post.user_id];
- }
-
- let status = 'offline';
- if (this.state.statuses) {
- status = this.state.statuses[post.user_id] || 'offline';
- }
-
- let isFlagged = false;
- if (this.state.flaggedPosts) {
- isFlagged = this.state.flaggedPosts.get(post.id) != null;
- }
-
- const reverseCount = arr.length - idx - 1;
-
- return (
- <SearchResultsItem
- key={post.id}
- channel={this.state.channels.get(post.channel_id)}
- compactDisplay={this.state.compactDisplay}
- post={post}
- lastPostCount={(reverseCount >= 0 && reverseCount < Constants.TEST_ID_COUNT) ? reverseCount : -1}
- user={profile}
- term={searchTerm}
- isMentionSearch={this.props.isMentionSearch}
- isFlaggedSearch={this.props.isFlaggedPosts}
- useMilitaryTime={this.props.useMilitaryTime}
- shrink={this.props.shrink}
- isFlagged={isFlagged}
- isBusy={this.state.isBusy}
- status={status}
- />
- );
- }, this);
- }
-
- return (
- <div className='sidebar-right__body'>
- <SearchResultsHeader
- isMentionSearch={this.props.isMentionSearch}
- toggleSize={this.props.toggleSize}
- shrink={this.props.shrink}
- isFlaggedPosts={this.props.isFlaggedPosts}
- isPinnedPosts={this.props.isPinnedPosts}
- channelDisplayName={this.props.channelDisplayName}
- isLoading={this.state.loading}
- />
- <div
- id='search-items-container'
- className='search-items-container'
- >
- {ctls}
- </div>
- </div>
- );
- }
-}
-
-SearchResults.propTypes = {
- isMentionSearch: PropTypes.bool,
- useMilitaryTime: PropTypes.bool.isRequired,
- toggleSize: PropTypes.func,
- shrink: PropTypes.func,
- isFlaggedPosts: PropTypes.bool,
- isPinnedPosts: PropTypes.bool,
- channelDisplayName: PropTypes.string.isRequired
-};
diff --git a/webapp/components/search_results_header.jsx b/webapp/components/search_results_header.jsx
deleted file mode 100644
index b3f77c413..000000000
--- a/webapp/components/search_results_header.jsx
+++ /dev/null
@@ -1,153 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import Constants from 'utils/constants.jsx';
-import {Tooltip, OverlayTrigger} from 'react-bootstrap';
-import * as GlobalActions from 'actions/global_actions.jsx';
-
-import {FormattedMessage} from 'react-intl';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class SearchResultsHeader extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleClose = this.handleClose.bind(this);
- this.toggleSize = this.toggleSize.bind(this);
- }
-
- handleClose(e) {
- e.preventDefault();
-
- GlobalActions.toggleSideBarAction(false);
-
- this.props.shrink();
- }
-
- toggleSize(e) {
- e.preventDefault();
- this.props.toggleSize();
- }
-
- render() {
- var title = (
- <FormattedMessage
- id='search_header.results'
- defaultMessage='Search Results'
- />
- );
-
- const closeSidebarTooltip = (
- <Tooltip id='closeSidebarTooltip'>
- <FormattedMessage
- id='rhs_header.closeSidebarTooltip'
- defaultMessage='Close Sidebar'
- />
- </Tooltip>
- );
-
- const expandSidebarTooltip = (
- <Tooltip id='expandSidebarTooltip'>
- <FormattedMessage
- id='rhs_header.expandSidebarTooltip'
- defaultMessage='Expand Sidebar'
- />
- </Tooltip>
- );
-
- const shrinkSidebarTooltip = (
- <Tooltip id='shrinkSidebarTooltip'>
- <FormattedMessage
- id='rhs_header.shrinkSidebarTooltip'
- defaultMessage='Shrink Sidebar'
- />
- </Tooltip>
- );
-
- if (this.props.isMentionSearch) {
- title = (
- <FormattedMessage
- id='search_header.title2'
- defaultMessage='Recent Mentions'
- />
- );
- } else if (this.props.isFlaggedPosts) {
- title = (
- <FormattedMessage
- id='search_header.title3'
- defaultMessage='Flagged Posts'
- />
- );
- } else if (this.props.isPinnedPosts) {
- title = (
- <FormattedMessage
- id='search_header.title4'
- defaultMessage='Pinned posts in {channelDisplayName}'
- values={{
- channelDisplayName: this.props.channelDisplayName
- }}
- />
- );
- }
-
- return (
- <div className='sidebar--right__header'>
- <span className='sidebar--right__title'>{title}</span>
- <div className='pull-right'>
- <button
- type='button'
- className='sidebar--right__expand'
- aria-label='Expand'
- onClick={this.toggleSize}
- >
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={Constants.OVERLAY_TIME_DELAY}
- placement='top'
- overlay={expandSidebarTooltip}
- >
- <i className='fa fa-expand'/>
- </OverlayTrigger>
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={Constants.OVERLAY_TIME_DELAY}
- placement='top'
- overlay={shrinkSidebarTooltip}
- >
- <i className='fa fa-compress'/>
- </OverlayTrigger>
- </button>
- <button
- type='button'
- className='sidebar--right__close'
- aria-label='Close'
- title='Close'
- onClick={this.handleClose}
- >
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={Constants.OVERLAY_TIME_DELAY}
- placement='top'
- overlay={closeSidebarTooltip}
- >
- <i className='fa fa-sign-out'/>
- </OverlayTrigger>
- </button>
- </div>
- </div>
- );
- }
-}
-
-SearchResultsHeader.propTypes = {
- isMentionSearch: PropTypes.bool,
- toggleSize: PropTypes.func,
- shrink: PropTypes.func,
- isFlaggedPosts: PropTypes.bool,
- isPinnedPosts: PropTypes.bool,
- channelDisplayName: PropTypes.string.isRequired,
- isLoading: PropTypes.bool.isRequired
-};
diff --git a/webapp/components/search_results_item.jsx b/webapp/components/search_results_item.jsx
deleted file mode 100644
index ddbab76ff..000000000
--- a/webapp/components/search_results_item.jsx
+++ /dev/null
@@ -1,353 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import PostMessageContainer from 'components/post_view/post_message_view';
-import UserProfile from './user_profile.jsx';
-import FileAttachmentListContainer from 'components/file_attachment_list';
-import ProfilePicture from './profile_picture.jsx';
-import CommentIcon from 'components/common/comment_icon.jsx';
-import DotMenu from 'components/dot_menu';
-import PostFlagIcon from 'components/post_view/post_flag_icon.jsx';
-import PostBodyAdditionalContent from 'components/post_view/post_body_additional_content.jsx';
-
-import TeamStore from 'stores/team_store.jsx';
-
-import * as GlobalActions from 'actions/global_actions.jsx';
-import * as Utils from 'utils/utils.jsx';
-import * as PostUtils from 'utils/post_utils.jsx';
-import Constants from 'utils/constants.jsx';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {FormattedMessage, FormattedDate} from 'react-intl';
-import {browserHistory, Link} from 'react-router/es6';
-
-export default class SearchResultsItem extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleFocusRHSClick = this.handleFocusRHSClick.bind(this);
- this.handleJumpClick = this.handleJumpClick.bind(this);
- this.handleDropdownOpened = this.handleDropdownOpened.bind(this);
- this.shrinkSidebar = this.shrinkSidebar.bind(this);
-
- this.state = {
- currentTeamDisplayName: TeamStore.getCurrent().name,
- width: '',
- height: '',
- dropdownOpened: false
- };
- }
-
- shouldComponentUpdate(nextProps, nextState) {
- if (!Utils.areObjectsEqual(nextState.post, this.props.post)) {
- return true;
- }
-
- if (nextProps.isFlagged !== this.props.isFlagged) {
- return true;
- }
-
- if (nextState.dropdownOpened !== this.state.dropdownOpened) {
- return true;
- }
-
- return false;
- }
-
- componentDidMount() {
- window.addEventListener('resize', () => {
- Utils.updateWindowDimensions(this);
- });
- }
-
- componentWillUnmount() {
- window.removeEventListener('resize', () => {
- Utils.updateWindowDimensions(this);
- });
- }
-
- shrinkSidebar() {
- setTimeout(() => {
- this.props.shrink();
- });
- }
-
- handleFocusRHSClick(e) {
- e.preventDefault();
- GlobalActions.emitPostFocusRightHandSideFromSearch(this.props.post, this.props.isMentionSearch);
- }
-
- handleJumpClick() {
- if (Utils.isMobile()) {
- GlobalActions.toggleSideBarAction(false);
- }
-
- this.shrinkSidebar();
- browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/pl/' + this.props.post.id);
- }
-
- handleDropdownOpened = (isOpened) => {
- this.setState({
- dropdownOpened: isOpened
- });
- }
-
- timeTag(post) {
- return (
- <time
- className='search-item-time'
- dateTime={Utils.getDateForUnixTicks(post.create_at).toISOString()}
- >
- <FormattedDate
- value={post.create_at}
- hour12={!this.props.useMilitaryTime}
- hour='2-digit'
- minute='2-digit'
- />
- </time>
- );
- }
-
- renderTimeTag(post) {
- return Utils.isMobile() ?
- this.timeTag(post) :
- (
- <Link
- to={`/${this.state.currentTeamDisplayName}/pl/${post.id}`}
- target='_blank'
- className='post__permalink'
- >
- {this.timeTag(post)}
- </Link>
- );
- }
-
- getClassName = () => {
- let className = 'post post--thread';
-
- if (this.props.compactDisplay) {
- className += ' post--compact';
- }
-
- if (this.state.dropdownOpened) {
- className += ' post--hovered';
- }
-
- return className;
- }
-
- render() {
- let channelName = null;
- const channel = this.props.channel;
- const user = this.props.user || {};
- const post = this.props.post;
-
- let idCount = -1;
- if (this.props.lastPostCount >= 0 && this.props.lastPostCount < Constants.TEST_ID_COUNT) {
- idCount = this.props.lastPostCount;
- }
-
- if (channel) {
- channelName = channel.display_name;
- if (channel.type === 'D') {
- channelName = (
- <FormattedMessage
- id='search_item.direct'
- defaultMessage='Direct Message (with {username})'
- values={{
- username: Utils.displayUsernameForUser(Utils.getDirectTeammate(channel.id))
- }}
- />
- );
- }
- }
-
- let overrideUsername;
- let disableProfilePopover = false;
- if (post.props &&
- post.props.from_webhook &&
- post.props.override_username &&
- global.window.mm_config.EnablePostUsernameOverride === 'true') {
- overrideUsername = post.props.override_username;
- disableProfilePopover = true;
- }
-
- let botIndicator;
- if (post.props && post.props.from_webhook) {
- botIndicator = <div className='bot-indicator'>{Constants.BOT_NAME}</div>;
- }
-
- const profilePic = (
- <ProfilePicture
- src={PostUtils.getProfilePicSrcForPost(post, user)}
- user={this.props.user}
- status={this.props.status}
- isBusy={this.props.isBusy}
- />
-
- );
-
- const profilePicContainer = (<div className='post__img'>{profilePic}</div>);
-
- let postClass = '';
- if (PostUtils.isEdited(this.props.post)) {
- postClass += ' post--edited';
- }
-
- let fileAttachment = null;
- if (post.file_ids && post.file_ids.length > 0) {
- fileAttachment = (
- <FileAttachmentListContainer
- post={post}
- compactDisplay={this.props.compactDisplay}
- />
- );
- }
-
- let message;
- let flagContent;
- let rhsControls;
- if (post.state === Constants.POST_DELETED) {
- message = (
- <p>
- <FormattedMessage
- id='post_body.deleted'
- defaultMessage='(message deleted)'
- />
- </p>
- );
- } else {
- flagContent = (
- <PostFlagIcon
- idPrefix={'searchPostFlag'}
- idCount={idCount}
- postId={post.id}
- isFlagged={this.props.isFlagged}
- />
- );
-
- rhsControls = (
- <div className='col__controls'>
- <DotMenu
- idPrefix={Constants.SEARCH_POST}
- idCount={idCount}
- post={post}
- isFlagged={this.props.isFlagged}
- handleDropdownOpened={this.handleDropdownOpened}
- />
- <CommentIcon
- idPrefix={'searchCommentIcon'}
- idCount={idCount}
- handleCommentClick={this.handleFocusRHSClick}
- searchStyle={'search-item__comment'}
- />
- <a
- onClick={this.handleJumpClick}
- className='search-item__jump'
- >
- <FormattedMessage
- id='search_item.jump'
- defaultMessage='Jump'
- />
- </a>
- </div>
- );
-
- const messageWrapper = (
- <PostMessageContainer
- post={post}
- options={{
- searchTerm: this.props.term,
- mentionHighlight: this.props.isMentionSearch
- }}
- />
- );
-
- message = (
- <PostBodyAdditionalContent
- post={post}
- message={messageWrapper}
- />
- );
- }
-
- let pinnedBadge;
- if (post.is_pinned) {
- pinnedBadge = (
- <span className='post__pinned-badge'>
- <FormattedMessage
- id='post_info.pinned'
- defaultMessage='Pinned'
- />
- </span>
- );
- }
-
- return (
- <div className='search-item__container'>
- <div className='date-separator'>
- <hr className='separator__hr'/>
- <div className='separator__text'>
- <FormattedDate
- value={post.create_at}
- day='numeric'
- month='long'
- year='numeric'
- />
- </div>
- </div>
- <div className={this.getClassName()}>
- <div className='search-channel__name'>{channelName}</div>
- <div className='post__content'>
- {profilePicContainer}
- <div>
- <div className='post__header'>
- <div className='col col__name'>
- <strong>
- <UserProfile
- user={user}
- overwriteName={overrideUsername}
- disablePopover={disableProfilePopover}
- status={this.props.status}
- isBusy={this.props.isBusy}
- />
- </strong>
- </div>
- {botIndicator}
- <div className='col'>
- {this.renderTimeTag(post)}
- {pinnedBadge}
- {flagContent}
- </div>
- {rhsControls}
- </div>
- <div className='search-item-snippet post__body'>
- <div className={postClass}>
- {message}
- {fileAttachment}
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- );
- }
-}
-
-SearchResultsItem.propTypes = {
- post: PropTypes.object,
- lastPostCount: PropTypes.number,
- user: PropTypes.object,
- channel: PropTypes.object,
- compactDisplay: PropTypes.bool,
- isMentionSearch: PropTypes.bool,
- isFlaggedSearch: PropTypes.bool,
- term: PropTypes.string,
- useMilitaryTime: PropTypes.bool.isRequired,
- shrink: PropTypes.func,
- isFlagged: PropTypes.bool,
- isBusy: PropTypes.bool,
- status: PropTypes.string
-};
diff --git a/webapp/components/searchable_channel_list.jsx b/webapp/components/searchable_channel_list.jsx
deleted file mode 100644
index 075debcaa..000000000
--- a/webapp/components/searchable_channel_list.jsx
+++ /dev/null
@@ -1,218 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import LoadingScreen from './loading_screen.jsx';
-
-import * as UserAgent from 'utils/user_agent.jsx';
-
-import $ from 'jquery';
-import PropTypes from 'prop-types';
-import React from 'react';
-import ReactDOM from 'react-dom';
-import {localizeMessage} from 'utils/utils.jsx';
-import {FormattedMessage} from 'react-intl';
-
-import loadingGif from 'images/load.gif';
-
-const NEXT_BUTTON_TIMEOUT_MILLISECONDS = 500;
-
-export default class SearchableChannelList extends React.Component {
- constructor(props) {
- super(props);
-
- this.createChannelRow = this.createChannelRow.bind(this);
- this.nextPage = this.nextPage.bind(this);
- this.previousPage = this.previousPage.bind(this);
- this.doSearch = this.doSearch.bind(this);
-
- this.nextTimeoutId = 0;
-
- this.state = {
- joiningChannel: '',
- page: 0,
- nextDisabled: false
- };
- }
-
- componentDidMount() {
- // only focus the search box on desktop so that we don't cause the keyboard to open on mobile
- if (!UserAgent.isMobile()) {
- this.refs.filter.focus();
- }
- }
-
- componentDidUpdate(prevProps, prevState) {
- if (prevState.page !== this.state.page) {
- $(this.refs.channelList).scrollTop(0);
- }
- }
-
- handleJoin(channel) {
- this.setState({joiningChannel: channel.id});
- this.props.handleJoin(
- channel,
- () => {
- this.setState({joiningChannel: ''});
- }
- );
- }
-
- createChannelRow(channel) {
- let joinButton;
- if (this.state.joiningChannel === channel.id) {
- joinButton = (
- <img
- className='join-channel-loading-gif'
- src={loadingGif}
- />
- );
- } else {
- joinButton = (
- <button
- onClick={this.handleJoin.bind(this, channel)}
- className='btn btn-primary'
- >
- <FormattedMessage
- id='more_channels.join'
- defaultMessage='Join'
- />
- </button>
- );
- }
-
- return (
- <div
- className='more-modal__row'
- key={channel.id}
- >
- <div className='more-modal__details'>
- <p className='more-modal__name'>{channel.display_name}</p>
- <p className='more-modal__description'>{channel.purpose}</p>
- </div>
- <div className='more-modal__actions'>
- {joinButton}
- </div>
- </div>
- );
- }
-
- nextPage(e) {
- e.preventDefault();
- this.setState({page: this.state.page + 1, nextDisabled: true});
- this.nextTimeoutId = setTimeout(() => this.setState({nextDisabled: false}), NEXT_BUTTON_TIMEOUT_MILLISECONDS);
- this.props.nextPage(this.state.page + 1);
- $(ReactDOM.findDOMNode(this.refs.channelListScroll)).scrollTop(0);
- }
-
- previousPage(e) {
- e.preventDefault();
- this.setState({page: this.state.page - 1});
- $(ReactDOM.findDOMNode(this.refs.channelListScroll)).scrollTop(0);
- }
-
- doSearch() {
- const term = this.refs.filter.value;
- this.props.search(term);
- if (term === '') {
- this.setState({page: 0});
- }
- }
-
- render() {
- const channels = this.props.channels;
- let listContent;
- let nextButton;
- let previousButton;
-
- if (channels == null) {
- listContent = <LoadingScreen/>;
- } else if (channels.length === 0) {
- listContent = (
- <div className='no-channel-message'>
- <p className='primary-message'>
- <FormattedMessage
- id='more_channels.noMore'
- defaultMessage='No more channels to join'
- />
- </p>
- {this.props.noResultsText}
- </div>
- );
- } else {
- const pageStart = this.state.page * this.props.channelsPerPage;
- const pageEnd = pageStart + this.props.channelsPerPage;
- const channelsToDisplay = this.props.channels.slice(pageStart, pageEnd);
- listContent = channelsToDisplay.map(this.createChannelRow);
-
- if (channelsToDisplay.length >= this.props.channelsPerPage) {
- nextButton = (
- <button
- className='btn btn-default filter-control filter-control__next'
- onClick={this.nextPage}
- disabled={this.state.nextDisabled}
- >
- <FormattedMessage
- id='more_channels.next'
- defaultMessage='Next'
- />
- </button>
- );
- }
-
- if (this.state.page > 0) {
- previousButton = (
- <button
- className='btn btn-default filter-control filter-control__prev'
- onClick={this.previousPage}
- >
- <FormattedMessage
- id='more_channels.prev'
- defaultMessage='Previous'
- />
- </button>
- );
- }
- }
-
- return (
- <div className='filtered-user-list'>
- <div className='filter-row'>
- <div className='col-sm-12'>
- <input
- id='searchChannelsTextbox'
- ref='filter'
- className='form-control filter-textbox'
- placeholder={localizeMessage('filtered_channels_list.search', 'Search channels')}
- onInput={this.doSearch}
- />
- </div>
- </div>
- <div
- ref='channelList'
- className='more-modal__list'
- >
- <div ref='channelListScroll'>
- {listContent}
- </div>
- </div>
- <div className='filter-controls'>
- {previousButton}
- {nextButton}
- </div>
- </div>
- );
- }
-}
-
-SearchableChannelList.defaultProps = {
- channels: []
-};
-
-SearchableChannelList.propTypes = {
- channels: PropTypes.arrayOf(PropTypes.object),
- channelsPerPage: PropTypes.number,
- nextPage: PropTypes.func.isRequired,
- search: PropTypes.func.isRequired,
- handleJoin: PropTypes.func.isRequired,
- noResultsText: PropTypes.object
-};
diff --git a/webapp/components/searchable_user_list/searchable_user_list.jsx b/webapp/components/searchable_user_list/searchable_user_list.jsx
deleted file mode 100644
index e936ed25f..000000000
--- a/webapp/components/searchable_user_list/searchable_user_list.jsx
+++ /dev/null
@@ -1,250 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import PropTypes from 'prop-types';
-import React from 'react';
-import ReactDOM from 'react-dom';
-import {FormattedMessage} from 'react-intl';
-
-import UserList from 'components/user_list.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-
-const NEXT_BUTTON_TIMEOUT = 500;
-
-export default class SearchableUserList extends React.Component {
- static propTypes = {
- users: PropTypes.arrayOf(PropTypes.object),
- usersPerPage: PropTypes.number,
- total: PropTypes.number,
- extraInfo: PropTypes.object,
- nextPage: PropTypes.func.isRequired,
- previousPage: PropTypes.func.isRequired,
- search: PropTypes.func.isRequired,
- actions: PropTypes.arrayOf(PropTypes.func),
- actionProps: PropTypes.object,
- actionUserProps: PropTypes.object,
- focusOnMount: PropTypes.bool,
- renderCount: PropTypes.func,
- renderFilterRow: PropTypes.func,
-
- page: PropTypes.number.isRequired,
- term: PropTypes.string.isRequired,
- onTermChange: PropTypes.func.isRequired
- };
-
- static defaultProps = {
- users: [],
- usersPerPage: 50, // eslint-disable-line no-magic-numbers
- extraInfo: {},
- actions: [],
- actionProps: {},
- actionUserProps: {},
- showTeamToggle: false,
- focusOnMount: false
- };
-
- constructor(props) {
- super(props);
-
- this.nextPage = this.nextPage.bind(this);
- this.previousPage = this.previousPage.bind(this);
- this.focusSearchBar = this.focusSearchBar.bind(this);
-
- this.handleInput = this.handleInput.bind(this);
-
- this.renderCount = this.renderCount.bind(this);
-
- this.nextTimeoutId = 0;
-
- this.state = {
- nextDisabled: false
- };
- }
-
- componentDidMount() {
- this.focusSearchBar();
- }
-
- componentDidUpdate(prevProps) {
- if (this.props.page !== prevProps.page || this.props.term !== prevProps.term) {
- this.refs.userList.scrollToTop();
- }
-
- this.focusSearchBar();
- }
-
- componentWillUnmount() {
- clearTimeout(this.nextTimeoutId);
- }
-
- nextPage(e) {
- e.preventDefault();
-
- this.setState({nextDisabled: true});
- this.nextTimeoutId = setTimeout(() => this.setState({nextDisabled: false}), NEXT_BUTTON_TIMEOUT);
-
- this.props.nextPage();
- $(ReactDOM.findDOMNode(this.refs.channelListScroll)).scrollTop(0);
- }
-
- previousPage(e) {
- e.preventDefault();
-
- this.props.previousPage();
- $(ReactDOM.findDOMNode(this.refs.channelListScroll)).scrollTop(0);
- }
-
- focusSearchBar() {
- if (this.props.focusOnMount) {
- this.refs.filter.focus();
- }
- }
-
- handleInput(e) {
- this.props.onTermChange(e.target.value);
- this.props.search(e.target.value);
- }
-
- renderCount(users) {
- if (!users) {
- return null;
- }
-
- const count = users.length;
- const total = this.props.total;
- const isSearch = Boolean(this.props.term);
-
- let startCount;
- let endCount;
- if (isSearch) {
- startCount = -1;
- endCount = -1;
- } else {
- startCount = this.props.page * this.props.usersPerPage;
- endCount = startCount + count;
- }
-
- if (this.props.renderCount) {
- return this.props.renderCount(count, this.props.total, startCount, endCount, isSearch);
- }
-
- if (this.props.total) {
- if (isSearch) {
- return (
- <FormattedMessage
- id='filtered_user_list.countTotal'
- defaultMessage='{count, number} {count, plural, one {member} other {members}} of {total, number} total'
- values={{
- count,
- total
- }}
- />
- );
- }
-
- return (
- <FormattedMessage
- id='filtered_user_list.countTotalPage'
- defaultMessage='{startCount, number} - {endCount, number} {count, plural, one {member} other {members}} of {total, number} total'
- values={{
- count,
- startCount: startCount + 1,
- endCount,
- total
- }}
- />
- );
- }
-
- return null;
- }
-
- render() {
- let nextButton;
- let previousButton;
- let usersToDisplay;
-
- if (this.props.term || !this.props.users) {
- usersToDisplay = this.props.users;
- } else if (!this.props.term) {
- const pageStart = this.props.page * this.props.usersPerPage;
- const pageEnd = pageStart + this.props.usersPerPage;
- usersToDisplay = this.props.users.slice(pageStart, pageEnd);
-
- if (usersToDisplay.length >= this.props.usersPerPage) {
- nextButton = (
- <button
- className='btn btn-default filter-control filter-control__next'
- onClick={this.nextPage}
- disabled={this.state.nextDisabled}
- >
- <FormattedMessage
- id='filtered_user_list.next'
- defaultMessage='Next'
- />
- </button>
- );
- }
-
- if (this.props.page > 0) {
- previousButton = (
- <button
- className='btn btn-default filter-control filter-control__prev'
- onClick={this.previousPage}
- >
- <FormattedMessage
- id='filtered_user_list.prev'
- defaultMessage='Previous'
- />
- </button>
- );
- }
- }
-
- let filterRow;
- if (this.props.renderFilterRow) {
- filterRow = this.props.renderFilterRow(this.handleInput);
- } else {
- filterRow = (
- <div className='col-xs-12'>
- <input
- ref='filter'
- className='form-control filter-textbox'
- placeholder={Utils.localizeMessage('filtered_user_list.search', 'Search users')}
- value={this.props.term}
- onInput={this.handleInput}
- />
- </div>
- );
- }
-
- return (
- <div className='filtered-user-list'>
- <div className='filter-row'>
- {filterRow}
- <div className='col-sm-12'>
- <span className='member-count pull-left'>{this.renderCount(usersToDisplay)}</span>
- </div>
- </div>
- <div
- className='more-modal__list'
- >
- <UserList
- ref='userList'
- users={usersToDisplay}
- extraInfo={this.props.extraInfo}
- actions={this.props.actions}
- actionProps={this.props.actionProps}
- actionUserProps={this.props.actionUserProps}
- />
- </div>
- <div className='filter-controls'>
- {previousButton}
- {nextButton}
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/searchable_user_list/searchable_user_list_container.jsx b/webapp/components/searchable_user_list/searchable_user_list_container.jsx
deleted file mode 100644
index 8c0694a0c..000000000
--- a/webapp/components/searchable_user_list/searchable_user_list_container.jsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import SearchableUserList from './searchable_user_list.jsx';
-
-export default class SearchableUserListContainer extends React.Component {
- static propTypes = {
- users: PropTypes.arrayOf(PropTypes.object),
- usersPerPage: PropTypes.number,
- total: PropTypes.number,
- extraInfo: PropTypes.object,
- nextPage: PropTypes.func.isRequired,
- search: PropTypes.func.isRequired,
- actions: PropTypes.arrayOf(PropTypes.func),
- actionProps: PropTypes.object,
- actionUserProps: PropTypes.object,
- focusOnMount: PropTypes.bool
- };
-
- constructor(props) {
- super(props);
-
- this.handleTermChange = this.handleTermChange.bind(this);
-
- this.nextPage = this.nextPage.bind(this);
- this.previousPage = this.previousPage.bind(this);
- this.search = this.search.bind(this);
-
- this.state = {
- term: '',
- page: 0
- };
- }
-
- handleTermChange(term) {
- this.setState({term});
- }
-
- nextPage() {
- this.setState({page: this.state.page + 1});
-
- this.props.nextPage(this.state.page + 1);
- }
-
- previousPage() {
- this.setState({page: this.state.page - 1});
- }
-
- search(term) {
- this.props.search(term);
-
- if (term !== '') {
- this.setState({page: 0});
- }
- }
-
- render() {
- return (
- <SearchableUserList
- {...this.props}
- nextPage={this.nextPage}
- previousPage={this.previousPage}
- search={this.search}
- page={this.state.page}
- term={this.state.term}
- onTermChange={this.handleTermChange}
- />
- );
- }
-}
diff --git a/webapp/components/select_team/components/select_team_item.jsx b/webapp/components/select_team/components/select_team_item.jsx
deleted file mode 100644
index 0d47ba7e1..000000000
--- a/webapp/components/select_team/components/select_team_item.jsx
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import * as Utils from 'utils/utils.jsx';
-import {addUserToTeamFromInvite} from 'actions/team_actions.jsx';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {Link, browserHistory} from 'react-router/es6';
-import {Tooltip, OverlayTrigger} from 'react-bootstrap';
-import {Constants} from 'utils/constants.jsx';
-
-export default class SelectTeamItem extends React.PureComponent {
- static propTypes = {
- team: PropTypes.object.isRequired,
- onTeamClick: PropTypes.func.isRequired,
- loading: PropTypes.bool.isRequired
- };
-
- handleTeamClick = () => {
- addUserToTeamFromInvite('', '', this.props.team.invite_id,
- () => {
- browserHistory.push(`/${this.props.team.name}/channels/town-square`);
- }
- );
- this.props.onTeamClick(this.props.team);
- }
-
- render() {
- let icon;
- const infoIcon = Constants.TEAM_INFO_SVG;
- if (this.props.loading) {
- icon = (
- <span className='fa fa-refresh fa-spin right signup-team__icon'/>
- );
- } else {
- icon = (
- <span className='fa fa-angle-right right signup-team__icon'/>
- );
- }
-
- var descriptionTooltip = '';
- var showDescriptionTooltip = '';
- if (this.props.team.description) {
- descriptionTooltip = (
- <Tooltip id='team-description__tooltip'>
- {this.props.team.description}
- </Tooltip>
- );
-
- showDescriptionTooltip = (
- <OverlayTrigger
- trigger={['hover', 'focus', 'click']}
- delayShow={1000}
- placement='top'
- overlay={descriptionTooltip}
- ref='descriptionOverlay'
- >
- <span
- className='icon icon--info'
- dangerouslySetInnerHTML={{__html: infoIcon}}
- />
- </OverlayTrigger>
- );
- }
-
- return (
- <div className='signup-team-dir'>
- {showDescriptionTooltip}
- <Link
- id={Utils.createSafeId(this.props.team.display_name)}
- onClick={this.handleTeamClick}
- >
- <span className='signup-team-dir__name'>{this.props.team.display_name}</span>
- {icon}
- </Link>
- </div>
- );
- }
-}
diff --git a/webapp/components/select_team/index.js b/webapp/components/select_team/index.js
deleted file mode 100644
index 87691a853..000000000
--- a/webapp/components/select_team/index.js
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getTeams} from 'mattermost-redux/actions/teams';
-
-import SelectTeam from './select_team.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getTeams
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(SelectTeam);
diff --git a/webapp/components/select_team/select_team.jsx b/webapp/components/select_team/select_team.jsx
deleted file mode 100644
index 7dded9891..000000000
--- a/webapp/components/select_team/select_team.jsx
+++ /dev/null
@@ -1,242 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import UserStore from 'stores/user_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-import * as UserAgent from 'utils/user_agent.jsx';
-import * as Utils from 'utils/utils.jsx';
-import AnnouncementBar from 'components/announcement_bar';
-import LoadingScreen from 'components/loading_screen.jsx';
-import * as GlobalActions from 'actions/global_actions.jsx';
-import SelectTeamItem from './components/select_team_item.jsx';
-
-import {Link} from 'react-router/es6';
-
-import {FormattedMessage} from 'react-intl';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import logoImage from 'images/logo.png';
-
-export default class SelectTeam extends React.Component {
- static propTypes = {
- actions: PropTypes.shape({
- getTeams: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
- this.onTeamChange = this.onTeamChange.bind(this);
- this.handleTeamClick = this.handleTeamClick.bind(this);
- this.teamContentsCompare = this.teamContentsCompare.bind(this);
-
- const state = this.getStateFromStores(false);
- state.loadingTeamId = '';
- this.state = state;
- }
-
- componentDidMount() {
- TeamStore.addChangeListener(this.onTeamChange);
- this.props.actions.getTeams(0, 200);
- }
-
- componentWillUnmount() {
- TeamStore.removeChangeListener(this.onTeamChange);
- }
-
- onTeamChange() {
- this.setState(this.getStateFromStores(true));
- }
-
- getStateFromStores(loaded) {
- return {
- teams: TeamStore.getAll(),
- teamMembers: TeamStore.getMyTeamMembers(),
- teamListings: TeamStore.getTeamListings(),
- loaded
- };
- }
-
- handleTeamClick(team) {
- this.setState({loadingTeamId: team.id});
- }
-
- teamContentsCompare(teamItemA, teamItemB) {
- return teamItemA.props.team.display_name.localeCompare(teamItemB.props.team.display_name);
- }
-
- render() {
- let openTeamContents = [];
- const isAlreadyMember = new Map();
- const isSystemAdmin = Utils.isSystemAdmin(UserStore.getCurrentUser().roles);
-
- for (const teamMember of this.state.teamMembers) {
- const teamId = teamMember.team_id;
- isAlreadyMember[teamId] = true;
- }
-
- for (const id in this.state.teamListings) {
- if (this.state.teamListings.hasOwnProperty(id) && !isAlreadyMember[id]) {
- const openTeam = this.state.teamListings[id];
- openTeamContents.push(
- <SelectTeamItem
- key={'team_' + openTeam.name}
- team={openTeam}
- onTeamClick={this.handleTeamClick}
- loading={this.state.loadingTeamId === openTeam.id}
- />
- );
- }
- }
-
- if (openTeamContents.length === 0 && (global.window.mm_config.EnableTeamCreation === 'true' || isSystemAdmin)) {
- openTeamContents = (
- <div className='signup-team-dir-err'>
- <div>
- <FormattedMessage
- id='signup_team.no_open_teams_canCreate'
- defaultMessage='No teams are available to join. Please create a new team or ask your administrator for an invite.'
- />
- </div>
- </div>
- );
- } else if (openTeamContents.length === 0) {
- openTeamContents = (
- <div className='signup-team-dir-err'>
- <div>
- <FormattedMessage
- id='signup_team.no_open_teams'
- defaultMessage='No teams are available to join. Please ask your administrator for an invite.'
- />
- </div>
- </div>
- );
- }
-
- if (Array.isArray(openTeamContents)) {
- openTeamContents = openTeamContents.sort(this.teamContentsCompare);
- }
-
- let openContent = (
- <div className='signup__content'>
- <h4>
- <FormattedMessage
- id='signup_team.join_open'
- defaultMessage='Teams you can join: '
- />
- </h4>
- <div className='signup-team-all'>
- {openTeamContents}
- </div>
- </div>
- );
-
- if (!this.state.loaded || this.state.loadingTeamId !== '') {
- openContent = <LoadingScreen/>;
- }
-
- let teamHelp = null;
- if (isSystemAdmin && (global.window.mm_config.EnableTeamCreation === 'false')) {
- teamHelp = (
- <FormattedMessage
- id='login.createTeamAdminOnly'
- defaultMessage='This option is only available for System Administrators, and does not show up for other users.'
- />
- );
- }
-
- let teamSignUp;
- if (isSystemAdmin || global.window.mm_config.EnableTeamCreation === 'true') {
- teamSignUp = (
- <div className='margin--extra'>
- <Link
- to='/create_team'
- className='signup-team-login'
- >
- <FormattedMessage
- id='login.createTeam'
- defaultMessage='Create a new team'
- />
- </Link>
- <div>
- {teamHelp}
- </div>
- </div>
- );
- }
-
- let adminConsoleLink;
- if (isSystemAdmin && !UserAgent.isMobileApp()) {
- adminConsoleLink = (
- <div className='margin--extra hidden-xs'>
- <Link
- to='/admin_console'
- className='signup-team-login'
- >
- <FormattedMessage
- id='signup_team_system_console'
- defaultMessage='Go to System Console'
- />
- </Link>
- </div>
- );
- }
-
- let description = null;
- if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.CustomBrand === 'true' && global.window.mm_config.EnableCustomBrand === 'true') {
- description = global.window.mm_config.CustomDescriptionText;
- } else {
- description = (
- <FormattedMessage
- id='web.root.signup_info'
- defaultMessage='All team communication in one place, searchable and accessible anywhere'
- />
- );
- }
-
- let headerButton;
- if (this.state.teamMembers.length) {
- headerButton = (
- <Link to='/'>
- <span className='fa fa-chevron-left'/>
- <FormattedMessage id='web.header.back'/>
- </Link>
- );
- } else {
- headerButton = (
- <a
- href='#'
- onClick={() => GlobalActions.emitUserLoggedOutEvent()}
- >
- <span className='fa fa-chevron-left'/>
- <FormattedMessage id='web.header.logout'/>
- </a>
- );
- }
- return (
- <div>
- <AnnouncementBar/>
- <div className='signup-header'>
- {headerButton}
- </div>
- <div className='col-sm-12'>
- <div className={'signup-team__container'}>
- <img
- className='signup-team-logo'
- src={logoImage}
- />
- <h1>{global.window.mm_config.SiteName}</h1>
- <h4 className='color--light'>
- {description}
- </h4>
- {openContent}
- {teamSignUp}
- {adminConsoleLink}
- </div>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/setting_item_max.jsx b/webapp/components/setting_item_max.jsx
deleted file mode 100644
index 1f0af181e..000000000
--- a/webapp/components/setting_item_max.jsx
+++ /dev/null
@@ -1,175 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {FormattedMessage} from 'react-intl';
-import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class SettingItemMax extends React.Component {
- constructor(props) {
- super(props);
-
- this.onKeyDown = this.onKeyDown.bind(this);
- }
-
- onKeyDown(e) {
- if (e.keyCode === Constants.KeyCodes.ENTER && this.props.submit) {
- this.props.submit(e);
- }
- }
-
- componentDidMount() {
- document.addEventListener('keydown', this.onKeyDown);
- }
-
- componentWillUnmount() {
- document.removeEventListener('keydown', this.onKeyDown);
- }
-
- render() {
- var clientError = null;
- if (this.props.client_error) {
- clientError = (
- <div className='form-group'>
- <label
- id='clientError'
- className='col-sm-12 has-error'
- >
- {this.props.client_error}
- </label>
- </div>
- );
- }
-
- var serverError = null;
- if (this.props.server_error) {
- serverError = (
- <div className='form-group'>
- <label
- id='serverError'
- className='col-sm-12 has-error'
- >
- {this.props.server_error}
- </label>
- </div>
- );
- }
-
- var extraInfo = null;
- let hintClass = 'setting-list__hint';
- if (this.props.infoPosition === 'top') {
- hintClass = 'padding-bottom x2';
- }
-
- if (this.props.extraInfo) {
- extraInfo = (<div className={hintClass}>{this.props.extraInfo}</div>);
- }
-
- var submit = '';
- if (this.props.submit) {
- submit = (
- <input
- id='saveSetting'
- type='submit'
- className='btn btn-sm btn-primary'
- href='#'
- onClick={this.props.submit}
- value={Utils.localizeMessage('setting_item_max.save', 'Save')}
- />
- );
- }
-
- var inputs = this.props.inputs;
- var widthClass;
- if (this.props.width === 'full') {
- widthClass = 'col-sm-12';
- } else if (this.props.width === 'medium') {
- widthClass = 'col-sm-10 col-sm-offset-2';
- } else {
- widthClass = 'col-sm-9 col-sm-offset-3';
- }
-
- let title;
- let titleProp = 'unknownTitle';
- if (this.props.title) {
- title = <li className='col-sm-12 section-title'>{this.props.title}</li>;
- titleProp = this.props.title;
- }
-
- let listContent = (
- <li className='setting-list-item'>
- {inputs}
- {extraInfo}
- </li>
- );
-
- if (this.props.infoPosition === 'top') {
- listContent = (
- <li>
- {extraInfo}
- {inputs}
- </li>
- );
- }
-
- let cancelButtonText;
- if (this.props.cancelButtonText) {
- cancelButtonText = this.props.cancelButtonText;
- } else {
- cancelButtonText = (
- <FormattedMessage
- id='setting_item_max.cancel'
- defaultMessage='Cancel'
- />
- );
- }
-
- return (
- <ul className='section-max form-horizontal'>
- {title}
- <li className={widthClass}>
- <ul className='setting-list'>
- {listContent}
- <li className='setting-list-item'>
- <hr/>
- {this.props.submitExtra}
- {serverError}
- {clientError}
- {submit}
- <a
- id={Utils.createSafeId(titleProp) + 'Cancel'}
- className='btn btn-sm'
- href='#'
- onClick={this.props.updateSection}
- >
- {cancelButtonText}
- </a>
- </li>
- </ul>
- </li>
- </ul>
- );
- }
-}
-
-SettingItemMax.propTypes = {
- inputs: PropTypes.array,
- client_error: PropTypes.string,
- server_error: PropTypes.string,
- extraInfo: PropTypes.element,
- infoPosition: PropTypes.string,
- updateSection: PropTypes.func,
- submit: PropTypes.func,
- title: PropTypes.node,
- width: PropTypes.string,
- submitExtra: PropTypes.node,
- cancelButtonText: PropTypes.node
-};
-
-SettingItemMax.defaultProps = {
- infoPosition: 'bottom'
-};
diff --git a/webapp/components/setting_item_min.jsx b/webapp/components/setting_item_min.jsx
deleted file mode 100644
index b7752056d..000000000
--- a/webapp/components/setting_item_min.jsx
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {FormattedMessage} from 'react-intl';
-import * as Utils from 'utils/utils.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default function SettingItemMin(props) {
- let editButton = null;
- let describeSection = null;
- if (!props.disableOpen && Utils.isMobile()) {
- editButton = (
- <li className='col-xs-12 col-sm-3 section-edit'>
- <a
- id={Utils.createSafeId(props.title) + 'Edit'}
- className='theme'
- href='#'
- onClick={props.updateSection}
- >
- <i className='fa fa-pencil'/>
- {props.describe}
- </a>
- </li>
- );
- } else if (!props.disableOpen) {
- editButton = (
- <li className='col-xs-12 col-sm-3 section-edit'>
- <a
- id={Utils.createSafeId(props.title) + 'Edit'}
- className='theme'
- href='#'
- onClick={props.updateSection}
- >
- <i className='fa fa-pencil'/>
- <FormattedMessage
- id='setting_item_min.edit'
- defaultMessage='Edit'
- />
- </a>
- </li>
- );
-
- describeSection = (
- <li
- id={Utils.createSafeId(props.title) + 'Desc'}
- className='col-xs-12 section-describe'
- >
- {props.describe}
- </li>
- );
- }
-
- return (
- <ul
- className='section-min'
- onClick={props.updateSection}
- >
- <li className='col-xs-12 col-sm-9 section-title'>{props.title}</li>
- {editButton}
- {describeSection}
- </ul>
- );
-}
-
-SettingItemMin.propTypes = {
- title: PropTypes.node,
- disableOpen: PropTypes.bool,
- updateSection: PropTypes.func,
- describe: PropTypes.node
-};
diff --git a/webapp/components/setting_picture.jsx b/webapp/components/setting_picture.jsx
deleted file mode 100644
index ec6dfbd20..000000000
--- a/webapp/components/setting_picture.jsx
+++ /dev/null
@@ -1,222 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React, {Component} from 'react';
-import {FormattedMessage} from 'react-intl';
-import exif2css from 'exif2css';
-
-import FormError from 'components/form_error.jsx';
-import loadingGif from 'images/load.gif';
-
-import Constants from 'utils/constants.jsx';
-
-export default class SettingPicture extends Component {
- static propTypes = {
- clientError: PropTypes.string,
- serverError: PropTypes.string,
- src: PropTypes.string,
- file: PropTypes.object,
- loadingPicture: PropTypes.bool,
- submitActive: PropTypes.bool,
- submit: PropTypes.func,
- title: PropTypes.string,
- onFileChange: PropTypes.func,
- updateSection: PropTypes.func
- };
-
- constructor(props) {
- super(props);
-
- this.state = {
- image: null
- };
- }
-
- componentWillReceiveProps(nextProps) {
- if (nextProps.file !== this.props.file) {
- this.setState({image: null});
-
- this.setPicture(nextProps.file);
- }
- }
-
- componentWillUnmount() {
- if (this.previewBlob) {
- URL.revokeObjectURL(this.previewBlob);
- }
- }
-
- setPicture = (file) => {
- if (file) {
- this.previewBlob = URL.createObjectURL(file);
-
- var reader = new FileReader();
- reader.onload = (e) => {
- const orientation = this.getExifOrientation(e.target.result);
- const orientationStyles = this.getOrientationStyles(orientation);
-
- this.setState({
- image: this.previewBlob,
- orientationStyles
- });
- };
- reader.readAsArrayBuffer(file);
- }
- }
-
- // based on https://stackoverflow.com/questions/7584794/accessing-jpeg-exif-rotation-data-in-javascript-on-the-client-side/32490603#32490603
- getExifOrientation(data) {
- var view = new DataView(data);
-
- if (view.getUint16(0, false) !== 0xFFD8) {
- return -2;
- }
-
- var length = view.byteLength;
- var offset = 2;
-
- while (offset < length) {
- var marker = view.getUint16(offset, false);
- offset += 2;
-
- if (marker === 0xFFE1) {
- if (view.getUint32(offset += 2, false) !== 0x45786966) {
- return -1;
- }
-
- var little = view.getUint16(offset += 6, false) === 0x4949;
- offset += view.getUint32(offset + 4, little);
- var tags = view.getUint16(offset, little);
- offset += 2;
-
- for (var i = 0; i < tags; i++) {
- if (view.getUint16(offset + (i * 12), little) === 0x0112) {
- return view.getUint16(offset + (i * 12) + 8, little);
- }
- }
- } else if ((marker & 0xFF00) === 0xFF00) {
- offset += view.getUint16(offset, false);
- } else {
- break;
- }
- }
- return -1;
- }
-
- getOrientationStyles(orientation) {
- const {
- transform,
- 'transform-origin': transformOrigin
- } = exif2css(orientation);
- return {transform, transformOrigin};
- }
-
- render() {
- let img;
- if (this.props.file) {
- const imageStyles = {
- backgroundImage: 'url(' + this.state.image + ')',
- ...this.state.orientationStyles
- };
-
- img = (
- <div
- className='profile-img-preview'
- style={imageStyles}
- />
- );
- } else {
- img = (
- <img
- ref='image'
- className='profile-img rounded'
- src={this.props.src}
- />
- );
- }
-
- let confirmButton;
- if (this.props.loadingPicture) {
- confirmButton = (
- <img
- className='spinner'
- src={loadingGif}
- />
- );
- } else {
- let confirmButtonClass = 'btn btn-sm';
- if (this.props.submitActive) {
- confirmButtonClass += ' btn-primary';
- } else {
- confirmButtonClass += ' btn-inactive disabled';
- }
-
- confirmButton = (
- <a
- className={confirmButtonClass}
- onClick={this.props.submit}
- >
- <FormattedMessage
- id='setting_picture.save'
- defaultMessage='Save'
- />
- </a>
- );
- }
-
- return (
- <ul className='section-max form-horizontal'>
- <li className='col-xs-12 section-title'>{this.props.title}</li>
- <li className='col-xs-offset-3 col-xs-8'>
- <ul className='setting-list'>
- <li className='setting-list-item'>
- {img}
- </li>
- <li className='setting-list-item padding-top x2'>
- <FormattedMessage
- id='setting_picture.help'
- defaultMessage='Upload a profile picture in BMP, JPG, JPEG or PNG format, at least {width}px in width and {height}px height.'
- values={{
- width: Constants.PROFILE_WIDTH,
- height: Constants.PROFILE_WIDTH
- }}
- />
- </li>
- <li className='setting-list-item'>
- <hr/>
- <FormError
- errors={[this.props.clientError, this.props.serverError]}
- type={'modal'}
- />
- <span className='btn btn-sm btn-primary btn-file sel-btn'>
- <FormattedMessage
- id='setting_picture.select'
- defaultMessage='Select'
- />
- <input
- ref='input'
- accept='.jpg,.png,.bmp'
- type='file'
- onChange={this.props.onFileChange}
- />
- </span>
- {confirmButton}
- <a
- className='btn btn-sm theme'
- href='#'
- onClick={this.props.updateSection}
- >
- <FormattedMessage
- id='setting_picture.cancel'
- defaultMessage='Cancel'
- />
- </a>
- </li>
- </ul>
- </li>
- </ul>
- );
- }
-}
diff --git a/webapp/components/setting_upload.jsx b/webapp/components/setting_upload.jsx
deleted file mode 100644
index 3f2af309a..000000000
--- a/webapp/components/setting_upload.jsx
+++ /dev/null
@@ -1,129 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import ReactDOM from 'react-dom';
-import {FormattedMessage} from 'react-intl';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class SettingsUpload extends React.Component {
- constructor(props) {
- super(props);
-
- this.doFileSelect = this.doFileSelect.bind(this);
- this.doSubmit = this.doSubmit.bind(this);
-
- this.state = {
- clientError: this.props.clientError,
- serverError: this.props.serverError,
- filename: ''
- };
- }
-
- componentWillReceiveProps() {
- this.setState({
- clientError: this.props.clientError,
- serverError: this.props.serverError
- });
- }
-
- doFileSelect(e) {
- e.preventDefault();
- var filename = $(e.target).val();
- if (filename.substring(3, 11) === 'fakepath') {
- filename = filename.substring(12);
- }
- this.setState({
- clientError: '',
- serverError: '',
- filename
- });
- }
-
- doSubmit(e) {
- e.preventDefault();
- var inputnode = ReactDOM.findDOMNode(this.refs.uploadinput);
- if (inputnode.files && inputnode.files[0]) {
- this.props.submit(inputnode.files[0]);
- } else {
- this.setState({clientError: true});
- }
- }
-
- render() {
- let clientError = null;
- if (this.state.clientError) {
- clientError = (
- <div className='file-status'>
- <FormattedMessage
- id='setting_upload.noFile'
- defaultMessage='No file selected.'
- />
- </div>
- );
- }
- let serverError = null;
- if (this.state.serverError) {
- serverError = (
- <div className='file-status'>{this.state.serverError}</div>
- );
- }
- let fileNameText = null;
- let submitButtonClass = 'btn btn-sm btn-primary disabled';
- if (this.state.filename) {
- fileNameText = (
- <div className='file-status file-name'>{this.state.filename}</div>
- );
- submitButtonClass = 'btn btn-sm btn-primary';
- }
-
- return (
- <ul className='section-max'>
- <li className='col-sm-12 section-title'>{this.props.title}</li>
- <li className='col-sm-offset-3 col-sm-9'>{this.props.helpText}</li>
- <li className='col-sm-offset-3 col-sm-9'>
- <ul className='setting-list'>
- <li className='setting-list-item'>
- <span className='btn btn-sm btn-primary btn-file sel-btn'>
- <FormattedMessage
- id='setting_upload.select'
- defaultMessage='Select file'
- />
- <input
- ref='uploadinput'
- accept={this.props.fileTypesAccepted}
- type='file'
- onChange={this.doFileSelect}
- />
- </span>
- <a
- className={submitButtonClass}
- onClick={this.doSubmit}
- >
- <FormattedMessage
- id='setting_upload.import'
- defaultMessage='Import'
- />
- </a>
- {fileNameText}
- {serverError}
- {clientError}
- </li>
- </ul>
- </li>
- </ul>
- );
- }
-}
-
-SettingsUpload.propTypes = {
- title: PropTypes.string.isRequired,
- submit: PropTypes.func.isRequired,
- fileTypesAccepted: PropTypes.string.isRequired,
- clientError: PropTypes.string,
- serverError: PropTypes.string,
- helpText: PropTypes.object
-};
diff --git a/webapp/components/settings_sidebar.jsx b/webapp/components/settings_sidebar.jsx
deleted file mode 100644
index cbd757356..000000000
--- a/webapp/components/settings_sidebar.jsx
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import * as UserAgent from 'utils/user_agent.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class SettingsSidebar extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleClick = this.handleClick.bind(this);
- }
- handleClick(tab, e) {
- e.preventDefault();
- this.props.updateTab(tab.name);
- $(e.target).closest('.settings-modal').addClass('display--content');
- }
- componentDidMount() {
- if (UserAgent.isFirefox()) {
- $('.settings-modal .settings-table .nav').addClass('position--top');
- }
- }
- render() {
- const tabList = this.props.tabs.map((tab) => {
- const key = `${tab.name}_li`;
- let className = '';
- if (this.props.activeTab === tab.name) {
- className = 'active';
- }
-
- return (
- <li
- key={key}
- className={className}
- >
- <a
- href='#'
- onClick={this.handleClick.bind(null, tab)}
- >
- <i className={tab.icon}/>
- {tab.uiName}
- </a>
- </li>
- );
- });
-
- return (
- <div>
- <ul className='nav nav-pills nav-stacked'>
- {tabList}
- </ul>
- </div>
- );
- }
-}
-
-SettingsSidebar.propTypes = {
- tabs: PropTypes.arrayOf(PropTypes.shape({
- name: PropTypes.string.isRequired,
- uiName: PropTypes.string.isRequired,
- icon: PropTypes.string.isRequired
- })).isRequired,
- activeTab: PropTypes.string,
- updateTab: PropTypes.func.isRequired
-};
diff --git a/webapp/components/shortcuts_modal.jsx b/webapp/components/shortcuts_modal.jsx
deleted file mode 100644
index 2d6f2499a..000000000
--- a/webapp/components/shortcuts_modal.jsx
+++ /dev/null
@@ -1,396 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import Constants from 'utils/constants.jsx';
-
-import ModalStore from 'stores/modal_store.jsx';
-
-import {intlShape, injectIntl, defineMessages} from 'react-intl';
-import {Modal} from 'react-bootstrap';
-import React from 'react';
-import PropTypes from 'prop-types';
-
-const allShortcuts = defineMessages({
- mainHeader: {
- default: {
- id: 'shortcuts.header',
- defaultMessage: 'Keyboard Shortcuts\tCtrl|/'
- },
- mac: {
- id: 'shortcuts.header.mac',
- defaultMessage: 'Keyboard Shortcuts\t⌘|/'
- }
- },
- navHeader: {
- id: 'shortcuts.nav.header',
- defaultMessage: 'Navigation'
- },
- navPrev: {
- default: {
- id: 'shortcuts.nav.prev',
- defaultMessage: 'Previous channel:\tAlt|Up'
- },
- mac: {
- id: 'shortcuts.nav.prev.mac',
- defaultMessage: 'Previous channel:\t⌥|Up'
- }
- },
- navNext: {
- default: {
- id: 'shortcuts.nav.next',
- defaultMessage: 'Next channel:\tAlt|Down'
- },
- mac: {
- id: 'shortcuts.nav.next.mac',
- defaultMessage: 'Next channel:\t⌥|Down'
- }
- },
- navUnreadPrev: {
- default: {
- id: 'shortcuts.nav.unread_prev',
- defaultMessage: 'Previous unread channel:\tAlt|Shift|Up'
- },
- mac: {
- id: 'shortcuts.nav.unread_prev.mac',
- defaultMessage: 'Previous unread channel:\t⌥|Shift|Up'
- }
- },
- navUnreadNext: {
- default: {
- id: 'shortcuts.nav.unread_next',
- defaultMessage: 'Next unread channel:\tAlt|Shift|Down'
- },
- mac: {
- id: 'shortcuts.nav.unread_next.mac',
- defaultMessage: 'Next unread channel:\t⌥|Shift|Down'
- }
- },
- navSwitcher: {
- default: {
- id: 'shortcuts.nav.switcher',
- defaultMessage: 'Quick channel switcher:\tCtrl|K'
- },
- mac: {
- id: 'shortcuts.nav.switcher.mac',
- defaultMessage: 'Quick channel switcher:\t⌘|K'
- }
- },
- navDMMenu: {
- default: {
- id: 'shortcuts.nav.direct_messages_menu',
- defaultMessage: 'Direct messages menu:\tCtrl|Shift|K'
- },
- mac: {
- id: 'shortcuts.nav.direct_messages_menu.mac',
- defaultMessage: 'Direct messages menu:\t⌘|Shift|K'
- }
- },
- navSettings: {
- default: {
- id: 'shortcuts.nav.settings',
- defaultMessage: 'Account settings:\tCtrl|Shift|A'
- },
- mac: {
- id: 'shortcuts.nav.settings.mac',
- defaultMessage: 'Account settings:\t⌘|Shift|A'
- }
- },
- navMentions: {
- default: {
- id: 'shortcuts.nav.mentions',
- defaultMessage: 'Recent mentions:\tCtrl|Shift|M'
- },
- mac: {
- id: 'shortcuts.nav.mentions.mac',
- defaultMessage: 'Recent mentions:\t⌘|Shift|M'
- }
- },
- msgHeader: {
- id: 'shortcuts.msgs.header',
- defaultMessage: 'Messages'
- },
- msgInputHeader: {
- id: 'shortcuts.msgs.input.header',
- defaultMessage: 'Works inside an empty input field'
- },
- msgEdit: {
- id: 'shortcuts.msgs.edit',
- defaultMessage: 'Edit last message in channel:\tUp'
- },
- msgReply: {
- id: 'shortcuts.msgs.reply',
- defaultMessage: 'Reply to last message in channel:\tShift|Up'
- },
- msgReprintPrev: {
- default: {
- id: 'shortcuts.msgs.reprint_prev',
- defaultMessage: 'Reprint previous message:\tCtrl|Up'
- },
- mac: {
- id: 'shortcuts.msgs.reprint_prev.mac',
- defaultMessage: 'Reprint previous message:\t⌘|Up'
- }
- },
- msgReprintNext: {
- default: {
- id: 'shortcuts.msgs.reprint_next',
- defaultMessage: 'Reprint next message:\tCtrl|Down'
- },
- mac: {
- id: 'shortcuts.msgs.reprint_next.mac',
- defaultMessage: 'Reprint next message:\t⌘|Down'
- }
- },
- msgCompHeader: {
- id: 'shortcuts.msgs.comp.header',
- defaultMessage: 'Autocomplete'
- },
- msgCompUsername: {
- id: 'shortcuts.msgs.comp.username',
- defaultMessage: 'Username:\t@|[a-z]|Tab'
- },
- msgCompChannel: {
- id: 'shortcuts.msgs.comp.channel',
- defaultMessage: 'Channel:\t~|[a-z]|Tab'
- },
- msgCompEmoji: {
- id: 'shortcuts.msgs.comp.emoji',
- defaultMessage: 'Emoji:\t:|[a-z]|Tab'
- },
- filesHeader: {
- id: 'shortcuts.files.header',
- defaultMessage: 'Files'
- },
- filesUpload: {
- default: {
- id: 'shortcuts.files.upload',
- defaultMessage: 'Upload files:\tCtrl|U'
- },
- mac: {
- id: 'shortcuts.files.upload.mac',
- defaultMessage: 'Upload files:\t⌘|U'
- }
- },
- browserHeader: {
- id: 'shortcuts.browser.header',
- defaultMessage: 'Built-in Browser Commands'
- },
- browserChannelPrev: {
- default: {
- id: 'shortcuts.browser.channel_prev',
- defaultMessage: 'Back in history:\tAlt|Left'
- },
- mac: {
- id: 'shortcuts.browser.channel_prev.mac',
- defaultMessage: 'Back in history:\t⌘|['
- }
- },
- browserChannelNext: {
- default: {
- id: 'shortcuts.browser.channel_next',
- defaultMessage: 'Forward in history:\tAlt|Right'
- },
- mac: {
- id: 'shortcuts.browser.channel_next.mac',
- defaultMessage: 'Forward in history:\t⌘|]'
- }
- },
- browserFontIncrease: {
- default: {
- id: 'shortcuts.browser.font_increase',
- defaultMessage: 'Zoom in:\tCtrl|+'
- },
- mac: {
- id: 'shortcuts.browser.font_increase.mac',
- defaultMessage: 'Zoom in:\t⌘|+'
- }
- },
- browserFontDecrease: {
- default: {
- id: 'shortcuts.browser.font_decrease',
- defaultMessage: 'Zoom out:\tCtrl|-'
- },
- mac: {
- id: 'shortcuts.browser.font_decrease.mac',
- defaultMessage: 'Zoom out:\t⌘|-'
- }
- },
- browserInputHeader: {
- id: 'shortcuts.browser.input.header',
- defaultMessage: 'Works inside an input field'
- },
- browserHighlightPrev: {
- id: 'shortcuts.browser.highlight_prev',
- defaultMessage: 'Highlight text to the previous line:\tShift|Up'
- },
- browserHighlightNext: {
- id: 'shortcuts.browser.highlight_next',
- defaultMessage: 'Highlight text to the next line:\tShift|Down'
- },
- browserNewline: {
- id: 'shortcuts.browser.newline',
- defaultMessage: 'Create a new line:\tShift|Enter'
- },
- info: {
- id: 'shortcuts.info',
- defaultMessage: 'Begin a message with / for a list of all the commands at your disposal.'
- }
-});
-
-class ShortcutsModal extends React.PureComponent {
- static propTypes = {
- intl: intlShape.isRequired,
- isMac: PropTypes.bool.isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.state = {
- show: false
- };
- }
-
- componentDidMount() {
- ModalStore.addModalListener(Constants.ActionTypes.TOGGLE_SHORTCUTS_MODAL, this.handleToggle);
- }
-
- componentWillUnmount() {
- ModalStore.removeModalListener(Constants.ActionTypes.TOGGLE_SHORTCUTS_MODAL, this.handleToggle);
- }
-
- handleToggle = (value) => {
- this.setState({
- show: value
- });
- }
-
- handleHide = () => {
- this.setState({show: false});
- }
-
- getShortcuts() {
- const {isMac} = this.props;
- const shortcuts = {};
- Object.keys(allShortcuts).forEach((s) => {
- if (isMac && allShortcuts[s].mac) {
- shortcuts[s] = allShortcuts[s].mac;
- } else if (!isMac && allShortcuts[s].default) {
- shortcuts[s] = allShortcuts[s].default;
- } else {
- shortcuts[s] = allShortcuts[s];
- }
- });
-
- return shortcuts;
- }
-
- render() {
- const shortcuts = this.getShortcuts();
- const {formatMessage} = this.props.intl;
-
- return (
- <Modal
- dialogClassName='shortcuts-modal'
- show={this.state.show}
- onHide={this.handleHide}
- onExited={this.handleHide}
- >
- <div className='shortcuts-content'>
- <Modal.Header closeButton={true}>
- <Modal.Title>
- <strong>{renderShortcut(formatMessage(shortcuts.mainHeader))}</strong>
- </Modal.Title>
- </Modal.Header>
- <Modal.Body ref='modalBody'>
- <div className='row'>
- <div className='col-sm-4'>
- <div className='section'>
- <div>
- <h4 className='section-title'><strong>{formatMessage(shortcuts.navHeader)}</strong></h4>
- {renderShortcut(formatMessage(shortcuts.navPrev))}
- {renderShortcut(formatMessage(shortcuts.navNext))}
- {renderShortcut(formatMessage(shortcuts.navUnreadPrev))}
- {renderShortcut(formatMessage(shortcuts.navUnreadNext))}
- {renderShortcut(formatMessage(shortcuts.navSwitcher))}
- {renderShortcut(formatMessage(shortcuts.navDMMenu))}
- {renderShortcut(formatMessage(shortcuts.navSettings))}
- {renderShortcut(formatMessage(shortcuts.navMentions))}
- </div>
- </div>
- </div>
- <div className='col-sm-4'>
- <div className='section'>
- <div>
- <h4 className='section-title'><strong>{formatMessage(shortcuts.msgHeader)}</strong></h4>
- <span><strong>{formatMessage(shortcuts.msgInputHeader)}</strong></span>
- <div className='subsection'>
- {renderShortcut(formatMessage(shortcuts.msgEdit))}
- {renderShortcut(formatMessage(shortcuts.msgReply))}
- {renderShortcut(formatMessage(shortcuts.msgReprintPrev))}
- {renderShortcut(formatMessage(shortcuts.msgReprintNext))}
- </div>
- <span><strong>{formatMessage(shortcuts.msgCompHeader)}</strong></span>
- <div className='subsection'>
- {renderShortcut(formatMessage(shortcuts.msgCompUsername))}
- {renderShortcut(formatMessage(shortcuts.msgCompChannel))}
- {renderShortcut(formatMessage(shortcuts.msgCompEmoji))}
- </div>
- </div>
- </div>
- </div>
- <div className='col-sm-4'>
- <div className='section'>
- <div>
- <h4 className='section-title'><strong>{formatMessage(shortcuts.filesHeader)}</strong></h4>
- {renderShortcut(formatMessage(shortcuts.filesUpload))}
- </div>
- <div className='section--lower'>
- <h4 className='section-title'><strong>{formatMessage(shortcuts.browserHeader)}</strong></h4>
- {renderShortcut(formatMessage(shortcuts.browserChannelPrev))}
- {renderShortcut(formatMessage(shortcuts.browserChannelNext))}
- {renderShortcut(formatMessage(shortcuts.browserFontIncrease))}
- {renderShortcut(formatMessage(shortcuts.browserFontDecrease))}
- <span><strong>{formatMessage(shortcuts.browserInputHeader)}</strong></span>
- <div className='subsection'>
- {renderShortcut(formatMessage(shortcuts.browserHighlightPrev))}
- {renderShortcut(formatMessage(shortcuts.browserHighlightNext))}
- {renderShortcut(formatMessage(shortcuts.browserNewline))}
- </div>
- </div>
- </div>
- </div>
- </div>
- <div className='info__label'>{formatMessage(shortcuts.info)}</div>
- </Modal.Body>
- </div>
- </Modal>
- );
- }
-}
-
-function renderShortcut(text) {
- if (!text) {
- return null;
- }
-
- const shortcut = text.split('\t');
- const description = <span>{shortcut[0]}</span>;
- const keys = shortcut[1].split('|').map((key) =>
- <span
- className='shortcut-key'
- key={key}
- >
- {key}
- </span>
- );
-
- return (
- <div className='shortcut-line'>
- {description}
- {keys}
- </div>
- );
-}
-
-export default injectIntl(ShortcutsModal);
diff --git a/webapp/components/should_verify_email.jsx b/webapp/components/should_verify_email.jsx
deleted file mode 100644
index bbc677910..000000000
--- a/webapp/components/should_verify_email.jsx
+++ /dev/null
@@ -1,117 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {FormattedMessage} from 'react-intl';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import {Link} from 'react-router/es6';
-
-import {resendVerification} from 'actions/user_actions.jsx';
-
-export default class ShouldVerifyEmail extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleResend = this.handleResend.bind(this);
-
- this.state = {
- resendStatus: 'none'
- };
- }
- handleResend() {
- const email = this.props.location.query.email;
-
- this.setState({resendStatus: 'sending'});
-
- resendVerification(
- email,
- () => {
- this.setState({resendStatus: 'success'});
- },
- () => {
- this.setState({resendStatus: 'failure'});
- }
- );
- }
- render() {
- let resendConfirm = '';
- if (this.state.resendStatus === 'success') {
- resendConfirm = (
- <div>
- <br/>
- <p className='alert alert-success'>
- <i className='fa fa-check'/>
- <FormattedMessage
- id='email_verify.sent'
- defaultMessage=' Verification email sent.'
- />
- </p>
- </div>
- );
- }
-
- if (this.state.resendStatus === 'failure') {
- resendConfirm = (
- <div>
- <br/>
- <p className='alert alert-danger'>
- <i className='fa fa-times'/>
- <FormattedMessage id='email_verify.failed'/>
- </p>
- </div>
- );
- }
-
- return (
- <div>
- <div className='signup-header'>
- <Link to='/'>
- <span className='fa fa-chevron-left'/>
- <FormattedMessage
- id='web.header.back'
- />
- </Link>
- </div>
- <div className='col-sm-12'>
- <div className='signup-team__container'>
- <h3>
- <FormattedMessage
- id='email_verify.almost'
- defaultMessage='{siteName}: You are almost done'
- values={{
- siteName: global.window.mm_config.SiteName
- }}
- />
- </h3>
- <div>
- <p>
- <FormattedMessage
- id='email_verify.notVerifiedBody'
- defaultMessage='Please verify your email address. Check your inbox for an email.'
- />
- </p>
- <button
- onClick={this.handleResend}
- className='btn btn-primary'
- >
- <FormattedMessage
- id='email_verify.resend'
- defaultMessage='Resend Email'
- />
- </button>
- {resendConfirm}
- </div>
- </div>
- </div>
- </div>
- );
- }
-}
-
-ShouldVerifyEmail.defaultProps = {
-};
-ShouldVerifyEmail.propTypes = {
- location: PropTypes.object.isRequired
-};
diff --git a/webapp/components/sidebar.jsx b/webapp/components/sidebar.jsx
deleted file mode 100644
index 3399734a6..000000000
--- a/webapp/components/sidebar.jsx
+++ /dev/null
@@ -1,994 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import ReactDOM from 'react-dom';
-import NewChannelFlow from './new_channel_flow.jsx';
-import MoreDirectChannels from 'components/more_direct_channels';
-import MoreChannels from 'components/more_channels';
-import SidebarHeader from './sidebar_header.jsx';
-import UnreadChannelIndicator from './unread_channel_indicator.jsx';
-import TutorialTip from './tutorial/tutorial_tip.jsx';
-import StatusIcon from './status_icon.jsx';
-
-import ChannelStore from 'stores/channel_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-import PreferenceStore from 'stores/preference_store.jsx';
-import ModalStore from 'stores/modal_store.jsx';
-
-import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
-import * as Utils from 'utils/utils.jsx';
-import * as ChannelUtils from 'utils/channel_utils.jsx';
-import * as ChannelActions from 'actions/channel_actions.jsx';
-import * as GlobalActions from 'actions/global_actions.jsx';
-
-import {trackEvent} from 'actions/diagnostics_actions.jsx';
-import {ActionTypes, Constants} from 'utils/constants.jsx';
-
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-
-const Preferences = Constants.Preferences;
-const TutorialSteps = Constants.TutorialSteps;
-
-import {Tooltip, OverlayTrigger} from 'react-bootstrap';
-import loadingGif from 'images/load.gif';
-
-import React from 'react';
-import {browserHistory, Link} from 'react-router/es6';
-
-import favicon from 'images/favicon/favicon-16x16.png';
-import redFavicon from 'images/favicon/redfavicon-16x16.png';
-
-import store from 'stores/redux_store.jsx';
-const dispatch = store.dispatch;
-const getState = store.getState;
-
-import {getChannelsByCategory} from 'mattermost-redux/selectors/entities/channels';
-import {savePreferences} from 'mattermost-redux/actions/preferences';
-
-export default class Sidebar extends React.Component {
- constructor(props) {
- super(props);
-
- this.badgesActive = false;
- this.firstUnreadChannel = null;
- this.lastUnreadChannel = null;
-
- this.isLeaving = new Map();
- this.isSwitchingChannel = false;
- this.closedDirectChannel = false;
-
- const state = this.getStateFromStores();
- state.newChannelModalType = '';
- state.showDirectChannelsModal = false;
- state.showMoreChannelsModal = false;
- state.loadingDMChannel = -1;
- state.inChannelChange = false;
- this.state = state;
- }
-
- getTotalUnreadCount = () => {
- const unreads = ChannelUtils.getCountsStateFromStores(this.state.currentTeam, this.state.teamMembers, this.state.unreadCounts);
- return {msgs: unreads.messageCount, mentions: unreads.mentionCount};
- }
-
- getStateFromStores = () => {
- const members = ChannelStore.getMyMembers();
- const teamMembers = TeamStore.getMyTeamMembers();
- const currentChannelId = ChannelStore.getCurrentId();
- const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999);
-
- const displayableChannels = getChannelsByCategory(store.getState());
-
- return {
- activeId: currentChannelId,
- members,
- teamMembers,
- ...displayableChannels,
- unreadCounts: JSON.parse(JSON.stringify(ChannelStore.getUnreadCounts())),
- showTutorialTip: tutorialStep === TutorialSteps.CHANNEL_POPOVER,
- currentTeam: TeamStore.getCurrent(),
- currentUser: UserStore.getCurrentUser(),
- townSquare: ChannelStore.getByName(Constants.DEFAULT_CHANNEL),
- offTopic: ChannelStore.getByName(Constants.OFFTOPIC_CHANNEL)
- };
- }
-
- onInChannelChange = () => {
- this.setState({inChannelChange: !this.state.inChannelChange});
- }
-
- componentDidMount() {
- ChannelStore.addChangeListener(this.onChange);
- UserStore.addChangeListener(this.onChange);
- UserStore.addInTeamChangeListener(this.onChange);
- UserStore.addInChannelChangeListener(this.onInChannelChange);
- UserStore.addStatusesChangeListener(this.onChange);
- TeamStore.addChangeListener(this.onChange);
- PreferenceStore.addChangeListener(this.onChange);
- ModalStore.addModalListener(ActionTypes.TOGGLE_DM_MODAL, this.onModalChange);
-
- this.updateTitle();
- this.updateUnreadIndicators();
-
- document.addEventListener('keydown', this.navigateChannelShortcut);
- document.addEventListener('keydown', this.navigateUnreadChannelShortcut);
- }
-
- shouldComponentUpdate(nextProps, nextState) {
- if (!Utils.areObjectsEqual(nextState, this.state)) {
- return true;
- }
- return false;
- }
-
- componentDidUpdate(prevProps, prevState) {
- this.updateTitle();
- this.updateUnreadIndicators();
- if (!Utils.isMobile()) {
- $('.sidebar--left .nav-pills__container').perfectScrollbar();
- }
-
- // reset the scrollbar upon switching teams
- if (this.state.currentTeam !== prevState.currentTeam) {
- this.refs.container.scrollTop = 0;
- $('.nav-pills__container').perfectScrollbar('update');
- }
-
- // close the LHS on mobile when you change channels
- if (this.state.activeId !== prevState.activeId) {
- if (this.closedDirectChannel) {
- this.closedDirectChannel = false;
- } else {
- $('.app__body .inner-wrap').removeClass('move--right');
- $('.app__body .sidebar--left').removeClass('move--right');
- $('.multi-teams .team-sidebar').removeClass('move--right');
- }
- }
- }
-
- componentWillUnmount() {
- ChannelStore.removeChangeListener(this.onChange);
- UserStore.removeChangeListener(this.onChange);
- UserStore.removeInTeamChangeListener(this.onChange);
- UserStore.removeInChannelChangeListener(this.onChange);
- UserStore.removeStatusesChangeListener(this.onChange);
- TeamStore.removeChangeListener(this.onChange);
- PreferenceStore.removeChangeListener(this.onChange);
- ModalStore.removeModalListener(ActionTypes.TOGGLE_DM_MODAL, this.onModalChange);
- document.removeEventListener('keydown', this.navigateChannelShortcut);
- document.removeEventListener('keydown', this.navigateUnreadChannelShortcut);
- }
-
- onModalChange = (value, args) => {
- this.showMoreDirectChannelsModal(args.startingUsers);
- }
-
- handleOpenMoreDirectChannelsModal = (e) => {
- e.preventDefault();
- if (this.state.showDirectChannelsModal) {
- this.hideMoreDirectChannelsModal();
- } else {
- this.showMoreDirectChannelsModal();
- }
- }
-
- onChange = () => {
- if (this.state.currentTeam.id !== TeamStore.getCurrentId()) {
- ChannelStore.clear();
- }
- this.setState(this.getStateFromStores());
- this.updateTitle();
- }
-
- updateTitle = () => {
- const channel = ChannelStore.getCurrent();
- if (channel && this.state.currentTeam) {
- let currentSiteName = '';
- if (global.window.mm_config.SiteName != null) {
- currentSiteName = global.window.mm_config.SiteName;
- }
-
- let currentChannelName = channel.display_name;
- if (channel.type === Constants.DM_CHANNEL) {
- const teammate = Utils.getDirectTeammate(channel.id);
- if (teammate != null) {
- currentChannelName = teammate.username;
- }
- } else if (channel.type === Constants.GM_CHANNEL) {
- currentChannelName = ChannelUtils.buildGroupChannelName(channel.id);
- }
-
- const unread = this.getTotalUnreadCount();
- const mentionTitle = unread.mentions > 0 ? '(' + unread.mentions + ') ' : '';
- const unreadTitle = unread.msgs > 0 ? '* ' : '';
- document.title = mentionTitle + unreadTitle + currentChannelName + ' - ' + this.state.currentTeam.display_name + ' ' + currentSiteName;
- }
- }
-
- onScroll = () => {
- this.updateUnreadIndicators();
- }
-
- updateUnreadIndicators = () => {
- const container = $(ReactDOM.findDOMNode(this.refs.container));
-
- var showTopUnread = false;
- var showBottomUnread = false;
-
- // Consider partially obscured channels as above/below
- const unreadMargin = 15;
-
- if (this.firstUnreadChannel) {
- var firstUnreadElement = $(ReactDOM.findDOMNode(this.refs[this.firstUnreadChannel]));
-
- if (firstUnreadElement.position().top + firstUnreadElement.height() < unreadMargin) {
- showTopUnread = true;
- }
- }
-
- if (this.lastUnreadChannel) {
- var lastUnreadElement = $(ReactDOM.findDOMNode(this.refs[this.lastUnreadChannel]));
-
- if (lastUnreadElement.position().top > container.height() - unreadMargin) {
- showBottomUnread = true;
- }
- }
-
- this.setState({
- showTopUnread,
- showBottomUnread
- });
- }
-
- updateScrollbarOnChannelChange = (channel) => {
- const curChannel = this.refs[channel.name].getBoundingClientRect();
- if ((curChannel.top - Constants.CHANNEL_SCROLL_ADJUSTMENT < 0) || (curChannel.top + curChannel.height > this.refs.container.getBoundingClientRect().height)) {
- this.refs.container.scrollTop = this.refs.container.scrollTop + (curChannel.top - Constants.CHANNEL_SCROLL_ADJUSTMENT);
- $('.nav-pills__container').perfectScrollbar('update');
- }
- }
-
- navigateChannelShortcut = (e) => {
- if (e.altKey && !e.shiftKey && (e.keyCode === Constants.KeyCodes.UP || e.keyCode === Constants.KeyCodes.DOWN)) {
- e.preventDefault();
-
- if (this.isSwitchingChannel) {
- return;
- }
-
- this.isSwitchingChannel = true;
- const allChannels = this.getDisplayedChannels();
- const curChannelId = this.state.activeId;
- let curIndex = -1;
- for (let i = 0; i < allChannels.length; i++) {
- if (allChannels[i].id === curChannelId) {
- curIndex = i;
- }
- }
- let nextIndex = curIndex;
- if (e.keyCode === Constants.KeyCodes.DOWN) {
- nextIndex = curIndex + 1;
- } else if (e.keyCode === Constants.KeyCodes.UP) {
- nextIndex = curIndex - 1;
- }
- const nextChannel = allChannels[Utils.mod(nextIndex, allChannels.length)];
- ChannelActions.goToChannel(nextChannel);
- this.updateScrollbarOnChannelChange(nextChannel);
- this.isSwitchingChannel = false;
- } else if (Utils.cmdOrCtrlPressed(e) && e.shiftKey && e.keyCode === Constants.KeyCodes.K) {
- this.handleOpenMoreDirectChannelsModal(e);
- }
- }
-
- navigateUnreadChannelShortcut = (e) => {
- if (e.altKey && e.shiftKey && (e.keyCode === Constants.KeyCodes.UP || e.keyCode === Constants.KeyCodes.DOWN)) {
- e.preventDefault();
-
- if (this.isSwitchingChannel) {
- return;
- }
-
- this.isSwitchingChannel = true;
- const allChannels = this.getDisplayedChannels();
- const curChannelId = this.state.activeId;
- let curIndex = -1;
- for (let i = 0; i < allChannels.length; i++) {
- if (allChannels[i].id === curChannelId) {
- curIndex = i;
- }
- }
- let nextIndex = curIndex;
- let count = 0;
- let increment = 0;
- if (e.keyCode === Constants.KeyCodes.UP) {
- increment = -1;
- } else if (e.keyCode === Constants.KeyCodes.DOWN) {
- increment = 1;
- }
- let unreadCounts = ChannelStore.getUnreadCount(allChannels[nextIndex].id);
- while (count < allChannels.length && unreadCounts.msgs === 0 && unreadCounts.mentions === 0) {
- nextIndex += increment;
- count++;
- nextIndex = Utils.mod(nextIndex, allChannels.length);
- unreadCounts = ChannelStore.getUnreadCount(allChannels[nextIndex].id);
- }
- if (unreadCounts.msgs !== 0 || unreadCounts.mentions !== 0) {
- const nextChannel = allChannels[nextIndex];
- ChannelActions.goToChannel(nextChannel);
- this.updateScrollbarOnChannelChange(nextChannel);
- }
- this.isSwitchingChannel = false;
- }
- }
-
- getDisplayedChannels = () => {
- return this.state.favoriteChannels.concat(this.state.publicChannels).concat(this.state.privateChannels).concat(this.state.directAndGroupChannels);
- }
-
- handleLeavePublicChannel = (e, channel) => {
- e.preventDefault();
- ChannelActions.leaveChannel(channel.id);
- trackEvent('ui', 'ui_public_channel_x_button_clicked');
- }
-
- handleLeavePrivateChannel = (e, channel) => {
- e.preventDefault();
- GlobalActions.showLeavePrivateChannelModal(channel);
- trackEvent('ui', 'ui_private_channel_x_button_clicked');
- }
-
- handleLeaveDirectChannel = (e, channel) => {
- e.preventDefault();
-
- if (!this.isLeaving.get(channel.id)) {
- this.isLeaving.set(channel.id, true);
-
- let id;
- let category;
- if (channel.type === Constants.DM_CHANNEL) {
- id = channel.teammate_id;
- category = Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW;
- } else {
- id = channel.id;
- category = Constants.Preferences.CATEGORY_GROUP_CHANNEL_SHOW;
- }
-
- const currentUserId = UserStore.getCurrentId();
- savePreferences(currentUserId, [{user_id: currentUserId, category, name: id, value: 'false'}])(dispatch, getState).then(
- () => {
- this.isLeaving.set(channel.id, false);
- }
- );
-
- if (ChannelUtils.isFavoriteChannel(channel)) {
- ChannelActions.unmarkFavorite(channel.id);
- }
-
- this.setState(this.getStateFromStores());
- trackEvent('ui', 'ui_direct_channel_x_button_clicked');
- }
-
- if (channel.id === this.state.activeId) {
- this.closedDirectChannel = true;
- browserHistory.push('/' + this.state.currentTeam.name + '/channels/town-square');
- }
- }
-
- showMoreChannelsModal = () => {
- this.setState({showMoreChannelsModal: true});
- trackEvent('ui', 'ui_channels_more_public');
- }
-
- hideMoreChannelsModal = () => {
- this.setState({showMoreChannelsModal: false});
- }
-
- showNewChannelModal = (type) => {
- this.setState({newChannelModalType: type});
- }
-
- hideNewChannelModal = () => {
- this.setState({newChannelModalType: ''});
- }
-
- showMoreDirectChannelsModal = (startingUsers) => {
- trackEvent('ui', 'ui_channels_more_direct');
- this.setState({showDirectChannelsModal: true, startingUsers});
- }
-
- hideMoreDirectChannelsModal = () => {
- this.setState({showDirectChannelsModal: false, startingUsers: null});
- }
-
- openLeftSidebar = () => {
- if (Utils.isMobile()) {
- setTimeout(() => {
- document.querySelector('.app__body .inner-wrap').classList.add('move--right');
- document.querySelector('.app__body .sidebar--left').classList.add('move--right');
- });
- }
- }
-
- openQuickSwitcher = (e) => {
- e.preventDefault();
- AppDispatcher.handleViewAction({
- type: ActionTypes.TOGGLE_QUICK_SWITCH_MODAL
- });
- }
-
- createTutorialTip = () => {
- const screens = [];
-
- let townSquareDisplayName = Constants.DEFAULT_CHANNEL_UI_NAME;
- if (this.state.townSquare) {
- townSquareDisplayName = this.state.townSquare.display_name;
- }
-
- let offTopicDisplayName = Constants.OFFTOPIC_CHANNEL_UI_NAME;
- if (this.state.offTopic) {
- offTopicDisplayName = this.state.offTopic.display_name;
- }
-
- screens.push(
- <div>
- <FormattedHTMLMessage
- id='sidebar.tutorialScreen1'
- defaultMessage='<h4>Channels</h4><p><strong>Channels</strong> organize conversations across different topics. They’re open to everyone on your team. To send private communications use <strong>Direct Messages</strong> for a single person or <strong>Private Channels</strong> for multiple people.</p>'
- />
- </div>
- );
-
- screens.push(
- <div>
- <FormattedHTMLMessage
- id='sidebar.tutorialScreen2'
- defaultMessage='<h4>"{townsquare}" and "{offtopic}" channels</h4>
- <p>Here are two public channels to start:</p>
- <p><strong>{townsquare}</strong> is a place for team-wide communication. Everyone in your team is a member of this channel.</p>
- <p><strong>{offtopic}</strong> is a place for fun and humor outside of work-related channels. You and your team can decide what other channels to create.</p>'
- values={{
- townsquare: townSquareDisplayName,
- offtopic: offTopicDisplayName
- }}
- />
- </div>
- );
-
- screens.push(
- <div>
- <FormattedHTMLMessage
- id='sidebar.tutorialScreen3'
- defaultMessage='<h4>Creating and Joining Channels</h4>
- <p>Click <strong>"More..."</strong> to create a new channel or join an existing one.</p>
- <p>You can also create a new public or private channel by clicking the <strong>"+" symbol</strong> next to the public or private channel header.</p>'
- />
- </div>
- );
-
- return (
- <TutorialTip
- placement='right'
- screens={screens}
- overlayClass='tip-overlay--sidebar'
- diagnosticsTag='tutorial_tip_2_channels'
- />
- );
- }
-
- createChannelElement = (channel, index, arr, handleClose) => {
- const members = this.state.members;
- const activeId = this.state.activeId;
- const channelMember = members[channel.id];
- const unreadCount = this.state.unreadCounts[channel.id] || {msgs: 0, mentions: 0};
- let msgCount;
-
- let linkClass = '';
- if (channel.id === activeId) {
- linkClass = 'active';
- }
-
- let rowClass = 'sidebar-item';
-
- var unread = false;
- if (channelMember) {
- msgCount = unreadCount.msgs + unreadCount.mentions;
- unread = msgCount > 0 || channelMember.mention_count > 0;
- }
-
- if (unread) {
- rowClass += ' unread-title';
-
- if (channel.id !== activeId) {
- if (!this.firstUnreadChannel) {
- this.firstUnreadChannel = channel.name;
- }
- this.lastUnreadChannel = channel.name;
- }
- }
-
- var badge = null;
- if (channelMember) {
- if (unreadCount.mentions) {
- badge = <span className='badge'>{unreadCount.mentions}</span>;
- this.badgesActive = true;
- }
- } else if (this.state.loadingDMChannel === index && channel.type === Constants.DM_CHANNEL) {
- badge = (
- <img
- className='channel-loading-gif pull-right'
- src={loadingGif}
- />
- );
- }
-
- if (unreadCount.mentions > 0) {
- rowClass += ' has-badge';
- }
-
- let displayName = channel.display_name;
-
- var icon = null;
- const globeIcon = Constants.GLOBE_ICON_SVG;
- const lockIcon = Constants.LOCK_ICON_SVG;
- if (channel.type === Constants.OPEN_CHANNEL) {
- icon = (
- <span
- className='icon icon__globe'
- dangerouslySetInnerHTML={{__html: globeIcon}}
- />
- );
- } else if (channel.type === Constants.PRIVATE_CHANNEL) {
- icon = (
- <span
- className='icon icon__lock'
- dangerouslySetInnerHTML={{__html: lockIcon}}
- />
- );
- } else if (channel.type === Constants.GM_CHANNEL) {
- displayName = ChannelUtils.buildGroupChannelName(channel.id);
- icon = <div className='status status--group'>{UserStore.getProfileListInChannel(channel.id, true).length}</div>;
- } else {
- // set up status icon for direct message channels (status is null for other channel types)
- icon = (
- <StatusIcon
- type='avatar'
- status={channel.status}
- />);
- }
-
- let closeButton = null;
- let removeTooltip = (
- <Tooltip id='remove-dm-tooltip'>
- <FormattedMessage
- id='sidebar.removeList'
- defaultMessage='Remove from list'
- />
- </Tooltip>
- );
- if (channel.type === Constants.OPEN_CHANNEL || channel.type === Constants.PRIVATE_CHANNEL) {
- removeTooltip = (
- <Tooltip id='remove-dm-tooltip'>
- <FormattedMessage
- id='sidebar.leave'
- defaultMessage='Leave channel'
- />
- </Tooltip>
- );
- }
- if (handleClose && !badge) {
- closeButton = (
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={1000}
- placement='top'
- overlay={removeTooltip}
- >
- <span
- onClick={(e) => handleClose(e, channel)}
- className='btn-close'
- >
- {'×'}
- </span>
- </OverlayTrigger>
- );
-
- rowClass += ' has-close';
- }
-
- let tutorialTip = null;
- if (this.state.showTutorialTip && channel.name === Constants.DEFAULT_CHANNEL) {
- tutorialTip = this.createTutorialTip();
- this.openLeftSidebar();
- }
-
- let link = '';
- if (channel.fake) {
- link = '/' + this.state.currentTeam.name + '/channels/' + channel.name + '?fakechannel=' + encodeURIComponent(JSON.stringify(channel));
- } else {
- link = '/' + this.state.currentTeam.name + '/channels/' + channel.name;
- }
-
- return (
- <li
- key={channel.name}
- ref={channel.name}
- className={linkClass}
- >
- <Link
- to={link}
- className={rowClass}
- onClick={this.trackChannelSelectedEvent}
- >
- {icon}
- <span className='sidebar-item__name'>{displayName}</span>
- {badge}
- {closeButton}
- </Link>
- {tutorialTip}
- </li>
- );
- }
-
- trackChannelSelectedEvent = () => {
- trackEvent('ui', 'ui_channel_selected');
- }
-
- render() {
- const switchChannelIcon = Constants.SWITCH_CHANNEL_ICON_SVG;
-
- // Check if we have all info needed to render
- if (this.state.currentTeam == null || this.state.currentUser == null) {
- return (<div/>);
- }
-
- this.lastBadgesActive = this.badgesActive;
- this.badgesActive = false;
-
- // keep track of the first and last unread channels so we can use them to set the unread indicators
- this.firstUnreadChannel = null;
- this.lastUnreadChannel = null;
-
- // create elements for all 4 types of channels
- const favoriteItems = this.state.favoriteChannels.
- map((channel, index, arr) => {
- if (channel.type === Constants.DM_CHANNEL || channel.type === Constants.GM_CHANNEL) {
- return this.createChannelElement(channel, index, arr, this.handleLeaveDirectChannel);
- } else if (global.window.mm_config.EnableXToLeaveChannelsFromLHS === 'true') {
- if (channel.type === Constants.OPEN_CHANNEL && channel.name !== Constants.DEFAULT_CHANNEL) {
- return this.createChannelElement(channel, index, arr, this.handleLeavePublicChannel);
- } else if (channel.type === Constants.PRIVATE_CHANNEL) {
- return this.createChannelElement(channel, index, arr, this.handleLeavePrivateChannel);
- }
- }
-
- return this.createChannelElement(channel);
- });
-
- const publicChannelItems = this.state.publicChannels.map((channel, index, arr) => {
- if (global.window.mm_config.EnableXToLeaveChannelsFromLHS !== 'true' ||
- channel.name === Constants.DEFAULT_CHANNEL
- ) {
- return this.createChannelElement(channel);
- }
- return this.createChannelElement(channel, index, arr, this.handleLeavePublicChannel);
- });
-
- const privateChannelItems = this.state.privateChannels.map((channel, index, arr) => {
- if (global.window.mm_config.EnableXToLeaveChannelsFromLHS !== 'true') {
- return this.createChannelElement(channel);
- }
- return this.createChannelElement(channel, index, arr, this.handleLeavePrivateChannel);
- });
-
- const directMessageItems = this.state.directAndGroupChannels.map((channel, index, arr) => {
- return this.createChannelElement(channel, index, arr, this.handleLeaveDirectChannel);
- });
-
- // update the favicon to show if there are any notifications
- if (this.lastBadgesActive !== this.badgesActive) {
- var link = document.createElement('link');
- link.type = 'image/x-icon';
- link.rel = 'shortcut icon';
- link.id = 'favicon';
- if (this.badgesActive) {
- link.href = redFavicon;
- } else {
- link.href = favicon;
- }
- var head = document.getElementsByTagName('head')[0];
- var oldLink = document.getElementById('favicon');
- if (oldLink) {
- head.removeChild(oldLink);
- }
- head.appendChild(link);
- }
-
- var directMessageMore = (
- <li key='more'>
- <a
- id='moreDirectMessage'
- href='#'
- onClick={this.handleOpenMoreDirectChannelsModal}
- >
- <FormattedMessage
- id='sidebar.moreElips'
- defaultMessage='More...'
- />
- </a>
- </li>
- );
-
- let showChannelModal = false;
- if (this.state.newChannelModalType !== '') {
- showChannelModal = true;
- }
-
- const createChannelTootlip = (
- <Tooltip id='new-channel-tooltip' >
- <FormattedMessage
- id='sidebar.createChannel'
- defaultMessage='Create new public channel'
- />
- </Tooltip>
- );
- const createGroupTootlip = (
- <Tooltip id='new-group-tooltip'>
- <FormattedMessage
- id='sidebar.createGroup'
- defaultMessage='Create new private channel'
- />
- </Tooltip>
- );
-
- const createDirectMessageTooltip = (
- <Tooltip
- id='new-group-tooltip'
- className='hidden-xs'
- >
- <FormattedMessage
- id='sidebar.createDirectMessage'
- defaultMessage='Create new direct message'
- />
- </Tooltip>
- );
-
- const above = (
- <FormattedMessage
- id='sidebar.unreads'
- defaultMessage='More unreads'
- />
- );
-
- const below = (
- <FormattedMessage
- id='sidebar.unreads'
- defaultMessage='More unreads'
- />
- );
-
- const isTeamAdmin = TeamStore.isTeamAdminForCurrentTeam();
- const isSystemAdmin = UserStore.isSystemAdminForCurrentUser();
-
- let createPublicChannelIcon = (
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={500}
- placement='top'
- overlay={createChannelTootlip}
- >
- <a
- id='createPublicChannel'
- className='add-channel-btn'
- href='#'
- onClick={this.showNewChannelModal.bind(this, Constants.OPEN_CHANNEL)}
- >
- {'+'}
- </a>
- </OverlayTrigger>
- );
-
- let createPrivateChannelIcon = (
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={500}
- placement='top'
- overlay={createGroupTootlip}
- >
- <a
- id='createPrivateChannel'
- className='add-channel-btn'
- href='#'
- onClick={this.showNewChannelModal.bind(this, Constants.PRIVATE_CHANNEL)}
- >
- {'+'}
- </a>
- </OverlayTrigger>
- );
-
- if (!ChannelUtils.showCreateOption(Constants.OPEN_CHANNEL, isTeamAdmin, isSystemAdmin)) {
- createPublicChannelIcon = null;
- }
-
- const createDirectMessageIcon = (
- <OverlayTrigger
- className='hidden-xs'
- delayShow={500}
- placement='top'
- overlay={createDirectMessageTooltip}
- >
- <a
- className='add-channel-btn'
- href='#'
- onClick={this.handleOpenMoreDirectChannelsModal}
- >
- {'+'}
- </a>
- </OverlayTrigger>
- );
-
- if (!ChannelUtils.showCreateOption(Constants.PRIVATE_CHANNEL, isTeamAdmin, isSystemAdmin)) {
- createPrivateChannelIcon = null;
- }
-
- let moreDirectChannelsModal;
- if (this.state.showDirectChannelsModal) {
- moreDirectChannelsModal = (
- <MoreDirectChannels
- onModalDismissed={this.hideMoreDirectChannelsModal}
- startingUsers={this.state.startingUsers}
- />
- );
- }
-
- let moreChannelsModal;
- if (this.state.showMoreChannelsModal) {
- moreChannelsModal = (
- <MoreChannels
- onModalDismissed={this.hideMoreChannelsModal}
- handleNewChannel={() => {
- this.hideMoreChannelsModal();
- this.showNewChannelModal(Constants.OPEN_CHANNEL);
- }}
- />
- );
- }
-
- let quickSwitchTextShortcutId = 'quick_switch_modal.channelsShortcut.windows';
- let quickSwitchTextShortcutDefault = '- CTRL+K';
- if (Utils.isMac()) {
- quickSwitchTextShortcutId = 'quick_switch_modal.channelsShortcut.mac';
- quickSwitchTextShortcutDefault = '- ⌘K';
- }
-
- const quickSwitchTextShortcut = (
- <span className='switch__shortcut hidden-xs'>
- <FormattedMessage
- id={quickSwitchTextShortcutId}
- defaultMessage={quickSwitchTextShortcutDefault}
- />
- </span>
- );
-
- return (
- <div
- className='sidebar--left'
- id='sidebar-left'
- key='sidebar-left'
- >
- <NewChannelFlow
- show={showChannelModal}
- channelType={this.state.newChannelModalType}
- onModalDismissed={this.hideNewChannelModal}
- />
- {moreDirectChannelsModal}
- {moreChannelsModal}
-
- <SidebarHeader
- teamDisplayName={this.state.currentTeam.display_name}
- teamDescription={this.state.currentTeam.description}
- teamName={this.state.currentTeam.name}
- teamType={this.state.currentTeam.type}
- currentUser={this.state.currentUser}
- />
-
- <UnreadChannelIndicator
- show={this.state.showTopUnread}
- extraClass='nav-pills__unread-indicator-top'
- text={above}
- />
- <UnreadChannelIndicator
- show={this.state.showBottomUnread}
- extraClass='nav-pills__unread-indicator-bottom'
- text={below}
- />
-
- <div
- ref='container'
- className='nav-pills__container'
- onScroll={this.onScroll}
- >
- {favoriteItems.length !== 0 && <ul className='nav nav-pills nav-stacked'>
- <li>
- <h4>
- <FormattedMessage
- id='sidebar.favorite'
- defaultMessage='FAVORITE CHANNELS'
- />
- </h4>
- </li>
- {favoriteItems}
- </ul>}
- <ul className='nav nav-pills nav-stacked'>
- <li>
- <h4>
- <FormattedMessage
- id='sidebar.channels'
- defaultMessage='PUBLIC CHANNELS'
- />
- {createPublicChannelIcon}
- </h4>
- </li>
- {publicChannelItems}
- <li>
- <a
- id='sidebarChannelsMore'
- href='#'
- className='nav-more'
- onClick={this.showMoreChannelsModal}
- >
- <FormattedMessage
- id='sidebar.moreElips'
- defaultMessage='More...'
- />
- </a>
- </li>
- </ul>
-
- <ul className='nav nav-pills nav-stacked'>
- <li>
- <h4>
- <FormattedMessage
- id='sidebar.pg'
- defaultMessage='PRIVATE CHANNELS'
- />
- {createPrivateChannelIcon}
- </h4>
- </li>
- {privateChannelItems}
- </ul>
- <ul className='nav nav-pills nav-stacked'>
- <li>
- <h4>
- <FormattedMessage
- id='sidebar.direct'
- defaultMessage='DIRECT MESSAGES'
- />
- {createDirectMessageIcon}
- </h4>
- </li>
- {directMessageItems}
- {directMessageMore}
- </ul>
- </div>
- <div className='sidebar__switcher'>
- <button
- className='btn btn-link'
- onClick={this.openQuickSwitcher}
- >
- <span
- className='icon icon__switch'
- dangerouslySetInnerHTML={{__html: switchChannelIcon}}
- />
- <FormattedMessage
- id={'channel_switch_modal.title'}
- defaultMessage='Switch Channels'
- />
- {quickSwitchTextShortcut}
- </button>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/sidebar_header.jsx b/webapp/components/sidebar_header.jsx
deleted file mode 100644
index 88fb0d8c4..000000000
--- a/webapp/components/sidebar_header.jsx
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import PreferenceStore from 'stores/preference_store.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import SidebarHeaderDropdown from './sidebar_header_dropdown.jsx';
-import {Tooltip, OverlayTrigger} from 'react-bootstrap';
-
-import {Preferences, TutorialSteps, Constants} from 'utils/constants.jsx';
-import {createMenuTip} from 'components/tutorial/tutorial_tip.jsx';
-import StatusDropdown from 'components/status_dropdown/index.jsx';
-
-export default class SidebarHeader extends React.Component {
- constructor(props) {
- super(props);
-
- this.state = this.getStateFromStores();
- }
-
- componentDidMount() {
- PreferenceStore.addChangeListener(this.onPreferenceChange);
- window.addEventListener('resize', this.handleResize);
- }
-
- componentWillUnmount() {
- PreferenceStore.removeChangeListener(this.onPreferenceChange);
- window.removeEventListener('resize', this.handleResize);
- }
-
- handleResize = () => {
- const isMobile = Utils.isMobile();
- this.setState({isMobile});
- }
-
- getPreferences = () => {
- if (!this.props.currentUser) {
- return {};
- }
- const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, this.props.currentUser.id, 999);
- const showTutorialTip = tutorialStep === TutorialSteps.MENU_POPOVER && !Utils.isMobile();
-
- return {showTutorialTip};
- }
-
- getStateFromStores = () => {
- const preferences = this.getPreferences();
- const isMobile = Utils.isMobile();
- return {...preferences, isMobile};
- }
-
- onPreferenceChange = () => {
- this.setState(this.getPreferences());
- }
-
- toggleDropdown = (e) => {
- e.preventDefault();
-
- this.refs.dropdown.toggleDropdown();
- }
-
- renderStatusDropdown = () => {
- if (this.state.isMobile) {
- return null;
- }
- return (
- <StatusDropdown/>
- );
- }
-
- render() {
- const statusDropdown = this.renderStatusDropdown();
-
- let tutorialTip = null;
- if (this.state.showTutorialTip) {
- tutorialTip = createMenuTip(this.toggleDropdown);
- }
-
- let teamNameWithToolTip = null;
- if (this.props.teamDescription === '') {
- teamNameWithToolTip = (
- <div className='team__name'>{this.props.teamDisplayName}</div>
- );
- } else {
- teamNameWithToolTip = (
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={Constants.OVERLAY_TIME_DELAY}
- placement='bottom'
- overlay={<Tooltip id='team-name__tooltip'>{this.props.teamDescription}</Tooltip>}
- ref='descriptionOverlay'
- >
- <div className='team__name'>{this.props.teamDisplayName}</div>
- </OverlayTrigger>
- );
- }
-
- return (
- <div className='team__header theme'>
- {tutorialTip}
- <div className='header__info'>
- {teamNameWithToolTip}
- <div className='user__name'>{'@' + this.props.currentUser.username}</div>
- </div>
- <SidebarHeaderDropdown
- ref='dropdown'
- teamType={this.props.teamType}
- teamDisplayName={this.props.teamDisplayName}
- teamName={this.props.teamName}
- currentUser={this.props.currentUser}
- />
- {statusDropdown}
- </div>
- );
- }
-}
-
-SidebarHeader.defaultProps = {
- teamDisplayName: '',
- teamDescription: '',
- teamType: ''
-};
-SidebarHeader.propTypes = {
- teamDisplayName: PropTypes.string,
- teamDescription: PropTypes.string,
- teamName: PropTypes.string,
- teamType: PropTypes.string,
- currentUser: PropTypes.object
-};
diff --git a/webapp/components/sidebar_header_dropdown.jsx b/webapp/components/sidebar_header_dropdown.jsx
deleted file mode 100644
index 63a5c2541..000000000
--- a/webapp/components/sidebar_header_dropdown.jsx
+++ /dev/null
@@ -1,643 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import ReactDOM from 'react-dom';
-import * as UserAgent from 'utils/user_agent.jsx';
-import * as Utils from 'utils/utils.jsx';
-import * as GlobalActions from 'actions/global_actions.jsx';
-
-import TeamStore from 'stores/team_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-import WebrtcStore from 'stores/webrtc_store.jsx';
-import AboutBuildModal from './about_build_modal.jsx';
-import SidebarHeaderDropdownButton from './sidebar_header_dropdown_button.jsx';
-import TeamMembersModal from './team_members_modal.jsx';
-import AddUsersToTeam from 'components/add_users_to_team';
-
-import {Constants, WebrtcActionTypes} from 'utils/constants.jsx';
-
-import {Dropdown} from 'react-bootstrap';
-import {FormattedMessage} from 'react-intl';
-import {Link} from 'react-router/es6';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class SidebarHeaderDropdown extends React.Component {
- static propTypes = {
- teamType: PropTypes.string,
- teamDisplayName: PropTypes.string,
- teamName: PropTypes.string,
- currentUser: PropTypes.object
- };
-
- static defaultProps = {
- teamType: ''
- };
-
- constructor(props) {
- super(props);
-
- this.toggleDropdown = this.toggleDropdown.bind(this);
-
- this.handleAboutModal = this.handleAboutModal.bind(this);
- this.aboutModalDismissed = this.aboutModalDismissed.bind(this);
- this.showAccountSettingsModal = this.showAccountSettingsModal.bind(this);
- this.showAddUsersToTeamModal = this.showAddUsersToTeamModal.bind(this);
- this.hideAddUsersToTeamModal = this.hideAddUsersToTeamModal.bind(this);
- this.showInviteMemberModal = this.showInviteMemberModal.bind(this);
- this.showGetTeamInviteLinkModal = this.showGetTeamInviteLinkModal.bind(this);
- this.showTeamMembersModal = this.showTeamMembersModal.bind(this);
- this.hideTeamMembersModal = this.hideTeamMembersModal.bind(this);
- this.showShortcutsModal = this.showShortcutsModal.bind(this);
-
- this.onTeamChange = this.onTeamChange.bind(this);
-
- this.renderCustomEmojiLink = this.renderCustomEmojiLink.bind(this);
-
- this.handleClick = this.handleClick.bind(this);
-
- this.state = {
- teamMembers: TeamStore.getMyTeamMembers(),
- teamListings: TeamStore.getTeamListings(),
- showAboutModal: false,
- showDropdown: false,
- showTeamMembersModal: false,
- showAddUsersToTeamModal: false
- };
- }
-
- handleClick(e) {
- if (WebrtcStore.isBusy()) {
- WebrtcStore.emitChanged({action: WebrtcActionTypes.IN_PROGRESS});
- e.preventDefault();
- }
- }
-
- toggleDropdown(val) {
- if (typeof (val) === 'boolean') {
- this.setState({showDropdown: val});
- return;
- }
-
- if (val && val.preventDefault) {
- val.preventDefault();
- }
-
- this.setState({showDropdown: !this.state.showDropdown});
- }
-
- handleAboutModal(e) {
- e.preventDefault();
-
- this.setState({
- showAboutModal: true,
- showDropdown: false
- });
- }
-
- aboutModalDismissed() {
- this.setState({showAboutModal: false});
- }
-
- showAccountSettingsModal(e) {
- e.preventDefault();
-
- this.setState({showDropdown: false});
-
- GlobalActions.showAccountSettingsModal();
- }
-
- showShortcutsModal(e) {
- e.preventDefault();
- this.setState({showDropdown: false});
-
- GlobalActions.showShortcutsModal();
- }
-
- showAddUsersToTeamModal(e) {
- e.preventDefault();
-
- this.setState({
- showAddUsersToTeamModal: true,
- showDropdown: false
- });
- }
-
- hideAddUsersToTeamModal() {
- this.setState({
- showAddUsersToTeamModal: false
- });
- }
-
- showInviteMemberModal(e) {
- e.preventDefault();
-
- this.setState({showDropdown: false});
-
- GlobalActions.showInviteMemberModal();
- }
-
- showGetTeamInviteLinkModal(e) {
- e.preventDefault();
-
- this.setState({showDropdown: false});
-
- GlobalActions.showGetTeamInviteLinkModal();
- }
-
- showTeamMembersModal(e) {
- e.preventDefault();
-
- this.setState({
- showTeamMembersModal: true
- });
- }
-
- hideTeamMembersModal() {
- this.setState({
- showTeamMembersModal: false
- });
- }
-
- componentDidMount() {
- TeamStore.addChangeListener(this.onTeamChange);
- }
-
- onTeamChange() {
- this.setState({
- teamMembers: TeamStore.getMyTeamMembers(),
- teamListings: TeamStore.getTeamListings(),
- showDropdown: false
- });
- }
-
- componentWillUnmount() {
- $(ReactDOM.findDOMNode(this.refs.dropdown)).off('hide.bs.dropdown');
- TeamStore.removeChangeListener(this.onTeamChange);
- }
-
- renderCustomEmojiLink() {
- if (window.mm_config.EnableCustomEmoji !== 'true' || !Utils.canCreateCustomEmoji(this.props.currentUser)) {
- return null;
- }
-
- return (
- <li>
- <Link
- id='customEmoji'
- onClick={this.handleClick}
- to={'/' + this.props.teamName + '/emoji'}
- >
- <FormattedMessage
- id='navbar_dropdown.emoji'
- defaultMessage='Custom Emoji'
- />
- </Link>
- </li>
- );
- }
-
- render() {
- const config = global.mm_config;
- const currentUser = this.props.currentUser;
- let teamLink = '';
- let inviteLink = '';
- let addMemberToTeam = '';
- let manageLink = '';
- let sysAdminLink = '';
- let isAdmin = false;
- let isSystemAdmin = false;
- let teamSettings = null;
- let integrationsLink = null;
-
- if (!currentUser) {
- return null;
- }
-
- if (currentUser != null) {
- isAdmin = TeamStore.isTeamAdminForCurrentTeam() || UserStore.isSystemAdminForCurrentUser();
- isSystemAdmin = UserStore.isSystemAdminForCurrentUser();
-
- inviteLink = (
- <li>
- <a
- id='sendEmailInvite'
- href='#'
- onClick={this.showInviteMemberModal}
- >
- <FormattedMessage
- id='navbar_dropdown.inviteMember'
- defaultMessage='Send Email Invite'
- />
- </a>
- </li>
- );
-
- addMemberToTeam = (
- <li>
- <a
- id='addUsersToTeam'
- href='#'
- onClick={this.showAddUsersToTeamModal}
- >
- <FormattedMessage
- id='navbar_dropdown.addMemberToTeam'
- defaultMessage='Add Members to Team'
- />
- </a>
- </li>
- );
-
- if (this.props.teamType === Constants.OPEN_TEAM && config.EnableUserCreation === 'true') {
- teamLink = (
- <li>
- <a
- id='getTeamInviteLink'
- href='#'
- onClick={this.showGetTeamInviteLinkModal}
- >
- <FormattedMessage
- id='navbar_dropdown.teamLink'
- defaultMessage='Get Team Invite Link'
- />
- </a>
- </li>
- );
- }
-
- if (global.window.mm_license.IsLicensed === 'true') {
- if (config.RestrictTeamInvite === Constants.PERMISSIONS_SYSTEM_ADMIN && !isSystemAdmin) {
- teamLink = null;
- inviteLink = null;
- addMemberToTeam = null;
- } else if (config.RestrictTeamInvite === Constants.PERMISSIONS_TEAM_ADMIN && !isAdmin) {
- teamLink = null;
- inviteLink = null;
- addMemberToTeam = null;
- }
- }
- }
-
- let membersName = (
- <FormattedMessage
- id='navbar_dropdown.manageMembers'
- defaultMessage='Manage Members'
- />
- );
-
- if (isAdmin) {
- teamSettings = (
- <li>
- <a
- id='teamSettings'
- href='#'
- data-toggle='modal'
- data-target='#team_settings'
- onClick={this.toggleDropdown}
- >
- <FormattedMessage
- id='navbar_dropdown.teamSettings'
- defaultMessage='Team Settings'
- />
- </a>
- </li>
- );
- } else {
- membersName = (
- <FormattedMessage
- id='navbar_dropdown.viewMembers'
- defaultMessage='View Members'
- />
- );
- }
-
- manageLink = (
- <li>
- <a
- href='#'
- onClick={this.showTeamMembersModal}
- >
- {membersName}
- </a>
- </li>
- );
-
- const integrationsEnabled =
- config.EnableIncomingWebhooks === 'true' ||
- config.EnableOutgoingWebhooks === 'true' ||
- config.EnableCommands === 'true' ||
- (config.EnableOAuthServiceProvider === 'true' && (isSystemAdmin || config.EnableOnlyAdminIntegrations !== 'true'));
- if (integrationsEnabled && (isAdmin || config.EnableOnlyAdminIntegrations !== 'true')) {
- integrationsLink = (
- <li>
- <Link
- id='Integrations'
- to={'/' + this.props.teamName + '/integrations'}
- onClick={this.handleClick}
- >
- <FormattedMessage
- id='navbar_dropdown.integrations'
- defaultMessage='Integrations'
- />
- </Link>
- </li>
- );
- }
-
- if (isSystemAdmin) {
- sysAdminLink = (
- <li>
- <Link
- id='systemConsole'
- to={'/admin_console'}
- onClick={this.handleClick}
- >
- <FormattedMessage
- id='navbar_dropdown.console'
- defaultMessage='System Console'
- />
- </Link>
- </li>
- );
- }
-
- const teams = [];
- let moreTeams = false;
-
- if (config.EnableTeamCreation === 'true' || UserStore.isSystemAdminForCurrentUser()) {
- teams.push(
- <li key='newTeam_li'>
- <Link
- id='createTeam'
- key='newTeam_a'
- to='/create_team'
- onClick={this.handleClick}
- >
- <FormattedMessage
- id='navbar_dropdown.create'
- defaultMessage='Create a New Team'
- />
- </Link>
- </li>
- );
- }
-
- const isAlreadyMember = this.state.teamMembers.reduce((result, item) => {
- result[item.team_id] = true;
- return result;
- }, {});
-
- for (const id in this.state.teamListings) {
- if (this.state.teamListings.hasOwnProperty(id) && !isAlreadyMember[id]) {
- moreTeams = true;
- break;
- }
- }
-
- if (moreTeams) {
- teams.push(
- <li key='joinTeam_li'>
- <Link
- id='joinAnotherTeam'
- onClick={this.handleClick}
- to='/select_team'
- >
- <FormattedMessage
- id='navbar_dropdown.join'
- defaultMessage='Join Another Team'
- />
- </Link>
- </li>
- );
- }
-
- teams.push(
- <li key='leaveTeam_li'>
- <a
- id='leaveTeam'
- href='#'
- onClick={GlobalActions.showLeaveTeamModal}
- >
- <FormattedMessage
- id='navbar_dropdown.leave'
- defaultMessage='Leave Team'
- />
- </a>
- </li>
- );
-
- let helpLink = null;
- if (config.HelpLink) {
- helpLink = (
- <li>
- <Link
- target='_blank'
- rel='noopener noreferrer'
- to={config.HelpLink}
- >
- <FormattedMessage
- id='navbar_dropdown.help'
- defaultMessage='Help'
- />
- </Link>
- </li>
- );
- }
-
- let reportLink = null;
- if (config.ReportAProblemLink) {
- reportLink = (
- <li>
- <Link
- target='_blank'
- rel='noopener noreferrer'
- to={config.ReportAProblemLink}
- >
- <FormattedMessage
- id='navbar_dropdown.report'
- defaultMessage='Report a Problem'
- />
- </Link>
- </li>
- );
- }
-
- let nativeAppLink = null;
- if (global.window.mm_config.AppDownloadLink && !UserAgent.isMobileApp()) {
- nativeAppLink = (
- <li>
- <Link
- target='_blank'
- rel='noopener noreferrer'
- to={global.window.mm_config.AppDownloadLink}
- >
- <FormattedMessage
- id='navbar_dropdown.nativeApps'
- defaultMessage='Download Apps'
- />
- </Link>
- </li>
- );
- }
-
- let teamMembersModal;
- if (this.state.showTeamMembersModal) {
- teamMembersModal = (
- <TeamMembersModal
- onLoad={this.toggleDropdown}
- onHide={this.hideTeamMembersModal}
- isAdmin={isAdmin}
- />
- );
- }
-
- let addUsersToTeamModal;
- if (this.state.showAddUsersToTeamModal) {
- addUsersToTeamModal = (
- <AddUsersToTeam
- onModalDismissed={this.hideAddUsersToTeamModal}
- />
- );
- }
-
- const keyboardShortcuts = (
- <li>
- <a
- id='keyboardShortcuts'
- href='#'
- onClick={this.showShortcutsModal}
- >
- <FormattedMessage
- id='navbar_dropdown.keyboardShortcuts'
- defaultMessage='Keyboard Shortcuts'
- />
- </a>
- </li>
- );
-
- const accountSettings = (
- <li>
- <a
- id='accountSettings'
- href='#'
- onClick={this.showAccountSettingsModal}
- >
- <FormattedMessage
- id='navbar_dropdown.accountSettings'
- defaultMessage='Account Settings'
- />
- </a>
- </li>
- );
-
- const about = (
- <li>
- <a
- href='#'
- onClick={this.handleAboutModal}
- >
- <FormattedMessage
- id='navbar_dropdown.about'
- defaultMessage='About Mattermost'
- />
- </a>
- </li>
- );
-
- const logout = (
- <li>
- <a
- id='logout'
- href='#'
- onClick={() => GlobalActions.emitUserLoggedOutEvent()}
- >
- <FormattedMessage
- id='navbar_dropdown.logout'
- defaultMessage='Logout'
- />
- </a>
- </li>
- );
-
- const customEmoji = this.renderCustomEmojiLink();
-
- // Dividers.
- let inviteDivider = null;
- if (inviteLink || teamLink || addMemberToTeam) {
- inviteDivider = <li className='divider'/>;
- }
-
- let teamDivider = null;
- if (teamSettings || manageLink || teams) {
- teamDivider = <li className='divider'/>;
- }
-
- let backstageDivider = null;
- if (integrationsLink || customEmoji) {
- backstageDivider = <li className='divider'/>;
- }
-
- let sysAdminDivider = null;
- if (sysAdminLink) {
- sysAdminDivider = <li className='divider'/>;
- }
-
- let helpDivider = null;
- if (helpLink || reportLink || nativeAppLink || about) {
- helpDivider = <li className='divider'/>;
- }
-
- let logoutDivider = null;
- if (logout) {
- logoutDivider = <li className='divider'/>;
- }
-
- return (
- <Dropdown
- id='sidebar-header-dropdown'
- open={this.state.showDropdown}
- onToggle={this.toggleDropdown}
- className='sidebar-header-dropdown'
- pullRight={true}
- >
- <SidebarHeaderDropdownButton
- bsRole='toggle'
- onClick={this.toggleDropdown}
- />
- <Dropdown.Menu>
- {accountSettings}
- {inviteDivider}
- {inviteLink}
- {teamLink}
- {addMemberToTeam}
- {teamDivider}
- {teamSettings}
- {manageLink}
- {teams}
- {backstageDivider}
- {integrationsLink}
- {customEmoji}
- {sysAdminDivider}
- {sysAdminLink}
- {helpDivider}
- {helpLink}
- {keyboardShortcuts}
- {reportLink}
- {nativeAppLink}
- {about}
- {logoutDivider}
- {logout}
- {teamMembersModal}
- <AboutBuildModal
- show={this.state.showAboutModal}
- onModalDismissed={this.aboutModalDismissed}
- />
- {addUsersToTeamModal}
- </Dropdown.Menu>
- </Dropdown>
- );
- }
-}
diff --git a/webapp/components/sidebar_header_dropdown_button.jsx b/webapp/components/sidebar_header_dropdown_button.jsx
deleted file mode 100644
index 82dd6eccf..000000000
--- a/webapp/components/sidebar_header_dropdown_button.jsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import Constants from 'utils/constants.jsx';
-import {Tooltip, OverlayTrigger} from 'react-bootstrap';
-import {FormattedMessage} from 'react-intl';
-
-export default class SidebarHeaderDropdownButton extends React.PureComponent {
- static propTypes = {
- bsRole: PropTypes.oneOf(['toggle']).isRequired, // eslint-disable-line react/no-unused-prop-types
- onClick: PropTypes.func.isRequired
- };
-
- render() {
- const mainMenuToolTip = (
- <Tooltip id='main-menu__tooltip'>
- <FormattedMessage
- id='sidebar.mainMenu'
- defaultMessage='Main menu'
- />
- </Tooltip>
- );
-
- return (
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={Constants.OVERLAY_TIME_DELAY}
- placement='right'
- overlay={mainMenuToolTip}
- >
- <a
- href='#'
- id='sidebarHeaderDropdownButton'
- className='sidebar-header-dropdown__toggle'
- onClick={this.props.onClick}
- >
- <span
- className='sidebar-header-dropdown__icon'
- dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}}
- />
- </a>
- </OverlayTrigger>
- );
- }
-}
diff --git a/webapp/components/sidebar_right/index.js b/webapp/components/sidebar_right/index.js
deleted file mode 100644
index 126ffc776..000000000
--- a/webapp/components/sidebar_right/index.js
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import SidebarRight from './sidebar_right.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps,
- postRightVisible: Boolean(state.views.rhs.selectedPostId),
- fromSearch: state.views.rhs.fromSearch,
- fromFlaggedPosts: state.views.rhs.fromFlaggedPosts,
- fromPinnedPosts: state.views.rhs.fromPinnedPosts
- };
-}
-
-export default connect(mapStateToProps)(SidebarRight);
diff --git a/webapp/components/sidebar_right/sidebar_right.jsx b/webapp/components/sidebar_right/sidebar_right.jsx
deleted file mode 100644
index f7c0a6400..000000000
--- a/webapp/components/sidebar_right/sidebar_right.jsx
+++ /dev/null
@@ -1,264 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-
-import SearchResults from 'components/search_results.jsx';
-import RhsThread from 'components/rhs_thread';
-import SearchBox from 'components/search_bar.jsx';
-import FileUploadOverlay from 'components/file_upload_overlay.jsx';
-import SearchStore from 'stores/search_store.jsx';
-import PostStore from 'stores/post_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-import PreferenceStore from 'stores/preference_store.jsx';
-import WebrtcStore from 'stores/webrtc_store.jsx';
-
-import {getFlaggedPosts, getPinnedPosts} from 'actions/post_actions.jsx';
-import {trackEvent} from 'actions/diagnostics_actions.jsx';
-import {postListScrollChange} from 'actions/global_actions.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-export default class SidebarRight extends React.Component {
- static propTypes = {
- channel: PropTypes.object,
- postRightVisible: PropTypes.bool,
- fromSearch: PropTypes.string,
- fromFlaggedPosts: PropTypes.bool,
- fromPinnedPosts: PropTypes.bool
- }
-
- constructor(props) {
- super(props);
-
- this.plScrolledToBottom = true;
-
- this.onPreferenceChange = this.onPreferenceChange.bind(this);
- this.onPostPinnedChange = this.onPostPinnedChange.bind(this);
- this.onSearchChange = this.onSearchChange.bind(this);
- this.onUserChange = this.onUserChange.bind(this);
- this.onShowSearch = this.onShowSearch.bind(this);
- this.onShrink = this.onShrink.bind(this);
- this.toggleSize = this.toggleSize.bind(this);
-
- this.doStrangeThings = this.doStrangeThings.bind(this);
-
- this.state = {
- searchVisible: SearchStore.getSearchResults() !== null,
- isMentionSearch: SearchStore.getIsMentionSearch(),
- isFlaggedPosts: SearchStore.getIsFlaggedPosts(),
- isPinnedPosts: SearchStore.getIsPinnedPosts(),
- expanded: false,
- fromSearch: false,
- currentUser: UserStore.getCurrentUser(),
- useMilitaryTime: PreferenceStore.getBool(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, Constants.Preferences.USE_MILITARY_TIME, false)
- };
- }
-
- componentDidMount() {
- SearchStore.addSearchChangeListener(this.onSearchChange);
- PostStore.addPostPinnedChangeListener(this.onPostPinnedChange);
- SearchStore.addShowSearchListener(this.onShowSearch);
- UserStore.addChangeListener(this.onUserChange);
- PreferenceStore.addChangeListener(this.onPreferenceChange);
- this.doStrangeThings();
- }
-
- componentWillUnmount() {
- SearchStore.removeSearchChangeListener(this.onSearchChange);
- PostStore.removePostPinnedChangeListener(this.onPostPinnedChange);
- SearchStore.removeShowSearchListener(this.onShowSearch);
- UserStore.removeChangeListener(this.onUserChange);
- PreferenceStore.removeChangeListener(this.onPreferenceChange);
- }
-
- shouldComponentUpdate(nextProps, nextState) {
- return !Utils.areObjectsEqual(nextState, this.state) || this.props.postRightVisible !== nextProps.postRightVisible;
- }
-
- componentWillUpdate(nextProps, nextState) {
- const isOpen = this.state.searchVisible || this.props.postRightVisible;
- const willOpen = nextState.searchVisible || nextProps.postRightVisible;
-
- if (!isOpen && willOpen) {
- trackEvent('ui', 'ui_rhs_opened');
- }
-
- if (!isOpen && willOpen) {
- this.setState({
- expanded: false
- });
- }
- }
-
- doStrangeThings() {
- // We should have a better way to do this stuff
- // Hence the function name.
- $('.app__body .inner-wrap').removeClass('.move--right');
- $('.app__body .inner-wrap').addClass('move--left');
- $('.app__body .sidebar--left').removeClass('move--right');
- $('.multi-teams .team-sidebar').removeClass('move--right');
- $('.app__body .sidebar--right').addClass('move--left');
-
- //$('.sidebar--right').prepend('<div class="sidebar__overlay"></div>');
- if (!this.state.searchVisible && !this.props.postRightVisible) {
- $('.app__body .inner-wrap').removeClass('move--left').removeClass('move--right');
- $('.app__body .sidebar--right').removeClass('move--left');
- return (
- <div/>
- );
- }
-
- /*setTimeout(() => {
- $('.sidebar__overlay').fadeOut('200', () => {
- $('.sidebar__overlay').remove();
- });
- }, 500);*/
- return null;
- }
-
- componentDidUpdate(prevProps, prevState) {
- const isOpen = this.state.searchVisible || this.props.postRightVisible;
- WebrtcStore.emitRhsChanged(isOpen);
- this.doStrangeThings();
-
- const wasOpen = prevState.searchVisible || prevProps.postRightVisible;
-
- if (isOpen && !wasOpen) {
- setTimeout(() => postListScrollChange(), 0);
- }
- }
-
- onPreferenceChange() {
- if (this.state.isFlaggedPosts) {
- getFlaggedPosts();
- }
-
- this.setState({
- useMilitaryTime: PreferenceStore.getBool(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, Constants.Preferences.USE_MILITARY_TIME, false)
- });
- }
-
- onPostPinnedChange() {
- if (this.props.channel && this.state.isPinnedPosts) {
- getPinnedPosts(this.props.channel.id);
- }
- }
-
- onShrink() {
- this.setState({
- expanded: false
- });
- }
-
- onSearchChange() {
- this.setState({
- searchVisible: SearchStore.getSearchResults() !== null || SearchStore.isLoading(),
- isMentionSearch: SearchStore.getIsMentionSearch(),
- isFlaggedPosts: SearchStore.getIsFlaggedPosts(),
- isPinnedPosts: SearchStore.getIsPinnedPosts()
- });
- }
-
- onUserChange() {
- this.setState({
- currentUser: UserStore.getCurrentUser()
- });
- }
-
- onShowSearch() {
- if (!this.state.searchVisible) {
- this.setState({
- searchVisible: true
- });
- }
- }
-
- toggleSize() {
- this.setState({expanded: !this.state.expanded});
- }
-
- render() {
- let content = null;
- let expandedClass = '';
-
- if (this.state.expanded) {
- expandedClass = 'sidebar--right--expanded';
- }
-
- var currentId = UserStore.getCurrentId();
- var searchForm = null;
- if (currentId) {
- searchForm = <SearchBox isFocus={this.state.searchVisible && Utils.isMobile()}/>;
- }
-
- const channel = this.props.channel;
-
- let channelDisplayName = '';
- if (channel) {
- if (channel.type === Constants.DM_CHANNEL || channel.type === Constants.GM_CHANNEL) {
- channelDisplayName = Utils.localizeMessage('rhs_root.direct', 'Direct Message');
- } else {
- channelDisplayName = channel.display_name;
- }
- }
-
- if (this.state.searchVisible) {
- content = (
- <div className='sidebar--right__content'>
- <div className='search-bar__container channel-header alt'>{searchForm}</div>
- <SearchResults
- isMentionSearch={this.state.isMentionSearch}
- isFlaggedPosts={this.state.isFlaggedPosts}
- isPinnedPosts={this.state.isPinnedPosts}
- useMilitaryTime={this.state.useMilitaryTime}
- toggleSize={this.toggleSize}
- shrink={this.onShrink}
- channelDisplayName={channelDisplayName}
- />
- </div>
- );
- } else if (this.props.postRightVisible) {
- content = (
- <div className='post-right__container'>
- <FileUploadOverlay overlayType='right'/>
- <div className='search-bar__container channel-header alt'>{searchForm}</div>
- <RhsThread
- fromFlaggedPosts={this.props.fromFlaggedPosts}
- fromSearch={this.props.fromSearch}
- fromPinnedPosts={this.props.fromPinnedPosts}
- isWebrtc={WebrtcStore.isBusy()}
- isMentionSearch={this.state.isMentionSearch}
- currentUser={this.state.currentUser}
- useMilitaryTime={this.state.useMilitaryTime}
- toggleSize={this.toggleSize}
- shrink={this.onShrink}
- />
- </div>
- );
- }
-
- if (!content) {
- expandedClass = '';
- }
-
- return (
- <div
- className={'sidebar--right ' + expandedClass}
- id='sidebar-right'
- >
- <div
- onClick={this.onShrink}
- className='sidebar--right__bg'
- />
- <div className='sidebar-right-container'>
- {content}
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/sidebar_right_menu.jsx b/webapp/components/sidebar_right_menu.jsx
deleted file mode 100644
index c8b6f1ec7..000000000
--- a/webapp/components/sidebar_right_menu.jsx
+++ /dev/null
@@ -1,554 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import TeamMembersModal from './team_members_modal.jsx';
-import ToggleModalButton from './toggle_modal_button.jsx';
-import AboutBuildModal from './about_build_modal.jsx';
-import AddUsersToTeam from 'components/add_users_to_team';
-
-import UserStore from 'stores/user_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-import SearchStore from 'stores/search_store.jsx';
-import PreferenceStore from 'stores/preference_store.jsx';
-import WebrtcStore from 'stores/webrtc_store.jsx';
-
-import * as GlobalActions from 'actions/global_actions.jsx';
-import {getFlaggedPosts} from 'actions/post_actions.jsx';
-import * as UserAgent from 'utils/user_agent.jsx';
-import * as Utils from 'utils/utils.jsx';
-import {Constants, WebrtcActionTypes} from 'utils/constants.jsx';
-
-const Preferences = Constants.Preferences;
-const TutorialSteps = Constants.TutorialSteps;
-
-import {FormattedMessage} from 'react-intl';
-import {Link} from 'react-router/es6';
-import {createMenuTip} from 'components/tutorial/tutorial_tip.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class SidebarRightMenu extends React.Component {
- constructor(props) {
- super(props);
-
- this.onChange = this.onChange.bind(this);
- this.handleClick = this.handleClick.bind(this);
- this.handleAboutModal = this.handleAboutModal.bind(this);
- this.showAddUsersToTeamModal = this.showAddUsersToTeamModal.bind(this);
- this.hideAddUsersToTeamModal = this.hideAddUsersToTeamModal.bind(this);
- this.searchMentions = this.searchMentions.bind(this);
- this.aboutModalDismissed = this.aboutModalDismissed.bind(this);
- this.getFlagged = this.getFlagged.bind(this);
-
- const state = this.getStateFromStores();
- state.showAboutModal = false;
- state.showAddUsersToTeamModal = false;
-
- this.state = state;
- }
-
- handleClick(e) {
- if (WebrtcStore.isBusy()) {
- WebrtcStore.emitChanged({action: WebrtcActionTypes.IN_PROGRESS});
- e.preventDefault();
- }
- }
-
- handleAboutModal() {
- this.setState({showAboutModal: true});
- }
-
- aboutModalDismissed() {
- this.setState({showAboutModal: false});
- }
-
- showAddUsersToTeamModal(e) {
- e.preventDefault();
-
- this.setState({
- showAddUsersToTeamModal: true,
- showDropdown: false
- });
- }
-
- hideAddUsersToTeamModal() {
- this.setState({
- showAddUsersToTeamModal: false
- });
- }
-
- getFlagged(e) {
- e.preventDefault();
- getFlaggedPosts();
- this.hideSidebars();
- }
-
- componentDidMount() {
- TeamStore.addChangeListener(this.onChange);
- PreferenceStore.addChangeListener(this.onChange);
- }
-
- componentWillUnmount() {
- TeamStore.removeChangeListener(this.onChange);
- PreferenceStore.removeChangeListener(this.onChange);
- }
-
- getStateFromStores() {
- const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999);
-
- return {
- currentUser: UserStore.getCurrentUser(),
- teamMembers: TeamStore.getMyTeamMembers(),
- teamListings: TeamStore.getTeamListings(),
- showTutorialTip: tutorialStep === TutorialSteps.MENU_POPOVER && Utils.isMobile()
- };
- }
-
- onChange() {
- this.setState(this.getStateFromStores());
- }
-
- searchMentions(e) {
- e.preventDefault();
- const user = this.state.currentUser;
- if (SearchStore.isMentionSearch) {
- GlobalActions.toggleSideBarAction(false);
- } else {
- GlobalActions.emitSearchMentionsEvent(user);
- this.hideSidebars();
- }
- }
-
- closeLeftSidebar() {
- if (Utils.isMobile()) {
- setTimeout(() => {
- document.querySelector('.app__body .inner-wrap').classList.remove('move--right');
- document.querySelector('.app__body .sidebar--left').classList.remove('move--right');
- });
- }
- }
-
- openRightSidebar() {
- if (Utils.isMobile()) {
- setTimeout(() => {
- document.querySelector('.app__body .inner-wrap').classList.add('move--left-small');
- document.querySelector('.app__body .sidebar--menu').classList.add('move--left');
- });
- }
- }
-
- hideSidebars() {
- if (Utils.isMobile()) {
- GlobalActions.toggleSideBarRightMenuAction();
- }
- }
-
- render() {
- const currentUser = UserStore.getCurrentUser();
- let teamLink;
- let inviteLink;
- let addUserToTeamLink;
- let teamSettingsLink;
- let manageLink;
- let consoleLink;
- let joinAnotherTeamLink;
- let isAdmin = false;
- let isSystemAdmin = false;
- let createTeam = null;
-
- if (currentUser != null) {
- isAdmin = TeamStore.isTeamAdminForCurrentTeam() || UserStore.isSystemAdminForCurrentUser();
- isSystemAdmin = UserStore.isSystemAdminForCurrentUser();
-
- inviteLink = (
- <li>
- <a
- href='#'
- onClick={GlobalActions.showInviteMemberModal}
- >
- <i className='icon fa fa-user-plus'/>
- <FormattedMessage
- id='sidebar_right_menu.inviteNew'
- defaultMessage='Send Email Invite'
- />
- </a>
- </li>
- );
-
- addUserToTeamLink = (
- <li>
- <a
- id='addUsersToTeam'
- href='#'
- onClick={this.showAddUsersToTeamModal}
- >
- <i className='icon fa fa-user-plus'/>
- <FormattedMessage
- id='sidebar_right_menu.addMemberToTeam'
- defaultMessage='Add Members to Team'
- />
- </a>
- </li>
- );
-
- if (this.props.teamType === Constants.OPEN_TEAM && global.mm_config.EnableUserCreation === 'true') {
- teamLink = (
- <li>
- <a
- href='#'
- onClick={GlobalActions.showGetTeamInviteLinkModal}
- >
- <i className='icon fa fa-link'/>
- <FormattedMessage
- id='sidebar_right_menu.teamLink'
- defaultMessage='Get Team Invite Link'
- />
- </a>
- </li>
- );
- }
-
- if (global.window.mm_license.IsLicensed === 'true') {
- if (global.window.mm_config.RestrictTeamInvite === Constants.PERMISSIONS_SYSTEM_ADMIN && !isSystemAdmin) {
- teamLink = null;
- inviteLink = null;
- addUserToTeamLink = null;
- } else if (global.window.mm_config.RestrictTeamInvite === Constants.PERMISSIONS_TEAM_ADMIN && !isAdmin) {
- teamLink = null;
- inviteLink = null;
- addUserToTeamLink = null;
- }
- }
-
- let moreTeams = false;
- const isAlreadyMember = this.state.teamMembers.reduce((result, item) => {
- result[item.team_id] = true;
- return result;
- }, {});
-
- for (const id in this.state.teamListings) {
- if (this.state.teamListings.hasOwnProperty(id) && !isAlreadyMember[id]) {
- moreTeams = true;
- break;
- }
- }
-
- if (moreTeams) {
- joinAnotherTeamLink = (
- <li key='joinTeam_li'>
- <Link to='/select_team'>
- <i className='icon fa fa-plus-square'/>
- <FormattedMessage
- id='navbar_dropdown.join'
- defaultMessage='Join Another Team'
- />
- </Link>
- </li>
- );
- }
-
- if (global.window.mm_config.EnableTeamCreation === 'true' || isSystemAdmin) {
- createTeam = (
- <li key='newTeam_li'>
- <Link
- id='createTeam'
- key='newTeam_a'
- to='/create_team'
- onClick={this.handleClick}
- >
- <i className='icon fa fa-plus-square'/>
- <FormattedMessage
- id='navbar_dropdown.create'
- defaultMessage='Create a New Team'
- />
- </Link>
- </li>
- );
- }
- }
-
- manageLink = (
- <li>
- <ToggleModalButton dialogType={TeamMembersModal}>
- <i className='icon fa fa-users'/>
- <FormattedMessage
- id='sidebar_right_menu.viewMembers'
- defaultMessage='View Members'
- />
- </ToggleModalButton>
- </li>
- );
-
- const leaveTeam = (
- <li key='leaveTeam_li'>
- <a
- id='leaveTeam'
- href='#'
- onClick={GlobalActions.showLeaveTeamModal}
- >
- <span
- className='icon'
- dangerouslySetInnerHTML={{__html: Constants.LEAVE_TEAM_SVG}}
- />
- <FormattedMessage
- id='navbar_dropdown.leave'
- defaultMessage='Leave Team'
- />
- </a>
- </li>
- );
-
- if (isAdmin) {
- teamSettingsLink = (
- <li>
- <a
- href='#'
- data-toggle='modal'
- data-target='#team_settings'
- >
- <i className='icon fa fa-globe'/>
- <FormattedMessage
- id='sidebar_right_menu.teamSettings'
- defaultMessage='Team Settings'
- />
- </a>
- </li>
- );
- manageLink = (
- <li>
- <ToggleModalButton
- dialogType={TeamMembersModal}
- dialogProps={{isAdmin}}
- >
- <i className='icon fa fa-users'/>
- <FormattedMessage
- id='sidebar_right_menu.manageMembers'
- defaultMessage='Manage Members'
- />
- </ToggleModalButton>
- </li>
- );
- }
-
- if (isSystemAdmin && !Utils.isMobile()) {
- consoleLink = (
- <li>
- <Link
- to={'/admin_console'}
- onClick={this.handleClick}
- >
- <i className='icon fa fa-wrench'/>
- <FormattedMessage
- id='sidebar_right_menu.console'
- defaultMessage='System Console'
- />
- </Link>
- </li>
- );
- }
-
- var siteName = '';
- if (global.window.mm_config.SiteName != null) {
- siteName = global.window.mm_config.SiteName;
- }
- var teamDisplayName = siteName;
- if (this.props.teamDisplayName) {
- teamDisplayName = this.props.teamDisplayName;
- }
-
- let helpLink = null;
- if (global.window.mm_config.HelpLink) {
- helpLink = (
- <li>
- <Link
- target='_blank'
- rel='noopener noreferrer'
- to={global.window.mm_config.HelpLink}
- >
- <i className='icon fa fa-question'/>
- <FormattedMessage
- id='sidebar_right_menu.help'
- defaultMessage='Help'
- />
- </Link>
- </li>
- );
- }
-
- let reportLink = null;
- if (global.window.mm_config.ReportAProblemLink) {
- reportLink = (
- <li>
- <Link
- target='_blank'
- rel='noopener noreferrer'
- to={global.window.mm_config.ReportAProblemLink}
- >
- <i className='icon fa fa-phone'/>
- <FormattedMessage
- id='sidebar_right_menu.report'
- defaultMessage='Report a Problem'
- />
- </Link>
- </li>
- );
- }
-
- let tutorialTip = null;
- if (this.state.showTutorialTip) {
- tutorialTip = createMenuTip((e) => e.preventDefault(), true);
- this.closeLeftSidebar();
- this.openRightSidebar();
- }
-
- let nativeAppLink = null;
- if (global.window.mm_config.AppDownloadLink && !UserAgent.isMobileApp()) {
- nativeAppLink = (
- <li>
- <Link
- target='_blank'
- rel='noopener noreferrer'
- to={global.window.mm_config.AppDownloadLink}
- >
- <i className='icon fa fa-mobile'/>
- <FormattedMessage
- id='sidebar_right_menu.nativeApps'
- defaultMessage='Download Apps'
- />
- </Link>
- </li>
- );
- }
-
- let addUsersToTeamModal;
- if (this.state.showAddUsersToTeamModal) {
- addUsersToTeamModal = (
- <AddUsersToTeam
- onModalDismissed={this.hideAddUsersToTeamModal}
- />
- );
- }
-
- let teamDivider = null;
- if (teamSettingsLink || manageLink || joinAnotherTeamLink || createTeam || leaveTeam) {
- teamDivider = <li className='divider'/>;
- }
-
- let consoleDivider = null;
- if (consoleLink) {
- consoleDivider = <li className='divider'/>;
- }
-
- return (
- <div
- className='sidebar--menu'
- id='sidebar-menu'
- >
- <div className='team__header theme'>
- <Link
- className='team__name'
- to='/channels/town-square'
- >
- {teamDisplayName}
- </Link>
- </div>
-
- <div className='nav-pills__container'>
- {tutorialTip}
- <ul className='nav nav-pills nav-stacked'>
- <li>
- <a
- href='#'
- onClick={this.searchMentions}
- >
- <i className='icon mentions'>{'@'}</i>
- <FormattedMessage
- id='sidebar_right_menu.recentMentions'
- defaultMessage='Recent Mentions'
- />
- </a>
- </li>
- <li>
- <a
- href='#'
- onClick={this.getFlagged}
- >
- <i className='icon fa fa-flag'/>
- <FormattedMessage
- id='sidebar_right_menu.flagged'
- defaultMessage='Flagged Posts'
- />
- </a>
- </li>
- <li className='divider'/>
- <li>
- <a
- href='#'
- onClick={() => GlobalActions.showAccountSettingsModal()}
- >
- <i className='icon fa fa-cog'/>
- <FormattedMessage
- id='sidebar_right_menu.accountSettings'
- defaultMessage='Account Settings'
- />
- </a>
- </li>
- <li className='divider'/>
- {inviteLink}
- {teamLink}
- {addUserToTeamLink}
- {teamDivider}
- {teamSettingsLink}
- {manageLink}
- {createTeam}
- {joinAnotherTeamLink}
- {leaveTeam}
- {consoleDivider}
- {consoleLink}
- <li className='divider'/>
- {helpLink}
- {reportLink}
- {nativeAppLink}
- <li>
- <a
- href='#'
- onClick={this.handleAboutModal}
- >
- <i className='icon fa fa-info'/>
- <FormattedMessage
- id='navbar_dropdown.about'
- defaultMessage='About Mattermost'
- />
- </a>
- </li>
- <li className='divider'/>
- <li>
- <a
- href='#'
- onClick={() => GlobalActions.emitUserLoggedOutEvent()}
- >
- <i className='icon fa fa-sign-out'/>
- <FormattedMessage
- id='sidebar_right_menu.logout'
- defaultMessage='Logout'
- />
- </a>
- </li>
- </ul>
- </div>
- <AboutBuildModal
- show={this.state.showAboutModal}
- onModalDismissed={this.aboutModalDismissed}
- />
- {addUsersToTeamModal}
- </div>
- );
- }
-}
-
-SidebarRightMenu.propTypes = {
- teamType: PropTypes.string,
- teamDisplayName: PropTypes.string
-};
diff --git a/webapp/components/signup/components/signup_email.jsx b/webapp/components/signup/components/signup_email.jsx
deleted file mode 100644
index bcf47b825..000000000
--- a/webapp/components/signup/components/signup_email.jsx
+++ /dev/null
@@ -1,520 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import LoadingScreen from 'components/loading_screen.jsx';
-
-import * as GlobalActions from 'actions/global_actions.jsx';
-import {trackEvent} from 'actions/diagnostics_actions.jsx';
-
-import BrowserStore from 'stores/browser_store.jsx';
-import {getInviteInfo} from 'actions/team_actions.jsx';
-import {loadMe, loginById, createUserWithInvite} from 'actions/user_actions.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-import {browserHistory, Link} from 'react-router/es6';
-
-import logoImage from 'images/logo.png';
-
-export default class SignupEmail extends React.Component {
- static get propTypes() {
- return {
- location: PropTypes.object
- };
- }
-
- constructor(props) {
- super(props);
-
- this.handleSubmit = this.handleSubmit.bind(this);
-
- this.getInviteInfo = this.getInviteInfo.bind(this);
- this.renderEmailSignup = this.renderEmailSignup.bind(this);
- this.isUserValid = this.isUserValid.bind(this);
-
- this.state = this.getInviteInfo();
- }
-
- componentDidMount() {
- trackEvent('signup', 'signup_user_01_welcome');
- }
-
- getInviteInfo() {
- let data = this.props.location.query.d;
- let hash = this.props.location.query.h;
- const inviteId = this.props.location.query.id;
- let email = '';
- let teamDisplayName = '';
- let teamName = '';
- let teamId = '';
- let loading = false;
- const serverError = '';
- const noOpenServerError = false;
-
- if (hash && hash.length > 0) {
- const parsedData = JSON.parse(data);
- email = parsedData.email;
- teamDisplayName = parsedData.display_name;
- teamName = parsedData.name;
- teamId = parsedData.id;
- } else if (inviteId && inviteId.length > 0) {
- loading = true;
- getInviteInfo(
- inviteId,
- (inviteData) => {
- if (!inviteData) {
- this.setState({loading: false});
- return;
- }
-
- this.setState({
- loading: false,
- serverError: '',
- teamDisplayName: inviteData.display_name,
- teamName: inviteData.name,
- teamId: inviteData.id
- });
- },
- () => {
- this.setState({
- loading: false,
- noOpenServerError: true,
- serverError: (
- <FormattedMessage
- id='signup_user_completed.invalid_invite'
- defaultMessage='The invite link was invalid. Please speak with your Administrator to receive an invitation.'
- />
- )
- });
- }
- );
-
- data = null;
- hash = null;
- }
-
- return {
- data,
- hash,
- email,
- teamDisplayName,
- teamName,
- teamId,
- inviteId,
- loading,
- serverError,
- noOpenServerError
- };
- }
-
- handleSignupSuccess(user, data) {
- trackEvent('signup', 'signup_user_02_complete');
- loginById(
- data.id,
- user.password,
- '',
- () => {
- if (this.state.hash > 0) {
- BrowserStore.setGlobalItem(this.state.hash, JSON.stringify({usedBefore: true}));
- }
-
- loadMe().then(
- () => {
- const query = this.props.location.query;
- if (query.redirect_to) {
- browserHistory.push(query.redirect_to);
- } else {
- GlobalActions.redirectUserToDefaultTeam();
- }
- }
- );
- },
- (err) => {
- if (err.id === 'api.user.login.not_verified.app_error') {
- browserHistory.push('/should_verify_email?email=' + encodeURIComponent(user.email) + '&teamname=' + encodeURIComponent(this.state.teamName));
- } else {
- this.setState({
- serverError: err.message,
- isSubmitting: false
- });
- }
- }
- );
- }
-
- isUserValid() {
- const providedEmail = this.refs.email.value.trim();
- if (!providedEmail) {
- this.setState({
- nameError: '',
- emailError: (<FormattedMessage id='signup_user_completed.required'/>),
- passwordError: '',
- serverError: ''
- });
- return false;
- }
-
- if (!Utils.isEmail(providedEmail)) {
- this.setState({
- nameError: '',
- emailError: (<FormattedMessage id='signup_user_completed.validEmail'/>),
- passwordError: '',
- serverError: ''
- });
- return false;
- }
-
- const providedUsername = this.refs.name.value.trim().toLowerCase();
- if (!providedUsername) {
- this.setState({
- nameError: (<FormattedMessage id='signup_user_completed.required'/>),
- emailError: '',
- passwordError: '',
- serverError: ''
- });
- return false;
- }
-
- const usernameError = Utils.isValidUsername(providedUsername);
- if (usernameError === 'Cannot use a reserved word as a username.') {
- this.setState({
- nameError: (<FormattedMessage id='signup_user_completed.reserved'/>),
- emailError: '',
- passwordError: '',
- serverError: ''
- });
- return false;
- } else if (usernameError) {
- this.setState({
- nameError: (
- <FormattedMessage
- id='signup_user_completed.usernameLength'
- values={{
- min: Constants.MIN_USERNAME_LENGTH,
- max: Constants.MAX_USERNAME_LENGTH
- }}
- />
- ),
- emailError: '',
- passwordError: '',
- serverError: ''
- });
- return false;
- }
-
- const providedPassword = this.refs.password.value;
- const pwdError = Utils.isValidPassword(providedPassword);
- if (pwdError) {
- this.setState({
- nameError: '',
- emailError: '',
- passwordError: pwdError,
- serverError: ''
- });
- return false;
- }
-
- return true;
- }
-
- handleSubmit(e) {
- e.preventDefault();
-
- // bail out if a submission is already in progress
- if (this.state.isSubmitting) {
- return;
- }
-
- if (this.isUserValid()) {
- this.setState({
- nameError: '',
- emailError: '',
- passwordError: '',
- serverError: '',
- isSubmitting: true
- });
-
- const user = {
- email: this.refs.email.value.trim(),
- username: this.refs.name.value.trim().toLowerCase(),
- password: this.refs.password.value,
- allow_marketing: true
- };
-
- createUserWithInvite(user,
- this.state.data,
- this.state.hash,
- this.state.inviteId,
- this.handleSignupSuccess.bind(this, user),
- (err) => {
- this.setState({
- serverError: err.message,
- isSubmitting: false
- });
- }
- );
- }
- }
-
- renderEmailSignup() {
- let emailError = null;
- let emailHelpText = (
- <span className='help-block'>
- <FormattedMessage
- id='signup_user_completed.emailHelp'
- defaultMessage='Valid email required for sign-up'
- />
- </span>
- );
- let emailDivStyle = 'form-group';
- if (this.state.emailError) {
- emailError = (<label className='control-label'>{this.state.emailError}</label>);
- emailHelpText = '';
- emailDivStyle += ' has-error';
- }
-
- let nameError = null;
- let nameHelpText = (
- <span className='help-block'>
- <FormattedMessage
- id='signup_user_completed.userHelp'
- defaultMessage="Username must begin with a letter, and contain between {min} to {max} lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'"
- values={{
- min: Constants.MIN_USERNAME_LENGTH,
- max: Constants.MAX_USERNAME_LENGTH
- }}
- />
- </span>
- );
- let nameDivStyle = 'form-group';
- if (this.state.nameError) {
- nameError = <label className='control-label'>{this.state.nameError}</label>;
- nameHelpText = '';
- nameDivStyle += ' has-error';
- }
-
- let passwordError = null;
- let passwordDivStyle = 'form-group';
- if (this.state.passwordError) {
- passwordError = <label className='control-label'>{this.state.passwordError}</label>;
- passwordDivStyle += ' has-error';
- }
-
- let yourEmailIs = null;
- if (this.state.email) {
- yourEmailIs = (
- <FormattedHTMLMessage
- id='signup_user_completed.emailIs'
- defaultMessage="Your email address is <strong>{email}</strong>. You'll use this address to sign in to {siteName}."
- values={{
- email: this.state.email,
- siteName: global.window.mm_config.SiteName
- }}
- />
- );
- }
-
- let emailContainerStyle = 'margin--extra';
- if (this.state.email) {
- emailContainerStyle = 'hidden';
- }
-
- return (
- <form>
- <div className='inner__content'>
- <div className={emailContainerStyle}>
- <h5><strong>
- <FormattedMessage
- id='signup_user_completed.whatis'
- defaultMessage="What's your email address?"
- />
- </strong></h5>
- <div className={emailDivStyle}>
- <input
- id='email'
- type='email'
- ref='email'
- className='form-control'
- defaultValue={this.state.email}
- placeholder=''
- maxLength='128'
- autoFocus={true}
- spellCheck='false'
- autoCapitalize='off'
- />
- {emailError}
- {emailHelpText}
- </div>
- </div>
- {yourEmailIs}
- <div className='margin--extra'>
- <h5><strong>
- <FormattedMessage
- id='signup_user_completed.chooseUser'
- defaultMessage='Choose your username'
- />
- </strong></h5>
- <div className={nameDivStyle}>
- <input
- id='name'
- type='text'
- ref='name'
- className='form-control'
- placeholder=''
- maxLength={Constants.MAX_USERNAME_LENGTH}
- spellCheck='false'
- autoCapitalize='off'
- />
- {nameError}
- {nameHelpText}
- </div>
- </div>
- <div className='margin--extra'>
- <h5><strong>
- <FormattedMessage
- id='signup_user_completed.choosePwd'
- defaultMessage='Choose your password'
- />
- </strong></h5>
- <div className={passwordDivStyle}>
- <input
- id='password'
- type='password'
- ref='password'
- className='form-control'
- placeholder=''
- maxLength='128'
- spellCheck='false'
- />
- {passwordError}
- </div>
- </div>
- <p className='margin--extra'>
- <button
- id='createAccountButton'
- type='submit'
- onClick={this.handleSubmit}
- className='btn-primary btn'
- disabled={this.state.isSubmitting}
- >
- <FormattedMessage
- id='signup_user_completed.create'
- defaultMessage='Create Account'
- />
- </button>
- </p>
- </div>
- </form>
- );
- }
-
- render() {
- let serverError = null;
- if (this.state.serverError) {
- serverError = (
- <div className={'form-group has-error'}>
- <label className='control-label'>{this.state.serverError}</label>
- </div>
- );
- }
-
- if (this.state.loading) {
- return (<LoadingScreen/>);
- }
-
- let emailSignup;
- if (global.window.mm_config.EnableSignUpWithEmail === 'true') {
- emailSignup = this.renderEmailSignup();
- } else {
- return null;
- }
-
- let terms = null;
- if (!this.state.noOpenServerError && emailSignup) {
- terms = (
- <p>
- <FormattedHTMLMessage
- id='create_team.agreement'
- defaultMessage="By proceeding to create your account and use {siteName}, you agree to our <a href='{TermsOfServiceLink}'>Terms of Service</a> and <a href='{PrivacyPolicyLink}'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}."
- values={{
- siteName: global.window.mm_config.SiteName,
- TermsOfServiceLink: global.window.mm_config.TermsOfServiceLink,
- PrivacyPolicyLink: global.window.mm_config.PrivacyPolicyLink
- }}
- />
- </p>
- );
- }
-
- if (this.state.noOpenServerError) {
- emailSignup = null;
- }
-
- let description = null;
- if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.CustomBrand === 'true' && global.window.mm_config.EnableCustomBrand === 'true') {
- description = global.window.mm_config.CustomDescriptionText;
- } else {
- description = (
- <FormattedMessage
- id='web.root.signup_info'
- defaultMessage='All team communication in one place, searchable and accessible anywhere'
- />
- );
- }
-
- return (
- <div>
- <div className='signup-header'>
- <Link to='/'>
- <span className='fa fa-chevron-left'/>
- <FormattedMessage
- id='web.header.back'
- />
- </Link>
- </div>
- <div className='col-sm-12'>
- <div className='signup-team__container padding--less'>
- <img
- className='signup-team-logo'
- src={logoImage}
- />
- <h1>{global.window.mm_config.SiteName}</h1>
- <h4 className='color--light'>
- {description}
- </h4>
- <h4 className='color--light'>
- <FormattedMessage
- id='signup_user_completed.lets'
- defaultMessage="Let's create your account"
- />
- </h4>
- <span className='color--light'>
- <FormattedMessage
- id='signup_user_completed.haveAccount'
- defaultMessage='Already have an account?'
- />
- {' '}
- <Link
- to={'/login'}
- query={this.props.location.query}
- >
- <FormattedMessage
- id='signup_user_completed.signIn'
- defaultMessage='Click here to sign in.'
- />
- </Link>
- </span>
- {emailSignup}
- {serverError}
- {terms}
- </div>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/signup/components/signup_ldap.jsx b/webapp/components/signup/components/signup_ldap.jsx
deleted file mode 100644
index a7ab14b9b..000000000
--- a/webapp/components/signup/components/signup_ldap.jsx
+++ /dev/null
@@ -1,268 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import FormError from 'components/form_error.jsx';
-
-import * as GlobalActions from 'actions/global_actions.jsx';
-import {addUserToTeamFromInvite} from 'actions/team_actions.jsx';
-import {loadMe, webLoginByLdap} from 'actions/user_actions.jsx';
-import {trackEvent} from 'actions/diagnostics_actions.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-import {browserHistory, Link} from 'react-router/es6';
-
-import logoImage from 'images/logo.png';
-
-export default class SignupLdap extends React.Component {
- static get propTypes() {
- return {
- location: PropTypes.object
- };
- }
-
- constructor(props) {
- super(props);
-
- this.handleLdapSignup = this.handleLdapSignup.bind(this);
- this.handleLdapSignupSuccess = this.handleLdapSignupSuccess.bind(this);
-
- this.handleLdapIdChange = this.handleLdapIdChange.bind(this);
- this.handleLdapPasswordChange = this.handleLdapPasswordChange.bind(this);
-
- this.state = ({
- ldapError: '',
- ldapId: '',
- ldapPassword: ''
- });
- }
-
- componentDidMount() {
- trackEvent('signup', 'signup_user_01_welcome');
- }
-
- handleLdapIdChange(e) {
- this.setState({
- ldapId: e.target.value
- });
- }
-
- handleLdapPasswordChange(e) {
- this.setState({
- ldapPassword: e.target.value
- });
- }
-
- handleLdapSignup(e) {
- e.preventDefault();
-
- this.setState({ldapError: ''});
-
- webLoginByLdap(
- this.state.ldapId,
- this.state.ldapPassword,
- null,
- this.handleLdapSignupSuccess,
- (err) => {
- this.setState({
- ldapError: err.message
- });
- }
- );
- }
-
- handleLdapSignupSuccess() {
- const hash = this.props.location.query.h;
- const data = this.props.location.query.d;
- const inviteId = this.props.location.query.id;
-
- if (inviteId || hash) {
- addUserToTeamFromInvite(
- data,
- hash,
- inviteId,
- () => {
- this.finishSignup();
- },
- () => {
- // there's not really a good way to deal with this, so just let the user log in like normal
- this.finishSignup();
- }
- );
- } else {
- this.finishSignup();
- }
- }
-
- finishSignup() {
- loadMe().then(
- () => {
- const query = this.props.location.query;
- GlobalActions.loadDefaultLocale();
- if (query.redirect_to) {
- browserHistory.push(query.redirect_to);
- } else {
- GlobalActions.redirectUserToDefaultTeam();
- }
- }
- );
- }
-
- render() {
- let ldapIdPlaceholder;
- if (global.window.mm_config.LdapLoginFieldName) {
- ldapIdPlaceholder = global.window.mm_config.LdapLoginFieldName;
- } else {
- ldapIdPlaceholder = Utils.localizeMessage('login.ldapUsername', 'AD/LDAP Username');
- }
-
- let errorClass = '';
- if (this.state.ldapError) {
- errorClass += ' has-error';
- }
-
- let ldapSignup;
- if (global.window.mm_config.EnableLdap === 'true' && global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.LDAP) {
- ldapSignup = (
- <div className='inner__content'>
- <h5>
- <strong>
- <FormattedMessage
- id='signup.ldap'
- defaultMessage='AD/LDAP Credentials'
- />
- </strong>
- </h5>
- <form
- onSubmit={this.handleLdapSignup}
- >
- <div className='signup__email-container'>
- <FormError
- error={this.state.ldapError}
- margin={true}
- />
- <div className={'form-group' + errorClass}>
- <input
- className='form-control'
- name='ldapId'
- value={this.state.ldapId}
- placeholder={ldapIdPlaceholder}
- onChange={this.handleLdapIdChange}
- spellCheck='false'
- autoCapitalize='off'
- />
- </div>
- <div className={'form-group' + errorClass}>
- <input
- type='password'
- className='form-control'
- name='password'
- value={this.state.ldapPassword}
- placeholder={Utils.localizeMessage('login.password', 'Password')}
- onChange={this.handleLdapPasswordChange}
- spellCheck='false'
- />
- </div>
- <div className='form-group'>
- <button
- type='submit'
- className='btn btn-primary'
- disabled={!this.state.ldapId || !this.state.ldapPassword}
- >
- <FormattedMessage
- id='login.signIn'
- defaultMessage='Sign in'
- />
- </button>
- </div>
- </div>
- </form>
- </div>
- );
- } else {
- return null;
- }
-
- let terms = null;
- if (ldapSignup) {
- terms = (
- <p>
- <FormattedHTMLMessage
- id='create_team.agreement'
- defaultMessage="By proceeding to create your account and use {siteName}, you agree to our <a href='{TermsOfServiceLink}'>Terms of Service</a> and <a href='{PrivacyPolicyLink}'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}."
- values={{
- siteName: global.window.mm_config.SiteName,
- TermsOfServiceLink: global.window.mm_config.TermsOfServiceLink,
- PrivacyPolicyLink: global.window.mm_config.PrivacyPolicyLink
- }}
- />
- </p>
- );
- }
-
- let description = null;
- if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.CustomBrand === 'true' && global.window.mm_config.EnableCustomBrand === 'true') {
- description = global.window.mm_config.CustomDescriptionText;
- } else {
- description = (
- <FormattedMessage
- id='web.root.signup_info'
- defaultMessage='All team communication in one place, searchable and accessible anywhere'
- />
- );
- }
-
- return (
- <div>
- <div className='signup-header'>
- <Link to='/'>
- <span className='fa fa-chevron-left'/>
- <FormattedMessage
- id='web.header.back'
- />
- </Link>
- </div>
- <div className='col-sm-12'>
- <div className='signup-team__container padding--less'>
- <img
- className='signup-team-logo'
- src={logoImage}
- />
- <h1>{global.window.mm_config.SiteName}</h1>
- <h4 className='color--light'>
- {description}
- </h4>
- <h4 className='color--light'>
- <FormattedMessage
- id='signup_user_completed.lets'
- defaultMessage="Let's create your account"
- />
- </h4>
- <span className='color--light'>
- <FormattedMessage
- id='signup_user_completed.haveAccount'
- defaultMessage='Already have an account?'
- />
- {' '}
- <Link
- to={'/login'}
- query={this.props.location.query}
- >
- <FormattedMessage
- id='signup_user_completed.signIn'
- defaultMessage='Click here to sign in.'
- />
- </Link>
- </span>
- {ldapSignup}
- {terms}
- </div>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/signup/signup_controller.jsx b/webapp/components/signup/signup_controller.jsx
deleted file mode 100644
index 2fd775428..000000000
--- a/webapp/components/signup/signup_controller.jsx
+++ /dev/null
@@ -1,375 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import FormError from 'components/form_error.jsx';
-import LoadingScreen from 'components/loading_screen.jsx';
-
-import UserStore from 'stores/user_store.jsx';
-import BrowserStore from 'stores/browser_store.jsx';
-
-import {Client4} from 'mattermost-redux/client';
-import * as GlobalActions from 'actions/global_actions.jsx';
-import {addUserToTeamFromInvite, getInviteInfo} from 'actions/team_actions.jsx';
-import {loadMe} from 'actions/user_actions.jsx';
-
-import logoImage from 'images/logo.png';
-import AnnouncementBar from 'components/announcement_bar';
-
-import {FormattedMessage} from 'react-intl';
-import {browserHistory, Link} from 'react-router/es6';
-
-export default class SignupController extends React.Component {
- constructor(props) {
- super(props);
-
- this.renderSignupControls = this.renderSignupControls.bind(this);
-
- let loading = false;
- let serverError = '';
- let noOpenServerError = false;
- let usedBefore = false;
-
- if (props.location.query) {
- const hash = props.location.query.h;
- const inviteId = props.location.query.id;
-
- if (inviteId) {
- loading = true;
- } else if (hash && !UserStore.getCurrentUser()) {
- usedBefore = BrowserStore.getGlobalItem(hash);
- } else if (!inviteId && global.window.mm_config.EnableOpenServer !== 'true' && !UserStore.getNoAccounts()) {
- noOpenServerError = true;
- serverError = (
- <FormattedMessage
- id='signup_user_completed.no_open_server'
- defaultMessage='This server does not allow open signups. Please speak with your Administrator to receive an invitation.'
- />
- );
- }
- }
-
- this.state = {
- loading,
- serverError,
- noOpenServerError,
- usedBefore
- };
- }
-
- componentDidMount() {
- BrowserStore.removeGlobalItem('team');
- if (this.props.location.query) {
- const hash = this.props.location.query.h;
- const data = this.props.location.query.d;
- const inviteId = this.props.location.query.id;
-
- const userLoggedIn = UserStore.getCurrentUser() != null;
-
- if ((inviteId || hash) && userLoggedIn) {
- addUserToTeamFromInvite(
- data,
- hash,
- inviteId,
- (team) => {
- loadMe().then(
- () => {
- browserHistory.push('/' + team.name + '/channels/town-square');
- }
- );
- },
- this.handleInvalidInvite
- );
-
- return;
- }
-
- if (inviteId) {
- getInviteInfo(
- inviteId,
- (inviteData) => {
- if (!inviteData) {
- return;
- }
-
- this.setState({ // eslint-disable-line react/no-did-mount-set-state
- serverError: '',
- loading: false
- });
- },
- this.handleInvalidInvite
- );
-
- return;
- }
-
- if (userLoggedIn) {
- GlobalActions.redirectUserToDefaultTeam();
- }
- }
- }
-
- handleInvalidInvite = (err) => {
- let serverError;
- if (err.server_error_id === 'store.sql_user.save.max_accounts.app_error') {
- serverError = err.message;
- } else {
- serverError = (
- <FormattedMessage
- id='signup_user_completed.invalid_invite'
- defaultMessage='The invite link was invalid. Please speak with your Administrator to receive an invitation.'
- />
- );
- }
-
- this.setState({
- noOpenServerError: true,
- loading: false,
- serverError
- });
- }
-
- renderSignupControls() {
- let signupControls = [];
-
- if (global.window.mm_config.EnableSignUpWithEmail === 'true') {
- signupControls.push(
- <Link
- className='btn btn-custom-login btn--full email'
- key='email'
- to={'/signup_email' + window.location.search}
- >
- <span>
- <span className='icon fa fa-envelope'/>
- <FormattedMessage
- id='signup.email'
- defaultMessage='Email and Password'
- />
- </span>
- </Link>
- );
- }
-
- if (global.window.mm_config.EnableSignUpWithGitLab === 'true') {
- signupControls.push(
- <a
- className='btn btn-custom-login btn--full gitlab'
- key='gitlab'
- href={Client4.getOAuthRoute() + '/gitlab/signup' + window.location.search}
- >
- <span>
- <span className='icon'/>
- <span>
- <FormattedMessage
- id='signup.gitlab'
- defaultMessage='GitLab Single Sign-On'
- />
- </span>
- </span>
- </a>
- );
- }
-
- if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_config.EnableSignUpWithGoogle === 'true') {
- signupControls.push(
- <a
- className='btn btn-custom-login btn--full google'
- key='google'
- href={Client4.getOAuthRoute() + '/google/signup' + window.location.search}
- >
- <span>
- <span className='icon'/>
- <span>
- <FormattedMessage
- id='signup.google'
- defaultMessage='Google Account'
- />
- </span>
- </span>
- </a>
- );
- }
-
- if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_config.EnableSignUpWithOffice365 === 'true') {
- signupControls.push(
- <a
- className='btn btn-custom-login btn--full office365'
- key='office365'
- href={Client4.getOAuthRoute() + '/office365/signup' + window.location.search}
- >
- <span>
- <span className='icon'/>
- <span>
- <FormattedMessage
- id='signup.office365'
- defaultMessage='Office 365'
- />
- </span>
- </span>
- </a>
- );
- }
-
- if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_config.EnableLdap === 'true') {
- signupControls.push(
- <Link
- className='btn btn-custom-login btn--full ldap'
- key='ldap'
- to={'/signup_ldap' + window.location.search}
- >
- <span>
- <span className='icon fa fa-folder-open fa--margin-top'/>
- <span>
- <FormattedMessage
- id='signup.ldap'
- defaultMessage='AD/LDAP Credentials'
- />
- </span>
- </span>
- </Link>
- );
- }
-
- if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_config.EnableSaml === 'true') {
- let query = '';
- if (window.location.search) {
- query = '&action=signup';
- } else {
- query = '?action=signup';
- }
-
- signupControls.push(
- <a
- className='btn btn-custom-login btn--full saml'
- key='saml'
- href={'/login/sso/saml' + window.location.search + query}
- >
- <span>
- <span className='icon fa fa-lock fa--margin-top'/>
- <span>
- {global.window.mm_config.SamlLoginButtonText}
- </span>
- </span>
- </a>
- );
- }
-
- if (signupControls.length === 0) {
- const signupDisabledError = (
- <FormattedMessage
- id='signup_user_completed.none'
- defaultMessage='No user creation method has been enabled. Please contact an administrator for access.'
- />
- );
- signupControls = (
- <FormError
- error={signupDisabledError}
- margin={true}
- />
- );
- } else if (signupControls.length === 1) {
- if (global.window.mm_config.EnableSignUpWithEmail === 'true') {
- return browserHistory.push('/signup_email' + window.location.search);
- } else if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_config.EnableLdap === 'true') {
- return browserHistory.push('/signup_ldap' + window.location.search);
- }
- }
-
- return signupControls;
- }
-
- render() {
- if (this.state.loading) {
- return (<LoadingScreen/>);
- }
-
- if (this.state.usedBefore) {
- return (
- <div>
- <FormattedMessage
- id='signup_user_completed.expired'
- defaultMessage="You've already completed the signup process for this invitation or this invitation has expired."
- />
- </div>
- );
- }
-
- let serverError = null;
- if (this.state.serverError) {
- serverError = (
- <div className={'form-group has-error'}>
- <label className='control-label'>{this.state.serverError}</label>
- </div>
- );
- }
-
- let signupControls;
- if (this.state.noOpenServerError || this.state.usedBefore) {
- signupControls = null;
- } else {
- signupControls = this.renderSignupControls();
- }
-
- return (
- <div>
- <AnnouncementBar/>
- <div className='signup-header'>
- <Link to='/'>
- <span className='fa fa-chevron-left'/>
- <FormattedMessage
- id='web.header.back'
- />
- </Link>
- </div>
- <div className='col-sm-12'>
- <div className='signup-team__container'>
- <img
- className='signup-team-logo'
- src={logoImage}
- />
- <div className='signup__content'>
- <h1>{global.window.mm_config.SiteName}</h1>
- <h4 className='color--light'>
- <FormattedMessage
- id='web.root.signup_info'
- />
- </h4>
- <div className='margin--extra'>
- <h5><strong>
- <FormattedMessage
- id='signup.title'
- defaultMessage='Create an account with:'
- />
- </strong></h5>
- </div>
- {signupControls}
- {serverError}
- </div>
- <span className='color--light'>
- <FormattedMessage
- id='signup_user_completed.haveAccount'
- defaultMessage='Already have an account?'
- />
- {' '}
- <Link
- to={'/login'}
- query={this.props.location.query}
- >
- <FormattedMessage
- id='signup_user_completed.signIn'
- defaultMessage='Click here to sign in.'
- />
- </Link>
- </span>
- </div>
- </div>
- </div>
- );
- }
-}
-
-SignupController.propTypes = {
- location: PropTypes.object
-};
diff --git a/webapp/components/spinner_button.jsx b/webapp/components/spinner_button.jsx
deleted file mode 100644
index b3b291ff8..000000000
--- a/webapp/components/spinner_button.jsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import loadingGif from 'images/load.gif';
-
-export default class SpinnerButton extends React.Component {
- static get propTypes() {
- return {
- children: PropTypes.node,
- spinning: PropTypes.bool.isRequired,
- onClick: PropTypes.func
- };
- }
-
- static get defaultProps() {
- return {
- spinning: false
- };
- }
-
- render() {
- const {spinning, children, ...props} = this.props; // eslint-disable-line no-use-before-define
-
- if (spinning) {
- return (
- <img
- className='spinner-button__gif'
- src={loadingGif}
- />
- );
- }
-
- return (
- <button
- className='btn btn-primary'
- {...props}
- >
- {children}
- </button>
- );
- }
-}
diff --git a/webapp/components/status_dropdown/index.jsx b/webapp/components/status_dropdown/index.jsx
deleted file mode 100644
index e200b2e34..000000000
--- a/webapp/components/status_dropdown/index.jsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import {setStatus} from 'mattermost-redux/actions/users';
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {
- getCurrentUser,
- getStatusForUserId
-} from 'mattermost-redux/selectors/entities/users';
-import {Client4} from 'mattermost-redux/client';
-
-import StatusDropdown from 'components/status_dropdown/status_dropdown.jsx';
-
-function mapStateToProps(state) {
- const currentUser = getCurrentUser(state);
- const userId = currentUser.id;
- const lastPicUpdate = currentUser.last_picture_update;
- const profilePicture = Client4.getProfilePictureUrl(userId, lastPicUpdate);
- const status = getStatusForUserId(state, currentUser.id);
- return {
- userId,
- profilePicture,
- status
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- setStatus
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(StatusDropdown);
diff --git a/webapp/components/status_dropdown/status_dropdown.jsx b/webapp/components/status_dropdown/status_dropdown.jsx
deleted file mode 100644
index 4b173a0ea..000000000
--- a/webapp/components/status_dropdown/status_dropdown.jsx
+++ /dev/null
@@ -1,158 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import {Dropdown} from 'react-bootstrap';
-import StatusIcon from 'components/status_icon.jsx';
-import PropTypes from 'prop-types';
-import {FormattedMessage} from 'react-intl';
-import {UserStatuses} from 'utils/constants.jsx';
-import BootstrapSpan from 'components/bootstrap_span.jsx';
-
-export default class StatusDropdown extends React.Component {
-
- static propTypes = {
- style: PropTypes.object,
- status: PropTypes.string,
- userId: PropTypes.string.isRequired,
- profilePicture: PropTypes.string,
- actions: PropTypes.shape({
- setStatus: PropTypes.func.isRequired
- }).isRequired
- }
-
- state = {
- showDropdown: false,
- mouseOver: false
- }
-
- onMouseEnter = () => {
- this.setState({mouseOver: true});
- }
-
- onMouseLeave = () => {
- this.setState({mouseOver: false});
- }
-
- onToggle = (showDropdown) => {
- this.setState({showDropdown});
- }
-
- closeDropdown = () => {
- this.setState({showDropdown: false});
- }
-
- setStatus = (status) => {
- this.props.actions.setStatus({
- user_id: this.props.userId,
- status
- });
- this.closeDropdown();
- }
-
- setOnline = (event) => {
- event.preventDefault();
- this.setStatus(UserStatuses.ONLINE);
- }
-
- setOffline = (event) => {
- event.preventDefault();
- this.setStatus(UserStatuses.OFFLINE);
- }
-
- setAway = (event) => {
- event.preventDefault();
- this.setStatus(UserStatuses.AWAY);
- }
-
- renderStatusOnlineAction = () => {
- return this.renderStatusAction(UserStatuses.ONLINE, this.setOnline);
- }
-
- renderStatusAwayAction = () => {
- return this.renderStatusAction(UserStatuses.AWAY, this.setAway);
- }
-
- renderStatusOfflineAction = () => {
- return this.renderStatusAction(UserStatuses.OFFLINE, this.setOffline);
- }
-
- renderProfilePicture = () => {
- if (!this.props.profilePicture) {
- return null;
- }
- return (
- <img
- className='user__picture'
- src={this.props.profilePicture}
- />
- );
- }
-
- renderStatusAction = (status, onClick) => {
- return (
- <li key={status}>
- <a
- href={'#'}
- onClick={onClick}
- >
- <FormattedMessage
- id={`status_dropdown.set_${status}`}
- defaultMessage={status}
- />
- </a>
- </li>
- );
- }
-
- renderStatusIcon = () => {
- if (this.state.mouseOver) {
- return (
- <span className={'status status-edit'}>
- <i
- className={'fa fa-caret-down'}
- />
- </span>
- );
- }
- return (
- <StatusIcon
- status={this.props.status}
- />
- );
- }
-
- render() {
- const statusIcon = this.renderStatusIcon();
- const profilePicture = this.renderProfilePicture();
- const actions = [
- this.renderStatusOnlineAction(),
- this.renderStatusAwayAction(),
- this.renderStatusOfflineAction()
- ];
- return (
- <Dropdown
- id={'status-dropdown'}
- open={this.state.showDropdown}
- onToggle={this.onToggle}
- style={this.props.style}
- >
- <BootstrapSpan
- bsRole={'toggle'}
- onMouseEnter={this.onMouseEnter}
- onMouseLeave={this.onMouseLeave}
- >
- <div className='status-wrapper'>
- {profilePicture}
- <div className='status_dropdown__toggle'>
- {statusIcon}
- </div>
- </div>
- </BootstrapSpan>
- <Dropdown.Menu>
- {actions}
- </Dropdown.Menu>
- </Dropdown>
- );
- }
-}
diff --git a/webapp/components/status_icon.jsx b/webapp/components/status_icon.jsx
deleted file mode 100644
index 2a891d665..000000000
--- a/webapp/components/status_icon.jsx
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import Constants from 'utils/constants.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default function StatusIcon(props) {
- const status = props.status;
- const type = props.type;
-
- if (!status) {
- return null;
- }
-
- let statusIcon = '';
- if (type === 'avatar') {
- if (status === 'online') {
- statusIcon = Constants.ONLINE_AVATAR_SVG;
- } else if (status === 'away') {
- statusIcon = Constants.AWAY_AVATAR_SVG;
- } else {
- statusIcon = Constants.OFFLINE_AVATAR_SVG;
- }
- } else if (status === 'online') {
- statusIcon = Constants.ONLINE_ICON_SVG;
- } else if (status === 'away') {
- statusIcon = Constants.AWAY_ICON_SVG;
- } else {
- statusIcon = Constants.OFFLINE_ICON_SVG;
- }
-
- return (
- <span
- className={'status ' + props.className}
- dangerouslySetInnerHTML={{__html: statusIcon}}
- />
- );
-}
-
-StatusIcon.defaultProps = {
- className: ''
-};
-
-StatusIcon.propTypes = {
- status: PropTypes.string,
- className: PropTypes.string,
- type: PropTypes.string
-};
diff --git a/webapp/components/suggestion/at_mention_provider.jsx b/webapp/components/suggestion/at_mention_provider.jsx
deleted file mode 100644
index 9cf32440f..000000000
--- a/webapp/components/suggestion/at_mention_provider.jsx
+++ /dev/null
@@ -1,165 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import Suggestion from './suggestion.jsx';
-import Provider from './provider.jsx';
-
-import UserStore from 'stores/user_store.jsx';
-
-import {autocompleteUsersInChannel} from 'actions/user_actions.jsx';
-
-import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
-import * as Utils from 'utils/utils.jsx';
-import {Constants, ActionTypes} from 'utils/constants.jsx';
-
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-import XRegExp from 'xregexp';
-
-class AtMentionSuggestion extends Suggestion {
- render() {
- const isSelection = this.props.isSelection;
- const user = this.props.item;
-
- let username;
- let description;
- let icon;
- if (user.username === 'all') {
- username = 'all';
- description = (
- <FormattedMessage
- id='suggestion.mention.all'
- defaultMessage='CAUTION: This mentions everyone in channel'
- />
- );
- icon = <i className='mention__image fa fa-users fa-2x'/>;
- } else if (user.username === 'channel') {
- username = 'channel';
- description = (
- <FormattedMessage
- id='suggestion.mention.channel'
- defaultMessage='Notifies everyone in the channel'
- />
- );
- icon = <i className='mention__image fa fa-users fa-2x'/>;
- } else if (user.username === 'here') {
- username = 'here';
- description = (
- <FormattedMessage
- id='suggestion.mention.here'
- defaultMessage='Notifies everyone in the channel and online'
- />
- );
- icon = <i className='mention__image fa fa-users fa-2x'/>;
- } else {
- username = user.username;
-
- if ((user.first_name || user.last_name) && user.nickname) {
- description = `- ${Utils.getFullName(user)} (${user.nickname})`;
- } else if (user.nickname) {
- description = `- (${user.nickname})`;
- } else if (user.first_name || user.last_name) {
- description = `- ${Utils.getFullName(user)}`;
- }
-
- icon = (
- <img
- className='mention__image'
- src={Utils.imageURLForUser(user)}
- />
- );
- }
-
- let className = 'mentions__name';
- if (isSelection) {
- className += ' suggestion--selected';
- }
-
- return (
- <div
- className={className}
- onClick={this.handleClick}
- >
- <div className='pull-left'>
- {icon}
- </div>
- <div className='pull-left mention--align'>
- <span>
- {'@' + username}
- </span>
- <span className='mention__fullname'>
- {' '}
- {description}
- </span>
- </div>
- </div>
- );
- }
-}
-
-export default class AtMentionProvider extends Provider {
- constructor(channelId) {
- super();
-
- this.channelId = channelId;
- }
-
- handlePretextChanged(suggestionId, pretext) {
- const captured = XRegExp.cache('(?:^|\\W)@([\\pL\\d\\-_.]*)$', 'i').exec(pretext.toLowerCase());
- if (!captured) {
- return false;
- }
-
- const prefix = captured[1];
-
- this.startNewRequest(suggestionId, prefix);
-
- autocompleteUsersInChannel(
- prefix,
- this.channelId,
- (data) => {
- if (this.shouldCancelDispatch(prefix)) {
- return;
- }
-
- const members = Object.assign([], data.users);
- for (const id of Object.keys(members)) {
- members[id] = {...members[id], type: Constants.MENTION_MEMBERS};
- }
-
- const nonmembers = data.out_of_channel || [];
- for (const id of Object.keys(nonmembers)) {
- nonmembers[id] = {...nonmembers[id], type: Constants.MENTION_NONMEMBERS};
- }
-
- let specialMentions = [];
- if (!pretext.startsWith('/msg')) {
- specialMentions = ['here', 'channel', 'all'].filter((item) => {
- return item.startsWith(prefix);
- }).map((name) => {
- return {username: name, type: Constants.MENTION_SPECIAL};
- });
- }
-
- let users = members.concat(specialMentions).concat(nonmembers);
- const me = UserStore.getCurrentUser();
- users = users.filter((user) => {
- return user.id !== me.id;
- });
-
- const mentions = users.map((user) => '@' + user.username);
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.SUGGESTION_RECEIVED_SUGGESTIONS,
- id: suggestionId,
- matchedPretext: `@${captured[1]}`,
- terms: mentions,
- items: users,
- component: AtMentionSuggestion
- });
- }
- );
-
- return true;
- }
-}
diff --git a/webapp/components/suggestion/channel_mention_provider.jsx b/webapp/components/suggestion/channel_mention_provider.jsx
deleted file mode 100644
index 1d85d8082..000000000
--- a/webapp/components/suggestion/channel_mention_provider.jsx
+++ /dev/null
@@ -1,143 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import Suggestion from './suggestion.jsx';
-import Provider from './provider.jsx';
-
-import {autocompleteChannels} from 'actions/channel_actions.jsx';
-
-import ChannelStore from 'stores/channel_store.jsx';
-
-import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
-import {Constants, ActionTypes} from 'utils/constants.jsx';
-
-import React from 'react';
-
-class ChannelMentionSuggestion extends Suggestion {
- render() {
- const isSelection = this.props.isSelection;
- const item = this.props.item;
-
- const channelName = item.channel.display_name;
- const purpose = item.channel.purpose;
-
- let className = 'mentions__name';
- if (isSelection) {
- className += ' suggestion--selected';
- }
-
- const description = '(~' + item.channel.name + ')';
-
- return (
- <div
- className={className}
- onClick={this.handleClick}
- >
- <div className='mention__align'>
- <span>
- {channelName}
- </span>
- <span className='mention__channelname'>
- {' '}
- {description}
- </span>
- </div>
- <div className='mention__purpose'>
- {purpose}
- </div>
- </div>
- );
- }
-}
-
-export default class ChannelMentionProvider extends Provider {
- constructor() {
- super();
-
- this.lastTermWithNoResults = '';
- this.lastCompletedWord = '';
- }
-
- handlePretextChanged(suggestionId, pretext) {
- const captured = (/(^|\s)(~([^~\r\n]*))$/i).exec(pretext.toLowerCase());
-
- if (!captured) {
- // Not a channel mention
- return false;
- }
-
- if (this.lastTermWithNoResults && pretext.startsWith(this.lastTermWithNoResults)) {
- // Just give up since we know it won't return any results
- return false;
- }
-
- if (this.lastCompletedWord && captured[0].startsWith(this.lastCompletedWord)) {
- // It appears we're still matching a channel handle that we already completed
- return false;
- }
-
- // Clear the last completed word since we've started to match new text
- this.lastCompletedWord = '';
-
- const prefix = captured[3];
-
- this.startNewRequest(suggestionId, prefix);
-
- autocompleteChannels(
- prefix,
- (channels) => {
- if (this.shouldCancelDispatch(prefix)) {
- return;
- }
-
- if (channels.length === 0) {
- this.lastTermWithNoResults = pretext;
- }
-
- // Wrap channels in an outer object to avoid overwriting the 'type' property.
- const wrappedChannels = [];
- const wrappedMoreChannels = [];
- const moreChannels = [];
- channels.forEach((item) => {
- if (ChannelStore.get(item.id)) {
- wrappedChannels.push({
- type: Constants.MENTION_CHANNELS,
- channel: item
- });
- return;
- }
-
- wrappedMoreChannels.push({
- type: Constants.MENTION_MORE_CHANNELS,
- channel: item
- });
-
- moreChannels.push(item);
- });
-
- const wrapped = wrappedChannels.concat(wrappedMoreChannels);
- const mentions = wrapped.map((item) => '~' + item.channel.name);
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_MORE_CHANNELS,
- channels: moreChannels
- });
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.SUGGESTION_RECEIVED_SUGGESTIONS,
- id: suggestionId,
- matchedPretext: captured[2],
- terms: mentions,
- items: wrapped,
- component: ChannelMentionSuggestion
- });
- }
- );
-
- return true;
- }
-
- handleCompleteWord(term) {
- this.lastCompletedWord = term;
- }
-}
diff --git a/webapp/components/suggestion/command_provider.jsx b/webapp/components/suggestion/command_provider.jsx
deleted file mode 100644
index 4f74ebd55..000000000
--- a/webapp/components/suggestion/command_provider.jsx
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import Suggestion from './suggestion.jsx';
-
-import {getSuggestedCommands} from 'actions/integration_actions.jsx';
-
-class CommandSuggestion extends Suggestion {
- render() {
- const {item, isSelection} = this.props;
-
- let className = 'command';
- if (isSelection) {
- className += ' suggestion--selected';
- }
-
- return (
- <div
- className={className}
- onClick={this.handleClick}
- >
- <div className='command__title'>
- <string>{item.suggestion} {item.hint}</string>
- </div>
- <div className='command__desc'>
- {item.description}
- </div>
- </div>
- );
- }
-}
-
-export default class CommandProvider {
- handlePretextChanged(suggestionId, pretext) {
- if (pretext.startsWith('/')) {
- getSuggestedCommands(pretext.toLowerCase(), suggestionId, CommandSuggestion, pretext.toLowerCase());
- }
- }
-}
diff --git a/webapp/components/suggestion/emoticon_provider.jsx b/webapp/components/suggestion/emoticon_provider.jsx
deleted file mode 100644
index 1de35dc20..000000000
--- a/webapp/components/suggestion/emoticon_provider.jsx
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-import {default as EmojiStore, EmojiMap} from 'stores/emoji_store.jsx';
-import * as Emoticons from 'utils/emoticons.jsx';
-import SuggestionStore from 'stores/suggestion_store.jsx';
-
-import Suggestion from './suggestion.jsx';
-
-import store from 'stores/redux_store.jsx';
-import {getCustomEmojisByName} from 'mattermost-redux/selectors/entities/emojis';
-
-const MIN_EMOTICON_LENGTH = 2;
-
-class EmoticonSuggestion extends Suggestion {
- render() {
- const text = this.props.term;
- const emoji = this.props.item.emoji;
-
- let className = 'emoticon-suggestion';
- if (this.props.isSelection) {
- className += ' suggestion--selected';
- }
-
- return (
- <div
- className={className}
- onClick={this.handleClick}
- >
- <div className='pull-left'>
- <img
- alt={text}
- className='emoticon-suggestion__image'
- src={EmojiStore.getEmojiImageUrl(emoji)}
- title={text}
- />
- </div>
- <div className='pull-left'>
- {text}
- </div>
- </div>
- );
- }
-}
-
-export default class EmoticonProvider {
- handlePretextChanged(suggestionId, pretext) {
- let hasSuggestions = false;
-
- // look for the potential emoticons at the start of the text, after whitespace, and at the start of emoji reaction commands
- const captured = (/(^|\s|^\+|^-)(:([^:\s]*))$/g).exec(pretext);
- if (captured) {
- const prefix = captured[1];
- const text = captured[2];
- const partialName = captured[3];
-
- if (partialName.length < MIN_EMOTICON_LENGTH) {
- SuggestionStore.clearSuggestions(suggestionId);
- return false;
- }
-
- const matched = [];
-
- // check for text emoticons if this isn't for an emoji reaction
- if (prefix !== '-' && prefix !== '+') {
- for (const emoticon of Object.keys(Emoticons.emoticonPatterns)) {
- if (Emoticons.emoticonPatterns[emoticon].test(text)) {
- SuggestionStore.addSuggestion(suggestionId, text, EmojiStore.get(emoticon), EmoticonSuggestion, text);
-
- hasSuggestions = true;
- }
- }
- }
-
- const emojis = new EmojiMap(getCustomEmojisByName(store.getState()));
-
- // check for named emoji
- for (const [name, emoji] of emojis) {
- if (emoji.aliases) {
- // This is a system emoji so it may have multiple names
- for (const alias of emoji.aliases) {
- if (alias.indexOf(partialName) !== -1) {
- matched.push({name: alias, emoji});
- break;
- }
- }
- } else if (name.indexOf(partialName) !== -1) {
- // This is a custom emoji so it only has one name
- matched.push({name, emoji});
- }
- }
-
- // sort the emoticons so that emoticons starting with the entered text come first
- matched.sort((a, b) => {
- const aName = a.name;
- const bName = b.name;
-
- const aPrefix = aName.startsWith(partialName);
- const bPrefix = bName.startsWith(partialName);
-
- if (aPrefix === bPrefix) {
- return aName.localeCompare(bName);
- } else if (aPrefix) {
- return -1;
- }
-
- return 1;
- });
-
- const terms = matched.map((item) => ':' + item.name + ':');
-
- SuggestionStore.clearSuggestions(suggestionId);
- if (terms.length > 0) {
- SuggestionStore.addSuggestions(suggestionId, terms, matched, EmoticonSuggestion, text);
-
- hasSuggestions = true;
- }
- }
-
- if (hasSuggestions) {
- // force the selection to be cleared since the order of elements may have changed
- SuggestionStore.clearSelection(suggestionId);
-
- return true;
- }
-
- return false;
- }
-}
diff --git a/webapp/components/suggestion/provider.jsx b/webapp/components/suggestion/provider.jsx
deleted file mode 100644
index a5b54fb26..000000000
--- a/webapp/components/suggestion/provider.jsx
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import SuggestionStore from 'stores/suggestion_store.jsx';
-
-export default class Provider {
- constructor() {
- this.latestPrefix = '';
- this.latestComplete = true;
- this.disableDispatches = false;
- }
-
- handlePretextChanged(suggestionId, pretext) { // eslint-disable-line no-unused-vars
- // NO-OP for inherited classes to override
- }
-
- startNewRequest(suggestionId, prefix) {
- this.latestPrefix = prefix;
- this.latestComplete = false;
-
- // Don't use the dispatcher here since this is only called while handling an event
- SuggestionStore.setSuggestionsPending(suggestionId, true);
- }
-
- shouldCancelDispatch(prefix) {
- if (this.disableDispatches) {
- return true;
- }
-
- if (prefix === this.latestPrefix) {
- this.latestComplete = true;
- } else if (this.latestComplete) {
- return true;
- }
-
- return false;
- }
-}
diff --git a/webapp/components/suggestion/search_channel_provider.jsx b/webapp/components/suggestion/search_channel_provider.jsx
deleted file mode 100644
index 650ec6973..000000000
--- a/webapp/components/suggestion/search_channel_provider.jsx
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import Suggestion from './suggestion.jsx';
-import Provider from './provider.jsx';
-
-import {autocompleteChannels} from 'actions/channel_actions.jsx';
-
-import ChannelStore from 'stores/channel_store.jsx';
-
-import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
-import {Constants, ActionTypes} from 'utils/constants.jsx';
-import {sortChannelsByDisplayName} from 'utils/channel_utils.jsx';
-
-import React from 'react';
-
-class SearchChannelSuggestion extends Suggestion {
- render() {
- const {item, isSelection} = this.props;
-
- let className = 'search-autocomplete__item';
- if (isSelection) {
- className += ' selected';
- }
-
- return (
- <div
- onClick={this.handleClick}
- className={className}
- >
- <i className='fa fa fa-plus-square'/>{item.name}
- </div>
- );
- }
-}
-
-export default class SearchChannelProvider extends Provider {
- handlePretextChanged(suggestionId, pretext) {
- const captured = (/\b(?:in|channel):\s*(\S*)$/i).exec(pretext.toLowerCase());
- if (captured) {
- const channelPrefix = captured[1];
-
- this.startNewRequest(suggestionId, channelPrefix);
-
- autocompleteChannels(
- channelPrefix,
- (data) => {
- if (this.shouldCancelDispatch(channelPrefix)) {
- return;
- }
-
- const publicChannels = data;
-
- const localChannels = ChannelStore.getAll();
- let privateChannels = [];
-
- for (const id of Object.keys(localChannels)) {
- const channel = localChannels[id];
- if (channel.name.startsWith(channelPrefix) && channel.type === Constants.PRIVATE_CHANNEL) {
- privateChannels.push(channel);
- }
- }
-
- let filteredPublicChannels = [];
- publicChannels.forEach((item) => {
- if (item.name.startsWith(channelPrefix)) {
- filteredPublicChannels.push(item);
- }
- });
-
- privateChannels = privateChannels.sort(sortChannelsByDisplayName);
- filteredPublicChannels = filteredPublicChannels.sort(sortChannelsByDisplayName);
-
- const channels = filteredPublicChannels.concat(privateChannels);
- const channelNames = channels.map((channel) => channel.name);
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.SUGGESTION_RECEIVED_SUGGESTIONS,
- id: suggestionId,
- matchedPretext: channelPrefix,
- terms: channelNames,
- items: channels,
- component: SearchChannelSuggestion
- });
- }
- );
- }
-
- return Boolean(captured);
- }
-}
diff --git a/webapp/components/suggestion/search_suggestion_list.jsx b/webapp/components/suggestion/search_suggestion_list.jsx
deleted file mode 100644
index 944ddaeca..000000000
--- a/webapp/components/suggestion/search_suggestion_list.jsx
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import React from 'react';
-import ReactDOM from 'react-dom';
-import Constants from 'utils/constants.jsx';
-import SuggestionList from './suggestion_list.jsx';
-
-import {FormattedMessage} from 'react-intl';
-
-import {Popover} from 'react-bootstrap';
-
-export default class SearchSuggestionList extends SuggestionList {
- static propTypes = {
- ...SuggestionList.propTypes
- };
-
- getContent() {
- return $(ReactDOM.findDOMNode(this.refs.popover)).find('.popover-content');
- }
-
- renderChannelDivider(type) {
- let text;
- if (type === Constants.OPEN_CHANNEL) {
- text = (
- <FormattedMessage
- id='suggestion.search.public'
- defaultMessage='Public Channels'
- />
- );
- } else {
- text = (
- <FormattedMessage
- id='suggestion.search.private'
- defaultMessage='Private Channels'
- />
- );
- }
-
- return (
- <div
- key={type + '-divider'}
- className='search-autocomplete__divider'
- >
- <span>{text}</span>
- </div>
- );
- }
-
- render() {
- if (this.state.items.length === 0) {
- return null;
- }
-
- const items = [];
- for (let i = 0; i < this.state.items.length; i++) {
- const item = this.state.items[i];
- const term = this.state.terms[i];
- const isSelection = term === this.state.selection;
-
- // ReactComponent names need to be upper case when used in JSX
- const Component = this.state.components[i];
-
- // temporary hack to add dividers between public and private channels in the search suggestion list
- if (i === 0 || item.type !== this.state.items[i - 1].type) {
- if (item.type === Constants.OPEN_CHANNEL) {
- items.push(this.renderChannelDivider(Constants.OPEN_CHANNEL));
- } else if (item.type === Constants.PRIVATE_CHANNEL) {
- items.push(this.renderChannelDivider(Constants.PRIVATE_CHANNEL));
- }
- }
-
- items.push(
- <Component
- key={term}
- ref={term}
- item={item}
- term={term}
- matchedPretext={this.state.matchedPretext[i]}
- isSelection={isSelection}
- onClick={this.props.onCompleteWord}
- />
- );
- }
-
- return (
- <Popover
- ref='popover'
- id='search-autocomplete__popover'
- className='search-help-popover autocomplete visible'
- placement='bottom'
- >
- {items}
- </Popover>
- );
- }
-}
diff --git a/webapp/components/suggestion/search_user_provider.jsx b/webapp/components/suggestion/search_user_provider.jsx
deleted file mode 100644
index f3191c408..000000000
--- a/webapp/components/suggestion/search_user_provider.jsx
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import Suggestion from './suggestion.jsx';
-import Provider from './provider.jsx';
-
-import {autocompleteUsersInTeam} from 'actions/user_actions.jsx';
-
-import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
-import * as Utils from 'utils/utils.jsx';
-import {ActionTypes} from 'utils/constants.jsx';
-
-import React from 'react';
-
-class SearchUserSuggestion extends Suggestion {
- render() {
- const {item, isSelection} = this.props;
-
- let className = 'search-autocomplete__item';
- if (isSelection) {
- className += ' selected';
- }
-
- const username = item.username;
- let description = '';
-
- if ((item.first_name || item.last_name) && item.nickname) {
- description = `- ${Utils.getFullName(item)} (${item.nickname})`;
- } else if (item.nickname) {
- description = `- (${item.nickname})`;
- } else if (item.first_name || item.last_name) {
- description = `- ${Utils.getFullName(item)}`;
- }
-
- return (
- <div
- className={className}
- onClick={this.handleClick}
- >
- <i className='fa fa fa-plus-square'/>
- <img
- className='profile-img rounded'
- src={Utils.imageURLForUser(item)}
- />
- <div className='mention--align'>
- <span>
- {username}
- </span>
- <span className='mention__fullname'>
- {' '}
- {description}
- </span>
- </div>
- </div>
- );
- }
-}
-
-export default class SearchUserProvider extends Provider {
- handlePretextChanged(suggestionId, pretext) {
- const captured = (/\bfrom:\s*(\S*)$/i).exec(pretext.toLowerCase());
- if (captured) {
- const usernamePrefix = captured[1];
-
- this.startNewRequest(suggestionId, usernamePrefix);
-
- autocompleteUsersInTeam(
- usernamePrefix,
- (data) => {
- if (this.shouldCancelDispatch(usernamePrefix)) {
- return;
- }
-
- const users = Object.assign([], data.users);
- const mentions = users.map((user) => user.username);
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.SUGGESTION_RECEIVED_SUGGESTIONS,
- id: suggestionId,
- matchedPretext: usernamePrefix,
- terms: mentions,
- items: users,
- component: SearchUserSuggestion
- });
- }
- );
- }
-
- return Boolean(captured);
- }
-}
diff --git a/webapp/components/suggestion/suggestion.jsx b/webapp/components/suggestion/suggestion.jsx
deleted file mode 100644
index ddfdabc7d..000000000
--- a/webapp/components/suggestion/suggestion.jsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-export default class Suggestion extends React.Component {
- static get propTypes() {
- return {
- item: PropTypes.object.isRequired,
- term: PropTypes.string.isRequired,
- matchedPretext: PropTypes.string.isRequired,
- isSelection: PropTypes.bool,
- onClick: PropTypes.func
- };
- }
-
- constructor(props) {
- super(props);
-
- this.handleClick = this.handleClick.bind(this);
- }
-
- handleClick(e) {
- e.preventDefault();
-
- this.props.onClick(this.props.term, this.props.matchedPretext);
- }
-}
diff --git a/webapp/components/suggestion/suggestion_box.jsx b/webapp/components/suggestion/suggestion_box.jsx
deleted file mode 100644
index 8f50a23ef..000000000
--- a/webapp/components/suggestion/suggestion_box.jsx
+++ /dev/null
@@ -1,441 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import Constants from 'utils/constants.jsx';
-import * as GlobalActions from 'actions/global_actions.jsx';
-import SuggestionStore from 'stores/suggestion_store.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import AutosizeTextarea from 'components/autosize_textarea.jsx';
-
-const KeyCodes = Constants.KeyCodes;
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class SuggestionBox extends React.Component {
- static propTypes = {
-
- /**
- * The list component to render, usually SuggestionList
- */
- listComponent: PropTypes.func.isRequired,
-
- /**
- * The HTML input box type
- */
- type: PropTypes.oneOf(['input', 'textarea', 'search']).isRequired,
-
- /**
- * The value of in the input
- */
- value: PropTypes.string.isRequired,
-
- /**
- * Array of suggestion providers
- */
- providers: PropTypes.arrayOf(PropTypes.object),
-
- /**
- * Where the list will be displayed relative to the input box, defaults to 'top'
- */
- listStyle: PropTypes.string,
-
- /**
- * Set to true to draw dividers between types of list items, defaults to false
- */
- renderDividers: PropTypes.bool,
-
- /**
- * Set to allow TAB to select an item in the list, defaults to true
- */
- completeOnTab: PropTypes.bool,
-
- /**
- * Function called when input box gains focus
- */
- onFocus: PropTypes.func,
-
- /**
- * Function called when input box loses focus
- */
- onBlur: PropTypes.func,
-
- /**
- * Function called when input box value changes
- */
- onChange: PropTypes.func,
-
- /**
- * Function called when a key is pressed and the input box is in focus
- */
- onKeyDown: PropTypes.func,
-
- /**
- * Function called when an item is selected
- */
- onItemSelected: PropTypes.func,
-
- /**
- * Flags if the suggestion_box is for the RHS (Reply).
- */
- isRHS: PropTypes.bool,
-
- /**
- * Function called when @mention is clicked
- */
- popoverMentionKeyClick: PropTypes.bool,
-
- /**
- * The number of characters required to show the suggestion list, defaults to 1
- */
- requiredCharacters: PropTypes.number,
-
- /**
- * If true, the suggestion box is opened on focus, default to false
- */
- openOnFocus: PropTypes.bool
- }
-
- static defaultProps = {
- type: 'input',
- listStyle: 'top',
- renderDividers: false,
- completeOnTab: true,
- isRHS: false,
- requiredCharacters: 1,
- openOnFocus: false
- }
-
- constructor(props) {
- super(props);
-
- this.handleBlur = this.handleBlur.bind(this);
- this.handleFocus = this.handleFocus.bind(this);
-
- this.handlePopoverMentionKeyClick = this.handlePopoverMentionKeyClick.bind(this);
- this.handleCompleteWord = this.handleCompleteWord.bind(this);
- this.handleChange = this.handleChange.bind(this);
- this.handleCompositionStart = this.handleCompositionStart.bind(this);
- this.handleCompositionUpdate = this.handleCompositionUpdate.bind(this);
- this.handleCompositionEnd = this.handleCompositionEnd.bind(this);
- this.handleKeyDown = this.handleKeyDown.bind(this);
- this.handlePretextChanged = this.handlePretextChanged.bind(this);
- this.blur = this.blur.bind(this);
-
- this.suggestionId = Utils.generateId();
- SuggestionStore.registerSuggestionBox(this.suggestionId);
-
- // Keep track of whether we're composing a CJK character so we can make suggestions for partial characters
- this.composing = false;
- }
-
- componentDidMount() {
- if (this.props.popoverMentionKeyClick) {
- SuggestionStore.addPopoverMentionKeyClickListener(this.props.isRHS, this.handlePopoverMentionKeyClick);
- }
- SuggestionStore.addPretextChangedListener(this.suggestionId, this.handlePretextChanged);
- }
-
- componentWillUnmount() {
- if (this.props.popoverMentionKeyClick) {
- SuggestionStore.removePopoverMentionKeyClickListener(this.props.isRHS, this.handlePopoverMentionKeyClick);
- }
- SuggestionStore.removePretextChangedListener(this.suggestionId, this.handlePretextChanged);
-
- SuggestionStore.unregisterSuggestionBox(this.suggestionId);
- }
-
- componentDidUpdate(prevProps) {
- if (this.props.providers !== prevProps.providers) {
- const textbox = this.getTextbox();
- const pretext = textbox.value.substring(0, textbox.selectionEnd);
- GlobalActions.emitSuggestionPretextChanged(this.suggestionId, pretext);
- }
- }
-
- getTextbox() {
- if (this.props.type === 'textarea' && this.refs.textbox) {
- const node = this.refs.textbox.getDOMNode();
- return node;
- }
-
- return this.refs.textbox;
- }
-
- recalculateSize() {
- if (this.props.type === 'textarea') {
- this.refs.textbox.recalculateSize();
- }
- }
-
- handleBlur() {
- setTimeout(() => {
- // Delay this slightly so that we don't clear the suggestions before we run click handlers on SuggestionList
- GlobalActions.emitClearSuggestions(this.suggestionId);
- }, 200);
-
- if (this.props.onBlur) {
- this.props.onBlur();
- }
- }
-
- handleFocus() {
- if (this.props.openOnFocus) {
- setTimeout(() => {
- const textbox = this.getTextbox();
- if (textbox) {
- const pretext = textbox.value.substring(0, textbox.selectionEnd);
- if (pretext.length >= this.props.requiredCharacters) {
- GlobalActions.emitSuggestionPretextChanged(this.suggestionId, pretext);
- }
- }
- });
- }
-
- if (this.props.onFocus) {
- this.props.onFocus();
- }
- }
-
- handleChange(e) {
- const textbox = this.getTextbox();
- const pretext = textbox.value.substring(0, textbox.selectionEnd);
-
- if (!this.composing && SuggestionStore.getPretext(this.suggestionId) !== pretext && pretext.length >= this.props.requiredCharacters) {
- GlobalActions.emitSuggestionPretextChanged(this.suggestionId, pretext);
- }
-
- if (this.props.onChange) {
- this.props.onChange(e);
- }
- }
-
- handleCompositionStart() {
- this.composing = true;
- }
-
- handleCompositionUpdate(e) {
- if (!e.data) {
- return;
- }
-
- // The caret appears before the CJK character currently being composed, so re-add it to the pretext
- const textbox = this.getTextbox();
- const pretext = textbox.value.substring(0, textbox.selectionStart) + e.data;
-
- if (SuggestionStore.getPretext(this.suggestionId) !== pretext) {
- GlobalActions.emitSuggestionPretextChanged(this.suggestionId, pretext);
- }
- }
-
- handleCompositionEnd() {
- this.composing = false;
- }
-
- handlePopoverMentionKeyClick(mentionKey) {
- let insertText = '@' + mentionKey;
-
- // if the current text does not end with a whitespace, then insert a space
- if (this.refs.textbox.value && (/[^\s]$/).test(this.refs.textbox.value)) {
- insertText = ' ' + insertText;
- }
-
- this.handleCompleteWord(insertText, '', false);
- }
-
- handleCompleteWord(term, matchedPretext, shouldEmitWordSuggestion = true) {
- const textbox = this.getTextbox();
- const caret = textbox.selectionEnd;
- const text = this.props.value;
- const pretext = textbox.value.substring(0, textbox.selectionEnd);
-
- let prefix;
- if (pretext.endsWith(matchedPretext)) {
- prefix = pretext.substring(0, pretext.length - matchedPretext.length);
- } else {
- // the pretext has changed since we got a term to complete so see if the term still fits the pretext
- const termWithoutMatched = term.substring(matchedPretext.length);
- const overlap = SuggestionBox.findOverlap(pretext, termWithoutMatched);
-
- prefix = pretext.substring(0, pretext.length - overlap.length - matchedPretext.length);
- }
-
- const suffix = text.substring(caret);
-
- const newValue = prefix + term + ' ' + suffix;
- this.refs.textbox.value = newValue;
-
- if (this.props.onChange) {
- // fake an input event to send back to parent components
- const e = {
- target: this.refs.textbox
- };
-
- // don't call handleChange or we'll get into an event loop
- this.props.onChange(e);
- }
-
- if (this.props.onItemSelected) {
- const items = SuggestionStore.getItems(this.suggestionId);
- const terms = SuggestionStore.getTerms(this.suggestionId);
- for (let i = 0; i < terms.length; i++) {
- if (terms[i] === term) {
- this.props.onItemSelected(items[i]);
- break;
- }
- }
- }
-
- textbox.focus();
-
- // set the caret position after the next rendering
- window.requestAnimationFrame(() => {
- if (textbox.value === newValue) {
- Utils.setCaretPosition(textbox, prefix.length + term.length + 1);
- }
- });
-
- for (const provider of this.props.providers) {
- if (provider.handleCompleteWord) {
- provider.handleCompleteWord(term, matchedPretext);
- }
- }
- if (shouldEmitWordSuggestion) {
- GlobalActions.emitCompleteWordSuggestion(this.suggestionId);
- }
- }
-
- handleKeyDown(e) {
- if (this.props.value && SuggestionStore.hasSuggestions(this.suggestionId)) {
- if (e.which === KeyCodes.UP) {
- GlobalActions.emitSelectPreviousSuggestion(this.suggestionId);
- e.preventDefault();
- } else if (e.which === KeyCodes.DOWN) {
- GlobalActions.emitSelectNextSuggestion(this.suggestionId);
- e.preventDefault();
- } else if (e.which === KeyCodes.ENTER || (this.props.completeOnTab && e.which === KeyCodes.TAB)) {
- this.handleCompleteWord(SuggestionStore.getSelection(this.suggestionId), SuggestionStore.getSelectedMatchedPretext(this.suggestionId));
- if (this.props.onKeyDown) {
- this.props.onKeyDown(e);
- }
- e.preventDefault();
- } else if (e.which === KeyCodes.ESCAPE) {
- GlobalActions.emitClearSuggestions(this.suggestionId);
- e.preventDefault();
- e.stopPropagation();
- } else if (this.props.onKeyDown) {
- this.props.onKeyDown(e);
- }
- } else if (this.props.onKeyDown) {
- this.props.onKeyDown(e);
- }
- }
-
- handlePretextChanged(pretext) {
- let handled = false;
- for (const provider of this.props.providers) {
- handled = provider.handlePretextChanged(this.suggestionId, pretext) || handled;
- }
-
- if (!handled) {
- SuggestionStore.clearSuggestions(this.suggestionId);
- }
- }
-
- blur() {
- this.refs.textbox.blur();
- }
-
- render() {
- const {
- type,
- listComponent,
- listStyle,
- renderDividers,
- ...props
- } = this.props;
-
- // Don't pass props used by SuggestionBox
- Reflect.deleteProperty(props, 'providers');
- Reflect.deleteProperty(props, 'onChange'); // We use onInput instead of onChange on the actual input
- Reflect.deleteProperty(props, 'onItemSelected');
- Reflect.deleteProperty(props, 'completeOnTab');
- Reflect.deleteProperty(props, 'isRHS');
- Reflect.deleteProperty(props, 'popoverMentionKeyClick');
- Reflect.deleteProperty(props, 'requiredCharacters');
- Reflect.deleteProperty(props, 'openOnFocus');
-
- const childProps = {
- ref: 'textbox',
- onBlur: this.handleBlur,
- onFocus: this.handleFocus,
- onInput: this.handleChange,
- onChange() { /* this is only here to suppress warnings about onChange not being implemented for read-write inputs */ },
- onCompositionStart: this.handleCompositionStart,
- onCompositionUpdate: this.handleCompositionUpdate,
- onCompositionEnd: this.handleCompositionEnd,
- onKeyDown: this.handleKeyDown
- };
-
- let textbox = null;
- if (type === 'input') {
- textbox = (
- <input
- type='text'
- autoComplete='off'
- {...props}
- {...childProps}
- />
- );
- } else if (type === 'search') {
- textbox = (
- <input
- type='search'
- autoComplete='off'
- {...props}
- {...childProps}
- />
- );
- } else if (type === 'textarea') {
- textbox = (
- <AutosizeTextarea
- {...props}
- {...childProps}
- />
- );
- }
-
- // This needs to be upper case so React doesn't think it's an html tag
- const SuggestionListComponent = listComponent;
-
- return (
- <div ref='container'>
- {textbox}
- {this.props.value.length >= this.props.requiredCharacters &&
- <SuggestionListComponent
- suggestionId={this.suggestionId}
- location={listStyle}
- renderDividers={renderDividers}
- onCompleteWord={this.handleCompleteWord}
- />
- }
- </div>
- );
- }
-
- // Finds the longest substring that's at both the end of b and the start of a. For example,
- // if a = "firepit" and b = "pitbull", findOverlap would return "pit".
- static findOverlap(a, b) {
- for (let i = b.length; i > 0; i--) {
- const substring = b.substring(0, i);
-
- if (a.endsWith(substring)) {
- return substring;
- }
- }
-
- return '';
- }
-}
diff --git a/webapp/components/suggestion/suggestion_list.jsx b/webapp/components/suggestion/suggestion_list.jsx
deleted file mode 100644
index 64e8713c5..000000000
--- a/webapp/components/suggestion/suggestion_list.jsx
+++ /dev/null
@@ -1,177 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import SuggestionStore from 'stores/suggestion_store.jsx';
-
-import $ from 'jquery';
-import React from 'react';
-import ReactDOM from 'react-dom';
-import PropTypes from 'prop-types';
-import {FormattedMessage} from 'react-intl';
-
-export default class SuggestionList extends React.Component {
- static propTypes = {
- suggestionId: PropTypes.string.isRequired,
- location: PropTypes.string,
- renderDividers: PropTypes.bool,
- onCompleteWord: PropTypes.func.isRequired
- };
-
- static defaultProps = {
- renderDividers: false
- };
-
- constructor(props) {
- super(props);
-
- this.getStateFromStores = this.getStateFromStores.bind(this);
-
- this.getContent = this.getContent.bind(this);
-
- this.handleSuggestionsChanged = this.handleSuggestionsChanged.bind(this);
-
- this.scrollToItem = this.scrollToItem.bind(this);
-
- this.state = this.getStateFromStores(props.suggestionId);
- }
-
- getStateFromStores(suggestionId) {
- const suggestions = SuggestionStore.getSuggestions(suggestionId || this.props.suggestionId);
-
- return {
- matchedPretext: suggestions.matchedPretext,
- items: suggestions.items,
- terms: suggestions.terms,
- components: suggestions.components,
- selection: suggestions.selection
- };
- }
-
- componentDidMount() {
- SuggestionStore.addSuggestionsChangedListener(this.props.suggestionId, this.handleSuggestionsChanged);
- }
-
- componentDidUpdate(prevProps, prevState) {
- if (this.state.selection !== prevState.selection && this.state.selection) {
- this.scrollToItem(this.state.selection);
- }
- }
-
- componentWillUnmount() {
- SuggestionStore.removeSuggestionsChangedListener(this.props.suggestionId, this.handleSuggestionsChanged);
- }
-
- getContent() {
- return $(ReactDOM.findDOMNode(this.refs.content));
- }
-
- handleSuggestionsChanged() {
- this.setState(this.getStateFromStores());
- }
-
- scrollToItem(term) {
- const content = this.getContent();
- if (!content || content.length === 0) {
- return;
- }
-
- const visibleContentHeight = content[0].clientHeight;
- const actualContentHeight = content[0].scrollHeight;
-
- if (visibleContentHeight < actualContentHeight) {
- const contentTop = content.scrollTop();
- const contentTopPadding = parseInt(content.css('padding-top'), 10);
- const contentBottomPadding = parseInt(content.css('padding-top'), 10);
-
- const item = $(ReactDOM.findDOMNode(this.refs[term]));
- const itemTop = item[0].offsetTop - parseInt(item.css('margin-top'), 10);
- const itemBottomMargin = parseInt(item.css('margin-bottom'), 10) + parseInt(item.css('padding-bottom'), 10);
- const itemBottom = item[0].offsetTop + item.height() + itemBottomMargin;
-
- if (itemTop - contentTopPadding < contentTop) {
- // the item is off the top of the visible space
- content.scrollTop(itemTop - contentTopPadding);
- } else if (itemBottom + contentTopPadding + contentBottomPadding > contentTop + visibleContentHeight) {
- // the item has gone off the bottom of the visible space
- content.scrollTop((itemBottom - visibleContentHeight) + contentTopPadding + contentBottomPadding);
- }
- }
- }
-
- renderDivider(type) {
- return (
- <div
- key={type + '-divider'}
- className='suggestion-list__divider'
- >
- <span>
- <FormattedMessage id={'suggestion.' + type}/>
- </span>
- </div>
- );
- }
-
- renderLoading(type) {
- return (
- <div
- key={type + '-loading'}
- className='suggestion-loader'
- >
- <i className='fa fa-spinner fa-pulse fa-fw margin-bottom'/>
- </div>
- );
- }
-
- render() {
- if (this.state.items.length === 0) {
- return null;
- }
-
- const items = [];
- let lastType;
- for (let i = 0; i < this.state.items.length; i++) {
- const item = this.state.items[i];
- const term = this.state.terms[i];
- const isSelection = term === this.state.selection;
-
- // ReactComponent names need to be upper case when used in JSX
- const Component = this.state.components[i];
-
- if (this.props.renderDividers && item.type !== lastType) {
- items.push(this.renderDivider(item.type));
- lastType = item.type;
- }
-
- if (item.loading) {
- items.push(this.renderLoading(item.type));
- continue;
- }
-
- items.push(
- <Component
- key={term}
- ref={term}
- item={this.state.items[i]}
- term={term}
- matchedPretext={this.state.matchedPretext[i]}
- isSelection={isSelection}
- onClick={this.props.onCompleteWord}
- />
- );
- }
-
- const mainClass = 'suggestion-list suggestion-list--' + this.props.location;
- const contentClass = 'suggestion-list__content suggestion-list__content--' + this.props.location;
-
- return (
- <div className={mainClass}>
- <div
- ref='content'
- className={contentClass}
- >
- {items}
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/suggestion/switch_channel_provider.jsx b/webapp/components/suggestion/switch_channel_provider.jsx
deleted file mode 100644
index ac4af9c8b..000000000
--- a/webapp/components/suggestion/switch_channel_provider.jsx
+++ /dev/null
@@ -1,283 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import Suggestion from './suggestion.jsx';
-import Provider from './provider.jsx';
-
-import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
-import {Constants, ActionTypes} from 'utils/constants.jsx';
-import * as Utils from 'utils/utils.jsx';
-import {sortChannelsByDisplayName, getChannelDisplayName} from 'utils/channel_utils.jsx';
-
-import React from 'react';
-
-import store from 'stores/redux_store.jsx';
-const getState = store.getState;
-
-import {Client4} from 'mattermost-redux/client';
-
-import {getCurrentUserId, searchProfiles} from 'mattermost-redux/selectors/entities/users';
-import {getChannelsInCurrentTeam, getMyChannelMemberships, getGroupChannels} from 'mattermost-redux/selectors/entities/channels';
-import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
-import {getBool} from 'mattermost-redux/selectors/entities/preferences';
-import {Preferences} from 'mattermost-redux/constants';
-
-class SwitchChannelSuggestion extends Suggestion {
- render() {
- const {item, isSelection} = this.props;
- const channel = item.channel;
- const globeIcon = Constants.GLOBE_ICON_SVG;
- const lockIcon = Constants.LOCK_ICON_SVG;
-
- let className = 'mentions__name';
- if (isSelection) {
- className += ' suggestion--selected';
- }
-
- let displayName = channel.display_name;
- let icon = null;
- if (channel.type === Constants.OPEN_CHANNEL) {
- icon = (
- <span
- className='icon icon__globe icon--body'
- dangerouslySetInnerHTML={{__html: globeIcon}}
- />
- );
- } else if (channel.type === Constants.PRIVATE_CHANNEL) {
- icon = (
- <span
- className='icon icon__lock icon--body'
- dangerouslySetInnerHTML={{__html: lockIcon}}
- />
- );
- } else if (channel.type === Constants.GM_CHANNEL) {
- displayName = getChannelDisplayName(channel);
- icon = <div className='status status--group'>{'G'}</div>;
- } else {
- icon = (
- <div className='pull-left'>
- <img
- className='mention__image'
- src={Utils.imageURLForUser(channel)}
- />
- </div>
- );
- }
-
- return (
- <div
- onClick={this.handleClick}
- className={className}
- >
- {icon}
- {displayName}
- </div>
- );
- }
-}
-
-let prefix = '';
-
-function quickSwitchSorter(wrappedA, wrappedB) {
- if (wrappedA.type === Constants.MENTION_CHANNELS && wrappedB.type === Constants.MENTION_MORE_CHANNELS) {
- return -1;
- } else if (wrappedB.type === Constants.MENTION_CHANNELS && wrappedA.type === Constants.MENTION_MORE_CHANNELS) {
- return 1;
- }
-
- const a = wrappedA.channel;
- const b = wrappedB.channel;
-
- let aDisplayName = getChannelDisplayName(a).toLowerCase();
- let bDisplayName = getChannelDisplayName(b).toLowerCase();
-
- if (a.type === Constants.DM_CHANNEL) {
- aDisplayName = aDisplayName.substring(1);
- }
-
- if (b.type === Constants.DM_CHANNEL) {
- bDisplayName = bDisplayName.substring(1);
- }
-
- const aStartsWith = aDisplayName.startsWith(prefix);
- const bStartsWith = bDisplayName.startsWith(prefix);
- if (aStartsWith && bStartsWith) {
- return sortChannelsByDisplayName(a, b);
- } else if (!aStartsWith && !bStartsWith) {
- return sortChannelsByDisplayName(a, b);
- } else if (aStartsWith) {
- return -1;
- }
-
- return 1;
-}
-
-export default class SwitchChannelProvider extends Provider {
- handlePretextChanged(suggestionId, channelPrefix) {
- if (channelPrefix) {
- prefix = channelPrefix;
- this.startNewRequest(suggestionId, channelPrefix);
-
- // Dispatch suggestions for local data
- const channels = getChannelsInCurrentTeam(getState()).concat(getGroupChannels(getState()));
- const users = Object.assign([], searchProfiles(getState(), channelPrefix, true));
- this.formatChannelsAndDispatch(channelPrefix, suggestionId, channels, users, true);
-
- // Fetch data from the server and dispatch
- this.fetchUsersAndChannels(channelPrefix, suggestionId);
-
- return true;
- }
-
- return false;
- }
-
- async fetchUsersAndChannels(channelPrefix, suggestionId) {
- let teamId = '';
- if (global.window.mm_config.RestrictDirectMessage === 'team') {
- teamId = store.getState().entities.teams.currentTeamId;
- }
- const usersAsync = Client4.autocompleteUsers(channelPrefix, teamId, '');
- const channelsAsync = Client4.searchChannels(getCurrentTeamId(getState()), channelPrefix);
-
- let usersFromServer = [];
- let channelsFromServer = [];
- try {
- usersFromServer = await usersAsync;
- channelsFromServer = await channelsAsync;
- } catch (err) {
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_ERROR,
- err
- });
- }
-
- if (this.shouldCancelDispatch(channelPrefix)) {
- return;
- }
-
- const users = Object.assign([], searchProfiles(getState(), channelPrefix, true), usersFromServer.users);
- const channels = getChannelsInCurrentTeam(getState()).concat(getGroupChannels(getState())).concat(channelsFromServer);
- this.formatChannelsAndDispatch(channelPrefix, suggestionId, channels, users);
- }
-
- formatChannelsAndDispatch(channelPrefix, suggestionId, allChannels, users, skipNotInChannel = false) {
- const channels = [];
- const members = getMyChannelMemberships(getState());
-
- if (this.shouldCancelDispatch(channelPrefix)) {
- return;
- }
-
- const currentId = getCurrentUserId(getState());
-
- const completedChannels = {};
-
- for (const id of Object.keys(allChannels)) {
- const channel = allChannels[id];
-
- if (completedChannels[channel.id]) {
- continue;
- }
-
- const member = members[channel.id];
-
- if (channel.display_name.toLowerCase().indexOf(channelPrefix.toLowerCase()) !== -1) {
- const newChannel = Object.assign({}, channel);
- const wrappedChannel = {channel: newChannel, name: newChannel.name};
- if (newChannel.type === Constants.GM_CHANNEL) {
- newChannel.name = getChannelDisplayName(newChannel);
- wrappedChannel.name = newChannel.name;
- const isGMVisible = getBool(getState(), Preferences.CATEGORY_GROUP_CHANNEL_SHOW, newChannel.id, false);
- if (isGMVisible) {
- wrappedChannel.type = Constants.MENTION_CHANNELS;
- } else {
- wrappedChannel.type = Constants.MENTION_MORE_CHANNELS;
- if (skipNotInChannel) {
- continue;
- }
- }
- } else if (member) {
- wrappedChannel.type = Constants.MENTION_CHANNELS;
- } else {
- wrappedChannel.type = Constants.MENTION_MORE_CHANNELS;
- if (skipNotInChannel || !newChannel.display_name.toLowerCase().startsWith(channelPrefix)) {
- continue;
- }
- }
-
- completedChannels[channel.id] = true;
- channels.push(wrappedChannel);
- }
- }
-
- for (let i = 0; i < users.length; i++) {
- const user = users[i];
-
- if (completedChannels[user.id]) {
- continue;
- }
-
- const isDMVisible = getBool(getState(), Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, user.id, false);
- let displayName = `@${user.username} `;
-
- if (user.id === currentId) {
- continue;
- }
-
- if ((user.first_name || user.last_name) && user.nickname) {
- displayName += `- ${Utils.getFullName(user)} (${user.nickname})`;
- } else if (user.nickname) {
- displayName += `- (${user.nickname})`;
- } else if (user.first_name || user.last_name) {
- displayName += `- ${Utils.getFullName(user)}`;
- }
-
- const wrappedChannel = {
- channel: {
- display_name: displayName,
- name: user.username,
- id: user.id,
- update_at: user.update_at,
- type: Constants.DM_CHANNEL,
- last_picture_update: user.last_picture_update || 0
- },
- name: user.username
- };
-
- if (isDMVisible) {
- wrappedChannel.type = Constants.MENTION_CHANNELS;
- } else {
- wrappedChannel.type = Constants.MENTION_MORE_CHANNELS;
- if (skipNotInChannel) {
- continue;
- }
- }
-
- completedChannels[user.id] = true;
- channels.push(wrappedChannel);
- }
-
- const channelNames = channels.
- sort(quickSwitchSorter).
- map((wrappedChannel) => wrappedChannel.channel.name);
-
- if (skipNotInChannel) {
- channels.push({
- type: Constants.MENTION_MORE_CHANNELS,
- loading: true
- });
- }
-
- setTimeout(() => {
- AppDispatcher.handleServerAction({
- type: ActionTypes.SUGGESTION_RECEIVED_SUGGESTIONS,
- id: suggestionId,
- matchedPretext: channelPrefix,
- terms: channelNames,
- items: channels,
- component: SwitchChannelSuggestion
- });
- }, 0);
- }
-}
diff --git a/webapp/components/suggestion/switch_team_provider.jsx b/webapp/components/suggestion/switch_team_provider.jsx
deleted file mode 100644
index ff2a8f24b..000000000
--- a/webapp/components/suggestion/switch_team_provider.jsx
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import Suggestion from './suggestion.jsx';
-import Provider from './provider.jsx';
-
-import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
-import {ActionTypes} from 'utils/constants.jsx';
-import LocalizationStore from 'stores/localization_store.jsx';
-
-import React from 'react';
-
-// Redux actions
-import store from 'stores/redux_store.jsx';
-const getState = store.getState;
-
-import * as Selectors from 'mattermost-redux/selectors/entities/teams';
-
-class SwitchTeamSuggestion extends Suggestion {
- render() {
- const {item, isSelection} = this.props;
-
- let className = 'mentions__name';
- if (isSelection) {
- className += ' suggestion--selected';
- }
-
- return (
- <div
- onClick={this.handleClick}
- className={className}
- >
- <div className='status'><i className='fa fa-group'/></div>
- {item.display_name}
- </div>
- );
- }
-}
-
-let prefix = '';
-
-function quickSwitchSorter(a, b) {
- const aDisplayName = a.display_name.toLowerCase();
- const bDisplayName = b.display_name.toLowerCase();
- const aStartsWith = aDisplayName.startsWith(prefix);
- const bStartsWith = bDisplayName.startsWith(prefix);
-
- if (aStartsWith && bStartsWith) {
- const locale = LocalizationStore.getLocale();
-
- if (aDisplayName !== bDisplayName) {
- return aDisplayName.localeCompare(bDisplayName, locale, {numeric: true});
- }
-
- return a.name.localeCompare(b.name, locale, {numeric: true});
- } else if (aStartsWith) {
- return -1;
- }
-
- return 1;
-}
-
-export default class SwitchTeamProvider extends Provider {
- handlePretextChanged(suggestionId, teamPrefix) {
- if (teamPrefix) {
- prefix = teamPrefix;
- this.startNewRequest(suggestionId, teamPrefix);
-
- const allTeams = Selectors.getMyTeams(getState());
-
- const teams = allTeams.filter((team) => {
- return team.display_name.toLowerCase().indexOf(teamPrefix) !== -1 ||
- team.name.indexOf(teamPrefix) !== -1;
- });
-
- const teamNames = teams.
- sort(quickSwitchSorter).
- map((team) => team.name);
-
- setTimeout(() => {
- AppDispatcher.handleServerAction({
- type: ActionTypes.SUGGESTION_RECEIVED_SUGGESTIONS,
- id: suggestionId,
- matchedPretext: teamPrefix,
- terms: teamNames,
- items: teams,
- component: SwitchTeamSuggestion
- });
- }, 0);
-
- return true;
- }
-
- return false;
- }
-}
diff --git a/webapp/components/team_general_tab.jsx b/webapp/components/team_general_tab.jsx
deleted file mode 100644
index 4886ba4fa..000000000
--- a/webapp/components/team_general_tab.jsx
+++ /dev/null
@@ -1,602 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import SettingItemMin from './setting_item_min.jsx';
-import SettingItemMax from './setting_item_max.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-
-import {FormattedMessage} from 'react-intl';
-import {updateTeam} from 'actions/team_actions.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-class GeneralTab extends React.Component {
- constructor(props) {
- super(props);
-
- this.updateSection = this.updateSection.bind(this);
- this.handleNameSubmit = this.handleNameSubmit.bind(this);
- this.handleInviteIdSubmit = this.handleInviteIdSubmit.bind(this);
- this.handleOpenInviteSubmit = this.handleOpenInviteSubmit.bind(this);
- this.handleDescriptionSubmit = this.handleDescriptionSubmit.bind(this);
- this.handleClose = this.handleClose.bind(this);
- this.onUpdateNameSection = this.onUpdateNameSection.bind(this);
- this.updateName = this.updateName.bind(this);
- this.updateDescription = this.updateDescription.bind(this);
- this.onUpdateDescriptionSection = this.onUpdateDescriptionSection.bind(this);
- this.onUpdateInviteIdSection = this.onUpdateInviteIdSection.bind(this);
- this.updateInviteId = this.updateInviteId.bind(this);
- this.onUpdateOpenInviteSection = this.onUpdateOpenInviteSection.bind(this);
- this.handleOpenInviteRadio = this.handleOpenInviteRadio.bind(this);
- this.handleGenerateInviteId = this.handleGenerateInviteId.bind(this);
-
- this.state = this.setupInitialState(props);
- }
-
- updateSection(section) {
- if ($('.section-max').length) {
- $('.settings-modal .modal-body').scrollTop(0).perfectScrollbar('update');
- }
- this.setState(this.setupInitialState(this.props));
- this.props.updateSection(section);
- }
-
- setupInitialState(props) {
- const team = props.team;
-
- return {
- name: team.display_name,
- invite_id: team.invite_id,
- allow_open_invite: team.allow_open_invite,
- description: team.description,
- serverError: '',
- clientError: ''
- };
- }
-
- componentWillReceiveProps(nextProps) {
- this.setState({
- name: nextProps.team.display_name,
- description: nextProps.team.description,
- invite_id: nextProps.team.invite_id,
- allow_open_invite: nextProps.team.allow_open_invite
- });
- }
-
- handleGenerateInviteId(e) {
- e.preventDefault();
-
- var newId = '';
- for (var i = 0; i < 32; i++) {
- newId += Math.floor(Math.random() * 16).toString(16);
- }
-
- this.setState({invite_id: newId});
- }
-
- handleOpenInviteRadio(openInvite) {
- this.setState({allow_open_invite: openInvite});
- }
-
- handleOpenInviteSubmit(e) {
- e.preventDefault();
-
- var state = {serverError: '', clientError: ''};
-
- var data = {...this.props.team};
- data.allow_open_invite = this.state.allow_open_invite;
- updateTeam(data,
- () => {
- this.updateSection('');
- },
- (err) => {
- state.serverError = err.message;
- this.setState(state);
- }
- );
- }
-
- handleNameSubmit(e) {
- e.preventDefault();
-
- var state = {serverError: '', clientError: ''};
- let valid = true;
-
- const name = this.state.name.trim();
- if (name) {
- state.clientError = '';
- } else {
- state.clientError = Utils.localizeMessage('general_tab.required', 'This field is required');
- valid = false;
- }
-
- this.setState(state);
-
- if (!valid) {
- return;
- }
-
- var data = {...this.props.team};
- data.display_name = this.state.name;
- updateTeam(data,
- () => {
- this.updateSection('');
- },
- (err) => {
- state.serverError = err.message;
- this.setState(state);
- }
- );
- }
-
- handleInviteIdSubmit(e) {
- e.preventDefault();
-
- var state = {serverError: '', clientError: ''};
- let valid = true;
-
- const inviteId = this.state.invite_id.trim();
- if (inviteId) {
- state.clientError = '';
- } else {
- state.clientError = Utils.localizeMessage('general_tab.required', 'This field is required');
- valid = false;
- }
-
- this.setState(state);
-
- if (!valid) {
- return;
- }
-
- var data = {...this.props.team};
- data.invite_id = this.state.invite_id;
- updateTeam(data,
- () => {
- this.updateSection('');
- },
- (err) => {
- state.serverError = err.message;
- this.setState(state);
- }
- );
- }
-
- handleClose() {
- this.updateSection('');
- }
-
- handleDescriptionSubmit(e) {
- e.preventDefault();
-
- var state = {serverError: '', clientError: ''};
- let valid = true;
-
- const description = this.state.description.trim();
- if (description === this.props.team.description) {
- state.clientError = Utils.localizeMessage('general_tab.chooseDescription', 'Please choose a new description for your team');
- valid = false;
- } else {
- state.clientError = '';
- }
-
- this.setState(state);
-
- if (!valid) {
- return;
- }
-
- var data = {...this.props.team};
- data.description = this.state.description;
- updateTeam(data,
- () => {
- this.updateSection('');
- },
- (err) => {
- state.serverError = err.message;
- this.setState(state);
- }
- );
- }
-
- componentDidMount() {
- $('#team_settings').on('hidden.bs.modal', this.handleClose);
- }
-
- componentWillUnmount() {
- $('#team_settings').off('hidden.bs.modal', this.handleClose);
- }
-
- onUpdateNameSection(e) {
- e.preventDefault();
- if (this.props.activeSection === 'name') {
- this.updateSection('');
- } else {
- this.updateSection('name');
- }
- }
-
- onUpdateDescriptionSection(e) {
- e.preventDefault();
- if (this.props.activeSection === 'description') {
- this.updateSection('');
- } else {
- this.updateSection('description');
- }
- }
-
- onUpdateInviteIdSection(e) {
- e.preventDefault();
- if (this.props.activeSection === 'invite_id') {
- this.updateSection('');
- } else {
- this.updateSection('invite_id');
- }
- }
-
- onUpdateOpenInviteSection(e) {
- e.preventDefault();
- if (this.props.activeSection === 'open_invite') {
- this.updateSection('');
- } else {
- this.updateSection('open_invite');
- }
- }
-
- updateName(e) {
- this.setState({name: e.target.value});
- }
-
- updateDescription(e) {
- this.setState({description: e.target.value});
- }
-
- updateInviteId(e) {
- this.setState({invite_id: e.target.value});
- }
-
- render() {
- let clientError = null;
- let serverError = null;
- if (this.state.clientError) {
- clientError = this.state.clientError;
- }
- if (this.state.serverError) {
- serverError = this.state.serverError;
- }
-
- let openInviteSection;
- if (this.props.activeSection === 'open_invite') {
- const inputs = [
- <div key='userOpenInviteOptions'>
- <div className='radio'>
- <label>
- <input
- id='teamOpenInvite'
- name='userOpenInviteOptions'
- type='radio'
- defaultChecked={this.state.allow_open_invite}
- onChange={this.handleOpenInviteRadio.bind(this, true)}
- />
- <FormattedMessage
- id='general_tab.yes'
- defaultMessage='Yes'
- />
- </label>
- <br/>
- </div>
- <div className='radio'>
- <label>
- <input
- id='teamOpenInviteNo'
- name='userOpenInviteOptions'
- type='radio'
- defaultChecked={!this.state.allow_open_invite}
- onChange={this.handleOpenInviteRadio.bind(this, false)}
- />
- <FormattedMessage
- id='general_tab.no'
- defaultMessage='No'
- />
- </label>
- <br/>
- </div>
- <div>
- <br/>
- <FormattedMessage
- id='general_tab.openInviteDesc'
- defaultMessage='When allowed, a link to this team will be included on the landing page allowing anyone with an account to join this team.'
- />
- </div>
- </div>
- ];
-
- openInviteSection = (
- <SettingItemMax
- title={Utils.localizeMessage('general_tab.openInviteTitle', 'Allow any user with an account on this server to join this team')}
- inputs={inputs}
- submit={this.handleOpenInviteSubmit}
- server_error={serverError}
- updateSection={this.onUpdateOpenInviteSection}
- />
- );
- } else {
- let describe = '';
- if (this.state.allow_open_invite === true) {
- describe = Utils.localizeMessage('general_tab.yes', 'Yes');
- } else {
- describe = Utils.localizeMessage('general_tab.no', 'No');
- }
-
- openInviteSection = (
- <SettingItemMin
- title={Utils.localizeMessage('general_tab.openInviteTitle', 'Allow any user with an account on this server to join this team')}
- describe={describe}
- updateSection={this.onUpdateOpenInviteSection}
- />
- );
- }
-
- let inviteSection;
-
- if (this.props.activeSection === 'invite_id') {
- const inputs = [];
-
- inputs.push(
- <div key='teamInviteSetting'>
- <div className='row'>
- <label className='col-sm-5 control-label visible-xs-block'/>
- <div className='col-sm-12'>
- <input
- id='teamInviteId'
- className='form-control'
- type='text'
- onChange={this.updateInviteId}
- value={this.state.invite_id}
- maxLength='32'
- />
- <div className='padding-top x2'>
- <a
- id='teamInviteIdRegenerate'
- href='#'
- onClick={this.handleGenerateInviteId}
- >
- <FormattedMessage
- id='general_tab.regenerate'
- defaultMessage='Regenerate'
- />
- </a>
- </div>
- </div>
- </div>
- <div className='setting-list__hint'>
- <FormattedMessage
- id='general_tab.codeLongDesc'
- defaultMessage='The Invite Code is used as part of the URL in the team invitation link created by {getTeamInviteLink} in the main menu. Regenerating creates a new team invitation link and invalidates the previous link.'
- values={{
- getTeamInviteLink: (
- <strong>
- <FormattedMessage
- id='general_tab.getTeamInviteLink'
- defaultMessage='Get Team Invite Link'
- />
- </strong>
- )
- }}
- />
- </div>
- </div>
- );
-
- inviteSection = (
- <SettingItemMax
- title={Utils.localizeMessage('general_tab.codeTitle', 'Invite Code')}
- inputs={inputs}
- submit={this.handleInviteIdSubmit}
- server_error={serverError}
- client_error={clientError}
- updateSection={this.onUpdateInviteIdSection}
- />
- );
- } else {
- inviteSection = (
- <SettingItemMin
- title={Utils.localizeMessage('general_tab.codeTitle', 'Invite Code')}
- describe={Utils.localizeMessage('general_tab.codeDesc', "Click 'Edit' to regenerate Invite Code.")}
- updateSection={this.onUpdateInviteIdSection}
- />
- );
- }
-
- let nameSection;
-
- if (this.props.activeSection === 'name') {
- const inputs = [];
-
- let teamNameLabel = (
- <FormattedMessage
- id='general_tab.teamName'
- defaultMessage='Team Name'
- />
- );
- if (Utils.isMobile()) {
- teamNameLabel = '';
- }
-
- inputs.push(
- <div
- key='teamNameSetting'
- className='form-group'
- >
- <label className='col-sm-5 control-label'>{teamNameLabel}</label>
- <div className='col-sm-7'>
- <input
- id='teamName'
- className='form-control'
- type='text'
- maxLength={Constants.MAX_TEAMNAME_LENGTH.toString()}
- onChange={this.updateName}
- value={this.state.name}
- />
- </div>
- </div>
- );
-
- const nameExtraInfo = <span>{Utils.localizeMessage('general_tab.teamNameInfo', 'Set the name of the team as it appears on your sign-in screen and at the top of the left-hand sidebar.')}</span>;
-
- nameSection = (
- <SettingItemMax
- title={Utils.localizeMessage('general_tab.teamName', 'Team Name')}
- inputs={inputs}
- submit={this.handleNameSubmit}
- server_error={serverError}
- client_error={clientError}
- updateSection={this.onUpdateNameSection}
- extraInfo={nameExtraInfo}
- />
- );
- } else {
- var describe = this.state.name;
-
- nameSection = (
- <SettingItemMin
- title={Utils.localizeMessage('general_tab.teamName', 'Team Name')}
- describe={describe}
- updateSection={this.onUpdateNameSection}
- />
- );
- }
-
- let descriptionSection;
-
- if (this.props.activeSection === 'description') {
- const inputs = [];
-
- let teamDescriptionLabel = (
- <FormattedMessage
- id='general_tab.teamDescription'
- defaultMessage='Team Description'
- />
- );
- if (Utils.isMobile()) {
- teamDescriptionLabel = '';
- }
-
- inputs.push(
- <div
- key='teamDescriptionSetting'
- className='form-group'
- >
- <label className='col-sm-5 control-label'>{teamDescriptionLabel}</label>
- <div className='col-sm-7'>
- <input
- id='teamDescription'
- className='form-control'
- type='text'
- maxLength={Constants.MAX_TEAMDESCRIPTION_LENGTH.toString()}
- onChange={this.updateDescription}
- value={this.state.description}
- />
- </div>
- </div>
- );
-
- const descriptionExtraInfo = <span>{Utils.localizeMessage('general_tab.teamDescriptionInfo', 'Team description provides additional information to help users select the right team. Maximum of 50 characters.')}</span>;
-
- descriptionSection = (
- <SettingItemMax
- title={Utils.localizeMessage('general_tab.teamDescription', 'Team Description')}
- inputs={inputs}
- submit={this.handleDescriptionSubmit}
- server_error={serverError}
- client_error={clientError}
- updateSection={this.onUpdateDescriptionSection}
- extraInfo={descriptionExtraInfo}
- />
- );
- } else {
- let describemsg = '';
- if (this.state.description) {
- describemsg = this.state.description;
- } else {
- describemsg = (
- <FormattedMessage
- id='general_tab.emptyDescription'
- defaultMessage="Click 'Edit' to add a team description."
- />
- );
- }
-
- descriptionSection = (
- <SettingItemMin
- title={Utils.localizeMessage('general_tab.teamDescription', 'Team Description')}
- describe={describemsg}
- updateSection={this.onUpdateDescriptionSection}
- />
- );
- }
-
- return (
- <div>
- <div className='modal-header'>
- <button
- id='closeButton'
- type='button'
- className='close'
- data-dismiss='modal'
- aria-label='Close'
- >
- <span aria-hidden='true'>
- {'×'}
- </span>
- </button>
- <h4
- className='modal-title'
- ref='title'
- >
- <div className='modal-back'>
- <i className='fa fa-angle-left'/>
- </div>
- <FormattedMessage
- id='general_tab.title'
- defaultMessage='General Settings'
- />
- </h4>
- </div>
- <div
- ref='wrapper'
- className='user-settings'
- >
- <h3 className='tab-header'>
- <FormattedMessage
- id='general_tab.title'
- defaultMessage='General Settings'
- />
- </h3>
- <div className='divider-dark first'/>
- {nameSection}
- <div className='divider-light'/>
- {descriptionSection}
- <div className='divider-light'/>
- {openInviteSection}
- <div className='divider-light'/>
- {inviteSection}
- <div className='divider-dark'/>
- </div>
- </div>
- );
- }
-}
-
-GeneralTab.propTypes = {
- updateSection: PropTypes.func.isRequired,
- team: PropTypes.object.isRequired,
- activeSection: PropTypes.string.isRequired
-};
-
-export default GeneralTab;
diff --git a/webapp/components/team_import_tab.jsx b/webapp/components/team_import_tab.jsx
deleted file mode 100644
index b310cdc12..000000000
--- a/webapp/components/team_import_tab.jsx
+++ /dev/null
@@ -1,227 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import * as utils from 'utils/utils.jsx';
-import SettingUpload from './setting_upload.jsx';
-
-import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl';
-
-const holders = defineMessages({
- importSlack: {
- id: 'team_import_tab.importSlack',
- defaultMessage: 'Import from Slack (Beta)'
- }
-});
-
-import React from 'react';
-
-class TeamImportTab extends React.Component {
- constructor(props) {
- super(props);
-
- this.onImportFailure = this.onImportFailure.bind(this);
- this.onImportSuccess = this.onImportSuccess.bind(this);
- this.doImportSlack = this.doImportSlack.bind(this);
-
- this.state = {
- status: 'ready',
- link: ''
- };
- }
-
- onImportFailure() {
- this.setState({status: 'fail'});
- }
-
- onImportSuccess(data) {
- this.setState({status: 'done', link: 'data:application/octet-stream;charset=utf-8,' + encodeURIComponent(atob(data.results))});
- }
-
- doImportSlack(file) {
- this.setState({status: 'in-progress', link: ''});
- utils.importSlack(file, this.onImportSuccess, this.onImportFailure);
- }
-
- render() {
- const {formatMessage} = this.props.intl;
- var uploadDocsLink = (
- <a
- href='https://docs.mattermost.com/administration/migrating.html#migrating-from-slack'
- target='_blank'
- rel='noopener noreferrer'
- >
- <FormattedMessage
- id='team_import_tab.importHelpDocsLink'
- defaultMessage='documentation'
- />
- </a>
- );
-
- var uploadExportInstructions = (
- <strong>
- <FormattedMessage
- id='team_import_tab.importHelpExportInstructions'
- defaultMessage='Slack > Team Settings > Import/Export Data > Export > Start Export'
- />
- </strong>
- );
-
- var uploadExporterLink = (
- <a
- href='https://github.com/grundleborg/slack-advanced-exporter'
- target='_blank'
- rel='noopener noreferrer'
- >
- <FormattedMessage
- id='team_import_tab.importHelpExporterLink'
- defaultMessage='Slack Advanced Exporter'
- />
- </a>
- );
-
- var uploadHelpText = (
- <div>
- <p>
- <FormattedMessage
- id='team_import_tab.importHelpLine1'
- defaultMessage="Slack import to Mattermost supports importing of messages in your Slack team's public channels."
- />
- </p>
- <p>
- <FormattedMessage
- id='team_import_tab.importHelpLine2'
- defaultMessage='To import a team from Slack, go to {exportInstructions}. See {uploadDocsLink} to learn more.'
- values={{
- exportInstructions: uploadExportInstructions,
- uploadDocsLink
- }}
- />
- </p>
- <p>
- <FormattedMessage
- id='team_import_tab.importHelpLine3'
- defaultMessage='To import posts with attached files, see {slackAdvancedExporterLink} for details.'
- values={{
- slackAdvancedExporterLink: uploadExporterLink
- }}
- />
- </p>
- </div>
- );
-
- var uploadSection = (
- <SettingUpload
- title={formatMessage(holders.importSlack)}
- submit={this.doImportSlack}
- helpText={uploadHelpText}
- fileTypesAccepted='.zip'
- />
- );
-
- var messageSection;
- switch (this.state.status) {
-
- case 'ready':
- messageSection = '';
- break;
- case 'in-progress':
- messageSection = (
- <p className='confirm-import alert alert-warning'><i className='fa fa-spinner fa-pulse'/>
- <FormattedMessage
- id='team_import_tab.importing'
- defaultMessage=' Importing...'
- />
- </p>
- );
- break;
- case 'done':
- messageSection = (
- <p className='confirm-import alert alert-success'>
- <i className='fa fa-check'/>
- <FormattedMessage
- id='team_import_tab.successful'
- defaultMessage=' Import successful: '
- />
- <a
- href={this.state.link}
- download='MattermostImportSummary.txt'
- >
- <FormattedMessage
- id='team_import_tab.summary'
- defaultMessage='View Summary'
- />
- </a>
- </p>
- );
- break;
- case 'fail':
- messageSection = (
- <p className='confirm-import alert alert-warning'>
- <i className='fa fa-warning'/>
- <FormattedMessage
- id='team_import_tab.failure'
- defaultMessage=' Import failure: '
- />
- <a
- href={this.state.link}
- download='MattermostImportSummary.txt'
- >
- <FormattedMessage
- id='team_import_tab.summary'
- defaultMessage='View Summary'
- />
- </a>
- </p>
- );
- break;
- }
-
- return (
- <div>
- <div className='modal-header'>
- <button
- type='button'
- className='close'
- data-dismiss='modal'
- aria-label='Close'
- >
- <span aria-hidden='true'>{'×'}</span>
- </button>
- <h4
- className='modal-title'
- ref='title'
- >
- <div className='modal-back'>
- <i className='fa fa-angle-left'/>
- </div>
- <FormattedMessage
- id='team_import_tab.import'
- defaultMessage='Import'
- />
- </h4>
- </div>
- <div
- ref='wrapper'
- className='user-settings'
- >
- <h3 className='tab-header'>
- <FormattedMessage
- id='team_import_tab.import'
- defaultMessage='Import'
- />
- </h3>
- <div className='divider-dark first'/>
- {uploadSection}
- <div className='divider-dark'/>
- {messageSection}
- </div>
- </div>
- );
- }
-}
-
-TeamImportTab.propTypes = {
- intl: intlShape.isRequired
-};
-
-export default injectIntl(TeamImportTab);
diff --git a/webapp/components/team_members_dropdown/index.js b/webapp/components/team_members_dropdown/index.js
deleted file mode 100644
index e7b5910a2..000000000
--- a/webapp/components/team_members_dropdown/index.js
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getUser} from 'mattermost-redux/actions/users';
-import {getTeamStats} from 'mattermost-redux/actions/teams';
-import {getChannelStats} from 'mattermost-redux/actions/channels';
-
-import TeamMembersDropdown from './team_members_dropdown.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getUser,
- getTeamStats,
- getChannelStats
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(TeamMembersDropdown);
diff --git a/webapp/components/team_members_dropdown/team_members_dropdown.jsx b/webapp/components/team_members_dropdown/team_members_dropdown.jsx
deleted file mode 100644
index 041c4a859..000000000
--- a/webapp/components/team_members_dropdown/team_members_dropdown.jsx
+++ /dev/null
@@ -1,398 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ConfirmModal from 'components/confirm_modal.jsx';
-
-import TeamStore from 'stores/team_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-import ChannelStore from 'stores/channel_store.jsx';
-
-import {removeUserFromTeam, updateTeamMemberRoles} from 'actions/team_actions.jsx';
-import {loadMyTeamMembers, updateActive} from 'actions/user_actions.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-import {browserHistory} from 'react-router/es6';
-
-export default class TeamMembersDropdown extends React.Component {
- static propTypes = {
- user: PropTypes.object.isRequired,
- teamMember: PropTypes.object.isRequired,
- actions: PropTypes.shape({
- getUser: PropTypes.func.isRequired,
- getTeamStats: PropTypes.func.isRequired,
- getChannelStats: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.handleMakeMember = this.handleMakeMember.bind(this);
- this.handleRemoveFromTeam = this.handleRemoveFromTeam.bind(this);
- this.handleMakeActive = this.handleMakeActive.bind(this);
- this.handleMakeNotActive = this.handleMakeNotActive.bind(this);
- this.handleMakeAdmin = this.handleMakeAdmin.bind(this);
- this.handleDemote = this.handleDemote.bind(this);
- this.handleDemoteSubmit = this.handleDemoteSubmit.bind(this);
- this.handleDemoteCancel = this.handleDemoteCancel.bind(this);
-
- this.state = {
- serverError: null,
- showDemoteModal: false,
- user: null,
- role: null
- };
- }
-
- handleMakeMember() {
- const me = UserStore.getCurrentUser();
- if (this.props.user.id === me.id && me.roles.includes('system_admin')) {
- this.handleDemote(this.props.user, 'team_user');
- } else {
- updateTeamMemberRoles(
- this.props.teamMember.team_id,
- this.props.user.id,
- 'team_user',
- () => {
- this.props.actions.getUser(this.props.user.id);
-
- if (this.props.user.id === me.id) {
- loadMyTeamMembers();
- }
- },
- (err) => {
- this.setState({serverError: err.message});
- }
- );
- }
- }
-
- handleRemoveFromTeam() {
- removeUserFromTeam(
- this.props.teamMember.team_id,
- this.props.user.id,
- () => {
- UserStore.removeProfileFromTeam(this.props.teamMember.team_id, this.props.user.id);
- UserStore.emitInTeamChange();
- this.props.actions.getTeamStats(this.props.teamMember.team_id);
- },
- (err) => {
- this.setState({serverError: err.message});
- }
- );
- }
-
- handleMakeActive() {
- updateActive(this.props.user.id, true,
- () => {
- this.props.actions.getChannelStats(ChannelStore.getCurrentId());
- this.props.actions.getTeamStats(this.props.teamMember.team_id);
- },
- (err) => {
- this.setState({serverError: err.message});
- }
- );
- }
-
- handleMakeNotActive() {
- updateActive(this.props.user.id, false,
- () => {
- this.props.actions.getChannelStats(ChannelStore.getCurrentId());
- this.props.actions.getTeamStats(this.props.teamMember.team_id);
- },
- (err) => {
- this.setState({serverError: err.message});
- }
- );
- }
-
- handleMakeAdmin() {
- const me = UserStore.getCurrentUser();
- if (this.props.user.id === me.id && me.roles.includes('system_admin')) {
- this.handleDemote(this.props.user, 'team_user team_admin');
- } else {
- updateTeamMemberRoles(
- this.props.teamMember.team_id,
- this.props.user.id,
- 'team_user team_admin',
- () => {
- this.props.actions.getUser(this.props.user.id);
- },
- (err) => {
- this.setState({serverError: err.message});
- }
- );
- }
- }
-
- handleDemote(user, role, newRole) {
- this.setState({
- serverError: this.state.serverError,
- showDemoteModal: true,
- user,
- role,
- newRole
- });
- }
-
- handleDemoteCancel() {
- this.setState({
- serverError: null,
- showDemoteModal: false,
- user: null,
- role: null,
- newRole: null
- });
- }
-
- handleDemoteSubmit() {
- updateTeamMemberRoles(
- this.props.teamMember.team_id,
- this.props.user.id,
- this.state.newRole,
- () => {
- this.props.actions.getUser(this.props.user.id);
-
- const teamUrl = TeamStore.getCurrentTeamUrl();
- if (teamUrl) {
- browserHistory.push(teamUrl);
- } else {
- browserHistory.push('/');
- }
- },
- (err) => {
- this.setState({serverError: err.message});
- }
- );
- }
-
- render() {
- let serverError = null;
- if (this.state.serverError) {
- serverError = (
- <div className='has-error'>
- <label className='has-error control-label'>{this.state.serverError}</label>
- </div>
- );
- }
-
- const teamMember = this.props.teamMember;
- const user = this.props.user;
- let currentRoles = (
- <FormattedMessage
- id='team_members_dropdown.member'
- defaultMessage='Member'
- />
- );
-
- if (teamMember.roles.length > 0 && Utils.isAdmin(teamMember.roles)) {
- currentRoles = (
- <FormattedMessage
- id='team_members_dropdown.teamAdmin'
- defaultMessage='Team Admin'
- />
- );
- }
-
- if (user.roles.length > 0 && Utils.isSystemAdmin(user.roles)) {
- currentRoles = (
- <FormattedMessage
- id='team_members_dropdown.systemAdmin'
- defaultMessage='System Admin'
- />
- );
- }
-
- const me = UserStore.getCurrentUser();
- let showMakeMember = Utils.isAdmin(teamMember.roles) && !Utils.isSystemAdmin(user.roles);
- let showMakeAdmin = !Utils.isAdmin(teamMember.roles) && !Utils.isSystemAdmin(user.roles);
- let showMakeActive = false;
- let showMakeNotActive = Utils.isSystemAdmin(user.roles);
-
- if (user.delete_at > 0) {
- currentRoles = (
- <FormattedMessage
- id='team_members_dropdown.inactive'
- defaultMessage='Inactive'
- />
- );
- showMakeMember = false;
- showMakeAdmin = false;
- showMakeActive = true;
- showMakeNotActive = false;
- }
-
- let makeAdmin = null;
- if (showMakeAdmin) {
- makeAdmin = (
- <li role='presentation'>
- <a
- role='menuitem'
- href='#'
- onClick={this.handleMakeAdmin}
- >
- <FormattedMessage
- id='team_members_dropdown.makeAdmin'
- defaultMessage='Make Team Admin'
- />
- </a>
- </li>
- );
- }
-
- let makeMember = null;
- if (showMakeMember) {
- makeMember = (
- <li role='presentation'>
- <a
- role='menuitem'
- href='#'
- onClick={this.handleMakeMember}
- >
- <FormattedMessage
- id='team_members_dropdown.makeMember'
- defaultMessage='Make Member'
- />
- </a>
- </li>
- );
- }
-
- let removeFromTeam = null;
- if (this.props.user.id !== me.id) {
- removeFromTeam = (
- <li role='presentation'>
- <a
- role='menuitem'
- href='#'
- onClick={this.handleRemoveFromTeam}
- >
- <FormattedMessage
- id='team_members_dropdown.leave_team'
- defaultMessage='Remove From Team'
- />
- </a>
- </li>
- );
- }
-
- const makeActive = null;
- if (showMakeActive) {
- // makeActive = (
- // <li role='presentation'>
- // <a
- // role='menuitem'
- // href='#'
- // onClick={this.handleMakeActive}
- // >
- // <FormattedMessage
- // id='team_members_dropdown.makeActive'
- // defaultMessage='Activate'
- // />
- // </a>
- // </li>
- // );
- }
-
- const makeNotActive = null;
- if (showMakeNotActive) {
- // makeNotActive = (
- // <li role='presentation'>
- // <a
- // role='menuitem'
- // href='#'
- // onClick={this.handleMakeNotActive}
- // >
- // <FormattedMessage
- // id='team_members_dropdown.makeInactive'
- // defaultMessage='Deactivate'
- // />
- // </a>
- // </li>
- // );
- }
-
- let makeDemoteModal = null;
- if (this.props.user.id === me.id) {
- const title = (
- <FormattedMessage
- id='team_members_dropdown.confirmDemoteRoleTitle'
- defaultMessage='Confirm demotion from System Admin role'
- />
- );
-
- const message = (
- <div>
- <FormattedMessage
- id='team_members_dropdown.confirmDemoteDescription'
- defaultMessage="If you demote yourself from the System Admin role and there is not another user with System Admin privileges, you'll need to re-assign a System Admin by accessing the Mattermost server through a terminal and running the following command."
- />
- <br/>
- <br/>
- <FormattedMessage
- id='team_members_dropdown.confirmDemotionCmd'
- defaultMessage='platform roles system_admin {username}'
- vallues={{
- username: me.username
- }}
- />
- {serverError}
- </div>
- );
-
- const confirmButton = (
- <FormattedMessage
- id='team_members_dropdown.confirmDemotion'
- defaultMessage='Confirm Demotion'
- />
- );
-
- makeDemoteModal = (
- <ConfirmModal
- show={this.state.showDemoteModal}
- title={title}
- message={message}
- confirmButtonText={confirmButton}
- onConfirm={this.handleDemoteSubmit}
- onCancel={this.handleDemoteCancel}
- />
- );
- }
-
- if (!removeFromTeam && !makeAdmin && !makeMember && !makeActive && !makeNotActive) {
- return <div>{currentRoles}</div>;
- }
-
- return (
- <div className='dropdown member-drop'>
- <a
- href='#'
- className='dropdown-toggle theme'
- type='button'
- data-toggle='dropdown'
- aria-expanded='true'
- >
- <span>{currentRoles} </span>
- <span className='fa fa-chevron-down'/>
- </a>
- <ul
- className='dropdown-menu member-menu'
- role='menu'
- >
- {removeFromTeam}
- {makeAdmin}
- {makeMember}
- {makeActive}
- {makeNotActive}
- </ul>
- {makeDemoteModal}
- {serverError}
- </div>
- );
- }
-}
diff --git a/webapp/components/team_members_modal.jsx b/webapp/components/team_members_modal.jsx
deleted file mode 100644
index 8307530c6..000000000
--- a/webapp/components/team_members_modal.jsx
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import MemberListTeam from 'components/member_list_team';
-import TeamStore from 'stores/team_store.jsx';
-
-import {FormattedMessage} from 'react-intl';
-
-import {Modal} from 'react-bootstrap';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class TeamMembersModal extends React.Component {
- constructor(props) {
- super(props);
-
- this.teamChanged = this.teamChanged.bind(this);
- this.onHide = this.onHide.bind(this);
-
- this.state = {
- team: TeamStore.getCurrent(),
- show: true
- };
- }
-
- componentDidMount() {
- TeamStore.addChangeListener(this.teamChanged);
- if (this.props.onLoad) {
- this.props.onLoad();
- }
- }
-
- componentWillUnmount() {
- TeamStore.removeChangeListener(this.teamChanged);
- }
-
- teamChanged() {
- this.setState({team: TeamStore.getCurrent()});
- }
-
- onHide() {
- this.setState({show: false});
- }
-
- render() {
- let teamDisplayName = '';
- if (this.state.team) {
- teamDisplayName = this.state.team.display_name;
- }
-
- return (
- <Modal
- dialogClassName='more-modal'
- show={this.state.show}
- onHide={this.onHide}
- onExited={this.props.onHide}
- >
- <Modal.Header closeButton={true}>
- <Modal.Title>
- <FormattedMessage
- id='team_member_modal.members'
- defaultMessage='{team} Members'
- values={{
- team: teamDisplayName
- }}
- />
- </Modal.Title>
- </Modal.Header>
- <Modal.Body>
- <MemberListTeam
- isAdmin={this.props.isAdmin}
- />
- </Modal.Body>
- </Modal>
- );
- }
-}
-
-TeamMembersModal.propTypes = {
- onHide: PropTypes.func.isRequired,
- isAdmin: PropTypes.bool.isRequired,
- onLoad: PropTypes.func
-};
diff --git a/webapp/components/team_settings.jsx b/webapp/components/team_settings.jsx
deleted file mode 100644
index 7108ce771..000000000
--- a/webapp/components/team_settings.jsx
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import TeamStore from 'stores/team_store.jsx';
-import ImportTab from './team_import_tab.jsx';
-import GeneralTab from './team_general_tab.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class TeamSettings extends React.Component {
- constructor(props) {
- super(props);
-
- this.onChange = this.onChange.bind(this);
-
- this.state = {team: TeamStore.getCurrent()};
- }
- componentDidMount() {
- TeamStore.addChangeListener(this.onChange);
- }
- componentWillUnmount() {
- TeamStore.removeChangeListener(this.onChange);
- }
- onChange() {
- var team = TeamStore.getCurrent();
-
- if (!Utils.areObjectsEqual(this.state.team, team)) {
- this.setState({team});
- }
- }
- render() {
- if (!this.state.team) {
- return null;
- }
- var result;
- switch (this.props.activeTab) {
- case 'general':
- result = (
- <div>
- <GeneralTab
- team={this.state.team}
- activeSection={this.props.activeSection}
- updateSection={this.props.updateSection}
- />
- </div>
- );
- break;
- case 'import':
- result = (
- <div>
- <ImportTab
- team={this.state.team}
- activeSection={this.props.activeSection}
- updateSection={this.props.updateSection}
- />
- </div>
- );
- break;
- default:
- result = (
- <div/>
- );
- break;
- }
- return result;
- }
-}
-
-TeamSettings.defaultProps = {
- activeTab: '',
- activeSection: ''
-};
-
-TeamSettings.propTypes = {
- activeTab: PropTypes.string.isRequired,
- activeSection: PropTypes.string.isRequired,
- updateSection: PropTypes.func.isRequired
-};
diff --git a/webapp/components/team_settings_modal.jsx b/webapp/components/team_settings_modal.jsx
deleted file mode 100644
index a1756da76..000000000
--- a/webapp/components/team_settings_modal.jsx
+++ /dev/null
@@ -1,133 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import ReactDOM from 'react-dom';
-import SettingsSidebar from './settings_sidebar.jsx';
-import TeamSettings from './team_settings.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl';
-
-const holders = defineMessages({
- generalTab: {
- id: 'team_settings_modal.generalTab',
- defaultMessage: 'General'
- },
- importTab: {
- id: 'team_settings_modal.importTab',
- defaultMessage: 'Import'
- }
-});
-
-import React from 'react';
-
-class TeamSettingsModal extends React.Component {
- constructor(props) {
- super(props);
-
- this.updateTab = this.updateTab.bind(this);
- this.updateSection = this.updateSection.bind(this);
-
- this.state = {
- activeTab: 'general',
- activeSection: ''
- };
- }
- componentDidMount() {
- const modal = $(ReactDOM.findDOMNode(this.refs.modal));
-
- modal.on('click', '.modal-back', function handleBackClick() {
- $(this).closest('.modal-dialog').removeClass('display--content');
- $(this).closest('.modal-dialog').find('.settings-table .nav li.active').removeClass('active');
- });
- modal.on('click', '.modal-header .close', () => {
- setTimeout(() => {
- $('.modal-dialog.display--content').removeClass('display--content');
- }, 500);
- });
-
- if (!Utils.isMobile()) {
- $('.settings-modal .settings-content').perfectScrollbar();
- }
- }
- updateTab(tab) {
- this.setState({activeTab: tab, activeSection: ''});
- if (!Utils.isMobile()) {
- $('.settings-modal .modal-body').scrollTop(0).perfectScrollbar('update');
- }
- }
- updateSection(section) {
- if ($('.section-max').length) {
- $('.settings-modal .modal-body').scrollTop(0).perfectScrollbar('update');
- }
- this.setState({activeSection: section});
- }
- render() {
- const {formatMessage} = this.props.intl;
- const tabs = [];
- tabs.push({name: 'general', uiName: formatMessage(holders.generalTab), icon: 'icon fa fa-cog'});
- tabs.push({name: 'import', uiName: formatMessage(holders.importTab), icon: 'icon fa fa-upload'});
-
- return (
- <div
- className='modal fade'
- ref='modal'
- id='team_settings'
- role='dialog'
- tabIndex='-1'
- aria-hidden='true'
- >
- <div className='modal-dialog settings-modal'>
- <div className='modal-content'>
- <div className='modal-header'>
- <button
- type='button'
- className='close'
- data-dismiss='modal'
- aria-label='Close'
- >
- <span aria-hidden='true'>
- {'×'}
- </span>
- </button>
- <h4
- className='modal-title'
- ref='title'
- >
- <FormattedMessage
- id='team_settings_modal.title'
- defaultMessage='Team Settings'
- />
- </h4>
- </div>
- <div className='modal-body settings-modal__body'>
- <div className='settings-table'>
- <div className='settings-links'>
- <SettingsSidebar
- tabs={tabs}
- activeTab={this.state.activeTab}
- updateTab={this.updateTab}
- />
- </div>
- <div className='settings-content minimize-settings'>
- <TeamSettings
- activeTab={this.state.activeTab}
- activeSection={this.state.activeSection}
- updateSection={this.updateSection}
- />
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- );
- }
-}
-
-TeamSettingsModal.propTypes = {
- intl: intlShape.isRequired
-};
-
-export default injectIntl(TeamSettingsModal);
diff --git a/webapp/components/team_sidebar/components/team_button.jsx b/webapp/components/team_sidebar/components/team_button.jsx
deleted file mode 100644
index 57f436eb2..000000000
--- a/webapp/components/team_sidebar/components/team_button.jsx
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import Constants from 'utils/constants.jsx';
-
-import {trackEvent} from 'actions/diagnostics_actions.jsx';
-import {switchTeams} from 'actions/team_actions.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import {Link} from 'react-router/es6';
-import {Tooltip, OverlayTrigger} from 'react-bootstrap';
-
-export default class TeamButton extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleSwitch = this.handleSwitch.bind(this);
- this.handleDisabled = this.handleDisabled.bind(this);
- }
-
- handleSwitch(e) {
- e.preventDefault();
- trackEvent('ui', 'ui_team_sidebar_switch_team');
- switchTeams(this.props.url);
- }
-
- handleDisabled(e) {
- e.preventDefault();
- }
-
- render() {
- let teamClass = this.props.active ? 'active' : '';
- const btnClass = this.props.btnClass;
- const disabled = this.props.disabled ? 'team-disabled' : '';
- const handleClick = (this.props.active || this.props.disabled) ? this.handleDisabled : this.handleSwitch;
- let badge;
-
- if (!teamClass) {
- teamClass = this.props.unread ? 'unread' : '';
-
- if (this.props.mentions) {
- badge = (
- <span className='badge pull-right small'>{this.props.mentions}</span>
- );
- }
- }
-
- let btn;
- let content = this.props.content;
- if (!content) {
- content = (
- <div className='team-btn__initials'>
- {this.props.displayName.substring(0, 2)}
- <div className='team-btn__content'>
- {this.props.displayName}
- </div>
- </div>
- );
- }
- if (this.props.isMobile) {
- btn = (
- <div className={'team-btn ' + btnClass}>
- {badge}
- {content}
- </div>
- );
- } else {
- btn = (
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={Constants.OVERLAY_TIME_DELAY}
- placement={this.props.placement}
- overlay={
- <Tooltip id={`tooltip-${this.props.url}`}>
- {this.props.tip}
- </Tooltip>
- }
- >
- <div className={'team-btn ' + btnClass}>
- {badge}
- {content}
- </div>
- </OverlayTrigger>
- );
- }
-
- return (
- <div
- className={`team-container ${teamClass}`}
- >
- <Link
- className={disabled}
- to={this.props.url}
- onClick={handleClick}
- >
- {btn}
- </Link>
- </div>
- );
- }
-}
-
-TeamButton.defaultProps = {
- btnClass: '',
- tip: '',
- placement: 'right',
- active: false,
- disabled: false,
- unread: false,
- mentions: 0
-};
-
-TeamButton.propTypes = {
- btnClass: PropTypes.string,
- url: PropTypes.string.isRequired,
- displayName: PropTypes.string,
- content: PropTypes.node,
- tip: PropTypes.node.isRequired,
- active: PropTypes.bool,
- disabled: PropTypes.bool,
- isMobile: PropTypes.bool,
- unread: PropTypes.bool,
- mentions: PropTypes.number,
- placement: PropTypes.oneOf(['left', 'right', 'top', 'bottom'])
-};
diff --git a/webapp/components/team_sidebar/index.js b/webapp/components/team_sidebar/index.js
deleted file mode 100644
index d130555fd..000000000
--- a/webapp/components/team_sidebar/index.js
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getTeams} from 'mattermost-redux/actions/teams';
-
-import TeamSidebar from './team_sidebar_controller.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getTeams
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(TeamSidebar);
diff --git a/webapp/components/team_sidebar/team_sidebar_controller.jsx b/webapp/components/team_sidebar/team_sidebar_controller.jsx
deleted file mode 100644
index 3cd30219f..000000000
--- a/webapp/components/team_sidebar/team_sidebar_controller.jsx
+++ /dev/null
@@ -1,189 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import TeamButton from './components/team_button.jsx';
-
-import TeamStore from 'stores/team_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-
-import {sortTeamsByDisplayName} from 'utils/team_utils.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import $ from 'jquery';
-import PropTypes from 'prop-types';
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-
-export default class TeamSidebar extends React.Component {
- static propTypes = {
- actions: PropTypes.shape({
- getTeams: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.getStateFromStores = this.getStateFromStores.bind(this);
- this.onChange = this.onChange.bind(this);
- this.handleResize = this.handleResize.bind(this);
- this.setStyles = this.setStyles.bind(this);
-
- this.state = this.getStateFromStores();
- }
-
- getStateFromStores() {
- const teamMembers = TeamStore.getMyTeamMembers();
- const currentTeamId = TeamStore.getCurrentId();
-
- return {
- teams: TeamStore.getAll(),
- teamListings: TeamStore.getTeamListings(),
- teamMembers,
- currentTeamId,
- show: teamMembers && teamMembers.length > 1,
- isMobile: Utils.isMobile()
- };
- }
-
- componentDidMount() {
- window.addEventListener('resize', this.handleResize);
- TeamStore.addChangeListener(this.onChange);
- TeamStore.addUnreadChangeListener(this.onChange);
- this.props.actions.getTeams(0, 200);
- this.setStyles();
- }
-
- componentWillUnmount() {
- window.removeEventListener('resize', this.handleResize);
- TeamStore.removeChangeListener(this.onChange);
- TeamStore.removeUnreadChangeListener(this.onChange);
- }
-
- componentDidUpdate(prevProps, prevState) {
- if (!this.state.isMobile) {
- $('.team-wrapper').perfectScrollbar();
- }
-
- // reset the scrollbar upon switching teams
- if (this.state.currentTeam !== prevState.currentTeam) {
- this.refs.container.scrollTop = 0;
- if (!this.state.isMobile) {
- $('.team-wrapper').perfectScrollbar('update');
- }
- }
- }
-
- onChange() {
- this.setState(this.getStateFromStores());
- this.setStyles();
- }
-
- handleResize() {
- const teamMembers = this.state.teamMembers;
- this.setState({show: teamMembers && teamMembers.length > 1});
- this.setStyles();
- }
-
- setStyles() {
- const root = document.querySelector('#root');
-
- if (this.state.show) {
- root.classList.add('multi-teams');
- } else {
- root.classList.remove('multi-teams');
- }
- }
-
- render() {
- if (!this.state.show) {
- return null;
- }
-
- const myTeams = [];
- const isSystemAdmin = Utils.isSystemAdmin(UserStore.getCurrentUser().roles);
- const isAlreadyMember = new Map();
- let moreTeams = false;
-
- for (const index in this.state.teamMembers) {
- if (this.state.teamMembers.hasOwnProperty(index)) {
- const teamMember = this.state.teamMembers[index];
- if (teamMember.delete_at > 0) {
- continue;
- }
- const teamId = teamMember.team_id;
- myTeams.push(Object.assign({
- unread: teamMember.msg_count > 0,
- mentions: teamMember.mention_count
- }, this.state.teams[teamId]));
- isAlreadyMember[teamId] = true;
- }
- }
-
- for (const id in this.state.teamListings) {
- if (this.state.teamListings.hasOwnProperty(id) && !isAlreadyMember[id]) {
- moreTeams = true;
- break;
- }
- }
-
- const teams = myTeams.
- sort(sortTeamsByDisplayName).
- map((team) => {
- return (
- <TeamButton
- key={'switch_team_' + team.name}
- url={`/${team.name}`}
- tip={team.display_name}
- active={team.id === this.state.currentTeamId}
- isMobile={this.state.isMobile}
- displayName={team.display_name}
- unread={team.unread}
- mentions={team.mentions}
- />
- );
- });
-
- if (moreTeams) {
- teams.push(
- <TeamButton
- btnClass='team-btn__add'
- key='more_teams'
- url='/select_team'
- isMobile={this.state.isMobile}
- tip={
- <FormattedMessage
- id='team_sidebar.join'
- defaultMessage='Other teams you can join.'
- />
- }
- content={<i className='fa fa-plus'/>}
- />
- );
- } else if (global.window.mm_config.EnableTeamCreation === 'true' || isSystemAdmin) {
- teams.push(
- <TeamButton
- btnClass='team-btn__add'
- key='more_teams'
- url='/create_team'
- isMobile={this.state.isMobile}
- tip={
- <FormattedMessage
- id='navbar_dropdown.create'
- defaultMessage='Create a New Team'
- />
- }
- content={<i className='fa fa-plus'/>}
- />
- );
- }
-
- return (
- <div className='team-sidebar'>
- <div className='team-wrapper'>
- {teams}
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/textbox.jsx b/webapp/components/textbox.jsx
deleted file mode 100644
index cf1d054cf..000000000
--- a/webapp/components/textbox.jsx
+++ /dev/null
@@ -1,318 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import AtMentionProvider from './suggestion/at_mention_provider.jsx';
-import ChannelMentionProvider from './suggestion/channel_mention_provider.jsx';
-import CommandProvider from './suggestion/command_provider.jsx';
-import EmoticonProvider from './suggestion/emoticon_provider.jsx';
-import SuggestionList from './suggestion/suggestion_list.jsx';
-import SuggestionBox from './suggestion/suggestion_box.jsx';
-import ErrorStore from 'stores/error_store.jsx';
-
-import * as TextFormatting from 'utils/text_formatting.jsx';
-import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-
-import {FormattedMessage} from 'react-intl';
-
-const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES;
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class Textbox extends React.Component {
- static propTypes = {
- id: PropTypes.string.isRequired,
- channelId: PropTypes.string,
- value: PropTypes.string.isRequired,
- onChange: PropTypes.func.isRequired,
- onKeyPress: PropTypes.func.isRequired,
- createMessage: PropTypes.string.isRequired,
- previewMessageLink: PropTypes.string,
- onKeyDown: PropTypes.func,
- onBlur: PropTypes.func,
- supportsCommands: PropTypes.bool.isRequired,
- handlePostError: PropTypes.func,
- suggestionListStyle: PropTypes.string,
- emojiEnabled: PropTypes.bool,
- isRHS: PropTypes.bool,
- popoverMentionKeyClick: React.PropTypes.bool,
- characterLimit: React.PropTypes.number
- };
-
- static defaultProps = {
- supportsCommands: true,
- isRHS: false,
- popoverMentionKeyClick: false,
- characterLimit: Constants.CHARACTER_LIMIT
- };
-
- constructor(props) {
- super(props);
-
- this.state = {
- connection: ''
- };
-
- this.suggestionProviders = [
- new AtMentionProvider(this.props.channelId),
- new ChannelMentionProvider(),
- new EmoticonProvider()
- ];
- if (props.supportsCommands) {
- this.suggestionProviders.push(new CommandProvider());
- }
- }
-
- componentDidMount() {
- ErrorStore.addChangeListener(this.onReceivedError);
- }
-
- componentWillMount() {
- this.checkMessageLength(this.props.value);
- }
-
- componentWillUnmount() {
- ErrorStore.removeChangeListener(this.onReceivedError);
- }
-
- onReceivedError = () => {
- const errorCount = ErrorStore.getConnectionErrorCount();
-
- if (errorCount > 1) {
- this.setState({connection: 'bad-connection'});
- } else {
- this.setState({connection: ''});
- }
- }
-
- handleChange = (e) => {
- this.checkMessageLength(e.target.value);
- this.props.onChange(e);
- }
-
- checkMessageLength = (message) => {
- if (this.props.handlePostError) {
- if (message.length > this.props.characterLimit) {
- const errorMessage = (
- <FormattedMessage
- id='create_post.error_message'
- defaultMessage='Your message is too long. Character count: {length}/{limit}'
- values={{
- length: message.length,
- limit: this.props.characterLimit
- }}
- />);
- this.props.handlePostError(errorMessage);
- } else {
- this.props.handlePostError(null);
- }
- }
- }
-
- handleKeyDown = (e) => {
- if (this.props.onKeyDown) {
- this.props.onKeyDown(e);
- }
- }
-
- handleBlur = (e) => {
- if (this.props.onBlur) {
- this.props.onBlur(e);
- }
- }
-
- handleHeightChange = (height, maxHeight) => {
- const wrapper = $(this.refs.wrapper);
-
- // Move over attachment icon to compensate for the scrollbar
- if (height > maxHeight) {
- wrapper.closest('.post-create').addClass('scroll');
- } else {
- wrapper.closest('.post-create').removeClass('scroll');
- }
- }
-
- focus = () => {
- const textbox = this.refs.message.getTextbox();
-
- textbox.focus();
- Utils.placeCaretAtEnd(textbox);
- }
-
- recalculateSize = () => {
- this.refs.message.recalculateSize();
- }
-
- togglePreview = (e) => {
- e.preventDefault();
- e.target.blur();
- this.setState((prevState) => {
- return {preview: !prevState.preview};
- });
- }
-
- hidePreview = () => {
- this.setState({preview: false});
- }
-
- componentWillReceiveProps(nextProps) {
- if (nextProps.channelId !== this.props.channelId) {
- // Update channel id for AtMentionProvider.
- const providers = this.suggestionProviders;
- for (let i = 0; i < providers.length; i++) {
- if (providers[i] instanceof AtMentionProvider) {
- providers[i] = new AtMentionProvider(nextProps.channelId);
- }
- }
- }
- }
-
- render() {
- const hasText = this.props.value && this.props.value.length > 0;
-
- let editHeader;
- if (this.props.previewMessageLink) {
- editHeader = (
- <span>
- {this.props.previewMessageLink}
- </span>
- );
- } else {
- editHeader = (
- <FormattedMessage
- id='textbox.edit'
- defaultMessage='Edit message'
- />
- );
- }
-
- let previewLink = null;
- if (Utils.isFeatureEnabled(PreReleaseFeatures.MARKDOWN_PREVIEW)) {
- previewLink = (
- <a
- onClick={this.togglePreview}
- className='textbox-preview-link'
- >
- {this.state.preview ? (
- editHeader
- ) : (
- <FormattedMessage
- id='textbox.preview'
- defaultMessage='Preview'
- />
- )}
- </a>
- );
- }
-
- const helpText = (
- <div
- style={{visibility: hasText ? 'visible' : 'hidden', opacity: hasText ? '0.45' : '0'}}
- className='help__format-text'
- >
- <b>
- <FormattedMessage
- id='textbox.bold'
- defaultMessage='**bold**'
- />
- </b>
- <i>
- <FormattedMessage
- id='textbox.italic'
- defaultMessage='_italic_'
- />
- </i>
- <span>
- {'~~'}
- <strike>
- <FormattedMessage
- id='textbox.strike'
- defaultMessage='strike'
- />
- </strike>
- {'~~ '}
- </span>
- <span>
- <FormattedMessage
- id='textbox.inlinecode'
- defaultMessage='`inline code`'
- />
- </span>
- <span>
- <FormattedMessage
- id='textbox.preformatted'
- defaultMessage='```preformatted```'
- />
- </span>
- <span>
- <FormattedMessage
- id='textbox.quote'
- defaultMessage='>quote'
- />
- </span>
- </div>
- );
-
- let textboxClassName = 'form-control custom-textarea';
- if (this.props.emojiEnabled) {
- textboxClassName += ' custom-textarea--emoji-picker';
- }
- if (this.state.connection) {
- textboxClassName += ' ' + this.state.connection;
- }
-
- return (
- <div
- ref='wrapper'
- className='textarea-wrapper'
- >
- <SuggestionBox
- id={this.props.id}
- ref='message'
- className={textboxClassName}
- type='textarea'
- spellCheck='true'
- placeholder={this.props.createMessage}
- onChange={this.handleChange}
- onKeyPress={this.props.onKeyPress}
- onKeyDown={this.handleKeyDown}
- onBlur={this.handleBlur}
- onHeightChange={this.handleHeightChange}
- style={{visibility: this.state.preview ? 'hidden' : 'visible'}}
- listComponent={SuggestionList}
- listStyle={this.props.suggestionListStyle}
- providers={this.suggestionProviders}
- channelId={this.props.channelId}
- value={this.props.value}
- renderDividers={true}
- isRHS={this.props.isRHS}
- popoverMentionKeyClick={this.props.popoverMentionKeyClick}
- />
- <div
- ref='preview'
- className='form-control custom-textarea textbox-preview-area'
- style={{display: this.state.preview ? 'block' : 'none'}}
- dangerouslySetInnerHTML={{__html: this.state.preview ? TextFormatting.formatText(this.props.value) : ''}}
- />
- <div className='help__text'>
- {helpText}
- {previewLink}
- <a
- target='_blank'
- rel='noopener noreferrer'
- href='/help/messaging'
- className='textbox-help-link'
- >
- <FormattedMessage
- id='textbox.help'
- defaultMessage='Help'
- />
- </a>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/toggle_modal_button.jsx b/webapp/components/toggle_modal_button.jsx
deleted file mode 100644
index 1e72e13b7..000000000
--- a/webapp/components/toggle_modal_button.jsx
+++ /dev/null
@@ -1,82 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-
-export default class ModalToggleButton extends React.Component {
- constructor(props) {
- super(props);
-
- this.show = this.show.bind(this);
- this.hide = this.hide.bind(this);
-
- this.state = {
- show: false
- };
- }
-
- show(e) {
- if (e) {
- e.preventDefault();
- }
- this.setState({show: true});
- }
-
- hide() {
- this.setState({show: false});
- }
-
- render() {
- const {children, dialogType, dialogProps, onClick, ...props} = this.props;
-
- // allow callers to provide an onClick which will be called before the modal is shown
- let clickHandler = this.show;
- if (onClick) {
- clickHandler = (e) => {
- onClick();
-
- this.show(e);
- };
- }
-
- let dialog;
- if (this.state.show) {
- // this assumes that all modals will have an onHide event and will show when mounted
- dialog = React.createElement(dialogType, Object.assign({}, dialogProps, {
- onHide: () => {
- this.hide();
-
- if (dialogProps.onHide) {
- dialogProps.onHide();
- }
- }
- }));
- }
-
- // nesting the dialog in the anchor tag looks like it shouldn't work, but it does due to how react-bootstrap
- // renders modals at the top level of the DOM instead of where you specify in the virtual DOM
- return (
- <a
- {...props}
- href='#'
- onClick={clickHandler}
- >
- {children}
- {dialog}
- </a>
- );
- }
-}
-
-ModalToggleButton.propTypes = {
- children: PropTypes.node.isRequired,
- dialogType: PropTypes.func.isRequired,
- dialogProps: PropTypes.object,
- onClick: PropTypes.func
-};
-
-ModalToggleButton.defaultProps = {
- dialogProps: {}
-};
diff --git a/webapp/components/tutorial/tutorial_intro_screens.jsx b/webapp/components/tutorial/tutorial_intro_screens.jsx
deleted file mode 100644
index 7600d15dd..000000000
--- a/webapp/components/tutorial/tutorial_intro_screens.jsx
+++ /dev/null
@@ -1,331 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import UserStore from 'stores/user_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-import PreferenceStore from 'stores/preference_store.jsx';
-import {savePreference} from 'actions/user_actions.jsx';
-import * as GlobalActions from 'actions/global_actions.jsx';
-import {trackEvent} from 'actions/diagnostics_actions.jsx';
-
-import {Constants, Preferences} from 'utils/constants.jsx';
-
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-import {browserHistory} from 'react-router/es6';
-
-import AppIcons from 'images/appIcons.png';
-
-const NUM_SCREENS = 3;
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class TutorialIntroScreens extends React.Component {
- static get propTypes() {
- return {
- townSquare: PropTypes.object,
- offTopic: PropTypes.object
- };
- }
- constructor(props) {
- super(props);
-
- this.handleNext = this.handleNext.bind(this);
- this.createScreen = this.createScreen.bind(this);
- this.createCircles = this.createCircles.bind(this);
- this.skipTutorial = this.skipTutorial.bind(this);
-
- this.state = {currentScreen: 0};
- }
- handleNext() {
- switch (this.state.currentScreen) {
- case 0:
- trackEvent('tutorial', 'tutorial_screen_1_welcome_to_mattermost_next');
- break;
- case 1:
- trackEvent('tutorial', 'tutorial_screen_2_how_mattermost_works_next');
- break;
- case 2:
- trackEvent('tutorial', 'tutorial_screen_3_youre_all_set_next');
- break;
- }
-
- if (this.state.currentScreen < 2) {
- this.setState({currentScreen: this.state.currentScreen + 1});
- return;
- }
-
- browserHistory.push(TeamStore.getCurrentTeamUrl() + '/channels/town-square');
-
- const step = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 0);
-
- savePreference(
- Preferences.TUTORIAL_STEP,
- UserStore.getCurrentId(),
- (step + 1).toString()
- );
- }
- skipTutorial(e) {
- e.preventDefault();
-
- switch (this.state.currentScreen) {
- case 0:
- trackEvent('tutorial', 'tutorial_screen_1_welcome_to_mattermost_skip');
- break;
- case 1:
- trackEvent('tutorial', 'tutorial_screen_2_how_mattermost_works_skip');
- break;
- case 2:
- trackEvent('tutorial', 'tutorial_screen_3_youre_all_set_skip');
- break;
- }
-
- savePreference(
- Preferences.TUTORIAL_STEP,
- UserStore.getCurrentId(),
- '999'
- );
-
- browserHistory.push(TeamStore.getCurrentTeamUrl() + '/channels/town-square');
- }
- createScreen() {
- switch (this.state.currentScreen) {
- case 0:
- return this.createScreenOne();
- case 1:
- return this.createScreenTwo();
- case 2:
- return this.createScreenThree();
- }
- return null;
- }
- createScreenOne() {
- const circles = this.createCircles();
-
- return (
- <div>
- <FormattedHTMLMessage
- id='tutorial_intro.screenOne'
- defaultMessage='<h3>Welcome to:</h3>
- <h1>Mattermost</h1>
- <p>Your team communication all in one place, instantly searchable and available anywhere.</p>
- <p>Keep your team connected to help them achieve what matters most.</p>'
- />
- {circles}
- </div>
- );
- }
- createScreenTwo() {
- const circles = this.createCircles();
-
- let appDownloadLink = null;
- let appDownloadImage = null;
- if (global.window.mm_config.AppDownloadLink) {
- // not using a FormattedHTMLMessage here since mm_config.AppDownloadLink is configurable and could be used
- // to inject HTML if we're not careful
- appDownloadLink = (
- <FormattedMessage
- id='tutorial_intro.mobileApps'
- defaultMessage='Install the apps for {link} for easy access and notifications on the go.'
- values={{
- link: (
- <a
- href={global.window.mm_config.AppDownloadLink}
- target='_blank'
- rel='noopener noreferrer'
- >
- <FormattedMessage
- id='tutorial_intro.mobileAppsLinkText'
- defaultMessage='PC, Mac, iOS and Android'
- />
- </a>
- )
- }}
- />
- );
-
- appDownloadImage = (
- <a
- href={global.window.mm_config.AppDownloadLink}
- target='_blank'
- rel='noopener noreferrer'
- >
- <img
- className='tutorial__app-icons'
- src={AppIcons}
- />
- </a>
- );
- }
-
- return (
- <div>
- <FormattedHTMLMessage
- id='tutorial_intro.screenTwo'
- defaultMessage='<h3>How Mattermost works:</h3>
- <p>Communication happens in public discussion channels, private channels and direct messages.</p>
- <p>Everything is archived and searchable from any web-enabled desktop, laptop or phone.</p>'
- />
- {appDownloadLink}
- {appDownloadImage}
- {circles}
- </div>
- );
- }
- createScreenThree() {
- const team = TeamStore.getCurrent();
- let inviteModalLink;
- let inviteText;
-
- if (global.window.mm_license.IsLicensed !== 'true' || global.window.mm_config.RestrictTeamInvite === Constants.PERMISSIONS_ALL) {
- if (team.type === Constants.INVITE_TEAM) {
- inviteModalLink = (
- <a
- className='intro-links'
- href='#'
- onClick={GlobalActions.showInviteMemberModal}
- >
- <FormattedMessage
- id='tutorial_intro.invite'
- defaultMessage='Invite teammates'
- />
- </a>
- );
- } else {
- inviteModalLink = (
- <a
- className='intro-links'
- href='#'
- onClick={GlobalActions.showGetTeamInviteLinkModal}
- >
- <FormattedMessage
- id='tutorial_intro.teamInvite'
- defaultMessage='Invite teammates'
- />
- </a>
- );
- }
-
- inviteText = (
- <p>
- {inviteModalLink}
- <FormattedMessage
- id='tutorial_intro.whenReady'
- defaultMessage=' when you’re ready.'
- />
- </p>
- );
- }
-
- const circles = this.createCircles();
-
- let supportInfo = null;
- if (global.window.mm_config.SupportEmail) {
- supportInfo = (
- <p>
- <FormattedMessage
- id='tutorial_intro.support'
- defaultMessage='Need anything, just email us at '
- />
- <a
- href={'mailto:' + global.window.mm_config.SupportEmail}
- target='_blank'
- rel='noopener noreferrer'
- >
- {global.window.mm_config.SupportEmail}
- </a>
- {'.'}
- </p>
- );
- }
-
- let townSquareDisplayName = Constants.DEFAULT_CHANNEL_UI_NAME;
- if (this.props.townSquare) {
- townSquareDisplayName = this.props.townSquare.display_name;
- }
-
- return (
- <div>
- <h3>
- <FormattedMessage
- id='tutorial_intro.allSet'
- defaultMessage='You’re all set'
- />
- </h3>
- {inviteText}
- {supportInfo}
- <FormattedMessage
- id='tutorial_intro.end'
- defaultMessage='Click "Next" to enter {channel}. This is the first channel teammates see when they sign up. Use it for posting updates everyone needs to know.'
- values={{
- channel: townSquareDisplayName
- }}
- />
- {circles}
- </div>
- );
- }
- createCircles() {
- const circles = [];
- for (let i = 0; i < NUM_SCREENS; i++) {
- let className = 'circle';
- if (i === this.state.currentScreen) {
- className += ' active';
- }
-
- circles.push(
- <a
- href='#'
- key={'circle' + i}
- className={className}
- onClick={(e) => { //eslint-disable-line no-loop-func
- e.preventDefault();
- this.setState({currentScreen: i});
- }}
- />
- );
- }
-
- return (
- <div className='tutorial__circles'>
- {circles}
- </div>
- );
- }
- render() {
- const screen = this.createScreen();
-
- return (
- <div className='tutorial-steps__container'>
- <div className='tutorial__content'>
- <div className='tutorial__steps'>
- {screen}
- <div className='tutorial__footer'>
- <button
- className='btn btn-primary'
- tabIndex='1'
- onClick={this.handleNext}
- >
- <FormattedMessage
- id='tutorial_intro.next'
- defaultMessage='Next'
- />
- </button>
- <a
- className='tutorial-skip'
- href='#'
- onClick={this.skipTutorial}
- >
- <FormattedMessage
- id='tutorial_intro.skip'
- defaultMessage='Skip tutorial'
- />
- </a>
- </div>
- </div>
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/tutorial/tutorial_tip.jsx b/webapp/components/tutorial/tutorial_tip.jsx
deleted file mode 100644
index 2db8c6cea..000000000
--- a/webapp/components/tutorial/tutorial_tip.jsx
+++ /dev/null
@@ -1,247 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import UserStore from 'stores/user_store.jsx';
-import PreferenceStore from 'stores/preference_store.jsx';
-import {savePreference} from 'actions/user_actions.jsx';
-import {trackEvent} from 'actions/diagnostics_actions.jsx';
-
-import Constants from 'utils/constants.jsx';
-
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-
-const Preferences = Constants.Preferences;
-import * as Utils from 'utils/utils.jsx';
-
-import {Overlay} from 'react-bootstrap';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-import tutorialGif from 'images/tutorialTip.gif';
-import tutorialGifWhite from 'images/tutorialTipWhite.gif';
-
-export default class TutorialTip extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleNext = this.handleNext.bind(this);
- this.toggle = this.toggle.bind(this);
- this.skipTutorial = this.skipTutorial.bind(this);
-
- this.state = {currentScreen: 0, show: false};
- }
- toggle() {
- const show = !this.state.show;
- this.setState({show});
-
- if (!show && this.state.currentScreen >= this.props.screens.length - 1) {
- const step = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 0);
-
- savePreference(
- Preferences.TUTORIAL_STEP,
- UserStore.getCurrentId(),
- (step + 1).toString()
- );
- }
- }
- handleNext() {
- if (this.state.currentScreen < this.props.screens.length - 1) {
- this.setState({currentScreen: this.state.currentScreen + 1});
- return;
- }
-
- if (this.props.diagnosticsTag) {
- let tag = this.props.diagnosticsTag;
-
- if (this.props.screens.length > 1) {
- tag += '_' + (this.state.currentScreen + 1).toString();
- }
-
- if (this.state.currentScreen === this.props.screens.length - 1) {
- tag += '_okay';
- } else {
- tag += '_next';
- }
-
- trackEvent('tutorial', tag);
- }
-
- this.closeRightSidebar();
- this.toggle();
- }
- closeRightSidebar() {
- if (Utils.isMobile()) {
- setTimeout(() => {
- document.querySelector('.app__body .inner-wrap').classList.remove('move--left-small');
- document.querySelector('.app__body .sidebar--menu').classList.remove('move--left');
- });
- }
- }
- skipTutorial(e) {
- e.preventDefault();
-
- if (this.props.diagnosticsTag) {
- let tag = this.props.diagnosticsTag;
- if (this.props.screens.length > 1) {
- tag += '_' + this.state.currentScreen;
- }
- tag += '_skip';
- trackEvent('tutorial', tag);
- }
-
- savePreference(
- Preferences.TUTORIAL_STEP,
- UserStore.getCurrentId(),
- '999'
- );
- }
- render() {
- const buttonText = this.state.currentScreen === this.props.screens.length - 1 ? (
- <FormattedMessage
- id='tutorial_tip.ok'
- defaultMessage='Okay'
- />
- ) : (
- <FormattedMessage
- id='tutorial_tip.next'
- defaultMessage='Next'
- />
- );
-
- const dots = [];
- if (this.props.screens.length > 1) {
- for (let i = 0; i < this.props.screens.length; i++) {
- let className = 'circle';
- if (i === this.state.currentScreen) {
- className += ' active';
- }
-
- dots.push(
- <a
- href='#'
- key={'dotactive' + i}
- className={className}
- onClick={(e) => { //eslint-disable-line no-loop-func
- e.preventDefault();
- this.setState({currentScreen: i});
- }}
- />
- );
- }
- }
-
- var tutorialGifImage = tutorialGif;
- if (this.props.overlayClass === 'tip-overlay--header' || this.props.overlayClass === 'tip-overlay--sidebar' || this.props.overlayClass === 'tip-overlay--header--up') {
- tutorialGifImage = tutorialGifWhite;
- }
-
- return (
- <div
- className={'tip-div ' + this.props.overlayClass}
- onClick={this.toggle}
- >
- <img
- className='tip-button'
- src={tutorialGifImage}
- width='35'
- onClick={this.toggle}
- ref='target'
- />
-
- <Overlay
- show={this.state.show}
- >
- <div className='tip-backdrop'/>
- </Overlay>
-
- <Overlay
- placement={this.props.placement}
- show={this.state.show}
- rootClose={true}
- onHide={this.toggle}
- target={() => this.refs.target}
- >
- <div className={'tip-overlay ' + this.props.overlayClass}>
- <div className='arrow'/>
- {this.props.screens[this.state.currentScreen]}
- <div className='tutorial__footer'>
- <div className='tutorial__circles'>{dots}</div>
- <div className='text-right'>
- <button
- className='btn btn-primary'
- onClick={this.handleNext}
- >
- {buttonText}
- </button>
- <div className='tip-opt'>
- <FormattedMessage
- id='tutorial_tip.seen'
- defaultMessage='Seen this before? '
- />
- <a
- href='#'
- onClick={this.skipTutorial}
- >
- <FormattedMessage
- id='tutorial_tip.out'
- defaultMessage='Opt out of these tips.'
- />
- </a>
- </div>
- </div>
- </div>
- </div>
- </Overlay>
- </div>
- );
- }
-}
-
-TutorialTip.defaultProps = {
- overlayClass: ''
-};
-
-TutorialTip.propTypes = {
- screens: PropTypes.array.isRequired,
- placement: PropTypes.string.isRequired,
- overlayClass: PropTypes.string,
- diagnosticsTag: PropTypes.string
-};
-
-export function createMenuTip(toggleFunc, onBottom) {
- const screens = [];
-
- screens.push(
- <div>
- <FormattedHTMLMessage
- id='sidebar_header.tutorial'
- defaultMessage='<h4>Main Menu</h4>
- <p>The <strong>Main Menu</strong> is where you can <strong>Invite New Members</strong>, access your <strong>Account Settings</strong> and set your <strong>Theme Color</strong>.</p>
- <p>Team administrators can also access their <strong>Team Settings</strong> from this menu.</p><p>System administrators will find a <strong>System Console</strong> option to administrate the entire system.</p>'
- />
- </div>
- );
-
- let placement = 'right';
- let arrow = 'left';
- if (onBottom) {
- placement = 'bottom';
- arrow = 'up';
- }
-
- return (
- <div
- onClick={toggleFunc}
- >
- <TutorialTip
- ref='tip'
- placement={placement}
- screens={screens}
- overlayClass={'tip-overlay--header--' + arrow}
- diagnosticsTag='tutorial_tip_3_main_menu'
- />
- </div>
- );
-}
diff --git a/webapp/components/tutorial/tutorial_view.jsx b/webapp/components/tutorial/tutorial_view.jsx
deleted file mode 100644
index 491e3146e..000000000
--- a/webapp/components/tutorial/tutorial_view.jsx
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import TutorialIntroScreens from './tutorial_intro_screens.jsx';
-
-import ChannelStore from 'stores/channel_store.jsx';
-import Constants from 'utils/constants.jsx';
-
-import $ from 'jquery';
-import React from 'react';
-
-export default class TutorialView extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleChannelChange = this.handleChannelChange.bind(this);
-
- this.state = {
- townSquare: ChannelStore.getByName(Constants.DEFAULT_CHANNEL)
- };
- }
- componentDidMount() {
- ChannelStore.addChangeListener(this.handleChannelChange);
-
- $('body').addClass('app__body');
- }
- componentWillUnmount() {
- ChannelStore.removeChangeListener(this.handleChannelChange);
-
- $('body').removeClass('app__body');
- }
- handleChannelChange() {
- this.setState({
- townSquare: ChannelStore.getByName(Constants.DEFAULT_CHANNEL)
- });
- }
- render() {
- return (
- <div
- id='app-content'
- className='app__content'
- >
- <TutorialIntroScreens
- townSquare={this.state.townSquare}
- />
- </div>
- );
- }
-}
diff --git a/webapp/components/unread_channel_indicator.jsx b/webapp/components/unread_channel_indicator.jsx
deleted file mode 100644
index 9462761ac..000000000
--- a/webapp/components/unread_channel_indicator.jsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import PropTypes from 'prop-types';
-
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-// Indicator for the left sidebar which indicate if there's unread posts in a channel that is not shown
-// because it is either above or below the screen
-import React from 'react';
-import Constants from 'utils/constants.jsx';
-
-export default function UnreadChannelIndicator(props) {
- const unreadIcon = Constants.UNREAD_ICON_SVG;
- let displayValue = 'none';
- if (props.show) {
- displayValue = 'block';
- }
-
- return (
- <div
- className={'nav-pills__unread-indicator ' + props.extraClass}
- style={{display: displayValue}}
- >
- {props.text}
- <span
- className='icon icon__unread'
- dangerouslySetInnerHTML={{__html: unreadIcon}}
- />
- </div>
- );
-}
-
-UnreadChannelIndicator.defaultProps = {
- show: false,
- extraClass: '',
- text: ''
-};
-UnreadChannelIndicator.propTypes = {
- show: PropTypes.bool,
- extraClass: PropTypes.string,
- text: PropTypes.object
-};
diff --git a/webapp/components/user_list.jsx b/webapp/components/user_list.jsx
deleted file mode 100644
index 0a187c36e..000000000
--- a/webapp/components/user_list.jsx
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import UserListRow from './user_list_row.jsx';
-import LoadingScreen from 'components/loading_screen.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-
-import Constants from 'utils/constants.jsx';
-
-export default class UserList extends React.Component {
- constructor(props) {
- super(props);
-
- this.scrollToTop = this.scrollToTop.bind(this);
- }
-
- scrollToTop() {
- if (this.refs.container) {
- this.refs.container.scrollTop = 0;
- }
- }
-
- render() {
- const users = this.props.users;
-
- let content;
- if (users == null) {
- return <LoadingScreen/>;
- } else if (users.length > 0) {
- content = users.map((user, index) => {
- return (
- <UserListRow
- key={user.id}
- user={user}
- extraInfo={this.props.extraInfo[user.id]}
- actions={this.props.actions}
- actionProps={this.props.actionProps}
- actionUserProps={this.props.actionUserProps[user.id]}
- userCount={(index >= 0 && index < Constants.TEST_ID_COUNT) ? index : -1}
- />
- );
- });
- } else {
- content = (
- <div
- key='no-users-found'
- className='more-modal__placeholder-row'
- >
- <p>
- <FormattedMessage
- id='user_list.notFound'
- defaultMessage='No users found'
- />
- </p>
- </div>
- );
- }
-
- return (
- <div ref='container'>
- {content}
- </div>
- );
- }
-}
-
-UserList.defaultProps = {
- users: [],
- extraInfo: {},
- actions: [],
- actionProps: {}
-};
-
-UserList.propTypes = {
- users: PropTypes.arrayOf(PropTypes.object),
- extraInfo: PropTypes.object,
- actions: PropTypes.arrayOf(PropTypes.func),
- actionProps: PropTypes.object,
- actionUserProps: PropTypes.object
-};
diff --git a/webapp/components/user_list_row.jsx b/webapp/components/user_list_row.jsx
deleted file mode 100644
index af71a8f0d..000000000
--- a/webapp/components/user_list_row.jsx
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ProfilePicture from 'components/profile_picture.jsx';
-
-import UserStore from 'stores/user_store.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-import {Client4} from 'mattermost-redux/client';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import {FormattedHTMLMessage} from 'react-intl';
-
-export default function UserListRow({user, extraInfo, actions, actionProps, actionUserProps, userCount}) {
- let buttons = null;
- if (actions) {
- buttons = actions.map((Action, index) => {
- return (
- <Action
- key={index.toString()}
- user={user}
- {...actionProps}
- {...actionUserProps}
- />
- );
- });
- }
-
- // QUICK HACK, NEEDS A PROP FOR TOGGLING STATUS
- let email = user.email;
- let emailStyle = 'more-modal__description';
- let status;
- if (extraInfo && extraInfo.length > 0) {
- email = (
- <FormattedHTMLMessage
- id='admin.user_item.emailTitle'
- defaultMessage='<strong>Email:</strong> {email}'
- values={{
- email: user.email
- }}
- />
- );
- emailStyle = '';
- } else if (user.status) {
- status = user.status;
- } else {
- status = UserStore.getStatus(user.id);
- }
-
- let userCountID = null;
- let userCountEmail = null;
- if (userCount >= 0) {
- userCountID = Utils.createSafeId('userListRowName' + userCount);
- userCountEmail = Utils.createSafeId('userListRowEmail' + userCount);
- }
-
- return (
- <div
- key={user.id}
- className='more-modal__row'
- >
- <ProfilePicture
- src={Client4.getProfilePictureUrl(user.id, user.last_picture_update)}
- status={status}
- width='32'
- height='32'
- />
- <div
- className='more-modal__details'
- >
- <div
- id={userCountID}
- className='more-modal__name'
- >
- {Utils.displayEntireNameForUser(user)}
- </div>
- <div
- id={userCountEmail}
- className={emailStyle}
- >
- {email}
- </div>
- {extraInfo}
- </div>
- <div
- className='more-modal__actions'
- >
- {buttons}
- </div>
- </div>
- );
-}
-
-UserListRow.defaultProps = {
- extraInfo: [],
- actions: [],
- actionProps: {},
- actionUserProps: {}
-};
-
-UserListRow.propTypes = {
- user: PropTypes.object.isRequired,
- extraInfo: PropTypes.arrayOf(PropTypes.object),
- actions: PropTypes.arrayOf(PropTypes.func),
- actionProps: PropTypes.object,
- actionUserProps: PropTypes.object,
- userCount: PropTypes.number
-};
diff --git a/webapp/components/user_profile.jsx b/webapp/components/user_profile.jsx
deleted file mode 100644
index 28b1e5bfb..000000000
--- a/webapp/components/user_profile.jsx
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ProfilePopover from './profile_popover.jsx';
-import Pluggable from 'plugins/pluggable';
-import * as Utils from 'utils/utils.jsx';
-
-import {OverlayTrigger} from 'react-bootstrap';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class UserProfile extends React.Component {
- constructor(props) {
- super(props);
-
- this.hideProfilePopover = this.hideProfilePopover.bind(this);
- }
- shouldComponentUpdate(nextProps) {
- if (!Utils.areObjectsEqual(nextProps.user, this.props.user)) {
- return true;
- }
-
- if (nextProps.overwriteName !== this.props.overwriteName) {
- return true;
- }
-
- if (nextProps.overwriteImage !== this.props.overwriteImage) {
- return true;
- }
-
- if (nextProps.disablePopover !== this.props.disablePopover) {
- return true;
- }
-
- if (nextProps.displayNameType !== this.props.displayNameType) {
- return true;
- }
-
- if (nextProps.status !== this.props.status) {
- return true;
- }
-
- if (nextProps.isBusy !== this.props.isBusy) {
- return true;
- }
-
- return false;
- }
-
- hideProfilePopover() {
- this.refs.overlay.hide();
- }
-
- render() {
- let name = '...';
- let profileImg = '';
-
- if (this.props.user) {
- name = Utils.displayUsername(this.props.user.id);
- profileImg = Utils.imageURLForUser(this.props.user);
- }
-
- if (this.props.overwriteName) {
- name = this.props.overwriteName;
- }
-
- if (this.props.disablePopover) {
- return <div className='user-popover'>{name}</div>;
- }
-
- return (
- <OverlayTrigger
- ref='overlay'
- trigger='click'
- placement='right'
- rootClose={true}
- overlay={
- <Pluggable>
- <ProfilePopover
- user={this.props.user}
- src={profileImg}
- status={this.props.status}
- isBusy={this.props.isBusy}
- hide={this.hideProfilePopover}
- isRHS={this.props.isRHS}
- hasMention={this.props.hasMention}
- />
- </Pluggable>
- }
- >
- <div
- className='user-popover'
- >
- {name}
- </div>
- </OverlayTrigger>
- );
- }
-}
-
-UserProfile.defaultProps = {
- user: {},
- overwriteName: '',
- overwriteImage: '',
- disablePopover: false,
- isRHS: false,
- hasMention: false
-};
-UserProfile.propTypes = {
- user: PropTypes.object,
- overwriteName: PropTypes.node,
- overwriteImage: PropTypes.string,
- disablePopover: PropTypes.bool,
- displayNameType: PropTypes.string,
- status: PropTypes.string,
- isBusy: PropTypes.bool,
- isRHS: PropTypes.bool,
- hasMention: PropTypes.bool
-};
diff --git a/webapp/components/user_settings/custom_theme_chooser.jsx b/webapp/components/user_settings/custom_theme_chooser.jsx
deleted file mode 100644
index 82bf99a0a..000000000
--- a/webapp/components/user_settings/custom_theme_chooser.jsx
+++ /dev/null
@@ -1,476 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import 'bootstrap-colorpicker';
-import $ from 'jquery';
-import PropTypes from 'prop-types';
-import React from 'react';
-import {Popover, OverlayTrigger} from 'react-bootstrap';
-import {defineMessages, FormattedMessage, intlShape, injectIntl} from 'react-intl';
-
-import Constants from 'utils/constants.jsx';
-import * as UserAgent from 'utils/user_agent.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-const messages = defineMessages({
- sidebarBg: {
- id: 'user.settings.custom_theme.sidebarBg',
- defaultMessage: 'Sidebar BG'
- },
- sidebarText: {
- id: 'user.settings.custom_theme.sidebarText',
- defaultMessage: 'Sidebar Text'
- },
- sidebarHeaderBg: {
- id: 'user.settings.custom_theme.sidebarHeaderBg',
- defaultMessage: 'Sidebar Header BG'
- },
- sidebarHeaderTextColor: {
- id: 'user.settings.custom_theme.sidebarHeaderTextColor',
- defaultMessage: 'Sidebar Header Text'
- },
- sidebarUnreadText: {
- id: 'user.settings.custom_theme.sidebarUnreadText',
- defaultMessage: 'Sidebar Unread Text'
- },
- sidebarTextHoverBg: {
- id: 'user.settings.custom_theme.sidebarTextHoverBg',
- defaultMessage: 'Sidebar Text Hover BG'
- },
- sidebarTextActiveBorder: {
- id: 'user.settings.custom_theme.sidebarTextActiveBorder',
- defaultMessage: 'Sidebar Text Active Border'
- },
- sidebarTextActiveColor: {
- id: 'user.settings.custom_theme.sidebarTextActiveColor',
- defaultMessage: 'Sidebar Text Active Color'
- },
- onlineIndicator: {
- id: 'user.settings.custom_theme.onlineIndicator',
- defaultMessage: 'Online Indicator'
- },
- awayIndicator: {
- id: 'user.settings.custom_theme.awayIndicator',
- defaultMessage: 'Away Indicator'
- },
- mentionBj: {
- id: 'user.settings.custom_theme.mentionBj',
- defaultMessage: 'Mention Jewel BG'
- },
- mentionColor: {
- id: 'user.settings.custom_theme.mentionColor',
- defaultMessage: 'Mention Jewel Text'
- },
- centerChannelBg: {
- id: 'user.settings.custom_theme.centerChannelBg',
- defaultMessage: 'Center Channel BG'
- },
- centerChannelColor: {
- id: 'user.settings.custom_theme.centerChannelColor',
- defaultMessage: 'Center Channel Text'
- },
- newMessageSeparator: {
- id: 'user.settings.custom_theme.newMessageSeparator',
- defaultMessage: 'New Message Separator'
- },
- linkColor: {
- id: 'user.settings.custom_theme.linkColor',
- defaultMessage: 'Link Color'
- },
- buttonBg: {
- id: 'user.settings.custom_theme.buttonBg',
- defaultMessage: 'Button BG'
- },
- buttonColor: {
- id: 'user.settings.custom_theme.buttonColor',
- defaultMessage: 'Button Text'
- },
- errorTextColor: {
- id: 'user.settings.custom_theme.errorTextColor',
- defaultMessage: 'Error Text Color'
- },
- mentionHighlightBg: {
- id: 'user.settings.custom_theme.mentionHighlightBg',
- defaultMessage: 'Mention Highlight BG'
- },
- mentionHighlightLink: {
- id: 'user.settings.custom_theme.mentionHighlightLink',
- defaultMessage: 'Mention Highlight Link'
- },
- codeTheme: {
- id: 'user.settings.custom_theme.codeTheme',
- defaultMessage: 'Code Theme'
- }
-});
-
-const HEX_CODE_LENGTH = 7;
-
-class CustomThemeChooser extends React.Component {
- constructor(props) {
- super(props);
- this.selectTheme = this.selectTheme.bind(this);
-
- const copyTheme = Object.assign({}, this.props.theme);
- delete copyTheme.type;
- delete copyTheme.image;
-
- this.state = {
- copyTheme: JSON.stringify(copyTheme)
- };
- }
-
- componentDidMount() {
- $('.color-picker').colorpicker({
- format: 'hex'
- });
- $('.color-picker').on('changeColor', this.onPickerChange);
- $('.group--code').on('change', this.onCodeThemeChange);
- document.addEventListener('click', this.closeColorpicker);
- }
-
- componentWillUnmount() {
- $('.color-picker').off('changeColor', this.onPickerChange);
- $('.group--code').off('change', this.onCodeThemeChange);
- document.removeEventListener('click', this.closeColorpicker);
- }
-
- componentDidUpdate() {
- const theme = this.props.theme;
- Constants.THEME_ELEMENTS.forEach((element) => {
- if (theme.hasOwnProperty(element.id) && element.id !== 'codeTheme') {
- $('#' + element.id).data('colorpicker').color.setColor(theme[element.id]);
- $('#' + element.id).colorpicker('update');
- }
- });
- }
-
- closeColorpicker(e) {
- if (!$(e.target).closest('.color-picker').length && Utils.isMobile()) {
- $('.color-picker').colorpicker('hide');
- }
- }
-
- onPickerChange = (e) => {
- const inputBox = e.target.childNodes[0];
- if (document.activeElement === inputBox && inputBox.value.length !== HEX_CODE_LENGTH) {
- return;
- }
-
- const theme = this.props.theme;
- if (theme[e.target.id] !== e.color.toHex()) {
- theme[e.target.id] = e.color.toHex();
- theme.type = 'custom';
- this.props.updateTheme(theme);
- }
- }
-
- pasteBoxChange = (e) => {
- let text = '';
-
- if (window.clipboardData && window.clipboardData.getData) { // IE
- text = window.clipboardData.getData('Text');
- } else {
- text = e.clipboardData.getData('Text');//e.clipboardData.getData('text/plain');
- }
-
- if (text.length === 0) {
- return;
- }
-
- let theme;
- try {
- theme = JSON.parse(text);
- } catch (err) {
- return;
- }
-
- this.setState({
- copyTheme: JSON.stringify(theme)
- });
-
- theme.type = 'custom';
- this.props.updateTheme(theme);
- }
-
- onChangeHandle = (e) => {
- e.stopPropagation();
- }
-
- selectTheme() {
- const textarea = this.refs.textarea;
- textarea.focus();
- textarea.setSelectionRange(0, this.state.copyTheme.length);
- }
-
- toggleSidebarStyles = (e) => {
- e.preventDefault();
-
- $(this.refs.sidebarStylesHeader).toggleClass('open');
- this.toggleSection(this.refs.sidebarStyles);
- }
-
- toggleCenterChannelStyles = (e) => {
- e.preventDefault();
-
- $(this.refs.centerChannelStylesHeader).toggleClass('open');
- this.toggleSection(this.refs.centerChannelStyles);
- }
-
- toggleLinkAndButtonStyles = (e) => {
- e.preventDefault();
-
- $(this.refs.linkAndButtonStylesHeader).toggleClass('open');
- this.toggleSection(this.refs.linkAndButtonStyles);
- }
-
- toggleSection(node) {
- if (UserAgent.isIos()) {
- // iOS doesn't support jQuery animations
- $(node).toggleClass('open');
- } else {
- $(node).slideToggle();
- }
- }
-
- onCodeThemeChange = (e) => {
- const theme = this.props.theme;
- theme.codeTheme = e.target.value;
- this.props.updateTheme(theme);
- }
-
- render() {
- const {formatMessage} = this.props.intl;
- const theme = this.props.theme;
-
- const sidebarElements = [];
- const centerChannelElements = [];
- const linkAndButtonElements = [];
- Constants.THEME_ELEMENTS.forEach((element, index) => {
- if (element.id === 'codeTheme') {
- const codeThemeOptions = [];
- let codeThemeURL = '';
-
- element.themes.forEach((codeTheme, codeThemeIndex) => {
- if (codeTheme.id === theme[element.id]) {
- codeThemeURL = codeTheme.iconURL;
- }
- codeThemeOptions.push(
- <option
- key={'code-theme-key' + codeThemeIndex}
- value={codeTheme.id}
- >
- {codeTheme.uiName}
- </option>
- );
- });
-
- var popoverContent = (
- <Popover
- bsStyle='info'
- id='code-popover'
- className='code-popover'
- >
- <img
- width='200'
- src={codeThemeURL}
- />
- </Popover>
- );
-
- centerChannelElements.push(
- <div
- className='col-sm-6 form-group'
- key={'custom-theme-key' + index}
- >
- <label className='custom-label'>{formatMessage(messages[element.id])}</label>
- <div
- className='input-group theme-group group--code dropdown'
- id={element.id}
- >
- <select
- className='form-control'
- type='text'
- defaultValue={theme[element.id]}
- >
- {codeThemeOptions}
- </select>
- <OverlayTrigger
- trigger={['hover', 'focus']}
- placement='top'
- overlay={popoverContent}
- ref='headerOverlay'
- >
- <span className='input-group-addon'>
- <img
- src={codeThemeURL}
- />
- </span>
- </OverlayTrigger>
- </div>
- </div>
- );
- } else if (element.group === 'centerChannelElements') {
- centerChannelElements.push(
- <div
- className='col-sm-6 form-group element'
- key={'custom-theme-key' + index}
- >
- <label className='custom-label'>{formatMessage(messages[element.id])}</label>
- <div
- className='input-group color-picker'
- id={element.id}
- >
- <input
- className='form-control'
- type='text'
- defaultValue={theme[element.id]}
- />
- <span className='input-group-addon'><i/></span>
- </div>
- </div>
- );
- } else if (element.group === 'sidebarElements') {
- sidebarElements.push(
- <div
- className='col-sm-6 form-group element'
- key={'custom-theme-key' + index}
- >
- <label className='custom-label'>{formatMessage(messages[element.id])}</label>
- <div
- className='input-group color-picker'
- id={element.id}
- >
- <input
- className='form-control'
- type='text'
- defaultValue={theme[element.id]}
- />
- <span className='input-group-addon'><i/></span>
- </div>
- </div>
- );
- } else {
- linkAndButtonElements.push(
- <div
- className='col-sm-6 form-group element'
- key={'custom-theme-key' + index}
- >
- <label className='custom-label'>{formatMessage(messages[element.id])}</label>
- <div
- className='input-group color-picker'
- id={element.id}
- >
- <input
- className='form-control'
- type='text'
- defaultValue={theme[element.id]}
- />
- <span className='input-group-addon'><i/></span>
- </div>
- </div>
- );
- }
- });
-
- const pasteBox = (
- <div className='col-sm-12'>
- <label className='custom-label'>
- <FormattedMessage
- id='user.settings.custom_theme.copyPaste'
- defaultMessage='Copy and paste to share theme colors:'
- />
- </label>
- <textarea
- ref='textarea'
- className='form-control'
- value={this.state.copyTheme}
- onPaste={this.pasteBoxChange}
- onChange={this.onChangeHandle}
- onClick={this.selectTheme}
- />
- </div>
- );
-
- return (
- <div className='appearance-section padding-top'>
- <div className='theme-elements row'>
- <div
- ref='sidebarStylesHeader'
- className='theme-elements__header'
- onClick={this.toggleSidebarStyles}
- >
- <FormattedMessage
- id='user.settings.custom_theme.sidebarTitle'
- defaultMessage='Sidebar Styles'
- />
- <div className='header__icon'>
- <i className='fa fa-plus'/>
- <i className='fa fa-minus'/>
- </div>
- </div>
- <div
- ref='sidebarStyles'
- className='theme-elements__body'
- >
- {sidebarElements}
- </div>
- </div>
- <div className='theme-elements row'>
- <div
- ref='centerChannelStylesHeader'
- className='theme-elements__header'
- onClick={this.toggleCenterChannelStyles}
- >
- <FormattedMessage
- id='user.settings.custom_theme.centerChannelTitle'
- defaultMessage='Center Channel Styles'
- />
- <div className='header__icon'>
- <i className='fa fa-plus'/>
- <i className='fa fa-minus'/>
- </div>
- </div>
- <div
- ref='centerChannelStyles'
- className='theme-elements__body'
- >
- {centerChannelElements}
- </div>
- </div>
- <div className='theme-elements row form-group'>
- <div
- ref='linkAndButtonStylesHeader'
- className='theme-elements__header'
- onClick={this.toggleLinkAndButtonStyles}
- >
- <FormattedMessage
- id='user.settings.custom_theme.linkButtonTitle'
- defaultMessage='Link and Button Styles'
- />
- <div className='header__icon'>
- <i className='fa fa-plus'/>
- <i className='fa fa-minus'/>
- </div>
- </div>
- <div
- ref='linkAndButtonStyles'
- className='theme-elements__body'
- >
- {linkAndButtonElements}
- </div>
- </div>
- <div className='row'>
- {pasteBox}
- </div>
- </div>
- );
- }
-}
-
-CustomThemeChooser.propTypes = {
- intl: intlShape.isRequired,
- theme: PropTypes.object.isRequired,
- updateTheme: PropTypes.func.isRequired
-};
-
-export default injectIntl(CustomThemeChooser);
diff --git a/webapp/components/user_settings/desktop_notification_settings.jsx b/webapp/components/user_settings/desktop_notification_settings.jsx
deleted file mode 100644
index abdbff512..000000000
--- a/webapp/components/user_settings/desktop_notification_settings.jsx
+++ /dev/null
@@ -1,464 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import SettingItemMin from 'components/setting_item_min.jsx';
-import SettingItemMax from 'components/setting_item_max.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-
-export default class DesktopNotificationSettings extends React.Component {
- constructor(props) {
- super(props);
-
- this.buildMaximizedSetting = this.buildMaximizedSetting.bind(this);
- this.buildMinimizedSetting = this.buildMinimizedSetting.bind(this);
-
- this.state = {};
- }
-
- buildMaximizedSetting() {
- const inputs = [];
- let extraInfo = null;
-
- const activityRadio = [false, false, false];
- if (this.props.activity === 'mention') {
- activityRadio[1] = true;
- } else if (this.props.activity === 'none') {
- activityRadio[2] = true;
- } else {
- activityRadio[0] = true;
- }
-
- let soundSection;
- let durationSection;
- if (this.props.activity !== 'none') {
- const soundRadio = [false, false];
- if (this.props.sound === 'false') {
- soundRadio[1] = true;
- } else {
- soundRadio[0] = true;
- }
-
- if (Utils.hasSoundOptions()) {
- soundSection = (
- <div>
- <hr/>
- <label>
- <FormattedMessage
- id='user.settings.notifications.desktop.sound'
- defaultMessage='Notification sound'
- />
- </label>
- <br/>
- <div className='radio'>
- <label>
- <input
- id='soundOn'
- type='radio'
- name='notificationSounds'
- checked={soundRadio[0]}
- onChange={() => this.props.setParentState('desktopSound', 'true')}
- />
- <FormattedMessage
- id='user.settings.notifications.on'
- defaultMessage='On'
- />
- </label>
- <br/>
- </div>
- <div className='radio'>
- <label>
- <input
- id='soundOff'
- type='radio'
- name='notificationSounds'
- checked={soundRadio[1]}
- onChange={() => this.props.setParentState('desktopSound', 'false')}
- />
- <FormattedMessage
- id='user.settings.notifications.off'
- defaultMessage='Off'
- />
- </label>
- <br/>
- </div>
- <br/>
- <span>
- <FormattedMessage
- id='user.settings.notifications.sounds_info'
- defaultMessage='Notification sounds are available on IE11, Safari, Chrome and Mattermost Desktop Apps.'
- />
- </span>
- </div>
- );
- } else {
- soundSection = (
- <div>
- <hr/>
- <label>
- <FormattedMessage
- id='user.settings.notifications.desktop.sound'
- defaultMessage='Notification sound'
- />
- </label>
- <br/>
- <FormattedMessage
- id='user.settings.notifications.soundConfig'
- defaultMessage='Please configure notification sounds in your browser settings'
- />
- </div>
- );
- }
-
- const durationRadio = [false, false, false, false];
- if (this.props.duration === '3') {
- durationRadio[0] = true;
- } else if (this.props.duration === '10') {
- durationRadio[2] = true;
- } else if (this.props.duration === '0') {
- durationRadio[3] = true;
- } else {
- durationRadio[1] = true;
- }
-
- durationSection = (
- <div>
- <hr/>
- <label>
- <FormattedMessage
- id='user.settings.notifications.desktop.duration'
- defaultMessage='Notification duration'
- />
- </label>
- <br/>
- <div className='radio'>
- <label>
- <input
- id='soundDuration3'
- type='radio'
- name='desktopDuration'
- checked={durationRadio[0]}
- onChange={() => this.props.setParentState('desktopDuration', '3')}
- />
- <FormattedMessage
- id='user.settings.notifications.desktop.seconds'
- defaultMessage='{seconds} seconds'
- values={{
- seconds: '3'
- }}
- />
- </label>
- <br/>
- </div>
- <div className='radio'>
- <label>
- <input
- id='soundDuration5'
- type='radio'
- name='desktopDuration'
- checked={durationRadio[1]}
- onChange={() => this.props.setParentState('desktopDuration', '5')}
- />
- <FormattedMessage
- id='user.settings.notifications.desktop.seconds'
- defaultMessage='{seconds} seconds'
- values={{
- seconds: '5'
- }}
- />
- </label>
- <br/>
- </div>
- <div className='radio'>
- <label>
- <input
- id='soundDuration10'
- type='radio'
- name='desktopDuration'
- checked={durationRadio[2]}
- onChange={() => this.props.setParentState('desktopDuration', '10')}
- />
- <FormattedMessage
- id='user.settings.notifications.desktop.seconds'
- defaultMessage='{seconds} seconds'
- values={{
- seconds: '10'
- }}
- />
- </label>
- </div>
- <div className='radio'>
- <label>
- <input
- id='soundDurationUnlimited'
- type='radio'
- name='desktopDuration'
- checked={durationRadio[3]}
- onChange={() => this.props.setParentState('desktopDuration', '0')}
- />
- <FormattedMessage
- id='user.settings.notifications.desktop.unlimited'
- defaultMessage='Unlimited'
- />
- </label>
- </div>
- </div>
- );
-
- extraInfo = (
- <span>
- <FormattedMessage
- id='user.settings.notifications.desktop.durationInfo'
- defaultMessage='Sets how long desktop notifications will remain on screen when using Firefox or Chrome. Desktop notifications in Edge and Safari can only stay on screen for a maximum of 5 seconds.'
- />
- </span>
- );
- }
-
- inputs.push(
- <div key='userNotificationLevelOption'>
- <label>
- <FormattedMessage
- id='user.settings.notifications.desktop'
- defaultMessage='Send desktop notifications'
- />
- </label>
- <br/>
- <div className='radio'>
- <label>
- <input
- id='desktopNotificationAllActivity'
- type='radio'
- name='desktopNotificationLevel'
- checked={activityRadio[0]}
- onChange={() => this.props.setParentState('desktopActivity', 'all')}
- />
- <FormattedMessage
- id='user.settings.notifications.allActivity'
- defaultMessage='For all activity'
- />
- </label>
- <br/>
- </div>
- <div className='radio'>
- <label>
- <input
- id='desktopNotificationMentions'
- type='radio'
- name='desktopNotificationLevel'
- checked={activityRadio[1]}
- onChange={() => this.props.setParentState('desktopActivity', 'mention')}
- />
- <FormattedMessage
- id='user.settings.notifications.onlyMentions'
- defaultMessage='Only for mentions and direct messages'
- />
- </label>
- <br/>
- </div>
- <div className='radio'>
- <label>
- <input
- id='desktopNotificationNever'
- type='radio'
- name='desktopNotificationLevel'
- checked={activityRadio[2]}
- onChange={() => this.props.setParentState('desktopActivity', 'none')}
- />
- <FormattedMessage
- id='user.settings.notifications.never'
- defaultMessage='Never'
- />
- </label>
- </div>
- <br/>
- <span>
- <FormattedMessage
- id='user.settings.notifications.info'
- defaultMessage='Desktop notifications are available on Edge, Firefox, Safari, Chrome and Mattermost Desktop Apps.'
- />
- </span>
- {soundSection}
- {durationSection}
- </div>
- );
-
- return (
- <SettingItemMax
- title={Utils.localizeMessage('user.settings.notifications.desktop.title', 'Desktop notifications')}
- extraInfo={extraInfo}
- inputs={inputs}
- submit={this.props.submit}
- server_error={this.props.error}
- updateSection={this.props.cancel}
- />
- );
- }
-
- buildMinimizedSetting() {
- let describe = '';
- if (this.props.activity === 'mention') {
- if (Utils.hasSoundOptions() && this.props.sound !== 'false') {
- if (this.props.duration === '0') { //eslint-disable-line no-lonely-if
- describe = (
- <FormattedMessage
- id='user.settings.notifications.desktop.mentionsSoundForever'
- defaultMessage='For mentions and direct messages, with sound, shown indefinitely'
- />
- );
- } else {
- describe = (
- <FormattedMessage
- id='user.settings.notifications.desktop.mentionsSoundTimed'
- defaultMessage='For mentions and direct messages, with sound, shown for {seconds} seconds'
- values={{
- seconds: this.props.duration
- }}
- />
- );
- }
- } else if (Utils.hasSoundOptions() && this.props.sound === 'false') {
- if (this.props.duration === '0') {
- describe = (
- <FormattedMessage
- id='user.settings.notifications.desktop.mentionsNoSoundForever'
- defaultMessage='For mentions and direct messages, without sound, shown indefinitely'
- />
- );
- } else {
- describe = (
- <FormattedMessage
- id='user.settings.notifications.desktop.mentionsNoSoundTimed'
- defaultMessage='For mentions and direct messages, without sound, shown for {seconds} seconds'
- values={{
- seconds: this.props.duration
- }}
- />
- );
- }
- } else {
- if (this.props.duration === '0') { //eslint-disable-line no-lonely-if
- describe = (
- <FormattedMessage
- id='user.settings.notifications.desktop.mentionsSoundHiddenForever'
- defaultMessage='For mentions and direct messages, shown indefinitely'
- />
- );
- } else {
- describe = (
- <FormattedMessage
- id='user.settings.notifications.desktop.mentionsSoundHiddenTimed'
- defaultMessage='For mentions and direct messages, shown for {seconds} seconds'
- values={{
- seconds: this.props.duration
- }}
- />
- );
- }
- }
- } else if (this.props.activity === 'none') {
- describe = (
- <FormattedMessage
- id='user.settings.notifications.off'
- defaultMessage='Off'
- />
- );
- } else {
- if (Utils.hasSoundOptions() && this.props.sound !== 'false') { //eslint-disable-line no-lonely-if
- if (this.props.duration === '0') { //eslint-disable-line no-lonely-if
- describe = (
- <FormattedMessage
- id='user.settings.notifications.desktop.allSoundForever'
- defaultMessage='For all activity, with sound, shown indefinitely'
- />
- );
- } else {
- describe = (
- <FormattedMessage
- id='user.settings.notifications.desktop.allSoundTimed'
- defaultMessage='For all activity, with sound, shown for {seconds} seconds'
- values={{
- seconds: this.props.duration
- }}
- />
- );
- }
- } else if (Utils.hasSoundOptions() && this.props.sound === 'false') {
- if (this.props.duration === '0') {
- describe = (
- <FormattedMessage
- id='user.settings.notifications.desktop.allNoSoundForever'
- defaultMessage='For all activity, without sound, shown indefinitely'
- />
- );
- } else {
- describe = (
- <FormattedMessage
- id='user.settings.notifications.desktop.allNoSoundTimed'
- defaultMessage='For all activity, without sound, shown for {seconds} seconds'
- values={{
- seconds: this.props.duration
- }}
- />
- );
- }
- } else {
- if (this.props.duration === '0') { //eslint-disable-line no-lonely-if
- describe = (
- <FormattedMessage
- id='user.settings.notifications.desktop.allSoundHiddenForever'
- defaultMessage='For all activity, shown indefinitely'
- />
- );
- } else {
- describe = (
- <FormattedMessage
- id='user.settings.notifications.desktop.allSoundHiddenTimed'
- defaultMessage='For all activity, shown for {seconds} seconds'
- values={{
- seconds: this.props.duration
- }}
- />
- );
- }
- }
- }
-
- const handleUpdateDesktopSection = function updateDesktopSection() {
- this.props.updateSection('desktop');
- }.bind(this);
-
- return (
- <SettingItemMin
- title={Utils.localizeMessage('user.settings.notifications.desktop.title', 'Desktop notifications')}
- describe={describe}
- updateSection={handleUpdateDesktopSection}
- />
- );
- }
-
- render() {
- if (this.props.active) {
- return this.buildMaximizedSetting();
- }
-
- return this.buildMinimizedSetting();
- }
-}
-
-DesktopNotificationSettings.propTypes = {
- activity: PropTypes.string.isRequired,
- sound: PropTypes.string.isRequired,
- duration: PropTypes.string.isRequired,
- updateSection: PropTypes.func,
- setParentState: PropTypes.func,
- submit: PropTypes.func,
- cancel: PropTypes.func,
- error: PropTypes.string,
- active: PropTypes.bool
-};
diff --git a/webapp/components/user_settings/email_notification_setting.jsx b/webapp/components/user_settings/email_notification_setting.jsx
deleted file mode 100644
index bfba794ca..000000000
--- a/webapp/components/user_settings/email_notification_setting.jsx
+++ /dev/null
@@ -1,262 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import {savePreference} from 'actions/user_actions.jsx';
-import {localizeMessage} from 'utils/utils.jsx';
-
-import {FormattedMessage} from 'react-intl';
-import SettingItemMin from 'components/setting_item_min.jsx';
-import SettingItemMax from 'components/setting_item_max.jsx';
-
-import {Preferences} from 'utils/constants.jsx';
-
-export default class EmailNotificationSetting extends React.Component {
- static propTypes = {
- activeSection: PropTypes.string.isRequired,
- updateSection: PropTypes.func.isRequired,
- enableEmail: PropTypes.bool.isRequired,
- emailInterval: PropTypes.number.isRequired,
- onSubmit: PropTypes.func.isRequired,
- onCancel: PropTypes.func.isRequired,
- serverError: PropTypes.string
- };
-
- constructor(props) {
- super(props);
-
- this.state = {
- enableEmail: props.enableEmail,
- emailInterval: props.emailInterval
- };
- }
-
- componentWillReceiveProps(nextProps) {
- if (nextProps.enableEmail !== this.props.enableEmail || nextProps.emailInterval !== this.props.emailInterval) {
- this.setState({
- enableEmail: nextProps.enableEmail,
- emailInterval: nextProps.emailInterval
- });
- }
- }
-
- handleChange = (enableEmail, emailInterval) => {
- this.setState({
- enableEmail,
- emailInterval
- });
- }
-
- handleSubmit = () => {
- // until the rest of the notification settings are moved to preferences, we have to do this separately
- savePreference(Preferences.CATEGORY_NOTIFICATIONS, Preferences.EMAIL_INTERVAL, this.state.emailInterval.toString());
-
- const {enableEmail} = this.state;
- this.props.onSubmit({enableEmail});
- }
-
- handleExpand = () => {
- this.props.updateSection('email');
- }
-
- handleCancel = (e) => {
- this.setState({
- enableEmail: this.props.enableEmail,
- emailInterval: this.props.emailInterval
- });
- this.props.onCancel(e);
- }
-
- render() {
- if (global.window.mm_config.SendEmailNotifications !== 'true' && this.props.activeSection === 'email') {
- const inputs = [];
-
- inputs.push(
- <div
- key='oauthEmailInfo'
- className='padding-top'
- >
- <FormattedMessage
- id='user.settings.notifications.email.disabled_long'
- defaultMessage='Email notifications have been disabled by your System Administrator.'
- />
- </div>
- );
-
- return (
- <SettingItemMax
- title={localizeMessage('user.settings.notifications.emailNotifications', 'Email notifications')}
- inputs={inputs}
- server_error={this.state.serverError}
- updateSection={this.handleCancel}
- />
- );
- }
-
- if (this.props.activeSection !== 'email') {
- let description;
-
- if (global.window.mm_config.SendEmailNotifications !== 'true') {
- description = (
- <FormattedMessage
- id='user.settings.notifications.email.disabled'
- defaultMessage='Disabled by System Administrator'
- />
- );
- } else if (this.props.enableEmail) {
- switch (this.state.emailInterval) {
- case Preferences.INTERVAL_IMMEDIATE:
- description = (
- <FormattedMessage
- id='user.settings.notifications.email.immediately'
- defaultMessage='Immediately'
- />
- );
- break;
- case Preferences.INTERVAL_HOUR:
- description = (
- <FormattedMessage
- id='user.settings.notifications.email.everyHour'
- defaultMessage='Every hour'
- />
- );
- break;
- default:
- description = (
- <FormattedMessage
- id='user.settings.notifications.email.everyXMinutes'
- defaultMessage='Every {count, plural, one {minute} other {{count, number} minutes}}'
- values={{count: this.state.emailInterval / 60}}
- />
- );
- }
- } else {
- description = (
- <FormattedMessage
- id='user.settings.notifications.email.never'
- defaultMessage='Never'
- />
- );
- }
-
- return (
- <SettingItemMin
- title={localizeMessage('user.settings.notifications.emailNotifications', 'Email notifications')}
- describe={description}
- updateSection={this.handleExpand}
- />
- );
- }
-
- let batchingOptions = null;
- let batchingInfo = null;
- if (global.window.mm_config.EnableEmailBatching === 'true') {
- batchingOptions = (
- <div>
- <div className='radio'>
- <label>
- <input
- id='emailNotificationMinutes'
- type='radio'
- name='emailNotifications'
- checked={this.state.emailInterval === Preferences.INTERVAL_FIFTEEN_MINUTES}
- onChange={() => this.handleChange('true', Preferences.INTERVAL_FIFTEEN_MINUTES)}
- />
- <FormattedMessage
- id='user.settings.notifications.email.everyXMinutes'
- defaultMessage='Every {count} minutes'
- values={{count: Preferences.INTERVAL_FIFTEEN_MINUTES / 60}}
- />
- </label>
- </div>
- <div className='radio'>
- <label>
- <input
- id='emailNotificationHour'
- type='radio'
- name='emailNotifications'
- checked={this.state.emailInterval === Preferences.INTERVAL_HOUR}
- onChange={() => this.handleChange('true', Preferences.INTERVAL_HOUR)}
- />
- <FormattedMessage
- id='user.settings.notifications.email.everyHour'
- defaultMessage='Every hour'
- />
- </label>
- </div>
- </div>
- );
-
- batchingInfo = (
- <FormattedMessage
- id='user.settings.notifications.emailBatchingInfo'
- defaultMessage='Notifications received over the time period selected are combined and sent in a single email.'
- />
- );
- }
-
- return (
- <SettingItemMax
- title={localizeMessage('user.settings.notifications.emailNotifications', 'Email notifications')}
- inputs={[
- <div key='userNotificationEmailOptions'>
- <label>
- <FormattedMessage
- id='user.settings.notifications.email.send'
- defaultMessage='Send email notifications'
- />
- </label>
- <div className='radio'>
- <label>
- <input
- id='emailNotificationImmediately'
- type='radio'
- name='emailNotifications'
- checked={this.state.emailInterval === Preferences.INTERVAL_IMMEDIATE}
- onChange={() => this.handleChange('true', Preferences.INTERVAL_IMMEDIATE)}
- />
- <FormattedMessage
- id='user.settings.notifications.email.immediately'
- defaultMessage='Immediately'
- />
- </label>
- </div>
- {batchingOptions}
- <div className='radio'>
- <label>
- <input
- id='emailNotificationNever'
- type='radio'
- name='emailNotifications'
- checked={this.state.emailInterval === Preferences.INTERVAL_NEVER}
- onChange={() => this.handleChange('false', Preferences.INTERVAL_NEVER)}
- />
- <FormattedMessage
- id='user.settings.notifications.email.never'
- defaultMessage='Never'
- />
- </label>
- </div>
- <br/>
- <div>
- <FormattedMessage
- id='user.settings.notifications.emailInfo'
- defaultMessage='Email notifications are sent for mentions and direct messages when you are offline or away from {siteName} for more than 5 minutes.'
- values={{
- siteName: global.window.mm_config.SiteName
- }}
- />
- {' '}
- {batchingInfo}
- </div>
- </div>
- ]}
- submit={this.handleSubmit}
- server_error={this.props.serverError}
- updateSection={this.handleCancel}
- />
- );
- }
-}
diff --git a/webapp/components/user_settings/import_theme_modal.jsx b/webapp/components/user_settings/import_theme_modal.jsx
deleted file mode 100644
index d732daa52..000000000
--- a/webapp/components/user_settings/import_theme_modal.jsx
+++ /dev/null
@@ -1,218 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import ModalStore from 'stores/modal_store.jsx';
-import {Modal} from 'react-bootstrap';
-
-import Constants from 'utils/constants.jsx';
-
-import {FormattedMessage} from 'react-intl';
-
-const ActionTypes = Constants.ActionTypes;
-
-import React from 'react';
-
-export default class ImportThemeModal extends React.Component {
- constructor(props) {
- super(props);
-
- this.updateShow = this.updateShow.bind(this);
- this.handleSubmit = this.handleSubmit.bind(this);
- this.handleChange = this.handleChange.bind(this);
-
- this.state = {
- value: '',
- inputError: '',
- show: false,
- callback: null
- };
- }
-
- componentDidMount() {
- ModalStore.addModalListener(ActionTypes.TOGGLE_IMPORT_THEME_MODAL, this.updateShow);
- }
-
- componentWillUnmount() {
- ModalStore.removeModalListener(ActionTypes.TOGGLE_IMPORT_THEME_MODAL, this.updateShow);
- }
-
- updateShow(show, args) {
- this.setState({
- show,
- callback: args.callback
- });
- }
-
- handleSubmit(e) {
- e.preventDefault();
-
- const text = this.state.value;
-
- if (!this.isInputValid(text)) {
- this.setState({
- inputError: (
- <FormattedMessage
- id='user.settings.import_theme.submitError'
- defaultMessage='Invalid format, please try copying and pasting in again.'
- />
- )
- });
- return;
- }
-
- const colors = text.split(',');
- const theme = {type: 'custom'};
-
- theme.sidebarBg = colors[0];
- theme.sidebarText = colors[5];
- theme.sidebarUnreadText = colors[5];
- theme.sidebarTextHoverBg = colors[4];
- theme.sidebarTextActiveBorder = colors[2];
- theme.sidebarTextActiveColor = colors[3];
- theme.sidebarHeaderBg = colors[1];
- theme.sidebarHeaderTextColor = colors[5];
- theme.onlineIndicator = colors[6];
- theme.awayIndicator = '#E0B333';
- theme.mentionBj = colors[7];
- theme.mentionColor = '#ffffff';
- theme.centerChannelBg = '#ffffff';
- theme.centerChannelColor = '#333333';
- theme.newMessageSeparator = '#F80';
- theme.linkColor = '#2389d7';
- theme.buttonBg = '#26a970';
- theme.buttonColor = '#ffffff';
- theme.mentionHighlightBg = '#fff2bb';
- theme.mentionHighlightLink = '#2f81b7';
- theme.codeTheme = 'github';
-
- this.state.callback(theme);
- this.setState({
- show: false,
- callback: null
- });
- }
-
- isInputValid(text) {
- if (text.length === 0) {
- return false;
- }
-
- if (text.indexOf(' ') !== -1) {
- return false;
- }
-
- if (text.length > 0 && text.indexOf(',') === -1) {
- return false;
- }
-
- if (text.length > 0) {
- const colors = text.split(',');
-
- if (colors.length !== 8) {
- return false;
- }
-
- for (let i = 0; i < colors.length; i++) {
- if (colors[i].length !== 7 && colors[i].length !== 4) {
- return false;
- }
-
- if (colors[i].charAt(0) !== '#') {
- return false;
- }
- }
- }
-
- return true;
- }
-
- handleChange(e) {
- const value = e.target.value;
- this.setState({value});
-
- if (this.isInputValid(value)) {
- this.setState({inputError: null});
- } else {
- this.setState({
- inputError: (
- <FormattedMessage
- id='user.settings.import_theme.submitError'
- defaultMessage='Invalid format, please try copying and pasting in again.'
- />
- )
- });
- }
- }
-
- render() {
- return (
- <span>
- <Modal
- show={this.state.show}
- onHide={() => this.setState({show: false})}
- >
- <Modal.Header closeButton={true}>
- <Modal.Title>
- <FormattedMessage
- id='user.settings.import_theme.importHeader'
- defaultMessage='Import Slack Theme'
- />
- </Modal.Title>
- </Modal.Header>
- <form
- role='form'
- className='form-horizontal'
- >
- <Modal.Body>
- <p>
- <FormattedMessage
- id='user.settings.import_theme.importBody'
- defaultMessage='To import a theme, go to a Slack team and look for "Preferences -> Sidebar Theme". Open the custom theme option, copy the theme color values and paste them here:'
- />
- </p>
- <div className='form-group less'>
- <div className='col-sm-12'>
- <input
- id='themeVector'
- type='text'
- className='form-control'
- value={this.state.value}
- onChange={this.handleChange}
- />
- <div className='input__help'>
- {this.state.inputError}
- </div>
- </div>
- </div>
- </Modal.Body>
- <Modal.Footer>
- <button
- id='cancelButton'
- type='button'
- className='btn btn-default'
- onClick={() => this.setState({show: false})}
- >
- <FormattedMessage
- id='user.settings.import_theme.cancel'
- defaultMessage='Cancel'
- />
- </button>
- <button
- id='submitButton'
- onClick={this.handleSubmit}
- type='submit'
- className='btn btn-primary'
- tabIndex='3'
- >
- <FormattedMessage
- id='user.settings.import_theme.submit'
- defaultMessage='Submit'
- />
- </button>
- </Modal.Footer>
- </form>
- </Modal>
- </span>
- );
- }
-}
diff --git a/webapp/components/user_settings/manage_languages.jsx b/webapp/components/user_settings/manage_languages.jsx
deleted file mode 100644
index f83733c12..000000000
--- a/webapp/components/user_settings/manage_languages.jsx
+++ /dev/null
@@ -1,135 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import SettingItemMax from '../setting_item_max.jsx';
-
-import * as I18n from 'i18n/i18n.jsx';
-import * as GlobalActions from 'actions/global_actions.jsx';
-import Constants from 'utils/constants.jsx';
-
-import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-import {updateUser} from 'actions/user_actions.jsx';
-import PropTypes from 'prop-types';
-import React from 'react';
-
-export default class ManageLanguage extends React.Component {
- constructor(props) {
- super(props);
-
- this.setLanguage = this.setLanguage.bind(this);
- this.changeLanguage = this.changeLanguage.bind(this);
- this.submitUser = this.submitUser.bind(this);
- this.state = {
- locale: props.locale
- };
- }
-
- setLanguage(e) {
- this.setState({locale: e.target.value});
- }
- changeLanguage(e) {
- e.preventDefault();
-
- this.submitUser({
- ...this.props.user,
- locale: this.state.locale
- });
- }
- submitUser(user) {
- updateUser(user, Constants.UserUpdateEvents.LANGUAGE,
- () => {
- GlobalActions.newLocalizationSelected(user.locale);
- },
- (err) => {
- let serverError;
- if (err.message) {
- serverError = err.message;
- } else {
- serverError = err;
- }
- this.setState({serverError});
- }
- );
- }
- render() {
- let serverError;
- if (this.state.serverError) {
- serverError = <label className='has-error'>{this.state.serverError}</label>;
- }
-
- const options = [];
- const locales = I18n.getLanguages();
-
- const languages = Object.keys(locales).map((l) => {
- return {
- value: locales[l].value,
- name: locales[l].name,
- order: locales[l].order
- };
- }).
- sort((a, b) => a.order - b.order);
-
- languages.forEach((lang) => {
- options.push(
- <option
- key={lang.value}
- value={lang.value}
- >
- {lang.name}
- </option>
- );
- });
-
- const input = (
- <div key='changeLanguage'>
- <br/>
- <label className='control-label'>
- <FormattedMessage
- id='user.settings.languages.change'
- defaultMessage='Change interface language'
- />
- </label>
- <div className='padding-top'>
- <select
- id='displayLanguage'
- ref='language'
- className='form-control'
- value={this.state.locale}
- onChange={this.setLanguage}
- >
- {options}
- </select>
- {serverError}
- </div>
- <div>
- <br/>
- <FormattedHTMLMessage
- id='user.settings.languages.promote'
- defaultMessage='Select which language Mattermost displays in the user interface.<br /><br />Would like to help with translations? Join the <a href="http://translate.mattermost.com/" target="_blank">Mattermost Translation Server</a> to contribute.'
- />
- </div>
- </div>
- );
-
- return (
- <SettingItemMax
- title={
- <FormattedMessage
- id='user.settings.display.language'
- defaultMessage='Language'
- />
- }
- width='medium'
- submit={this.changeLanguage}
- inputs={[input]}
- updateSection={this.props.updateSection}
- />
- );
- }
-}
-
-ManageLanguage.propTypes = {
- user: PropTypes.object.isRequired,
- locale: PropTypes.string.isRequired,
- updateSection: PropTypes.func.isRequired
-};
diff --git a/webapp/components/user_settings/premade_theme_chooser.jsx b/webapp/components/user_settings/premade_theme_chooser.jsx
deleted file mode 100644
index 1bb2c6be9..000000000
--- a/webapp/components/user_settings/premade_theme_chooser.jsx
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class PremadeThemeChooser extends React.Component {
- constructor(props) {
- super(props);
- this.state = {};
- }
- render() {
- const theme = this.props.theme;
-
- const premadeThemes = [];
- const allowedThemes = global.mm_config.AllowedThemes ? global.mm_config.AllowedThemes.split(',') : [];
- const hasAllowedThemes = allowedThemes.length > 1 || (allowedThemes[0] && allowedThemes[0].trim().length > 0);
-
- for (const k in Constants.THEMES) {
- if (Constants.THEMES.hasOwnProperty(k)) {
- if (hasAllowedThemes && allowedThemes.indexOf(k) < 0) {
- continue;
- }
-
- const premadeTheme = $.extend(true, {}, Constants.THEMES[k]);
-
- let activeClass = '';
- if (premadeTheme.type === theme.type) {
- activeClass = 'active';
- }
-
- premadeThemes.push(
- <div
- className='col-xs-6 col-sm-3 premade-themes'
- key={'premade-theme-key' + k}
- >
- <div
- className={activeClass}
- onClick={() => this.props.updateTheme(premadeTheme)}
- >
- <label>
- <img
- className='img-responsive'
- src={premadeTheme.image}
- />
- <div className='theme-label'>{Utils.toTitleCase(premadeTheme.type)}</div>
- </label>
- </div>
- </div>
- );
- }
- }
-
- return (
- <div className='row appearance-section'>
- <div className='clearfix'>
- {premadeThemes}
- </div>
- </div>
- );
- }
-}
-
-PremadeThemeChooser.propTypes = {
- theme: PropTypes.object.isRequired,
- updateTheme: PropTypes.func.isRequired
-};
diff --git a/webapp/components/user_settings/user_settings.jsx b/webapp/components/user_settings/user_settings.jsx
deleted file mode 100644
index 4ad4153ce..000000000
--- a/webapp/components/user_settings/user_settings.jsx
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import UserStore from 'stores/user_store.jsx';
-import * as utils from 'utils/utils.jsx';
-import NotificationsTab from './user_settings_notifications.jsx';
-import SecurityTab from './user_settings_security';
-import GeneralTab from './user_settings_general';
-import DisplayTab from './user_settings_display.jsx';
-import AdvancedTab from './user_settings_advanced.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class UserSettings extends React.Component {
- constructor(props) {
- super(props);
-
- this.onListenerChange = this.onListenerChange.bind(this);
-
- this.state = {user: UserStore.getCurrentUser()};
- }
-
- componentDidMount() {
- UserStore.addChangeListener(this.onListenerChange);
- }
-
- componentWillUnmount() {
- UserStore.removeChangeListener(this.onListenerChange);
- }
-
- onListenerChange() {
- var user = UserStore.getCurrentUser();
- if (!utils.areObjectsEqual(this.state.user, user)) {
- this.setState({user});
- }
- }
-
- render() {
- if (this.props.activeTab === 'general') {
- return (
- <div>
- <GeneralTab
- user={this.state.user}
- activeSection={this.props.activeSection}
- updateSection={this.props.updateSection}
- updateTab={this.props.updateTab}
- closeModal={this.props.closeModal}
- collapseModal={this.props.collapseModal}
- />
- </div>
- );
- } else if (this.props.activeTab === 'security') {
- return (
- <div>
- <SecurityTab
- user={this.state.user}
- activeSection={this.props.activeSection}
- updateSection={this.props.updateSection}
- updateTab={this.props.updateTab}
- closeModal={this.props.closeModal}
- collapseModal={this.props.collapseModal}
- setEnforceFocus={this.props.setEnforceFocus}
- />
- </div>
- );
- } else if (this.props.activeTab === 'notifications') {
- return (
- <div>
- <NotificationsTab
- user={this.state.user}
- activeSection={this.props.activeSection}
- updateSection={this.props.updateSection}
- updateTab={this.props.updateTab}
- closeModal={this.props.closeModal}
- collapseModal={this.props.collapseModal}
- />
- </div>
- );
- } else if (this.props.activeTab === 'display') {
- return (
- <div>
- <DisplayTab
- user={this.state.user}
- activeSection={this.props.activeSection}
- updateSection={this.props.updateSection}
- updateTab={this.props.updateTab}
- closeModal={this.props.closeModal}
- collapseModal={this.props.collapseModal}
- setEnforceFocus={this.props.setEnforceFocus}
- setRequireConfirm={this.props.setRequireConfirm}
- />
- </div>
- );
- } else if (this.props.activeTab === 'advanced') {
- return (
- <div>
- <AdvancedTab
- user={this.state.user}
- activeSection={this.props.activeSection}
- updateSection={this.props.updateSection}
- updateTab={this.props.updateTab}
- closeModal={this.props.closeModal}
- collapseModal={this.props.collapseModal}
- />
- </div>
- );
- }
-
- return <div/>;
- }
-}
-
-UserSettings.propTypes = {
- activeTab: PropTypes.string,
- activeSection: PropTypes.string,
- updateSection: PropTypes.func,
- updateTab: PropTypes.func,
- closeModal: PropTypes.func.isRequired,
- collapseModal: PropTypes.func.isRequired,
- setEnforceFocus: PropTypes.func.isRequired,
- setRequireConfirm: PropTypes.func.isRequired
-};
diff --git a/webapp/components/user_settings/user_settings_advanced.jsx b/webapp/components/user_settings/user_settings_advanced.jsx
deleted file mode 100644
index 0565310d5..000000000
--- a/webapp/components/user_settings/user_settings_advanced.jsx
+++ /dev/null
@@ -1,587 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import SettingItemMin from '../setting_item_min.jsx';
-import SettingItemMax from '../setting_item_max.jsx';
-
-import PreferenceStore from 'stores/preference_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-
-import Constants from 'utils/constants.jsx';
-const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES;
-import * as Utils from 'utils/utils.jsx';
-
-import {savePreferences} from 'actions/user_actions.jsx';
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import {FormattedMessage} from 'react-intl';
-
-export default class AdvancedSettingsDisplay extends React.Component {
- constructor(props) {
- super(props);
-
- this.getStateFromStores = this.getStateFromStores.bind(this);
- this.updateSection = this.updateSection.bind(this);
- this.updateSetting = this.updateSetting.bind(this);
- this.toggleFeature = this.toggleFeature.bind(this);
- this.saveEnabledFeatures = this.saveEnabledFeatures.bind(this);
-
- this.renderFormattingSection = this.renderFormattingSection.bind(this);
- this.renderJoinLeaveSection = this.renderJoinLeaveSection.bind(this);
-
- this.state = this.getStateFromStores();
- }
-
- getStateFromStores() {
- let preReleaseFeaturesKeys = Object.keys(PreReleaseFeatures);
- const advancedSettings = PreferenceStore.getCategory(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS);
- const settings = {
- send_on_ctrl_enter: PreferenceStore.get(
- Constants.Preferences.CATEGORY_ADVANCED_SETTINGS,
- 'send_on_ctrl_enter',
- 'false'
- ),
- formatting: PreferenceStore.get(
- Constants.Preferences.CATEGORY_ADVANCED_SETTINGS,
- 'formatting',
- 'true'
- ),
- join_leave: PreferenceStore.get(
- Constants.Preferences.CATEGORY_ADVANCED_SETTINGS,
- 'join_leave',
- 'true'
- )
- };
-
- const webrtcEnabled = global.mm_config.EnableWebrtc === 'true';
- const linkPreviewsEnabled = global.mm_config.EnableLinkPreviews === 'true';
-
- if (!webrtcEnabled) {
- preReleaseFeaturesKeys = preReleaseFeaturesKeys.filter((f) => f !== 'WEBRTC_PREVIEW');
- }
-
- if (!linkPreviewsEnabled) {
- preReleaseFeaturesKeys = preReleaseFeaturesKeys.filter((f) => f !== 'EMBED_PREVIEW');
- }
-
- let enabledFeatures = 0;
- for (const [name, value] of advancedSettings) {
- for (const key of preReleaseFeaturesKeys) {
- const feature = PreReleaseFeatures[key];
-
- if (name === Constants.FeatureTogglePrefix + feature.label) {
- settings[name] = value;
-
- if (value === 'true') {
- enabledFeatures += 1;
- }
- }
- }
- }
-
- return {preReleaseFeatures: PreReleaseFeatures,
- settings,
- preReleaseFeaturesKeys,
- enabledFeatures
- };
- }
-
- updateSetting(setting, value) {
- const settings = this.state.settings;
- settings[setting] = value;
- this.setState(settings);
- }
-
- toggleFeature(feature, checked) {
- const settings = this.state.settings;
- settings[Constants.FeatureTogglePrefix + feature] = String(checked);
-
- let enabledFeatures = 0;
- Object.keys(this.state.settings).forEach((setting) => {
- if (setting.lastIndexOf(Constants.FeatureTogglePrefix) === 0 && this.state.settings[setting] === 'true') {
- enabledFeatures++;
- }
- });
-
- this.setState({settings, enabledFeatures});
- }
-
- saveEnabledFeatures() {
- const features = [];
- Object.keys(this.state.settings).forEach((setting) => {
- if (setting.lastIndexOf(Constants.FeatureTogglePrefix) === 0) {
- features.push(setting);
- }
- });
-
- this.handleSubmit(features);
- }
-
- handleSubmit(settings) {
- const preferences = [];
- const userId = UserStore.getCurrentId();
-
- // this should be refactored so we can actually be certain about what type everything is
- (Array.isArray(settings) ? settings : [settings]).forEach((setting) => {
- preferences.push({
- user_id: userId,
- category: Constants.Preferences.CATEGORY_ADVANCED_SETTINGS,
- name: setting,
- value: this.state.settings[setting]
- });
- });
-
- savePreferences(
- preferences,
- () => {
- this.updateSection('');
- }
- );
- }
-
- updateSection(section) {
- if ($('.section-max').length) {
- $('.settings-modal .modal-body').scrollTop(0).perfectScrollbar('update');
- }
- if (!section) {
- this.setState(this.getStateFromStores());
- }
- this.props.updateSection(section);
- }
-
- renderOnOffLabel(enabled) {
- if (enabled === 'false') {
- return (
- <FormattedMessage
- id='user.settings.advance.off'
- defaultMessage='Off'
- />
- );
- }
-
- return (
- <FormattedMessage
- id='user.settings.advance.on'
- defaultMessage='On'
- />
- );
- }
-
- renderFormattingSection() {
- if (this.props.activeSection === 'formatting') {
- return (
- <SettingItemMax
- title={
- <FormattedMessage
- id='user.settings.advance.formattingTitle'
- defaultMessage='Enable Post Formatting'
- />
- }
- inputs={[
- <div key='formattingSetting'>
- <div className='radio'>
- <label>
- <input
- id='postFormattingOn'
- type='radio'
- name='formatting'
- checked={this.state.settings.formatting !== 'false'}
- onChange={this.updateSetting.bind(this, 'formatting', 'true')}
- />
- <FormattedMessage
- id='user.settings.advance.on'
- defaultMessage='On'
- />
- </label>
- <br/>
- </div>
- <div className='radio'>
- <label>
- <input
- id='postFormattingOff'
- type='radio'
- name='formatting'
- checked={this.state.settings.formatting === 'false'}
- onChange={this.updateSetting.bind(this, 'formatting', 'false')}
- />
- <FormattedMessage
- id='user.settings.advance.off'
- defaultMessage='Off'
- />
- </label>
- <br/>
- </div>
- <div>
- <br/>
- <FormattedMessage
- id='user.settings.advance.formattingDesc'
- defaultMessage='If enabled, posts will be formatted to create links, show emoji, style the text, and add line breaks. By default, this setting is enabled. Changing this setting requires the page to be refreshed.'
- />
- </div>
- </div>
- ]}
- submit={() => this.handleSubmit('formatting')}
- server_error={this.state.serverError}
- updateSection={(e) => {
- this.updateSection('');
- e.preventDefault();
- }}
- />
- );
- }
-
- return (
- <SettingItemMin
- title={
- <FormattedMessage
- id='user.settings.advance.formattingTitle'
- defaultMessage='Enable Post Formatting'
- />
- }
- describe={this.renderOnOffLabel(this.state.settings.formatting)}
- updateSection={() => this.props.updateSection('formatting')}
- />
- );
- }
-
- renderJoinLeaveSection() {
- if (window.mm_config.BuildEnterpriseReady === 'true' && window.mm_license && window.mm_license.IsLicensed === 'true') {
- if (this.props.activeSection === 'join_leave') {
- return (
- <SettingItemMax
- title={
- <FormattedMessage
- id='user.settings.advance.joinLeaveTitle'
- defaultMessage='Enable Join/Leave Messages'
- />
- }
- inputs={[
- <div key='joinLeaveSetting'>
- <div className='radio'>
- <label>
- <input
- id='joinLeaveOn'
- type='radio'
- name='join_leave'
- checked={this.state.settings.join_leave !== 'false'}
- onChange={this.updateSetting.bind(this, 'join_leave', 'true')}
- />
- <FormattedMessage
- id='user.settings.advance.on'
- defaultMessage='On'
- />
- </label>
- <br/>
- </div>
- <div className='radio'>
- <label>
- <input
- id='joinLeaveOff'
- type='radio'
- name='join_leave'
- checked={this.state.settings.join_leave === 'false'}
- onChange={this.updateSetting.bind(this, 'join_leave', 'false')}
- />
- <FormattedMessage
- id='user.settings.advance.off'
- defaultMessage='Off'
- />
- </label>
- <br/>
- </div>
- <div>
- <br/>
- <FormattedMessage
- id='user.settings.advance.joinLeaveDesc'
- defaultMessage='When "On", System Messages saying a user has joined or left a channel will be visible. When "Off", the System Messages about joining or leaving a channel will be hidden. A message will still show up when you are added to a channel, so you can receive a notification.'
- />
- </div>
- </div>
- ]}
- submit={() => this.handleSubmit('join_leave')}
- server_error={this.state.serverError}
- updateSection={(e) => {
- this.updateSection('');
- e.preventDefault();
- }}
- />
- );
- }
-
- return (
- <SettingItemMin
- title={
- <FormattedMessage
- id='user.settings.advance.joinLeaveTitle'
- defaultMessage='Enable Join/Leave Messages'
- />
- }
- describe={this.renderOnOffLabel(this.state.settings.join_leave)}
- updateSection={() => this.props.updateSection('join_leave')}
- />
- );
- }
-
- return null;
- }
-
- renderFeatureLabel(feature) {
- switch (feature) {
- case 'MARKDOWN_PREVIEW':
- return (
- <FormattedMessage
- id='user.settings.advance.markdown_preview'
- defaultMessage='Show markdown preview option in message input box'
- />
- );
- case 'EMBED_PREVIEW':
- return (
- <FormattedMessage
- id='user.settings.advance.embed_preview'
- defaultMessage='For the first web link in a message, display a preview of website content below the message, if available'
- />
- );
- case 'WEBRTC_PREVIEW':
- return (
- <FormattedMessage
- id='user.settings.advance.webrtc_preview'
- defaultMessage='Enable the ability to make and receive one-on-one WebRTC calls'
- />
- );
- default:
- return null;
- }
- }
-
- render() {
- const serverError = this.state.serverError || null;
- let ctrlSendSection;
-
- if (this.props.activeSection === 'advancedCtrlSend') {
- const ctrlSendActive = [
- this.state.settings.send_on_ctrl_enter === 'true',
- this.state.settings.send_on_ctrl_enter === 'false'
- ];
-
- const inputs = [
- <div key='ctrlSendSetting'>
- <div className='radio'>
- <label>
- <input
- id='ctrlSendOn'
- type='radio'
- name='sendOnCtrlEnter'
- checked={ctrlSendActive[0]}
- onChange={this.updateSetting.bind(this, 'send_on_ctrl_enter', 'true')}
- />
- <FormattedMessage
- id='user.settings.advance.on'
- defaultMessage='On'
- />
- </label>
- <br/>
- </div>
- <div className='radio'>
- <label>
- <input
- id='ctrlSendOff'
- type='radio'
- name='sendOnCtrlEnter'
- checked={ctrlSendActive[1]}
- onChange={this.updateSetting.bind(this, 'send_on_ctrl_enter', 'false')}
- />
- <FormattedMessage
- id='user.settings.advance.off'
- defaultMessage='Off'
- />
- </label>
- <br/>
- </div>
- <div>
- <br/>
- <FormattedMessage
- id='user.settings.advance.sendDesc'
- defaultMessage='If enabled ENTER inserts a new line and CTRL+ENTER submits the message.'
- />
- </div>
- </div>
- ];
- ctrlSendSection = (
- <SettingItemMax
- title={
- <FormattedMessage
- id='user.settings.advance.sendTitle'
- defaultMessage='Send messages on CTRL+ENTER'
- />
- }
- inputs={inputs}
- submit={() => this.handleSubmit('send_on_ctrl_enter')}
- server_error={serverError}
- updateSection={(e) => {
- this.updateSection('');
- e.preventDefault();
- }}
- />
- );
- } else {
- ctrlSendSection = (
- <SettingItemMin
- title={
- <FormattedMessage
- id='user.settings.advance.sendTitle'
- defaultMessage='Send messages on CTRL+ENTER'
- />
- }
- describe={this.renderOnOffLabel(this.state.settings.send_on_ctrl_enter)}
- updateSection={() => this.props.updateSection('advancedCtrlSend')}
- />
- );
- }
-
- const formattingSection = this.renderFormattingSection();
- let formattingSectionDivider = null;
- if (formattingSection) {
- formattingSectionDivider = <div className='divider-light'/>;
- }
-
- const displayJoinLeaveSection = this.renderJoinLeaveSection();
- let displayJoinLeaveSectionDivider = null;
- if (displayJoinLeaveSection) {
- displayJoinLeaveSectionDivider = <div className='divider-light'/>;
- }
-
- let previewFeaturesSection;
- let previewFeaturesSectionDivider;
- if (this.state.preReleaseFeaturesKeys.length > 0) {
- previewFeaturesSectionDivider = (
- <div className='divider-light'/>
- );
-
- if (this.props.activeSection === 'advancedPreviewFeatures') {
- const inputs = [];
-
- this.state.preReleaseFeaturesKeys.forEach((key) => {
- const feature = this.state.preReleaseFeatures[key];
- inputs.push(
- <div key={'advancedPreviewFeatures_' + feature.label}>
- <div className='checkbox'>
- <label>
- <input
- id={'advancedPreviewFeatures' + feature.label}
- type='checkbox'
- checked={this.state.settings[Constants.FeatureTogglePrefix + feature.label] === 'true'}
- onChange={(e) => {
- this.toggleFeature(feature.label, e.target.checked);
- }}
- />
- {this.renderFeatureLabel(key)}
- </label>
- </div>
- </div>
- );
- });
-
- inputs.push(
- <div key='advancedPreviewFeatures_helptext'>
- <br/>
- <FormattedMessage
- id='user.settings.advance.preReleaseDesc'
- defaultMessage="Check any pre-released features you'd like to preview. You may also need to refresh the page before the setting will take effect."
- />
- </div>
- );
- previewFeaturesSection = (
- <SettingItemMax
- title={
- <FormattedMessage
- id='user.settings.advance.preReleaseTitle'
- defaultMessage='Preview pre-release features'
- />
- }
- inputs={inputs}
- submit={this.saveEnabledFeatures}
- server_error={serverError}
- updateSection={(e) => {
- this.updateSection('');
- e.preventDefault();
- }}
- />
- );
- } else {
- previewFeaturesSection = (
- <SettingItemMin
- title={Utils.localizeMessage('user.settings.advance.preReleaseTitle', 'Preview pre-release features')}
- describe={
- <FormattedMessage
- id='user.settings.advance.enabledFeatures'
- defaultMessage='{count, number} {count, plural, one {Feature} other {Features}} Enabled'
- values={{count: this.state.enabledFeatures}}
- />
- }
- updateSection={() => this.props.updateSection('advancedPreviewFeatures')}
- />
- );
- }
- }
-
- return (
- <div>
- <div className='modal-header'>
- <button
- id='closeButton'
- type='button'
- className='close'
- data-dismiss='modal'
- aria-label='Close'
- onClick={this.props.closeModal}
- >
- <span aria-hidden='true'>{'×'}</span>
- </button>
- <h4
- className='modal-title'
- ref='title'
- >
- <div className='modal-back'>
- <i
- className='fa fa-angle-left'
- onClick={this.props.collapseModal}
- />
- </div>
- <FormattedMessage
- id='user.settings.advance.title'
- defaultMessage='Advanced Settings'
- />
- </h4>
- </div>
- <div className='user-settings'>
- <h3 className='tab-header'>
- <FormattedMessage
- id='user.settings.advance.title'
- defaultMessage='Advanced Settings'
- />
- </h3>
- <div className='divider-dark first'/>
- {ctrlSendSection}
- {formattingSectionDivider}
- {formattingSection}
- {displayJoinLeaveSectionDivider}
- {displayJoinLeaveSection}
- {previewFeaturesSectionDivider}
- {previewFeaturesSection}
- <div className='divider-dark'/>
- </div>
- </div>
- );
- }
-}
-
-AdvancedSettingsDisplay.propTypes = {
- user: PropTypes.object,
- updateSection: PropTypes.func,
- updateTab: PropTypes.func,
- activeSection: PropTypes.string,
- closeModal: PropTypes.func.isRequired,
- collapseModal: PropTypes.func.isRequired
-};
diff --git a/webapp/components/user_settings/user_settings_display.jsx b/webapp/components/user_settings/user_settings_display.jsx
deleted file mode 100644
index f10b29900..000000000
--- a/webapp/components/user_settings/user_settings_display.jsx
+++ /dev/null
@@ -1,673 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import SettingItemMin from '../setting_item_min.jsx';
-import SettingItemMax from '../setting_item_max.jsx';
-import ManageLanguages from './manage_languages.jsx';
-import ThemeSetting from './user_settings_theme.jsx';
-
-import PreferenceStore from 'stores/preference_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-import * as Utils from 'utils/utils.jsx';
-import * as I18n from 'i18n/i18n.jsx';
-import {savePreferences} from 'actions/user_actions.jsx';
-
-import Constants from 'utils/constants.jsx';
-const Preferences = Constants.Preferences;
-
-import {FormattedMessage} from 'react-intl';
-
-function getDisplayStateFromStores() {
- return {
- militaryTime: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', 'false'),
- channelDisplayMode: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.CHANNEL_DISPLAY_MODE, Preferences.CHANNEL_DISPLAY_MODE_DEFAULT),
- messageDisplay: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT),
- collapseDisplay: PreferenceStore.get(Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.COLLAPSE_DISPLAY, Preferences.COLLAPSE_DISPLAY_DEFAULT)
- };
-}
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-export default class UserSettingsDisplay extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleSubmit = this.handleSubmit.bind(this);
- this.handleClockRadio = this.handleClockRadio.bind(this);
- this.updateSection = this.updateSection.bind(this);
- this.updateState = this.updateState.bind(this);
- this.createCollapseSection = this.createCollapseSection.bind(this);
-
- this.state = getDisplayStateFromStores();
- }
-
- handleSubmit() {
- const userId = UserStore.getCurrentId();
-
- const timePreference = {
- user_id: userId,
- category: Preferences.CATEGORY_DISPLAY_SETTINGS,
- name: 'use_military_time',
- value: this.state.militaryTime
- };
-
- const channelDisplayModePreference = {
- user_id: userId,
- category: Preferences.CATEGORY_DISPLAY_SETTINGS,
- name: Preferences.CHANNEL_DISPLAY_MODE,
- value: this.state.channelDisplayMode
- };
- const messageDisplayPreference = {
- user_id: userId,
- category: Preferences.CATEGORY_DISPLAY_SETTINGS,
- name: Preferences.MESSAGE_DISPLAY,
- value: this.state.messageDisplay
- };
- const collapseDisplayPreference = {
- user_id: userId,
- category: Preferences.CATEGORY_DISPLAY_SETTINGS,
- name: Preferences.COLLAPSE_DISPLAY,
- value: this.state.collapseDisplay
- };
-
- savePreferences([timePreference, channelDisplayModePreference, messageDisplayPreference, collapseDisplayPreference],
- () => {
- this.updateSection('');
- }
- );
- }
-
- handleClockRadio(militaryTime) {
- this.setState({militaryTime});
- }
-
- handleChannelDisplayModeRadio(channelDisplayMode) {
- this.setState({channelDisplayMode});
- }
-
- handlemessageDisplayRadio(messageDisplay) {
- this.setState({messageDisplay});
- }
-
- handleCollapseRadio(collapseDisplay) {
- this.setState({collapseDisplay});
- }
-
- updateSection(section) {
- if ($('.section-max').length) {
- $('.settings-modal .modal-body').scrollTop(0).perfectScrollbar('update');
- }
- this.updateState();
- this.props.updateSection(section);
- }
-
- updateState() {
- const newState = getDisplayStateFromStores();
- if (!Utils.areObjectsEqual(newState, this.state)) {
- this.setState(newState);
- }
- }
-
- createCollapseSection() {
- if (this.props.activeSection === 'collapse') {
- const collapseFormat = [false, false];
- if (this.state.collapseDisplay === 'false') {
- collapseFormat[0] = true;
- } else {
- collapseFormat[1] = true;
- }
-
- const handleUpdateCollapseSection = (e) => {
- this.updateSection('');
- e.preventDefault();
- };
-
- const inputs = [
- <div key='userDisplayCollapseOptions'>
- <div className='radio'>
- <label>
- <input
- id='collapseFormat'
- type='radio'
- name='collapseFormat'
- checked={collapseFormat[0]}
- onChange={this.handleCollapseRadio.bind(this, 'false')}
- />
- <FormattedMessage
- id='user.settings.display.collapseOn'
- defaultMessage='On'
- />
- </label>
- <br/>
- </div>
- <div className='radio'>
- <label>
- <input
- id='collapseFormatOff'
- type='radio'
- name='collapseFormat'
- checked={collapseFormat[1]}
- onChange={this.handleCollapseRadio.bind(this, 'true')}
- />
- <FormattedMessage
- id='user.settings.display.collapseOff'
- defaultMessage='Off'
- />
- </label>
- <br/>
- </div>
- <div>
- <br/>
- <FormattedMessage
- id='user.settings.display.collapseDesc'
- defaultMessage='Set whether previews of image links show as expanded or collapsed by default. This setting can also be controlled using the slash commands /expand and /collapse.'
- />
- </div>
- </div>
- ];
-
- return (
- <SettingItemMax
- title={
- <FormattedMessage
- id='user.settings.display.collapseDisplay'
- defaultMessage='Default appearance of image link previews'
- />
- }
- inputs={inputs}
- submit={this.handleSubmit}
- server_error={this.state.serverError}
- updateSection={handleUpdateCollapseSection}
- />
- );
- }
-
- let describe;
- if (this.state.collapseDisplay === 'false') {
- describe = (
- <FormattedMessage
- id='user.settings.display.collapseOn'
- defaultMessage='Expanded'
- />
- );
- } else {
- describe = (
- <FormattedMessage
- id='user.settings.display.collapseOff'
- defaultMessage='Collapsed'
- />
- );
- }
-
- const handleUpdateCollapseSection = () => {
- this.props.updateSection('collapse');
- };
-
- return (
- <SettingItemMin
- title={
- <FormattedMessage
- id='user.settings.display.collapseDisplay'
- defaultMessage='Default appearance of image link previews'
- />
- }
- describe={describe}
- updateSection={handleUpdateCollapseSection}
- />
- );
- }
-
- render() {
- const serverError = this.state.serverError || null;
- let clockSection;
- let channelDisplayModeSection;
- let languagesSection;
- let messageDisplaySection;
-
- const collapseSection = this.createCollapseSection();
-
- if (this.props.activeSection === 'clock') {
- const clockFormat = [false, false];
- if (this.state.militaryTime === 'true') {
- clockFormat[1] = true;
- } else {
- clockFormat[0] = true;
- }
-
- const handleUpdateClockSection = (e) => {
- this.updateSection('');
- e.preventDefault();
- };
-
- const inputs = [
- <div key='userDisplayClockOptions'>
- <div className='radio'>
- <label>
- <input
- id='clockFormat12h'
- type='radio'
- name='clockFormat'
- checked={clockFormat[0]}
- onChange={this.handleClockRadio.bind(this, 'false')}
- />
- <FormattedMessage
- id='user.settings.display.normalClock'
- defaultMessage='12-hour clock (example: 4:00 PM)'
- />
- </label>
- <br/>
- </div>
- <div className='radio'>
- <label>
- <input
- id='clockFormat24h'
- type='radio'
- name='clockFormat'
- checked={clockFormat[1]}
- onChange={this.handleClockRadio.bind(this, 'true')}
- />
- <FormattedMessage
- id='user.settings.display.militaryClock'
- defaultMessage='24-hour clock (example: 16:00)'
- />
- </label>
- <br/>
- </div>
- <div>
- <br/>
- <FormattedMessage
- id='user.settings.display.preferTime'
- defaultMessage='Select how you prefer time displayed.'
- />
- </div>
- </div>
- ];
-
- clockSection = (
- <SettingItemMax
- title={
- <FormattedMessage
- id='user.settings.display.clockDisplay'
- defaultMessage='Clock Display'
- />
- }
- inputs={inputs}
- submit={this.handleSubmit}
- server_error={serverError}
- updateSection={handleUpdateClockSection}
- />
- );
- } else {
- let describe;
- if (this.state.militaryTime === 'true') {
- describe = (
- <FormattedMessage
- id='user.settings.display.militaryClock'
- defaultMessage='24-hour clock (example: 16:00)'
- />
- );
- } else {
- describe = (
- <FormattedMessage
- id='user.settings.display.normalClock'
- defaultMessage='12-hour clock (example: 4:00 PM)'
- />
- );
- }
-
- const handleUpdateClockSection = () => {
- this.props.updateSection('clock');
- };
-
- clockSection = (
- <SettingItemMin
- title={
- <FormattedMessage
- id='user.settings.display.clockDisplay'
- defaultMessage='Clock Display'
- />
- }
- describe={describe}
- updateSection={handleUpdateClockSection}
- />
- );
- }
-
- if (this.props.activeSection === Preferences.MESSAGE_DISPLAY) {
- const messageDisplay = [false, false];
- if (this.state.messageDisplay === Preferences.MESSAGE_DISPLAY_CLEAN) {
- messageDisplay[0] = true;
- } else {
- messageDisplay[1] = true;
- }
-
- const inputs = [
- <div key='userDisplayNameOptions'>
- <div className='radio'>
- <label>
- <input
- id='messageFormatStandard'
- type='radio'
- name='messageDisplay'
- checked={messageDisplay[0]}
- onChange={this.handlemessageDisplayRadio.bind(this, Preferences.MESSAGE_DISPLAY_CLEAN)}
- />
- <FormattedMessage
- id='user.settings.display.messageDisplayClean'
- defaultMessage='Standard'
- />
- {': '}
- <span className='font-weight--normal'>
- <FormattedMessage
- id='user.settings.display.messageDisplayCleanDes'
- defaultMessage='Easy to scan and read.'
- />
- </span>
- </label>
- <br/>
- </div>
- <div className='radio'>
- <label>
- <input
- id='messageFormatCompact'
- type='radio'
- name='messageDisplay'
- checked={messageDisplay[1]}
- onChange={this.handlemessageDisplayRadio.bind(this, Preferences.MESSAGE_DISPLAY_COMPACT)}
- />
- <FormattedMessage
- id='user.settings.display.messageDisplayCompact'
- defaultMessage='Compact'
- />
- {': '}
- <span className='font-weight--normal'>
- <FormattedMessage
- id='user.settings.display.messageDisplayCompactDes'
- defaultMessage='Fit as many messages on the screen as we can.'
- />
- </span>
- </label>
- <br/>
- </div>
- <div>
- <br/>
- <FormattedMessage
- id='user.settings.display.messageDisplayDescription'
- defaultMessage='Select how messages in a channel should be displayed.'
- />
- </div>
- </div>
- ];
-
- messageDisplaySection = (
- <SettingItemMax
- title={
- <FormattedMessage
- id='user.settings.display.messageDisplayTitle'
- defaultMessage='Message Display'
- />
- }
- inputs={inputs}
- submit={this.handleSubmit}
- server_error={serverError}
- updateSection={(e) => {
- this.updateSection('');
- e.preventDefault();
- }}
- />
- );
- } else {
- let describe;
- if (this.state.messageDisplay === Preferences.MESSAGE_DISPLAY_CLEAN) {
- describe = (
- <FormattedMessage
- id='user.settings.display.messageDisplayClean'
- defaultMessage='Standard'
- />
- );
- } else {
- describe = (
- <FormattedMessage
- id='user.settings.display.messageDisplayCompact'
- defaultMessage='Compact'
- />
- );
- }
-
- messageDisplaySection = (
- <SettingItemMin
- title={
- <FormattedMessage
- id='user.settings.display.messageDisplayTitle'
- defaultMessage='Message Display'
- />
- }
- describe={describe}
- updateSection={() => {
- this.props.updateSection(Preferences.MESSAGE_DISPLAY);
- }}
- />
- );
- }
-
- if (this.props.activeSection === Preferences.CHANNEL_DISPLAY_MODE) {
- const channelDisplayMode = [false, false];
- if (this.state.channelDisplayMode === Preferences.CHANNEL_DISPLAY_MODE_FULL_SCREEN) {
- channelDisplayMode[0] = true;
- } else {
- channelDisplayMode[1] = true;
- }
-
- const inputs = [
- <div key='userDisplayNameOptions'>
- <div className='radio'>
- <label>
- <input
- id='channelDisplayFormatFullScreen'
- type='radio'
- name='channelDisplayMode'
- checked={channelDisplayMode[0]}
- onChange={this.handleChannelDisplayModeRadio.bind(this, Preferences.CHANNEL_DISPLAY_MODE_FULL_SCREEN)}
- />
- <FormattedMessage
- id='user.settings.display.fullScreen'
- defaultMessage='Full width'
- />
- </label>
- <br/>
- </div>
- <div className='radio'>
- <label>
- <input
- id='channelDisplayFormatCentered'
- type='radio'
- name='channelDisplayMode'
- checked={channelDisplayMode[1]}
- onChange={this.handleChannelDisplayModeRadio.bind(this, Preferences.CHANNEL_DISPLAY_MODE_CENTERED)}
- />
- <FormattedMessage
- id='user.settings.display.fixedWidthCentered'
- defaultMessage='Fixed width, centered'
- />
- </label>
- <br/>
- </div>
- <div>
- <br/>
- <FormattedMessage
- id='user.settings.display.channeldisplaymode'
- defaultMessage='Select the width of the center channel.'
- />
- </div>
- </div>
- ];
-
- channelDisplayModeSection = (
- <SettingItemMax
- title={
- <FormattedMessage
- id='user.settings.display.channelDisplayTitle'
- defaultMessage='Channel Display Mode'
- />
- }
- inputs={inputs}
- submit={this.handleSubmit}
- server_error={serverError}
- updateSection={(e) => {
- this.updateSection('');
- e.preventDefault();
- }}
- />
- );
- } else {
- let describe;
- if (this.state.channelDisplayMode === Preferences.CHANNEL_DISPLAY_MODE_FULL_SCREEN) {
- describe = (
- <FormattedMessage
- id='user.settings.display.fullScreen'
- defaultMessage='Full width'
- />
- );
- } else {
- describe = (
- <FormattedMessage
- id='user.settings.display.fixedWidthCentered'
- defaultMessage='Fixed width, centered'
- />
- );
- }
-
- channelDisplayModeSection = (
- <SettingItemMin
- title={
- <FormattedMessage
- id='user.settings.display.channelDisplayTitle'
- defaultMessage='Channel Display Mode'
- />
- }
- describe={describe}
- updateSection={() => {
- this.props.updateSection(Preferences.CHANNEL_DISPLAY_MODE);
- }}
- />
- );
- }
-
- let userLocale = this.props.user.locale;
- if (this.props.activeSection === 'languages') {
- if (!I18n.isLanguageAvailable(userLocale)) {
- userLocale = global.window.mm_config.DefaultClientLocale;
- }
- languagesSection = (
- <ManageLanguages
- user={this.props.user}
- locale={userLocale}
- updateSection={(e) => {
- this.updateSection('');
- e.preventDefault();
- }}
- />
- );
- } else {
- let locale;
- if (I18n.isLanguageAvailable(userLocale)) {
- locale = I18n.getLanguageInfo(userLocale).name;
- } else {
- locale = I18n.getLanguageInfo(global.window.mm_config.DefaultClientLocale).name;
- }
-
- languagesSection = (
- <SettingItemMin
- title={
- <FormattedMessage
- id='user.settings.display.language'
- defaultMessage='Language'
- />
- }
- width='medium'
- describe={locale}
- updateSection={() => {
- this.updateSection('languages');
- }}
- />
- );
- }
-
- let themeSection;
- if (global.mm_config.EnableThemeSelection !== 'false') {
- themeSection = (
- <ThemeSetting
- selected={this.props.activeSection === 'theme'}
- updateSection={this.updateSection}
- setRequireConfirm={this.props.setRequireConfirm}
- setEnforceFocus={this.props.setEnforceFocus}
- />
- );
- }
-
- return (
- <div>
- <div className='modal-header'>
- <button
- id='closeButton'
- type='button'
- className='close'
- data-dismiss='modal'
- aria-label='Close'
- onClick={this.props.closeModal}
- >
- <span aria-hidden='true'>{'×'}</span>
- </button>
- <h4
- className='modal-title'
- ref='title'
- >
- <div className='modal-back'>
- <i
- className='fa fa-angle-left'
- onClick={this.props.collapseModal}
- />
- </div>
- <FormattedMessage
- id='user.settings.display.title'
- defaultMessage='Display Settings'
- />
- </h4>
- </div>
- <div className='user-settings'>
- <h3 className='tab-header'>
- <FormattedMessage
- id='user.settings.display.title'
- defaultMessage='Display Settings'
- />
- </h3>
- <div className='divider-dark first'/>
- {themeSection}
- <div className='divider-dark'/>
- {clockSection}
- <div className='divider-dark'/>
- {collapseSection}
- <div className='divider-dark'/>
- {messageDisplaySection}
- <div className='divider-dark'/>
- {channelDisplayModeSection}
- <div className='divider-dark'/>
- {languagesSection}
- </div>
- </div>
- );
- }
-}
-
-UserSettingsDisplay.propTypes = {
- user: PropTypes.object,
- updateSection: PropTypes.func,
- updateTab: PropTypes.func,
- activeSection: PropTypes.string,
- closeModal: PropTypes.func.isRequired,
- collapseModal: PropTypes.func.isRequired,
- setRequireConfirm: PropTypes.func.isRequired,
- setEnforceFocus: PropTypes.func.isRequired
-};
diff --git a/webapp/components/user_settings/user_settings_general/index.js b/webapp/components/user_settings/user_settings_general/index.js
deleted file mode 100644
index 90fd58bf2..000000000
--- a/webapp/components/user_settings/user_settings_general/index.js
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getMe} from 'mattermost-redux/actions/users';
-
-import UserSettingsGeneralTab from './user_settings_general.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getMe
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(UserSettingsGeneralTab);
diff --git a/webapp/components/user_settings/user_settings_general/user_settings_general.jsx b/webapp/components/user_settings/user_settings_general/user_settings_general.jsx
deleted file mode 100644
index fc5f82f29..000000000
--- a/webapp/components/user_settings/user_settings_general/user_settings_general.jsx
+++ /dev/null
@@ -1,1245 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import SettingItemMin from 'components/setting_item_min.jsx';
-import SettingItemMax from 'components/setting_item_max.jsx';
-import SettingPicture from 'components/setting_picture.jsx';
-
-import UserStore from 'stores/user_store.jsx';
-import ErrorStore from 'stores/error_store.jsx';
-
-import Constants from 'utils/constants.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage, FormattedDate} from 'react-intl';
-import {updateUser, uploadProfileImage} from 'actions/user_actions.jsx';
-import {trackEvent} from 'actions/diagnostics_actions.jsx';
-
-const holders = defineMessages({
- usernameReserved: {
- id: 'user.settings.general.usernameReserved',
- defaultMessage: 'This username is reserved, please choose a new one.'
- },
- usernameRestrictions: {
- id: 'user.settings.general.usernameRestrictions',
- defaultMessage: "Username must begin with a letter, and contain between {min} to {max} lowercase characters made up of numbers, letters, and the symbols '.', '-', and '_'."
- },
- validEmail: {
- id: 'user.settings.general.validEmail',
- defaultMessage: 'Please enter a valid email address.'
- },
- emailMatch: {
- id: 'user.settings.general.emailMatch',
- defaultMessage: 'The new emails you entered do not match.'
- },
- checkEmail: {
- id: 'user.settings.general.checkEmail',
- defaultMessage: 'Check your email at {email} to verify the address.'
- },
- validImage: {
- id: 'user.settings.general.validImage',
- defaultMessage: 'Only JPG or PNG images may be used for profile pictures'
- },
- imageTooLarge: {
- id: 'user.settings.general.imageTooLarge',
- defaultMessage: 'Unable to upload profile image. File is too large.'
- },
- uploadImage: {
- id: 'user.settings.general.uploadImage',
- defaultMessage: "Click 'Edit' to upload an image."
- },
- uploadImageMobile: {
- id: 'user.settings.general.mobile.uploadImage',
- defaultMessage: 'Click to upload an image.'
- },
- fullName: {
- id: 'user.settings.general.fullName',
- defaultMessage: 'Full Name'
- },
- nickname: {
- id: 'user.settings.general.nickname',
- defaultMessage: 'Nickname'
- },
- username: {
- id: 'user.settings.general.username',
- defaultMessage: 'Username'
- },
- profilePicture: {
- id: 'user.settings.general.profilePicture',
- defaultMessage: 'Profile Picture'
- },
- close: {
- id: 'user.settings.general.close',
- defaultMessage: 'Close'
- },
- position: {
- id: 'user.settings.general.position',
- defaultMessage: 'Position'
- }
-});
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-class UserSettingsGeneralTab extends React.Component {
- static propTypes = {
- intl: intlShape.isRequired,
- user: PropTypes.object.isRequired,
- updateSection: PropTypes.func.isRequired,
- updateTab: PropTypes.func.isRequired,
- activeSection: PropTypes.string.isRequired,
- closeModal: PropTypes.func.isRequired,
- collapseModal: PropTypes.func.isRequired,
- actions: PropTypes.shape({
- getMe: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
- this.submitActive = false;
-
- this.submitUsername = this.submitUsername.bind(this);
- this.submitNickname = this.submitNickname.bind(this);
- this.submitName = this.submitName.bind(this);
- this.submitEmail = this.submitEmail.bind(this);
- this.submitUser = this.submitUser.bind(this);
- this.submitPicture = this.submitPicture.bind(this);
- this.submitPosition = this.submitPosition.bind(this);
-
- this.updateUsername = this.updateUsername.bind(this);
- this.updateFirstName = this.updateFirstName.bind(this);
- this.updateLastName = this.updateLastName.bind(this);
- this.updateNickname = this.updateNickname.bind(this);
- this.updateEmail = this.updateEmail.bind(this);
- this.updateConfirmEmail = this.updateConfirmEmail.bind(this);
- this.updatePicture = this.updatePicture.bind(this);
- this.updateSection = this.updateSection.bind(this);
- this.updatePosition = this.updatePosition.bind(this);
-
- this.state = this.setupInitialState(props);
- }
-
- submitUsername(e) {
- e.preventDefault();
-
- const user = Object.assign({}, this.props.user);
- const username = this.state.username.trim().toLowerCase();
-
- const {formatMessage} = this.props.intl;
- const usernameError = Utils.isValidUsername(username);
- if (usernameError === 'Cannot use a reserved word as a username.') {
- this.setState({clientError: formatMessage(holders.usernameReserved), serverError: ''});
- return;
- } else if (usernameError) {
- this.setState({clientError: formatMessage(holders.usernameRestrictions, {min: Constants.MIN_USERNAME_LENGTH, max: Constants.MAX_USERNAME_LENGTH}), serverError: ''});
- return;
- }
-
- if (user.username === username) {
- this.updateSection('');
- return;
- }
-
- user.username = username;
-
- trackEvent('settings', 'user_settings_update', {field: 'username'});
-
- this.submitUser(user, Constants.UserUpdateEvents.USERNAME, false);
- }
-
- submitNickname(e) {
- e.preventDefault();
-
- const user = Object.assign({}, this.props.user);
- const nickname = this.state.nickname.trim();
-
- if (user.nickname === nickname) {
- this.updateSection('');
- return;
- }
-
- user.nickname = nickname;
-
- trackEvent('settings', 'user_settings_update', {field: 'username'});
-
- this.submitUser(user, Constants.UserUpdateEvents.NICKNAME, false);
- }
-
- submitName(e) {
- e.preventDefault();
-
- const user = Object.assign({}, this.props.user);
- const firstName = this.state.firstName.trim();
- const lastName = this.state.lastName.trim();
-
- if (user.first_name === firstName && user.last_name === lastName) {
- this.updateSection('');
- return;
- }
-
- user.first_name = firstName;
- user.last_name = lastName;
-
- trackEvent('settings', 'user_settings_update', {field: 'fullname'});
-
- this.submitUser(user, Constants.UserUpdateEvents.FULLNAME, false);
- }
-
- submitEmail(e) {
- e.preventDefault();
-
- const user = Object.assign({}, this.props.user);
- const email = this.state.email.trim().toLowerCase();
- const confirmEmail = this.state.confirmEmail.trim().toLowerCase();
-
- const {formatMessage} = this.props.intl;
-
- if (email === user.email && (confirmEmail === '' || confirmEmail === user.email)) {
- this.updateSection('');
- return;
- }
-
- if (email === '' || !Utils.isEmail(email)) {
- this.setState({emailError: formatMessage(holders.validEmail), clientError: '', serverError: ''});
- return;
- }
-
- if (email !== confirmEmail) {
- this.setState({emailError: formatMessage(holders.emailMatch), clientError: '', serverError: ''});
- return;
- }
-
- user.email = email;
- trackEvent('settings', 'user_settings_update', {field: 'email'});
- this.submitUser(user, Constants.UserUpdateEvents.EMAIL, true);
- }
-
- submitUser(user, type, emailUpdated) {
- updateUser(user, type,
- () => {
- this.updateSection('');
- this.props.actions.getMe();
- const verificationEnabled = global.window.mm_config.SendEmailNotifications === 'true' && global.window.mm_config.RequireEmailVerification === 'true' && emailUpdated;
-
- if (verificationEnabled) {
- ErrorStore.storeLastError({message: this.props.intl.formatMessage(holders.checkEmail, {email: user.email})});
- ErrorStore.emitChange();
- this.setState({emailChangeInProgress: true});
- }
- },
- (err) => {
- let serverError;
- if (err.message) {
- serverError = err.message;
- } else {
- serverError = err;
- }
- this.setState({serverError, emailError: '', clientError: ''});
- }
- );
- }
-
- submitPicture(e) {
- e.preventDefault();
-
- if (!this.state.pictureFile) {
- return;
- }
-
- if (!this.submitActive) {
- return;
- }
-
- trackEvent('settings', 'user_settings_update', {field: 'picture'});
-
- const {formatMessage} = this.props.intl;
- const file = this.state.pictureFile;
-
- if (file.type !== 'image/jpeg' && file.type !== 'image/png') {
- this.setState({clientError: formatMessage(holders.validImage), serverError: ''});
- return;
- } else if (file.size > this.state.maxFileSize) {
- this.setState({clientError: formatMessage(holders.imageTooLarge), serverError: ''});
- return;
- }
-
- this.setState({loadingPicture: true});
-
- uploadProfileImage(
- file,
- () => {
- this.updateSection('');
- this.submitActive = false;
- },
- (err) => {
- var state = this.setupInitialState(this.props);
- state.serverError = err.message;
- this.setState(state);
- }
- );
- }
-
- submitPosition(e) {
- e.preventDefault();
-
- const user = Object.assign({}, this.props.user);
- const position = this.state.position.trim();
-
- if (user.position === position) {
- this.updateSection('');
- return;
- }
-
- user.position = position;
-
- trackEvent('settings', 'user_settings_update', {field: 'position'});
-
- this.submitUser(user, Constants.UserUpdateEvents.Position, false);
- }
-
- updateUsername(e) {
- this.setState({username: e.target.value});
- }
-
- updateFirstName(e) {
- this.setState({firstName: e.target.value});
- }
-
- updateLastName(e) {
- this.setState({lastName: e.target.value});
- }
-
- updateNickname(e) {
- this.setState({nickname: e.target.value});
- }
-
- updatePosition(e) {
- this.setState({position: e.target.value});
- }
-
- updateEmail(e) {
- this.setState({email: e.target.value});
- }
-
- updateConfirmEmail(e) {
- this.setState({confirmEmail: e.target.value});
- }
-
- updatePicture(e) {
- if (e.target.files && e.target.files[0]) {
- this.setState({pictureFile: e.target.files[0]});
-
- this.submitActive = true;
- this.setState({clientError: null});
- } else {
- this.setState({pictureFile: null});
- }
- }
-
- updateSection(section) {
- if ($('.section-max').length) {
- $('.settings-modal .modal-body').scrollTop(0).perfectScrollbar('update');
- }
- const emailChangeInProgress = this.state.emailChangeInProgress;
- this.setState(Object.assign({}, this.setupInitialState(this.props), {emailChangeInProgress, clientError: '', serverError: '', emailError: ''}));
- this.submitActive = false;
- this.props.updateSection(section);
- }
-
- setupInitialState(props) {
- const user = props.user;
-
- return {
- username: user.username,
- firstName: user.first_name,
- lastName: user.last_name,
- nickname: user.nickname,
- position: user.position,
- originalEmail: user.email,
- email: '',
- confirmEmail: '',
- pictureFile: null,
- loadingPicture: false,
- emailChangeInProgress: false,
- maxFileSize: global.window.mm_config.MaxFileSize
- };
- }
-
- createEmailSection() {
- let emailSection;
-
- if (this.props.activeSection === 'email') {
- const emailEnabled = global.window.mm_config.SendEmailNotifications === 'true';
- const emailVerificationEnabled = global.window.mm_config.RequireEmailVerification === 'true';
- const inputs = [];
-
- let helpText = (
- <FormattedMessage
- id='user.settings.general.emailHelp1'
- defaultMessage='Email is used for sign-in, notifications, and password reset. Email requires verification if changed.'
- />
- );
-
- if (!emailEnabled) {
- helpText = (
- <div className='setting-list__hint col-sm-12 text-danger'>
- <FormattedMessage
- id='user.settings.general.emailHelp2'
- defaultMessage='Email has been disabled by your System Administrator. No notification emails will be sent until it is enabled.'
- />
- </div>
- );
- } else if (!emailVerificationEnabled) {
- helpText = (
- <FormattedMessage
- id='user.settings.general.emailHelp3'
- defaultMessage='Email is used for sign-in, notifications, and password reset.'
- />
- );
- } else if (this.state.emailChangeInProgress) {
- const newEmail = UserStore.getCurrentUser().email;
- if (newEmail) {
- helpText = (
- <FormattedMessage
- id='user.settings.general.emailHelp4'
- defaultMessage='A verification email was sent to {email}.'
- values={{
- email: newEmail
- }}
- />
- );
- }
- }
-
- let submit = null;
-
- if (this.props.user.auth_service === '') {
- inputs.push(
- <div key='currentEmailSetting'>
- <div className='form-group'>
- <label className='col-sm-5 control-label'>
- <FormattedMessage
- id='user.settings.general.currentEmail'
- defaultMessage='Current Email'
- />
- </label>
- <div className='col-sm-7'>
- <label className='control-label'>{this.state.originalEmail}</label>
- </div>
- </div>
- </div>
- );
-
- inputs.push(
- <div key='emailSetting'>
- <div className='form-group'>
- <label className='col-sm-5 control-label'>
- <FormattedMessage
- id='user.settings.general.newEmail'
- defaultMessage='New Email'
- />
- </label>
- <div className='col-sm-7'>
- <input
- id='primaryEmail'
- className='form-control'
- type='email'
- onChange={this.updateEmail}
- value={this.state.email}
- />
- </div>
- </div>
- </div>
- );
-
- inputs.push(
- <div key='confirmEmailSetting'>
- <div className='form-group'>
- <label className='col-sm-5 control-label'>
- <FormattedMessage
- id='user.settings.general.confirmEmail'
- defaultMessage='Confirm Email'
- />
- </label>
- <div className='col-sm-7'>
- <input
- id='confirmEmail'
- className='form-control'
- type='email'
- onChange={this.updateConfirmEmail}
- value={this.state.confirmEmail}
- />
- </div>
- </div>
- {helpText}
- </div>
- );
-
- submit = this.submitEmail;
- } else if (this.props.user.auth_service === Constants.GITLAB_SERVICE) {
- inputs.push(
- <div
- key='oauthEmailInfo'
- className='form-group'
- >
- <div className='setting-list__hint col-sm-12'>
- <FormattedMessage
- id='user.settings.general.emailGitlabCantUpdate'
- defaultMessage='Login occurs through GitLab. Email cannot be updated. Email address used for notifications is {email}.'
- values={{
- email: this.state.originalEmail
- }}
- />
- </div>
- {helpText}
- </div>
- );
- } else if (this.props.user.auth_service === Constants.GOOGLE_SERVICE) {
- inputs.push(
- <div
- key='oauthEmailInfo'
- className='form-group'
- >
- <div className='setting-list__hint col-sm-12'>
- <FormattedMessage
- id='user.settings.general.emailGoogleCantUpdate'
- defaultMessage='Login occurs through Google Apps. Email cannot be updated. Email address used for notifications is {email}.'
- values={{
- email: this.state.originalEmail
- }}
- />
- </div>
- {helpText}
- </div>
- );
- } else if (this.props.user.auth_service === Constants.OFFICE365_SERVICE) {
- inputs.push(
- <div
- key='oauthEmailInfo'
- className='form-group'
- >
- <div className='setting-list__hint col-sm-12'>
- <FormattedMessage
- id='user.settings.general.emailOffice365CantUpdate'
- defaultMessage='Login occurs through Office 365. Email cannot be updated. Email address used for notifications is {email}.'
- values={{
- email: this.state.originalEmail
- }}
- />
- </div>
- {helpText}
- </div>
- );
- } else if (this.props.user.auth_service === Constants.LDAP_SERVICE) {
- inputs.push(
- <div
- key='oauthEmailInfo'
- className='padding-bottom'
- >
- <div className='setting-list__hint col-sm-12'>
- <FormattedMessage
- id='user.settings.general.emailLdapCantUpdate'
- defaultMessage='Login occurs through AD/LDAP. Email cannot be updated. Email address used for notifications is {email}.'
- values={{
- email: this.state.originalEmail
- }}
- />
- </div>
- </div>
- );
- } else if (this.props.user.auth_service === Constants.SAML_SERVICE) {
- inputs.push(
- <div
- key='oauthEmailInfo'
- className='padding-bottom'
- >
- <div className='setting-list__hint col-sm-12'>
- <FormattedMessage
- id='user.settings.general.emailSamlCantUpdate'
- defaultMessage='Login occurs through SAML. Email cannot be updated. Email address used for notifications is {email}.'
- values={{
- email: this.state.originalEmail
- }}
- />
- </div>
- {helpText}
- </div>
- );
- }
-
- emailSection = (
- <SettingItemMax
- title={
- <FormattedMessage
- id='user.settings.general.email'
- defaultMessage='Email'
- />
- }
- inputs={inputs}
- submit={submit}
- server_error={this.state.serverError}
- client_error={this.state.emailError}
- updateSection={(e) => {
- this.updateSection('');
- e.preventDefault();
- }}
- />
- );
- } else {
- let describe = '';
- if (this.props.user.auth_service === '') {
- if (this.state.emailChangeInProgress) {
- const newEmail = UserStore.getCurrentUser().email;
- if (newEmail) {
- describe = (
- <FormattedHTMLMessage
- id='user.settings.general.newAddress'
- defaultMessage='New Address: {email}<br />Check your email to verify the above address.'
- values={{
- email: newEmail
- }}
- />
- );
- } else {
- describe = (
- <FormattedMessage
- id='user.settings.general.checkEmailNoAddress'
- defaultMessage='Check your email to verify your new address'
- />
- );
- }
- } else {
- describe = UserStore.getCurrentUser().email;
- }
- } else if (this.props.user.auth_service === Constants.GITLAB_SERVICE) {
- describe = (
- <FormattedMessage
- id='user.settings.general.loginGitlab'
- defaultMessage='Login done through GitLab ({email})'
- values={{
- email: this.state.originalEmail
- }}
- />
- );
- } else if (this.props.user.auth_service === Constants.GOOGLE_SERVICE) {
- describe = (
- <FormattedMessage
- id='user.settings.general.loginGoogle'
- defaultMessage='Login done through Google Apps ({email})'
- values={{
- email: this.state.originalEmail
- }}
- />
- );
- } else if (this.props.user.auth_service === Constants.OFFICE365_SERVICE) {
- describe = (
- <FormattedMessage
- id='user.settings.general.loginOffice365'
- defaultMessage='Login done through Office 365 ({email})'
- values={{
- email: this.state.originalEmail
- }}
- />
- );
- } else if (this.props.user.auth_service === Constants.LDAP_SERVICE) {
- describe = (
- <FormattedMessage
- id='user.settings.general.loginLdap'
- defaultMessage='Login done through AD/LDAP ({email})'
- values={{
- email: this.state.originalEmail
- }}
- />
- );
- } else if (this.props.user.auth_service === Constants.SAML_SERVICE) {
- describe = (
- <FormattedMessage
- id='user.settings.general.loginSaml'
- defaultMessage='Login done through SAML ({email})'
- values={{
- email: this.state.originalEmail
- }}
- />
- );
- }
-
- emailSection = (
- <SettingItemMin
- title={
- <FormattedMessage
- id='user.settings.general.email'
- defaultMessage='Email'
- />
- }
- describe={describe}
- updateSection={() => {
- this.updateSection('email');
- }}
- />
- );
- }
-
- return emailSection;
- }
-
- render() {
- const user = this.props.user;
- const {formatMessage} = this.props.intl;
-
- let clientError = null;
- if (this.state.clientError) {
- clientError = this.state.clientError;
- }
- let serverError = null;
- if (this.state.serverError) {
- serverError = this.state.serverError;
- }
-
- let nameSection;
- const inputs = [];
-
- if (this.props.activeSection === 'name') {
- let extraInfo;
- let submit = null;
- if (this.props.user.auth_service === '' ||
- ((this.props.user.auth_service === 'ldap' || this.props.user.auth_service === Constants.SAML_SERVICE) &&
- (global.window.mm_config.FirstNameAttributeSet === 'false' || global.window.mm_config.LastNameAttributeSet === 'false'))) {
- inputs.push(
- <div
- key='firstNameSetting'
- className='form-group'
- >
- <label className='col-sm-5 control-label'>
- <FormattedMessage
- id='user.settings.general.firstName'
- defaultMessage='First Name'
- />
- </label>
- <div className='col-sm-7'>
- <input
- id='firstName'
- className='form-control'
- type='text'
- onChange={this.updateFirstName}
- value={this.state.firstName}
- />
- </div>
- </div>
- );
-
- inputs.push(
- <div
- key='lastNameSetting'
- className='form-group'
- >
- <label className='col-sm-5 control-label'>
- <FormattedMessage
- id='user.settings.general.lastName'
- defaultMessage='Last Name'
- />
- </label>
- <div className='col-sm-7'>
- <input
- id='lastName'
- className='form-control'
- type='text'
- onChange={this.updateLastName}
- value={this.state.lastName}
- />
- </div>
- </div>
- );
-
- function notifClick(e) {
- e.preventDefault();
- this.updateSection('');
- this.props.updateTab('notifications');
- }
-
- const notifLink = (
- <a
- href='#'
- onClick={notifClick.bind(this)}
- >
- <FormattedMessage
- id='user.settings.general.notificationsLink'
- defaultMessage='Notifications'
- />
- </a>
- );
-
- extraInfo = (
- <span>
- <FormattedMessage
- id='user.settings.general.notificationsExtra'
- defaultMessage='By default, you will receive mention notifications when someone types your first name. Go to {notify} settings to change this default.'
- values={{
- notify: (notifLink)
- }}
- />
- </span>
- );
-
- submit = this.submitName;
- } else {
- extraInfo = (
- <span>
- <FormattedMessage
- id='user.settings.general.field_handled_externally'
- defaultMessage='This field is handled through your login provider. If you want to change it, you need to do so through your login provider.'
- />
- </span>
- );
- }
-
- nameSection = (
- <SettingItemMax
- title={formatMessage(holders.fullName)}
- inputs={inputs}
- submit={submit}
- server_error={serverError}
- client_error={clientError}
- updateSection={(e) => {
- this.updateSection('');
- e.preventDefault();
- }}
- extraInfo={extraInfo}
- />
- );
- } else {
- let describe = '';
-
- if (user.first_name && user.last_name) {
- describe = user.first_name + ' ' + user.last_name;
- } else if (user.first_name) {
- describe = user.first_name;
- } else if (user.last_name) {
- describe = user.last_name;
- } else {
- describe = (
- <FormattedMessage
- id='user.settings.general.emptyName'
- defaultMessage="Click 'Edit' to add your full name"
- />
- );
- if (Utils.isMobile()) {
- describe = (
- <FormattedMessage
- id='user.settings.general.mobile.emptyName'
- defaultMessage='Click to add your full name'
- />
- );
- }
- }
-
- nameSection = (
- <SettingItemMin
- title={formatMessage(holders.fullName)}
- describe={describe}
- updateSection={() => {
- this.updateSection('name');
- }}
- />
- );
- }
-
- let nicknameSection;
- if (this.props.activeSection === 'nickname') {
- let extraInfo;
- let submit = null;
- if ((this.props.user.auth_service === 'ldap' || this.props.user.auth_service === Constants.SAML_SERVICE) && global.window.mm_config.NicknameAttributeSet === 'true') {
- extraInfo = (
- <span>
- <FormattedMessage
- id='user.settings.general.field_handled_externally'
- defaultMessage='This field is handled through your login provider. If you want to change it, you need to do so though your login provider.'
- />
- </span>
- );
- } else {
- let nicknameLabel = (
- <FormattedMessage
- id='user.settings.general.nickname'
- defaultMessage='Nickname'
- />
- );
- if (Utils.isMobile()) {
- nicknameLabel = '';
- }
-
- inputs.push(
- <div
- key='nicknameSetting'
- className='form-group'
- >
- <label className='col-sm-5 control-label'>{nicknameLabel}</label>
- <div className='col-sm-7'>
- <input
- id='nickname'
- className='form-control'
- type='text'
- onChange={this.updateNickname}
- value={this.state.nickname}
- maxLength={Constants.MAX_NICKNAME_LENGTH}
- autoCapitalize='off'
- />
- </div>
- </div>
- );
-
- extraInfo = (
- <span>
- <FormattedMessage
- id='user.settings.general.nicknameExtra'
- defaultMessage='Use Nickname for a name you might be called that is different from your first name and username. This is most often used when two or more people have similar sounding names and usernames.'
- />
- </span>
- );
-
- submit = this.submitNickname;
- }
-
- nicknameSection = (
- <SettingItemMax
- title={formatMessage(holders.nickname)}
- inputs={inputs}
- submit={submit}
- server_error={serverError}
- client_error={clientError}
- updateSection={(e) => {
- this.updateSection('');
- e.preventDefault();
- }}
- extraInfo={extraInfo}
- />
- );
- } else {
- let describe = '';
- if (user.nickname) {
- describe = user.nickname;
- } else {
- describe = (
- <FormattedMessage
- id='user.settings.general.emptyNickname'
- defaultMessage="Click 'Edit' to add a nickname"
- />
- );
- if (Utils.isMobile()) {
- describe = (
- <FormattedMessage
- id='user.settings.general.mobile.emptyNickname'
- defaultMessage='Click to add a nickname'
- />
- );
- }
- }
-
- nicknameSection = (
- <SettingItemMin
- title={formatMessage(holders.nickname)}
- describe={describe}
- updateSection={() => {
- this.updateSection('nickname');
- }}
- />
- );
- }
-
- let usernameSection;
- if (this.props.activeSection === 'username') {
- let extraInfo;
- let submit = null;
- if (this.props.user.auth_service === '') {
- let usernameLabel = (
- <FormattedMessage
- id='user.settings.general.username'
- defaultMessage='Username'
- />
- );
- if (Utils.isMobile()) {
- usernameLabel = '';
- }
-
- inputs.push(
- <div
- key='usernameSetting'
- className='form-group'
- >
- <label className='col-sm-5 control-label'>{usernameLabel}</label>
- <div className='col-sm-7'>
- <input
- id='username'
- maxLength={Constants.MAX_USERNAME_LENGTH}
- className='form-control'
- type='text'
- onChange={this.updateUsername}
- value={this.state.username}
- autoCapitalize='off'
- />
- </div>
- </div>
- );
-
- extraInfo = (
- <span>
- <FormattedMessage
- id='user.settings.general.usernameInfo'
- defaultMessage='Pick something easy for teammates to recognize and recall.'
- />
- </span>
- );
-
- submit = this.submitUsername;
- } else {
- extraInfo = (
- <span>
- <FormattedMessage
- id='user.settings.general.field_handled_externally'
- defaultMessage='This field is handled through your login provider. If you want to change it, you need to do so though your login provider.'
- />
- </span>
- );
- }
-
- usernameSection = (
- <SettingItemMax
- title={formatMessage(holders.username)}
- inputs={inputs}
- submit={submit}
- server_error={serverError}
- client_error={clientError}
- updateSection={(e) => {
- this.updateSection('');
- e.preventDefault();
- }}
- extraInfo={extraInfo}
- />
- );
- } else {
- usernameSection = (
- <SettingItemMin
- title={formatMessage(holders.username)}
- describe={UserStore.getCurrentUser().username}
- updateSection={() => {
- this.updateSection('username');
- }}
- />
- );
- }
-
- let positionSection;
- if (this.props.activeSection === 'position') {
- let extraInfo;
- let submit = null;
- if ((this.props.user.auth_service === 'ldap' || this.props.user.auth_service === Constants.SAML_SERVICE) && global.window.mm_config.PositionAttributeSet === 'true') {
- extraInfo = (
- <span>
- <FormattedMessage
- id='user.settings.general.field_handled_externally'
- defaultMessage='This field is handled through your login provider. If you want to change it, you need to do so though your login provider.'
- />
- </span>
- );
- } else {
- let positionLabel = (
- <FormattedMessage
- id='user.settings.general.position'
- defaultMessage='Position'
- />
- );
- if (Utils.isMobile()) {
- positionLabel = '';
- }
-
- inputs.push(
- <div
- key='positionSetting'
- className='form-group'
- >
- <label className='col-sm-5 control-label'>{positionLabel}</label>
- <div className='col-sm-7'>
- <input
- id='position'
- className='form-control'
- type='text'
- onChange={this.updatePosition}
- value={this.state.position}
- maxLength={Constants.MAX_POSITION_LENGTH}
- autoCapitalize='off'
- />
- </div>
- </div>
- );
-
- extraInfo = (
- <span>
- <FormattedMessage
- id='user.settings.general.positionExtra'
- defaultMessage='Use Position for your role or job title. This will be shown in your profile popover.'
- />
- </span>
- );
-
- submit = this.submitPosition;
- }
-
- positionSection = (
- <SettingItemMax
- title={formatMessage(holders.position)}
- inputs={inputs}
- submit={submit}
- server_error={serverError}
- client_error={clientError}
- updateSection={(e) => {
- this.updateSection('');
- e.preventDefault();
- }}
- extraInfo={extraInfo}
- />
- );
- } else {
- let describe = '';
- if (user.position) {
- describe = user.position;
- } else {
- describe = (
- <FormattedMessage
- id='user.settings.general.emptyPosition'
- defaultMessage="Click 'Edit' to add your job title / position"
- />
- );
- if (Utils.isMobile()) {
- describe = (
- <FormattedMessage
- id='user.settings.general.mobile.emptyPosition'
- defaultMessage='Click to add your job title / position'
- />
- );
- }
- }
-
- positionSection = (
- <SettingItemMin
- title={formatMessage(holders.position)}
- describe={describe}
- updateSection={() => {
- this.updateSection('position');
- }}
- />
- );
- }
-
- const emailSection = this.createEmailSection();
-
- let pictureSection;
- if (this.props.activeSection === 'picture') {
- pictureSection = (
- <SettingPicture
- title={formatMessage(holders.profilePicture)}
- submit={this.submitPicture}
- src={Utils.imageURLForUser(user)}
- serverError={serverError}
- clientError={clientError}
- updateSection={(e) => {
- this.updateSection('');
- e.preventDefault();
- }}
- file={this.state.pictureFile}
- onFileChange={this.updatePicture}
- submitActive={this.submitActive}
- loadingPicture={this.state.loadingPicture}
- />
- );
- } else {
- let minMessage = formatMessage(holders.uploadImage);
- if (Utils.isMobile()) {
- minMessage = formatMessage(holders.uploadImageMobile);
- }
- if (user.last_picture_update) {
- minMessage = (
- <FormattedMessage
- id='user.settings.general.imageUpdated'
- defaultMessage='Image last updated {date}'
- values={{
- date: (
- <FormattedDate
- value={new Date(user.last_picture_update)}
- day='2-digit'
- month='short'
- year='numeric'
- />
- )
- }}
- />
- );
- }
- pictureSection = (
- <SettingItemMin
- title={formatMessage(holders.profilePicture)}
- describe={minMessage}
- updateSection={() => {
- this.updateSection('picture');
- }}
- />
- );
- }
-
- return (
- <div>
- <div className='modal-header'>
- <button
- id='closeUserSettings'
- type='button'
- className='close'
- data-dismiss='modal'
- aria-label={formatMessage(holders.close)}
- onClick={this.props.closeModal}
- >
- <span aria-hidden='true'>{'×'}</span>
- </button>
- <h4
- className='modal-title'
- ref='title'
- >
- <div className='modal-back'>
- <i
- className='fa fa-angle-left'
- onClick={this.props.collapseModal}
- />
- </div>
- <FormattedMessage
- id='user.settings.general.title'
- defaultMessage='General Settings'
- />
- </h4>
- </div>
- <div className='user-settings'>
- <h3 className='tab-header'>
- <FormattedMessage
- id='user.settings.general.title'
- defaultMessage='General Settings'
- />
- </h3>
- <div className='divider-dark first'/>
- {nameSection}
- <div className='divider-light'/>
- {usernameSection}
- <div className='divider-light'/>
- {nicknameSection}
- <div className='divider-light'/>
- {positionSection}
- <div className='divider-light'/>
- {emailSection}
- <div className='divider-light'/>
- {pictureSection}
- <div className='divider-dark'/>
- </div>
- </div>
- );
- }
-}
-
-export default injectIntl(UserSettingsGeneralTab);
diff --git a/webapp/components/user_settings/user_settings_modal.jsx b/webapp/components/user_settings/user_settings_modal.jsx
deleted file mode 100644
index 665dbed68..000000000
--- a/webapp/components/user_settings/user_settings_modal.jsx
+++ /dev/null
@@ -1,309 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import ReactDOM from 'react-dom';
-import ConfirmModal from '../confirm_modal.jsx';
-import UserSettings from './user_settings.jsx';
-import SettingsSidebar from '../settings_sidebar.jsx';
-
-import ModalStore from 'stores/modal_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-
-import {Modal} from 'react-bootstrap';
-
-import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl';
-
-const holders = defineMessages({
- general: {
- id: 'user.settings.modal.general',
- defaultMessage: 'General'
- },
- security: {
- id: 'user.settings.modal.security',
- defaultMessage: 'Security'
- },
- notifications: {
- id: 'user.settings.modal.notifications',
- defaultMessage: 'Notifications'
- },
- display: {
- id: 'user.settings.modal.display',
- defaultMessage: 'Display'
- },
- advanced: {
- id: 'user.settings.modal.advanced',
- defaultMessage: 'Advanced'
- },
- confirmTitle: {
- id: 'user.settings.modal.confirmTitle',
- defaultMessage: 'Discard Changes?'
- },
- confirmMsg: {
- id: 'user.settings.modal.confirmMsg',
- defaultMessage: 'You have unsaved changes, are you sure you want to discard them?'
- },
- confirmBtns: {
- id: 'user.settings.modal.confirmBtns',
- defaultMessage: 'Yes, Discard'
- }
-});
-
-import React from 'react';
-
-class UserSettingsModal extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleHide = this.handleHide.bind(this);
- this.handleHidden = this.handleHidden.bind(this);
- this.handleCollapse = this.handleCollapse.bind(this);
- this.handleConfirm = this.handleConfirm.bind(this);
- this.handleCancelConfirmation = this.handleCancelConfirmation.bind(this);
- this.handleToggle = this.handleToggle.bind(this);
- this.handleKeyDown = this.handleKeyDown.bind(this);
-
- this.closeModal = this.closeModal.bind(this);
- this.collapseModal = this.collapseModal.bind(this);
-
- this.updateTab = this.updateTab.bind(this);
- this.updateSection = this.updateSection.bind(this);
- this.onUserChanged = this.onUserChanged.bind(this);
-
- this.state = {
- active_tab: 'general',
- active_section: '',
- showConfirmModal: false,
- enforceFocus: true,
- currentUser: UserStore.getCurrentUser(),
- show: false
- };
-
- this.requireConfirm = false;
- this.mounted = false;
- }
-
- onUserChanged() {
- if (this.mounted) {
- this.setState({currentUser: UserStore.getCurrentUser()});
- }
- }
-
- componentDidMount() {
- this.mounted = true;
- UserStore.addChangeListener(this.onUserChanged);
- ModalStore.addModalListener(Constants.ActionTypes.TOGGLE_ACCOUNT_SETTINGS_MODAL, this.handleToggle);
- document.addEventListener('keydown', this.handleKeyDown);
- }
-
- componentWillUnmount() {
- this.mounted = false;
- ModalStore.removeModalListener(Constants.ActionTypes.TOGGLE_ACCOUNT_SETTINGS_MODAL, this.handleToggle);
- document.removeEventListener('keydown', this.handleKeyDown);
- }
-
- componentDidUpdate() {
- UserStore.removeChangeListener(this.onUserChanged);
- if (!Utils.isMobile()) {
- $('.settings-modal .modal-body').perfectScrollbar();
- }
- }
-
- handleKeyDown(e) {
- if (Utils.cmdOrCtrlPressed(e) && e.shiftKey && e.keyCode === Constants.KeyCodes.A) {
- this.setState({
- show: !this.state.show
- });
- }
- }
-
- handleToggle(value) {
- this.setState({
- show: value
- });
- }
-
- // Called when the close button is pressed on the main modal
- handleHide() {
- if (this.requireConfirm) {
- this.afterConfirm = () => this.handleHide();
- this.showConfirmModal();
-
- return;
- }
-
- this.setState({
- show: false
- });
- }
-
- // called after the dialog is fully hidden and faded out
- handleHidden() {
- this.setState({
- active_tab: 'general',
- active_section: ''
- });
- }
-
- // Called to hide the settings pane when on mobile
- handleCollapse() {
- $(ReactDOM.findDOMNode(this.refs.modalBody)).closest('.modal-dialog').removeClass('display--content');
-
- this.setState({
- active_tab: '',
- active_section: ''
- });
- }
-
- handleConfirm() {
- this.setState({
- showConfirmModal: false,
- enforceFocus: true
- });
-
- this.requireConfirm = false;
-
- if (this.afterConfirm) {
- this.afterConfirm();
- this.afterConfirm = null;
- }
- }
-
- handleCancelConfirmation() {
- this.setState({
- showConfirmModal: false,
- enforceFocus: true
- });
-
- this.afterConfirm = null;
- }
-
- showConfirmModal(afterConfirm) {
- this.setState({
- showConfirmModal: true,
- enforceFocus: false
- });
-
- if (afterConfirm) {
- this.afterConfirm = afterConfirm;
- }
- }
-
- // Called by settings tabs when their close button is pressed
- closeModal() {
- if (this.requireConfirm) {
- this.showConfirmModal(this.closeModal);
- } else {
- this.handleHide();
- }
- }
-
- // Called by settings tabs when their back button is pressed
- collapseModal() {
- if (this.requireConfirm) {
- this.showConfirmModal(this.collapseModal);
- } else {
- this.handleCollapse();
- }
- }
-
- updateTab(tab, skipConfirm) {
- if (!skipConfirm && this.requireConfirm) {
- this.showConfirmModal(() => this.updateTab(tab, true));
- } else {
- this.setState({
- active_tab: tab,
- active_section: ''
- });
- }
-
- if (!Utils.isMobile()) {
- $('.settings-modal .modal-body').scrollTop(0).perfectScrollbar('update');
- }
- }
-
- updateSection(section, skipConfirm) {
- if (!skipConfirm && this.requireConfirm) {
- this.showConfirmModal(() => this.updateSection(section, true));
- } else {
- this.setState({active_section: section});
- }
- }
-
- render() {
- const {formatMessage} = this.props.intl;
- if (this.state.currentUser == null) {
- return (<div/>);
- }
- var tabs = [];
-
- tabs.push({name: 'general', uiName: formatMessage(holders.general), icon: 'icon fa fa-gear'});
- tabs.push({name: 'security', uiName: formatMessage(holders.security), icon: 'icon fa fa-lock'});
- tabs.push({name: 'notifications', uiName: formatMessage(holders.notifications), icon: 'icon fa fa-exclamation-circle'});
- tabs.push({name: 'display', uiName: formatMessage(holders.display), icon: 'icon fa fa-eye'});
- tabs.push({name: 'advanced', uiName: formatMessage(holders.advanced), icon: 'icon fa fa-list-alt'});
-
- return (
- <Modal
- dialogClassName='settings-modal'
- show={this.state.show}
- onHide={this.handleHide}
- onExited={this.handleHidden}
- enforceFocus={this.state.enforceFocus}
- >
- <Modal.Header closeButton={true}>
- <Modal.Title>
- <FormattedMessage
- id='user.settings.modal.title'
- defaultMessage='Account Settings'
- />
- </Modal.Title>
- </Modal.Header>
- <Modal.Body ref='modalBody'>
- <div className='settings-table'>
- <div className='settings-links'>
- <SettingsSidebar
- tabs={tabs}
- activeTab={this.state.active_tab}
- updateTab={this.updateTab}
- />
- </div>
- <div className='settings-content minimize-settings'>
- <UserSettings
- ref='userSettings'
- activeTab={this.state.active_tab}
- activeSection={this.state.active_section}
- updateSection={this.updateSection}
- updateTab={this.updateTab}
- closeModal={this.closeModal}
- collapseModal={this.collapseModal}
- setEnforceFocus={(enforceFocus) => this.setState({enforceFocus})}
- setRequireConfirm={
- (requireConfirm) => {
- this.requireConfirm = requireConfirm;
- }
- }
- />
- </div>
- </div>
- </Modal.Body>
- <ConfirmModal
- title={formatMessage(holders.confirmTitle)}
- message={formatMessage(holders.confirmMsg)}
- confirmButtonText={formatMessage(holders.confirmBtns)}
- show={this.state.showConfirmModal}
- onConfirm={this.handleConfirm}
- onCancel={this.handleCancelConfirmation}
- />
- </Modal>
- );
- }
-}
-
-UserSettingsModal.propTypes = {
- intl: intlShape.isRequired
-};
-
-export default injectIntl(UserSettingsModal);
diff --git a/webapp/components/user_settings/user_settings_notifications.jsx b/webapp/components/user_settings/user_settings_notifications.jsx
deleted file mode 100644
index 5d6457527..000000000
--- a/webapp/components/user_settings/user_settings_notifications.jsx
+++ /dev/null
@@ -1,911 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import SettingItemMin from 'components/setting_item_min.jsx';
-import SettingItemMax from 'components/setting_item_max.jsx';
-import DesktopNotificationSettings from './desktop_notification_settings.jsx';
-
-import UserStore from 'stores/user_store.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-import {updateUserNotifyProps} from 'actions/user_actions.jsx';
-
-import EmailNotificationSetting from './email_notification_setting.jsx';
-import {FormattedMessage} from 'react-intl';
-
-function getNotificationsStateFromStores() {
- const user = UserStore.getCurrentUser();
-
- let desktop = 'default';
- let sound = 'true';
- let desktopDuration = '5';
- let comments = 'never';
- let enableEmail = 'true';
- let pushActivity = 'mention';
- let pushStatus = Constants.UserStatuses.ONLINE;
-
- if (user.notify_props) {
- if (user.notify_props.desktop) {
- desktop = user.notify_props.desktop;
- }
- if (user.notify_props.desktop_sound) {
- sound = user.notify_props.desktop_sound;
- }
- if (user.notify_props.desktop_duration) {
- desktopDuration = user.notify_props.desktop_duration;
- }
- if (user.notify_props.comments) {
- comments = user.notify_props.comments;
- }
- if (user.notify_props.email) {
- enableEmail = user.notify_props.email;
- }
- if (user.notify_props.push) {
- pushActivity = user.notify_props.push;
- }
- if (user.notify_props.push_status) {
- pushStatus = user.notify_props.push_status;
- }
- }
-
- let usernameKey = false;
- let customKeys = '';
- let firstNameKey = false;
- let channelKey = false;
-
- if (user.notify_props) {
- if (user.notify_props.mention_keys) {
- const keys = user.notify_props.mention_keys.split(',');
-
- if (keys.indexOf(user.username) === -1) {
- usernameKey = false;
- } else {
- usernameKey = true;
- keys.splice(keys.indexOf(user.username), 1);
- if (keys.indexOf(`@${user.username}`) !== -1) {
- keys.splice(keys.indexOf(`@${user.username}`), 1);
- }
- }
-
- customKeys = keys.join(',');
- }
-
- if (user.notify_props.first_name) {
- firstNameKey = user.notify_props.first_name === 'true';
- }
-
- if (user.notify_props.channel) {
- channelKey = user.notify_props.channel === 'true';
- }
- }
-
- return {
- desktopActivity: desktop,
- desktopDuration,
- enableEmail,
- pushActivity,
- pushStatus,
- desktopSound: sound,
- usernameKey,
- customKeys,
- customKeysChecked: customKeys.length > 0,
- firstNameKey,
- channelKey,
- notifyCommentsLevel: comments
- };
-}
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class NotificationsTab extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleSubmit = this.handleSubmit.bind(this);
- this.handleCancel = this.handleCancel.bind(this);
- this.updateSection = this.updateSection.bind(this);
- this.setStateValue = this.setStateValue.bind(this);
- this.onListenerChange = this.onListenerChange.bind(this);
- this.handleEmailRadio = this.handleEmailRadio.bind(this);
- this.updateUsernameKey = this.updateUsernameKey.bind(this);
- this.updateFirstNameKey = this.updateFirstNameKey.bind(this);
- this.updateChannelKey = this.updateChannelKey.bind(this);
- this.updateCustomMentionKeys = this.updateCustomMentionKeys.bind(this);
- this.updateState = this.updateState.bind(this);
- this.onCustomChange = this.onCustomChange.bind(this);
- this.createPushNotificationSection = this.createPushNotificationSection.bind(this);
-
- this.state = getNotificationsStateFromStores();
- }
-
- handleSubmit({enableEmail = this.state.enableEmail}) {
- const data = {};
- data.user_id = this.props.user.id;
- data.email = enableEmail;
- data.desktop_sound = this.state.desktopSound;
- data.desktop = this.state.desktopActivity;
- data.desktop_duration = this.state.desktopDuration;
- data.push = this.state.pushActivity;
- data.push_status = this.state.pushStatus;
- data.comments = this.state.notifyCommentsLevel;
-
- const mentionKeys = [];
- if (this.state.usernameKey) {
- mentionKeys.push(this.props.user.username);
- }
-
- let stringKeys = mentionKeys.join(',');
- if (this.state.customKeys.length > 0 && this.state.customKeysChecked) {
- stringKeys += ',' + this.state.customKeys;
- }
-
- data.mention_keys = stringKeys;
- data.first_name = this.state.firstNameKey.toString();
- data.channel = this.state.channelKey.toString();
-
- updateUserNotifyProps(
- data,
- () => {
- this.props.updateSection('');
- $('.settings-modal .modal-body').scrollTop(0).perfectScrollbar('update');
- },
- (err) => {
- this.setState({serverError: err.message});
- }
- );
- }
-
- handleCancel(e) {
- e.preventDefault();
- this.updateState();
- this.props.updateSection('');
- $('.settings-modal .modal-body').scrollTop(0).perfectScrollbar('update');
- }
-
- setStateValue(key, value) {
- const data = {};
- data[key] = value;
- this.setState(data);
- }
-
- updateSection(section) {
- this.updateState();
- this.props.updateSection(section);
- }
-
- updateState() {
- const newState = getNotificationsStateFromStores();
- if (!Utils.areObjectsEqual(newState, this.state)) {
- this.setState(newState);
- }
- }
-
- componentDidMount() {
- UserStore.addChangeListener(this.onListenerChange);
- }
-
- componentWillUnmount() {
- UserStore.removeChangeListener(this.onListenerChange);
- }
-
- onListenerChange() {
- this.updateState();
- }
-
- handleNotifyCommentsRadio(notifyCommentsLevel) {
- this.setState({notifyCommentsLevel});
- this.refs.wrapper.focus();
- }
-
- handlePushRadio(pushActivity) {
- this.setState({pushActivity});
- this.refs.wrapper.focus();
- }
-
- handlePushStatusRadio(pushStatus) {
- this.setState({pushStatus});
- this.refs.wrapper.focus();
- }
-
- handleEmailRadio(enableEmail) {
- this.setState({enableEmail});
- this.refs.wrapper.focus();
- }
-
- updateUsernameKey(val) {
- this.setState({usernameKey: val});
- }
-
- updateFirstNameKey(val) {
- this.setState({firstNameKey: val});
- }
-
- updateChannelKey(val) {
- this.setState({channelKey: val});
- }
-
- updateCustomMentionKeys() {
- const checked = this.refs.customcheck.checked;
-
- if (checked) {
- const text = this.refs.custommentions.value;
-
- // remove all spaces and split string into individual keys
- this.setState({customKeys: text.replace(/ /g, ''), customKeysChecked: true});
- } else {
- this.setState({customKeys: '', customKeysChecked: false});
- }
- }
-
- onCustomChange() {
- this.refs.customcheck.checked = true;
- this.updateCustomMentionKeys();
- }
-
- createPushNotificationSection() {
- if (this.props.activeSection === 'push') {
- const inputs = [];
- let extraInfo = null;
- let submit = null;
-
- if (global.window.mm_config.SendPushNotifications === 'true') {
- const pushActivityRadio = [false, false, false];
- if (this.state.pushActivity === 'all') {
- pushActivityRadio[0] = true;
- } else if (this.state.pushActivity === 'none') {
- pushActivityRadio[2] = true;
- } else {
- pushActivityRadio[1] = true;
- }
-
- const pushStatusRadio = [false, false, false];
- if (this.state.pushStatus === Constants.UserStatuses.ONLINE) {
- pushStatusRadio[0] = true;
- } else if (this.state.pushStatus === Constants.UserStatuses.AWAY) {
- pushStatusRadio[1] = true;
- } else {
- pushStatusRadio[2] = true;
- }
-
- let pushStatusSettings;
- if (this.state.pushActivity !== 'none') {
- pushStatusSettings = (
- <div>
- <hr/>
- <label>
- <FormattedMessage
- id='user.settings.notifications.push_notification.status'
- defaultMessage='Trigger push notifications when'
- />
- </label>
- <br/>
- <div className='radio'>
- <label>
- <input
- id='pushNotificationOnline'
- type='radio'
- name='pushNotificationStatus'
- checked={pushStatusRadio[0]}
- onChange={this.handlePushStatusRadio.bind(this, Constants.UserStatuses.ONLINE)}
- />
- <FormattedMessage
- id='user.settings.push_notification.online'
- defaultMessage='Online, away or offline'
- />
- </label>
- <br/>
- </div>
- <div className='radio'>
- <label>
- <input
- id='pushNotificationAway'
- type='radio'
- name='pushNotificationStatus'
- checked={pushStatusRadio[1]}
- onChange={this.handlePushStatusRadio.bind(this, Constants.UserStatuses.AWAY)}
- />
- <FormattedMessage
- id='user.settings.push_notification.away'
- defaultMessage='Away or offline'
- />
- </label>
- <br/>
- </div>
- <div className='radio'>
- <label>
- <input
- id='pushNotificationOffline'
- type='radio'
- name='pushNotificationStatus'
- checked={pushStatusRadio[2]}
- onChange={this.handlePushStatusRadio.bind(this, Constants.UserStatuses.OFFLINE)}
- />
- <FormattedMessage
- id='user.settings.push_notification.offline'
- defaultMessage='Offline'
- />
- </label>
- </div>
- </div>
- );
-
- extraInfo = (
- <span>
- <FormattedMessage
- id='user.settings.push_notification.status_info'
- defaultMessage='Notification alerts are only pushed to your mobile device when your online status matches the selection above.'
- />
- </span>
- );
- }
-
- inputs.push(
- <div key='userNotificationLevelOption'>
- <label>
- <FormattedMessage
- id='user.settings.push_notification.send'
- defaultMessage='Send mobile push notifications'
- />
- </label>
- <br/>
- <div className='radio'>
- <label>
- <input
- id='pushNotificationAllActivity'
- type='radio'
- name='pushNotificationLevel'
- checked={pushActivityRadio[0]}
- onChange={this.handlePushRadio.bind(this, 'all')}
- />
- <FormattedMessage
- id='user.settings.push_notification.allActivity'
- defaultMessage='For all activity'
- />
- </label>
- <br/>
- </div>
- <div className='radio'>
- <label>
- <input
- id='pushNotificationMentions'
- type='radio'
- name='pushNotificationLevel'
- checked={pushActivityRadio[1]}
- onChange={this.handlePushRadio.bind(this, 'mention')}
- />
- <FormattedMessage
- id='user.settings.push_notification.onlyMentions'
- defaultMessage='For mentions and direct messages'
- />
- </label>
- <br/>
- </div>
- <div className='radio'>
- <label>
- <input
- id='pushNotificationNever'
- type='radio'
- name='pushNotificationLevel'
- checked={pushActivityRadio[2]}
- onChange={this.handlePushRadio.bind(this, 'none')}
- />
- <FormattedMessage
- id='user.settings.notifications.never'
- defaultMessage='Never'
- />
- </label>
- </div>
- <br/>
- <span>
- <FormattedMessage
- id='user.settings.push_notification.info'
- defaultMessage='Notification alerts are pushed to your mobile device when there is activity in Mattermost.'
- />
- </span>
- {pushStatusSettings}
- </div>
- );
-
- submit = this.handleSubmit;
- } else {
- inputs.push(
- <div
- key='oauthEmailInfo'
- className='padding-top'
- >
- <FormattedMessage
- id='user.settings.push_notification.disabled_long'
- defaultMessage='Push notifications for mobile devices have been disabled by your System Administrator.'
- />
- </div>
- );
- }
-
- return (
- <SettingItemMax
- title={Utils.localizeMessage('user.settings.notifications.push', 'Mobile push notifications')}
- extraInfo={extraInfo}
- inputs={inputs}
- submit={submit}
- server_error={this.state.serverError}
- updateSection={this.handleCancel}
- />
- );
- }
-
- let describe = '';
- if (this.state.pushActivity === 'all') {
- if (this.state.pushStatus === Constants.UserStatuses.AWAY) {
- describe = (
- <FormattedMessage
- id='user.settings.push_notification.allActivityAway'
- defaultMessage='For all activity when away or offline'
- />
- );
- } else if (this.state.pushStatus === Constants.UserStatuses.OFFLINE) {
- describe = (
- <FormattedMessage
- id='user.settings.push_notification.allActivityOffline'
- defaultMessage='For all activity when offline'
- />
- );
- } else {
- describe = (
- <FormattedMessage
- id='user.settings.push_notification.allActivityOnline'
- defaultMessage='For all activity when online, away or offline'
- />
- );
- }
- } else if (this.state.pushActivity === 'none') {
- describe = (
- <FormattedMessage
- id='user.settings.notifications.never'
- defaultMessage='Never'
- />
- );
- } else if (global.window.mm_config.SendPushNotifications === 'false') {
- describe = (
- <FormattedMessage
- id='user.settings.push_notification.disabled'
- defaultMessage='Disabled by System Administrator'
- />
- );
- } else {
- if (this.state.pushStatus === Constants.UserStatuses.AWAY) { //eslint-disable-line no-lonely-if
- describe = (
- <FormattedMessage
- id='user.settings.push_notification.onlyMentionsAway'
- defaultMessage='For mentions and direct messages when away or offline'
- />
- );
- } else if (this.state.pushStatus === Constants.UserStatuses.OFFLINE) {
- describe = (
- <FormattedMessage
- id='user.settings.push_notification.onlyMentionsOffline'
- defaultMessage='For mentions and direct messages when offline'
- />
- );
- } else {
- describe = (
- <FormattedMessage
- id='user.settings.push_notification.onlyMentionsOnline'
- defaultMessage='For mentions and direct messages when online, away or offline'
- />
- );
- }
- }
-
- const handleUpdatePushSection = () => {
- this.props.updateSection('push');
- };
-
- return (
- <SettingItemMin
- title={Utils.localizeMessage('user.settings.notifications.push', 'Mobile push notifications')}
- describe={describe}
- updateSection={handleUpdatePushSection}
- />
- );
- }
-
- render() {
- const serverError = this.state.serverError;
- const user = this.props.user;
-
- let keysSection;
- let handleUpdateKeysSection;
- if (this.props.activeSection === 'keys') {
- const inputs = [];
-
- if (user.first_name) {
- const handleUpdateFirstNameKey = (e) => {
- this.updateFirstNameKey(e.target.checked);
- };
- inputs.push(
- <div key='userNotificationFirstNameOption'>
- <div className='checkbox'>
- <label>
- <input
- id='notificationTriggerFirst'
- type='checkbox'
- checked={this.state.firstNameKey}
- onChange={handleUpdateFirstNameKey}
- />
- <FormattedMessage
- id='user.settings.notifications.sensitiveName'
- defaultMessage='Your case sensitive first name "{first_name}"'
- values={{
- first_name: user.first_name
- }}
- />
- </label>
- </div>
- </div>
- );
- }
-
- const handleUpdateUsernameKey = (e) => {
- this.updateUsernameKey(e.target.checked);
- };
- inputs.push(
- <div key='userNotificationUsernameOption'>
- <div className='checkbox'>
- <label>
- <input
- id='notificationTriggerUsername'
- type='checkbox'
- checked={this.state.usernameKey}
- onChange={handleUpdateUsernameKey}
- />
- <FormattedMessage
- id='user.settings.notifications.sensitiveUsername'
- defaultMessage='Your non-case sensitive username "{username}"'
- values={{
- username: user.username
- }}
- />
- </label>
- </div>
- </div>
- );
-
- const handleUpdateChannelKey = (e) => {
- this.updateChannelKey(e.target.checked);
- };
- inputs.push(
- <div key='userNotificationChannelOption'>
- <div className='checkbox'>
- <label>
- <input
- id='notificationTriggerShouts'
- type='checkbox'
- checked={this.state.channelKey}
- onChange={handleUpdateChannelKey}
- />
- <FormattedMessage
- id='user.settings.notifications.channelWide'
- defaultMessage='Channel-wide mentions "@channel", "@all", "@here"'
- />
- </label>
- </div>
- </div>
- );
-
- inputs.push(
- <div key='userNotificationCustomOption'>
- <div className='checkbox'>
- <label>
- <input
- id='notificationTriggerCustom'
- ref='customcheck'
- type='checkbox'
- checked={this.state.customKeysChecked}
- onChange={this.updateCustomMentionKeys}
- />
- <FormattedMessage
- id='user.settings.notifications.sensitiveWords'
- defaultMessage='Other non-case sensitive words, separated by commas:'
- />
- </label>
- </div>
- <input
- id='notificationTriggerCustomText'
- ref='custommentions'
- className='form-control mentions-input'
- type='text'
- defaultValue={this.state.customKeys}
- onChange={this.onCustomChange}
- />
- </div>
- );
-
- const extraInfo = (
- <span>
- <FormattedMessage
- id='user.settings.notifications.mentionsInfo'
- defaultMessage='Mentions trigger when someone sends a message that includes your username (@{username}) or any of the options selected above.'
- values={{
- username: user.username
- }}
- />
- </span>
- );
-
- keysSection = (
- <SettingItemMax
- title={Utils.localizeMessage('user.settings.notifications.wordsTrigger', 'Words that trigger mentions')}
- inputs={inputs}
- submit={this.handleSubmit}
- server_error={serverError}
- updateSection={this.handleCancel}
- extraInfo={extraInfo}
- />
- );
- } else {
- let keys = ['@' + user.username];
- if (this.state.firstNameKey) {
- keys.push(user.first_name);
- }
- if (this.state.usernameKey) {
- keys.push(user.username);
- }
-
- if (this.state.channelKey) {
- keys.push('@channel');
- keys.push('@all');
- keys.push('@here');
- }
- if (this.state.customKeys.length > 0) {
- keys = keys.concat(this.state.customKeys.split(','));
- }
-
- let describe = '';
- for (let i = 0; i < keys.length; i++) {
- if (keys[i] !== '') {
- describe += '"' + keys[i] + '", ';
- }
- }
-
- if (describe.length > 0) {
- describe = describe.substring(0, describe.length - 2);
- } else {
- describe = (
- <FormattedMessage
- id='user.settings.notifications.noWords'
- defaultMessage='No words configured'
- />
- );
- }
-
- handleUpdateKeysSection = function updateKeysSection() {
- this.props.updateSection('keys');
- }.bind(this);
-
- keysSection = (
- <SettingItemMin
- title={Utils.localizeMessage('user.settings.notifications.wordsTrigger', 'Words that trigger mentions')}
- describe={describe}
- updateSection={handleUpdateKeysSection}
- />
- );
- }
-
- let commentsSection;
- let handleUpdateCommentsSection;
- if (this.props.activeSection === 'comments') {
- const commentsActive = [false, false, false];
- if (this.state.notifyCommentsLevel === 'never') {
- commentsActive[2] = true;
- } else if (this.state.notifyCommentsLevel === 'root') {
- commentsActive[1] = true;
- } else {
- commentsActive[0] = true;
- }
-
- const inputs = [];
-
- inputs.push(
- <div key='userNotificationLevelOption'>
- <div className='radio'>
- <label>
- <input
- id='notificationCommentsAny'
- type='radio'
- name='commentsNotificationLevel'
- checked={commentsActive[0]}
- onChange={this.handleNotifyCommentsRadio.bind(this, 'any')}
- />
- <FormattedMessage
- id='user.settings.notifications.commentsAny'
- defaultMessage='Mention any comments in a thread you participated in (This will include both mentions to your root post and any comments after you commented on a post)'
- />
- </label>
- <br/>
- </div>
- <div className='radio'>
- <label>
- <input
- id='notificationCommentsRoot'
- type='radio'
- name='commentsNotificationLevel'
- checked={commentsActive[1]}
- onChange={this.handleNotifyCommentsRadio.bind(this, 'root')}
- />
- <FormattedMessage
- id='user.settings.notifications.commentsRoot'
- defaultMessage='Mention any comments on your post'
- />
- </label>
- <br/>
- </div>
- <div className='radio'>
- <label>
- <input
- id='notificationCommentsNever'
- type='radio'
- name='commentsNotificationLevel'
- checked={commentsActive[2]}
- onChange={this.handleNotifyCommentsRadio.bind(this, 'never')}
- />
- <FormattedMessage
- id='user.settings.notifications.commentsNever'
- defaultMessage='No mentions for comments'
- />
- </label>
- </div>
- </div>
- );
-
- const extraInfo = (
- <span>
- <FormattedMessage
- id='user.settings.notifications.commentsInfo'
- defaultMessage="In addition to notifications for when you're mentioned, select if you would like to receive notifications on reply threads."
- />
- </span>
- );
-
- commentsSection = (
- <SettingItemMax
- title={Utils.localizeMessage('user.settings.notifications.comments', 'Reply notifications')}
- extraInfo={extraInfo}
- inputs={inputs}
- submit={this.handleSubmit}
- server_error={serverError}
- updateSection={this.handleCancel}
- />
- );
- } else {
- let describe = '';
- if (this.state.notifyCommentsLevel === 'never') {
- describe = (
- <FormattedMessage
- id='user.settings.notifications.commentsNever'
- defaultMessage="Do not trigger notifications on messages in reply threads unless I'm mentioned"
- />
- );
- } else if (this.state.notifyCommentsLevel === 'root') {
- describe = (
- <FormattedMessage
- id='user.settings.notifications.commentsRoot'
- defaultMessage='Trigger notifications on messages in threads that I start'
- />
- );
- } else {
- describe = (
- <FormattedMessage
- id='user.settings.notifications.commentsAny'
- defaultMessage='Trigger notifications on messages in reply threads that I start or participate in'
- />
- );
- }
-
- handleUpdateCommentsSection = function updateCommentsSection() {
- this.props.updateSection('comments');
- }.bind(this);
-
- commentsSection = (
- <SettingItemMin
- title={Utils.localizeMessage('user.settings.notifications.comments', 'Reply notifications')}
- describe={describe}
- updateSection={handleUpdateCommentsSection}
- />
- );
- }
-
- const pushNotificationSection = this.createPushNotificationSection();
- const enableEmail = this.state.enableEmail === 'true';
-
- return (
- <div>
- <div className='modal-header'>
- <button
- id='closeButton'
- type='button'
- className='close'
- data-dismiss='modal'
- onClick={this.props.closeModal}
- >
- <span aria-hidden='true'>{'×'}</span>
- </button>
- <h4
- className='modal-title'
- ref='title'
- >
- <div className='modal-back'>
- <i
- className='fa fa-angle-left'
- onClick={this.props.collapseModal}
- />
- </div>
- <FormattedMessage
- id='user.settings.notifications.title'
- defaultMessage='Notification Settings'
- />
- </h4>
- </div>
- <div
- ref='wrapper'
- className='user-settings'
- >
- <h3 className='tab-header'>
- <FormattedMessage
- id='user.settings.notifications.header'
- defaultMessage='Notifications'
- />
- </h3>
- <div className='divider-dark first'/>
- <DesktopNotificationSettings
- activity={this.state.desktopActivity}
- sound={this.state.desktopSound}
- duration={this.state.desktopDuration}
- updateSection={this.updateSection}
- setParentState={this.setStateValue}
- submit={this.handleSubmit}
- cancel={this.handleCancel}
- error={this.state.serverError}
- active={this.props.activeSection === 'desktop'}
- />
- <div className='divider-light'/>
- <EmailNotificationSetting
- activeSection={this.props.activeSection}
- updateSection={this.props.updateSection}
- enableEmail={enableEmail}
- emailInterval={Utils.getEmailInterval(enableEmail)}
- onSubmit={this.handleSubmit}
- onCancel={this.handleCancel}
- serverError={this.state.serverError}
- />
- <div className='divider-light'/>
- {pushNotificationSection}
- <div className='divider-light'/>
- {keysSection}
- <div className='divider-light'/>
- {commentsSection}
- <div className='divider-dark'/>
- </div>
- </div>
-
- );
- }
-}
-
-NotificationsTab.defaultProps = {
- user: null,
- activeSection: '',
- activeTab: ''
-};
-NotificationsTab.propTypes = {
- user: PropTypes.object,
- updateSection: PropTypes.func,
- updateTab: PropTypes.func,
- activeSection: PropTypes.string,
- activeTab: PropTypes.string,
- closeModal: PropTypes.func.isRequired,
- collapseModal: PropTypes.func.isRequired
-};
diff --git a/webapp/components/user_settings/user_settings_security/index.js b/webapp/components/user_settings/user_settings_security/index.js
deleted file mode 100644
index a3e83d7de..000000000
--- a/webapp/components/user_settings/user_settings_security/index.js
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getMe, getUserAccessTokensForUser, createUserAccessToken, revokeUserAccessToken, clearUserAccessTokens} from 'mattermost-redux/actions/users';
-import * as UserUtils from 'mattermost-redux/utils/user_utils';
-
-import SecurityTab from './user_settings_security.jsx';
-
-function mapStateToProps(state, ownProps) {
- const tokensEnabled = state.entities.general.config.EnableUserAccessTokens === 'true';
- const userHasTokenRole = UserUtils.hasUserAccessTokenRole(ownProps.user.roles) || UserUtils.isSystemAdmin(ownProps.user.roles);
-
- return {
- ...ownProps,
- userAccessTokens: state.entities.users.myUserAccessTokens,
- canUseAccessTokens: tokensEnabled && userHasTokenRole
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators({
- getMe,
- getUserAccessTokensForUser,
- createUserAccessToken,
- revokeUserAccessToken,
- clearUserAccessTokens
- }, dispatch)
- };
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(SecurityTab);
diff --git a/webapp/components/user_settings/user_settings_security/user_settings_security.jsx b/webapp/components/user_settings/user_settings_security/user_settings_security.jsx
deleted file mode 100644
index cbf3814bd..000000000
--- a/webapp/components/user_settings/user_settings_security/user_settings_security.jsx
+++ /dev/null
@@ -1,1469 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import SettingItemMin from 'components/setting_item_min.jsx';
-import SettingItemMax from 'components/setting_item_max.jsx';
-import AccessHistoryModal from 'components/access_history_modal';
-import ActivityLogModal from 'components/activity_log_modal';
-import ToggleModalButton from 'components/toggle_modal_button.jsx';
-import ConfirmModal from 'components/confirm_modal.jsx';
-
-import PreferenceStore from 'stores/preference_store.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-import Constants from 'utils/constants.jsx';
-
-import {updatePassword, getAuthorizedApps, deactivateMfa, deauthorizeOAuthApp} from 'actions/user_actions.jsx';
-import {trackEvent} from 'actions/diagnostics_actions.jsx';
-import {isMobile} from 'utils/user_agent.jsx';
-
-import $ from 'jquery';
-import PropTypes from 'prop-types';
-import React from 'react';
-import * as UserUtils from 'mattermost-redux/utils/user_utils';
-import {FormattedMessage, FormattedTime, FormattedDate, FormattedHTMLMessage} from 'react-intl';
-import {browserHistory, Link} from 'react-router/es6';
-
-import icon50 from 'images/icon50x50.png';
-
-const TOKEN_CREATING = 'creating';
-const TOKEN_CREATED = 'created';
-const TOKEN_NOT_CREATING = 'not_creating';
-
-export default class SecurityTab extends React.Component {
- static propTypes = {
- user: PropTypes.object,
- activeSection: PropTypes.string,
- updateSection: PropTypes.func,
- updateTab: PropTypes.func,
- closeModal: PropTypes.func.isRequired,
- collapseModal: PropTypes.func.isRequired,
- setEnforceFocus: PropTypes.func.isRequired,
-
- /*
- * The personal access tokens for the user
- */
- userAccessTokens: PropTypes.object,
-
- /*
- * Set if access tokens are enabled and this user can use them
- */
- canUseAccessTokens: PropTypes.bool,
-
- actions: PropTypes.shape({
- getMe: PropTypes.func.isRequired,
-
- /*
- * Function to get personal access tokens for a user
- */
- getUserAccessTokensForUser: PropTypes.func.isRequired,
-
- /*
- * Function to create a personal access token
- */
- createUserAccessToken: PropTypes.func.isRequired,
-
- /*
- * Function to revoke a personal access token
- */
- revokeUserAccessToken: PropTypes.func.isRequired,
-
- /*
- * Function to clear personal access tokens locally
- */
- clearUserAccessTokens: PropTypes.func.isRequired
- }).isRequired
- }
-
- constructor(props) {
- super(props);
-
- this.state = this.getDefaultState();
- }
-
- getDefaultState() {
- return {
- currentPassword: '',
- newPassword: '',
- confirmPassword: '',
- passwordError: '',
- serverError: '',
- tokenError: '',
- showConfirmModal: false,
- authService: this.props.user.auth_service
- };
- }
-
- componentDidMount() {
- if (global.mm_config.EnableOAuthServiceProvider === 'true') {
- getAuthorizedApps(
- (authorizedApps) => {
- this.setState({authorizedApps, serverError: null}); //eslint-disable-line react/no-did-mount-set-state
- },
- (err) => {
- this.setState({serverError: err.message}); //eslint-disable-line react/no-did-mount-set-state
- }
- );
- }
-
- if (this.props.canUseAccessTokens) {
- this.props.actions.clearUserAccessTokens();
- const userId = this.props.user ? this.props.user.id : '';
- this.props.actions.getUserAccessTokensForUser(userId, 0, 200);
- }
- }
-
- submitPassword = (e) => {
- e.preventDefault();
-
- var user = this.props.user;
- var currentPassword = this.state.currentPassword;
- var newPassword = this.state.newPassword;
- var confirmPassword = this.state.confirmPassword;
-
- if (currentPassword === '') {
- this.setState({passwordError: Utils.localizeMessage('user.settings.security.currentPasswordError', 'Please enter your current password.'), serverError: ''});
- return;
- }
-
- const passwordErr = Utils.isValidPassword(newPassword);
- if (passwordErr !== '') {
- this.setState({
- passwordError: passwordErr,
- serverError: ''
- });
- return;
- }
-
- if (newPassword !== confirmPassword) {
- var defaultState = Object.assign(this.getDefaultState(), {passwordError: Utils.localizeMessage('user.settings.security.passwordMatchError', 'The new passwords you entered do not match.'), serverError: ''});
- this.setState(defaultState);
- return;
- }
-
- updatePassword(
- user.id,
- currentPassword,
- newPassword,
- () => {
- this.props.updateSection('');
- this.props.actions.getMe();
- this.setState(this.getDefaultState());
- },
- (err) => {
- var state = this.getDefaultState();
- if (err.message) {
- state.serverError = err.message;
- } else {
- state.serverError = err;
- }
- state.passwordError = '';
- this.setState(state);
- }
- );
- }
-
- setupMfa = (e) => {
- e.preventDefault();
- browserHistory.push('/mfa/setup');
- }
-
- removeMfa = () => {
- deactivateMfa(
- () => {
- if (global.window.mm_license.MFA === 'true' &&
- global.window.mm_config.EnableMultifactorAuthentication === 'true' &&
- global.window.mm_config.EnforceMultifactorAuthentication === 'true') {
- window.location.href = '/mfa/setup';
- return;
- }
-
- this.props.updateSection('');
- this.setState(this.getDefaultState());
- },
- (err) => {
- const state = this.getDefaultState();
- if (err.message) {
- state.serverError = err.message;
- } else {
- state.serverError = err;
- }
- this.setState(state);
- }
- );
- }
-
- updateCurrentPassword = (e) => {
- this.setState({currentPassword: e.target.value});
- }
-
- updateNewPassword = (e) => {
- this.setState({newPassword: e.target.value});
- }
-
- updateConfirmPassword = (e) => {
- this.setState({confirmPassword: e.target.value});
- }
-
- deauthorizeApp = (e) => {
- e.preventDefault();
- const appId = e.currentTarget.getAttribute('data-app');
- deauthorizeOAuthApp(
- appId,
- () => {
- const authorizedApps = this.state.authorizedApps.filter((app) => {
- return app.id !== appId;
- });
-
- this.setState({authorizedApps, serverError: null});
- },
- (err) => {
- this.setState({serverError: err.message});
- }
- );
- }
-
- createMfaSection = () => {
- let updateSectionStatus;
- let submit;
-
- if (this.props.activeSection === 'mfa') {
- let content;
- let extraInfo;
- if (this.props.user.mfa_active) {
- let mfaRemoveHelp;
- let mfaButtonText;
-
- if (global.window.mm_config.EnforceMultifactorAuthentication === 'true') {
- mfaRemoveHelp = (
- <FormattedMessage
- id='user.settings.mfa.requiredHelp'
- defaultMessage='Multi-factor authentication is required on this server. Resetting is only recommended when you need to switch code generation to a new mobile device. You will be required to set it up again immediately.'
- />
- );
-
- mfaButtonText = (
- <FormattedMessage
- id='user.settings.mfa.reset'
- defaultMessage='Reset MFA on your account'
- />
- );
- } else {
- mfaRemoveHelp = (
- <FormattedMessage
- id='user.settings.mfa.removeHelp'
- defaultMessage='Removing multi-factor authentication means you will no longer require a phone-based passcode to sign-in to your account.'
- />
- );
-
- mfaButtonText = (
- <FormattedMessage
- id='user.settings.mfa.remove'
- defaultMessage='Remove MFA from your account'
- />
- );
- }
-
- content = (
- <div key='mfaQrCode'>
- <a
- className='btn btn-primary'
- href='#'
- onClick={this.removeMfa}
- >
- {mfaButtonText}
- </a>
- <br/>
- </div>
- );
-
- extraInfo = (
- <span>
- {mfaRemoveHelp}
- </span>
- );
- } else {
- content = (
- <div key='mfaQrCode'>
- <a
- className='btn btn-primary'
- href='#'
- onClick={this.setupMfa}
- >
- <FormattedMessage
- id='user.settings.mfa.add'
- defaultMessage='Add MFA to your account'
- />
- </a>
- <br/>
- </div>
- );
-
- extraInfo = (
- <span>
- <FormattedMessage
- id='user.settings.mfa.addHelp'
- defaultMessage='Adding multi-factor authentication will make your account more secure by requiring a code from your mobile phone each time you sign in.'
- />
- </span>
- );
- }
-
- const inputs = [];
- inputs.push(
- <div
- key='mfaSetting'
- className='padding-top'
- >
- {content}
- </div>
- );
-
- updateSectionStatus = function resetSection(e) {
- this.props.updateSection('');
- this.setState({serverError: null});
- e.preventDefault();
- }.bind(this);
-
- return (
- <SettingItemMax
- title={Utils.localizeMessage('user.settings.mfa.title', 'Multi-factor Authentication')}
- inputs={inputs}
- extraInfo={extraInfo}
- submit={submit}
- server_error={this.state.serverError}
- updateSection={updateSectionStatus}
- width='medium'
- />
- );
- }
-
- let describe;
- if (this.props.user.mfa_active) {
- describe = Utils.localizeMessage('user.settings.security.active', 'Active');
- } else {
- describe = Utils.localizeMessage('user.settings.security.inactive', 'Inactive');
- }
-
- updateSectionStatus = function updateSection() {
- this.props.updateSection('mfa');
- }.bind(this);
-
- return (
- <SettingItemMin
- title={Utils.localizeMessage('user.settings.mfa.title', 'Multi-factor Authentication')}
- describe={describe}
- updateSection={updateSectionStatus}
- />
- );
- }
-
- createPasswordSection = () => {
- let updateSectionStatus;
-
- if (this.props.activeSection === 'password') {
- const inputs = [];
- let submit;
-
- if (this.props.user.auth_service === '') {
- submit = this.submitPassword;
-
- inputs.push(
- <div
- key='currentPasswordUpdateForm'
- className='form-group'
- >
- <label className='col-sm-5 control-label'>
- <FormattedMessage
- id='user.settings.security.currentPassword'
- defaultMessage='Current Password'
- />
- </label>
- <div className='col-sm-7'>
- <input
- id='currentPassword'
- className='form-control'
- type='password'
- onChange={this.updateCurrentPassword}
- value={this.state.currentPassword}
- />
- </div>
- </div>
- );
- inputs.push(
- <div
- key='newPasswordUpdateForm'
- className='form-group'
- >
- <label className='col-sm-5 control-label'>
- <FormattedMessage
- id='user.settings.security.newPassword'
- defaultMessage='New Password'
- />
- </label>
- <div className='col-sm-7'>
- <input
- id='newPassword'
- className='form-control'
- type='password'
- onChange={this.updateNewPassword}
- value={this.state.newPassword}
- />
- </div>
- </div>
- );
- inputs.push(
- <div
- key='retypeNewPasswordUpdateForm'
- className='form-group'
- >
- <label className='col-sm-5 control-label'>
- <FormattedMessage
- id='user.settings.security.retypePassword'
- defaultMessage='Retype New Password'
- />
- </label>
- <div className='col-sm-7'>
- <input
- id='confirmPassword'
- className='form-control'
- type='password'
- onChange={this.updateConfirmPassword}
- value={this.state.confirmPassword}
- />
- </div>
- </div>
- );
- } else if (this.props.user.auth_service === Constants.GITLAB_SERVICE) {
- inputs.push(
- <div
- key='oauthEmailInfo'
- className='form-group'
- >
- <div className='setting-list__hint col-sm-12'>
- <FormattedMessage
- id='user.settings.security.passwordGitlabCantUpdate'
- defaultMessage='Login occurs through GitLab. Password cannot be updated.'
- />
- </div>
- </div>
- );
- } else if (this.props.user.auth_service === Constants.LDAP_SERVICE) {
- inputs.push(
- <div
- key='oauthEmailInfo'
- className='form-group'
- >
- <div className='setting-list__hint col-sm-12'>
- <FormattedMessage
- id='user.settings.security.passwordLdapCantUpdate'
- defaultMessage='Login occurs through AD/LDAP. Password cannot be updated.'
- />
- </div>
- </div>
- );
- } else if (this.props.user.auth_service === Constants.SAML_SERVICE) {
- inputs.push(
- <div
- key='oauthEmailInfo'
- className='form-group'
- >
- <div className='setting-list__hint col-sm-12'>
- <FormattedMessage
- id='user.settings.security.passwordSamlCantUpdate'
- defaultMessage='This field is handled through your login provider. If you want to change it, you need to do so through your login provider.'
- />
- </div>
- </div>
- );
- } else if (this.props.user.auth_service === Constants.GOOGLE_SERVICE) {
- inputs.push(
- <div
- key='oauthEmailInfo'
- className='form-group'
- >
- <div className='setting-list__hint col-sm-12'>
- <FormattedMessage
- id='user.settings.security.passwordGoogleCantUpdate'
- defaultMessage='Login occurs through Google Apps. Password cannot be updated.'
- />
- </div>
- </div>
- );
- } else if (this.props.user.auth_service === Constants.OFFICE365_SERVICE) {
- inputs.push(
- <div
- key='oauthEmailInfo'
- className='form-group'
- >
- <div className='setting-list__hint col-sm-12'>
- <FormattedMessage
- id='user.settings.security.passwordOffice365CantUpdate'
- defaultMessage='Login occurs through Office 365. Password cannot be updated.'
- />
- </div>
- </div>
- );
- }
-
- updateSectionStatus = function resetSection(e) {
- this.props.updateSection('');
- this.setState({currentPassword: '', newPassword: '', confirmPassword: '', serverError: null, passwordError: null});
- e.preventDefault();
- $('.settings-modal .modal-body').scrollTop(0).perfectScrollbar('update');
- }.bind(this);
-
- return (
- <SettingItemMax
- title={
- <FormattedMessage
- id='user.settings.security.password'
- defaultMessage='Password'
- />
- }
- inputs={inputs}
- submit={submit}
- server_error={this.state.serverError}
- client_error={this.state.passwordError}
- updateSection={updateSectionStatus}
- />
- );
- }
-
- let describe;
-
- if (this.props.user.auth_service === '') {
- const d = new Date(this.props.user.last_password_update);
- const hours12 = !PreferenceStore.getBool(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, Constants.Preferences.USE_MILITARY_TIME, false);
-
- describe = (
- <FormattedMessage
- id='user.settings.security.lastUpdated'
- defaultMessage='Last updated {date} at {time}'
- values={{
- date: (
- <FormattedDate
- value={d}
- day='2-digit'
- month='short'
- year='numeric'
- />
- ),
- time: (
- <FormattedTime
- value={d}
- hour12={hours12}
- hour='2-digit'
- minute='2-digit'
- />
- )
- }}
- />
- );
- } else if (this.props.user.auth_service === Constants.GITLAB_SERVICE) {
- describe = (
- <FormattedMessage
- id='user.settings.security.loginGitlab'
- defaultMessage='Login done through GitLab'
- />
- );
- } else if (this.props.user.auth_service === Constants.LDAP_SERVICE) {
- describe = (
- <FormattedMessage
- id='user.settings.security.loginLdap'
- defaultMessage='Login done through AD/LDAP'
- />
- );
- } else if (this.props.user.auth_service === Constants.SAML_SERVICE) {
- describe = (
- <FormattedMessage
- id='user.settings.security.loginSaml'
- defaultMessage='Login done through SAML'
- />
- );
- } else if (this.props.user.auth_service === Constants.GOOGLE_SERVICE) {
- describe = (
- <FormattedMessage
- id='user.settings.security.loginGoogle'
- defaultMessage='Login done through Google Apps'
- />
- );
- } else if (this.props.user.auth_service === Constants.OFFICE365_SERVICE) {
- describe = (
- <FormattedMessage
- id='user.settings.security.loginOffice365'
- defaultMessage='Login done through Office 365'
- />
- );
- }
-
- updateSectionStatus = function updateSection() {
- this.props.updateSection('password');
- }.bind(this);
-
- return (
- <SettingItemMin
- title={
- <FormattedMessage
- id='user.settings.security.password'
- defaultMessage='Password'
- />
- }
- describe={describe}
- updateSection={updateSectionStatus}
- />
- );
- }
-
- createSignInSection = () => {
- let updateSectionStatus;
- const user = this.props.user;
-
- if (this.props.activeSection === 'signin') {
- let emailOption;
- let gitlabOption;
- let googleOption;
- let office365Option;
- let ldapOption;
- let samlOption;
-
- if (user.auth_service === '') {
- if (global.window.mm_config.EnableSignUpWithGitLab === 'true') {
- gitlabOption = (
- <div className='padding-bottom x2'>
- <Link
- className='btn btn-primary'
- to={'/claim/email_to_oauth?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service + '&new_type=' + Constants.GITLAB_SERVICE}
- >
- <FormattedMessage
- id='user.settings.security.switchGitlab'
- defaultMessage='Switch to using GitLab SSO'
- />
- </Link>
- <br/>
- </div>
- );
- }
-
- if (global.window.mm_config.EnableSignUpWithGoogle === 'true') {
- googleOption = (
- <div className='padding-bottom x2'>
- <Link
- className='btn btn-primary'
- to={'/claim/email_to_oauth?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service + '&new_type=' + Constants.GOOGLE_SERVICE}
- >
- <FormattedMessage
- id='user.settings.security.switchGoogle'
- defaultMessage='Switch to using Google SSO'
- />
- </Link>
- <br/>
- </div>
- );
- }
-
- if (global.window.mm_config.EnableSignUpWithOffice365 === 'true') {
- office365Option = (
- <div className='padding-bottom x2'>
- <Link
- className='btn btn-primary'
- to={'/claim/email_to_oauth?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service + '&new_type=' + Constants.OFFICE365_SERVICE}
- >
- <FormattedMessage
- id='user.settings.security.switchOffice365'
- defaultMessage='Switch to using Office 365 SSO'
- />
- </Link>
- <br/>
- </div>
- );
- }
-
- if (global.window.mm_config.EnableLdap === 'true') {
- ldapOption = (
- <div className='padding-bottom x2'>
- <Link
- className='btn btn-primary'
- to={'/claim/email_to_ldap?email=' + encodeURIComponent(user.email)}
- >
- <FormattedMessage
- id='user.settings.security.switchLdap'
- defaultMessage='Switch to using AD/LDAP'
- />
- </Link>
- <br/>
- </div>
- );
- }
-
- if (global.window.mm_config.EnableSaml === 'true') {
- samlOption = (
- <div className='padding-bottom x2'>
- <Link
- className='btn btn-primary'
- to={'/claim/email_to_oauth?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service + '&new_type=' + Constants.SAML_SERVICE}
- >
- <FormattedMessage
- id='user.settings.security.switchSaml'
- defaultMessage='Switch to using SAML SSO'
- />
- </Link>
- <br/>
- </div>
- );
- }
- } else if (global.window.mm_config.EnableSignUpWithEmail === 'true') {
- let link;
- if (user.auth_service === Constants.LDAP_SERVICE) {
- link = '/claim/ldap_to_email?email=' + encodeURIComponent(user.email);
- } else {
- link = '/claim/oauth_to_email?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service;
- }
-
- emailOption = (
- <div className='padding-bottom x2'>
- <Link
- className='btn btn-primary'
- to={link}
- >
- <FormattedMessage
- id='user.settings.security.switchEmail'
- defaultMessage='Switch to using email and password'
- />
- </Link>
- <br/>
- </div>
- );
- }
-
- const inputs = [];
- inputs.push(
- <div key='userSignInOption'>
- {emailOption}
- {gitlabOption}
- {googleOption}
- {office365Option}
- {ldapOption}
- {samlOption}
- </div>
- );
-
- updateSectionStatus = function updateSection(e) {
- this.props.updateSection('');
- this.setState({serverError: null});
- e.preventDefault();
- }.bind(this);
-
- const extraInfo = (
- <span>
- <FormattedMessage
- id='user.settings.security.oneSignin'
- defaultMessage='You may only have one sign-in method at a time. Switching sign-in method will send an email notifying you if the change was successful.'
- />
- </span>
- );
-
- return (
- <SettingItemMax
- title={Utils.localizeMessage('user.settings.security.method', 'Sign-in Method')}
- extraInfo={extraInfo}
- inputs={inputs}
- server_error={this.state.serverError}
- updateSection={updateSectionStatus}
- />
- );
- }
-
- updateSectionStatus = function updateSection() {
- this.props.updateSection('signin');
- }.bind(this);
-
- let describe = (
- <FormattedMessage
- id='user.settings.security.emailPwd'
- defaultMessage='Email and Password'
- />
- );
- if (this.props.user.auth_service === Constants.GITLAB_SERVICE) {
- describe = (
- <FormattedMessage
- id='user.settings.security.gitlab'
- defaultMessage='GitLab'
- />
- );
- } else if (this.props.user.auth_service === Constants.GOOGLE_SERVICE) {
- describe = (
- <FormattedMessage
- id='user.settings.security.google'
- defaultMessage='Google'
- />
- );
- } else if (this.props.user.auth_service === Constants.OFFICE365_SERVICE) {
- describe = (
- <FormattedMessage
- id='user.settings.security.office365'
- defaultMessage='Office 365'
- />
- );
- } else if (this.props.user.auth_service === Constants.LDAP_SERVICE) {
- describe = (
- <FormattedMessage
- id='user.settings.security.ldap'
- defaultMessage='AD/LDAP'
- />
- );
- } else if (this.props.user.auth_service === Constants.SAML_SERVICE) {
- describe = (
- <FormattedMessage
- id='user.settings.security.saml'
- defaultMessage='SAML'
- />
- );
- }
-
- return (
- <SettingItemMin
- title={Utils.localizeMessage('user.settings.security.method', 'Sign-in Method')}
- describe={describe}
- updateSection={updateSectionStatus}
- />
- );
- }
-
- createOAuthAppsSection = () => {
- let updateSectionStatus;
-
- if (this.props.activeSection === 'apps') {
- let apps;
- if (this.state.authorizedApps && this.state.authorizedApps.length > 0) {
- apps = this.state.authorizedApps.map((app) => {
- const homepage = (
- <a
- href={app.homepage}
- target='_blank'
- rel='noopener noreferrer'
- >
- {app.homepage}
- </a>
- );
-
- return (
- <div
- key={app.id}
- className='padding-bottom x2 authorized-app'
- >
- <div className='col-sm-10'>
- <div className='authorized-app__name'>
- {app.name}
- <span className='authorized-app__url'>
- {' -'} {homepage}
- </span>
- </div>
- <div className='authorized-app__description'>{app.description}</div>
- <div className='authorized-app__deauthorize'>
- <a
- href='#'
- data-app={app.id}
- onClick={this.deauthorizeApp}
- >
- <FormattedMessage
- id='user.settings.security.deauthorize'
- defaultMessage='Deauthorize'
- />
- </a>
- </div>
- </div>
- <div className='col-sm-2 pull-right'>
- <img
- alt={app.name}
- src={app.icon_url || icon50}
- />
- </div>
- <br/>
- </div>
- );
- });
- } else {
- apps = (
- <div className='padding-bottom x2 authorized-app'>
- <div className='setting-list__hint'>
- <FormattedMessage
- id='user.settings.security.noApps'
- defaultMessage='No OAuth 2.0 Applications are authorized.'
- />
- </div>
- </div>
- );
- }
-
- const inputs = [];
- let wrapperClass;
- let helpText;
- if (Array.isArray(apps)) {
- wrapperClass = 'authorized-apps__wrapper';
-
- helpText = (
- <div className='authorized-apps__help'>
- <FormattedMessage
- id='user.settings.security.oauthAppsHelp'
- defaultMessage='Applications act on your behalf to access your data based on the permissions you grant them.'
- />
- </div>
- );
- }
-
- inputs.push(
- <div
- className={wrapperClass}
- key='authorizedApps'
- >
- {apps}
- </div>
- );
-
- updateSectionStatus = function updateSection(e) {
- this.props.updateSection('');
- this.setState({serverError: null});
- e.preventDefault();
- }.bind(this);
-
- const title = (
- <div>
- <FormattedMessage
- id='user.settings.security.oauthApps'
- defaultMessage='OAuth 2.0 Applications'
- />
- {helpText}
- </div>
- );
-
- return (
- <SettingItemMax
- title={title}
- inputs={inputs}
- server_error={this.state.serverError}
- updateSection={updateSectionStatus}
- width='full'
- cancelButtonText={
- <FormattedMessage
- id='user.settings.security.close'
- defaultMessage='Close'
- />
- }
- />
- );
- }
-
- updateSectionStatus = function updateSection() {
- this.props.updateSection('apps');
- }.bind(this);
-
- return (
- <SettingItemMin
- title={Utils.localizeMessage('user.settings.security.oauthApps', 'OAuth 2.0 Applications')}
- describe={
- <FormattedMessage
- id='user.settings.security.oauthAppsDescription'
- defaultMessage="Click 'Edit' to manage your OAuth 2.0 Applications"
- />
- }
- updateSection={updateSectionStatus}
- />
- );
- }
-
- startCreatingToken = () => {
- this.setState({tokenCreationState: TOKEN_CREATING});
- }
-
- stopCreatingToken = () => {
- this.setState({tokenCreationState: TOKEN_NOT_CREATING});
- }
-
- handleCreateToken = async () => {
- this.handleCancelConfirm();
-
- const description = this.refs.newtokendescription ? this.refs.newtokendescription.value : '';
-
- if (description === '') {
- this.setState({tokenError: Utils.localizeMessage('user.settings.tokens.nameRequired', 'Please enter a description.')});
- return;
- }
-
- this.setState({tokenError: ''});
-
- const userId = this.props.user ? this.props.user.id : '';
- const {data, error} = await this.props.actions.createUserAccessToken(userId, description);
-
- if (data) {
- this.setState({tokenCreationState: TOKEN_CREATED, newToken: data});
- } else if (error) {
- this.setState({serverError: error.message});
- }
- }
-
- handleCancelConfirm = () => {
- this.setState({
- showConfirmModal: false,
- confirmTitle: null,
- confirmMessage: null,
- confirmButton: null,
- confirmComplete: null
- });
- }
-
- confirmCreateToken = () => {
- if (UserUtils.isSystemAdmin(this.props.user.roles)) {
- this.setState({
- showConfirmModal: true,
- confirmTitle: (
- <FormattedMessage
- id='user.settings.tokens.confirmCreateTitle'
- defaultMessage='Create System Admin Personal Access Token'
- />
- ),
- confirmMessage: (
- <div className='alert alert-danger'>
- <FormattedHTMLMessage
- id='user.settings.tokens.confirmCreateMessage'
- defaultMessage='You are generating a personal access token with System Admin permissions. Are you sure want to create this token?'
- />
- </div>
- ),
- confirmButton: (
- <FormattedMessage
- id='user.settings.tokens.confirmCreateButton'
- defaultMessage='Yes, Create'
- />
- ),
- confirmComplete: () => {
- this.handleCreateToken();
- trackEvent('settings', 'system_admin_create_user_access_token');
- }
- });
-
- return;
- }
-
- this.handleCreateToken();
- }
-
- saveTokenKeyPress = (e) => {
- if (e.which === Constants.KeyCodes.ENTER) {
- this.confirmCreateToken();
- }
- }
-
- confirmRevokeToken = (tokenId) => {
- const token = this.props.userAccessTokens[tokenId];
-
- this.setState({
- showConfirmModal: true,
- confirmTitle: (
- <FormattedMessage
- id='user.settings.tokens.confirmDeleteTitle'
- defaultMessage='Delete Token?'
- />
- ),
- confirmMessage: (
- <div className='alert alert-danger'>
- <FormattedHTMLMessage
- id='user.settings.tokens.confirmDeleteMessage'
- defaultMessage='Any integrations using this token will no longer be able to access the Mattermost API. You cannot undo this action. <br /><br />Are you sure want to delete the {description} token?'
- values={{
- description: token.description
- }}
- />
- </div>
- ),
- confirmButton: (
- <FormattedMessage
- id='user.settings.tokens.confirmDeleteButton'
- defaultMessage='Yes, Delete'
- />
- ),
- confirmComplete: () => {
- this.revokeToken(tokenId);
- trackEvent('settings', 'revoke_user_access_token');
- }
- });
- }
-
- revokeToken = async (tokenId) => {
- const {error} = await this.props.actions.revokeUserAccessToken(tokenId);
- if (error) {
- this.setState({serverError: error.message});
- }
- this.handleCancelConfirm();
- }
-
- createTokensSection = () => {
- let updateSectionStatus;
- let tokenListClass = '';
-
- if (this.props.activeSection === 'tokens') {
- const tokenList = [];
- Object.values(this.props.userAccessTokens).forEach((token) => {
- if (this.state.newToken && this.state.newToken.id === token.id) {
- return;
- }
-
- tokenList.push(
- <div
- key={token.id}
- className='setting-box__item'
- >
- <div className='whitespace--nowrap overflow--ellipsis'>
- <FormattedMessage
- id='user.settings.tokens.tokenDesc'
- defaultMessage='Token Description: '
- />
- {token.description}
- </div>
- <div className='setting-box__token-id whitespace--nowrap overflow--ellipsis'>
- <FormattedMessage
- id='user.settings.tokens.tokenId'
- defaultMessage='Token ID: '
- />
- {token.id}
- </div>
- <div>
- <a
- name={token.id}
- href='#'
- onClick={(e) => {
- e.preventDefault();
- this.confirmRevokeToken(token.id);
- }}
- >
- <FormattedMessage
- id='user.settings.tokens.delete'
- defaultMessage='Delete'
- />
- </a>
- </div>
- <hr className='margin-bottom margin-top x2'/>
- </div>
- );
- });
-
- let noTokenText;
- if (tokenList.length === 0) {
- noTokenText = (
- <FormattedMessage
- key='notokens'
- id='user.settings.tokens.userAccessTokensNone'
- defaultMessage='No personal access tokens.'
- />
- );
- }
-
- let extraInfo;
- if (isMobile()) {
- extraInfo = (
- <span>
- <FormattedHTMLMessage
- id='user.settings.tokens.description_mobile'
- defaultMessage='<a href="https://about.mattermost.com/default-user-access-tokens" target="_blank">Personal access tokens</a> function similar to session tokens and can be used by integrations to <a href="https://about.mattermost.com/default-api-authentication" target="_blank">authenticate against the REST API</a>. Create new tokens on your desktop.'
- />
- </span>
- );
- } else {
- extraInfo = (
- <span>
- <FormattedHTMLMessage
- id='user.settings.tokens.description'
- defaultMessage='<a href="https://about.mattermost.com/default-user-access-tokens" target="_blank">Personal access tokens</a> function similar to session tokens and can be used by integrations to <a href="https://about.mattermost.com/default-api-authentication" target="_blank">authenticate against the REST API</a>.'
- />
- </span>
- );
- }
-
- let newTokenSection;
- if (this.state.tokenCreationState === TOKEN_CREATING) {
- newTokenSection = (
- <div className='padding-left x2'>
- <div className='row'>
- <label className='col-sm-auto control-label padding-right x2'>
- <FormattedMessage
- id='user.settings.tokens.name'
- defaultMessage='Token Description: '
- />
- </label>
- <div className='col-sm-5'>
- <input
- ref='newtokendescription'
- className='form-control'
- type='text'
- maxLength={64}
- onKeyPress={this.saveTokenKeyPress}
- />
- </div>
- </div>
- <div>
- <div className='padding-top x2'>
- <FormattedMessage
- id='user.settings.tokens.nameHelp'
- defaultMessage='Enter a description for your token to remember what it does.'
- />
- </div>
- <div>
- <label
- id='clientError'
- className='has-error margin-top margin-bottom'
- >
- {this.state.tokenError}
- </label>
- </div>
- <button
- className='btn btn-primary'
- onClick={this.confirmCreateToken}
- >
- <FormattedMessage
- id='user.settings.tokens.save'
- defaultMessage='Save'
- />
- </button>
- <button
- className='btn btn-default'
- onClick={this.stopCreatingToken}
- >
- <FormattedMessage
- id='user.settings.tokens.cancel'
- defaultMessage='Cancel'
- />
- </button>
- </div>
- </div>
- );
- } else if (this.state.tokenCreationState === TOKEN_CREATED) {
- if (tokenList.length === 0) {
- tokenListClass = ' hidden';
- }
-
- newTokenSection = (
- <div
- className='alert alert-warning'
- >
- <i className='fa fa-warning margin-right'/>
- <FormattedMessage
- id='user.settings.tokens.copy'
- defaultMessage="Please copy the access token below. You won't be able to see it again!"
- />
- <br/>
- <br/>
- <div className='whitespace--nowrap overflow--ellipsis'>
- <FormattedMessage
- id='user.settings.tokens.name'
- defaultMessage='Token Description: '
- />
- {this.state.newToken.description}
- </div>
- <div className='whitespace--nowrap overflow--ellipsis'>
- <FormattedMessage
- id='user.settings.tokens.id'
- defaultMessage='Token ID: '
- />
- {this.state.newToken.id}
- </div>
- <strong className='word-break--all'>
- <FormattedMessage
- id='user.settings.tokens.token'
- defaultMessage='Access Token: '
- />
- {this.state.newToken.token}
- </strong>
- </div>
- );
- } else {
- newTokenSection = (
- <a
- className='btn btn-primary'
- href='#'
- onClick={this.startCreatingToken}
- >
- <FormattedMessage
- id='user.settings.tokens.create'
- defaultMessage='Create New Token'
- />
- </a>
- );
- }
-
- const inputs = [];
- inputs.push(
- <div
- key='tokensSetting'
- className='padding-top'
- >
- <div key='tokenList'>
- <div className={'alert alert-transparent' + tokenListClass}>
- {tokenList}
- {noTokenText}
- </div>
- {newTokenSection}
- </div>
- </div>
- );
-
- updateSectionStatus = function resetSection(e) {
- this.props.updateSection('');
- this.setState({newToken: null, tokenCreationState: TOKEN_NOT_CREATING, serverError: null, tokenError: ''});
- e.preventDefault();
- }.bind(this);
-
- return (
- <SettingItemMax
- title={Utils.localizeMessage('user.settings.tokens.title', 'Personal Access Tokens')}
- inputs={inputs}
- extraInfo={extraInfo}
- infoPosition='top'
- server_error={this.state.serverError}
- updateSection={updateSectionStatus}
- width='full'
- cancelButtonText={
- <FormattedMessage
- id='user.settings.security.close'
- defaultMessage='Close'
- />
- }
- />
- );
- }
-
- const describe = Utils.localizeMessage('user.settings.tokens.clickToEdit', "Click 'Edit' to manage your personal access tokens");
-
- updateSectionStatus = function updateSection() {
- this.props.updateSection('tokens');
- }.bind(this);
-
- return (
- <SettingItemMin
- title={Utils.localizeMessage('user.settings.tokens.title', 'Personal Access Tokens')}
- describe={describe}
- updateSection={updateSectionStatus}
- />
- );
- }
-
- render() {
- const user = this.props.user;
- const config = window.mm_config;
-
- const passwordSection = this.createPasswordSection();
-
- let numMethods = 0;
- numMethods = config.EnableSignUpWithGitLab === 'true' ? numMethods + 1 : numMethods;
- numMethods = config.EnableSignUpWithGoogle === 'true' ? numMethods + 1 : numMethods;
- numMethods = config.EnableLdap === 'true' ? numMethods + 1 : numMethods;
- numMethods = config.EnableSaml === 'true' ? numMethods + 1 : numMethods;
-
- // If there are other sign-in methods and either email is enabled or the user's account is email, then allow switching
- let signInSection;
- if ((config.EnableSignUpWithEmail === 'true' || user.auth_service === '') && numMethods > 0) {
- signInSection = this.createSignInSection();
- }
-
- let mfaSection;
- if (config.EnableMultifactorAuthentication === 'true' &&
- global.window.mm_license.IsLicensed === 'true' &&
- (user.auth_service === '' || user.auth_service === Constants.LDAP_SERVICE)) {
- mfaSection = this.createMfaSection();
- }
-
- let oauthSection;
- if (config.EnableOAuthServiceProvider === 'true') {
- oauthSection = this.createOAuthAppsSection();
- }
-
- let tokensSection;
- if (this.props.canUseAccessTokens) {
- tokensSection = this.createTokensSection();
- }
-
- return (
- <div>
- <div className='modal-header'>
- <button
- type='button'
- className='close'
- data-dismiss='modal'
- aria-label={Utils.localizeMessage('user.settings.security.close', 'Close')}
- onClick={this.props.closeModal}
- >
- <span aria-hidden='true'>{'×'}</span>
- </button>
- <h4
- className='modal-title'
- ref='title'
- >
- <div className='modal-back'>
- <i
- className='fa fa-angle-left'
- onClick={this.props.collapseModal}
- />
- </div>
- <FormattedMessage
- id='user.settings.security.title'
- defaultMessage='Security Settings'
- />
- </h4>
- </div>
- <div className='user-settings'>
- <h3 className='tab-header'>
- <FormattedMessage
- id='user.settings.security.title'
- defaultMessage='Security Settings'
- />
- </h3>
- <div className='divider-dark first'/>
- {passwordSection}
- <div className='divider-light'/>
- {mfaSection}
- <div className='divider-light'/>
- {oauthSection}
- <div className='divider-light'/>
- {tokensSection}
- <div className='divider-light'/>
- {signInSection}
- <div className='divider-dark'/>
- <br/>
- <ToggleModalButton
- className='security-links theme'
- dialogType={AccessHistoryModal}
- >
- <i className='fa fa-clock-o'/>
- <FormattedMessage
- id='user.settings.security.viewHistory'
- defaultMessage='View Access History'
- />
- </ToggleModalButton>
- <br/>
- <ToggleModalButton
- className='security-links theme'
- dialogType={ActivityLogModal}
- >
- <i className='fa fa-clock-o'/>
- <FormattedMessage
- id='user.settings.security.logoutActiveSessions'
- defaultMessage='View and Logout of Active Sessions'
- />
- </ToggleModalButton>
- </div>
- <ConfirmModal
- title={this.state.confirmTitle}
- message={this.state.confirmMessage}
- confirmButtonText={this.state.confirmButton}
- show={this.state.showConfirmModal}
- onConfirm={this.state.confirmComplete || (() => {})} //eslint-disable-line no-empty-function
- onCancel={this.handleCancelConfirm}
- />
- </div>
- );
- }
-}
-
-SecurityTab.defaultProps = {
- user: {},
- activeSection: ''
-};
diff --git a/webapp/components/user_settings/user_settings_theme.jsx b/webapp/components/user_settings/user_settings_theme.jsx
deleted file mode 100644
index 871e3ccae..000000000
--- a/webapp/components/user_settings/user_settings_theme.jsx
+++ /dev/null
@@ -1,362 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import ReactDOM from 'react-dom';
-import CustomThemeChooser from './custom_theme_chooser.jsx';
-import PremadeThemeChooser from './premade_theme_chooser.jsx';
-import SettingItemMin from '../setting_item_min.jsx';
-import SettingItemMax from '../setting_item_max.jsx';
-
-import PreferenceStore from 'stores/preference_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-
-import AppDispatcher from '../../dispatcher/app_dispatcher.jsx';
-import * as UserActions from 'actions/user_actions.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-
-import {FormattedMessage} from 'react-intl';
-
-import {ActionTypes, Constants, Preferences} from 'utils/constants.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default class ThemeSetting extends React.Component {
- constructor(props) {
- super(props);
-
- this.onChange = this.onChange.bind(this);
- this.submitTheme = this.submitTheme.bind(this);
- this.updateTheme = this.updateTheme.bind(this);
- this.resetFields = this.resetFields.bind(this);
- this.handleImportModal = this.handleImportModal.bind(this);
-
- this.state = this.getStateFromStores();
-
- this.originalTheme = Object.assign({}, this.state.theme);
- }
-
- componentDidMount() {
- UserStore.addChangeListener(this.onChange);
-
- if (this.props.selected) {
- $(ReactDOM.findDOMNode(this.refs[this.state.theme])).addClass('active-border');
- }
- }
-
- componentDidUpdate() {
- if (this.props.selected) {
- $('.color-btn').removeClass('active-border');
- $(ReactDOM.findDOMNode(this.refs[this.state.theme])).addClass('active-border');
- }
- }
-
- componentWillReceiveProps(nextProps) {
- if (this.props.selected && !nextProps.selected) {
- this.resetFields();
- }
- }
-
- componentWillUnmount() {
- UserStore.removeChangeListener(this.onChange);
-
- if (this.props.selected) {
- const state = this.getStateFromStores();
- Utils.applyTheme(state.theme);
- }
- }
-
- getStateFromStores() {
- const teamId = TeamStore.getCurrentId();
-
- const theme = PreferenceStore.getTheme(teamId);
- if (!theme.codeTheme) {
- theme.codeTheme = Constants.DEFAULT_CODE_THEME;
- }
-
- let showAllTeamsCheckbox = false;
- let applyToAllTeams = true;
-
- if (global.window.mm_license.IsLicensed === 'true' && global.window.mm_license.LDAP === 'true') {
- // show the "apply to all teams" checkbox if the user is on more than one team
- showAllTeamsCheckbox = Object.keys(TeamStore.getAll()).length > 1;
-
- // check the "apply to all teams" checkbox by default if the user has any team-specific themes
- applyToAllTeams = PreferenceStore.getCategory(Preferences.CATEGORY_THEME).size <= 1;
- }
-
- return {
- teamId: TeamStore.getCurrentId(),
- theme,
- type: theme.type || 'premade',
- showAllTeamsCheckbox,
- applyToAllTeams
- };
- }
-
- onChange() {
- const newState = this.getStateFromStores();
-
- if (!Utils.areObjectsEqual(this.state, newState)) {
- this.setState(newState);
- }
-
- this.props.setEnforceFocus(true);
- }
-
- scrollToTop() {
- $('.ps-container.modal-body').scrollTop(0);
- }
-
- submitTheme(e) {
- e.preventDefault();
-
- const teamId = this.state.applyToAllTeams ? '' : this.state.teamId;
-
- UserActions.saveTheme(
- teamId,
- this.state.theme,
- () => {
- this.props.setRequireConfirm(false);
- this.originalTheme = Object.assign({}, this.state.theme);
- this.scrollToTop();
- this.props.updateSection('');
- }
- );
- }
-
- updateTheme(theme) {
- let themeChanged = this.state.theme.length === theme.length;
- if (!themeChanged) {
- for (const field in theme) {
- if (theme.hasOwnProperty(field)) {
- if (this.state.theme[field] !== theme[field]) {
- themeChanged = true;
- break;
- }
- }
- }
- }
-
- this.props.setRequireConfirm(themeChanged);
-
- this.setState({theme});
- Utils.applyTheme(theme);
- }
-
- updateType(type) {
- this.setState({type});
- }
-
- resetFields() {
- const state = this.getStateFromStores();
- state.serverError = null;
- this.setState(state);
- this.scrollToTop();
-
- Utils.applyTheme(state.theme);
-
- this.props.setRequireConfirm(false);
- }
-
- handleImportModal() {
- AppDispatcher.handleViewAction({
- type: ActionTypes.TOGGLE_IMPORT_THEME_MODAL,
- value: true,
- callback: this.updateTheme
- });
-
- this.props.setEnforceFocus(false);
- }
-
- render() {
- var serverError;
- if (this.state.serverError) {
- serverError = this.state.serverError;
- }
-
- const displayCustom = this.state.type === 'custom';
- const allowCustomThemes = global.mm_config.AllowCustomThemes !== 'false';
-
- let custom;
- let premade;
- if (displayCustom && allowCustomThemes) {
- custom = (
- <div key='customThemeChooser'>
- <CustomThemeChooser
- theme={this.state.theme}
- updateTheme={this.updateTheme}
- />
- </div>
- );
- } else {
- premade = (
- <div key='premadeThemeChooser'>
- <br/>
- <PremadeThemeChooser
- theme={this.state.theme}
- updateTheme={this.updateTheme}
- />
- </div>
- );
- }
-
- let themeUI;
- if (this.props.selected) {
- const inputs = [];
-
- if (allowCustomThemes) {
- inputs.push(
- <div
- className='radio'
- key='premadeThemeColorLabel'
- >
- <label>
- <input
- id='standardThemes'
- type='radio'
- name='theme'
- checked={!displayCustom}
- onChange={this.updateType.bind(this, 'premade')}
- />
- <FormattedMessage
- id='user.settings.display.theme.themeColors'
- defaultMessage='Theme Colors'
- />
- </label>
- <br/>
- </div>
- );
- }
-
- inputs.push(premade);
-
- if (allowCustomThemes) {
- inputs.push(
- <div
- className='radio'
- key='customThemeColorLabel'
- >
- <label>
- <input
- id='customThemes'
- type='radio'
- name='theme'
- checked={displayCustom}
- onChange={this.updateType.bind(this, 'custom')}
- />
- <FormattedMessage
- id='user.settings.display.theme.customTheme'
- defaultMessage='Custom Theme'
- />
- </label>
- </div>
- );
-
- inputs.push(custom);
-
- inputs.push(
- <div key='otherThemes'>
- <br/>
- <a
- id='otherThemes'
- href='http://docs.mattermost.com/help/settings/theme-colors.html#custom-theme-examples'
- target='_blank'
- rel='noopener noreferrer'
- >
- <FormattedMessage
- id='user.settings.display.theme.otherThemes'
- defaultMessage='See other themes'
- />
- </a>
- </div>
- );
-
- inputs.push(
- <div
- key='importSlackThemeButton'
- className='padding-top'
- >
- <a
- id='slackImportTheme'
- className='theme'
- onClick={this.handleImportModal}
- >
- <FormattedMessage
- id='user.settings.display.theme.import'
- defaultMessage='Import theme colors from Slack'
- />
- </a>
- </div>
- );
- }
-
- let allTeamsCheckbox = null;
- if (this.state.showAllTeamsCheckbox) {
- allTeamsCheckbox = (
- <div className='checkbox user-settings__submit-checkbox'>
- <label>
- <input
- id='applyThemeToAllTeams'
- type='checkbox'
- checked={this.state.applyToAllTeams}
- onChange={(e) => this.setState({applyToAllTeams: e.target.checked})}
- />
- <FormattedMessage
- id='user.settings.display.theme.applyToAllTeams'
- defaultMessage='Apply new theme to all my teams'
- />
- </label>
- </div>
- );
- }
-
- themeUI = (
- <SettingItemMax
- inputs={inputs}
- submitExtra={allTeamsCheckbox}
- submit={this.submitTheme}
- server_error={serverError}
- width='full'
- updateSection={(e) => {
- this.props.updateSection('');
- e.preventDefault();
- }}
- />
- );
- } else {
- themeUI = (
- <SettingItemMin
- title={
- <FormattedMessage
- id='user.settings.display.theme.title'
- defaultMessage='Theme'
- />
- }
- describe={
- <FormattedMessage
- id='user.settings.display.theme.describe'
- defaultMessage='Open to manage your theme'
- />
- }
- updateSection={() => {
- this.props.updateSection('theme');
- }}
- />
- );
- }
-
- return themeUI;
- }
-}
-
-ThemeSetting.propTypes = {
- selected: PropTypes.bool.isRequired,
- updateSection: PropTypes.func.isRequired,
- setRequireConfirm: PropTypes.func.isRequired,
- setEnforceFocus: PropTypes.func.isRequired
-};
diff --git a/webapp/components/view_image.jsx b/webapp/components/view_image.jsx
deleted file mode 100644
index cc0bcee9d..000000000
--- a/webapp/components/view_image.jsx
+++ /dev/null
@@ -1,385 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import PropTypes from 'prop-types';
-import React from 'react';
-import {Modal} from 'react-bootstrap';
-
-import * as GlobalActions from 'actions/global_actions.jsx';
-
-import * as FileUtils from 'utils/file_utils';
-import * as Utils from 'utils/utils.jsx';
-
-import Constants from 'utils/constants.jsx';
-const KeyCodes = Constants.KeyCodes;
-
-import {getFileUrl, getFilePreviewUrl} from 'mattermost-redux/utils/file_utils';
-
-import AudioVideoPreview from './audio_video_preview.jsx';
-import CodePreview from './code_preview.jsx';
-import FileInfoPreview from './file_info_preview.jsx';
-import PDFPreview from './pdf_preview.jsx';
-import ViewImagePopoverBar from './view_image_popover_bar.jsx';
-
-import loadingGif from 'images/load.gif';
-
-export default class ViewImageModal extends React.Component {
- constructor(props) {
- super(props);
-
- this.showImage = this.showImage.bind(this);
- this.loadImage = this.loadImage.bind(this);
-
- this.handleNext = this.handleNext.bind(this);
- this.handlePrev = this.handlePrev.bind(this);
- this.handleKeyPress = this.handleKeyPress.bind(this);
-
- this.onModalShown = this.onModalShown.bind(this);
- this.onModalHidden = this.onModalHidden.bind(this);
-
- this.handleGetPublicLink = this.handleGetPublicLink.bind(this);
- this.onMouseEnterImage = this.onMouseEnterImage.bind(this);
- this.onMouseLeaveImage = this.onMouseLeaveImage.bind(this);
-
- this.state = {
- imgId: this.props.startId,
- imgHeight: '100%',
- loaded: Utils.fillArray(false, this.props.fileInfos.length),
- progress: Utils.fillArray(0, this.props.fileInfos.length),
- showFooter: false
- };
- }
-
- handleNext(e) {
- if (e) {
- e.stopPropagation();
- }
- let id = this.state.imgId + 1;
- if (id > this.props.fileInfos.length - 1) {
- id = 0;
- }
- this.showImage(id);
- }
-
- handlePrev(e) {
- if (e) {
- e.stopPropagation();
- }
- let id = this.state.imgId - 1;
- if (id < 0) {
- id = this.props.fileInfos.length - 1;
- }
- this.showImage(id);
- }
-
- handleKeyPress(e) {
- if (e.keyCode === KeyCodes.RIGHT) {
- this.handleNext();
- } else if (e.keyCode === KeyCodes.LEFT) {
- this.handlePrev();
- }
- }
-
- onModalShown(nextProps) {
- $(window).on('keyup', this.handleKeyPress);
-
- this.showImage(nextProps.startId);
- }
-
- onModalHidden() {
- $(window).off('keyup', this.handleKeyPress);
-
- if (this.refs.video) {
- this.refs.video.stop();
- }
- }
-
- componentWillReceiveProps(nextProps) {
- if (nextProps.show === true && this.props.show === false) {
- this.onModalShown(nextProps);
- } else if (nextProps.show === false && this.props.show === true) {
- this.onModalHidden();
- }
-
- if (this.props.fileInfos.length !== nextProps.fileInfos.length) {
- this.setState({
- loaded: Utils.fillArray(false, nextProps.fileInfos.length),
- progress: Utils.fillArray(0, nextProps.fileInfos.length)
- });
- }
- }
-
- showImage(id) {
- this.setState({imgId: id});
-
- const imgHeight = $(window).height() - 100;
- this.setState({imgHeight});
-
- if (!this.state.loaded[id]) {
- this.loadImage(id);
- }
- }
-
- loadImage(index) {
- const fileInfo = this.props.fileInfos[index];
- const fileType = Utils.getFileType(fileInfo.extension);
-
- if (fileType === 'image') {
- let previewUrl;
- if (fileInfo.has_image_preview) {
- previewUrl = getFilePreviewUrl(fileInfo.id);
- } else {
- // some images (eg animated gifs) just show the file itself and not a preview
- previewUrl = getFileUrl(fileInfo.id);
- }
-
- Utils.loadImage(
- previewUrl,
- () => this.handleImageLoaded(index),
- (completedPercentage) => this.handleImageProgress(index, completedPercentage)
- );
- } else {
- // there's nothing to load for non-image files
- this.handleImageLoaded(index);
- }
- }
-
- handleImageLoaded = (index) => {
- this.setState((prevState) => {
- return {
- loaded: {
- ...prevState.loaded,
- [index]: true
- }
- };
- });
- }
-
- handleImageProgress = (index, completedPercentage) => {
- this.setState((prevState) => {
- return {
- progress: {
- ...prevState.progress,
- [index]: completedPercentage
- }
- };
- });
- }
-
- handleGetPublicLink() {
- this.props.onModalDismissed();
-
- GlobalActions.showGetPublicLinkModal(this.props.fileInfos[this.state.imgId].id);
- }
-
- onMouseEnterImage() {
- this.setState({showFooter: true});
- }
-
- onMouseLeaveImage() {
- this.setState({showFooter: false});
- }
-
- render() {
- if (this.props.fileInfos.length < 1 || this.props.fileInfos.length - 1 < this.state.imgId) {
- return null;
- }
-
- const fileInfo = this.props.fileInfos[this.state.imgId];
- const fileUrl = getFileUrl(fileInfo.id);
-
- let content;
- if (this.state.loaded[this.state.imgId]) {
- const fileType = Utils.getFileType(fileInfo.extension);
-
- if (fileType === 'image' || fileType === 'svg') {
- content = (
- <ImagePreview
- fileInfo={fileInfo}
- fileUrl={fileUrl}
- />
- );
- } else if (fileType === 'video' || fileType === 'audio') {
- content = (
- <AudioVideoPreview
- fileInfo={fileInfo}
- fileUrl={fileUrl}
- />
- );
- } else if (PDFPreview.supports(fileInfo)) {
- content = (
- <PDFPreview
- fileInfo={fileInfo}
- fileUrl={fileUrl}
- />
- );
- } else if (CodePreview.supports(fileInfo)) {
- content = (
- <CodePreview
- fileInfo={fileInfo}
- fileUrl={fileUrl}
- />
- );
- } else {
- content = (
- <FileInfoPreview
- fileInfo={fileInfo}
- fileUrl={fileUrl}
- />
- );
- }
- } else {
- // display a progress indicator when the preview for an image is still loading
- const progress = Math.floor(this.state.progress[this.state.imgId]);
-
- content = (
- <LoadingImagePreview
- progress={progress}
- loading={Utils.localizeMessage('view_image.loading', 'Loading ')}
- />
- );
- }
-
- let leftArrow = null;
- let rightArrow = null;
- if (this.props.fileInfos.length > 1) {
- leftArrow = (
- <a
- ref='previewArrowLeft'
- className='modal-prev-bar'
- href='#'
- onClick={this.handlePrev}
- >
- <i className='image-control image-prev'/>
- </a>
- );
-
- rightArrow = (
- <a
- ref='previewArrowRight'
- className='modal-next-bar'
- href='#'
- onClick={this.handleNext}
- >
- <i className='image-control image-next'/>
- </a>
- );
- }
-
- let closeButtonClass = 'modal-close';
- if (this.state.showFooter) {
- closeButtonClass += ' modal-close--show';
- }
-
- return (
- <Modal
- show={this.props.show}
- onHide={this.props.onModalDismissed}
- className='modal-image'
- dialogClassName='modal-image'
- >
- <Modal.Body
- modalClassName='modal-image__body'
- onClick={this.props.onModalDismissed}
- >
- <div
- className={'modal-image__wrapper'}
- onClick={this.props.onModalDismissed}
- >
- <div
- onMouseEnter={this.onMouseEnterImage}
- onMouseLeave={this.onMouseLeaveImage}
- onClick={(e) => e.stopPropagation()}
- >
- <div
- className={closeButtonClass}
- onClick={this.props.onModalDismissed}
- />
- <div className='modal-image__content'>
- {content}
- </div>
- <ViewImagePopoverBar
- show={this.state.showFooter}
- fileId={this.state.imgId}
- totalFiles={this.props.fileInfos.length}
- filename={fileInfo.name}
- fileURL={fileUrl}
- onGetPublicLink={this.handleGetPublicLink}
- />
- </div>
- </div>
- {leftArrow}
- {rightArrow}
- </Modal.Body>
- </Modal>
- );
- }
-}
-
-ViewImageModal.defaultProps = {
- show: false,
- fileInfos: [],
- startId: 0
-};
-ViewImageModal.propTypes = {
- show: PropTypes.bool.isRequired,
- onModalDismissed: PropTypes.func.isRequired,
- fileInfos: PropTypes.arrayOf(PropTypes.object).isRequired,
- startId: PropTypes.number
-};
-
-function LoadingImagePreview({progress, loading}) {
- let progressView = null;
- if (progress) {
- progressView = (
- <span className='loader-percent'>
- {loading + progress + '%'}
- </span>
- );
- }
-
- return (
- <div className='view-image__loading'>
- <img
- className='loader-image'
- src={loadingGif}
- />
- {progressView}
- </div>
- );
-}
-
-LoadingImagePreview.propTypes = {
- progress: PropTypes.number,
- loading: PropTypes.string
-};
-
-function ImagePreview({fileInfo, fileUrl}) {
- let previewUrl;
- if (fileInfo.has_preview_image) {
- previewUrl = getFilePreviewUrl(fileInfo.id);
- } else {
- previewUrl = fileUrl;
- }
-
- if (!FileUtils.canDownloadFiles()) {
- return <img src={previewUrl}/>;
- }
-
- return (
- <a
- href={fileUrl}
- target='_blank'
- rel='noopener noreferrer'
- download={true}
- >
- <img src={previewUrl}/>
- </a>
- );
-}
-
-ImagePreview.propTypes = {
- fileInfo: PropTypes.object.isRequired,
- fileUrl: PropTypes.string.isRequired
-};
diff --git a/webapp/components/view_image_popover_bar.jsx b/webapp/components/view_image_popover_bar.jsx
deleted file mode 100644
index 6a092cbbe..000000000
--- a/webapp/components/view_image_popover_bar.jsx
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import PropTypes from 'prop-types';
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-
-import * as FileUtils from 'utils/file_utils';
-
-export default class ViewImagePopoverBar extends React.PureComponent {
- render() {
- var publicLink = '';
- if (global.window.mm_config.EnablePublicLink === 'true') {
- publicLink = (
- <div>
- <a
- href='#'
- className='public-link text'
- data-title='Public Image'
- onClick={this.props.onGetPublicLink}
- >
- <FormattedMessage
- id='view_image_popover.publicLink'
- defaultMessage='Get Public Link'
- />
- </a>
- <span className='text'>{' | '}</span>
- </div>
- );
- }
-
- var footerClass = 'modal-button-bar';
- if (this.props.show) {
- footerClass += ' footer--show';
- }
-
- let downloadLinks = null;
- if (FileUtils.canDownloadFiles()) {
- downloadLinks = (
- <div className='image-links'>
- {publicLink}
- <a
- href={this.props.fileURL}
- download={this.props.filename}
- className='text'
- target='_blank'
- rel='noopener noreferrer'
- >
- <FormattedMessage
- id='view_image_popover.download'
- defaultMessage='Download'
- />
- </a>
- </div>
- );
- }
-
- return (
- <div
- ref='imageFooter'
- className={footerClass}
- >
- <span className='pull-left text'>
- <FormattedMessage
- id='view_image_popover.file'
- defaultMessage='File {count, number} of {total, number}'
- values={{
- count: (this.props.fileId + 1),
- total: this.props.totalFiles
- }}
- />
- </span>
- {downloadLinks}
- </div>
- );
- }
-}
-ViewImagePopoverBar.defaultProps = {
- show: false,
- imgId: 0,
- totalFiles: 0,
- filename: '',
- fileURL: ''
-};
-
-ViewImagePopoverBar.propTypes = {
- show: PropTypes.bool.isRequired,
- fileId: PropTypes.number.isRequired,
- totalFiles: PropTypes.number.isRequired,
- filename: PropTypes.string.isRequired,
- fileURL: PropTypes.string.isRequired,
- onGetPublicLink: PropTypes.func.isRequired
-};
diff --git a/webapp/components/webrtc/components/webrtc_header.jsx b/webapp/components/webrtc/components/webrtc_header.jsx
deleted file mode 100644
index a5f679da0..000000000
--- a/webapp/components/webrtc/components/webrtc_header.jsx
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import Constants from 'utils/constants.jsx';
-
-import {Tooltip, OverlayTrigger} from 'react-bootstrap';
-import {FormattedMessage} from 'react-intl';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-
-export default function WebrtcHeader(props) {
- const title = (
- <FormattedMessage
- id='webrtc.header'
- defaultMessage='Call with {username}'
- values={{
- username: props.username
- }}
- />
- );
-
- const closeSidebarTooltip = (
- <Tooltip id='closeSidebarTooltip'>
- <FormattedMessage
- id='rhs_header.closeTooltip'
- defaultMessage='Close Sidebar'
- />
- </Tooltip>
- );
-
- const expandSidebarTooltip = (
- <Tooltip id='expandSidebarTooltip'>
- <FormattedMessage
- id='rhs_header.expandTooltip'
- defaultMessage='Expand Sidebar'
- />
- </Tooltip>
- );
-
- const shrinkSidebarTooltip = (
- <Tooltip id='shrinkSidebarTooltip'>
- <FormattedMessage
- id='rhs_header.expandTooltip'
- defaultMessage='Shrink Sidebar'
- />
- </Tooltip>
- );
-
- return (
- <div className='sidebar--right__header'>
- <span className='sidebar--right__title'>{title}</span>
- <div className='pull-right'>
- <button
- type='button'
- className='sidebar--right__expand'
- aria-label='Expand'
- onClick={props.toggleSize}
- >
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={Constants.OVERLAY_TIME_DELAY}
- placement='top'
- overlay={expandSidebarTooltip}
- >
- <i className='fa fa-expand'/>
- </OverlayTrigger>
- <OverlayTrigger
- trigger={['hover', 'focus']}
- delayShow={Constants.OVERLAY_TIME_DELAY}
- placement='top'
- overlay={shrinkSidebarTooltip}
- >
- <i className='fa fa-compress'/>
- </OverlayTrigger>
- </button>
- <button
- type='button'
- className='sidebar--right__close'
- aria-label='Close'
- title='Close'
- onClick={props.onClose}
- >
- <OverlayTrigger
- delayShow={Constants.OVERLAY_TIME_DELAY}
- placement='top'
- overlay={closeSidebarTooltip}
- >
- <i className='fa fa-sign-out'/>
- </OverlayTrigger>
- </button>
- </div>
- </div>
- );
-}
-
-WebrtcHeader.propTypes = {
- username: PropTypes.string.isRequired,
- onClose: PropTypes.func.isRequired,
- toggleSize: PropTypes.func
-};
diff --git a/webapp/components/webrtc/components/webrtc_notification.jsx b/webapp/components/webrtc/components/webrtc_notification.jsx
deleted file mode 100644
index efa6ee637..000000000
--- a/webapp/components/webrtc/components/webrtc_notification.jsx
+++ /dev/null
@@ -1,319 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import WebSocketClient from 'client/web_websocket_client.jsx';
-
-import UserStore from 'stores/user_store.jsx';
-import WebrtcStore from 'stores/webrtc_store.jsx';
-
-import * as GlobalActions from 'actions/global_actions.jsx';
-import * as WebrtcActions from 'actions/webrtc_actions.jsx';
-import * as Utils from 'utils/utils.jsx';
-import {Constants, WebrtcActionTypes} from 'utils/constants.jsx';
-
-import React from 'react';
-
-import {FormattedMessage} from 'react-intl';
-
-import ring from 'images/ring.mp3';
-
-const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES;
-
-export default class WebrtcNotification extends React.Component {
- constructor() {
- super();
-
- this.mounted = false;
-
- this.closeNotification = this.closeNotification.bind(this);
- this.onIncomingCall = this.onIncomingCall.bind(this);
- this.onCancelCall = this.onCancelCall.bind(this);
- this.onRhs = this.onRhs.bind(this);
- this.handleClose = this.handleClose.bind(this);
- this.handleAnswer = this.handleAnswer.bind(this);
- this.handleTimeout = this.handleTimeout.bind(this);
- this.stopRinging = this.stopRinging.bind(this);
- this.closeRightHandSide = this.closeRightHandSide.bind(this);
-
- this.state = {
- userCalling: null,
- rhsOpened: false
- };
- }
-
- componentDidMount() {
- WebrtcStore.addNotifyListener(this.onIncomingCall);
- WebrtcStore.addChangedListener(this.onCancelCall);
- WebrtcStore.addRhsChangedListener(this.onRhs);
- this.mounted = true;
- }
-
- componentWillUnmount() {
- WebrtcStore.removeNotifyListener(this.onIncomingCall);
- WebrtcStore.removeChangedListener(this.onCancelCall);
- WebrtcStore.removeRhsChangedListener(this.onRhs);
- if (this.refs.ring) {
- this.refs.ring.removeListener('ended', this.handleTimeout);
- }
- this.mounted = false;
- }
-
- componentDidUpdate() {
- if (this.state.userCalling) {
- this.refs.ring.addEventListener('ended', this.handleTimeout);
- }
- }
-
- closeNotification() {
- this.setState({
- userCalling: null
- });
- }
-
- stopRinging() {
- if (this.refs.ring) {
- this.refs.ring.pause();
- this.refs.ring.currentTime = 0;
- }
- this.setState({userCalling: null});
- }
-
- closeRightHandSide(e) {
- e.preventDefault();
- GlobalActions.emitCloseRightHandSide();
- }
-
- onIncomingCall(incoming) {
- if (this.mounted) {
- const userId = incoming.from_user_id;
- const userMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
- const featureEnabled = Utils.isFeatureEnabled(PreReleaseFeatures.WEBRTC_PREVIEW);
-
- if (featureEnabled) {
- if (WebrtcStore.isBusy()) {
- WebSocketClient.sendMessage('webrtc', {
- action: WebrtcActionTypes.BUSY,
- from_user_id: UserStore.getCurrentId(),
- to_user_id: userId
- });
- this.stopRinging();
- } else if (userMedia) {
- WebrtcStore.setVideoCallWith(userId);
- this.setState({
- userCalling: UserStore.getProfile(userId)
- });
- } else {
- WebSocketClient.sendMessage('webrtc', {
- action: WebrtcActionTypes.UNSUPPORTED,
- from_user_id: UserStore.getCurrentId(),
- to_user_id: userId
- });
- this.stopRinging();
- }
- } else {
- WebSocketClient.sendMessage('webrtc', {
- action: WebrtcActionTypes.DISABLED,
- from_user_id: UserStore.getCurrentId(),
- to_user_id: userId
- });
- this.stopRinging();
- }
- }
- }
-
- onCancelCall(message) {
- if (message && message.action !== WebrtcActionTypes.CANCEL) {
- return;
- } else if (message && message.action === WebrtcActionTypes.CANCEL && this.state.userCalling && message.from_user_id !== this.state.userCalling.id) {
- return;
- }
-
- WebrtcStore.setVideoCallWith(null);
- this.closeNotification();
- }
-
- onRhs(rhsOpened) {
- this.setState({rhsOpened});
- }
-
- handleTimeout() {
- WebSocketClient.sendMessage('webrtc', {
- action: WebrtcActionTypes.NO_ANSWER,
- from_user_id: UserStore.getCurrentId(),
- to_user_id: this.state.userCalling.id
- });
-
- this.onCancelCall();
- }
-
- handleAnswer(e) {
- if (e) {
- e.preventDefault();
- }
-
- const caller = this.state.userCalling;
- if (caller) {
- const callerId = caller.id;
- const currentUserId = UserStore.getCurrentId();
- const message = {
- action: WebrtcActionTypes.ANSWER,
- from_user_id: currentUserId,
- to_user_id: callerId
- };
-
- GlobalActions.emitCloseRightHandSide();
- WebrtcActions.initWebrtc(callerId, false);
- WebSocketClient.sendMessage('webrtc', message);
-
- // delay till next tick (this will give time to listen for events
- setTimeout(() => {
- //we switch from and to user to handle the event locally
- message.from_user_id = callerId;
- message.to_user_id = currentUserId;
- WebrtcActions.handle(message);
- }, 0);
-
- this.closeNotification();
- }
- }
-
- handleClose(e) {
- if (e) {
- e.preventDefault();
- }
- if (this.state.userCalling) {
- WebSocketClient.sendMessage('webrtc', {
- action: WebrtcActionTypes.DECLINE,
- from_user_id: UserStore.getCurrentId(),
- to_user_id: this.state.userCalling.id
- });
- }
-
- this.onCancelCall();
- }
-
- render() {
- const user = this.state.userCalling;
- if (user) {
- const username = Utils.displayUsername(user.id);
- const profileImgSrc = Utils.imageURLForUser(user);
- const profileImg = (
- <img
- className='user-popover__image'
- src={profileImgSrc}
- height='128'
- width='128'
- key='user-popover-image'
- />
- );
- const answerBtn = (
- <svg
- className='webrtc-icons__pickup'
- xmlns='http://www.w3.org/2000/svg'
- width='48'
- height='48'
- viewBox='-10 -10 68 68'
- onClick={this.handleAnswer}
- >
- <circle
- cx='24'
- cy='24'
- r='34'
- >
- <title>
- <FormattedMessage
- id='webrtc.notification.answer'
- defaultMessage='Answer'
- />
- </title>
- </circle>
- <path
- transform='translate(-10,-10)'
- fill='#fff'
- d='M29.854,37.627c1.723,1.904 3.679,3.468 5.793,4.684l3.683,-3.334c0.469,-0.424 1.119,-0.517 1.669,-0.302c1.628,0.63 3.331,1.021 5.056,1.174c0.401,0.026 0.795,0.199 1.09,0.525c0.295,0.326 0.433,0.741 0.407,1.153l-0.279,5.593c-0.02,0.418 -0.199,0.817 -0.525,1.112c-0.326,0.296 -0.741,0.434 -1.159,0.413c-6.704,-0.504 -13.238,-3.491 -18.108,-8.87c-4.869,-5.38 -7.192,-12.179 -7.028,-18.899c0.015,-0.413 0.199,-0.817 0.526,-1.113c0.326,-0.295 0.74,-0.433 1.153,-0.407l5.593,0.279c0.407,0.02 0.812,0.193 1.107,0.519c0.29,0.32 0.428,0.735 0.413,1.137c-0.018,1.732 0.202,3.464 0.667,5.147c0.159,0.569 0.003,1.207 -0.466,1.631l-3.683,3.334c1.005,2.219 2.368,4.32 4.091,6.224Z'
- />
- </svg>
- );
-
- const rejectBtn = (
- <svg
- className='webrtc-icons__cancel'
- xmlns='http://www.w3.org/2000/svg'
- width='48'
- height='48'
- viewBox='-10 -10 68 68'
- onClick={this.handleClose}
- >
- <circle
- cx='24'
- cy='24'
- r='34'
- >
- <title>
- <FormattedMessage
- id='webrtc.notification.decline'
- defaultMessage='Decline'
- />
- </title>
- </circle>
- <path
- transform='scale(0.7), translate(11,10)'
- d='M24 18c-3.21 0-6.3.5-9.2 1.44v6.21c0 .79-.46 1.47-1.12 1.8-1.95.98-3.74 2.23-5.33 3.7-.36.35-.85.57-1.4.57-.55 0-1.05-.22-1.41-.59L.59 26.18c-.37-.37-.59-.87-.59-1.42 0-.55.22-1.05.59-1.42C6.68 17.55 14.93 14 24 14s17.32 3.55 23.41 9.34c.37.36.59.87.59 1.42 0 .55-.22 1.05-.59 1.41l-4.95 4.95c-.36.36-.86.59-1.41.59-.54 0-1.04-.22-1.4-.57-1.59-1.47-3.38-2.72-5.33-3.7-.66-.33-1.12-1.01-1.12-1.8v-6.21C30.3 18.5 27.21 18 24 18z'
- fill='white'
- />
- </svg>
- );
-
- const msg = (
- <div>
- <FormattedMessage
- id='webrtc.notification.incoming_call'
- defaultMessage='{username} is calling you.'
- values={{
- username
- }}
- />
- <div
- className='webrtc-buttons webrtc-icons active'
- style={{marginTop: '5px'}}
- >
- {answerBtn}
- {rejectBtn}
- </div>
- </div>
- );
-
- return (
- <div className='webrtc-notification'>
- <audio
- ref='ring'
- src={ring}
- autoPlay={true}
- />
- <div>
- {profileImg}
- </div>
- {msg}
- </div>
- );
- } else if (this.state.rhsOpened && WebrtcStore.isBusy()) {
- return (
- <div
- className='webrtc__notification--rhs'
- onClick={this.closeRightHandSide}
- >
- <i className='fa fa-phone'/>
- <FormattedMessage
- id='webrtc.notification.returnToCall'
- defaultMessage='Return to ongoing call with {username}'
- values={{
- username: Utils.displayUsername(WebrtcStore.getVideoCallWith())
- }}
- />
- </div>
- );
- }
-
- return <div/>;
- }
-}
diff --git a/webapp/components/webrtc/components/webrtc_sidebar.jsx b/webapp/components/webrtc/components/webrtc_sidebar.jsx
deleted file mode 100644
index 82ac2d98a..000000000
--- a/webapp/components/webrtc/components/webrtc_sidebar.jsx
+++ /dev/null
@@ -1,136 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-
-import WebrtcController from '../webrtc_controller.jsx';
-import UserStore from 'stores/user_store.jsx';
-import WebrtcStore from 'stores/webrtc_store.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-import React from 'react';
-
-export default class SidebarRight extends React.Component {
- constructor(props) {
- super(props);
-
- this.plScrolledToBottom = true;
-
- this.onShrink = this.onShrink.bind(this);
- this.toggleSize = this.toggleSize.bind(this);
- this.onInitializeVideoCall = this.onInitializeVideoCall.bind(this);
-
- this.doStrangeThings = this.doStrangeThings.bind(this);
-
- this.state = {
- expanded: false,
- currentUser: UserStore.getCurrentUser(),
- videoCallVisible: false,
- isCaller: false,
- videoCallWithUserId: null
- };
- }
-
- componentDidMount() {
- WebrtcStore.addInitListener(this.onInitializeVideoCall);
- this.doStrangeThings();
- }
-
- componentWillUnmount() {
- WebrtcStore.removeInitListener(this.onInitializeVideoCall);
- }
-
- shouldComponentUpdate(nextProps, nextState) {
- return !Utils.areObjectsEqual(nextState, this.state);
- }
-
- doStrangeThings() {
- // We should have a better way to do this stuff
- // Hence the function name.
- $('.app__body .inner-wrap').removeClass('move--right');
- $('.app__body .inner-wrap').addClass('webrtc--show');
- $('.app__body .sidebar--left').removeClass('move--right');
- $('.multi-teams .team-sidebar').removeClass('move--right');
- $('.app__body .webrtc').addClass('webrtc--show');
-
- //$('.sidebar--right').prepend('<div class="sidebar__overlay"></div>');
- if (!this.state.videoCallVisible) {
- $('.app__body .inner-wrap').removeClass('webrtc--show').removeClass('move--right');
- $('.app__body .webrtc').removeClass('webrtc--show');
- return (
- <div/>
- );
- }
- return null;
- }
-
- componentDidUpdate() {
- this.doStrangeThings();
- }
-
- onShrink() {
- this.setState({expanded: false});
- }
-
- toggleSize(e) {
- if (e) {
- e.preventDefault();
- }
- this.setState((prevState) => {
- return {expanded: !prevState.expanded};
- });
- }
-
- onInitializeVideoCall(userId, isCaller) {
- let expanded = this.state.expanded;
- if (userId === null) {
- expanded = false;
- }
- this.setState({
- videoCallVisible: (userId !== null),
- isCaller,
- videoCallWithUserId: userId,
- expanded
- });
-
- if (userId !== null) {
- this.forceUpdate();
- }
- }
-
- render() {
- let content = null;
- let expandedClass = '';
-
- if (this.state.expanded) {
- expandedClass = 'sidebar--right--expanded';
- }
-
- if (this.state.videoCallVisible) {
- content = (
- <WebrtcController
- currentUser={this.state.currentUser}
- userId={this.state.videoCallWithUserId}
- isCaller={this.state.isCaller}
- expanded={this.state.expanded}
- toggleSize={this.toggleSize}
- />
- );
- }
-
- return (
- <div
- className={'sidebar--right webrtc ' + expandedClass}
- id='sidebar-webrtc'
- >
- <div
- onClick={this.onShrink}
- className='sidebar--right__bg'
- />
- <div className='sidebar-right-container'>
- {content}
- </div>
- </div>
- );
- }
-}
diff --git a/webapp/components/webrtc/webrtc_controller.jsx b/webapp/components/webrtc/webrtc_controller.jsx
deleted file mode 100644
index 576dc7b58..000000000
--- a/webapp/components/webrtc/webrtc_controller.jsx
+++ /dev/null
@@ -1,1244 +0,0 @@
-// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import UserStore from 'stores/user_store.jsx';
-import ChannelStore from 'stores/channel_store.jsx';
-import WebrtcStore from 'stores/webrtc_store.jsx';
-
-import WebSocketClient from 'client/web_websocket_client.jsx';
-import Janus from 'janus';
-
-import SearchBox from '../search_bar.jsx';
-import WebrtcHeader from './components/webrtc_header.jsx';
-import ConnectingScreen from 'components/loading_screen.jsx';
-
-import {trackEvent} from 'actions/diagnostics_actions.jsx';
-import * as WebrtcActions from 'actions/webrtc_actions.jsx';
-
-import * as Utils from 'utils/utils.jsx';
-import {Constants, UserStatuses, WebrtcActionTypes} from 'utils/constants.jsx';
-
-import PropTypes from 'prop-types';
-
-import React from 'react';
-import {FormattedMessage} from 'react-intl';
-
-import ring from 'images/ring.mp3';
-
-const VIDEO_WIDTH = 640;
-const VIDEO_HEIGHT = 360;
-const MIN_ASPECT = 1.777;
-const MAX_ASPECT = 1.778;
-const ALREADY_REGISTERED_ERROR = 477;
-const USERNAME_TAKEN = 476;
-
-export default class WebrtcController extends React.Component {
- constructor(props) {
- super(props);
-
- this.mounted = false;
- this.localMedia = null;
- this.session = null;
- this.videocall = null;
-
- this.handleResize = this.handleResize.bind(this);
- this.handleClose = this.handleClose.bind(this);
- this.close = this.close.bind(this);
- this.clearError = this.clearError.bind(this);
-
- this.getLocalMedia = this.getLocalMedia.bind(this);
- this.stopMediaStream = this.stopMediaStream.bind(this);
- this.previewVideo = this.previewVideo.bind(this);
- this.stopRinging = this.stopRinging.bind(this);
-
- this.handleMakeOffer = this.handleMakeOffer.bind(this);
- this.handleCancelOffer = this.handleCancelOffer.bind(this);
- this.handleWebrtcEvent = this.handleWebrtcEvent.bind(this);
- this.handleVideoCallEvent = this.handleVideoCallEvent.bind(this);
- this.handleRemoteStream = this.handleRemoteStream.bind(this);
-
- this.onStatusChange = this.onStatusChange.bind(this);
- this.onCallDeclined = this.onCallDeclined.bind(this);
- this.onUnsupported = this.onUnsupported.bind(this);
- this.onNoAnswer = this.onNoAnswer.bind(this);
- this.onBusy = this.onBusy.bind(this);
- this.onDisabled = this.onDisabled.bind(this);
- this.onFailed = this.onFailed.bind(this);
- this.onCancelled = this.onCancelled.bind(this);
- this.onConnectCall = this.onConnectCall.bind(this);
-
- this.onSessionCreated = this.onSessionCreated.bind(this);
- this.onSessionError = this.onSessionError.bind(this);
-
- this.doCall = this.doCall.bind(this);
- this.doAnswer = this.doAnswer.bind(this);
- this.doHangup = this.doHangup.bind(this);
- this.doCleanup = this.doCleanup.bind(this);
-
- this.renderButtons = this.renderButtons.bind(this);
- this.onToggleVideo = this.onToggleVideo.bind(this);
- this.onToggleAudio = this.onToggleAudio.bind(this);
- this.onToggleRemoteMute = this.onToggleRemoteMute.bind(this);
- this.toggleIcons = this.toggleIcons.bind(this);
-
- const currentUser = UserStore.getCurrentUser();
- const remoteUser = UserStore.getProfile(props.userId);
- const remoteUserImage = Utils.imageURLForUser(remoteUser);
-
- this.state = {
- windowWidth: Utils.windowWidth(),
- windowHeight: Utils.windowHeight(),
- channelId: ChannelStore.getCurrentId(),
- currentUser,
- currentUserImage: Utils.imageURLForUser(currentUser),
- remoteUserImage,
- localMediaLoaded: false,
- isPaused: false,
- isMuted: false,
- isRemotePaused: false,
- isRemoteMuted: false,
- isCalling: false,
- isAnswering: false,
- callInProgress: false,
- error: null,
- errorType: '',
- ended: null
- };
- }
-
- componentDidMount() {
- window.addEventListener('resize', this.handleResize);
- WebrtcStore.addChangedListener(this.handleWebrtcEvent);
- UserStore.addStatusesChangeListener(this.onStatusChange);
-
- this.mounted = true;
- this.previewVideo();
-
- if (this.props.isCaller) {
- this.handleMakeOffer();
- }
- }
-
- componentWillUnmount() {
- window.removeEventListener('resize', this.handleResize);
- WebrtcStore.removeChangedListener(this.handleWebrtcEvent);
- UserStore.removeStatusesChangeListener(this.onStatusChange);
- this.mounted = false;
- this.close();
- }
-
- componentWillReceiveProps(nextProps) {
- if ((nextProps.currentUser !== this.props.currentUser) ||
- (nextProps.userId !== this.props.userId) ||
- (nextProps.isCaller !== this.props.isCaller)) {
- const remoteUser = UserStore.getProfile(nextProps.userId);
- const remoteUserImage = Utils.imageURLForUser(remoteUser);
- this.setState({
- error: null,
- remoteUserImage
- });
- }
-
- if (nextProps.isCaller && nextProps.expanded === this.props.expanded) {
- this.startCall = true;
- }
- }
-
- componentDidUpdate() {
- if (this.props.isCaller && this.startCall) {
- this.startCall = false;
- this.handleMakeOffer();
- }
- }
-
- handleResize() {
- this.setState({
- windowWidth: Utils.windowWidth(),
- windowHeight: Utils.windowHeight()
- });
- }
-
- clearError() {
- setTimeout(() => {
- this.setState({error: null, ended: null, errorType: ''});
- }, Constants.WEBRTC_CLEAR_ERROR_DELAY);
- }
-
- getLocalMedia(constraints, element, callback) {
- const media = constraints || {audio: true, video: true};
- navigator.mediaDevices.getUserMedia(media).
- then((stream) => {
- if (element) {
- element.srcObject = stream;
- }
-
- if (callback && typeof callback === 'function') {
- callback(null, stream);
- }
- }).
- catch((error) => {
- callback(error);
- });
- }
-
- stopMediaStream(stream) {
- const tracks = stream.getTracks();
- tracks.forEach((track) => {
- track.stop();
- });
- }
-
- previewVideo() {
- if (this.mounted) {
- if (this.localMedia) {
- this.setState({
- localMediaLoaded: true,
- error: null
- });
- this.localMedia.enabled = true;
- } else {
- this.getLocalMedia(
- {
- audio: true,
- video: {
- minAspectRatio: MIN_ASPECT,
- maxAspectRatio: MAX_ASPECT,
- width: VIDEO_WIDTH,
- height: VIDEO_HEIGHT
- }
- },
- this.refs['local-video'],
- (error, stream) => {
- if (error) {
- this.setState({
- error: (
- <FormattedMessage
- id='webrtc.mediaError'
- defaultMessage='Unable to access Camera and Microphone'
- />
- )
- });
- return;
- }
- this.localMedia = stream;
- this.setState({
- localMediaLoaded: true
- });
- });
- }
- }
- }
-
- stopRinging() {
- if (this.refs.ring) {
- this.refs.ring.pause();
- this.refs.ring.currentTime = 0;
- }
- }
-
- handleMakeOffer() {
- if (UserStore.getStatus(this.props.userId) === UserStatuses.OFFLINE) {
- this.onStatusChange();
- } else {
- const connectingMsg = (
- <FormattedMessage
- id='calling_screen'
- defaultMessage='Calling'
- />
- );
-
- this.setState({
- isCalling: true,
- isAnswering: false,
- callInProgress: false,
- error: null,
- ended: null,
- connectingMsg
- });
-
- WebrtcStore.setVideoCallWith(this.props.userId);
-
- const user = this.state.currentUser;
- WebSocketClient.sendMessage('webrtc', {
- action: WebrtcActionTypes.NOTIFY,
- from_user_id: user.id,
- to_user_id: this.props.userId
- });
- }
- }
-
- handleCancelOffer() {
- this.setState({
- isCalling: false,
- isAnswering: false,
- callInProgress: false,
- error: null,
- ended: null
- });
-
- const user = this.state.currentUser;
- WebSocketClient.sendMessage('webrtc', {
- action: WebrtcActionTypes.CANCEL,
- from_user_id: user.id,
- to_user_id: this.props.userId
- });
-
- this.doCleanup();
- }
-
- handleWebrtcEvent(message) {
- switch (message.action) {
- case WebrtcActionTypes.DECLINE:
- this.onCallDeclined();
- this.clearError();
- break;
- case WebrtcActionTypes.UNSUPPORTED:
- this.onUnsupported();
- this.clearError();
- break;
- case WebrtcActionTypes.BUSY:
- this.onBusy();
- this.clearError();
- break;
- case WebrtcActionTypes.NO_ANSWER:
- this.onNoAnswer();
- this.clearError();
- break;
- case WebrtcActionTypes.FAILED:
- this.onFailed();
- this.clearError();
- break;
- case WebrtcActionTypes.ANSWER:
- this.onConnectCall();
- break;
- case WebrtcActionTypes.CANCEL:
- this.onCancelled();
- this.clearError();
- break;
- case WebrtcActionTypes.MUTED:
- this.onToggleRemoteMute(message);
- break;
- case WebrtcActionTypes.IN_PROGRESS:
- this.setState({
- error: (
- <FormattedMessage
- id='webrtc.inProgress'
- defaultMessage='You have a call in progress. Please hang up first.'
- />
- ),
- errorType: ' warning'
- });
- this.clearError();
- break;
- case WebrtcActionTypes.DISABLED:
- this.onDisabled();
- this.clearError();
- break;
- }
- }
-
- handleVideoCallEvent(msg, jsep) {
- const result = msg.result;
-
- if (result) {
- const event = result.event;
- switch (event) {
- case 'registered':
- if (this.state.isCalling) {
- this.doCall();
- }
- break;
- case 'incomingcall':
- this.doAnswer(jsep);
- break;
- case 'accepted':
- this.stopRinging();
-
- if (jsep) {
- this.videocall.handleRemoteJsep({jsep});
- }
- break;
- case 'hangup':
- this.doHangup(false);
- break;
- }
- } else {
- const errorCode = msg.error_code;
- if (errorCode !== ALREADY_REGISTERED_ERROR && errorCode !== USERNAME_TAKEN) {
- this.doHangup(true);
- } else if (this.state.isCalling) {
- this.doCall();
- }
- }
- }
-
- handleRemoteStream(stream) {
- // attaching stream to where they belong
- this.refs['main-video'].srcObject = stream;
-
- let isRemotePaused = false;
- let isRemoteMuted = false;
- const videoTracks = stream.getVideoTracks();
- const audioTracks = stream.getAudioTracks();
- if (!videoTracks || videoTracks.length === 0 || videoTracks[0].muted || !videoTracks[0].enabled) {
- isRemotePaused = true;
- }
-
- if (!audioTracks || audioTracks.length === 0 || audioTracks[0].muted || !audioTracks[0].enabled) {
- isRemoteMuted = true;
- }
-
- this.setState({
- isCalling: false,
- isAnswering: false,
- callInProgress: true,
- isMuted: false,
- isPaused: false,
- error: null,
- ended: null,
- isRemotePaused,
- isRemoteMuted
- });
- this.toggleIcons();
- }
-
- handleClose(e) {
- e.preventDefault();
- if (this.state.callInProgress) {
- this.setState({
- error: (
- <FormattedMessage
- id='webrtc.inProgress'
- defaultMessage='You have a call in progress. Please hang up first.'
- />
- ),
- errorType: ' warning'
- });
- } else if (this.state.isCalling) {
- this.handleCancelOffer();
- this.close();
- } else {
- this.close();
- }
- }
-
- close() {
- this.doCleanup();
-
- if (this.session) {
- this.session.destroy();
- this.session = null;
- }
-
- if (this.localMedia) {
- this.stopMediaStream(this.localMedia);
- this.localMedia = null;
- }
-
- WebrtcActions.initWebrtc(null, false);
- }
-
- onStatusChange() {
- const status = UserStore.getStatus(this.props.userId);
-
- if (status === UserStatuses.OFFLINE) {
- const error = (
- <FormattedMessage
- id='webrtc.offline'
- defaultMessage='{username} is offline'
- values={{
- username: Utils.displayUsername(this.props.userId)
- }}
- />
- );
-
- if (this.state.isCalling || this.state.isAnswering) {
- this.setState({
- isCalling: false,
- isAnswering: false,
- callInProgress: false,
- error
- });
- } else {
- this.setState({
- error
- });
- }
- } else if (status !== UserStatuses.OFFLINE && this.state.error) {
- this.setState({
- error: null,
- ended: null
- });
- }
- }
-
- onCallDeclined() {
- let error = null;
-
- if (this.state.isCalling) {
- error = (
- <FormattedMessage
- id='webrtc.declined'
- defaultMessage='Your call has been declined by {username}'
- values={{
- username: Utils.displayUsername(this.props.userId)
- }}
- />
- );
- }
-
- this.stopRinging();
-
- this.setState({
- isCalling: false,
- isAnswering: false,
- callInProgress: false,
- error
- });
-
- this.doCleanup();
- }
-
- onUnsupported() {
- if (this.mounted) {
- this.stopRinging();
-
- this.setState({
- error: (
- <FormattedMessage
- id='webrtc.unsupported'
- defaultMessage='Call to {username} not successful. Their client does not support video calls.'
- values={{
- username: Utils.displayUsername(this.props.userId)
- }}
- />
- ),
- callInProgress: false,
- isCalling: false,
- isAnswering: false
- });
- }
-
- this.doCleanup();
- }
-
- onNoAnswer() {
- let error = null;
-
- if (this.state.isCalling) {
- error = (
- <FormattedMessage
- id='webrtc.noAnswer'
- defaultMessage='{username} is not answering the call'
- values={{
- username: Utils.displayUsername(this.props.userId)
- }}
- />
- );
- }
- this.stopRinging();
-
- this.setState({
- isCalling: false,
- isAnswering: false,
- callInProgress: false,
- error
- });
-
- this.doCleanup();
- }
-
- onBusy() {
- let error = null;
-
- if (this.state.isCalling) {
- error = (
- <FormattedMessage
- id='webrtc.busy'
- defaultMessage='{username} is busy'
- values={{
- username: Utils.displayUsername(this.props.userId)
- }}
- />
- );
- }
- this.stopRinging();
-
- this.setState({
- isCalling: false,
- isAnswering: false,
- callInProgress: false,
- error
- });
-
- this.doCleanup();
- }
-
- onDisabled() {
- let error = null;
-
- if (this.state.isCalling) {
- error = (
- <FormattedMessage
- id='webrtc.disabled'
- defaultMessage='{username} has WebRTC disabled, and cannot receive calls. To enable the feature, they must go to Account Settings > Advanced > Preview pre-release features and turn on WebRTC.'
- values={{
- username: Utils.displayUsername(this.props.userId)
- }}
- />
- );
- }
-
- this.stopRinging();
-
- this.setState({
- isCalling: false,
- isAnswering: false,
- callInProgress: false,
- error
- });
-
- this.doCleanup();
- }
-
- onFailed() {
- trackEvent('api', 'api_users_webrtc_failed');
-
- this.setState({
- isCalling: false,
- isAnswering: false,
- callInProgress: false,
- isPaused: false,
- isMuted: false,
- isRemotePaused: false,
- isRemoteMuted: false,
- error: (
- <FormattedMessage
- id='webrtc.failed'
- defaultMessage='There was a problem connecting the video call'
- />
- )
- });
-
- this.stopRinging();
-
- this.doCleanup();
- }
-
- onCancelled() {
- if (this.mounted && this.state.isAnswering) {
- this.stopRinging();
- this.setState({
- isCalling: false,
- isAnswering: false,
- callInProgress: false,
- error: (
- <FormattedMessage
- id='webrtc.cancelled'
- defaultMessage='{username} cancelled the call'
- values={{
- username: Utils.displayUsername(this.props.userId)
- }}
- />
- )
- });
- }
-
- this.doCleanup();
- }
-
- onConnectCall() {
- WebrtcActions.webrtcToken(
- (info) => {
- const connectingMsg = (
- <FormattedMessage
- id='connecting_screen'
- defaultMessage='Connecting'
- />
- );
-
- this.setState({isAnswering: !this.state.isCalling, connectingMsg});
- if (this.session) {
- this.onSessionCreated();
- } else {
- const iceServers = [];
-
- if (info.stun_uri) {
- iceServers.push({
- urls: [info.stun_uri]
- });
- }
-
- if (info.turn_uri) {
- iceServers.push({
- urls: [info.turn_uri],
- username: info.turn_username,
- credential: info.turn_password
- });
- }
-
- Janus.init({debug: global.mm_config.EnableDeveloper === 'true'});
- this.session = new Janus({
- server: info.gateway_url,
- iceServers,
- token: info.token,
- success: this.onSessionCreated,
- error: this.onSessionError
- });
- }
- },
- () => {
- this.onSessionError();
- });
- }
-
- onSessionCreated() {
- if (this.videocall) {
- this.doCall();
- } else {
- this.session.attach({
- plugin: 'janus.plugin.videocall',
- success: (plugin) => {
- this.videocall = plugin;
- this.videocall.send({message: {request: 'register', username: this.state.currentUser.id}});
- },
- error: this.onSessionError,
- onmessage: this.handleVideoCallEvent,
- onremotestream: this.handleRemoteStream
- });
- }
- }
-
- onSessionError() {
- const user = this.state.currentUser;
- WebSocketClient.sendMessage('webrtc', {
- action: WebrtcActionTypes.FAILED,
- from_user_id: user.id,
- to_user_id: this.props.userId
- });
-
- this.onFailed();
- }
-
- doCall() {
- // delay call so receiver has time to register
- setTimeout(() => {
- this.videocall.createOffer({
- stream: this.localMedia,
- success: (jsep) => {
- const body = {request: 'call', username: this.props.userId};
- this.videocall.send({message: body, jsep});
- },
- error: () => {
- this.doHangup(true);
- }
- });
- }, Constants.WEBRTC_TIME_DELAY);
- }
-
- doAnswer(jsep) {
- trackEvent('api', 'api_users_webrtc_start');
- this.videocall.createAnswer({
- jsep,
- stream: this.localMedia,
- success: (jsepSuccess) => {
- const body = {request: 'accept'};
- this.videocall.send({message: body, jsep: jsepSuccess});
- },
- error: () => {
- this.doHangup(true);
- }
- });
- }
-
- doHangup(error, manual) {
- trackEvent('api', 'api_users_webrtc_end');
- if (this.videocall && this.state.callInProgress) {
- this.videocall.send({message: {request: 'hangup'}});
- this.videocall.hangup();
- this.toggleIcons();
-
- if (this.localMedia) {
- this.localMedia.getVideoTracks()[0].enabled = true;
- this.localMedia.getAudioTracks()[0].enabled = true;
- }
- }
-
- if (error) {
- this.onSessionError();
- return this.doCleanup();
- }
-
- WebrtcStore.setVideoCallWith(null);
- WebrtcStore.emitRhsChanged(false);
-
- if (manual) {
- return this.close();
- }
-
- this.setState({
- isCalling: false,
- isAnswering: false,
- callInProgress: false,
- isPaused: false,
- isMuted: false,
- isRemotePaused: false,
- isRemoteMuted: false,
- error: null,
- ended: (
- <FormattedMessage
- id='webrtc.callEnded'
- defaultMessage='Call with {username} ended.'
- values={{
- username: Utils.displayUsername(this.props.userId)
- }}
- />
- )
- });
- this.clearError();
- return this.doCleanup();
- }
-
- doCleanup() {
- WebrtcStore.setVideoCallWith(null);
-
- if (this.videocall) {
- this.videocall.detach();
- this.videocall = null;
- }
- }
-
- onToggleVideo() {
- const shouldPause = !this.state.isPaused;
- if (shouldPause) {
- this.videocall.unmuteVideo();
- } else {
- this.videocall.muteVideo();
- }
-
- const user = this.state.currentUser;
- WebSocketClient.sendMessage('webrtc', {
- action: WebrtcActionTypes.MUTED,
- from_user_id: user.id,
- to_user_id: this.props.userId,
- type: 'video',
- mute: shouldPause
- });
-
- this.setState({
- isPaused: shouldPause,
- error: null,
- ended: null
- });
- }
-
- onToggleAudio() {
- const shouldMute = !this.state.isMuted;
- if (shouldMute) {
- this.videocall.unmuteAudio();
- } else {
- this.videocall.muteAudio();
- }
-
- const user = this.state.currentUser;
- WebSocketClient.sendMessage('webrtc', {
- action: WebrtcActionTypes.MUTED,
- from_user_id: user.id,
- to_user_id: this.props.userId,
- type: 'audio',
- mute: shouldMute
- });
-
- this.setState({
- isMuted: shouldMute,
- error: null,
- ended: null
- });
- }
-
- onToggleRemoteMute(message) {
- if (message.type === 'video') {
- this.setState({
- isRemotePaused: message.mute
- });
- } else {
- this.setState({isRemoteMuted: message.mute, error: null, ended: null});
- }
- }
-
- toggleIcons() {
- const icons = this.refs.icons;
- if (icons) {
- icons.classList.toggle('hidden');
- icons.classList.toggle('active');
- }
- }
-
- renderButtons() {
- let buttons;
- if (this.state.isCalling) {
- buttons = (
- <svg
- id='cancel'
- className='webrtc-icons__cancel'
- xmlns='http://www.w3.org/2000/svg'
- width='48'
- height='48'
- viewBox='-10 -10 68 68'
- onClick={() => this.handleCancelOffer()}
- >
- <circle
- cx='24'
- cy='24'
- r='34'
- >
- <title>
- <FormattedMessage
- id='webrtc.cancel'
- defaultMessage='Cancel Call'
- />
- </title>
- </circle>
- <path
- transform='scale(0.8), translate(6,10)'
- d='M24 18c-3.21 0-6.3.5-9.2 1.44v6.21c0 .79-.46 1.47-1.12 1.8-1.95.98-3.74 2.23-5.33 3.7-.36.35-.85.57-1.4.57-.55 0-1.05-.22-1.41-.59L.59 26.18c-.37-.37-.59-.87-.59-1.42 0-.55.22-1.05.59-1.42C6.68 17.55 14.93 14 24 14s17.32 3.55 23.41 9.34c.37.36.59.87.59 1.42 0 .55-.22 1.05-.59 1.41l-4.95 4.95c-.36.36-.86.59-1.41.59-.54 0-1.04-.22-1.4-.57-1.59-1.47-3.38-2.72-5.33-3.7-.66-.33-1.12-1.01-1.12-1.8v-6.21C30.3 18.5 27.21 18 24 18z'
- fill='white'
- />
- </svg>
- );
- } else if (!this.state.callInProgress && this.state.localMediaLoaded) {
- buttons = (
- <svg
- id='call'
- className='webrtc-icons__call'
- xmlns='http://www.w3.org/2000/svg'
- width='48'
- height='48'
- viewBox='-10 -10 68 68'
- onClick={() => this.handleMakeOffer()}
- disabled={UserStore.getStatus(this.props.userId) === 'offline'}
- >
- <circle
- cx='24'
- cy='24'
- r='34'
- >
- <title>
- <FormattedMessage
- id='webrtc.call'
- defaultMessage='Call'
- />
- </title>
- </circle>
- <path
- transform='translate(-10,-10)'
- fill='#fff'
- d='M29.854,37.627c1.723,1.904 3.679,3.468 5.793,4.684l3.683,-3.334c0.469,-0.424 1.119,-0.517 1.669,-0.302c1.628,0.63 3.331,1.021 5.056,1.174c0.401,0.026 0.795,0.199 1.09,0.525c0.295,0.326 0.433,0.741 0.407,1.153l-0.279,5.593c-0.02,0.418 -0.199,0.817 -0.525,1.112c-0.326,0.296 -0.741,0.434 -1.159,0.413c-6.704,-0.504 -13.238,-3.491 -18.108,-8.87c-4.869,-5.38 -7.192,-12.179 -7.028,-18.899c0.015,-0.413 0.199,-0.817 0.526,-1.113c0.326,-0.295 0.74,-0.433 1.153,-0.407l5.593,0.279c0.407,0.02 0.812,0.193 1.107,0.519c0.29,0.32 0.428,0.735 0.413,1.137c-0.018,1.732 0.202,3.464 0.667,5.147c0.159,0.569 0.003,1.207 -0.466,1.631l-3.683,3.334c1.005,2.219 2.368,4.32 4.091,6.224Z'
- />
- </svg>
- );
- } else if (this.state.callInProgress) {
- const onClass = 'on';
- const offClass = 'off';
- let audioOnClass = offClass;
- let audioOffClass = onClass;
- let videoOnClass = offClass;
- let videoOffClass = onClass;
-
- let audioTitle = (
- <FormattedMessage
- id='webrtc.mute_audio'
- defaultMessage='Mute'
- />
- );
-
- let videoTitle = (
- <FormattedMessage
- id='webrtc.pause_video'
- defaultMessage='Turn off Video'
- />
- );
-
- if (this.state.isMuted) {
- audioOnClass = onClass;
- audioOffClass = offClass;
- audioTitle = (
- <FormattedMessage
- id='webrtc.unmute_audio'
- defaultMessage='Unmute'
- />
- );
- }
-
- if (this.state.isPaused) {
- videoOnClass = onClass;
- videoOffClass = offClass;
- videoTitle = (
- <FormattedMessage
- id='webrtc.unpause_video'
- defaultMessage='Turn on Video'
- />
- );
- }
-
- buttons = (
- <div
- ref='icons'
- className='webrtc-icons hidden'
- >
-
- <svg
- id='mute-audio'
- className='webrtc-icons__call'
- xmlns='http://www.w3.org/2000/svg'
- width='48'
- height='48'
- viewBox='-10 -10 68 68'
- onClick={() => this.onToggleAudio()}
- >
- <circle
- cx='24'
- cy='24'
- r='34'
- >
- <title>{audioTitle}</title>
- </circle>
- <path
- className={audioOnClass}
- transform='scale(0.6), translate(17,18)'
- d='M38 22h-3.4c0 1.49-.31 2.87-.87 4.1l2.46 2.46C37.33 26.61 38 24.38 38 22zm-8.03.33c0-.11.03-.22.03-.33V10c0-3.32-2.69-6-6-6s-6 2.68-6 6v.37l11.97 11.96zM8.55 6L6 8.55l12.02 12.02v1.44c0 3.31 2.67 6 5.98 6 .45 0 .88-.06 1.3-.15l3.32 3.32c-1.43.66-3 1.03-4.62 1.03-5.52 0-10.6-4.2-10.6-10.2H10c0 6.83 5.44 12.47 12 13.44V42h4v-6.56c1.81-.27 3.53-.9 5.08-1.81L39.45 42 42 39.46 8.55 6z'
- fill='white'
- />
- <path
- className={audioOffClass}
- transform='scale(0.6), translate(17,18)'
- d='M24 28c3.31 0 5.98-2.69 5.98-6L30 10c0-3.32-2.68-6-6-6-3.31 0-6 2.68-6 6v12c0 3.31 2.69 6 6 6zm10.6-6c0 6-5.07 10.2-10.6 10.2-5.52 0-10.6-4.2-10.6-10.2H10c0 6.83 5.44 12.47 12 13.44V42h4v-6.56c6.56-.97 12-6.61 12-13.44h-3.4z'
- fill='white'
- />
- </svg>
-
- <svg
- id='mute-video'
- className='webrtc-icons__call'
- xmlns='http://www.w3.org/2000/svg'
- width='48'
- height='48'
- viewBox='-10 -10 68 68'
- onClick={() => this.onToggleVideo()}
- >
- <circle
- cx='24'
- cy='24'
- r='34'
- >
- <title>{videoTitle}</title>
- </circle>
- <path
- className={videoOnClass}
- transform='scale(0.6), translate(17,16)'
- d='M40 8H15.64l8 8H28v4.36l1.13 1.13L36 16v12.36l7.97 7.97L44 36V12c0-2.21-1.79-4-4-4zM4.55 2L2 4.55l4.01 4.01C4.81 9.24 4 10.52 4 12v24c0 2.21 1.79 4 4 4h29.45l4 4L44 41.46 4.55 2zM12 16h1.45L28 30.55V32H12V16z'
- fill='white'
- />
- <path
- className={videoOffClass}
- transform='scale(0.6), translate(17,16)'
- d='M40 8H8c-2.21 0-4 1.79-4 4v24c0 2.21 1.79 4 4 4h32c2.21 0 4-1.79 4-4V12c0-2.21-1.79-4-4-4zm-4 24l-8-6.4V32H12V16h16v6.4l8-6.4v16z'
- fill='white'
- />
- </svg>
-
- <svg
- id='hangup'
- className='webrtc-icons__cancel'
- xmlns='http://www.w3.org/2000/svg'
- width='48'
- height='48'
- viewBox='-10 -10 68 68'
- onClick={() => this.doHangup(false, true)}
- >
- <circle
- cx='24'
- cy='24'
- r='34'
- >
- <title>
- <FormattedMessage
- id='webrtc.hangup'
- defaultMessage='Hang up'
- />
- </title>
- </circle>
- <path
- transform='scale(0.7), translate(11,10)'
- d='M24 18c-3.21 0-6.3.5-9.2 1.44v6.21c0 .79-.46 1.47-1.12 1.8-1.95.98-3.74 2.23-5.33 3.7-.36.35-.85.57-1.4.57-.55 0-1.05-.22-1.41-.59L.59 26.18c-.37-.37-.59-.87-.59-1.42 0-.55.22-1.05.59-1.42C6.68 17.55 14.93 14 24 14s17.32 3.55 23.41 9.34c.37.36.59.87.59 1.42 0 .55-.22 1.05-.59 1.41l-4.95 4.95c-.36.36-.86.59-1.41.59-.54 0-1.04-.22-1.4-.57-1.59-1.47-3.38-2.72-5.33-3.7-.66-.33-1.12-1.01-1.12-1.8v-6.21C30.3 18.5 27.21 18 24 18z'
- fill='white'
- />
- </svg>
-
- </div>
- );
- }
-
- return buttons;
- }
-
- render() {
- const currentId = UserStore.getCurrentId();
- const remoteImage = (<img src={this.state.remoteUserImage}/>);
- let localImage;
- let localVideoHidden = '';
- let remoteVideoHidden = 'hidden';
- let error;
- let remoteMute;
- let localImageHidden = 'webrtc__local-image hidden';
- let remoteImageHidden = 'webrtc__remote-image';
-
- if (this.state.error) {
- error = (
- <div className='webrtc__error'>
- <div className='form-group has-error'>
- <label className={'control-label' + this.state.errorType}>{this.state.error}</label>
- </div>
- </div>
- );
- } else if (this.state.ended) {
- error = (
- <div className='webrtc__error'>
- <div className='form-group'>
- <label className='control-label'>{this.state.ended}</label>
- </div>
- </div>
- );
- }
-
- if (this.state.isRemoteMuted) {
- remoteMute = (
- <div className='webrtc__remote-mute'>
- <svg
- xmlns='http://www.w3.org/2000/svg'
- width='60'
- height='60'
- viewBox='-10 -10 68 68'
- >
- <path
- className='off'
- transform='scale(0.6), translate(17,18)'
- d='M38 22h-3.4c0 1.49-.31 2.87-.87 4.1l2.46 2.46C37.33 26.61 38 24.38 38 22zm-8.03.33c0-.11.03-.22.03-.33V10c0-3.32-2.69-6-6-6s-6 2.68-6 6v.37l11.97 11.96zM8.55 6L6 8.55l12.02 12.02v1.44c0 3.31 2.67 6 5.98 6 .45 0 .88-.06 1.3-.15l3.32 3.32c-1.43.66-3 1.03-4.62 1.03-5.52 0-10.6-4.2-10.6-10.2H10c0 6.83 5.44 12.47 12 13.44V42h4v-6.56c1.81-.27 3.53-.9 5.08-1.81L39.45 42 42 39.46 8.55 6z'
- fill='white'
- />
- <path
- className='on'
- transform='scale(0.6), translate(17,18)'
- d='M24 28c3.31 0 5.98-2.69 5.98-6L30 10c0-3.32-2.68-6-6-6-3.31 0-6 2.68-6 6v12c0 3.31 2.69 6 6 6zm10.6-6c0 6-5.07 10.2-10.6 10.2-5.52 0-10.6-4.2-10.6-10.2H10c0 6.83 5.44 12.47 12 13.44V42h4v-6.56c6.56-.97 12-6.61 12-13.44h-3.4z'
- fill='white'
- />
- </svg>
- </div>
- );
- }
-
- let searchForm;
- if (currentId != null) {
- searchForm = <SearchBox/>;
- }
-
- const buttons = this.renderButtons();
- const calling = this.state.isCalling;
- let connecting;
- let audio;
- if (calling || this.state.isAnswering) {
- if (calling) {
- audio = (
- <audio
- ref='ring'
- src={ring}
- autoPlay={true}
- />
- );
- }
-
- connecting = (
- <div className='connecting'>
- <ConnectingScreen
- position='absolute'
- message={this.state.connectingMsg}
- />
- {audio}
- </div>
- );
- }
-
- if (this.state.callInProgress) {
- if (this.state.isPaused) {
- localVideoHidden = 'hidden';
- localImageHidden = 'webrtc__local-image';
- localImage = (<img src={this.state.currentUserImage}/>);
- }
-
- if (this.state.isRemotePaused) {
- remoteVideoHidden = 'hidden';
- remoteImageHidden = 'webrtc__remote-image';
- } else {
- remoteVideoHidden = '';
- remoteImageHidden = 'webrtc__remote-image hidden';
- }
- }
-
- return (
- <div className='post-right__container'>
- <div className='search-bar__container channel-header alt'>{searchForm}</div>
- <div className='sidebar-right__body'>
- <WebrtcHeader
- username={Utils.displayUsername(this.props.userId)}
- onClose={this.handleClose}
- toggleSize={this.props.toggleSize}
- />
- <div className='post-right__scroll'>
- <div id='videos'>
- {remoteMute}
- <div
- id='main-video'
- className={remoteVideoHidden}
- autoPlay={true}
- >
- <video
- ref='main-video'
- autoPlay={true}
- />
- </div>
- <div
- id='local-video'
- className={localVideoHidden}
- >
- <video
- ref='local-video'
- autoPlay={true}
- muted={true}
- />
- </div>
- <div className={remoteImageHidden}>
- {remoteImage}
- </div>
- <div className={localImageHidden}>
- {localImage}
- </div>
- </div>
- {error}
- {connecting}
- <div className='webrtc-buttons'>
- {buttons}
- </div>
- </div>
- </div>
- </div>
- );
- }
-}
-
-WebrtcController.propTypes = {
- currentUser: PropTypes.object,
- userId: PropTypes.string.isRequired,
- isCaller: PropTypes.bool.isRequired,
- expanded: PropTypes.bool.isRequired,
- toggleSize: PropTypes.func
-};
diff --git a/webapp/components/youtube_video/index.js b/webapp/components/youtube_video/index.js
deleted file mode 100644
index 592e52240..000000000
--- a/webapp/components/youtube_video/index.js
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {connect} from 'react-redux';
-import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
-
-import YoutubeVideo from './youtube_video.jsx';
-
-function mapStateToProps(state, ownProps) {
- return {
- ...ownProps,
- currentChannelId: getCurrentChannelId(state)
- };
-}
-
-export default connect(mapStateToProps)(YoutubeVideo);
diff --git a/webapp/components/youtube_video/youtube_video.jsx b/webapp/components/youtube_video/youtube_video.jsx
deleted file mode 100644
index c2bfa317c..000000000
--- a/webapp/components/youtube_video/youtube_video.jsx
+++ /dev/null
@@ -1,245 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {getYoutubeVideoInfo} from 'actions/integration_actions.jsx';
-import * as Utils from 'utils/utils.jsx';
-
-const ytRegex = /(?:http|https):\/\/(?:www\.|m\.)?(?:(?:youtube\.com\/(?:(?:v\/)|(?:(?:watch|embed\/watch)(?:\/|.*v=))|(?:embed\/)|(?:user\/[^/]+\/u\/[0-9]\/)))|(?:youtu\.be\/))([^#&?]*)/;
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-export default class YoutubeVideo extends React.PureComponent {
- static propTypes = {
- channelId: PropTypes.string.isRequired,
- currentChannelId: PropTypes.string.isRequired,
- link: PropTypes.string.isRequired,
- show: PropTypes.bool.isRequired,
- onLinkLoaded: PropTypes.func
- }
-
- constructor(props) {
- super(props);
-
- this.updateStateFromProps = this.updateStateFromProps.bind(this);
- this.handleReceivedMetadata = this.handleReceivedMetadata.bind(this);
- this.handleMetadataError = this.handleMetadataError.bind(this);
- this.loadWithoutKey = this.loadWithoutKey.bind(this);
-
- this.play = this.play.bind(this);
- this.stop = this.stop.bind(this);
-
- this.state = {
- loaded: false,
- failed: false,
- playing: false,
- title: ''
- };
- }
-
- componentWillMount() {
- this.updateStateFromProps(this.props);
- }
-
- componentWillReceiveProps(nextProps) {
- this.updateStateFromProps(nextProps);
- }
-
- updateStateFromProps(props) {
- const link = props.link;
-
- const match = link.trim().match(ytRegex);
- if (!match || match[1].length !== 11) {
- return;
- }
-
- if (props.show === false) {
- this.stop();
- }
-
- if (props.channelId !== props.currentChannelId) {
- this.stop();
- }
-
- this.setState({
- videoId: match[1],
- time: this.handleYoutubeTime(link)
- });
- }
-
- handleYoutubeTime(link) {
- const timeRegex = /[\\?&]t=([0-9]+h)?([0-9]+m)?([0-9]+s?)/;
-
- const time = link.match(timeRegex);
- if (!time || !time[0]) {
- return '';
- }
-
- const hours = time[1] ? time[1].match(/([0-9]+)h/) : null;
- const minutes = time[2] ? time[2].match(/([0-9]+)m/) : null;
- const seconds = time[3] ? time[3].match(/([0-9]+)s?/) : null;
-
- let ticks = 0;
-
- if (hours && hours[1]) {
- ticks += parseInt(hours[1], 10) * 3600;
- }
-
- if (minutes && minutes[1]) {
- ticks += parseInt(minutes[1], 10) * 60;
- }
-
- if (seconds && seconds[1]) {
- ticks += parseInt(seconds[1], 10);
- }
-
- return '&start=' + ticks.toString();
- }
-
- componentDidMount() {
- const key = global.window.mm_config.GoogleDeveloperKey;
- if (key) {
- getYoutubeVideoInfo(key, this.state.videoId,
- this.handleReceivedMetadata, this.handleMetadataError);
- } else {
- this.loadWithoutKey();
- }
- this.props.onLinkLoaded();
- }
-
- loadWithoutKey() {
- this.setState({
- loaded: true,
- thumb: 'https://i.ytimg.com/vi/' + this.state.videoId + '/hqdefault.jpg'
- });
- }
-
- handleMetadataError() {
- this.setState({
- failed: true,
- loaded: true,
- title: Utils.localizeMessage('youtube_video.notFound', 'Video not found')
- });
- }
-
- handleReceivedMetadata(data) {
- if (!data || !data.items || !data.items.length || !data.items[0].snippet) {
- this.setState({
- failed: true,
- loaded: true,
- title: Utils.localizeMessage('youtube_video.notFound', 'Video not found')
- });
- return null;
- }
- const metadata = data.items[0].snippet;
- let thumb = 'https://i.ytimg.com/vi/' + this.state.videoId + '/hqdefault.jpg';
- if (metadata.liveBroadcastContent === 'live') {
- thumb = 'https://i.ytimg.com/vi/' + this.state.videoId + '/hqdefault_live.jpg';
- }
-
- this.setState({
- loaded: true,
- receivedYoutubeData: true,
- title: metadata.title,
- thumb
- });
- return null;
- }
-
- play() {
- this.setState({playing: true});
- }
-
- stop() {
- this.setState({playing: false});
- }
-
- render() {
- if (!this.state.loaded) {
- return (
- <div
- className='post__embed-container'
- >
- <div className='video-loading'/>
- </div>
- );
- }
-
- let header;
- if (this.state.title) {
- header = (
- <h4>
- <span className='video-type'>{'Youtube - '}</span>
- <span className='video-title'>
- <a
- href={this.props.link}
- target='blank'
- rel='noopener noreferrer'
- >
- {this.state.title}
- </a>
- </span>
- </h4>
- );
- }
-
- let content;
- if (this.state.failed) {
- content = (
- <div>
- <div className='video-thumbnail__container'>
- <div className='video-thumbnail__error'>
- <div><i className='fa fa-warning fa-2x'/></div>
- <div>{Utils.localizeMessage('youtube_video.notFound', 'Video not found')}</div>
- </div>
- </div>
- </div>
- );
- } else if (this.state.playing) {
- content = (
- <iframe
- src={'https://www.youtube.com/embed/' + this.state.videoId + '?autoplay=1&autohide=1&border=0&wmode=opaque&fs=1&enablejsapi=1' + this.state.time}
- width='480px'
- height='360px'
- type='text/html'
- frameBorder='0'
- allowFullScreen='allowfullscreen'
- />
- );
- } else {
- content = (
- <div className='embed-responsive embed-responsive-4by3 video-div__placeholder'>
- <div className='video-thumbnail__container'>
- <img
- className='video-thumbnail'
- src={this.state.thumb}
- />
- <div className='block'>
- <span className='play-button'><span/></span>
- </div>
- </div>
- </div>
- );
- }
-
- return (
- <div
- className='post__embed-container'
- >
- <div>
- {header}
- <div
- className='video-div embed-responsive-item'
- onClick={this.play}
- >
- {content}
- </div>
- </div>
- </div>
- );
- }
-
- static isYoutubeLink(link) {
- return link.trim().match(ytRegex);
- }
-}